Files
MeowOS-For-R3P2/patch_v2/hello_app/hello.c
T
MeowOS 9f19f2887e feat(hello): 控件库demo演示页(agent-159) — M2.71 Material组件展示+交互
- home加「WIDGETS DEMO」入口 + ACT_DEMO activity; demo控件启动push一次创建(owner=ACT_DEMO, 命中只owner==active, home/demo零重建)
- 覆盖: Button五型+禁用/Switch/Slider/TextField(阻塞IME)/Checkbox/Radio(互斥)/ProgressBar(线圆动画)/Card(嵌子按钮)/Divider/Badge(3)/TabBar/NavigationBar/TopAppBar(返回箭头)/Dialog+BottomSheet/FAB/LazyColumn(100项)
- 交互真实控件状态机; 配色全取meow_theme(随MD3/MIUIx+明暗); 局部刷d_redraw(矩形)+meow_ui_flush_region不整屏不冲状态栏; 原home逻辑逐字未改
- 交叉编译0warning; hello=803b23fb meow_ui.a=92e61fb8(未变未碰SDK); 宿主离屏PPM目检3帧渲染正确
- 遗留真机观感; 滑动=按scroll重建内容控件(SDK无位置setter, 方案§0记录修正)
2026-08-30 23:28:50 +08:00

630 lines
29 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* hello.c — MeowOS SDK 版示例应用(链 meow_ui.a,B1)。
*
* 契约(MEOW_APP_SDK.md §1/§3/§6):应用只调 meow_ui API —— 屏幕(back_buf +
* flush 上屏)与输入(meow_ui_run 事件循环)由框架接管;不再 open/mmap/ioctl
* /dev/fb0、不再读 /dev/input/event*。
*
* 两大职责:
* A) 原有:验证「清屏 + 画文本 + 画按钮 + 响应触摸」最小闭环 + 通知触发按钮
* (meow_notify_send,§9.1)端到端测试通知链路;
* B) 新增:**控件库 Demo 演示页**(§5.8 第一/二批组件)——作为 SDK 控件库的
* 验证载体 + 活文档:用真实控件(meow_btn_* / meow_switch_* / meow_slider_* /
* meow_widget_custom_create / meow_*)搭「展示 + 可交互」界面,用户刷 hello
* 从 home 的「WIDGETS DEMO」按钮进入,逐项验证各组件外观 / 命中 / 状态。
*
* 交互:
* - home:点 TAP ME → 回圈计数;6 个通知触发行;CANCEL LAST;FULLSCREEN;
* 新增「WIDGETS DEMO」按钮 → 进入 demo 页;左缘左滑 → 退出。
* - demo 页:TopAppBar 返回箭头 / 左缘左滑 → 回 home;内容区展示并交互
* Button 五型(含禁用) / Switch / Slider / TextField(点开阻塞 IME) / Checkbox /
* Radio(单选组) / ProgressBar(线性+圆形带动画) / Card(嵌子项) / Divider /
* Badge / TabBar / NavigationBar(底部) / TopAppBar(顶部) / Dialog+BottomSheet /
* FAB / LazyColumn(100 项列表,swipe 滚动)。配色取 meow_theme 语义色板(随系统
* MD3/MIUIx + 明暗自动切,零硬编码品牌色)。交互局部刷新(meow_ui_flush_region,
* 不整屏、不冲状态栏)。
*
* 页面/Activity(§6.1 ①):home(根,ACT_HOME 0)、全屏(ACT_FULL 1)、demo(ACT_DEMO 2)
* 三态互斥;控件 owner=创建时 active Activity,命中只在 owner==active 生效(根治跨页
* 残留)。demo 控件在启动 push(ACT_DEMO) 下一次性创建(owner=ACT_DEMO),此后 home
* 态不命中不绘制、demo 态 home 控件不命中。
*
* 滚动实现(关键:SDK 控件不透明——结构体对外不可见,App 不能读/写控件坐标;也无
* 位置 setter):
* - 内容控件以**绝对坐标**创建(App 侧另存 rect_t 几何用于局部 flush/命中)。
* - 页滚动 = 按「base_y - scroll」**重建**内容控件(仍处于 ACT_DEMO,owner 不变);
* 各控件跨重建状态存于 g_ds(App 持有),重建后恢复。
* - LazyColumn 是第二滚动区:滚它时 meow_list_scroll_by + 列表区局部刷新,不重建。
* - 因内容仅略超一屏(max_scroll 小),一次滑动仅触发 1~2 次重建,代价可接受。
*
* 宿主/qemu 离屏:设 MEOW_UI_DUMP=<ppm 路径> 时,首帧 flush 即把 back_buf 写成
* PPM 截图供检查;设备上无副作用。
*
* Build(见 build_hello.sh):
* zig-mipsel-cc -Os -static hello.c meow_ui.a libz.a -lpthread -o hello
*/
#include "meow_ui.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
/* §6.1 ①:Activity 归属。home=根(0);全屏=独立 Activity(无控件);demo=独立 Activity。 */
#define ACT_HOME 0
#define ACT_FULL 1
#define ACT_DEMO 2
/* ---- home 既有控件 ---- */
static meow_widget *g_btn;
static meow_widget *g_nbtn[6]; /* 通知触发按钮行 */
static meow_widget *g_cancel; /* 取消最近一条通知 */
static meow_widget *g_fs_btn; /* FULLSCREEN 进入按钮(主界面) */
static meow_widget *g_demo_btn; /* 「控件库 Demo」入口按钮(主界面,新增) */
static int g_fullscreen = 0; /* 1=处于全屏视图(点任意处退出回主界面) */
static const char *g_notif_labels[6] = { "INFO", "MEDIA", "HIGH", "PROG", "ONGO", "TGT" };
static uint32_t g_last_notif = 0; /* 最近一次 send 返回的 id,供 cancel */
static int g_taps = 0;
static int g_secs = 0;
static char g_btn_label[32] = "TAP ME";
/* ---- demo 演示页状态 ---- */
static int g_demo = 0; /* 1=处于 demo 演示页 */
static int g_demo_scroll = 0; /* 内容区滚动偏移(base 空间 px) */
static int g_demo_focus = 0; /* 1=LazyColumn 聚焦(swipe 滚列表而非页面) */
static int g_demo_fab = 0; /* FAB 点击计数反馈 */
static int g_demo_prog = 0; /* ProgressBar 动画当前值 0..100 */
static uint64_t g_demo_prog_last = 0;
static int g_demo_ct = 0; /* 内容视口顶(屏幕 y,= statusbar+56+8) */
static int g_demo_cb = 0; /* 内容视口底(屏幕 y,=608,NavBar 上方留白) */
static int g_demo_content_h = 0; /* 内容总高(base 空间;决定 max_scroll) */
/* SDK 控件不透明:App 自记几何,用于局部 flush / 命中 / scroll 重建。 */
typedef struct { int x, y, w, h; } rect_t;
#define RX(x,y,w,h) ((rect_t){ (x), (y), (w), (h) })
/* demo 各控件跨重建保留的状态(app 持有;重建后按此恢复) */
static struct {
int sw_on; int slider; char tf[32];
int ck; int r1, r2; int btn[5]; int card; int tab; int list; int prog;
} g_ds = { .sw_on = 1, .slider = 40, .r1 = 1, .tab = 1, .list = 0, .prog = 0 };
/* demo 内容布局常量(base 空间) */
#define DCL_X 16
#define DCR_X 252
#define DCW 212
#define DFW 448
#define DBH 44
#define DCH 52 /* 一行两列按钮步进 */
/* demo 内容槽:控件 + 其当前屏幕几何(rebuild 时重算) */
#define D_SLOT_MAX 64
static struct { meow_widget *w; rect_t r; } g_dslot[D_SLOT_MAX];
static int g_dslot_n = 0;
/* demo 分区小标题(纯文本,非控件;屏幕坐标在 rebuild 时算好) */
#define D_TITLE_MAX 16
static struct { int x, y; const char *s; } g_dtitle[D_TITLE_MAX];
static int g_dtitle_n = 0;
/* demo 交互组件引用(rebuild 时 g_* 与 r_* 同步重指/recompute) */
static meow_widget *g_tb; static rect_t r_tb; /* TopAppBar(chrome 固定) */
static meow_widget *g_nav; static rect_t r_nav; /* NavigationBar(chrome 固定) */
static meow_widget *g_fab; static rect_t r_fab; /* FAB(chrome 固定,可重建换计数 label) */
static meow_widget *g_dbtn_b[5]; static rect_t r_btn[5]; /* Button 五型 */
static meow_widget *g_dbtn_d; /* DISABLED 按钮(禁用不命中,无需 rect) */
static meow_widget *g_dsw; static rect_t r_dsw; /* Switch */
static meow_widget *g_dslider; static rect_t r_slider, r_slider_txt; /* Slider + 值文本 */
static meow_widget *g_dtf; static rect_t r_dtf; /* TextField */
static meow_widget *g_dcb; static rect_t r_dcb; /* Checkbox */
static meow_widget *g_dr1, *g_dr2; static rect_t r_dr1, r_dr2; /* Radio 单选组 */
static meow_widget *g_dprl; static rect_t r_prl; /* ProgressBar 线性 */
static meow_widget *g_dprc; static rect_t r_prc; /* ProgressBar 圆形 */
static meow_widget *g_dcard; static rect_t r_card; /* Card */
static meow_widget *g_dcard_btn; static rect_t r_cardbtn; /* Card 内容子按钮 */
static meow_widget *g_dbadge; /* Badge */
static meow_widget *g_dtab; static rect_t r_tab; /* TabBar */
static meow_widget *g_dbtn_dlg; static rect_t r_dlg; /* 打开 Dialog 的按钮 */
static meow_widget *g_dbtn_sheet; static rect_t r_sheet;/* 打开 BottomSheet 的按钮 */
static meow_widget *g_dlc; static rect_t r_list; /* LazyColumn */
/* Dialog / BottomSheet(阻塞模态;非控件,main() 创建一次) */
static meow_dialog *g_dlg;
static meow_sheet *g_sheet;
/* 前向声明 */
static void redraw(void);
static void demo_rebuild_to(int scroll);
static void demo_paint(void);
/* M2.51:统一日志改用 SDK meow_log()。 */
static void on_sigterm(int s) { (void)s; meow_ui_request_exit(); }
/* ============================================================ */
/* 通知触发(home 既有) */
/* ============================================================ */
static void notif_send(int which)
{
meow_notify n;
memset(&n, 0, sizeof n);
n.progress = -1;
n.timeout_ms = MEOW_NOTIFY_TIMEOUT_NEVER;
n.urgency = MEOW_NOTIFY_URG_NORMAL;
const char *label = "?";
switch (which) {
case 0: label = "INFO"; n.cat = MEOW_NOTIFY_CAT_GENERAL;
n.title = "测试-普通通知"; n.body = "普通 INFO 通知,timeout=5000ms"; n.timeout_ms = 5000; break;
case 1: label = "MEDIA"; n.cat = MEOW_NOTIFY_CAT_MEDIA; n.urgency = MEOW_NOTIFY_URG_LOW;
n.title = "测试-媒体通知"; n.body = "媒体单例,正在播放,timeout=-1 常驻";
n.timeout_ms = MEOW_NOTIFY_TIMEOUT_ONGOING; break;
case 2: label = "HIGH"; n.cat = MEOW_NOTIFY_CAT_GENERAL; n.urgency = MEOW_NOTIFY_URG_HIGH;
n.title = "测试-紧急度HIGH"; n.body = "高紧急度:顶部横幅+提示音"; n.timeout_ms = 3000; break;
case 3: label = "PROG"; n.cat = MEOW_NOTIFY_CAT_PROGRESS;
n.title = "测试-进度通知"; n.body = "下载进度 40%"; n.timeout_ms = 8000; n.progress = 40; break;
case 4: label = "ONGO"; n.cat = MEOW_NOTIFY_CAT_GENERAL;
n.title = "测试-常驻通知"; n.body = "常驻不自动过期(timeout=-1),需手动取消";
n.timeout_ms = MEOW_NOTIFY_TIMEOUT_ONGOING; break;
case 5: label = "TGT"; n.cat = MEOW_NOTIFY_CAT_MESSAGE;
n.title = "测试-跳转Activity"; n.body = "点击跳转 target_activity=main";
n.timeout_ms = 6000; n.target_activity = "main"; n.icon = NULL; break;
default: return;
}
uint32_t id = meow_notify_send(&n);
g_last_notif = id;
meow_log(MEOW_LOG_INFO,
"notify btn=%s id=%u cat=%s urg=%s tmo=%d prog=%d target=%s title=%s",
label, (unsigned)id, meow_notify_category_str(n.cat),
meow_notify_urgency_str(n.urgency), n.timeout_ms, n.progress,
n.target_activity ? n.target_activity : "-", n.title ? n.title : "-");
}
static void notif_cancel(void)
{
int r = meow_notify_cancel(g_last_notif);
meow_log(MEOW_LOG_INFO, "notify cancel id=%u result=%d", (unsigned)g_last_notif, r);
g_last_notif = 0;
}
static void set_fullscreen(int on)
{
g_fullscreen = on ? 1 : 0;
meow_ui_set_fullscreen(g_fullscreen);
meow_ui_statusbar_set_collapsed(g_fullscreen);
if (on) meow_ui_activity_push(ACT_FULL);
else meow_ui_activity_pop();
meow_log(MEOW_LOG_INFO, g_fullscreen ? "fullscreen ON (collapsed), activity->%d"
: "fullscreen OFF (restore), activity->%d",
meow_ui_activity_active());
}
/* ============================================================ */
/* demo 演示页:布局 / 绘制 / 交互 */
/* ============================================================ */
static void d_slot(meow_widget *w, rect_t r)
{
if (g_dslot_n < D_SLOT_MAX) { g_dslot[g_dslot_n].w = w; g_dslot[g_dslot_n].r = r; g_dslot_n++; }
}
static void d_title(int x, int y, const char *s)
{
if (g_dtitle_n < D_TITLE_MAX) { g_dtitle[g_dtitle_n].x = x; g_dtitle[g_dtitle_n].y = y;
g_dtitle[g_dtitle_n].s = s; g_dtitle_n++; }
}
/* LazyColumn 数据源(100 项虚拟化) */
static int demo_list_src(int idx, char *label, int len, void *ctx)
{
(void)ctx;
if (idx < 0 || idx >= 100) return 0;
snprintf(label, (size_t)len, "list item %d", idx);
return 1;
}
/* 释放当前全部内容控件(含 Card 的独立子按钮);chrome 不在其列。 */
static void demo_free_content(void)
{
for (int i = 0; i < g_dslot_n; i++) meow_widget_free(g_dslot[i].w);
g_dslot_n = 0;
if (g_dcard_btn) { meow_widget_free(g_dcard_btn); g_dcard_btn = NULL; }
g_dtitle_n = 0;
}
/* 按 scroll 重建全部内容控件(owner=ACT_DEMO:调用方须已 push ACT_DEMO)。
* 控件以绝对坐标创建于 (x, base_y - scroll),几何同步写入 r_* / 槽;状态从 g_ds 恢复。 */
static void demo_build_at(int scroll)
{
int ct = meow_ui_statusbar_height() + 56 + 8;
g_demo_ct = ct; g_demo_cb = 608;
g_dslot_n = 0; g_dtitle_n = 0;
int yb = 0;
#define SY(base) (ct + (base) - scroll) /* base 空间 → 屏幕 y */
#define SLOT(w,x,y,ww,hh) do { rect_t _r=RX(x,y,ww,hh); d_slot((w),_r); } while(0)
/* ---- Buttons(五型 + 禁用) ---- */
d_title(DCL_X, SY(yb), "BUTTONS 5-variants"); yb += 26;
r_btn[0] = RX(DCL_X, SY(yb), DCW, DBH); g_dbtn_b[0] = meow_btn_create_variant(r_btn[0].x, r_btn[0].y, DCW, DBH, "FILLED", MEOW_BTN_FILLED); SLOT(g_dbtn_b[0], r_btn[0].x, r_btn[0].y, r_btn[0].w, r_btn[0].h);
r_btn[1] = RX(DCR_X, SY(yb), DCW, DBH); g_dbtn_b[1] = meow_btn_create_variant(r_btn[1].x, r_btn[1].y, DCW, DBH, "TONAL", MEOW_BTN_TONAL); SLOT(g_dbtn_b[1], r_btn[1].x, r_btn[1].y, r_btn[1].w, r_btn[1].h);
yb += DCH;
r_btn[2] = RX(DCL_X, SY(yb), DCW, DBH); g_dbtn_b[2] = meow_btn_create_variant(r_btn[2].x, r_btn[2].y, DCW, DBH, "OUTLINED", MEOW_BTN_OUTLINED); SLOT(g_dbtn_b[2], r_btn[2].x, r_btn[2].y, r_btn[2].w, r_btn[2].h);
r_btn[3] = RX(DCR_X, SY(yb), DCW, DBH); g_dbtn_b[3] = meow_btn_create_variant(r_btn[3].x, r_btn[3].y, DCW, DBH, "TEXT", MEOW_BTN_TEXT); SLOT(g_dbtn_b[3], r_btn[3].x, r_btn[3].y, r_btn[3].w, r_btn[3].h);
yb += DCH;
r_btn[4] = RX(DCL_X, SY(yb), DCW, DBH); g_dbtn_b[4] = meow_btn_create_variant(r_btn[4].x, r_btn[4].y, DCW, DBH, "ELEVATED", MEOW_BTN_ELEVATED); SLOT(g_dbtn_b[4], r_btn[4].x, r_btn[4].y, r_btn[4].w, r_btn[4].h);
g_dbtn_d = meow_btn_create_variant(DCR_X, SY(yb), DCW, DBH, "DISABLED", MEOW_BTN_FILLED);
SLOT(g_dbtn_d, DCR_X, SY(yb), DCW, DBH);
meow_widget_set_enabled(g_dbtn_d, 0); /* 禁用态:不命中 + 变暗 */
yb += DCH;
for (int i = 0; i < 5; i++) meow_widget_set_selected(g_dbtn_b[i], g_ds.btn[i]);
SLOT(meow_divider_create(DCL_X, SY(yb), DFW, 0), DCL_X, SY(yb), DFW, 1); yb += 16;
/* ---- Input / State(Switch / Slider / TextField / Checkbox / Radio) ---- */
d_title(DCL_X, SY(yb), "INPUT & STATE"); yb += 26;
r_dsw = RX(DCL_X, SY(yb), 56, 32); g_dsw = meow_switch_create(r_dsw.x, r_dsw.y, g_ds.sw_on); SLOT(g_dsw, r_dsw.x, r_dsw.y, r_dsw.w, r_dsw.h); yb += 44;
r_slider_txt = RX(DCL_X, SY(yb), 100, 24); yb += 24;
r_slider = RX(DCL_X, SY(yb), DFW, 40); g_dslider = meow_slider_create(r_slider.x, r_slider.y, r_slider.w, 0, 100, g_ds.slider); SLOT(g_dslider, r_slider.x, r_slider.y, r_slider.w, r_slider.h); yb += 48;
r_dtf = RX(DCL_X, SY(yb), DFW, 44); g_dtf = meow_textfield_create(r_dtf.x, r_dtf.y, r_dtf.w, r_dtf.h, "Type here...", g_ds.tf, 32, MEOW_IME_ASCII); SLOT(g_dtf, r_dtf.x, r_dtf.y, r_dtf.w, r_dtf.h); yb += 54;
r_dcb = RX(DCL_X, SY(yb), 34, 34); g_dcb = meow_checkbox_create(r_dcb.x, r_dcb.y); SLOT(g_dcb, r_dcb.x, r_dcb.y, r_dcb.w, r_dcb.h);
r_dr1 = RX(DCR_X, SY(yb), 34, 34); g_dr1 = meow_radio_create(r_dr1.x, r_dr1.y); SLOT(g_dr1, r_dr1.x, r_dr1.y, r_dr1.w, r_dr1.h);
yb += 40;
r_dr2 = RX(DCR_X, SY(yb), 34, 34); g_dr2 = meow_radio_create(r_dr2.x, r_dr2.y); SLOT(g_dr2, r_dr2.x, r_dr2.y, r_dr2.w, r_dr2.h);
yb += 40;
meow_widget_set_selected(g_dcb, g_ds.ck);
meow_widget_set_selected(g_dr1, g_ds.r1);
meow_widget_set_selected(g_dr2, g_ds.r2);
/* ---- ProgressBar(线性 + 圆形,on_tick 动画) ---- */
d_title(DCL_X, SY(yb), "PROGRESS (animated)"); yb += 26;
r_prl = RX(DCL_X, SY(yb), DFW, 8); g_dprl = meow_progress_create(r_prl.x, r_prl.y, r_prl.w, r_prl.h, g_ds.prog); SLOT(g_dprl, r_prl.x, r_prl.y, r_prl.w, r_prl.h); yb += 16;
r_prc = RX(DCL_X + 400, SY(yb), 34, 34); g_dprc = meow_progress_circular_create(r_prc.x + 17, r_prc.y + 17, 17, g_ds.prog); SLOT(g_dprc, r_prc.x, r_prc.y, r_prc.w, r_prc.h); yb += 48;
/* ---- Container(Card / Badge / TabBar) ---- */
d_title(DCL_X, SY(yb), "CONTAINER"); yb += 26;
r_card = RX(DCL_X, SY(yb), DFW, 88); g_dcard = meow_card_create(r_card.x, r_card.y, r_card.w, r_card.h); SLOT(g_dcard, r_card.x, r_card.y, r_card.w, r_card.h);
r_cardbtn = RX(DCL_X + 16, SY(yb) + 20, DFW - 32, 40); g_dcard_btn = meow_btn_create_variant(r_cardbtn.x, r_cardbtn.y, r_cardbtn.w, r_cardbtn.h, "IN-CARD BUTTON", MEOW_BTN_TONAL);
meow_widget_set_selected(g_dcard_btn, g_ds.card);
meow_card_set_content(g_dcard, g_dcard_btn);
yb += 96;
g_dbadge = meow_badge_create(DCL_X + 60, SY(yb) + 10);
SLOT(g_dbadge, DCL_X + 52, SY(yb) + 2, 18, 18);
meow_badge_set(g_dbadge, 3);
yb += 30;
r_tab = RX(DCL_X, SY(yb), DFW, 36); g_dtab = meow_tabbar_create(r_tab.x, r_tab.y, r_tab.w, r_tab.h); SLOT(g_dtab, r_tab.x, r_tab.y, r_tab.w, r_tab.h);
meow_tabbar_add_tab(g_dtab, "Playlist");
meow_tabbar_add_tab(g_dtab, "Queue");
meow_tabbar_add_tab(g_dtab, "Now");
meow_tabbar_set_selected(g_dtab, g_ds.tab);
yb += 44;
/* ---- Modals(Dialog + BottomSheet) ---- */
d_title(DCL_X, SY(yb), "MODALS (blocking)"); yb += 26;
r_dlg = RX(DCL_X, SY(yb), DCW, DBH); g_dbtn_dlg = meow_btn_create_variant(r_dlg.x, r_dlg.y, r_dlg.w, r_dlg.h, "DIALOG", MEOW_BTN_OUTLINED); SLOT(g_dbtn_dlg, r_dlg.x, r_dlg.y, r_dlg.w, r_dlg.h);
r_sheet = RX(DCR_X, SY(yb), DCW, DBH); g_dbtn_sheet = meow_btn_create_variant(r_sheet.x, r_sheet.y, r_sheet.w, r_sheet.h, "SHEET", MEOW_BTN_OUTLINED); SLOT(g_dbtn_sheet, r_sheet.x, r_sheet.y, r_sheet.w, r_sheet.h);
yb += DCH;
/* ---- LazyColumn(100 项列表滚动) ---- */
d_title(DCL_X, SY(yb), "LIST (LazyColumn, tap+swipe)"); yb += 26;
r_list = RX(DCL_X, SY(yb), DFW, 150); g_dlc = meow_lazycolumn_create(r_list.x, r_list.y, r_list.w, r_list.h, 40, 100, demo_list_src, NULL);
SLOT(g_dlc, r_list.x, r_list.y, r_list.w, r_list.h);
meow_list_set_scroll(g_dlc, g_ds.list);
yb += 158;
g_demo_content_h = yb;
#undef SY
#undef SLOT
}
/* demo 全页重绘:背景 + 内容控件(已定位) + 标题 + slider 值 + chrome(后画覆盖) + 内容区 flush */
static void demo_paint(void)
{
const meow_theme *th = meow_theme_get();
int sb = meow_ui_statusbar_height();
meow_ui_fill_rect(0, sb, 480, 720 - sb, th->bg);
for (int i = 0; i < g_dslot_n; i++) meow_widget_draw(g_dslot[i].w);
for (int i = 0; i < g_dtitle_n; i++)
meow_ui_text(g_dtitle[i].x, g_dtitle[i].y, g_dtitle[i].s, 20, th->primary);
if (g_dslider) {
char buf[28]; snprintf(buf, sizeof buf, "slider = %d", meow_slider_get(g_dslider));
meow_ui_text(r_slider_txt.x, r_slider_txt.y, buf, 20, th->dim);
}
/* chrome(固定;后画覆盖内容溢出区) */
meow_widget_draw(g_tb);
meow_widget_draw(g_nav);
meow_widget_draw(g_fab);
meow_ui_flush_region(0, sb, 480, 720 - sb);
}
/* 重建内容到指定 scroll(clamp 到 [0, max_scroll]);进入/滚动共用 */
static void demo_rebuild_to(int scroll)
{
int vp_h = g_demo_cb - g_demo_ct;
int maxs = g_demo_content_h - vp_h;
if (maxs < 0) maxs = 0;
if (scroll < 0) scroll = 0;
if (scroll > maxs) scroll = maxs;
demo_free_content();
demo_build_at(scroll);
g_demo_scroll = scroll;
demo_paint();
}
/* 局部刷新一个控件:清底 + 重画 + flush 其几何矩形(App 自记 rect) */
static void d_redraw(meow_widget *w, rect_t r)
{
if (!w) return;
const meow_theme *th = meow_theme_get();
meow_ui_fill_rect(r.x, r.y, r.w, r.h, th->bg);
meow_widget_draw(w);
meow_ui_flush_region(r.x, r.y, r.w, r.h);
}
static void d_toggle_sel(meow_widget *w, rect_t r)
{
if (!w) return;
meow_widget_set_selected(w, !meow_widget_selected(w));
d_redraw(w, r);
}
/* Tap:落点在内容控件上,返回 1=已消费 */
static int demo_content_tap(int x, int y)
{
g_demo_focus = 0; /* 默认切回页滚动;仅命中列表时改置 1 */
for (int i = 0; i < 5; i++)
if (meow_widget_hit(g_dbtn_b[i], x, y)) { d_toggle_sel(g_dbtn_b[i], r_btn[i]); g_ds.btn[i] = meow_widget_selected(g_dbtn_b[i]); return 1; }
if (meow_widget_hit(g_dr1, x, y)) { meow_widget_set_selected(g_dr1, 1); meow_widget_set_selected(g_dr2, 0); g_ds.r1 = 1; g_ds.r2 = 0; d_redraw(g_dr1, r_dr1); d_redraw(g_dr2, r_dr2); return 1; }
if (meow_widget_hit(g_dr2, x, y)) { meow_widget_set_selected(g_dr2, 1); meow_widget_set_selected(g_dr1, 0); g_ds.r2 = 1; g_ds.r1 = 0; d_redraw(g_dr1, r_dr1); d_redraw(g_dr2, r_dr2); return 1; }
if (meow_widget_hit(g_dcb, x, y)) { d_toggle_sel(g_dcb, r_dcb); g_ds.ck = meow_widget_selected(g_dcb); return 1; }
if (meow_widget_hit(g_dsw, x, y)) { g_ds.sw_on = !meow_switch_get(g_dsw); meow_switch_set(g_dsw, g_ds.sw_on); d_redraw(g_dsw, r_dsw); return 1; }
if (meow_widget_hit(g_dslider, x, y)) {
int frac = x - r_slider.x; if (frac < 0) frac = 0; if (frac > r_slider.w) frac = r_slider.w;
g_ds.slider = frac * 100 / r_slider.w;
meow_slider_set(g_dslider, g_ds.slider);
d_redraw(g_dslider, r_slider);
{
const meow_theme *th = meow_theme_get();
char buf[28]; snprintf(buf, sizeof buf, "slider = %d", g_ds.slider);
meow_ui_fill_rect(r_slider_txt.x, r_slider_txt.y, r_slider_txt.w, r_slider_txt.h, th->bg);
meow_ui_text(r_slider_txt.x, r_slider_txt.y, buf, 20, th->dim);
meow_ui_flush_region(r_slider_txt.x, r_slider_txt.y, r_slider_txt.w, r_slider_txt.h);
}
return 1;
}
if (meow_widget_hit(g_dtf, x, y)) {
meow_textfield_activate(g_dtf); /* 阻塞 IME,返回后刷新 + 状态回写 */
strncpy(g_ds.tf, meow_textfield_text(g_dtf), sizeof g_ds.tf - 1);
g_ds.tf[sizeof g_ds.tf - 1] = 0;
d_redraw(g_dtf, r_dtf);
return 1;
}
if (meow_widget_hit(g_dcard_btn, x, y)) { d_toggle_sel(g_dcard_btn, r_cardbtn); g_ds.card = meow_widget_selected(g_dcard_btn); return 1; }
if (g_dtab) {
int t = meow_tabbar_hit(g_dtab, x, y);
if (t >= 0) { g_ds.tab = t; meow_tabbar_set_selected(g_dtab, t); d_redraw(g_dtab, r_tab); return 1; }
}
if (meow_widget_hit(g_dbtn_dlg, x, y)) { meow_dialog_show(g_dlg); meow_log(MEOW_LOG_INFO, "dialog result=%d", meow_dialog_result(g_dlg)); d_redraw(g_dbtn_dlg, r_dlg); return 1; }
if (meow_widget_hit(g_dbtn_sheet, x, y)) { int r = meow_sheet_open(g_sheet); meow_log(MEOW_LOG_INFO, "sheet result=%d", r); d_redraw(g_dbtn_sheet, r_sheet); return 1; }
if (meow_widget_hit(g_dlc, x, y)) { g_demo_focus = 1; return 1; }
return 0;
}
/* demo 页 on_tap 分发(chrome → 内容) */
static void demo_on_tap(int x, int y)
{
if (g_tb) {
int ah = meow_topappbar_hit(g_tb, x, y);
if (ah == MEOW_TOPBAR_BACK) { g_demo = 0; meow_ui_activity_pop(); redraw(); return; }
}
if (g_fab && meow_widget_hit(g_fab, x, y)) {
g_demo_fab++;
char lb[16]; snprintf(lb, sizeof lb, "+%d", g_demo_fab);
meow_widget_free(g_fab);
g_fab = meow_fab_create(r_fab.x, r_fab.y, 56, lb);
meow_widget_draw(g_fab);
meow_ui_flush_region(r_fab.x, r_fab.y, r_fab.w, r_fab.h);
return;
}
if (g_nav) {
int idx = meow_navigationbar_hit(g_nav, x, y);
if (idx >= 0) { meow_navigationbar_set_selected(g_nav, idx); d_redraw(g_nav, r_nav); return; }
}
if (demo_content_tap(x, y)) return;
}
/* demo 页 on_tick:ProgressBar 动画(局部刷,~120ms 步进) */
static void demo_tick(uint64_t now)
{
if (g_demo_prog_last == 0) g_demo_prog_last = now;
if (now - g_demo_prog_last < 120) return;
g_demo_prog_last = now;
g_ds.prog = (g_ds.prog + 3) % 101;
g_demo_prog = g_ds.prog;
meow_progress_set(g_dprl, g_demo_prog); d_redraw(g_dprl, r_prl);
meow_progress_set(g_dprc, g_demo_prog); d_redraw(g_dprc, r_prc);
}
/* demo 页 on_swipe:LEFT=回 home;UP/DOWN=列表聚焦滚列表,否则页滚动 */
static void demo_swipe(int dir, int dist)
{
if (dir == MEOW_SWIPE_LEFT) { g_demo = 0; meow_ui_activity_pop(); redraw(); return; }
int del = 0;
if (dir == MEOW_SWIPE_UP) del = dist; /* 手指上移 → 偏移增大 */
else if (dir == MEOW_SWIPE_DOWN) del = -dist;
if (del == 0) return;
if (g_demo_focus && g_dlc) {
meow_list_scroll_by(g_dlc, del);
g_ds.list = meow_list_scroll(g_dlc);
d_redraw(g_dlc, r_list); /* 列表区局部刷新 */
} else {
demo_rebuild_to(g_demo_scroll + del);
}
}
/* 进入 demo 演示页(重建到顶部) */
static void demo_enter(void)
{
g_demo = 1;
g_demo_focus = 0;
meow_ui_activity_push(ACT_DEMO);
demo_rebuild_to(0);
meow_log(MEOW_LOG_INFO, "demo enter scroll=0");
}
/* 启动时创建 chrome(固定)+ 内容(scroll=0);push(ACT_DEMO) 后创建使 owner=ACT_DEMO。 */
static void demo_init(void)
{
int sb = meow_ui_statusbar_height();
meow_ui_activity_push(ACT_DEMO);
r_tb = RX(0, sb, 480, 56); g_tb = meow_topappbar_create(0, sb, 480, 56, "Widgets Demo");
meow_topappbar_set_back(g_tb, 1);
r_nav = RX(0, 680, 480, 40); g_nav = meow_navigationbar_create(0, 680, 480, 40);
meow_navigationbar_add_item(g_nav, "Buttons");
meow_navigationbar_add_item(g_nav, "List");
meow_navigationbar_add_item(g_nav, "More");
meow_navigationbar_set_selected(g_nav, 0);
r_fab = RX(406, 616, 56, 56); g_fab = meow_fab_create(406, 616, 56, "+");
demo_build_at(0);
meow_ui_activity_pop();
g_demo_scroll = 0;
}
/* ============================================================ */
/* 既有 home 绘制 / 事件 */
/* ============================================================ */
static void redraw(void)
{
const meow_theme *th = meow_theme_get();
int sb = meow_ui_statusbar_height();
if (g_fullscreen) {
meow_ui_clear(th->accent);
meow_ui_text_center(240, 330, "FULLSCREEN ACTIVE", 28, 0xFFFFFF);
meow_ui_text_center(240, 366, "tap anywhere = back to home", 20, 0xFFFFFF);
meow_ui_text_center(240, 394, "(left-edge swipe also returns)", 20, 0xFFFFFF);
meow_ui_flush();
return;
}
if (sb > 0) meow_ui_fill_rect(0, sb, 480, 720 - sb, th->bg);
else meow_ui_clear(th->bg);
meow_ui_text_center(240, sb + 8, "MEOWOS HELLO", 28, th->fg);
meow_ui_text_center(240, sb + 38, "SDK: meow_ui.a", 20, th->dim);
meow_widget_draw(g_btn);
meow_ui_text_center(240, 372, "tap = send notification (see log)", 20, th->dim);
for (int i = 0; i < 6; i++) meow_widget_draw(g_nbtn[i]);
meow_widget_draw(g_cancel);
meow_widget_draw(g_fs_btn);
meow_widget_draw(g_demo_btn);
char line[64];
snprintf(line, sizeof line, "taps=%d uptime=%ds", g_taps, g_secs);
meow_ui_text_center(240, 516, line, 20, th->dim);
meow_ui_text(20, 660, "left-swipe = exit", 20, th->dim);
meow_ui_flush();
}
static void on_tap(int x, int y)
{
if (g_demo) { demo_on_tap(x, y); return; }
if (g_fullscreen) { set_fullscreen(0); redraw(); return; }
if (meow_widget_hit(g_demo_btn, x, y)) { demo_enter(); return; }
for (int i = 0; i < 6; i++) {
if (meow_widget_hit(g_nbtn[i], x, y)) { notif_send(i); redraw(); return; }
}
if (meow_widget_hit(g_cancel, x, y)) { notif_cancel(); redraw(); return; }
if (meow_widget_hit(g_fs_btn, x, y)) { set_fullscreen(1); redraw(); return; }
if (meow_widget_hit(g_btn, x, y)) {
g_taps++;
snprintf(g_btn_label, sizeof g_btn_label, "TAPPED x%d", g_taps);
meow_widget_free(g_btn);
g_btn = meow_btn_create(120, 300, 240, 64, g_btn_label);
redraw();
}
}
static void on_key(int keycode, int is_down)
{
(void)keycode; (void)is_down;
meow_log(MEOW_LOG_DEBUG, "key");
}
static void on_swipe_progress(int x_start, int dx)
{
meow_ui_swipe_hint(x_start + dx);
}
static void on_swipe(int dir, int dx, int dy)
{
(void)dy;
if (g_demo) { demo_swipe(dir, dx); return; }
if (dir == MEOW_SWIPE_LEFT) {
if (g_fullscreen) { set_fullscreen(0); redraw(); }
else { meow_log(MEOW_LOG_INFO, "exit:swipe"); meow_ui_request_exit(); }
}
}
static void on_tick(void)
{
uint64_t now = meow_ui_time_ms();
if (g_demo) { demo_tick(now); return; }
static uint64_t last = 0;
if (now - last >= 1000) {
last = now;
g_secs++;
redraw();
}
}
int main(void)
{
meow_log(MEOW_LOG_INFO, "start");
meow_ui_opts o = { 480, 720 };
if (meow_ui_init(&o) != 0) { meow_log(MEOW_LOG_ERROR, "init failed"); return 1; }
meow_theme_add_listener(NULL);
signal(SIGTERM, on_sigterm);
g_btn = meow_btn_create(120, 300, 240, 64, g_btn_label);
for (int i = 0; i < 6; i++)
g_nbtn[i] = meow_btn_create(3 + i * 80, 400, 74, 56, g_notif_labels[i]);
g_cancel = meow_btn_create(36, 464, 200, 42, "CANCEL LAST");
g_fs_btn = meow_btn_create(244, 464, 200, 42, "FULLSCREEN");
g_demo_btn = meow_btn_create(36, 560, 408, 44, "WIDGETS DEMO");
/* Dialog / BottomSheet 实例(阻塞模态;打开前排空残留触摸由 SDK 处理) */
g_dlg = meow_dialog_create(MEOW_DLG_SELECT, "Pick a fruit");
const char *items[] = { "Apple", "Banana", "Cherry" };
meow_dialog_set_items(g_dlg, items, 3, 1);
g_sheet = meow_sheet_create("Options", 0);
meow_sheet_add_item(g_sheet, "Copy");
meow_sheet_add_item(g_sheet, "Delete");
meow_sheet_add_item(g_sheet, "Share");
demo_init();
redraw();
meow_ui_events ev = { on_key, on_tap, on_swipe, on_tick, on_swipe_progress };
meow_ui_run(&ev);
meow_widget_free(g_demo_btn);
for (int i = 0; i < 6; i++) meow_widget_free(g_nbtn[i]);
meow_widget_free(g_cancel);
meow_widget_free(g_fs_btn);
meow_widget_free(g_btn);
/* demo 内容 + chrome */
demo_free_content();
meow_widget_free(g_tb);
meow_widget_free(g_nav);
meow_widget_free(g_fab);
meow_dialog_free(g_dlg);
meow_sheet_free(g_sheet);
meow_ui_shutdown();
meow_log(MEOW_LOG_INFO, "exit");
return 0;
}