diff --git a/examples/hello/app.c b/examples/hello/app.c new file mode 100644 index 0000000..bbf9dc9 --- /dev/null +++ b/examples/hello/app.c @@ -0,0 +1,140 @@ +/* app.c — hello 应用装配层:main + 组装 + 事件分发。 + * + * 职责(SDK_VIEW_FRAMEWORK_DESIGN.md §B.2):main 只做初始化/建页/跑事件循环/清理;把 + * meow_ui_run 的平铺输入回调(key/tap/swipe/tick/touch)路由到当前活动页(home/full/demo)。 + * 页面切换统一走 meow_view_page_push/pop(活动栈 + on_enter/on_leave 的唯一驱动,App 不再 + * 分别调 activity_push/pop)。业务变化经 logic 监听器送回 UI(见 ui/home/home.c)。 + * + * 契约(MEOW_APP_SDK.md §1/§3/§6):应用只调 meow_ui API。 + */ +#include "hello_internal.h" +#include "meow_ui_nav.h" /* ①:App 侧 M3 导航上报封装(上报页面深度/顶层) */ +#include "meow_ui_media.h" /* ②:媒体会话封装(demo 页按钮上报 META + 收 PLAYCTL) */ +#include + +/* Dialog / BottomSheet(阻塞模态;非控件,main() 创建一次,demo 页打开/关) */ +meow_dialog *g_dlg; +meow_sheet *g_sheet; + +static meow_view *g_home_view; +static meow_view *g_full_view; +static meow_view *g_demo_view; + +static void on_sigterm(int s) { (void)s; meow_ui_request_exit(); } + +/* ② 媒体会话播放控制回调(launcher 下发 MSG_PLAYCTL 到最活跃媒体会话)。 */ +static void demo_media_playctl(int cmd, int64_t seek_ms) +{ + meow_log(MEOW_LOG_INFO, "media playctl cmd=%d seek=%lld", cmd, (long long)seek_ms); +} + +/* ① 导航上报:把当前 Activity 栈深/顶层报给 launcher(M3 导航栈)。每个页面 on_enter 时调用 + * (view_page_push/pop 会同步触发新页 on_enter),保证 pop/push 后 launcher 的 nav_stack 跟随。 + * depth 取 meow_ui_activity_depth()(实际 push 页数,≥1);top 取 activity id 映射的字符串。 */ +void hello_nav_report(void) +{ + int depth = meow_ui_activity_depth(); + if (depth <= 0) depth = 1; + int id = meow_ui_activity_active(); + const char *top = (id == ACT_FULL) ? "full" : (id == ACT_DEMO) ? "demo" : "home"; + meow_ui_nav_report((uint32_t)depth, top); +} + +/* ============================================================ */ +/* 事件分发:meow_ui_run 平铺回调 → 当前页 */ +/* ============================================================ */ + +static void app_on_key(int keycode, int is_down) +{ + (void)keycode; (void)is_down; + meow_log(MEOW_LOG_DEBUG, "key"); +} + +static void app_on_tap(int x, int y) +{ + if (demo_is_active()) { demo_on_tap(x, y); return; } + if (full_is_active()) { meow_view_page_pop(); return; } /* 全屏:点任意处回 home */ + home_on_tap(x, y); +} + +static void app_on_swipe(int dir, int dx, int dy) +{ + (void)dy; + if (demo_is_active()) { demo_swipe(dir, dx); return; } + if (dir == MEOW_SWIPE_LEFT) { + if (full_is_active()) { meow_view_page_pop(); } + else { meow_log(MEOW_LOG_INFO, "exit:swipe"); meow_ui_request_exit(); } + } +} + +/* M2.71-bug2/6:拖动过程回调(只对 demo 页有意义;home/全屏不拖拽)。 */ +static void app_on_touch_down(int x, int y) +{ + if (demo_is_active()) demo_touch_down(x, y); +} +static void app_on_touch_move(int x, int y) +{ + if (demo_is_active()) demo_touch_move(x, y); +} + +static void app_on_swipe_progress(int x_start, int dx) +{ + meow_ui_swipe_hint(x_start + dx); +} + +static void app_on_tick(void) +{ + uint64_t now = meow_ui_time_ms(); + if (demo_is_active()) { demo_tick(now); return; } + static uint64_t last = 0; /* home/全屏计时:1s 一跳(demo 不计数,同旧) */ + if (now - last >= 1000) { last = now; logic_secs_add(1); } +} + +/* ============================================================ */ +/* main */ +/* ============================================================ */ +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); + meow_ui_nav_init(); /* ① 读 MEOW_ACTIVITY/MEOW_CHAIN 初始化导航栈 */ + meow_ui_media_init(NULL); /* ② app_id=env MEOW_LOG_APP */ + + /* ② 注册播放控制回调(launcher 经媒体会话下发 MSG_PLAYCTL):hello 只打日志验证。 */ + meow_ui_media_on_playctl(demo_media_playctl); + + signal(SIGTERM, on_sigterm); + + /* 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"); + + /* 建三页 view(on_create 建各自控件/状态)+ 弹层实例 */ + g_home_view = home_view_create(); + g_full_view = full_view_create(); + g_demo_view = demo_view_create(); + + /* 进入根页(activity_push + home on_enter → home_redraw) */ + meow_view_page_push(ACT_HOME); + + meow_ui_events ev = { app_on_key, app_on_tap, app_on_swipe, app_on_tick, app_on_swipe_progress, + app_on_touch_down, app_on_touch_move }; + meow_ui_run(&ev); + + /* 清理:meow_view_free 触发各页 on_destroy(home 控件 / demo 内容+chrome) */ + meow_view_free(g_full_view); + meow_view_free(g_demo_view); + meow_view_free(g_home_view); + meow_dialog_free(g_dlg); + meow_sheet_free(g_sheet); + meow_ui_shutdown(); + meow_log(MEOW_LOG_INFO, "exit"); + return 0; +} diff --git a/examples/hello/build_hello.sh b/examples/hello/build_hello.sh new file mode 100755 index 0000000..98edcb1 --- /dev/null +++ b/examples/hello/build_hello.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Build the hello app (SDK B1, 视图框架批次③目录嵌套+视图深度化): 链 meow_ui.a(已内含 meow_font.o), +# 额外链系统 libz.a(meow_font 的 inflate 依赖)+ -lpthread。静态 MIPS32r2 (musl sf)。 +# 批次②:hello.c 拆为多文件(app.c + ui/home.c + ui/full.c + ui/demo.c + ui/common/chrome.c + +# logic/notify.c + logic/state.c)。 +# 批次③:ui/home、ui/demo 子目录化(ui/home/home.c + ui/demo/demo.c);demo 列表区块抽成 +# CONTAINER 子视图 ui/demo/list/list.c。仅改列文件,编译期不改 SDK(patch_v2/sdk/ 零触及)。 +# mipsel -Wall -Wextra 0 warning。 +set -e +cd "$(dirname "$0")" +ROOT="$HOME/Project/R3ProII" +CC="$ROOT/.tools/zig-mipsel-cc" +SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf" +"$CC" -Os -static -Wall -Wextra -I../sdk -I. \ + app.c ui/home/home.c ui/full.c ui/demo/demo.c ui/demo/list/list.c ui/common/chrome.c \ + logic/notify.c logic/state.c \ + ../sdk/meow_ui.a "$SYSROOT/lib/libz.a" -lpthread -o hello +ls -la hello +file hello diff --git a/examples/hello/hello b/examples/hello/hello new file mode 100755 index 0000000..95cf810 Binary files /dev/null and b/examples/hello/hello differ diff --git a/examples/hello/hello_internal.h b/examples/hello/hello_internal.h new file mode 100644 index 0000000..1c66f55 --- /dev/null +++ b/examples/hello/hello_internal.h @@ -0,0 +1,120 @@ +/* hello_internal.h — hello 应用内部共享头(UI 各页 + app 装配层 + 宿主测试面)。 + * + * 层级(SDK_VIEW_FRAMEWORK_DESIGN.md §B.1/B.2): + * - ui/ 每页一个 view 文件(持视图态 scroll/focus/drag/几何);共享 chrome 在 ui/common/; + * - logic 下纯业务模块(state: taps/secs;notify: 通知发送/取消),UI 只经 logic_* / notify_* 调用; + * - app.c main + 装配 + 事件分发(把 meow_ui_run 的平铺回调路由到各页)。 + * 页面组织:home/full/demo 各建一个 meow_view(ACTIVITY),生命周期用 + * meow_view_page_push/pop 驱动(on_enter/on_leave 唯一入口);绘图仍走 batch① 已接线的 + * 路径(home/full 手绘 + demo 走 meow_ui_paint_full 注册表),on_draw 本批置 NULL(见报告)。 + * + * 本头是全应用/测试的跨 TU 声明:只声明(extern),不定义,避免多定义。宿主 test_p1p2p3_repro + * 直接 #include 本头 + 链接各 .c,驱动 demo 真实函数(demo_paint/demo_rebuild_to/demo_touch_down) + * 对 back_buf 做断言——这是测「真实 demo 代码 + 真实 SDK」,不是模拟桩。 + */ +#ifndef HELLO_INTERNAL_H +#define HELLO_INTERNAL_H + +#include +#include "meow_ui.h" +#include "meow_ui_view.h" +#include "logic/logic.h" + +/* ---- App 级弹层(app.c 创建;demo 页打开/关)---- */ +extern meow_dialog *g_dlg; /* Dialog(阻塞模态;main 创建一次) */ +extern meow_sheet *g_sheet; /* BottomSheet(阻塞模态;main 创建一次) */ + +/* ---- Activity 归属(§6.1 ①)---- */ +#define ACT_HOME 0 +#define ACT_FULL 1 +#define ACT_DEMO 2 + +/* ---- 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) }) + +/* ---- 视图控制器(ctx = biz-UI 绑定点)---- + * 本批控制器为薄壳:持 meow_view 回指针;页面细粒度视图态(g_ds/g_drag_* /scroll 等)留在各页 + * 模块作用域全局(设计定案 B:demo 视图态不迁 logic)。控制器未来可吸收这些状态。 */ +typedef struct { + meow_view *view; + int active; /* 是否有页进入(与模块 active 全局同步) */ + int _pad; +} home_ctrl; +typedef struct { + meow_view *view; + int active; + int _pad; +} full_ctrl; +typedef struct { + meow_view *view; + int active; /* = g_demo */ + int focus; /* = g_demo_focus */ + int scroll; /* = g_demo_scroll */ + int _pad; +} demo_ctrl; + +/* ---- home 页(ui/home/home.c)---- */ +meow_view *home_view_create(void); /* 建 home view;on_create 建 home 控件 + 注册业务监听器 */ +int home_on_tap(int x, int y);/* home 态命中分发:返回 1=已消费(含导航到 demo/full) */ + +/* ---- full 页(ui/full.c)---- */ +meow_view *full_view_create(void); /* 建 full view(无控件;全屏文本由 full_draw 手绘) */ +int full_is_active(void); /* 1=处于全屏态 */ +void full_draw(void); /* 画全屏帧(clear accent + 文本 + flush) */ + +/* ---- demo 页(ui/demo/demo.c)---- */ +meow_view *demo_view_create(void); /* 建 demo view;on_create 调 demo_init() 建 chrome+内容 */ +int demo_is_active(void); /* 1=处于 demo 页 */ +void demo_on_tap(int x, int y); +void demo_touch_down(int x, int y); +void demo_touch_move(int x, int y); +void demo_swipe(int dir, int dist); +void demo_tick(uint64_t now); + +/* demo 构建/绘制入口(宿主 test_p1p2p3_repro 直接驱动 + demo view 复用): + * demo_init 建 chrome+内容(scroll=0);demo_paint 整帧按 z 重画;demo_rebuild_to 重建到 scroll。 */ +void demo_init(void); +void demo_paint(void); +void demo_rebuild_to(int scroll); + +/* ---- demo 视图态(设计定案 B:g_ds 非列表字段/scroll/drag_page 留 ui/demo/demo.c,不迁 logic; + * 列表视图态批③ 迁 ui/demo/list/list.c——下列四项定义在 list.c,经本头 extern 跨 TU 共享)。---- + * 暴露为 extern,供宿主测试对真实 demo 状态断言(P1/P2/P3)。 */ +extern int g_demo; /* 1=处于 demo 演示页 */ +extern int g_demo_focus; /* 1=LazyColumn 聚焦(list.c) */ +extern int g_demo_scroll; /* 内容区滚动偏移 */ +extern int g_drag_list; /* 手指在列表:拖拽滚列表(list.c) */ +extern int g_drag_page; /* 手指在页内容:拖拽滚页面 */ +extern meow_widget *g_dlc; /* LazyColumn(list.c) */ +extern meow_widget *g_dprc; /* ProgressBar 圆形 */ +extern rect_t r_prc; /* 圆形进度外接框 */ +extern rect_t r_list; /* 列表区几何(list.c) */ +extern rect_t r_dr1, r_dr2; /* Radio 几何 */ + +/* ---- 列表区 CONTAINER 子视图(ui/demo/list/list.c;批③ 视图深度化)---- + * demo 页把列表逻辑区块抽成 meow_view CONTAINER 子视图挂到 DEMO ACTIVITY view children, + * 承接列表交互(tap/down/move/swipe)、列表视图态与列表区块标题 on_draw 直绘。 + * 嵌套生命周期由 SDK view_enter/view_leave 递归驱动(随 demo 页进出;list_view_enter/leave + * 已退役,无需 App 层显式接管)。 */ +meow_view *list_view_create(void); /* 建 CONTAINER 子视图(须即 meow_view_add_child) */ +meow_view *list_view_get(void); /* 返回子视图(宿主测嵌套 on_enter/on_leave 断言) */ +void list_view_bind(meow_widget *w, rect_t list, rect_t title); /* 内容重建后绑控件+几何+恢复态 */ +void list_view_release(void); /* 内容释放后摘引用/几何 */ +int list_view_hit(int x, int y); /* tap 分发给列表区,命中即消费(含选中) */ +int list_view_down(int x, int y); /* touch_down 命中列表:置 focus+drag_list */ +int list_view_move(int y); /* touch_move 拖动列表:1=在列表拖动 */ +int list_view_swipe(int del); /* swipe:列表聚焦时滚列表,1=已消费 */ + +/* ---- chrome(ui/common/chrome.c)---- */ +extern meow_widget *g_tb; /* TopAppBar(chrome 固定) */ +extern meow_widget *g_nav; /* NavigationBar(chrome 固定) */ +extern meow_widget *g_fab; /* FAB(chrome 置顶;demo 交互可重建换 label) */ +extern rect_t r_tb, r_nav, r_fab; +void chrome_create(void); /* 建 chrome(调用方须先 push ACT_DEMO 使 owner=ACT_DEMO) */ +void chrome_free(void); /* 释放 chrome(demo on_destroy 调) */ + +/* ---- App 级导航/媒体接线(app.c)---- */ +void hello_nav_report(void); /* 上报当前 Activity 栈深/顶层(各页 on_enter 调) */ + +#endif /* HELLO_INTERNAL_H */ diff --git a/examples/hello/logic/logic.h b/examples/hello/logic/logic.h new file mode 100644 index 0000000..f655ad2 --- /dev/null +++ b/examples/hello/logic/logic.h @@ -0,0 +1,33 @@ +/* logic/logic.h — 业务层接口(纯 C,不依赖 meow_ui.h / meow_view)。 + * + * 分层契约(SDK_VIEW_FRAMEWORK_DESIGN.md §B.1): + * ① UI→业务 = 纯函数命令 + getter(logic_* / notify_* 返回业务态,业务不回调 UI); + * ② 业务→UI = 可选 push 监听器(logic_listener_fn + logic_set_listener); + * ③ view 的 ctx = 视图控制器(biz-UI 绑定点)。 + * 本头只声明数据类型与函数原型,不含任何 meow_ui.h / meow_ui_view.h 依赖—— + * logic/state.c 因此保持纯 C;logic/notify.c 仅因要用 SDK 通知 API 而 include meow_ui.h。 + */ +#ifndef HELLO_LOGIC_H +#define HELLO_LOGIC_H + +/* 监听器事件类型(业务态变化 → 通知 UI) */ +#define LOGIC_EV_TAPS 1 /* taps 计数变化 */ +#define LOGIC_EV_SECS 2 /* secs 秒计数变化 */ +#define LOGIC_EV_PROG 3 /* demo 进度值变化(批③ 语义值上收) */ + +typedef void (*logic_listener_fn)(int ev, void *ctx); /* ctx = 监听器注册时给的视图控制器 */ + +/* ---- 计数业务态(logic/state.c)---- */ +int logic_taps_get(void); /* 返回累计 taps */ +void logic_taps_bump(void); /* taps++,并触发监听器(若已注册) */ +int logic_secs_get(void); /* 返回过去秒数 */ +void logic_secs_add(int n); /* secs += n,并触发监听器(若已注册) */ +int logic_prog_get(void); /* 返回 demo 进度值 0..100(批③ 语义值上收) */ +void logic_prog_set(int p); /* 设置进度值(clamp 0..100),并触发监听器(若已注册) */ +void logic_set_listener(logic_listener_fn cb, void *ctx); /* 注册业务→UI 推送监听器(可 NULL 注销) */ + +/* ---- 通知业务(logic/notify.c)---- */ +void notif_send(int which); /* 按索引发一条通知(数据/超时/优先级在业务层定) */ +void notif_cancel(void); /* 取消最近一条发送的通知 */ + +#endif /* HELLO_LOGIC_H */ diff --git a/examples/hello/logic/notify.c b/examples/hello/logic/notify.c new file mode 100644 index 0000000..8675bbd --- /dev/null +++ b/examples/hello/logic/notify.c @@ -0,0 +1,56 @@ +/* logic/notify.c — 通知业务(notif_send/notif_cancel)。 + * + * 纯业务:只依赖 SDK 通知数据类型/API(meow_notify_*)+ meow_log,不碰任何 UI 控件/view。 + * UI 只经 notif_send()/notif_cancel() 调用(契约 ①);本层不回调 UI,最近一条 id 由本层持有。 + * 注:boundary 折中——通知是 SDK 服务(§9.1),故本文件 include meow_ui.h 仅用其通知类型/函数。 + */ +#include "logic.h" +#include "meow_ui.h" +#include +#include + +static uint32_t g_last_notif = 0; /* 最近一次 send 返回的 id,供 cancel */ + +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 : "-"); +} + +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; +} diff --git a/examples/hello/logic/state.c b/examples/hello/logic/state.c new file mode 100644 index 0000000..56c52ed --- /dev/null +++ b/examples/hello/logic/state.c @@ -0,0 +1,51 @@ +/* logic/state.c — 计数业务态(taps/secs)+ 可选推送监听器(biz→UI)。 + * + * 纯 C(无 meow_ui.h 依赖):状态 + getter/setter + 监听器。UI 经 logic_taps_bump()/ + * logic_secs_add() 改状态;业务改后触发监听器(若已注册)通知 UI 刷新——契约 ①②(§B.1)。 + */ +#include "logic.h" +#include + +static int g_taps = 0; +static int g_secs = 0; +static int g_prog = 0; /* demo 进度语义值 0..100(批③ 自 ui/demo/demo.c 上收) */ +static logic_listener_fn g_listener = NULL; +static void *g_listener_ctx = NULL; + +int logic_taps_get(void) +{ + return g_taps; +} +void logic_taps_bump(void) +{ + g_taps++; + if (g_listener) g_listener(LOGIC_EV_TAPS, g_listener_ctx); +} + +int logic_secs_get(void) +{ + return g_secs; +} +void logic_secs_add(int n) +{ + g_secs += n; + if (g_listener) g_listener(LOGIC_EV_SECS, g_listener_ctx); +} + +int logic_prog_get(void) +{ + return g_prog; +} +void logic_prog_set(int p) +{ + if (p < 0) p = 0; + if (p > 100) p = 100; + g_prog = p; + if (g_listener) g_listener(LOGIC_EV_PROG, g_listener_ctx); +} + +void logic_set_listener(logic_listener_fn cb, void *ctx) +{ + g_listener = cb; + g_listener_ctx = ctx; +} diff --git a/examples/hello/render_test.png b/examples/hello/render_test.png new file mode 100644 index 0000000..e6f1caa Binary files /dev/null and b/examples/hello/render_test.png differ diff --git a/examples/hello/ui/common/chrome.c b/examples/hello/ui/common/chrome.c new file mode 100644 index 0000000..25a2fea --- /dev/null +++ b/examples/hello/ui/common/chrome.c @@ -0,0 +1,36 @@ +/* ui/common/chrome.c — demo 页共享 chrome(TopAppBar / NavigationBar / FAB)。 + * + * 与 demo 内容分离:chrome 固定不随内容重建滚动;显式 set_z(999) 置顶(§6.2), + * 区域/整帧重画都最后画 chrome。FAB 可被 demo 交互重建换计数 label(见 demo_on_tap)。 + * chrome 控件 owner=ACT_DEMO(由调用方 demo_init 先 push ACT_DEMO 再建),命中/绘制只在 demo 态生效。 + */ +#include "hello_internal.h" + +meow_widget *g_tb = NULL; /* TopAppBar(chrome 固定) */ +meow_widget *g_nav = NULL; /* NavigationBar(chrome 固定) */ +meow_widget *g_fab = NULL; /* FAB(chrome 固定,可重建换计数 label) */ +rect_t r_tb, r_nav, r_fab; + +void chrome_create(void) +{ + int sb = meow_ui_statusbar_height(); + 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, "+"); + /* chrome 显式置顶(§6.2):> 一切自动 z(127),区域重画/整帧都最后画 chrome。 */ + meow_widget_set_z(g_tb, 999); + meow_widget_set_z(g_nav, 999); + meow_widget_set_z(g_fab, 999); +} + +void chrome_free(void) +{ + if (g_tb) { meow_widget_free(g_tb); g_tb = NULL; } + if (g_nav) { meow_widget_free(g_nav); g_nav = NULL; } + if (g_fab) { meow_widget_free(g_fab); g_fab = NULL; } +} diff --git a/examples/hello/ui/demo/demo.c b/examples/hello/ui/demo/demo.c new file mode 100644 index 0000000..2968da6 --- /dev/null +++ b/examples/hello/ui/demo/demo.c @@ -0,0 +1,498 @@ +/* ui/demo/demo.c — 控件库 Demo 演示页(ACT_DEMO)view + 控制器(批次③ 目录嵌套后)。 + * + * 内容控件以绝对坐标创建,页滚动 = 按「base_y - scroll」重建内容(owner=ACT_DEMO 不变); + * 各控件跨重建状态存 g_ds(App 持有),重建后恢复。绘制走批次① z 序注册表:demo_paint 调 + * meow_ui_set_page_bg + meow_ui_paint_full(整帧按 z 重画:内容→标题/slider值→chrome 置顶), + * 局部刷新统一 meow_ui_invalidate(清底+按 z 重画相交绘制物)。旧 d_paint_region/d_redraw/ + * d_rect_overlap(App 层 z 栈重画)已删、下沉 SDK。 + * + * 批次②③ 拆分/深度化(SDK_VIEW_FRAMEWORK_DESIGN §B.2/§B.4/§E③): + * - 列表区逻辑区块抽成 CONTAINER 子视图 ui/demo/list/list.c(挂到本页 ACTIVITY view children), + * 列表视图态(g_dlc/r_list/g_demo_focus/g_drag_list)随迁;本页只做页级分发(子>父)。 + * - 进度语义值 g_ds.prog 已上收 logic/state.c(logic_prog_get/set,经监听器)。 + * - 设计定案 B:其余视图态(demo 页 g_ds 非列表字段/g_demo_scroll/drag_page)留本文件(不迁 logic); + * UI→业务只经 logic_* / notify_*。页面进入/离开经 meow_view_page_push/pop 驱动。 + */ +#include "hello_internal.h" +#include "meow_ui_media.h" /* ② demo 页按钮上报媒体 META + 收 PLAYCTL */ +#include +#include +#include + +/* 供 demo_content_tap / touch 回调用的前向声明 */ +static void demo_slider_drag(int x); +static void demo_media_send(void); + +static demo_ctrl g_demo_ctrl; + +/* ---- demo 演示页状态 ---- */ +int g_demo = 0; /* 1=处于 demo 演示页 */ +int g_demo_scroll = 0; /* 内容区滚动偏移(base 空间 px) */ +static int g_demo_fab = 0; /* FAB 点击计数反馈 */ +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) */ +/* 注:g_demo_focus/g_drag_list/g_dlc/r_list 已迁 ui/demo/list/list.c(列表视图态归列表区 + * CONTAINER 子视图),本文件仅经 hello_internal.h 的 extern 引用;g_demo_prog 已上收 logic/state.c。 */ + +/* M2.71-bug2/6:拖动过程(跟手)状态。on_touch_down 锁一种拖拽,on_touch_move 实时更新。 + * g_drag_list(列表拖拽)/g_drag_list_last_y 归列表区 CONTAINER 子视图 list.c(extern 引用)。 + * 注:g_drag_page 仍为本页「页内容拖拽」标志。 */ +static int g_drag_slider = 0; /* 手指按在 slider */ +int g_drag_page = 0; /* 手指按在页内容(非 slider/list):拖拽滚页面 */ +static int g_drag_page_last_y = 0; /* 页面拖拽上一帧 y */ +static int g_drag_page_delta = 0; /* 页面拖拽累计位移(节流重建,≥8px 才重建一次) */ + +/* SDK 控件不透明:App 自记几何,用于局部 flush / 命中 / scroll 重建。 */ + +/* demo 各控件跨重建保留的状态(app 持有;重建后按此恢复)—— + * 列表态 list/list_sel 已迁 ui/demo/list/list.c(列表区 CONTAINER 子视图)持有; + * 进度语义值 prog 已上收 logic/state.c(logic_prog_*)。 */ +static struct { + int sw_on; int slider; char tf[32]; char tf_py[32]; + int ck; int r1, r2; int btn[5]; int card; int tab; +} g_ds = { .sw_on = 1, .slider = 40, .r1 = 1, .tab = 1 }; + +/* 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;chrome 在 ui/common/chrome.c) */ +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_dtf_py; static rect_t r_dtf_py; /* TextField (拼音 IME 演示) */ +static meow_widget *g_dcb; static rect_t r_dcb; /* Checkbox */ +static meow_widget *g_dr1, *g_dr2; rect_t r_dr1, r_dr2; /* Radio 单选组 */ +static meow_widget *g_dprl; static rect_t r_prl; /* ProgressBar 线性 */ +meow_widget *g_dprc; 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 的按钮 */ +/* ② 媒体会话上报按钮(demo 页演示):点按 → meow_ui_media_meta 上报一条播放中元数据。 */ +static meow_widget *g_dbtn_media; static rect_t r_media; +/* LazyColumn:g_dlc/r_list 已迁 list.c(列表区 CONTAINER 子视图),此处仅 extern 引用。 */ + +/* ---- demo 分区标题/slider 值文本:注册为 CUSTOM 绘制物(§6.2 区域重画会重画它们)---- + * 显式 z=D_TITLE_Z:> 一切自动 z(=127)、< chrome 置顶(999)。非控件直绘必须注册,否则 + * meow_ui_invalidate 清底后会漏画。 */ +#define D_TITLE_Z 128 +static int g_dtitle_cid[D_TITLE_MAX]; /* 分区标题绘制物 id(rebuild 时注销/重注册) */ +static int g_slider_cid = 0; /* slider 值文本绘制物 id */ + +/* CUSTOM 绘制回调:分区标题(ctx=title 下标,指向 g_dtitle[i]) */ +static void demo_draw_title(void *ctx) +{ + int i = (int)(intptr_t)ctx; + if (i < 0 || i >= g_dtitle_n) return; + const meow_theme *th = meow_theme_get(); + meow_ui_text(g_dtitle[i].x, g_dtitle[i].y, g_dtitle[i].s, 20, th->primary); +} +/* CUSTOM 绘制回调:slider 当前值文本(bbox=r_slider_txt) */ +static void demo_draw_slider_val(void *ctx) +{ + (void)ctx; + if (!g_dslider) return; + const meow_theme *th = meow_theme_get(); + 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); +} + +/* ---- 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) { + int i = g_dtitle_n; + g_dtitle[i].x = x; g_dtitle[i].y = y; g_dtitle[i].s = s; + g_dtitle_n++; + int tw = meow_ui_text_width(s, 20); + int th = meow_ui_text_height(20); + g_dtitle_cid[i] = meow_draw_custom_register(D_TITLE_Z, x, y, tw, th, + demo_draw_title, (void *)(intptr_t)i); + } +} + +/* 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 的独立子按钮)与 CUSTOM 绘制物;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; } + for (int i = 0; i < g_dtitle_n; i++) + if (g_dtitle_cid[i] > 0) { meow_draw_unregister(g_dtitle_cid[i]); g_dtitle_cid[i] = 0; } + if (g_slider_cid > 0) { meow_draw_unregister(g_slider_cid); g_slider_cid = 0; } + g_dtitle_n = 0; + list_view_release(); /* 列表控件已在上方 g_dslot 释放;摘容器引用/几何 */ +} + +/* 按 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), 160, 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; + g_slider_cid = meow_draw_custom_register(D_TITLE_Z, r_slider_txt.x, r_slider_txt.y, + r_slider_txt.w, r_slider_txt.h, + demo_draw_slider_val, NULL); + + 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; + /* 拼音演示:MEOW_IME_PINYIN,输入「nihao」出候选「你好」上屏 */ + r_dtf_py = RX(DCL_X, SY(yb), DFW, 44); g_dtf_py = meow_textfield_create(r_dtf_py.x, r_dtf_py.y, r_dtf_py.w, r_dtf_py.h, "拼音: nihao", g_ds.tf_py, 32, MEOW_IME_PINYIN); SLOT(g_dtf_py, r_dtf_py.x, r_dtf_py.y, r_dtf_py.w, r_dtf_py.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 动画)---- + * 进度语义值已上收 logic/state.c;重建从 logic_prog_get() 恢复。 */ + 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, logic_prog_get()); SLOT(g_dprl, r_prl.x, r_prl.y, r_prl.w, r_prl.h); yb += 16; + r_prc = RX(DCL_X + 400 + 17 - 20, SY(yb) + 17 - 20, 40, 40); + g_dprc = meow_progress_circular_create(r_prc.x + 20, r_prc.y + 20, 17, logic_prog_get()); + 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; + + /* ---- Media Session(② demo 按钮上报媒体 META,验证 SDK 封装) ---- */ + d_title(DCL_X, SY(yb), "MEDIA SESSION (meta)"); yb += 26; + r_media = RX(DCL_X, SY(yb), DCW, DBH); g_dbtn_media = meow_btn_create_variant(r_media.x, r_media.y, r_media.w, r_media.h, "SEND META", MEOW_BTN_FILLED); SLOT(g_dbtn_media, r_media.x, r_media.y, r_media.w, r_media.h); yb += DCH; + + /* ---- LazyColumn(100 项列表滚动) ---- + * 本区块标题改由列表区 CONTAINER 子视图(ui/demo/list/list.c)的 on_draw 绘制(注册表→ + * invalidate 链路);列表滚动/选中由 list_view_bind 从子视图持有态恢复(不再经 g_ds)。 */ + int list_title_y = SY(yb); yb += 26; /* 标题条(子视图 on_draw 直绘,几何计入容器) */ + 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); + list_view_bind(g_dlc, r_list, RX(DCL_X, list_title_y, DFW, 26)); + yb += 158; + + g_demo_content_h = yb; +#undef SY +#undef SLOT +} + +/* demo 全页重绘:整帧按 z 序重画全部(set_page_bg 清底 + 注册表按 z 升序)。 */ +void demo_paint(void) +{ + const meow_theme *th = meow_theme_get(); + meow_ui_set_page_bg(th->bg); /* 页面底 = 主题 bg(区域/整帧重画清底用) */ + meow_ui_paint_full(); /* 整帧按 z 序重画 */ +} + +/* 重建内容到指定 scroll(clamp 到 [0, max_scroll]);进入/滚动共用 */ +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(); +} + +/* ---- demo 交互 ---- */ + +/* 局部刷新统一走 SDK meow_ui_invalidate(清底 + 按 z 重画相交绘制物 + 上屏)。 */ +static void d_toggle_sel(meow_widget *w, rect_t r) +{ + if (!w) return; + meow_widget_set_selected(w, !meow_widget_selected(w)); + meow_ui_invalidate(r.x, r.y, r.w, r.h); +} + +/* Tap:落点在内容控件上,返回 1=已消费 */ +static int demo_content_tap(int x, int y) +{ + g_demo_focus = 0; /* 默认切回页滚动;仅命中列表时改置 1 */ + + /* 嵌套命中(子>父,§A.7 设计):列表区 CONTAINER 子视图先发,命中即消费。 */ + if (list_view_hit(x, y)) return 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; meow_ui_invalidate(r_dr1.x, r_dr1.y, r_dr1.w, r_dr1.h); meow_ui_invalidate(r_dr2.x, r_dr2.y, r_dr2.w, r_dr2.h); 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; meow_ui_invalidate(r_dr1.x, r_dr1.y, r_dr1.w, r_dr1.h); meow_ui_invalidate(r_dr2.x, r_dr2.y, r_dr2.w, r_dr2.h); 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); meow_ui_invalidate(r_dsw.x, r_dsw.y, r_dsw.w, r_dsw.h); return 1; } + if (meow_widget_hit(g_dslider, x, y)) { demo_slider_drag(x); 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; + meow_ui_invalidate(r_dtf.x, r_dtf.y, r_dtf.w, r_dtf.h); + return 1; + } + if (meow_widget_hit(g_dtf_py, x, y)) { + meow_textfield_activate(g_dtf_py); /* 拼音 IME */ + strncpy(g_ds.tf_py, meow_textfield_text(g_dtf_py), sizeof g_ds.tf_py - 1); + g_ds.tf_py[sizeof g_ds.tf_py - 1] = 0; + meow_ui_invalidate(r_dtf_py.x, r_dtf_py.y, r_dtf_py.w, r_dtf_py.h); + 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); meow_ui_invalidate(r_tab.x, r_tab.y, r_tab.w, r_tab.h); 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)); meow_ui_invalidate(r_dlg.x, r_dlg.y, r_dlg.w, r_dlg.h); 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); meow_ui_invalidate(r_sheet.x, r_sheet.y, r_sheet.w, r_sheet.h); return 1; } + if (meow_widget_hit(g_dbtn_media, x, y)) { demo_media_send(); meow_ui_invalidate(r_media.x, r_media.y, r_media.w, r_media.h); return 1; } + return 0; /* 列表区分发已由顶部 list_view_hit 处理(子>父) */ +} + +/* ② 经 SDK 媒体会话封装上报一条「播放中」元数据(best-effort:无 launcher 静默降级)。 + * 上报后 launcher(通知中心媒体卡片/主界面 Now Playing 页)可显示;demo 在 on_playctl 回调 + * 打日志验证 receive 侧。 */ +static void demo_media_send(void) +{ + meow_ui_media_meta(1 /* playing */, 0, 180000, + "MeowOS Demo Track", "SDK", "meow_ui_media", + NULL, NULL); + meow_log(MEOW_LOG_INFO, "demo: media meta sent (playing, 0/180000)"); +} + +/* demo 页 on_tap 分发(chrome → 内容) */ +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) { meow_view_page_pop(); 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_set_z(g_fab, 999); /* 重建后重挂顶:新 entry 自动 z 低,须显式置顶 */ + meow_ui_invalidate(r_fab.x, r_fab.y, r_fab.w, r_fab.h); /* 重画 FAB 及其下重叠 z 栈 */ + return; + } + if (g_nav) { + int idx = meow_navigationbar_hit(g_nav, x, y); + if (idx >= 0) { meow_navigationbar_set_selected(g_nav, idx); meow_ui_invalidate(r_nav.x, r_nav.y, r_nav.w, r_nav.h); return; } + } + demo_content_tap(x, y); +} + +/* demo 页 on_tick:ProgressBar 动画(局部刷,~120ms 步进)。 + * 进度语义值已上收 logic/state.c(logic_prog_get/set),本处只驱动显示。 */ +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; + int p = logic_prog_get() + 3; if (p > 100) p -= 101; /* (v+3)%101,wrap 0..100 */ + logic_prog_set(p); + meow_progress_set(g_dprl, p); meow_ui_invalidate(r_prl.x, r_prl.y, r_prl.w, r_prl.h); + meow_progress_set(g_dprc, p); meow_ui_invalidate(r_prc.x, r_prc.y, r_prc.w, r_prc.h); +} + +/* 按触碰 X 落 slider 值并局部刷(按下即定位 + 拖动实时,M2.71-bug2) */ +static void demo_slider_drag(int x) +{ + 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); + meow_ui_invalidate(r_slider.x, r_slider.y, r_slider.w, r_slider.h); + meow_ui_invalidate(r_slider_txt.x, r_slider_txt.y, r_slider_txt.w, r_slider_txt.h); /* 重画值文本 */ +} + +/* demo 页 on_touch_down:锁一种拖拽(slider > list > 页内容)。 + * 列表区走 list_view_down(子>父:命中列表区由列表 CONTAINER 子视图收口)。 */ +void demo_touch_down(int x, int y) +{ + g_drag_slider = 0; g_drag_list = 0; g_drag_page = 0; g_drag_page_delta = 0; + if (!g_demo) return; + if (meow_widget_hit(g_dslider, x, y)) { g_drag_slider = 1; demo_slider_drag(x); return; } + if (list_view_down(x, y)) return; /* 列表区:置 focus+drag_list(list.c) */ + g_demo_focus = 0; + if (y >= g_demo_ct && y < g_demo_cb && x >= 0 && x < meow_ui_width()) { + g_drag_page = 1; g_drag_page_last_y = y; + } +} + +/* demo 页 on_touch_move:拖动过程实时更新(slider 值 / 列表滚动 / 页面滚动节流重建)。 */ +void demo_touch_move(int x, int y) +{ + if (!g_demo) return; + if (g_drag_slider) { demo_slider_drag(x); return; } + if (list_view_move(y)) return; /* 列表拖动(list.c) */ + if (g_drag_page) { + int dy = y - g_drag_page_last_y; g_drag_page_last_y = y; + g_drag_page_delta += -dy; /* 手指上移(y 减) → scroll 增(显示更低内容),与 on_swipe 一致 */ + if (g_drag_page_delta >= 8 || g_drag_page_delta <= -8) { + demo_rebuild_to(g_demo_scroll + g_drag_page_delta); + g_drag_page_delta = 0; + } + } +} + +/* demo 页 on_swipe:LEFT=回 home;UP/DOWN=列表聚焦滚列表,否则页滚动。 */ +void demo_swipe(int dir, int dist) +{ + if (dir == MEOW_SWIPE_LEFT) { meow_view_page_pop(); return; } + if (dir != MEOW_SWIPE_UP && dir != MEOW_SWIPE_DOWN) return; + if (g_drag_page || g_drag_list) return; /* 本次手势已实时跟手滚动过 */ + int del = (dir == MEOW_SWIPE_UP) ? dist : -dist; /* 手指上移 → 偏移增大 */ + if (list_view_swipe(del)) return; /* 列表聚焦:滚列表(list.c,子>父消费) */ + demo_rebuild_to(g_demo_scroll + del); +} + +/* ---- demo 页 view(on_enter/on_leave/on_destroy 驱动进入/离开/释放)---- */ + +static void demo_page_enter(meow_view *v, void *ctx) +{ + (void)v; + ((demo_ctrl *)ctx)->active = 1; + g_demo = 1; + g_demo_focus = 0; + hello_nav_report(); /* ① 上报 demo 栈深/顶层 */ + /* 列表区 CONTAINER 子视图随本页进入由 SDK view_enter 递归驱动(其 on_draw 在 demo_rebuild_to + * →demo_paint 中经注册表绘制;meow_draw_view_entered 含祖先已进入判定,首批可见)。 */ + demo_rebuild_to(0); + meow_log(MEOW_LOG_INFO, "demo enter scroll=0"); +} + +static void demo_page_leave(meow_view *v, void *ctx) +{ + (void)v; + ((demo_ctrl *)ctx)->active = 0; + g_demo = 0; + /* 列表区子视图由 SDK view_leave 在父之前递归离开(子<先>父,§A.4/D.1)。 */ +} + +static void demo_page_destroy(meow_view *v, void *ctx) +{ + (void)v; (void)ctx; + demo_free_content(); /* 内容控件 + CUSTOM 标题/slider值 */ + chrome_free(); /* chrome(TopAppBar / NavigationBar / FAB) */ +} + +void demo_init(void) +{ + meow_ui_activity_push(ACT_DEMO); + chrome_create(); /* chrome 位置/置顶在 chrome.c */ + demo_build_at(0); + meow_ui_activity_pop(); + g_demo_scroll = 0; +} + +int demo_is_active(void) +{ + return g_demo; +} + +/* demo view:on_create 建 chrome+内容(一次,owner=ACT_DEMO);进入时 rebuild 到顶。 + * 视图深度化:先挂列表区 CONTAINER 子视图到本 ACTIVITY view 的 children,再建内容。 + * 列表子视图 id 用非页面值,不占 g_view_by_id(见 list.c)。 */ +static void demo_page_create(meow_view *v, void *ctx) +{ + (void)v; + ((demo_ctrl *)ctx)->view = v; + meow_view *lv = list_view_create(); + if (lv) meow_view_add_child(v, lv); + demo_init(); +} + +meow_view *demo_view_create(void) +{ + static const meow_view_hooks h = { + .on_create = demo_page_create, + .on_enter = demo_page_enter, + .on_leave = demo_page_leave, + .on_destroy = demo_page_destroy, + }; + return meow_view_create(ACT_DEMO, MEOW_VIEW_ACTIVITY, &h, &g_demo_ctrl); +} diff --git a/examples/hello/ui/demo/list/list.c b/examples/hello/ui/demo/list/list.c new file mode 100644 index 0000000..220e28e --- /dev/null +++ b/examples/hello/ui/demo/list/list.c @@ -0,0 +1,166 @@ +/* ui/demo/list/list.c — 控件库 Demo 页内「列表区」CONTAINER 子视图(批次③ 视图深度化)。 + * + * 目标(SDK_VIEW_FRAMEWORK_DESIGN.md §B.4/§E③): + * 把 demo 页内的 LazyColumn 列表逻辑区块抽成一个 meow_view CONTAINER 子视图, + * 挂到 demo ACTIVITY view 的 children(meow_view_add_child),验证 + * ① 嵌套生命周期:on_enter/on_leave 顺序(父进入→子进入;子离开→父离开); + * ② 嵌套命中(子>父):点落列表区由本子视图消费,落列表区外回退父页; + * ③ on_draw 直绘:list 区块标题改由本子视图 on_draw 经注册表绘制(view→注册表→invalidate 链路)。 + * + * 职责边界(诚实): + * - 本子视图为**组织/结构节点 + 列表交互收口**:持有 list 控件引用 + 列表视图态 + * (focus/drag/scroll/sel),并对外暴露 list_view_hit/down/move/swipe 供 demo 页分发改派, + * 实现「子>父」命中。list **控件创建/释放**仍留在 demo 页内容重建流(demo_build_at/ + * demo_free_content),因为 widget 由 z 序注册表 auto 注册/注销、并随 scroll 重建——完整 + * 的「控件归容器拥有」需 SDK 侧支持(本批冻结 patch_v2/sdk/),故以绑定引用的方式折中。 + * - SDK(meow_ui_view.c)的 view_enter/view_leave **递归 children**(批次①→③ 后接线): + * 本子视图随 demo 页 on_enter/on_leave 自动进入/离开,无需 App 层显式驱动(list_view_enter/leave + * 已退役);父 on_enter 重绘时子视图经 meow_draw_view_entered(含祖先已进入)可见。 + * - on_draw 是回调、view 不缓存像素(非保留,§5.8.7);绘制由 meow_ui_paint_full/invalidate + * 触发,仅当本子视图 state 含 MEOW_VIEW_STATE_ENTERED 时注册表才绘制(meow_draw_view_entered)。 + */ +#include "hello_internal.h" + +/* ---- 本子视图(CONTAINER,非页面:id 用越界值避免占用 g_view_by_id 顶层页槽位) ---- */ +static meow_view *g_list_view = NULL; + +/* ---- 列表视图态(跨重建保留;extern 供宿主测试对真实状态断言,见 hello_internal.h)---- + * g_dlc/r_list/g_demo_focus/g_drag_list 从 demo.c 迁入本文件(语义归列表区)。 + * g_demo_focus 亦被 demo.c 的页/列表滚动分流读取,经 extern 跨 TU 共享。 */ +meow_widget *g_dlc = NULL; +rect_t r_list = { 0, 0, 0, 0 }; +int g_demo_focus = 0; /* 1=LazyColumn 聚焦(swipe 滚列表而非页面) */ +int g_drag_list = 0; /* 手指按在 LazyColumn:拖拽滚列表 */ + +static rect_t r_list_title = { 0, 0, 0, 0 }; /* list 区块标题外接框(on_draw 用) */ +static int g_list_scroll = 0; /* 跨重建保留:列表滚动偏移 */ +static int g_list_sel = 0; /* 跨重建保留:选中行 */ +static int g_drag_list_last_y = 0; /* 列表拖拽上一帧 y */ + +/* ---- on_draw:list 区块标题直绘(注册表→invalidate 链路实测,批次③)---- + * 原 demo.c 用 d_title() 把标题注册成 CUSTOM 绘制物;本表标题改由本子视图 on_draw 绘制, + * 注册表在整帧/区域重画时经 draw_one(VIEW) 调 v->hooks.on_draw(需本子视图已进入)。 */ +static void list_view_draw(meow_view *v, const meow_theme *th, void *ctx) +{ + (void)ctx; + if (!g_dlc) return; /* 列表未建成(几何未绑)时不画 */ + meow_ui_text(meow_view_screen_x(v), meow_view_screen_y(v), + "LIST (LazyColumn, tap+swipe)", 20, th->primary); +} + +static void list_page_enter(meow_view *v, void *ctx) +{ + (void)v; (void)ctx; + meow_log(MEOW_LOG_INFO, "demo list view enter"); +} +static void list_page_leave(meow_view *v, void *ctx) +{ + (void)v; (void)ctx; + meow_log(MEOW_LOG_INFO, "demo list view leave"); +} +static void list_page_destroy(meow_view *v, void *ctx) +{ + (void)v; (void)ctx; + g_dlc = NULL; + r_list = RX(0, 0, 0, 0); + r_list_title = RX(0, 0, 0, 0); + g_list_view = NULL; /* meow_view_free 递归释放前清引用,防悬垂 */ +} + +/* 建 CONTAINER 子视图(demo_page_create 调,随即 meow_view_add_child 到 demo ACTIVITY view)。 + * 进入/离开经 SDK view_enter/view_leave 递归驱动(随 demo 页),本文件不再显式接管。 */ +meow_view *list_view_create(void) +{ + static const meow_view_hooks h = { + .on_enter = list_page_enter, + .on_leave = list_page_leave, + .on_destroy = list_page_destroy, + .on_draw = list_view_draw, + }; + /* id 用非页面值:容器不占 g_view_by_id(那是 page_push/pop 顶层页查找表)。 */ + g_list_view = meow_view_create(0x1000, MEOW_VIEW_CONTAINER, &h, NULL); + return g_list_view; +} +meow_view *list_view_get(void) +{ + return g_list_view; +} + +/* 内容重建后绑定:list 控件 + 几何(list 区 + 标题条),并恢复/应用跨重建的 scroll/select。 + * demo_build_at 在创建 g_dlc 后调本函数,把标题条与列表区几何写进容器 view(on_draw/命中用)。 */ +void list_view_bind(meow_widget *w, rect_t list, rect_t title) +{ + g_dlc = w; + r_list = list; + r_list_title = title; + if (g_list_view) { + g_list_view->x = title.x; + g_list_view->y = title.y; + g_list_view->w = title.w; + g_list_view->h = title.h + list.h; /* 标题条 + 列表区 */ + } + if (g_dlc) { + meow_list_set_scroll(g_dlc, g_list_scroll); + meow_list_set_selected(g_dlc, g_list_sel); + } +} + +/* 内容释放后清引用(demo_free_content 调;list 控件已由 demo 释放,本处只摘引用)。 */ +void list_view_release(void) +{ + g_dlc = NULL; + r_list = RX(0, 0, 0, 0); + r_list_title = RX(0, 0, 0, 0); + if (g_list_view) g_list_view->x = g_list_view->y = g_list_view->w = g_list_view->h = 0; +} + +/* 点是否落列表区(嵌套命中断言/分发改派用)。 */ +int list_view_contains(int x, int y) +{ + return g_dlc && meow_widget_hit(g_dlc, x, y); +} + +/* Tap 分发给列表区(嵌套命中:demo_content_tap 先查本子视图,命中即消费——子>父)。 + * 命中行→置选中并局部刷;返回 1=已消费。 */ +int list_view_hit(int x, int y) +{ + if (!list_view_contains(x, y)) return 0; + g_demo_focus = 1; + int li = meow_list_tap_at(g_dlc, x, y); + if (li >= 0) { g_list_sel = li; meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h); } + return 1; +} + +/* Touch Down 命中列表:置列表聚焦 + 拖拽锁;返回 1=已消费(demo_touch_down 改派)。 */ +int list_view_down(int x, int y) +{ + if (!list_view_contains(x, y)) return 0; + g_demo_focus = 1; + g_drag_list = 1; + g_drag_list_last_y = y; + return 1; +} + +/* Touch Move 拖动列表:跟随滚动;已有 drag 锁(由 down 设立);返回 1=在列表拖动。 */ +int list_view_move(int y) +{ + if (!g_drag_list) return 0; + int dy = y - g_drag_list_last_y; + g_drag_list_last_y = y; + if (dy) { + meow_list_scroll_by(g_dlc, -dy); + g_list_scroll = meow_list_scroll(g_dlc); + meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h); + } + return 1; +} + +/* Swipe:列表聚焦时滚列表(返回 1=已消费;否则 demo 页滚页面)。del=方向加权位移。 */ +int list_view_swipe(int del) +{ + if (!(g_demo_focus && g_dlc)) return 0; + meow_list_scroll_by(g_dlc, del); + g_list_scroll = meow_list_scroll(g_dlc); + meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h); + return 1; +} diff --git a/examples/hello/ui/full.c b/examples/hello/ui/full.c new file mode 100644 index 0000000..eca2b6e --- /dev/null +++ b/examples/hello/ui/full.c @@ -0,0 +1,57 @@ +/* ui/full.c — 全屏页(ACT_FULL)view + 控制器。 + * + * 无控件:进入时声明全屏+状态栏收合,绘制 accent 底 + 三行文本;点任意处/左缘左滑回 home。 + * 页面切换由 meow_view_page_push/pop 驱动 on_enter/on_leave(activity_push/pop 在 page_push/pop 内)。 + * 注:on_leave 里 activity_active() 仍为 ACT_FULL(activity_pop 在其后),故 OFF 日志的 activity + * id 与旧 set_fullscreen(0) 略异——纯诊断日志差异,功能无变化(见报告)。 + */ +#include "hello_internal.h" + +static full_ctrl g_full_ctrl; +static int g_full = 0; /* 1=处于全屏视图 */ + +void full_draw(void) +{ + const meow_theme *th = meow_theme_get(); + 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(); +} + +static void full_page_enter(meow_view *v, void *ctx) +{ + (void)v; + ((full_ctrl *)ctx)->active = 1; + g_full = 1; + meow_ui_set_fullscreen(1); + meow_ui_statusbar_set_collapsed(1); + hello_nav_report(); /* ① 上报 full 栈深/顶层 */ + full_draw(); + meow_log(MEOW_LOG_INFO, "fullscreen ON (collapsed), activity->%d", meow_ui_activity_active()); +} + +static void full_page_leave(meow_view *v, void *ctx) +{ + (void)v; + ((full_ctrl *)ctx)->active = 0; + g_full = 0; + meow_ui_set_fullscreen(0); + meow_ui_statusbar_set_collapsed(0); + meow_log(MEOW_LOG_INFO, "fullscreen OFF (restore), activity->%d", meow_ui_activity_active()); +} + +int full_is_active(void) +{ + return g_full; +} + +meow_view *full_view_create(void) +{ + static const meow_view_hooks h = { + .on_enter = full_page_enter, + .on_leave = full_page_leave, + }; + return meow_view_create(ACT_FULL, MEOW_VIEW_ACTIVITY, &h, &g_full_ctrl); +} diff --git a/examples/hello/ui/home/home.c b/examples/hello/ui/home/home.c new file mode 100644 index 0000000..e743ed6 --- /dev/null +++ b/examples/hello/ui/home/home.c @@ -0,0 +1,120 @@ +/* ui/home/home.c — 主页(ACT_HOME)view + 控制器(批次③ 目录嵌套后)。 + * + * 负责 home 控件(TAP ME / 6 通知行 / CANCEL LAST / FULLSCREEN / WIDGETS DEMO)+ 手绘(非注册表 + * 路径,batch① 仅 demo 迁注册表,home 保持命令式 draw+flush)+ 命中分发 + 业务监听(logic 推送)。 + * UI→业务只经 logic_taps_bump()/notif_send()/notif_cancel();业务变化经 logic_set_listener 回调 + * home_state_listener 刷新(契约 ①②)。导航到 demo/full 经 meow_view_page_push(单一入口)。 + */ +#include "hello_internal.h" +#include +#include + +static home_ctrl g_home_ctrl; + +/* ---- 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 const char *g_notif_labels[6] = { "INFO", "MEDIA", "HIGH", "PROG", "ONGO", "TGT" }; +static char g_btn_label[32] = "TAP ME"; + +void home_redraw(void) +{ + const meow_theme *th = meow_theme_get(); + int sb = meow_ui_statusbar_height(); + 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", logic_taps_get(), logic_secs_get()); + meow_ui_text_center(240, 516, line, 20, th->dim); + meow_ui_text(20, 660, "left-swipe = exit", 20, th->dim); + meow_ui_flush(); +} + +/* 业务→UI 监听器:taps 变化 → 重建按钮 label;secs 变化 → 重绘(仅本页在前台时,否则只改业务 + * 不覆写全屏帧——例如全屏态下 logic_secs_add 仍触发,但本页 active=0 跳过 redraw)。 */ +static void home_state_listener(int ev, void *ctx) +{ + home_ctrl *c = (home_ctrl *)ctx; + if (ev == LOGIC_EV_TAPS) { + char lb[32]; + snprintf(lb, sizeof lb, "TAPPED x%d", logic_taps_get()); + meow_widget_free(g_btn); + g_btn = meow_btn_create(120, 300, 240, 64, lb); + } + if (!c->active) return; /* 本页不在前台(全屏):不覆写当前帧 */ + home_redraw(); +} + +int home_on_tap(int x, int y) +{ + if (meow_widget_hit(g_demo_btn, x, y)) { meow_view_page_push(ACT_DEMO); return 1; } + for (int i = 0; i < 6; i++) { + if (meow_widget_hit(g_nbtn[i], x, y)) { notif_send(i); home_redraw(); return 1; } + } + if (meow_widget_hit(g_cancel, x, y)) { notif_cancel(); home_redraw(); return 1; } + if (meow_widget_hit(g_fs_btn, x, y)) { meow_view_page_push(ACT_FULL); return 1; } + if (meow_widget_hit(g_btn, x, y)) { logic_taps_bump(); return 1; } + return 0; +} + +static void home_page_create(meow_view *v, void *ctx) +{ + (void)v; + ((home_ctrl *)ctx)->view = v; + 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"); + /* 注册业务→UI 推送监听器(biz-UI 绑定点 = 本页控制器 ctx)。 */ + logic_set_listener(home_state_listener, ctx); +} + +static void home_page_enter(meow_view *v, void *ctx) +{ + (void)v; + ((home_ctrl *)ctx)->active = 1; + hello_nav_report(); /* ① 上报 home 栈深/顶层(pop 回 home / 首次进入) */ + home_redraw(); +} + +static void home_page_leave(meow_view *v, void *ctx) +{ + (void)v; + ((home_ctrl *)ctx)->active = 0; /* 默认不销毁子控件;弹回时 on_enter 重绘恢复 */ +} + +static void home_page_destroy(meow_view *v, void *ctx) +{ + (void)v; (void)ctx; + 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); + g_btn = g_cancel = g_fs_btn = g_demo_btn = NULL; + for (int i = 0; i < 6; i++) g_nbtn[i] = NULL; +} + +meow_view *home_view_create(void) +{ + static const meow_view_hooks h = { + .on_create = home_page_create, + .on_enter = home_page_enter, + .on_leave = home_page_leave, + .on_destroy = home_page_destroy, + }; + return meow_view_create(ACT_HOME, MEOW_VIEW_ACTIVITY, &h, &g_home_ctrl); +}