Compare commits
3
Commits
0af6af7c7e
...
a1687486bc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1687486bc | ||
|
|
a70cdbc0f7 | ||
|
|
64cde13c8d |
No files matched your search
@@ -22,7 +22,7 @@ MeowOS 应用框架 SDK 的**公开仓库**——面向第三方开发者,让
|
|||||||
MeowOS-SDK/
|
MeowOS-SDK/
|
||||||
├── sdk/ # meow_ui 应用框架静态库源码(自包含,可交叉 → sdk/meow_ui.a)
|
├── sdk/ # meow_ui 应用框架静态库源码(自包含,可交叉 → sdk/meow_ui.a)
|
||||||
│ ├── meow_ui.h # 伞头(#include 聚合 include/ 子头;App 只 include 这个)
|
│ ├── meow_ui.h # 伞头(#include 聚合 include/ 子头;App 只 include 这个)
|
||||||
│ ├── include/ # 20 个模块子头(含 box/anim/scroll/str 等)+ meow_ipc.h / meow_font.h / meow_notif.h(vendored)
|
│ ├── include/ # 24 个模块子头(含 box/anim/scroll/str/lazygrid/select/cachegov/net 通道等)+ meow_ipc.h / meow_font.h / meow_notif.h(vendored)
|
||||||
│ ├── meow_font.c # 预渲染位图字体运行时(meow_font.a 源码,自包含)
|
│ ├── meow_font.c # 预渲染位图字体运行时(meow_font.a 源码,自包含)
|
||||||
│ ├── build_sdk.sh # 交叉编译 meow_ui.a(mipsel 静态库,内含 meow_font.o)
|
│ ├── build_sdk.sh # 交叉编译 meow_ui.a(mipsel 静态库,内含 meow_font.o)
|
||||||
│ └── test_*.c # SDK 宿主逻辑测试(test_host_stub.c 打桩,无硬件可跑)
|
│ └── test_*.c # SDK 宿主逻辑测试(test_host_stub.c 打桩,无硬件可跑)
|
||||||
@@ -56,10 +56,18 @@ cd ../examples/hello
|
|||||||
- **动画基座 `meow_anim` / `meow_anim_tick`**(§12.2):声明式 tween(linear/ease-out/ease-in-out/overshoot),静态池 ≤16,无动画零开销;只 flush 动画影响区。
|
- **动画基座 `meow_anim` / `meow_anim_tick`**(§12.2):声明式 tween(linear/ease-out/ease-in-out/overshoot),静态池 ≤16,无动画零开销;只 flush 动画影响区。
|
||||||
- **滚动物理 `meow_scroll`**(§12.3):速度采样 + 松手 fling + 边界 clamp/简单回弹 + snap 吸附;给 `meow_list`/`W_LIST`/`W_LISTVIEW` 加惯性滚动。
|
- **滚动物理 `meow_scroll`**(§12.3):速度采样 + 松手 fling + 边界 clamp/简单回弹 + snap 吸附;给 `meow_list`/`W_LIST`/`W_LISTVIEW` 加惯性滚动。
|
||||||
- **事件分发形式化**(§6.4):单一仲裁者——z 序自顶向下命中、命中即消费、down 捕获整个手势、模态层隔离、唯一焦点(点外释放/滚动容器转移)。
|
- **事件分发形式化**(§6.4):单一仲裁者——z 序自顶向下命中、命中即消费、down 捕获整个手势、模态层隔离、唯一焦点(点外释放/滚动容器转移)。
|
||||||
|
- **视图事件路由**(§A.7,M7.0a):`meow_view_dispatch_event` 接入 `meow_ui_run`——TAP/KEY/SWIPE/LONG_PRESS 先走视图树 `on_event` 路由,命中即消费(不再落 App `on_*`);无视图树/未命中回落 App 原生 `on_*` 回调(完全向后兼容)。页内子导航按设计定案 b 标注**废弃**(不实现),页内「子页」用 `meow_view_add_child` 挂 CONTAINER 子视图(组合嵌套)。
|
||||||
- **ListView 虚拟化**(§5.5):固定 item 池(视口+2)、多行富 item、段头、空态、完全自绘钩子;App 只提供「总数 + bind」回调,几千/上万行不卡。
|
- **ListView 虚拟化**(§5.5):固定 item 池(视口+2)、多行富 item、段头、空态、完全自绘钩子;App 只提供「总数 + bind」回调,几千/上万行不卡。
|
||||||
|
- **ListView 左滑删除**(§5.5):列表行横向手势 `meow_listview_swipe_begin/move/end` 三级喂入 + 删除/回弹动画;`meow_listview_set_swipe_delete` 绑删除回调,行自绘 `set_item_draw` 配合画右露删除区(主题 `error/onError`)。
|
||||||
|
- **圆形图标按钮 `meow_iconbtn_create`**(M7.0a):圆形命中 + 位图图标(`sb_icons[]`/`MEOW_ICON_*`)+ 实时按压/按住态;供电源菜单/播放控制/锁屏件用。
|
||||||
|
- **主题色板导出/注入**(§8.1.5,M7.0a):`meow_theme_export`/`meow_theme_inject`——「单一权威桥」,导出当前 `style×mode` 解析生效的权威主题(9 角色字段),或注入一份自定义板覆写当前生效主题(`NULL` 恢复内建,覆写随 `meow_theme_reload()` 保留);供 stateless 绘制原语读单一权威、或 launcher/App 自定义背景/强调/风格。
|
||||||
- **字符串资源 / i18n**(§13):`meow_str`/`meow_strf`/`meow_str_set_locale`——内嵌 `zh_cn`/`en_us` 双语表 + TF 卡覆盖表;UI 文案零硬编码,按 locale 取整串。
|
- **字符串资源 / i18n**(§13):`meow_str`/`meow_strf`/`meow_str_set_locale`——内嵌 `zh_cn`/`en_us` 双语表 + TF 卡覆盖表;UI 文案零硬编码,按 locale 取整串。
|
||||||
- **图标库 `sb_icons`**(§5.8):30 个 MD3 Material Symbols 位图动作图标(`MEOW_ICON_*`),`meow_topappbar_set_item_icon` 把 TopAppBar 动作项换成位图图标(修复 ⚙↻⌕ 豆腐)。
|
- **图标库 `sb_icons`**(§5.8):30 个 MD3 Material Symbols 位图动作图标(`MEOW_ICON_*`),`meow_topappbar_set_item_icon` 把 TopAppBar 动作项换成位图图标(修复 ⚙↻⌕ 豆腐)。
|
||||||
- **图片降采样 `meow_img_downscale`**(`include/meow_ui_image.h`):把解码图降到 ≤maxdim 边长省内存(封面/大图用,如 8.28MB→0.78MB)。
|
- **图片降采样 `meow_img_downscale`**(`include/meow_ui_image.h`):把解码图降到 ≤maxdim 边长省内存(封面/大图用,如 8.28MB→0.78MB)。
|
||||||
|
- **2 维虚拟化宫格 `meow_lazygrid`**(§12.4):卡片/图标宫格二维虚拟化——只实例化可见行(固定 item 池 ≤96、视口行数+2×cols),`count+bind` 数据源、长列表不卡;命中 `meow_lazygrid_hit/tap_at` 把滚动+网格坐标映射回 item idx;滚动复用 `meow_scroll`(fling/snap)。
|
||||||
|
- **枚举/预设选择器 `meow_select`**(§5.8.6):开箱即用下拉模态——BottomSheet(标题 + `meow_listview` SINGLE 单选列表)+ `meow_ui_modal_capture/restore` 背景快照(关闭无残影);点选项=选中+`on_change` 回调,点面板外/左缘=取消。
|
||||||
|
- **磁盘页缓存治理 `meow_cachegov`**(OOM_PAGECACHE_GOVERNANCE):App 登记缓存文件 + read_pos 回调,SDK 做 DONTNEED 治理(L0 曲终整文件丢 / L1 播放中丢已读前缀保 keep_hot 窗口);refault 滚窗逐级回退(放大 keep_hot→降 L0→关 A);`meow_cachegov_download/prefetch_paused` 提供给下载/预取方降载查询。
|
||||||
|
- **网络自愿通道 `meow_ui_net`**(netmgrd 期 4):联网前 `meow_net_policy_check(host)` 自查是否被策略放行(netmgrd 不在/超时→乐观放行);`meow_net_report(rx,tx)` 自愿上报本进程收发字节(best-effort,失败静默)。
|
||||||
|
|
||||||
## 构建要求
|
## 构建要求
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -14,8 +14,9 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架
|
|||||||
rm -f meow_ui.a *.o
|
rm -f meow_ui.a *.o
|
||||||
|
|
||||||
# meow_font.c = 预渲染位图字体运行时(自包含:只含 meow_font.h + 标准头;最小 zlib ABI 自声明,
|
# meow_font.c = 预渲染位图字体运行时(自包含:只含 meow_font.h + 标准头;最小 zlib ABI 自声明,
|
||||||
# 链接时用 sysroot 的 libz.a),随 SDK 一并 vendored。新模块(box/anim/scroll/str)随主仓增量并入。
|
# 链接时用 sysroot 的 libz.a),随 SDK 一并 vendored。新模块(box/anim/scroll/str + cachegov/lazygrid/
|
||||||
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_font meow_ui_box meow_ui_anim meow_ui_scroll meow_ui_str"
|
# select/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_font meow_ui_box meow_ui_anim meow_ui_scroll meow_ui_str meow_cachegov meow_lazygrid meow_select meow_net_ipc"
|
||||||
CFLAGS="-Os -static -Iinclude -I."
|
CFLAGS="-Os -static -Iinclude -I."
|
||||||
for f in $OBJS; do
|
for f in $OBJS; do
|
||||||
echo "== cc $f =="
|
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 */
|
||||||
@@ -35,6 +35,12 @@
|
|||||||
/* 列表编码上限(count 个 NUL 字符串时,distinct 工作缓冲上限) */
|
/* 列表编码上限(count 个 NUL 字符串时,distinct 工作缓冲上限) */
|
||||||
#define MEOW_IPC_CARGO 1024u /* 守护进程入队拷贝上限(可小于 MEOW_IPC_MAX) */
|
#define MEOW_IPC_CARGO 1024u /* 守护进程入队拷贝上限(可小于 MEOW_IPC_MAX) */
|
||||||
|
|
||||||
|
/* ---- page-cache 治理(OOM_PAGECACHE_GOVERNANCE.md,③混合)----
|
||||||
|
* meow_svc_perf(C:/proc/meminfo Cached 监控降载)把「是否暂停下载/预取」写入此共享状态文件
|
||||||
|
* (一行式 key=value;App/SDK meow_cachegov 只读,系统级单点,不重复起线程)。
|
||||||
|
* 路径可被 env MEOW_CACHEGOV_C_FILE 覆盖(宿主测试隔离)。 */
|
||||||
|
#define MEOW_CACHEGOV_C_FILE "/tmp/meow_cachegov_c.state"
|
||||||
|
|
||||||
/* ---- 消息类型(FRAMEWORK_DESIGN §1 表)---- */
|
/* ---- 消息类型(FRAMEWORK_DESIGN §1 表)---- */
|
||||||
enum {
|
enum {
|
||||||
MSG_NONE = 0x0000,
|
MSG_NONE = 0x0000,
|
||||||
@@ -601,4 +607,34 @@ typedef struct m_net_dns_result {
|
|||||||
char text[MEOW_IPC_MAX - 8];
|
char text[MEOW_IPC_MAX - 8];
|
||||||
} m_net_dns_result_t;
|
} m_net_dns_result_t;
|
||||||
|
|
||||||
|
/* ---- 期 4:SDK 网络自愿通道(NETMGR_DESIGN §6.3/§4;0xEE/0xEF)----
|
||||||
|
* 一、meow_net_policy_check(host):框架 App 联网前自查 host 是否被 netmgrd 策略放行——
|
||||||
|
* 服务端以调用方 app_id(msg.app_id)→ 实体 app:<pkg>,评估 policy_net_blocked(entity)
|
||||||
|
* || dns_query_domain_denied(entity, host),放行=allow 1 / 拒绝=block 0;netmgrd 不在 → SDK 侧放行。
|
||||||
|
* 二、meow_net_report(rx,tx):App 自愿上报本进程收发字节数(内核无 per-socket/per-uid 计数,
|
||||||
|
* §4 诚实声明的「字节级记账走 SDK 自报」);netmgrd 记入 per-实体统计(status JSON entity_bytes)。
|
||||||
|
* 读级/上报任意调用者(自愿自报,无副作用;block 判定由服务端策略引擎权威执行)。
|
||||||
|
* (0x00EE 曾留作「事件推送 订阅制」,期 4 起改作 SDK 通道;事件推送期 2 从未实装。) */
|
||||||
|
enum { /* 与主 enum 分列,追加式不改既有行 */
|
||||||
|
MSG_NET_SDK = 0x00EE, /* ↑ op=0 策略检查 / op=1 字节上报 */
|
||||||
|
MSG_NET_SDK_RESULT = 0x00EF /* ↓ code + (CHECK)allow + 状态文本 */
|
||||||
|
};
|
||||||
|
enum { NET_SDK_CHECK = 0, NET_SDK_REPORT = 1 };
|
||||||
|
|
||||||
|
typedef struct m_net_sdk {
|
||||||
|
uint32_t op; /* NET_SDK_CHECK / NET_SDK_REPORT */
|
||||||
|
char app_id[MEOW_APP_ID_MAX]; /* 调用方 application(SDK 用 env MEOW_LOG_APP;CLI/测试可任意) */
|
||||||
|
char host[64]; /* CHECK:待查域名 */
|
||||||
|
uint32_t rx; /* REPORT:本进程累计 RX 字节 */
|
||||||
|
uint32_t tx; /* REPORT:本进程累计 TX 字节 */
|
||||||
|
} m_net_sdk_t; /* 4+32+64+4+4 = 108 B */
|
||||||
|
|
||||||
|
typedef struct m_net_sdk_result {
|
||||||
|
uint32_t code; /* MEOW_OK / MEOW_ERR_* */
|
||||||
|
int32_t allow; /* CHECK:1=allow 0=block(block 由服务端策略引擎判定) */
|
||||||
|
uint16_t text_len;
|
||||||
|
uint16_t pad;
|
||||||
|
char text[MEOW_IPC_MAX - 12];
|
||||||
|
} m_net_sdk_result_t;
|
||||||
|
|
||||||
#endif /* MEOW_IPC_H */
|
#endif /* MEOW_IPC_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 */
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* meow_ui_net.h — MeowOS SDK 网络自愿通道(NETMGR_DESIGN §6.3/§4;追加式 API,含入 meow_ui.h)。
|
||||||
|
*
|
||||||
|
* 定位:框架 App 联网前的**自愿自治**通道 + **自愿字节上报**,二者都不强制(防君子不防小人)。
|
||||||
|
* - meow_net_policy_check(host):联网前自查 host 是否被 netmgrd 管控策略放行。
|
||||||
|
* 返回 0=allow(可联网);1=block(策略拒绝,App 应中止本次联网)。
|
||||||
|
* netmgrd 不在 / 超时 → 0(乐观放行,绝不因监控不可用而误封业务)。
|
||||||
|
* - meow_net_report(rx,tx):App 自愿上报本进程累计收发字节。内核无 per-socket/per-uid
|
||||||
|
* 字节计数器(NETMGR_DESIGN §4 表),§4 的「字节级精确记账走 SDK 自报通道」由此落地;
|
||||||
|
* netmgrd 记入 per-实体统计(status JSON `entity_bytes`)。best-effort,失败静默不影响业务。
|
||||||
|
* 用法:App 在发起 HTTP/TLS 前调 meow_net_policy_check(host),若返回 1 则按产品语义处理(如提示被限)。
|
||||||
|
*/
|
||||||
|
#ifndef MEOW_UI_NET_H
|
||||||
|
#define MEOW_UI_NET_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* 检查 host 是否被策略放行。0=allow;1=block;netmgrd 不在/超时 → 0(乐观放行)。
|
||||||
|
* host 为域名(如 "music.163.com");判定以当前 App(MEOW_LOG_APP)为实体,服务端策略引擎权威执行。 */
|
||||||
|
int meow_net_policy_check(const char *host);
|
||||||
|
|
||||||
|
/* 上报本进程累计收发字节(best-effort,失败静默)。 */
|
||||||
|
void meow_net_report(uint32_t rx, uint32_t tx);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MEOW_UI_NET_H */
|
||||||
@@ -62,6 +62,28 @@ void meow_theme_remove_listener(meow_theme_listener cb);
|
|||||||
/* [D4] 脱机访问两套内置色板(无 settings 文件也可靠):返回当前解析生效的色板副本。 */
|
/* [D4] 脱机访问两套内置色板(无 settings 文件也可靠):返回当前解析生效的色板副本。 */
|
||||||
meow_theme meow_theme_defaults(meow_theme_mode m);
|
meow_theme meow_theme_defaults(meow_theme_mode m);
|
||||||
|
|
||||||
|
/* ---- §8.1.5 色板导出/注入(单一权威桥,M7.0a)----
|
||||||
|
* 用户在 M7 定案「SDK meow_theme 为唯一权威」:launcher 的 s_ui_theme() 将改为读 SDK 的薄壳,
|
||||||
|
* 不再自持 4 套静态色板。本接口是那个前置(launcher 侧改动不在此批)——
|
||||||
|
*
|
||||||
|
* meow_theme_export(out) 把当前「style×mode」解析生效的完整权威主题拷给调用方。
|
||||||
|
* 调用方是 stateless 的绘制原语时(如 launcher s_ui_theme),由此读
|
||||||
|
* 单一权威,再按它的角色字段(9 色)映射到自己的绘制便利结构,即:
|
||||||
|
* surface → surface
|
||||||
|
* surfaceVariant → surface_variant
|
||||||
|
* surfaceContainer → surface_container
|
||||||
|
* primary → primary
|
||||||
|
* onSurface → on_surface
|
||||||
|
* onSurfaceVariant → on_surface_variant
|
||||||
|
* outline → outline
|
||||||
|
* error → error
|
||||||
|
* surfaceTint → surface_tint
|
||||||
|
* meow_theme_inject(src) 用一份自定义板覆写当前生效主题(如 launcher 自定义背景/强调/风格),
|
||||||
|
* src==NULL 恢复内建(随 settings 解析)。覆写不随 meow_theme_reload() 丢弃
|
||||||
|
* (程序化覆写优先于 settings,与 meow_ui_set_style 语义一致)。 */
|
||||||
|
void meow_theme_export(meow_theme *out);
|
||||||
|
void meow_theme_inject(const meow_theme *src);
|
||||||
|
|
||||||
/* ---- §8.1 顶级主题风格族(minimal,2026-08-30)----
|
/* ---- §8.1 顶级主题风格族(minimal,2026-08-30)----
|
||||||
* 主题 = 顶级主题风格族(ui.style:MD3/MIUIx)× 明暗(display.theme)。本批只落「状态栏图标集
|
* 主题 = 顶级主题风格族(ui.style:MD3/MIUIx)× 明暗(display.theme)。本批只落「状态栏图标集
|
||||||
* 随 ui.style 换集」的最小闭环:meow_ui_set_style 记录 style(同值忽略)+ 触发 theme_reload/
|
* 随 ui.style 换集」的最小闭环:meow_ui_set_style 记录 style(同值忽略)+ 触发 theme_reload/
|
||||||
|
|||||||
@@ -210,6 +210,17 @@ enum {
|
|||||||
MEOW_ICON_HEART = 29,
|
MEOW_ICON_HEART = 29,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* ---- IconButton(圆形/位图图标按钮,M7.0a 缺口#2)----
|
||||||
|
* 圆形命中 + 位图图标(sb_icons[],Material Symbols Rounded 单通道 alpha)+ 按压态(实时)。
|
||||||
|
* 用途:电源菜单圆钮 / Now Playing 播放控制 / 锁屏常用件等「位图图标 + 圆形」按钮。
|
||||||
|
* icon_idx = sb_icons[] 下标(用上面 MEOW_ICON_* 枚举或 SB_I_*;越界画空位图,命中照常)。
|
||||||
|
* size = 按钮直径(外接正方形 w=h=size;图标 ink 高 = size*6/10,MD3 图标比)。
|
||||||
|
* 语义色:底 surfaceContainerLow、图标 onSurface;按压叠 onSurface 状态层(实时,同 meow_btn);
|
||||||
|
* 禁用变淡;选中态 primaryContainer/onPrimaryContainer(meow_widget_set_selected)。
|
||||||
|
* 命中:圆形(半径=size/2,圆心=按钮中心),owner==active + enabled 双闸门。
|
||||||
|
* 返回按钮句柄;App 在 on_tap 里用 meow_widget_hit 判定是否命中本钮(命中即触发动作)。 */
|
||||||
|
meow_widget *meow_iconbtn_create(int x, int y, int size, int icon_idx);
|
||||||
|
|
||||||
/* ---- NavigationBar(底部导航栏)---- */
|
/* ---- NavigationBar(底部导航栏)---- */
|
||||||
meow_widget *meow_navigationbar_create(int x, int y, int w, int h);
|
meow_widget *meow_navigationbar_create(int x, int y, int w, int h);
|
||||||
int meow_navigationbar_add_item(meow_widget *w, const char *label);
|
int meow_navigationbar_add_item(meow_widget *w, const char *label);
|
||||||
@@ -375,6 +386,25 @@ int meow_listview_set_item_draw(meow_widget *lv, meow_listview_draw_fn draw,
|
|||||||
/* 空态占位:count==0 时视口内居中画 empty_text(NULL 不画)。返回 0=成功。 */
|
/* 空态占位:count==0 时视口内居中画 empty_text(NULL 不画)。返回 0=成功。 */
|
||||||
int meow_listview_set_empty_text(meow_widget *lv, const char *text);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#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_net_ipc.c — MeowOS SDK 网络自愿通道 → netmgrd IPC 桥(期4;追加式协议 0x00EE/0x00EF)。
|
||||||
|
*
|
||||||
|
* 与 meow_notify_ipc.c 同型:只做「一次性 connect + send + read」,不维护连接/订阅循环。
|
||||||
|
* - meow_net_policy_check(host):MSG_NET_SDK op=NET_SDK_CHECK(app_id=MEOW_LOG_APP + host);
|
||||||
|
* 读到 allow=1 → 返回 0(allow),allow=0 → 返回 1(block);任何失败(无 socket/超时/非法应答)
|
||||||
|
* → 返回 0(乐观放行,绝不因监控不可用误封业务——NETMGR_DESIGN §6.3 自愿性质)。
|
||||||
|
* - meow_net_report(rx,tx):MSG_NET_SDK op=NET_SDK_REPORT;send 后读一次应答头即关(不读文本),
|
||||||
|
* best-effort,失败静默(返回值 void)。
|
||||||
|
* 映射层(SDK 入参 ↔ IPC m_net_sdk_t 字段)集中在本文件;app_id 取 env MEOW_LOG_APP(与 meow_ui_log.c
|
||||||
|
* 口径一致),socket 路径取 env MEOW_NET_SOCK(宿主/qemu 覆盖)缺省 /usr/data/meow_net.sock。
|
||||||
|
*
|
||||||
|
* 线程安全:app_id pthread_once 缓存;本文件纯传输层不调 meow_log(与 meow_notify_ipc.c 同约定)。
|
||||||
|
*/
|
||||||
|
#include "meow_net_ipc.h"
|
||||||
|
#include "meow_ipc.h" /* 共享协议:帧头/m_net_sdk_t/m_net_sdk_result_t/MSG_NET_SDK* —— 防漂移 */
|
||||||
|
|
||||||
|
#include <stdio.h> /* snprintf */
|
||||||
|
#include <stdlib.h> /* getenv */
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h> /* EINTR */
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#define MEOW_NET_IPC_TIMEOUT_MS 500 /* 应答读超时:netmgrd 单线程 poll,>500ms 视为降级 */
|
||||||
|
|
||||||
|
static pthread_once_t g_once = PTHREAD_ONCE_INIT;
|
||||||
|
static char g_app_id[MEOW_APP_ID_MAX] = "app";
|
||||||
|
|
||||||
|
static void ipc_init(void)
|
||||||
|
{
|
||||||
|
const char *s = getenv("MEOW_LOG_APP");
|
||||||
|
if (s && s[0]) snprintf(g_app_id, sizeof g_app_id, "%s", s);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *meow_net_ipc_app_id(void)
|
||||||
|
{
|
||||||
|
pthread_once(&g_once, ipc_init);
|
||||||
|
return g_app_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* socket 路径:优先 env MEOW_NET_SOCK,缺省 /usr/data/meow_net.sock。 */
|
||||||
|
static const char *ipc_sock_path(void)
|
||||||
|
{
|
||||||
|
const char *s = getenv("MEOW_NET_SOCK");
|
||||||
|
return (s && s[0]) ? s : "/usr/data/meow_net.sock";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 连接 socket;失败 -1(调用方降级)。 */
|
||||||
|
static int conn_path(const char *path)
|
||||||
|
{
|
||||||
|
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (fd < 0) return -1;
|
||||||
|
struct sockaddr_un a;
|
||||||
|
memset(&a, 0, sizeof a);
|
||||||
|
a.sun_family = AF_UNIX;
|
||||||
|
strncpy(a.sun_path, path, sizeof a.sun_path - 1);
|
||||||
|
if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; }
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
static int write_all(int fd, const uint8_t *p, size_t n)
|
||||||
|
{
|
||||||
|
size_t off = 0;
|
||||||
|
while (off < n) {
|
||||||
|
ssize_t w = write(fd, p + off, n - off);
|
||||||
|
if (w < 0) { if (errno == EINTR) continue; return 0; }
|
||||||
|
if (w == 0) return 0;
|
||||||
|
off += (size_t)w;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
static int read_all(int fd, uint8_t *p, size_t n)
|
||||||
|
{
|
||||||
|
size_t off = 0;
|
||||||
|
while (off < n) {
|
||||||
|
ssize_t r = read(fd, p + off, n - off);
|
||||||
|
if (r < 0) { if (errno == EINTR) continue; return 0; }
|
||||||
|
if (r == 0) return 0;
|
||||||
|
off += (size_t)r;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
static int send_frame(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len)
|
||||||
|
{
|
||||||
|
uint8_t hdr[MEOW_IPC_HDR_LEN];
|
||||||
|
uint32_t magic = MEOW_IPC_MAGIC, rl = len;
|
||||||
|
uint16_t ver = MEOW_IPC_VERSION, t = type;
|
||||||
|
memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &t, 2);
|
||||||
|
memcpy(hdr + 8, &req_id, 4); memcpy(hdr + 12, &rl, 4);
|
||||||
|
if (!write_all(fd, hdr, sizeof hdr)) return 0;
|
||||||
|
if (len && payload && !write_all(fd, payload, len)) return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
static void set_timeout(int fd)
|
||||||
|
{
|
||||||
|
struct timeval tv;
|
||||||
|
tv.tv_sec = MEOW_NET_IPC_TIMEOUT_MS / 1000;
|
||||||
|
tv.tv_usec = (MEOW_NET_IPC_TIMEOUT_MS % 1000) * 1000;
|
||||||
|
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
|
||||||
|
}
|
||||||
|
|
||||||
|
int meow_net_policy_check(const char *host)
|
||||||
|
{
|
||||||
|
if (!host || !host[0]) return 0;
|
||||||
|
m_net_sdk_t q;
|
||||||
|
memset(&q, 0, sizeof q);
|
||||||
|
q.op = NET_SDK_CHECK;
|
||||||
|
snprintf(q.app_id, sizeof q.app_id, "%s", meow_net_ipc_app_id());
|
||||||
|
snprintf(q.host, sizeof q.host, "%s", host);
|
||||||
|
|
||||||
|
int fd = conn_path(ipc_sock_path());
|
||||||
|
if (fd < 0) return 0; /* java socket:乐观放行 */
|
||||||
|
set_timeout(fd);
|
||||||
|
int allow = 0; /* 默认放行 */
|
||||||
|
if (send_frame(fd, MSG_NET_SDK, 0x4E455443u /* "NETC" */, &q, (uint32_t)sizeof q)) {
|
||||||
|
uint8_t rhdr[MEOW_IPC_HDR_LEN];
|
||||||
|
if (read_all(fd, rhdr, sizeof rhdr)) {
|
||||||
|
uint16_t rtype; uint32_t rlen;
|
||||||
|
memcpy(&rtype, rhdr + 6, 2); memcpy(&rlen, rhdr + 12, 4);
|
||||||
|
if (rtype == MSG_NET_SDK_RESULT && rlen >= 10) {
|
||||||
|
uint8_t pay[12];
|
||||||
|
if (read_all(fd, pay, 12)) { /* code(4)+allow(4)+text_len(2)+pad(2) */
|
||||||
|
m_net_sdk_result_t res;
|
||||||
|
memset(&res, 0, sizeof res);
|
||||||
|
memcpy(&res.code, pay, 4);
|
||||||
|
memcpy(&res.allow, pay + 4, 4);
|
||||||
|
memcpy(&res.text_len, pay + 8, 2);
|
||||||
|
if (res.text_len < sizeof res.text)
|
||||||
|
read_all(fd, (uint8_t *)res.text, res.text_len);
|
||||||
|
if (res.code == MEOW_OK) allow = (res.allow != 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
return allow ? 0 : 1; /* allow=1 → 返回 0(放行);allow=0 → 返回 1(block) */
|
||||||
|
}
|
||||||
|
|
||||||
|
void meow_net_report(uint32_t rx, uint32_t tx)
|
||||||
|
{
|
||||||
|
m_net_sdk_t q;
|
||||||
|
memset(&q, 0, sizeof q);
|
||||||
|
q.op = NET_SDK_REPORT;
|
||||||
|
snprintf(q.app_id, sizeof q.app_id, "%s", meow_net_ipc_app_id());
|
||||||
|
q.rx = rx; q.tx = tx;
|
||||||
|
int fd = conn_path(ipc_sock_path());
|
||||||
|
if (fd < 0) return; /* 无 socket:静默 */
|
||||||
|
set_timeout(fd);
|
||||||
|
if (send_frame(fd, MSG_NET_SDK, 0x4E455452u /* "NETR" */, &q, (uint32_t)sizeof q)) {
|
||||||
|
uint8_t rhdr[MEOW_IPC_HDR_LEN];
|
||||||
|
read_all(fd, rhdr, sizeof rhdr); /* best-effort:读一次应答头即关,不解析 */
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* meow_net_ipc.h — MeowOS SDK 网络自愿通道 → netmgrd IPC 桥(内部头,不随 meow_ui.h 发布)。
|
||||||
|
*
|
||||||
|
* 定位(NETMGR_DESIGN §6.3/§4):让框架 App 的 meow_net_policy_check()/meow_net_report() 真正经
|
||||||
|
* AF_UNIX IPC 到 netmgrd(/usr/data/meow_net.sock)。协议帧与负载结构复用共享 meow_ipc.h:
|
||||||
|
* - MSG_NET_SDK(0x00EE) ↑:op=NET_SDK_CHECK(app_id+host)/ NET_SDK_REPORT(app_id+rx+tx);
|
||||||
|
* - MSG_NET_SDK_RESULT(0x00EF) ↓:code + (CHECK)allow + 状态文本。
|
||||||
|
* 约定:只做「一次性 connect + send + read」,不维护订阅循环(同 meow_notify_ipc.c 型);
|
||||||
|
* 全部失败路径优雅降级(无 socket/写失败/超时/非法应答 → check 返回 0 放行、report 静默)。
|
||||||
|
*/
|
||||||
|
#ifndef MEOW_NET_IPC_H
|
||||||
|
#define MEOW_NET_IPC_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "meow_ui.h" /* meow_net_policy_check / meow_net_report */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* 当前 app 的 app_id(env MEOW_LOG_APP,缺省 "app";pthread_once 缓存,线程安全)。 */
|
||||||
|
const char *meow_net_ipc_app_id(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MEOW_NET_IPC_H */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
+7
-1
@@ -32,11 +32,13 @@
|
|||||||
* include/meow_ui_log.h 应用日志(§9)
|
* include/meow_ui_log.h 应用日志(§9)
|
||||||
* include/meow_ui_notify.h 应用通知(§9.1)
|
* include/meow_ui_notify.h 应用通知(§9.1)
|
||||||
* include/meow_ui_config.h 应用配置读写(§9.2)
|
* include/meow_ui_config.h 应用配置读写(§9.2)
|
||||||
* include/meow_ui_font.h 字体列表(§5.7)+ 字体分层(§10)
|
* include/meow_ui_font.h 字体列表(§5.7)+ 字体分层(§10)+ 矢量字 meow_ft_*(§10.1)
|
||||||
* include/meow_ui_audio.h 独占音频客户端(§11)
|
* include/meow_ui_audio.h 独占音频客户端(§11)
|
||||||
* include/meow_ui_box.h 线性布局引擎(§12.1:meow_box,固定/weight/wrap/对齐/嵌套)
|
* include/meow_ui_box.h 线性布局引擎(§12.1:meow_box,固定/weight/wrap/对齐/嵌套)
|
||||||
* include/meow_ui_anim.h 动画基座+UI 节拍器(§12.2:tween/easing/池+脏区联动)
|
* include/meow_ui_anim.h 动画基座+UI 节拍器(§12.2:tween/easing/池+脏区联动)
|
||||||
* include/meow_ui_scroll.h 滚动物理(§12.3:速度跟踪/fling/边界回弹/snap)
|
* 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)
|
* include/meow_ui_str.h 字符串资源/i18n(§13:meow_str/meow_strf/meow_str_set_locale)
|
||||||
*
|
*
|
||||||
* 其余模块头(非伞头、App 按需显式 include,均保持不动):
|
* 其余模块头(非伞头、App 按需显式 include,均保持不动):
|
||||||
@@ -70,6 +72,10 @@
|
|||||||
#include "include/meow_ui_box.h"
|
#include "include/meow_ui_box.h"
|
||||||
#include "include/meow_ui_anim.h"
|
#include "include/meow_ui_anim.h"
|
||||||
#include "include/meow_ui_scroll.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_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 */
|
#endif /* MEOW_UI_H */
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
static unsigned char *g_modal_cap = NULL;
|
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 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();
|
unsigned char *bb = meow_ui_back_buf();
|
||||||
int line = meow_ui_back_buf_pitch();
|
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);
|
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();
|
unsigned char *bb = meow_ui_back_buf();
|
||||||
if (!g_modal_cap || !bb) return;
|
if (!g_modal_cap || !bb) return;
|
||||||
@@ -235,7 +235,7 @@ void meow_dialog_show(meow_dialog *d)
|
|||||||
/* §6.4:模态作用域——本层打开期间下层(view/widget)完全收不到触摸;点卡片外由背板消费
|
/* §6.4:模态作用域——本层打开期间下层(view/widget)完全收不到触摸;点卡片外由背板消费
|
||||||
* (d->done=1 result=0 取消),不透传。嵌套弹层 depth 递增。 */
|
* (d->done=1 result=0 取消),不透传。嵌套弹层 depth 递增。 */
|
||||||
meow_ui_modal *m = meow_ui_modal_begin();
|
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);
|
dlg_paint(d, th);
|
||||||
meow_ui_flush();
|
meow_ui_flush();
|
||||||
@@ -291,7 +291,7 @@ void meow_dialog_show(meow_dialog *d)
|
|||||||
usleep(16000);
|
usleep(16000);
|
||||||
}
|
}
|
||||||
meow_ui_modal_end(m); /* §6.4:关闭模态作用域(下层恢复接收触摸) */
|
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; }
|
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 ph = sheet_h(s, th);
|
||||||
int py = H - ph, pad = 24;
|
int py = H - ph, pad = 24;
|
||||||
meow_ui_modal *m = meow_ui_modal_begin(); /* §6.4:模态作用域(下层收不到触摸) */
|
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_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150);
|
||||||
meow_gfx_fill_round(0, py, W, ph, th->radius_card, th->surfaceContainerHighest);
|
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);
|
usleep(16000);
|
||||||
}
|
}
|
||||||
meow_ui_modal_end(m); /* §6.4:关闭模态作用域 */
|
meow_ui_modal_end(m); /* §6.4:关闭模态作用域 */
|
||||||
modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */
|
meow_ui_modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */
|
||||||
return s->result;
|
return s->result;
|
||||||
}
|
}
|
||||||
void meow_sheet_free(meow_sheet *s) { if (s) free(s); }
|
void meow_sheet_free(meow_sheet *s) { if (s) free(s); }
|
||||||
+13
-1
@@ -26,6 +26,7 @@
|
|||||||
#include "meow_ui_sys.h"
|
#include "meow_ui_sys.h"
|
||||||
#include "meow_ui_vol.h"
|
#include "meow_ui_vol.h"
|
||||||
#include "meow_ui_media.h"
|
#include "meow_ui_media.h"
|
||||||
|
#include "meow_ui_view.h" /* M7.0a 缺口#7:把 meow_view_dispatch_event 接入 meow_ui_run */
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -140,7 +141,7 @@ static int meow_input_jack_debounce(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---- §10.5 叠加让位:通知中心叠加会话期输入排空(播放中 App 不被 SIGSTOP 的配套)----
|
/* ---- §10.5 叠加让位:通知中心叠加会话期输入排空(播放中 App 不被 SIGSTOP 的配套)----
|
||||||
* 媒体播放中的 App 前台唤起通知中心时 launcher 不冻结 App(保 PCM 供流,App 播放线程同进程),
|
* 媒体播放中的 App 前台唤起通知中心时 launcher 不冻结 App(保 PCM 供流,ncm 播放线程同进程),
|
||||||
* App 输入线程仍在读同一批 evdev 设备(每 fd 各收一份事件副本,与 launcher 互不剥夺)——若不
|
* App 输入线程仍在读同一批 evdev 设备(每 fd 各收一份事件副本,与 launcher 互不剥夺)——若不
|
||||||
* 抑制,面板上的一次点击会被 App 再消费一次(如面板媒体卡片「暂停」与 App 同坐标控件双重触发)。
|
* 抑制,面板上的一次点击会被 App 再消费一次(如面板媒体卡片「暂停」与 App 同坐标控件双重触发)。
|
||||||
* 故标记存在期:排空本进程 fd 队列并丢弃、EVIOCGKEY 位图静默对齐(不产生边沿语义,系统键动作
|
* 故标记存在期:排空本进程 fd 队列并丢弃、EVIOCGKEY 位图静默对齐(不产生边沿语义,系统键动作
|
||||||
@@ -714,8 +715,14 @@ void meow_ui_run(const meow_ui_events *ev)
|
|||||||
/* §6.1 触摸分流:③ 系统全局(IME/状态栏…已在上面消费) → ② App 全局控件
|
/* §6.1 触摸分流:③ 系统全局(IME/状态栏…已在上面消费) → ② App 全局控件
|
||||||
* (命中返回 1=消费,否则落到 ① active Activity 页面控件)。 */
|
* (命中返回 1=消费,否则落到 ① active Activity 页面控件)。 */
|
||||||
if (meow_ui_global_hit(e.a, e.b)) continue;
|
if (meow_ui_global_hit(e.a, e.b)) continue;
|
||||||
|
/* M7.0a 缺口#7:meow_view 事件路由(§A.7)——有活动页 view 子树且其 on_event
|
||||||
|
* 命中即消费(不再落 App on_tap);无 view 树/未命中返回 0 → 回落 on_tap 原路径。 */
|
||||||
|
if (meow_view_dispatch_event(MEOW_EV_TAP, e.a, e.b)) continue;
|
||||||
if (g_events_set && g_events.on_tap) g_events.on_tap(e.a, e.b);
|
if (g_events_set && g_events.on_tap) g_events.on_tap(e.a, e.b);
|
||||||
} else if (e.type == MEOW_EV_KEY) {
|
} else if (e.type == MEOW_EV_KEY) {
|
||||||
|
/* M7.0a 缺口#7:先进 view 路由,命中即消费;否则回落 App on_key(语义不变——
|
||||||
|
* 音量/媒体系统键已在输入线程消化,这里只决定 App/页面是否收到语义事件)。 */
|
||||||
|
if (meow_view_dispatch_event(MEOW_EV_KEY, e.a, e.b)) continue;
|
||||||
if (g_events_set && g_events.on_key) g_events.on_key(e.a, e.b);
|
if (g_events_set && g_events.on_key) g_events.on_key(e.a, e.b);
|
||||||
} else if (e.type == MEOW_EV_SWIPE) {
|
} else if (e.type == MEOW_EV_SWIPE) {
|
||||||
if (in_startup) continue; /* 启动残留被当左缘滑动:丢弃,防进入即返回 */
|
if (in_startup) continue; /* 启动残留被当左缘滑动:丢弃,防进入即返回 */
|
||||||
@@ -725,6 +732,9 @@ void meow_ui_run(const meow_ui_events *ev)
|
|||||||
/* 拼音 IME 候选栏横滑翻页(英文模式 handle_event 返回 0 → 沿用既有行为)。 */
|
/* 拼音 IME 候选栏横滑翻页(英文模式 handle_event 返回 0 → 沿用既有行为)。 */
|
||||||
if (meow_ime_active() && meow_ime_handle_event(&e)) continue;
|
if (meow_ime_active() && meow_ime_handle_event(&e)) continue;
|
||||||
meow_ui_gesture_end(); /* §6.4:抬手(滑动)边沿,释放手势捕获 */
|
meow_ui_gesture_end(); /* §6.4:抬手(滑动)边沿,释放手势捕获 */
|
||||||
|
/* M7.0a 缺口#7:view 路由先于 App 窗口的 on_swipe 与默认返回手势——页 view
|
||||||
|
* 可消费滑动(页内返回/翻页/横幅);命中即跳过下面对 on_swipe/默认退出的分发。 */
|
||||||
|
if (meow_view_dispatch_event(MEOW_EV_SWIPE, e.a, e.b)) continue;
|
||||||
/* A-0 全局左缘返回(方案A):
|
/* A-0 全局左缘返回(方案A):
|
||||||
* - APP 注册 on_swipe → APP 接管,自行决定(meow_ui_request_exit / 页内返回);
|
* - APP 注册 on_swipe → APP 接管,自行决定(meow_ui_request_exit / 页内返回);
|
||||||
* - APP 未注册 on_swipe 且非全屏(sb_active)→ SDK 默认退出 app(返回=退出);
|
* - APP 未注册 on_swipe 且非全屏(sb_active)→ SDK 默认退出 app(返回=退出);
|
||||||
@@ -783,6 +793,8 @@ void meow_ui_run(const meow_ui_events *ev)
|
|||||||
/* 拼音 IME 打开:长按退格清空字段(英文模式 handle_event 返回 0 → 走既有长按回调)。 */
|
/* 拼音 IME 打开:长按退格清空字段(英文模式 handle_event 返回 0 → 走既有长按回调)。 */
|
||||||
if (meow_ime_active() && meow_ime_handle_event(&e)) continue;
|
if (meow_ime_active() && meow_ime_handle_event(&e)) continue;
|
||||||
meow_ui_gesture_end(); /* §6.4:长按已触发,其抬手不再 TAP/SWIPE——顺手释放捕获 */
|
meow_ui_gesture_end(); /* §6.4:长按已触发,其抬手不再 TAP/SWIPE——顺手释放捕获 */
|
||||||
|
/* M7.0a 缺口#7:view 路由先于 §6.3 通用长按回调(页 view 可消费长按)。 */
|
||||||
|
if (meow_view_dispatch_event(MEOW_EV_LONG_PRESS, e.a, e.b)) continue;
|
||||||
if (g_long_press_cb) g_long_press_cb(e.a, e.b);
|
if (g_long_press_cb) g_long_press_cb(e.a, e.b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,6 +137,13 @@ unsigned char *meow_ui_back_buf(void);
|
|||||||
int meow_ui_back_buf_pitch(void);
|
int meow_ui_back_buf_pitch(void);
|
||||||
int meow_ui_back_buf_bpp(void);
|
int meow_ui_back_buf_bpp(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);
|
||||||
|
|
||||||
/* 实心圆角矩形(控件按钮底色用;公共 §3 只有空心 draw_rect_round)。[D3] */
|
/* 实心圆角矩形(控件按钮底色用;公共 §3 只有空心 draw_rect_round)。[D3] */
|
||||||
void meow_gfx_fill_round(int x, int y, int w, int h, int r, uint32_t color);
|
void meow_gfx_fill_round(int x, int y, int w, int h, int r, uint32_t color);
|
||||||
/* 圆角矩形半透明填充(M2.71-bug4):禁用变暗等状态层用,圆角外不叠色(防灰方块)。 */
|
/* 圆角矩形半透明填充(M2.71-bug4):禁用变暗等状态层用,圆角外不叠色(防灰方块)。 */
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ static char g_background[64] = ""; /* display.background(black
|
|||||||
static meow_theme_listener g_listeners[8];
|
static meow_theme_listener g_listeners[8];
|
||||||
static int g_nlisteners = 0;
|
static int g_nlisteners = 0;
|
||||||
|
|
||||||
|
/* §8.1.5 色板注入:一份自定义板覆写当前生效主题(src==NULL 恢复内建)。
|
||||||
|
* 覆写优先于 settings 解析(随 meow_theme_reload() 重读 settings 后仍生效,与 meow_ui_set_style
|
||||||
|
* 的「程序化覆写不清 settings」语义一致)。 */
|
||||||
|
static meow_theme g_inject;
|
||||||
|
static int g_inject_set = 0;
|
||||||
|
|
||||||
/* ---- 读 /usr/data/meow/settings.conf 的 key=value(兼容 "key = value" 带空格形式)----
|
/* ---- 读 /usr/data/meow/settings.conf 的 key=value(兼容 "key = value" 带空格形式)----
|
||||||
* 与 launcher meow_settings_import 同款解析:按 '=' 切分,key/value 各自去首尾空白,
|
* 与 launcher meow_settings_import 同款解析:按 '=' 切分,key/value 各自去首尾空白,
|
||||||
* 精确匹配 key(避免 display.theme 误匹配 display.theme_mode)。返回 0=命中,-1=未命中。 */
|
* 精确匹配 key(避免 display.theme 误匹配 display.theme_mode)。返回 0=命中,-1=未命中。 */
|
||||||
@@ -295,6 +301,7 @@ static void broadcast(void)
|
|||||||
/* 当前「style×mode」主题:style=0/1(MD3/MIUIX),dark=0(light)/1(dark)。 */
|
/* 当前「style×mode」主题:style=0/1(MD3/MIUIX),dark=0(light)/1(dark)。 */
|
||||||
static const meow_theme *theme_current(int style, int dark)
|
static const meow_theme *theme_current(int style, int dark)
|
||||||
{
|
{
|
||||||
|
if (g_inject_set) return &g_inject; /* §8.1.5 注入板优先 */
|
||||||
if (style < MEOW_STYLE_MD3) style = MEOW_STYLE_MD3;
|
if (style < MEOW_STYLE_MD3) style = MEOW_STYLE_MD3;
|
||||||
if (style > MEOW_STYLE_MIUIX) style = MEOW_STYLE_MIUIX;
|
if (style > MEOW_STYLE_MIUIX) style = MEOW_STYLE_MIUIX;
|
||||||
return &g_theme[style][dark ? 1 : 0];
|
return &g_theme[style][dark ? 1 : 0];
|
||||||
@@ -339,6 +346,26 @@ meow_theme meow_theme_defaults(meow_theme_mode m)
|
|||||||
return g_theme[MEOW_STYLE_MD3][resolve_mode(m) == MEOW_THEME_DARK ? 1 : 0];
|
return g_theme[MEOW_STYLE_MD3][resolve_mode(m) == MEOW_THEME_DARK ? 1 : 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- §8.1.5 色板导出/注入(单一权威桥)----
|
||||||
|
* 导出:把当前「style×mode」解析生效的完整权威主题拷给调用方(不取指针以防调用方误改内部表)。
|
||||||
|
* 注入:用一份自定义板覆写当前生效主题;NULL 恢复内建。二者都触发 load(g_loaded==0 时先读
|
||||||
|
* settings.conf),使宿主无 settings 文件(单测/脱机)也可靠。 */
|
||||||
|
void meow_theme_export(meow_theme *out)
|
||||||
|
{
|
||||||
|
if (!out) return;
|
||||||
|
const meow_theme *t = meow_theme_get(); /* 解析 g_mode(AUTO→明/暗)+ g_style + 背景覆写 */
|
||||||
|
*out = *t;
|
||||||
|
}
|
||||||
|
|
||||||
|
void meow_theme_inject(const meow_theme *src)
|
||||||
|
{
|
||||||
|
if (!g_loaded) { load_settings(); g_loaded = 1; }
|
||||||
|
if (!src) { g_inject_set = 0; broadcast(); return; }
|
||||||
|
g_inject = *src;
|
||||||
|
g_inject_set = 1;
|
||||||
|
broadcast(); /* 通知 App/状态栏/控件下一帧用新注入板重绘 */
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- §8.1.2 顶级主题风格族(minimal)----
|
/* ---- §8.1.2 顶级主题风格族(minimal)----
|
||||||
* meow_ui_style() 返回当前 ui.style(读取走 load_settings,随 meow_theme_reload() 重读,
|
* meow_ui_style() 返回当前 ui.style(读取走 load_settings,随 meow_theme_reload() 重读,
|
||||||
* 故 launcher 改 settings.conf 后经 sb_sdk_refresh→theme_reload 即跟手)。
|
* 故 launcher 改 settings.conf 后经 sb_sdk_refresh→theme_reload 即跟手)。
|
||||||
|
|||||||
+10
-2
@@ -156,6 +156,10 @@ int meow_view_add_child(meow_view *parent, meow_view *child)
|
|||||||
if (parent->nchildren >= MEOW_VIEW_MAX_CHILD) return -1;
|
if (parent->nchildren >= MEOW_VIEW_MAX_CHILD) return -1;
|
||||||
parent->children[parent->nchildren++] = child;
|
parent->children[parent->nchildren++] = child;
|
||||||
child->parent = parent;
|
child->parent = parent;
|
||||||
|
/* 递归修正(§A.4「子视图随父进出」):父已进入时挂上的子视图**立即**进入
|
||||||
|
* (on_enter 不遗漏、参与事件分发)。页在 push/pop 时仍由 view_enter/view_leave 递归驱动,
|
||||||
|
* 此处只补「运行期动态挂子」的间隙(hello 目录嵌套实为 create 后 page_push 前 add,不受影响)。 */
|
||||||
|
if (parent->state & MEOW_VIEW_STATE_ENTERED) view_enter(child);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,11 +222,15 @@ void meow_view_page_replace(int id)
|
|||||||
g_top_view = next ? next : cur;
|
g_top_view = next ? next : cur;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- 页内子导航(预留扩展位 a 桩:本批不实现,仅返回占位)---- */
|
/* ---- 页内子导航(预留扩展位 a,**标注废弃**)----
|
||||||
|
* 设计定案 b(SDK_VIEW_FRAMEWORK_DESIGN.md §F.2:只做子视图容器嵌套,顶层页切仍走 activity
|
||||||
|
* 栈,不做页内子导航栈):本函数对为「将来 a」预留的桩,**不实现、不供依赖**——调用方不得
|
||||||
|
* 认为会建立/弹出子导航。页内「子页」请用 meow_view_add_child 挂 CONTAINER 子视图(组合嵌套),
|
||||||
|
* 或以 activity 栈驱动页切换(page_push/pop/replace)。保留符号仅为未来 a 的占位。 */
|
||||||
int meow_view_sub_push(meow_view *v, meow_view *sub)
|
int meow_view_sub_push(meow_view *v, meow_view *sub)
|
||||||
{
|
{
|
||||||
(void)v; (void)sub;
|
(void)v; (void)sub;
|
||||||
return 0; /* 预留:将来在 ACTIVITY view 内维护子栈 */
|
return 0; /* 预留:将来在 ACTIVITY view 内维护子栈(见设计定案 b,不实现) */
|
||||||
}
|
}
|
||||||
meow_view *meow_view_sub_pop(meow_view *v)
|
meow_view *meow_view_sub_pop(meow_view *v)
|
||||||
{
|
{
|
||||||
|
|||||||
+9
-4
@@ -82,12 +82,17 @@ void meow_view_page_replace(int id); /* 旧top on_leave + 新页 on_en
|
|||||||
|
|
||||||
/* 入口:把语义事件注入当前活动页 view 子树路由——先发子视图(按 z 降序),命中即消费,
|
/* 入口:把语义事件注入当前活动页 view 子树路由——先发子视图(按 z 降序),命中即消费,
|
||||||
* 再落本 view 的 on_event(其内做该 view 的 widget 命中)。返回 1=已消费(§A.7)。
|
* 再落本 view 的 on_event(其内做该 view 的 widget 命中)。返回 1=已消费(§A.7)。
|
||||||
* 未接线于 meow_ui_run(App 可在 on_tap/on_swipe 等回调里改用它实现 SDK 原生路由)。 */
|
* 已接线于 meow_ui_run(M7.0a):在 §6.4 分流之上,TAP/KEY/SWIPE/LONG_PRESS 先经本路由,
|
||||||
|
* 命中即消费(不再落 App 的 on_* 回调);返回 0 时回落 on_* 原路径(无 view 树 / 未命中 →
|
||||||
|
* 完全向后兼容)。App 也可在自持事件循环里直接调用本函数实现 SDK 原生路由。 */
|
||||||
int meow_view_dispatch_event(int ev, int a, int b);
|
int meow_view_dispatch_event(int ev, int a, int b);
|
||||||
|
|
||||||
/* ---- 页内子导航(预留扩展位 a;本批为空实现/桩,仅在 ACTIVITY view 内维护子栈)---- */
|
/* ---- 页内子导航(预留扩展位 a,**标注废弃**)----
|
||||||
int meow_view_sub_push(meow_view *v, meow_view *sub); /* 预留;返回 0(未实现于本批) */
|
* 设计定案 b(§F.2):不做页内子导航栈,只做子视图容器嵌套;顶层页切仍走 activity 栈。
|
||||||
meow_view *meow_view_sub_pop(meow_view *v); /* 预留;返回 NULL(未实现于本批) */
|
* 故本组为**不实现、不供依赖**的桩(返回 0/NULL);页内子页请用 meow_view_add_child 挂
|
||||||
|
* CONTAINER 子视图,或以 page_push/pop/replace 走 activity 栈。保留符号仅作将来 a 的占位。 */
|
||||||
|
int meow_view_sub_push(meow_view *v, meow_view *sub); /* 废弃(不实现);返回 0 */
|
||||||
|
meow_view *meow_view_sub_pop(meow_view *v); /* 废弃(不实现);返回 NULL */
|
||||||
|
|
||||||
/* ---- 坐标:view 相对父内容原点的累计(父坐标链求和 → 屏幕绝对坐标)---- */
|
/* ---- 坐标:view 相对父内容原点的累计(父坐标链求和 → 屏幕绝对坐标)---- */
|
||||||
int meow_view_screen_x(const meow_view *v);
|
int meow_view_screen_x(const meow_view *v);
|
||||||
|
|||||||
+263
-29
@@ -15,7 +15,8 @@ enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3,
|
|||||||
W_TEXTFIELD = 7, W_CARD = 8, W_DIVIDER = 9, W_BADGE = 10,
|
W_TEXTFIELD = 7, W_CARD = 8, W_DIVIDER = 9, W_BADGE = 10,
|
||||||
W_CHECKBOX = 11, W_RADIO = 12, W_PROGRESS = 13,
|
W_CHECKBOX = 11, W_RADIO = 12, W_PROGRESS = 13,
|
||||||
W_TOPAPPBAR = 14, W_NAVBAR = 15, W_TABBAR = 16, W_FAB = 17,
|
W_TOPAPPBAR = 14, W_NAVBAR = 15, W_TABBAR = 16, W_FAB = 17,
|
||||||
W_LISTVIEW = 18 }; /* §5.5 结构化列表(行=图标+标题+副标题+右侧操作+选中) */
|
W_LISTVIEW = 18, /* §5.5 结构化列表(行=图标+标题+副标题+右侧操作+选中) */
|
||||||
|
W_ICONBTN = 19 }; /* M7.0a 缺口#2:圆形/位图图标按钮(圆形命中 + 按压态) */
|
||||||
|
|
||||||
/* 布局子项数组上限(固定尺寸子项先做;超出静默拒收)。 */
|
/* 布局子项数组上限(固定尺寸子项先做;超出静默拒收)。 */
|
||||||
#define MEOW_LAYOUT_MAX_CHILD 32
|
#define MEOW_LAYOUT_MAX_CHILD 32
|
||||||
@@ -100,6 +101,8 @@ struct meow_widget {
|
|||||||
meow_listview_tap_fn lv_tap_cb; /* 行点击回调(idx, op) */
|
meow_listview_tap_fn lv_tap_cb; /* 行点击回调(idx, op) */
|
||||||
void *lv_tap_ctx; /* 回调 ctx */
|
void *lv_tap_ctx; /* 回调 ctx */
|
||||||
|
|
||||||
|
/* W_ICONBTN 位图图标:sb_icons[] 下标(= MEOW_ICON_* 或 SB_I_*;<0 画空位图,命中照常)。 */
|
||||||
|
int icon_id;
|
||||||
/* §5.5 虚拟化层(W_LISTVIEW 扩展:固定 item 池 + 多行渲染器 + 完全自绘 + 空态。
|
/* §5.5 虚拟化层(W_LISTVIEW 扩展:固定 item 池 + 多行渲染器 + 完全自绘 + 空态。
|
||||||
* calloc 归零,安全;vs_count/vs_bind 均非 NULL 才进入 pool 模式,src2 模式不受影响。) */
|
* calloc 归零,安全;vs_count/vs_bind 均非 NULL 才进入 pool 模式,src2 模式不受影响。) */
|
||||||
meow_listview_count_fn vs_count; /* 虚拟化总数回调(NULL=src2 模式) */
|
meow_listview_count_fn vs_count; /* 虚拟化总数回调(NULL=src2 模式) */
|
||||||
@@ -113,6 +116,21 @@ struct meow_widget {
|
|||||||
int show_icon; /* 1=画左图标/缩略图(默认 1) */
|
int show_icon; /* 1=画左图标/缩略图(默认 1) */
|
||||||
int show_right;/* 1=画右尾件(默认 1) */
|
int show_right;/* 1=画右尾件(默认 1) */
|
||||||
char empty_text[64]; /* 空态占位文案(首字符 0=不画) */
|
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 时按控件内建默认)----
|
/* ---- §5.8 batch1 内部辅助:opts 取值(opts_used==0 时按控件内建默认)----
|
||||||
@@ -225,7 +243,9 @@ static int g_live_prev_hold = 0; /* 该按钮上次渲染是否为按住
|
|||||||
|
|
||||||
static void live_btn_register(meow_widget *w)
|
static void live_btn_register(meow_widget *w)
|
||||||
{
|
{
|
||||||
if (!w || w->type != W_BTN) return;
|
if (!w) return;
|
||||||
|
/* M7.0a 缺口#2:IconButton 也是实时按压钮(同 meow_btn 的按压/按住即时重绘)。 */
|
||||||
|
if (w->type != W_BTN && w->type != W_ICONBTN) return;
|
||||||
for (int i = 0; i < g_live_btn_n; i++)
|
for (int i = 0; i < g_live_btn_n; i++)
|
||||||
if (g_live_btns[i] == w) return;
|
if (g_live_btns[i] == w) return;
|
||||||
if (g_live_btn_n < MEOW_LIVE_BTN_MAX) g_live_btns[g_live_btn_n++] = w;
|
if (g_live_btn_n < MEOW_LIVE_BTN_MAX) g_live_btns[g_live_btn_n++] = w;
|
||||||
@@ -246,6 +266,15 @@ static void live_btn_unregister(meow_widget *w)
|
|||||||
/* 命中:返回坐标所在的「当前 active Activity」的最上层按钮(创建序 = 绘制序,最后者在上)。
|
/* 命中:返回坐标所在的「当前 active Activity」的最上层按钮(创建序 = 绘制序,最后者在上)。
|
||||||
* §6.1 ①:仅扫描 owner == 当前 active Activity 的按钮 —— 非 active Activity 的控件
|
* §6.1 ①:仅扫描 owner == 当前 active Activity 的按钮 —— 非 active Activity 的控件
|
||||||
* 绝不参与命中(根治 agent-119 遗留的「全屏页触摸到主页按钮位置 → 闪现 accent 底」)。 */
|
* 绝不参与命中(根治 agent-119 遗留的「全屏页触摸到主页按钮位置 → 闪现 accent 底」)。 */
|
||||||
|
/* 圆形命中(IconButton;圆心=按钮中心、半径=size/2)。 */
|
||||||
|
static int iconbtn_circular_hit(const meow_widget *w, int x, int y)
|
||||||
|
{
|
||||||
|
int cx = w->x + w->w / 2, cy = w->y + w->h / 2;
|
||||||
|
int r = (w->w < w->h ? w->w : w->h) / 2;
|
||||||
|
int dx = x - cx, dy = y - cy;
|
||||||
|
return (dx * dx + dy * dy) <= (r * r);
|
||||||
|
}
|
||||||
|
|
||||||
static meow_widget *live_btn_hit(int x, int y)
|
static meow_widget *live_btn_hit(int x, int y)
|
||||||
{
|
{
|
||||||
int active = meow_ui_activity_active();
|
int active = meow_ui_activity_active();
|
||||||
@@ -253,8 +282,12 @@ static meow_widget *live_btn_hit(int x, int y)
|
|||||||
for (int i = 0; i < g_live_btn_n; i++) {
|
for (int i = 0; i < g_live_btn_n; i++) {
|
||||||
meow_widget *w = g_live_btns[i];
|
meow_widget *w = g_live_btns[i];
|
||||||
if (w->owner != active) continue; /* 非 active activity:跳过 */
|
if (w->owner != active) continue; /* 非 active activity:跳过 */
|
||||||
if (x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h)
|
/* M7.0a 缺口#2:IconButton 按圆形命中(角部按下不点亮圆钮);其余按矩形。 */
|
||||||
|
if (w->type == W_ICONBTN) {
|
||||||
|
if (iconbtn_circular_hit(w, x, y)) r = w;
|
||||||
|
} else if (x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h) {
|
||||||
r = w; /* 越后创建越上:保留最后命中 */
|
r = w; /* 越后创建越上:保留最后命中 */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@@ -540,6 +573,20 @@ int meow_btn_set_variant(meow_widget *w, int variant)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- §5.8.6 M7.0a 缺口#2:IconButton(圆形/位图图标按钮)---- */
|
||||||
|
meow_widget *meow_iconbtn_create(int x, int y, int size, int icon_idx)
|
||||||
|
{
|
||||||
|
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
|
||||||
|
if (!t) return NULL;
|
||||||
|
t->type = W_ICONBTN; t->x = x; t->y = y; t->w = size; t->h = size;
|
||||||
|
t->owner = meow_ui_activity_active(); /* §6.1 ①:同既有控件绑当前 active Activity */
|
||||||
|
t->state = MEOW_WS_ENABLED;
|
||||||
|
t->icon_id = icon_idx;
|
||||||
|
live_btn_register(t); /* 实时按压/按住即时重绘(同 meow_btn,agent-119) */
|
||||||
|
meow_draw_widget_register(t); /* §6.2 自动注册(创建即注册,App 零调用) */
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- TextField ---- */
|
/* ---- TextField ---- */
|
||||||
meow_widget *meow_textfield_create(int x, int y, int w, int h, const char *placeholder,
|
meow_widget *meow_textfield_create(int x, int y, int w, int h, const char *placeholder,
|
||||||
char *buf, int maxlen, meow_ime_mode mode)
|
char *buf, int maxlen, meow_ime_mode mode)
|
||||||
@@ -1129,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->owner = meow_ui_activity_active();
|
||||||
t->state = MEOW_WS_ENABLED;
|
t->state = MEOW_WS_ENABLED;
|
||||||
t->count = count > 0 ? count : 0; t->src2 = src; t->ctx = ctx;
|
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->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 布局不变) */
|
t->lines = 2; t->show_icon = 1; t->show_right = 1; /* 默认双行富 item + 图标 + 右尾件(src2 布局不变) */
|
||||||
if (t->sel_mode == MEOW_LIST_SEL_MULTI && t->count > 0) {
|
if (t->sel_mode == MEOW_LIST_SEL_MULTI && t->count > 0) {
|
||||||
@@ -1361,6 +1408,127 @@ int meow_listview_set_empty_text(meow_widget *w, const char *text)
|
|||||||
return 0;
|
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)
|
static int hit(meow_widget *w, int x, int y)
|
||||||
{
|
{
|
||||||
if (!w) return 0;
|
if (!w) return 0;
|
||||||
@@ -1374,9 +1542,11 @@ int meow_widget_hit(meow_widget *w, int x, int y)
|
|||||||
if (w->owner != meow_ui_activity_active()) return 0;
|
if (w->owner != meow_ui_activity_active()) return 0;
|
||||||
/* §5.8.3:禁用不响应命中(也不进 pressed)。 */
|
/* §5.8.3:禁用不响应命中(也不进 pressed)。 */
|
||||||
if (widget_disabled(w)) return 0;
|
if (widget_disabled(w)) return 0;
|
||||||
/* §5.8.1:自定义画布用 App 回调判定命中(可非矩形);其余矩形内含。 */
|
/* §5.8.1:自定义画布用 App 回调判定命中(可非矩形);IconButton 圆形命中;其余矩形内含。 */
|
||||||
if (w->type == W_CUSTOM)
|
if (w->type == W_CUSTOM)
|
||||||
return w->hit_cb ? w->hit_cb(w, x, y, w->ctx) : hit(w, x, y);
|
return w->hit_cb ? w->hit_cb(w, x, y, w->ctx) : hit(w, x, y);
|
||||||
|
if (w->type == W_ICONBTN)
|
||||||
|
return iconbtn_circular_hit(w, x, y);
|
||||||
return hit(w, x, y);
|
return hit(w, x, y);
|
||||||
}
|
}
|
||||||
int meow_btn_pressed(meow_widget *w)
|
int meow_btn_pressed(meow_widget *w)
|
||||||
@@ -1480,6 +1650,35 @@ void meow_widget_draw(meow_widget *w)
|
|||||||
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90);
|
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case W_ICONBTN: {
|
||||||
|
/* M7.0a 缺口#2:圆形底 + 位图图标 + 按压/按住状态层(实时)。
|
||||||
|
* 按压三路来源(同 meow_btn,agent-119):状态机 MEOW_WS_PRESSED / 实时触摸命中 /
|
||||||
|
* 位图取自 sb_icons[](meow_gfx_blit_icon_ink,只写像素不标顶栏脏区——控件矩形由
|
||||||
|
* 本 draw 的 clip + invalidation 管理);图标墨色 onSurface,选中/按住淡向底。 */
|
||||||
|
int live = meow_input_touch_down() &&
|
||||||
|
iconbtn_circular_hit(w, meow_input_touch_x(), meow_input_touch_y());
|
||||||
|
int press = (w->state & MEOW_WS_PRESSED) || live;
|
||||||
|
int hold = live && meow_input_touch_hold_ms() >= (unsigned)meow_ui_long_press_ms();
|
||||||
|
int disabled = widget_disabled(w);
|
||||||
|
if (disabled) press = 0;
|
||||||
|
int cx = w->x + w->w / 2, cy = w->y + w->h / 2;
|
||||||
|
int r = (w->w < w->h ? w->w : w->h) / 2;
|
||||||
|
int inkh = (w->h * 6) / 10; /* MD3 图标比:约 24/40 → 0.6*size */
|
||||||
|
if (inkh < 8) inkh = (w->h > 0) ? w->h : 8;
|
||||||
|
int selected = (w->state & MEOW_WS_SELECTED);
|
||||||
|
uint32_t base = selected ? th->primaryContainer : widget_bg(w, th, th->surfaceContainerLow);
|
||||||
|
uint32_t fgc = selected ? th->onPrimaryContainer : widget_fg(w, th, th->onSurface);
|
||||||
|
uint32_t bg = (hold) ? state_tint(base, th, 24)
|
||||||
|
: (press ? state_tint(base, th, 12) : base);
|
||||||
|
if (disabled) bg = state_tint(base, th, 38); /* 禁用变淡(圆形底直接变灰) */
|
||||||
|
if (w->state & MEOW_WS_FOCUSED) bg = state_tint(base, th, 8); /* 焦点浅提亮 */
|
||||||
|
meow_gfx_fill_circle(cx, cy, r, bg);
|
||||||
|
if (w->icon_id >= 0)
|
||||||
|
meow_gfx_blit_icon_ink(w->icon_id, cx - MEOW_SB_ICON_SIZE / 2, inkh, fgc, cy);
|
||||||
|
if (w->state & MEOW_WS_FOCUSED)
|
||||||
|
meow_gfx_ring_arc(cx, cy, r - 1, 2, 0, 360, th->primary); /* 焦点圆形描边 */
|
||||||
|
break;
|
||||||
|
}
|
||||||
case W_SLIDER: {
|
case W_SLIDER: {
|
||||||
/* 语义色:轨道=onSurfaceVariant(dim)、填充=primary(accent)、滑块=fg(onSurface)。 */
|
/* 语义色:轨道=onSurfaceVariant(dim)、填充=primary(accent)、滑块=fg(onSurface)。 */
|
||||||
int trace_y = w->y + w->h / 2 - 3;
|
int trace_y = w->y + w->h / 2 - 3;
|
||||||
@@ -1606,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_x() >= vx0 && meow_input_touch_x() < vx0 + w->w &&
|
||||||
meow_input_touch_y() >= ry && meow_input_touch_y() < ry + item)
|
meow_input_touch_y() >= ry && meow_input_touch_y() < ry + item)
|
||||||
st |= MEOW_LISTVIEW_ST_PRESSED;
|
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) */
|
} else { /* ② 默认 renderer:固定 item 池(进入视口才 bind) */
|
||||||
for (int i = first; i < last; i++) {
|
for (int i = first; i < last; i++) {
|
||||||
@@ -1621,23 +1830,37 @@ void meow_widget_draw(meow_widget *w)
|
|||||||
int press = meow_input_touch_down() &&
|
int press = meow_input_touch_down() &&
|
||||||
meow_input_touch_x() >= vx0 && meow_input_touch_x() < vx0 + w->w &&
|
meow_input_touch_x() >= vx0 && meow_input_touch_x() < vx0 + w->w &&
|
||||||
meow_input_touch_y() >= ry && meow_input_touch_y() < ry + item;
|
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 段名 */
|
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;
|
int sty = ry + (item - thh) / 2;
|
||||||
if (it->title && sty >= vy0 && sty + thh <= vy1)
|
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;
|
int sy = ry + item - 1;
|
||||||
if (sy >= cl && sy < cr)
|
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)
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 选中/按压背景(primaryContainer / 半透明主题色按压层) */
|
/* 选中/按压背景(primaryContainer / 半透明主题色按压层) */
|
||||||
if (sel) meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->primaryContainer);
|
if (sel) meow_ui_fill_rect(rlx, cl, w->w, cr - cl, th->primaryContainer);
|
||||||
else if (press) meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->primary, 26);
|
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 textcol = sel ? th->onPrimaryContainer : th->onSurface;
|
||||||
uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
|
uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
|
||||||
|
|
||||||
@@ -1648,38 +1871,38 @@ void meow_widget_draw(meow_widget *w)
|
|||||||
: th->onSurfaceVariant);
|
: th->onSurfaceVariant);
|
||||||
int icy = ry + (item - isz) / 2;
|
int icy = ry + (item - isz) / 2;
|
||||||
if (icy + isz >= vy0 && icy < vy1)
|
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 或无副标题)单行居中);越界不画 */
|
/* 标题 + 副标题(双行居中;单行(lines==1 或无副标题)单行居中);越界不画 */
|
||||||
if (w->lines == 2 && it->subtitle && it->subtitle[0]) {
|
if (w->lines == 2 && it->subtitle && it->subtitle[0]) {
|
||||||
int ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
int ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
||||||
if (it->title && ty >= vy0 && ty + thh <= vy1)
|
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;
|
int sy = ty + thh + MEOW_LISTV_SUB_GAP;
|
||||||
if (sy >= vy0 && sy + thh <= vy1)
|
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 {
|
} else {
|
||||||
int ty = ry + (item - thh) / 2;
|
int ty = ry + (item - thh) / 2;
|
||||||
if (it->title && ty >= vy0 && ty + thh <= vy1)
|
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:箭头/开关/复选/⋯) */
|
/* 右尾件(show_right=1:箭头/开关/复选/⋯) */
|
||||||
if (w->show_right && it->right != MEOW_LIST_RIGHT_NONE) {
|
if (w->show_right && it->right != MEOW_LIST_RIGHT_NONE) {
|
||||||
int ax = w->x + w->w - pad - 24; /* 右缘动作槽(约 24px) */
|
int ax = rlx + w->w - pad - 24; /* 右缘动作槽(约 24px) */
|
||||||
if (ax >= op_start)
|
if (ax >= rop)
|
||||||
listv_draw_right(th, ax, ry, item, it->right, it->on, subcol);
|
listv_draw_right(th, ax, ry, item, it->right, it->on, subcol);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 分隔线(仅落在可见带内才画) */
|
/* 分隔线(仅落在可见带内才画) */
|
||||||
int sy = ry + item - 1;
|
int sy = ry + item - 1;
|
||||||
if (sy >= cl && sy < cr)
|
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 → 变暗) */
|
/* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */
|
||||||
if (!it->enabled)
|
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 {
|
} else {
|
||||||
@@ -1697,7 +1920,18 @@ void meow_widget_draw(meow_widget *w)
|
|||||||
int sel = (i == w->sel);
|
int sel = (i == w->sel);
|
||||||
uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface;
|
uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface;
|
||||||
uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
|
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
|
uint32_t iccol = sel ? th->onPrimaryContainer
|
||||||
@@ -1705,38 +1939,38 @@ void meow_widget_draw(meow_widget *w)
|
|||||||
: th->onSurfaceVariant);
|
: th->onSurfaceVariant);
|
||||||
int icy = ry + (item - isz) / 2;
|
int icy = ry + (item - isz) / 2;
|
||||||
if (icy + isz >= vy0 && icy < vy1) /* 图标不越视口边界才画(防泄漏) */
|
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;
|
int ty;
|
||||||
if (it.subtitle && it.subtitle[0]) {
|
if (it.subtitle && it.subtitle[0]) {
|
||||||
ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
||||||
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
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;
|
int sy = ty + thh + MEOW_LISTV_SUB_GAP;
|
||||||
if (sy >= vy0 && sy + thh <= vy1)
|
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 {
|
} else {
|
||||||
ty = ry + (item - thh) / 2;
|
ty = ry + (item - thh) / 2;
|
||||||
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
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) {
|
if (it.right != MEOW_LIST_RIGHT_NONE) {
|
||||||
int ax = w->x + w->w - pad - 24; /* 右缘动作槽(约 24px) */
|
int ax = rlx + w->w - pad - 24; /* 右缘动作槽(约 24px) */
|
||||||
if (ax >= op_start)
|
if (ax >= rop)
|
||||||
listv_draw_right(th, ax, ry, item, it.right, it.on, subcol);
|
listv_draw_right(th, ax, ry, item, it.right, it.on, subcol);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 分隔线(仅落在可见带内才画) */
|
/* 分隔线(仅落在可见带内才画) */
|
||||||
int sy = ry + item - 1;
|
int sy = ry + item - 1;
|
||||||
if (sy >= cl && sy < cr)
|
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 → 变暗) */
|
/* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */
|
||||||
if (!it.enabled)
|
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);
|
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
|
||||||
|
|||||||
+3
-1
@@ -58,14 +58,16 @@ static void umbrella_smoke(void)
|
|||||||
(void)&meow_ui_text_center; (void)&meow_ui_time_ms; (void)&meow_ui_effective_rate;
|
(void)&meow_ui_text_center; (void)&meow_ui_time_ms; (void)&meow_ui_effective_rate;
|
||||||
/* 图片层(§4) */
|
/* 图片层(§4) */
|
||||||
(void)&meow_img_load_file; (void)&meow_ui_img_cover; (void)&meow_img_w;
|
(void)&meow_img_load_file; (void)&meow_ui_img_cover; (void)&meow_img_w;
|
||||||
/* 主题(§8) */
|
/* 主题(§8 + §8.1.5 色板导出/注入) */
|
||||||
(void)&meow_theme_get; (void)&meow_theme_defaults; (void)&meow_ui_set_style;
|
(void)&meow_theme_get; (void)&meow_theme_defaults; (void)&meow_ui_set_style;
|
||||||
|
(void)&meow_theme_export; (void)&meow_theme_inject;
|
||||||
/* 输入法(§5.1) */
|
/* 输入法(§5.1) */
|
||||||
(void)&meow_ui_text_input; (void)&meow_ime_text; (void)&meow_ime_composition;
|
(void)&meow_ui_text_input; (void)&meow_ime_text; (void)&meow_ime_composition;
|
||||||
/* 控件层(§5/§5.8/§5.8.6/§5.5) */
|
/* 控件层(§5/§5.8/§5.8.6/§5.5) */
|
||||||
(void)&meow_btn_create; (void)&meow_widget_custom_create; (void)&meow_row_create;
|
(void)&meow_btn_create; (void)&meow_widget_custom_create; (void)&meow_row_create;
|
||||||
(void)&meow_topappbar_hit; (void)&meow_listview_create; (void)&meow_list_tap_at;
|
(void)&meow_topappbar_hit; (void)&meow_listview_create; (void)&meow_list_tap_at;
|
||||||
(void)&meow_widget_set_opts; (void)&meow_widget_state; (void)&meow_progress_set;
|
(void)&meow_widget_set_opts; (void)&meow_widget_state; (void)&meow_progress_set;
|
||||||
|
(void)&meow_iconbtn_create; /* M7.0a 缺口#2 */
|
||||||
/* 输入层/生命周期(§6)+ 长按(§6.3) */
|
/* 输入层/生命周期(§6)+ 长按(§6.3) */
|
||||||
(void)&meow_ui_run; (void)&meow_ui_swipe_hint; (void)&meow_ui_set_long_press_cb;
|
(void)&meow_ui_run; (void)&meow_ui_swipe_hint; (void)&meow_ui_set_long_press_cb;
|
||||||
/* Activity 栈(§6.1) */
|
/* Activity 栈(§6.1) */
|
||||||
|
|||||||
Reference in new issue
Block a user