# MeowOS 应用开发框架 SDK(meow_ui)设计 > 状态:实现版(2026-08-29 设计定案;接口以 `patch_v2/sdk/meow_ui.h` 为准)。路线 B:**框架承载**——MeowAPPs 不再裸 `mmap("/dev/fb0")` 自绘、自读输入,而是链接统一静态库 `meow_ui.a`,用 API 开发业务;渲染/控件/图片/输入/导航由框架收口。 > 目标:应用只写业务,不重复造轮子;观感、交互、生命周期统一;避免每个应用踩一遍 fb 双缓冲 / ioctl 号 / 返回键 / 息屏交互这些坑。 --- ## 公共头模块索引(头文件拆分,2026-09-01) `patch_v2/sdk/meow_ui.h` 为**伞头**:只做 `#include` 聚合 + 注释索引,不定义新 API。所有既有 App(hello/files/ncm/launcher)只 `#include "meow_ui.h"` 即获得全部 SDK 接口,**零改动继续编译**;`meow_ui.a` 语义等价(头重组后 `.o` 逐字节一致,md5 与拆分前相同)。接口一律以 `patch_v2/sdk/meow_ui.h` 为准(与拆分前一致,未改名/未改值/未改签)。 按模块拆出的公共子头位于 `patch_v2/sdk/include/`(各自自洽、可单独 include;相互依赖最小化,需要交叉引用时用不透明句柄前向声明): | 子头 | 模块 | 内容(对应本文档小节) | |---|---|---| | `meow_ui_types.h` | 核心类型/常量 | 物理键 keycode、滑动方向、`meow_widget`/`meow_img` 不透明句柄 | | `meow_ui_draw.h` | 绘制层 | §3 绘制 + §7 时间 + §7.1 刷新率调度 | | `meow_ui_image.h` | 图片层 | §4 图片 | | `meow_ui_theme.h` | 主题/配置 | §8 `meow_theme`、MD3 语义色板、风格族 | | `meow_ui_ime.h` | 输入法 | §5.1 IME(ASCII/NUM/PINYIN) | | `meow_ui_widgets.h` | 控件层 | §5 + §5.8 基座 + §5.8.6 MD3 组件 + §5.5 W_LISTVIEW | | `meow_ui_input.h` | 输入层/生命周期 | §6 + §6.3 通用长按 | | `meow_ui_activity.h` | Activity 栈 | §6.1 三层控件模型 | | `meow_ui_zorder.h` | 绘制物注册表 / z 序 | §6.2 区域重画 | | `meow_ui_statusbar.h` | 全局状态栏/全屏 | §3.1 | | `meow_ui_dialog.h` | 模态 | §5.8.6 Dialog/BottomSheet | | `meow_ui_log.h` | 应用日志 | §9 | | `meow_ui_notify.h` | 应用通知 | §9.1 | | `meow_ui_config.h` | 应用配置读写 | §9.2 | | `meow_ui_font.h` | 字体 | §5.7 卡片字体列表 + §10 字体分层 + §10.1 矢量字 `meow_ft_*` | | `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 内部件,不随伞头发布。 --- ## 1. 问题与定案 **现状(APPS.md §3.3)**:应用必须"独占屏幕自绘"——自行 `open("/dev/fb0")`+`mmap` 绘制;自行 `open("/dev/input/event*")` 读输入;自行管返回。全部 480×720、RGB565、双缓冲 yoffset、ioctl 号 0x4600/0x4602,每个应用都要重写一遍,观感必然乱。 **定案:路线 B(框架承载)**。理由: - 现状只有 1 个示范应用能凑合;要开放多应用/第三方,裸自绘 = 爆炸。 - 渲染基建已就绪(`back_buf` + `meow_present` 无闪烁、`meow_font` 字库、`blend565` 混叠、脏区追踪),把它开放成 app 可链接的 API,是低成本高收益。 **分层决策**(分两步落地,不一次做完): - **B1 渲染/控件/图片 SDK**(本文档主体,优先):应用链接 `meow_ui.a`。依赖现成 `meow_font.a` + 内建 `stb_image`(单头文件、静态编入、解 PNG/JPEG/BMP)。**不依赖阶段 D,可独立先行。** - **B2 输入/生命周期/导航**(衔接阶段 D):把应用从"自读 event / 自管返回"解放,走 IPC + M3 导航栈。本文档 §5 给衔接设计,B2 实现归阶段 D。 --- ## 2. 架构分层 ``` ┌─────────────────────────────────────────────┐ │ MeowAPP(业务逻辑,只调 meow_ui API) │ ├─────────────────────────────────────────────┤ │ meow_ui 静态库(meow_ui.a) │ │ ├─ 绘制层 meow_draw_* │ │ ├─ 控件层 meow_widget_*(按钮/列表/滑条…)│ │ ├─ 图片层 meow_img_*(stb_image 解码) │ │ ├─ 输入层 meow_input_*(语义事件) │ ← B1:只收事件;B2:接 M3 导航 │ └─ 主题/配置 meow_theme_*(颜色/间距/字体) │ ├─────────────────────────────────────────────┤ │ 渲染后端 meow_ui_gfx(复用 launcher 机制) │ │ back_buf(RGB565) + meow_present(dirty) │ │ + meow_font(位图字库) + blend565 │ └─────────────────────────────────────────────┘ ``` **关键约定**: - 应用**只调 API**,绝不 `open fb`、`mmap`、`ioctl`、`open event`。屏幕/输入由 meow_ui 在应用 `meow_app_start()` 时建立、退出时释放。 - 渲染统一走 `back_buf` + `meow_present`:应用画到离屏,只在 `meow_ui_flush()` 时单次原子 blit 到 fb0(无撕裂无频闪,复用 M2.16 地基)。 - 颜色统一 24-bit RGB(`0xRRGGBB`),meow_ui 内部做 `rgb565()` 转换;字体绘制用 `meow_font`,字号约定 28(标题/状态栏)/20(正文/按钮)。 --- ## 3. 绘制层 API(B1) 所有绘制先画进离屏 back_buf;调用 `meow_ui_flush()` 才上屏。坐标 0,0 = 左上。 ```c /* 初始化/销毁(应用 main 里 meow_app_start 的懒加载) */ int meow_ui_init(const meow_ui_opts *opts); /* 开 fb、读几何、载字库、建 back_buf */ void meow_ui_shutdown(void); /* 帧缓冲几何(应用需自适应时读取) */ int meow_ui_width(void); /* 480 */ int meow_ui_height(void); /* 720 */ /* 基本绘制(全部画进 back_buf) */ void meow_ui_clear(uint32_t color); /* 整屏清(填背景色) */ void meow_ui_put_px(int x, int y, uint32_t color); void meow_ui_fill_rect(int x, int y, int w, int h, uint32_t color); void meow_ui_draw_rect(int x, int y, int w, int h, uint32_t color); /* 空心框 */ void meow_ui_draw_rect_round(int x, int y, int w, int h, int r, uint32_t color); /* 圆角框 */ /* 文本 */ void meow_ui_text(int x, int y, const char *utf8, int size, uint32_t color); /* size=20/28 */ void meow_ui_text_right(int right_x, int y, const char *utf8, int size, uint32_t color); void meow_ui_text_center(int cx, int y, const char *utf8, int size, uint32_t color); int meow_ui_text_width(const char *utf8, int size); int meow_ui_text_height(int size); /* = meow_font_line_height */ /* 上屏:vsync + 原子 blit 脏区(内建脏区追踪,应用无需关心) */ void meow_ui_flush(void); void meow_ui_flush_region(int x, int y, int w, int h); /* 只刷新指定区 */ /* 半透明(已有 blend565:前景按 alpha 与背景混叠) */ void meow_ui_fill_rect_alpha(int x, int y, int w, int h, uint32_t color, int alpha); /* 0-255 */ ``` > 说明:`meow_ui_text_*` 底层即 `meow_font_drawtext` + `meow_font_width_text`;`meow_ui_fill_rect_alpha` 复用 `blend565`。**模糊/毛玻璃不在 B1 承诺**(硬件无 GPU、CPU 软渲;仅在静态小区域可考虑,见 §7)。 > **M2.73 渲染残影/脏区修复(以 `meow_ui_widget.c` / `meow_ui_ime.c` / `hello_app/hello.c` 为准)**:① **局部刷新不再「清底 + 只重画触发控件」**,改为按 z 栈重画刷新区内的完整控件序列(`d_paint_region`,见 §5.8.6/§5.1)——避免「重叠区里上层浮层被下层动态控件(如进度条)实时重绘时清掉/盖住」;② **IME/面板类全屏叠加控件关闭时做背景快照/恢复**(`ime_capture`/`ime_restore`),根治关闭后屏底残留像素;③ `W_LIST` 视口裁剪(只画视口内行/分隔线/文本),根治滚动时标题栏/底部脏区。 ### 3.1 全局状态栏(系统级组件,A+C 方案并入) **定位**:状态栏是**系统级全局组件**,由 SDK 统一绘制——所有界面(launcher 菜单 + MeowAPP 画面)默认都显示,**由 SDK 运行时 `meow_ui_set_fullscreen(1)` 控制隐藏/收合**(app.json `fullscreen` / `route..fullscreen` 由 launcher 解析并注入 env `MEOW_FULLSCREEN` 预设,见 §3.1 注 [源:审计 2026-08-30] / docs/MEOWOS_SPEC_REF §3.1 A-12)。数据由 SDK 内置状态读取器抓系统快照(与 launcher 同源单点一致),非各画各的。 ```c /* 状态栏显隐(默认显示;APP 声明全屏则隐藏——沉浸) */ void meow_ui_set_statusbar(int visible); /* 1=显示(默认) 0=隐藏(全屏) */ int meow_ui_statusbar_visible(void); /* 全屏:SDK 运行时 meow_ui_set_fullscreen(1) 控制;app.json `fullscreen`/`route..fullscreen` 由 launcher 解析并注入 env MEOW_FULLSCREEN 预设(外观=状态栏收合至提示线 + 保留左缘返回/顶部提示线/下滑通知中心)。[源:审计 2026-08-30] */ void meow_ui_set_fullscreen(int on); /* 状态栏高度(px,供 APP 布局上移内容,避免被盖——status bar inset) */ int meow_ui_statusbar_height(void); ``` **绘制规则**: - `meow_ui_flush()` 上屏前,SDK 自动在顶部画状态栏(**28px**,`meow_ui_statusbar_height()` 即此值;用户定 40→28,栏内字号 20),内容:时钟 / 电量 / WiFi / 蓝牙 / 音量(与 launcher 一致)。 - 状态栏数据由 SDK 内置读取器周期刷新(~1s 轻刷,读 `/sys/class/power_supply/*`、WiFi 状态、时钟、音量寄存器——与 launcher 同一批 source)。 - **z 顺序**:状态栏是最顶层。APP 内容需 `meow_ui_statusbar_height()` 下移(inset),否则被状态栏盖住(有状态栏 OS 的通用做法)。 - **全屏**:运行时 `meow_ui_set_fullscreen(1)` 控制(或在 app.json 声明 `fullscreen` / `route..fullscreen`,由 launcher 解析注入 `MEOW_FULLSCREEN` 预设)→ 状态栏**收合至提示线** + 保留左缘返回 / 顶部提示线 / 下滑通知中心(见 docs/MEOWOS_SPEC_REF §3.1 A-13);退出恢复。 **与 launcher 统一(A+C)**:launcher 迁移到 SDK 后,其状态栏(现私有 `draw_statusbar()`)改走 SDK 这一套,全局统一、单点一致。 **注意(诚实)**:这是 SDK 的较大增强——需引入系统状态读取器 + 顶部留白(inset)协调 + 与 APP 内容 z 顺序。非全屏 APP 必须给顶部 inset,否则内容被盖。API 名实现时以 `meow_ui.h` 为准。 **「同一个状态栏」正确闭环([源:2026-08-30] 收敛修正,覆盖上文部分表述)**: - **SDK 全局状态栏 = 系统级组件,仅系统进程(launcher)链接渲染**:状态栏(`meow_ui_sys_statusbar_render` 等)**只由系统 UI 进程使用**;**App 进程不渲染状态栏**(§6.1 ③ 系统级控件仅系统内部;App 拿不到、不自绘、不可自建全局叠加/悬浮层)。 - **App 前台由系统进程叠加显示**:App 前台时状态栏作为**系统叠加层**盖在 App 内容之上(复用 fb 双缓冲叠加,见 `FRAMEWORK_DESIGN §10.2`/`§10.5`、`devdocs/plan/NOTIF_CENTER_APP_OVERLAY_PLAN.md`);与通知中心/电源菜单区别——状态栏**常驻**(仅顶条 28px,非模态面板)。 - **数据源唯一(launcher 统一喂/取)**:状态栏数据(时间/电量/WiFi/音量/亮度)由 launcher **单一权威源**喂给系统进程状态栏 → 主界面与 App 两边显示**必然一致**(根治「launcher 右侧有亮度、App 没有」);SDK 内置读取器仅作系统进程兜底,**App 侧不再渲染状态栏**。 - **右侧 = MD3 图标 + 变量值(非文本式)**:右侧为「图标+变量值」——电量图标+%、WiFi 图标(信号分档)、音量图标+%、亮度图标+%;复用 `sb_icons.h`/`SB_I_*`/`sb_blit_icon`(MD3 Material Symbols,alpha 位图,颜色由主题表提供),颜色随 `meow_theme`(`ui.style`×mode)日/夜。文本式右侧串(`statusbar_right_str` 的 `batt% WIFI 蓝牙 VOL nn% 亮度nn%`)退役/图标化。 - **顶级主题(`ui.style`)切换更换图标集**:不同风格族用不同状态栏图标集——MD3=默认图标、MIUIx=另套基础;`meow_ui_set_style` 时状态栏图标随切换(见 §8.1)。 - **对上文的影响**:「SDK 在 `meow_ui_flush()` 前自动画状态栏 / App 需 `meow_ui_statusbar_height()` 下移(inset)」的表述,在「状态栏=系统进程渲染」模型下**仅适用于系统进程**;App 侧不再自绘状态栏、顶层 28px 命中归系统(§6.1 交互分层 ③)。App 视觉是否留 inset 由系统进程叠加实现决定(实现期定,不违背本定案)。 --- ## 4. 图片层 API(B1) 内建 `stb_image`(单头文件,静态编入 `meow_ui.a`),解 PNG/JPEG/BMP,统一转 RGBA。**这是对"图片渲染"的直接答案**(背景/封面/图标)。 ```c /* 加载图片(从 RAM 缓冲或 TF 卡文件)。返回句柄,失败 NULL。 * mem: 应用可先读文件到 RAM 再喂给解码器;meow_ui 不解读自身以外的 IO。 */ meow_img *meow_img_load_file(const char *path); /* 内部读文件 + stb 解码 */ meow_img *meow_img_load_mem(const void *buf, int len); void meow_img_free(meow_img *img); /* 查询(const 正确) */ int meow_img_w(const meow_img *img); int meow_img_h(const meow_img *img); /* 访问器:取 RGBA 像素指针(4 通道 straight alpha;供取色/直读像素)。 * meow_img 对外 opaque,本函数为其唯一像素读入口;NULL 返回 NULL。 */ const unsigned char *meow_img_pixels(const meow_img *img); /* 绘制:整图 / 缩放 / 带 alpha */ void meow_ui_img(int x, int y, meow_img *img); /* 原始尺寸 */ void meow_ui_img_scaled(int x, int y, int w, int h, meow_img *img); /* 最近邻缩放 */ void meow_ui_img_alpha(int x, int y, int w, int h, meow_img *img, int alpha); /* 最常用:封面/图标九宫格或圆形裁剪(封面/专辑图等) */ void meow_ui_img_cover(int x, int y, int size, meow_img *img); /* 居中正方形裁剪成 size×size */ ``` > **裁剪/缩放**:软渲用最近邻(快),不做高质量插值(省 CPU)。封面用 `meow_ui_img_cover` 居中裁方 + 缩放到目标尺寸即可。 > > **内存瘦身**:封面/背景大图驻留会耗尽 anon(设备 RAM 仅 56MB);`meow_img_downscale(im, maxdim)` 最近邻等比缩放到最长边 ≤maxdim(原图已够小返回 NULL),调用方负责 free 旧/新图。 ### 4.1 封面取色(`meow_img_color_*`,批5) 独立公共头 `meow_img_color.h`(仿 `meow_lyric_view.h` 模式,**不随 `meow_ui.h` 伞头发布**;App/launcher 显式 `#include "meow_img_color.h"` 并单链 `meow_img_color.c`——**不并入 `meow_ui.a`**,避免并发重编/四 App 重链)。从解码后的 RGBA 像素或 `meow_img` 句柄提取「主色 + 次色」,供播放页/歌词页动态主题(背景渐变/文字 accent)。 ```c /* 从 RGBA 像素缓冲取主色+次色(纯算法;样本副采样)。返回 0=成功;-1=失败。 */ int meow_img_duocolor_rgba(const uint8_t *rgba, int w, int h, uint32_t *main, uint32_t *second); /* 从 meow_img 句柄取主色+次色(经 meow_img_pixels 读像素)。返回 0=成功;-1=失败。 */ int meow_img_duocolor(const meow_img *im, uint32_t *main, uint32_t *second); /* 由主/次色派生播放页 + 歌词页封面主题(纯函数;th 提供默认回退色)。 */ typedef struct meow_img_color_theme { uint32_t bg_top, bg_bottom; /* 播放页渐变顶/底(主/次各压暗 55%) */ uint32_t fg, fg_dim; /* 背景上可读文字色 + 次要文字色 */ uint32_t accent, accent_fg; /* 强调色(控件/进度)+ 其上可读图标色 */ meow_lyric_view_colors lyr; /* 歌词页 set_colors 覆盖(scrim=主色压暗 α176) */ } meow_img_color_theme; void meow_img_color_derive(uint32_t main, uint32_t second, const meow_theme *th, meow_img_color_theme *out); ``` - **取色语义**:输入 RGBA(stb_image 输出,4 通道 straight alpha),最多遍历 ~3.6k 采样(内部自适应副采样步进,等效缩略图成本,大图/缩略图成本恒定);色域分桶 **4×4×4=64**(RGB 各 4 段),跳过近黑(luma<24)/近白(luma>244)/透明(alpha<64),按「像素数 + 饱和度」加权(偏鲜艳);**主色**=加权最大桶中心;**次色**=与主色 RGB 色距最大的显著桶(≥主桶 1/7≈14%)中心;无显著桶 → 次色=主色缩暗 2/3(渐变互补)。返回 -1=参数非法/有效采样不足(调用方回退全局 `meow_theme`)。 - **派生 `meow_img_color_theme`**:`bg_top/bg_bottom`(主/次各压暗 55% 防刺眼 + 保文字可读)、`fg`(主/次平均亮度选黑/白)、`fg_dim`(向背景靠拢的次要文字)、`accent=次色`、`accent_fg`(accent 上可读图标色)、`lyr`(歌词页 `meow_lyric_view_set_colors` 覆盖:scrim=主色压暗(α176)、cur=fg,gray 留 0 由 lyric_view 以 cur 自动递减)。 - **经 `meow_img_pixels` 读像素**(本 TU 已上收 agent-402 遗留的「局部补全结构体」):`meow_img_duocolor` 不再局部补全 opaque 布局,改用 §4 正式访问器。 --- ## 5. 控件层 API(B1) 内置常用控件,观感统一(MD3 优先 / MIUIx 备选,见 §6 主题)。控件状态由 meow_ui 维护,绘制走 back_buf。 ```c typedef struct meow_widget meow_widget; /* 创建/销毁;控件 layout 由应用每次 flush 前调用 meow_widget_draw 绘制 */ meow_widget *meow_btn_create(int x, int y, int w, int h, const char *label); meow_widget *meow_slider_create(int x, int y, int w, int min, int max, int val); meow_widget *meow_switch_create(int x, int y, int on); meow_widget *meow_list_create(/* 数据源回调 */); void meow_widget_free(meow_widget *w); /* 绘制 */ void meow_widget_draw(meow_widget *w); /* 状态/取值 */ void meow_slider_set(meow_widget *s, int val); int meow_slider_get(meow_widget *s); void meow_switch_set(meow_widget *s, int on); ``` > 控件命中(触摸点是否落在控件内)由输入层语义事件驱动,应用在 `meow_ui_on_tap(x,y)` 回调里做分发——见下节。 > **B1 先做最小控件集**(按钮/滑条/开关/列表四件套),其余(下拉、进度圈、通知卡片)随阶段 B 通知中心 / 流媒体客户端需要用再加。 ### 5.1 输入法(IME)—— 内置触屏软键盘 **背景**:R3 Pro II 无独立键盘设备(仅触摸 goodix-ts + 物理按键 md-gpio-keys / jz adc keyboard),无 USB/蓝牙 HID 键盘栈。**任何需要文本输入的应用(输入框/搜索/设置项)都依赖 IME,且必须由 SDK 内置**——应用绝不能自己实现键盘,否则观感/行为不一、重复造轮子。 **形态**:系统级单例软键盘,作为控件层的一个特例(`meow_ime`),应用调 `meow_ui_text_input()` 弹出,键盘覆盖在应用之上,由 SDK 绘制与分发(复用 `back_buf` + `meow_present` 无闪烁地基,不干扰业务渲染)。 **B1 范围**:**英文 QWERTY 键盘**(数字+字母+符号),覆盖字符输入、退格 Backspace、空格、回车/完成。 **不在 B1**:中文拼音/词库选字——复杂度高、依赖词库,留到后期独立 IME(流媒体客户端/中文搜索需要时再上)。B1 的 IME 只保证"能输入 ASCII 文本"。(**后注**:中文拼音 IME 已落地——M2.88 agent-229 `meow_pinyin.c` 静态表 403 音节+329 词,组合条+候选栏+翻页+中英切换+九键数字盘,流媒体客户端搜索已接 `MEOW_IME_PINYIN`。) ```c /* 弹出软键盘,输入到应用提供的缓冲。返回 1=确认(fn=enter),0=取消(fn=esc/背景点)。 * 回调驱动:输入变化由 SDK 内部循环处理,应用只需在 done 时读 out。 */ typedef enum { MEOW_IME_ASCII, MEOW_IME_NUM } meow_ime_mode; /* 全键盘/数字键盘 */ typedef struct { meow_ime_mode mode; char buf[256]; /* 应用提供的输入缓冲(初始值可预填,如 Wi-Fi 密码) */ int maxlen; /* 回调:每次字符变化回调一次(用于实时预览/校验) */ void (*on_change)(const char *text, void *ctx); void *ctx; } meow_ime_ctx; /* 阻塞:弹出键盘直到确认/取消。返回 1=确认(buf 已回填),0=取消。 */ int meow_ui_text_input(meow_ime_ctx *ctx); /* 非阻塞变体:返回后应用在 on_tick 里轮询 meow_ime_active()/meow_ime_text() */ void meow_ui_text_input_async(meow_ime_ctx *ctx); int meow_ime_active(void); const char *meow_ime_text(void); ``` **布局(480×720 竖屏)**:键盘占底部约 40% 高(~290px),上方为输入预览行 + 候选区。键位 40×40(够 touch target),触屏点按输入。 **物理键辅助**(兼容未来硬件键/可访问性):方向键移动光标、播放/确认键=选择、音量=翻页。B1 以触摸为主,物理键作为可选加分项(SDK 内实现)。 **[B1 后续 / 不阻塞]**:剪贴板、长按重复等仍留后续。**中文拼音 IME 已落地**(M2.88,agent-229 · `e9b6b69`,设计稿 `devdocs/plan/IME_PINYIN_DESIGN.md`):`meow_pinyin.c` 静态表词库(403 音节 + 329 词,rodata ~20.5KB 零堆)+ 音节切分 DFS+联想引擎,SDK 经 `MEOW_IME_PINYIN` 尾部追加零 ABI 破坏接入(组合条 + 候选栏 + 翻页 + 中英切换,英文零回归)。**数字键盘九键布局**(M2.89,`21ff5e8`):NUM 模式改居中九键(1-9 自上而下 3×3,0 垫底居中,底行 ESC|0|退格+OK),符号行保留;宿主 test_ime_fix/test_pinyin 全 PASS。 > **实现对齐(M2.73,`meow_ui_ime.c`,以源码为准)**:实现与上表**关键出入**——① 键盘占底部约 **300px**(`KB_TOP=420`,720-420=300),键位 **44×36**(非 40×40);② **回显行 = 实时输入回显**(把 `g_ime_buf` 拼下划线 `"%s_"` 画在面板顶部条),**非候选/联想区**,候选区原留给中文输入法;**中文拼音 IME 已落地**(M2.88 agent-229,候选栏/组合条见 `meow_ime.c`/`meow_pinyin.c`);③ 关闭时**键盘区背景快照/恢复**(`ime_capture`/`ime_restore`,根治关闭后键盘像素残留);④ **配色走主题**(`meow_theme` 语义色,深/浅自动切),不硬编码黑底白键;⑤ **shift 切换大小写**;⑥ 阻塞 `meow_ui_text_input` 打字循环喂 `meow_ui_activity_ping`(重置息屏计时,进度条不卡死);⑦ **OK(回车)= 提交并关闭**(拷贝到 app `ctx->buf`)、**ESC(X)/ 点键盘外背景 = 取消并关闭**;非阻塞 `meow_ui_text_input_async` 用 `meow_ime_active()`/`meow_ime_text()` 轮询(同 close 语义)。上表「布局 40×40 / 物理键辅助 / 候选区」为设计稿表述,已按实际覆盖。 --- ### 5.2 横向分页(横向滑动切换页面)设计模板 [源:2026-08-30] > **性质**:SDK 设计模板,供后续 APP 主页/子页**按需选用**;参照 HibyOS 播放器主页横划进播放页的实现思路。**B1 不实现**,本小节只列设计要点与接口草案(标注「草案/待定案」),供后续落地时对齐,避免与既有手势/控件约定冲突。 **模板目标**:多页横向滑动切换,流畅不卡;供 APP 主页/子页复用。 **1) 手势分流(关键,与既有手势不冲突)**:按**起始位置**分流,三者互斥—— - 触点落在**屏幕左缘(≤24px)热区** → SDK **左滑返回**(不动画,`on_swipe(LEFT)`,见 §6 与 spec-ref §5.1)。 - 触点落在**中部**左/右滑 → **页切换**(页视图接管)。 - **顶部**下滑 → **通知中心**(不夺走既有顶部热区)。 > 命中判定:先判断按下 x 落在左缘/顶部热区还是中部,再定手势语义;互斥,不与既有「左缘返回 + 顶部下滑通知中心」冲突。 **2) 预渲染快照缓存(抗卡顿核心)**: - 相邻页各预渲染一张 `back_buf` 快照(页小数据 + 内容缓存)。切换时**只 blit 快照**,**不重跑布局/控件绘制**(页多不卡的关键)。 - 缓存上限 = 当前页前后各 1 页(共 3 张 ≈ 3 × 屏宽 × 高 × 2B;480×720 RGB565 每张 ≈ 0.66MB → 3 张约 2MB,远低于 14MB 内存预算;按更保守 2MB/张 × 3 ≈ 6MB 口径亦可接受)。 - **LRU 淘汰**远页;回滚重建时显示「加载中」轻提示。 **3) 拖动 / 吸附**: - 手指**跟手平移当前页 + 露出下一页边缘**:单帧内多次 blit 位移 + **一次** `meow_present`(vsync 门控 ≤60 帧,**不做逐帧重排版**)。 - 松手**过阈值** → 补 2-4 帧平移动画**吸附相邻页**;**未过阈值** → 回弹当前页;**首/末页**不滑(或橡皮筋回弹)。 **4) 性能要点**:切换 = 快照 blit + 位移平移(**内存拷贝远快于重绘**) + 节流 present;拖动时**零 UI 重算**(不触发布局/控件重绘)→ 页多不卡。 **5) SDK 建议 API 草案(「草案/待定案」,不实现)**: ```c typedef struct meow_page_view meow_page_view; /* 数据源回调:页数 / 每页渲染回调 */ typedef struct { int (*page_count)(void *ctx); /* 总页数 */ void (*draw_page)(meow_page_view *pv, int page, uint16_t *snapshot, void *ctx); /* 渲染该页到快照 buf */ void *ctx; } meow_page_view_source; meow_page_view *meow_page_view_create(const meow_page_view_source *src, int x, int y, int w, int h); void meow_page_view_free(meow_page_view *pv); int meow_page_view_set(meow_page_view *pv, int page); /* 跳页(快照重建 + 吸附动画) */ int meow_page_view_current(const meow_page_view *pv); /* 当前页 */ ``` > SDK **内部**处理:手势分流(**左缘返回除外**,仍归 SDK 手势层 `on_swipe(LEFT)`)、快照缓存、平移吸附、LRU 淘汰、加载中提示。实现时以 `meow_ui.h` 为准。 **6) 关联**:与既有「**全局左缘返回 + 顶部滑条 + 状态栏收合/通知中心**」手势约定一致(spec-ref §5.1/§3.1)——左缘返回仍为全局手势(`on_swipe(LEFT)`/`on_swipe_progress`);页视图**只接管中部横滑**;顶部下滑仍归通知中心。三者按起始位置分流,互斥不冲突。 ### 5.3 SDK 可复用设计模板清单 [源:2026-08-30] > **性质**:把 MeowOS 应用开发里反复出现的**界面结构**抽象为可复用的设计模板(设计要点 + 交互约定 + 绘制/数据模型 + 性能注意 + 复用组合),供各 App 与设置页按需选用、观感统一。**本小节是目录**:列出全部模板及优先级/状态;已展开为设计模板者见对应小节。**所有模板与既有手势/控件约定一致**(见 §5.2/§6/spec-ref §5.1/§3.1),且相互可组合。 | # | 类别 | 模板 | 一句用途 | 优先级/状态 | |---|------|------|---------|------------| | ① | A | 分组设置表单 | 分组标题 + 行(开关/滑条/下拉/子菜单/信息),数据源 = setting 键 | ✅ **A+B 先行**(已展开 §5.4) | | ② | A | 列表+选择 | 行数据源回调(图标+标题+副标题+右侧操作),滚动/单选/多选/搜索 | ✅ **A+B 先行**(已展开 §5.5) | | ③ | B | 弹窗/对话框 | 确认/选择/输入/信息,模态/非模态,返回/电源键关闭 | ✅ **A+B 先行**(已展开 §5.6) | | ④ | B | 顶/底部下拉面板 | 快捷设置/通知中心/侧边栏,收合动画、内容懒载 | ✅ **A+B 先行**(已展开 §5.7) | | ⑤ | C | 媒体控制/播放页 | 进度/控制/封面/歌词滚动 | 🔵 随播放器 app 细化 | | ⑥ | C | 沉浸内容页 | 全屏/左滑返回/图片缩放/歌词随乐 | 🔵 随播放器 app 细化 | | ⑦ | C | 进度/加载 | 进度条/转圈/骨架屏/下拉刷新 | 🔵 随对应 app 细化 | | ⑧ | D | 卡片/磁贴 | 圆角卡片/图标宫格/应用网格 | 🔵 随 MD3 细化 | | ⑨ | D | 导航栏/工具栏 | 顶标题+返回/底栏 tabs/面包屑 | 🔵 随导航细化 | | ⑩ | D | 列表项布局 | 图标+文本+右侧操作,点击/长按/滑删 | 🔵 随 MD3 细化 | | ⑪ | E | 拖拽排序/长按菜单/时间轴 | 拖拽排序/长按菜单/时间轴 | ⚪ **E 后置(可选)** | **优先级 / 状态说明**: - **A+B 先行(近期展开,§5.4-§5.7)**:设置表单/列表/弹窗/面板是现阶段 MeowOS 应用(设置、通知、App 列表、WiFi)最常用的四类结构,先定下来沉淀范型,落地时可复用 launcher 已调通的 `s_row_*`/`s_dd_modal` 逻辑。 - **C-D 随对应 app/MD3 细化**:媒体/沉浸/加载随播放器类 app 落地时细化;卡片/导航/列表项随 MD3 观感统一时细化——避免过早抽象、先让 app 跑通。 - **E 后置**:拖拽排序/长按菜单/时间轴为可选增强,非首批通用结构,后续需要时再立项。 - **已含横向分页**:多页横向滑动已在 §5.2 单独展开,**不重复**列入本清单。 - **可扩展**:本清单按需增项(新模板补一行;若展开则新增一节),编号后续连续。 > **手势一致性(此处只述结论,不重复展开)**:全部模板与既有「**全局左缘返回 + 顶部滑条 + 状态栏收合 + 通知中心热区**」手势约定一致(spec-ref §5.1/§3.1、本文件 §5.2/§6)——左缘 ≤24px 内划≥90px 抬手=返回(`on_swipe(LEFT)`/`on_swipe_progress`);顶部下滑=通知中心;**中部**横滑/竖滑才归内容。各模板**不重定义**这些全局手势,只在其上做内部命中分流。 --- ### 5.4 设计模板 A① 分组设置表单 [源:2026-08-30] > **参照**:`patch_v2/meow_launcherd/settings_page.c` 设置页(`SMENU` 表 + `srow_t` + `draw_settings_page`/`run_settings_page`)。**性质**:SDK 设计模板,供 App/设置页按需选用;`meow_ui.h` 现状控件最小集(按钮/滑条/开关/列表,§5)只提供单控件,本模板是把它们**组织成「分组设置表单」的结构范型**。API 为「草案/待定案」,不实现。 **摘要**:一页设置表单 = 若干**分组标题行** + 若干**设置行**。每行 `label + 控件`,控件类型决定取值方式——开关 / 滑条 / 下拉(枚举·预设)/ 子菜单 / 信息 / 列表。数据源 = **setting 键**(经 `meow_settings_*` 读写,见 spec-ref §4),UI 只绑定键、不直接触存储文件。 **关键交互**: - **分组标题行**(`key=NULL`)仅展示分组名(如「显示/网络/电源/字体主题」),不可选中。 - **行命中分发**:触摸命中某行后按行控件类型分发—— - **开关**:`meow_settings_set_switch(key, !on)`(或 `set_bool`),行右侧画开关。 - **滑条**:按下/拖动沿 track 取值 → `meow_settings_set_int(key, v)`(后台 clamp 到 min..max + 同步缓存),行内画 track+thumb+当前值。 - **下拉(枚举/预设)**:点行 → 弹**单选**弹窗(复用 B③ §5.6)→ `meow_settings_set_enum/set_str(key, idx)`。 - **子菜单**(如字体 family→weight 两级):点行 → 二级列表(复用 A② §5.5 容器)→ 选中写回串值(如卡根相对路径 `MeowOS/fonts///font.png`)。 - **信息/列表**:点行 → 打开只读详情或子列表(如「关于本机」「WiFi 连接状态」「附近WiFi」)。 - **滚动**:整行步进(`SROW_H`),选中行钳制在视口内;列表拖动滚动(纵向位移超阈值判滚动、非 tap)。 - **返回/取消**:左缘返回手势回上级(全局手势,不由本模板重定义)。 **绘制/数据模型**: - **行描述结构**(参照 `srow_t`):`{label, key, ctrl(min,max,step), presets/preset_labels/preset_n, net_list}`;`ctrl ∈ {HEADER, SWITCH, SLIDER, ENUM, PRESET, INFO, LIST}`;`key=NULL` 表示分组标题行。 - **单卡片 + 固定行高**:`SET_CARD_X/W/Y/H` + `SROW_H`(行高统一,便于整行滚动对位与裁剪;视口高取行高的整数倍)。 - **可见性**:`s_row_visible(i)`(如多字体 6 个 class 行仅在开关开时可见)→ 压缩索引 `s_vis_of/vis_at`;标题行恒显。 - **值格式化**:`s_fmt_*`(秒→「×分×秒」、分钟→「HH:MM」、枚举→中文标签、预设项显示名)。 - **命中/滚动换算**:`s_row_at_content_y(cy)`(content 坐标→行号);滑条单独记录 track 纵坐标供拖动命中。 **性能注意**: - **局部刷新**:静态展示不主动刷新,仅在交互时经 `meow_dirty` 区标脏 + `meow_ui_flush_region` 只刷变化区(滑条拖动/开关切换/选中行高亮)。 - 滑条拖动**实时** `set_int`(后台 clamp、同步缓存,不逐 1% 落盘),松手才落盘;避免拖动时每帧写文件。 - 滚动用整行步进 + 区间钳制,不做逐像素重排。 **复用组合**:A① 是容器;行内容用 A②(列表项布局)/ B③(下拉弹窗)+ SDK 控件(`meow_btn/slider/switch`);设置页顶部「快捷开关」组可再被 B④ 面板复用。 **API 草案(「草案/待定案」,不实现;实现以 `meow_ui.h` 为准)**: ```c typedef enum { MEOW_FROW_HDR, MEOW_FROW_SWITCH, MEOW_FROW_SLIDER, MEOW_FROW_ENUM, MEOW_FROW_PRESET, MEOW_FROW_INFO, MEOW_FROW_LIST } meow_frow_type_t; typedef struct { const char *label; const char *key; /* setting 键,经 meow_settings_* 读写;NULL=分组标题行 */ meow_frow_type_t ctrl; /* 控件类型 */ int min, max, step; /* SLIDER 取值域/步进 */ } meow_frow_t; meow_widget *meow_ui_setform_create(const meow_frow_t *rows, int n, int x, int y, int w, int h); int meow_ui_setform_sel(meow_widget *f); /* 当前选中行 */ void meow_ui_setform_scroll(meow_widget *f, int offset); /* 整行步进滚动 */ int meow_ui_setform_hit_row(meow_widget *f, int x, int y);/* 命中行号(-1=分组标题/卡片外) */ ``` > 说明:`meow_ui_setform_*` 与 launcher 的 `srow_t`/`SMENU`/`run_settings_page` 同构(一行=label+key+控件),实现期可直接借已调通逻辑。**区分「行描述表」与「每行当前值」**——值一律经 `meow_settings_get_*` 读取,不缓存在行结构里(避免双写漂移)。 --- ### 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;虚拟化层(固定 item 池 + 多行富 item + 完全自绘 + 段头 + 空态),2026-09-02,见 §5.5「虚拟化层」。)** **摘要**:可滚动选择容器。**行数据源回调**逐行给出(图标 / 标题 / 副标题 / 右侧操作 / 是否可选),容器负责绘制/滚动/选中/命中;支持**单选、多选、长按**(上下文操作)。适合「已保存网络 / 附近WiFi / 蓝牙设备 / 歌曲列表 / 设置子菜单」等。 **关键交互**: - **行内容**:图标(可选 `meow_img*` 或内置符号)+ 标题 + 副标题(次要信息,如 SSID/设备名/时长)+ **右侧操作**(如箭头/开关/详情钮)。点整行=选中/进入;点右侧操作=该行动作。 - **单选/多选**:`mode` 决定——单选(高亮当前 + 确认回填);多选(复选标记);「无选择」= 纯入口列表。 - **长按**:长按行 → 上下文菜单(如删除/重命名),或不弹菜单仅标记长按态(复用 B③ 小弹窗)。 - **搜索/过滤**:顶部搜索框(IME 输入,§5.1)→ 数据源回调按关键字过滤重算行数(B1 IME 仅英文/数字)。 - **滚动**:局部 dirty;整行步进或像素滚动均可;首/末行钳制(橡皮筋或截断)。 **绘制/数据模型**: - **行数据源回调**(`meow_list_source` 的结构化扩展): ```c typedef struct { const meow_img *icon; /* 可选 */ const char *title; /* 主文本 */ const char *subtitle; /* 副文本,可空 */ int right; /* 右侧操作类型(无/箭头/开关/详情钮) */ int enabled; /* 可选中性 */ } meow_list_item; typedef int (*meow_list2_source)(int idx, meow_list_item *out, void *ctx); /* 返回 1=有该项,0=越界 */ ``` - **容器**:视口(x,y,w,h) + 总行数(count,可再查询更新) + 滚动偏移;`count` 由数据源动态变化(搜索过滤/懒加载)。 - **命中**:行 = `y/row_h`;**右侧操作细分命中**(行右侧 1/3 区域判操作,其余判整行)。命中判定避免 goodix 时序问题——**按下不锁原点**、抬手时用最新有效坐标(参照 §5.6 与 meow_touch 语义)。 - **选中态**:单选高亮条 / 多选复选标记,用主题 accent 色。 **性能注意**: - **局部 dirty**:滚动/翻页只刷变化行(`meow_ui_flush_region` 行块或视口带),不整屏重绘;数据源回调每行**轻量填充**,不在回调里做 IO(大图标用缓存/延迟加载)。 - **懒加载**:行多(如几百首歌曲)时数据源按需返回,仅落入视口 ± 预取若干行才真正加载。 - **内存**:行数据瞬时(回调填充到栈上 `meow_list_item`),不长期持有全部行对象;图标句柄归属调用方。 **复用组合**:A② 是 A① 的子菜单/列表行、B③ 的「选择」数据源、播放页歌单的通用底座;选中结果回填到 A① 的下拉项。 **API 草案(「草案/待定案」,不实现;实现以 `meow_ui.h` 为准)**: ```c typedef enum { MEOW_LIST_SEL_NONE, MEOW_LIST_SEL_SINGLE, MEOW_LIST_SEL_MULTI } meow_list_sel; typedef int (*meow_list2_source)(int idx, meow_list_item *out, void *ctx); meow_widget *meow_ui_listview_create(int x, int y, int w, int h, int count, meow_list2_source src, void *ctx, meow_list_sel mode); void meow_ui_listview_set_sel(meow_widget *lv, int idx); /* 单选/多选当前选中集 */ int meow_ui_listview_get_sel(meow_widget *lv, int *out, int max); void meow_ui_listview_scroll(meow_widget *lv, int offset); 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 只能自行造「滑动窗口」补载(如 ncm playlist.c 的 `pl_maybe_ensure` + `appstate_song_copy`)。 > 本层把「固定 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。 #### 左滑删除(swipe-delete,M7.3 通知中心缺口 #9) > **定位**:给 `meow_listview` 的行加**横向左滑手势 + 删除/回弹动画**(配合行自绘 `set_item_draw`)。 > SDK 在自绘行前把该行横向偏移 `swipe_dx` 传给 `x`,并画出右露的**删除区**(主题 `error`/`onError`); > 手势由 App 在 `on_touch_move` 喂三级,**不修改** `tap_at`/滚动语义(`meow_ui_widgets.h` 追加)。 **手势判定表**(`swipe_begin/move/end` 三级,App 在触摸回调里喂): - **横向** `|dx|>|dy|` 且超容差 = 滑动(`move` 返回 1,本列表消费——App 不要再去 `scroll_by`); - **纵向** `|dy|>|dx|` 且超容差 = 滚动手势优先(返回 0,取消本行 swipe,App 照常滚动); - **点按** 无位移(`end` 时未 started)= 行点击(返回 0,App 照常 `tap_at`)。 **动画**:超阈值 → 行滑出动画(`meow_anim`,§12.2),结束后回调 `on_delete(idx)`;未超 → 回弹。 `swipe_offset` 返回当前滑动行的 `dx`(<0=左移),自绘行绘制时据此位移;`swipe_active`=1 表示有行处于滑动/动画中。 **API 一览(`include/meow_ui_widgets.h`,§5.5)**: ```c typedef void (*meow_listview_swipe_fn)(int idx, void *ctx); void meow_listview_set_swipe_delete(meow_widget *lv, int enabled, meow_listview_swipe_fn on_delete, void *ctx); int meow_listview_swipe_begin(meow_widget *lv, int x, int y); /* touch-down:1=命中可滑行行(喂后续 move) */ int meow_listview_swipe_move(meow_widget *lv, int x, int y); /* touch-move:1=消费(横向);0=让位(取消/纵向) */ int meow_listview_swipe_end(meow_widget *lv, int x, int y); /* release:1=消费(删/回弹动画);0=未滑动(tap) */ int meow_listview_swipe_active(meow_widget *lv); /* 1=有行处于滑动/动画中 */ int meow_listview_swipe_offset(meow_widget *lv); /* 当前滑动行的 dx(<0=左移;自绘行画用) */ ``` > **兼容/内存**:纯手势 + 动画层(配合 `set_item_draw` 自绘),不新增常驻结构;删除动画走 §12.2 > `meow_anim` 静态池(≤16),热路径零 malloc;与行点击/滚动互不冲突(见上判定表)。 > **M7 用途**:通知中心(M7.3)通知条左滑删除(缺口 #9)。 --- ### 5.6 设计模板 B③ 弹窗/对话框 [源:2026-08-30] > **参照**:`settings_page.c` `s_dd_modal()`(WiFi 列表 / 关于本机 / 字体子菜单共用同一模态)。**性质**:SDK 设计模板,覆盖 **确认/选择/输入/信息** 四种;模态/非模态;返回/电源键关闭;遮罩。`meow_ui.h` 无通用弹窗,本模板为「草案/待定案」。 **摘要**:在应用之上弹出**居中卡片**(遮罩压暗 + 圆角卡片 + 标题 + 内容 + 操作),用于打断当前流程的一次性决策。按用途分四型:确认(是/否)、选择(单选列表,可复用 A② 数据源)、输入(复用 IME §5.1)、信息(只读展示,如「关于本机」)。 **关键交互**: - **模态 vs 非模态**:模态=遮罩吞掉背景输入、打开期间应用主界面不可交互(返回/电源关闭);非模态=不遮罩、可同时操作背景(如播放页浮层)。默认模态。 - **关闭途径**:① 点操作钮确认/取消;② 点遮罩外=取消;③ 返回键(左缘返回手势 / 虚拟返回键)→ 取消;④ **电源键单击** → 确认当前高亮项(launcher `s_dd_modal` 的电源抬起=确认语义)。音量键上下移动高亮。 - **选择弹窗**:选项列表高亮 + 预勾选(`cur` 非 -1 时);`cur=-1` = 只读展示不预选(「关于本机」)。 - **物理键可访问**:音量=移动高亮、电源=确认、返回=取消(借鉴 `s_dd_modal` 的 `kp_*` 键轮询)。 **绘制/数据模型**: - **绘制**(参照 `s_dd_modal`,即画即显一次成型): 1. 全屏遮罩 `meow_ui_fill_rect_alpha(0,0,W,H, 0x000000, 150)`(压暗)。 2. 居中卡片 `meow_gfx_fill_round(d_x,d_y,d_w,d_h, 16, surface_container)` + `meow_ui_draw_rect_round(..., 16, outline)` 描边;圆角 16。 3. 标题(theme `on_surface`,20 号)+ 选项行(行高 ~48;选中行 `primary` α36 高亮 + 白字;预勾选项右侧画小方块)。 4. **不压状态栏**:卡片 y 钳制 `d_y ≥ meow_ui_statusbar_height()`;底部留 ≥20px。 - **尺寸**:宽 ~348、居中;高 = 标题 + n×行高 + 内边距;内容过多滚动(或扩容)。 - **数据模型**:`{title, item[], n, cur, type}`;选择型=选项数组;信息型=多行文本;输入型=携 IME ctx。 - **命中**:抬手时用**最新有效坐标**(不锁按下原点,goodix 免疫);卡片内命中→选项;卡片外→取消。打开前**双排空触摸残留**(`s_dd_modal` 的 open 后 read 排空→40ms→再排空,吸收 goodix 抬起长尾)。 **性能注意**: - **即画即显**:弹窗打开时**一次性**画好遮罩+卡片+选项,再进事件循环(`s_dd_modal` 初始绘制一次,仅高亮变化才重绘);不每帧重画静态内容。 - 重绘只刷变化区(切换选中高亮时,仅重画遮罩+卡片或行块 `flush_region`)。 - 无动画(B1 无 tween);如需淡入淡出,用 `fill_rect_alpha` 单次叠加以免每帧重算。 **复用组合**:B③ 是 A① 下拉(enum/preset 选中)、A② 长按上下文菜单、B④ 面板关闭确认、电源菜单的**通用弹窗底座**;输入型接 IME §5.1;选择型数据源同 A②。 **API 草案(「草案/待定案」,不实现;实现以 `meow_ui.h` 为准)**: ```c typedef enum { MEOW_DLG_CONFIRM, MEOW_DLG_SELECT, MEOW_DLG_INPUT, MEOW_DLG_INFO } meow_dlg_t; typedef struct meow_dialog meow_dialog; meow_dialog *meow_ui_dialog_create(meow_dlg_t t, const char *title); void meow_ui_dialog_set_items(meow_dialog *d, const char *const *item, int n, int cur); void meow_ui_dialog_set_ime(meow_dialog *d, meow_ime_ctx *ime); /* INPUT 型 */ void meow_ui_dialog_show(meow_dialog *d); /* 阻塞到确认/取消 */ int meow_ui_dialog_result(meow_dialog *d); /* 选中=选项下标;输入=IME buf;确认=1/0 */ void meow_ui_dialog_close(meow_dialog *d); ``` > 说明:模态=默认;非模态=`show` 非阻塞变体(启动后由 `on_tick` 驱动)。返回/电源关闭语义**沿用全局约定**(返回=取消;电源=确认当前高亮),本模板不重定义。 --- ### 5.7 设计模板 B④ 顶/底部下拉面板 [源:2026-08-30] > **性质**:SDK 设计模板,覆盖**顶部下拉**(快捷设置/通知中心)与**底部面板**(操作抽屉/侧边栏),带**收合动画**与**内容懒载**。**关键前提**:与既有「全局左缘返回 + 顶部滑条 + 状态栏收合 + 通知中心热区」手势一致(spec-ref §5.1/§3.1、A-13)——本模板**不夺走**「顶部下滑=通知中心」,而是**按起始位置/触达途径与既有热区分流**。API「草案/待定案」。 **摘要**:沿某种边缘滑出/推入的**非全屏浮层**。顶/底部下拉面板 = 手指从对应边缘向内滑 → 面板从边缘滑出(带收合动画),内容按需加载。典型:快捷设置(亮度/音量/开关)、通知中心(列表)、底部操作抽屉;可再收合。 **关键交互**: - **手势分流(互斥,与既有约定一致)**: - **左缘 ≤24px 向内划** → **全局左缘返回**(`on_swipe(LEFT)`,SDK 兜底;本面板**不**接管)。 - **顶部边缘下滑** → **通知中心**(既有热区,`on_swipe(DOWN)`/顶部热区;全屏下热区缩小但仍保留,见 spec-ref §5.1/A-13)。 - **底部边缘上滑** → **底部面板**(本模板新增;底部边缘无既有冲突)。 - **快捷设置**:为不与通知中心冲突,**待定案**——建议从通知中心二级拉出、或屏幕右上角/右侧缘单独热区触达、或「通知中心内点箭头展开」。**不新占顶部下滑**(避免与 §5.1 冲突)。 - **侧边栏**(可选):右缘向左滑出(右侧无既有手势,安全)。 - **收合动画**:跟手平移面板(手指拖多长露多长),松手**过半阈值** → 补 2-4 帧**短插值**吸附到位;**未过** → 回弹。动画只在松手时做一次,不做逐帧几何重算(B1 无 tween;短插值=有限帧序列)。`[落点 2026-08-30]`:launcher 通知中心(顶部面板)已按此模板实现「跟手下拉 + 松手锚定」(progress=手指位移映射,40% 阈值补到位/回弹收回,复用快照 blit 平移,不逐帧重排)——见 NOTIFICATION_SYSTEM §9(⑨)与 devdocs NOTIF_CENTER_UI_PLAN §16。 - **内容懒载**:面板**首次拉出**才构建内容(或预建空容器),避免打开即全量绘制;面板内滚动局部 dirty。 - **关闭**:点面板外关闭 / 再划回边缘收合 / 返回键关闭(模态时)。 **绘制/数据模型**: - 面板 = 一个**矩形区域**(顶/底边缘滑出),内容区 + 头部(把手/标题/关闭钮)。绘制走 back_buf,`meow_ui_flush_region` 只刷面板交汇区。 - **内容**:快捷设置=一组开关/滑条(复用 A① 控件);通知中心=列表(复用 A②);操作抽屉=一组按钮(复用 `meow_btn`)。 - **数据模型**:`{edge, x, y, w, h, content_widget, collapsed, anim_t}`;内容视图句柄(可为 A①② 或自定义);懒载=首次展开时创建内容视图。 - **布局 inset**:顶部面板 y 钳制 `≥ meow_ui_statusbar_height()`(非全屏不压状态栏);全屏时顶部热区缩小。 **性能注意**: - **内容懒载**:展开才构建;收合后保留视图(缓存)或销毁(按需)。 - **收合动画**:短插值(≤4 帧),只 blit 面板+交汇带,**零内容重排**(内容已预渲染到 offscreen 或复用快照,与 §5.2 快照思想同源)。 - **局部 dirty**:面板滑入滑出用 `meow_ui_flush_region` 刷边界带;内容行变化刷行块。 **复用组合**:B④ 顶栏=快捷设置(A① 控件组)+ 通知中心(A② 列表);底部面板=操作抽屉(`meow_btn` 组 / A② 动作行);侧边栏=A② + 导航。 **API 草案(「草案/待定案」,不实现;实现以 `meow_ui.h` 为准)**: ```c typedef enum { MEOW_PANEL_TOP, MEOW_PANEL_BOTTOM, MEOW_PANEL_EDGE_R } meow_panel_edge; typedef struct meow_panel meow_panel; meow_panel *meow_ui_panel_create(meow_panel_edge e, int x, int y, int w, int h); void meow_ui_panel_set_content(meow_panel *p, meow_widget *content); /* 懒载:首展创建 */ int meow_ui_panel_expand(meow_panel *p); /* 1=展开(短插值动画) 0=收合;返回位移 */ int meow_ui_panel_is_open(const meow_panel *p); void meow_ui_panel_set_collapsed(meow_panel *p, int collapsed); ``` > 说明:顶部面板**默认不占用「顶部下滑=通知中心」**(§5.1)——顶部热区归属通知中心;快捷设置在通知中心内二段展开为**待定案**(见关键交互),实现前须列出可与左缘返回/通知中心分流的具体触达途径,勿凭空抢占。 --- ### 5.8 控件层架构升级:统一控件 + 扩展接口 + 通用化 [源:2026-08-30] > **性质**:控件层的**演进蓝图**(后续实现权威)。把现有最小控件集(§5)与设计模板(§5.2-§5.7)收敛到**统一控件架构**——一个 `meow_widget` 基座承载「标准控件(SDK 内置)」与「自定义画布(App 提供 render/hit 回调)」,二者**同构**;并给出**三大通用化设计**:① 可组合布局(Row/Column/Box + Spacer);② Modifier 式通用 Options(`meow_widget_opts`,通吃所有控件);③ 统一状态机(enabled/selected/focused/pressed,触摸 + 物理键焦点)+ MD3 完整语义色板(日/夜自动切换)。 > > **✅ 已实现(M2.70/M2.71,详见 §5.8.8 分批)**:第一批 = 通用基座(§5.8.1-§5.8.5,M2.70/0.7.65);第二批 = Material 组件子集(§5.8.6,M2.71/0.7.66)。实现以 `meow_ui.h` / `meow_ui_widget.c` 为准;下文仍保留「草案/待定案」表述处对应**未落地**的部分(如物理键整页焦点导航、弹性布局子项),勿当作已实现。**不破坏**既有 btn/slider/switch/list(§5)与设计模板(§5.2-§5.7)——既有 create 签名一字不改,Options/状态机走**新增挂载器**(§5.8.2/§5.8.3)。**移植 Compose/Flutter 的思想而非照搬**(见 §5.8.7 取舍)。 #### 5.8.0 一句话定位 - **标准控件** = SDK 内置实现(`meow_btn/slider/switch/list`,及后续 Material 子集 §5.8.6),一个 `meow_widget`。 - **自定义画布** = App 提供 render/hit 回调(`meow_widget_custom_create`),**同一个 `meow_widget`**。 - 两者**同构**:共用同一套生命周期 / 命中分流 / 局部刷新 / 主题 / 状态机 / Activity 归属(§6.1 ①)。差异只在「绘制内容从哪来」(内置 switch vs App 回调)与「命中怎么判」(矩形内含 vs App 回调)。 #### 5.8.1 统一控件架构(基座 + 扩展接口) **现状**(§5 / `meow_ui_widget.c`):`struct meow_widget` 用 `type` + `switch(type)` 分发绘制;命中统一矩形内含(`meow_widget_hit`);控件状态由 SDK 维护;owner 绑定创建时 Activity(§6.1 ①)。 **演进**(同构、最小侵入): - `meow_widget` 基座**保留**现有字段(type/x/y/w/h/owner/label/…),内部**追加**一块承载:统一 Options(§5.8.2)+ 状态机位(§5.8.3)+ 自定义回调。 - **类型枚举扩展**:新增 `W_CUSTOM`。标准控件照旧走 `switch(type)` 内置绘制;`W_CUSTOM` 走 App 回调。**其余类型(btn/slider/switch/list)行为不变**(向后兼容)。 - **扩展点(App 魔改三处)**:① **自绘内容**(`render_cb` 决定画什么);② **自定义命中**(`hit_cb` 决定怎么响应,不只矩形);③ **复用通用层**(Options + 状态机 + MD3 色板由 SDK 收口——App **只写「画什么/怎么响应」**,生命周期 / 命中分流 / 局部刷新 / 主题 / 状态 / Activity 归属全由 SDK 处理)。 **API 草案(「草案/待定案」,实现以 `meow_ui.h` 为准)**: ```c /* —— 自定义画布回调(App 提供;SDK 调用时上下文已就绪)—— * render:SDK 在 meow_widget_draw 时调用,th=当前生效主题(MD3 语义色板,§5.8.4); * w 的 x/y/w/h 已就位;App 用 §3/§4 绘制 API 自绘(往 back_buf 画)。 * hit :命中判定,App 自定义(如圆形/区域外镂空);返回 1=命中。 */ typedef void (*meow_widget_render_fn)(meow_widget *w, const meow_theme *th, void *ctx); typedef int (*meow_widget_hit_fn)(meow_widget *w, int x, int y, void *ctx); /* —— 创建:自定义画布(x,y,w,h = 绝对坐标,同既有控件)—— * 生命周期/Activity 归属:创建时绑定当前 active Activity(同 meow_btn_create,§6.1 ①)。 */ meow_widget *meow_widget_custom_create(int x, int y, int w, int h, meow_widget_render_fn render_cb, meow_widget_hit_fn hit_cb, void *ctx); /* 标准控件:既有 create 一字不改(向后兼容); * 统一用 Options(§5.8.2)修饰、状态机(§5.8.3)统一视觉/交互。 */ ``` > **同构理由**:若自定义画布由 App 直接往 back_buf 自绘 + 自行分发命中,会重复造轮子、且与既有控件/模板(§5.2-§5.7 复用 btn/slider/switch/list)行为不一致。收敛到 `meow_widget` 基座后,自定义与内置一样享受:owner 绑定、命中双闸门(`meow_widget_hit` 仅限 `owner==active`,§6.1 ①)、局部刷新(`meow_ui_flush_region`,§3)、主题注入、状态层与 disabled/focused/selected 通用能力。**App 魔改 = 提供 render/hit 两回调,其余 SDK 收口。** #### 5.8.2 Modifier 式通用 Options(`meow_widget_opts`) **目的**:把「padding / 背景 / 圆角 / 描边 / disabled / selected」等**通用修饰**从每个控件里抽出来,**所有控件(含自定义)通吃**。不做链式 DSL(软渲、极简 → **轻量结构体**),经 `meow_widget_set_opts` 一次性挂载。 ```c /* —— 通用 Options(轻量结构体,非链式 DSL)—— * 0 / -1 表示「本项用控件或主题默认」,App 只设想改的项。 */ typedef struct meow_widget_opts { uint32_t bg; /* 背景色(0=控件/主题默认,如按钮=th->primary) */ uint32_t fg; /* 前景/文字(0=主题 onSurface) */ uint32_t stroke; /* 描边色(0=无描边) */ int stroke_w; /* 描边宽(px) */ int radius; /* 圆角(px;-1=控件默认,如按钮 10) */ int pad_x; /* 内边距(水平,px) */ int pad_y; /* 内边距(垂直,px) */ int margin_x; /* 外边距(水平,px;布局用) */ int margin_y; /* 外边距(垂直,px;布局用) */ int disabled; /* 1=禁用(视觉变暗 + 不响应命中,见状态机 §5.8.3) */ int selected; /* 1=选中(列表高亮/勾选,见状态机 §5.8.3) */ } meow_widget_opts; /* 挂载:作用于任意控件(标准/自定义)。w 内部浅拷这份 opts。 * 返回 0=成功;NULL/无效=负值(不销毁 w)。 */ int meow_widget_set_opts(meow_widget *w, const meow_widget_opts *opts); /* 取回当前 opts(浅拷到 out;0=成功)。自定义画布用它读通用修饰。 */ int meow_widget_opts_get(const meow_widget *w, meow_widget_opts *out); ``` **应用规则**: - **标准控件**:绘制时**先读 opts 覆盖默认**(如按钮默认 bg=`th->primary`、radius=10;pad 按控件内建)。`meow_widget_draw` 内部统一套用 bg/radius/stroke/pad 后再画内容 → 一个按钮可变成卡片/圆角块。既有控件零改动即获得通用修饰能力。 - **自定义画布**:App 在 `render_cb` 里**主动读 `meow_widget_opts_get`** 决定自绘底色/圆角/描边;SDK **不替**自定义画布画背景(App 全权自绘),但 **disabled/selected/focused/pressed** 状态层由 SDK 统一暴露(App 读状态位自行叠,见 §5.8.3)。 - **命名区分**:`meow_widget_opts`(控件修饰)与 `meow_ui_opts`(§3 init 宽高覆写)同名后缀不同、用途不同,互不相关。 > **为何不改 create 签名**:既有 `meow_btn_create(x,y,w,h,label)` 等被 App/hello 与设计模板(§5.4-§5.7)广泛使用,改签名即破坏兼容。故 Options 走**独立 set_opts 挂载器**,标准控件零改动获得通用修饰——「扩展/兼容」而非「替换」。 #### 5.8.3 统一状态机(enabled / selected / focused / pressed) **目的**:把「控件处于什么状态、怎么表现」统一为**一套状态机**,标准/自定义控件共用,不再各写各的。 | 态 | 含义 | 视觉/交互 | 触达来源 | |---|---|---|---| | `enabled` | 可交互(默认 1) | 置 0=变暗 + 不响应 hit + 不进 `pressed` | `meow_widget_set_state(w, MEOW_WS_ENABLED, 0)` | | `selected` | 被选中(列表高亮/单选/多选/开关 on) | 底色用 `primaryContainer`、前景 `onPrimaryContainer`(或描边 `primary`) | App 设 / 控件自身逻辑(如 list 选中、switch on) | | `focused` | 物理键焦点(方向键导航当前命中者) | 外圈描边(1-2px `primary`)+ 可选底 `primary` α8 | **SDK 整页焦点导航**(物理键,见下) | | `pressed` | 按下(触屏按压/物理键触发) | MD3 状态层叠色(基色向 `onSurface` 叠 α,见 §5.8.4) | SDK 触摸层(`meow_widget_touch_refresh`)/ App `meow_btn_pressed` | **API 草案**: ```c /* 状态位(可组合) */ #define MEOW_WS_ENABLED (1u<<0) #define MEOW_WS_SELECTED (1u<<1) #define MEOW_WS_FOCUSED (1u<<2) #define MEOW_WS_PRESSED (1u<<3) /* 设/查状态(置位/清位,其它位不动)。 */ void meow_widget_set_state(meow_widget *w, unsigned bits, int on); /* on=1 置位 / 0 清位 */ unsigned meow_widget_state(const meow_widget *w); /* 位掩码 */ /* 便捷:禁用/启用、选中/取消(单态等效于 set_state)。 */ int meow_widget_enabled(const meow_widget *w); void meow_widget_set_enabled(meow_widget *w, int on); int meow_widget_selected(const meow_widget *w); ``` **一致化规则(标准 + 自定义共用)**: - **绘制**:`enabled=0` → 整体变暗(状态层向 `onSurface` 叠 α,或按 `surfaceVariant` 淡化);`selected` → 语义色板高亮;`pressed` → 在底色叠状态层(alpha 递增);`focused` → 外圈描边 + 低 α 底。**标准控件由 SDK 自动叠加**;**自定义画布**由 App 在 render 里读 `meow_widget_state()` 自行叠加(SDK 暴露状态位;可选辅助 `meow_widget_draw_state_layer(w,th)` 一键叠状态层——**草案/待定案**)。 - **命中/交互**:`enabled=0` 不响应 hit(`meow_widget_hit` 返回 0)、不进 `pressed`;`pressed` 不跨 owner(沿用 §6.1 ① stale 保护)。 - **触摸 + 物理键焦点**: - **触摸**:`pressed`/`selected` 由 SDK 触摸层驱动(把 `g_live_btns[]` 从「仅 W_BTN」扩展为**按活动 Activity 分区**的控件注册集 + 实时按压刷新,§6.1 ①,见 §5.8.8 分批)。 - **物理键焦点**:无鼠标,物理键(VOL± / 中心确认键,见 §5.1 物理键辅助与 §6 `on_key`)驱动**整页焦点导航**——SDK 维护「当前 active Activity 内可聚焦控件集(`focused` 态)」,VOL± 上下移动、确认键触发。**该导航属 SDK 收口**(不入 App `on_key` 语义),App 只需控件存在;`focused` 由 SDK 置位。**草案/待定案**(见 §5.8.7 硬件约束)。 - **默认态**:控件默认 `ENABLED`;`slider`/`switch`/`list` 的选中态映射到 `selected`(开关 on、列表 sel)。 #### 5.8.4 MD3 完整语义色板(日/夜自动切换) **现状**(§8 / `meow_ui_theme`):`{bg, fg, accent, dim, font_title, font_body}` 四色 + 日/夜两套 + AUTO 跟随系统,控件用 `th->accent/dim/fg`。 **演进**:把 `meow_ui_theme` **在结构体尾部追加 MD3 语义色板字段**(**扩展兼容**——既有 `bg/fg/accent/dim` 保留为兼容别名/映射,见下表);标准/自定义控件统一从 `meow_theme`(`th` 传入 render)取色,不再硬编码(如现有按钮文字 `0xFFFFFF`)。 **MD3 语义色板(日/夜各自取值)草案**: ```c typedef struct meow_theme { /* —— 既有字段(兼容保留,映射见下表)—— */ uint32_t bg, fg, accent, dim; int font_title, font_body; /* 28 / 20 */ /* —— MD3 语义色板(新增;日/夜各一组,AUTO 按 meow_theme_mode 切换)—— * 取值对齐 Material 3 语义色(本地化到主题 primary 等),非固定品牌色。 */ uint32_t onPrimary, primaryContainer, onPrimaryContainer; uint32_t secondary, onSecondary, secondaryContainer, onSecondaryContainer; uint32_t tertiary, onTertiary, tertiaryContainer, onTertiaryContainer; uint32_t surface, onSurface, surfaceVariant, onSurfaceVariant; uint32_t surfaceContainerLowest, surfaceContainerLow, surfaceContainer, surfaceContainerHigh, surfaceContainerHighest; uint32_t surfaceDim, surfaceBright, surfaceTint; uint32_t outline, outlineVariant, scrim; uint32_t error, onError, errorContainer, onErrorContainer; uint32_t inverseSurface, inverseOnSurface, inversePrimary; uint32_t shadow; /* 仅状态层/描边近似用;无 GPU 不做投影(§5.8.7) */ } meow_theme; ``` **与既有字段映射(等价保留,代码可继续用旧字段)**: | 旧字段 | 等价 MD3 语义色 | 说明 | |---|---|---| | `bg` | `surface`(/`background`) | 页面底 | | `fg` | `onSurface` | 正文/前景 | | `accent` | `primary` | 强调色(按钮/选中) | | `dim` | `onSurfaceVariant` | 次要文字/弱前景 | **取色规则**: - **标准控件**:绘制读 `meow_theme_get()`,全用语义色(primary/surface/surfaceVariant/outline/error/…),不再硬编码。 - **自定义画布**:`render_cb` 的 `th` 参数即当前生效主题(MD3 板),App 取 `th->primary/onPrimary/surface/...` 自绘 → **自动日/夜切色**,无需 App 额外查色板。 - **日/夜切换**:沿用 `meow_theme_mode`(LIGHT/DARK/AUTO,§8)+ `meow_theme_get` 返回当前板;AUTO 按 `display.theme_mode` 解析(日/夜),切换时经 `meow_theme_listener` 广播(§8),App 在 `on_tick` 重绘(`meow_ui_flush_region`)。 - **状态层(MD3 overlay)**:`pressed/focused/disabled/selected` 视觉用「状态层叠色」——基色(如 `primary`/`surface`)向 `onSurface` 叠 alpha(对应 MD3 state-layer 思想),取代现有 `blend_color`(`meow_ui_widget.c` 已内置);演进时把状态层颜色来源改为语义色板,不外露给 App。 > **注意**:`meow_ui_theme` 追加字段 = **`meow_ui.a` ABI 变化** → 需按 §9/§10「**同步重编 hello**(App 也链 `meow_ui.a`)」一并处理(§5.8.8 分批)。 #### 5.8.5 可组合布局(Row / Column / Box + Spacer) **目的**:让 App 少手算坐标。既有控件是**绝对定位**(x,y,w,h);布局是**相对布局容器**,内部在 arrange 时把子控件**算成绝对坐标**(结果仍写进每个控件的 x,y,w,h)→ 与现有绝对定位**完全兼容**——布局只是「帮你算坐标」的容器,不改变控件绘制 / 命中 / 状态机。 ```c /* 布局容器:Row(横排)/ Column(纵排)/ Box(叠放)。坐标 = 容器矩形(绝对)。 */ enum { MEOW_LAYOUT_ROW, MEOW_LAYOUT_COLUMN, MEOW_LAYOUT_BOX } meow_layout_kind; meow_widget *meow_row_create(int x, int y, int w, int h, int spacing); meow_widget *meow_column_create(int x, int y, int w, int h, int spacing); meow_widget *meow_box_create(int x, int y, int w, int h); /* 添加子控件(子可为普通控件/自定义/另一布局/Spacer)。返回 0=成功。 */ int meow_layout_add(meow_widget *layout, meow_widget *child); /* 计算子控件绝对坐标(写入各 child 的 x,y,w,h);此后照常 draw/hit。 */ void meow_layout_arrange(meow_widget *layout); /* 固定尺寸空白(也作 flex 撑余量)。 */ meow_widget *meow_layout_spacer(int w, int h); ``` - **坐标语义**:child 的 x,y 在加入布局后由 arrange 覆写为布局内绝对位置;child 的 w/h 若为 0 表示「按内容/弹性」由布局分配(**草案/待定案**——B1 控件皆有确定 w/h,先支持固定尺寸子项,弹性为后续增强)。 - **与绝对定位兼容**:控件最终 x,y,w,h 即绝对坐标,故 `meow_widget_draw/hit`(§5)、`meow_ui_flush_region`(§3)、owner 闸门(§6.1 ①)照常不变。 - **局部刷新**:交互仍只刷变化区(`meow_ui_flush_region`),布局不导致整页重排;仅 arrange 时算一次坐标。 - **嵌套**:布局可作另一布局的子控件(Row 内嵌 Column),arrange 递归。 #### 5.8.6 Material 组件子集(第二批,已实现 M2.71/0.7.66) > **性质**:Material 3 组件子集,**第二批追加批次**(基于第一批基座落地,M2.71 已实现)。观感对齐 MD3、本地化到主题语义色。每项一句用途(API 见 `meow_ui.h`,实现见 `meow_ui_widget.c` `meow_*_create`): | 组件 | 一句用途 | 批次 | |---|---|---| | TextField | 单/多行文本输入(复用 IME §5.1) | ✅ 第二批 | | Card | 圆角容器卡片(`surfaceContainer` 底 + 可选描边/状态层) | ✅ 第二批 | | Divider | 横向/纵向分隔线 | ✅ 第二批 | | Badge | 角标(红点/计数) | ✅ 第二批 | | Checkbox | 复选(`selected` 态 + `primary` 勾) | ✅ 第二批 | | Radio | 单选(单选组;`selected` 态) | ✅ 第二批 | | ProgressBar | 线性/圆形进度(确定性 + 不定性) | ✅ 第二批 | | Button(五型) | filled / tonal / outlined / text / elevated(`meow_btn` 扩展五型,Options 区分) | ✅ 第二批 | | TopAppBar | 顶部标题栏(标题 + 返回 + 操作) | ✅ 第二批 | | NavigationBar | 底部导航栏(图标 + 文字) | ✅ 第二批 | | TabBar | 顶部分页 Tab(swipe 联动 §5.2) | ✅ 第二批 | | 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 字库无这些码点,字体码点画图标会出豆腐),新增把某动作项改为**位图图标**的接口: > > ```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,重新生成 `patch_v2/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)。 > **M2.73 修复(以 `meow_ui_widget.c` / `hello_app/hello.c` 为准)**: > - **列表容器(W_LIST / LazyColumn / LazyRow)视口裁剪**:`meow_widget_draw` case `W_LIST` 现在计算视口绝对边界 `vx0/vx1/vy0/vy1`,**只绘制落在视口内的行、分隔线、文本**(整项可见才画文本/越界不画),滚动时不再把内容画到视口外再标脏上屏 → 根治「标题栏/底部残留脏区」。**SDK 全局生效**(非 App 侧)。 > - **z 序重叠区修复**:App 侧 `hello_app/hello.c` 的 `d_paint_region`(局部刷新)改为**按 z 栈重画该区全部控件**(内容→标题→slider 值→chrome),取代旧「清底 + 只画单个控件」——防止「下层动态控件(如圆形进度条)实时重绘盖住/抹掉上层浮层(FAB)」。**⚠️ 暂注**:此方案当前在 **App(hello)侧**实现,属**示范/落地点**;**SDK 级全局 z 序(框架统一管理层间重绘顺序)仍待评估,未正式纳入 SDK 公共 API**——在落定前请勿把「全局 z 序」写成正式规范,App 自定义绘制如需 z 序要参照 `d_paint_region` 自行重画完整栈。 #### 5.8.7 取舍 / 不做(移植思想而非照搬) > **关键(防照搬桌面 UI 框架踩坑)**:本层是**软渲 + 无 GPU + 无鼠标 + 14MB 内存/极简 rootfs** 的嵌入式环境(§11)。移植 Compose/Flutter 的**思想**(组合/状态驱动/语义色),**不照搬其运行时**: - **不做声明式运行时(状态→UI 全量重算)**:Compose/Flutter 把 UI 声明为状态函数、每帧全量 recompose/rebuild;软渲(CPU 逐像素、back_buf 全帧 blit)扛不住高频全量重算。MeowOS 控件为**命令式 + 局部重绘**(状态变化只刷变化区,§5.4/§5.5/§7)。状态机(§5.8.3)只驱动**控件自身**局部重绘,不触发整页重建。 - **不做重度动画/模糊/阴影**:无 GPU(≤60fps 软渲,§11)。动画=状态式短插值(§5.2/§5.7「松手补 2-4 帧吸附」);模糊=「降采样」廉价版(§11,封面缩 1/8 铺底,仅切歌算一次);阴影**不做投影**(无 GPU 合成,仅用 `shadow` 语义色做描边/状态层近似,避免每帧多 pass 铺底)。 - **不做 hover**:无鼠标(仅触摸 goodix + 物理键)。按压反馈=触摸 `pressed` 状态层(§5.8.3);鼠标 hover 一律不做;**物理键焦点**(`focused`)作为「无鼠标导航」替代——只做方向键移动 + 确认,不做悬停。 - **组件轻量**:单个控件=1 个 `meow_widget`(小结构体);无对象树/无布局引擎运行时;options 是轻量结构体(非链式 DSL);布局只在 arrange 算一次坐标、不维护昂贵约束求解。**无虚拟 DOM / 无增量 reconcile**。 - **语义色而非品牌色**:所有组件取 `meow_theme` 语义色(primary/surface/…,§5.8.4),App 不自配色 → 日/夜/系统观感统一点(§8)。 - **移思想不照搬**:借 Composition / Modifier / 语义色 / 状态层的**命名与心智模型**,实现为**轻量 C 结构体 + 命令式重绘**;不引入 GC/协程/运行时反射,不用虚拟机。 #### 5.8.8 分批实现建议 **第一批 = 通用基座(✅ 已实现,M2.70/0.7.65;落地后 all 控件升级地基)**: 1. **MD3 语义色板扩展**(§5.8.4):`meow_theme` 尾部追加语义色字段 + 日/夜取值 + 映射旧字段(bg/fg/accent/dim);`meow_theme_get` 返回新板。 2. **统一 Options**(§5.8.2):`meow_widget_opts` + `meow_widget_set_opts/get_opts`;标准控件绘制读 opts。 3. **统一状态机**(§5.8.3):`enabled/selected/focused/pressed` 状态位 + `meow_widget_state/set_state/enabled/selected`;标准控件绘制叠状态层;`disabled` 阻断 hit/pressed。 4. **自定义画布融合**(§5.8.1):`meow_widget_custom_create` + `W_CUSTOM`;基座统一 render(th 注入)/hit;与标准控件同构(owner/命中双闸门/局部刷新/主题/状态机)。 5. **可组合布局**(§5.8.5):`meow_row/column/box + meow_layout_add/arrange + spacer`;固定尺寸子项先做,算绝对坐标(与绝对定位兼容)。 6. **实时按压刷新扩展**:把 `g_live_btns[]`(仅 W_BTN)扩展为「按活动 Activity 分区」控件注册集(§6.1 ①),供标准 + 自定义画布共用按压态即时刷新。 **第二批 = Material 组件子集(✅ 已实现,M2.71/0.7.66,§5.8.6)**:TextField/Card/Divider/Badge/Checkbox/Radio/ProgressBar/Button 五型/TopAppBar/NavigationBar/TabBar/BottomSheet/Dialog/FloatingActionButton/列表容器——基于第一批基座实现,多数复用既有设计模板(§5.2-§5.7)的「MD3 观感化」。 **同步重编注意(关键)**:`meow_ui_theme` 追加字段 / `meow_widget` 结构变化 → **`meow_ui.a` ABI 变化** → 需按 §9/§10「**同步重编 hello**(App 也链 `meow_ui.a meow_font.a libz.a -lpthread`)」一并处理;`build_sdk.sh` 一键重编 + `hello_app/build_hello.sh` 重链。**B1 既有控件与模板零改动**(§5.8.1/§5.8.2 均向后兼容)。 #### 5.8.9 枚举 / 预设选择器(`meow_select`,M7.2 设置页缺口 #4) > **定位**:给「枚举 / 预设选择器」一个开箱即用的**下拉模态**(`include/meow_select.h`,追加入 `meow_ui.h` 伞头)—— > 采样率/码率/主题/语言等枚举、均衡器预设等。封装 = **BottomSheet(标题 + 选项列表)+ `meow_listview`(SINGLE) 单选列表**: > 选项数组 + 当前选中(高亮)+ 点选项 = 选中 + 关闭 + `on_change` 回调;点面板外/左缘返回 = 取消。 > 阻塞式(内层 `meow_input_pop` 轮询,与 `meow_dialog` 同构);打开前排空残留触摸。 > 复用 `meow_dialog` 的遮罩/快照恢复(scrim 只叠一层、关闭无残影)。 **API(`include/meow_select.h`)**: ```c typedef struct meow_select meow_select; typedef void (*meow_select_fn)(int idx, void *ctx); /* on_change:idx=选中项下标(0..n-1) */ #define MEOW_SELECT_MAX_ITEMS 32 /* 选项上限(静态数组;越界 add 忽略并返回 -1) */ /* 组件式(可复用 + on_change 回调) */ meow_select *meow_select_create(const char *title, int cur); /* cur=初始选中项;越界 clamp */ int meow_select_add_item(meow_select *s, const char *label); /* 返回项下标;满/无效= -1 */ void meow_select_set_on_change(meow_select *s, meow_select_fn cb, void *ctx); int meow_select_open(meow_select *s); /* 阻塞到确认/取消:选中→result=idx+on_change(idx)+返回 idx;取消→-1 */ void meow_select_free(meow_select *s); /* 一次性(无回调,返回值即选择);opts=const char* 数组;n=个数;cur=初始选中 */ int meow_select_show(const char *title, const char *const *opts, int n, int cur); ``` - **内存纪律**:选项数组**定长**(≤`MEOW_SELECT_MAX_ITEMS`,栈/数组持有,非 malloc 长列表;越界 `add_item` 返回 -1); 列表在 `open` 时创建(冷路径)、`close` 时释放;**open 循环零 malloc**。 - **选择语义**:确认 → 置 `result` + 调 `on_change(idx)` + 返回 `idx`;取消/点面板外/左缘返回 → 返回 `-1`(不调 `on_change`)。 - **M7 用途**:设置页(M7.2)采样率/码率/主题/语言等枚举选择(缺口 #4)。 --- ### 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。 MeowOS 的声卡由**独占音频守护 `meow_audiod`** 持有(`hw:0,0`;M2.90 入固件,svcd 托管,HibyOS 模式不启动)。**App 不再直开 ALSA**,一律经 SDK 客户端 `meow_ui_audio.c`(编进 `meow_ui.a`)喂 PCM: ```c int meow_audio_open(int rate, int ch, int fmt); /* 返回 stream_id(>0);<0=错误(-EBUSY 被占且被抢等) */ int meow_audio_open_ex(int rate, int ch, int fmt, uint32_t flags); /* 带 flags(A3+DSD:flags=AUDIO_FLAG_DSD 且 fmt=AUDIO_FMT_DOP → DoP DSD 流) */ int meow_audio_write(const void *pcm, int frames);/* 阻塞(socket 背压);返回写入帧数, <0=流已死 */ int meow_audio_pause(int sid, int paused); /* 1=暂停 0=恢复 */ int meow_audio_close(int sid); /* 关流 */ int meow_audio_query(meow_audio_state *out); /* 当前流/路由/音量/underrun 快照 */ int meow_audio_set_volume(int pct); /* audiod 唯一音量权威:写 DAC + DSD_VOLUME 双组 + 持久化 */ /* 被抢占回调(可选):收到 MSG_AUDIO_PREEMPTED 时调(SDK 内部读线程分发) */ void meow_audio_on_preempt(void (*cb)(const char *by_app, void *ctx), void *ctx); ``` - **fmt 枚举**:1=S16_LE 2=S24_LE 3=S32_LE 4=FLOAT_LE 5=AUDIO_FMT_DOP(v1 实装 S16/S24/S32;FLOAT v1 拒绝);服务端做**格式归一 + DAC 原生率跟随**(无 SRC,44.1-192k 全覆盖)。 - **`meow_audio_open_ex` / DoP DSD(A3+DSD 定案,勿回退)**:比 `meow_audio_open` 多 `flags`。`flags=AUDIO_FLAG_DSD(0x1)` 且 `fmt=AUDIO_FMT_DOP(=5)` → **DoP DSD 流**:App 侧把 DSD 解码封装为 DoP(每样本 32-bit 容器:bits[31:16]=16bit DSD 数据、bits[15:8]=标记 0xFA/0x05、bits[7:0]=0;独立格式值避免 S24 名义(3B)/实际(4B)歧义);audiod **只透传**(不引 dsd2pcm、不做 DOP_EN 之外处理);DSD 打不开/不支持 → open 返回负值、报错关流(**不降级**,勿静默回退 PCM 伪装)。 - **`meow_audio_state.dsd`(追加字段,ABI 兼容)**:当前流是否为 DoP DSD(`fmt==AUDIO_FMT_DOP` 且 audiod 已授权)。`meow_audio_state` 其它字段不变,此键为追加、旧结构体兼容。 - **`meow_audio_set_volume`(音量收编 v2,audiod 唯一权威)**:写 DAC numid=1/2 + DSD_VOLUME **双组**(保切换连续)+ 持久化 `settings.conf audio.volume`(与 launcher/watchdog 同源同键)。BT 输出目标时音量由 AVRCP 侧控,audiod 不下发 DAC 反转。返回 0 成功;<0=audiod 不在。 - **独占+抢占**:后开流赢,旧流收 `MSG_AUDIO_PREEMPTED`(App 收到自行暂停 UI);**socket EOF=回收**(客户端崩溃/被 SIGKILL 不泄漏)。流绑定 socket 连接而非 App 前台状态(后台播放见 AUDIOD_DESIGN §3)。 - **懒连接 + 断线重连**:open 时连 `/usr/data/meow_audio.sock`;write 遇 EPIPE → 标记流死,App 需重新 open。**不做隐式直开回退**:audiod 不在 → open 返回 -ENOENT;调试可 `MEOW_AUDIO_DIRECT=1` 允许 App 自带直开路径(仅开发用,非正式能力)。 - **调试 CLI**(root 免鉴权,ssh 直呼):`meow_audio status` / `meow_audio tone [freq] [ms] [rate]`(正弦测试音,排查无声不用开 App)/ `route <0|2|3>` / `vol [0-100]` / **`monitor [秒]`**(261,终端实时电平表:200ms 刷新 rms/peak dBFS + 文本电平条,缺省 10s,0=持续)。 - **⚠️ ALSA UAPI 两坑(真机定位,App 自写直开路径时勿回退)**:① hw_params 须「全放行」初始化(params_any 语义:mask 全 1、interval [0,UINT_MAX]),memset 0 的 interval → HW_PARAMS 恒 EINVAL;② access/format/subformat mask 须**先清零再单点**,留全 1 会被 choose 成 MMAP_INTERLEAVED → write 恒 EINVAL。 - **PCM 死锁已根治(M2.91,原「M2.90 一播 PCM 即内核硬挂死」)**:根因 = 未烧 hbc3000 FPGA 时驱动对第二次 `hbc3000_start` 不可重入死锁;audiod 路由线程启动时**无条件写 Output Port Switch**(4.4→3/3.5→2/无→4,同值先写 5 强制重烧)武装 FPGA。调查全程 `devdocs/plan/AUDIO_HANG_INVESTIGATION.md`。 - **长流停摆已根治(M2.93 护栏 + M2.94 几何)**:驱动默认巨 buffer(上限 1048576 帧≈23s)致 hbc3000 桥周期中断稀缺 wedge(播 ~12s 停摆)→ audiod `alsa_hw_params` 收窄 **period=1024×4**(tiny_alsa 惯例,tonepcm2 矩阵 60s 零 stall 实证);257 护栏:ring 有界等待/cond timedwait/popen 有界收割/O_NONBLOCK+poll 写/wedge 自救/心跳 watchdog/下行写不全丢弃。 - **音量语义(M2.95 四路统一,勿回退)**:CS43198 音量寄存器 = **衰减量**(0=最响,255=静音);launcher/audiod/watchdog/SDK 统一映射 `dac = 255 - pct*255/100`。**炸耳安全(M2.96)**:`dac_vol_mute` 必须写 **255(真静音)**——旧版写 0=最大响度,流回收/重配瞬间满幅输出(真机事故)。⚠️ 非线性(0.5dB/步,50%≈-64dB),感知曲线(dB taper)待做。 - **电平监控(261,已提交待 M2.97 固件化)**:播放线程对 ring 取出 PCM 逐样本整数累加,~100ms 窗口(逐帧判边界)结算 RMS/peak dBFS(deci-dB,地板 -990=-inf);`MSG_AUDIO_STATE` 追加 `rms=/peak=/lvlseq=` 键(**追加键,SDK `meow_audio_state` 结构体未动,ABI/协议兼容**);流活跃时 1s 节拍写 TF `MeowOS/logs/audio_monitor_.log`(流停即停,无流零开销)。 - **流媒体客户端迁移范式(A2,e013621)**:`ncm_app/app/play/audio_out.c` 新增 `SINK_AUDIOD` 后端(与 ALSA/FILE/NULL 并列,**默认 audiod**),被抢占回调→暂停、resume 检测流死自动重开、播放路径全量 `meow_log`;UI/缓存/解码零改动。新 App 照此接。 --- ## 6. 输入层 / 生命周期(B1 部分 + B2 衔接) **B1(本阶段)**:应用不再自读 `/dev/input/event*`,meow_ui 建立输入线程,把按键/触摸翻译成语义事件回调: ```c /* 应用注册事件回调(meow_app_start 时传入) */ typedef struct { void (*on_key)(int keycode, int is_down); /* KEY_VOLUP/VOLDOWN/PLAYPAUSE/POWER…(SDK 已消化系统语义) */ void (*on_tap)(int x, int y); /* 触摸抬起落点 */ void (*on_swipe)(int dir, int dx, int dy); /* 左缘返回手势=LEFT;中部滑动=RIGHT/UP/DOWN */ void (*on_tick)(void); /* 空闲周期回调(动画/时钟用,见 §7) */ void (*on_swipe_progress)(int x_start, int dx);/* 左缘返回手势过程实时回调;x_start=按下X, dx=当前X-按下X */ } meow_ui_events; void meow_ui_run(const meow_ui_events *ev); /* 阻塞:事件循环,直到 meow_app 请求退出 */ void meow_ui_request_exit(void); /* 应用调它请求退出(相当于触返回/按退出键) */ /* 左缘返回手势的顶部视觉条(SDK 已自动绘制,应用一般无需自画)。 * 画屏幕顶部 5px、从 x=0 到 x 的横条(主题 accent 色);x<=0 清条。应用可在 * on_swipe_progress 里用它自画(长度=当前 X=x_start+dx),或留空全靠 SDK。 */ void meow_ui_swipe_hint(int x); ``` - **返回语义**:B1 下按"返回"(左缘返回手势/虚拟返回键)→ 由应用决定:`on_swipe(LEFT)` 是返回手势,应用自行 `meow_ui_request_exit()` 回应用列表;若想在页内返回,可在 `on_swipe(LEFT)` 里处理并 `return` 不退出。 - **左缘返回手势(抬手触发)**:手指从**屏幕左缘(≤24px)向内划 ≥90px**,**抬手(release 边沿)后才触发** `on_swipe(LEFT)`;过程以 `on_swipe_progress` 实时上报并自动画顶部进度条(`meow_ui_swipe_hint`,长度=当前 X)。中部左/右滑不触发返回(中部右滑→`on_swipe(RIGHT)` 内容导航;中部左滑→落 TAP)。**与设计稿 §2.2 的偏差**:§2.2 写「左縁 + 向左 mdx<-90」物理不可达(左縁起点再向左最多走 ~24px),故取「左縁向内(右)划 ≥90px」实现,事件仍报 `MEOW_SWIPE_LEFT`(保持「左滑=返回」契约),方向与 watchdog immersive 兜底一致。 - **沉浸模式**:应用声明(app.json `immersive=1`)→ launcher 写 `/tmp/meowapp_state=1` → watchdog 隐藏虚拟返回键,返回手势归 SDK。这是 B1 可做、B2 转正的一部分。 **B2(衔接阶段 D)**:把 `on_key`/`on_swipe` 换成走 IPC 的语义导航(`MSG_NAV` 上报 + 启动链路),应用不再自己决定"返回回哪层",而是上报当前 Activity,由框架导航栈统一回退。届时: - `meow_ui_run` 改为从 IPC 收启动链路(`MEOW_CHAIN`/`MEOW_ACTIVITY` 环境变量)初始化导航栈; - 返回 = 栈回退,到根才 `meow_ui_request_exit()`。**本文档 §5 的控件与 §3/§4 绘制在 B2 不变**,只换输入/导航后端。 ### 6.1 控件生命周期与归属(Activity 栈 + 三层控件模型)[源:2026-08-30] > **性质**:SDK 控件生命周期的权威约定(B1/B2 均遵守)。根治「跨页面控件残留」——控件命中/绘制必须严格绑定其所属页面,否则会出现"按钮已不可见但仍被触摸命中"(如全屏页触摸到主页按钮位置闪现)。 **核心:控件严格归属创建它的 Activity,不跨页面共享。** #### 三层职责(从 App 到系统,风险从低到高) | 层 | 谁负责 | 谁能用 | 生命周期 | |---|---|---|---| | **① 页面控件** | 归属唯一 owner Activity | App | 随 Activity 栈;owner 在栈顶(active)才绘制+响应触摸 | | **② App 跨 Activity 控件** | **SDK 托管**(逻辑+渲染,框架职责) | App 可声明使用 | 钉在 App 的 Activity 栈**之上**,跨 Activity 常驻 | | **③ 系统级控件** | MeowOS/SDK 框架内部 | **仅系统组件,用户 App 禁调用** | 随系统,不开放 | - **① 页面控件**:`meow_btn_create` 等创建时绑定当前 Activity;命中(`live_btn_hit`)只在**当前 active Activity 的控件集**里找;对象**不销毁**(Activity 内对象池复用),弹栈回来自动恢复 → **回页面零重建**。 - **② App 跨 Activity 控件**:因为 App 页面本身依赖 SDK,跨 Activity UI 也由 **SDK 负责逻辑与渲染**。App 用 `meow_ui_global_*`(如下)声明,SDK 托管(钉在 App 栈之上、切 Activity 仍存在、逻辑/命中统一调度)。例:App 内部底部播放栏(多 Activity 共享)。 - **③ 系统级控件**:状态栏、通知中心入口、左缘返回/全屏滑条、电源菜单、全局返回手势指示等**全局叠加层**——**只留 SDK 内部接口位,用户侧 App 一律不可调用**(开放成「悬浮窗/全局叠加层」会引入遮挡、冲突、性能与安全风险)。App 这类需求只能走 SDK 标准能力,不得自行叠加全局层。 #### 交互分层(命中分流) 触摸命中顺序(从顶往下): - **+0 当前模态(scrim)层(命中首层,根治穿透)**:命中 modal 项=处理;命中 scrim 外区=**关闭 modal 并【消费,不放行】**; - 然后 **③ 系统全局 → ② App 全局 → ① 当前 Activity 页面控件**;某层命中即消费,否则落到下一层。 - **+0 模态层由 SDK 命中分发统一保证**(`meow_ui_input`/`meow_widget_hit`):任何模态(Dialog/Sheet/设置弹窗/电源菜单/通知中心)自动"点外关闭 + 不穿透",各界面不得各自局部补丁(此前设置页用排空/防抖等为局部实现,未上收;本条目根治穿透,统一模态层拦截)。 #### 与 agent-119「即时重绘注册表」的关系 `g_live_btns[]` 从进程级全局,改为**按 Activity 分区**的控件注册集;非 active Activity 的控件不参与命中。这直接根治 agent-119 遗留的"全屏页触摸到主界面按钮位置 → 短暂闪现 accent 底"(页面控件残留,跨页不复用)。 #### API 定案(本轮已在 B1 实现) ```c /* ① Activity 栈 */ void meow_ui_activity_push(int id); void meow_ui_activity_pop(void); int meow_ui_activity_active(void); /* 当前 active activity id */ /* ② App 跨 Activity 控件(SDK 托管) */ typedef void (*meow_global_render_fn)(void *ctx); /* 每次渲染帧回调(SDK 调用) */ typedef int (*meow_global_hit_fn)(void *ctx, int x, int y); /* 命中返回1=消费 */ void meow_ui_global_add(int id, meow_global_render_fn r, meow_global_hit_fn h, void *ctx); void meow_ui_global_remove(int id); /* ③ 系统级控件:仅框架内部,无公共 API(不开放) */ ``` #### 实现期定案 / 与上文的偏差(2026-08-30 落地,保持文档实时一致) - **控件绑定 owner**:`meow_btn_create` 等所有控件(btn/slider/switch/list)创建时即取 `meow_ui_activity_active()` 作为唯一 `owner`(`meow_widget` 新增 `int owner` 字段)。栈空时 `active()` 返回 **0(根/default Activity)**——未显式管理栈的 App 所有控件默认归属根 0。 - **命中双闸门**:除 `live_btn_hit`(实时按压重绘)外,**公共 `meow_widget_hit` 同样仅限 `owner==active`** 才返回命中(页面控件只对自身 Activity 负责,不跨页复用)。SDK 侧 `meow_widget_touch_refresh` 另加 stale 保护:按压期间若发生 Activity 切换(如点 FULLSCREEN 进全屏),静默清除其按住引用、不绘制,杜绝在新页底上画出旧按钮 accent 残留。 - **对象复用(非销毁)**:§6.1 的「Activity 内对象池复用」本轮**从简落地**——控件对象不随 pop 销毁,SDK 不维护独立对象池;App 自行持有控件指针(hello 用静态全局),弹栈回来自动恢复命中(零重建)。注册表按**指针去重**(`live_btn_register` 查重),故反复 push/pop 不重复注册、不泄漏。 - **② 为可用骨架**:SDK 维护全局控件列表(`meow_ui_global.c`),渲染帧 `meow_ui_global_render()` 在 active activity 内容之上、全局状态栏之下调用 render_cb;触摸 TAP 先 `meow_ui_global_hit()`(返回 1=消费),否则落 `on_tap`。无 z 序细分/无按压态管理/无命中区排布(待 B2 细化);同 id `global_add` 幂等。 - **③ 系统级(状态栏/通知中心/返回滑条/电源菜单)**:未开放任何公共悬浮/叠加层 API,既有状态栏/返回滑条逻辑原样保留。 #### 系统 UI 归属与进程隔离(源:2026-08-30 定案,跨引用 FRAMEWORK_DESIGN §10.2) > 把「系统组件归谁渲染」在 SDK 规范层面钉死,明确 **APP 进程不渲染系统 UI**、**SDK 负责系统组件的进程与负责 APP 的进程相互独立**。 - **系统组件渲染归系统进程(launcher)**:状态栏 / 通知中心(下拉)/ 电源菜单 / 全局左缘返回与滑条等**全局叠加层**,由系统进程(meow_launcherd)负责;**APP 进程不渲染系统 UI**。 - **SDK 提供系统组件 API 仅供系统进程链接调用**:SDK 保留系统级控件内部接口位(上面 ③);**用户 App 拿不到**、**不可自建悬浮层/全局叠加**——App 这类需求只能走 SDK 标准能力。 - **进程隔离(核心)**:SDK 负责**系统组件渲染**的进程(launcher)与负责 **APP 渲染**的进程**相互独立**——APP 卡死/崩溃时,系统 UI(launcher)**不崩**,始终可息屏/返回/下拉通知中心(对应 APPS §3.4.1 物理键全局接管、APP 禁映射)。 - **APP 侧职责**:只画自己的内容;经 IPC 上报**全屏声明**(`fullscreen`/`route..fullscreen`)、**内容区域**(inset)与**前台 activity**;系统 UI 据此做叠画 + 命中分流(§6.1 交互分层:③ 系统全局 → ② App 全局 → ① 当前 Activity 页面控件)。 - **前台 activity / keep_awake(M2.17,`meow_ipc.h`)**:前台 App 经 IPC 向 launcher 上报当前顶层 activity (`MSG_ACT_FOCUS=0x0033` 上报顶层 / `MSG_ACT_BACK=0x0034` 回到根·清 keep_awake,号以 `meow_ipc.h` 为准); 系统侧 `fg_keep_awake()` 依注册表判断该 activity 是否声明 keep_awake(`app.json` 的 `awake.=1` 或 `route..keep_awake=1`,运行时同类),决定是否**前台保活**(超时不自动息屏)。App 只上报 activity,不写保活判定 (主界面无上报/空 = 非 keep_awake,照常超时息屏)。 - **App 前台划出通知中心(跨进程,[源:2026-08-30],M2.82 已实现)**:全屏 App 前台时顶部下滑——**App 进程 SDK 只检测手势 (`MEOW_EV_NOTIF_PULL`→`meow_ui_sys_notif_pull()`)并经 IPC 上报**(`MSG_NOTIF_OPEN=0x0028`/`MSG_NOTIF_CLOSE=0x0029`,号以 `meow_ipc.h` 为准),**不渲染系统 UI**; 通知中心由 launcher 渲染(SIGSTOP App+读回其帧+叠加 `run_notif_center`+接管输入+关闭恢复,面板内 App 崩溃即关), SDK 侧 `meow_ui_nav.c`/`meow_ui_media.c` 封装同步落地(**agent-206 · 9b41957**)。 见 `FRAMEWORK_DESIGN §10.5` / `devdocs/plan/NOTIF_CENTER_APP_OVERLAY_PLAN.md`。 ### 6.4 事件分发形式化(2026-09-02):命中测试 / 捕获 / 消费 / 层间隔离 / 唯一焦点 > **定位**:把此前各处 ad-hoc 的「命中/捕获/消费/层间隔离/焦点」补丁(模态穿透、弹层叠黑、 > list 焦点残留、同坐标二次消费)**上收为单一仲裁者**。三个历史根因(设置页滑条穿透、 > Dialog/Sheet 弹层叠黑、list 焦点残留导致主页面滑不动)与「注入事件被下层同坐标控件二次消费」 > 都在本节模型内覆盖。**兼容铁律:只追加 API、不删改既有签名**;未调用本节 API 的 App 行为与 > 之前完全一致(命中/捕获仍可由 App 在 `on_tap`/`on_touch_*` 自理)。hello/files/ncm/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 提供**帧驱动回调 + 内置时间**,应用据此做低成本的帧序列/淡入淡出: ```c /* on_tick(ev) 里用 meow_ui_time_ms() 做时间轴动画 */ uint64_t meow_ui_time_ms(void); /* 单调毫秒时间戳 */ /* 动画 = 应用在 on_tick 里根据时间更新状态 → 调绘制 → meow_ui_flush_region 只刷变化区 */ ``` **约束**: - 只做**状态时间轴动画**(如滑条拖动回馈、列表项滑动、淡入淡出用 `fill_rect_alpha` 叠加),**不做实时 tween 每帧重算几何**(CPU 软渲扛不住)。 - 动图/视频:**B1 不提供**(无解码器/无 GPU)。若确需"动画封面",用**预渲染位图帧序列**(应用持有若干帧,`on_tick` 按时间切帧 + `flush_region`),这是唯一低成本的动图方案。 - 毛玻璃/模糊背景:**支持廉价版**(HibyOS 同款降采样模糊,见 §11)——用 `meow_ui_img_scaled` 缩 1/8 铺底 + `fill_rect_alpha` 遮罩;**仅在切歌/换封面时算一次,非每帧**。真高斯卷积/全屏动态模糊不做。 --- ## 8. 主题/配置(B1) 观感统一的核心,也是回应"控件样式效果"。**内置日间 / 夜间两套主题,跟随 MeowOS 系统设置的主题模式自动切换。** ```c typedef enum { MEOW_THEME_LIGHT, MEOW_THEME_DARK, MEOW_THEME_AUTO } meow_theme_mode; typedef struct { uint32_t bg; /* 背景色 */ uint32_t fg; /* 前景/正文 */ uint32_t accent; /* 强调色(按钮/选中) */ uint32_t dim; /* 次要文字 */ int font_title; /* 28 */ int font_body; /* 20 */ /* MD3 优先 / MIUIx 备选 */ } meow_theme; /* 当前生效主题(应用只读)。AUTO 时按系统主题设置(display.theme_mode)解析, * 日间取 light 表、夜间取 dark 表;由框架在主题模式变化时刷新。 */ const meow_theme *meow_theme_get(void); /* 设置主题模式(AUTO 时联动系统设置键 display.theme_mode)。 */ void meow_theme_set_mode(meow_theme_mode m); meow_theme_mode meow_theme_get_mode(void); /* 主题变化通知:应用注册后,AUTO 切换日/夜时收到回调(据此重绘)。 */ typedef void (*meow_theme_listener)(meow_theme_mode m); void meow_theme_add_listener(meow_theme_listener cb); void meow_theme_remove_listener(meow_theme_listener cb); ``` **双主题 + 跟随系统设置**: - **日间/夜间**:SDK 内置两套标准色板(`meow_theme_light` / `meow_theme_dark`),日间高亮(浅底深字)、夜间低亮度(深底浅字,护眼省电)。框架所有控件/绘制默认读当前生效主题,应用 `meow_theme_get()` 只读、不自配色。 - **跟随系统设置**:主题模式 `display.theme_mode`(`light`/`dark`/`auto`)是 `meow_settings` 的一个设置键。`auto` 时按系统时间(如 6:00-18:00 日间,其余夜间)或系统亮度/环境判定切换(B1 先用固定时段,后续可接入环境光,见遗留)。框架监听该键,变时对注册的 listener 广播,实体 UI / Web / 应用统一重绘。 - **主题切换即时生效**:`meow_theme_set_mode` 非"只改设置",会立即广播 listener 并让当前绘制的控件用新主题重绘(应用在 `on_tick` 里据 listener 触发 `meow_ui_flush_region`)。 > 主题随 `meow_settings` 的 `display.theme_mode` 键分发;框架统一,应用不必自配色——这正是"实体 UI / Web 面板 / 应用全程一致观感"的保证。 ### 8.1 主题增强:顶级主题 `ui.style` × 明暗 `display.theme` × 背景 `display.background` [源:2026-08-30] > ✅ **已实现(M2.70/0.7.65 起 + MIUIx 完整包 M2.90/0.7.85)**:`meow_ui_set_style`/`meow_ui_style`(`MEOW_STYLE_MD3/MIUIX`)、`meow_theme_get`(MD3 语义色板 + 风格参数 + 背景 surface/bg)、状态栏图标随 `ui.style` 换集(详见 §3.1「同一个状态栏」)。**MIUIx 完整落定**:`meow_ui_theme.c` 为 `MEOW_STYLE_MIUIX` 实现整套 light/dark 配色(暖色基面 + 小米橙 primary + 蓝 tertiary 辅助、更大圆角 20/28、间距 12、细描边 1)+ `display.background` 按 style×mode 派生 MIUIx 色板(§8.1.1 矩阵);设置页 `ui.style` 一键切换即时生效。`meow_settings.c g_defs` 值域已由 `"md3,miui"` 对齐为 `"md3,miuix"`(label "Material You,MIUIx"),存量 `settings.conf`/备份里的 `"miui"` 兼容映射为 `miuix`(见 §8.1.1 注)。**状态栏图标集仍为 MD3 一套**(`sb_icons.h` 无 per-style 换集接口),MIUIx 仅换配色,见 §8.1.2 注。 > **一句话**:主题 = **顶级主题风格族(`ui.style`:MD3 / MIUIx)× 明暗(`display.theme`:dark / light)** 共同决定「整套配色 + 控件形态 + 背景底」。`display.background` **不再是独立全局维度**,而是「当前 style × 当前 mode」下**可选背景底选项集**——**可选集与默认随 style / mode 切换一起变**。**切明暗** → 同 style 的浅/深背景自动切换(修复"浅色不搭");**切 style** → 整套配色 + 背景换族(修复"背景仅对主页生效/只有深色")。本定案建立在 §5.8.4 的 MD3 语义色板之上(色板 = MD3 语义色;本节管 style×mode 选哪套 + 风格参数 + 背景源)。 #### 8.1.1 主题模型(三键语义) | 设置键 | 域 | 语义 | 层级/关系 | |---|---|---|---| | `ui.style` | `md3` / `miuix` | **顶级主题风格族**:定义整套配色风格 / 控件形态 / 圆角 / 间距 / 描边 | 顶级维度;切 style → 整套配色+背景换族 | | `display.theme` | `dark` / `light` | 该 style 下的**明暗**(深浅) | 次级维度;切明暗 → 同 style 浅/深背景自动切换 | | `display.background` | 见 §8.1.4 | **由「当前 style × 当前 mode」决定的背景底选项集**(非独立全局维度) | 派生维度;可选集+默认随 style/mode 一起变 | **style × mode 矩阵(可选背景底集 / 默认)**: | style | mode | 背景底可选集 | 默认 | |---|---|---|---| | MD3 | dark | 黑 / 深蓝 / 墨绿(深色系) | 黑 | | MD3 | light | 白 / 浅蓝 / 浅绿(浅色系) | 白 | | MIUIx | dark | MIUIx 深色系一套(草案) | 随 MIUIx 深色默认 | | MIUIx | light | MIUIx 浅色系一套(草案) | 随 MIUIx 浅色默认 | > **修复语义**:此前 `display.background`(`black`/`blue`/`green`)只对 launcher 主页生效、只有深色、浅色不搭(launcher `settings_page.c s_commit` 的 `display.background` 分支 / `bg_presets`——`blue→0x0A1428`、`green→0x0A2814`、`else→0x000000`)。本定案将背景改为「style×mode 派生选项集」:**每种背景配色定义深/浅两套 surface/bg**(§8.1.4),随 `display.theme` 切换 → 深浅都搭、全界面(状态栏/未声明背景 App/主页)同步。 #### 8.1.2 SDK 接口(一键切换) ```c typedef enum { MEOW_STYLE_MD3 = 0, MEOW_STYLE_MIUIX = 1 } meow_ui_style; /* 设置顶级主题风格族(对应设置键 ui.style)。立即生效并触发全局刷新(见 §8.1.4)。 */ void meow_ui_set_style(int style); /* 越界 clamp 到合法值;同值忽略 */ int meow_ui_style(void); /* 当前 style(默认 MEOW_STYLE_MD3) */ /* 当前「style × mode」完整主题:MD3 语义色板 + 风格参数 + 背景 surface/bg。 * = 现有 meow_theme_get() 的扩展(结构体尾部追加,见 §5.8.4 + §8.1.3;旧字段 bg/fg/accent/dim 保留映射)。 * 应用只读,不自配色。 */ 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` 等价(待定案)。 - **状态栏图标随 `ui.style` 换集**:状态栏右侧图标集与风格族绑定(MD3=默认图标、MIUIx=另套基础);`meow_ui_set_style` 时框架**一并切换状态栏图标集**(触发全局刷新,见 §8.1.4),颜色随 `meow_theme`(style×mode)日/夜(详见 §3.1「同一个状态栏」正确闭环)。 > **现状(M2.90 审计)**:图标集机制尚无 per-style 换集接口——`sb_icons.h` 只有一套 MD3 Material Symbols 位图(`meow_gfx_blit_icon(idx,…)` 按固定索引查 `sb_icons[]`),`meow_ui_set_style` 只改配色(颜色随 `meow_theme`),**不切换图标图形集**。MIUIx 深度图标(另套基础图形)需新增第二张图标表 + 按 `meow_ui_style()` 选择,**未实现**(留后续迭代;当前 MD3/MIUIx 共用同一图标图形、仅配色不同)。 #### 8.1.3 两套风格参数(草案值) `meow_theme` 在 §5.8.4 **尾部追加 MD3 语义色板**基础上,**再尾追风格参数段**(圆角/间距/描边/控件形态)。旧字段 `bg/fg/accent/dim` 保留(映射见 §5.8.4 表)。`font_title`/`font_body`(28/20)不变。 | 参数 | MD3(默认,Material You) | MIUIx(基础,可感知变化) | |---|---|---| | 语义色 | 柔和 tonal(`primary` 蓝、`surface` 中性、`surfaceVariant` 次级) | 更饱和、偏品牌的 `primary`;`surface` 稍偏冷、`surfaceVariant` 稍深 | | radius(圆角) | 中等:按钮 ≈12px / 卡片 ≈16px(草案) | 更大:按钮 ≈18px / 卡片 ≈24px(草案) | | spacing(间距) | 标准 8px 步进 | 略大 10px 步进(草案) | | 描边 / 形态 | **无边框**(靠色面 + 状态层叠色区分) | 更扁平;**细描边**(`outline` 1px,草案);控件更扁平 | | 控件风格 | Material You:圆角填充 + 状态层 | 扁平、圆角更大、间距更大(深度视觉细节后续迭代) | > 两套为**草案值**,实现可微调;关键保证 **MIUIx 与 MD3 有可感知差异**(圆角/描边/间距/主色),避免"切了没感觉"。`MEOW_STYLE_MD3` 默认;MIUIx 深度细节(阴影/渐变/动画)留后续迭代。 #### 8.1.4 背景 = 全局背景源 `display.background`(选项集随 style×mode 变,见 §8.1.1 矩阵)决定主题的 `surface`/`bg`(背景底)→ **同步跟随**三类面: 1. **状态栏(SDK 全局)**:背景 `surface` 即状态栏底; 2. **未声明背景的 App**:App 未自画背景 / 未在 app.json 声明 `background` 字段 → 用系统背景(`meow_theme_get()` 的 `surface`/`bg`); 3. **launcher 主页**:主页背景(launcher `menu_bg`)随背景。 **App 显式声明背景 → 覆盖默认(App 声明优先)**。判定(现状):app.json(§9)**当前无 `background` 字段**(字段现为 name/exec/package/version/framework/immersive/icon),App 亦无系统级"声明背景"API → **App 未自画背景即视为未声明 → 跟随系统**。判定规则:App **自画背景**(自己 `meow_ui_fill_rect` 铺底 / `meow_ui_clear` 指定色)或将来在 app.json 声明 `background` 字段(草案/未实现)→ 用其自绘;否则跟随系统背景。 **深浅成对**:每种 background 配色(黑/深蓝/墨绿/白/浅蓝/浅绿/…)各定义 **深/浅两套 surface/bg**(进 `meow_theme_light` / `meow_theme_dark` 各自取值),随 `display.theme` 切换 → 切明暗时背景自动换成同 style 的对应深浅(浅色模式不再"黑底硬搭")。背景若为**图片路径**(`display.background` 现也允许 `MeowAPPs/backgrounds/xx.png`,语义保留):图片本身即观感 → **深浅切换作用于文字/角色色(surface/onSurface 等)与控件**,背景图不随深浅换(待定案)。 --- ## 9. 目录/构建约定 ``` patch_v2/apps_assets/apps//app.json # 应用描述(新增 SDK/immersive 字段,见下) patch_v2/hello_app/hello.c # 应用源码(改造为调 meow_ui 的示例) patch_v2/sdk/ # SDK 源码 ├── meow_ui.h # 对外 API(本文档 §3-§8) ├── meow_ui_gfx.c # 渲染后端(back_buf+present+font) ├── meow_ui_widget.c # 控件 ├── meow_ui_activity.c # §6.1 ① Activity 栈(控件归属) ├── meow_ui_global.c # §6.1 ② App 跨 Activity 控件(SDK 托管骨架) ├── meow_ui_img.c # stb_image 解码 ├── meow_ui_input.c # 输入线程+语义事件 ├── meow_ui_theme.c ├── stb_image.h / stb_image.c # 单头文件(vendor) patch_v2/meow_launcherd/meow_font.a # 复用(静态链接) ``` 构建出 `patch_v2/sdk/meow_ui.a`;应用编译链(zig-mipsel-cc)额外链 `meow_ui.a meow_font.a libz.a -lpthread`(`meow_font.a` 已内联进 `meow_ui.a`,故实际只需 `meow_ui.a libz.a -lpthread`)。 > **构建耦合(已知设计,非缺陷)**:`build_sdk.sh` 用 `-I../meow_launcherd` 引 `meow_ipc.h`(`meow_notify_ipc.c` 复用共享协议头,同一份、杜绝两端漂移),故 SDK 编译期依赖 launcher 头、**非独立库**;**运行时无碍** —— App 只链 `meow_ui.a`(含 meow_font.o + freetype .o + meow_ui_ft.o),不链入 launcher 符号,故 app 侧无 launcher 依赖。 > > **矢量字(§10.1)不改变 App 链接命令**:FreeType 裁剪后 .o 编入 `meow_ui.a`(`build_sdk.sh` vendor 拉取 + `-DFT_DISABLE_*` 裁剪,静态 musl);App 不调 `meow_ft_*` → `meow_ui_ft.o` 不拉取 → freetype 零增量;调 `meow_ft_*` → 自动拉入,App 无需额外 `-l` 任何 freetype/zlib/png 库。 **app.json 扩展字段**(向后兼容,缺省即旧行为): ```json { "name": "Hello App", "exec": "/usr/data/mnt/sd_0/MeowAPPs/os.meow.hello/hello", "package": "os.meow.hello", // 反域名权威 app_id;目录名=package,launcher 取之为 app_id "version": "0.1", "framework": 1, // 1=走 meow_ui(框架承载);缺省/0=裸自绘(旧) "immersive": 0, // 1=沉浸模式(隐藏虚拟返回键,左缘滑动返回) "icon": "apps/os.meow.hello/icon.png", // 可选:列表图标(供图片层) "background": 0, // M4:可注册无头后台服务(MSG_REGISTER 权限位0) "background_audio": 1, // M4:播放中退出转后台保活(媒体会话活跃=保活,见 §10.5) "background_keepalive": 0 // 批①:进入后台即保活(不依赖媒体会话)=通用常驻 } ``` 上例 ``=`os.meow.hello`(= `apps//` 目录名 = `package` 字段值);exec 即 `<挂载点>/MeowAPPs//`。 > **app 后台常驻语义(M4 + 批①,launcher `meow_bgplay.c` 判定,勿与 `background`(无头服务注册)混用)**: > - `background_audio=true`(M4):媒体会话**活跃**(PLAYING/PAUSED/BUFFER)即保活;暂停也驻留(补 M4 仅 PLAYING 缺口,根治「声明后台播放的 App 一暂停就被杀」)。ncm 已声明。 > - `background_keepalive=true`(批①):**进入后台即保活**,不依赖媒体会话=**通用常驻**(声音/无媒体会话的 App 亦可常驻)。是 `background_audio` 的超集(`meow_bg_should_keep`:keepalive=1 恒驻留;否则 bg_audio && media_resident)。 > - `background=true`(M4):仅注册**无头后台服务**(不同义于保活;`service.` 声明服务策略)。 > - 解析器注意 3 键前缀关系(`background` 是 `background_audio` 前缀、`background_keepalive` 又是 `background` 前缀):键匹配独立(`json_str` ident 前缀隔离),命中顺序靠 `strstr` 先行匹配无副作用——`"background_keepalive":true` 行即使先被 `"background"` 分支命中,`json_str(buf,"background")` 读出的也是 `_keepalive…`≠`true`,自然留 0,无害。**枚举/语义见 `devdocs/plan/APP_BACKGROUND_KEEPALIVE_P1.md`**。 > **app 字体来源(M2.x 已落地)**:app 若**自带字库**(`load_handle`,见 §5.7.3,**未实现**)则用它;否则 SDK `meow_ui_gfx.c load_font()` **优先读 launcher 注入的环境变量** `MEOW_FONT_PNG`/`MEOW_FONT_IDX`(=系统 `font.file` 当前生效字体,绝对路径)/`MEOW_FONT_APP`(family),无 env 则回退卡默认 `MeowOS/fonts/default/regular/font.png`(M2.6 字重结构)→ 固件。因此**app 未自带字库时跟随系统 `font.file`**,不再硬编码 `MeowAPPs/fonts`。 > > **app 多字体(M2.5x,`display.multi_font`)**:launcher 注入 `MEOW_MULTI_FONT=0/1` 与 6 类 `MEOW_FONT_CLS_<类>_PNG/IDX`(命中则覆盖全局;类值为空=跟随全局不注入)。SDK `meow_ui_gfx` 的 `meow_gfx_load_multifont()` 读 env → 调 `meow_font_config_multifont()`(同 png 去重、≤4 套上限、缺类回退全局)构建**逐字符分类路由**:`meow_font_drawtext/width_text/line_height` 按 `meow_font_class_of(cp)`(`MEOW_FCLS_CJK/_CJK_SYM/_NUM/_LATIN/_LATIN_SYM/_OTHER`)用对应字库取字形与度量;`meow_font_has_glyph(cp)` 可预判缺字。`display.multi_font=0`/无 env → 单字体(现状,零额外开销)。底层接口见 meow_font.h:`meow_font_load_slot`/`meow_font_unload_slot`/`meow_font_enable_multifont`/`meow_font_set_class_slot`/`meow_font_config_multifont`;`MEOW_MAX_SLOTS=5`。 > > **app 字体档位(M2.5x,`app.json "fonts"` 声明 + `font.load_full`)**:launcher 按 `app.json "fonts":"common|gbk|full"` 与系统「加载大字库」开关(`font.load_full`,默认 0;开=强制 ≥gbk 档)算出**生效档**,注入 `MEOW_FONT_TIER=<0|1|2>`(0=**common**≈4k 常用 1=**gbk**≈20k+ 大 2=**full** 全/可选),并把 `MEOW_FONT_PNG/IDX` 指向该档字库绝对路径——`meow_font_effective_paths_tiered()` 在卡内 `MeowOS/fonts///` 按 `font.json` 的 `charset`(`common|gbk|full`)选库(gbk 请求接受 gbk/full;full 请求优先 full 再降级 gbk;**M2.6 优先同 family(其次同 weight)**;无匹配回退全局 `font.file`)。SDK `meow_ui_gfx.c load_font()` 载入后,应用可经 `meow_font_tier()` 查询当前档、`meow_font_set_tier()` 切档(触发 `meow_font_reload_tier()` **真重载**:大字库切换先 unload 旧再 load 新,释放旧 atlas,防 GBK/Full 大库双驻留)。**判缺字**:`meow_font_has_glyph(cp)` 预判当前字库是否含该码位(common 小库下缺字→app 回退/提示,避免静默豆腐)。底层接口见 meow_font.h `meow_font_reload_tier`;`font/gen/gen_font.py --charset common|gbk|full [--weight ]` 按档生成 `font.png/font.idx/font.json`(`font.json` 写 `weight`+`charset`+`glyph_count`)。 --- ## 10. 与阶段关系 / 落地顺序 - **B1(本文档,先行)**:`meow_ui.h` + gfx/widget/img/input/theme 最小实现 + `meow_ui.a`;**改造 hello.c 为 SDK 版**作为示例;出包 `r3proii_m2_20.upt` 或独立应用包验证。**不依赖阶段 D。** - **B2(衔接阶段 D)**:输入/导航后端换成 IPC + M3 导航栈,应用框架字段 `framework=1` 的应用自动升级为"导航栈承载",裸应用保持兼容。 - **依赖**:`meow_present/back_buf/dirty`(M2.16 地基,✅)、`meow_font`(✅)、`blend565`(✅)——B1 全部基于现有能力,**无需新的硬件依赖**;仅新增 stb_image(单头文件)与输入线程。 ### 10.1 矢量字(`meow_ft_*`,2026-09-02 追加) **定位(SDK 演进的最后一块)**:SDK 位图字库(`meow_font`)只有**离散字号档位**,且全字形 atlas 稳态 17.3MiB(`FONT_MEMORY_DISCIPLINE.md` 实测)在 27MB 整机装不下 → 大字库/大字号场景必然出豆腐。阅读器 R1/R2 的 FreeType 管线已在真机验证(MiSans OTF 渲染,见 `READER_ENGINE_NOTES §5`)。本组 API 把该管线**上收 SDK**,让任意 App 用上**无级字号 + CJK 全字形**(解决豆腐类问题的根)。 **与位图字库并存(关键约定)**:App **显式**调用 `meow_ft_*` 才走矢量;既有 `meow_ui_text` 路径**一字不改**(默认仍是 `meow_font`)。`meow_ft_*` 全组是**追加 API**,不删改任何既有签名。 **API(`include/meow_ui_font.h`,公共)**: ```c int meow_ft_load(const char *path); /* 载入 TTF/OTF;0=成功;重载清缓存 */ void meow_ft_unload(void); /* 释放矢量字体(可再 load) */ int meow_ft_loaded(void); /* 1=已载入;0=text/width 降级位图 */ int meow_ft_text(int x, int y, const char *utf8, int px, uint32_t color); /* 矢量渲染;0=矢量;-1=无字体→已降级位图 */ int meow_ft_width(const char *utf8, int px); /* 矢量宽度(px,advance 累加,不栅格化) */ int meow_ft_line_height(int px); /* ascender - descender + 1 */ int meow_ft_ascender(int px); int meow_ft_descender(int px); int meow_ft_cache_set_slots(int slots); /* glyph LRU 容量(默认 512;256 省内存 / 1024 提命中) */ int meow_ft_cache_slots(void); int meow_ft_cache_used(void); ``` - 坐标约定:`(x,y)=行首升线顶(同 meow_ui_text);baseline = y + meow_ft_ascender(px)`;px=字号(无级);color=24-bit RGB(内部按 back_buf 底色逐像素 blend565 反锯齿)。 - 渲染/测宽:`meow_ft_text`/`meow_ft_width` 逐码点(UTF-8),从 LRU 缓存取字形(**灰阶 `FT_LOAD_RENDER|FT_LOAD_NO_HINTING|FT_LOAD_TARGET_LIGHT`**,按当前 back_buf 底色 blend565);`meow_ft_width` 用 `FT_Load_Glyph` 不 RENDER(`FT_LOAD_NO_HINTING|FT_LOAD_TARGET_LIGHT`),不占 LRU 槽。 **降级回退(绝不豆腐不崩)**:加载失败 / 无卡 / 无字体文件 / 未调用 `meow_ft_load` → `meow_ft_loaded()==0`;此时 `meow_ft_text` **自动改走** `meow_ui_text`(位图字库 → 5x7 兜底),`meow_ft_width/line_height` 降级为位图字库度量,返回 `-1` 供 App 感知。App 若坚持 vector(缺字时)应先用 `meow_ft_loaded()` 判可用性。 **内存纪律**(目标单 App **≤1.5MB 增量**): - `FT_New_Face(path)` **按需读文件**(`stream->base=NULL`,不整文件载入 → 零大块 anon;TTF/OTF disk-backed,可被内核回收重读)。**不做** mmap 后 `FT_New_Memory_Face`(会把整文件映射进地址空间)。 - anon 只含 FT 结构(~几十 KB)+ glyph LRU 位图。**glyph LRU 512 默认(≈0.46MB:28px CJK 约 900B/字),可配 256/1024**;容量设小/换字号/换字体时清空重建(key=`(px<<32)|cp`,不同字号可共存)。 - **增量只发生在 App 调用 `meow_ft_*` 时**:`meow_ui_ft.o` 是 meow_ui.a 独立成员,链接器仅在 App 引用 `meow_ft_*` 符号时拉取它及随之的 FreeType .o;不调 `meow_ft_*` 的 App **零增量**(`meow_ui_ft.o`/freetype 不拉入)。已验证:不调 ft 的 App 链接产物无任何 FT 符号。 - FreeType 走 `sdk/vendor/`(**gitignore**,`build_sdk.sh` 可复现拉取 + 裁剪构建:`-DFT_DISABLE_ZLIB/BZIP2/PNG/HARFBUZZ/BROTLI=TRUE`,静态 musl);无需 zlib/png,App 链接命令**不变**(仍 `meow_ui.a libz.a -lpthread`)。 **构建**:`build_sdk.sh` 增加 §0 FreeType vendor 拉取 + 裁剪构建(产物 `sdk/vendor/freetype-build/libfreetype.a`,其 .o 编入 meow_ui.a)。SDK 自身 `-Os -static -Wall -Wextra` 交叉 0 warning(clang 不报既有 gcc 的 misleading-indentation)。 **License**:FreeType 2.14.3(FreeType License,BSD 风格宽松许可;静态链入闭源固件无 LGPL 式 relink 义务,与阅读器 vendor 同源同版本)。NOTICE 同 `patch_v2/reader_app/NOTICE` 的 FreeType 条目。 --- ## 11. 硬件约束(务必让应用作者知道) | 能力 | 可行性 | 说明 | |---|---|---| | 图片(PNG/JPEG/BMP 静态图) | ✅ 支持 | stb_image 解码 + 最近邻缩放;封面居中裁方 | | 半透明叠加 | ✅ 支持 | `fill_rect_alpha`(blend565),面板/遮罩可用 | | 圆角/描边 | ✅ 支持 | 圆角框 + 字体描边 | | 毛玻璃/模糊背景 | ✅ 支持(廉价版) | **HibyOS 同款「降采样模糊」**:封面缩到 1/8 再放大铺底(天然低通滤波抹掉高频),叠一层 `fill_rect_alpha` 半透明遮罩,清晰小封面叠其上。**背景只在切歌/换封面时算一次,非每帧**;不是真高斯卷积、不需 GPU。用 `meow_ui_img_blur_bg()` 生成背景 cache。**已实测依据**:设备 `playing_plane/` 无模糊背景图 + 无 GPU/无 SIMD/14MB 内存 → 运行时模糊只能走降采样近似,成本微秒级 | | 控件动画(时间轴/帧序列) | ⚠️ 有限 | 只做状态式动画;实时 tween 不做;动图=预渲染帧序列 | | 动图(GIF/WebP 动画) | ⚠️ 弱支持 | 用预渲染位图帧序列模拟;不承诺实时 GIF 解码 | | 视频 | ❌ 不做 | 无解码器/无 GPU/内存 14MB 会 OOM;如需"视频"用预渲染帧序列 | | 刷新率 | 上限 60(默认)/30/15 | 应用动画别贪高帧率,软渲耗 CPU | --- ## 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 2 维虚拟化宫格(`meow_lazygrid`,M7.4 主界面前置) > **性质**:给「卡片/图标宫格」提供**二维虚拟化**(`include/meow_lazygrid.h`,追加入 `meow_ui.h` 伞头)—— > 应用入口宫格、图标墙、设置快捷卡等。只实例化**可见行**(视口内),行高/列几何(`cols`+`cell_w/h`+`gap`); > 滚动复用 §12.3 `meow_scroll`(fling/snap,与 listview 同款惯性);命中 `hit`/`tap_at` 把滚动+网格坐标映射回 `item idx`。 > **非 `meow_widget`**:自包含控件(不注册 z 序/绘制注册表),由宿主(launcher session)在 draw 阶段调 > `meow_lazygrid_draw`、命中阶段调 `hit`/`tap_at`、滚动喂 `meow_scroll`——与 `meow_scroll`/`meow_box` 同为**正交模块**, > 不侵入 `struct meow_widget`(**全追加式**)。 **数据源 / 渲染**: - 数据源:`count_fn()` 返回总项数(任意长),`bind_fn(index, item, ctx)` 仅当 item「进入视口」(绑定到固定池槽)时调一次; 回滚命中同槽不重绑(同 listview 池语义);`count` 每帧实时查(支持动态长度/搜索过滤)。 - 固定 item 池:池容量 = 视口行数 + 2(× cols),静态有界(≤`MEOW_LAZYGRID_POOL_MAX`=96)。 - 渲染:默认 renderer 画「圆角卡片(icon+名+状态)」;App 也可经 `set_item_draw` 完全自绘 (主界面卡片宫格用:icon+名+后台标注)。支持选中高亮/禁用变淡/空态占位。 **API(`include/meow_lazygrid.h`)**: ```c typedef struct meow_lazygrid meow_lazygrid; typedef struct meow_lazygrid_item { const char *title, *subtitle; /* 主/副文本(subtitle 可空);title 必填 */ const meow_img *icon; /* 格内图标(NULL=无) */ int enabled; /* 1=可交互;0=变淡不响应 */ void *user; /* App 私有(跨 bind 保存业务态;SDK 不碰) */ } meow_lazygrid_item; typedef int (*meow_lazygrid_count_fn)(void *ctx); typedef void (*meow_lazygrid_bind_fn)(int index, meow_lazygrid_item *item, void *ctx); typedef void (*meow_lazygrid_draw_fn)(int index, int x, int y, int w, int h, int state, void *ctx); typedef void (*meow_lazygrid_tap_fn)(int idx, void *ctx); #define MEOW_LAZYGRID_POOL_MAX 96 /* 生命周期(冷路径 create/free;热路径 draw/hit/scroll 零 malloc) */ meow_lazygrid *meow_lazygrid_create(int x, int y, int w, int h, int cols, int cell_w, int cell_h, int gap); void meow_lazygrid_free(meow_lazygrid *g); void meow_lazygrid_set_rect(meow_lazygrid *g, int x, int y, int w, int h); /* 视口矩形(绝对坐标) */ void meow_lazygrid_set_cols(meow_lazygrid *g, int cols); void meow_lazygrid_set_cell(meow_lazygrid *g, int cell_w, int cell_h, int gap); /* 数据源 + 渲染 */ int meow_lazygrid_set_vsource(meow_lazygrid *g, meow_lazygrid_count_fn count, meow_lazygrid_bind_fn bind, void *ctx); int meow_lazygrid_set_item_draw(meow_lazygrid *g, meow_lazygrid_draw_fn draw, void *ctx); /* NULL=默认卡片 */ int meow_lazygrid_set_empty_text(meow_lazygrid *g, const char *text); /* 滚动(像素级;可视行数按 offset 实时推) */ void meow_lazygrid_set_scroll(meow_lazygrid *g, int off); void meow_lazygrid_scroll_by(meow_lazygrid *g, int dy); int meow_lazygrid_scroll(meow_lazygrid *g); int meow_lazygrid_max_scroll(meow_lazygrid *g); /* max(0, 内容高 - 视口高) */ int meow_lazygrid_row_h(const meow_lazygrid *g); /* 行高(=cell_h+gap) */ meow_scroll *meow_lazygrid_scroll_attach(meow_lazygrid *g); /* 包一个 meow_scroll 会话;NULL=池满 */ /* 选中 / 命中 */ void meow_lazygrid_set_on_tap(meow_lazygrid *g, meow_lazygrid_tap_fn cb, void *ctx); void meow_lazygrid_set_sel(meow_lazygrid *g, int idx); /* 越界 clamp;<0 清除;标脏 */ int meow_lazygrid_sel(const meow_lazygrid *g); int meow_lazygrid_hit(meow_lazygrid *g, int x, int y); /* 命中项 idx(滚动+网格坐标映射);-1=未命中/禁用 */ int meow_lazygrid_tap_at(meow_lazygrid *g, int x, int y);/* 命中:置选中+回调+返回 idx/-1 */ /* 绘制 */ void meow_lazygrid_draw(meow_lazygrid *g, const meow_theme *th); ``` - **内存纪律**:静态有界 item 池(冷路径 create/set_vsource 分配,热路径 draw/hit/scroll **零 malloc**),与 listview 池同源;不常驻 count 个 item 对象。 - **M7 用途**:主界面(M7.4)应用入口/图标墙**虚拟化宫格**(并行+回退位)。 ### 12.5 对既有 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. SDK 系统侧通道(网络自愿 / 磁盘缓存治理) > **性质**:App 与系统治理模块(netmgrd / svc_perf)之间的**自愿自治**接口——不强制(防君子不防小人), > best-effort,失败静默不影响业务。`include/meow_ui_net.h` 与 `include/meow_cachegov.h` 均**追加入 `meow_ui.h` 伞头**。 ### 14.1 网络自愿通道(`meow_ui_net`,netmgrd 期4) > **性质**:框架 App 联网前的**自愿策略自查**通道 + **自愿字节上报**。权威方案 > `devdocs/plan/NETMGR_DESIGN.md` §6.3/§4(内核无 per-socket/per-uid 字节计数器,§4 的「字节级精确记账走 SDK 自报通道」由此落地)。 **API(`include/meow_ui_net.h`)**: ```c /* 检查 host 是否被策略放行。0=allow(可联网);1=block(策略拒绝,App 应中止本次联网)。 * host 为域名(如流媒体客户端 API 主机名);判定以当前 App(MEOW_LOG_APP)为实体,服务端策略引擎权威执行。 * netmgrd 不在 / 超时 → 返回 0(乐观放行,绝不因监控不可用而误封业务)。 */ int meow_net_policy_check(const char *host); /* 上报本进程累计收发字节(best-effort,失败静默)。netmgrd 记入 per-实体统计(status JSON `entity_bytes`)。 */ void meow_net_report(uint32_t rx, uint32_t tx); ``` - **用法**:App 在发起 HTTP/TLS 前调 `meow_net_policy_check(host)`;返回 1 则按产品语义处理(如提示被限)。 - **代价**:单查询/单上报,无常驻状态,失败静默不影响业务;热路径零 malloc。 - **用途**:框架 App(阅读器/流媒体客户端/文件等)联网前自查 + 自愿上报流量(netmgrd 期4 收口)。 ### 14.2 磁盘缓存治理(`meow_cachegov`,OOM page-cache 治理 A 侧) > **性质**:磁盘缓存 **page-cache** 治理 SDK 库。归属(`devdocs/plan/ARCHITECTURE_PRINCIPLES.md` ③混合): > **A(DONTNEED 热路径,需要 read_pos)** 随 SDK 库(进程内直读 read_pos,无 IPC);**C(设备级 > `/proc/meminfo` Cached 监控降载)** 归系统服务 `meow_svc_perf`,本模块只读其共享状态文件 > (`MEOW_CACHEGOV_C_FILE`)作为「降载」信号;**App(如流媒体客户端)侧只调本库接口**:登记文件 + read_pos > 回调 + tick + drop_finished + 查询降载,不写自己的治理算法。权威方案 `devdocs/plan/OOM_PAGECACHE_GOVERNANCE.md`。 **分级(§2.2,红线 L2)**: - **L0(默认,安全)**:曲终/commit 后对整文件 `posix_fadvise(DONTNEED)`——播放器已退出解码循环。 - **L1(可选,主杠杆)**:播放中丢已读前缀 `[0, read_pos-keep_hot)`,保 `keep_hot` 窗口供倒带; 仅 `drop_mode>=1` 且 A 未回退到 ≥2 时执行。 - **L2(禁止,红线)**:绝不丢 read_pos 前方/当前未解码页。 - **underrun 回退(§2.3,SDK 内置)**:App 在 pread 慢(数据在盘上但 >threshold ms)处上报 `meow_cachegov_note_refault()`;本库滚窗 10s,超门限逐级回退(放大 keep_hot → 降 L0 → 关 A)。 **API(`include/meow_cachegov.h`)**: ```c typedef struct meow_cachegov_cfg { int enabled; /* 总开关:0=A 完全不丢页(register/tick/drop_finished 全部 no-op) */ int cached_threshold_kb; /* 保留:C 侧阈值归 svc_perf(本库恒 0,只读不控,防误用) */ int dirty_warn_kb; /* 保留:同上(svc_perf 用;本库只透传日志) */ size_t keep_hot_bytes; /* L1 保留窗口:read_pos 之后保多少不丢(回退时自动放大) */ size_t min_hot_ahead; /* L2 红线:read_pos 前方至少保多少——仅防御注释,绝不参与 drop */ int drop_mode; /* 0=仅曲终(L0);1=曲终+播放中丢已读前缀(L0+L1,配 seek 回退) */ int refault_backoff; /* 10s 窗口内 refault 触发回退的门限(次数) */ } meow_cachegov_cfg_t; int meow_cachegov_register(const char *path, uint64_t size, uint64_t (*read_pos_fn)(void *ctx), void *ctx); /* 登记缓存文件;重复 path 幂等 */ int meow_cachegov_unregister(const char *path); /* 注销(曲终/切歌后清理);找到并删/不在册 */ int meow_cachegov_tick(void); /* 周期调用:①按回退档丢已读前缀 ②评估 refault 滚窗回退 */ int meow_cachegov_drop_finished(const char *path); /* 曲终/commit:整文件 L0 DONTNEED + 注销条目 */ int meow_cachegov_download_paused(void); /* 1=应暂停(A 回退硬暂停 OR svc_perf C 降载) */ int meow_cachegov_prefetch_paused(void); int meow_cachegov_set_cfg(const meow_cachegov_cfg_t *cfg); int meow_cachegov_get_cfg(meow_cachegov_cfg_t *cfg); void meow_cachegov_note_refault(void); /* pread 慢处上报一次 refault(线程安全) */ int meow_cachegov_backoff_level(void); /* 0=正常 1=keep_hot 放大 2=降 L0 3=A 关 */ ``` - **内存/代价**:自含(pthread + meow_log),热路径零 malloc;`set_cfg` 运行时覆盖编译期默认。 - **红线**:`min_hot_ahead` 只是防御注释、**绝不参与 drop**;本库无论何档都不丢 `[read_pos, ...)` 任何未解码区。 - **M7 用途**:流媒体客户端(dev.player.ncm)缓存文件治理(登记缓存 + read_pos + tick + 曲终 `drop_finished`); 与 `meow_svc_perf` 的 C 侧降载协同,治 OOM 的 page-cache 驻留。 --- ## 15. 遗留 / 待实现 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 抢)。 3. 流媒体客户端跑通后迭代控件集(播放页/列表/歌词),非一次性补齐。 4. **`meow_ui_img_blur_bg`(降采样模糊背景,HibyOS 同款)API 待加**:已在 §11 确认可行性(缩 1/8 放大铺底 + α 遮罩,切歌算一次);函数签名与实现留到 B1 之后(或并入流媒体客户端播放页时)。当前 SDK B1 的图片层先用 `meow_ui_img_scaled` + `fill_rect_alpha` 手动组合即可达到同等观感。 5. 视频**已定不做**:无解码器/无 GPU/内存 14MB 会 OOM;"视频"一律用预渲染帧序列模拟。若确需播放具体小视频,宿主机离线转帧序列 PNG → 打包到 TF 卡 → SDK 按帧播放(规避设备一切解码负担)。 ### B1 落地状态(2026-08-29) - **B1.0 渲染后端** ✅ `meow_ui_gfx.c`:自建 fb 上下文(自 mmap `/dev/fb0`、自建 `back_buf`、独立脏区集 + vsync+原子 blit);无 fb 时(宿主/qemu)离屏 480×720,`MEOW_UI_DUMP=` 可把 back_buf 导出 PPM 截图。 - **B1.1 绘制层** ✅ §3 全量:`meow_ui_clear/put_px/fill_rect/draw_rect/draw_rect_round/fill_rect_alpha/blend565` + `text/text_right/text_center/text_width/text_height`。字库(真机)优先,回退内建 5×7 点阵。 - **B1.2 图片层** ✅ `meow_ui_img.c` + 内建 `stb_image`(PNG/JPEG/BMP→RGBA):`meow_img_load_file/mem/free/w/h`、`meow_ui_img/scaled/alpha/cover`(最近邻 + 居中裁方)。宿主已用 test.png 验证预览/裁剪/缩放。 - **B1.3 控件层** ✅ `meow_ui_widget.c`:`meow_btn/slider/switch/list` 最小集(绘制 + 命中 + 取值)。 - **B1.4 输入层** ✅ `meow_ui_input.c`:输入线程读 `event*`(触摸 goodix-ts / md-gpio-keys / ADC 键),翻译成 `on_key/on_tap/on_swipe/on_tick` 语义事件;`meow_ui_run/request_exit`。手势专项(GESTURE_BACK):左缘返回手势=抬手触发 + `MEOW_EV_SWIPE_PROGRESS` 进度上报 + `on_swipe_progress` 回调;SDK 自动画顶部进度条 `meow_ui_swipe_hint`。 - **配套:Launcher 统一触摸接口 `meow_touch`**(M2.25,launcher 内部,应用不依赖)✅ `patch_v2/meow_launcherd/meow_touch.{h,c}`:Launcher 自身 UI(应用列表 / 电源菜单 / 确认框)的低性能触摸解析收敛为一份,goodix 时序免疫(`BTN_TOUCH=1` 不锁原点,等按下后首个有效 `ABS` 才锁;抬手位移 ≥65px 判滑动不命中)。应用侧**不**用它——应用触摸由本 SDK 输入层(`meow_ui_input.c`)翻译成 `on_tap/on_swipe` 回调,两者互不干扰;与 SDK 触摸保持一致的 goodix 时序免疫语义(详见 `meow_touch.h` 注释)。 - **B1.5 主题** ✅ `meow_ui_theme.c`:日间/夜间两套色板 + `get/set_mode/get_mode/add/remove_listener`;跟随 `display.theme_mode`(`/usr/data/meow/settings.conf`)。AUTO 按本地 6:00-18:00 日间。 - **B1.6 IME** ✅ `meow_ui_ime.c`:英文 QWERTY / 数字软键盘(ASCII),`meow_ui_text_input`(阻塞)+ `meow_ui_text_input_async/meow_ime_active/meow_ime_text`;覆盖底部约 300px,背景点=取消、OK=确认。 - **构建** ✅ `patch_v2/sdk/meow_ui.a`(含 meow_font.o 内联,应用只需链 `meow_ui.a libz.a -lpthread`);`build_sdk.sh` 一键重编。宿主 + `qemu-mipsel` 均跑通;示例 `hello` 改造为 SDK 版(见 §9 目录与 `patch_v2/hello_app/build_hello.sh`)。 - **验证**:`meow_ui.a` mipsel 静态编译链接 ✅(宿主 demo + qemu 均导出可见画面到 `device_test/`);真机 /dev/fb0 几何与输入触屏**未实测**(设备本次不可达,逻辑参照 launcher 既有实现)。 **实现期 API 微调(相对本文档)**: - [D1] `meow_ui_opts` 补充 `width/height`(0=自动读 fb;离屏缺省 480×720)。 - [D2] `meow_list_create` 数据源回调定案为 `int (*)(int idx, char *label, int len, void *ctx)`。 - [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 作为设计蓝本。 --- ## 16. 歌词组件(`meow_lyric` / `meow_lyric_view` / `meow_media_lyric`,批1-3) 三件套把网易云 ncm 的歌词能力**通用化上收**,供 launcher Now Playing 与 ncm 等任意 App 复用;launcher 与 ncm 共用同一份实现,避免两端漂移。 ### 16.1 `meow_lyric`(批1:LRC 时间轴解析 / 查询 / 折行) 独立公共头 `meow_lyric.h`(**不随 `meow_ui.h` 伞头发布**;App 显式 `#include "meow_lyric.h"`)。纯 libc + pthread、**不依赖其它 SDK 对象**(不含 meow_log,便于 non-meow_ui.a 使用方独立链入);宿主 gcc 可直接编译单测。 ```c void meow_lyric_clear(void); /* 清空全部数据 */ int meow_lyric_parse(const char *lrc, const char *tlrc); /* 解析原文+翻译,按时间升序排序,返回行数 */ int meow_lyric_count(void); int meow_lyric_line_at(int64_t ms); /* last timestamp<=ms;首戳前=0;末戳后=n-1 */ const char *meow_lyric_line_text(int i); /* 第 i 行原文;越界 "" */ const char *meow_lyric_line_tl(int i); /* 第 i 行翻译;越界/无翻译 "" */ int64_t meow_lyric_line_time(int i); /* 第 i 行时间戳(ms);越界 -1(供「点行跳转」seek) */ void meow_lyric_set_max_lines(int max); /* 行上限 [1,MEOW_LYRIC_LINES_MAX],下次 parse 生效 */ int meow_lyric_wrap(const char *txt, int size, int maxw, char seg[][MEOW_LYRIC_SEG_SZ], int max_rows, meow_lyric_width_fn width, void *ctx); /* 折行 ≤max_rows 段(宽测经回调) */ ``` - **容量/静态存储**:行数硬上限 `MEOW_LYRIC_LINES_MAX`(600);单行文本(原文/翻译)上限 `MEOW_LYRIC_TEXT_MAX`(192) 字节;折行单段 `MEOW_LYRIC_SEG_SZ`(256)。 - **输入**:标准 LRC 行 `[mm:ss.xx]正文`;lrc=原文、tlrc=翻译,翻译按相同 timestamp 合并到对应原文行(存 tl),无匹配忽略。服务器字面序列:网易云把多行文本用字面 `\n`/`\r` 塞进一行(渲染豆腐/凌乱)→ 按空格替换(`\r\n`/`\n\r` 折单空格,语义同 ncm copy_lrc_text)。 - **时间戳小数位自适应**:1 位=0.1s(×100)、2 位=0.01s(×10,标准 LRC 厘秒)、3 位=0.001s(×1,网易云 LRC 毫秒)、>3 位截断到毫秒——避免把网易云 3 位毫秒当 2 位厘秒解析导致时间放大。 - **线程安全(单写多读,同 ncm lyric.c 交错安全)**:数据后台线程写(parse/clear/set_max_lines)、UI 线程读(count/line_at/line_text/line_tl/line_time);全部函数内部持同一 pthread 互斥、函数级粒度、无嵌套(清空/解析用 nolock 私有版防自锁死)。**读返回指针指向静态存储,在下次 parse/clear 前有效**(调用方不得跨重建持有)。 ### 16.2 `meow_lyric_view`(批2:歌词页渲染控件) 独立公共头 `meow_lyric_view.h`(仿 meow_ui_view.h 模式,不随 `meow_ui.h` 伞头发布)。把 ncm 歌词页布局上收成 SDK 控件:活动行纯白(居中偏上锚定)、上下行按行距逐级灰阶、翻译行紧跟原文不叠行(块高含翻译行)、超宽折行(中西文兼容)、上方保底 ≥above_min 行已过、总视觉行数 ≤max_rows、点行 seek 命中与绘制一致。 ```c meow_lyric_view *meow_lyric_view_create(int x,int y,int w,int h,meow_lyric_width_fn width,void *width_ctx); void meow_lyric_view_free(meow_lyric_view *lv); meow_widget *meow_lyric_view_widget(const meow_lyric_view *lv); /* 底层 W_CUSTOM 控件 */ void meow_lyric_view_set_pos(meow_lyric_view *lv, int64_t pos_ms); /* 播放器位置 */ void meow_lyric_view_set_colors(meow_lyric_view *lv, const meow_lyric_view_colors *c); /* NULL=回主题默认 */ void meow_lyric_view_set_layout(meow_lyric_view *lv, const meow_lyric_view_layout *ls); /* NULL=回内建默认 */ void meow_lyric_view_set_empty(meow_lyric_view *lv, const char *txt); /* 无歌词占位;NULL=默认 */ void meow_lyric_view_set_on_seek(meow_lyric_view *lv, meow_lyric_seek_fn cb, void *ctx); /* 点行 seek */ uint32_t meow_lyric_view_cur_color(const meow_lyric_view *lv); /* 主题默认或覆盖的活动行色 */ uint32_t meow_lyric_view_line_color(const meow_lyric_view *lv, int dist); /* 距活动行 dist(0..5) 行色 */ int meow_lyric_view_hit_line(meow_lyric_view *lv, int x, int y); /* 命中行下标;未命中 -1 */ int meow_lyric_view_visible(meow_lyric_view *lv, int64_t pos_ms, meow_lyric_view_vis *out, int cap); ``` - **数据来源**:绑定批1 `meow_lyric`(全局单例,线程安全)+ 播放位置(`set_pos`);宽度测量用回调注入(`meow_lyric_width_fn`,包 meow_ui_text_width;宿主测试传自测函数)。App 可只用查询接口(`visible`/`hit_line`)在自绘流程里复用布局逻辑。 - **主题**:SDK 全局 `meow_theme` 为默认(明暗背景 scrim、活动行 onSurface=dark 纯白、灰阶递减);`meow_lyric_view_colors` 为批5 封面取色覆盖入口(**字段 0=用主题默认**):`scrim`(黑叠加封面之上)、`cur`(活动行色)、`gray[6]`(距活动行 0..5 灰阶;gray[0]=cur,字段 0=自动递减)、`scrim_alpha`(<0=默认176,0=不画 scrim)。 - **布局参数**(`meow_lyric_view_layout`,NULL=内建默认):`pad`(24)/`sub_lh`(44)/`cur_sz`(28)/`oth_sz`(24)/`tl_sz`(20)/`max_rows`(9,≤`MEOW_LYRIC_VIEW_MAX_ROWS`=9)/`above_min`(2)/`above_off`(50 偏上)。 - **可见条目**(`meow_lyric_view_vis`,布局/命中/绘制共用同一算法,保证「点行 seek 命中与绘制一致」):`ent`=歌词行下标、`rw`=原文折行段数、`bh`=块高(子行数)=rw+当前行翻译占 1、`sz`=字号、`y`=块顶 y(绝对屏幕坐标)。 ### 16.3 `meow_media_lyric_*`(批3:媒体会话 live 歌词源) `meow_media.h`(launcher 侧**媒体会话注册表**,launcher/ncm 共用)。App 声明 MF_PLAYBACK + 上报 `MSG_META` → 成为媒体源;最近活跃者=当前源;通知中心媒体卡片 / Now Playing / 物理媒体键 / 控制器统一消费。 ```c int meow_media_meta(const m_meta_t *m, int fd); /* 按 app_id 归并会话;-1=会话满/参数非法 */ const meow_media_session_t *meow_media_current(void); /* 最近活跃源;无返回 NULL */ int meow_media_has_source(void); /* 当前源存在且 state!=STOPPED */ int meow_media_app_playing(const char *app_id); /* 叠加媒体保活(WORK_STATE §10.5):state==PLAYING 且匹配 */ int meow_media_app_resident(const char *app_id); /* 批①常驻判定:会话存在且 state!=STOPPED */ int meow_media_playctl(uint32_t cmd, int64_t seek_ms); /* 向当前源下发 PLAYCTL */ int meow_media_playctl_to(const char *app_id, uint32_t cmd, int64_t seek_ms); void meow_media_clear(const char *app_id); /* 清某 app 会话;空/NULL=清全部 */ void meow_media_tick(void); /* ~1s:播放/缓冲超时清槽回空态 */ uint32_t meow_media_display_pos(const meow_media_session_t *s);/* 播放中按墙钟推进(clamp);暂停/缓冲原样 */ /* 歌词(批3 · live lyric 数据源) */ int meow_media_lyric(const m_lyric_t *l); /* 上报当前曲歌词分片;-1=会话不存在/参数非法 */ int meow_media_lyric_valid(const meow_media_session_t *s); /* 会话携带有效歌词(lrc 非空) */ const char *meow_media_lyric_lrc(const meow_media_session_t *s); /* 原文;无则 "" */ const char *meow_media_lyric_tlrc(const meow_media_session_t *s); /* 翻译;无则 "" */ ``` - **会话**:一个 App 一个槽(`MEOW_MEDIA_MAX`=4,app_id 归并防占满);当前源=最近活跃(`last_active_ms`/`active_seq` 最大)。源死亡判定:播放中(1)/缓冲(3) 超 `MEOW_MEDIA_STALE_MS`(30000) 无 META → 清槽回空态(App 死/服务断);暂停(2)/停止(0) 不超时。`state`:STOPPED=0 / PLAYING=1 / PAUSED=2 / BUFFER=3。 - **live 歌词(批3)**:App 经 `MSG_META_LYRIC` 分片上报当前曲歌词 → 按 app_id 归并会话内有序缓冲(`lyric_lrc`/`lyric_tlrc`,超 `MEOW_MEDIA_LRC_MAX`/`TLRC_MAX` 截断);`lyric_seq`=歌曲代际(新歌/新词自增,launcher 见新 seq 清旧词)。**取数链**:桌面歌词页 `meow_media_lyric_lrc/tlrc` 取文本 → 批1 `meow_lyric_parse` 解析 → `meow_lyric_line_at(meow_media_display_pos(s))` → 批2 `meow_lyric_view` 实时滚动。 - **依赖注入**:发送/日志经 `meow_media_ops`(launcher 用 `ipc_send`/`logmsg`;宿主单测用 socket write/NULL),可独立单测(`test_media.c`)。发送 `MSG_META_LYRIC` 前 App 须先报 `MSG_META` 注册会话,否则歌词被丢弃(防 fd=0 槽)。 --- *文档基于 2026-08-29 现有渲染基建(M2.16 无闪烁地基 + meow_font + blend565)编写;API 名会随 B1 实现微调,实现时以 `meow_ui.h` 为准。*