sdk(M7.0a): 视图事件接线 + IconButton + 主题色板导出/注入(launcher 重写基座前置)

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
This commit is contained in:
AskaEth committed 2026-09-02 16:50:40 +08:00
1 parent e67cc50afa
commit a8c51be1fc
12 files changed
+595 -11

No files matched your search

+39
View File
@@ -765,8 +765,25 @@ meow_widget *meow_layout_spacer(int w, int h);
| BottomSheet | 底部面板(复用 §5.7 B④ 面板) | ✅ 第二批 | | BottomSheet | 底部面板(复用 §5.7 B④ 面板) | ✅ 第二批 |
| Dialog | 弹窗(复用 §5.6 B③ 弹窗) | ✅ 第二批 | | Dialog | 弹窗(复用 §5.6 B③ 弹窗) | ✅ 第二批 |
| FloatingActionButton | 悬浮操作钮(圆角增强;无投影,仅描边/状态层近似) | ✅ 第二批 | | FloatingActionButton | 悬浮操作钮(圆角增强;无投影,仅描边/状态层近似) | ✅ 第二批 |
| IconButton | 圆形/位图图标按钮(圆形命中 + 按压态;电源菜单/播放控制/锁屏通用) | 🆕 M7.0a |
| 列表容器 | 可滚动列表(复用 §5.5 A② listview) | ✅ 第二批 | | 列表容器 | 可滚动列表(复用 §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)**。 > **说明**:多数 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 字库无这些码点,字体码点画图标会出豆腐),新增把某动作项改为**位图图标**的接口: > **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); 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` 机制)。 - **设置页联动**:设置页 `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 下」的浅/深背景随切换自动换。 - **明暗联动**:`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` 等价(待定案)。 - **命名对齐(与 `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)。 - [D3] 新增内部 `meow_gfx_fill_round/blit_rgba/mark_dirty`(控件/图片用,非公共 API)。
- [D4] 新增 `meow_theme_defaults(mode)`(脱机取内置色板)。 - [D4] 新增 `meow_theme_defaults(mode)`(脱机取内置色板)。
- [D5] 新增 `on_swipe_progress` 回调 + `meow_ui_swipe_hint(x)`(左缘返回手势视觉条)+ 内部 `MEOW_EV_SWIPE_PROGRESS` / `meow_input_inject`(宿主验证用)。 - [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 作为设计蓝本。 - `meow_ui.h` 为对外权威签名;本文件 §3-§8 作为设计蓝本。
--- ---
+22
View File
@@ -62,6 +62,28 @@ void meow_theme_remove_listener(meow_theme_listener cb);
/* [D4] 脱机访问两套内置色板(无 settings 文件也可靠):返回当前解析生效的色板副本。 */ /* [D4] 脱机访问两套内置色板(无 settings 文件也可靠):返回当前解析生效的色板副本。 */
meow_theme meow_theme_defaults(meow_theme_mode m); 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)---- /* ---- §8.1 顶级主题风格族(minimal,2026-08-30)----
* 主题 = 顶级主题风格族(ui.style:MD3/MIUIx)× 明暗(display.theme)。本批只落「状态栏图标集 * 主题 = 顶级主题风格族(ui.style:MD3/MIUIx)× 明暗(display.theme)。本批只落「状态栏图标集
* 随 ui.style 换集」的最小闭环:meow_ui_set_style 记录 style(同值忽略)+ 触发 theme_reload/ * 随 ui.style 换集」的最小闭环:meow_ui_set_style 记录 style(同值忽略)+ 触发 theme_reload/
+11
View File
@@ -210,6 +210,17 @@ enum {
MEOW_ICON_HEART = 29, 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(底部导航栏)---- */ /* ---- NavigationBar(底部导航栏)---- */
meow_widget *meow_navigationbar_create(int x, int y, int w, int h); meow_widget *meow_navigationbar_create(int x, int y, int w, int h);
int meow_navigationbar_add_item(meow_widget *w, const char *label); int meow_navigationbar_add_item(meow_widget *w, const char *label);
+12
View File
@@ -26,6 +26,7 @@
#include "meow_ui_sys.h" #include "meow_ui_sys.h"
#include "meow_ui_vol.h" #include "meow_ui_vol.h"
#include "meow_ui_media.h" #include "meow_ui_media.h"
#include "meow_ui_view.h" /* M7.0a 缺口#7:把 meow_view_dispatch_event 接入 meow_ui_run */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -714,8 +715,14 @@ void meow_ui_run(const meow_ui_events *ev)
/* §6.1 触摸分流:③ 系统全局(IME/状态栏…已在上面消费) → ② App 全局控件 /* §6.1 触摸分流:③ 系统全局(IME/状态栏…已在上面消费) → ② App 全局控件
* (命中返回 1=消费,否则落到 ① active Activity 页面控件)。 */ * (命中返回 1=消费,否则落到 ① active Activity 页面控件)。 */
if (meow_ui_global_hit(e.a, e.b)) continue; 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); if (g_events_set && g_events.on_tap) g_events.on_tap(e.a, e.b);
} else if (e.type == MEOW_EV_KEY) { } 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); if (g_events_set && g_events.on_key) g_events.on_key(e.a, e.b);
} else if (e.type == MEOW_EV_SWIPE) { } else if (e.type == MEOW_EV_SWIPE) {
if (in_startup) continue; /* 启动残留被当左缘滑动:丢弃,防进入即返回 */ if (in_startup) continue; /* 启动残留被当左缘滑动:丢弃,防进入即返回 */
@@ -725,6 +732,9 @@ void meow_ui_run(const meow_ui_events *ev)
/* 拼音 IME 候选栏横滑翻页(英文模式 handle_event 返回 0 → 沿用既有行为)。 */ /* 拼音 IME 候选栏横滑翻页(英文模式 handle_event 返回 0 → 沿用既有行为)。 */
if (meow_ime_active() && meow_ime_handle_event(&e)) continue; if (meow_ime_active() && meow_ime_handle_event(&e)) continue;
meow_ui_gesture_end(); /* §6.4:抬手(滑动)边沿,释放手势捕获 */ 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): /* A-0 全局左缘返回(方案A):
* - APP 注册 on_swipe → APP 接管,自行决定(meow_ui_request_exit / 页内返回); * - APP 注册 on_swipe → APP 接管,自行决定(meow_ui_request_exit / 页内返回);
* - APP 未注册 on_swipe 且非全屏(sb_active)→ SDK 默认退出 app(返回=退出); * - APP 未注册 on_swipe 且非全屏(sb_active)→ SDK 默认退出 app(返回=退出);
@@ -783,6 +793,8 @@ void meow_ui_run(const meow_ui_events *ev)
/* 拼音 IME 打开:长按退格清空字段(英文模式 handle_event 返回 0 → 走既有长按回调)。 */ /* 拼音 IME 打开:长按退格清空字段(英文模式 handle_event 返回 0 → 走既有长按回调)。 */
if (meow_ime_active() && meow_ime_handle_event(&e)) continue; if (meow_ime_active() && meow_ime_handle_event(&e)) continue;
meow_ui_gesture_end(); /* §6.4:长按已触发,其抬手不再 TAP/SWIPE——顺手释放捕获 */ 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); if (g_long_press_cb) g_long_press_cb(e.a, e.b);
} }
} }
+27
View File
@@ -37,6 +37,12 @@ static char g_background[64] = ""; /* display.background(black
static meow_theme_listener g_listeners[8]; static meow_theme_listener g_listeners[8];
static int g_nlisteners = 0; 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" 带空格形式)---- /* ---- 读 /usr/data/meow/settings.conf 的 key=value(兼容 "key = value" 带空格形式)----
* 与 launcher meow_settings_import 同款解析:按 '=' 切分,key/value 各自去首尾空白, * 与 launcher meow_settings_import 同款解析:按 '=' 切分,key/value 各自去首尾空白,
* 精确匹配 key(避免 display.theme 误匹配 display.theme_mode)。返回 0=命中,-1=未命中。 */ * 精确匹配 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)。 */ /* 当前「style×mode」主题:style=0/1(MD3/MIUIX),dark=0(light)/1(dark)。 */
static const meow_theme *theme_current(int style, int 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_MD3) style = MEOW_STYLE_MD3;
if (style > MEOW_STYLE_MIUIX) style = MEOW_STYLE_MIUIX; if (style > MEOW_STYLE_MIUIX) style = MEOW_STYLE_MIUIX;
return &g_theme[style][dark ? 1 : 0]; 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]; 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)---- /* ---- §8.1.2 顶级主题风格族(minimal)----
* meow_ui_style() 返回当前 ui.style(读取走 load_settings,随 meow_theme_reload() 重读, * meow_ui_style() 返回当前 ui.style(读取走 load_settings,随 meow_theme_reload() 重读,
* 故 launcher 改 settings.conf 后经 sb_sdk_refresh→theme_reload 即跟手)。 * 故 launcher 改 settings.conf 后经 sb_sdk_refresh→theme_reload 即跟手)。
+10 -2
View File
@@ -156,6 +156,10 @@ int meow_view_add_child(meow_view *parent, meow_view *child)
if (parent->nchildren >= MEOW_VIEW_MAX_CHILD) return -1; if (parent->nchildren >= MEOW_VIEW_MAX_CHILD) return -1;
parent->children[parent->nchildren++] = child; parent->children[parent->nchildren++] = child;
child->parent = parent; 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; return 0;
} }
@@ -218,11 +222,15 @@ void meow_view_page_replace(int id)
g_top_view = next ? next : cur; 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) int meow_view_sub_push(meow_view *v, meow_view *sub)
{ {
(void)v; (void)sub; (void)v; (void)sub;
return 0; /* 预留:将来在 ACTIVITY view 内维护子栈 */ return 0; /* 预留:将来在 ACTIVITY view 内维护子栈(见设计定案 b,不实现) */
} }
meow_view *meow_view_sub_pop(meow_view *v) meow_view *meow_view_sub_pop(meow_view *v)
{ {
+9 -4
View File
@@ -82,12 +82,17 @@ void meow_view_page_replace(int id); /* 旧top on_leave + 新页 on_en
/* 入口:把语义事件注入当前活动页 view 子树路由——先发子视图(按 z 降序),命中即消费, /* 入口:把语义事件注入当前活动页 view 子树路由——先发子视图(按 z 降序),命中即消费,
* 再落本 view 的 on_event(其内做该 view 的 widget 命中)。返回 1=已消费(§A.7)。 * 再落本 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); int meow_view_dispatch_event(int ev, int a, int b);
/* ---- 页内子导航(预留扩展位 a;本批为空实现/桩,仅在 ACTIVITY view 内维护子栈)---- */ /* ---- 页内子导航(预留扩展位 a,**标注废弃**)----
int meow_view_sub_push(meow_view *v, meow_view *sub); /* 预留;返回 0(未实现于本批) */ * 设计定案 b(§F.2):不做页内子导航栈,只做子视图容器嵌套;顶层页切仍走 activity 栈。
meow_view *meow_view_sub_pop(meow_view *v); /* 预留;返回 NULL(未实现于本批) */ * 故本组为**不实现、不供依赖**的桩(返回 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 相对父内容原点的累计(父坐标链求和 → 屏幕绝对坐标)---- */ /* ---- 坐标:view 相对父内容原点的累计(父坐标链求和 → 屏幕绝对坐标)---- */
int meow_view_screen_x(const meow_view *v); int meow_view_screen_x(const meow_view *v);
+67 -4
View File
@@ -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_TEXTFIELD = 7, W_CARD = 8, W_DIVIDER = 9, W_BADGE = 10,
W_CHECKBOX = 11, W_RADIO = 12, W_PROGRESS = 13, W_CHECKBOX = 11, W_RADIO = 12, W_PROGRESS = 13,
W_TOPAPPBAR = 14, W_NAVBAR = 15, W_TABBAR = 16, W_FAB = 17, 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 #define MEOW_LAYOUT_MAX_CHILD 32
@@ -100,6 +101,8 @@ struct meow_widget {
meow_listview_tap_fn lv_tap_cb; /* 行点击回调(idx, op) */ meow_listview_tap_fn lv_tap_cb; /* 行点击回调(idx, op) */
void *lv_tap_ctx; /* 回调 ctx */ void *lv_tap_ctx; /* 回调 ctx */
/* W_ICONBTN 位图图标:sb_icons[] 下标(= MEOW_ICON_* 或 SB_I_*;<0 画空位图,命中照常)。 */
int icon_id;
/* §5.5 虚拟化层(W_LISTVIEW 扩展:固定 item 池 + 多行渲染器 + 完全自绘 + 空态。 /* §5.5 虚拟化层(W_LISTVIEW 扩展:固定 item 池 + 多行渲染器 + 完全自绘 + 空态。
* calloc 归零,安全;vs_count/vs_bind 均非 NULL 才进入 pool 模式,src2 模式不受影响。) */ * calloc 归零,安全;vs_count/vs_bind 均非 NULL 才进入 pool 模式,src2 模式不受影响。) */
meow_listview_count_fn vs_count; /* 虚拟化总数回调(NULL=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) 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++) for (int i = 0; i < g_live_btn_n; i++)
if (g_live_btns[i] == w) return; if (g_live_btns[i] == w) return;
if (g_live_btn_n < MEOW_LIVE_BTN_MAX) g_live_btns[g_live_btn_n++] = w; 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」的最上层按钮(创建序 = 绘制序,最后者在上)。 /* 命中:返回坐标所在的「当前 active Activity」的最上层按钮(创建序 = 绘制序,最后者在上)。
* §6.1 ①:仅扫描 owner == 当前 active Activity 的按钮 —— 非 active Activity 的控件 * §6.1 ①:仅扫描 owner == 当前 active Activity 的按钮 —— 非 active Activity 的控件
* 绝不参与命中(根治 agent-119 遗留的「全屏页触摸到主页按钮位置 → 闪现 accent 底」)。 */ * 绝不参与命中(根治 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) static meow_widget *live_btn_hit(int x, int y)
{ {
int active = meow_ui_activity_active(); 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++) { for (int i = 0; i < g_live_btn_n; i++) {
meow_widget *w = g_live_btns[i]; meow_widget *w = g_live_btns[i];
if (w->owner != active) continue; /* 非 active activity:跳过 */ 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; /* 越后创建越上:保留最后命中 */ r = w; /* 越后创建越上:保留最后命中 */
}
} }
return r; return r;
} }
@@ -540,6 +558,20 @@ int meow_btn_set_variant(meow_widget *w, int variant)
return 0; 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 ---- */ /* ---- TextField ---- */
meow_widget *meow_textfield_create(int x, int y, int w, int h, const char *placeholder, meow_widget *meow_textfield_create(int x, int y, int w, int h, const char *placeholder,
char *buf, int maxlen, meow_ime_mode mode) 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; if (w->owner != meow_ui_activity_active()) return 0;
/* §5.8.3:禁用不响应命中(也不进 pressed)。 */ /* §5.8.3:禁用不响应命中(也不进 pressed)。 */
if (widget_disabled(w)) return 0; if (widget_disabled(w)) return 0;
/* §5.8.1:自定义画布用 App 回调判定命中(可非矩形);其余矩形内含。 */ /* §5.8.1:自定义画布用 App 回调判定命中(可非矩形);IconButton 圆形命中;其余矩形内含。 */
if (w->type == W_CUSTOM) if (w->type == W_CUSTOM)
return w->hit_cb ? w->hit_cb(w, x, y, w->ctx) : hit(w, x, y); 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); return hit(w, x, y);
} }
int meow_btn_pressed(meow_widget *w) 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); if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90);
break; 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: { case W_SLIDER: {
/* 语义色:轨道=onSurfaceVariant(dim)、填充=primary(accent)、滑块=fg(onSurface)。 */ /* 语义色:轨道=onSurfaceVariant(dim)、填充=primary(accent)、滑块=fg(onSurface)。 */
int trace_y = w->y + w->h / 2 - 3; int trace_y = w->y + w->h / 2 - 3;
+121
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
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;
}
+122
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
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;
}
+152
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <assert.h>
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;
}
+3 -1
View File
@@ -58,14 +58,16 @@ static void umbrella_smoke(void)
(void)&meow_ui_text_center; (void)&meow_ui_time_ms; (void)&meow_ui_effective_rate; (void)&meow_ui_text_center; (void)&meow_ui_time_ms; (void)&meow_ui_effective_rate;
/* 图片层(§4) */ /* 图片层(§4) */
(void)&meow_img_load_file; (void)&meow_ui_img_cover; (void)&meow_img_w; (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_get; (void)&meow_theme_defaults; (void)&meow_ui_set_style;
(void)&meow_theme_export; (void)&meow_theme_inject;
/* 输入法(§5.1) */ /* 输入法(§5.1) */
(void)&meow_ui_text_input; (void)&meow_ime_text; (void)&meow_ime_composition; (void)&meow_ui_text_input; (void)&meow_ime_text; (void)&meow_ime_composition;
/* 控件层(§5/§5.8/§5.8.6/§5.5) */ /* 控件层(§5/§5.8/§5.8.6/§5.5) */
(void)&meow_btn_create; (void)&meow_widget_custom_create; (void)&meow_row_create; (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_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_widget_set_opts; (void)&meow_widget_state; (void)&meow_progress_set;
(void)&meow_iconbtn_create; /* M7.0a 缺口#2 */
/* 输入层/生命周期(§6)+ 长按(§6.3) */ /* 输入层/生命周期(§6)+ 长按(§6.3) */
(void)&meow_ui_run; (void)&meow_ui_swipe_hint; (void)&meow_ui_set_long_press_cb; (void)&meow_ui_run; (void)&meow_ui_swipe_hint; (void)&meow_ui_set_long_press_cb;
/* Activity 栈(§6.1) */ /* Activity 栈(§6.1) */