feat: M6.10–M6.19 批量收编 + M6.19 推卡验收
- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘) - OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复 - ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640 - 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫) - 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
This commit is contained in:
1 parent
c2ee7626a4
commit
3d70a3dd2c
176 files changed
+15507
-597
No files matched your search
@@ -40,7 +40,7 @@ FT_INCS="-I$FT_BUILD -I$FT_BUILD/include -I$FT_SRC/include"
|
||||
rm -f meow_ui.a *.o
|
||||
|
||||
# -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_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media meow_ui_audio stb_image meow_ui_box meow_ui_anim meow_ui_scroll meow_ui_str meow_ui_ft meow_net_ipc"
|
||||
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_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media meow_ui_audio stb_image meow_ui_box meow_ui_anim meow_ui_scroll meow_lazygrid meow_select meow_ui_str meow_ui_ft meow_net_ipc meow_cachegov"
|
||||
CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd"
|
||||
for f in $OBJS; do
|
||||
echo "== cc $f =="
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* meow_cachegov.h — 磁盘缓存 page-cache 治理 SDK 库(A 的主导/治本侧)。
|
||||
*
|
||||
* 归属(devdocs/plan/ARCHITECTURE_PRINCIPLES.md ③混合,2026-09-02 用户定案):
|
||||
* - A(DONTNEED 热路径,需要 read_pos) → 随 SDK 库(本文件,进程内直读 read_pos,无 IPC)。
|
||||
* - C(设备级 /proc/meminfo Cached 监控降载)→ 归系统服务 meow_svc_perf;本模块只读其共享状态
|
||||
* 文件(MEOW_CACHEGOV_C_FILE)作为「降载」信号,供 App 查询。
|
||||
* - App(如 ncm)侧只调本库接口:登记文件 + read_pos 回调 + tick + drop_finished + 查询降载,
|
||||
* 不写自己的治理算法。
|
||||
*
|
||||
* 权威方案:devdocs/plan/OOM_PAGECACHE_GOVERNANCE.md。红线:**L2(丢 read_pos 前方)禁止**,
|
||||
* 本库绝不 drop `[read_pos, ...)` 任何未解码区。
|
||||
*
|
||||
* 分级(§2.2):
|
||||
* L0(默认,安全): 曲终/commit 后对整文件 posix_fadvise(DONTNEED)——播放器已退出解码循环。
|
||||
* L1(可选,主杠杆): 播放中丢已读前缀 `[0, read_pos-keep_hot)`,保 keep_hot 窗口供倒带;
|
||||
* 仅 drop_mode>=1 且 A 未回退到 ≥2 时执行。
|
||||
* L2(禁止,红线): 绝不丢 read_pos 前方/当前未解码页。
|
||||
*
|
||||
* underrun 回退(§2.3,SDK 内置):App 在 pread 慢(数据在盘上但 >threshold ms)处上报
|
||||
* meow_cachegov_note_refault();本库滚窗 10s,超门限逐级回退(放大 keep_hot → 降 L0 → 关 A)。
|
||||
*
|
||||
* 接口按 OOM 文档 §4.2 + §4.4;值语义与 mem_watch 的「滞回三档」风格对齐(自含:pthread + meow_log)。
|
||||
*/
|
||||
#ifndef MEOW_CACHEGOV_H
|
||||
#define MEOW_CACHEGOV_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct meow_cachegov_cfg {
|
||||
int enabled; /* 总开关:0=A 完全不丢页(register/tick/drop_finished 全部 no-op) */
|
||||
int cached_threshold_kb; /* 保留:C 侧阈值归 svc_perf(本库恒 0,只读不控,防误用) */
|
||||
int dirty_warn_kb; /* 保留:同上(svc_perf 用;本库只透传日志) */
|
||||
size_t keep_hot_bytes; /* L1 保留窗口:read_pos 之后保多少不丢(回退时自动放大) */
|
||||
size_t min_hot_ahead; /* L2 红线:read_pos 前方至少保多少——仅防御注释,**绝不参与 drop** */
|
||||
int drop_mode; /* 0=仅曲终(L0);1=曲终+播放中丢已读前缀(L0+L1,配 seek 回退) */
|
||||
int refault_backoff; /* 10s 窗口内 refault 触发回退的门限(次数) */
|
||||
} meow_cachegov_cfg_t;
|
||||
|
||||
/* 登记本次治理的缓存文件(正式缓存文件路径 + 大小 + read_pos 回调 + 回调 ctx)。
|
||||
* 返回 0=ok / 负=错。重复 path 幂等(替换条目)。 */
|
||||
int meow_cachegov_register(const char *path, uint64_t size,
|
||||
uint64_t (*read_pos_fn)(void *ctx), void *ctx);
|
||||
|
||||
/* 注销该 path 的治理条目(曲终/切歌后清理)。返回 0=找到并删 / 负=不在册。 */
|
||||
int meow_cachegov_unregister(const char *path);
|
||||
|
||||
/* 周期调用(播放循环节拍 / 监控线程均可):
|
||||
* ① 按回退档执行 L1 丢弃(丢已读前缀 [0, read_pos-keep_hot));
|
||||
* ② 评估 refault 滚窗做逐级回退。 */
|
||||
int meow_cachegov_tick(void);
|
||||
|
||||
/* 曲终/commit 后:对刚播完文件做整文件 L0 DONTNEED + 注销该条目。
|
||||
* 回退档>=3(A 整体关闭)时不丢页(L0 也被关,见 §2.3 步骤 3)。 */
|
||||
int meow_cachegov_drop_finished(const char *path);
|
||||
|
||||
/* 下载/预取方查询(1=应暂停)。数据源 = A 侧回退硬暂停 OR svc_perf C(Cached 超阈值)降载。 */
|
||||
int meow_cachegov_download_paused(void);
|
||||
int meow_cachegov_prefetch_paused(void);
|
||||
|
||||
/* 设置/读取治理配置(运行时覆盖编译期默认)。 */
|
||||
int meow_cachegov_set_cfg(const meow_cachegov_cfg_t *cfg);
|
||||
int meow_cachegov_get_cfg(meow_cachegov_cfg_t *cfg);
|
||||
|
||||
/* ---- refault 上报 + A 回退状态(underrun 检测,§2.3)---- */
|
||||
/* App 在 pread 慢(数据已在盘上、读出 >threshold ms)处上报一次 refault。线程安全。 */
|
||||
void meow_cachegov_note_refault(void);
|
||||
/* A 当前回退档:0=正常 1=keep_hot 放大 2=降 L0(L1 关) 3=A 关。 */
|
||||
int meow_cachegov_backoff_level(void);
|
||||
|
||||
/* ---- 宿主测试桩(设备永不调用;-1=真实时钟,>=0 时用注入值替代 monotonic 时钟)---- */
|
||||
void meow_cachegov_test_set_time_ms(int64_t t);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* MEOW_CACHEGOV_H */
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* meow_lazygrid.h — SDK 2 维虚拟化宫格(§12.4:meow_lazygrid,M7.4 主界面前置)。
|
||||
*
|
||||
* 定位:给「卡片/图标宫格」提供**二维虚拟化**——应用入口宫格、图标墙、设置快捷卡等。
|
||||
* 只实例化**可见行**(视口内),行高/列几何 + gap;滚动复用 §12.3 meow_scroll(fling/snap),
|
||||
* 与 meow_listview 同款惯性;命中 meow_lazygrid_hit/tap_at 把滚动+网格坐标映射回 item idx。
|
||||
*
|
||||
* 设计(仿 meow_listview 的 vsource 思想 + meow_scroll 滚动 + meow_box 列几何):
|
||||
* - 数据源:count_fn() 返回总项数(任意长),bind_fn(index, item) 仅当 item「进入视口」
|
||||
* (绑定到固定池槽)时调一次;回滚命中同槽不重绑(同 listview 池语义)。
|
||||
* - 固定 item 池:池容量 = 视口行数 + 2(× cols),静态有界(≤MEOW_LAZYGRID_POOL_MAX)。
|
||||
* - 列几何:cols + cell_w + cell_h + gap;行高 row_h = cell_h + gap;按 scroll offset 推可见行。
|
||||
* - 渲染:默认 renderer 画「圆角卡片(icon+名+状态)」;App 也可经 set_item_draw 完全自绘
|
||||
* (主界面卡片宫格用:icon+名+后台标注)。支持选中高亮/禁用变淡/空态占位。
|
||||
* - 滚动:接 meow_scroll(meow_lazygrid_scroll 返回会话;track/fling/snap 与 listview 同款)。
|
||||
*
|
||||
* 内存纪律(M7 §5.2):静态有界 item 池(冷路径 create/set_vsource 分配,热路径 draw/hit/scroll
|
||||
* 零 malloc),与 listview 池同源;不常驻 count 个 item 对象。
|
||||
*
|
||||
* 非 meow_widget:lazygrid 是**自包含控件**(不注册进 z 序/绘制注册表),由宿主(launcher session)
|
||||
* 在 draw 阶段调 meow_lazygrid_draw、命中阶段调 meow_lazygrid_hit/tap_at、滚动喂 meow_scroll——
|
||||
* 与 meow_scroll/meow_box 同为「正交模块」,不侵入 struct meow_widget(全追加式)。
|
||||
*/
|
||||
#ifndef MEOW_UI_LAZYGRID_H
|
||||
#define MEOW_UI_LAZYGRID_H
|
||||
|
||||
#include "meow_ui_types.h" /* meow_img 前向 + 基础类型(子头不引伞头,避免相对路径依赖) */
|
||||
#include "meow_ui_theme.h" /* meow_theme(draw 取语义色) */
|
||||
#include "meow_ui_scroll.h" /* meow_scroll(滚动集成;子头引依赖子头,同 widgets.h→theme.h) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct meow_lazygrid meow_lazygrid;
|
||||
|
||||
/* ---- cell / item ---- */
|
||||
/* 单格数据(bind 回调填充;SDK 固定池持有;App 跨 bind 用 user 存业务态/图/index)。 */
|
||||
typedef struct meow_lazygrid_item {
|
||||
const char *title; /* 主文本(名;必填,NULL 视为空格) */
|
||||
const char *subtitle; /* 副文本(状态/后台标注),可空 */
|
||||
const meow_img *icon; /* 可选:格内图标(NULL=无) */
|
||||
int enabled; /* 1=可交互;0=变淡不响应 */
|
||||
void *user; /* App 私有(跨 bind 保存业务态;SDK 不碰) */
|
||||
} meow_lazygrid_item;
|
||||
|
||||
/* 数据源:count 返回总项数(>=0,任意长);bind 在 item 进入视口时调一次填充 item。
|
||||
* 二者均不常驻 item 对象;count 每帧实时查(支持动态长度/搜索过滤)。 */
|
||||
typedef int (*meow_lazygrid_count_fn)(void *ctx);
|
||||
typedef void (*meow_lazygrid_bind_fn)(int index, meow_lazygrid_item *item, void *ctx);
|
||||
|
||||
/* 完全自绘回调(覆盖默认 renderer):index=项号;x,y,w,h=格矩形(已裁剪到宫格视口);
|
||||
* state=MEOW_LAZYGRID_ST_* 状态位(选中/禁用组合)。App 用 §3 绘制 API 自绘整格。 */
|
||||
typedef void (*meow_lazygrid_draw_fn)(int index, int x, int y, int w, int h,
|
||||
int state, void *ctx);
|
||||
/* state 位(draw 回调收到)。 */
|
||||
#define MEOW_LAZYGRID_ST_SELECTED (1u<<0)
|
||||
#define MEOW_LAZYGRID_ST_DISABLED (1u<<1)
|
||||
|
||||
/* 命中/点击回调(idx=项号;App 在 tap_at 命中后触发动作)。 */
|
||||
typedef void (*meow_lazygrid_tap_fn)(int idx, void *ctx);
|
||||
|
||||
#define MEOW_LAZYGRID_POOL_MAX 96 /* 池容量上限(固定;保证静态有界) */
|
||||
|
||||
/* ---- 生命周期(冷路径 create/free;热路径 draw/hit/scroll 零 malloc)----
|
||||
* cols=列数(>=1,越界 clamp);cell_w/cell_h=格宽高;gap=格间距(水平与垂直同用)。 */
|
||||
meow_lazygrid *meow_lazygrid_create(int x, int y, int w, int h, int cols,
|
||||
int cell_w, int cell_h, int gap);
|
||||
void meow_lazygrid_free(meow_lazygrid *g);
|
||||
|
||||
/* ---- 几何配置 ---- */
|
||||
void meow_lazygrid_set_rect(meow_lazygrid *g, int x, int y, int w, int h); /* 视口矩形(绝对坐标) */
|
||||
void meow_lazygrid_set_cols(meow_lazygrid *g, int cols);
|
||||
void meow_lazygrid_set_cell(meow_lazygrid *g, int cell_w, int cell_h, int gap);
|
||||
|
||||
/* ---- 数据源 + 渲染 ---- */
|
||||
int meow_lazygrid_set_vsource(meow_lazygrid *g, meow_lazygrid_count_fn count,
|
||||
meow_lazygrid_bind_fn bind, void *ctx);
|
||||
int meow_lazygrid_set_item_draw(meow_lazygrid *g, meow_lazygrid_draw_fn draw,
|
||||
void *ctx); /* NULL=用默认卡片 renderer */
|
||||
int meow_lazygrid_set_empty_text(meow_lazygrid *g, const char *text);
|
||||
|
||||
/* ---- 滚动(像素级;可视行数按 offset 实时推) ---- */
|
||||
void meow_lazygrid_set_scroll(meow_lazygrid *g, int off);
|
||||
void meow_lazygrid_scroll_by(meow_lazygrid *g, int dy);
|
||||
int meow_lazygrid_scroll(meow_lazygrid *g);
|
||||
int meow_lazygrid_max_scroll(meow_lazygrid *g); /* max(0, 内容高 - 视口高) */
|
||||
int meow_lazygrid_row_h(const meow_lazygrid *g); /* 行高(=cell_h+gap) */
|
||||
/* 便捷:包一个 meow_scroll 会话(set/max/get/redraw 指回本宫格;redraw 只刷视口区)。
|
||||
* App 在 on_touch_move 喂 meow_scroll_track、松手 meow_scroll_fling。返回 NULL=池满。 */
|
||||
meow_scroll *meow_lazygrid_scroll_attach(meow_lazygrid *g);
|
||||
|
||||
/* ---- 选中 / 命中 ---- */
|
||||
void meow_lazygrid_set_on_tap(meow_lazygrid *g, meow_lazygrid_tap_fn cb, void *ctx);
|
||||
void meow_lazygrid_set_sel(meow_lazygrid *g, int idx); /* 越界 clamp;<0 清除;标脏 */
|
||||
int meow_lazygrid_sel(const meow_lazygrid *g);
|
||||
int meow_lazygrid_hit(meow_lazygrid *g, int x, int y); /* 命中项 idx(滚动+网格坐标映射);-1=未命中/禁用 */
|
||||
int meow_lazygrid_tap_at(meow_lazygrid *g, int x, int y); /* 命中:置选中 + 回调 + 返回 idx/-1 */
|
||||
|
||||
/* ---- 绘制 ---- */
|
||||
void meow_lazygrid_draw(meow_lazygrid *g, const meow_theme *th);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* MEOW_UI_LAZYGRID_H */
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* meow_select.h — SDK 枚举/预设选择器(§5.8.6 模态组件,M7.2 设置页缺口 #4)。
|
||||
*
|
||||
* 定位:给「枚举 / 预设选择器」一个开箱即用的**下拉模态**——设置页采样率/码率/主题/语言等
|
||||
* 枚举、均衡器预设等。封装 = BottomSheet(标题 + 选项列表)+ meow_listview(SINGLE) 单选列表:
|
||||
* 选项数组 + 当前选中(高亮)+ 点选项 = 选中 + 关闭 + on_change 回调;点面板外/左缘返回 = 取消。
|
||||
*
|
||||
* 设计(§3 缺口 #4):选项用 meow_listview(SINGLE) 渲染(单选高亮、可惯性滚动、虚拟化长列表),
|
||||
* 外层是模态 BottomSheet(复用 meow_dialog 的遮罩/快照恢复,scrim 只叠一层/关闭无残影)。
|
||||
* 阻塞式(内层 meow_input_pop 轮询,与 meow_dialog 同构);打开前排空残留触摸。
|
||||
*
|
||||
* 内存纪律:选项数组定长(≤MEOW_SELECT_MAX_ITEMS,栈/数组持有,非 malloc 长列表);
|
||||
* 列表在 open 时创建(冷路径)、close 时释放;open 循环零 malloc。
|
||||
*/
|
||||
#ifndef MEOW_UI_SELECT_H
|
||||
#define MEOW_UI_SELECT_H
|
||||
|
||||
#include "meow_ui_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct meow_select meow_select;
|
||||
|
||||
/* 选中回调(on_change):idx=被选中的选项下标(0..n-1)。 */
|
||||
typedef void (*meow_select_fn)(int idx, void *ctx);
|
||||
|
||||
#define MEOW_SELECT_MAX_ITEMS 32 /* 选项上限(静态数组;越界 add 忽略并返回 -1) */
|
||||
|
||||
/* ---- 组件式(可复用 + on_change 回调)---- */
|
||||
meow_select *meow_select_create(const char *title, int cur); /* cur=初始选中项;越界 clamp */
|
||||
int meow_select_add_item(meow_select *s, const char *label); /* 返回项下标;满/无效= -1 */
|
||||
void meow_select_set_on_change(meow_select *s, meow_select_fn cb, void *ctx);
|
||||
/* 打开模态(阻塞到确认/取消):选中 → 置 result=idx + 调 on_change(idx)+ 返回 idx;
|
||||
* 取消/点面板外/左缘返回 → 返回 -1(不调 on_change)。 */
|
||||
int meow_select_open(meow_select *s);
|
||||
void meow_select_free(meow_select *s);
|
||||
|
||||
/* ---- 一次性(无回调,返回值即选择)----
|
||||
* opts=选项数组(const char* 数组);n=个数;cur=初始选中。返回选中下标/-1=取消。 */
|
||||
int meow_select_show(const char *title, const char *const *opts, int n, int cur);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* MEOW_UI_SELECT_H */
|
||||
@@ -386,6 +386,25 @@ int meow_listview_set_item_draw(meow_widget *lv, meow_listview_draw_fn draw,
|
||||
/* 空态占位:count==0 时视口内居中画 empty_text(NULL 不画)。返回 0=成功。 */
|
||||
int meow_listview_set_empty_text(meow_widget *lv, const char *text);
|
||||
|
||||
/* ============================================================ */
|
||||
/* §5.5 左滑删除(M7.3 通知中心缺口 #9):列表行横向手势 + 删除/回弹动画 */
|
||||
/* ------------------------------------------------------------
|
||||
* 行自绘 set_item_draw 配合(SDK 在自绘行前把该行横向偏移 swipe_dx 传给 x,并画右露删除区)。
|
||||
* 手势由 App 在 on_touch_move 喂三级:begin(touch-down)/ move(touch-move)/ end(release)。
|
||||
* 判定:横向小位移|dx|>|dy| 且超容差=swipe(move 返回 1=本列表消费,App 勿滚动);纵向
|
||||
* |dy|>|dx| 且超容差=滚动手势优先(返回 0 并取消本行 swipe,App 照常 scroll_by);点按
|
||||
* 无位移(end 时未 started)=行点击(返回 0,App 照常 tap_at)。不修改 tap_at/滚动语义。
|
||||
* 动画:超阈值 → 删除动画(行滑出 meow_anim,结束后回调 on_delete(idx));未超 → 回弹。
|
||||
* 颜色:删除区用主题 error/onError;不与行点击/滚动冲突(见判定表)。 */
|
||||
typedef void (*meow_listview_swipe_fn)(int idx, void *ctx);
|
||||
void meow_listview_set_swipe_delete(meow_widget *lv, int enabled,
|
||||
meow_listview_swipe_fn on_delete, void *ctx);
|
||||
int meow_listview_swipe_begin(meow_widget *lv, int x, int y); /* touch-down:1=命中可滑行(喂后续 move) */
|
||||
int meow_listview_swipe_move(meow_widget *lv, int x, int y); /* touch-move:1=消费(横向);0=让位(取消/纵向) */
|
||||
int meow_listview_swipe_end(meow_widget *lv, int x, int y); /* release:1=消费(删/回弹动画);0=未滑动(tap) */
|
||||
int meow_listview_swipe_active(meow_widget *lv); /* 1=有行处于滑动/动画中 */
|
||||
int meow_listview_swipe_offset(meow_widget *lv); /* 当前滑动行的 dx(<0=左移;自绘行画用) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
/* sdk/meow_cachegov.c — 磁盘缓存 page-cache 治理 SDK 库(A:DONTNEED 主导侧)。
|
||||
*
|
||||
* 权威方案:devdocs/plan/OOM_PAGECACHE_GOVERNANCE.md。归属(ARCHITECTURE_PRINCIPLES ③混合):
|
||||
* A 热路径(register/tick/drop_finished/refault)随 SDK 库;C(/proc/meminfo Cached 监控)
|
||||
* 归 meow_svc_perf,本库只读其共享状态文件 MEOW_CACHEGOV_C_FILE 作为降载信号。
|
||||
*
|
||||
* 红线(务必保留本注释):**L2 禁止**——绝不丢 `[read_pos, ...)` 任何未解码区(含 read_pos 前方
|
||||
* 刚写入尚未解码的页)。本库所有 drop 的上界只能是 read_pos-keep_hot(L1)或整文件曲终(L0)。
|
||||
*
|
||||
* 分层:只依赖 pthread + meow_log + POSIX(posix_fadvise),不依赖 core/App 其它 TU。
|
||||
* 自含:refault 滚窗 + 三级回退(放大 keep_hot → 降 L0 → 关 A)。
|
||||
*/
|
||||
#include "include/meow_cachegov.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* meow_ui.h:SDK 公共伞头(含 meow_log/meow_ui_log.h + 公共类型)。 */
|
||||
#include "meow_ui.h"
|
||||
/* 共享协议头:C 侧状态文件路径(meow_svc_perf 写入 / 各 App 读取,同源防漂移)。 */
|
||||
#include "meow_ipc.h"
|
||||
|
||||
#define MEOW_CACHEGOV_MAXREG 8 /* 同时治理的缓存文件上限(ncm 单点播放,远小于此) */
|
||||
#define MEOW_CACHEGOV_REFAULTRING 64 /* refault 滚窗环形缓冲 */
|
||||
#define MEOW_CACHEGOV_WINDOW_MS 10000 /* refault 滚窗 10s */
|
||||
#define MEOW_CACHEGOV_HOTESCALATE 1 /* 回退档>=1 时 keep_hot 放大倍数(翻倍) */
|
||||
#define MEOW_CACHEGOV_DROP_THROTTLE (64u * 1024u) /* L1 每次至少推进 64KB 才再 drop(省 open/fadvise) */
|
||||
|
||||
typedef struct cg_entry {
|
||||
char path[512];
|
||||
uint64_t size; /* 标称大小(流式=nominal;命中=实际) */
|
||||
uint64_t (*read_pos_fn)(void *ctx);
|
||||
void *ctx;
|
||||
uint64_t dropped_to; /* 已 DONTNEED 的已读前缀上界(只推进,不回退) */
|
||||
int used;
|
||||
} cg_entry_t;
|
||||
|
||||
static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||
static struct meow_cachegov_cfg g_cfg = {
|
||||
.enabled = 1,
|
||||
.cached_threshold_kb = 0, /* C 归属 svc_perf,本库只读 */
|
||||
.dirty_warn_kb = 0, /* 同上 */
|
||||
.keep_hot_bytes = (256u * 1024u), /* 保 256KB 解码窗口供倒带 */
|
||||
.min_hot_ahead = (0), /* L2 红线防御值;绝不参与 drop */
|
||||
.drop_mode = 0, /* 默认仅曲终(L0);L1 需显式开(带 seek 回退) */
|
||||
.refault_backoff = 3, /* 10s 内 3 次 refault → 回退 */
|
||||
};
|
||||
static cg_entry_t g_reg[MEOW_CACHEGOV_MAXREG];
|
||||
static int g_backoff = 0; /* 0=正常 1=keep_hot放大 2=降L0 3=A关 */
|
||||
static int64_t g_last_step_ms = -1000000000000LL; /* 上次升/降档时刻(初始很远 → 首次可动作) */
|
||||
static int64_t g_refault_ring[MEOW_CACHEGOV_REFAULTRING];
|
||||
static int g_refault_head = 0;
|
||||
static int g_refault_n = 0;
|
||||
|
||||
/* ---- 时钟(宿主测试桩可注入) ---- */
|
||||
static int64_t g_test_now_ms = -1;
|
||||
static int64_t now_ms(void)
|
||||
{
|
||||
if (g_test_now_ms >= 0) return g_test_now_ms;
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
}
|
||||
void meow_cachegov_test_set_time_ms(int64_t t) { g_test_now_ms = t; }
|
||||
|
||||
/* ---- C 侧状态文件(svc_perf 写 / 本库读) ---- */
|
||||
static const char *c_file(void)
|
||||
{
|
||||
const char *e = getenv("MEOW_CACHEGOV_C_FILE");
|
||||
return (e && e[0]) ? e : MEOW_CACHEGOV_C_FILE;
|
||||
}
|
||||
|
||||
/* C 状态(svc_perf 写)最近一次解析缓存:下载 cb 每 4KB 块会查一次,避免每块都读文件(200ms TTL)。 */
|
||||
static int g_cached_c_dl = 0, g_cached_c_pf = 0;
|
||||
static int64_t g_cached_c_at = 0;
|
||||
|
||||
/* 读 C 状态文件(缺省 0=不降载)。解析失败/文件不存在 → 全部 0。 */
|
||||
static void read_c_state(int *dl_pause, int *pf_pause, long *cached_kb, long *dirty_kb)
|
||||
{
|
||||
if (dl_pause) *dl_pause = 0;
|
||||
if (pf_pause) *pf_pause = 0;
|
||||
if (cached_kb) *cached_kb = 0;
|
||||
if (dirty_kb) *dirty_kb = 0;
|
||||
FILE *f = fopen(c_file(), "r");
|
||||
if (!f) return;
|
||||
char line[160];
|
||||
while (fgets(line, sizeof line, f)) {
|
||||
if (!strncmp(line, "cached_kb=", 10)) { if (cached_kb) *cached_kb = atol(line + 10); }
|
||||
else if (!strncmp(line, "dirty_kb=", 9)) { if (dirty_kb) *dirty_kb = atol(line + 9); }
|
||||
else if (!strncmp(line, "dl_pause=", 9)) { if (dl_pause) *dl_pause = atol(line + 9) ? 1 : 0; }
|
||||
else if (!strncmp(line, "prefetch_pause=", 15)) { if (pf_pause) *pf_pause = atol(line + 15) ? 1 : 0; }
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/* ---- refault 滚窗 ---- */
|
||||
void meow_cachegov_note_refault(void)
|
||||
{
|
||||
int64_t t = now_ms();
|
||||
pthread_mutex_lock(&g_mu);
|
||||
g_refault_ring[g_refault_head] = t;
|
||||
g_refault_head = (g_refault_head + 1) % MEOW_CACHEGOV_REFAULTRING;
|
||||
if (g_refault_n < MEOW_CACHEGOV_REFAULTRING) g_refault_n++;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
}
|
||||
|
||||
static int refaults_last_window(void)
|
||||
{
|
||||
int64_t now = now_ms();
|
||||
int cnt = 0, start = (g_refault_head - g_refault_n + MEOW_CACHEGOV_REFAULTRING) %
|
||||
MEOW_CACHEGOV_REFAULTRING;
|
||||
for (int i = 0; i < g_refault_n; i++) {
|
||||
int64_t t = g_refault_ring[(start + i) % MEOW_CACHEGOV_REFAULTRING];
|
||||
if (t >= now - MEOW_CACHEGOV_WINDOW_MS) cnt++;
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/* 曲终/cur_finished 后:整文件 L0 DONTNEED。 */
|
||||
int meow_cachegov_drop_finished(const char *path)
|
||||
{
|
||||
if (!path) return -1;
|
||||
pthread_mutex_lock(&g_mu);
|
||||
for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++) {
|
||||
if (g_reg[i].used && strcmp(g_reg[i].path, path) == 0) {
|
||||
g_reg[i].used = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int lbf = g_backoff, en = g_cfg.enabled;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
|
||||
if (!en || lbf >= 3) { /* A 已整体关闭:连曲终 L0 也不丢(§2.3 步骤 3) */
|
||||
meow_log(MEOW_LOG_INFO, "[cachegov] drop_finished skip %s (A off, backoff=%d)", path, lbf);
|
||||
return 0;
|
||||
}
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0) return -1;
|
||||
/* len=0 → 到 EOF(整文件)。曲终播放器已不再读该文件,重播=冷缓存首读(等价未命中)。 */
|
||||
posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
|
||||
close(fd);
|
||||
meow_log(MEOW_LOG_INFO, "[cachegov] drop_finished L0 %s", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* L1:丢已读前缀 [dropped_to, read_pos-keep_hot)。返回 1=有丢。 */
|
||||
static int entry_drop_l1(cg_entry_t *e, size_t keep)
|
||||
{
|
||||
if (!e->read_pos_fn) return 0;
|
||||
uint64_t pos = e->read_pos_fn(e->ctx);
|
||||
if (pos <= keep) return 0; /* keep_hot 覆盖整个已读前缀,无需丢 */
|
||||
uint64_t drop_end = pos - (uint64_t)keep; /* L1 上界:read_pos 前方绝不丢(L2 红线) */
|
||||
if (drop_end <= e->dropped_to) return 0; /* 无推进(seek 回退 / 前进不足) */
|
||||
if (drop_end - e->dropped_to < MEOW_CACHEGOV_DROP_THROTTLE && e->dropped_to != 0)
|
||||
return 0; /* 节流:不足 64KB 不重复 open */
|
||||
int fd = open(e->path, O_RDONLY);
|
||||
if (fd < 0) return 0;
|
||||
off_t off = (off_t)e->dropped_to;
|
||||
off_t len = (off_t)(drop_end - e->dropped_to);
|
||||
ssize_t r = posix_fadvise(fd, off, len, POSIX_FADV_DONTNEED);
|
||||
close(fd);
|
||||
if (r != 0) return 0;
|
||||
e->dropped_to = drop_end;
|
||||
meow_log(MEOW_LOG_INFO, "[cachegov] L1 drop %s off=%lld len=%lld (pos=%lld keep=%zu)",
|
||||
e->path, (long long)off, (long long)len, (long long)pos, keep);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int meow_cachegov_tick(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_mu);
|
||||
int64_t now = now_ms();
|
||||
/* ① 回退评估(滚窗 10s):refault 超门限 → 升一档;滚窗干净 ≥10s → 降一档。
|
||||
* 每升降一档需间隔 ≥10s(逐级回退,不因窗内持续 refault 一次跳过多档,防误回退)。 */
|
||||
int cnt = refaults_last_window();
|
||||
if (cnt >= g_cfg.refault_backoff && g_cfg.refault_backoff > 0 && g_backoff < 3 &&
|
||||
(now - g_last_step_ms) >= MEOW_CACHEGOV_WINDOW_MS) {
|
||||
g_backoff = g_backoff + 1;
|
||||
g_last_step_ms = now;
|
||||
meow_log(MEOW_LOG_INFO, "[cachegov] backoff -> %d (refault=%d/%lds)",
|
||||
g_backoff, cnt, MEOW_CACHEGOV_WINDOW_MS / 1000);
|
||||
} else if (cnt == 0 && g_backoff > 0 &&
|
||||
(now - g_last_step_ms) >= MEOW_CACHEGOV_WINDOW_MS) {
|
||||
g_backoff = g_backoff - 1;
|
||||
g_last_step_ms = now;
|
||||
meow_log(MEOW_LOG_INFO, "[cachegov] backoff -> %d (clean window)", g_backoff);
|
||||
}
|
||||
|
||||
size_t keep = g_cfg.keep_hot_bytes;
|
||||
if (g_backoff >= MEOW_CACHEGOV_HOTESCALATE) keep = g_cfg.keep_hot_bytes * 2; /* 档1:放大 keep_hot */
|
||||
int l1_on = g_cfg.enabled && g_cfg.drop_mode >= 1 && g_backoff < 2; /* 档2:L1 关 */
|
||||
int en = g_cfg.enabled;
|
||||
|
||||
/* ② L1 丢弃 */
|
||||
int dropped = 0;
|
||||
if (en && l1_on) {
|
||||
for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++)
|
||||
if (g_reg[i].used) dropped += entry_drop_l1(&g_reg[i], keep);
|
||||
}
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
return dropped;
|
||||
}
|
||||
|
||||
/* ---- 注册 / 注销 ---- */
|
||||
int meow_cachegov_register(const char *path, uint64_t size,
|
||||
uint64_t (*read_pos_fn)(void *ctx), void *ctx)
|
||||
{
|
||||
if (!path) return -1;
|
||||
pthread_mutex_lock(&g_mu);
|
||||
int slot = -1;
|
||||
for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++) {
|
||||
if (g_reg[i].used && strcmp(g_reg[i].path, path) == 0) { slot = i; break; }
|
||||
if (!g_reg[i].used && slot < 0) slot = i;
|
||||
}
|
||||
if (slot < 0) { pthread_mutex_unlock(&g_mu); return -1; }
|
||||
snprintf(g_reg[slot].path, sizeof g_reg[slot].path, "%s", path);
|
||||
g_reg[slot].size = size;
|
||||
g_reg[slot].read_pos_fn = read_pos_fn;
|
||||
g_reg[slot].ctx = ctx;
|
||||
g_reg[slot].dropped_to = 0;
|
||||
g_reg[slot].used = 1;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
meow_log(MEOW_LOG_INFO, "[cachegov] register %s size=%llu", path, (unsigned long long)size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_cachegov_unregister(const char *path)
|
||||
{
|
||||
if (!path) return -1;
|
||||
pthread_mutex_lock(&g_mu);
|
||||
int rc = -1;
|
||||
for (int i = 0; i < MEOW_CACHEGOV_MAXREG; i++)
|
||||
if (g_reg[i].used && strcmp(g_reg[i].path, path) == 0) { g_reg[i].used = 0; rc = 0; break; }
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* ---- 降载查询(A 回退 OR svc_perf C) ---- */
|
||||
static void c_state(int *dl_pause, int *pf_pause)
|
||||
{
|
||||
int64_t now = now_ms();
|
||||
pthread_mutex_lock(&g_mu);
|
||||
if (g_cached_c_at == 0 || (now - g_cached_c_at) > 200) {
|
||||
read_c_state(&g_cached_c_dl, &g_cached_c_pf, NULL, NULL);
|
||||
g_cached_c_at = now;
|
||||
}
|
||||
if (dl_pause) *dl_pause = g_cached_c_dl;
|
||||
if (pf_pause) *pf_pause = g_cached_c_pf;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
}
|
||||
|
||||
int meow_cachegov_download_paused(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_mu);
|
||||
int a = (g_backoff >= 2);
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
int c = 0;
|
||||
c_state(&c, NULL);
|
||||
return (a || c) ? 1 : 0;
|
||||
}
|
||||
|
||||
int meow_cachegov_prefetch_paused(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_mu);
|
||||
int a = (g_backoff >= 3);
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
int c = 0;
|
||||
c_state(NULL, &c);
|
||||
return (a || c) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* ---- 配置 ---- */
|
||||
int meow_cachegov_set_cfg(const struct meow_cachegov_cfg *cfg)
|
||||
{
|
||||
if (!cfg) return -1;
|
||||
pthread_mutex_lock(&g_mu);
|
||||
g_cfg = *cfg;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_cachegov_get_cfg(struct meow_cachegov_cfg *cfg)
|
||||
{
|
||||
if (!cfg) return -1;
|
||||
pthread_mutex_lock(&g_mu);
|
||||
*cfg = g_cfg;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_cachegov_backoff_level(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_mu);
|
||||
int v = g_backoff;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
return v;
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* meow_lazygrid.c — §12.4 2 维虚拟化宫格(M7.4 主界面前置)。
|
||||
*
|
||||
* 自包含控件(非 meow_widget,不注册 z 序/绘制注册表):宿主在 draw 阶段调 meow_lazygrid_draw、
|
||||
* 命中阶段调 meow_lazygrid_hit/tap_at、滚动喂 meow_scroll(meow_lazygrid_scroll 返回会话)。
|
||||
* 只实例化**可见行**:固定 item 池(视口行数 + 2 × cols,静态有界 ≤MEOW_LAZYGRID_POOL_MAX),
|
||||
* bind 仅在 item 进入视口时调一次(回滚同槽不重绑,同 listview 池语义)。
|
||||
*
|
||||
* 内存纪律:池在 create/set_vsource/set_cell(冷路径)分配;draw/hit/set_scroll 热路径零 malloc。
|
||||
* 颜色全取 meow_theme_get() 语义色(日/夜/style 自动),不硬编码。
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "include/meow_lazygrid.h"
|
||||
#include "meow_ui_internal.h" /* meow_gfx_clip_push/pop + meow_gfx_fill_round */
|
||||
#include "include/meow_ui_scroll.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct meow_lazygrid {
|
||||
int x, y, w, h; /* 视口矩形(绝对坐标) */
|
||||
int cols; /* 列数 */
|
||||
int cell_w, cell_h, gap; /* 格宽/格高/间距 */
|
||||
int row_h; /* 行高 = cell_h + gap(缓存) */
|
||||
|
||||
int scroll; /* 滚动偏移(像素) */
|
||||
int sel; /* 选中项(-1=无) */
|
||||
|
||||
meow_lazygrid_count_fn count_fn; void *vctx;
|
||||
meow_lazygrid_bind_fn bind;
|
||||
meow_lazygrid_draw_fn draw_cb; void *draw_ctx;
|
||||
meow_lazygrid_tap_fn tap_cb; void *tap_ctx;
|
||||
|
||||
/* 固定 item 池(冷路径分配;slot 按 item index mod pool_n 映射) */
|
||||
meow_lazygrid_item *pool;
|
||||
int *pool_idx; /* 每槽绑定的 item index(-1=空闲) */
|
||||
int pool_n;
|
||||
|
||||
char empty_text[64]; /* 空态占位(首字符 0=不画) */
|
||||
};
|
||||
|
||||
/* 行高(= cell_h + gap)。 */
|
||||
int meow_lazygrid_row_h(const meow_lazygrid *g)
|
||||
{ return g ? g->row_h : 0; }
|
||||
|
||||
/* 总行数。 */
|
||||
static int lg_rows(const meow_lazygrid *g)
|
||||
{
|
||||
int n = g->count_fn ? g->count_fn(g->vctx) : 0;
|
||||
if (n < 0) n = 0;
|
||||
if (n == 0) return 0;
|
||||
return (n + g->cols - 1) / g->cols;
|
||||
}
|
||||
/* 总项数。 */
|
||||
static int lg_count(const meow_lazygrid *g)
|
||||
{ return g->count_fn ? g->count_fn(g->vctx) : 0; }
|
||||
|
||||
/* 内容总高(行高 × 行数 − 末行后无 gap)。 */
|
||||
static int lg_content_h(const meow_lazygrid *g)
|
||||
{
|
||||
int rows = lg_rows(g);
|
||||
if (rows <= 0) return 0;
|
||||
return rows * g->cell_h + (rows - 1) * g->gap;
|
||||
}
|
||||
|
||||
/* ---- 生命周期 ---- */
|
||||
meow_lazygrid *meow_lazygrid_create(int x, int y, int w, int h, int cols,
|
||||
int cell_w, int cell_h, int gap)
|
||||
{
|
||||
if (cols < 1) cols = 1;
|
||||
meow_lazygrid *g = (meow_lazygrid *)calloc(1, sizeof *g);
|
||||
if (!g) return NULL;
|
||||
g->x = x; g->y = y; g->w = w; g->h = h; g->cols = cols;
|
||||
g->cell_w = cell_w > 0 ? cell_w : 1;
|
||||
g->cell_h = cell_h > 0 ? cell_h : 1;
|
||||
g->gap = gap > 0 ? gap : 0;
|
||||
g->row_h = g->cell_h + g->gap;
|
||||
g->sel = -1;
|
||||
return g;
|
||||
}
|
||||
|
||||
void meow_lazygrid_free(meow_lazygrid *g)
|
||||
{
|
||||
if (!g) return;
|
||||
free(g->pool); free(g->pool_idx);
|
||||
free(g);
|
||||
}
|
||||
|
||||
void meow_lazygrid_set_rect(meow_lazygrid *g, int x, int y, int w, int h)
|
||||
{
|
||||
if (!g) return;
|
||||
g->x = x; g->y = y; g->w = w; g->h = h;
|
||||
}
|
||||
void meow_lazygrid_set_cols(meow_lazygrid *g, int cols)
|
||||
{
|
||||
if (!g) return;
|
||||
if (cols < 1) cols = 1;
|
||||
if (g->cols == cols) return;
|
||||
g->cols = cols;
|
||||
}
|
||||
void meow_lazygrid_set_cell(meow_lazygrid *g, int cell_w, int cell_h, int gap)
|
||||
{
|
||||
if (!g) return;
|
||||
g->cell_w = cell_w > 0 ? cell_w : 1;
|
||||
g->cell_h = cell_h > 0 ? cell_h : 1;
|
||||
g->gap = gap > 0 ? gap : 0;
|
||||
g->row_h = g->cell_h + g->gap; /* 行高变化 → 可见行数变 → 池容量随之调整 */
|
||||
}
|
||||
|
||||
/* ---- item 池(冷路径;有界 ≤MEOW_LAZYGRID_POOL_MAX)---- */
|
||||
static int lg_pool_cap(const meow_lazygrid *g)
|
||||
{
|
||||
int rh = g->row_h; if (rh < 8) rh = 8;
|
||||
int rows = (g->h / rh) + 1; if (rows < 1) rows = 1;
|
||||
int cap = (rows + 2) * g->cols;
|
||||
if (cap < 8) cap = 8;
|
||||
if (cap > MEOW_LAZYGRID_POOL_MAX) cap = MEOW_LAZYGRID_POOL_MAX;
|
||||
return cap;
|
||||
}
|
||||
/* 确保池容量 ≥ cap(有界扩张;重分配清空绑定 → 下一帧按需重绑)。返回 0=成功;-1=分配失败。 */
|
||||
static int lg_ensure_pool(meow_lazygrid *g)
|
||||
{
|
||||
if (!g->count_fn || !g->bind) return 0;
|
||||
int cap = lg_pool_cap(g);
|
||||
if (g->pool && g->pool_n >= cap) return 0;
|
||||
meow_lazygrid_item *np = (meow_lazygrid_item *)calloc((size_t)cap, sizeof *np);
|
||||
int *ni = (int *)calloc((size_t)cap, sizeof *ni);
|
||||
if (!np || !ni) { free(np); free(ni); return -1; }
|
||||
for (int i = 0; i < cap; i++) ni[i] = -1;
|
||||
free(g->pool); free(g->pool_idx);
|
||||
g->pool = np; g->pool_idx = ni; g->pool_n = cap;
|
||||
return 0;
|
||||
}
|
||||
/* 取 index 对应的池槽(按 item index mod pool_n 映射;槽绑定不匹配 → 防御性重绑)。 */
|
||||
static meow_lazygrid_item *lg_slot(meow_lazygrid *g, int index)
|
||||
{
|
||||
if (!g->pool || g->pool_n <= 0) return NULL;
|
||||
int s = (int)((unsigned)index % (unsigned)g->pool_n);
|
||||
if (g->pool_idx[s] != index) {
|
||||
memset(&g->pool[s], 0, sizeof g->pool[s]);
|
||||
g->bind(index, &g->pool[s], g->vctx);
|
||||
g->pool_idx[s] = index;
|
||||
}
|
||||
return &g->pool[s];
|
||||
}
|
||||
|
||||
/* ---- 数据源 + 渲染 ---- */
|
||||
int meow_lazygrid_set_vsource(meow_lazygrid *g, meow_lazygrid_count_fn count,
|
||||
meow_lazygrid_bind_fn bind, void *ctx)
|
||||
{
|
||||
if (!g || !count || !bind) return -1;
|
||||
g->count_fn = count; g->bind = bind; g->vctx = ctx;
|
||||
if (lg_ensure_pool(g) != 0) { /* 分配固定 item 池(有界,冷路径) */
|
||||
g->count_fn = NULL; g->bind = NULL; /* 池分配失败:回退为空 */
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int meow_lazygrid_set_item_draw(meow_lazygrid *g, meow_lazygrid_draw_fn draw,
|
||||
void *ctx)
|
||||
{
|
||||
if (!g) return -1;
|
||||
g->draw_cb = draw; g->draw_ctx = ctx; /* NULL=用默认卡片 renderer */
|
||||
return 0;
|
||||
}
|
||||
int meow_lazygrid_set_empty_text(meow_lazygrid *g, const char *text)
|
||||
{
|
||||
if (!g) return -1;
|
||||
if (text) {
|
||||
strncpy(g->empty_text, text, sizeof g->empty_text - 1);
|
||||
g->empty_text[sizeof g->empty_text - 1] = '\0';
|
||||
} else {
|
||||
g->empty_text[0] = '\0';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 滚动 ---- */
|
||||
int meow_lazygrid_max_scroll(meow_lazygrid *g)
|
||||
{
|
||||
if (!g) return 0;
|
||||
int ch = lg_content_h(g);
|
||||
int ms = ch - g->h;
|
||||
return ms > 0 ? ms : 0;
|
||||
}
|
||||
void meow_lazygrid_set_scroll(meow_lazygrid *g, int off)
|
||||
{
|
||||
if (!g) return;
|
||||
int ms = meow_lazygrid_max_scroll(g);
|
||||
if (off < 0) off = 0;
|
||||
if (off > ms) off = ms;
|
||||
g->scroll = off;
|
||||
}
|
||||
void meow_lazygrid_scroll_by(meow_lazygrid *g, int dy)
|
||||
{ if (g) meow_lazygrid_set_scroll(g, g->scroll + dy); }
|
||||
int meow_lazygrid_scroll(meow_lazygrid *g) { return g ? g->scroll : 0; }
|
||||
|
||||
/* ---- meow_scroll 集成(callbacks 指回本宫格)---- */
|
||||
static void lg_s_set(void *c, int off) { meow_lazygrid_set_scroll((meow_lazygrid *)c, off); }
|
||||
static int lg_s_max(void *c) { return meow_lazygrid_max_scroll((meow_lazygrid *)c); }
|
||||
static int lg_s_get(void *c) { return meow_lazygrid_scroll((meow_lazygrid *)c); }
|
||||
static void lg_s_redraw(void *c)
|
||||
{
|
||||
meow_lazygrid *g = (meow_lazygrid *)c;
|
||||
if (g && g->w > 0 && g->h > 0) {
|
||||
meow_lazygrid_draw(g, meow_theme_get());
|
||||
meow_ui_flush_region(g->x, g->y, g->w, g->h); /* 只刷视口区 */
|
||||
}
|
||||
}
|
||||
meow_scroll *meow_lazygrid_scroll_attach(meow_lazygrid *g)
|
||||
{
|
||||
if (!g) return NULL;
|
||||
return meow_scroll_create(lg_s_set, lg_s_max, lg_s_get, lg_s_redraw, g);
|
||||
}
|
||||
|
||||
/* ---- 选中 ---- */
|
||||
void meow_lazygrid_set_sel(meow_lazygrid *g, int idx)
|
||||
{
|
||||
if (!g) return;
|
||||
int n = lg_count(g);
|
||||
if (idx < 0) { g->sel = -1; return; }
|
||||
if (n > 0 && idx >= n) idx = n - 1;
|
||||
g->sel = idx;
|
||||
}
|
||||
int meow_lazygrid_sel(const meow_lazygrid *g) { return g ? g->sel : -1; }
|
||||
void meow_lazygrid_set_on_tap(meow_lazygrid *g, meow_lazygrid_tap_fn cb, void *ctx)
|
||||
{
|
||||
if (!g) return;
|
||||
g->tap_cb = cb; g->tap_ctx = ctx;
|
||||
}
|
||||
int meow_lazygrid_tap_at(meow_lazygrid *g, int x, int y)
|
||||
{
|
||||
int idx = meow_lazygrid_hit(g, x, y);
|
||||
if (idx < 0) return -1;
|
||||
meow_lazygrid_set_sel(g, idx);
|
||||
if (g->tap_cb) g->tap_cb(idx, g->tap_ctx);
|
||||
return idx;
|
||||
}
|
||||
|
||||
/* ---- 命中(滚动 + 网格坐标 → item idx)----
|
||||
* 点在格内(跨过 gap 区域返回 -1);禁用格不响应(同 listview enabled=0 语义)。 */
|
||||
int meow_lazygrid_hit(meow_lazygrid *g, int x, int y)
|
||||
{
|
||||
if (!g) return -1;
|
||||
if (x < g->x || x >= g->x + g->w || y < g->y || y >= g->y + g->h) return -1;
|
||||
/* 列格(水平):col = px / (cell_w+gap),落点在 gap 内 → 未命中。 */
|
||||
int px = x - g->x;
|
||||
int cstep = g->cell_w + g->gap;
|
||||
int col = px / cstep;
|
||||
if (col < 0 || col >= g->cols) return -1;
|
||||
if ((px - col * cstep) >= g->cell_w) return -1;
|
||||
/* 行格(垂直,含滚动):row = (py+scroll)/row_h;落点在 gap 内 → 未命中。 */
|
||||
int py = y - g->y + g->scroll;
|
||||
int rstep = g->row_h;
|
||||
int row = py / rstep;
|
||||
if (row < 0) return -1;
|
||||
if ((py - row * rstep) >= g->cell_h) return -1;
|
||||
int idx = row * g->cols + col;
|
||||
int n = lg_count(g);
|
||||
if (idx < 0 || idx >= n) return -1;
|
||||
/* 禁用格不响应(顺手 bind 临时 enabled 查:与 listview hit 同法)。 */
|
||||
if (g->bind) {
|
||||
meow_lazygrid_item it;
|
||||
memset(&it, 0, sizeof it);
|
||||
g->bind(idx, &it, g->vctx);
|
||||
if (!it.enabled) return -1;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
/* ---- 绘制(只画可见行;clip 钳到宫格视口)---- */
|
||||
static void lg_draw_cell(meow_lazygrid *g, const meow_theme *th, int index,
|
||||
int cell_x, int cell_y, meow_lazygrid_item *it)
|
||||
{
|
||||
int sel = (index == g->sel);
|
||||
int txtcol = sel ? th->onPrimaryContainer : th->onSurface;
|
||||
int subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
|
||||
uint32_t bg = sel ? th->primaryContainer : th->surfaceContainerLow;
|
||||
meow_gfx_fill_round(cell_x, cell_y, g->cell_w, g->cell_h, th->radius_card, bg);
|
||||
|
||||
int pad = 8;
|
||||
int thh = meow_ui_text_height(th->font_body);
|
||||
int chh = g->cell_h, cw = g->cell_w;
|
||||
/* 图标(可选):居中于格顶 ~50%。 */
|
||||
if (it->icon) {
|
||||
int isz = cw * 6 / 10; if (isz > chh * 5 / 10) isz = chh * 5 / 10;
|
||||
if (isz < 16) isz = 16;
|
||||
int icx = cell_x + cw / 2, icy = cell_y + pad + isz / 2;
|
||||
meow_ui_img_scaled(icx - isz / 2, icy - isz / 2, isz, isz, (meow_img *)it->icon);
|
||||
}
|
||||
/* 标题 + 副标题(主界面:名 + 状态)。底部对齐。 */
|
||||
int has_sub = (it->subtitle && it->subtitle[0]);
|
||||
int ty = cell_y + chh - pad - thh - (has_sub ? (thh + 2) : 0);
|
||||
if (it->title) meow_ui_text_center(cell_x + cw / 2, ty, it->title, th->font_body, txtcol);
|
||||
if (has_sub) meow_ui_text_center(cell_x + cw / 2, ty + thh + 2, it->subtitle, th->font_body, subcol);
|
||||
|
||||
/* 选中描边(轮廓);禁用整体变淡。 */
|
||||
if (sel) meow_ui_draw_rect_round(cell_x, cell_y, cw, chh, th->radius_card, th->primary);
|
||||
if (!it->enabled)
|
||||
meow_ui_fill_rect_alpha(cell_x, cell_y, cw, chh, th->onSurface, 90);
|
||||
}
|
||||
|
||||
void meow_lazygrid_draw(meow_lazygrid *g, const meow_theme *th)
|
||||
{
|
||||
if (!g || !th) return;
|
||||
meow_gfx_clip_push(g->x, g->y, g->w, g->h); /* 格画不出宫格视口(防泄漏) */
|
||||
|
||||
int n = lg_count(g);
|
||||
int rows = lg_rows(g);
|
||||
lg_ensure_pool(g); /* 防御:视口/列几何变化时确保池容量 */
|
||||
|
||||
/* 空态占位:count==0 且设 empty_text → 视口内居中。 */
|
||||
if (n == 0 && g->empty_text[0]) {
|
||||
int thh = meow_ui_text_height(th->font_body);
|
||||
meow_ui_text_center(g->x + g->w / 2, g->y + (g->h - thh) / 2, g->empty_text,
|
||||
th->font_body, th->onSurfaceVariant);
|
||||
meow_gfx_clip_pop();
|
||||
return;
|
||||
}
|
||||
|
||||
int off = g->scroll;
|
||||
int cstep = g->cell_w + g->gap;
|
||||
int rstep = g->row_h;
|
||||
if (rstep <= 0) rstep = 1;
|
||||
int first = off / rstep;
|
||||
int last = (off + g->h) / rstep + 1; if (last > rows) last = rows;
|
||||
if (first < 0) first = 0;
|
||||
if (last > rows) last = rows;
|
||||
for (int r = first; r < last; r++) {
|
||||
int ry = g->y + (r * rstep - off); /* 本行格顶视口坐标 */
|
||||
for (int c = 0; c < g->cols; c++) {
|
||||
int index = r * g->cols + c;
|
||||
if (index >= n) break; /* 末行余下空格 */
|
||||
int cx = g->x + c * cstep;
|
||||
/* 行完全滚出视口(裁剪由 clip 兜底,这里跳过整行) */
|
||||
if (ry >= g->y + g->h) break;
|
||||
if (ry + g->cell_h <= g->y) continue;
|
||||
|
||||
if (g->draw_cb) {
|
||||
int st = (index == g->sel) ? MEOW_LAZYGRID_ST_SELECTED : 0;
|
||||
g->draw_cb(index, cx, ry, g->cell_w, g->cell_h, st, g->draw_ctx);
|
||||
} else {
|
||||
meow_lazygrid_item *it = lg_slot(g, index);
|
||||
if (!it) continue;
|
||||
lg_draw_cell(g, th, index, cx, ry, it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
meow_gfx_clip_pop();
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* meow_select.c — §5.8.6 枚举/预设选择器(M7.2 设置页缺口 #4)。
|
||||
*
|
||||
* 封装 = BottomSheet(标题 + 选项列表)+ meow_listview(SINGLE) 单选列表。选项用 meow_listview
|
||||
* 渲染(单选高亮 primaryContainer、可滚动 meow_scroll 惯性);外层模态复用 meow_ui_modal_*
|
||||
* + meow_ui_modal_capture/restore(与 meow_dialog 同源:scrim 只叠一层/关闭无残影)。
|
||||
* 阻塞式(meow_input_pop 轮询);打开前排空残留触摸;点面板外/左缘返回 = 取消;点选项 = 选中 +
|
||||
* 关闭 + on_change 回调。选项数组定长(≤MEOW_SELECT_MAX_ITEMS)。
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "include/meow_select.h"
|
||||
#include "meow_ui_internal.h" /* meow_input_pop/drain + meow_ui_modal_capture/restore */
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define MEOW_SELECT_ROW 48 /* 选项行高(px) */
|
||||
|
||||
struct meow_select {
|
||||
char title[96];
|
||||
char items[MEOW_SELECT_MAX_ITEMS][64];
|
||||
int nitems;
|
||||
int cur; /* 初始/当前选中项 */
|
||||
int result; /* 选中下标 / -1=取消 */
|
||||
int done;
|
||||
int h; /* 面板高度(0=自动) */
|
||||
meow_select_fn cb;
|
||||
void *ctx;
|
||||
};
|
||||
|
||||
/* meow_listview src2 数据源:填 title=options[idx](右尾件无;enabled=1)。 */
|
||||
static int select_src2(int idx, meow_list_item *out, void *ctx)
|
||||
{
|
||||
meow_select *s = (meow_select *)ctx;
|
||||
if (idx < 0 || idx >= s->nitems) return 0;
|
||||
memset(out, 0, sizeof *out);
|
||||
out->title = s->items[idx];
|
||||
out->right = MEOW_LIST_RIGHT_NONE;
|
||||
out->enabled = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
meow_select *meow_select_create(const char *title, int cur)
|
||||
{
|
||||
meow_select *s = (meow_select *)calloc(1, sizeof *s);
|
||||
if (!s) return NULL;
|
||||
if (title) { strncpy(s->title, title, sizeof s->title - 1); s->title[sizeof s->title - 1] = '\0'; }
|
||||
s->cur = cur; s->result = -1; s->done = 0;
|
||||
return s;
|
||||
}
|
||||
int meow_select_add_item(meow_select *s, const char *label)
|
||||
{
|
||||
if (!s || s->nitems >= MEOW_SELECT_MAX_ITEMS) return -1;
|
||||
if (label) { strncpy(s->items[s->nitems], label, 63); s->items[s->nitems][63] = '\0'; }
|
||||
int idx = s->nitems;
|
||||
s->nitems++;
|
||||
return idx;
|
||||
}
|
||||
void meow_select_set_on_change(meow_select *s, meow_select_fn cb, void *ctx)
|
||||
{ if (s) { s->cb = cb; s->ctx = ctx; } }
|
||||
|
||||
int meow_select_open(meow_select *s)
|
||||
{
|
||||
if (!s || s->nitems <= 0) return -1;
|
||||
const meow_theme *th = meow_theme_get();
|
||||
meow_input_start();
|
||||
meow_input_drain(); /* 排空残留触摸(goodix 长尾) */
|
||||
|
||||
s->done = 0; s->result = -1;
|
||||
int W = meow_ui_width(), H = meow_ui_height();
|
||||
int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0;
|
||||
int pad = 24;
|
||||
int title_h = meow_ui_text_height(th->font_title);
|
||||
int rowh = MEOW_SELECT_ROW;
|
||||
int avail = H - sb - 20;
|
||||
int min_h = pad + title_h + 12 + rowh + 20;
|
||||
int want = (s->h > 0) ? s->h : min_h;
|
||||
int est = pad + title_h + 12 + s->nitems * rowh + 20;
|
||||
if (est > want) want = est;
|
||||
if (want > avail) want = avail;
|
||||
if (want < min_h) want = (min_h < avail) ? min_h : avail;
|
||||
int ph = want, py = H - ph; /* 底部面板(顶部圆角) */
|
||||
int list_y = py + pad + title_h + 12;
|
||||
int list_h = ph - (list_y - py) - pad; /* 底部余量 */
|
||||
if (list_h < rowh) list_h = rowh;
|
||||
|
||||
/* 选项列表(meow_listview[SINGLE]:单选高亮 + 可滚动)。open 时创建,close 释放(冷路径)。 */
|
||||
meow_widget *lv = meow_listview_create(0, list_y, W, list_h, s->nitems,
|
||||
select_src2, s, MEOW_LIST_SEL_SINGLE);
|
||||
if (!lv) return -1;
|
||||
meow_listview_set_item_h(lv, rowh);
|
||||
/* 定位初始选中项在视口内。 */
|
||||
int target = s->cur * rowh - (list_h - rowh) / 2;
|
||||
int maxs = meow_listview_max_scroll(lv);
|
||||
if (target < 0) target = 0;
|
||||
if (target > maxs) target = maxs;
|
||||
meow_listview_set_scroll(lv, target);
|
||||
meow_listview_set_sel(lv, s->cur);
|
||||
meow_scroll *sc = meow_scroll_wrap(lv, 1); /* 惯性滚动(fling/snap) */
|
||||
|
||||
/* §6.4 模态作用域 + 背景快照(关闭即还原,无残影/不叠黑)。 */
|
||||
meow_ui_modal *m = meow_ui_modal_begin();
|
||||
meow_ui_modal_capture();
|
||||
/* 首次绘制:遮罩 + 底部面板(顶部圆角)+ 标题 + 选项列表。 */
|
||||
meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150);
|
||||
meow_gfx_fill_round(0, py, W, ph, th->radius_card, th->surfaceContainerHighest);
|
||||
meow_ui_text(pad, py + pad, s->title, th->font_title, th->onSurface);
|
||||
meow_widget_draw(lv);
|
||||
meow_ui_flush();
|
||||
|
||||
int dy_start = 0, down = 0;
|
||||
while (!s->done) {
|
||||
meow_input_ev e;
|
||||
while (meow_input_pop(&e)) {
|
||||
meow_ui_activity_ping(); /* 模态期间交互也重置息屏计时 */
|
||||
if (e.type == MEOW_EV_TOUCH_DOWN) { down = 1; dy_start = e.b; }
|
||||
else if (e.type == MEOW_EV_TOUCH_MOVE) {
|
||||
if (down && sc) {
|
||||
meow_scroll_track(sc, e.b - dy_start, meow_ui_time_ms());
|
||||
dy_start = e.b;
|
||||
}
|
||||
} else if (e.type == MEOW_EV_TAP) {
|
||||
int x = e.a, y = e.b;
|
||||
if (y < py) { s->done = 1; s->result = -1; break; } /* 点面板外 = 取消 */
|
||||
int idx = meow_listview_tap_at(lv, x, y); /* 单选高亮 = 选中 */
|
||||
if (idx >= 0) {
|
||||
s->result = idx; s->cur = idx;
|
||||
if (s->cb) s->cb(idx, s->ctx); /* 关闭即回调 */
|
||||
s->done = 1;
|
||||
}
|
||||
/* 面板内但未命中项:保持打开(等下一次点击)。 */
|
||||
} else if (e.type == MEOW_EV_SWIPE) {
|
||||
if (e.a == MEOW_SWIPE_LEFT) { s->done = 1; s->result = -1; } /* 返回 = 取消 */
|
||||
else if (sc) meow_scroll_fling(sc);
|
||||
}
|
||||
}
|
||||
meow_anim_tick(); /* 推进 fling */
|
||||
if (!s->done) usleep(16000);
|
||||
}
|
||||
meow_ui_modal_end(m);
|
||||
meow_ui_modal_restore();
|
||||
if (sc) meow_scroll_free(sc);
|
||||
meow_widget_free(lv);
|
||||
return s->result;
|
||||
}
|
||||
void meow_select_free(meow_select *s) { if (s) free(s); }
|
||||
|
||||
int meow_select_show(const char *title, const char *const *opts, int n, int cur)
|
||||
{
|
||||
if (!opts || n <= 0) return -1;
|
||||
meow_select *s = meow_select_create(title, cur);
|
||||
if (!s) return -1;
|
||||
for (int i = 0; i < n && i < MEOW_SELECT_MAX_ITEMS; i++) meow_select_add_item(s, opts[i]);
|
||||
int r = meow_select_open(s);
|
||||
meow_select_free(s);
|
||||
return r;
|
||||
}
|
||||
@@ -37,6 +37,8 @@
|
||||
* include/meow_ui_box.h 线性布局引擎(§12.1:meow_box,固定/weight/wrap/对齐/嵌套)
|
||||
* include/meow_ui_anim.h 动画基座+UI 节拍器(§12.2:tween/easing/池+脏区联动)
|
||||
* include/meow_ui_scroll.h 滚动物理(§12.3:速度跟踪/fling/边界回弹/snap)
|
||||
* include/meow_lazygrid.h 2 维虚拟化宫格(§12.4:meow_lazygrid,M7.4 主界面前置)
|
||||
* include/meow_select.h 枚举/预设选择器(M7.2 设置页缺口 #4:BottomSheet+单选列表)
|
||||
* include/meow_ui_str.h 字符串资源/i18n(§13:meow_str/meow_strf/meow_str_set_locale)
|
||||
*
|
||||
* 其余模块头(非伞头、App 按需显式 include,均保持不动):
|
||||
@@ -70,7 +72,10 @@
|
||||
#include "include/meow_ui_box.h"
|
||||
#include "include/meow_ui_anim.h"
|
||||
#include "include/meow_ui_scroll.h"
|
||||
#include "include/meow_lazygrid.h"
|
||||
#include "include/meow_select.h"
|
||||
#include "include/meow_ui_str.h"
|
||||
#include "include/meow_ui_net.h" /* 期4 网络自愿通道:meow_net_policy_check / meow_net_report */
|
||||
#include "include/meow_cachegov.h" /* 磁盘缓存 page-cache 治理(A:DONTNEED L0/L1 + refault 回退;C 归 svc_perf) */
|
||||
|
||||
#endif /* MEOW_UI_H */
|
||||
@@ -27,7 +27,7 @@
|
||||
static unsigned char *g_modal_cap = NULL;
|
||||
static int g_modal_cap_line = 0, g_modal_cap_h = 0, g_modal_cap_bpp = 0;
|
||||
|
||||
static void modal_capture(void)
|
||||
void meow_ui_modal_capture(void)
|
||||
{
|
||||
unsigned char *bb = meow_ui_back_buf();
|
||||
int line = meow_ui_back_buf_pitch();
|
||||
@@ -43,7 +43,7 @@ static void modal_capture(void)
|
||||
if (g_modal_cap) memcpy(g_modal_cap, bb, n);
|
||||
}
|
||||
|
||||
static void modal_restore(void)
|
||||
void meow_ui_modal_restore(void)
|
||||
{
|
||||
unsigned char *bb = meow_ui_back_buf();
|
||||
if (!g_modal_cap || !bb) return;
|
||||
@@ -235,7 +235,7 @@ void meow_dialog_show(meow_dialog *d)
|
||||
/* §6.4:模态作用域——本层打开期间下层(view/widget)完全收不到触摸;点卡片外由背板消费
|
||||
* (d->done=1 result=0 取消),不透传。嵌套弹层 depth 递增。 */
|
||||
meow_ui_modal *m = meow_ui_modal_begin();
|
||||
modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原(scrim 只叠一层/无残影);
|
||||
meow_ui_modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原(scrim 只叠一层/无残影);
|
||||
* 嵌套模态在各自打开时拍「当前已渲染帧」,故两层弹开不叠黑。 */
|
||||
dlg_paint(d, th);
|
||||
meow_ui_flush();
|
||||
@@ -291,7 +291,7 @@ void meow_dialog_show(meow_dialog *d)
|
||||
usleep(16000);
|
||||
}
|
||||
meow_ui_modal_end(m); /* §6.4:关闭模态作用域(下层恢复接收触摸) */
|
||||
modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */
|
||||
meow_ui_modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */
|
||||
}
|
||||
|
||||
int meow_dialog_result(meow_dialog *d) { return d ? d->result : 0; }
|
||||
@@ -355,7 +355,7 @@ int meow_sheet_open(meow_sheet *s)
|
||||
int ph = sheet_h(s, th);
|
||||
int py = H - ph, pad = 24;
|
||||
meow_ui_modal *m = meow_ui_modal_begin(); /* §6.4:模态作用域(下层收不到触摸) */
|
||||
modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原(scrim 只叠一层/无残影) */
|
||||
meow_ui_modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原(scrim 只叠一层/无残影) */
|
||||
/* 首次绘制:遮罩 + 底部面板(顶部圆角) */
|
||||
meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150);
|
||||
meow_gfx_fill_round(0, py, W, ph, th->radius_card, th->surfaceContainerHighest);
|
||||
@@ -389,7 +389,7 @@ int meow_sheet_open(meow_sheet *s)
|
||||
usleep(16000);
|
||||
}
|
||||
meow_ui_modal_end(m); /* §6.4:关闭模态作用域 */
|
||||
modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */
|
||||
meow_ui_modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */
|
||||
return s->result;
|
||||
}
|
||||
void meow_sheet_free(meow_sheet *s) { if (s) free(s); }
|
||||
@@ -979,6 +979,9 @@ static int statusbar_touched(void)
|
||||
* App 进程(非系统 UI 渲染者)此时不上屏;200ms 节流缓存存在性(别每帧 stat)。
|
||||
* 计数 g_overlay_suppressed 供宿主单测断言(meow_gfx_overlay_suppressed_count)。 */
|
||||
static unsigned long long g_overlay_suppressed = 0;
|
||||
/* §后台化保活-残帧:记录「上一次 present 是否处于叠加自抑态」,用于在抑制→不抑制的恢复边沿
|
||||
* 强制整帧重画(盖掉 launcher 在后台化期间画进 buffer0 的桌面/主界面帧)。 */
|
||||
static int g_was_overlay_suppressed = 0;
|
||||
|
||||
static int overlay_present_suppressed(void)
|
||||
{
|
||||
@@ -1009,7 +1012,18 @@ static void present(void)
|
||||
* (保 PCM 供流,ncm 播放线程同进程),改由 App 进程在此自抑上屏——绘制照常进 back_buf,
|
||||
* 只是不 present 到 fb,防 App 重绘穿透 launcher 叠加层。系统 UI 进程(launcher 渲染面板)
|
||||
* 不抑制。抑制期脏区保留累积(超上限自然升级整帧),标记消失后下一次 present 一次上屏。 */
|
||||
if (overlay_present_suppressed()) { g_overlay_suppressed++; return; }
|
||||
if (overlay_present_suppressed()) { g_overlay_suppressed++; g_was_overlay_suppressed = 1; return; }
|
||||
/* §后台化保活-残帧:抑制→不抑制的**恢复边沿**强制整帧重画。App 被 launcher 转后台保活
|
||||
* (/tmp/meow_notif_active 存在)期间 present 被自抑,back_buf 仍保留 App 自己的完整帧;
|
||||
* 但 launcher 已把桌面/主界面整帧画进 buffer0 盖在 App 残帧上。恢复前台时若只 present
|
||||
* 抑制期累积的小脏区(时钟/状态栏),launcher 的桌面帧会在 App 未重画区域透出(真机
|
||||
* 「桌面叠在 ncm UI 下面/旧播放界面层叠」。标记由有→无即「回到前台」,强制 dirty_full
|
||||
* 让下一次 present 把 App 完整 back_buf 一次上屏,盖掉桌面帧。仅真实 App 进程(launcher
|
||||
* 系统 UI 恒不抑制,本边沿不触发)。 */
|
||||
if (g_was_overlay_suppressed) {
|
||||
g_was_overlay_suppressed = 0;
|
||||
dirty_full();
|
||||
}
|
||||
/* 帧率门控(A-1/A-2,从 launcher 迁入):present 距上次 ≥ period(1000/eff_rate) 才呈现;
|
||||
* 未满周期则保留脏区(不 dirty_reset),下一轮再试。refresh_always_max=1 时跳过门控(恒满速)。 */
|
||||
unsigned long long now = now_ms();
|
||||
|
||||
@@ -131,6 +131,13 @@ void meow_gfx_mark_full(void);
|
||||
* 菜单内容盖到 App 内容上)——叠加前用本函数撤销整帧脏,再按需标局部脏(agent-146)。 */
|
||||
void meow_gfx_dirty_reset(void);
|
||||
|
||||
/* ---- 模态背景快照/恢复(meow_ui_dialog.c 提供;dialog/sheet/select 共用)----
|
||||
* open 前调用 capture 把整帧背景拍下(modal_capture),close 后调用 restore 回填 + 整帧上屏
|
||||
* (modal_restore)→ scrim 每次只叠一层、关闭即还原无残影(M2.71-bug3)。共享同一份快照缓冲,
|
||||
* 各模态组件(meow_select 等)无需重复实现。 */
|
||||
void meow_ui_modal_capture(void);
|
||||
void meow_ui_modal_restore(void);
|
||||
|
||||
/* 返回离屏 back_buf 指针(launcher 系统组件与 SDK 共用同一 back_buf 用)。 */
|
||||
unsigned char *meow_ui_back_buf(void);
|
||||
/* back_buf 的每行字节数(pitch)与位深(16/32)。M2.71-bug3:模态/IME 快照恢复用。 */
|
||||
|
||||
+196
-25
@@ -116,6 +116,21 @@ struct meow_widget {
|
||||
int show_icon; /* 1=画左图标/缩略图(默认 1) */
|
||||
int show_right;/* 1=画右尾件(默认 1) */
|
||||
char empty_text[64]; /* 空态占位文案(首字符 0=不画) */
|
||||
|
||||
/* §5.5 左滑删除(列表行横向手势:滑动偏移/删除动画/回弹;calloc 归零,安全)。
|
||||
* 手势由 App 在 on_touch_move 喂(swipe_begin/move/end);横向小位移判 swipe、纵向滚动优先;
|
||||
* 行自绘 set_item_draw 配合(M7.3 通知行)。swipe_anim 由 meow_anim 静态池驱动。 */
|
||||
int swipe_enabled; /* 1=左滑删除可用(on_delete 回调有效) */
|
||||
int swipe_active; /* 1=当前行处于滑动态(swipe_idx 有效) */
|
||||
int swipe_idx; /* 正被滑动的行号(-1=无) */
|
||||
int swipe_dx; /* 当前行横向偏移(<=0=左移;滑动/动画中) */
|
||||
int swipe_drag; /* 1=手指拖动中(限行内) */
|
||||
int swipe_start_x, swipe_start_y; /* 手势起点(判定方向用) */
|
||||
int swipe_started; /* 1=已裁定为横向 swipe(纵向滚动手势被抑制) */
|
||||
int swipe_anim_delete;/* 1=删除动画(目标 -w);0=回弹(目标 0) */
|
||||
meow_listview_swipe_fn swipe_cb; /* 删除回调(idx) */
|
||||
void *swipe_ctx;
|
||||
meow_anim *swipe_anim; /* 滑动/回弹动画(非 NULL=进行中) */
|
||||
};
|
||||
|
||||
/* ---- §5.8 batch1 内部辅助:opts 取值(opts_used==0 时按控件内建默认)----
|
||||
@@ -1161,7 +1176,7 @@ meow_widget *meow_listview_create(int x, int y, int w, int h, int count,
|
||||
t->owner = meow_ui_activity_active();
|
||||
t->state = MEOW_WS_ENABLED;
|
||||
t->count = count > 0 ? count : 0; t->src2 = src; t->ctx = ctx;
|
||||
t->sel = -1; t->item_h = 56; t->scroll = 0;
|
||||
t->sel = -1; t->item_h = 56; t->scroll = 0; t->swipe_idx = -1;
|
||||
t->sel_mode = (mode >= MEOW_LIST_SEL_NONE && mode <= MEOW_LIST_SEL_MULTI) ? mode : MEOW_LIST_SEL_NONE;
|
||||
t->lines = 2; t->show_icon = 1; t->show_right = 1; /* 默认双行富 item + 图标 + 右尾件(src2 布局不变) */
|
||||
if (t->sel_mode == MEOW_LIST_SEL_MULTI && t->count > 0) {
|
||||
@@ -1393,6 +1408,127 @@ int meow_listview_set_empty_text(meow_widget *w, const char *text)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
/* §5.5 左滑删除(M7.3 缺口 #9):手势判定 + 删除/回弹动画 */
|
||||
/* ============================================================ */
|
||||
|
||||
#define MEOW_SWIPE_SLOP 10 /* 手势判定容差(px):超过才裁定方向 */
|
||||
/* 删除阈值:|dx| ≥ 行宽*2/5。 */
|
||||
static int swipe_thresh(const meow_widget *w) { return w->w * 2 / 5; }
|
||||
|
||||
/* 动画更新:设行偏移 + 重绘列表(只画可见行;widget clip 已裁剪)。 */
|
||||
static void swipe_anim_update(meow_anim *a, float v, void *ctx)
|
||||
{
|
||||
(void)a;
|
||||
meow_widget *lv = (meow_widget *)ctx;
|
||||
if (!lv) return;
|
||||
lv->swipe_dx = (int)v;
|
||||
meow_widget_draw(lv);
|
||||
}
|
||||
static void swipe_anim_done(meow_anim *a, int canceled, void *ctx)
|
||||
{
|
||||
(void)a;
|
||||
meow_widget *lv = (meow_widget *)ctx;
|
||||
if (!lv) return;
|
||||
int del = lv->swipe_anim_delete;
|
||||
int idx = lv->swipe_idx;
|
||||
lv->swipe_anim = NULL;
|
||||
lv->swipe_anim_delete = 0;
|
||||
lv->swipe_active = 0; lv->swipe_idx = -1; lv->swipe_dx = 0;
|
||||
if (canceled) return; /* 取消:回正常位(无回调) */
|
||||
if (del && lv->swipe_cb) /* 删除:滑出完成 → 回调(App 移除行 + 刷新 count) */
|
||||
lv->swipe_cb(idx, lv->swipe_ctx);
|
||||
}
|
||||
|
||||
void meow_listview_set_swipe_delete(meow_widget *lv, int enabled,
|
||||
meow_listview_swipe_fn on_delete, void *ctx)
|
||||
{
|
||||
if (!lv || lv->type != W_LISTVIEW) return;
|
||||
if (lv->swipe_anim) { meow_anim_cancel(lv->swipe_anim); lv->swipe_anim = NULL; }
|
||||
lv->swipe_enabled = enabled ? 1 : 0;
|
||||
lv->swipe_cb = on_delete; lv->swipe_ctx = ctx;
|
||||
lv->swipe_active = 0; lv->swipe_idx = -1; lv->swipe_dx = 0; lv->swipe_drag = 0; lv->swipe_started = 0;
|
||||
}
|
||||
|
||||
int meow_listview_swipe_begin(meow_widget *lv, int x, int y)
|
||||
{
|
||||
if (!lv || lv->type != W_LISTVIEW || !lv->swipe_enabled) return 0;
|
||||
int idx = meow_listview_hit(lv, x, y);
|
||||
if (idx < 0) return 0;
|
||||
if (lv->swipe_anim) return 1; /* 已有动画:吞掉,不干扰当前行 */
|
||||
lv->swipe_drag = 1; lv->swipe_started = 0;
|
||||
lv->swipe_idx = idx; lv->swipe_dx = 0;
|
||||
lv->swipe_start_x = x; lv->swipe_start_y = y;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int meow_listview_swipe_move(meow_widget *lv, int x, int y)
|
||||
{
|
||||
if (!lv || lv->type != W_LISTVIEW || !lv->swipe_drag) return 0;
|
||||
int dx = x - lv->swipe_start_x, dy = y - lv->swipe_start_y;
|
||||
int adx = dx < 0 ? -dx : dx, ady = dy < 0 ? -dy : dy;
|
||||
if (!lv->swipe_started) {
|
||||
if (ady > adx && ady > MEOW_SWIPE_SLOP) { /* 纵向优先:取消 swipe,让位滚动 */
|
||||
lv->swipe_drag = 0; lv->swipe_active = 0; lv->swipe_idx = -1; lv->swipe_dx = 0;
|
||||
return 0;
|
||||
}
|
||||
if (adx > MEOW_SWIPE_SLOP && adx > ady) { /* 横向小位移:裁定为 swipe */
|
||||
lv->swipe_started = 1; lv->swipe_active = 1;
|
||||
} else {
|
||||
return 0; /* 太小未裁定:暂不消费 */
|
||||
}
|
||||
} else {
|
||||
if (ady > adx && ady > MEOW_SWIPE_SLOP) { /* 中途纵向接管:取消,让位滚动 */
|
||||
lv->swipe_drag = 0; lv->swipe_active = 0; lv->swipe_idx = -1; lv->swipe_dx = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/* 只左滑(dx<0)露右删除区;右滑 clamp 到 0;触底 clamp 到 -w。 */
|
||||
int off = dx < 0 ? dx : 0;
|
||||
if (off < -lv->w) off = -lv->w;
|
||||
if (off == lv->swipe_dx) return 1;
|
||||
lv->swipe_dx = off;
|
||||
meow_gfx_mark_dirty(lv->x, lv->y, lv->w, lv->h);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int meow_listview_swipe_end(meow_widget *lv, int x, int y)
|
||||
{
|
||||
(void)x; (void)y;
|
||||
if (!lv || lv->type != W_LISTVIEW) return 0;
|
||||
if (!lv->swipe_drag) return 0; /* 非 swipe 行(tap/滚动)→ App 自理 */
|
||||
lv->swipe_drag = 0;
|
||||
if (!lv->swipe_started) return 0; /* 无位移 = 点击 → App 走 tap_at */
|
||||
int dx = lv->swipe_dx;
|
||||
int del = (-dx >= swipe_thresh(lv)); /* 超阈值 → 删除;否则回弹 */
|
||||
int item = listv_item_h(lv);
|
||||
int ry = lv->y + ((unsigned)lv->swipe_idx * (unsigned)item - lv->scroll);
|
||||
if (lv->swipe_anim) { meow_anim_cancel(lv->swipe_anim); lv->swipe_anim = NULL; }
|
||||
lv->swipe_anim_delete = del;
|
||||
meow_anim_desc d; memset(&d, 0, sizeof d);
|
||||
d.from = (float)dx;
|
||||
d.to = (float)(del ? -lv->w : 0);
|
||||
d.duration_ms = del ? 160u : 140u;
|
||||
d.ease = del ? MEOW_EASE_OUT : MEOW_EASE_OVERSHOOT;
|
||||
d.update = swipe_anim_update; d.done = swipe_anim_done; d.ctx = lv;
|
||||
meow_anim *an = meow_anim_start(&d);
|
||||
if (!an) { /* 池满:直接终态(防卡死) */
|
||||
if (del) { int i = lv->swipe_idx; lv->swipe_idx = -1; lv->swipe_dx = 0;
|
||||
if (lv->swipe_cb) lv->swipe_cb(i, lv->swipe_ctx); }
|
||||
lv->swipe_active = 0; lv->swipe_dx = 0;
|
||||
meow_gfx_mark_dirty(lv->x, lv->y, lv->w, lv->h);
|
||||
return 1;
|
||||
}
|
||||
lv->swipe_anim = an;
|
||||
meow_anim_set_region(an, lv->x, ry < lv->y ? lv->y : ry, lv->w, item); /* 动画帧只刷该行带 */
|
||||
return 1;
|
||||
}
|
||||
|
||||
int meow_listview_swipe_active(meow_widget *lv)
|
||||
{ return (lv && lv->type == W_LISTVIEW && (lv->swipe_active || lv->swipe_anim)) ? 1 : 0; }
|
||||
int meow_listview_swipe_offset(meow_widget *lv)
|
||||
{ return (lv && lv->type == W_LISTVIEW) ? lv->swipe_dx : 0; }
|
||||
|
||||
static int hit(meow_widget *w, int x, int y)
|
||||
{
|
||||
if (!w) return 0;
|
||||
@@ -1669,7 +1805,17 @@ void meow_widget_draw(meow_widget *w)
|
||||
meow_input_touch_x() >= vx0 && meow_input_touch_x() < vx0 + w->w &&
|
||||
meow_input_touch_y() >= ry && meow_input_touch_y() < ry + item)
|
||||
st |= MEOW_LISTVIEW_ST_PRESSED;
|
||||
w->vs_draw(i, vx0, ry, w->w, item, st, w->vs_ctx);
|
||||
int rx0 = vx0; /* 左滑行:行 x 左移 + 右露删除区 */
|
||||
if (w->swipe_active && w->swipe_idx == i && w->swipe_dx < 0) {
|
||||
meow_ui_fill_rect(vx0 + w->w + w->swipe_dx, ry, -w->swipe_dx, item, th->error);
|
||||
int thh2 = meow_ui_text_height(th->font_body);
|
||||
int dly = ry + (item - thh2) / 2;
|
||||
if (dly >= vy0 && dly + thh2 <= vy1)
|
||||
meow_ui_text_center(vx0 + w->w + w->swipe_dx + (-w->swipe_dx) / 2, dly,
|
||||
meow_str("common.delete"), th->font_body, th->onError);
|
||||
rx0 = vx0 + w->swipe_dx;
|
||||
}
|
||||
w->vs_draw(i, rx0, ry, w->w, item, st, w->vs_ctx);
|
||||
}
|
||||
} else { /* ② 默认 renderer:固定 item 池(进入视口才 bind) */
|
||||
for (int i = first; i < last; i++) {
|
||||
@@ -1684,23 +1830,37 @@ void meow_widget_draw(meow_widget *w)
|
||||
int press = meow_input_touch_down() &&
|
||||
meow_input_touch_x() >= vx0 && meow_input_touch_x() < vx0 + w->w &&
|
||||
meow_input_touch_y() >= ry && meow_input_touch_y() < ry + item;
|
||||
/* 左滑删除(M7.3):本行被滑动 → 右露删除区 + 行 x 左移。非滑动行 shift=0 → 输出不变。 */
|
||||
int shift = 0;
|
||||
if (w->swipe_active && w->swipe_idx == i && w->swipe_dx < 0) {
|
||||
meow_ui_fill_rect(vx0 + w->w + w->swipe_dx, ry, -w->swipe_dx, item, th->error);
|
||||
int thh2 = meow_ui_text_height(th->font_body);
|
||||
int dly = ry + (item - thh2) / 2;
|
||||
if (dly >= vy0 && dly + thh2 <= vy1)
|
||||
meow_ui_text_center(vx0 + w->w + w->swipe_dx + (-w->swipe_dx) / 2, dly,
|
||||
meow_str("common.delete"), th->font_body, th->onError);
|
||||
shift = w->swipe_dx;
|
||||
}
|
||||
int rlx = vx0 + shift; /* 行内容左移后的 x */
|
||||
int rtx = text_x + shift; /* 文本 x(随行左移) */
|
||||
int rop = op_start + shift; /* 操作区分界 x */
|
||||
|
||||
if (it->section) { /* 段头占位行(非粘性):surfaceContainerHigh 底 + primary 段名 */
|
||||
meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->surfaceContainerHigh);
|
||||
meow_ui_fill_rect(rlx, cl, w->w, cr - cl, th->surfaceContainerHigh);
|
||||
int sty = ry + (item - thh) / 2;
|
||||
if (it->title && sty >= vy0 && sty + thh <= vy1)
|
||||
listv_text_trunc(text_x, sty, it->title, text_maxw, th->font_body, th->primary);
|
||||
listv_text_trunc(rtx, sty, it->title, text_maxw, th->font_body, th->primary);
|
||||
int sy = ry + item - 1;
|
||||
if (sy >= cl && sy < cr)
|
||||
meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant);
|
||||
meow_ui_fill_rect(rlx, sy, w->w, 1, th->outlineVariant);
|
||||
if (!it->enabled)
|
||||
meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90);
|
||||
meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->onSurface, 90);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 选中/按压背景(primaryContainer / 半透明主题色按压层) */
|
||||
if (sel) meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->primaryContainer);
|
||||
else if (press) meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->primary, 26);
|
||||
if (sel) meow_ui_fill_rect(rlx, cl, w->w, cr - cl, th->primaryContainer);
|
||||
else if (press) meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->primary, 26);
|
||||
uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface;
|
||||
uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
|
||||
|
||||
@@ -1711,38 +1871,38 @@ void meow_widget_draw(meow_widget *w)
|
||||
: th->onSurfaceVariant);
|
||||
int icy = ry + (item - isz) / 2;
|
||||
if (icy + isz >= vy0 && icy < vy1)
|
||||
listv_draw_icon(th, vx0 + pad, icy, it->icon_kind, it->icon, iccol);
|
||||
listv_draw_icon(th, rlx + pad, icy, it->icon_kind, it->icon, iccol);
|
||||
}
|
||||
|
||||
/* 标题 + 副标题(双行居中;单行(lines==1 或无副标题)单行居中);越界不画 */
|
||||
if (w->lines == 2 && it->subtitle && it->subtitle[0]) {
|
||||
int ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
||||
if (it->title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it->title, text_maxw, th->font_body, textcol);
|
||||
listv_text_trunc(rtx, ty, it->title, text_maxw, th->font_body, textcol);
|
||||
int sy = ty + thh + MEOW_LISTV_SUB_GAP;
|
||||
if (sy >= vy0 && sy + thh <= vy1)
|
||||
listv_text_trunc(text_x, sy, it->subtitle, text_maxw, th->font_body, subcol);
|
||||
listv_text_trunc(rtx, sy, it->subtitle, text_maxw, th->font_body, subcol);
|
||||
} else {
|
||||
int ty = ry + (item - thh) / 2;
|
||||
if (it->title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it->title, text_maxw, th->font_body, textcol);
|
||||
listv_text_trunc(rtx, ty, it->title, text_maxw, th->font_body, textcol);
|
||||
}
|
||||
|
||||
/* 右尾件(show_right=1:箭头/开关/复选/⋯) */
|
||||
if (w->show_right && it->right != MEOW_LIST_RIGHT_NONE) {
|
||||
int ax = w->x + w->w - pad - 24; /* 右缘动作槽(约 24px) */
|
||||
if (ax >= op_start)
|
||||
int ax = rlx + w->w - pad - 24; /* 右缘动作槽(约 24px) */
|
||||
if (ax >= rop)
|
||||
listv_draw_right(th, ax, ry, item, it->right, it->on, subcol);
|
||||
}
|
||||
|
||||
/* 分隔线(仅落在可见带内才画) */
|
||||
int sy = ry + item - 1;
|
||||
if (sy >= cl && sy < cr)
|
||||
meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant);
|
||||
meow_ui_fill_rect(rlx, sy, w->w, 1, th->outlineVariant);
|
||||
|
||||
/* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */
|
||||
if (!it->enabled)
|
||||
meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90);
|
||||
meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->onSurface, 90);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1760,7 +1920,18 @@ void meow_widget_draw(meow_widget *w)
|
||||
int sel = (i == w->sel);
|
||||
uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface;
|
||||
uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
|
||||
if (sel) meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->primaryContainer);
|
||||
/* 左滑删除(M7.3):本行被滑动 → 右露删除区 + 行 x 左移。非滑动行 shift=0 → 输出不变。 */
|
||||
int shift = 0, rlx = vx0, rtx = text_x, rop = op_start;
|
||||
if (w->swipe_active && w->swipe_idx == i && w->swipe_dx < 0) {
|
||||
meow_ui_fill_rect(vx0 + w->w + w->swipe_dx, ry, -w->swipe_dx, item, th->error);
|
||||
int thh2 = meow_ui_text_height(th->font_body);
|
||||
int dly = ry + (item - thh2) / 2;
|
||||
if (dly >= vy0 && dly + thh2 <= vy1)
|
||||
meow_ui_text_center(vx0 + w->w + w->swipe_dx + (-w->swipe_dx) / 2, dly,
|
||||
meow_str("common.delete"), th->font_body, th->onError);
|
||||
shift = w->swipe_dx; rlx = vx0 + shift; rtx = text_x + shift; rop = op_start + shift;
|
||||
}
|
||||
if (sel) meow_ui_fill_rect(rlx, cl, w->w, cr - cl, th->primaryContainer);
|
||||
|
||||
/* 图标(左) */
|
||||
uint32_t iccol = sel ? th->onPrimaryContainer
|
||||
@@ -1768,38 +1939,38 @@ void meow_widget_draw(meow_widget *w)
|
||||
: th->onSurfaceVariant);
|
||||
int icy = ry + (item - isz) / 2;
|
||||
if (icy + isz >= vy0 && icy < vy1) /* 图标不越视口边界才画(防泄漏) */
|
||||
listv_draw_icon(th, vx0 + pad, icy, it.icon_kind, it.icon, iccol);
|
||||
listv_draw_icon(th, rlx + pad, icy, it.icon_kind, it.icon, iccol);
|
||||
|
||||
/* 标题 + 副标题(两行居中;无副标题=单行居中;越界不画防泄漏) */
|
||||
int ty;
|
||||
if (it.subtitle && it.subtitle[0]) {
|
||||
ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
||||
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it.title, text_maxw, th->font_body, textcol);
|
||||
listv_text_trunc(rtx, ty, it.title, text_maxw, th->font_body, textcol);
|
||||
int sy = ty + thh + MEOW_LISTV_SUB_GAP;
|
||||
if (sy >= vy0 && sy + thh <= vy1)
|
||||
listv_text_trunc(text_x, sy, it.subtitle, text_maxw, th->font_body, subcol);
|
||||
listv_text_trunc(rtx, sy, it.subtitle, text_maxw, th->font_body, subcol);
|
||||
} else {
|
||||
ty = ry + (item - thh) / 2;
|
||||
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it.title, text_maxw, th->font_body, textcol);
|
||||
listv_text_trunc(rtx, ty, it.title, text_maxw, th->font_body, textcol);
|
||||
}
|
||||
|
||||
/* 右侧操作区 */
|
||||
if (it.right != MEOW_LIST_RIGHT_NONE) {
|
||||
int ax = w->x + w->w - pad - 24; /* 右缘动作槽(约 24px) */
|
||||
if (ax >= op_start)
|
||||
int ax = rlx + w->w - pad - 24; /* 右缘动作槽(约 24px) */
|
||||
if (ax >= rop)
|
||||
listv_draw_right(th, ax, ry, item, it.right, it.on, subcol);
|
||||
}
|
||||
|
||||
/* 分隔线(仅落在可见带内才画) */
|
||||
int sy = ry + item - 1;
|
||||
if (sy >= cl && sy < cr)
|
||||
meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant);
|
||||
meow_ui_fill_rect(rlx, sy, w->w, 1, th->outlineVariant);
|
||||
|
||||
/* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */
|
||||
if (!it.enabled)
|
||||
meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90);
|
||||
meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->onSurface, 90);
|
||||
}
|
||||
}
|
||||
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
|
||||
|
||||
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,189 @@
|
||||
/* test_meow_cachegov.c — 宿主测试:SDK meow_cachegov(A:DONTNEED L0/L1 + refault 回退)。
|
||||
*
|
||||
* 背景(OOM_PAGECACHE_GOVERNANCE §2/§4,③混合):A 的 DONTNEED/read_pos 热路径随 SDK 库;
|
||||
* 本测试用宿主桩:
|
||||
* - 真实 posix_fadvise(文件数据完整性校验:drop 后文件内容不变、drop_finished 可再读);
|
||||
* - meow_log 桩捕获 "[cachegov] L1 drop off=.. len=.." 断言丢弃范围([0, read_pos-keep_hot));
|
||||
* - meow_cachegov_test_set_time_ms() 注入时钟驱动 refault 滚窗回退(放大 keep_hot→降L0→关A);
|
||||
* - MEOW_CACHEGOV_C_FILE 指向临时文件写入 fake C 状态,断言 download_paused/prefetch_paused。
|
||||
*
|
||||
* 编译(patch_v2/sdk 目录下):
|
||||
* gcc -Os -Wall -Wextra -I. -Iinclude -I../meow_launcherd -c meow_cachegov.c -o /tmp/cg.o
|
||||
* gcc -Os -Wall -Wextra -I. -Iinclude -I../meow_launcherd test_meow_cachegov.c /tmp/cg.o -lpthread -o test_meow_cachegov
|
||||
* 运行:./test_meow_cachegov (exit 0=PASS / 1=FAIL)
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include "meow_cachegov.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/* ---- meow_log 桩:捕获最近一条 [cachegov] 行,供断言 ---- */
|
||||
static char g_last_log[512];
|
||||
void meow_log(int lvl, const char *fmt, ...)
|
||||
{
|
||||
(void)lvl;
|
||||
va_list ap; va_start(ap, fmt);
|
||||
vsnprintf(g_last_log, sizeof g_last_log, fmt, ap);
|
||||
va_end(ap);
|
||||
printf(" [cachegov.log] %s\n", g_last_log);
|
||||
}
|
||||
static int log_has(const char *needle) { return strstr(g_last_log, needle) != NULL; }
|
||||
|
||||
static int g_pass = 0, g_fail = 0;
|
||||
static void check(const char *name, int cond)
|
||||
{
|
||||
if (cond) { g_pass++; printf("PASS %s\n", name); }
|
||||
else { g_fail++; printf("FAIL %s\n", name); }
|
||||
}
|
||||
|
||||
/* ---- read_pos 桩:由测试驱动当前读指针 ---- */
|
||||
static uint64_t g_pos;
|
||||
static uint64_t fake_read_pos(void *ctx) { (void)ctx; return g_pos; }
|
||||
|
||||
/* 建一个 size 字节的临时文件并填充确定性内容。 */
|
||||
static int make_file(const char *path, size_t size)
|
||||
{
|
||||
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (fd < 0) return -1;
|
||||
for (size_t off = 0; off < size; off += 4096) {
|
||||
char tmp[4096];
|
||||
memset(tmp, (int)(off / 4096) & 0xFF, sizeof tmp);
|
||||
size_t n = (size - off) < sizeof tmp ? (size - off) : sizeof tmp;
|
||||
if (write(fd, tmp, n) != (ssize_t)n) { close(fd); return -1; }
|
||||
}
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 校验文件内容仍为确定性模式(drop 后数据完整)。 */
|
||||
static int verify_file(const char *path, size_t size)
|
||||
{
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0) return 0;
|
||||
char tmp[4096];
|
||||
size_t off = 0;
|
||||
int ok = 1;
|
||||
while (off < size) {
|
||||
size_t n = (size - off) < sizeof tmp ? (size - off) : sizeof tmp;
|
||||
ssize_t r = read(fd, tmp, n);
|
||||
if (r != (ssize_t)n) { ok = 0; break; }
|
||||
char expect[4096];
|
||||
memset(expect, (int)(off / 4096) & 0xFF, n);
|
||||
if (memcmp(tmp, expect, n) != 0) { ok = 0; break; }
|
||||
off += (size_t)n;
|
||||
}
|
||||
close(fd);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
char path[320], cfile[360];
|
||||
snprintf(path, sizeof path, "/tmp/meow_cg_%ld.bin", (long)getpid());
|
||||
snprintf(cfile, sizeof cfile, "/tmp/meow_cg_%ld.state", (long)getpid());
|
||||
setenv("MEOW_CACHEGOV_C_FILE", cfile, 1);
|
||||
unlink(path); unlink(cfile);
|
||||
|
||||
const size_t size = 1024u * 1024u; /* 1MB 测试文件 */
|
||||
check("make_file", make_file(path, size) == 0);
|
||||
|
||||
/* 1) cfg:默认 + set/get 往返 */
|
||||
struct meow_cachegov_cfg c = {0};
|
||||
meow_cachegov_get_cfg(&c);
|
||||
check("cfg default enabled", c.enabled == 1);
|
||||
check("cfg default keep_hot=256KB", c.keep_hot_bytes == 256u * 1024u);
|
||||
struct meow_cachegov_cfg c2 = c;
|
||||
c2.keep_hot_bytes = 128u * 1024u;
|
||||
c2.drop_mode = 1;
|
||||
c2.refault_backoff = 2;
|
||||
check("set_cfg", meow_cachegov_set_cfg(&c2) == 0);
|
||||
meow_cachegov_get_cfg(&c);
|
||||
check("get_cfg roundtrip", c.keep_hot_bytes == 128u * 1024u && c.drop_mode == 1 && c.refault_backoff == 2);
|
||||
|
||||
/* 2) L1 drop:read_pos=512KB、keep_hot=128KB → drop [0, 384KB) off=0 len=384KB */
|
||||
check("register", meow_cachegov_register(path, size, fake_read_pos, NULL) == 0);
|
||||
g_pos = 512u * 1024u;
|
||||
meow_cachegov_tick();
|
||||
check("L1 drop off=0 len=384KB", log_has("L1 drop") && strstr(g_last_log, "off=0 len=393216"));
|
||||
check("L1 range bounded by read_pos-keep_hot (no L2)",
|
||||
!strstr(g_last_log, "off=393216") && strstr(g_last_log, "len=393216"));
|
||||
|
||||
/* 3) L1 推进:read_pos=800KB → drop [384KB, 800-128KB=672KB) off=384KB len=288KB */
|
||||
g_pos = 800u * 1024u;
|
||||
meow_cachegov_tick();
|
||||
check("L1 drop off=384KB len=288KB", log_has("L1 drop") && strstr(g_last_log, "off=393216") && strstr(g_last_log, "len=294912"));
|
||||
|
||||
/* 4) refault 回退档1(放大 keep_hot 翻倍):3 次触发 2 次 → 0→1(每升降一档需间隔 ≥10s */
|
||||
meow_cachegov_test_set_time_ms(0);
|
||||
meow_cachegov_note_refault(); meow_cachegov_note_refault();
|
||||
meow_cachegov_tick();
|
||||
check("backoff -> 1 (refault*2)", meow_cachegov_backoff_level() == 1);
|
||||
check("file intact after backoff1", verify_file(path, size));
|
||||
/* 档1 且 keep_hot=128KB*2=256KB:read_pos=1000KB → drop 上界=744KB(已丢到 672KB) */
|
||||
g_pos = 1000u * 1024u;
|
||||
meow_cachegov_tick();
|
||||
check("backoff1 L1 keep_hot enlarged (no L2)", meow_cachegov_backoff_level() == 1);
|
||||
|
||||
/* 5) 再 2 次(间隔 10s)→ 档2(L1 关,只 L0)→ download_paused=1 */
|
||||
meow_cachegov_test_set_time_ms(11000);
|
||||
meow_cachegov_note_refault(); meow_cachegov_note_refault();
|
||||
meow_cachegov_tick();
|
||||
check("backoff -> 2 (L1 off)", meow_cachegov_backoff_level() == 2);
|
||||
g_pos = 900u * 1024u;
|
||||
meow_cachegov_tick(); /* L1 off:应无新的 L1 drop 日志 */
|
||||
check("level2 no L1 drop (only L0)", !log_has("L1 drop"));
|
||||
check("download_paused @backoff>=2", meow_cachegov_download_paused() == 1);
|
||||
|
||||
/* 6) 再 2 次(间隔 10s)→ 档3(A 全关)→ prefetch_paused=1 + drop_finished 不再丢页 */
|
||||
meow_cachegov_test_set_time_ms(22000);
|
||||
meow_cachegov_note_refault(); meow_cachegov_note_refault();
|
||||
meow_cachegov_tick();
|
||||
check("backoff -> 3 (A off)", meow_cachegov_backoff_level() == 3);
|
||||
check("prefetch_paused @backoff>=3", meow_cachegov_prefetch_paused() == 1);
|
||||
meow_cachegov_test_set_time_ms(23000);
|
||||
meow_cachegov_drop_finished(path);
|
||||
check("drop_finished skip @A-off", log_has("drop_finished skip"));
|
||||
check("file intact after A-off", verify_file(path, size));
|
||||
|
||||
/* 7) C 侧降载:write fake C state dl_pause=1;滚窗干净逐级降回 0 后 download_paused 仍 =1 */
|
||||
{
|
||||
FILE *f = fopen(cfile, "w");
|
||||
check("write C state", f != NULL);
|
||||
if (f) { fputs("cached_kb=35000\ndirty_kb=1000\ndl_pause=1\nprefetch_pause=0\n", f); fclose(f); }
|
||||
}
|
||||
meow_cachegov_test_set_time_ms(33000);
|
||||
meow_cachegov_tick(); /* 3→2 */
|
||||
meow_cachegov_test_set_time_ms(44000);
|
||||
meow_cachegov_tick(); /* 2→1 */
|
||||
meow_cachegov_test_set_time_ms(55000);
|
||||
meow_cachegov_tick(); /* 1→0 */
|
||||
check("backoff de-escalated to 0", meow_cachegov_backoff_level() == 0);
|
||||
check("download_paused via C file", meow_cachegov_download_paused() == 1);
|
||||
|
||||
/* 8) L0 drop_finished(A 正常态):整文件 DONTNEED + 数据完整 + 注销条目 */
|
||||
unlink(cfile);
|
||||
meow_cachegov_register(path, size, fake_read_pos, NULL);
|
||||
meow_cachegov_test_set_time_ms(66000);
|
||||
meow_cachegov_drop_finished(path);
|
||||
check("drop_finished L0 log", log_has("drop_finished L0"));
|
||||
check("file intact after L0", verify_file(path, size));
|
||||
check("unregister removes entry", meow_cachegov_unregister(path) != 0); /* 已注销 → 找不到 */
|
||||
|
||||
/* 9) disable 时 drop/tick 均 no-op:数据仍完整 */
|
||||
c2.enabled = 0; meow_cachegov_set_cfg(&c2);
|
||||
meow_cachegov_register(path, size, fake_read_pos, NULL);
|
||||
g_pos = 512u * 1024u;
|
||||
meow_cachegov_tick();
|
||||
meow_cachegov_drop_finished(path);
|
||||
check("file intact when disabled", verify_file(path, size));
|
||||
|
||||
unlink(path); unlink(cfile);
|
||||
printf("----\n%d passed, %d failed\n", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,277 @@
|
||||
/* test_meow_ui_lazygrid.c — 宿主验证 §12.4 2 维虚拟化宫格(meow_lazygrid):
|
||||
* T1 虚拟化正确性:count/cols/max_scroll;首帧绑定可见行 item;scroll 下滚仅补新入视口的 item;
|
||||
* 回滚不重绑;全量扫过 = count(每 index 恰 bind 一次);命中把滚动+网格坐标映射回 idx 正确。
|
||||
* T2 默认 renderer:圆角卡片底 / 标题/副标题 / 选中高亮(primaryContainer)/ 禁用变淡。
|
||||
* T3 完全自绘 set_item_draw:每可见格调一次 + 自绘内容像素。
|
||||
* T4 空态占位(count=0 + empty_text 居中)。
|
||||
* T5 meow_scroll 集成(meow_lazygrid_scroll_attach:track/fling/snap 到行边界)。
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_lazygrid test_meow_ui_lazygrid.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_lazygrid.c meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_meow_ui_lazygrid
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp:真实像素断言 */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int g_pass = 0, g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { g_pass++; } \
|
||||
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
|
||||
} while (0)
|
||||
|
||||
/* ---- back_buf 像素读取(真实渲染断言) ---- */
|
||||
static void rd_px(int x, int y, int *r, int *g, int *b)
|
||||
{
|
||||
unsigned char *bb = meow_ui_back_buf();
|
||||
int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp();
|
||||
if (!bb || x < 0 || y < 0) { *r = *g = *b = 0; return; }
|
||||
if (bpp == 16) {
|
||||
uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2);
|
||||
*r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2;
|
||||
*g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4;
|
||||
*b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2);
|
||||
} else {
|
||||
uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4);
|
||||
*r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF;
|
||||
}
|
||||
}
|
||||
static int near2(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 14; }
|
||||
static int px_near(int x, int y, int r, int g, int b)
|
||||
{
|
||||
int rr, gg, bb; rd_px(x, y, &rr, &gg, &bb);
|
||||
return near2(rr, r) && near2(gg, g) && near2(bb, b);
|
||||
}
|
||||
static int region_has(int x, int y, int w, int h, int r, int g, int b)
|
||||
{
|
||||
int cnt = 0;
|
||||
for (int dy = 0; dy < h; dy++)
|
||||
for (int dx = 0; dx < w; dx++)
|
||||
if (px_near(x + dx, y + dy, r, g, b)) cnt++;
|
||||
return cnt;
|
||||
}
|
||||
#define UNPK(c, rr, gg, bb) do { (rr) = ((c) >> 16) & 0xFF; (gg) = ((c) >> 8) & 0xFF; (bb) = (c) & 0xFF; } while (0)
|
||||
|
||||
/* ---- 网格几何(测试统一) ---- */
|
||||
#define GX 20
|
||||
#define GY 50
|
||||
#define GW 440
|
||||
#define GH 200
|
||||
#define COLS 3
|
||||
#define CW 100
|
||||
#define CH 80
|
||||
#define GAP 10
|
||||
#define ROWH (CH + GAP) /* 90 */
|
||||
#define N 20 /* 默认总项数 */
|
||||
|
||||
static int g_count_override; /* <0 = 用默认 N;>=0 = 覆盖(空态/小列表) */
|
||||
static int g_bind_total; /* bind 回调计数(含 hit 的临时 bind) */
|
||||
static int g_draw_total; /* draw_cb(自绘)计数 */
|
||||
static int g_disabled_at; /* 该 index 返回 enabled=0(-1=无) */
|
||||
static int g_tap_cb_idx; /* tap_at 回调记录的选中项 */
|
||||
|
||||
static void lg_tap_cb(int idx, void *ctx) { (void)ctx; g_tap_cb_idx = idx; }
|
||||
|
||||
static int lg_count(void *ctx) { (void)ctx; return g_count_override >= 0 ? g_count_override : N; }
|
||||
static void lg_bind(int idx, meow_lazygrid_item *it, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
g_bind_total++;
|
||||
memset(it, 0, sizeof *it);
|
||||
static char t[32], s[32];
|
||||
snprintf(t, sizeof t, "cell %d", idx);
|
||||
snprintf(s, sizeof s, "st %d", idx);
|
||||
it->title = t; it->subtitle = s;
|
||||
it->enabled = 1;
|
||||
if (g_disabled_at == idx) it->enabled = 0;
|
||||
}
|
||||
|
||||
/* ---- T3 完全自绘:每格画实心块 + 角标记 ---- */
|
||||
static void mydraw(int idx, int x, int y, int w, int h, int st, void *ctx)
|
||||
{
|
||||
(void)idx; (void)ctx;
|
||||
g_draw_total++;
|
||||
meow_ui_fill_rect(x, y, w, h, 0x334455); /* 自定义格底 */
|
||||
meow_ui_fill_rect(x + 4, y + 4, 8, 8, 0xFF0000); /* 角标记 */
|
||||
if (st & MEOW_LAZYGRID_ST_SELECTED) meow_ui_fill_rect(x + w - 12, y + 4, 8, 8, 0x00FF00);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
meow_ui_opts o = { 480, 720 };
|
||||
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
|
||||
const meow_theme *th = meow_theme_get();
|
||||
int scl_r, scl_g, scl_b, on_r, on_g, on_b, pc_r, pc_g, pc_b, osv_r, osv_g, osv_b, opc_r, opc_g, opc_b;
|
||||
UNPK(th->surfaceContainerLow, scl_r, scl_g, scl_b);
|
||||
UNPK(th->onSurface, on_r, on_g, on_b);
|
||||
UNPK(th->primaryContainer, pc_r, pc_g, pc_b);
|
||||
UNPK(th->onSurfaceVariant, osv_r, osv_g, osv_b);
|
||||
UNPK(th->onPrimaryContainer, opc_r, opc_g, opc_b);
|
||||
g_count_override = -1; g_bind_total = 0; g_disabled_at = -1;
|
||||
|
||||
/* ============ T1 虚拟化正确性 ============ */
|
||||
{
|
||||
meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP);
|
||||
CHECK(g != NULL, "create");
|
||||
CHECK(meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL) == 0, "vsource");
|
||||
CHECK(meow_lazygrid_max_scroll(g) == 7 * CH + 6 * GAP - GH, "max_scroll %d (expect %d)",
|
||||
meow_lazygrid_max_scroll(g), 7 * CH + 6 * GAP - GH); /* 620-200=420 */
|
||||
CHECK(meow_lazygrid_scroll(g) == 0, "scroll0");
|
||||
meow_ui_clear(th->bg);
|
||||
meow_lazygrid_draw(g, th);
|
||||
|
||||
int B0 = g_bind_total;
|
||||
int vis_rows = GH / ROWH + 1; /* 3 */
|
||||
int expect0 = vis_rows * COLS; /* 9(row0..2 × 3 格) */
|
||||
CHECK(B0 == expect0, "first draw binds %d got %d", expect0, B0);
|
||||
|
||||
/* 下滚 1 行(90px):新入视口 = 末 1 行 3 格 → bind +3(其余同槽不重绑) */
|
||||
meow_lazygrid_set_scroll(g, ROWH);
|
||||
meow_lazygrid_draw(g, th);
|
||||
int B1 = g_bind_total;
|
||||
CHECK(B1 == B0 + COLS, "scroll+1row binds +%d got %d (was %d)", COLS, B1 - B0, B0);
|
||||
|
||||
/* 回滚到顶:槽仍在 → 不重绑(此段不调 hit,避免临时 bind 干扰计数) */
|
||||
meow_lazygrid_set_scroll(g, 0);
|
||||
meow_lazygrid_draw(g, th);
|
||||
CHECK(g_bind_total == B1, "scroll-back no rebind got %d (was %d)", g_bind_total, B1);
|
||||
|
||||
/* 全量扫过:每个 index 进入视口时 bind 恰一次 → 总 == N(有界,非 per-frame × count) */
|
||||
int prev = 0;
|
||||
while (prev <= N) {
|
||||
int off = prev * ROWH;
|
||||
meow_lazygrid_set_scroll(g, off);
|
||||
meow_lazygrid_draw(g, th);
|
||||
if (off >= meow_lazygrid_max_scroll(g)) break;
|
||||
prev++;
|
||||
}
|
||||
CHECK(g_bind_total == N, "full sweep binds each index once got %d (expect %d)", g_bind_total, N);
|
||||
|
||||
/* 命中:索引 = 滚动 + 网格坐标映射(此后的 hit 走临时 bind,不影响上面的总数断言) */
|
||||
meow_lazygrid_set_scroll(g, 0);
|
||||
CHECK(meow_lazygrid_hit(g, GX + 10, GY + 10) == 0, "hit cell0 got %d", meow_lazygrid_hit(g, GX + 10, GY + 10));
|
||||
meow_lazygrid_set_scroll(g, ROWH);
|
||||
/* scroll=90:GY+10(视口顶)→ 内容 y=100 → row1([90,170])col0 → idx=3 */
|
||||
CHECK(meow_lazygrid_hit(g, GX + 10, GY + 10) == COLS,
|
||||
"hit maps scroll+grid got %d (expect 3)", meow_lazygrid_hit(g, GX + 10, GY + 10));
|
||||
/* gap 区不命中 */
|
||||
CHECK(meow_lazygrid_hit(g, GX + CW + GAP / 2, GY + 10) == -1, "horizontal gap miss got %d",
|
||||
meow_lazygrid_hit(g, GX + CW + GAP / 2, GY + 10));
|
||||
/* 越界列(row0 左侧无格/第 4 列)不命中 */
|
||||
meow_lazygrid_set_scroll(g, 0);
|
||||
CHECK(meow_lazygrid_hit(g, GX + COLS * (CW + GAP) + 10, GY + 10) == -1, "col overflow miss got %d",
|
||||
meow_lazygrid_hit(g, GX + COLS * (CW + GAP) + 10, GY + 10));
|
||||
CHECK(meow_lazygrid_hit(g, GX + 10, GY + ROWH) == COLS, "hit row1 col0 idx=3 got %d",
|
||||
meow_lazygrid_hit(g, GX + 10, GY + ROWH));
|
||||
|
||||
/* tap_at:命中 → 置选中 + 回调 */
|
||||
g_tap_cb_idx = -1;
|
||||
meow_lazygrid_set_on_tap(g, lg_tap_cb, NULL);
|
||||
int tap_idx = meow_lazygrid_tap_at(g, GX + 50, GY + 40);
|
||||
CHECK(tap_idx == 0, "tap_at idx0 got %d", tap_idx);
|
||||
CHECK(meow_lazygrid_sel(g) == 0, "sel set to 0");
|
||||
CHECK(g_tap_cb_idx == 0, "tap cb idx0 got %d", g_tap_cb_idx);
|
||||
/* 点 row1,col1(idx=4) → 该格命中 */
|
||||
int idx4 = meow_lazygrid_hit(g, GX + 1 * (CW + GAP) + 10, GY + ROWH + 10);
|
||||
CHECK(idx4 == 4, "hit row1col1 idx=%d (expect 4)", idx4);
|
||||
|
||||
meow_lazygrid_free(g);
|
||||
}
|
||||
|
||||
/* ============ T2 默认 renderer:卡片底 / 标题 / 选中高亮 / 禁用 ============ */
|
||||
{
|
||||
meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP);
|
||||
meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL);
|
||||
g_count_override = 3; g_disabled_at = 2; /* 3 格:cell2 禁用 */
|
||||
meow_ui_clear(th->bg);
|
||||
meow_lazygrid_set_sel(g, 0);
|
||||
meow_lazygrid_draw(g, th);
|
||||
/* 选中 cell0 → primaryContainer 底([GX,GY,100,80]) */
|
||||
CHECK(region_has(GX + 4, GY + 4, 90, 60, pc_r, pc_g, pc_b) > 40, "selected cell primaryContainer bg");
|
||||
/* 未选中 cell1 → surfaceContainerLow 底 */
|
||||
int x1 = GX + 1 * (CW + GAP);
|
||||
CHECK(region_has(x1 + 4, GY + 4, 90, 60, scl_r, scl_g, scl_b) > 40, "cell1 surfaceContainerLow bg");
|
||||
/* 标题画在 cell0 底部(选中 → onPrimaryContainer;副标题在下方 onPrimaryContainer 同色带) */
|
||||
int thh = meow_ui_text_height(th->font_body);
|
||||
int title_y = GY + CH - 8 - thh - (thh + 2); /* = lg_draw_cell 的 ty */
|
||||
int c0cx = GX + CW / 2;
|
||||
CHECK(region_has(c0cx - 30, title_y, 60, thh, opc_r, opc_g, opc_b) > 2, "cell0 title drawn (onPrimaryContainer)");
|
||||
/* 禁用 cell2 变淡:左上角(图标/文本区之外)不再是纯 surfaceContainerLow(被 onSurface α 叠暗) */
|
||||
int x2 = GX + 2 * (CW + GAP);
|
||||
CHECK(!px_near(x2 + 4, GY + 4, scl_r, scl_g, scl_b), "disabled cell overlaid (bg dimmed)");
|
||||
meow_lazygrid_free(g);
|
||||
}
|
||||
|
||||
/* ============ T3 完全自绘(set_item_draw) ============ */
|
||||
{
|
||||
meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP);
|
||||
meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL);
|
||||
g_count_override = 3; g_disabled_at = -1; g_draw_total = 0;
|
||||
CHECK(meow_lazygrid_set_item_draw(g, mydraw, NULL) == 0, "set_item_draw");
|
||||
meow_ui_clear(th->bg);
|
||||
meow_lazygrid_draw(g, th);
|
||||
CHECK(g_draw_total == 3, "draw_cb per visible cell got %d (expect 3)", g_draw_total);
|
||||
CHECK(region_has(GX + 4, GY + 4, 8, 8, 0xFF, 0x00, 0x00) > 0, "custom cell content drawn");
|
||||
meow_lazygrid_free(g);
|
||||
}
|
||||
|
||||
/* ============ T4 空态 ============ */
|
||||
{
|
||||
meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP);
|
||||
meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL);
|
||||
g_count_override = 0;
|
||||
meow_lazygrid_set_empty_text(g, "No data");
|
||||
const meow_theme *t2 = meow_theme_get();
|
||||
meow_ui_clear(th->bg);
|
||||
meow_lazygrid_draw(g, t2);
|
||||
int thh = meow_ui_text_height(t2->font_body);
|
||||
int ety = GY + (GH - thh) / 2;
|
||||
CHECK(region_has(GX + GW / 2 - 60, ety, 120, thh, osv_r, osv_g, osv_b) > 2, "empty state text drawn");
|
||||
meow_lazygrid_free(g);
|
||||
}
|
||||
|
||||
/* ============ T5 meow_scroll 集成(fling/snap) ============ */
|
||||
{
|
||||
g_count_override = -1; g_disabled_at = -1;
|
||||
meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP);
|
||||
meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL);
|
||||
meow_scroll *s = meow_lazygrid_scroll_attach(g);
|
||||
CHECK(s != NULL, "scroll attach");
|
||||
meow_scroll_set_snap(s, ROWH);
|
||||
uint64_t t = meow_ui_time_ms();
|
||||
for (int i = 0; i < 12; i++) meow_scroll_track(s, 30, t + (uint64_t)i * 16);
|
||||
int start = meow_scroll_offset(s);
|
||||
CHECK(start > 0, "drag advances offset got %d", start);
|
||||
meow_scroll_fling(s);
|
||||
CHECK(meow_scroll_active(s) == 1, "fling active");
|
||||
int max_off = start, guard = 0;
|
||||
while (meow_scroll_active(s) && guard++ < 300) {
|
||||
meow_anim_tick();
|
||||
int cur = meow_scroll_offset(s);
|
||||
if (max_off < cur) max_off = cur;
|
||||
usleep(10000);
|
||||
}
|
||||
meow_anim_tick();
|
||||
int end = meow_scroll_offset(s);
|
||||
CHECK(end >= start, "fling advances %d vs %d", end, start);
|
||||
/* snap 到行边界;若撞底(max_scroll=420 非 row_h 的整数倍)则停在边界(clamp 优先) */
|
||||
CHECK((end % ROWH) == 0 || end == meow_lazygrid_max_scroll(g),
|
||||
"fling ends snapped-or-boundary got %d (%%%d, max %d)", end, end % ROWH, meow_lazygrid_max_scroll(g));
|
||||
CHECK(end <= meow_lazygrid_max_scroll(g), "fling in range %d (max %d)", end, meow_lazygrid_max_scroll(g));
|
||||
CHECK(meow_lazygrid_scroll(g) == end, "grid scroll mirrors offset");
|
||||
meow_scroll_free(s);
|
||||
meow_lazygrid_free(g);
|
||||
}
|
||||
|
||||
meow_ui_shutdown();
|
||||
printf("lazygrid: %d pass / %d fail\n", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,178 @@
|
||||
/* test_meow_ui_select.c — 宿主验证枚举/预设选择器(meow_select):
|
||||
* T1 打开→点选项→单选高亮(primaryContainer)+ 选中 + 关闭即回调(on_change(idx))+ 返回值。
|
||||
* T2 取消:点面板外(遮罩)→ 返回 -1,不调 on_change。
|
||||
* T3 meow_select_show 一次性:返回选中下标。
|
||||
*
|
||||
* 阻塞式模态用后台线程驱动(同 test_ime_fix 范式),主线程经 meow_input_inject 喂触摸。
|
||||
* 编译(宿主 gcc):
|
||||
* gcc -o test_meow_ui_select test_meow_ui_select.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_select.c meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_meow_ui_select
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h" /* meow_input_inject + back_buf 读 */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
|
||||
static int g_pass = 0, g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { g_pass++; } \
|
||||
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
|
||||
} while (0)
|
||||
|
||||
static void rd_px(int x, int y, int *r, int *g, int *b)
|
||||
{
|
||||
unsigned char *bb = meow_ui_back_buf();
|
||||
int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp();
|
||||
if (!bb || x < 0 || y < 0) { *r = *g = *b = 0; return; }
|
||||
if (bpp == 16) {
|
||||
uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2);
|
||||
*r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2;
|
||||
*g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4;
|
||||
*b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2);
|
||||
} else {
|
||||
uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4);
|
||||
*r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF;
|
||||
}
|
||||
}
|
||||
static int near2(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 14; }
|
||||
#define UNPK(c, rr, gg, bb) do { (rr) = ((c) >> 16) & 0xFF; (gg) = ((c) >> 8) & 0xFF; (bb) = (c) & 0xFF; } while (0)
|
||||
|
||||
/* ---- 驱动:后台线程跑阻塞 meow_select_open ---- */
|
||||
typedef struct {
|
||||
meow_select *s;
|
||||
int ret;
|
||||
} sel_arg;
|
||||
static void *sel_driver(void *p)
|
||||
{
|
||||
sel_arg *a = (sel_arg *)p;
|
||||
a->ret = meow_select_open(a->s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ---- on_change 回调记录 ---- */
|
||||
static int g_cb = -1;
|
||||
static void on_change(int idx, void *ctx) { (void)ctx; g_cb = idx; }
|
||||
|
||||
/* 计算面板/选项列表几何(与 meow_select_open 同式;用于注入命中坐标 + 像素采样) */
|
||||
static int sel_list_y(int nitems)
|
||||
{
|
||||
const meow_theme *th = meow_theme_get();
|
||||
int W = meow_ui_width(), H = meow_ui_height();
|
||||
int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0;
|
||||
int pad = 24, rowh = 48;
|
||||
int title_h = meow_ui_text_height(th->font_title);
|
||||
int avail = H - sb - 20;
|
||||
int min_h = pad + title_h + 12 + rowh + 20;
|
||||
int want = min_h;
|
||||
int est = pad + title_h + 12 + nitems * rowh + 20;
|
||||
if (est > want) want = est;
|
||||
if (want > avail) want = avail;
|
||||
if (want < min_h) want = (min_h < avail) ? min_h : avail;
|
||||
int py = H - want;
|
||||
return py + pad + title_h + 12;
|
||||
}
|
||||
static int sel_sheet_top(int nitems)
|
||||
{
|
||||
const meow_theme *th = meow_theme_get();
|
||||
int H = meow_ui_height();
|
||||
int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0;
|
||||
int pad = 24, rowh = 48;
|
||||
int title_h = meow_ui_text_height(th->font_title);
|
||||
int avail = H - sb - 20;
|
||||
int min_h = pad + title_h + 12 + rowh + 20;
|
||||
int want = min_h;
|
||||
int est = pad + title_h + 12 + nitems * rowh + 20;
|
||||
if (est > want) want = est;
|
||||
if (want > avail) want = avail;
|
||||
if (want < min_h) want = (min_h < avail) ? min_h : avail;
|
||||
return H - want;
|
||||
}
|
||||
#define SEL_ROW 48
|
||||
|
||||
/* ============ T1 选中 + 回调 + 单选高亮 ============ */
|
||||
static void test_select_highlight(void)
|
||||
{
|
||||
meow_select *s = meow_select_create("Choose", 2);
|
||||
meow_select_add_item(s, "Alpha");
|
||||
meow_select_add_item(s, "Beta");
|
||||
meow_select_add_item(s, "Gamma");
|
||||
meow_select_set_on_change(s, on_change, NULL);
|
||||
int list_y = sel_list_y(3);
|
||||
sel_arg a = { s, -999 };
|
||||
g_cb = -1;
|
||||
pthread_t th; pthread_create(&th, NULL, sel_driver, &a);
|
||||
/* 等 open 的 drain + 首帧绘制完成(高亮已画进 back_buf) */
|
||||
usleep(150000);
|
||||
/* 单选高亮:cur=2(第 3 行)选中背景 = primaryContainer */
|
||||
const meow_theme *thm = meow_theme_get();
|
||||
int pc_r, pc_g, pc_b; UNPK(thm->primaryContainer, pc_r, pc_g, pc_b);
|
||||
int hx = 6, hy = list_y + 2 * SEL_ROW + SEL_ROW / 2;
|
||||
int r, g, b; rd_px(hx, hy, &r, &g, &b);
|
||||
CHECK(near2(r, pc_r) && near2(g, pc_g) && near2(b, pc_b),
|
||||
"T1 highlight row2 primaryContainer got %02X%02X%02X", r, g, b);
|
||||
/* 点第 1 行(idx 0)→ 选中 + 关闭 + 回调 */
|
||||
meow_input_inject(MEOW_EV_TAP, 100, list_y + 0 * SEL_ROW + SEL_ROW / 2);
|
||||
pthread_join(th, NULL);
|
||||
CHECK(a.ret == 0, "T1 open return idx0 got %d", a.ret);
|
||||
CHECK(g_cb == 0, "T1 on_change(0) got %d", g_cb);
|
||||
meow_select_free(s);
|
||||
}
|
||||
|
||||
/* ============ T2 取消(点面板外) ============ */
|
||||
static void test_select_cancel(void)
|
||||
{
|
||||
meow_select *s = meow_select_create("Choose", 0);
|
||||
meow_select_add_item(s, "Alpha");
|
||||
meow_select_add_item(s, "Beta");
|
||||
meow_select_set_on_change(s, on_change, NULL);
|
||||
sel_arg a = { s, -999 };
|
||||
g_cb = -1;
|
||||
pthread_t th; pthread_create(&th, NULL, sel_driver, &a);
|
||||
usleep(150000);
|
||||
int top = sel_sheet_top(2);
|
||||
meow_input_inject(MEOW_EV_TAP, 240, (top > 30) ? 10 : 10); /* 面板外(遮罩) */
|
||||
pthread_join(th, NULL);
|
||||
CHECK(a.ret == -1, "T2 cancel returns -1 got %d", a.ret);
|
||||
CHECK(g_cb == -1, "T2 on_change NOT fired on cancel got %d", g_cb);
|
||||
meow_select_free(s);
|
||||
}
|
||||
|
||||
/* ============ T3 meow_select_show 一次性 ============ */
|
||||
static void *show_driver(void *p)
|
||||
{
|
||||
(void)p;
|
||||
static const char *opts[] = { "A", "B", "C", "D" };
|
||||
static int r = -999;
|
||||
r = meow_select_show("Preset", opts, 4, 1);
|
||||
return (void *)(long)r;
|
||||
}
|
||||
static void test_select_show(void)
|
||||
{
|
||||
g_cb = -1;
|
||||
pthread_t th; pthread_create(&th, NULL, show_driver, NULL);
|
||||
usleep(150000);
|
||||
int list_y = sel_list_y(4);
|
||||
meow_input_inject(MEOW_EV_TAP, 100, list_y + 2 * SEL_ROW + SEL_ROW / 2); /* 选第 3 项 idx2 */
|
||||
long r = 0; pthread_join(th, (void **)&r);
|
||||
CHECK(r == 2, "T3 show returns idx2 got %ld", r);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
meow_ui_opts o = { 480, 720 };
|
||||
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
|
||||
test_select_highlight();
|
||||
test_select_cancel();
|
||||
test_select_show();
|
||||
meow_ui_shutdown();
|
||||
printf("select: %d pass / %d fail\n", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,210 @@
|
||||
/* test_meow_ui_swipe.c — 宿主验证列表行左滑删除(§5.5,M7.3 缺口 #9):
|
||||
* T1 滑动手势判定与冲突消解:begin 命中可滑行返回 1;横向小位移 = swipe(move 返回 1 消费);
|
||||
* 纵向大位移 = 滚动手势优先(move 返回 0 并取消本行 swipe);非启用行 begin 返回 0。
|
||||
* T2 左滑超阈值 → 删除动画(meow_anim 滑出),结束后 on_delete 回调恰一次 + swipe 状态复位。
|
||||
* T3 未超阈值 → 回弹,on_delete 不触发,swipe_dx 回到 0。
|
||||
* T4 点按(无位移):begin 后 end(未 started)返回 0(App 走 tap_at),不触发 on_delete。
|
||||
* T5 右露删除区像素:滑动中重绘列表 → 右露区画主题 error 底 + 行内容左移。
|
||||
*
|
||||
* 编译(宿主 gcc):
|
||||
* gcc -o test_meow_ui_swipe test_meow_ui_swipe.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_meow_ui_swipe
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int g_pass = 0, g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { g_pass++; } \
|
||||
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
|
||||
} while (0)
|
||||
|
||||
static void rd_px(int x, int y, int *r, int *g, int *b)
|
||||
{
|
||||
unsigned char *bb = meow_ui_back_buf();
|
||||
int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp();
|
||||
if (!bb || x < 0 || y < 0) { *r = *g = *b = 0; return; }
|
||||
if (bpp == 16) {
|
||||
uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2);
|
||||
*r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2;
|
||||
*g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4;
|
||||
*b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2);
|
||||
} else {
|
||||
uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4);
|
||||
*r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF;
|
||||
}
|
||||
}
|
||||
static int near2(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 14; }
|
||||
static int px_near(int x, int y, int r, int g, int b)
|
||||
{
|
||||
int rr, gg, bb; rd_px(x, y, &rr, &gg, &bb);
|
||||
return near2(rr, r) && near2(gg, g) && near2(bb, b);
|
||||
}
|
||||
static int region_has(int x, int y, int w, int h, int r, int g, int b)
|
||||
{
|
||||
int cnt = 0;
|
||||
for (int dy = 0; dy < h; dy++)
|
||||
for (int dx = 0; dx < w; dx++)
|
||||
if (px_near(x + dx, y + dy, r, g, b)) cnt++;
|
||||
return cnt;
|
||||
}
|
||||
#define UNPK(c, rr, gg, bb) do { (rr) = ((c) >> 16) & 0xFF; (gg) = ((c) >> 8) & 0xFF; (bb) = (c) & 0xFF; } while (0)
|
||||
|
||||
#define LVX 20
|
||||
#define LVY 50
|
||||
#define LVW 440
|
||||
#define LVH 300
|
||||
#define ITEM 56
|
||||
#define CNT 10
|
||||
|
||||
static int del_count; /* on_delete 回调次数 */
|
||||
static int del_idx; /* 最后一次删除 idx */
|
||||
static void on_delete(int idx, void *ctx) { (void)ctx; del_count++; del_idx = idx; }
|
||||
|
||||
static int scount(void *ctx) { (void)ctx; return CNT; }
|
||||
static void sbind(int idx, meow_lv_item *it, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
memset(it, 0, sizeof *it);
|
||||
static char t[32];
|
||||
snprintf(t, sizeof t, "notif %d", idx);
|
||||
it->title = t; it->icon_kind = MEOW_LIST_ICON_NONE; it->right = MEOW_LIST_RIGHT_NONE;
|
||||
it->enabled = 1;
|
||||
}
|
||||
|
||||
static meow_widget *make_list(void)
|
||||
{
|
||||
meow_widget *lv = meow_listview_create(LVX, LVY, LVW, LVH, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
meow_listview_set_item_h(lv, ITEM);
|
||||
meow_listview_set_vsource(lv, scount, sbind, NULL);
|
||||
meow_listview_set_item_renderer(lv, 2, 0, 0); /* 双行、无图标、无尾件 */
|
||||
meow_listview_set_swipe_delete(lv, 1, on_delete, NULL);
|
||||
return lv;
|
||||
}
|
||||
|
||||
/* 推进 meow_anim 直到 swipe 动画结束(模拟 on_tick 节拍)。 */
|
||||
static void pump_swipe(meow_widget *lv)
|
||||
{
|
||||
int guard = 0;
|
||||
while (meow_listview_swipe_active(lv) && guard++ < 80) {
|
||||
meow_anim_tick();
|
||||
usleep(10000);
|
||||
}
|
||||
meow_anim_tick();
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
meow_ui_opts o = { 480, 720 };
|
||||
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
|
||||
const meow_theme *th = meow_theme_get();
|
||||
int err_r, err_g, err_b; UNPK(th->error, err_r, err_g, err_b);
|
||||
|
||||
int row0y = LVY + ITEM / 2; /* 行 0 中心 y(scroll=0) */
|
||||
|
||||
/* ============ T1 手势判定 / 冲突消解 ============ */
|
||||
{
|
||||
meow_widget *lv = make_list();
|
||||
/* begin 命中可滑行行 → 1 */
|
||||
CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "T1 begin on swipe row = 1");
|
||||
/* 横向小位移 → 裁定 swipe,move 返回 1(消费) */
|
||||
CHECK(meow_listview_swipe_move(lv, 40, row0y) == 1, "T1 move horizontal = 1 (consumed)");
|
||||
int dx = meow_listview_swipe_offset(lv);
|
||||
CHECK(dx == -60, "T1 dx=-60 got %d", dx);
|
||||
/* 又一小位移(不超容差也不改变方向) */
|
||||
CHECK(meow_listview_swipe_move(lv, 30, row0y) == 1, "T1 move horizontal 2 = 1");
|
||||
CHECK(meow_listview_swipe_end(lv, 30, row0y) == 1, "T1 end started swipe = 1 (bounce)");
|
||||
pump_swipe(lv);
|
||||
CHECK(meow_listview_swipe_offset(lv) == 0, "T1 bounce back dx=0 got %d", meow_listview_swipe_offset(lv));
|
||||
CHECK(del_count == 0, "T1 no delete on bounce");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
{
|
||||
/* 纵向大位移 → 滚动手势优先(move 返回 0 + 取消) */
|
||||
meow_widget *lv = make_list();
|
||||
CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "T1 begin2 = 1");
|
||||
CHECK(meow_listview_swipe_move(lv, 100, row0y + 80) == 0, "T1 vertical move = 0 (yield to scroll)");
|
||||
CHECK(meow_listview_swipe_active(lv) == 0, "T1 vertical cancels swipe");
|
||||
CHECK(meow_listview_swipe_offset(lv) == 0, "T1 vertical dx=0");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
{
|
||||
/* 非启用行 begin → 0 */
|
||||
meow_widget *lv = make_list();
|
||||
meow_listview_set_swipe_delete(lv, 0, NULL, NULL);
|
||||
CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 0, "T1 disabled begin = 0");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ============ T2 超阈值 → 删除动画 + on_delete ============ */
|
||||
{
|
||||
del_count = 0; del_idx = -1;
|
||||
meow_widget *lv = make_list();
|
||||
CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "T2 begin = 1");
|
||||
/* 左滑 200px(阈值=LVW*2/5=176)→ 超阈值 */
|
||||
CHECK(meow_listview_swipe_move(lv, 100 - 200, row0y) == 1, "T2 move big left = 1");
|
||||
CHECK(meow_listview_swipe_end(lv, 100 - 200, row0y) == 1, "T2 end = 1 (delete anim)");
|
||||
CHECK(meow_listview_swipe_active(lv) == 1, "T2 delete anim active");
|
||||
pump_swipe(lv);
|
||||
CHECK(del_count == 1, "T2 on_delete fired once got %d", del_count);
|
||||
CHECK(del_idx == 0, "T2 on_delete idx0 got %d", del_idx);
|
||||
CHECK(meow_listview_swipe_active(lv) == 0, "T2 state reset inactive");
|
||||
CHECK(meow_listview_swipe_offset(lv) == 0, "T2 dx reset 0 got %d", meow_listview_swipe_offset(lv));
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ============ T3 未超阈值 → 回弹,on_delete 不触发 ============ */
|
||||
{
|
||||
del_count = 0;
|
||||
meow_widget *lv = make_list();
|
||||
CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "T3 begin = 1");
|
||||
CHECK(meow_listview_swipe_move(lv, 55, row0y) == 1, "T3 move left 45px = 1");
|
||||
CHECK(meow_listview_swipe_offset(lv) == -45, "T3 dx=-45 got %d", meow_listview_swipe_offset(lv));
|
||||
CHECK(meow_listview_swipe_end(lv, 55, row0y) == 1, "T3 end = 1 (bounce anim)");
|
||||
CHECK(meow_listview_swipe_active(lv) == 1, "T3 bounce active");
|
||||
pump_swipe(lv);
|
||||
CHECK(del_count == 0, "T3 on_delete NOT fired got %d", del_count);
|
||||
CHECK(meow_listview_swipe_offset(lv) == 0, "T3 bounce dx=0 got %d", meow_listview_swipe_offset(lv));
|
||||
CHECK(meow_listview_swipe_active(lv) == 0, "T3 inactive after bounce");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ============ T4 点按(无位移)= 行点击,end 返回 0 ============ */
|
||||
{
|
||||
del_count = 0;
|
||||
meow_widget *lv = make_list();
|
||||
CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "T4 begin = 1");
|
||||
CHECK(meow_listview_swipe_end(lv, 100, row0y) == 0, "T4 tap end = 0 (let App tap_at)");
|
||||
CHECK(del_count == 0, "T4 no delete on tap");
|
||||
CHECK(meow_listview_swipe_active(lv) == 0, "T4 inactive after tap");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ============ T5 右露删除区像素(默认 renderer) ============ */
|
||||
{
|
||||
meow_widget *lv = make_list();
|
||||
CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "T5 begin = 1");
|
||||
CHECK(meow_listview_swipe_move(lv, 100 - 200, row0y) == 1, "T5 move big left = 1");
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv);
|
||||
/* 右露区 [LVX+LVW+dx, row0, -dx, ITEM] = [260,50,200,56] → error 底 */
|
||||
CHECK(region_has(270, row0y - ITEM / 2 + 4, 180, ITEM - 8, err_r, err_g, err_b) > 50,
|
||||
"T5 delete reveal error bg got %d", region_has(270, row0y - ITEM / 2 + 4, 180, ITEM - 8, err_r, err_g, err_b));
|
||||
meow_listview_swipe_end(lv, 100 - 200, row0y);
|
||||
pump_swipe(lv);
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
meow_ui_shutdown();
|
||||
printf("swipe: %d pass / %d fail\n", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user