M2.60→M2.61 骨架: SDK Activity 栈 + 控件归属(agent-122) + 按钮去圈/即时重绘(agent-119) + SDK 模板文档(agent-121)
- SDK §6.1 三层控件模型: Activity栈(push/pop/active) + 页面控件归属 owner + meow_ui_global_* 骨架(②) + 系统级仅内部接口(③) - meow_widget 加 owner; live_btn_hit/meow_widget_hit 仅 active activity 命中; touch_refresh stale 保护 - 根治"全屏页触摸主页按钮闪现"(跨页面控件残留); 控件对象复用弹栈不重建 - hello 迁移双 Activity(home/full): 全屏左滑=弹栈回主页, 根左滑=退出 - agent-119 按钮去圈(纯色accent+黑12/24%)+即时局部重绘保留; agent-121 SDK模板§5.3-5.7
This commit is contained in:
1 parent
07c556e907
commit
6824defdf9
13 files changed
+613
-23
No files matched your search
+314
@@ -202,6 +202,270 @@ const char *meow_ime_text(void);
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### 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/meow_launcherd.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` 为准)**:
|
||||||
|
```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「草案/待定案」,不实现。
|
||||||
|
|
||||||
|
**摘要**:可滚动选择容器。**行数据源回调**逐行给出(图标 / 标题 / 副标题 / 右侧操作 / 是否可选),容器负责绘制/滚动/选中/命中;支持**单选、多选、长按**(上下文操作)。适合「已保存网络 / 附近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 列表。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.6 设计模板 B③ 弹窗/对话框 [源:2026-08-30]
|
||||||
|
|
||||||
|
> **参照**:`meow_launcherd.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;短插值=有限帧序列)。
|
||||||
|
- **内容懒载**:面板**首次拉出**才构建内容(或预建空容器),避免打开即全量绘制;面板内滚动局部 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)——顶部热区归属通知中心;快捷设置在通知中心内二段展开为**待定案**(见关键交互),实现前须列出可与左缘返回/通知中心分流的具体触达途径,勿凭空抢占。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 6. 输入层 / 生命周期(B1 部分 + B2 衔接)
|
## 6. 输入层 / 生命周期(B1 部分 + B2 衔接)
|
||||||
|
|
||||||
**B1(本阶段)**:应用不再自读 `/dev/input/event*`,meow_ui 建立输入线程,把按键/触摸翻译成语义事件回调:
|
**B1(本阶段)**:应用不再自读 `/dev/input/event*`,meow_ui 建立输入线程,把按键/触摸翻译成语义事件回调:
|
||||||
@@ -233,6 +497,54 @@ void meow_ui_swipe_hint(int x);
|
|||||||
- `meow_ui_run` 改为从 IPC 收启动链路(`MEOW_CHAIN`/`MEOW_ACTIVITY` 环境变量)初始化导航栈;
|
- `meow_ui_run` 改为从 IPC 收启动链路(`MEOW_CHAIN`/`MEOW_ACTIVITY` 环境变量)初始化导航栈;
|
||||||
- 返回 = 栈回退,到根才 `meow_ui_request_exit()`。**本文档 §5 的控件与 §3/§4 绘制在 B2 不变**,只换输入/导航后端。
|
- 返回 = 栈回退,到根才 `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 标准能力,不得自行叠加全局层。
|
||||||
|
|
||||||
|
#### 交互分层(命中分流)
|
||||||
|
触摸命中顺序:**③ 系统全局 → ② App 全局 → ① 当前 Activity 页面控件**;某层命中即消费,否则落到下一层。
|
||||||
|
|
||||||
|
#### 与 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,既有状态栏/返回滑条逻辑原样保留。
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. 动画/时间(B1 最小可用)
|
## 7. 动画/时间(B1 最小可用)
|
||||||
@@ -299,6 +611,8 @@ patch_v2/sdk/ # SDK 源码
|
|||||||
├── meow_ui.h # 对外 API(本文档 §3-§8)
|
├── meow_ui.h # 对外 API(本文档 §3-§8)
|
||||||
├── meow_ui_gfx.c # 渲染后端(back_buf+present+font)
|
├── meow_ui_gfx.c # 渲染后端(back_buf+present+font)
|
||||||
├── meow_ui_widget.c # 控件
|
├── 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_img.c # stb_image 解码
|
||||||
├── meow_ui_input.c # 输入线程+语义事件
|
├── meow_ui_input.c # 输入线程+语义事件
|
||||||
├── meow_ui_theme.c
|
├── meow_ui_theme.c
|
||||||
|
|||||||
@@ -148,6 +148,8 @@
|
|||||||
- **全局状态栏**:SDK 内置,默认显示(时钟/电量/WiFi/蓝牙/音量),1s 轻刷,z 最顶;仅 APP 声明全屏/`fullscreen:1` 才隐藏;非全屏 APP 须用 `meow_ui_statusbar_height()` 做 inset `[meow_ui.h §3.1][MIGRATION_AC §3]`
|
- **全局状态栏**:SDK 内置,默认显示(时钟/电量/WiFi/蓝牙/音量),1s 轻刷,z 最顶;仅 APP 声明全屏/`fullscreen:1` 才隐藏;非全屏 APP 须用 `meow_ui_statusbar_height()` 做 inset `[meow_ui.h §3.1][MIGRATION_AC §3]`
|
||||||
- **全屏机制(M2.5x,`app.json fullscreen` 即 app 级 + `route.<id>.fullscreen` 即 activity 级)**:launcher 启动 app/切 activity 时据声明**预先设 SDK 全屏**(env `MEOW_FULLSCREEN`)。进入/退出全屏:**状态栏向上收出屏顶、只留一条提示线高度**(非全隐藏),带上收/恢复的**自然过渡动画**。**全屏下保留**:左缘返回手势 + 顶部动态伸缩提示线;顶部下滑仍可展开通知中心,但**顶部热区缩小**(不影响全屏 app)`[源:用户 2026-08-30]`(细化既有「全屏=隐藏状态栏/隐藏返回键」的表述,见 §7 A-13)
|
- **全屏机制(M2.5x,`app.json fullscreen` 即 app 级 + `route.<id>.fullscreen` 即 activity 级)**:launcher 启动 app/切 activity 时据声明**预先设 SDK 全屏**(env `MEOW_FULLSCREEN`)。进入/退出全屏:**状态栏向上收出屏顶、只留一条提示线高度**(非全隐藏),带上收/恢复的**自然过渡动画**。**全屏下保留**:左缘返回手势 + 顶部动态伸缩提示线;顶部下滑仍可展开通知中心,但**顶部热区缩小**(不影响全屏 app)`[源:用户 2026-08-30]`(细化既有「全屏=隐藏状态栏/隐藏返回键」的表述,见 §7 A-13)
|
||||||
- 沉浸:`app.json immersive:1` → launcher 写 `/tmp/meowapp_state=1` → 隐藏虚拟返回键,手势归 SDK `[APPS §3.5][GESTURE_BACK §2.4]`
|
- 沉浸:`app.json immersive:1` → launcher 写 `/tmp/meowapp_state=1` → 隐藏虚拟返回键,手势归 SDK `[APPS §3.5][GESTURE_BACK §2.4]`
|
||||||
|
- **横向分页设计模板(M2.5x 预留,见 `MEOW_APP_SDK.md` §5.2)**:主页/子页多页横向滑动复用的设计模板(参照 HibyOS 播放器主页横划进播放页)。与左缘返回/顶部下滑**按起始位置分流**互斥——左缘 ≤热区→**左滑返回**(§5.1);中部左/右滑→**页切换**;顶部下滑→**通知中心**。实现要点:相邻页各预渲染一张快照、切换只 blit 快照 + 跟手平移/吸附(vsync ≤60 帧、零 UI 重算)。**注**:模板 API 为「草案/待定案」,当前未实现 `[源:2026-08-30]`
|
||||||
|
- **SDK 可复用设计模板清单(M2.5x 预留,见 `MEOW_APP_SDK.md` §5.3)**:把反复出现的界面结构抽象为可复用设计模板——①**分组设置表单** ②**列表+选择** ③**弹窗/对话框** ④**顶/底下拉面板**(A+B 先行,已展开 §5.4-§5.7)+ 媒体播放/沉浸/加载(C)、卡片/导航/列表项(D,随 app/MD3 细化)、拖拽排序/长按菜单/时间轴(E,后置),共 11 项;横向分页另见 §5.2。**所有模板与既有手势约定一致**:左缘返回 + 顶部滑条 + 状态栏收合 + 通知中心热区(本 §5.1/§3.1/A-13)——模板只在内部做命中分流,**不重定义全局手势**;顶部/底部面板与左缘返回/通知中心按起始位置与触达途径分流互斥(快捷设置触达途径为**待定案**)。**注**:A①②/B③④ 模板 API 均为「草案/待定案」,当前未实现 `[源:2026-08-30]`
|
||||||
|
|
||||||
### 5.2 主题
|
### 5.2 主题
|
||||||
- **日/夜/自动**:SDK `meow_theme` 内置 `meow_theme_light/dark`;模式键 `display.theme_mode`(SYSTEM_SETTINGS 记为 `manual`/`auto`——**与 SDK 枚举 light/dark/auto 冲突**,见 §A);AUTO 判时段 `[SYSTEM_SETTINGS §1][MEOW_APP_SDK §8]`
|
- **日/夜/自动**:SDK `meow_theme` 内置 `meow_theme_light/dark`;模式键 `display.theme_mode`(SYSTEM_SETTINGS 记为 `manual`/`auto`——**与 SDK 枚举 light/dark/auto 冲突**,见 §A);AUTO 判时段 `[SYSTEM_SETTINGS §1][MEOW_APP_SDK §8]`
|
||||||
|
|||||||
Binary file not shown.
@@ -28,6 +28,12 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
|
/* §6.1 ①:Activity 归属。主界面(根,stack 空时 active()=0)绑定全部按钮控件;
|
||||||
|
* 全屏沉浸页为独立 Activity(无控件)——触摸它绝不命中主界面按钮(根治跨页残留)。
|
||||||
|
* 进全屏 meow_ui_activity_push(ACT_FULL),退出 pop 回根(主界面按钮恢复可点,对象复用)。 */
|
||||||
|
#define ACT_HOME 0
|
||||||
|
#define ACT_FULL 1
|
||||||
|
|
||||||
static meow_widget *g_btn;
|
static meow_widget *g_btn;
|
||||||
static meow_widget *g_nbtn[6]; /* 通知触发按钮行 */
|
static meow_widget *g_nbtn[6]; /* 通知触发按钮行 */
|
||||||
static meow_widget *g_cancel; /* 取消最近一条通知 */
|
static meow_widget *g_cancel; /* 取消最近一条通知 */
|
||||||
@@ -128,13 +134,19 @@ static void notif_cancel(void)
|
|||||||
* - fullscreen(1) + collapsed(1):状态栏「上收出屏只留提示线」(不完全沉浸,inset=4px),
|
* - fullscreen(1) + collapsed(1):状态栏「上收出屏只留提示线」(不完全沉浸,inset=4px),
|
||||||
* 顶部提示线热区仍可下拉,左缘返回手势(app 已注册 on_swipe)仍生效——SDK 自动处理,非 app 拦截;
|
* 顶部提示线热区仍可下拉,左缘返回手势(app 已注册 on_swipe)仍生效——SDK 自动处理,非 app 拦截;
|
||||||
* - fullscreen(0) + collapsed(0):恢复整条状态栏主界面。
|
* - fullscreen(0) + collapsed(0):恢复整条状态栏主界面。
|
||||||
* SDK 基座默认瞬切(g_sbar_collapse_anim_ms=0);如需过渡动画可先 meow_ui_statusbar_animate(ms)。 */
|
* SDK 基座默认瞬切(g_sbar_collapse_anim_ms=0);如需过渡动画可先 meow_ui_statusbar_animate(ms)。
|
||||||
|
* §6.1 ①:全屏 = 独立 Activity——进时压栈 ACT_FULL(主界面控件因 owner=ACT_HOME 不再参与命中,
|
||||||
|
* 根治「全屏页触摸到主页按钮区域闪现」);退时弹栈回根,主界面按钮恢复可点(对象复用零重建)。 */
|
||||||
static void set_fullscreen(int on)
|
static void set_fullscreen(int on)
|
||||||
{
|
{
|
||||||
g_fullscreen = on ? 1 : 0;
|
g_fullscreen = on ? 1 : 0;
|
||||||
meow_ui_set_fullscreen(g_fullscreen);
|
meow_ui_set_fullscreen(g_fullscreen);
|
||||||
meow_ui_statusbar_set_collapsed(g_fullscreen);
|
meow_ui_statusbar_set_collapsed(g_fullscreen);
|
||||||
meow_log(MEOW_LOG_INFO, g_fullscreen ? "fullscreen ON (collapsed)" : "fullscreen OFF (restore)");
|
if (on) meow_ui_activity_push(ACT_FULL);
|
||||||
|
else meow_ui_activity_pop();
|
||||||
|
meow_log(MEOW_LOG_INFO, g_fullscreen ? "fullscreen ON (collapsed), activity->%d"
|
||||||
|
: "fullscreen OFF (restore), activity->%d",
|
||||||
|
meow_ui_activity_active());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void redraw(void)
|
static void redraw(void)
|
||||||
@@ -219,7 +231,12 @@ static void on_swipe_progress(int x_start, int dx)
|
|||||||
static void on_swipe(int dir, int dx, int dy)
|
static void on_swipe(int dir, int dx, int dy)
|
||||||
{
|
{
|
||||||
(void)dx; (void)dy;
|
(void)dx; (void)dy;
|
||||||
if (dir == MEOW_SWIPE_LEFT) { meow_log(MEOW_LOG_INFO, "exit:swipe"); meow_ui_request_exit(); }
|
if (dir == MEOW_SWIPE_LEFT) {
|
||||||
|
/* §6.1 导航栈语义:非根(全屏)→ 左滑返回主页(pop),不退出;根 → 退出回应用列表。
|
||||||
|
* 保证 on_swipe(LEFT) 在沉浸页=退出全屏回主页,而非直接退出整个 app。 */
|
||||||
|
if (g_fullscreen) { set_fullscreen(0); redraw(); }
|
||||||
|
else { meow_log(MEOW_LOG_INFO, "exit:swipe"); meow_ui_request_exit(); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_tick(void)
|
static void on_tick(void)
|
||||||
|
|||||||
Binary file not shown.
@@ -10,7 +10,7 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架
|
|||||||
rm -f meow_ui.a *.o
|
rm -f meow_ui.a *.o
|
||||||
|
|
||||||
# -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移)
|
# -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移)
|
||||||
OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol stb_image"
|
OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global stb_image"
|
||||||
CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd"
|
CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd"
|
||||||
for f in $OBJS; do
|
for f in $OBJS; do
|
||||||
echo "== cc $f =="
|
echo "== cc $f =="
|
||||||
|
|||||||
@@ -156,6 +156,43 @@ void meow_ui_request_exit(void); /* 请求退出(相当于返回/
|
|||||||
* x<=0 时清掉此前画的条(填回背景色)。不发 flush,由调用方 meow_ui_flush_region 上屏。 */
|
* x<=0 时清掉此前画的条(填回背景色)。不发 flush,由调用方 meow_ui_flush_region 上屏。 */
|
||||||
void meow_ui_swipe_hint(int x);
|
void meow_ui_swipe_hint(int x);
|
||||||
|
|
||||||
|
/* ============================================================ */
|
||||||
|
/* §6.1 控件生命周期与归属(Activity 栈 + 三层控件模型) [源:2026-08-30] */
|
||||||
|
/* ============================================================ */
|
||||||
|
/* 根治「跨页面控件残留」:控件命中/绘制必须严格绑定其所属页面,否则会出现
|
||||||
|
* "按钮已不可见但仍被触摸命中"(如全屏页触摸到主页按钮位置闪现 accent 底)。
|
||||||
|
*
|
||||||
|
* 三层职责(从 App 到系统,风险从低到高):
|
||||||
|
* ① 页面控件:归属唯一 owner Activity;命中/实时按压只在「当前 active Activity
|
||||||
|
* 的控件集」里生效。对象不销毁(同 Activity 内复用),弹栈回来自动恢复 →
|
||||||
|
* 回页面零重建。
|
||||||
|
* ② App 跨 Activity 控件(SDK 托管):钉在 App 的 Activity 栈之上、跨 Activity 常驻,
|
||||||
|
* 逻辑/渲染由 SDK 调度。本轮为「可用骨架」(见上 API 注释),待 B2 细化。
|
||||||
|
* ③ 系统级控件(状态栏/通知中心/返回滑条/电源菜单):仅框架内部,无公共 API(不开放)。
|
||||||
|
*
|
||||||
|
* 触摸命中分流顺序:③ 系统全局 → ② App 全局 → ① 当前 active Activity 页面控件;
|
||||||
|
* 某层命中即消费,否则落到下一层。 */
|
||||||
|
|
||||||
|
/* ① Activity 栈。控件创建时绑定「当前 active Activity」为 owner;弹栈回来自动恢复。
|
||||||
|
* 栈空时 active() 返回 0(根/default Activity),故未显式管理栈的 App 默认所有控件
|
||||||
|
* 归属根 Activity 0。push 新 Activity(进子页/全屏)、pop 回上级(回根)。 */
|
||||||
|
void meow_ui_activity_push(int id);
|
||||||
|
void meow_ui_activity_pop(void);
|
||||||
|
int meow_ui_activity_active(void); /* 当前 active activity id(栈空=0) */
|
||||||
|
|
||||||
|
/* ② App 跨 Activity 控件(SDK 托管,骨架)。SDK 维护全局控件列表:
|
||||||
|
* - 渲染:每帧(有脏区需上屏时,meow_ui_gfx present())在 active activity 内容之上、
|
||||||
|
* 全局状态栏之下调用 render_cb(跨 Activity 常驻);
|
||||||
|
* - 触摸:TAP 先给 global hit_cb(返回 1=消费),否则落到 active activity 页面控件。
|
||||||
|
* 逻辑/渲染由 SDK 调度,App 只需声明。注:骨架,待 B2 细化。 */
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* ③ 系统级控件:状态栏/通知中心/左缘返回滑条/电源菜单等全局叠加层——仅 SDK 内部接口,
|
||||||
|
* 不向用户 App 开放任何公共悬浮/叠加层 API。既有状态栏/返回滑条逻辑(§3.1)不动。 */
|
||||||
|
|
||||||
/* ============================================================ */
|
/* ============================================================ */
|
||||||
/* §3.1 全局状态栏 / 全屏(A-0,SDK 系统级组件) */
|
/* §3.1 全局状态栏 / 全屏(A-0,SDK 系统级组件) */
|
||||||
/* ============================================================ */
|
/* ============================================================ */
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* meow_ui_activity.c — Activity 栈(§6.1 ①)。
|
||||||
|
*
|
||||||
|
* 控件严格归属创建它的 Activity;命中/实时按压只在「当前 active Activity」的控件集里
|
||||||
|
* 生效(见 meow_ui_widget.c 的 owner 绑定 + live_btn_hit/meow_widget_hit 过滤)。
|
||||||
|
* 栈空时 active() 返回 0(根/default Activity)。
|
||||||
|
*
|
||||||
|
* 依赖:无(纯静态数组 + 无外部符号),宿主/真机均可链入,便于单测。
|
||||||
|
* 语义:push = 压栈(新 Activity 成为 active); pop = 弹栈(回上级,栈空则停在根 0)。
|
||||||
|
*/
|
||||||
|
#include "meow_ui.h"
|
||||||
|
|
||||||
|
#define MEOW_ACT_MAX 32
|
||||||
|
|
||||||
|
static int g_act[MEOW_ACT_MAX];
|
||||||
|
static int g_act_n = 0;
|
||||||
|
|
||||||
|
void meow_ui_activity_push(int id)
|
||||||
|
{
|
||||||
|
if (g_act_n >= MEOW_ACT_MAX)
|
||||||
|
return; /* 栈满:静默钳制(骨架级,不崩) */
|
||||||
|
g_act[g_act_n++] = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void meow_ui_activity_pop(void)
|
||||||
|
{
|
||||||
|
if (g_act_n > 0) g_act_n--;
|
||||||
|
}
|
||||||
|
|
||||||
|
int meow_ui_activity_active(void)
|
||||||
|
{
|
||||||
|
return g_act_n > 0 ? g_act[g_act_n - 1] : 0;
|
||||||
|
}
|
||||||
@@ -652,6 +652,10 @@ static void present(void)
|
|||||||
if (!g_refresh_always_max) {
|
if (!g_refresh_always_max) {
|
||||||
if (g_last_present_ms && now - g_last_present_ms < (unsigned long long)period) return;
|
if (g_last_present_ms && now - g_last_present_ms < (unsigned long long)period) return;
|
||||||
}
|
}
|
||||||
|
/* §6.1 ②:App 跨 Activity 全局控件(SDK 托管骨架)。在 active activity 内容之上、
|
||||||
|
* 全局状态栏之下绘制——跨 Activity 常驻,仅在有帧需上屏(已过帧率门控)时调用。
|
||||||
|
* render_cb 内的 meow_ui_* 绘制会自行标脏,随下方 dirty 列表一并上屏。 */
|
||||||
|
meow_ui_global_render();
|
||||||
/* 全局状态栏(A-0)+ 上收(collapsed)基座:上屏前把状态栏画进 back_buf 顶部行。
|
/* 全局状态栏(A-0)+ 上收(collapsed)基座:上屏前把状态栏画进 back_buf 顶部行。
|
||||||
* 展开态=整条(MUI_SBAR_H 行);收合态只占/清「提示线」区域(MUI_SBAR_COLLAPSE_H 行),
|
* 展开态=整条(MUI_SBAR_H 行);收合态只占/清「提示线」区域(MUI_SBAR_COLLAPSE_H 行),
|
||||||
* app 内容(inset=提示线高)可上移到提示线下缘,无 28px 空条。全屏/隐藏时 active()==0
|
* app 内容(inset=提示线高)可上移到提示线下缘,无 28px 空条。全屏/隐藏时 active()==0
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* meow_ui_global.c — App 跨 Activity 控件(§6.1 ②,SDK 托管骨架)。
|
||||||
|
*
|
||||||
|
* SDK 维护一个全局控件列表(钉在 App 的 Activity 栈之上、跨 Activity 常驻):
|
||||||
|
* - 渲染:meow_ui_gfx present() 每帧在 active activity 内容之上、全局状态栏之下调
|
||||||
|
* meow_ui_global_render()(遍历列表调 render_cb);
|
||||||
|
* - 触摸:meow_ui_input TAP 分发先给 meow_ui_global_hit(x,y)——命中返回 1=消费,
|
||||||
|
* 否则落到 active activity 页面控件(①)。
|
||||||
|
*
|
||||||
|
* 骨架(待 B2 细化):无 z 序细分/无按压态管理/无命中区排布——App 的 render/hit 回调
|
||||||
|
* 自行决定绘制与命中。跨 Activity 常驻由「列表不随 Activity 栈变化」天然保证。
|
||||||
|
*/
|
||||||
|
#include "meow_ui.h"
|
||||||
|
|
||||||
|
#define MEOW_GLOBAL_MAX 16
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int id;
|
||||||
|
meow_global_render_fn render;
|
||||||
|
meow_global_hit_fn hit;
|
||||||
|
void *ctx;
|
||||||
|
} gentry;
|
||||||
|
|
||||||
|
static gentry g_globals[MEOW_GLOBAL_MAX];
|
||||||
|
static int g_global_n = 0;
|
||||||
|
|
||||||
|
void meow_ui_global_add(int id, meow_global_render_fn r, meow_global_hit_fn h, void *ctx)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < g_global_n; i++)
|
||||||
|
if (g_globals[i].id == id) return; /* 同 id 幂等(重复 add 不新增,骨架) */
|
||||||
|
if (g_global_n >= MEOW_GLOBAL_MAX) return;
|
||||||
|
g_globals[g_global_n].id = id;
|
||||||
|
g_globals[g_global_n].render = r;
|
||||||
|
g_globals[g_global_n].hit = h;
|
||||||
|
g_globals[g_global_n].ctx = ctx;
|
||||||
|
g_global_n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void meow_ui_global_remove(int id)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < g_global_n; i++) {
|
||||||
|
if (g_globals[i].id == id) {
|
||||||
|
g_globals[i] = g_globals[--g_global_n];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void meow_ui_global_render(void)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < g_global_n; i++)
|
||||||
|
if (g_globals[i].render) g_globals[i].render(g_globals[i].ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int meow_ui_global_hit(int x, int y)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < g_global_n; i++)
|
||||||
|
if (g_globals[i].hit && g_globals[i].hit(g_globals[i].ctx, x, y))
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -310,6 +310,10 @@ void meow_ui_run(const meow_ui_events *ev)
|
|||||||
startup_cooldown_until = now_ms() + STARTUP_COOLDOWN_MS; /* M2.22(C):启动窗口丢弃 SWIPE */
|
startup_cooldown_until = now_ms() + STARTUP_COOLDOWN_MS; /* M2.22(C):启动窗口丢弃 SWIPE */
|
||||||
|
|
||||||
while (!g_exit) {
|
while (!g_exit) {
|
||||||
|
/* agent-119:实时触摸按压/按住/释放 → 即时重绘命中按钮区(不等 app 重绘)。
|
||||||
|
* IME 打开时软键盘为覆盖层且拦截触摸,不驱动背后按钮视觉,跳过。 */
|
||||||
|
if (!meow_ime_active()) meow_widget_touch_refresh();
|
||||||
|
|
||||||
meow_input_ev e;
|
meow_input_ev e;
|
||||||
int got = 0;
|
int got = 0;
|
||||||
while (meow_input_pop(&e)) {
|
while (meow_input_pop(&e)) {
|
||||||
@@ -323,6 +327,9 @@ void meow_ui_run(const meow_ui_events *ev)
|
|||||||
meow_ui_swipe_hint(-1);
|
meow_ui_swipe_hint(-1);
|
||||||
meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6);
|
meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6);
|
||||||
if (meow_ime_active() && meow_ime_handle_tap(e.a, e.b)) continue;
|
if (meow_ime_active() && meow_ime_handle_tap(e.a, e.b)) continue;
|
||||||
|
/* §6.1 触摸分流:③ 系统全局(IME/状态栏…已在上面消费) → ② App 全局控件
|
||||||
|
* (命中返回 1=消费,否则落到 ① active Activity 页面控件)。 */
|
||||||
|
if (meow_ui_global_hit(e.a, e.b)) continue;
|
||||||
if (g_events_set && g_events.on_tap) g_events.on_tap(e.a, e.b);
|
if (g_events_set && g_events.on_tap) g_events.on_tap(e.a, e.b);
|
||||||
} else if (e.type == MEOW_EV_KEY) {
|
} else if (e.type == MEOW_EV_KEY) {
|
||||||
if (g_events_set && g_events.on_key) g_events.on_key(e.a, e.b);
|
if (g_events_set && g_events.on_key) g_events.on_key(e.a, e.b);
|
||||||
|
|||||||
@@ -64,6 +64,19 @@ unsigned int meow_input_touch_hold_ms(void); /* 按下已持续毫秒
|
|||||||
* (把按下时刻回拨),便于测按住态;down=0 复位。设备运行时无调用方。 */
|
* (把按下时刻回拨),便于测按住态;down=0 复位。设备运行时无调用方。 */
|
||||||
void meow_input_touch_set(int down, int x, int y, unsigned hold_ms);
|
void meow_input_touch_set(int down, int x, int y, unsigned hold_ms);
|
||||||
|
|
||||||
|
/* ---- 控件层实时刷新(meow_ui_widget.c)----
|
||||||
|
* 每帧由 meow_ui_run 调用(主线程):实时触摸按下/释放/按住达阈值时,重绘命中的按钮区
|
||||||
|
* 并局部上屏,让按压/按住/释放视觉即时反映,无需 app 在 on_tick/on_tap 重绘(agent-119)。
|
||||||
|
* 仅作用于 SDK 控件按钮(meow_widget_draw 的 W_BTN);app 纯自绘不适用。 */
|
||||||
|
void meow_widget_touch_refresh(void);
|
||||||
|
|
||||||
|
/* ---- App 跨 Activity 全局控件(meow_ui_global.c,§6.1 ②骨架)----
|
||||||
|
* 渲染帧(meow_ui_gfx present())在 active activity 内容之上、全局状态栏之下调用
|
||||||
|
* meow_ui_global_render;触摸 TAP 分发先给 meow_ui_global_hit(命中返回 1=消费,
|
||||||
|
* 否则落到 active activity 页面控件)。跨 Activity 常驻,由 SDK 调度。 */
|
||||||
|
void meow_ui_global_render(void);
|
||||||
|
int meow_ui_global_hit(int x, int y);
|
||||||
|
|
||||||
/* ---- IME(meow_ui_ime.c)---- */
|
/* ---- IME(meow_ui_ime.c)---- */
|
||||||
/* 当前是否显示软键盘。 */
|
/* 当前是否显示软键盘。 */
|
||||||
int meow_ime_active(void);
|
int meow_ime_active(void);
|
||||||
|
|||||||
+121
-19
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3 };
|
enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3 };
|
||||||
|
|
||||||
/* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层 + 描边环)。
|
/* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层)。
|
||||||
* 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */
|
* 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */
|
||||||
#define MEOW_BTN_HOLD_MS 500u
|
#define MEOW_BTN_HOLD_MS 500u
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ static uint32_t blend_color(uint32_t c, uint32_t o, int t)
|
|||||||
struct meow_widget {
|
struct meow_widget {
|
||||||
int type;
|
int type;
|
||||||
int x, y, w, h;
|
int x, y, w, h;
|
||||||
|
int owner; /* §6.1 ①:创建时绑定的 Activity;命中只在 owner==active 才生效 */
|
||||||
char label[128];
|
char label[128];
|
||||||
int pressed; /* 按钮按下视觉态 */
|
int pressed; /* 按钮按下视觉态 */
|
||||||
int min, max, val; /* 滑条 */
|
int min, max, val; /* 滑条 */
|
||||||
@@ -37,12 +38,105 @@ struct meow_widget {
|
|||||||
void *ctx;
|
void *ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* ---- 实时按压即时刷新(agent-119)----
|
||||||
|
* 按压/按住视觉由 meow_widget_draw 读实时触摸(meow_input_touch_*)呈现,但 app 只在
|
||||||
|
* on_tick(1s)/on_tap 重绘 → 快速点/按住时按压态刷新滞后/漏。此处在 SDK 触摸按下/释放/
|
||||||
|
* 按住达阈值时主动重绘命中的按钮区域并局部上屏(不等 app 重绘),即时反映按下变深/
|
||||||
|
* 释放恢复,连续快速点击每按都反映。仅针对 SDK 控件按钮(W_BTN);app 纯自绘不适用。
|
||||||
|
* 语义不变:on_tap / meow_btn_pressed / meow_widget_hit 零改动;非按钮区域不动。 */
|
||||||
|
#define MEOW_LIVE_BTN_MAX 64
|
||||||
|
static meow_widget *g_live_btns[MEOW_LIVE_BTN_MAX];
|
||||||
|
static int g_live_btn_n = 0;
|
||||||
|
static meow_widget *g_live_btn = NULL; /* 实时按压中最上层按钮(无 = NULL) */
|
||||||
|
static int g_live_prev_hold = 0; /* 该按钮上次渲染是否为按住态 */
|
||||||
|
|
||||||
|
static void live_btn_register(meow_widget *w)
|
||||||
|
{
|
||||||
|
if (!w || w->type != W_BTN) return;
|
||||||
|
for (int i = 0; i < g_live_btn_n; i++)
|
||||||
|
if (g_live_btns[i] == w) return;
|
||||||
|
if (g_live_btn_n < MEOW_LIVE_BTN_MAX) g_live_btns[g_live_btn_n++] = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void live_btn_unregister(meow_widget *w)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < g_live_btn_n; i++) {
|
||||||
|
if (g_live_btns[i] == w) {
|
||||||
|
g_live_btns[i] = g_live_btns[--g_live_btn_n];
|
||||||
|
if (g_live_btn == w) g_live_btn = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (g_live_btn == w) g_live_btn = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 命中:返回坐标所在的「当前 active Activity」的最上层按钮(创建序 = 绘制序,最后者在上)。
|
||||||
|
* §6.1 ①:仅扫描 owner == 当前 active Activity 的按钮 —— 非 active Activity 的控件
|
||||||
|
* 绝不参与命中(根治 agent-119 遗留的「全屏页触摸到主页按钮位置 → 闪现 accent 底」)。 */
|
||||||
|
static meow_widget *live_btn_hit(int x, int y)
|
||||||
|
{
|
||||||
|
int active = meow_ui_activity_active();
|
||||||
|
meow_widget *r = NULL;
|
||||||
|
for (int i = 0; i < g_live_btn_n; i++) {
|
||||||
|
meow_widget *w = g_live_btns[i];
|
||||||
|
if (w->owner != active) continue; /* 非 active activity:跳过 */
|
||||||
|
if (x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h)
|
||||||
|
r = w; /* 越后创建越上:保留最后命中 */
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 重绘并局部刷新一个按钮区(绘制内部调 meow_gfx_fill_round 已标脏;flush_region 合区+上屏)。 */
|
||||||
|
static void live_btn_redraw(meow_widget *w)
|
||||||
|
{
|
||||||
|
if (!w) return;
|
||||||
|
meow_widget_draw(w);
|
||||||
|
meow_ui_flush_region(w->x, w->y, w->w, w->h);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 由 meow_ui_run 每帧调用(主线程):检测实时触摸状态迁移,即时重绘命中按钮的按压/按住/
|
||||||
|
* 释放,不等 app on_tick/on_tap。仅在触摸状态相对上次渲染发生变化时动作(非全屏、省性能)。 */
|
||||||
|
void meow_widget_touch_refresh(void)
|
||||||
|
{
|
||||||
|
int active = meow_ui_activity_active();
|
||||||
|
/* §6.1 ①:若当前按住按钮已不属于 active activity(按压期间发生了 Activity 切换,
|
||||||
|
* 例如点 FULLSCREEN 后进入全屏),静默清引用——不绘制、不闪现(否则会在新页面底上
|
||||||
|
* 画出旧按钮的 accent 残留)。 */
|
||||||
|
if (g_live_btn && g_live_btn->owner != active) {
|
||||||
|
g_live_btn = NULL;
|
||||||
|
g_live_prev_hold = 0;
|
||||||
|
}
|
||||||
|
int down = meow_input_touch_down();
|
||||||
|
meow_widget *hit = down ? live_btn_hit(meow_input_touch_x(), meow_input_touch_y()) : NULL;
|
||||||
|
int hold = down && hit && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS;
|
||||||
|
|
||||||
|
if (!down) {
|
||||||
|
/* 释放:把最近按压按钮恢复为常态 */
|
||||||
|
if (g_live_btn) { live_btn_redraw(g_live_btn); g_live_btn = NULL; }
|
||||||
|
g_live_prev_hold = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hit != g_live_btn) {
|
||||||
|
/* 按下/滑至新按钮:先恢复旧(若手指滑离),再按压新 */
|
||||||
|
if (g_live_btn) live_btn_redraw(g_live_btn);
|
||||||
|
g_live_btn = hit;
|
||||||
|
g_live_prev_hold = hold;
|
||||||
|
if (hit) live_btn_redraw(hit);
|
||||||
|
} else if (hit && hold != g_live_prev_hold) {
|
||||||
|
/* 同一按钮按住达阈值:升级到更强态 */
|
||||||
|
live_btn_redraw(hit);
|
||||||
|
g_live_prev_hold = hold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
meow_widget *meow_btn_create(int x, int y, int w, int h, const char *label)
|
meow_widget *meow_btn_create(int x, int y, int w, int h, const char *label)
|
||||||
{
|
{
|
||||||
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
|
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
|
||||||
if (!t) return NULL;
|
if (!t) return NULL;
|
||||||
t->type = W_BTN; t->x = x; t->y = y; t->w = w; t->h = h;
|
t->type = W_BTN; t->x = x; t->y = y; t->w = w; t->h = h;
|
||||||
|
t->owner = meow_ui_activity_active(); /* §6.1 ①:绑定当前 active Activity */
|
||||||
if (label) { strncpy(t->label, label, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; }
|
if (label) { strncpy(t->label, label, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; }
|
||||||
|
live_btn_register(t);
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +145,7 @@ meow_widget *meow_slider_create(int x, int y, int w, int min, int max, int val)
|
|||||||
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
|
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
|
||||||
if (!t) return NULL;
|
if (!t) return NULL;
|
||||||
t->type = W_SLIDER; t->x = x; t->y = y; t->w = w; t->h = 40;
|
t->type = W_SLIDER; t->x = x; t->y = y; t->w = w; t->h = 40;
|
||||||
|
t->owner = meow_ui_activity_active();
|
||||||
t->min = min; t->max = (max > min) ? max : min + 1; t->val = (val < min) ? min : (val > t->max) ? t->max : val;
|
t->min = min; t->max = (max > min) ? max : min + 1; t->val = (val < min) ? min : (val > t->max) ? t->max : val;
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
@@ -60,6 +155,7 @@ meow_widget *meow_switch_create(int x, int y, int on)
|
|||||||
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
|
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
|
||||||
if (!t) return NULL;
|
if (!t) return NULL;
|
||||||
t->type = W_SWITCH; t->x = x; t->y = y; t->w = 56; t->h = 32;
|
t->type = W_SWITCH; t->x = x; t->y = y; t->w = 56; t->h = 32;
|
||||||
|
t->owner = meow_ui_activity_active();
|
||||||
t->on = on ? 1 : 0;
|
t->on = on ? 1 : 0;
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
@@ -70,11 +166,17 @@ meow_widget *meow_list_create(int x, int y, int w, int h, int count,
|
|||||||
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
|
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
|
||||||
if (!t) return NULL;
|
if (!t) return NULL;
|
||||||
t->type = W_LIST; t->x = x; t->y = y; t->w = w; t->h = h;
|
t->type = W_LIST; t->x = x; t->y = y; t->w = w; t->h = h;
|
||||||
|
t->owner = meow_ui_activity_active();
|
||||||
t->count = count; t->src = src; t->ctx = ctx; t->sel = 0;
|
t->count = count; t->src = src; t->ctx = ctx; t->sel = 0;
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
void meow_widget_free(meow_widget *w) { if (w) free(w); }
|
void meow_widget_free(meow_widget *w)
|
||||||
|
{
|
||||||
|
if (!w) return;
|
||||||
|
live_btn_unregister(w);
|
||||||
|
free(w);
|
||||||
|
}
|
||||||
|
|
||||||
static int hit(meow_widget *w, int x, int y)
|
static int hit(meow_widget *w, int x, int y)
|
||||||
{
|
{
|
||||||
@@ -82,7 +184,13 @@ static int hit(meow_widget *w, int x, int y)
|
|||||||
return x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h;
|
return x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h;
|
||||||
}
|
}
|
||||||
|
|
||||||
int meow_widget_hit(meow_widget *w, int x, int y) { return hit(w, x, y); }
|
int meow_widget_hit(meow_widget *w, int x, int y)
|
||||||
|
{
|
||||||
|
if (!w) return 0;
|
||||||
|
/* §6.1 ①:页面控件只对自身 Activity 负责,不跨页面复用命中。 */
|
||||||
|
if (w->owner != meow_ui_activity_active()) return 0;
|
||||||
|
return hit(w, x, y);
|
||||||
|
}
|
||||||
int meow_btn_pressed(meow_widget *w) { return w && w->type == W_BTN ? w->pressed : 0; }
|
int meow_btn_pressed(meow_widget *w) { return w && w->type == W_BTN ? w->pressed : 0; }
|
||||||
|
|
||||||
void meow_slider_set(meow_widget *w, int val)
|
void meow_slider_set(meow_widget *w, int val)
|
||||||
@@ -112,22 +220,16 @@ void meow_widget_draw(meow_widget *w)
|
|||||||
meow_input_touch_y() >= w->y && meow_input_touch_y() < w->y + w->h;
|
meow_input_touch_y() >= w->y && meow_input_touch_y() < w->y + w->h;
|
||||||
int press = w->pressed || live;
|
int press = w->pressed || live;
|
||||||
int hold = live && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS;
|
int hold = live && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS;
|
||||||
if (hold) {
|
/* 纯色状态层(无描边环、无分层):一层即满,深浅二态均由 accent 向黑调出。
|
||||||
/* 按住:更强态——更深状态层 + 内缩圆角描边环(浅 accent),贴 MD3 强调;
|
* 常态=accent、按下=accent+黑12%、按住=accent+黑24%。整体一层不「先底色再套圈」。 */
|
||||||
* 动效取舍:状态层按规范即时切换(MD3 ripple 属额外动画,依赖 on_tick 逐帧,
|
uint32_t bg;
|
||||||
* 非本「加视觉态」范围),不做 1-2 帧插值。 */
|
if (hold)
|
||||||
uint32_t hbg = blend_color(th->accent, 0x000000, 24);
|
bg = blend_color(th->accent, 0x000000, 24);
|
||||||
meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, hbg);
|
else if (press)
|
||||||
if (w->w > 6 && w->h > 6)
|
bg = blend_color(th->accent, 0x000000, 12);
|
||||||
meow_ui_draw_rect_round(w->x + 1, w->y + 1, w->w - 2, w->h - 2, 9,
|
else
|
||||||
blend_color(th->accent, 0xFFFFFF, 45));
|
bg = th->accent;
|
||||||
} else if (press) {
|
meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, bg);
|
||||||
/* 按下:轻状态层(accent 向黑调 12%,即变深/偏移),释放恢复;MD3 表面按压。 */
|
|
||||||
meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10,
|
|
||||||
blend_color(th->accent, 0x000000, 12));
|
|
||||||
} else {
|
|
||||||
meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, th->accent);
|
|
||||||
}
|
|
||||||
meow_ui_text_center(w->x + w->w / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2,
|
meow_ui_text_center(w->x + w->w / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2,
|
||||||
w->label, th->font_body, 0xFFFFFF);
|
w->label, th->font_body, 0xFFFFFF);
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in new issue
Block a user