diff --git a/hello b/hello index 11374f9..074f141 100755 Binary files a/hello and b/hello differ diff --git a/hello.c b/hello.c index a7a8285..86489e2 100644 --- a/hello.c +++ b/hello.c @@ -2,17 +2,39 @@ * * 契约(MEOW_APP_SDK.md §1/§3/§6):应用只调 meow_ui API —— 屏幕(back_buf + * flush 上屏)与输入(meow_ui_run 事件循环)由框架接管;不再 open/mmap/ioctl - * /dev/fb0、不再读 /dev/input/event*。本应用验证「清屏 + 画文本 + 画按钮 + - * 响应触摸」这一最小工作闭环,并集成了通知触发按钮(meow_notify_send,§9.1) - * 用于端到端测试通知链路(SDK → IPC → launcher 中央存储 → 10s TTL 落盘)。 + * /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」按钮进入,逐项验证各组件外观 / 命中 / 状态。 * * 交互: - * - 点按钮 → tap 计数 +1(按钮标签实时更新); - * - 通知触发行(6 个按钮):每个点击 meow_notify_send() 发一条不同参数的通知, - * 覆盖 普通INFO / 媒体(单例) / HIGH紧急度 / 进度(40%) / 常驻 / 跳转Activity; - * - CANCEL LAST 按钮:meow_notify_cancel(最近一次 send 的 id); - * - 左缘左滑(抬手触发)→ 请求退出(回应用列表);滑动过程顶部出现随位移增长的进度条; - * - 空闲时 on_tick 每秒刷新一行状态(含运行秒数)。 + * - 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= 时,首帧 flush 即把 back_buf 写成 * PPM 截图供检查;设备上无副作用。 @@ -28,16 +50,17 @@ #include #include -/* §6.1 ①:Activity 归属。主界面(根,stack 空时 active()=0)绑定全部按钮控件; - * 全屏沉浸页为独立 Activity(无控件)——触摸它绝不命中主界面按钮(根治跨页残留)。 - * 进全屏 meow_ui_activity_push(ACT_FULL),退出 pop 回根(主界面按钮恢复可点,对象复用)。 */ +/* §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 */ @@ -45,72 +68,107 @@ static int g_taps = 0; static int g_secs = 0; static char g_btn_label[32] = "TAP ME"; -/* M2.51:统一日志改用 SDK meow_log()(按 app 分离 + 级别过滤 + 降级), - * 删除自造轮子的 hellolog()(自读 MEOW_LOG_DIR + snprintf + fopen/fprintf)。 */ +/* ---- 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(); } -/* 通知触发:对应第 which 个按钮(0..5)。SDK 端 meow_notify_send() 自动注册未声明 - * 类型(本应用用到 4 种:GENERAL / MEDIA / PROGRESS / MESSAGE,均在 4 种上限内), - * 超时语义见 meow_ui.h §9.1;媒体类型为单例(重发替换)。 */ +/* ============================================================ */ +/* 通知触发(home 既有) */ +/* ============================================================ */ static void notif_send(int which) { meow_notify n; memset(&n, 0, sizeof n); - n.progress = -1; /* 缺省不显示进度条 */ + n.progress = -1; n.timeout_ms = MEOW_NOTIFY_TIMEOUT_NEVER; n.urgency = MEOW_NOTIFY_URG_NORMAL; const char *label = "?"; switch (which) { - case 0: /* 普通 INFO */ - label = "INFO"; - n.cat = MEOW_NOTIFY_CAT_GENERAL; - n.title = "测试-普通通知"; - n.body = "普通 INFO 通知,timeout=5000ms"; - n.timeout_ms = 5000; - break; - case 1: /* 媒体单例(ongoing,不自动过期) */ - 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: /* HIGH 紧急度 */ - 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: /* 常驻(timeout=-1,不自动过期,需手动取消) */ - label = "ONGO"; - n.cat = MEOW_NOTIFY_CAT_GENERAL; - n.title = "测试-常驻通知"; - n.body = "常驻不自动过期(timeout=-1),需手动取消"; - n.timeout_ms = MEOW_NOTIFY_TIMEOUT_ONGOING; - break; - case 5: /* 带 target_activity + icon(SDK 结构含 icon 字段;NULL/空=用 app 默认图标) */ - 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; /* 空 → launcher 用 app 默认图标(app.json icon) */ - break; - default: - return; + 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); @@ -119,8 +177,7 @@ static void notif_send(int which) "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 : "-"); + n.target_activity ? n.target_activity : "-", n.title ? n.title : "-"); } static void notif_cancel(void) @@ -130,13 +187,6 @@ static void notif_cancel(void) g_last_notif = 0; } -/* 进入/退出全屏视图(MeowOS 全屏机制,§3.1): - * - fullscreen(1) + collapsed(1):状态栏「上收出屏只留提示线」(不完全沉浸,inset=4px), - * 顶部提示线热区仍可下拉,左缘返回手势(app 已注册 on_swipe)仍生效——SDK 自动处理,非 app 拦截; - * - fullscreen(0) + collapsed(0):恢复整条状态栏主界面。 - * SDK 基座默认瞬切(g_sbar_collapse_anim_ms=0);如需过渡动画可先 meow_ui_statusbar_animate(ms)。 - * §6.1 ①:全屏 = 独立 Activity——进时压栈 ACT_FULL(主界面控件因 owner=ACT_HOME 不再参与命中, - * 根治「全屏页触摸到主页按钮区域闪现」);退时弹栈回根,主界面按钮恢复可点(对象复用零重建)。 */ static void set_fullscreen(int on) { g_fullscreen = on ? 1 : 0; @@ -145,16 +195,315 @@ static void set_fullscreen(int on) 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", + : "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(); /* A-0 全局状态栏 inset:内容下移避免被盖 */ + int sb = meow_ui_statusbar_height(); if (g_fullscreen) { - /* 全屏页:纯色占满 + 居中提示;点任意处 / 左缘左滑(SDK on_swipe)返回 */ 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); @@ -162,8 +511,6 @@ static void redraw(void) meow_ui_flush(); return; } - /* inset:只清「状态栏之下」内容区(top sb px 保留给系统状态栏——由 launcher 进程叠加绘制; - * App 进程不渲染状态栏、也不冲掉它)。非全屏时 App 内容只在 y>=sb。 */ 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); @@ -173,6 +520,7 @@ static void redraw(void) 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); @@ -182,33 +530,17 @@ static void redraw(void) static void on_tap(int x, int y) { - if (g_fullscreen) { - /* 全屏页:点任意处退出回主界面(左缘左滑由 SDK on_swipe 处理,见下) */ - set_fullscreen(0); - redraw(); - return; - } + 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_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); - /* 重建按钮标签(release 旧控件,重新 create 保持几何) */ meow_widget_free(g_btn); g_btn = meow_btn_create(120, 300, 240, 64, g_btn_label); redraw(); @@ -217,26 +549,20 @@ static void on_tap(int x, int y) static void on_key(int keycode, int is_down) { - /* §3.4.1:物理键语义由系统统一管理,APP 禁映射(电源=息屏/亮屏、音量=调音量、播放=媒体)。 - * 这里仅记录日志,不做任何 exit/confirm 行为。 */ (void)keycode; (void)is_down; - meow_log(MEOW_LOG_DEBUG, "key"); /* 不处理:SDK 内部消化系统键语义 */ + meow_log(MEOW_LOG_DEBUG, "key"); } static void on_swipe_progress(int x_start, int dx) { - /* 左滑过程实时回调:SDK 已自动画顶部进度条;这里演示 app 侧接入(画同长度条,幂等—— - * 长度=当前 X=x_start+dx,与 SDK 自动画一致,不会误清条)。 - * 抬手达标时 SDK 清条 + 触发 on_swipe(LEFT)→退出。 */ meow_ui_swipe_hint(x_start + dx); } static void on_swipe(int dir, int dx, int dy) { - (void)dx; (void)dy; + (void)dy; + if (g_demo) { demo_swipe(dir, dx); return; } if (dir == MEOW_SWIPE_LEFT) { - /* §6.1 导航栈语义:非根(全屏)→ 左滑返回主页(pop),不退出;根 → 退出回应用列表。 - * 保证 on_swipe(LEFT) 在沉浸页=退出全屏回主页,而非直接退出整个 app。 */ if (g_fullscreen) { set_fullscreen(0); redraw(); } else { meow_log(MEOW_LOG_INFO, "exit:swipe"); meow_ui_request_exit(); } } @@ -244,9 +570,9 @@ static void on_swipe(int dir, int dx, int dy) static void on_tick(void) { - /* 每秒刷新一次状态行(避免 16ms 高频重绘) */ - static uint64_t last = 0; 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++; @@ -259,26 +585,44 @@ 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; } - /* M2.46 不再强制 set_mode 深色——主题跟随系统 display.theme/theme_mode(manual/auto+时段), - * 与 launcher s_ui_theme() 判定对齐(meow_theme_get() 已读 settings.conf)。 */ - meow_theme_add_listener(NULL); /* noop: 预留主题变化重绘 */ + meow_theme_add_listener(NULL); - signal(SIGTERM, on_sigterm); /* 看门狗返回键 → 干净退出 */ + 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;