diff --git a/.gitignore b/.gitignore index 014399d..ef9b0b2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,13 @@ # ---- 宿主测试二进制(SDK test_*.c 编译出的可执行;源码 .c 仍跟踪)---- test_ime_fix +test_meow_ui_anim +test_meow_ui_box test_meow_ui_config +test_meow_ui_dispatch +test_meow_ui_listvirt +test_meow_ui_scroll +test_meow_ui_str test_meow_ui_theme test_meow_ui_vol test_meow_ui_widgets diff --git a/README.md b/README.md index 06eca73..c94ccc1 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ MeowOS 应用框架 SDK 的**公开仓库**——面向第三方开发者,让 MeowOS-SDK/ ├── sdk/ # meow_ui 应用框架静态库源码(自包含,可交叉 → sdk/meow_ui.a) │ ├── meow_ui.h # 伞头(#include 聚合 include/ 子头;App 只 include 这个) -│ ├── include/ # 16 个模块子头 + meow_ipc.h / meow_font.h / meow_notif.h(协议/字体) +│ ├── include/ # 20 个模块子头(含 box/anim/scroll/str 等)+ meow_ipc.h / meow_font.h / meow_notif.h(vendored) │ ├── meow_font.c # 预渲染位图字体运行时(meow_font.a 源码,自包含) │ ├── build_sdk.sh # 交叉编译 meow_ui.a(mipsel 静态库,内含 meow_font.o) │ └── test_*.c # SDK 宿主逻辑测试(test_host_stub.c 打桩,无硬件可跑) @@ -48,6 +48,19 @@ cd ../examples/hello ./build_hello.sh # 产出 examples/hello/hello(静态 MIPS32r2 ELF) ``` +## SDK 能力速览 + +统一控件 / 输入 / 主题 / 字体 / 通知 / 导航之外,本版 SDK 还提供(详见 [docs/MEOW_APP_SDK.md](docs/MEOW_APP_SDK.md)): + +- **布局引擎 `meow_box`**(§12.1):线性布局纯计算器——固定/权重/wrap 尺寸、间隙/边距、主轴与交叉轴对齐、嵌套子 box;`meow_view_set_layout` 与视图框架 opt-in 集成。 +- **动画基座 `meow_anim` / `meow_anim_tick`**(§12.2):声明式 tween(linear/ease-out/ease-in-out/overshoot),静态池 ≤16,无动画零开销;只 flush 动画影响区。 +- **滚动物理 `meow_scroll`**(§12.3):速度采样 + 松手 fling + 边界 clamp/简单回弹 + snap 吸附;给 `meow_list`/`W_LIST`/`W_LISTVIEW` 加惯性滚动。 +- **事件分发形式化**(§6.4):单一仲裁者——z 序自顶向下命中、命中即消费、down 捕获整个手势、模态层隔离、唯一焦点(点外释放/滚动容器转移)。 +- **ListView 虚拟化**(§5.5):固定 item 池(视口+2)、多行富 item、段头、空态、完全自绘钩子;App 只提供「总数 + bind」回调,几千/上万行不卡。 +- **字符串资源 / i18n**(§13):`meow_str`/`meow_strf`/`meow_str_set_locale`——内嵌 `zh_cn`/`en_us` 双语表 + TF 卡覆盖表;UI 文案零硬编码,按 locale 取整串。 +- **图标库 `sb_icons`**(§5.8):30 个 MD3 Material Symbols 位图动作图标(`MEOW_ICON_*`),`meow_topappbar_set_item_icon` 把 TopAppBar 动作项换成位图图标(修复 ⚙↻⌕ 豆腐)。 +- **图片降采样 `meow_img_downscale`**(`include/meow_ui_image.h`):把解码图降到 ≤maxdim 边长省内存(封面/大图用,如 8.28MB→0.78MB)。 + ## 构建要求 - **交叉工具链**(mipsel musl 静态):MeowOS 开发机默认路径 diff --git a/docs/APP_DEV_GUIDE.md b/docs/APP_DEV_GUIDE.md index 75660e2..592bdd8 100644 --- a/docs/APP_DEV_GUIDE.md +++ b/docs/APP_DEV_GUIDE.md @@ -424,6 +424,15 @@ am resolve # 路由决策(返回 CHAIN) - App 一般不需要联网;确需联网时**克制请求**(见 §8 风控式节制)。 - 系统层面(`netmgrd` 期 0/1):无 iptables 用户态 / 无 cgroup / 无 conntrack → **内核级每应用管控不可行**;主杠杆 = DNS 代理(域名级)+ 连接普查(inet_diag)+ **SDK 自愿通道**。App 走 SDK 自愿通道配合管控是后续方向;当前 App 如联网,请自律、尊重断网降级。 +### 7.11 字符串资源 / i18n(`meow_str_*`,2026-09-02 追加) + +- **任何 UI 文案都不要硬编码**,用 `meow_str("域.名")` 按当前语言取串(示例:`meow_str("common.ok")`)。`meow_str` 永不返回 `NULL`:未知 id 原样返回 `id`,方便你打点/兜底展示。 +- **`meow_strf(out, outsz, id, ...)`**:取串后按 `snprintf` 语义格式化进调用方缓冲(自带 NUL;`out==NULL`/`outsz<=0` 返回 `-1`)。 +- **切换语言**:`meow_str_set_locale("zh_cn"|"en_us")` 即时生效;locale 存系统设置 `settings.conf` 的 `ui.locale`(默认 `zh_cn`,SDK 启动惰性读取)。App 里做语言切换页时调用它即可。 +- **覆盖/追加**:可不改二进制在 TF 卡放 `MeowData//strings/.txt`(每行 `id=text`,UTF-8)覆盖内嵌或新增自己的串(如 `player.pause=暂停`);无卡/无文件/无该 id → 回落内嵌双语默认表(`common.*` + `ime.*` 内置,见 `include/meow_ui_str.h`)。 +- **id 规范**:`域.名`;SDK 自带 `common.*`/`ime.*`,你自建 `域`(如 `player.*`)避免撞名。 +- **边界**:当前为 i18n 雏形,仅 `zh_cn`/`en_us` 双语**整串**取用;**不做复数规则 / BiDi / RTL / 动态字体回退**(详见 `MEOW_APP_SDK.md §13.7`)。 + --- ## 8. 规范与红线 diff --git a/docs/MEOW_APP_SDK.md b/docs/MEOW_APP_SDK.md index 53a615f..bf65f40 100644 --- a/docs/MEOW_APP_SDK.md +++ b/docs/MEOW_APP_SDK.md @@ -30,6 +30,7 @@ | `meow_ui_config.h` | 应用配置读写 | §9.2 | | `meow_ui_font.h` | 字体 | §5.7 卡片字体列表 + §10 字体分层 | | `meow_ui_audio.h` | 独占音频客户端 | §11 | +| `meow_ui_str.h` | 字符串资源/i18n | §13 `meow_str`/`meow_strf`/`meow_str_set_locale` | 其余按需显式 include、**不**随伞头发布的模块头(原样保留、本次未改):`meow_ui_view.h` / `meow_ui_nav.h` / `meow_ui_media.h` / `meow_ui_sys.h` / `meow_ui_vol.h`。`meow_ui_internal.h` 为 SDK 内部件,不随伞头发布。 @@ -373,7 +374,7 @@ int meow_ui_setform_hit_row(meow_widget *f, int x, int y);/* 命中行号(-1= ### 5.5 设计模板 A② 列表+选择 [源:2026-08-30] -> **性质**:SDK 设计模板,供 App 主页/子页按需选用;对既有 `meow_list_create`(B1 最小集,§5,仅 label 文本)做**结构化扩展**(行=图标+标题+副标题+右侧操作 + 选中态)。API「草案/待定案」,不实现。**(✅ 已实现:结构化列表控件 `W_LISTVIEW`,M2.76 · agent-186 · 243148e,见 §5.8.8 分批;长按事件 `MEOW_EV_LONG_PRESS` + `meow_ui_set_long_press_cb/ms`,M2.77 · agent-191 · e95552b。)** +> **性质**:SDK 设计模板,供 App 主页/子页按需选用;对既有 `meow_list_create`(B1 最小集,§5,仅 label 文本)做**结构化扩展**(行=图标+标题+副标题+右侧操作 + 选中态)。API「草案/待定案」,不实现。**(✅ 已实现:结构化列表控件 `W_LISTVIEW`,M2.76 · agent-186 · 243148e,见 §5.8.8 分批;长按事件 `MEOW_EV_LONG_PRESS` + `meow_ui_set_long_press_cb/ms`,M2.77 · agent-191 · e95552b;虚拟化层(固定 item 池 + 多行富 item + 完全自绘 + 段头 + 空态),2026-09-02,见 §5.5「虚拟化层」。)** **摘要**:可滚动选择容器。**行数据源回调**逐行给出(图标 / 标题 / 副标题 / 右侧操作 / 是否可选),容器负责绘制/滚动/选中/命中;支持**单选、多选、长按**(上下文操作)。适合「已保存网络 / 附近WiFi / 蓝牙设备 / 歌曲列表 / 设置子菜单」等。 @@ -420,6 +421,51 @@ int meow_ui_listview_hit(meow_widget *lv, int x, int y); /* 命中行; ``` > 说明:既有 `meow_list_create`(label-only)保留;`meow_ui_listview_*` 为**结构化扩展**(图标/副标题/右侧/选中),可作为真则替换裸 label 列表。 +#### 虚拟化层(2026-09-02,W_LISTVIEW 扩展;API 追加式,旧 API 一字不改) + +> **痛点**:W_LISTVIEW(src2 每帧按需取行)只画可见行,但**每帧重取全部可见行**;大列表(几千/ +> 上万首)App 只能自行造「滑动窗口」补载(如超长歌单的滑动窗口补载)。 +> 本层把「固定 item 池 + 多行富 item + 完全自绘」收编为 SDK 级能力,让 App 只提供「总数 + bind」回调。 + +**关键交互/数据模型**: +- **数据源**:`meow_listview_count_fn`(返回总行数,任意长 ≥65535,不常驻 item 对象)+ `meow_listview_bind_fn` + (`index, meow_lv_item*, ctx`,仅在行「进入视口」(绑定到固定池槽)时被调**一次**)。`count` 每帧实时查 + (搜索过滤/懒加载动态长度)。 +- **固定 item 池**:池容量 = 视口行数 + 2 静态槽(有界,`MEOW_LV_POOL_MAX`=96),行离开视口不销毁、 + **回滚不重绑**(同一 index 命中同一槽 → bind 不重调);滚动只改 viewport 偏移(复用既有 set_scroll/max_scroll), + 不重绑未进视口的项。**整表扫过后池发生 wrap(capsection=1`(title=段名)→ 段头占位行(非粘性;粘性为后续增强)。 +- **空态**:`meow_listview_set_empty_text(text)`——count==0 时视口内居中画文案。 + +**与既有机制集成**:焦点/事件走 §6.4(`meow_listview_tap_at` 命中行自动 `meow_ui_focus_acquire`,点外由 +`meow_ui_focus_tap` 释放——list 焦点残留根治下沉);滚动接 §12.3 `meow_scroll`(`meow_scroll_wrap(lv, 1)`, +`track`+`fling` 跟手/惯性,`set_snap(item_h)` 吸附到行边界);选中/按压态样式走主题 +(`primaryContainer`/半透明 `primary` 按压层);`enabled=0` 行变淡且命中不响应。 + +**API 一览(`include/meow_ui_widgets.h`,§5.5)**: +```c +typedef struct meow_lv_item { const char *title, *subtitle; const meow_img *icon; int icon_kind; + int right, on, enabled, section; void *user; } meow_lv_item; +typedef int (*meow_listview_count_fn)(void *ctx); +typedef void (*meow_listview_bind_fn)(int index, meow_lv_item *item, void *ctx); +typedef void (*meow_listview_draw_fn)(int idx, int x, int y, int w, int h, int state, void *ctx); +int meow_listview_set_vsource(meow_widget *lv, meow_listview_count_fn count, + meow_listview_bind_fn bind, void *ctx); /* 切 pool 模式 */ +int meow_listview_set_item_renderer(meow_widget *lv, int lines, int show_icon, int show_right); +int meow_listview_set_item_tier(meow_widget *lv, int lines); /* 1→48 / 2→56 */ +int meow_listview_set_item_draw(meow_widget *lv, meow_listview_draw_fn draw, void *ctx); +int meow_listview_set_empty_text(meow_widget *lv, const char *text); +``` +> 兼容:既有 `meow_listview_*`(src2 模式)全部保留不动(`src2==NULL` 走既有路径);本层为叠加。 +> `meow_widget` 尾部追加虚拟化字段(`calloc` 归零,安全);`meow_ui.a` 需重编,App 链新库(老 ABI 不重建名)。 +> 宿主验证 `test_meow_ui_listvirt`(T1 虚拟化/bind 有界/回滚、T2 多行/截断/图标/尾件、T3 段头/空态、 +> T4 自绘、T5 焦点/fling 联动)+ 全量回归 widgets/dispatch/box/anim/scroll/ime/pinyin/overlay/notif/ +> sdk_input_keys/p1p2p3 全 PASS;交叉 -Wall -Wextra 0 warning。 + --- ### 5.6 设计模板 B③ 弹窗/对话框 [源:2026-08-30] @@ -755,6 +801,58 @@ meow_widget *meow_layout_spacer(int w, int h); --- +> **TopAppBar 位图动作图标(M2.9x,追加式)**:TopAppBar 动作项默认是**文本**(`meow_topappbar_add_action`)。为支持「⚙↻⌕」类**动作图标**(4k 字库无这些码点,字体码点画图标会出豆腐),新增把某动作项改为**位图图标**的接口: +> +> ```c +> int meow_topappbar_set_item_icon(meow_widget *w, int idx, int icon_idx); +> ``` +> - `idx` = 动作项下标(`meow_topappbar_add_action` 返回值;越界返回负值)。 +> - `icon_idx` = `sb_icons[]` 位图索引(用 `MEOW_ICON_*` 常量,见下表;<0 非法)。 +> - 返回 `0`=成功;`NULL`/非 TopAppBar/越界 `idx`/非法 `icon_idx`=负值。 +> - 绘制:与状态栏**同源**位图(Material Symbols Rounded 32x32 单通道 alpha,`meow_gfx_blit_icon_ink`), +> 图标槽在动作格内水平居中、ink 垂直对齐栏中点,颜色取主题 `onSurface`、**按压态向栏底色淡 12%**。 +> - **兼容**:未调用本 API 的动作项保持文本(行为不变);`items` 文本条目不感知位图化。 +> +> **sb_icons[] 索引表**(Material Symbols Rounded,MD3,单通道 alpha 32x32;Apache-2.0, +> 来源 google/material-design-icons,重新生成 `sdk/tools/gen_action_icons.py`): +> +> | 索引 | 状态栏 `SB_I_*`(SDK 内部) | TopAppBar `MEOW_ICON_*`(App 用) | 图形 | 用途 | +> |---|---|---|---|---| +> | 0 | `SB_I_WIFI` | — | Wi-Fi | 状态栏 WiFi(满格) | +> | 1 | `SB_I_BT` | — | 蓝牙 | 状态栏蓝牙(连接中) | +> | 2 | `SB_I_BATT` | — | 电池 | 状态栏电量 | +> | 3 | `SB_I_VOL` | — | 音量 | 状态栏音量 | +> | 4 | `SB_I_BRIGHT` | — | 亮度 | 状态栏背光 | +> | 5 | `SB_I_WIFI_STRONG` | — | Wi-Fi | 状态栏(-60dBm 强) | +> | 6 | `SB_I_WIFI_MEDIUM` | — | Wi-Fi | 状态栏(-75dBm 中) | +> | 7 | `SB_I_WIFI_WEAK` | — | Wi-Fi | 状态栏(更弱) | +> | 8 | `SB_I_WIFI_NO` | — | Wi-Fi | 状态栏(开未连接) | +> | 9 | `SB_I_WIFI_OFF` | — | Wi-Fi | 状态栏(关闭) | +> | 10 | `SB_I_BT_NO` | — | 蓝牙 | 状态栏(未连接) | +> | 11 | `SB_I_BT_CONN` | — | 蓝牙 | 状态栏(已连接) | +> | 12 | `SB_I_BT_DIS` | — | 蓝牙 | 状态栏(关闭) | +> | 13 | `SB_I_BT_SEARCH` | — | 蓝牙 | 状态栏(搜索中) | +> | 14 | `SB_I_BATT_CHG` | — | 电池 | 状态栏(充电) | +> | 15 | `SB_I_SETTINGS` | `MEOW_ICON_SETTINGS` | ⚙ 齿轮 | 设置 | +> | 16 | `SB_I_REFRESH` | `MEOW_ICON_REFRESH` | ↻ 回转 | 刷新 | +> | 17 | `SB_I_SEARCH` | `MEOW_ICON_SEARCH` | 🔍 放大镜 | 搜索 | +> | 18 | `SB_I_PLAY_ARROW` | `MEOW_ICON_PLAY_ARROW` | ▶ | 播放 | +> | 19 | `SB_I_PAUSE` | `MEOW_ICON_PAUSE` | ⏸ | 暂停 | +> | 20 | `SB_I_SKIP_NEXT` | `MEOW_ICON_SKIP_NEXT` | ⏭ | 下一首 | +> | 21 | `SB_I_SKIP_PREV` | `MEOW_ICON_SKIP_PREV` | ⏮ | 上一首 | +> | 22 | `SB_I_ARROW_BACK` | `MEOW_ICON_ARROW_BACK` | ← | 返回 | +> | 23 | `SB_I_CLOSE` | `MEOW_ICON_CLOSE` | × | 关闭 | +> | 24 | `SB_I_ADD` | `MEOW_ICON_ADD` | + | 添加 | +> | 25 | `SB_I_CHECK` | `MEOW_ICON_CHECK` | ✓ | 勾选 | +> | 26 | `SB_I_LYRICS` | `MEOW_ICON_LYRICS` | 歌词 | 歌词 | +> | 27 | `SB_I_QUEUE_MUSIC` | `MEOW_ICON_QUEUE_MUSIC` | 队列 | 播放列表 | +> | 28 | `SB_I_DOWNLOAD` | `MEOW_ICON_DOWNLOAD` | ↧ | 下载 | +> | 29 | `SB_I_HEART` | `MEOW_ICON_HEART` | ♥ | 收藏 | +> +> **索引约定**:状态栏占用 `0-14`(既有序号,**勿改序**);TopAppBar 动作从 `15` 起连续追加(只追加,既有索引不变)。`MEOW_ICON_*`(公共头 `meow_ui_widgets.h`)与 `SB_I_*`(SDK `sb_icons.h`)**同值**,二者须保持表序一致(`test_umbrella.c` 有 `_Static_assert` 锁 15/21/27/29)。 + +--- + ### 5.9 音频客户端 API(`meow_audio_*`,M2.90 落地) > **权威规范:`devdocs/plan/AUDIOD_DESIGN.md`**(用户定案五项决策)。**已实现**:A1(agent-244/246,audiod+CLI+SDK 客户端,M2.90 入固件)+ A2(流媒体客户端迁 SINK_AUDIOD,e013621)+ 后续批次(M2.91 FPGA 开机武装 / M2.93 长流护栏 / M2.94 几何收窄 / M2.95 音量反转 / M2.96 炸耳修复 / 261 电平监控待 M2.97 固件化)。IPC 号段 `0x00C0-0x00CA` 见 `meow_ipc.h` / docs/MEOWOS_SPEC_REF §4。 @@ -881,6 +979,99 @@ void meow_ui_global_remove(int id); --- +### 6.4 事件分发形式化(2026-09-02):命中测试 / 捕获 / 消费 / 层间隔离 / 唯一焦点 + +> **定位**:把此前各处 ad-hoc 的「命中/捕获/消费/层间隔离/焦点」补丁(模态穿透、弹层叠黑、 +> list 焦点残留、同坐标二次消费)**上收为单一仲裁者**。三个历史根因(设置页滑条穿透、 +> Dialog/Sheet 弹层叠黑、list 焦点残留导致主页面滑不动)与「注入事件被下层同坐标控件二次消费」 +> 都在本节模型内覆盖。**兼容铁律:只追加 API、不删改既有签名**;未调用本节 API 的 App 行为与 +> 之前完全一致(命中/捕获仍可由 App 在 `on_tap`/`on_touch_*` 自理)。hello/files/reader 等 +> 既有 App 只把 SDK 提供的语义用于**收敛 bug**,不改正常交互语义。 + +#### 6.4.1 分发模型(自上而下命中 + 命中即消费 + down 捕获 + 模态层间隔离) + +触摸事件沿 **z 序自顶向下**命中(模态层**永远先审**);某一层命中即消费(返回 consumed 才继续 +下探)。一次 **touch-down 命中后捕获整个手势直到 up**——期间 move/up 只发给捕获目标,**防滑出后 +误触发下层**(根治「手指滑出控件仍点下层」)。**模态层存在时下层完全收不到事件**(含兜底: +模态背板未命中的事件由背板消费,不透传)。 + +层序(顶 → 底)与 §6.1 三层模型 +0 模态层合成一张表: + +| 层 | 命中语义 | 是否透传 | +|---|---|---| +| **+0 模态层**(Dialog/Sheet/通知中心/电源菜单等) | 模态始终先审;未命中的事件由**背板消费**(点外=取消/关闭) | **不透传** | +| **③ 系统全局**(IME/状态栏/通知中心入口……已在运行循环消费) | 命中即消费 | 返回 1 才透传 | +| **② App 跨 Activity 全局**(`meow_ui_global_hit`) | 命中返回 1=消费 | 否则落下层 | +| **① 当前 Activity 页面控件**(view/widget,z 降序) | 命中即消费 | 否则让 App 自理 | + +SDK 提供统一仲裁者(`meow_ui_hit_topdown` 沿 z 降序返回首个命中控件,含 `owner==active`+enable +过滤)。**模态存在时它恒返回模态哨兵**——下层控件(即使同坐标)绝不会成为捕获/命中目标。 + +手势捕获由 `meow_ui_gesture_begin/owner/end` 表达:`meow_ui_run` 在 `MEOW_EV_TOUCH_DOWN` 时 +自动 `begin`(命中并捕获),随后 move/up 只发给捕获目标(`meow_ui_gesture_owner` 恒定), +抬手(TAP/SWIPE/LONG_PRESS/NOTIF_PULL/清队列)自动 `end` 释放。模态期间 begin 捕获的是模态 +哨兵——下层不被捕获,天然根治穿透。 + +#### 6.4.2 焦点模型(唯一持有者 + 点外释放 + 滚动容器转移) + +**焦点持有者唯一**(`meow_ui_focus_owner`)。点非 focus 区域 → **释放焦点并触发通知回调** +(`meow_ui_focus_tap`,list 焦点残留根治)。滚动容器(W_LIST/W_LISTVIEW)在 +`meow_list_tap_at`/`meow_listview_tap_at` 命中行时自动 `meow_ui_focus_acquire`(列表成为唯一 +焦点);任何落在该容器外的触摸(`meow_ui_focus_tap` 判定 hit != focus owner)释放焦点并通知, +回退到页面级滚动。 + +**滚动容器间焦点转移规则**: + +1. 某滚动容器(list/lazycolumn/row)获得焦点后,随后的**纵向** swipe / 拖动滚动该容器; +2. 任何命中落在该容器**外**的触摸(含空白、页面内容、其它控件)→ 焦点释放 + `lost` 回调, + 后续拖动滚动**页面**(不再滚列表); +3. 焦点释放/转移不销毁容器控件(`meow_widget` 仍由 App/视图持有),下次命中行重新 acquire。 + +#### 6.4.3 公共 API(至 `meow_ui_input.h`,仅追加) + +```c +/* 模态作用域:dialog/sheet/App 自绘模态「打开时 begin、关闭时 end」;支持嵌套(depth 递增)。 */ +typedef struct meow_ui_modal meow_ui_modal; +meow_ui_modal *meow_ui_modal_begin(void); /* 打开模态层入栈;返回句柄(NULL=栈满) */ +void meow_ui_modal_end(meow_ui_modal *m); /* 关闭:弹到 m(含)一次性关上嵌套;幂等 */ +int meow_ui_modal_active(void); /* 当前是否有任一模态作用域 */ +int meow_ui_modal_depth(void); /* 模态栈深(弹层叠开计数) */ + +/* 自上而下命中:z 降序返回首个命中控件;模态存在时恒返回模态哨兵(背板消费,不透传)。 */ +void *meow_ui_hit_topdown(int x, int y); +int meow_ui_widget_hit(meow_widget *w, int x, int y); /* meow_widget_hit 公共别名 */ + +/* 手势捕获:down 命中即捕获直到 up;期间 move/up 只给捕获目标(滑出不误触)。 */ +void *meow_ui_gesture_begin(int x, int y); +void *meow_ui_gesture_owner(void); +void meow_ui_gesture_end(void); + +/* 唯一焦点:点非 focus 区域 → 释放并通知(list 焦点残留根治)。 */ +typedef void (*meow_ui_focus_lost_fn)(void *owner, void *ctx); +void meow_ui_focus_set_lost_cb(meow_ui_focus_lost_fn cb, void *ctx); +void *meow_ui_focus_owner(void); +int meow_ui_focus_acquire(void *owner); +int meow_ui_focus_release(void *owner); +void meow_ui_focus_tap(int x, int y); +``` + +#### 6.4.4 迁移与兼容 + +- **dialog/sheet**(`meow_ui_dialog.c`):`meow_dialog_show`/`meow_sheet_open` 已迁到 + `meow_ui_modal_begin/end`——模态打开期间 `meow_ui_modal_active()` 为真,下层收不到触摸; + 点卡片外由背板消费(关闭)。**弹层叠黑**由既有「打开前整帧快照 + 关闭还原 + 高亮仅重绘卡片 + 不叠遮罩」保证(M2.71-bug3),与本节模态层隔离正交、互不影响。 +- **list/scroll**(`meow_ui_widget.c`):`meow_list_tap_at`/`meow_listview_tap_at` 命中行自动 + `meow_ui_focus_acquire`,点外由 `meow_ui_run` 的 `meow_ui_focus_tap` 统一释放——list 焦点残留 + 根治下沉到 SDK;App 层不再需要各自「点非 list 区域手动清 focus」补丁。 +- **注入/叠加让位**:`overlay_input_suppressed`(§10.5)是**系统叠加层**的层间隔离(面板输入由 + launcher 接管、App 排空副本),与本节模态层隔离互补共同防「同坐标二次消费」,语义不变。 +- 以上均为**行为收敛**(修 bug),正常交互语义不变;`p1p2p3_repro`/`widgets`/`ime_fix`/`pinyin`/ + `overlay_suppress`/`notif_gesture`/`sdk_fixes`/`sb_collapse`/`box`/`anim`/`scroll`/`sdk_input_keys` + 全量回归通过。 + +--- + ## 7. 动画/时间(B1 最小可用) 当前无动画框架。B1 提供**帧驱动回调 + 内置时间**,应用据此做低成本的帧序列/淡入淡出: @@ -1073,7 +1264,188 @@ patch_v2/meow_launcherd/meow_font.a # 复用(静态链接) --- -## 12. 遗留 / 待实现 +## 12. 控件层扩展:可组合布局引擎 / 动画基座 / 滚动物理 + +> **性质**:三块**独立增量**(对照 §5.8.5 的「固定尺寸 Row/Column/Box」与 §7 的「B1 最小动画」做升级), +> **全部追加式**:新头 `include/meow_ui_box.h` / `include/meow_ui_anim.h` / `include/meow_ui_scroll.h` + +> 新 TU `meow_ui_box.c` / `meow_ui_anim.c` / `meow_ui_scroll.c`;**不改任何既有 API 签名/行为**。 +> 既有控件仍是**绝对定位**(x,y,w,h);布局只是「帮你算坐标」的容器,不改变绘制/命中/状态机。 +> 旧绝对坐标**永远有效**——不用这三块的 App 完全照旧。 + +### 12.1 线性布局引擎(`meow_box`) + +在既有 `meow_row/column/box`(§5.8.5,只支持固定尺寸+居中)之上,提供更完整的线性布局: + +- **容器**:竖直(`MEOW_BOX_V`)/ 水平(`MEOW_BOX_H`)。 +- **子项尺寸策略**:固定(`MEOW_BOX_FIXED`,取控件当前 w/h)| 权重(`MEOW_BOX_WEIGHT`,均分剩余主轴空间)| wrap(`MEOW_BOX_WRAP`,按内容测量)。 +- **间距/边距**:容器 `padding`(left/top/right/bottom)+ 项间 `gap`;子项 margin 读取既有 `meow_widget_opts.margin_x/y`。 +- **主轴对齐**:start / center / end / space-between / space-around / space-evenly。 +- **交叉轴对齐**:start / center / end / stretch(填满容器交叉向减 margin)。 +- **嵌套**:box 内可再挂子 box(`meow_box_add_sub`),求解深度 ≤ `MEOW_BOX_MAX_DEPTH`(16,静态栈,无递归爆炸)。 + +```c +typedef struct meow_box meow_box; +typedef enum { MEOW_BOX_H = 0, MEOW_BOX_V = 1 } meow_box_dir; +typedef enum { MEOW_BOX_FIXED = 0, MEOW_BOX_WEIGHT = 1, MEOW_BOX_WRAP = 2 } meow_box_size; +typedef enum { + MEOW_BOX_ALIGN_START = 0, MEOW_BOX_ALIGN_CENTER, MEOW_BOX_ALIGN_END, + MEOW_BOX_ALIGN_SPACE_BETWEEN, MEOW_BOX_ALIGN_SPACE_AROUND, MEOW_BOX_ALIGN_SPACE_EVENLY, +} meow_box_align; +typedef enum { MEOW_BOX_CROSS_START = 0, MEOW_BOX_CROSS_CENTER, MEOW_BOX_CROSS_END, MEOW_BOX_CROSS_STRETCH } meow_box_cross; + +meow_box *meow_box_new(meow_box_dir dir); /* 命名避开既有 meow_box_create(W_LAYOUT) */ +void meow_box_free(meow_box *b); +void meow_box_set_rect(meow_box *b, int x, int y, int w, int h); /* 容器矩形(绝对坐标) */ +void meow_box_set_padding(meow_box *b, int l, int t, int r, int btm); +void meow_box_set_gap(meow_box *b, int gap); +void meow_box_set_align(meow_box *b, int main_align, int cross_align); +int meow_box_add(meow_box *b, meow_widget *w, int size, int weight); /* 固定/权重 */ +int meow_box_add_ex(meow_box *b, meow_widget *w, int size, int weight, + int min, int preferred, meow_box_measure_fn m, void *mctx); /* WRAP 测量 */ +int meow_box_add_sub(meow_box *b, meow_box *sub, int size, int weight); /* 嵌套 */ + +/* 单遍求解:把每个子控件 x/y/w/h 写进 meow_widget(新增 meow_widget_set_rect);深度≤16 静态栈。 + * 子控件 margin(widget opts.margin_x/y)参与排布;嵌套 sub 递归。 */ +void meow_box_arrange(meow_box *b); +``` + +- **与视图框架集成(opt-in)**:box 可作 `meow_view` 的布局提供者;view **不设**就保持现有绝对坐标行为。 + `meow_view_set_layout(v, box)` 登记 + 立即排一次;`meow_view_relayout(v)` 手动重排; + 顶层页在 `meow_view_page_push/pop/replace` 进入时自动重排。box 矩形默认跟 view 内容矩形(w/h 未设时自动填)。 + ```c + void meow_view_set_layout(meow_view *v, meow_box *box); /* NULL=取消 */ + void meow_view_relayout(meow_view *v); + ``` +- **控件尺寸测量**:`MEOW_BOX_FIXED`/`WRAP`(无 measure 回调)直接取控件当前 `w/h`(对既有固定尺寸控件天然兼容); + `WRAP` 可用 `meow_box_measure_fn`(给出主轴向 min/preferred)供内容自适应的自定义控件用。 +- **内存纪律**:box 静态池、定长子项数组(`MEOW_BOX_MAX_CHILD=24`),arrange 热路径零 malloc;嵌套用深度≤16 静态栈。 + +### 12.2 动画基座 + UI 节拍器(`meow_anim` / `meow_anim_tick`) + +把 §7「应用在 on_tick 里用 `meow_ui_time_ms` 做时间轴」收敛成**声明式 tween**: + +- **缓动**:linear / ease-out / ease-in-out / overshoot(`meow_ease_apply`)。 +- **动画对象**:`meow_anim_start` 一次性声明 from/to/duration/easing + `update`(每帧插值回调)+ `done`(完成/取消回调);**并行多动画**(静态池 ≤ `MEOW_ANIM_MAX=16`)、**取消**(`meow_anim_cancel`)。 +- **节拍**:`meow_anim_tick()` 挂既有 `meow_ui_run` 主循环(on_tick 之后)自动调用;**无动画时零开销**(只查计数即返回),不引入常驻高频轮询。阻塞 IME 循环(`meow_ui_feed_tick`)不喂 tick(动画在模态期暂停)。 +- **与脏区集成**:`meow_anim_set_region(a,x,y,w,h)` 让每帧**只 flush 动画影响区**,不做整屏重绘;app 的 `update` 回调也可自行 `meow_ui_flush_region`。 + +```c +typedef enum { MEOW_EASE_LINEAR = 0, MEOW_EASE_OUT, MEOW_EASE_IN_OUT, MEOW_EASE_OVERSHOOT } meow_ease; +float meow_ease_apply(meow_ease e, float t); /* t∈[0,1]→[0,1];overshoot 中后段可略>1、末点=1 */ + +typedef struct meow_anim meow_anim; +typedef void (*meow_anim_update_fn)(meow_anim *a, float v, void *ctx); +typedef void (*meow_anim_done_fn)(meow_anim *a, int canceled, void *ctx); +typedef struct meow_anim_desc { + float from, to; uint32_t duration_ms; meow_ease ease; + meow_anim_update_fn update; meow_anim_done_fn done; void *ctx; +} meow_anim_desc; + +meow_anim *meow_anim_start(const meow_anim_desc *d); +void meow_anim_cancel(meow_anim *a); +int meow_anim_active(meow_anim *a); +float meow_anim_value(meow_anim *a); +void meow_anim_set_region(meow_anim *a, int x, int y, int w, int h); +void meow_anim_tick(void); /* 推进所有活动动画(挂主循环;无动画零开销) */ +unsigned meow_anim_count(void); +``` + +- **与 §7 的关系**:§7(B1 最小可用)是手写时间轴;本模块是正式基座。应用可任选其一或混用。 +- **内存纪律**:静态池 ≤16,start/cancel/tick 全程零 malloc。 + +### 12.3 滚动物理(`meow_scroll`) + +给既有滚动容器(`meow_list` / `W_LIST` lazycolumn/lazyrow / `meow_listview`)加**可惯性滚动**物理层: + +- **速度跟踪**:`meow_scroll_track(dy, t)`(拖动跟手 + 采样),fling 时用**最近 N 个采样加权**得初速。 +- **松手 fling**:`meow_scroll_fling(s)` 起一条减速度曲线动画(用 §12.2 动画基座驱动)推进位移;每帧**只 redraw 列表视口区**(不整屏)。 +- **边界过冲 clamp + 简单回弹**:free 目标越界 → clamp 到 `[0,max]`;快速撞墙补两段短 IN_OUT 回弹(不做复杂 spring)。 +- **滚动停止自动归位**:`meow_scroll_set_snap(step)` 可选吸附到 step 倍数。 +- **跟手拖动行为保持现状不变**:`track` 内部调既有 `meow_list(viewport)_set_scroll`(clamp 一致),与 app 直接 `scroll_by` 等价;app 把拖动的 `scroll_by` 换成 `track` 即可获得速度采样。 + +```c +typedef struct meow_scroll meow_scroll; +meow_scroll *meow_scroll_create(meow_scroll_set_fn set, meow_scroll_max_fn max, + meow_scroll_get_fn get, meow_scroll_redraw_fn redraw, void *ctx); +meow_scroll *meow_scroll_wrap(meow_widget *list, int is_listview); /* 1=W_LISTVIEW / 0=W_LIST 或 lazycolumn/row */ +void meow_scroll_free(meow_scroll *s); +void meow_scroll_track(meow_scroll *s, int dy, uint64_t t_ms); /* 跟手拖动 + 速度采样 */ +void meow_scroll_fling(meow_scroll *s); /* 松手惯性滑动(内部算初速) */ +void meow_scroll_stop(meow_scroll *s); /* 立即停止 */ +int meow_scroll_velocity(meow_scroll *s); /* 加权初速 px/s */ +int meow_scroll_active(meow_scroll *s); +int meow_scroll_offset(meow_scroll *s); +void meow_scroll_set_snap(meow_scroll *s, int step); +void meow_scroll_set_friction(meow_scroll *s, int tau_ms); /* 默认 180ms */ +``` + +- **内存纪律**:静态池(应用侧按需建会话),track/fling/tick 全程零 malloc;无 fling 时 meow_scroll 不参与节拍(由 `meow_anim_tick` 活动计数保证零开销)。**不引 libm**(SDK 免 `-lm` 约定)。 + +### 12.4 对既有 App 的零影响(迁移说明) + +- **追加式**:三块全为新 API;既有 `meow_row/column/box`(§5.8.5)与全部 `meow_*_create/hit/scroll` 签名一行未改。 +- **旧绝对坐标永远有效**:box 只是把坐标算进 `meow_widget`(`meow_widget_set_rect`);不用 box 的 App 照旧 `on_layout`/绝对定位。 +- **机制层新增**:`meow_ui_input.c` 主循环新增 `meow_anim_tick()` 调用(无动画零开销);`meow_ui_view.c` 新增布局登记表(未设布局的 view 行为不变);`meow_ui_widget.c` 新增 `meow_widget_set_rect`(只设字段不标脏,既有 `meow_layout_arrange` 不受影响)。 +- **同步重编注意**:`meow_ui.a` 内容变化(新增 box/anim/scroll 三个 `.o`,`meow_ui_widget.o`/`meow_ui_view.o`/`meow_ui_input.o` 重编)→ 链 `meow_ui.a` 的 App 需**重链**(`build_sdk.sh` 一键重编 + 各 App `build_*.sh` 重链)。既有 App **不改源码**即可零回归。 + +--- + +## 13. 字符串资源 / i18n(`meow_str_*`,2026-09-02 追加) + +> **性质**:字符串资源外置(i18n 雏形)——公开 SDK 前,SDK/App 的 UI 文案不再硬编码在 C 源码:第三方开发者按系统 locale 取对应语言串,且可不改 SDK 二进制用 TF 卡覆盖/追加。**追加式**:新头 `include/meow_ui_str.h` + 新 `.o` `meow_ui_str.c`;既有 `meow_ui.h` 伞头自动聚合(仅新增 #include),既有 API/控件签名一行未改。 + +### 13.1 API(追加式,纯 libc,宿主 gcc 可直接单测) + +```c +const char *meow_str(const char *id); /* 按当前 locale 取 id 文案;静态串,永不 NULL;未知 id 原样返回 id */ +int meow_strf(char *out, int outsz, const char *id, ...); /* 取串后按 snprintf 语义格式化进 out(自带 NUL);out==NULL/outsz<=0→-1 */ +void meow_str_set_locale(const char *locale); /* 切 locale("zh_cn"|"en_us"),即时生效 + 重载 TF 覆盖表 + best-effort 持久化 ui.locale */ +const char *meow_str_locale(void); /* 当前生效 locale(缺省 "zh_cn") */ +``` + +> `meow_strf` 用**调用方自带缓冲**(而非静态/线程局部),避免 IME/模态等**重入路径**的缓冲竞态,且与 SDK 既有 `snprintf` 习惯一致(无格式参数时等价深拷贝 `meow_str`)。这是把任务书 `meow_strf(id, ...)` 落地为 `(out, outsz, id, ...)` 的定案(可重入、线程安全优先于少一个参数)。 + +### 13.2 取值优先级(四层,逐层回落,永不返回 NULL) + +1. 当前 locale 的 **TF 覆盖表**:`MeowData//strings/.txt`,`id=text` 行格式 UTF-8,`meow_str_set_locale` 加载进静态数组(≤256 条); +2. 内嵌表**当前语言**(`en_us→en`,其余→`zh`); +3. 内嵌表**内置默认**(`zh_cn`,locale 缺省/未知语言回落); +4. 仍不可得 → **原样返回 `id`**(调用方可展示 id/打点,绝不见 NULL)。 + +> 无卡/无覆盖文件/无该 id → 自动回落**内嵌双语默认表**(编译进二进制,必可用)。内嵌表含 `common.*`(确定/取消/返回/确认/完成/保存/删除/编辑/搜索/输入/请输入/请选择/加载中/暂无数据/重试/失败/重试失败/关闭/未知/设置/点击确定编辑,共 21 条)+ `ime.*`(空格/确定/取消/拼英/?123/清/拼音/(无候选)/中,共 9 条),`zh_cn`+`en_us` 双语。 + +### 13.3 ID 规范 + +`域.名`(`common.ok` / `common.cancel` / `player.pause`)。SDK 自带 `common.*` 通用组 + `ime.*` IME 键面组;第三方可自建 `域`(如 `player.*`),经 TF 覆盖表追加,不改 SDK 二进制。 + +### 13.4 locale 存系统设置 + +locale 权威值 = 板载 `settings.conf` 的 `ui.locale`(默认 `zh_cn`)。SDK 首次调用**惰性读取**;`meow_str_set_locale` 即时切换(运行时)并 **best-effort 持久化**回 `ui.locale`(流式改写、保留其它行——绝不 O_TRUNC 整文件;失败静默,切换仍生效)。SDK 与 launcher 系统组件读同一份,后续系统 UI 复用同键即当前语言。 + +### 13.5 性能 + +**启动时一次加载**到静态线性表:内嵌为编译期静态表;覆盖表为启动/切 locale 时的静态数组(≤256 条)。`meow_str` 只做 O(n) 线性扫描(表小可接受),**热路径零 malloc**(无堆申请、无引用计数)。 + +### 13.6 SDK 控件接入 + +- **dialog**(§5.8.6):默认按钮 `"CANCEL"/"OK"` → `meow_str("common.cancel"/"common.ok")`;INPUT 预览占位 `"(tap OK to edit...)"` → `meow_str("common.tap_ok_edit")`。 +- **IME**(§5.1):拼音键面/徽标(空格/确定/拼英/?123/清/拼音/(无候选))与 ASCII/NUM 顶条「中」→ `meow_str("ime.*")`。 +- **空态/加载态**:SDK 不硬编码空态文案;`common.loading`/`common.no_data` 供 App 空态/加载中直接用(如 `meow_listview_set_empty_text(w, meow_str("common.no_data"))`)。 +- **launcher 等系统组件**:后续经同框架接入(本任务只管 SDK 侧;launcher 复用同 `settings.conf ui.locale`)。 + +### 13.7 边界(明示不做,写清理由) + +- **不做复数规则**:需占位符格式化协议 + 每语言复数表,超出「按 locale 取整串」的最小必要范围; +- **不做 BiDi / RTL / 动态字体回退**:需文本整形 + 布局镜像 + 字体回退策略,面向 480×720 单行/短文案场景暂不需要; +- 当前形态为 i18n **雏形**,复杂变体(复数/语言方向/字体重载)留后续批次;locale 仅 `zh_cn`/`en_us` 双语(任意串作覆盖表文件名,内嵌按非 `en` 视为内置默认)。 + +### 13.8 同步重编注意 + +`build_sdk.sh` OBJS 新增 `meow_ui_str`;`meow_ui_str.o`(含内嵌表)+ `meow_ui_dialog.o`/`meow_ui_ime.o` 重编 → **链 `meow_ui.a` 的 App 需重链**(一键 `build_sdk.sh`)。既有 App **不改源码**即零回归(仅按钮/键面文案随 locale 变化)。宿主回归测试需在编译命令补 `meow_ui_str.c`(各 test_*.c 头注释已同步)。 + +--- + +## 14. 遗留 / 待实现 1. `meow_ui` 渲染后端需从 launcher 里**抽公共绘制实现**(`back_buf`/`meow_dirty_*`/`meow_present`/`meow_font_bind` 目前是 launcher 静态函数——需提取为可链接的 `meow_ui_gfx` 或改为共享 obj,注意 launcher 与 app 是**不同进程**,不能共享同一 back_buf;`meow_ui.a` 是**独立库**,内部自建 fb 上下文,不复用 launcher 的全局 `g_dirty`)。 2. 输入线程优先级:vs 主循环 10ms 热键轮询的竞态,需在 B1 实现时定义清楚(应用进程内独立事件循环,不与 launcher 抢)。