From a8c51be1fc072b3649156a9a3378df06341fb086 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Wed, 2 Sep 2026 16:50:40 +0800 Subject: [PATCH] =?UTF-8?q?sdk(M7.0a):=20=E8=A7=86=E5=9B=BE=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E6=8E=A5=E7=BA=BF=20+=20IconButton=20+=20=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E8=89=B2=E6=9D=BF=E5=AF=BC=E5=87=BA/=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=EF=BC=88launcher=20=E9=87=8D=E5=86=99=E5=9F=BA?= =?UTF-8?q?=E5=BA=A7=E5=89=8D=E7=BD=AE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit M7 launcher 整体 MD3 重写的 SDK 基座补全(只动 patch_v2/sdk/): - 缺口#7 meow_view 事件接线:meow_view_dispatch_event 接入 meow_ui_run 主循环 (§6.4 分流之上,TAP/KEY/SWIPE/LONG_PRESS 先经 view 路由、命中即消费、未命中回落 on_* —— 无 view 树/未命中完全向后兼容);meow_view_add_child 在父已进入时挂子视图 立即进入(递归 children 修正);meow_view_sub_push/pop 标注废弃(设计定案 b 不做页内 子导航栈)。 - 缺口#2 IconButton:meow_iconbtn_create —— sb_icons 位图 + 圆形命中 + 按压/按住态 (实时,同 meow_btn)+ owner/enabled 闸门;电源菜单/播放控制/锁屏通用件。 - 缺口#6 SDK 半主题收口:meow_theme_export/meow_theme_inject(§8.1.5 色板导出/注入), launcher s_ui_theme() 将据此变读 SDK 的薄壳(launcher 侧改动不在此批)。 - 文档:MEOW_APP_SDK.md 补 meow_iconbtn_create + meow_theme_export/inject 说明。 - 验证:新增 test_meow_ui_iconbtn / test_meow_ui_theme_export / test_meow_ui_view_event 全 PASS;全量 SDK 宿主回归(widgets/dispatch/box/anim/scroll/listvirt/str/ime/pinyin/ overlay/notif/input_keys/p1p2p3/umbrella/ft)全 PASS;交叉 -Wall -Wextra 0 warning。 meow_ui.a md5: be24214e24faadba63577f3a98c2c9d1 -> 7fc83f7731cadb2f91df1c3ed7d3814f --- MEOW_APP_SDK.md | 39 ++++++ patch_v2/sdk/include/meow_ui_theme.h | 22 ++++ patch_v2/sdk/include/meow_ui_widgets.h | 11 ++ patch_v2/sdk/meow_ui_input.c | 12 ++ patch_v2/sdk/meow_ui_theme.c | 27 ++++ patch_v2/sdk/meow_ui_view.c | 12 +- patch_v2/sdk/meow_ui_view.h | 13 +- patch_v2/sdk/meow_ui_widget.c | 71 ++++++++++- patch_v2/sdk/test_meow_ui_iconbtn.c | 121 ++++++++++++++++++ patch_v2/sdk/test_meow_ui_theme_export.c | 122 ++++++++++++++++++ patch_v2/sdk/test_meow_ui_view_event.c | 152 +++++++++++++++++++++++ patch_v2/sdk/test_umbrella.c | 4 +- 12 files changed, 595 insertions(+), 11 deletions(-) create mode 100644 patch_v2/sdk/test_meow_ui_iconbtn.c create mode 100644 patch_v2/sdk/test_meow_ui_theme_export.c create mode 100644 patch_v2/sdk/test_meow_ui_view_event.c diff --git a/MEOW_APP_SDK.md b/MEOW_APP_SDK.md index baf7523..0abfb13 100644 --- a/MEOW_APP_SDK.md +++ b/MEOW_APP_SDK.md @@ -765,8 +765,25 @@ meow_widget *meow_layout_spacer(int w, int h); | BottomSheet | 底部面板(复用 §5.7 B④ 面板) | ✅ 第二批 | | Dialog | 弹窗(复用 §5.6 B③ 弹窗) | ✅ 第二批 | | FloatingActionButton | 悬浮操作钮(圆角增强;无投影,仅描边/状态层近似) | ✅ 第二批 | +| IconButton | 圆形/位图图标按钮(圆形命中 + 按压态;电源菜单/播放控制/锁屏通用) | 🆕 M7.0a | | 列表容器 | 可滚动列表(复用 §5.5 A② listview) | ✅ 第二批 | +> **IconButton(M7.0a,`meow_iconbtn_create`)**:把「位图图标 + 圆形命中 + 按压态」收敛为单一组件, +> 供电源菜单圆钮 / Now Playing 播放控制 / 锁屏常用件等「图标圆钮」使用(launcher M7 控件化缺口 #2)。 +> +> ```c +> meow_widget *meow_iconbtn_create(int x, int y, int size, int icon_idx); +> ``` +> - `x,y,size`:按钮外接正方形(`w=h=size`,圆心 = `(x+size/2, y+size/2)`,半径 = `size/2`)。 +> - `icon_idx`:`sb_icons[]` 位图下标(用 `MEOW_ICON_*` 或 `SB_I_*`;`<0` 画空位图、命中照常)。 +> - **命中**:圆形(`meow_widget_hit` 走半径判定,角部按下不点亮);`owner==active` + `enabled` 双闸门。 +> - **按压/按住态**:与 `meow_btn` 同源三路(状态机 `MEOW_WS_PRESSED` / 实时触摸命中 / `w->pressed`), +> 经 `meow_widget_touch_refresh` 即时重绘(按压叠 `onSurface` 12%、按住 24%,禁用向 `onSurface` 38% 变淡)。 +> - **语义色**:底 `surfaceContainerLow`、图标 `onSurface`;选中(`meow_widget_set_selected`)→ +> `primaryContainer`/`onPrimaryContainer`;焦点用圆形描边(`ring_arc`)。 +> - **图标绘制**:与状态栏/TopAppBar 同源位图(`meow_gfx_blit_icon_ink`,Material Symbols Rounded 单通道 alpha), +> ink 高 = `size*6/10`(MD3 24/40 图标比),垂直居中于圆心,只写像素不标顶栏脏区(控件 `clip` 管界)。 + > **说明**:多数 Material 组件是**既有设计模板(§5.2-§5.7)的「MD3 观感化」**——如 Card=圆角容器、BottomSheet 复用 §5.7 面板、Dialog 复用 §5.6 弹窗、TabBar 联动 §5.2 分页、列表容器复用 §5.5 listview。故 Material 子集**不重复造结构**,只把这些模板的绘制观感对齐 MD3 语义色 + 统一基座。**第二批已实现(M2.71/0.7.66,见 §5.8.8)**。 > **TopAppBar 位图动作图标(M2.9x,追加式)**:TopAppBar 动作项默认是**文本**(`meow_topappbar_add_action`)。为支持「⚙↻⌕」类**动作图标**(4k 字库无这些码点,字体码点画图标会出豆腐),新增把某动作项改为**位图图标**的接口: @@ -1160,6 +1177,27 @@ int meow_ui_style(void); /* 当前 style(默认 MEOW_STYLE_MD3) const meow_theme *meow_theme_get(void); ``` +> **色板导出/注入(§8.1.5,M7.0a 主题收口前置)**:M7 用户定案「**SDK `meow_theme` 为唯一权威**」—— +> launcher 侧 `s_ui_theme()`(`font_theme.c` 4 套静态 `meow_ui_theme_t`)将改为读 SDK 的**薄壳**,不再自持色板。 +> SDK 侧为此前置提供「导出 / 注入」一对接口(launcher 薄壳改造不在本批): +> +> ```c +> /* 导出:把当前「style×mode」解析生效的完整权威主题拷给调用方(单一权威来源)。 */ +> void meow_theme_export(meow_theme *out); +> /* 注入:用一份自定义板覆写当前生效主题;NULL 恢复内建(随 settings 解析)。 */ +> void meow_theme_inject(const meow_theme *src); +> ``` +> - **launcher 薄壳配方**:`s_ui_theme()` 调 `meow_theme_export(&th)`,再按角色字段映射到自己渲染用的 +> `meow_ui_theme_t`(9 色)——`surface→surface`、`surfaceVariant→surface_variant`、 +> `surfaceContainer→surface_container`、`primary→primary`、`onSurface→on_surface`、 +> `onSurfaceVariant→on_surface_variant`、`outline→outline`、`error→error`、`surfaceTint→surface_tint`。 +> 全部色值取自 SDK,**单一权威**;launcher 自持的 4 套静态色板退役。 +> - **注入**:供需要程序化覆写的调用方(如 launcher 自定义背景/强调、或 demo 预览另一调色板); +> 覆写**不随** `meow_theme_reload()` 丢弃(程序化覆写优先于 settings,与 `meow_ui_set_style` 语义一致); +> `inject(NULL)` 恢复内建。 +> - **一致性**:导出/注入/`meow_theme_get()` 三者同源(导出=拷贝 `meow_theme_get()` 结果;注入接管 +> `meow_theme_get()` 返回),保证「一处权威、处处同值」。 + - **设置页联动**:设置页 `ui.style` → `meow_ui_set_style` → 框架**全局刷新**(状态栏 SDK 全局 / 未声明背景的 App / launcher 主页,§8.1.4)。App 在 `on_tick` 据通知重绘(`meow_ui_flush_region`,沿用 `meow_theme_listener` 机制)。 - **明暗联动**:`display.theme` / `display.theme_mode`(auto)变化仍走现有 `meow_theme_mode` + `meow_theme_listener`(§8 现状),仅「同一 style 下」的浅/深背景随切换自动换。 - **命名对齐(与 `meow_ui.h` / launcher 现状)**:SDK **App 侧**结构体权威名 = **`meow_theme`**(既有 `meow_theme_get()`),**不是** `meow_ui_theme`;launcher 内部另有独立结构体 `meow_ui_theme_t`(`font_theme.c`,仅供状态栏/设置页,**与 App 侧无关、不暴露**)。故本节接口统一用 `meow_theme` / `meow_theme_get`;若实现期另起 `meow_ui_theme()`/`meow_ui_theme_t` 作同义别名亦可,视为与 `meow_theme` 等价(待定案)。 @@ -1505,6 +1543,7 @@ locale 权威值 = 板载 `settings.conf` 的 `ui.locale`(默认 `zh_cn`)。 - [D3] 新增内部 `meow_gfx_fill_round/blit_rgba/mark_dirty`(控件/图片用,非公共 API)。 - [D4] 新增 `meow_theme_defaults(mode)`(脱机取内置色板)。 - [D5] 新增 `on_swipe_progress` 回调 + `meow_ui_swipe_hint(x)`(左缘返回手势视觉条)+ 内部 `MEOW_EV_SWIPE_PROGRESS` / `meow_input_inject`(宿主验证用)。 +- [D6](M7.0a)新增 `meow_iconbtn_create`(圆形/位图图标按钮,§5.8.6)+ `meow_theme_export`/`meow_theme_inject`(§8.1.5 色板导出/注入);`meow_view_dispatch_event` 接入 `meow_ui_run` 主循环(§6.4 之上,TAP/KEY/SWIPE/LONG_PRESS 先经 view 路由、命中即消费、未命中回落 `on_*`);`meow_view` 的 enter/leave 递归 children 已实现 + `meow_view_add_child` 在父进入后挂子立即进入;`meow_view_sub_push/pop` 标注为废弃(设计定案 b 不做页内子导航栈)。 - `meow_ui.h` 为对外权威签名;本文件 §3-§8 作为设计蓝本。 --- diff --git a/patch_v2/sdk/include/meow_ui_theme.h b/patch_v2/sdk/include/meow_ui_theme.h index ebc80b0..849d9d0 100644 --- a/patch_v2/sdk/include/meow_ui_theme.h +++ b/patch_v2/sdk/include/meow_ui_theme.h @@ -62,6 +62,28 @@ void meow_theme_remove_listener(meow_theme_listener cb); /* [D4] 脱机访问两套内置色板(无 settings 文件也可靠):返回当前解析生效的色板副本。 */ meow_theme meow_theme_defaults(meow_theme_mode m); +/* ---- §8.1.5 色板导出/注入(单一权威桥,M7.0a)---- + * 用户在 M7 定案「SDK meow_theme 为唯一权威」:launcher 的 s_ui_theme() 将改为读 SDK 的薄壳, + * 不再自持 4 套静态色板。本接口是那个前置(launcher 侧改动不在此批)—— + * + * meow_theme_export(out) 把当前「style×mode」解析生效的完整权威主题拷给调用方。 + * 调用方是 stateless 的绘制原语时(如 launcher s_ui_theme),由此读 + * 单一权威,再按它的角色字段(9 色)映射到自己的绘制便利结构,即: + * surface → surface + * surfaceVariant → surface_variant + * surfaceContainer → surface_container + * primary → primary + * onSurface → on_surface + * onSurfaceVariant → on_surface_variant + * outline → outline + * error → error + * surfaceTint → surface_tint + * meow_theme_inject(src) 用一份自定义板覆写当前生效主题(如 launcher 自定义背景/强调/风格), + * src==NULL 恢复内建(随 settings 解析)。覆写不随 meow_theme_reload() 丢弃 + * (程序化覆写优先于 settings,与 meow_ui_set_style 语义一致)。 */ +void meow_theme_export(meow_theme *out); +void meow_theme_inject(const meow_theme *src); + /* ---- §8.1 顶级主题风格族(minimal,2026-08-30)---- * 主题 = 顶级主题风格族(ui.style:MD3/MIUIx)× 明暗(display.theme)。本批只落「状态栏图标集 * 随 ui.style 换集」的最小闭环:meow_ui_set_style 记录 style(同值忽略)+ 触发 theme_reload/ diff --git a/patch_v2/sdk/include/meow_ui_widgets.h b/patch_v2/sdk/include/meow_ui_widgets.h index c43824b..93136b1 100644 --- a/patch_v2/sdk/include/meow_ui_widgets.h +++ b/patch_v2/sdk/include/meow_ui_widgets.h @@ -210,6 +210,17 @@ enum { MEOW_ICON_HEART = 29, }; +/* ---- IconButton(圆形/位图图标按钮,M7.0a 缺口#2)---- + * 圆形命中 + 位图图标(sb_icons[],Material Symbols Rounded 单通道 alpha)+ 按压态(实时)。 + * 用途:电源菜单圆钮 / Now Playing 播放控制 / 锁屏常用件等「位图图标 + 圆形」按钮。 + * icon_idx = sb_icons[] 下标(用上面 MEOW_ICON_* 枚举或 SB_I_*;越界画空位图,命中照常)。 + * size = 按钮直径(外接正方形 w=h=size;图标 ink 高 = size*6/10,MD3 图标比)。 + * 语义色:底 surfaceContainerLow、图标 onSurface;按压叠 onSurface 状态层(实时,同 meow_btn); + * 禁用变淡;选中态 primaryContainer/onPrimaryContainer(meow_widget_set_selected)。 + * 命中:圆形(半径=size/2,圆心=按钮中心),owner==active + enabled 双闸门。 + * 返回按钮句柄;App 在 on_tap 里用 meow_widget_hit 判定是否命中本钮(命中即触发动作)。 */ +meow_widget *meow_iconbtn_create(int x, int y, int size, int icon_idx); + /* ---- NavigationBar(底部导航栏)---- */ meow_widget *meow_navigationbar_create(int x, int y, int w, int h); int meow_navigationbar_add_item(meow_widget *w, const char *label); diff --git a/patch_v2/sdk/meow_ui_input.c b/patch_v2/sdk/meow_ui_input.c index 5df331f..8863236 100644 --- a/patch_v2/sdk/meow_ui_input.c +++ b/patch_v2/sdk/meow_ui_input.c @@ -26,6 +26,7 @@ #include "meow_ui_sys.h" #include "meow_ui_vol.h" #include "meow_ui_media.h" +#include "meow_ui_view.h" /* M7.0a 缺口#7:把 meow_view_dispatch_event 接入 meow_ui_run */ #include #include @@ -714,8 +715,14 @@ void meow_ui_run(const meow_ui_events *ev) /* §6.1 触摸分流:③ 系统全局(IME/状态栏…已在上面消费) → ② App 全局控件 * (命中返回 1=消费,否则落到 ① active Activity 页面控件)。 */ if (meow_ui_global_hit(e.a, e.b)) continue; + /* M7.0a 缺口#7:meow_view 事件路由(§A.7)——有活动页 view 子树且其 on_event + * 命中即消费(不再落 App on_tap);无 view 树/未命中返回 0 → 回落 on_tap 原路径。 */ + if (meow_view_dispatch_event(MEOW_EV_TAP, e.a, e.b)) continue; if (g_events_set && g_events.on_tap) g_events.on_tap(e.a, e.b); } else if (e.type == MEOW_EV_KEY) { + /* M7.0a 缺口#7:先进 view 路由,命中即消费;否则回落 App on_key(语义不变—— + * 音量/媒体系统键已在输入线程消化,这里只决定 App/页面是否收到语义事件)。 */ + if (meow_view_dispatch_event(MEOW_EV_KEY, e.a, e.b)) continue; if (g_events_set && g_events.on_key) g_events.on_key(e.a, e.b); } else if (e.type == MEOW_EV_SWIPE) { if (in_startup) continue; /* 启动残留被当左缘滑动:丢弃,防进入即返回 */ @@ -725,6 +732,9 @@ void meow_ui_run(const meow_ui_events *ev) /* 拼音 IME 候选栏横滑翻页(英文模式 handle_event 返回 0 → 沿用既有行为)。 */ if (meow_ime_active() && meow_ime_handle_event(&e)) continue; meow_ui_gesture_end(); /* §6.4:抬手(滑动)边沿,释放手势捕获 */ + /* M7.0a 缺口#7:view 路由先于 App 窗口的 on_swipe 与默认返回手势——页 view + * 可消费滑动(页内返回/翻页/横幅);命中即跳过下面对 on_swipe/默认退出的分发。 */ + if (meow_view_dispatch_event(MEOW_EV_SWIPE, e.a, e.b)) continue; /* A-0 全局左缘返回(方案A): * - APP 注册 on_swipe → APP 接管,自行决定(meow_ui_request_exit / 页内返回); * - APP 未注册 on_swipe 且非全屏(sb_active)→ SDK 默认退出 app(返回=退出); @@ -783,6 +793,8 @@ void meow_ui_run(const meow_ui_events *ev) /* 拼音 IME 打开:长按退格清空字段(英文模式 handle_event 返回 0 → 走既有长按回调)。 */ if (meow_ime_active() && meow_ime_handle_event(&e)) continue; meow_ui_gesture_end(); /* §6.4:长按已触发,其抬手不再 TAP/SWIPE——顺手释放捕获 */ + /* M7.0a 缺口#7:view 路由先于 §6.3 通用长按回调(页 view 可消费长按)。 */ + if (meow_view_dispatch_event(MEOW_EV_LONG_PRESS, e.a, e.b)) continue; if (g_long_press_cb) g_long_press_cb(e.a, e.b); } } diff --git a/patch_v2/sdk/meow_ui_theme.c b/patch_v2/sdk/meow_ui_theme.c index 8ad3a35..b2a727a 100644 --- a/patch_v2/sdk/meow_ui_theme.c +++ b/patch_v2/sdk/meow_ui_theme.c @@ -37,6 +37,12 @@ static char g_background[64] = ""; /* display.background(black static meow_theme_listener g_listeners[8]; static int g_nlisteners = 0; +/* §8.1.5 色板注入:一份自定义板覆写当前生效主题(src==NULL 恢复内建)。 + * 覆写优先于 settings 解析(随 meow_theme_reload() 重读 settings 后仍生效,与 meow_ui_set_style + * 的「程序化覆写不清 settings」语义一致)。 */ +static meow_theme g_inject; +static int g_inject_set = 0; + /* ---- 读 /usr/data/meow/settings.conf 的 key=value(兼容 "key = value" 带空格形式)---- * 与 launcher meow_settings_import 同款解析:按 '=' 切分,key/value 各自去首尾空白, * 精确匹配 key(避免 display.theme 误匹配 display.theme_mode)。返回 0=命中,-1=未命中。 */ @@ -295,6 +301,7 @@ static void broadcast(void) /* 当前「style×mode」主题:style=0/1(MD3/MIUIX),dark=0(light)/1(dark)。 */ static const meow_theme *theme_current(int style, int dark) { + if (g_inject_set) return &g_inject; /* §8.1.5 注入板优先 */ if (style < MEOW_STYLE_MD3) style = MEOW_STYLE_MD3; if (style > MEOW_STYLE_MIUIX) style = MEOW_STYLE_MIUIX; return &g_theme[style][dark ? 1 : 0]; @@ -339,6 +346,26 @@ meow_theme meow_theme_defaults(meow_theme_mode m) return g_theme[MEOW_STYLE_MD3][resolve_mode(m) == MEOW_THEME_DARK ? 1 : 0]; } +/* ---- §8.1.5 色板导出/注入(单一权威桥)---- + * 导出:把当前「style×mode」解析生效的完整权威主题拷给调用方(不取指针以防调用方误改内部表)。 + * 注入:用一份自定义板覆写当前生效主题;NULL 恢复内建。二者都触发 load(g_loaded==0 时先读 + * settings.conf),使宿主无 settings 文件(单测/脱机)也可靠。 */ +void meow_theme_export(meow_theme *out) +{ + if (!out) return; + const meow_theme *t = meow_theme_get(); /* 解析 g_mode(AUTO→明/暗)+ g_style + 背景覆写 */ + *out = *t; +} + +void meow_theme_inject(const meow_theme *src) +{ + if (!g_loaded) { load_settings(); g_loaded = 1; } + if (!src) { g_inject_set = 0; broadcast(); return; } + g_inject = *src; + g_inject_set = 1; + broadcast(); /* 通知 App/状态栏/控件下一帧用新注入板重绘 */ +} + /* ---- §8.1.2 顶级主题风格族(minimal)---- * meow_ui_style() 返回当前 ui.style(读取走 load_settings,随 meow_theme_reload() 重读, * 故 launcher 改 settings.conf 后经 sb_sdk_refresh→theme_reload 即跟手)。 diff --git a/patch_v2/sdk/meow_ui_view.c b/patch_v2/sdk/meow_ui_view.c index 645e677..2bbd9ab 100644 --- a/patch_v2/sdk/meow_ui_view.c +++ b/patch_v2/sdk/meow_ui_view.c @@ -156,6 +156,10 @@ int meow_view_add_child(meow_view *parent, meow_view *child) if (parent->nchildren >= MEOW_VIEW_MAX_CHILD) return -1; parent->children[parent->nchildren++] = child; child->parent = parent; + /* 递归修正(§A.4「子视图随父进出」):父已进入时挂上的子视图**立即**进入 + * (on_enter 不遗漏、参与事件分发)。页在 push/pop 时仍由 view_enter/view_leave 递归驱动, + * 此处只补「运行期动态挂子」的间隙(hello 目录嵌套实为 create 后 page_push 前 add,不受影响)。 */ + if (parent->state & MEOW_VIEW_STATE_ENTERED) view_enter(child); return 0; } @@ -218,11 +222,15 @@ void meow_view_page_replace(int id) g_top_view = next ? next : cur; } -/* ---- 页内子导航(预留扩展位 a 桩:本批不实现,仅返回占位)---- */ +/* ---- 页内子导航(预留扩展位 a,**标注废弃**)---- + * 设计定案 b(SDK_VIEW_FRAMEWORK_DESIGN.md §F.2:只做子视图容器嵌套,顶层页切仍走 activity + * 栈,不做页内子导航栈):本函数对为「将来 a」预留的桩,**不实现、不供依赖**——调用方不得 + * 认为会建立/弹出子导航。页内「子页」请用 meow_view_add_child 挂 CONTAINER 子视图(组合嵌套), + * 或以 activity 栈驱动页切换(page_push/pop/replace)。保留符号仅为未来 a 的占位。 */ int meow_view_sub_push(meow_view *v, meow_view *sub) { (void)v; (void)sub; - return 0; /* 预留:将来在 ACTIVITY view 内维护子栈 */ + return 0; /* 预留:将来在 ACTIVITY view 内维护子栈(见设计定案 b,不实现) */ } meow_view *meow_view_sub_pop(meow_view *v) { diff --git a/patch_v2/sdk/meow_ui_view.h b/patch_v2/sdk/meow_ui_view.h index 73d8a96..41988db 100644 --- a/patch_v2/sdk/meow_ui_view.h +++ b/patch_v2/sdk/meow_ui_view.h @@ -82,12 +82,17 @@ void meow_view_page_replace(int id); /* 旧top on_leave + 新页 on_en /* 入口:把语义事件注入当前活动页 view 子树路由——先发子视图(按 z 降序),命中即消费, * 再落本 view 的 on_event(其内做该 view 的 widget 命中)。返回 1=已消费(§A.7)。 - * 未接线于 meow_ui_run(App 可在 on_tap/on_swipe 等回调里改用它实现 SDK 原生路由)。 */ + * 已接线于 meow_ui_run(M7.0a):在 §6.4 分流之上,TAP/KEY/SWIPE/LONG_PRESS 先经本路由, + * 命中即消费(不再落 App 的 on_* 回调);返回 0 时回落 on_* 原路径(无 view 树 / 未命中 → + * 完全向后兼容)。App 也可在自持事件循环里直接调用本函数实现 SDK 原生路由。 */ int meow_view_dispatch_event(int ev, int a, int b); -/* ---- 页内子导航(预留扩展位 a;本批为空实现/桩,仅在 ACTIVITY view 内维护子栈)---- */ -int meow_view_sub_push(meow_view *v, meow_view *sub); /* 预留;返回 0(未实现于本批) */ -meow_view *meow_view_sub_pop(meow_view *v); /* 预留;返回 NULL(未实现于本批) */ +/* ---- 页内子导航(预留扩展位 a,**标注废弃**)---- + * 设计定案 b(§F.2):不做页内子导航栈,只做子视图容器嵌套;顶层页切仍走 activity 栈。 + * 故本组为**不实现、不供依赖**的桩(返回 0/NULL);页内子页请用 meow_view_add_child 挂 + * CONTAINER 子视图,或以 page_push/pop/replace 走 activity 栈。保留符号仅作将来 a 的占位。 */ +int meow_view_sub_push(meow_view *v, meow_view *sub); /* 废弃(不实现);返回 0 */ +meow_view *meow_view_sub_pop(meow_view *v); /* 废弃(不实现);返回 NULL */ /* ---- 坐标:view 相对父内容原点的累计(父坐标链求和 → 屏幕绝对坐标)---- */ int meow_view_screen_x(const meow_view *v); diff --git a/patch_v2/sdk/meow_ui_widget.c b/patch_v2/sdk/meow_ui_widget.c index eb727cb..86bdc35 100644 --- a/patch_v2/sdk/meow_ui_widget.c +++ b/patch_v2/sdk/meow_ui_widget.c @@ -15,7 +15,8 @@ enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3, W_TEXTFIELD = 7, W_CARD = 8, W_DIVIDER = 9, W_BADGE = 10, W_CHECKBOX = 11, W_RADIO = 12, W_PROGRESS = 13, W_TOPAPPBAR = 14, W_NAVBAR = 15, W_TABBAR = 16, W_FAB = 17, - W_LISTVIEW = 18 }; /* §5.5 结构化列表(行=图标+标题+副标题+右侧操作+选中) */ + W_LISTVIEW = 18, /* §5.5 结构化列表(行=图标+标题+副标题+右侧操作+选中) */ + W_ICONBTN = 19 }; /* M7.0a 缺口#2:圆形/位图图标按钮(圆形命中 + 按压态) */ /* 布局子项数组上限(固定尺寸子项先做;超出静默拒收)。 */ #define MEOW_LAYOUT_MAX_CHILD 32 @@ -100,6 +101,8 @@ struct meow_widget { meow_listview_tap_fn lv_tap_cb; /* 行点击回调(idx, op) */ void *lv_tap_ctx; /* 回调 ctx */ + /* W_ICONBTN 位图图标:sb_icons[] 下标(= MEOW_ICON_* 或 SB_I_*;<0 画空位图,命中照常)。 */ + int icon_id; /* §5.5 虚拟化层(W_LISTVIEW 扩展:固定 item 池 + 多行渲染器 + 完全自绘 + 空态。 * calloc 归零,安全;vs_count/vs_bind 均非 NULL 才进入 pool 模式,src2 模式不受影响。) */ meow_listview_count_fn vs_count; /* 虚拟化总数回调(NULL=src2 模式) */ @@ -225,7 +228,9 @@ static int g_live_prev_hold = 0; /* 该按钮上次渲染是否为按住 static void live_btn_register(meow_widget *w) { - if (!w || w->type != W_BTN) return; + if (!w) return; + /* M7.0a 缺口#2:IconButton 也是实时按压钮(同 meow_btn 的按压/按住即时重绘)。 */ + if (w->type != W_BTN && w->type != W_ICONBTN) return; for (int i = 0; i < g_live_btn_n; i++) if (g_live_btns[i] == w) return; if (g_live_btn_n < MEOW_LIVE_BTN_MAX) g_live_btns[g_live_btn_n++] = w; @@ -246,6 +251,15 @@ static void live_btn_unregister(meow_widget *w) /* 命中:返回坐标所在的「当前 active Activity」的最上层按钮(创建序 = 绘制序,最后者在上)。 * §6.1 ①:仅扫描 owner == 当前 active Activity 的按钮 —— 非 active Activity 的控件 * 绝不参与命中(根治 agent-119 遗留的「全屏页触摸到主页按钮位置 → 闪现 accent 底」)。 */ +/* 圆形命中(IconButton;圆心=按钮中心、半径=size/2)。 */ +static int iconbtn_circular_hit(const meow_widget *w, int x, int y) +{ + int cx = w->x + w->w / 2, cy = w->y + w->h / 2; + int r = (w->w < w->h ? w->w : w->h) / 2; + int dx = x - cx, dy = y - cy; + return (dx * dx + dy * dy) <= (r * r); +} + static meow_widget *live_btn_hit(int x, int y) { int active = meow_ui_activity_active(); @@ -253,8 +267,12 @@ static meow_widget *live_btn_hit(int x, int y) for (int i = 0; i < g_live_btn_n; i++) { meow_widget *w = g_live_btns[i]; if (w->owner != active) continue; /* 非 active activity:跳过 */ - if (x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h) + /* M7.0a 缺口#2:IconButton 按圆形命中(角部按下不点亮圆钮);其余按矩形。 */ + if (w->type == W_ICONBTN) { + if (iconbtn_circular_hit(w, x, y)) r = w; + } else if (x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h) { r = w; /* 越后创建越上:保留最后命中 */ + } } return r; } @@ -540,6 +558,20 @@ int meow_btn_set_variant(meow_widget *w, int variant) return 0; } +/* ---- §5.8.6 M7.0a 缺口#2:IconButton(圆形/位图图标按钮)---- */ +meow_widget *meow_iconbtn_create(int x, int y, int size, int icon_idx) +{ + meow_widget *t = (meow_widget *)calloc(1, sizeof *t); + if (!t) return NULL; + t->type = W_ICONBTN; t->x = x; t->y = y; t->w = size; t->h = size; + t->owner = meow_ui_activity_active(); /* §6.1 ①:同既有控件绑当前 active Activity */ + t->state = MEOW_WS_ENABLED; + t->icon_id = icon_idx; + live_btn_register(t); /* 实时按压/按住即时重绘(同 meow_btn,agent-119) */ + meow_draw_widget_register(t); /* §6.2 自动注册(创建即注册,App 零调用) */ + return t; +} + /* ---- TextField ---- */ meow_widget *meow_textfield_create(int x, int y, int w, int h, const char *placeholder, char *buf, int maxlen, meow_ime_mode mode) @@ -1374,9 +1406,11 @@ int meow_widget_hit(meow_widget *w, int x, int y) if (w->owner != meow_ui_activity_active()) return 0; /* §5.8.3:禁用不响应命中(也不进 pressed)。 */ if (widget_disabled(w)) return 0; - /* §5.8.1:自定义画布用 App 回调判定命中(可非矩形);其余矩形内含。 */ + /* §5.8.1:自定义画布用 App 回调判定命中(可非矩形);IconButton 圆形命中;其余矩形内含。 */ if (w->type == W_CUSTOM) return w->hit_cb ? w->hit_cb(w, x, y, w->ctx) : hit(w, x, y); + if (w->type == W_ICONBTN) + return iconbtn_circular_hit(w, x, y); return hit(w, x, y); } int meow_btn_pressed(meow_widget *w) @@ -1480,6 +1514,35 @@ void meow_widget_draw(meow_widget *w) if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90); break; } + case W_ICONBTN: { + /* M7.0a 缺口#2:圆形底 + 位图图标 + 按压/按住状态层(实时)。 + * 按压三路来源(同 meow_btn,agent-119):状态机 MEOW_WS_PRESSED / 实时触摸命中 / + * 位图取自 sb_icons[](meow_gfx_blit_icon_ink,只写像素不标顶栏脏区——控件矩形由 + * 本 draw 的 clip + invalidation 管理);图标墨色 onSurface,选中/按住淡向底。 */ + int live = meow_input_touch_down() && + iconbtn_circular_hit(w, meow_input_touch_x(), meow_input_touch_y()); + int press = (w->state & MEOW_WS_PRESSED) || live; + int hold = live && meow_input_touch_hold_ms() >= (unsigned)meow_ui_long_press_ms(); + int disabled = widget_disabled(w); + if (disabled) press = 0; + int cx = w->x + w->w / 2, cy = w->y + w->h / 2; + int r = (w->w < w->h ? w->w : w->h) / 2; + int inkh = (w->h * 6) / 10; /* MD3 图标比:约 24/40 → 0.6*size */ + if (inkh < 8) inkh = (w->h > 0) ? w->h : 8; + int selected = (w->state & MEOW_WS_SELECTED); + uint32_t base = selected ? th->primaryContainer : widget_bg(w, th, th->surfaceContainerLow); + uint32_t fgc = selected ? th->onPrimaryContainer : widget_fg(w, th, th->onSurface); + uint32_t bg = (hold) ? state_tint(base, th, 24) + : (press ? state_tint(base, th, 12) : base); + if (disabled) bg = state_tint(base, th, 38); /* 禁用变淡(圆形底直接变灰) */ + if (w->state & MEOW_WS_FOCUSED) bg = state_tint(base, th, 8); /* 焦点浅提亮 */ + meow_gfx_fill_circle(cx, cy, r, bg); + if (w->icon_id >= 0) + meow_gfx_blit_icon_ink(w->icon_id, cx - MEOW_SB_ICON_SIZE / 2, inkh, fgc, cy); + if (w->state & MEOW_WS_FOCUSED) + meow_gfx_ring_arc(cx, cy, r - 1, 2, 0, 360, th->primary); /* 焦点圆形描边 */ + break; + } case W_SLIDER: { /* 语义色:轨道=onSurfaceVariant(dim)、填充=primary(accent)、滑块=fg(onSurface)。 */ int trace_y = w->y + w->h / 2 - 3; diff --git a/patch_v2/sdk/test_meow_ui_iconbtn.c b/patch_v2/sdk/test_meow_ui_iconbtn.c new file mode 100644 index 0000000..6da9656 --- /dev/null +++ b/patch_v2/sdk/test_meow_ui_iconbtn.c @@ -0,0 +1,121 @@ +/* test_meow_ui_iconbtn.c — M7.0a 缺口#2 宿主验证:IconButton 圆形命中 + 按压态 + 位图图标。 + * + * 覆盖: + * I1 圆形命中:中心/边界命中,角部/外缘不命中(区别于矩形按钮); + * I2 owner==active + enabled 双闸门:非 active 或禁用不命中; + * I3 按压态视觉:设 MEOW_WS_PRESSED 后重绘,圆心像素向 onSurface 变亮(与未按不同); + * 禁用时圆心向灰变暗; + * I4 实时按压(meow_input_touch 注入):按下命中圆心 → 圆心变亮;释放恢复。 + * + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global): + * gcc -Wall -Wextra -o test_meow_ui_iconbtn test_meow_ui_iconbtn.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \ + * meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \ + * meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:./test_meow_ui_iconbtn + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include +#include +#include +#include + +static int g_fail = 0; +#define CHECK(cond, name) do { \ + if (cond) printf(" PASS %s\n", name); \ + else { printf(" FAIL %s\n", name); g_fail++; } } while (0) + +/* ---- back_buf 像素读取(断言真实渲染,不依赖目检) ---- */ +static void rd_px(int x, int y, unsigned short *p) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + if (!bb || x < 0 || y < 0) { *p = 0; return; } + if (bpp == 16) *p = *(unsigned short *)(bb + (size_t)y * line + (size_t)x * 2); + else *p = (unsigned short)(*(unsigned int *)(bb + (size_t)y * line + (size_t)x * 4) >> 16); +} +static unsigned short px565_at(int x, int y) +{ + unsigned short p; rd_px(x, y, &p); return p; +} + +int main(void) +{ + setenv("MEOW_UI_DUMP", "/tmp/iconbtn.ppm", 1); + meow_ui_opts o = { 480, 720 }; + assert(meow_ui_init(&o) == 0); + meow_ui_activity_push(7); /* owner 对齐:IconButton 绑定 active=7 */ + meow_theme_set_mode(MEOW_THEME_DARK); + + /* size=64 → 外接框 (100,100,64,64) → 圆心 (132,132) r=32 */ + meow_widget *ic = meow_iconbtn_create(100, 100, 64, MEOW_ICON_PLAY_ARROW); + assert(ic != NULL); + + /* ============ I1 圆形命中 ============ */ + printf("I1 circular hit\n"); + CHECK(meow_widget_hit(ic, 132, 132), "center hits"); + CHECK(meow_widget_hit(ic, 100, 132), "left edge (boundary) hits"); + CHECK(meow_widget_hit(ic, 132, 100), "top edge (boundary) hits"); + CHECK(!meow_widget_hit(ic, 100, 100), "corner (dist>r) MISSES (rect would hit)"); + CHECK(!meow_widget_hit(ic, 132, 165), "below center (dist 33>32) MISSES"); + + /* ============ I2 owner 闸门:切 active 后不命中 ============ */ + printf("I2 owner gate\n"); + meow_ui_activity_push(8); /* 换 active:original owner=7 ≠ active=8 */ + CHECK(!meow_widget_hit(ic, 132, 132), "non-active owner -> MISS (gate)"); + meow_ui_activity_pop(); /* 回 active=7 */ + CHECK(meow_widget_hit(ic, 132, 132), "back to active -> hits"); + /* enabled 闸门 */ + meow_widget_set_enabled(ic, 0); + CHECK(!meow_widget_hit(ic, 132, 132), "disabled -> MISS"); + meow_widget_set_enabled(ic, 1); + CHECK(meow_widget_hit(ic, 132, 132), "enabled -> hits"); + + /* ============ I3 按压态视觉(状态机位 + 禁用变暗) ============ + * 采样点选 (162,132):圆内 (r=30<32) 但超出图标 ink 右缘(约 x≤148)——取的是圆形底色, + * 而非图标墨色(圆心为图标覆盖,不适合测底色 tint)。 */ + printf("I3 pressed visual (state machine / disabled)\n"); + const meow_theme *th = meow_theme_get(); + meow_ui_clear(th->bg); + unsigned short p_bg = px565_at(100, 100); /* 空屏角部 = 页面底色 */ + meow_widget_set_state(ic, MEOW_WS_PRESSED, 0); /* 未按 */ + meow_widget_draw(ic); + unsigned short p_idle = px565_at(162, 132); + meow_widget_set_state(ic, MEOW_WS_PRESSED, 1); /* 按下 */ + meow_widget_draw(ic); + unsigned short p_press = px565_at(162, 132); + CHECK(p_press != p_idle, "pressed circle-bg pixel differs from idle (tint layer)"); + /* 禁用:向灰变暗,圆 bg 应更接近 onSurface 混合(与 idle 不同且更亮/更灰) */ + meow_widget_set_state(ic, MEOW_WS_PRESSED, 0); + meow_widget_set_enabled(ic, 0); + meow_widget_draw(ic); + unsigned short p_dis = px565_at(162, 132); + CHECK(p_dis != p_idle, "disabled circle-bg pixel differs from idle (dim)"); + meow_widget_set_enabled(ic, 1); + /* 角部(圆外)不应出现圆形底色:等于页面底色(圆形命中/填充不外溢) */ + meow_widget_draw(ic); + CHECK(px565_at(100, 100) == p_bg, "corner (outside circle) = page bg (no stray fill)"); + + /* ============ I4 实时按压(meow_input_touch 注入) ============ */ + printf("I4 live press via input touch\n"); + meow_widget_set_state(ic, MEOW_WS_PRESSED, 0); + meow_ui_clear(th->bg); + meow_widget_draw(ic); + unsigned short q_idle = px565_at(162, 132); + meow_input_touch_set(1, 132, 132, 0); /* 按在圆心 */ + meow_widget_draw(ic); + unsigned short q_live = px565_at(162, 132); + CHECK(q_live != q_idle, "live touch press -> circle bg brightens"); + meow_input_touch_set(0, 0, 0, 0); /* 释放 */ + meow_widget_draw(ic); + CHECK(px565_at(162, 132) == q_idle, "release -> circle bg restores"); + + meow_widget_free(ic); + meow_ui_activity_pop(); + meow_ui_shutdown(); + printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail); + return g_fail; +} diff --git a/patch_v2/sdk/test_meow_ui_theme_export.c b/patch_v2/sdk/test_meow_ui_theme_export.c new file mode 100644 index 0000000..6260499 --- /dev/null +++ b/patch_v2/sdk/test_meow_ui_theme_export.c @@ -0,0 +1,122 @@ +/* test_meow_ui_theme_export.c — M7.0a 主题收口前置(缺口#6 SDK 半)宿主验证: + * §8.1.5 色板导出/注入——meow_theme_export 拷当前权威主题;meow_theme_inject 覆写当前生效主题。 + * + * 覆盖: + * T1 导出一致性:export 后关键字段 == meow_theme_get()(当前 style×mode 权威板); + * T2 注入:inject 一份自定义板 → meow_theme_get()/export 都返回注入板(单一权威反映); + * 注入恢复内建:inject(NULL) 后回到目标 style×mode 的内建板; + * T3 launcher 薄壳映射契约:导出板能映射出 meow_ui_theme_t 的 9 个角色字段(surface/ + * surface_variant/surface_container/primary/on_surface/on_surface_variant/outline/error/ + * surface_tint),值与 SDK meow_theme 对应字段一致。 + * + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global): + * gcc -Wall -Wextra -o test_meow_ui_theme_export test_meow_ui_theme_export.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \ + * meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \ + * meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:./test_meow_ui_theme_export + */ +#include "meow_ui.h" +#include +#include +#include +#include + +static int g_fail = 0; +#define CHECK(cond, name) do { \ + if (cond) printf(" PASS %s\n", name); \ + else { printf(" FAIL %s\n", name); g_fail++; } } while (0) + +/* launcher meow_ui_theme_t 的 9 字段形状(测试内复刻字段名,不依赖 launcher 内部头)。 */ +typedef struct { + uint32_t surface, surface_variant, surface_container; + uint32_t primary, on_surface, on_surface_variant; + uint32_t outline, error, surface_tint; +} shell_theme; + +static shell_theme export_shell(const meow_theme *t) +{ + shell_theme s; + s.surface = t->surface; + s.surface_variant = t->surfaceVariant; + s.surface_container = t->surfaceContainer; + s.primary = t->primary; + s.on_surface = t->onSurface; + s.on_surface_variant= t->onSurfaceVariant; + s.outline = t->outline; + s.error = t->error; + s.surface_tint = t->surfaceTint; + return s; +} + +int main(void) +{ + setenv("MEOW_UI_DUMP", "/tmp/theme_export.ppm", 1); + meow_ui_opts o = { 480, 720 }; + assert(meow_ui_init(&o) == 0); + + /* ---- T1 导出一致性(MD3 dark) ---- */ + printf("T1 export consistency\n"); + meow_theme_set_mode(MEOW_THEME_DARK); + meow_ui_set_style(MEOW_STYLE_MD3); + const meow_theme *t0 = meow_theme_get(); + meow_theme e0; meow_theme_export(&e0); + CHECK(e0.surface == t0->surface, "export.surface == get.surface"); + CHECK(e0.primary == t0->primary, "export.primary == get.primary"); + CHECK(e0.surfaceVariant== t0->surfaceVariant,"export.surfaceVariant == get.surfaceVariant"); + CHECK(e0.onSurface == t0->onSurface, "export.onSurface == get.onSurface"); + CHECK(e0.radius == t0->radius, "export.radius == get.radius"); + CHECK(e0.bg == t0->bg, "export.bg == get.bg (background override applied)"); + + /* MIUIx light:确认导出随 style×mode 切换(不是固定板) */ + meow_ui_set_style(MEOW_STYLE_MIUIX); + meow_theme_set_mode(MEOW_THEME_LIGHT); + const meow_theme *t1 = meow_theme_get(); + meow_theme e1; meow_theme_export(&e1); + CHECK(e1.primary == 0xF57200 && t1->primary == 0xF57200, "MIUIx light primary exported"); + CHECK(e1.bg == t1->bg && e1.surface == t1->surface, "MIUIx light bg/surface exported"); + CHECK(e1.surface == (uint32_t)0xF7F3EE, "MIUIx light surface == warm default"); + + /* ---- T2 注入覆写 + 恢复内建 ---- */ + printf("T2 inject override + restore\n"); + meow_theme custom; memset(&custom, 0, sizeof custom); + custom.surface = 0x112233; custom.primary = 0xABCDEF; custom.onSurface = 0x0F0F0F; + custom.radius = 99; custom.radius_card = 100; + meow_theme_inject(&custom); + const meow_theme *ti = meow_theme_get(); + meow_theme ei; meow_theme_export(&ei); + CHECK(ti->primary == 0xABCDEF && ti->surface == 0x112233 && ti->radius == 99, + "inject -> get() reads injected palette"); + CHECK(ei.primary == 0xABCDEF && ei.surface == 0x112233, "inject -> export reads injected palette"); + /* meow_theme_export == meow_theme_get even when injected(单一权威) */ + CHECK(ei.onSurface == ti->onSurface && ei.radius_card == ti->radius_card, + "export == get while injected"); + /* 注入恢复内建 */ + meow_theme_inject(NULL); + const meow_theme *tr = meow_theme_get(); + CHECK(tr->primary == 0xF57200 && tr->surface == 0xF7F3EE, + "inject(NULL) restores builtin (MIUIx light)"); + + /* ---- T3 launcher 薄壳映射契约(9 角色字段) ---- */ + printf("T3 shell mapping contract\n"); + meow_theme_export(&e1); + shell_theme sh = export_shell(&e1); + CHECK(sh.surface == e1.surface, "shell.surface == surface"); + CHECK(sh.surface_variant == e1.surfaceVariant, "shell.surface_variant == surfaceVariant"); + CHECK(sh.surface_container == e1.surfaceContainer, "shell.surface_container == surfaceContainer"); + CHECK(sh.primary == e1.primary, "shell.primary == primary"); + CHECK(sh.on_surface == e1.onSurface, "shell.on_surface == onSurface"); + CHECK(sh.on_surface_variant == e1.onSurfaceVariant, "shell.on_surface_variant == onSurfaceVariant"); + CHECK(sh.outline == e1.outline, "shell.outline == outline"); + CHECK(sh.error == e1.error, "shell.error == error"); + CHECK(sh.surface_tint == e1.surfaceTint, "shell.surface_tint == surfaceTint"); + /* 关键角色字段值与 MIUIx light 内建一致(证明读的是 SDK 单一权威,而非 launcher 自持 4 套) */ + CHECK(sh.surface == 0xF7F3EE && sh.primary == 0xF57200 && sh.error == 0xB3261E, + "shell roles match SDK builtin (single authority)"); + + meow_ui_shutdown(); + printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail); + return g_fail; +} diff --git a/patch_v2/sdk/test_meow_ui_view_event.c b/patch_v2/sdk/test_meow_ui_view_event.c new file mode 100644 index 0000000..42c1a2e --- /dev/null +++ b/patch_v2/sdk/test_meow_ui_view_event.c @@ -0,0 +1,152 @@ +/* test_meow_ui_view_event.c — M7.0a 缺口#7 宿主验证:meow_view 事件接线 + 递归 enter/leave。 + * + * A 部(无 run 循环,直调 meow_view_*):递归 enter/leave 顺序 + 运行期动态挂子视图立即进入 + * (view.c meow_view_add_child 修正)+ 子>父 on_event 路由。 + * B 部(经 meow_ui_run 主循环):把语义事件注入队列 → run 循环先经 meow_view_dispatch_event + * 路由,命中的事件不再落 App on_* 回调(on_tap/on_key/on_swipe/long_press 零触发),未命中 + * 的事件照常回落 App 回调——验证「接线于 meow_ui_run、于 §6.4 分流之上」。 + * + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global): + * gcc -Wall -Wextra -o test_meow_ui_view_event test_meow_ui_view_event.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \ + * meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \ + * meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm + * 运行:./test_meow_ui_view_event + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" /* MEOW_EV_* */ +#include "meow_ui_view.h" +#include +#include +#include +#include +#include +#include + +static int g_fail = 0; +#define CHECK(cond, name) do { \ + if (cond) printf(" PASS %s\n", name); \ + else { printf(" FAIL %s\n", name); g_fail++; } } while (0) + +/* ---------- A 部:递归 enter/leave 顺序 + 运行期动态挂子 ---------- */ +static int g_trace[16]; static int g_trace_n = 0; +static void vtrace_enter(meow_view *v, void *ctx) { (void)ctx; g_trace[g_trace_n++] = v->id; } +static void vtrace_leave(meow_view *v, void *ctx) { (void)ctx; g_trace[g_trace_n++] = -v->id; } +static int g_child3_hits = 0; +static int vev_child3(meow_view *v, int ev, int a, int b, void *ctx) +{ (void)v;(void)ctx; if (ev == MEOW_EV_TAP && a == 33 && b == 33) { g_child3_hits++; return 1; } return 0; } + +/* ---------- B 部:run 循环接线 ---------- */ +static int g_tap_calls = 0, g_key_calls = 0, g_swipe_calls = 0, g_lp_calls = 0; +static void app_on_tap(int x, int y) { (void)x;(void)y; g_tap_calls++; } +static void app_on_key(int k, int d) { (void)k;(void)d; g_key_calls++; } +static void app_on_swipe(int dir, int dx, int dy) { (void)dir;(void)dx;(void)dy; g_swipe_calls++; } +static void app_lp(int x, int y) { (void)x;(void)y; g_lp_calls++; } + +/* 父 view:消费 TAP@(10,10) 与 LONG_PRESS(任何)。 */ +static int vev_parent(meow_view *v, int ev, int a, int b, void *ctx) +{ (void)v;(void)ctx; + if (ev == MEOW_EV_TAP && a == 10 && b == 10) return 1; + if (ev == MEOW_EV_LONG_PRESS) return 1; + return 0; } +/* 子 view:消费 KEY(任何)与 SWIPE_LEFT。 */ +static int vev_child(meow_view *v, int ev, int a, int b, void *ctx) +{ (void)v;(void)b;(void)ctx; + if (ev == MEOW_EV_KEY) return 1; + if (ev == MEOW_EV_SWIPE && a == MEOW_SWIPE_LEFT) return 1; + return 0; } + +static void *driver(void *arg) +{ + (void)arg; + usleep(700000); /* 等 startup cooldown(500ms) 过后再发 */ + meow_input_inject(MEOW_EV_KEY, MEOW_KEY_VOLUMEUP, 1); /* child 消费 → app_on_key 不触发 */ + usleep(80000); + meow_input_inject(MEOW_EV_TAP, 10, 10); /* parent 消费 → app_on_tap 不触发 */ + usleep(80000); + meow_input_inject(MEOW_EV_TAP, 50, 50); /* 无 view 命中 → 回落 app_on_tap */ + usleep(80000); + meow_input_inject(MEOW_EV_SWIPE, MEOW_SWIPE_LEFT, 80); /* child 消费 → app_on_swipe 不触发 */ + usleep(80000); + meow_input_inject(MEOW_EV_SWIPE, MEOW_SWIPE_RIGHT, 80); /* 无 view 命中 → 回落 app_on_swipe */ + usleep(80000); + meow_input_inject(MEOW_EV_LONG_PRESS, 10, 10); /* parent 消费 → long_press 不触发 */ + usleep(80000); + meow_ui_request_exit(); + return NULL; +} + +int main(void) +{ + setenv("MEOW_UI_DUMP", "/tmp/view_event.ppm", 1); + meow_ui_opts o = { 480, 720 }; + assert(meow_ui_init(&o) == 0); + + /* ================= A 部:递归 enter/leave + 动态挂子 ================= */ + printf("A: recursive enter/leave + runtime child add\n"); + { + static const meow_view_hooks ph = { .on_enter = vtrace_enter, .on_leave = vtrace_leave, + .on_event = vev_parent }; + static const meow_view_hooks ch = { .on_enter = vtrace_enter, .on_leave = vtrace_leave, + .on_event = vev_child }; + static const meow_view_hooks c3 = { .on_enter = vtrace_enter, .on_leave = vtrace_leave, + .on_event = vev_child3 }; + meow_view *pv = meow_view_create(1, MEOW_VIEW_ACTIVITY, &ph, NULL); + meow_view *cv = meow_view_create(2, MEOW_VIEW_CONTAINER, &ch, NULL); + assert(meow_view_add_child(pv, cv) == 0); + g_trace_n = 0; + meow_view_page_push(1); /* pv.enter → cv.enter */ + CHECK(g_trace_n == 2 && g_trace[0] == 1 && g_trace[1] == 2, "push: parent.enter -> child.enter"); + /* 运行期动态挂第三个子视图(父已 ENTERED)→ 立即进入(view.c 修正) */ + meow_view *c3v = meow_view_create(3, MEOW_VIEW_CONTAINER, &c3, NULL); + g_trace_n = 0; + assert(meow_view_add_child(pv, c3v) == 0); + CHECK(g_trace_n == 1 && g_trace[0] == 3, "runtime add-child-while-entered -> child.enter fires"); + /* 新挂子 view 参与事件路由:命中 (33,33) 被子 3 消费 */ + g_child3_hits = 0; + CHECK(meow_view_dispatch_event(MEOW_EV_TAP, 33, 33) == 1 && g_child3_hits == 1, + "runtime-added child participates in dispatch"); + /* 离开:子视图(挂载序 2 → 3)先后离开 → 父(递归离开,子->父) */ + g_trace_n = 0; + meow_view_page_pop(); + CHECK(g_trace_n == 3 && g_trace[0] == -2 && g_trace[1] == -3 && g_trace[2] == -1, + "pop: child2.leave -> child3.leave -> parent.leave"); + meow_view_free(pv); /* 递归释放 2/3 */ + printf(" PASS A: recursive order + dynamic child\n"); + } + + /* ================= B 部:meow_ui_run 接线(§6.4 之上) ================= */ + printf("B: meow_view_dispatch_event wired into meow_ui_run\n"); + { + static const meow_view_hooks ph = { .on_event = vev_parent }; + static const meow_view_hooks ch = { .on_event = vev_child }; + meow_view *pv = meow_view_create(20, MEOW_VIEW_ACTIVITY, &ph, NULL); + meow_view *cv = meow_view_create(21, MEOW_VIEW_CONTAINER, &ch, NULL); + assert(meow_view_add_child(pv, cv) == 0); + meow_view_page_push(20); /* 进入父+子(g_top_view=20) */ + + g_tap_calls = g_key_calls = g_swipe_calls = g_lp_calls = 0; + meow_ui_set_long_press_cb(app_lp); + meow_ui_events ev; memset(&ev, 0, sizeof ev); + ev.on_tap = app_on_tap; ev.on_key = app_on_key; ev.on_swipe = app_on_swipe; + + pthread_t thd; pthread_create(&thd, NULL, driver, NULL); + meow_ui_run(&ev); /* 阻塞直到 driver 里 request_exit */ + pthread_join(thd, NULL); + + CHECK(g_key_calls == 0, "KEY consumed by child -> app_on_key NOT called"); + CHECK(g_tap_calls == 1, "TAP(10,10) consumed by parent; TAP(50,50) fell through -> exactly 1 on_tap"); + CHECK(g_swipe_calls == 1, "SWIPE_LEFT consumed by child; SWIPE_RIGHT fell through -> exactly 1 on_swipe"); + CHECK(g_lp_calls == 0, "LONG_PRESS consumed by parent -> long_press cb NOT called"); + printf(" PASS B: run loop routes through views, fallback preserved\n"); + + meow_view_page_pop(); + meow_view_free(pv); + } + + meow_ui_shutdown(); + printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail); + return g_fail; +} diff --git a/patch_v2/sdk/test_umbrella.c b/patch_v2/sdk/test_umbrella.c index 9604ce4..efd8c10 100644 --- a/patch_v2/sdk/test_umbrella.c +++ b/patch_v2/sdk/test_umbrella.c @@ -58,14 +58,16 @@ static void umbrella_smoke(void) (void)&meow_ui_text_center; (void)&meow_ui_time_ms; (void)&meow_ui_effective_rate; /* 图片层(§4) */ (void)&meow_img_load_file; (void)&meow_ui_img_cover; (void)&meow_img_w; - /* 主题(§8) */ + /* 主题(§8 + §8.1.5 色板导出/注入) */ (void)&meow_theme_get; (void)&meow_theme_defaults; (void)&meow_ui_set_style; + (void)&meow_theme_export; (void)&meow_theme_inject; /* 输入法(§5.1) */ (void)&meow_ui_text_input; (void)&meow_ime_text; (void)&meow_ime_composition; /* 控件层(§5/§5.8/§5.8.6/§5.5) */ (void)&meow_btn_create; (void)&meow_widget_custom_create; (void)&meow_row_create; (void)&meow_topappbar_hit; (void)&meow_listview_create; (void)&meow_list_tap_at; (void)&meow_widget_set_opts; (void)&meow_widget_state; (void)&meow_progress_set; + (void)&meow_iconbtn_create; /* M7.0a 缺口#2 */ /* 输入层/生命周期(§6)+ 长按(§6.3) */ (void)&meow_ui_run; (void)&meow_ui_swipe_hint; (void)&meow_ui_set_long_press_cb; /* Activity 栈(§6.1) */