在既有 W_LISTVIEW (src2 每帧取行) 之上叠加虚拟化层 (API 追加式, 旧 API 一字不改): - 数据源 count_fn + bind_fn (index, meow_lv_item*, ctx); count 每帧实时查 (任意长度≥65535, 不常驻 item 对象); bind 仅在行进入视口时被调一次 - 固定 item 池: 视口行数+2 静态槽 (有界 ≤96), 回滚不重绑 (同 index 命中同槽); 整表扫过后 wrap 回收合法但每次 draw 有界 (≤视口窗口) - 多行富 item: 主标题/副标题/左图标·缩略图/右尾件; 行高 set_item_h(px) 或 set_item_tier(1→48/2→56); 默认 renderer 主题语义色绘制; set_item_draw 完全自绘; section 段头占位行(非粘性); 空态占位 - 集成: tap_at→meow_ui_focus_acquire (§6.4); meow_scroll_wrap(lv,1) track+fling+snap(行边界); 选中/按压态走主题; enabled=0 变淡不响应 - 兼容: src2==NULL 走既有路径; W_LIST/W_LISTVIEW 单行用法零改动 验证(宿主): 新测试 test_meow_ui_listvirt T1-T5 全 44 PASS (10000 项滚动窗口内容正确/hit 映射/ bind 有界=count/回滚不重绑/多行渲染/截断/图标占位/段头/空态/自绘/焦点/fling 吸附); 全量回归 widgets/dispatch/box/anim/scroll/ime/pinyin/overlay/notif/sdk_input_keys/p1p2p3 全 PASS; 交叉 zig-mipsel -Wall -Wextra 全 SDK TU 0 warning; meow_ui.a 新 md5=2a2d8000b95dd211d09dc736d5538ad0 文档: MEOW_APP_SDK.md §5.5 同步虚拟化层(API 一览/池语义/集成/回归).
125 KiB
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 字体分层 |
meow_ui_audio.h |
独占音频客户端 | §11 |
其余按需显式 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 = 左上。
/* 初始化/销毁(应用 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.<id>.fullscreen 由 launcher 解析并注入 env MEOW_FULLSCREEN 预设,见 §3.1 注 [源:审计 2026-08-30] / docs/MEOWOS_SPEC_REF §3.1 A-12)。数据由 SDK 内置状态读取器抓系统快照(与 launcher 同源单点一致),非各画各的。
/* 状态栏显隐(默认显示;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.<id>.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.<id>.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。这是对"图片渲染"的直接答案(背景/封面/图标)。
/* 加载图片(从 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);
/* 查询 */
int meow_img_w(meow_img *img);
int meow_img_h(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居中裁方 + 缩放到目标尺寸即可。
5. 控件层 API(B1)
内置常用控件,观感统一(MD3 优先 / MIUIx 备选,见 §6 主题)。控件状态由 meow_ui 维护,绘制走 back_buf。
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。)
/* 弹出软键盘,输入到应用提供的缓冲。返回 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(回车)= 提交并关闭(拷贝到 appctx->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 草案(「草案/待定案」,不实现):
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/<family>/<weight>/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 为准):
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的结构化扩展):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 为准):
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(cap<count),回收合法重绑但每次 draw 有界(≤视口窗口)。 - 多行富 item:
meow_lv_item含 主标题 / 副标题(第二行小字)/ 左图标·缩略图 / 右尾件(chevron·开关·复选·⋯)/section(段头占位行)/enabled。行高可配:set_item_h(精确 px)或set_item_tier(lines)(1→48 / 2→56)。 - 默认 renderer(
meow_listview_set_item_renderer(lines, show_icon, show_right))用主题语义色绘制富 item; 完全自绘:meow_listview_set_item_draw(draw_fn, ctx)覆盖默认 renderer(App 收 idx/rect/state 自绘整行)。 - 段头:bind 返回
item->section=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):
typedef struct meow_lv_item { const char *title, *subtitle; const meow_img *icon; int icon_kind;
int right, on, enabled, section; void *user; } meow_lv_item;
typedef int (*meow_listview_count_fn)(void *ctx);
typedef void (*meow_listview_bind_fn)(int index, meow_lv_item *item, void *ctx);
typedef void (*meow_listview_draw_fn)(int idx, int x, int y, int w, int h, int state, void *ctx);
int meow_listview_set_vsource(meow_widget *lv, meow_listview_count_fn count,
meow_listview_bind_fn bind, void *ctx); /* 切 pool 模式 */
int meow_listview_set_item_renderer(meow_widget *lv, int lines, int show_icon, int show_right);
int meow_listview_set_item_tier(meow_widget *lv, int lines); /* 1→48 / 2→56 */
int meow_listview_set_item_draw(meow_widget *lv, meow_listview_draw_fn draw, void *ctx);
int meow_listview_set_empty_text(meow_widget *lv, const char *text);
兼容:既有
meow_listview_*(src2 模式)全部保留不动(src2==NULL走既有路径);本层为叠加。meow_widget尾部追加虚拟化字段(calloc归零,安全);meow_ui.a需重编,App 链新库(老 ABI 不重建名)。 宿主验证test_meow_ui_listvirt(T1 虚拟化/bind 有界/回滚、T2 多行/截断/图标/尾件、T3 段头/空态、 T4 自绘、T5 焦点/fling 联动)+ 全量回归 widgets/dispatch/box/anim/scroll/ime/pinyin/overlay/notif/ sdk_input_keys/p1p2p3 全 PASS;交叉 -Wall -Wextra 0 warning。
5.6 设计模板 B③ 弹窗/对话框 [源:2026-08-30]
参照:
settings_page.cs_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,即画即显一次成型):- 全屏遮罩
meow_ui_fill_rect_alpha(0,0,W,H, 0x000000, 150)(压暗)。 - 居中卡片
meow_gfx_fill_round(d_x,d_y,d_w,d_h, 16, surface_container)+meow_ui_draw_rect_round(..., 16, outline)描边;圆角 16。 - 标题(theme
on_surface,20 号)+ 选项行(行高 ~48;选中行primaryα36 高亮 + 白字;预勾选项右侧画小方块)。 - 不压状态栏:卡片 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 为准):
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 冲突)。
- 侧边栏(可选):右缘向左滑出(右侧无既有手势,安全)。
- 左缘 ≤24px 向内划 → 全局左缘返回(
- 收合动画:跟手平移面板(手指拖多长露多长),松手过半阈值 → 补 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 为准):
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 为准):
/* —— 自定义画布回调(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 一次性挂载。
/* —— 通用 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 草案:
/* 状态位(可组合) */
#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 收口(不入 Appon_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 语义色板(日/夜各自取值)草案:
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.aABI 变化 → 需按 §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)→ 与现有绝对定位完全兼容——布局只是「帮你算坐标」的容器,不改变控件绘制 / 命中 / 状态机。
/* 布局容器: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.cmeow_*_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 | 悬浮操作钮(圆角增强;无投影,仅描边/状态层近似) | ✅ 第二批 |
| 列表容器 | 可滚动列表(复用 §5.5 A② listview) | ✅ 第二批 |
说明:多数 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)。
M2.73 修复(以
meow_ui_widget.c/hello_app/hello.c为准):
- 列表容器(W_LIST / LazyColumn / LazyRow)视口裁剪:
meow_widget_drawcaseW_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 控件升级地基):
- MD3 语义色板扩展(§5.8.4):
meow_theme尾部追加语义色字段 + 日/夜取值 + 映射旧字段(bg/fg/accent/dim);meow_theme_get返回新板。 - 统一 Options(§5.8.2):
meow_widget_opts+meow_widget_set_opts/get_opts;标准控件绘制读 opts。 - 统一状态机(§5.8.3):
enabled/selected/focused/pressed状态位 +meow_widget_state/set_state/enabled/selected;标准控件绘制叠状态层;disabled阻断 hit/pressed。 - 自定义画布融合(§5.8.1):
meow_widget_custom_create+W_CUSTOM;基座统一 render(th 注入)/hit;与标准控件同构(owner/命中双闸门/局部刷新/主题/状态机)。 - 可组合布局(§5.8.5):
meow_row/column/box + meow_layout_add/arrange + spacer;固定尺寸子项先做,算绝对坐标(与绝对定位兼容)。 - 实时按压刷新扩展:把
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.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:
int meow_audio_open(int rate, int ch, int fmt); /* 返回 stream_id(>0);<0=错误(-EBUSY 被占且被抢等) */
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 快照 */
/* 被抢占回调(可选):收到 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(v1 实装 S16/S24/S32);服务端做格式归一 + DAC 原生率跟随(无 SRC,44.1-192k 全覆盖)。
- 独占+抢占:后开流赢,旧流收
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=键(追加键,SDKmeow_audio_state结构体未动,ABI/协议兼容);流活跃时 1s 节拍写 TFMeowOS/logs/audio_monitor_<bootts>.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 建立输入线程,把按键/触摸翻译成语义事件回调:
/* 应用注册事件回调(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 实现)
/* ① 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 细化);同 idglobal_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.<id>.fullscreen)、内容区域(inset)与前台 activity;系统 UI 据此做叠画 + 命中分流(§6.1 交互分层:③ 系统全局 → ② App 全局 → ① 当前 Activity 页面控件)。 - 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)释放焦点并通知,
回退到页面级滚动。
滚动容器间焦点转移规则:
- 某滚动容器(list/lazycolumn/row)获得焦点后,随后的纵向 swipe / 拖动滚动该容器;
- 任何命中落在该容器外的触摸(含空白、页面内容、其它控件)→ 焦点释放 +
lost回调, 后续拖动滚动页面(不再滚列表); - 焦点释放/转移不销毁容器控件(
meow_widget仍由 App/视图持有),下次命中行重新 acquire。
6.4.3 公共 API(至 meow_ui_input.h,仅追加)
/* 模态作用域: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 提供帧驱动回调 + 内置时间,应用据此做低成本的帧序列/淡入淡出:
/* 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 系统设置的主题模式自动切换。
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 主页生效、只有深色、浅色不搭(launchersettings_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 接口(一键切换)
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);
- 设置页联动:设置页
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(背景底)→ 同步跟随三类面:
- 状态栏(SDK 全局):背景
surface即状态栏底; - 未声明背景的 App:App 未自画背景 / 未在 app.json 声明
background字段 → 用系统背景(meow_theme_get()的surface/bg); - 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/<pkg>/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。
构建耦合(已知设计,非缺陷):
build_sdk.sh用-I../meow_launcherd引meow_ipc.h(meow_notify_ipc.c复用共享协议头,同一份、杜绝两端漂移),故 SDK 编译期依赖 launcher 头、非独立库;运行时无碍 —— App 只链meow_ui.a(含 meow_font.o),不链入 launcher 符号,故 app 侧无 launcher 依赖。
app.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" // 可选:列表图标(供图片层)
}
上例 <pkg>=os.meow.hello(= apps/<pkg>/ 目录名 = package 字段值);exec 即 <挂载点>/MeowAPPs/<pkg>/<binary>。
app 字体来源(M2.x 已落地):app 若自带字库(
load_handle,见 §5.7.3,未实现)则用它;否则 SDKmeow_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(命中则覆盖全局;类值为空=跟随全局不注入)。SDKmeow_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/<family>/<weight>/按font.json的charset(common|gbk|full)选库(gbk 请求接受 gbk/full;full 请求优先 full 再降级 gbk;M2.6 优先同 family(其次同 weight);无匹配回退全局font.file)。SDKmeow_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.hmeow_font_reload_tier;font/gen/gen_font.py --charset common|gbk|full [--weight <name>]按档生成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(单头文件)与输入线程。
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+ 新 TUmeow_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,静态栈,无递归爆炸)。
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 未设时自动填)。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。
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即可获得速度采样。
typedef struct meow_scroll meow_scroll;
meow_scroll *meow_scroll_create(meow_scroll_set_fn set, meow_scroll_max_fn max,
meow_scroll_get_fn get, meow_scroll_redraw_fn redraw, void *ctx);
meow_scroll *meow_scroll_wrap(meow_widget *list, int is_listview); /* 1=W_LISTVIEW / 0=W_LIST 或 lazycolumn/row */
void meow_scroll_free(meow_scroll *s);
void meow_scroll_track(meow_scroll *s, int dy, uint64_t t_ms); /* 跟手拖动 + 速度采样 */
void meow_scroll_fling(meow_scroll *s); /* 松手惯性滑动(内部算初速) */
void meow_scroll_stop(meow_scroll *s); /* 立即停止 */
int meow_scroll_velocity(meow_scroll *s); /* 加权初速 px/s */
int meow_scroll_active(meow_scroll *s);
int meow_scroll_offset(meow_scroll *s);
void meow_scroll_set_snap(meow_scroll *s, int step);
void meow_scroll_set_friction(meow_scroll *s, int tau_ms); /* 默认 180ms */
- 内存纪律:静态池(应用侧按需建会话),track/fling/tick 全程零 malloc;无 fling 时 meow_scroll 不参与节拍(由
meow_anim_tick活动计数保证零开销)。不引 libm(SDK 免-lm约定)。
12.4 对既有 App 的零影响(迁移说明)
- 追加式:三块全为新 API;既有
meow_row/column/box(§5.8.5)与全部meow_*_create/hit/scroll签名一行未改。 - 旧绝对坐标永远有效:box 只是把坐标算进
meow_widget(meow_widget_set_rect);不用 box 的 App 照旧on_layout/绝对定位。 - 机制层新增:
meow_ui_input.c主循环新增meow_anim_tick()调用(无动画零开销);meow_ui_view.c新增布局登记表(未设布局的 view 行为不变);meow_ui_widget.c新增meow_widget_set_rect(只设字段不标脏,既有meow_layout_arrange不受影响)。 - 同步重编注意:
meow_ui.a内容变化(新增 box/anim/scroll 三个.o,meow_ui_widget.o/meow_ui_view.o/meow_ui_input.o重编)→ 链meow_ui.a的 App 需重链(build_sdk.sh一键重编 + 各 Appbuild_*.sh重链)。既有 App 不改源码即可零回归。
13. 遗留 / 待实现
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)。- 输入线程优先级:vs 主循环 10ms 热键轮询的竞态,需在 B1 实现时定义清楚(应用进程内独立事件循环,不与 launcher 抢)。
- 流媒体客户端跑通后迭代控件集(播放页/列表/歌词),非一次性补齐。
meow_ui_img_blur_bg(降采样模糊背景,HibyOS 同款)API 待加:已在 §11 确认可行性(缩 1/8 放大铺底 + α 遮罩,切歌算一次);函数签名与实现留到 B1 之后(或并入流媒体客户端播放页时)。当前 SDK B1 的图片层先用meow_ui_img_scaled+fill_rect_alpha手动组合即可达到同等观感。- 视频已定不做:无解码器/无 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=<path>可把 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.amipsel 静态编译链接 ✅(宿主 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(宿主验证用)。 meow_ui.h为对外权威签名;本文件 §3-§8 作为设计蓝本。
文档基于 2026-08-29 现有渲染基建(M2.16 无闪烁地基 + meow_font + blend565)编写;API 名会随 B1 实现微调,实现时以 meow_ui.h 为准。