sdk: 增量同步主仓新模块(box/anim/scroll/dispatch/listvirt/str/sb_icons/img)
subtree pull sdk 来自主仓 pub/sdk(主仓 commit 68ce46a 等)。新增: - 布局引擎 meow_ui_box / 动画基座 meow_ui_anim / 滚动物理 meow_ui_scroll - §6.4 事件分发形式化 / W_LISTVIEW 虚拟化多行 / meow_str 字符串资源 i18n - sb_icons 动作图标扩充 + topbar 位图图标 / meow_img_downscale - tools/ 图标生成脚本与 SVG 源 - 对应宿主 test_*.c 与 demo_box_scroll.c build_sdk.sh 冲突解析:保留公开仓自包含版(vendored meow_font + -Iinclude -I.), OBJS 手工并入 4 个新模块(meow_ui_box/anim/scroll/str)。保留 vendored meow_font.c/ meow_ipc.h/meow_font.h/meow_notif.h,剔除宿主测试二进制与编译产物。
No files matched your search
@@ -14,8 +14,8 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架
|
||||
rm -f meow_ui.a *.o
|
||||
|
||||
# meow_font.c = 预渲染位图字体运行时(自包含:只含 meow_font.h + 标准头;最小 zlib ABI 自声明,
|
||||
# 链接时用 sysroot 的 libz.a),随 SDK 一并 vendored。
|
||||
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"
|
||||
# 链接时用 sysroot 的 libz.a),随 SDK 一并 vendored。新模块(box/anim/scroll/str)随主仓增量并入。
|
||||
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"
|
||||
CFLAGS="-Os -static -Iinclude -I."
|
||||
for f in $OBJS; do
|
||||
echo "== cc $f =="
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/* demo_box_scroll.c — 宿主离屏 demo:§12.1 meow_box 布局 + §12.3 meow_scroll fling 端到端。
|
||||
* 渲染一屏「标题(固定) / 列表(weight 撑满) / 底栏(固定)」的 box 布局(cross 拉伸),
|
||||
* 再对列表跑一次 fling(速度跟踪→惯性滑动到停止处),并 dump PPM 供目检。
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 SDK 源 + 打桩 font/global):
|
||||
* gcc -o demo_box_scroll demo_box_scroll.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:MEOW_UI_DUMP=/tmp/demo_box.ppm ./demo_box_scroll
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define NROW 60
|
||||
#define ITEM 44
|
||||
static int src(int idx, char *label, int len, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (idx < 0 || idx >= NROW) return 0;
|
||||
snprintf(label, (size_t)len, "item %d", idx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
/* 离屏 dump */
|
||||
setenv("MEOW_UI_DUMP", "/tmp/demo_box.ppm", 1);
|
||||
meow_ui_opts o = { 480, 720 };
|
||||
if (meow_ui_init(&o) != 0) { printf("DEMO FAIL: init\n"); return 1; }
|
||||
const meow_theme *th = meow_theme_get();
|
||||
meow_ui_clear(th->bg);
|
||||
|
||||
/* ---- box 布局:标题(固定) / 列表(weight) / 底栏(固定),交叉轴拉伸 ---- */
|
||||
meow_box *bx = meow_box_new(MEOW_BOX_V);
|
||||
meow_box_set_rect(bx, 0, 0, 480, 720);
|
||||
meow_box_set_padding(bx, 12, 12, 12, 12);
|
||||
meow_box_set_gap(bx, 8);
|
||||
meow_box_set_align(bx, MEOW_BOX_ALIGN_START, MEOW_BOX_CROSS_STRETCH);
|
||||
|
||||
meow_widget *header = meow_btn_create(0, 0, 480, 44, "Header (fixed)");
|
||||
meow_widget *list = meow_list_create(0, 0, 480, 100, NROW, src, NULL);
|
||||
meow_list_set_item_h(list, ITEM);
|
||||
meow_widget *footer = meow_btn_create(0, 0, 480, 52, "Footer (fixed)");
|
||||
|
||||
meow_box_add(bx, header, MEOW_BOX_FIXED, 0);
|
||||
meow_box_add(bx, list, MEOW_BOX_WEIGHT, 1);
|
||||
meow_box_add(bx, footer, MEOW_BOX_FIXED, 0);
|
||||
meow_box_arrange(bx);
|
||||
|
||||
int hr[4], lr[4], fr[4];
|
||||
meow_widget_rect(header, hr);
|
||||
meow_widget_rect(list, lr);
|
||||
meow_widget_rect(footer, fr);
|
||||
printf("DEMO layout: header(%d,%d,%d,%d) list(%d,%d,%d,%d) footer(%d,%d,%d,%d)\n",
|
||||
hr[0], hr[1], hr[2], hr[3], lr[0], lr[1], lr[2], lr[3], fr[0], fr[1], fr[2], fr[3]);
|
||||
|
||||
meow_widget_draw(header);
|
||||
meow_widget_draw(list);
|
||||
meow_widget_draw(footer);
|
||||
meow_ui_flush(); /* 上屏并 MEOW_UI_DUMP 写 PPM *//* (host 离屏无 fb → 只落盘) */
|
||||
|
||||
/* ---- fling:对列表做一次惯性下滑(速度跟踪→减速→停)---- */
|
||||
meow_scroll *s = meow_scroll_wrap(list, 0);
|
||||
int maxs = meow_list_max_scroll(list);
|
||||
uint64_t t = meow_ui_time_ms();
|
||||
for (int i = 0; i < 14; i++) meow_scroll_track(s, 40, t + (uint64_t)i * 16);
|
||||
meow_scroll_fling(s);
|
||||
int guard = 0;
|
||||
while (meow_scroll_active(s) && guard++ < 300) { meow_anim_tick(); usleep(10000); }
|
||||
meow_anim_tick();
|
||||
int end = meow_scroll_offset(s);
|
||||
printf("DEMO fling: max_scroll=%d final=%d (active=%d)\n", maxs, end, meow_scroll_active(s));
|
||||
|
||||
meow_scroll_free(s);
|
||||
meow_box_free(bx);
|
||||
meow_widget_free(list);
|
||||
meow_widget_free(header);
|
||||
meow_widget_free(footer);
|
||||
meow_ui_shutdown();
|
||||
printf("DEMO OK\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* meow_ui_anim.h — SDK 动画基座 + UI 节拍器(§12.2:meow_anim / meow_anim_tick)。
|
||||
*
|
||||
* 定位:给应用提供**时间驱动 tween**(from/to/duration/easing)+ 完成/取消回调,并行多动画
|
||||
* (静态池≤MEOW_ANIM_MAX)+ 与脏区集成(动画帧只标动画影响区,不做整屏重绘)。
|
||||
*
|
||||
* 节拍:**挂既有 on_tick 机制**(meow_ui_run 主循环 / meow_ui_feed_tick 阻塞 IME 循环),
|
||||
* 无常驻高频轮询;无动画时 meow_anim_tick() 只做一个计数检查即返回(零开销)。
|
||||
*
|
||||
* 内存纪律:静态池(≤MEOW_ANIM_MAX),start/cancel/tick 全程零 malloc。
|
||||
*
|
||||
* 与 §7 的关系:§7(B1 最小可用)是「应用自己在 on_tick 里用 meow_ui_time_ms 做时间轴」;
|
||||
* 本模块把这件事收敛成声明式 tween 对象 + 结束回调,是 §7 的**正式基座**(应用可任选其一或混用)。
|
||||
*/
|
||||
#ifndef MEOW_UI_ANIM_H
|
||||
#define MEOW_UI_ANIM_H
|
||||
|
||||
#include "meow_ui_types.h" /* 基础类型(子头不引伞头,避免相对路径依赖) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- 缓动曲线(t∈[0,1] → 曲线化的进度;OVERSHOOT 在中后段可略>1、末点恰=1)---- */
|
||||
typedef enum {
|
||||
MEOW_EASE_LINEAR = 0,
|
||||
MEOW_EASE_OUT = 1, /* 快进慢停(decelerate) */
|
||||
MEOW_EASE_IN_OUT = 2, /* 慢入慢出 */
|
||||
MEOW_EASE_OVERSHOOT = 3, /* 越过目标再回稳(小回弹) */
|
||||
} meow_ease;
|
||||
|
||||
float meow_ease_apply(meow_ease e, float t);
|
||||
|
||||
/* ---- 动画对象(不透明句柄;静态池)---- */
|
||||
#define MEOW_ANIM_MAX 16
|
||||
|
||||
typedef struct meow_anim meow_anim;
|
||||
|
||||
/* 每帧回调:v = 当前插值值(from + (to-from)*ease(t))。app 在此更新状态并绘制。 */
|
||||
typedef void (*meow_anim_update_fn)(meow_anim *a, float v, void *ctx);
|
||||
/* 完成/取消回调:canceled=1=被取消,0=自然完成。 */
|
||||
typedef void (*meow_anim_done_fn)(meow_anim *a, int canceled, void *ctx);
|
||||
|
||||
typedef struct meow_anim_desc {
|
||||
float from;
|
||||
float to;
|
||||
uint32_t duration_ms; /* 时长(ms);0 视为瞬发(下一 tick 完成) */
|
||||
meow_ease ease;
|
||||
meow_anim_update_fn update; /* 可 NULL */
|
||||
meow_anim_done_fn done; /* 可 NULL */
|
||||
void *ctx;
|
||||
} meow_anim_desc;
|
||||
|
||||
/* 启动一个动画(空闲槽;池满返回 NULL)。start 即置 value=from 并回调一次 update(0)。 */
|
||||
meow_anim *meow_anim_start(const meow_anim_desc *d);
|
||||
/* 取消(触发 done(canceled=1),不再 update)。 */
|
||||
void meow_anim_cancel(meow_anim *a);
|
||||
/* 查询:仍在运行(value 未达终点)。 */
|
||||
int meow_anim_active(meow_anim *a);
|
||||
/* 查询:当前插值值。 */
|
||||
float meow_anim_value(meow_anim *a);
|
||||
|
||||
/* 与脏区集成:动画每 tick 只 flush 影响区(x,y,w,h;x<0 表示不自动 flush)。
|
||||
* app 的 update 回调通常也会用 meow_ui_flush_region 自刷;设了这个则 tick 自动刷一次,
|
||||
* 保证「动画帧只标脏动画影响区,不整屏」。 */
|
||||
void meow_anim_set_region(meow_anim *a, int x, int y, int w, int h);
|
||||
|
||||
/* ---- 节拍器(挂在 on_tick / meow_ui_feed_tick;无动画零开销)---- */
|
||||
void meow_anim_tick(void); /* 推进所有活动动画(完成/取消在内部处理) */
|
||||
unsigned meow_anim_count(void); /* 活动动画数(0=空闲) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* MEOW_UI_ANIM_H */
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* meow_ui_box.h — SDK 线性布局引擎(§12.1:meow_box)。
|
||||
*
|
||||
* 定位:既有的 meow_row/column/box(§5.8.5,W_LAYOUT)只支持「固定尺寸子项 + 居中」,
|
||||
* 本模块是其**独立增强**:竖直/水平容器,子项 = 固定尺寸 | weight 权重 | wrap(内容测量),
|
||||
* 支持 margin/padding / 主轴对齐(start/center/end/space-between/around/evenly)/ 交叉轴对齐
|
||||
* (start/center/end/stretch)/ 嵌套。
|
||||
*
|
||||
* 与绝对定位完全兼容:meow_box 是**纯布局计算器**,不是控件,不参与 z 序注册表/命中/绘制;
|
||||
* arrange 把每个子控件的 x/y/w/h 写进现有 meow_widget(用新增 meow_widget_set_rect),
|
||||
* 此后照常 draw/hit/flush_region/owner 闸门——与既有控件绝对定位零冲突。
|
||||
*
|
||||
* 与视图框架集成(opt-in):meow_box 可作 meow_view 的布局提供者(meow_view_set_layout);
|
||||
* view 不设就保持现有绝对坐标行为(§12.1.4)。
|
||||
*
|
||||
* 内存纪律:静态池 + 定长 items 数组(≥MEOW_BOX_MAX_CHILD),热路径(arrange)零 malloc;
|
||||
* 嵌套用深度≤MEOW_BOX_MAX_DEPTH 的静态栈单遍求解,无递归爆炸。
|
||||
*/
|
||||
#ifndef MEOW_UI_BOX_H
|
||||
#define MEOW_UI_BOX_H
|
||||
|
||||
#include "meow_ui_types.h" /* meow_widget 前向 + 基础类型(子头不引伞头,避免相对路径依赖) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct meow_box meow_box;
|
||||
|
||||
/* 主轴方向 */
|
||||
typedef enum {
|
||||
MEOW_BOX_H = 0, /* 横向(row) */
|
||||
MEOW_BOX_V = 1, /* 纵向(column) */
|
||||
} meow_box_dir;
|
||||
|
||||
/* 子项主轴尺寸策略 */
|
||||
typedef enum {
|
||||
MEOW_BOX_FIXED = 0, /* 固定尺寸:内容主轴向取控件当前 w/h */
|
||||
MEOW_BOX_WEIGHT = 1, /* 权重:均分剩余主轴空间(weight>0) */
|
||||
MEOW_BOX_WRAP = 2, /* wrap:按内容测量(min/preferred 或 measure 回调) */
|
||||
} meow_box_size;
|
||||
|
||||
/* 主轴对齐 */
|
||||
typedef enum {
|
||||
MEOW_BOX_ALIGN_START = 0,
|
||||
MEOW_BOX_ALIGN_CENTER = 1,
|
||||
MEOW_BOX_ALIGN_END = 2,
|
||||
MEOW_BOX_ALIGN_SPACE_BETWEEN = 3,
|
||||
MEOW_BOX_ALIGN_SPACE_AROUND = 4,
|
||||
MEOW_BOX_ALIGN_SPACE_EVENLY = 5,
|
||||
} meow_box_align;
|
||||
|
||||
/* 交叉轴对齐 */
|
||||
typedef enum {
|
||||
MEOW_BOX_CROSS_START = 0,
|
||||
MEOW_BOX_CROSS_CENTER = 1,
|
||||
MEOW_BOX_CROSS_END = 2,
|
||||
MEOW_BOX_CROSS_STRETCH = 3,
|
||||
} meow_box_cross;
|
||||
|
||||
/* 内容测量回调(size==MEOW_BOX_WRAP 时用;可 NULL):给出主轴向 min/preferred 尺寸。
|
||||
* ctx = meow_box_add_ex 的 mctx。 */
|
||||
typedef void (*meow_box_measure_fn)(void *ctx, int *min_main, int *pref_main);
|
||||
|
||||
/* 子项描述(add/add_ex/add_sub 提供;app 只读)。w 与 sub 二选一。 */
|
||||
typedef struct meow_box_item {
|
||||
meow_widget *w; /* 子控件(sub==NULL 时用) */
|
||||
meow_box *sub; /* 嵌套 box(w==NULL 时用) */
|
||||
int size; /* MEOW_BOX_* 尺寸策略 */
|
||||
int weight; /* size==WEIGHT 时>0 */
|
||||
int min; /* size==WRAP 的主轴下限 */
|
||||
int preferred;/* size==WRAP 的主轴目标 */
|
||||
meow_box_measure_fn measure; /* size==WRAP 内容测量(可 NULL) */
|
||||
void *mctx;
|
||||
} meow_box_item;
|
||||
|
||||
#define MEOW_BOX_MAX_CHILD 24 /* 每容器子项上限(静态数组) */
|
||||
#define MEOW_BOX_MAX_DEPTH 16 /* 嵌套深度上限(静态求解栈) */
|
||||
|
||||
/* ---- 生命周期(静态池;new 冷路径,arrange 热路径零 malloc)----
|
||||
* 注:命名用 meow_box_new(meow_box_create 已被 §5.8.5 的 W_LAYOUT box 占用)。 */
|
||||
meow_box *meow_box_new(meow_box_dir dir);
|
||||
void meow_box_free(meow_box *b);
|
||||
|
||||
/* ---- 容器配置 ---- */
|
||||
void meow_box_set_rect(meow_box *b, int x, int y, int w, int h); /* 容器矩形(绝对坐标) */
|
||||
void meow_box_set_padding(meow_box *b, int l, int t, int r, int btm);
|
||||
void meow_box_set_gap(meow_box *b, int gap);
|
||||
void meow_box_set_align(meow_box *b, int main_align, int cross_align);
|
||||
|
||||
/* ---- 子项添加(返回 0=成功;越界/无效=负值,不清空既有子项)---- */
|
||||
int meow_box_add(meow_box *b, meow_widget *w, int size, int weight);
|
||||
/* add_ex:WRAP 用 min/preferred(主轴向)+ 可选内容测量回调。 */
|
||||
int meow_box_add_ex(meow_box *b, meow_widget *w, int size, int weight,
|
||||
int min, int preferred, meow_box_measure_fn m, void *mctx);
|
||||
/* 嵌套子 box(parent 布局会为 sub 分配矩形并递归 arrange)。 */
|
||||
int meow_box_add_sub(meow_box *b, meow_box *sub, int size, int weight);
|
||||
|
||||
/* ---- 求解(单遍;深度≤MEOW_BOX_MAX_DEPTH 静态栈,无递归)----
|
||||
* 把每个子控件 x/y/w/h 写进其 meow_widget(meow_widget_set_rect);
|
||||
* 嵌套 sub 的矩形写进 sub 并递归。子控件的 margin(meow_widget_opts.margin_x/y)参与排布。 */
|
||||
void meow_box_arrange(meow_box *b);
|
||||
|
||||
/* ---- 查询 ---- */
|
||||
int meow_box_count(const meow_box *b);
|
||||
meow_widget *meow_box_widget_at(const meow_box *b, int idx); /* 子项控件(无则 NULL) */
|
||||
meow_box *meow_box_sub_at(const meow_box *b, int idx); /* 子项嵌套 box(无则 NULL) */
|
||||
void meow_box_rect(const meow_box *b, int out[4]); /* x,y,w,h */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* MEOW_UI_BOX_H */
|
||||
@@ -26,6 +26,9 @@ int meow_img_w(meow_img *img);
|
||||
int meow_img_h(meow_img *img);
|
||||
|
||||
void meow_ui_img(int x, int y, meow_img *img); /* 原始尺寸 */
|
||||
/* 内存瘦身:最近邻等比缩放到最长边 ≤maxdim,返回新图;原图已 ≤maxdim 返回 NULL。
|
||||
* 调用方负责 free 旧图与返回值(若返回)。封面/背景大图驻留内存瘦身用。 */
|
||||
meow_img *meow_img_downscale(meow_img *img, int maxdim);
|
||||
void meow_ui_img_scaled(int x, int y, int w, int h, meow_img *img); /* 最近邻缩放 */
|
||||
void meow_ui_img_alpha(int x, int y, int w, int h, meow_img *img, int alpha);
|
||||
void meow_ui_img_cover(int x, int y, int size, meow_img *img); /* 居中裁方 → size×size */
|
||||
|
||||
@@ -58,6 +58,60 @@ void meow_ui_set_long_press_cb(meow_long_press_fn cb); /* 注册/更换回调
|
||||
void meow_ui_set_long_press_ms(int ms); /* 长按阈值(ms);<=0 恢复默认 500 */
|
||||
int meow_ui_long_press_ms(void); /* 当前阈值(默认 500) */
|
||||
|
||||
/* ============================================================ */
|
||||
/* §6.4 事件分发形式化(2026-09-02)—— 统一「命中测试 / 捕获 / 消费 / 层间隔离 / 焦点」
|
||||
*
|
||||
* 背景:此前命中/捕获/消费/层间隔离各处 ad-hoc 补丁(模式层穿透、弹层叠黑、list 焦点残留、
|
||||
* 同坐标二次消费)。本节把三层模型的形式化落地为**单一仲裁者**(SDK 内部实现,见
|
||||
* devdocs/plan/SDK_VIEW_FRAMEWORK_DESIGN.md §A.7 命中分流 ③②① + 本 SDK 内部拓扑):
|
||||
*
|
||||
* 触摸沿 z 序**自顶向下**命中:模态层永远先审;某一层命中即消费(返回 consumed 才继续下探)。
|
||||
* 某一次 touch-down 命中后,捕获直到 up;期间 move/up 只发给捕获目标(防滑出误触发下层)。
|
||||
* 模态层存在时下层**完全收不到**事件——未命中面板的事件由模态背板消费,不透传。
|
||||
*
|
||||
* 兼容:**只追加、不删改既有签名**;未调用本节 API 的 App 行为与之前完全一致(命中/捕获仍
|
||||
* 由 App 在 on_tap/on_touch_* 里自理)。hello/files/ncm/reader 等既有 App 只把 SDK 提供的
|
||||
* 语义用于收敛 bug,不改正常交互。
|
||||
*/
|
||||
/* ---- 模态作用域(modal scope)----
|
||||
* 一组自顶向下优先的叠层。dialog/sheet/App 自绘模态在「打开时 begin、关闭时 end」。
|
||||
* 作用域存在(meow_ui_modal_active / depth>0)时,下层(view/widget)完全收不到触摸;
|
||||
* 未命中面板的事件由背板消费(meow_ui_hit_topdown 恒返回模态哨兵)。支持嵌套(弹层叠开,
|
||||
* depth 递增)。begin 返回作用域句柄;end 销栈(幂等,NULL 忽略)。 */
|
||||
typedef struct meow_ui_modal meow_ui_modal;
|
||||
meow_ui_modal *meow_ui_modal_begin(void); /* 打开模态层入栈;返回句柄(NULL=栈满/上限) */
|
||||
void meow_ui_modal_end(meow_ui_modal *m); /* 关闭:弹到 m(含 m,一次性关上嵌套) */
|
||||
int meow_ui_modal_active(void); /* 当前是否有任一模态作用域打开(depth>0) */
|
||||
int meow_ui_modal_depth(void); /* 模态栈深(弹层叠开计数) */
|
||||
|
||||
/* ---- 自上而下命中 ---- */
|
||||
/* 沿 z 序(模态最高优先,其次注册控件 z 降序;均含 owner==active+enable 过滤)返回第一个
|
||||
* 命中绘制物(meow_widget* 或 meow_view*);无命中返回 NULL。模态存在时恒返回模态哨兵
|
||||
* (背板消费,不透传)。供 gesture_begin/focus_tap/App 自绘模态命中统一使用。 */
|
||||
void *meow_ui_hit_topdown(int x, int y);
|
||||
/* meow_widget_hit 的公共别名(owner==active + enable + 矩形/自定义命中回调一致)。 */
|
||||
int meow_ui_widget_hit(meow_widget *w, int x, int y);
|
||||
|
||||
/* ---- 手势捕获(gesture capture)----
|
||||
* touch-down 命中目标捕获整个手势直到 up。捕获期 meow_ui_gesture_owner 恒返回捕获目标;
|
||||
* 后续 move/up 只发给它(防滑出后误触发下层)。begin 返回捕获目标(未命中=NULL 不捕获);
|
||||
* end/抬手释放。 */
|
||||
void *meow_ui_gesture_begin(int x, int y); /* 命中+捕获;返回捕获目标(模态存在时=模态哨兵) */
|
||||
void *meow_ui_gesture_owner(void); /* 当前手势捕获目标(无=NULL) */
|
||||
void meow_ui_gesture_end(void); /* 手势结束(抬手/TAP/SWIPE 边沿):释放捕获 */
|
||||
|
||||
/* ---- 唯一焦点(focus,root 根治 list 焦点残留 / 滚动容器间转移)----
|
||||
* 焦点持有者唯一。点非焦点区域 → 释放焦点并触发 lost 回调;滚动容器(list/scroll)在
|
||||
* 获得焦点时 acquire,点外由 meow_ui_focus_tap 统一释放——「命中非 focus 区域 → 释放并通知」。
|
||||
* 转移规则:某滚动容器 acquire 后,随后的纵向 swipe / 拖动滚该容器;任何落在该容器外的
|
||||
* 触摸(hit != focus owner)释放焦点,回退到页面级滚动。 */
|
||||
typedef void (*meow_ui_focus_lost_fn)(void *owner, void *ctx); /* 焦点被释放/转移时回调 */
|
||||
void meow_ui_focus_set_lost_cb(meow_ui_focus_lost_fn cb, void *ctx); /* 注册/更换焦点丢失回调 */
|
||||
void *meow_ui_focus_owner(void); /* 当前唯一焦点持有者(无=NULL) */
|
||||
int meow_ui_focus_acquire(void *owner); /* 设为唯一焦点(覆盖旧焦点并触发其 lost) */
|
||||
int meow_ui_focus_release(void *owner); /* owner==当前焦点才释放+通知(NULL=任意) */
|
||||
void meow_ui_focus_tap(int x, int y); /* TAP 落点:命中焦点持有者→保位;否则释放+通知 */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* meow_ui_scroll.h — SDK 滚动物理(§12.3:meow_scroll)。
|
||||
*
|
||||
* 定位:给既有滚动容器(W_LIST / W_LISTVIEW / LazyColumn / LazyRow)加**可惯性滚动**的
|
||||
* **物理层**——速度跟踪(最近 N 个采样加权)、松手 fling(减速度曲线,用 §12.2 动画基座驱动)、
|
||||
* 边界过冲 clamp + 简单回弹(不做复杂 spring)、滚动停止可选 snap。**跟手拖动行为保持现状
|
||||
* 不变**(track 内部调既有 set_scroll,clamp 一致)。
|
||||
*
|
||||
* 设计:meow_scroll 是**滚动会话**,包住一个滚动控件(或任意的 set/max/get/redraw 回调)。
|
||||
* 拖动态:app 在 on_touch_move 里把位移喂给 meow_scroll_track(dy, t)(等价于 app 直接调
|
||||
* meow_list_scroll_by,且额外记录速度采样);松手:app 调 meow_scroll_fling(s),内部用
|
||||
* 加权速度采样得出初速 → 起一条 ease-out 动画推进位移 → 每帧只 redraw(flush 列表视口区)。
|
||||
*
|
||||
* 内存纪律:静态池(应用侧按需创建会话),track/fling/tick 全程零 malloc;无动画时
|
||||
* meow_scroll 不参与节拍(fling 由 meow_anim_tick 驱动,活动计数为 0 则零开销)。
|
||||
*/
|
||||
#ifndef MEOW_UI_SCROLL_H
|
||||
#define MEOW_UI_SCROLL_H
|
||||
|
||||
#include "meow_ui_types.h" /* meow_widget 前向 + 基础类型(子头不引伞头,避免相对路径依赖) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct meow_scroll meow_scroll;
|
||||
|
||||
/* ---- 通用回调(wrap 之外的自定义滚动源用)---- */
|
||||
typedef void (*meow_scroll_set_fn)(void *ctx, int off);
|
||||
typedef int (*meow_scroll_max_fn)(void *ctx);
|
||||
typedef int (*meow_scroll_get_fn)(void *ctx);
|
||||
typedef void (*meow_scroll_redraw_fn)(void *ctx); /* 只刷滚动视口区(不整屏) */
|
||||
|
||||
/* 创建会话:app 提供 set/max/get/redraw。返回 NULL=池满。 */
|
||||
meow_scroll *meow_scroll_create(meow_scroll_set_fn set, meow_scroll_max_fn max,
|
||||
meow_scroll_get_fn get, meow_scroll_redraw_fn redraw,
|
||||
void *ctx);
|
||||
/* 便捷:包一个 meow_widget 列表(is_listview:1=W_LISTVIEW / 0=W_LIST 或 lazycolumn/row)。
|
||||
* redraw 只 flush 该控件矩形(视口区)。 */
|
||||
meow_scroll *meow_scroll_wrap(meow_widget *list, int is_listview);
|
||||
void meow_scroll_free(meow_scroll *s);
|
||||
|
||||
/* 拖动跟手 + 速度采样:dy=本次位移(px);t_ms=采样时刻(单调毫秒)。
|
||||
* 内部 set_scroll(pos+dy)(clamp 到 [0,max],与 app 直接 scroll_by 行为一致)。 */
|
||||
void meow_scroll_track(meow_scroll *s, int dy, uint64_t t_ms);
|
||||
|
||||
/* 松手 fling:内部用「最近 N 个采样加权」算初速,起减速度曲线动画推进位移;
|
||||
* 边界过冲 clamp + 简单回弹;每帧只 redraw 视口区。停止后可选 snap。 */
|
||||
void meow_scroll_fling(meow_scroll *s);
|
||||
/* 立即停止(取消 fling/回弹/snap,停在当前 offset)。 */
|
||||
void meow_scroll_stop(meow_scroll *s);
|
||||
|
||||
/* 当前加权初速(px/s;为 fling 内部所用,供 app 读取/调试)。 */
|
||||
int meow_scroll_velocity(meow_scroll *s);
|
||||
|
||||
/* 查询/配置 */
|
||||
int meow_scroll_active(meow_scroll *s); /* 是否有活动 fling/回弹/snap */
|
||||
int meow_scroll_offset(meow_scroll *s); /* 当前 scroll(get) */
|
||||
void meow_scroll_set_snap(meow_scroll *s, int step); /* >0=fling 停止后吸附到 step 倍数 */
|
||||
void meow_scroll_set_friction(meow_scroll *s, int tau_ms); /* 摩擦时间常数(默认 180ms) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* MEOW_UI_SCROLL_H */
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* meow_ui_str.h — SDK 字符串资源(i18n 雏形,§13.meow_str_*)。
|
||||
*
|
||||
* 定位:公开 SDK 前,系统/App UI 文案不得再硬编码在 C 源码里——第三方开发者必须能按
|
||||
* 系统 locale 拿到对应语言文案,且能在不改 SDK 二进制的情况下用 TF 卡覆盖/追加。
|
||||
* 本头声明字符串资源框架的**对外 API**(追加式,既有 App 零改动)。
|
||||
*
|
||||
* 单表双语内置(zh_cn = 内置默认 / en_us),四层取值优先级(见 meow_ui_str.c):
|
||||
* meow_str(id):按当前 locale 取串;override 覆盖表 > 内嵌当前语言 > 内嵌内置默认(zh_cn);
|
||||
* 全程不可得则原样返回 id(调用方永不见 NULL)。
|
||||
* meow_strf(out,outsz,id,...):取串后按 snprintf 语义格式化进调用方缓冲(自带 NUL 终止)。
|
||||
* meow_str_set_locale(locale):切当前 locale("zh_cn"|"en_us"),即时生效 + 重载 TF 覆盖表
|
||||
* + best-effort 持久化到板载 settings.conf 的 ui.locale。
|
||||
*
|
||||
* 依赖:纯 libc(stdio/stdlib/string/stdarg),宿主 gcc 可直接编译单测;不依赖其它 SDK 对象。
|
||||
* - locale 初值:启动首次调用惰性读系统设置 settings.conf 的 `ui.locale`,缺省 "zh_cn"。
|
||||
* - TF 覆盖表:`<MEOW_DATA_DIR>/../strings/<locale>.txt`(即 MeowData/<pkg>/strings/,见
|
||||
* §9.2 数据目录约定),`id=text` 行格式 UTF-8;无卡/无文件/不含该 id → 内嵌表兜底。
|
||||
* - 热路径(meow_str)零 malloc:内嵌为编译期静态表,覆盖表为启动加载的静态数组。
|
||||
*
|
||||
* 边界(§13 明示不做,写清理由):
|
||||
* 不做复数规则 / BiDi / RTL / 动态字体回退——当前形态是「按 locale 取整串」的 i18n 雏形,
|
||||
* 面向 480×720 单行/短文案 UI;复数变体需占位符格式化协议,BiDi/RTL 需文本整形 + 布局镜像,
|
||||
* 两者都超出「字符串资源外置」的最小必要范围,留待后续批次。
|
||||
*/
|
||||
#ifndef MEOW_UI_STR_H
|
||||
#define MEOW_UI_STR_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 按当前 locale 取 id 对应的文案。id 形如 `域.名`(common.ok / player.pause)。
|
||||
* 返回静态字符串(内嵌表或覆盖表;永不返回 NULL)。未知 id 原样返回 id。 */
|
||||
const char *meow_str(const char *id);
|
||||
|
||||
/* 取 id 文案后按 snprintf 语义格式化进 out(outsz 为缓冲字节数,含 NUL)。
|
||||
* 返回 snprintf 样式的「应写入字符数」(不截断);out==NULL 或 outsz<=0 返回 -1。
|
||||
* 设计理由:调用方自带缓冲,避免静态/线程局部缓冲在 IME/模态等重入路径的竞态,
|
||||
* 且与 SDK 既有 snprintf 习惯一致(无格式参数时等价深拷贝 meow_str 结果)。 */
|
||||
int meow_strf(char *out, int outsz, const char *id, ...);
|
||||
|
||||
/* 设当前 locale(识别 "zh_cn"|"en_us";任意串作为覆盖表文件名 + 内嵌按非 en 处理为内置默认)。
|
||||
* 即时生效(下次 meow_str 即按新 locale);重载该 locale 的 TF 覆盖表;
|
||||
* best-effort 持久化到 settings.conf 的 ui.locale(失败静默,运行时仍切换)。
|
||||
* 须在 App 主线程(UI 线程)调用,避免与 meow_str 并发写覆盖表。 */
|
||||
void meow_str_set_locale(const char *locale);
|
||||
|
||||
/* 当前生效 locale(内部缓冲;未初始化时读 settings 后返回,默认 "zh_cn")。 */
|
||||
const char *meow_str_locale(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* MEOW_UI_STR_H */
|
||||
@@ -33,6 +33,8 @@ void meow_widget_free(meow_widget *w);
|
||||
|
||||
void meow_widget_draw(meow_widget *w); /* 每次 flush 前调用 */
|
||||
int meow_widget_hit(meow_widget *w, int x, int y); /* 点是否落在控件内 */
|
||||
/* 写控件矩形(§12.1 meow_box 布局引擎用;也可供 app 布局器用)。只设字段不标脏。 */
|
||||
void meow_widget_set_rect(meow_widget *w, int x, int y, int wd, int ht);
|
||||
|
||||
void meow_slider_set(meow_widget *s, int val);
|
||||
int meow_slider_get(meow_widget *s);
|
||||
@@ -184,6 +186,29 @@ void meow_topappbar_set_back(meow_widget *w, int on); /* 1=显示返回箭头
|
||||
int meow_topappbar_add_action(meow_widget *w, const char *label); /* 返回动作下标 */
|
||||
#define MEOW_TOPBAR_BACK (-2)
|
||||
int meow_topappbar_hit(meow_widget *w, int x, int y); /* MEOW_TOPBAR_BACK=返回 idx>=0=动作 -1=无 */
|
||||
/* TopAppBar 位图动作图标(sb_icons[],Material Symbols Rounded 32x32 单通道 alpha,Apache-2.0)。
|
||||
* 动作项默认文本(add_action);经本 API 把某项改成位图:绘制取主题 onSurface(按压淡色),
|
||||
* 与状态栏同源 blit(ink 垂直对齐栏中点)。返回 0=成功;NULL/非 topbar/越界 idx/非法 icon=负值。 */
|
||||
int meow_topappbar_set_item_icon(meow_widget *w, int idx, int icon_idx);
|
||||
/* 位图动作图标索引(= sb_icons[] 下标,与 SDK sb_icons.h 的 SB_I_* 同值)。
|
||||
* 状态栏占用 0-14(不动);动作图标从 15 起连续追加,勿改序(须与 sb_icons.h 表序一致)。 */
|
||||
enum {
|
||||
MEOW_ICON_SETTINGS = 15,
|
||||
MEOW_ICON_REFRESH = 16,
|
||||
MEOW_ICON_SEARCH = 17,
|
||||
MEOW_ICON_PLAY_ARROW = 18,
|
||||
MEOW_ICON_PAUSE = 19,
|
||||
MEOW_ICON_SKIP_NEXT = 20,
|
||||
MEOW_ICON_SKIP_PREV = 21,
|
||||
MEOW_ICON_ARROW_BACK = 22,
|
||||
MEOW_ICON_CLOSE = 23,
|
||||
MEOW_ICON_ADD = 24,
|
||||
MEOW_ICON_CHECK = 25,
|
||||
MEOW_ICON_LYRICS = 26,
|
||||
MEOW_ICON_QUEUE_MUSIC = 27,
|
||||
MEOW_ICON_DOWNLOAD = 28,
|
||||
MEOW_ICON_HEART = 29,
|
||||
};
|
||||
|
||||
/* ---- NavigationBar(底部导航栏)---- */
|
||||
meow_widget *meow_navigationbar_create(int x, int y, int w, int h);
|
||||
@@ -291,6 +316,65 @@ void meow_listview_set_on_tap(meow_widget *lv, meow_listview_tap_fn cb, void *ct
|
||||
/* 命中行:置选中 + 回调(若设)+ 返回 idx;未命中返回 -1。 */
|
||||
int meow_listview_tap_at(meow_widget *lv, int x, int y);
|
||||
|
||||
/* ============================================================ */
|
||||
/* §5.5 虚拟化列表(W_LISTVIEW 扩展层,2026-09-02):固定 item 池 + 多行富 item + 完全自绘
|
||||
* ------------------------------------------------------------
|
||||
* 在既有 W_LISTVIEW(src2 每帧按需取行,行为不变)之上做**虚拟化层**(API 追加式,旧 API 保留):
|
||||
* - 数据源:count_fn() 返回总行数(任意长度,≥65535 不常驻 item 对象);
|
||||
* bind_fn(index, item, ctx) 仅在行「进入视口」(绑定到固定池槽)时调用一次;
|
||||
* - 固定 item 池:池容量 = 视口行数 + 2 静态槽(有界,见实现 MEOW_LV_POOL_MAX);
|
||||
* 行离开视口不销毁,回滚不重绑(同一 index 命中同一槽 → bind 不重调);
|
||||
* - 滚动只改 viewport 偏移(复用既有 set_scroll/max_scroll),不重绑未进视口的项;
|
||||
* - 多行富 item:主标题+副标题(第二行小字)+左图标/缩略图+右尾件(chevron/开关/复选/⋯);
|
||||
* 行高可配(set_item_h 精确 px / set_item_tier 单/双行档位默认高);
|
||||
* - 默认 renderer 用主题语义色绘制富 item;App 也可经 set_item_draw 完全自绘。
|
||||
* 段头:bind 回调把 item->section=1(title=段名)→ 段头占位行(非粘性),默认 renderer 特殊绘制。
|
||||
* 空态:set_empty_text 设置文案,count==0 时视口内居中画出。
|
||||
* 兼容:既有 meow_listview_*(src2 模式)全部保留不动;本层为叠加,src2==NULL 时走既有路径。 */
|
||||
|
||||
/* 虚拟化行 item(bind 回调填充;SDK 固定池持有;不常驻 src2 那样的全量行对象)。 */
|
||||
typedef struct meow_lv_item {
|
||||
const char *title; /* 主标题(必填,NULL 视为空行) */
|
||||
const char *subtitle; /* 副标题(第二行小字),可空 */
|
||||
const meow_img *icon; /* 可选:左图标/缩略图(icon_kind==MEOW_LIST_ICON_IMG 时用) */
|
||||
int icon_kind; /* MEOW_LIST_ICON_*(NONE=无图标) */
|
||||
int right; /* MEOW_LIST_RIGHT_* 右尾件 */
|
||||
int on; /* right==SWITCH 时开关态(0/1) */
|
||||
int enabled; /* 1=可交互;0=变淡不响应 */
|
||||
int section; /* 1=段头占位行(title=段名;默认 renderer 特殊绘制,非粘性) */
|
||||
void *user; /* App 私有(跨 bind 保存业务态/图/index;SDK 不碰) */
|
||||
} meow_lv_item;
|
||||
|
||||
/* 虚拟化数据源:count 返回当前行数(>=0,任意长);bind 在行进入视口时被调一次填充 item。
|
||||
* 二者均不常驻 item 对象;count 每帧实时查(支持搜索过滤/懒加载动态长度)。 */
|
||||
typedef int (*meow_listview_count_fn)(void *ctx);
|
||||
typedef void (*meow_listview_bind_fn)(int index, meow_lv_item *item, void *ctx);
|
||||
|
||||
/* 完全自绘回调(覆盖默认 renderer):idx=行号;x,y,w,h=行视口矩形(已裁剪到列表视口);
|
||||
* state=MEOW_LISTVIEW_ST_* 状态位(选中/按压/禁用组合)。App 用 §3 绘制 API 自绘整行。 */
|
||||
typedef void (*meow_listview_draw_fn)(int idx, int x, int y, int w, int h,
|
||||
int state, void *ctx);
|
||||
/* state 位(set_item_draw 的 draw 回调收到;选中/按压/禁用组合,供自绘整行)。 */
|
||||
#define MEOW_LISTVIEW_ST_SELECTED (1u<<0)
|
||||
#define MEOW_LISTVIEW_ST_PRESSED (1u<<1)
|
||||
#define MEOW_LISTVIEW_ST_DISABLED (1u<<2)
|
||||
|
||||
/* 挂虚拟化数据源:切到 pool 模式(count_fn/bind_fn 均非 NULL 才生效;否则回退 src2 模式)。
|
||||
* 返回 0=成功;NULL 回调/无效=负值。挂虚拟源后既有 sel/滚动/命中/焦点照常。 */
|
||||
int meow_listview_set_vsource(meow_widget *lv, meow_listview_count_fn count,
|
||||
meow_listview_bind_fn bind, void *ctx);
|
||||
/* 默认 renderer 配置:lines=1/2(单行/双行档位,影响副标题显示与行高默认);
|
||||
* show_icon=1 画左图标/缩略图;show_right=1 画右尾件。返回 0=成功。 */
|
||||
int meow_listview_set_item_renderer(meow_widget *lv, int lines, int show_icon,
|
||||
int show_right);
|
||||
/* 行档位默认行高:lines=1→48(单行)、2→56(双行);与 set_item_h(精确 px)互补。 */
|
||||
int meow_listview_set_item_tier(meow_widget *lv, int lines);
|
||||
/* 完全自绘:设 draw_item 回调(覆盖默认 renderer);NULL=用默认 renderer。返回 0=成功。 */
|
||||
int meow_listview_set_item_draw(meow_widget *lv, meow_listview_draw_fn draw,
|
||||
void *ctx);
|
||||
/* 空态占位:count==0 时视口内居中画 empty_text(NULL 不画)。返回 0=成功。 */
|
||||
int meow_listview_set_empty_text(meow_widget *lv, const char *text);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
* include/meow_ui_config.h 应用配置读写(§9.2)
|
||||
* include/meow_ui_font.h 字体列表(§5.7)+ 字体分层(§10)
|
||||
* include/meow_ui_audio.h 独占音频客户端(§11)
|
||||
* include/meow_ui_box.h 线性布局引擎(§12.1:meow_box,固定/weight/wrap/对齐/嵌套)
|
||||
* include/meow_ui_anim.h 动画基座+UI 节拍器(§12.2:tween/easing/池+脏区联动)
|
||||
* include/meow_ui_scroll.h 滚动物理(§12.3:速度跟踪/fling/边界回弹/snap)
|
||||
* include/meow_ui_str.h 字符串资源/i18n(§13:meow_str/meow_strf/meow_str_set_locale)
|
||||
*
|
||||
* 其余模块头(非伞头、App 按需显式 include,均保持不动):
|
||||
* meow_ui_view.h / meow_ui_nav.h / meow_ui_media.h / meow_ui_sys.h / meow_ui_vol.h
|
||||
@@ -63,5 +67,9 @@
|
||||
#include "include/meow_ui_config.h"
|
||||
#include "include/meow_ui_font.h"
|
||||
#include "include/meow_ui_audio.h"
|
||||
#include "include/meow_ui_box.h"
|
||||
#include "include/meow_ui_anim.h"
|
||||
#include "include/meow_ui_scroll.h"
|
||||
#include "include/meow_ui_str.h"
|
||||
|
||||
#endif /* MEOW_UI_H */
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* meow_ui_anim.c — 动画基座 + UI 节拍器(§12.2)。
|
||||
*
|
||||
* 时间驱动 tween:start 记录起始时刻(meow_ui_time_ms,单调),tick 按经过时间求 t,
|
||||
* 代入 ease 得 v = from + (to-from)*ease(t),回调 update;t 到 1 → 回调 done(canceled=0) 并回收。
|
||||
* 取消 → 回调 done(canceled=1) 并回收。静态池(≤MEOW_ANIM_MAX),热路径零 malloc。
|
||||
* 无动画时 meow_anim_tick() 仅查活动计数即返回(零开销)。
|
||||
*
|
||||
* 节拍挂接:由 meow_ui_run 主循环(on_tick 之后)与 meow_ui_feed_tick(阻塞 IME 循环)调用,
|
||||
* 不引入常驻高频轮询。modal/dialog 阻塞循环不喂 tick(动画暂停,符合「模态期不做动画」惯例)。
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "include/meow_ui_anim.h"
|
||||
#include <string.h>
|
||||
|
||||
struct meow_anim {
|
||||
int active;
|
||||
float from, to;
|
||||
uint32_t duration_ms;
|
||||
meow_ease ease;
|
||||
uint64_t start_ms;
|
||||
float value;
|
||||
int region_set;
|
||||
int rx, ry, rw, rh;
|
||||
meow_anim_update_fn update;
|
||||
meow_anim_done_fn done;
|
||||
void *ctx;
|
||||
};
|
||||
|
||||
static struct meow_anim g_pool[MEOW_ANIM_MAX];
|
||||
static unsigned g_active = 0; /* 活动动画计数(0 = 空闲,tick 直退) */
|
||||
|
||||
/* ---- 缓动 ---- */
|
||||
float meow_ease_apply(meow_ease e, float t)
|
||||
{
|
||||
if (e == MEOW_EASE_OUT) {
|
||||
/* ease-out quad */
|
||||
float u = 1.0f - t;
|
||||
return 1.0f - u * u;
|
||||
}
|
||||
if (e == MEOW_EASE_IN_OUT) {
|
||||
/* ease-in-out quad */
|
||||
if (t < 0.5f) return 2.0f * t * t;
|
||||
float u = -2.0f * t + 2.0f;
|
||||
return 1.0f - u * u * 0.5f;
|
||||
}
|
||||
if (e == MEOW_EASE_OVERSHOOT) {
|
||||
/* ease-out-back(越过目标再回稳);t=1 → 恰为 1 */
|
||||
const float c1 = 1.70158f;
|
||||
const float c3 = c1 + 1.0f;
|
||||
float u = t - 1.0f;
|
||||
return 1.0f + c3 * u * u * u + c1 * u * u;
|
||||
}
|
||||
/* MEOW_EASE_LINEAR */
|
||||
return t;
|
||||
}
|
||||
|
||||
/* ---- 生命周期 ---- */
|
||||
meow_anim *meow_anim_start(const meow_anim_desc *d)
|
||||
{
|
||||
if (!d) return NULL;
|
||||
for (int i = 0; i < MEOW_ANIM_MAX; i++) {
|
||||
struct meow_anim *a = &g_pool[i];
|
||||
if (a->active) continue;
|
||||
a->active = 1;
|
||||
a->from = d->from;
|
||||
a->to = d->to;
|
||||
a->duration_ms = d->duration_ms ? d->duration_ms : 1; /* 0 → 瞬发(下一 tick 完成) */
|
||||
a->ease = d->ease;
|
||||
a->start_ms = meow_ui_time_ms();
|
||||
a->update = d->update;
|
||||
a->done = d->done;
|
||||
a->ctx = d->ctx;
|
||||
a->region_set = 0;
|
||||
a->value = d->from;
|
||||
g_active++;
|
||||
if (a->update) a->update(a, a->value, a->ctx); /* 起始帧回调(t=0) */
|
||||
return a;
|
||||
}
|
||||
return NULL; /* 池满 */
|
||||
}
|
||||
|
||||
void meow_anim_cancel(meow_anim *a)
|
||||
{
|
||||
if (!a || !a->active) return;
|
||||
a->active = 0;
|
||||
if (g_active > 0) g_active--;
|
||||
if (a->done) a->done(a, 1, a->ctx);
|
||||
}
|
||||
|
||||
int meow_anim_active(meow_anim *a) { return (a && a->active) ? 1 : 0; }
|
||||
float meow_anim_value(meow_anim *a) { return a ? a->value : 0.0f; }
|
||||
|
||||
void meow_anim_set_region(meow_anim *a, int x, int y, int w, int h)
|
||||
{
|
||||
if (!a) return;
|
||||
if (x < 0) { a->region_set = 0; return; }
|
||||
a->region_set = 1;
|
||||
a->rx = x; a->ry = y; a->rw = w > 0 ? w : 0; a->rh = h > 0 ? h : 0;
|
||||
}
|
||||
|
||||
/* ---- 节拍器 ---- */
|
||||
unsigned meow_anim_count(void) { return (unsigned)g_active; }
|
||||
|
||||
void meow_anim_tick(void)
|
||||
{
|
||||
if (g_active == 0) return; /* 无动画:零开销直接返回 */
|
||||
uint64_t now = meow_ui_time_ms();
|
||||
for (int i = 0; i < MEOW_ANIM_MAX; i++) {
|
||||
struct meow_anim *a = &g_pool[i];
|
||||
if (!a->active) continue;
|
||||
uint64_t elapsed = now - a->start_ms;
|
||||
float t = (float)(elapsed) / (float)a->duration_ms;
|
||||
int finished = (t >= 1.0f);
|
||||
if (t > 1.0f) t = 1.0f;
|
||||
if (t < 0.0f) t = 0.0f;
|
||||
float v = a->from + (a->to - a->from) * meow_ease_apply(a->ease, t);
|
||||
a->value = v;
|
||||
if (a->update) a->update(a, v, a->ctx);
|
||||
if (a->region_set && a->rw > 0 && a->rh > 0)
|
||||
meow_ui_flush_region(a->rx, a->ry, a->rw, a->rh); /* 只刷动画影响区 */
|
||||
if (finished) {
|
||||
a->active = 0;
|
||||
if (g_active > 0) g_active--;
|
||||
if (a->done) a->done(a, 0, a->ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* meow_ui_box.c — 线性布局引擎(§12.1 meow_box)。
|
||||
*
|
||||
* meow_box 是**纯布局计算器**(非控件):静态池创建、定长子项数组、arrange 单遍求解,
|
||||
* 把每个子控件的 x/y/w/h 写进 meow_widget(meow_widget_set_rect);嵌套 sub-box 递归求解。
|
||||
* 用显式静态栈(深度≤MEOW_BOX_MAX_DEPTH)做嵌套展开,无 C 递归,杜绝栈爆炸。
|
||||
*
|
||||
* 求解模型(主轴向):
|
||||
* - 每个子项占位 = lead(margin) + extent + trail(margin);项间再叠 box->gap。
|
||||
* - FIXED/WRAP 的 extent 先定;WEIGHT 的 extent=0(待均分)。
|
||||
* - leftover = content_main - foot;若权重和>0 → 均分 leftover;否则按主轴对齐分配。
|
||||
* 交叉轴:按 START/CENTER/END/STRETCH 对齐,STRETCH 填满容器交叉向减 margin。
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "include/meow_ui_box.h"
|
||||
#include "meow_ui_internal.h" /* meow_widget_rect(只读几何) */
|
||||
#include <string.h>
|
||||
|
||||
#define MEOW_BOX_POOL 16 /* 静态池大小(并发 box 数) */
|
||||
|
||||
struct meow_box {
|
||||
int dir; /* MEOW_BOX_H / MEOW_BOX_V */
|
||||
int x, y, w, h; /* 容器矩形(绝对坐标) */
|
||||
int pad_l, pad_t, pad_r, pad_b;
|
||||
int gap;
|
||||
int main_align;
|
||||
int cross_align;
|
||||
meow_box_item items[MEOW_BOX_MAX_CHILD];
|
||||
int n;
|
||||
};
|
||||
|
||||
static meow_box g_pool[MEOW_BOX_POOL];
|
||||
static unsigned char g_used[MEOW_BOX_POOL];
|
||||
|
||||
/* ---- 静态池存取 ---- */
|
||||
meow_box *meow_box_new(meow_box_dir dir)
|
||||
{
|
||||
for (int i = 0; i < MEOW_BOX_POOL; i++) {
|
||||
if (!g_used[i]) {
|
||||
g_used[i] = 1;
|
||||
meow_box *b = &g_pool[i];
|
||||
memset(b, 0, sizeof *b);
|
||||
b->dir = (dir == MEOW_BOX_H) ? MEOW_BOX_H : MEOW_BOX_V;
|
||||
return b;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
void meow_box_free(meow_box *b)
|
||||
{
|
||||
if (!b) return;
|
||||
for (int i = 0; i < MEOW_BOX_POOL; i++)
|
||||
if (&g_pool[i] == b) { g_used[i] = 0; return; }
|
||||
}
|
||||
|
||||
void meow_box_set_rect(meow_box *b, int x, int y, int w, int h)
|
||||
{
|
||||
if (!b) return;
|
||||
b->x = x; b->y = y;
|
||||
b->w = w > 0 ? w : 0; b->h = h > 0 ? h : 0;
|
||||
}
|
||||
void meow_box_set_padding(meow_box *b, int l, int t, int r, int btm)
|
||||
{
|
||||
if (!b) return;
|
||||
b->pad_l = l; b->pad_t = t; b->pad_r = r; b->pad_b = btm;
|
||||
}
|
||||
void meow_box_set_gap(meow_box *b, int gap)
|
||||
{
|
||||
if (!b) return;
|
||||
b->gap = gap > 0 ? gap : 0;
|
||||
}
|
||||
void meow_box_set_align(meow_box *b, int main_align, int cross_align)
|
||||
{
|
||||
if (!b) return;
|
||||
b->main_align = main_align;
|
||||
b->cross_align = cross_align;
|
||||
}
|
||||
|
||||
/* ---- 子项 ---- */
|
||||
int meow_box_add(meow_box *b, meow_widget *w, int size, int weight)
|
||||
{
|
||||
return meow_box_add_ex(b, w, size, weight, 0, 0, NULL, NULL);
|
||||
}
|
||||
int meow_box_add_ex(meow_box *b, meow_widget *w, int size, int weight,
|
||||
int min, int preferred, meow_box_measure_fn m, void *mctx)
|
||||
{
|
||||
if (!b || !w || b->n >= MEOW_BOX_MAX_CHILD) return -1;
|
||||
if (size != MEOW_BOX_FIXED && size != MEOW_BOX_WEIGHT && size != MEOW_BOX_WRAP) return -1;
|
||||
if (size == MEOW_BOX_WEIGHT && weight <= 0) return -1;
|
||||
for (int i = 0; i < b->n; i++)
|
||||
if (b->items[i].w == w) return -1; /* 同控件去重 */
|
||||
meow_box_item *it = &b->items[b->n++];
|
||||
memset(it, 0, sizeof *it);
|
||||
it->w = w; it->size = size; it->weight = weight > 0 ? weight : 1;
|
||||
it->min = min > 0 ? min : 0;
|
||||
it->preferred = preferred > 0 ? preferred : 0;
|
||||
it->measure = m; it->mctx = mctx;
|
||||
return 0;
|
||||
}
|
||||
int meow_box_add_sub(meow_box *b, meow_box *sub, int size, int weight)
|
||||
{
|
||||
if (!b || !sub || b->n >= MEOW_BOX_MAX_CHILD) return -1;
|
||||
if (size != MEOW_BOX_FIXED && size != MEOW_BOX_WEIGHT && size != MEOW_BOX_WRAP) return -1;
|
||||
if (size == MEOW_BOX_WEIGHT && weight <= 0) return -1;
|
||||
for (int i = 0; i < b->n; i++)
|
||||
if (b->items[i].sub == sub) return -1;
|
||||
meow_box_item *it = &b->items[b->n++];
|
||||
memset(it, 0, sizeof *it);
|
||||
it->sub = sub; it->size = size; it->weight = weight > 0 ? weight : 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 查询 ---- */
|
||||
int meow_box_count(const meow_box *b) { return b ? b->n : 0; }
|
||||
meow_widget *meow_box_widget_at(const meow_box *b, int idx)
|
||||
{ return (b && idx >= 0 && idx < b->n) ? b->items[idx].w : NULL; }
|
||||
meow_box *meow_box_sub_at(const meow_box *b, int idx)
|
||||
{ return (b && idx >= 0 && idx < b->n) ? b->items[idx].sub : NULL; }
|
||||
void meow_box_rect(const meow_box *b, int out[4])
|
||||
{
|
||||
if (!out) return;
|
||||
if (!b) { out[0] = out[1] = out[2] = out[3] = 0; return; }
|
||||
out[0] = b->x; out[1] = b->y; out[2] = b->w; out[3] = b->h;
|
||||
}
|
||||
|
||||
/* ---- 内部:取子项 margin(widget opts;sub 无 margin)---- */
|
||||
static void item_margin(const meow_box_item *it, meow_box_dir dir,
|
||||
int *main_before, int *main_after, int *cross)
|
||||
{
|
||||
*main_before = *main_after = *cross = 0;
|
||||
if (!it->w) return;
|
||||
meow_widget_opts o;
|
||||
if (meow_widget_opts_get(it->w, &o) != 0) return;
|
||||
int mx = o.margin_x > 0 ? o.margin_x : 0;
|
||||
int my = o.margin_y > 0 ? o.margin_y : 0;
|
||||
if (dir == MEOW_BOX_H) { *main_before = mx; *main_after = mx; *cross = my; }
|
||||
else { *main_before = my; *main_after = my; *cross = mx; }
|
||||
}
|
||||
|
||||
/* ---- 内部:子项主轴向 extent ----
|
||||
* FIXED → 控件/嵌套盒当前主轴向尺寸
|
||||
* WRAP → measure 回调(若有)否则控件当前尺寸;下限 clamp 到 min
|
||||
* WEIGHT → 0(由均分阶段填充) */
|
||||
static int item_extent(const meow_box_item *it, int main_is_w)
|
||||
{
|
||||
int extent = 0;
|
||||
if (it->w) {
|
||||
int r[4]; meow_widget_rect(it->w, r);
|
||||
extent = main_is_w ? r[2] : r[3];
|
||||
} else if (it->sub) {
|
||||
extent = main_is_w ? it->sub->w : it->sub->h;
|
||||
}
|
||||
if (it->size == MEOW_BOX_WRAP && it->measure) {
|
||||
int mn = 0, pf = 0;
|
||||
it->measure(it->mctx, &mn, &pf);
|
||||
if (pf > 0) extent = pf;
|
||||
else if (mn > 0) extent = mn;
|
||||
if (mn > 0 && extent < mn) extent = mn; /* clamp 到下限 */
|
||||
}
|
||||
if (extent < 0) extent = 0;
|
||||
return extent;
|
||||
}
|
||||
|
||||
/* ---- 单容器求解(一次 pass:算 extent + 位置,写全部子项矩形)----
|
||||
* 子项为嵌套 meow_box 时只写其矩形(父求结果),后续由 meow_box_arrange 的
|
||||
* 显式栈递归求解其内部。 */
|
||||
static void box_layout_one(meow_box *b)
|
||||
{
|
||||
int n = b->n;
|
||||
if (n <= 0) return;
|
||||
int cx = b->x + b->pad_l;
|
||||
int cy = b->y + b->pad_t;
|
||||
int cw = b->w - b->pad_l - b->pad_r; if (cw < 0) cw = 0;
|
||||
int ch = b->h - b->pad_t - b->pad_b; if (ch < 0) ch = 0;
|
||||
int main_is_w = (b->dir == MEOW_BOX_H);
|
||||
int main_size = main_is_w ? cw : ch;
|
||||
int cross_size = main_is_w ? ch : cw;
|
||||
int gap = b->gap;
|
||||
|
||||
int ext[MEOW_BOX_MAX_CHILD], lead[MEOW_BOX_MAX_CHILD],
|
||||
trail[MEOW_BOX_MAX_CHILD], wgt[MEOW_BOX_MAX_CHILD];
|
||||
int nw = 0, wsum = 0;
|
||||
long long foot = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
const meow_box_item *it = &b->items[i];
|
||||
int mb, ma, cr;
|
||||
item_margin(it, (meow_box_dir)b->dir, &mb, &ma, &cr);
|
||||
lead[i] = mb; trail[i] = ma; (void)cr;
|
||||
wgt[i] = (it->size == MEOW_BOX_WEIGHT) ? it->weight : 0;
|
||||
if (it->size == MEOW_BOX_WEIGHT) {
|
||||
ext[i] = 0; nw++; wsum += it->weight;
|
||||
} else {
|
||||
ext[i] = item_extent(it, main_is_w);
|
||||
}
|
||||
foot += (long long)lead[i] + trail[i] + ext[i];
|
||||
}
|
||||
foot += (long long)gap * (n - 1);
|
||||
|
||||
long long leftover = (long long)main_size - foot;
|
||||
|
||||
if (nw > 0 && leftover > 0) {
|
||||
/* 均分剩余到权重项;余数塞给最后一个权重项铺满精确 */
|
||||
long long consumed = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (wgt[i] > 0) { ext[i] = (int)(leftover * wgt[i] / wsum); consumed += ext[i]; }
|
||||
}
|
||||
for (int i = n - 1; i >= 0; i--) {
|
||||
if (wgt[i] > 0) { ext[i] += (int)(leftover - consumed); break; }
|
||||
}
|
||||
leftover = 0;
|
||||
}
|
||||
|
||||
/* 主轴对齐(仅当无权重、leftover != 0 有意义;有权重时 leftover≈0 → START 等效) */
|
||||
int off0 = 0;
|
||||
int intergap = gap;
|
||||
if (nw == 0) {
|
||||
if (leftover > 0) {
|
||||
switch (b->main_align) {
|
||||
case MEOW_BOX_ALIGN_CENTER: off0 = (int)(leftover / 2); break;
|
||||
case MEOW_BOX_ALIGN_END: off0 = (int)leftover; break;
|
||||
case MEOW_BOX_ALIGN_SPACE_BETWEEN: off0 = 0;
|
||||
intergap = (n > 1) ? gap + (int)(leftover / (n - 1)) : gap; break;
|
||||
case MEOW_BOX_ALIGN_SPACE_AROUND: off0 = (int)(leftover / (2 * n));
|
||||
intergap = gap + (int)(leftover / n); break;
|
||||
case MEOW_BOX_ALIGN_SPACE_EVENLY: off0 = (int)(leftover / (n + 1));
|
||||
intergap = gap + (int)(leftover / (n + 1)); break;
|
||||
default: off0 = 0; break; /* START */
|
||||
}
|
||||
} else {
|
||||
off0 = 0; intergap = gap; /* 溢出:从起点排(可越界,交滚动容器) */
|
||||
}
|
||||
}
|
||||
|
||||
/* 主轴定位 + 交叉轴定位,写子控件矩形(主轴起点 = 内容原点 cx/cy)。
|
||||
* rel = 相对内容原点的主轴游标;content_rel = 本项内容相对起点;content_pos = 绝对主轴坐标。 */
|
||||
int main_base = main_is_w ? cx : cy;
|
||||
int rel = off0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
const meow_box_item *it = &b->items[i];
|
||||
int content_rel = rel + lead[i];
|
||||
int content_pos = main_base + content_rel;
|
||||
if (it->w) {
|
||||
int r[4]; meow_widget_rect(it->w, r);
|
||||
meow_widget_opts o; int has = (meow_widget_opts_get(it->w, &o) == 0);
|
||||
int cmx = (has && o.margin_x > 0) ? o.margin_x : 0;
|
||||
int cmy = (has && o.margin_y > 0) ? o.margin_y : 0;
|
||||
int cm = main_is_w ? cmy : cmx;
|
||||
int base = main_is_w ? r[3] : r[2];
|
||||
int cross = 0, cross_ext = 0;
|
||||
if (b->cross_align == MEOW_BOX_CROSS_STRETCH) {
|
||||
cross_ext = cross_size - 2 * cm; if (cross_ext < 0) cross_ext = 0;
|
||||
cross = cm;
|
||||
} else {
|
||||
cross_ext = base;
|
||||
int pad = cross_size - 2 * cm - cross_ext; if (pad < 0) pad = 0;
|
||||
if (b->cross_align == MEOW_BOX_CROSS_CENTER) cross = cm + pad / 2;
|
||||
else if (b->cross_align == MEOW_BOX_CROSS_END) cross = cm + pad;
|
||||
else cross = cm; /* START */
|
||||
}
|
||||
if (main_is_w)
|
||||
meow_widget_set_rect(it->w, content_pos, cy + cross, ext[i], cross_ext);
|
||||
else
|
||||
meow_widget_set_rect(it->w, cx + cross, content_pos, cross_ext, ext[i]);
|
||||
} else if (it->sub) {
|
||||
meow_box *sb = it->sub;
|
||||
int base = main_is_w ? sb->h : sb->w;
|
||||
int cross = 0, cross_ext = 0;
|
||||
if (b->cross_align == MEOW_BOX_CROSS_STRETCH) {
|
||||
cross_ext = cross_size; if (cross_ext < 0) cross_ext = 0;
|
||||
cross = 0;
|
||||
} else {
|
||||
cross_ext = base;
|
||||
int pad = cross_size - cross_ext; if (pad < 0) pad = 0;
|
||||
if (b->cross_align == MEOW_BOX_CROSS_CENTER) cross = pad / 2;
|
||||
else if (b->cross_align == MEOW_BOX_CROSS_END) cross = pad;
|
||||
else cross = 0;
|
||||
}
|
||||
if (main_is_w) { sb->x = content_pos; sb->y = cy + cross; sb->w = ext[i]; sb->h = cross_ext; }
|
||||
else { sb->x = cx + cross; sb->y = content_pos; sb->w = cross_ext; sb->h = ext[i]; }
|
||||
}
|
||||
if (i < n - 1)
|
||||
rel = content_rel + ext[i] + trail[i] + intergap;
|
||||
else
|
||||
rel = content_rel + ext[i] + trail[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 求解:单遍,显式静态栈展开嵌套(深度≤MEOW_BOX_MAX_DEPTH)---- */
|
||||
static meow_box *g_stack[MEOW_BOX_MAX_DEPTH];
|
||||
|
||||
void meow_box_arrange(meow_box *b)
|
||||
{
|
||||
if (!b) return;
|
||||
int top = 0;
|
||||
g_stack[0] = b;
|
||||
while (top >= 0) {
|
||||
meow_box *cur = g_stack[top--];
|
||||
box_layout_one(cur); /* 写全部直接子项矩形(含 sub->rect) */
|
||||
for (int i = 0; i < cur->n; i++) {
|
||||
meow_box *sb = cur->items[i].sub;
|
||||
if (!sb) continue;
|
||||
if (top + 1 >= MEOW_BOX_MAX_DEPTH) continue; /* 深度上限:跳过(矩形已被父写定) */
|
||||
g_stack[++top] = sb;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ static void dlg_card(meow_dialog *d, const meow_theme *th, const dlg_geom *g)
|
||||
int y = g->row_y;
|
||||
if (d->type == MEOW_DLG_INPUT) {
|
||||
char buf[300];
|
||||
snprintf(buf, sizeof buf, "%s", d->ime.buf[0] ? d->ime.buf : "(tap OK to edit...)");
|
||||
snprintf(buf, sizeof buf, "%s", d->ime.buf[0] ? d->ime.buf : meow_str("common.tap_ok_edit"));
|
||||
meow_ui_text(g->dx + pad, y, buf, th->font_body, th->onSurfaceVariant);
|
||||
} else {
|
||||
for (int i = 0; i < nmsg; i++) {
|
||||
@@ -138,10 +138,10 @@ static void dlg_card(meow_dialog *d, const meow_theme *th, const dlg_geom *g)
|
||||
int bx = g->dx + pad, cx = bx + g->btn_w + 12, by = g->btn_y;
|
||||
meow_ui_draw_rect_round(bx, by, g->btn_w, g->btn_h, 10, th->primary);
|
||||
meow_ui_text_center(bx + g->btn_w / 2, by + (g->btn_h - meow_ui_text_height(th->font_body)) / 2,
|
||||
"CANCEL", th->font_body, th->primary);
|
||||
meow_str("common.cancel"), th->font_body, th->primary);
|
||||
meow_gfx_fill_round(cx, by, g->btn_w, g->btn_h, 10, th->primary);
|
||||
meow_ui_text_center(cx + g->btn_w / 2, by + (g->btn_h - meow_ui_text_height(th->font_body)) / 2,
|
||||
"OK", th->font_body, th->onPrimary);
|
||||
meow_str("common.ok"), th->font_body, th->onPrimary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +232,11 @@ void meow_dialog_show(meow_dialog *d)
|
||||
meow_input_start();
|
||||
meow_input_drain(); /* 排空残留触摸(goodix 长尾) */
|
||||
d->done = 0; d->result = 0;
|
||||
modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原(scrim 只叠一层/无残影) */
|
||||
/* §6.4:模态作用域——本层打开期间下层(view/widget)完全收不到触摸;点卡片外由背板消费
|
||||
* (d->done=1 result=0 取消),不透传。嵌套弹层 depth 递增。 */
|
||||
meow_ui_modal *m = meow_ui_modal_begin();
|
||||
modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原(scrim 只叠一层/无残影);
|
||||
* 嵌套模态在各自打开时拍「当前已渲染帧」,故两层弹开不叠黑。 */
|
||||
dlg_paint(d, th);
|
||||
meow_ui_flush();
|
||||
|
||||
@@ -286,6 +290,7 @@ void meow_dialog_show(meow_dialog *d)
|
||||
if (d->done) break;
|
||||
usleep(16000);
|
||||
}
|
||||
meow_ui_modal_end(m); /* §6.4:关闭模态作用域(下层恢复接收触摸) */
|
||||
modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */
|
||||
}
|
||||
|
||||
@@ -349,7 +354,8 @@ int meow_sheet_open(meow_sheet *s)
|
||||
int W = meow_ui_width(), H = meow_ui_height();
|
||||
int ph = sheet_h(s, th);
|
||||
int py = H - ph, pad = 24;
|
||||
modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原 */
|
||||
meow_ui_modal *m = meow_ui_modal_begin(); /* §6.4:模态作用域(下层收不到触摸) */
|
||||
modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原(scrim 只叠一层/无残影) */
|
||||
/* 首次绘制:遮罩 + 底部面板(顶部圆角) */
|
||||
meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150);
|
||||
meow_gfx_fill_round(0, py, W, ph, th->radius_card, th->surfaceContainerHighest);
|
||||
@@ -382,6 +388,7 @@ int meow_sheet_open(meow_sheet *s)
|
||||
if (s->done) break;
|
||||
usleep(16000);
|
||||
}
|
||||
meow_ui_modal_end(m); /* §6.4:关闭模态作用域 */
|
||||
modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */
|
||||
return s->result;
|
||||
}
|
||||
|
||||
@@ -266,6 +266,28 @@ void meow_draw_view_set_z(struct meow_view *v, int z)
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
/* §6.4 自上而下命中(事件分发形式化) */
|
||||
/* ============================================================ */
|
||||
void *meow_draw_hit_topdown(int x, int y)
|
||||
{
|
||||
void *best = NULL;
|
||||
int bestz = -1, bestseq = -1;
|
||||
for (int i = 0; i < g_n; i++) {
|
||||
draw_entry *e = &g_e[i];
|
||||
e_compute_auto(e);
|
||||
/* 只对注册控件做顶层命中;view 是容器(其子 widget 是独立更高 z 的绘制物,各自命中),
|
||||
* CUSTOM 是 App 直绘(不参与命中)。meow_widget_hit 内部含 owner==active + enable +
|
||||
* 矩形/自定义命中回调。 */
|
||||
if (e->kind != MEOW_ITEM_WIDGET || !e->w) continue;
|
||||
if (!meow_widget_hit(e->w, x, y)) continue;
|
||||
if (e->z > bestz || (e->z == bestz && e->seq > bestseq)) {
|
||||
bestz = e->z; bestseq = e->seq; best = e->w;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
/* page bg / 区域重画 / 整帧 */
|
||||
/* ============================================================ */
|
||||
|
||||
@@ -254,22 +254,21 @@ int meow_ui_back_buf_bpp(void) { return g_bpp; }
|
||||
* 从而 launcher 与 app 统一渲染栈(同一离屏 + 同一 present)。 */
|
||||
unsigned char *meow_ui_back_buf(void) { return g_back_buf; }
|
||||
|
||||
/* ================= 状态栏 MD3 图标 blit(写进本 SDK back_buf) =================
|
||||
* 复用 launcher sb_icons.h(Material Symbols rounded,32x32 单通道 alpha)作为状态栏
|
||||
* 右侧图标簇(「同一个状态栏」图标式,见 meow_ui_sys_statusbar_render)。
|
||||
* idx = sb_icons[] 索引(SB_I_*,见 meow_ui_internal.h)
|
||||
/* ================= MD3 图标 blit(写进本 SDK back_buf) =================
|
||||
* 复用 sb_icons.h(Material Symbols rounded,32x32 单通道 alpha)——状态栏右侧图标簇
|
||||
* (meow_ui_sys_statusbar_render)与 TopAppBar 位图动作图标(W_TOPAPPBAR)共用同源位图。
|
||||
* idx = sb_icons[] 索引(状态栏 SB_I_*、动作 MEOW_ICON_*,见 meow_ui_internal.h)
|
||||
* x = 图标槽左缘(槽宽 SB_ICON_SIZE=32)
|
||||
* y = 图标槽顶(仅作参考;实际按 ink 垂直中心对齐 cy)
|
||||
* ink_h = 目标 ink 高(>0 等比缩放;0 = 保持位图自然 ink 尺寸——WiFi 强弱档用)
|
||||
* fg = 24-bit RGB 前景(主题 on_surface / on_surface_variant,日/夜由主题表提供)
|
||||
* cy = ink 垂直中心线(= 状态栏文字中心,28px 栏约 14)
|
||||
* cy = ink 垂直中心线(状态栏=28px 栏约 14;TopAppBar=栏中点)
|
||||
* 先求位图 ink 边界(阈值 32,忽略近透明抗锯齿),按 ink 在槽内水平居中、垂直中心对齐 cy;
|
||||
* 像素按 alpha 软边混合进 back_buf(自 g_fb/g_line/g_xres/g_yres,与 launcher sb_blit_icon 同源算法)。 */
|
||||
void meow_gfx_blit_icon(int idx, int x, int y, int ink_h, uint32_t fg, int cy)
|
||||
/* 像素核心:只采样混合进 back_buf,不标脏区。供状态栏路径(blit 后另标顶栏脏区)与
|
||||
* 控件路径(meow_gfx_blit_icon_ink,控件 invalidation 独立管理脏区)共用,避免状态栏的
|
||||
* 顶栏脏区假设污染控件绘制(topbar 不在屏幕最顶行时标错区域)。 */
|
||||
static void icon_blit_px(int idx, int x, int ink_h, uint32_t fg, int cy)
|
||||
{
|
||||
(void)y;
|
||||
if (!g_init_ok || !g_fb) return;
|
||||
if (idx < 0 || idx >= (int)(sizeof sb_icons / sizeof sb_icons[0])) return;
|
||||
const unsigned char *src = sb_icons[idx].data;
|
||||
uint16_t col = rgb565(fg);
|
||||
/* 1) ink 边界(含空图兜底) */
|
||||
@@ -277,8 +276,10 @@ void meow_gfx_blit_icon(int idx, int x, int y, int ink_h, uint32_t fg, int cy)
|
||||
for (int i = 0; i < SB_ICON_SIZE * SB_ICON_SIZE; i++) {
|
||||
if (src[i] >= 32) {
|
||||
int xx = i % SB_ICON_SIZE, yy = i / SB_ICON_SIZE;
|
||||
if (xx < minx) minx = xx; if (xx > maxx) maxx = xx;
|
||||
if (yy < miny) miny = yy; if (yy > maxy) maxy = yy;
|
||||
if (xx < minx) minx = xx;
|
||||
if (xx > maxx) maxx = xx;
|
||||
if (yy < miny) miny = yy;
|
||||
if (yy > maxy) maxy = yy;
|
||||
}
|
||||
}
|
||||
if (maxx < minx || maxy < miny) { minx = miny = 0; maxx = maxy = SB_ICON_SIZE - 1; }
|
||||
@@ -311,10 +312,28 @@ void meow_gfx_blit_icon(int idx, int x, int y, int ink_h, uint32_t fg, int cy)
|
||||
row[tx] = (uint16_t)((r << 11) | (g << 5) | b);
|
||||
}
|
||||
}
|
||||
/* 整条顶栏作为脏区标出(图标在顶条内,present 只重画顶条即可) */
|
||||
}
|
||||
|
||||
/* 状态栏路径:blit 像素 + 把整条顶栏标脏(present 只重画顶条即可)。 */
|
||||
void meow_gfx_blit_icon(int idx, int x, int y, int ink_h, uint32_t fg, int cy)
|
||||
{
|
||||
(void)y;
|
||||
if (!g_init_ok || !g_fb) return;
|
||||
if (idx < 0 || idx >= (int)(sizeof sb_icons / sizeof sb_icons[0])) return;
|
||||
icon_blit_px(idx, x, ink_h, fg, cy);
|
||||
dirty_add(x, 0, SB_ICON_SIZE, MUI_SBAR_H);
|
||||
}
|
||||
|
||||
/* 控件路径(TopAppBar 动作图标):只写像素,不标「顶栏」脏区——控件矩形由
|
||||
* meow_widget_draw 的 clip + invalidation(setter 标脏)独立管理,避免把状态栏的
|
||||
* 顶栏脏区假设带进控件绘制(topbar 不位于屏幕最顶行时标错区域、触发误重画)。 */
|
||||
void meow_gfx_blit_icon_ink(int idx, int x, int ink_h, uint32_t fg, int cy)
|
||||
{
|
||||
if (!g_init_ok || !g_fb) return;
|
||||
if (idx < 0 || idx >= (int)(sizeof sb_icons / sizeof sb_icons[0])) return;
|
||||
icon_blit_px(idx, x, ink_h, fg, cy);
|
||||
}
|
||||
|
||||
/* ================= 像素写入 ================= */
|
||||
static void px_write_rgb565(int x, int y, uint16_t c16)
|
||||
{
|
||||
|
||||
@@ -362,10 +362,10 @@ static const char *py_key_label(const struct kbkey *k, char *tmp)
|
||||
switch (k->kind) {
|
||||
case K_CH: { char c = (char)tolower((unsigned char)k->ch); tmp[0] = c; tmp[1] = 0; return tmp; }
|
||||
case K_BACK: return "⌫";
|
||||
case K_SPACE: return "空格";
|
||||
case K_ENTER: return "确定";
|
||||
case K_SWITCH: return "拼/英";
|
||||
case K_SYMBOL: return "?123";
|
||||
case K_SPACE: return meow_str("ime.space");
|
||||
case K_ENTER: return meow_str("ime.enter");
|
||||
case K_SWITCH: return meow_str("ime.switch");
|
||||
case K_SYMBOL: return meow_str("ime.symbol");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
@@ -398,9 +398,9 @@ static void meow_ime_draw_py(const meow_theme *th)
|
||||
snprintf(pybuf, sizeof pybuf, "%s_", g_py_buf);
|
||||
meow_ui_text(14, PY_COMBO_Y + 8, pybuf, th->font_body, th->onSurface);
|
||||
meow_gfx_fill_round(PY_CLEAR_X, PY_COMBO_Y + 4, PY_CLEAR_W, PY_BADGE_H, 8, th->secondaryContainer);
|
||||
meow_ui_text_center(PY_CLEAR_X + PY_CLEAR_W / 2, PY_COMBO_Y + 4 + (PY_BADGE_H - th20) / 2, "清", 20, th->onSecondaryContainer);
|
||||
meow_ui_text_center(PY_CLEAR_X + PY_CLEAR_W / 2, PY_COMBO_Y + 4 + (PY_BADGE_H - th20) / 2, meow_str("ime.clear"), 20, th->onSecondaryContainer);
|
||||
meow_gfx_fill_round(PY_BADGE_X, PY_BADGE_Y, PY_BADGE_W, PY_BADGE_H, 8, th->primaryContainer);
|
||||
meow_ui_text_center(PY_BADGE_X + PY_BADGE_W / 2, PY_BADGE_Y + (PY_BADGE_H - th20) / 2, "拼音", 20, th->onPrimaryContainer);
|
||||
meow_ui_text_center(PY_BADGE_X + PY_BADGE_W / 2, PY_BADGE_Y + (PY_BADGE_H - th20) / 2, meow_str("ime.pinyin_mode"), 20, th->onPrimaryContainer);
|
||||
|
||||
/* 候选栏:◀ ▶ 翻页 + 横排候选(高亮第 0 项) */
|
||||
meow_ui_fill_rect(0, PY_CAND_Y, 480, PY_CAND_H, th->surfaceContainerHighest);
|
||||
@@ -409,7 +409,7 @@ static void meow_ime_draw_py(const meow_theme *th)
|
||||
meow_gfx_fill_round(PY_NEXT_X, PY_CAND_Y, PY_NEXT_W, PY_CAND_H, 8, th->surfaceContainerLow);
|
||||
meow_ui_text_center(PY_NEXT_X + PY_NEXT_W / 2, PY_CAND_Y + (PY_CAND_H - th20) / 2, "▶", 20, th->onSurface);
|
||||
if (g_py_nc == 0) {
|
||||
meow_ui_text(PY_CAND_X0, PY_CAND_Y + (PY_CAND_H - th20) / 2, "(无候选)", th->font_body, th->onSurfaceVariant);
|
||||
meow_ui_text(PY_CAND_X0, PY_CAND_Y + (PY_CAND_H - th20) / 2, meow_str("ime.no_candidate"), th->font_body, th->onSurfaceVariant);
|
||||
} else {
|
||||
int x = PY_CAND_X0;
|
||||
for (int i = 0; i < PY_PAGE; i++) {
|
||||
@@ -458,7 +458,7 @@ void meow_ime_draw(void)
|
||||
{
|
||||
int th20 = meow_ui_text_height(20);
|
||||
meow_gfx_fill_round(ASCII_MID_X, KB_TOP + 7, ASCII_MID_W, 28, 8, th->primaryContainer);
|
||||
meow_ui_text_center(ASCII_MID_X + ASCII_MID_W / 2, KB_TOP + 7 + (28 - th20) / 2, "中", 20, th->onPrimaryContainer);
|
||||
meow_ui_text_center(ASCII_MID_X + ASCII_MID_W / 2, KB_TOP + 7 + (28 - th20) / 2, meow_str("ime.to_pinyin"), 20, th->onPrimaryContainer);
|
||||
}
|
||||
/* 键 */
|
||||
for (int i = 0; i < g_nkeys; i++) {
|
||||
@@ -471,9 +471,9 @@ void meow_ime_draw(void)
|
||||
char label[2] = { k->ch, 0 };
|
||||
if (is_letter(k->ch)) label[0] = g_shift ? (char)toupper((unsigned char)k->ch)
|
||||
: (char)tolower((unsigned char)k->ch);
|
||||
if (k->kind == K_SPACE) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "SPACE", th->font_body, fg); }
|
||||
else if (k->kind == K_ENTER) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "OK", 20, fg); }
|
||||
else if (k->kind == K_ESC) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "X", 20, fg); }
|
||||
if (k->kind == K_SPACE) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, meow_str("ime.space"), th->font_body, fg); }
|
||||
else if (k->kind == K_ENTER) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, meow_str("ime.enter"), 20, fg); }
|
||||
else if (k->kind == K_ESC) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, meow_str("ime.esc"), 20, fg); }
|
||||
else { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, label, 20, fg); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,30 @@ static unsigned char *scale_buf(const meow_img *im, int dw, int dh, int cover)
|
||||
return out;
|
||||
}
|
||||
|
||||
/* 内存瘦身:把大图按最近邻等比缩放到「最长边 ≤ maxdim」,返回新图;调用方负责
|
||||
* meow_img_free 旧图 + 新图。原图已 ≤maxdim → 返回 NULL(无需缩放)。
|
||||
* 典型:网易云全尺寸封面 1448×1448 RGBA=8MB,显示只需 ≤480px(屏幕 480 宽)→
|
||||
* 480×480=0.9MB,省 ~7MB anon(设备 RAM 仅 56MB,OOM 真机被杀)。
|
||||
* 复用 scale_buf(cover=0,最近邻锁比例),不做插值(App 封面/背景无需高质量缩放)。 */
|
||||
meow_img *meow_img_downscale(meow_img *im, int maxdim)
|
||||
{
|
||||
if (!im || maxdim <= 0) return NULL;
|
||||
if (im->w <= maxdim && im->h <= maxdim) return NULL; /* 已够小,不缩放 */
|
||||
int dw, dh;
|
||||
if (im->w >= im->h) {
|
||||
dw = maxdim;
|
||||
dh = (int)((long)im->h * maxdim / im->w);
|
||||
} else {
|
||||
dh = maxdim;
|
||||
dw = (int)((long)im->w * maxdim / im->h);
|
||||
}
|
||||
if (dw < 1) dw = 1;
|
||||
if (dh < 1) dh = 1;
|
||||
unsigned char *tmp = scale_buf(im, dw, dh, 0);
|
||||
if (!tmp) return NULL;
|
||||
return img_alloc(dw, dh, tmp);
|
||||
}
|
||||
|
||||
void meow_ui_img(int x, int y, meow_img *im)
|
||||
{
|
||||
if (!im) return;
|
||||
|
||||
@@ -62,6 +62,9 @@
|
||||
#define MAX_DEVS 8
|
||||
#define MEOW_APP_ACTIVITY_FILE "/tmp/meow_app_activity" /* M2.71-bug5:App 活动→launcher 重置息屏计时 */
|
||||
|
||||
/* §6.4 事件分发形式化:手势捕获终点前向声明(meow_input_drain 之前定义后置,见下方实现)。 */
|
||||
void meow_ui_gesture_end(void);
|
||||
|
||||
/* ── 内部事件:系统级「下拉展开通知中心」(M2.5x 接线)──
|
||||
* 触摸状态机在抬手判定处识别「SWIPE_DOWN 起始于顶部热区」后压入;事件循环按系统手势处理
|
||||
* (路由 meow_ui_sys_notif_pull),不下发给 APP 的 on_swipe(顶部热区为系统保留手势)。
|
||||
@@ -197,6 +200,7 @@ void meow_input_drain(void)
|
||||
pthread_mutex_lock(&g_qm);
|
||||
g_qh = 0; g_qt = 0;
|
||||
pthread_mutex_unlock(&g_qm);
|
||||
meow_ui_gesture_end(); /* §6.4:清空队列=丢弃未完成手势(如开 IME/模态前),释放捕获防悬垂 */
|
||||
}
|
||||
|
||||
static void push_ev(int type, int a, int b)
|
||||
@@ -404,6 +408,89 @@ int meow_input_long_press_poll(void)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
/* §6.4 事件分发形式化:模态作用域 / 自上而下命中 / 手势捕获 / 唯一焦点
|
||||
* ------------------------------------------------------------
|
||||
* 统一仲裁者:把 ad-hoc 的「命中/捕获/消费/层间隔离/焦点」收敛为单一模型。
|
||||
* 兼容:本节 API 只追加;未调用者行为与之前完全一致(命中/捕获仍由 App 自理)。 */
|
||||
/* ============================================================ */
|
||||
|
||||
/* ---- 模态作用域栈(弹层叠开,depth 递增;存在则下层完全收不到触摸)---- */
|
||||
#define MEOW_MODAL_SCOPE_MAX 8
|
||||
static void *g_modal_stack[MEOW_MODAL_SCOPE_MAX];
|
||||
static int g_modal_depth = 0;
|
||||
static char g_modal_sentinel; /* 「模态哨兵」地址:模态存在时 hit_topdown 的返回占位 */
|
||||
|
||||
meow_ui_modal *meow_ui_modal_begin(void)
|
||||
{
|
||||
if (g_modal_depth >= MEOW_MODAL_SCOPE_MAX) return NULL; /* 栈满:不弹(骨架级静默) */
|
||||
void *h = &g_modal_stack[g_modal_depth];
|
||||
g_modal_stack[g_modal_depth++] = h;
|
||||
return (meow_ui_modal *)h;
|
||||
}
|
||||
void meow_ui_modal_end(meow_ui_modal *m)
|
||||
{
|
||||
if (!m) return;
|
||||
for (int i = 0; i < g_modal_depth; i++)
|
||||
if (g_modal_stack[i] == (void *)m) {
|
||||
g_modal_depth = i; /* 弹到 m(含),一次性关上嵌套上层 */
|
||||
return;
|
||||
}
|
||||
}
|
||||
int meow_ui_modal_active(void) { return g_modal_depth > 0; }
|
||||
int meow_ui_modal_depth(void) { return g_modal_depth; }
|
||||
|
||||
/* ---- 自上而下命中(模态门控 + z 降序控件命中)---- */
|
||||
void *meow_ui_hit_topdown(int x, int y)
|
||||
{
|
||||
if (meow_ui_modal_active()) return &g_modal_sentinel; /* 模态存在:恒由模态/背板消费,不透传 */
|
||||
return meow_draw_hit_topdown(x, y);
|
||||
}
|
||||
int meow_ui_widget_hit(meow_widget *w, int x, int y) { return meow_widget_hit(w, x, y); }
|
||||
|
||||
/* ---- 手势捕获(down 捕获直到 up)---- */
|
||||
static void *g_capture = NULL;
|
||||
void *meow_ui_gesture_begin(int x, int y)
|
||||
{
|
||||
void *t = meow_ui_hit_topdown(x, y);
|
||||
g_capture = t; /* 覆盖旧捕获(一次手势至多一个目标;命中 NULL=未捕获,move/up 照旧) */
|
||||
return g_capture;
|
||||
}
|
||||
void *meow_ui_gesture_owner(void) { return g_capture; }
|
||||
void meow_ui_gesture_end(void) { g_capture = NULL; }
|
||||
|
||||
/* ---- 唯一焦点(list 焦点残留根治 / 滚动容器间转移)---- */
|
||||
static void *g_focus = NULL;
|
||||
static meow_ui_focus_lost_fn g_focus_lost = NULL;
|
||||
static void *g_focus_lost_ctx = NULL;
|
||||
|
||||
void meow_ui_focus_set_lost_cb(meow_ui_focus_lost_fn cb, void *ctx)
|
||||
{ g_focus_lost = cb; g_focus_lost_ctx = ctx; }
|
||||
void *meow_ui_focus_owner(void) { return g_focus; }
|
||||
int meow_ui_focus_acquire(void *owner)
|
||||
{
|
||||
if (!owner) return 0;
|
||||
void *prev = g_focus;
|
||||
g_focus = owner;
|
||||
if (prev && prev != owner && g_focus_lost) g_focus_lost(prev, g_focus_lost_ctx);
|
||||
return 1;
|
||||
}
|
||||
int meow_ui_focus_release(void *owner)
|
||||
{
|
||||
if (!g_focus) return 0;
|
||||
if (owner && owner != g_focus) return 0;
|
||||
void *prev = g_focus;
|
||||
g_focus = NULL;
|
||||
if (g_focus_lost) g_focus_lost(prev, g_focus_lost_ctx);
|
||||
return 1;
|
||||
}
|
||||
void meow_ui_focus_tap(int x, int y)
|
||||
{
|
||||
if (!g_focus) return;
|
||||
void *hit = meow_ui_hit_topdown(x, y);
|
||||
if (hit != g_focus) meow_ui_focus_release(g_focus); /* 点非焦点区 → 释放并通知 */
|
||||
}
|
||||
|
||||
/* ---- 系统键(音量/媒体)去重 + EVIOCGKEY 状态轮询 ----
|
||||
* M2.55/agent-217 之后 SDK 在输入线程消化系统键语义;但真机物理键(jz adc keyboard event2、
|
||||
* md-gpio-keys event0、earpods_adc event3)的按下不一定进入 evdev 事件流(事件流可被其它进程
|
||||
@@ -620,6 +707,10 @@ void meow_ui_run(const meow_ui_events *ev)
|
||||
meow_ui_swipe_hint(-1);
|
||||
meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6);
|
||||
if (meow_ime_active() && meow_ime_handle_event(&e)) continue;
|
||||
/* §6.4 手势结束(抬手边沿)+ 焦点点外释放:先释放手势捕获(move/up 已结束),
|
||||
* 再对落点做「命中焦点所有者→保位;否则释放并通知」(list 焦点残留根治)。 */
|
||||
meow_ui_gesture_end();
|
||||
meow_ui_focus_tap(e.a, e.b);
|
||||
/* §6.1 触摸分流:③ 系统全局(IME/状态栏…已在上面消费) → ② App 全局控件
|
||||
* (命中返回 1=消费,否则落到 ① active Activity 页面控件)。 */
|
||||
if (meow_ui_global_hit(e.a, e.b)) continue;
|
||||
@@ -633,6 +724,7 @@ void meow_ui_run(const meow_ui_events *ev)
|
||||
meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6);
|
||||
/* 拼音 IME 候选栏横滑翻页(英文模式 handle_event 返回 0 → 沿用既有行为)。 */
|
||||
if (meow_ime_active() && meow_ime_handle_event(&e)) continue;
|
||||
meow_ui_gesture_end(); /* §6.4:抬手(滑动)边沿,释放手势捕获 */
|
||||
/* A-0 全局左缘返回(方案A):
|
||||
* - APP 注册 on_swipe → APP 接管,自行决定(meow_ui_request_exit / 页内返回);
|
||||
* - APP 未注册 on_swipe 且非全屏(sb_active)→ SDK 默认退出 app(返回=退出);
|
||||
@@ -652,6 +744,7 @@ void meow_ui_run(const meow_ui_events *ev)
|
||||
* 起点若同时落在左缘,拖动期可能已画过 MEOW_EV_SWIPE_PROGRESS 左缘指示条,
|
||||
* 抬手定判为 NOTIF_PULL 后须清掉(否则通知中心打开后残留返回条)。 */
|
||||
meow_ui_swipe_hint(-1);
|
||||
meow_ui_gesture_end(); /* §6.4:抬手边沿,释放手势捕获 */
|
||||
meow_ui_sys_notif_pull(e.a); /* e.a = 手势按下原点 Y */
|
||||
} else if (e.type == MEOW_EV_TOUCH_DOWN || e.type == MEOW_EV_TOUCH_MOVE) {
|
||||
/* M2.71-bug2/6:拖动过程(跟手)。IME 弹出时触摸优先喂键盘(键盘覆盖层拦截,
|
||||
@@ -660,6 +753,9 @@ void meow_ui_run(const meow_ui_events *ev)
|
||||
* IME 打开时先把压点喂给拼音(候选栏滑动翻页判定 g_ime_swipe_y),再丢弃。 */
|
||||
if (in_startup) continue;
|
||||
if (meow_ime_active()) { meow_ime_handle_event(&e); continue; }
|
||||
/* §6.4:down 捕获——命中目标捕获整个手势直到 up;随后 move/up 只发给该目标
|
||||
* (防滑出后误触发下层)。真实触发放置捕获(宿主直调 on_touch_* 不受影响)。 */
|
||||
if (e.type == MEOW_EV_TOUCH_DOWN) meow_ui_gesture_begin(e.a, e.b);
|
||||
if (g_events_set && g_events.on_touch_down && e.type == MEOW_EV_TOUCH_DOWN)
|
||||
g_events.on_touch_down(e.a, e.b);
|
||||
if (g_events_set && g_events.on_touch_move && e.type == MEOW_EV_TOUCH_MOVE)
|
||||
@@ -686,6 +782,7 @@ void meow_ui_run(const meow_ui_events *ev)
|
||||
if (in_startup) continue;
|
||||
/* 拼音 IME 打开:长按退格清空字段(英文模式 handle_event 返回 0 → 走既有长按回调)。 */
|
||||
if (meow_ime_active() && meow_ime_handle_event(&e)) continue;
|
||||
meow_ui_gesture_end(); /* §6.4:长按已触发,其抬手不再 TAP/SWIPE——顺手释放捕获 */
|
||||
if (g_long_press_cb) g_long_press_cb(e.a, e.b);
|
||||
}
|
||||
}
|
||||
@@ -697,6 +794,9 @@ void meow_ui_run(const meow_ui_events *ev)
|
||||
g_events.on_tick();
|
||||
}
|
||||
}
|
||||
/* §12.2 动画节拍器:推进所有活动动画(无动画时零开销)。挂既有 on_tick 机制,
|
||||
* 不引入常驻高频轮询;动画 update 回调只 flush 其影响区(脏区联动)。 */
|
||||
meow_anim_tick();
|
||||
|
||||
/* A-0 全局状态栏 1s 轻刷:电量/WiFi/音量 + 时钟。时钟每秒变 → 重画顶条并上屏,
|
||||
* 即使 APP 空闲(未主动 flush)也保持时钟走动。
|
||||
|
||||
@@ -76,6 +76,10 @@ void meow_ui_sys_statusbar_render_off(int yoff);
|
||||
/* 把第 idx 个 MD3 图标(sb_icons[])按 ink 等比 blit 进 back_buf;x=槽左缘、ink_h=目标 ink 高
|
||||
* (0=自然 ink,WiFi 强弱档用)、fg=24bit 前景、cy=ink 垂直中心线。见 meow_ui_gfx.c。 */
|
||||
void meow_gfx_blit_icon(int idx, int x, int y, int ink_h, uint32_t fg, int cy);
|
||||
/* 同 meow_gfx_blit_icon,但**不标「顶栏」脏区**:供控件(W_TOPAPPBAR 位图动作图标)用——
|
||||
* 控件矩形由 meow_widget_draw 的 clip 与 invalidation 独立管理,状态栏路径的顶栏脏区假设
|
||||
* 不适用于位于屏幕内容区的 topbar。像素算法与状态栏同源(ink 居中 + alpha 软边混合)。 */
|
||||
void meow_gfx_blit_icon_ink(int idx, int x, int ink_h, uint32_t fg, int cy);
|
||||
|
||||
/* ---- 顶级主题风格族(§8.1.2,minimal):状态栏图标集/配色随 ui.style(md3/miuix)----
|
||||
* meow_ui_theme.c 实现:读 settings.conf 的 ui.style(md3→0 / miuix→1),缓存,随
|
||||
@@ -234,6 +238,11 @@ void meow_draw_view_unregister(struct meow_view *v); /* view free:注
|
||||
int meow_draw_view_entered(const struct meow_view *v); /* view 是否已进入(绘制过滤参考) */
|
||||
void meow_draw_view_set_z(struct meow_view *v, int z); /* view 显式 z(z<=0=清除回退自动) */
|
||||
|
||||
/* §6.4 事件分发形式化:沿 z 序自顶向下命中(owner==active+enable 过滤;模态门控在
|
||||
* meow_ui_hit_topdown,本函数只做 z 降序控件命中)。返回最高命中 z 的 meow_widget*,
|
||||
* 无命中返回 NULL。实现见 meow_ui_draw.c(读绘制注册表,不改 struct meow_widget)。 */
|
||||
void *meow_draw_hit_topdown(int x, int y);
|
||||
|
||||
/* 控件布局深度反推(meow_ui_draw.c 用;widget.c 实现,读 struct meow_widget 内部字段)。
|
||||
* 让绘制注册表在「无 meow_view 时」仍能从 W_LAYOUT children 反推子>父深度。 */
|
||||
int meow_widget_is_layout(const struct meow_widget *w); /* 1 = W_LAYOUT */
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* meow_ui_scroll.c — 滚动物理(§12.3)。
|
||||
*
|
||||
* 速度采样:track 时把 (pos, t) 记进定长环形(≤MEOW_SCROLL_SAMPLES),fling 时取
|
||||
* 最近 MEOW_SCROLL_WINDOW ms 窗口,按「间隔速度 × 近者权重」加权得出初速(px/s)。
|
||||
* 位置模型:ease-out quad 的位移曲线(末速恰为 0),初速与输入 v 对齐,时长 = 2×tau。
|
||||
* 边界:free 目标越界 → clamp 到 [0,max];若越界速度大 → 补两段短 IN_OUT 回弹
|
||||
* (overshoot 超过边界方向的内侧再回位),不做弹簧。停止后可选 snap 到 step 倍数。
|
||||
*
|
||||
* 不引 libm(SDK 约定,免 -lm);只用整数 + 纯浮点四则。
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "include/meow_ui_scroll.h"
|
||||
#include "meow_ui_internal.h" /* meow_widget_rect(定位 redraw 视口区) */
|
||||
#include "include/meow_ui_anim.h"
|
||||
#include <string.h>
|
||||
|
||||
#define MEOW_SCROLL_POOL 8
|
||||
#define MEOW_SCROLL_SAMPLES 8 /* 速度采样点数(最近 N 加权) */
|
||||
#define MEOW_SCROLL_WINDOW 110 /* 速度窗口(ms) */
|
||||
#define MEOW_SCROLL_TAU 180 /* 默认摩擦时间常数(ms) */
|
||||
#define MEOW_SCROLL_MIN_SPEED 60 /* 低于此的初速不 fling(px/s) */
|
||||
#define MEOW_SCROLL_MAX_DUR 900 /* fling 时长上限(ms) */
|
||||
#define MEOW_SCROLL_BOUNCE_MS 110 /* 每段回弹时长(ms) */
|
||||
#define MEOW_SCROLL_SNAP_MS 120
|
||||
|
||||
typedef enum {
|
||||
MEOW_SCROLL_IDLE = 0,
|
||||
MEOW_SCROLL_FLING,
|
||||
MEOW_SCROLL_BOUNCE1,
|
||||
MEOW_SCROLL_BOUNCE2,
|
||||
MEOW_SCROLL_SNAP,
|
||||
} meow_scroll_phase;
|
||||
|
||||
struct meow_scroll {
|
||||
meow_scroll_set_fn set;
|
||||
meow_scroll_max_fn max;
|
||||
meow_scroll_get_fn get;
|
||||
meow_scroll_redraw_fn redraw;
|
||||
void *ctx;
|
||||
|
||||
int snap_step; /* >0 = 吸附步长 */
|
||||
int tau_ms; /* 摩擦时间常数 */
|
||||
/* 速度采样(顺序数组,满则前移,≤SAMPLES) */
|
||||
int v_pos[MEOW_SCROLL_SAMPLES];
|
||||
uint64_t v_t[MEOW_SCROLL_SAMPLES];
|
||||
int v_n;
|
||||
|
||||
meow_scroll_phase phase;
|
||||
meow_anim *anim;
|
||||
int maxv; /* fling 开始时缓存 max_scroll */
|
||||
int bounce; /* fling 边界回弹幅度(带符号;0=不回弹) */
|
||||
int bounce_start; /* 回弹回位起点(= clamp 边界) */
|
||||
int snap_to; /* snap 目标 */
|
||||
};
|
||||
|
||||
static meow_scroll g_pool[MEOW_SCROLL_POOL];
|
||||
static unsigned char g_used[MEOW_SCROLL_POOL];
|
||||
|
||||
/* ---- 生命周期 ---- */
|
||||
meow_scroll *meow_scroll_create(meow_scroll_set_fn set, meow_scroll_max_fn max,
|
||||
meow_scroll_get_fn get, meow_scroll_redraw_fn redraw,
|
||||
void *ctx)
|
||||
{
|
||||
if (!set || !max || !get) return NULL;
|
||||
for (int i = 0; i < MEOW_SCROLL_POOL; i++) {
|
||||
if (!g_used[i]) {
|
||||
g_used[i] = 1;
|
||||
meow_scroll *s = &g_pool[i];
|
||||
memset(s, 0, sizeof *s);
|
||||
s->set = set; s->max = max; s->get = get; s->redraw = redraw; s->ctx = ctx;
|
||||
s->tau_ms = MEOW_SCROLL_TAU;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
void meow_scroll_free(meow_scroll *s)
|
||||
{
|
||||
if (!s) return;
|
||||
if (s->anim) meow_anim_cancel(s->anim);
|
||||
s->anim = NULL;
|
||||
for (int i = 0; i < MEOW_SCROLL_POOL; i++)
|
||||
if (&g_pool[i] == s) { g_used[i] = 0; return; }
|
||||
}
|
||||
|
||||
/* ---- wrap:包一个 meow_widget 列表 ---- */
|
||||
typedef struct { meow_widget *w; int lv; } wrap_ctx;
|
||||
static void wrap_set(void *c, int off)
|
||||
{ wrap_ctx *wc = c; if (wc->lv) meow_listview_set_scroll(wc->w, off); else meow_list_set_scroll(wc->w, off); }
|
||||
static int wrap_max(void *c)
|
||||
{ wrap_ctx *wc = c; return wc->lv ? meow_listview_max_scroll(wc->w) : meow_list_max_scroll(wc->w); }
|
||||
static int wrap_get(void *c)
|
||||
{ wrap_ctx *wc = c; return wc->lv ? meow_listview_scroll(wc->w) : meow_list_scroll(wc->w); }
|
||||
static void wrap_redraw(void *c)
|
||||
{
|
||||
wrap_ctx *wc = c;
|
||||
if (!wc->w) return;
|
||||
int r[4]; meow_widget_rect(wc->w, r);
|
||||
if (r[2] > 0 && r[3] > 0) meow_ui_flush_region(r[0], r[1], r[2], r[3]); /* 只刷视口区 */
|
||||
}
|
||||
|
||||
static wrap_ctx g_wrap_ctx[MEOW_SCROLL_POOL];
|
||||
static int g_wrap_used[MEOW_SCROLL_POOL];
|
||||
|
||||
meow_scroll *meow_scroll_wrap(meow_widget *list, int is_listview)
|
||||
{
|
||||
if (!list) return NULL;
|
||||
meow_scroll *s = meow_scroll_create(wrap_set, wrap_max, wrap_get, wrap_redraw, NULL);
|
||||
if (!s) return NULL;
|
||||
for (int i = 0; i < MEOW_SCROLL_POOL; i++) {
|
||||
if (!g_wrap_used[i]) {
|
||||
g_wrap_used[i] = 1;
|
||||
g_wrap_ctx[i].w = list; g_wrap_ctx[i].lv = is_listview ? 1 : 0;
|
||||
s->ctx = &g_wrap_ctx[i];
|
||||
return s;
|
||||
}
|
||||
}
|
||||
meow_scroll_free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ---- 速度采样 ---- */
|
||||
static void sample_push(meow_scroll *s, int pos, uint64_t t)
|
||||
{
|
||||
if (s->v_n >= MEOW_SCROLL_SAMPLES) { /* 满:前移丢弃最旧 */
|
||||
for (int i = 1; i < s->v_n; i++) { s->v_pos[i - 1] = s->v_pos[i]; s->v_t[i - 1] = s->v_t[i]; }
|
||||
s->v_n--;
|
||||
}
|
||||
s->v_pos[s->v_n] = pos; s->v_t[s->v_n] = t; s->v_n++;
|
||||
}
|
||||
|
||||
/* 加权初速:取窗口内采样,按间隔速度 × 近者权重加权平均(px/s)。 */
|
||||
static int sample_velocity(meow_scroll *s, uint64_t now)
|
||||
{
|
||||
int lo = 0;
|
||||
while (lo < s->v_n && now > s->v_t[lo] && (now - s->v_t[lo]) > MEOW_SCROLL_WINDOW) lo++;
|
||||
if (s->v_n - lo < 2) return 0;
|
||||
long long sum_w = 0, sum_vw = 0;
|
||||
for (int i = lo; i < s->v_n - 1; i++) {
|
||||
uint64_t dt = s->v_t[i + 1] - s->v_t[i];
|
||||
if (dt == 0) continue;
|
||||
long long v = (long long)(s->v_pos[i + 1] - s->v_pos[i]) * 1000LL / (long long)dt;
|
||||
long long w = (i - lo + 1); /* 近者权重高 */
|
||||
sum_vw += v * w; sum_w += w;
|
||||
}
|
||||
if (sum_w == 0) return 0;
|
||||
return (int)(sum_vw / sum_w);
|
||||
}
|
||||
|
||||
int meow_scroll_velocity(meow_scroll *s)
|
||||
{
|
||||
if (!s) return 0;
|
||||
return sample_velocity(s, meow_ui_time_ms());
|
||||
}
|
||||
|
||||
void meow_scroll_track(meow_scroll *s, int dy, uint64_t t_ms)
|
||||
{
|
||||
if (!s || dy == 0) return;
|
||||
int off = s->get(s->ctx) + dy; /* set 会 clamp 到 [0,max](与 scroll_by 一致) */
|
||||
s->set(s->ctx, off);
|
||||
int got = s->get(s->ctx); /* clamp 后的真实 offset */
|
||||
sample_push(s, got, t_ms);
|
||||
}
|
||||
|
||||
/* ---- 动画驱动回调 ---- */
|
||||
static void fling_update(meow_anim *a, float v, void *ctx)
|
||||
{
|
||||
(void)a;
|
||||
meow_scroll *s = (meow_scroll *)ctx;
|
||||
s->set(s->ctx, (int)v);
|
||||
if (s->redraw) s->redraw(s->ctx);
|
||||
}
|
||||
static void fling_done(meow_anim *a, int canceled, void *ctx)
|
||||
{
|
||||
(void)a;
|
||||
meow_scroll *s = (meow_scroll *)ctx;
|
||||
s->anim = NULL;
|
||||
if (canceled) { s->phase = MEOW_SCROLL_IDLE; return; }
|
||||
if (s->phase == MEOW_SCROLL_FLING) {
|
||||
if (s->bounce != 0) {
|
||||
/* 边界回弹段1:向内侧过冲 → 段2 回位(bounce 已带符号;顶界 +,底界 -) */
|
||||
int from = s->get(s->ctx);
|
||||
int to = from + s->bounce; /* bounce 已带符号 */
|
||||
if (to < 0) to = 0;
|
||||
if (to > s->maxv) to = s->maxv;
|
||||
if (to == from) { s->phase = MEOW_SCROLL_IDLE; return; }
|
||||
s->phase = MEOW_SCROLL_BOUNCE1;
|
||||
meow_anim_desc d; memset(&d, 0, sizeof d);
|
||||
d.from = (float)from; d.to = (float)to; d.duration_ms = MEOW_SCROLL_BOUNCE_MS;
|
||||
d.ease = MEOW_EASE_IN_OUT; d.update = fling_update; d.done = fling_done; d.ctx = s;
|
||||
s->anim = meow_anim_start(&d);
|
||||
return;
|
||||
}
|
||||
/* 无回弹 → snap 或收尾 */
|
||||
if (s->snap_step > 0 && s->get(s->ctx) != s->snap_to) {
|
||||
int from = s->get(s->ctx);
|
||||
int to = s->snap_to;
|
||||
if (to < 0) to = 0;
|
||||
if (to > s->maxv) to = s->maxv;
|
||||
if (to != from) {
|
||||
s->phase = MEOW_SCROLL_SNAP;
|
||||
meow_anim_desc d; memset(&d, 0, sizeof d);
|
||||
d.from = (float)from; d.to = (float)to; d.duration_ms = MEOW_SCROLL_SNAP_MS;
|
||||
d.ease = MEOW_EASE_IN_OUT; d.update = fling_update; d.done = fling_done; d.ctx = s;
|
||||
s->anim = meow_anim_start(&d);
|
||||
return;
|
||||
}
|
||||
}
|
||||
s->phase = MEOW_SCROLL_IDLE;
|
||||
return;
|
||||
}
|
||||
if (s->phase == MEOW_SCROLL_BOUNCE1) {
|
||||
int from = s->get(s->ctx);
|
||||
/* 回位到 bounce 起点(= clamp 边界) */
|
||||
int to = s->bounce_start;
|
||||
if (to < 0) to = 0;
|
||||
if (to > s->maxv) to = s->maxv;
|
||||
s->phase = MEOW_SCROLL_BOUNCE2;
|
||||
if (to == from) { s->phase = MEOW_SCROLL_IDLE; return; }
|
||||
meow_anim_desc d; memset(&d, 0, sizeof d);
|
||||
d.from = (float)from; d.to = (float)to; d.duration_ms = MEOW_SCROLL_BOUNCE_MS;
|
||||
d.ease = MEOW_EASE_IN_OUT; d.update = fling_update; d.done = fling_done; d.ctx = s;
|
||||
s->anim = meow_anim_start(&d);
|
||||
return;
|
||||
}
|
||||
if (s->phase == MEOW_SCROLL_BOUNCE2 || s->phase == MEOW_SCROLL_SNAP) {
|
||||
s->phase = MEOW_SCROLL_IDLE;
|
||||
return;
|
||||
}
|
||||
s->phase = MEOW_SCROLL_IDLE;
|
||||
}
|
||||
|
||||
/* ---- fling ---- */
|
||||
void meow_scroll_fling(meow_scroll *s)
|
||||
{
|
||||
if (!s) return;
|
||||
if (s->anim) { meow_anim_cancel(s->anim); s->anim = NULL; }
|
||||
s->phase = MEOW_SCROLL_IDLE;
|
||||
int v = sample_velocity(s, meow_ui_time_ms());
|
||||
if (v > -MEOW_SCROLL_MIN_SPEED && v < MEOW_SCROLL_MIN_SPEED) return; /* 太慢不 fling */
|
||||
int max = s->max(s->ctx);
|
||||
if (max < 0) max = 0;
|
||||
s->maxv = max;
|
||||
int pos0 = s->get(s->ctx);
|
||||
int dist = (int)((long long)v * s->tau_ms / 1000LL); /* 摩擦滑行总位移(px) */
|
||||
int free_target = pos0 + dist;
|
||||
int clamped = free_target, over = 0;
|
||||
if (clamped > max) { over = clamped - max; clamped = max; }
|
||||
if (clamped < 0) { over = -clamped; clamped = 0; }
|
||||
if (clamped == pos0) return; /* 无位移 */
|
||||
|
||||
int dir; /* 回弹方向:底界向 -1(往内上),顶界向 +1(往内下) */
|
||||
if (free_target > max) dir = -1;
|
||||
else if (free_target < 0) dir = 1;
|
||||
else dir = 0;
|
||||
|
||||
int dur = 2 * s->tau_ms; /* ease-out quad:初速与 v 对齐,末速为 0 */
|
||||
if (dur > MEOW_SCROLL_MAX_DUR) dur = MEOW_SCROLL_MAX_DUR;
|
||||
|
||||
s->bounce = 0;
|
||||
s->snap_to = pos0;
|
||||
if (s->snap_step > 0) {
|
||||
int half = s->snap_step / 2;
|
||||
s->snap_to = ((pos0 + half) / s->snap_step) * s->snap_step;
|
||||
if (s->snap_to > max) s->snap_to = max;
|
||||
}
|
||||
if (over > 12) { /* 快速撞墙:clamp + 简单回弹 */
|
||||
int amt = over / 10; if (amt < 3) amt = 3; if (amt > 14) amt = 14;
|
||||
s->bounce = dir * amt; /* 带符号的向内幅度 */
|
||||
/* 记录回位起点 = clamp 边界 */
|
||||
s->bounce_start = clamped;
|
||||
}
|
||||
|
||||
s->phase = MEOW_SCROLL_FLING;
|
||||
meow_anim_desc d; memset(&d, 0, sizeof d);
|
||||
d.from = (float)pos0; d.to = (float)clamped; d.duration_ms = (uint32_t)dur;
|
||||
d.ease = MEOW_EASE_OUT; d.update = fling_update; d.done = fling_done; d.ctx = s;
|
||||
s->anim = meow_anim_start(&d);
|
||||
}
|
||||
|
||||
void meow_scroll_stop(meow_scroll *s)
|
||||
{
|
||||
if (!s) return;
|
||||
if (s->anim) { meow_anim_cancel(s->anim); s->anim = NULL; }
|
||||
s->phase = MEOW_SCROLL_IDLE;
|
||||
}
|
||||
|
||||
int meow_scroll_active(meow_scroll *s) { return (s && s->phase != MEOW_SCROLL_IDLE) ? 1 : 0; }
|
||||
int meow_scroll_offset(meow_scroll *s) { return s ? s->get(s->ctx) : 0; }
|
||||
void meow_scroll_set_snap(meow_scroll *s, int step) { if (s) s->snap_step = step > 0 ? step : 0; }
|
||||
void meow_scroll_set_friction(meow_scroll *s, int tau_ms)
|
||||
{ if (s) s->tau_ms = (tau_ms > 20 && tau_ms <= 2000) ? tau_ms : MEOW_SCROLL_TAU; }
|
||||
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* meow_ui_str.c — SDK 字符串资源框架(i18n 雏形,§13.meow_str_*)。
|
||||
*
|
||||
* 背景:SDK/系统 UI 文案此前全部硬编码在 C 源码(dialog 的 "OK/CANCEL"、IME 的 "空格/确定/
|
||||
* 拼音/(无候选)" 等),第三方开发者无法按语言取串,公开 SDK 前必须外置。本 TU 提供
|
||||
* 「按 locale 取整串」的最小框架:内嵌双语默认表(编译进 meow_ui.a,必可用)+ TF 卡覆盖表
|
||||
* (MeowData/<pkg>/strings/<locale>.txt,开发者可加壳不改二进制)+ locale 存系统设置
|
||||
* settings.conf 的 `ui.locale`(默认 zh_cn)。
|
||||
*
|
||||
* 取值优先级(meow_str,四层,逐层回落,永不返回 NULL):
|
||||
* ① 当前 locale 的 TF 覆盖表(meow_ui_str_set_locale 加载,静态数组,≤256 条)
|
||||
* ② 内嵌表中当前语言(en_us→en,其余→zh)
|
||||
* ③ 内嵌表中内置默认(zh_cn,即 locale 缺省/未知语言时回退)
|
||||
* ④ 仍不可得 → 原样返回 id(调用方可展示 id / 打点,绝不见 NULL)
|
||||
*
|
||||
* 设计要点:
|
||||
* - 零 malloc 热路径:内嵌为编译期静态表;覆盖表为启动/切 locale 时的静态数组,
|
||||
* meow_str 只做线性扫描(表 ≤256,O(n) 可接受),不申请任何堆。
|
||||
* - 纯 libc:不依赖其它 SDK TU,宿主 gcc 直接可单测(同 meow_ui_config.c / meow_ui_vol.c)。
|
||||
* - locale 初值惰性读 settings.conf ui.locale(默认 zh_cn);meow_str_set_locale 即时生效。
|
||||
* - 覆盖表解析兼容 "key = value"('=' 两侧去空白),忽略空行/无 '=' 行;UTF-8 原样透传。
|
||||
* - 幂等:settings 只读一次;覆盖表按需加载。locale 修改须在 App 主线程(防并发写覆盖表)。
|
||||
*
|
||||
* 边界(§13 明示不做):无复数规则/BiDi/RTL/动态字体回退,理由见 meow_ui_str.h。
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#define MEOW_STR_SETTINGS_FILE "/usr/data/meow/settings.conf" /* 板载系统设置(与 theme/vol 同源) */
|
||||
#define MEOW_STR_LOCALE_MAX 16
|
||||
#define MEOW_STR_OVERRIDE_MAX 256 /* 覆盖表上限(§13 性能约束 ≤256) */
|
||||
#define MEOW_STR_OVR_ID_MAX 64
|
||||
#define MEOW_STR_OVR_TEXT_MAX 128
|
||||
#define MEOW_STR_PATH_MAX 384
|
||||
|
||||
/* ---- 内嵌双语默认表(编译进二进制,必可用;zh = 内置默认)----
|
||||
* id 规范:`域.名`(common.* 为 SDK 自带通用组;ime.* 为 SDK 自带 IME 键面组;
|
||||
* 第三方 App 可自建 域(如 player.pause)经 TF 覆盖表追加,不改 SDK 二进制)。 */
|
||||
typedef struct meow_str_embed {
|
||||
const char *id;
|
||||
const char *zh;
|
||||
const char *en;
|
||||
} meow_str_embed;
|
||||
|
||||
static const meow_str_embed g_embed[] = {
|
||||
/* common.*:通用 UI(dialog 默认按钮 / 空态占位可复用) */
|
||||
{ "common.ok", "确定", "OK" },
|
||||
{ "common.cancel", "取消", "Cancel" },
|
||||
{ "common.back", "返回", "Back" },
|
||||
{ "common.confirm", "确认", "Confirm" },
|
||||
{ "common.done", "完成", "Done" },
|
||||
{ "common.save", "保存", "Save" },
|
||||
{ "common.delete", "删除", "Delete" },
|
||||
{ "common.edit", "编辑", "Edit" },
|
||||
{ "common.search", "搜索", "Search" },
|
||||
{ "common.input", "输入", "Input" },
|
||||
{ "common.please_input", "请输入", "Please input" },
|
||||
{ "common.select", "请选择", "Select" },
|
||||
{ "common.loading", "加载中...", "Loading..." },
|
||||
{ "common.no_data", "暂无数据", "No data" },
|
||||
{ "common.retry", "重试", "Retry" },
|
||||
{ "common.failed", "失败", "Failed" },
|
||||
{ "common.retry_fail", "重试失败", "Retry failed" },
|
||||
{ "common.close", "关闭", "Close" },
|
||||
{ "common.unknown", "未知", "Unknown" },
|
||||
{ "common.settings", "设置", "Settings" },
|
||||
{ "common.tap_ok_edit", "点击确定编辑", "tap OK to edit..." },
|
||||
/* ime.*:SDK 内置 IME 键面/徽标(meow_ui_ime.c 接入) */
|
||||
{ "ime.space", "空格", "Space" },
|
||||
{ "ime.enter", "确定", "OK" },
|
||||
{ "ime.esc", "取消", "Cancel" },
|
||||
{ "ime.switch", "拼/英", "PY/EN" },
|
||||
{ "ime.symbol", "?123", "?123" },
|
||||
{ "ime.clear", "清", "Clear" },
|
||||
{ "ime.pinyin_mode", "拼音", "Pinyin" },
|
||||
{ "ime.no_candidate", "(无候选)", "(none)" },
|
||||
{ "ime.to_pinyin", "中", "中" },
|
||||
};
|
||||
#define MEOW_STR_EMBED_N (sizeof(g_embed) / sizeof(g_embed[0]))
|
||||
|
||||
/* ---- 运行时状态(静态,零 malloc)---- */
|
||||
static char g_locale[MEOW_STR_LOCALE_MAX] = "zh_cn";
|
||||
static int g_locale_loaded = 0; /* 1 = 已从 settings 读过一次 */
|
||||
static int g_override_loaded = 0; /* 1 = 覆盖表已按当前 locale 加载 */
|
||||
|
||||
/* 覆盖表(当前 locale):线性静态数组(id/text 各自定长),≤ MEOW_STR_OVERRIDE_MAX。 */
|
||||
static char g_ov_id[MEOW_STR_OVERRIDE_MAX][MEOW_STR_OVR_ID_MAX];
|
||||
static char g_ov_text[MEOW_STR_OVERRIDE_MAX][MEOW_STR_OVR_TEXT_MAX];
|
||||
static int g_ov_n = 0;
|
||||
|
||||
/* ---- settings.conf 读(同 meow_ui_theme.c get_setting_str 口径:'=' 切分,精确匹配 key)----
|
||||
* 返回 0=命中并把值拷入 buf;-1=未命中/文件不可读/超长。 */
|
||||
static int str_setting_get(const char *key, char *buf, int buflen)
|
||||
{
|
||||
FILE *f = fopen(MEOW_STR_SETTINGS_FILE, "r");
|
||||
if (!f || buflen <= 0) return -1;
|
||||
char line[160];
|
||||
int kl = (int)strlen(key);
|
||||
int found = 0;
|
||||
while (fgets(line, sizeof line, f)) {
|
||||
char *eq = strchr(line, '=');
|
||||
if (!eq) continue;
|
||||
char *k = line;
|
||||
while (*k == ' ' || *k == '\t') k++;
|
||||
int n = (int)(eq - k);
|
||||
while (n > 0 && (k[n-1] == ' ' || k[n-1] == '\t')) n--;
|
||||
if (n != kl || strncmp(k, key, (size_t)kl) != 0) continue;
|
||||
const char *v = eq + 1;
|
||||
while (*v == ' ' || *v == '\t') v++;
|
||||
int m = 0;
|
||||
while (*v && *v != '\n' && *v != '\r' && m < buflen - 1) buf[m++] = *v++;
|
||||
while (m > 0 && (buf[m-1] == ' ' || buf[m-1] == '\t')) m--;
|
||||
buf[m] = 0;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
fclose(f);
|
||||
return found ? 0 : -1;
|
||||
}
|
||||
|
||||
/* ---- settings.conf 写一个字符串键(best-effort):流式拷贝到 .tmp,命中键行改写为
|
||||
* "key = value"(launcher 落盘格式),键缺失则末尾追加;rename 原子替换。保留其余注释/设置,
|
||||
* 绝不 O_TRUNC 整文件(同 meow_ui_vol_set 思路)。失败静默返回 -1(读不改状态、不崩)。 */
|
||||
static int str_setting_set(const char *key, const char *val)
|
||||
{
|
||||
if (!key || !key[0] || !val) return -1;
|
||||
FILE *in = fopen(MEOW_STR_SETTINGS_FILE, "r");
|
||||
char tmp[MEOW_STR_PATH_MAX];
|
||||
int kl = (int)strlen(key);
|
||||
snprintf(tmp, sizeof tmp, "%s.tmp", MEOW_STR_SETTINGS_FILE);
|
||||
FILE *out = fopen(tmp, "w");
|
||||
if (!out) { if (in) fclose(in); return -1; }
|
||||
|
||||
int found = 0;
|
||||
char line[256];
|
||||
while (in && fgets(line, sizeof line, in)) {
|
||||
char *eq = strchr(line, '=');
|
||||
char *k = line;
|
||||
while (*k == ' ' || *k == '\t') k++;
|
||||
int n = eq ? (int)(eq - k) : 0;
|
||||
while (n > 0 && (k[n-1] == ' ' || k[n-1] == '\t')) n--;
|
||||
if (eq && n == kl && strncmp(k, key, (size_t)kl) == 0) {
|
||||
fprintf(out, "%s = %s\n", key, val);
|
||||
found = 1;
|
||||
} else {
|
||||
fputs(line, out);
|
||||
}
|
||||
}
|
||||
if (in) fclose(in);
|
||||
if (!found) fprintf(out, "%s = %s\n", key, val);
|
||||
fclose(out);
|
||||
|
||||
if (rename(tmp, MEOW_STR_SETTINGS_FILE) != 0) { remove(tmp); return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- TF 覆盖表路径:<MEOW_DATA_DIR 去尾 /data>/strings/<locale>.txt(= MeowData/<pkg>/strings/)。
|
||||
* 无 MEOW_DATA_DIR(手动跑/宿主/无卡)→ path 置空(覆盖表不加载,内嵌兜底)。返回 path 是否有效。 */
|
||||
static int str_override_path(char *path, int n)
|
||||
{
|
||||
const char *env = getenv("MEOW_DATA_DIR");
|
||||
if (!env || !env[0] || n <= 0) { if (n > 0) path[0] = 0; return 0; }
|
||||
char base[MEOW_STR_PATH_MAX];
|
||||
snprintf(base, sizeof base, "%s", env);
|
||||
size_t bn = strlen(base);
|
||||
/* launcher 注入的 MEOW_DATA_DIR = <pkg>/data;去尾 "/data" 回到 <pkg>,再拼 strings/。 */
|
||||
if (bn >= 5 && strcmp(base + bn - 5, "/data") == 0) base[bn - 5] = 0;
|
||||
if (snprintf(path, (size_t)n, "%s/strings/%s.txt", base, g_locale) >= n) { path[0] = 0; return 0; }
|
||||
return path[0] != 0;
|
||||
}
|
||||
|
||||
/* 解析覆盖表(`id=text` 行,'=' 两侧去空白;空行/无 '=' 行跳过;UTF-8 原样透传)。
|
||||
* 覆盖优先级:同名覆盖内嵌,未覆盖该 id 则回退内嵌。 */
|
||||
static void str_load_override(void)
|
||||
{
|
||||
char path[MEOW_STR_PATH_MAX];
|
||||
g_ov_n = 0;
|
||||
if (!str_override_path(path, sizeof path)) return;
|
||||
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) return;
|
||||
char line[MEOW_STR_OVR_TEXT_MAX + 16];
|
||||
while (fgets(line, sizeof line, f) && g_ov_n < MEOW_STR_OVERRIDE_MAX) {
|
||||
char *eq = strchr(line, '=');
|
||||
if (!eq) continue; /* 注释/空行/非 key=value */
|
||||
char *k = line;
|
||||
while (*k == ' ' || *k == '\t') k++; /* id 左去空白 */
|
||||
int klen = (int)(eq - k);
|
||||
while (klen > 0 && (k[klen-1] == ' ' || k[klen-1] == '\t')) klen--;
|
||||
if (klen <= 0 || klen >= MEOW_STR_OVR_ID_MAX) continue;
|
||||
const char *v = eq + 1;
|
||||
while (*v == ' ' || *v == '\t') v++; /* text 左去空白 */
|
||||
int vlen = 0;
|
||||
while (v[vlen] && v[vlen] != '\n' && v[vlen] != '\r' && vlen < MEOW_STR_OVR_TEXT_MAX - 1)
|
||||
vlen++;
|
||||
while (vlen > 0 && (v[vlen-1] == ' ' || v[vlen-1] == '\t')) vlen--; /* text 右去空白 */
|
||||
if (vlen <= 0) continue;
|
||||
|
||||
memcpy(g_ov_id[g_ov_n], k, (size_t)klen);
|
||||
g_ov_id[g_ov_n][klen] = 0;
|
||||
memcpy(g_ov_text[g_ov_n], v, (size_t)vlen);
|
||||
g_ov_text[g_ov_n][vlen] = 0;
|
||||
g_ov_n++;
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/* 惰性初值:首次调用读 settings.conf ui.locale(缺省 "zh_cn")。幂等(只读一次)。 */
|
||||
static void str_init_locale(void)
|
||||
{
|
||||
if (g_locale_loaded) return;
|
||||
char buf[MEOW_STR_LOCALE_MAX];
|
||||
if (str_setting_get("ui.locale", buf, sizeof buf) == 0 && buf[0])
|
||||
snprintf(g_locale, sizeof g_locale, "%s", buf);
|
||||
else
|
||||
snprintf(g_locale, sizeof g_locale, "zh_cn");
|
||||
g_locale_loaded = 1;
|
||||
}
|
||||
|
||||
/* 惰性加载覆盖表(对当前 g_locale)。幂等(只加载一次直到 set_locale 重载)。 */
|
||||
static void str_ensure_override(void)
|
||||
{
|
||||
if (g_override_loaded) return;
|
||||
str_load_override();
|
||||
g_override_loaded = 1;
|
||||
}
|
||||
|
||||
/* meow_str / meow_str_locale 前置:确保 locale 初值 + 覆盖表就绪。 */
|
||||
static void str_init(void)
|
||||
{
|
||||
str_init_locale();
|
||||
str_ensure_override();
|
||||
}
|
||||
|
||||
/* 持久化 ui.locale(best-effort)。失败静默(运行时仍切换,仅不跨启动)。 */
|
||||
static void str_persist_locale(void)
|
||||
{
|
||||
str_setting_set("ui.locale", g_locale);
|
||||
}
|
||||
|
||||
const char *meow_str_locale(void)
|
||||
{
|
||||
str_init();
|
||||
return g_locale;
|
||||
}
|
||||
|
||||
const char *meow_str(const char *id)
|
||||
{
|
||||
if (!id) return "";
|
||||
str_init();
|
||||
int en = (strcmp(g_locale, "en_us") == 0);
|
||||
|
||||
/* ① 当前 locale 的 TF 覆盖表(最高优先) */
|
||||
for (int i = 0; i < g_ov_n; i++)
|
||||
if (strcmp(g_ov_id[i], id) == 0) return g_ov_text[i];
|
||||
|
||||
/* ② 内嵌当前语言(en_us→en;其余→zh)③ 兜底内嵌内置默认(zh)——同一表内按语言取。 */
|
||||
for (size_t i = 0; i < MEOW_STR_EMBED_N; i++)
|
||||
if (strcmp(g_embed[i].id, id) == 0) return en ? g_embed[i].en : g_embed[i].zh;
|
||||
|
||||
/* ④ 未知 id:原样返回调用方(永不 NULL)。 */
|
||||
return id;
|
||||
}
|
||||
|
||||
int meow_strf(char *out, int outsz, const char *id, ...)
|
||||
{
|
||||
if (!out || outsz <= 0) return -1;
|
||||
const char *s = meow_str(id);
|
||||
va_list ap;
|
||||
va_start(ap, id);
|
||||
int r = vsnprintf(out, (size_t)outsz, s, ap);
|
||||
va_end(ap);
|
||||
return r;
|
||||
}
|
||||
|
||||
void meow_str_set_locale(const char *locale)
|
||||
{
|
||||
if (!locale || !locale[0]) return;
|
||||
str_init(); /* 确保 locale 初值 + 覆盖表基座就绪 */
|
||||
snprintf(g_locale, sizeof g_locale, "%s", locale);
|
||||
str_load_override(); /* 按新 locale 重载覆盖表 */
|
||||
g_override_loaded = 1;
|
||||
str_persist_locale(); /* best-effort 持久化(失败静默) */
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h"
|
||||
#include "meow_ui_view.h"
|
||||
#include "include/meow_ui_box.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -17,6 +18,50 @@
|
||||
static meow_view *g_view_by_id[MEOW_VIEW_BY_ID_MAX]; /* id → 顶层页 view(page_push/pop 查找) */
|
||||
static meow_view *g_top_view = NULL; /* 当前进入的顶层页 view(生命周期跟踪) */
|
||||
|
||||
/* ---- 布局提供者注册表(view → meow_box,opt-in) ---- */
|
||||
static struct { meow_view *v; meow_box *box; } g_view_layout[MEOW_VIEW_BY_ID_MAX];
|
||||
static int g_view_layout_n = 0;
|
||||
|
||||
static void layout_reg_set(meow_view *v, meow_box *box)
|
||||
{
|
||||
for (int i = 0; i < g_view_layout_n; i++) {
|
||||
if (g_view_layout[i].v == v) {
|
||||
g_view_layout[i].box = box;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (g_view_layout_n < MEOW_VIEW_BY_ID_MAX) {
|
||||
g_view_layout[g_view_layout_n].v = v;
|
||||
g_view_layout[g_view_layout_n].box = box;
|
||||
g_view_layout_n++;
|
||||
}
|
||||
}
|
||||
static meow_box *layout_reg_get(const meow_view *v)
|
||||
{
|
||||
for (int i = 0; i < g_view_layout_n; i++)
|
||||
if (g_view_layout[i].v == v) return g_view_layout[i].box;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 重排:把 view 的布局 box 解算一次(box 矩形未设时自动跟 view 内容矩形)。 */
|
||||
void meow_view_relayout(meow_view *v)
|
||||
{
|
||||
if (!v) return;
|
||||
meow_box *b = layout_reg_get(v);
|
||||
if (!b) return;
|
||||
int r[4]; meow_box_rect(b, r);
|
||||
if (r[2] <= 0 || r[3] <= 0) /* w/h 未设 → 跟 view 内容矩形 */
|
||||
meow_box_set_rect(b, v->x, v->y, v->w, v->h);
|
||||
meow_box_arrange(b);
|
||||
}
|
||||
void meow_view_set_layout(meow_view *v, struct meow_box *box)
|
||||
{
|
||||
if (!v) return;
|
||||
if (!box) { layout_reg_set(v, NULL); return; }
|
||||
layout_reg_set(v, box);
|
||||
meow_view_relayout(v); /* 立即按当前尺寸排一次 */
|
||||
}
|
||||
|
||||
static void run_fn(meow_view_fn f, meow_view *v)
|
||||
{
|
||||
if (f) f(v, v->ctx);
|
||||
@@ -146,6 +191,7 @@ void meow_view_page_push(int id)
|
||||
if (cur && cur != next) view_leave(cur);
|
||||
meow_ui_activity_push(id);
|
||||
view_enter(next);
|
||||
meow_view_relayout(next); /* 布局提供者(meow_box)随页进入自动排一次 */
|
||||
g_top_view = next ? next : cur;
|
||||
}
|
||||
|
||||
@@ -156,6 +202,7 @@ void meow_view_page_pop(void)
|
||||
meow_ui_activity_pop();
|
||||
meow_view *next = view_map_get(meow_ui_activity_active());
|
||||
view_enter(next);
|
||||
meow_view_relayout(next);
|
||||
g_top_view = next ? next : NULL;
|
||||
}
|
||||
|
||||
@@ -167,6 +214,7 @@ void meow_view_page_replace(int id)
|
||||
meow_view *next = view_map_get(id);
|
||||
if (cur && cur != next) view_leave(cur);
|
||||
view_enter(next);
|
||||
meow_view_relayout(next);
|
||||
g_top_view = next ? next : cur;
|
||||
}
|
||||
|
||||
|
||||
@@ -97,6 +97,14 @@ int meow_view_screen_y(const meow_view *v);
|
||||
meow_view *meow_view_parent(const meow_view *v);
|
||||
int meow_view_get_state(const meow_view *v); /* MEOW_VIEW_STATE_* 位掩码 */
|
||||
|
||||
/* ---- 布局提供者(§12.1.4:meow_box 作为 view 的 layout,opt-in)----
|
||||
* meow_box(见 include/meow_ui_box.h)是**纯布局计算器**,arrange 把子控件算成绝对坐标。
|
||||
* 设了布局的 view:其直接挂载控件应**同时**加入该 box(box 会在 arrange 时写这些控件的
|
||||
* x/y/w/h);page 进入/relayout 时框架自动 arrange。未设布局的 view 保持现有绝对坐标行为
|
||||
* (on_layout 里自己算,零改动)。box 矩形默认跟 view 内容矩形(w/h 未设时自动填)。 */
|
||||
void meow_view_set_layout(meow_view *v, struct meow_box *box); /* NULL=取消 */
|
||||
void meow_view_relayout(meow_view *v); /* 手动重排(改 box 后调) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -23,6 +23,12 @@ enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3,
|
||||
/* 容器(TopAppBar 动作 / NavigationBar 项 / TabBar 页签)子项上限。 */
|
||||
#define MEOW_CONTAINER_MAX 8
|
||||
|
||||
/* TopAppBar 位图动作图标(Material Symbols Rounded 单通道 alpha,见 sb_icons.h):
|
||||
* 动作单元格宽(32px 图标槽水平居中 + 两侧留白 8px)与目标 ink 高(MD3 24dp 图标)。
|
||||
* ink 垂直中线取栏中点(mid),与状态栏 28px 栏「文字中线」风格一致。 */
|
||||
#define MEOW_TOPBAR_ACTION_W 48
|
||||
#define MEOW_TOPBAR_ICON_INK 24
|
||||
|
||||
/* 按钮「按住」判定阈值:视觉上区分「短按按下」与「按住」(更深状态层)。
|
||||
* 阈值取 meow_ui_long_press_ms()(§6.3 通用长按默认 500ms,App 可配)——与长按事件同源,
|
||||
* 避免「长按已触发但视觉未加深」的不一致。触发语义不变(on_tap 仍按抬手判定)。 */
|
||||
@@ -79,6 +85,9 @@ struct meow_widget {
|
||||
char items[MEOW_CONTAINER_MAX][24]; /* W_TOPAPPBAR 动作 / W_NAVBAR 项 / W_TABBAR 页签 */
|
||||
int nitems; /* 上述项数 */
|
||||
int selected; /* W_NAVBAR / W_TABBAR 选中下标 */
|
||||
/* W_TOPAPPBAR 动作位图图标:每项 sb_icons[] 索引(≥0=画该图标;-1=画 items[i] 文本)。
|
||||
* 追加字段(calloc 归零为 0,故 create/add_action 显式置 -1 保证文本默认)。 */
|
||||
int item_icon[MEOW_CONTAINER_MAX];
|
||||
/* M2.71-bug8:W_LIST 行点击回调(可选)。 */
|
||||
meow_list_tap_fn tap_cb; /* 命中行回调(可 NULL) */
|
||||
void *tap_ctx; /* 回调 ctx */
|
||||
@@ -90,6 +99,20 @@ struct meow_widget {
|
||||
int sel_bits_n; /* 位图字数 */
|
||||
meow_listview_tap_fn lv_tap_cb; /* 行点击回调(idx, op) */
|
||||
void *lv_tap_ctx; /* 回调 ctx */
|
||||
|
||||
/* §5.5 虚拟化层(W_LISTVIEW 扩展:固定 item 池 + 多行渲染器 + 完全自绘 + 空态。
|
||||
* calloc 归零,安全;vs_count/vs_bind 均非 NULL 才进入 pool 模式,src2 模式不受影响。) */
|
||||
meow_listview_count_fn vs_count; /* 虚拟化总数回调(NULL=src2 模式) */
|
||||
meow_listview_bind_fn vs_bind; /* 虚拟化 bind 回调(NULL=src2 模式) */
|
||||
meow_listview_draw_fn vs_draw; /* 完全自绘回调(非 NULL 覆盖默认 renderer) */
|
||||
void *vs_ctx; /* 上述回调的 ctx */
|
||||
meow_lv_item *vs_pool; /* 固定 item 池(槽数 vs_pool_n) */
|
||||
int vs_pool_n; /* 池容量(视口行数 + 2,静态有界) */
|
||||
int *vs_bound; /* 每槽绑定的行号(-1=空闲;pool 索引 mod 映射) */
|
||||
int lines; /* 行档位 1/2(默认 2=双行) */
|
||||
int show_icon; /* 1=画左图标/缩略图(默认 1) */
|
||||
int show_right;/* 1=画右尾件(默认 1) */
|
||||
char empty_text[64]; /* 空态占位文案(首字符 0=不画) */
|
||||
};
|
||||
|
||||
/* ---- §5.8 batch1 内部辅助:opts 取值(opts_used==0 时按控件内建默认)----
|
||||
@@ -110,6 +133,22 @@ static int widget_disabled(const meow_widget *w) { return !(w->state & MEOW_WS_E
|
||||
static uint32_t state_tint(uint32_t base, const meow_theme *th, int t)
|
||||
{ return blend_color(base, th->onSurface, t); }
|
||||
|
||||
/* ---- TopAppBar 位图动作图标辅助(绘制与命中共用的「项宽」/「按压淡色」)----
|
||||
* 项宽:位图图标用固定单元格 MEOW_TOPBAR_ACTION_W(图标槽在该格内居中);文本项沿用
|
||||
* 原「文本宽 + 12」不等式(行为不变)。命中与绘制同源,避免 icon/text 混合排布错位。 */
|
||||
static int topbar_item_w(const meow_widget *w, const meow_theme *th, int i)
|
||||
{
|
||||
if (w->item_icon[i] >= 0) return MEOW_TOPBAR_ACTION_W;
|
||||
return meow_ui_text_width(w->items[i], th->font_body) + 12;
|
||||
}
|
||||
/* 动作颜色:默认 onSurface;按压态向栏底色(surfaceContainerHigh)淡 12%(§5.8.4 状态层)。 */
|
||||
static uint32_t topbar_action_color(const meow_widget *w, const meow_theme *th)
|
||||
{
|
||||
uint32_t col = th->onSurface;
|
||||
if (w->state & MEOW_WS_PRESSED) col = blend_color(col, th->surfaceContainerHigh, 12);
|
||||
return col;
|
||||
}
|
||||
|
||||
/* ---- §5.8.2/§5.8.3 公共 API:Options + 状态机 ---- */
|
||||
int meow_widget_set_opts(meow_widget *w, const meow_widget_opts *opts)
|
||||
{
|
||||
@@ -338,6 +377,8 @@ void meow_widget_free(meow_widget *w)
|
||||
meow_draw_widget_unregister(w); /* §6.2:绘制物注册表自动注销(stale id 失效勿用) */
|
||||
live_btn_unregister(w);
|
||||
if (w->sel_bits) { free(w->sel_bits); w->sel_bits = NULL; } /* W_LISTVIEW 多选位图 */
|
||||
if (w->vs_pool) { free(w->vs_pool); w->vs_pool = NULL; } /* W_LISTVIEW 虚拟化池 */
|
||||
if (w->vs_bound) { free(w->vs_bound); w->vs_bound = NULL; }
|
||||
free(w);
|
||||
}
|
||||
|
||||
@@ -465,6 +506,17 @@ void meow_widget_rect(const meow_widget *w, int out[4])
|
||||
out[0] = w->x; out[1] = w->y; out[2] = w->w; out[3] = w->h;
|
||||
}
|
||||
|
||||
/* 写控件矩形(§12.1 meow_box 布局引擎用:把算出的绝对坐标写进控件)。
|
||||
* 只设字段,不标脏(arrange ≠ 绘制;app 在 arrange 后自行重绘 + flush_region)。
|
||||
* 尺寸 clamp 到 >=0。既有的 meow_layout_arrange 在 TU 内直接访问字段,不受影响。 */
|
||||
void meow_widget_set_rect(meow_widget *w, int x, int y, int wd, int ht)
|
||||
{
|
||||
if (!w) return;
|
||||
w->x = x; w->y = y;
|
||||
w->w = wd > 0 ? wd : 0;
|
||||
w->h = ht > 0 ? ht : 0;
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
/* §5.8.6 batch2 Material 组件子集 API(create/setter/getter/hit) */
|
||||
/* ============================================================ */
|
||||
@@ -688,6 +740,7 @@ meow_widget *meow_topappbar_create(int x, int y, int w, int h, const char *title
|
||||
t->owner = meow_ui_activity_active();
|
||||
t->state = MEOW_WS_ENABLED;
|
||||
t->back_on = 0; t->nitems = 0;
|
||||
for (int i = 0; i < MEOW_CONTAINER_MAX; i++) t->item_icon[i] = -1; /* 默认文本项 */
|
||||
if (title) { strncpy(t->label, title, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; }
|
||||
meow_draw_widget_register(t);
|
||||
return t;
|
||||
@@ -710,10 +763,24 @@ int meow_topappbar_add_action(meow_widget *w, const char *label)
|
||||
if (label) strncpy(w->items[w->nitems], label, 23);
|
||||
w->items[w->nitems][23] = 0;
|
||||
int idx = w->nitems;
|
||||
w->item_icon[idx] = -1; /* 文本项(后续可用 set_item_icon 转位图) */
|
||||
w->nitems++;
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
return idx;
|
||||
}
|
||||
/* 把第 idx 个动作项设为位图图标(sb_icons[] 索引,MEOW_ICON_* 或 SB_I_*)。追加式 API:
|
||||
* 文本项(add_action 默认)行为不变;仅「设为图标后」该动作改用 meow_gfx_blit_icon_ink
|
||||
* 绘制(32px 槽居中 + ink 垂直对齐栏中点),颜色取主题 onSurface/按压态淡色。
|
||||
* 返回 0=成功;NULL/非 topbar/越界 idx/非法 icon_idx=负值。 */
|
||||
int meow_topappbar_set_item_icon(meow_widget *w, int idx, int icon_idx)
|
||||
{
|
||||
if (!w || w->type != W_TOPAPPBAR) return -1;
|
||||
if (idx < 0 || idx >= w->nitems) return -1;
|
||||
if (icon_idx < 0) return -1; /* ≥0 的顶层校验;越界索引由 blit 内部 clamp 兜底 */
|
||||
w->item_icon[idx] = icon_idx;
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
return 0;
|
||||
}
|
||||
int meow_topappbar_hit(meow_widget *w, int x, int y)
|
||||
{
|
||||
if (!w || w->type != W_TOPAPPBAR) return -1;
|
||||
@@ -722,7 +789,7 @@ int meow_topappbar_hit(meow_widget *w, int x, int y)
|
||||
const meow_theme *th = meow_theme_get();
|
||||
int ax = w->x + w->w - 10;
|
||||
for (int i = w->nitems - 1; i >= 0; i--) {
|
||||
int aw = meow_ui_text_width(w->items[i], th->font_body) + 12;
|
||||
int aw = topbar_item_w(w, th, i); /* 位图图标用固定单元格;文本项用原不等式 */
|
||||
ax -= aw;
|
||||
if (x >= ax && x < ax + aw && x >= w->x + 100) return i; /* 不压标题区/返回区 */
|
||||
}
|
||||
@@ -887,6 +954,8 @@ int meow_list_tap_at(meow_widget *w, int x, int y)
|
||||
int idx = meow_list_hit_index(w, x, y);
|
||||
if (idx < 0) return -1;
|
||||
meow_list_set_selected(w, idx);
|
||||
/* §6.4:列表成为唯一焦点——点外由 meow_ui_focus_tap 统一释放(list 焦点残留根治)。 */
|
||||
meow_ui_focus_acquire((void *)w);
|
||||
if (w->tap_cb) w->tap_cb(idx, w->tap_ctx);
|
||||
return idx;
|
||||
}
|
||||
@@ -910,6 +979,58 @@ extern void meow_ui_img_scaled(int x, int y, int w, int h, meow_img *img) __attr
|
||||
|
||||
static int listv_item_h(const meow_widget *w) { return (w->item_h > 0) ? w->item_h : 56; }
|
||||
|
||||
/* §5.5 虚拟化辅助:当前行数(pool 模式实时查 count_fn;src2 模式用缓存的 w->count)。 */
|
||||
static int listv_count(const meow_widget *w)
|
||||
{
|
||||
if (!w) return 0;
|
||||
if (w->vs_count) { int n = w->vs_count(w->vs_ctx); return n < 0 ? 0 : n; }
|
||||
return w->count;
|
||||
}
|
||||
|
||||
/* 池容量估算:视口行数 + 2 静态槽(按最小行高 MEOW_LV_MIN_ITEM_H 估算,静态有界)。
|
||||
* 真实列表行高远大于最小行高,故 `viewport/item_h + 1 <= cap` 恒成立 → 无槽冲突重绑。 */
|
||||
#define MEOW_LV_MIN_ITEM_H 8 /* 池容量估算用的最小行高(真实列表远大于此) */
|
||||
#define MEOW_LV_POOL_MAX 96 /* 池容量上限(固定;保证静态有界) */
|
||||
static int listv_pool_cap(const meow_widget *w)
|
||||
{
|
||||
int cap = (w->h > 0) ? (w->h / MEOW_LV_MIN_ITEM_H + 2) : 8;
|
||||
if (cap < 8) cap = 8;
|
||||
if (cap > MEOW_LV_POOL_MAX) cap = MEOW_LV_POOL_MAX;
|
||||
return cap;
|
||||
}
|
||||
/* 确保池容量 ≥ cap(有界扩张;pool 模式调用)。返回 0=成功;-1=分配失败(仍可用,槽冲突时防御性重绑)。 */
|
||||
static int listv_ensure_pool(meow_widget *w)
|
||||
{
|
||||
if (!w->vs_count || !w->vs_bind) return 0;
|
||||
int cap = listv_pool_cap(w);
|
||||
if (w->vs_pool && w->vs_pool_n >= cap) return 0;
|
||||
/* 重分配(有界:cap ≤ MEOW_LV_POOL_MAX,与 item 总数无关)。 */
|
||||
meow_lv_item *np = (meow_lv_item *)calloc((size_t)cap, sizeof *np);
|
||||
int *nb = (int *)calloc((size_t)cap, sizeof *nb);
|
||||
if (!np || !nb) { free(np); free(nb); return -1; }
|
||||
if (w->vs_pool && w->vs_pool_n > 0) {
|
||||
memcpy(np, w->vs_pool, (size_t)(w->vs_pool_n < cap ? w->vs_pool_n : cap) * sizeof *np);
|
||||
memcpy(nb, w->vs_bound, (size_t)(w->vs_pool_n < cap ? w->vs_pool_n : cap) * sizeof *nb);
|
||||
}
|
||||
for (int i = 0; i < cap; i++) nb[i] = -1; /* 全新/重分配:全部槽空闲 → 下一帧按需绑定 */
|
||||
free(w->vs_pool); free(w->vs_bound);
|
||||
w->vs_pool = np; w->vs_bound = nb; w->vs_pool_n = cap;
|
||||
return 0;
|
||||
}
|
||||
/* 取 index 对应的池槽:pool 槽按 index mod cap 映射(回滚命中同槽 → 不重绑)。
|
||||
* 槽绑定的 index 不匹配 → 防御性重新 bind(保证内容正确,即使池不足)。返回槽指针。 */
|
||||
static meow_lv_item *listv_slot(meow_widget *w, int index)
|
||||
{
|
||||
if (!w->vs_pool || w->vs_pool_n <= 0) return NULL;
|
||||
int s = (int)((unsigned)index % (unsigned)w->vs_pool_n);
|
||||
if (w->vs_bound[s] != index) {
|
||||
memset(&w->vs_pool[s], 0, sizeof w->vs_pool[s]);
|
||||
w->vs_bind(index, &w->vs_pool[s], w->vs_ctx);
|
||||
w->vs_bound[s] = index;
|
||||
}
|
||||
return &w->vs_pool[s];
|
||||
}
|
||||
|
||||
/* 文本截断:超出 maxw 加 "..."(复用 browser_text_trunc 的逐字符宽度累加)。 */
|
||||
static void listv_text_trunc(int x, int y, const char *s, int maxw, int size, uint32_t col)
|
||||
{
|
||||
@@ -1010,6 +1131,7 @@ meow_widget *meow_listview_create(int x, int y, int w, int h, int count,
|
||||
t->count = count > 0 ? count : 0; t->src2 = src; t->ctx = ctx;
|
||||
t->sel = -1; t->item_h = 56; t->scroll = 0;
|
||||
t->sel_mode = (mode >= MEOW_LIST_SEL_NONE && mode <= MEOW_LIST_SEL_MULTI) ? mode : MEOW_LIST_SEL_NONE;
|
||||
t->lines = 2; t->show_icon = 1; t->show_right = 1; /* 默认双行富 item + 图标 + 右尾件(src2 布局不变) */
|
||||
if (t->sel_mode == MEOW_LIST_SEL_MULTI && t->count > 0) {
|
||||
t->sel_bits_n = (t->count + 31) / 32;
|
||||
t->sel_bits = (uint32_t *)calloc((size_t)t->sel_bits_n, sizeof(uint32_t));
|
||||
@@ -1021,7 +1143,11 @@ meow_widget *meow_listview_create(int x, int y, int w, int h, int count,
|
||||
int meow_listview_set_item_h(meow_widget *w, int item)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return -1;
|
||||
if (item > 0) { w->item_h = item; meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); }
|
||||
if (item > 0) {
|
||||
w->item_h = item;
|
||||
listv_ensure_pool(w); /* 行高变小 → 可见行增多 → 池容量随之调整 */
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
}
|
||||
return w->item_h;
|
||||
}
|
||||
void meow_listview_set_count(meow_widget *w, int count)
|
||||
@@ -1030,10 +1156,11 @@ void meow_listview_set_count(meow_widget *w, int count)
|
||||
if (count < 0) count = 0;
|
||||
if (count == w->count) return;
|
||||
w->count = count;
|
||||
if (w->sel >= w->count) w->sel = -1;
|
||||
if (w->sel_mode == MEOW_LIST_SEL_MULTI) { /* 重分配多选位图 */
|
||||
int n = listv_count(w); /* pool 模式忽略 set_count(count 实时查) */
|
||||
if (w->sel >= n) w->sel = -1;
|
||||
if (w->sel_mode == MEOW_LIST_SEL_MULTI && !w->vs_count) { /* 重分配多选位图(src2 模式) */
|
||||
free(w->sel_bits); w->sel_bits = NULL; w->sel_bits_n = 0;
|
||||
int need = (count + 31) / 32;
|
||||
int need = (n + 31) / 32;
|
||||
if (need > 0) {
|
||||
w->sel_bits_n = need;
|
||||
w->sel_bits = (uint32_t *)calloc((size_t)need, sizeof(uint32_t));
|
||||
@@ -1047,8 +1174,9 @@ void meow_listview_set_count(meow_widget *w, int count)
|
||||
int meow_listview_max_scroll(meow_widget *w)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return 0;
|
||||
int ms = w->count * listv_item_h(w) - w->h;
|
||||
return ms < 0 ? 0 : ms;
|
||||
long long ms = (long long)listv_count(w) * listv_item_h(w) - w->h;
|
||||
if (ms < 0) return 0;
|
||||
return ms > 0x7FFFFFFF ? 0x7FFFFFFF : (int)ms;
|
||||
}
|
||||
|
||||
void meow_listview_set_scroll(meow_widget *w, int offset)
|
||||
@@ -1068,8 +1196,9 @@ void meow_listview_set_sel(meow_widget *w, int idx)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return;
|
||||
if (w->sel_mode == MEOW_LIST_SEL_NONE) return;
|
||||
if (w->count <= 0) { w->sel = -1; return; }
|
||||
if (idx >= w->count) idx = w->count - 1; /* 越界 clamp(§5.5 首/末行钳制) */
|
||||
int n = listv_count(w);
|
||||
if (n <= 0) { w->sel = -1; return; }
|
||||
if (idx >= n) idx = n - 1; /* 越界 clamp(§5.5 首/末行钳制) */
|
||||
if (w->sel_mode == MEOW_LIST_SEL_SINGLE) {
|
||||
if (idx == w->sel) return;
|
||||
w->sel = idx; /* idx<0 = 清除 */
|
||||
@@ -1090,9 +1219,9 @@ int meow_listview_get_sel(meow_widget *w, int *out, int max)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return 0;
|
||||
if (!out || max <= 0) return 0;
|
||||
int n = 0;
|
||||
int n = 0, total = listv_count(w);
|
||||
if (w->sel_mode == MEOW_LIST_SEL_MULTI) {
|
||||
for (int i = 0; i < w->count && n < max; i++)
|
||||
for (int i = 0; i < total && n < max; i++)
|
||||
if (w->sel_bits && (w->sel_bits[i >> 5] & (1u << (i & 31)))) out[n++] = i;
|
||||
} else if (w->sel >= 0 && n < max) {
|
||||
out[n++] = w->sel;
|
||||
@@ -1103,8 +1232,8 @@ int meow_listview_sel_count(meow_widget *w)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return 0;
|
||||
if (w->sel_mode == MEOW_LIST_SEL_MULTI) {
|
||||
int n = 0;
|
||||
for (int i = 0; i < w->count; i++)
|
||||
int n = 0, total = listv_count(w);
|
||||
for (int i = 0; i < total; i++)
|
||||
if (w->sel_bits && (w->sel_bits[i >> 5] & (1u << (i & 31)))) n++;
|
||||
return n;
|
||||
}
|
||||
@@ -1118,12 +1247,18 @@ int meow_listview_hit(meow_widget *w, int x, int y)
|
||||
if (x < w->x || x >= w->x + w->w || y < w->y || y >= w->y + w->h) return -1;
|
||||
int item = listv_item_h(w);
|
||||
int idx = (y - w->y + w->scroll) / item;
|
||||
if (idx < 0 || idx >= w->count) return -1;
|
||||
int n = listv_count(w);
|
||||
if (idx < 0 || idx >= n) return -1;
|
||||
if (w->src2) { /* 禁用行不响应(§5.5) */
|
||||
meow_list_item it;
|
||||
memset(&it, 0, sizeof it);
|
||||
if (!w->src2(idx, &it, w->ctx)) return -1;
|
||||
if (!it.enabled) return -1;
|
||||
} else if (w->vs_bind) { /* pool 模式:bind 临时查 enabled */
|
||||
meow_lv_item it;
|
||||
memset(&it, 0, sizeof it);
|
||||
w->vs_bind(idx, &it, w->vs_ctx);
|
||||
if (!it.enabled) return -1;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
@@ -1138,6 +1273,11 @@ int meow_listview_hit_op(meow_widget *w, int x, int y)
|
||||
memset(&it, 0, sizeof it);
|
||||
if (!w->src2(idx, &it, w->ctx)) return 0;
|
||||
if (it.right == MEOW_LIST_RIGHT_NONE) return 0;
|
||||
} else if (w->vs_bind) {
|
||||
meow_lv_item it;
|
||||
memset(&it, 0, sizeof it);
|
||||
w->vs_bind(idx, &it, w->vs_ctx);
|
||||
if (it.right == MEOW_LIST_RIGHT_NONE) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -1152,10 +1292,75 @@ int meow_listview_tap_at(meow_widget *w, int x, int y)
|
||||
if (idx < 0) return -1;
|
||||
int op = meow_listview_hit_op(w, x, y);
|
||||
meow_listview_set_sel(w, idx);
|
||||
meow_ui_focus_acquire((void *)w); /* §6.4:列表成为唯一焦点(点外由 focus_tap 释放) */
|
||||
if (w->lv_tap_cb) w->lv_tap_cb(idx, op, w->lv_tap_ctx);
|
||||
return idx;
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
/* §5.5 虚拟化层 API(W_LISTVIEW 扩展;pool 模式;src2 模式不变) */
|
||||
/* ============================================================ */
|
||||
int meow_listview_set_vsource(meow_widget *w, meow_listview_count_fn count,
|
||||
meow_listview_bind_fn bind, void *ctx)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW || !count || !bind) return -1;
|
||||
w->vs_count = count; w->vs_bind = bind; w->vs_ctx = ctx;
|
||||
w->vs_draw = NULL; /* 重挂源:清自绘(默认 renderer) */
|
||||
if (listv_ensure_pool(w) != 0) { /* 分配固定 item 池(有界) */
|
||||
w->vs_count = NULL; w->vs_bind = NULL; /* 池分配失败:回退 src2 模式 */
|
||||
return -1;
|
||||
}
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_listview_set_item_renderer(meow_widget *w, int lines, int show_icon,
|
||||
int show_right)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return -1;
|
||||
if (lines < 1) lines = 1;
|
||||
if (lines > 2) lines = 2;
|
||||
w->lines = lines; w->show_icon = show_icon ? 1 : 0; w->show_right = show_right ? 1 : 0;
|
||||
if (w->item_h <= 0) w->item_h = (lines == 1) ? 48 : 56; /* 未显式定行高 → 档位默认 */
|
||||
listv_ensure_pool(w); /* 行档位可能变化 → 确保池容量 */
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_listview_set_item_tier(meow_widget *w, int lines)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return -1;
|
||||
if (lines < 1) lines = 1;
|
||||
if (lines > 2) lines = 2;
|
||||
w->lines = lines;
|
||||
w->item_h = (lines == 1) ? 48 : 56; /* 档位默认高(可随后 set_item_h 覆盖) */
|
||||
listv_ensure_pool(w);
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_listview_set_item_draw(meow_widget *w, meow_listview_draw_fn draw,
|
||||
void *ctx)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return -1;
|
||||
w->vs_draw = draw; w->vs_ctx = ctx; /* 自绘覆盖默认 renderer;NULL=用默认 */
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_listview_set_empty_text(meow_widget *w, const char *text)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return -1;
|
||||
if (text) {
|
||||
strncpy(w->empty_text, text, sizeof w->empty_text - 1);
|
||||
w->empty_text[sizeof w->empty_text - 1] = '\0';
|
||||
} else {
|
||||
w->empty_text[0] = '\0';
|
||||
}
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hit(meow_widget *w, int x, int y)
|
||||
{
|
||||
if (!w) return 0;
|
||||
@@ -1180,7 +1385,8 @@ int meow_btn_pressed(meow_widget *w)
|
||||
void meow_slider_set(meow_widget *w, int val)
|
||||
{
|
||||
if (!w || w->type != W_SLIDER) return;
|
||||
if (val < w->min) val = w->min; if (val > w->max) val = w->max;
|
||||
if (val < w->min) val = w->min;
|
||||
if (val > w->max) val = w->max;
|
||||
if (val == w->val) return;
|
||||
w->val = val;
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); /* 滑块/轨道随值变化:标脏 */
|
||||
@@ -1351,79 +1557,187 @@ void meow_widget_draw(meow_widget *w)
|
||||
break;
|
||||
}
|
||||
case W_LISTVIEW: {
|
||||
/* §5.5 结构化列表(纵向 LazyColumn):只画可见行(虚拟化)+ 视口裁剪(M2.73 纪律)。
|
||||
* 行 = 图标(左) + 标题 + 副标题(次要色) + 右侧操作区;选中行 = primaryContainer /
|
||||
* onPrimaryContainer(§5.8.3);禁用行变淡不响应(命中由 hit 层保证)。全语义色。 */
|
||||
/* §5.5 结构化列表(纵向):只画可见行 + 视口裁剪(M2.73)。三路:
|
||||
* ① src2 模式(既有):每帧按需取行(meow_list2_source),行为不变;
|
||||
* ② 虚拟化 pool 模式(set_vsource + set_item_renderer):固定 item 池(进入视口才 bind),
|
||||
* 默认 renderer 画多行富 item(图标+标题+副标题+右尾件),行高可配;滚动只移 viewport;
|
||||
* ③ 完全自绘(set_item_draw):App 自绘每可见行(SDK 只给矩形+状态位)。
|
||||
* 空态:count==0 且设 empty_text → 视口内居中。全语义色;选中/按压/禁用态走主题。 */
|
||||
int item = listv_item_h(w);
|
||||
int vp = w->h; /* 纵向 */
|
||||
int off = w->scroll;
|
||||
int maxs = w->count * item - vp;
|
||||
int count = listv_count(w);
|
||||
int maxs = (int)((long long)count * item - vp);
|
||||
if (maxs < 0) maxs = 0;
|
||||
if (off < 0) off = 0;
|
||||
if (off > maxs) off = maxs;
|
||||
int first = (item > 0) ? off / item : 0;
|
||||
int last = (item > 0) ? (off + vp) / item + 1 : 0;
|
||||
if (first < 0) first = 0;
|
||||
if (last > w->count) last = w->count;
|
||||
if (last > count) last = count;
|
||||
int vx0 = w->x, vy0 = w->y, vy1 = w->y + w->h;
|
||||
int thh = meow_ui_text_height(th->font_body);
|
||||
int pad = MEOW_LISTV_PAD, isz = MEOW_LISTV_ICON;
|
||||
int text_x = vx0 + pad + isz + pad;
|
||||
int op_start = vx0 + (w->w * 2 / 3); /* 行右 1/3 = 操作区(§5.5 命中) */
|
||||
int text_maxw = op_start - pad - text_x; /* 标题/副标题可用宽(不压操作区) */
|
||||
int text_x = vx0 + pad + ((w->show_icon) ? (isz + pad) : 0); /* 无图标则无图标间隙 */
|
||||
int op_start = (w->show_right) ? (vx0 + (w->w * 2 / 3)) /* 行右 1/3 = 操作区 */
|
||||
: (vx0 + w->w - pad);
|
||||
int text_maxw = (w->show_right) ? (op_start - pad - text_x) /* 标题/副标题可用宽(不压操作区) */
|
||||
: ((vx0 + w->w - pad) - text_x);
|
||||
if (text_maxw < 8) text_maxw = 8;
|
||||
for (int i = first; i < last; i++) {
|
||||
meow_list_item it;
|
||||
memset(&it, 0, sizeof it);
|
||||
if (!w->src2 || !w->src2(i, &it, w->ctx)) continue;
|
||||
int ry = w->y + (i * item - off);
|
||||
if (ry >= vy1) break;
|
||||
if (ry + item <= vy0) continue;
|
||||
int cl = ry > vy0 ? ry : vy0; /* 可见顶界(裁剪用) */
|
||||
int cr = (ry + item) < vy1 ? (ry + item) : vy1; /* 可见底界 */
|
||||
int sel = (i == w->sel);
|
||||
uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface;
|
||||
uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
|
||||
if (sel) meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->primaryContainer);
|
||||
|
||||
/* 图标(左) */
|
||||
uint32_t iccol = sel ? th->onPrimaryContainer
|
||||
: ((it.icon_kind == MEOW_LIST_ICON_FOLDER) ? th->primary
|
||||
: th->onSurfaceVariant);
|
||||
int icy = ry + (item - isz) / 2;
|
||||
if (icy + isz >= vy0 && icy < vy1) /* 图标不越视口边界才画(防泄漏) */
|
||||
listv_draw_icon(th, vx0 + pad, icy, it.icon_kind, it.icon, iccol);
|
||||
/* 空态占位(§5.5):count==0 且设了 empty_text → 视口内居中画文案。 */
|
||||
if (count == 0 && w->empty_text[0]) {
|
||||
int ty = vy0 + (w->h - thh) / 2;
|
||||
meow_ui_text_center(vx0 + w->w / 2, ty, w->empty_text, th->font_body,
|
||||
th->onSurfaceVariant);
|
||||
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
|
||||
break;
|
||||
}
|
||||
|
||||
/* 标题 + 副标题(两行居中;无副标题=单行居中;越界不画防泄漏) */
|
||||
int ty;
|
||||
if (it.subtitle && it.subtitle[0]) {
|
||||
ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
||||
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it.title, text_maxw, th->font_body, textcol);
|
||||
int sy = ty + thh + MEOW_LISTV_SUB_GAP;
|
||||
if (sy >= vy0 && sy + thh <= vy1)
|
||||
listv_text_trunc(text_x, sy, it.subtitle, text_maxw, th->font_body, subcol);
|
||||
} else {
|
||||
ty = ry + (item - thh) / 2;
|
||||
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it.title, text_maxw, th->font_body, textcol);
|
||||
if (w->vs_count && w->vs_bind) { /* 虚拟化 pool 模式 */
|
||||
listv_ensure_pool(w); /* 防御:视口/行高变化时确保池容量(cap 已够则零开销保留绑定) */
|
||||
if (w->vs_draw) { /* ③ 完全自绘:App 画整行(widget clip 已裁到列表视口) */
|
||||
for (int i = first; i < last; i++) {
|
||||
int ry = w->y + (i * item - off);
|
||||
if (ry >= vy1) break;
|
||||
int st = 0;
|
||||
if (i == w->sel && w->sel_mode != MEOW_LIST_SEL_NONE) st |= MEOW_LISTVIEW_ST_SELECTED;
|
||||
if (meow_input_touch_down() &&
|
||||
meow_input_touch_x() >= vx0 && meow_input_touch_x() < vx0 + w->w &&
|
||||
meow_input_touch_y() >= ry && meow_input_touch_y() < ry + item)
|
||||
st |= MEOW_LISTVIEW_ST_PRESSED;
|
||||
w->vs_draw(i, vx0, ry, w->w, item, st, w->vs_ctx);
|
||||
}
|
||||
} else { /* ② 默认 renderer:固定 item 池(进入视口才 bind) */
|
||||
for (int i = first; i < last; i++) {
|
||||
meow_lv_item *it = listv_slot(w, i); /* 绑定到固定池槽(回滚同槽不重绑) */
|
||||
if (!it) continue;
|
||||
int ry = w->y + (i * item - off);
|
||||
if (ry >= vy1) break;
|
||||
if (ry + item <= vy0) continue;
|
||||
int cl = ry > vy0 ? ry : vy0; /* 可见顶界(裁剪用) */
|
||||
int cr = (ry + item) < vy1 ? (ry + item) : vy1;
|
||||
int sel = (i == w->sel && w->sel_mode != MEOW_LIST_SEL_NONE);
|
||||
int press = meow_input_touch_down() &&
|
||||
meow_input_touch_x() >= vx0 && meow_input_touch_x() < vx0 + w->w &&
|
||||
meow_input_touch_y() >= ry && meow_input_touch_y() < ry + item;
|
||||
|
||||
if (it->section) { /* 段头占位行(非粘性):surfaceContainerHigh 底 + primary 段名 */
|
||||
meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->surfaceContainerHigh);
|
||||
int sty = ry + (item - thh) / 2;
|
||||
if (it->title && sty >= vy0 && sty + thh <= vy1)
|
||||
listv_text_trunc(text_x, sty, it->title, text_maxw, th->font_body, th->primary);
|
||||
int sy = ry + item - 1;
|
||||
if (sy >= cl && sy < cr)
|
||||
meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant);
|
||||
if (!it->enabled)
|
||||
meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 选中/按压背景(primaryContainer / 半透明主题色按压层) */
|
||||
if (sel) meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->primaryContainer);
|
||||
else if (press) meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->primary, 26);
|
||||
uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface;
|
||||
uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
|
||||
|
||||
/* 左图标/缩略图(show_icon=1;越界不画防泄漏) */
|
||||
if (w->show_icon) {
|
||||
uint32_t iccol = sel ? th->onPrimaryContainer
|
||||
: ((it->icon_kind == MEOW_LIST_ICON_FOLDER) ? th->primary
|
||||
: th->onSurfaceVariant);
|
||||
int icy = ry + (item - isz) / 2;
|
||||
if (icy + isz >= vy0 && icy < vy1)
|
||||
listv_draw_icon(th, vx0 + pad, icy, it->icon_kind, it->icon, iccol);
|
||||
}
|
||||
|
||||
/* 标题 + 副标题(双行居中;单行(lines==1 或无副标题)单行居中);越界不画 */
|
||||
if (w->lines == 2 && it->subtitle && it->subtitle[0]) {
|
||||
int ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
||||
if (it->title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it->title, text_maxw, th->font_body, textcol);
|
||||
int sy = ty + thh + MEOW_LISTV_SUB_GAP;
|
||||
if (sy >= vy0 && sy + thh <= vy1)
|
||||
listv_text_trunc(text_x, sy, it->subtitle, text_maxw, th->font_body, subcol);
|
||||
} else {
|
||||
int ty = ry + (item - thh) / 2;
|
||||
if (it->title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it->title, text_maxw, th->font_body, textcol);
|
||||
}
|
||||
|
||||
/* 右尾件(show_right=1:箭头/开关/复选/⋯) */
|
||||
if (w->show_right && it->right != MEOW_LIST_RIGHT_NONE) {
|
||||
int ax = w->x + w->w - pad - 24; /* 右缘动作槽(约 24px) */
|
||||
if (ax >= op_start)
|
||||
listv_draw_right(th, ax, ry, item, it->right, it->on, subcol);
|
||||
}
|
||||
|
||||
/* 分隔线(仅落在可见带内才画) */
|
||||
int sy = ry + item - 1;
|
||||
if (sy >= cl && sy < cr)
|
||||
meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant);
|
||||
|
||||
/* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */
|
||||
if (!it->enabled)
|
||||
meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* ① src2 模式(既有):每帧按需取行(meow_list2_source),行为不变。
|
||||
* 行 = 图标(左) + 标题 + 副标题(次要色) + 右侧操作区;选中行 = primaryContainer。 */
|
||||
for (int i = first; i < last; i++) {
|
||||
meow_list_item it;
|
||||
memset(&it, 0, sizeof it);
|
||||
if (!w->src2 || !w->src2(i, &it, w->ctx)) continue;
|
||||
int ry = w->y + (i * item - off);
|
||||
if (ry >= vy1) break;
|
||||
if (ry + item <= vy0) continue;
|
||||
int cl = ry > vy0 ? ry : vy0; /* 可见顶界(裁剪用) */
|
||||
int cr = (ry + item) < vy1 ? (ry + item) : vy1; /* 可见底界 */
|
||||
int sel = (i == w->sel);
|
||||
uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface;
|
||||
uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
|
||||
if (sel) meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->primaryContainer);
|
||||
|
||||
/* 右侧操作区 */
|
||||
if (it.right != MEOW_LIST_RIGHT_NONE) {
|
||||
int ax = w->x + w->w - pad - 24; /* 右缘动作槽(约 24px) */
|
||||
if (ax >= op_start)
|
||||
listv_draw_right(th, ax, ry, item, it.right, it.on, subcol);
|
||||
/* 图标(左) */
|
||||
uint32_t iccol = sel ? th->onPrimaryContainer
|
||||
: ((it.icon_kind == MEOW_LIST_ICON_FOLDER) ? th->primary
|
||||
: th->onSurfaceVariant);
|
||||
int icy = ry + (item - isz) / 2;
|
||||
if (icy + isz >= vy0 && icy < vy1) /* 图标不越视口边界才画(防泄漏) */
|
||||
listv_draw_icon(th, vx0 + pad, icy, it.icon_kind, it.icon, iccol);
|
||||
|
||||
/* 标题 + 副标题(两行居中;无副标题=单行居中;越界不画防泄漏) */
|
||||
int ty;
|
||||
if (it.subtitle && it.subtitle[0]) {
|
||||
ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
||||
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it.title, text_maxw, th->font_body, textcol);
|
||||
int sy = ty + thh + MEOW_LISTV_SUB_GAP;
|
||||
if (sy >= vy0 && sy + thh <= vy1)
|
||||
listv_text_trunc(text_x, sy, it.subtitle, text_maxw, th->font_body, subcol);
|
||||
} else {
|
||||
ty = ry + (item - thh) / 2;
|
||||
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it.title, text_maxw, th->font_body, textcol);
|
||||
}
|
||||
|
||||
/* 右侧操作区 */
|
||||
if (it.right != MEOW_LIST_RIGHT_NONE) {
|
||||
int ax = w->x + w->w - pad - 24; /* 右缘动作槽(约 24px) */
|
||||
if (ax >= op_start)
|
||||
listv_draw_right(th, ax, ry, item, it.right, it.on, subcol);
|
||||
}
|
||||
|
||||
/* 分隔线(仅落在可见带内才画) */
|
||||
int sy = ry + item - 1;
|
||||
if (sy >= cl && sy < cr)
|
||||
meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant);
|
||||
|
||||
/* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */
|
||||
if (!it.enabled)
|
||||
meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90);
|
||||
}
|
||||
|
||||
/* 分隔线(仅落在可见带内才画) */
|
||||
int sy = ry + item - 1;
|
||||
if (sy >= cl && sy < cr)
|
||||
meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant);
|
||||
|
||||
/* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */
|
||||
if (!it.enabled)
|
||||
meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90);
|
||||
}
|
||||
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
|
||||
break;
|
||||
@@ -1575,12 +1889,21 @@ void meow_widget_draw(meow_widget *w)
|
||||
tx = w->x + 48;
|
||||
}
|
||||
if (w->label[0]) meow_ui_text(tx, ty, w->label, th->font_title, th->onSurface);
|
||||
/* 动作区:从右往左排。位图图标(item_icon>=0)用 sb_icons[] 位图(Material Symbols
|
||||
* Rounded)经 meow_gfx_blit_icon_ink 画(32px 槽在动作格内水平居中 + ink 垂直对齐 mid,
|
||||
* 颜色 = onSurface/按压淡色);文本项沿用 meow_ui_text_center(行为不变)。 */
|
||||
int ay = mid - meow_ui_text_height(th->font_body) / 2;
|
||||
uint32_t acol = topbar_action_color(w, th);
|
||||
int ax = w->x + w->w - 12;
|
||||
for (int i = w->nitems - 1; i >= 0; i--) {
|
||||
int aw = meow_ui_text_width(w->items[i], th->font_body) + 12;
|
||||
int aw = topbar_item_w(w, th, i);
|
||||
ax -= aw;
|
||||
meow_ui_text_center(ax + aw / 2, ay, w->items[i], th->font_body, th->onSurface);
|
||||
if (w->item_icon[i] >= 0) {
|
||||
int slot_x = ax + (aw - MEOW_SB_ICON_SIZE) / 2; /* 32px 槽在格内居中 */
|
||||
meow_gfx_blit_icon_ink(w->item_icon[i], slot_x, MEOW_TOPBAR_ICON_INK, acol, mid);
|
||||
} else {
|
||||
meow_ui_text_center(ax + aw / 2, ay, w->items[i], th->font_body, acol);
|
||||
}
|
||||
}
|
||||
meow_ui_fill_rect(w->x, w->y + w->h - 1, w->w, 1, th->outlineVariant);
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
/* sb_icons.h — 状态栏/通知中心图标(Material Symbols Rounded, MD3),单通道 alpha 位图 32x32。
|
||||
* 生成自 material-symbols@0.47.0 rounded 字体光栅化;每像素 0..255(亮度/alpha)。
|
||||
* ★ 为日/夜间预留:位图只存亮度;绘制颜色由调用方主题表提供,换主题仅改配色。 */
|
||||
/* sb_icons.h — 状态栏 + 动作位图图标(Material Symbols Rounded, MD3),单通道 alpha 位图 32x32。
|
||||
* 生成自 material-symbols rounded(google/material-design-icons,Apache-2.0)SVG 栅格化:
|
||||
* 图标族 = FILL=1 实心(与状态栏 WiFi/BT/BATT 现有实心风格一致);每像素 0..255(亮度/alpha)。
|
||||
* ★ 为日/夜间预留:位图只存亮度;绘制颜色由调用方主题表提供,换主题仅改配色。
|
||||
* 索引布局(兼容):状态栏 SB_I_WIFI=0..SB_I_BATT_CHG=14(原样保留);TopAppBar 动作
|
||||
* MEOW_ICON_SETTINGS=15..MEOW_ICON_HEART=29(只追加,既有索引不变)。动作图标为 15-29,
|
||||
* 经 meow_topappbar_set_item_icon(w, idx, MEOW_ICON_*) 使 W_TOPAPPBAR 动作项画位图。
|
||||
* 重新生成:python3 tools/gen_action_icons.py(SVG 缓存 tools/icons_src/,可复现)。 */
|
||||
#ifndef SB_ICONS_H
|
||||
#define SB_ICONS_H
|
||||
|
||||
@@ -196,6 +201,521 @@ static const unsigned char SB_ICON_BATT_CHG[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,191,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,207,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,95,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,239,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,223,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,255,255,191,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,159,255,255,255,191,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,63,255,255,255,255,207,63,63,63,63,63,63,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,191,255,255,255,255,255,255,255,255,255,255,191,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,95,255,255,255,255,255,255,255,255,255,255,239,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,223,255,255,255,255,255,255,255,255,255,255,111,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,255,255,255,255,255,255,255,255,255,255,191,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,239,255,255,255,255,255,255,255,255,255,239,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,111,255,255,255,255,79,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,175,255,255,255,175,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,239,255,255,239,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,255,255,255,79,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,95,255,255,175,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,159,255,239,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,223,255,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,255,143,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,79,223,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,143,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,95,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
};
|
||||
|
||||
/* ===== TopAppBar 动作图标(Material Symbols Rounded, MD3, Apache-2.0)====
|
||||
|
||||
* 复用状态栏同源 32x32 单通道 alpha;绘制颜色/按压态由 meow_ui_widget.c 主题表提供。 */
|
||||
|
||||
static const unsigned char SB_ICON_SETTINGS[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,2,4,4,4,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,1,0,40,84,85,85,84,40,0,1,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,3,0,83,248,255,255,255,255,248,83,0,3,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,2,3,1,0,1,2,213,255,249,252,252,249,255,213,2,1,0,1,3,2,0,0,0,0,0,0,
|
||||
0,0,0,0,0,2,0,0,0,5,3,17,239,254,254,255,255,254,254,239,17,3,5,0,0,0,2,0,0,0,0,0,
|
||||
0,0,0,0,2,0,33,55,5,0,0,45,254,254,255,255,255,255,254,254,46,0,0,5,55,33,0,2,0,0,0,0,
|
||||
0,0,0,3,0,104,246,255,216,115,95,221,254,254,255,255,255,255,254,254,223,96,114,216,255,246,104,0,3,0,0,0,
|
||||
0,0,2,1,36,251,255,253,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,253,255,251,36,1,2,0,0,
|
||||
0,0,3,0,174,255,251,255,253,252,252,253,255,254,251,251,251,251,254,255,253,252,252,253,255,251,255,174,0,3,0,0,
|
||||
0,3,0,67,255,252,255,255,255,255,255,255,253,255,255,255,255,255,255,253,255,255,255,255,255,255,252,255,67,0,3,0,
|
||||
0,3,0,171,253,251,255,255,255,255,255,253,255,243,151,95,92,142,234,255,253,255,255,255,255,255,251,253,171,0,3,0,
|
||||
0,4,0,149,255,248,255,255,255,255,253,255,216,41,0,0,0,0,28,198,255,253,255,255,255,255,248,255,149,0,4,0,
|
||||
0,2,1,29,217,255,254,255,255,254,254,245,41,0,6,4,4,5,0,20,231,254,254,255,255,254,255,217,29,1,2,0,
|
||||
0,0,1,0,19,172,253,253,255,251,255,163,0,5,0,0,0,0,5,0,129,255,251,255,253,253,172,19,0,1,0,0,
|
||||
0,0,0,3,0,11,238,255,254,251,255,107,1,4,0,0,0,0,3,1,75,255,252,254,255,239,11,0,3,0,0,0,
|
||||
0,0,0,3,0,10,238,255,254,251,255,107,1,4,0,0,0,0,3,1,75,255,252,254,255,237,9,0,2,0,0,0,
|
||||
0,0,1,0,19,172,253,253,255,251,255,163,0,5,0,0,0,0,6,0,129,255,251,255,253,252,167,16,0,1,0,0,
|
||||
0,2,1,29,217,255,254,255,255,254,254,245,41,0,6,4,4,5,0,21,231,254,254,255,255,254,255,212,25,1,1,0,
|
||||
0,4,0,149,255,248,255,255,255,255,253,255,217,41,0,0,0,0,28,198,255,253,255,255,255,255,248,255,140,0,4,0,
|
||||
0,3,0,171,253,251,255,255,255,255,255,253,255,243,152,96,93,143,234,255,253,255,255,255,255,255,251,253,162,0,4,0,
|
||||
0,3,0,67,255,252,255,255,255,255,255,255,253,255,255,255,255,255,255,253,255,255,255,255,255,255,252,254,60,0,3,0,
|
||||
0,0,3,0,174,255,251,255,253,252,252,253,255,254,251,251,251,251,254,255,253,252,252,253,255,251,255,162,0,3,0,0,
|
||||
0,0,2,1,36,251,255,253,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,252,255,244,26,2,2,0,0,
|
||||
0,0,0,3,0,104,246,255,216,115,96,223,254,254,255,255,255,255,254,254,222,95,117,220,255,241,90,0,3,0,0,0,
|
||||
0,0,0,0,2,0,33,55,5,0,0,46,254,254,255,255,255,255,254,254,45,0,0,8,56,29,0,2,0,0,0,0,
|
||||
0,0,0,0,0,2,0,0,0,5,3,17,239,254,254,255,255,254,254,239,17,3,5,0,0,0,2,0,0,0,0,0,
|
||||
0,0,0,0,0,0,2,3,1,0,1,2,213,255,249,252,252,249,255,213,2,1,0,1,3,2,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,3,0,83,248,255,255,255,255,248,83,0,3,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,1,0,40,84,85,85,84,40,0,1,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,2,4,4,4,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
static const unsigned char SB_ICON_REFRESH[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,2,4,4,3,3,4,4,2,0,0,0,0,3,4,2,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,3,2,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,3,0,0,40,100,144,164,164,141,94,32,0,0,5,0,88,159,39,0,2,0,0,0,
|
||||
0,0,0,0,0,0,0,1,2,0,58,180,247,255,255,255,255,255,255,242,167,47,0,15,238,255,163,0,4,0,0,0,
|
||||
0,0,0,0,0,0,1,2,0,127,253,255,255,255,251,237,237,252,255,255,255,248,109,4,233,251,169,0,3,0,0,0,
|
||||
0,0,0,0,0,0,3,0,153,255,254,255,192,98,43,21,21,45,102,197,255,255,255,151,225,255,168,0,3,0,0,0,
|
||||
0,0,0,0,0,3,0,127,255,251,245,97,0,0,0,0,0,0,0,0,101,246,254,255,251,255,169,0,3,0,0,0,
|
||||
0,0,0,0,3,0,53,255,252,245,63,0,3,4,2,1,1,4,1,0,0,58,242,251,250,255,169,0,3,0,0,0,
|
||||
0,0,0,0,2,0,184,255,255,95,0,6,0,0,0,0,0,0,12,18,23,8,116,255,249,255,168,0,3,0,0,0,
|
||||
0,0,0,2,0,37,250,254,195,0,4,0,0,0,0,3,0,89,226,232,234,236,229,251,248,251,170,0,3,0,0,0,
|
||||
0,0,0,4,0,102,255,255,95,1,4,0,0,0,0,4,0,172,255,255,255,255,255,255,255,255,165,0,4,0,0,0,
|
||||
0,0,0,4,0,146,255,250,40,0,2,0,0,0,0,2,0,39,158,168,168,168,168,169,169,159,41,0,2,0,0,0,
|
||||
0,0,0,3,0,165,255,236,20,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,3,0,165,255,236,20,0,1,0,0,0,0,0,0,2,4,3,3,3,3,4,4,4,2,0,0,0,0,0,
|
||||
0,0,0,4,0,146,255,250,40,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,0,0,0,0,0,
|
||||
0,0,0,4,0,102,255,255,95,1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,
|
||||
0,0,0,2,0,37,250,254,195,0,4,0,0,0,0,0,0,0,0,0,0,4,0,160,226,88,0,3,0,0,0,0,
|
||||
0,0,0,0,2,0,184,255,255,95,0,6,0,0,0,0,0,0,0,0,6,0,100,255,255,165,0,4,0,0,0,0,
|
||||
0,0,0,0,3,0,53,255,252,245,63,0,3,4,2,1,1,2,4,2,0,65,246,251,249,44,0,2,0,0,0,0,
|
||||
0,0,0,0,0,3,0,127,255,251,245,97,0,0,0,0,0,0,0,0,102,245,251,255,115,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,0,3,0,153,255,254,255,192,98,43,21,22,44,102,197,255,255,255,143,0,3,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,1,2,0,127,253,255,255,255,251,237,237,251,255,255,255,248,116,0,3,1,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,1,2,0,57,180,247,255,255,255,255,255,255,244,171,49,0,2,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,3,0,0,40,100,144,164,164,142,97,35,0,0,3,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,2,4,4,3,3,4,4,2,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
static const unsigned char SB_ICON_SEARCH[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,1,4,2,0,0,0,1,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,3,0,0,0,7,17,11,0,0,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,3,0,19,111,185,221,233,227,201,141,48,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,88,228,255,255,255,255,255,255,255,250,143,5,0,1,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,3,0,117,255,255,255,241,194,171,182,224,255,255,255,185,8,2,1,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,3,0,85,255,250,255,142,30,0,0,0,12,95,232,253,255,165,0,3,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,1,1,18,234,253,254,97,0,0,2,3,2,0,0,37,227,252,255,79,0,3,0,0,0,0,0,0,0,0,0,
|
||||
0,0,4,0,112,255,255,144,0,6,2,0,0,0,1,6,0,61,252,254,195,0,2,0,0,0,0,0,0,0,0,0,
|
||||
0,0,2,0,189,254,241,23,2,2,0,0,0,0,0,0,4,0,182,255,247,30,0,2,0,0,0,0,0,0,0,0,
|
||||
0,1,0,7,223,255,192,0,2,0,0,0,0,0,0,0,4,1,111,255,255,69,0,3,0,0,0,0,0,0,0,0,
|
||||
0,1,0,17,233,255,168,1,3,0,0,0,0,0,0,0,4,0,86,255,254,85,0,4,0,0,0,0,0,0,0,0,
|
||||
0,1,0,11,228,255,180,0,2,0,0,0,0,0,0,0,4,1,98,255,255,76,0,3,0,0,0,0,0,0,0,0,
|
||||
0,0,1,0,204,255,225,7,2,1,0,0,0,0,0,0,4,0,153,255,251,40,0,2,0,0,0,0,0,0,0,0,
|
||||
0,0,4,0,144,255,255,93,0,6,0,0,0,0,0,3,0,21,236,255,209,3,1,0,0,0,0,0,0,0,0,0,
|
||||
0,0,2,0,44,254,253,233,36,0,4,4,4,4,4,0,0,178,255,255,101,0,4,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,3,0,143,255,253,225,66,0,0,0,0,0,27,175,255,254,207,0,4,1,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,1,3,5,185,255,255,254,181,115,90,102,154,235,255,255,253,242,99,0,5,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,1,1,8,163,255,255,255,255,255,255,255,255,255,207,252,255,255,100,0,5,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,0,82,191,243,255,255,255,249,204,104,2,98,255,251,255,100,0,5,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,1,2,0,0,31,67,83,74,41,0,0,2,0,99,255,251,255,100,0,5,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,3,1,0,0,0,0,0,0,3,1,5,0,99,255,251,255,100,0,5,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,2,3,4,3,2,0,0,0,0,5,0,99,255,251,255,100,0,3,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,99,255,251,255,100,0,2,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,99,255,251,254,69,0,3,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,99,255,255,94,0,4,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,69,93,2,1,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
static const unsigned char SB_ICON_PLAY_ARROW[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,1,0,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,1,0,39,39,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,2,0,48,247,249,134,10,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,90,255,252,255,212,72,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,85,254,252,251,255,255,156,23,0,2,2,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,86,255,252,255,253,253,255,228,93,0,0,4,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,86,255,252,255,255,255,251,255,255,177,38,0,1,2,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,86,255,252,255,255,255,255,254,252,255,241,115,2,0,1,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,86,255,252,255,255,255,255,255,255,252,255,255,197,51,0,2,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,86,255,252,255,255,255,255,255,255,255,254,251,255,246,29,0,2,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,86,255,252,255,255,255,255,255,255,255,254,251,255,246,29,0,2,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,86,255,252,255,255,255,255,255,255,252,255,255,197,51,0,2,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,86,255,252,255,255,255,255,254,252,255,241,115,2,0,1,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,86,255,252,255,255,255,251,255,255,177,38,0,1,2,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,86,255,252,255,253,253,255,228,93,0,0,4,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,85,254,252,251,255,255,156,23,0,2,2,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,90,255,252,255,212,72,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,2,0,47,247,249,134,10,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,1,0,38,39,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,1,0,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
static const unsigned char SB_ICON_PAUSE[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,2,3,2,0,0,0,0,0,0,0,0,2,3,2,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,1,0,27,81,52,0,1,0,0,0,0,1,0,52,81,27,0,1,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,3,0,70,238,255,255,139,0,2,0,0,2,0,139,255,255,238,70,0,3,0,0,0,0,0,0,
|
||||
0,0,0,0,0,1,1,4,216,255,249,253,255,52,0,2,2,0,52,255,253,249,255,216,4,1,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,17,239,254,254,251,253,88,0,4,4,0,88,253,251,254,254,239,17,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,17,237,255,254,252,255,86,0,4,4,0,85,255,252,254,255,237,17,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,17,238,255,254,252,255,86,0,4,4,0,86,255,252,254,255,238,17,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,17,238,255,254,252,255,86,0,4,4,0,86,255,252,254,255,238,17,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,17,238,255,254,252,255,86,0,4,4,0,86,255,252,254,255,238,17,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,17,238,255,254,252,255,86,0,4,4,0,86,255,252,254,255,238,17,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,17,238,255,254,252,255,86,0,4,4,0,86,255,252,254,255,238,17,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,17,238,255,254,252,255,86,0,4,4,0,86,255,252,254,255,238,17,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,17,238,255,254,252,255,86,0,4,4,0,86,255,252,254,255,238,17,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,17,238,255,254,252,255,86,0,4,4,0,86,255,252,254,255,238,17,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,17,238,255,254,252,255,86,0,4,4,0,86,255,252,254,255,238,17,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,17,238,255,254,252,255,86,0,4,4,0,86,255,252,254,255,238,17,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,17,237,255,254,252,255,86,0,4,4,0,85,255,252,254,255,237,17,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,17,239,254,254,251,253,88,0,4,4,0,88,253,251,254,254,239,17,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,1,1,4,216,255,249,253,255,52,0,2,2,0,52,255,253,249,255,216,4,1,1,0,0,0,0,0,
|
||||
0,0,0,0,0,0,3,0,70,238,255,255,139,0,2,0,0,2,0,139,255,255,238,70,0,3,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,1,0,27,80,52,0,1,0,0,0,0,1,0,52,80,27,0,1,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,2,3,2,0,0,0,0,0,0,0,0,2,3,2,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
static const unsigned char SB_ICON_SKIP_NEXT[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,2,1,3,1,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,2,2,156,230,86,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,2,0,62,198,141,11,0,4,1,0,0,0,0,0,1,0,16,243,255,175,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,4,0,173,255,255,213,66,0,0,3,0,0,0,0,1,0,17,232,254,167,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,168,252,247,255,255,141,11,0,4,1,0,0,1,0,17,234,255,168,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,169,255,251,253,253,255,212,65,0,0,3,0,1,0,17,234,255,168,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,169,255,251,255,255,251,255,255,140,10,0,2,1,0,17,234,255,168,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,169,255,251,255,255,255,253,254,255,210,61,0,3,0,17,234,255,168,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,169,255,251,255,255,255,255,255,251,255,252,45,0,0,17,234,255,168,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,169,255,251,255,255,255,255,255,251,255,253,45,0,0,17,234,255,168,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,169,255,251,255,255,255,253,254,255,210,61,0,3,0,17,234,255,168,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,169,255,251,255,255,251,255,255,140,10,0,2,1,0,17,234,255,168,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,169,255,251,253,253,255,212,65,0,0,3,0,1,0,17,234,255,168,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,168,252,247,255,255,142,11,0,4,1,0,0,1,0,17,234,255,168,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,4,0,173,255,255,213,66,0,0,3,0,0,0,0,1,0,17,232,254,167,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,2,0,62,198,141,11,0,4,1,0,0,0,0,0,1,0,16,243,255,175,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,2,2,155,230,86,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,2,1,3,1,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
static const unsigned char SB_ICON_SKIP_PREV[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,2,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,1,3,1,2,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,86,230,155,2,2,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,175,255,243,16,0,1,0,0,0,0,0,1,4,0,11,141,198,62,0,2,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,167,254,232,17,0,1,0,0,0,0,3,0,0,66,213,255,255,173,0,4,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,168,255,234,17,0,1,0,0,1,4,0,11,141,255,255,247,252,168,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,168,255,234,17,0,1,0,3,0,0,64,211,255,253,253,251,255,169,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,168,255,234,17,0,1,2,0,10,139,255,255,251,255,255,251,255,169,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,168,255,234,17,0,3,0,60,210,255,254,253,255,255,255,251,255,169,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,168,255,234,17,0,0,46,253,255,251,255,255,255,255,255,251,255,169,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,168,255,234,17,0,0,45,253,255,251,255,255,255,255,255,251,255,169,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,168,255,234,17,0,3,0,60,210,255,254,253,255,255,255,251,255,169,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,168,255,234,17,0,1,2,0,10,139,255,255,251,255,255,251,255,169,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,168,255,234,17,0,1,0,3,0,0,64,211,255,253,253,251,255,169,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,168,255,234,17,0,1,0,0,1,4,0,10,141,255,255,247,252,168,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,167,254,232,17,0,1,0,0,0,0,3,0,0,66,213,255,255,173,0,4,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,175,255,243,16,0,1,0,0,0,0,0,1,4,0,11,141,198,62,0,2,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,86,230,155,2,2,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,1,3,1,2,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,2,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
static const unsigned char SB_ICON_ARROW_BACK[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,18,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,60,232,228,41,1,2,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,4,0,60,242,255,255,73,0,3,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,4,0,60,242,252,255,147,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,4,0,60,242,252,255,149,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,60,242,252,255,149,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,4,0,60,242,252,255,149,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,4,0,60,242,255,255,152,0,7,5,3,4,4,4,4,4,4,4,4,3,1,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,60,242,255,250,133,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,2,0,59,242,255,253,213,81,88,87,85,86,86,86,86,86,86,86,86,85,86,76,5,0,1,0,0,0,
|
||||
0,0,0,1,1,20,237,255,252,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,139,0,4,0,0,0,
|
||||
0,0,0,1,1,20,237,255,252,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,139,0,4,0,0,0,
|
||||
0,0,0,0,2,0,59,242,255,253,213,81,88,87,85,86,86,86,86,86,86,86,86,85,86,76,5,0,1,0,0,0,
|
||||
0,0,0,0,0,3,0,60,242,255,250,133,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,4,0,60,242,255,255,153,0,7,5,3,4,4,4,4,4,4,4,4,3,1,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,4,0,60,242,252,255,149,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,4,0,60,242,252,255,149,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,4,0,60,242,252,255,149,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,4,0,60,242,252,255,148,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,4,0,60,242,255,255,77,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,60,233,229,42,1,2,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,20,18,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
static const unsigned char SB_ICON_CLOSE[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,2,2,3,0,0,0,0,0,0,0,0,0,0,0,0,3,2,2,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,2,0,72,184,90,0,4,0,0,0,0,0,0,0,0,4,0,90,184,72,0,2,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,202,255,255,100,0,5,0,0,0,0,0,0,5,0,99,255,255,202,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,90,254,251,255,100,0,5,0,0,0,0,5,0,99,255,251,254,90,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,0,2,0,99,255,251,255,100,0,5,0,0,5,0,99,255,251,255,99,0,2,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,4,0,99,255,251,255,100,0,5,5,0,99,255,251,255,99,0,4,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,5,0,99,255,251,255,100,0,0,100,255,251,255,99,0,5,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,5,0,99,255,251,255,90,90,255,251,255,99,0,5,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,5,0,99,255,254,255,255,254,255,100,0,5,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,5,0,89,249,252,252,249,90,0,5,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,5,0,90,249,252,252,249,90,0,5,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,5,0,100,255,254,255,255,254,255,100,0,5,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,5,0,99,255,251,255,90,89,255,251,255,100,0,5,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,5,0,99,255,251,255,100,0,0,99,255,251,255,100,0,5,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,4,0,99,255,251,255,99,0,5,5,0,99,255,251,255,100,0,4,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,2,0,99,255,251,255,99,0,5,0,0,5,0,99,255,251,255,100,0,2,0,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,90,254,251,255,99,0,5,0,0,0,0,5,0,99,255,251,254,91,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,3,0,202,255,255,99,0,5,0,0,0,0,0,0,5,0,99,255,255,201,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,2,0,71,184,90,0,4,0,0,0,0,0,0,0,0,4,0,90,184,71,0,2,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,2,2,3,0,0,0,0,0,0,0,0,0,0,0,0,3,2,2,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
static const unsigned char SB_ICON_ADD[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,60,60,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,3,0,56,255,255,56,0,3,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,90,254,254,90,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,85,255,255,85,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,86,255,255,86,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,86,255,255,86,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,3,4,4,4,4,8,4,88,255,255,88,4,8,4,4,4,4,3,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,75,255,255,75,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,55,86,85,86,86,88,85,143,255,255,143,85,88,86,86,85,86,55,0,1,0,0,0,0,0,
|
||||
0,0,0,0,3,0,60,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,60,0,3,0,0,0,0,
|
||||
0,0,0,0,3,0,60,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,60,0,3,0,0,0,0,
|
||||
0,0,0,0,0,1,0,55,86,85,86,86,88,85,143,255,255,143,85,88,86,86,85,86,55,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,75,255,255,75,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,3,4,4,4,4,8,4,88,255,255,88,4,8,4,4,4,4,3,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,86,255,255,86,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,86,255,255,86,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,85,255,255,85,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,90,254,254,90,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,3,0,56,255,255,56,0,3,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,60,60,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
static const unsigned char SB_ICON_CHECK[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,3,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,0,99,91,0,1,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,141,255,255,62,0,3,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,142,255,252,242,41,1,2,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,142,255,252,246,66,0,2,0,0,0,0,
|
||||
0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,3,0,142,255,252,246,67,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,1,3,0,142,255,252,246,68,0,4,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,2,0,2,0,0,0,0,0,1,3,0,142,255,251,246,68,0,4,0,0,0,0,0,0,0,
|
||||
0,0,0,1,1,23,197,212,42,0,4,0,0,0,1,3,0,142,255,251,247,69,0,4,0,0,0,0,0,0,0,0,
|
||||
0,0,0,3,0,76,255,255,230,42,0,4,0,1,3,0,142,255,251,247,69,0,4,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,1,2,11,179,255,254,229,41,0,4,3,0,142,255,251,247,70,0,4,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,1,0,6,182,255,255,229,40,0,0,142,255,251,248,70,0,4,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,2,0,6,182,255,255,229,42,135,255,251,248,71,0,5,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,2,0,6,182,255,255,246,255,253,248,71,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,2,0,6,182,255,250,254,248,72,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,2,0,7,180,255,247,73,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,2,0,4,105,55,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,1,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
static const unsigned char SB_ICON_LYRICS[1024]={
|
||||
0,0,0,0,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,1,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,9,131,167,164,163,90,
|
||||
0,0,1,0,52,85,85,86,86,86,86,86,86,86,86,86,86,86,86,85,72,10,0,1,3,0,78,255,255,255,255,255,
|
||||
0,2,0,139,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,213,32,1,5,0,88,251,248,239,233,167,
|
||||
3,0,52,255,253,251,252,252,252,252,252,252,252,252,252,252,252,252,252,252,248,255,144,0,8,0,86,255,255,97,16,0,
|
||||
4,0,88,253,251,255,255,255,255,254,254,254,254,254,254,254,254,255,255,255,250,255,121,0,8,1,86,255,255,83,0,5,
|
||||
4,0,85,255,252,255,255,255,254,255,255,255,255,255,255,255,255,254,255,252,255,144,8,5,3,0,86,255,255,87,1,4,
|
||||
4,0,86,255,252,255,255,255,255,239,238,238,238,238,238,237,253,255,253,255,113,0,0,0,10,14,86,255,255,86,0,4,
|
||||
4,0,86,255,252,255,253,252,99,22,23,21,21,21,22,22,49,219,255,169,0,4,11,140,221,233,226,252,255,86,0,4,
|
||||
4,0,86,255,252,254,255,243,3,0,0,0,0,0,0,0,0,176,255,40,2,7,188,255,255,255,255,252,255,86,0,4,
|
||||
4,0,86,255,252,255,253,255,169,93,93,91,91,91,92,91,122,248,211,1,0,97,255,248,253,254,254,252,255,86,0,4,
|
||||
4,0,86,255,252,255,255,255,255,254,253,253,253,253,253,253,255,255,172,0,0,164,253,251,255,255,255,252,254,87,0,4,
|
||||
4,0,86,255,252,255,253,252,97,19,20,18,18,18,19,18,44,232,180,1,0,164,253,251,255,255,255,252,255,79,0,4,
|
||||
4,0,86,255,252,254,255,243,3,0,0,0,0,0,0,0,0,187,224,0,0,97,255,248,253,254,251,253,248,27,0,2,
|
||||
4,0,86,255,252,255,253,255,169,93,93,91,88,86,88,86,123,246,253,42,2,7,188,255,255,255,255,255,113,0,3,0,
|
||||
4,0,86,255,252,255,255,255,255,254,253,253,255,255,255,255,255,254,255,169,0,4,11,140,220,232,203,94,0,2,0,0,
|
||||
4,0,86,255,252,255,253,252,96,19,21,18,46,219,251,250,251,254,253,255,111,0,0,0,5,15,0,0,2,0,0,0,
|
||||
4,0,86,255,252,254,255,243,4,0,0,0,0,176,255,251,255,255,255,253,255,142,11,3,0,0,0,3,0,0,0,0,
|
||||
4,0,86,255,252,255,253,255,170,88,90,87,124,246,254,254,255,255,255,255,252,255,198,12,2,2,0,0,0,0,0,0,
|
||||
4,0,86,255,252,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,248,255,108,0,4,0,0,0,0,0,0,
|
||||
4,0,86,255,252,255,255,255,252,252,252,252,251,254,255,255,255,255,255,255,255,251,251,155,0,4,0,0,0,0,0,0,
|
||||
4,0,86,255,252,255,255,255,254,254,254,254,254,254,254,254,254,254,254,254,254,248,255,117,0,4,0,0,0,0,0,0,
|
||||
4,0,86,255,252,255,255,253,255,255,255,255,255,255,255,255,255,255,255,255,255,255,224,21,1,1,0,0,0,0,0,0,
|
||||
4,0,86,255,252,255,253,255,238,238,238,238,238,238,238,238,238,238,237,236,229,171,34,0,2,0,0,0,0,0,0,0,
|
||||
4,0,85,255,251,251,255,128,18,20,17,17,17,17,17,17,17,17,17,17,11,0,0,1,0,0,0,0,0,0,0,0,
|
||||
4,0,89,252,247,255,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,
|
||||
3,0,65,255,255,127,0,4,2,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,1,0,88,89,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
static const unsigned char SB_ICON_QUEUE_MUSIC[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,0,0,0,
|
||||
0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0,0,
|
||||
0,0,0,0,0,16,17,17,17,17,17,17,17,17,17,17,17,17,16,0,0,1,0,3,17,17,17,18,2,0,0,0,
|
||||
0,0,2,3,156,233,232,234,234,234,234,234,234,234,234,234,234,232,233,156,4,2,31,209,237,234,233,233,206,32,1,2,
|
||||
0,1,0,12,251,255,255,255,255,255,255,255,255,255,255,255,255,255,255,251,15,0,89,255,255,255,255,255,255,80,0,4,
|
||||
0,0,2,0,85,167,167,168,168,168,168,168,168,168,168,168,168,167,167,85,1,0,85,253,252,197,167,170,131,9,1,1,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,86,255,255,76,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,86,255,255,88,3,7,4,1,0,0,
|
||||
0,0,2,0,85,167,167,168,168,168,168,168,168,168,168,168,168,167,167,85,1,0,86,255,255,86,0,4,0,0,0,0,
|
||||
0,1,0,12,251,255,255,255,255,255,255,255,255,255,255,255,255,255,255,251,15,0,87,255,255,86,0,4,0,0,0,0,
|
||||
0,0,2,3,156,233,232,234,234,234,234,234,234,234,234,234,234,232,233,155,5,0,86,255,255,86,0,4,0,0,0,0,
|
||||
0,0,0,0,0,20,21,21,21,21,21,21,21,21,18,17,17,17,16,0,6,3,88,255,255,86,0,4,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,78,255,255,86,0,4,0,0,0,0,
|
||||
0,0,1,0,27,85,86,87,87,87,87,86,87,77,6,1,1,3,0,7,68,80,122,254,255,86,0,4,0,0,0,0,
|
||||
0,1,1,8,216,255,255,255,255,255,255,255,255,255,139,0,6,0,56,214,255,255,255,252,255,86,0,4,0,0,0,0,
|
||||
0,1,1,8,216,255,255,255,255,255,255,255,255,255,139,0,2,28,237,255,252,252,254,252,255,86,0,4,0,0,0,0,
|
||||
0,0,1,0,27,84,85,85,86,86,86,85,86,76,5,3,0,128,255,249,255,255,255,252,255,86,0,4,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,169,254,251,255,255,255,252,254,87,0,4,0,0,0,0,
|
||||
0,0,0,0,2,4,4,4,4,4,4,4,4,3,1,4,0,151,254,250,255,255,255,252,255,65,0,3,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,60,255,253,250,252,249,255,220,10,1,1,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,121,255,255,255,255,233,55,0,2,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,62,146,164,125,28,0,2,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,3,4,2,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
static const unsigned char SB_ICON_DOWNLOAD[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,1,1,9,139,139,9,1,1,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,77,255,255,77,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,88,251,251,88,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,86,255,255,86,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,86,255,255,86,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,4,0,86,255,255,86,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,3,4,5,0,86,255,255,86,0,5,4,3,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,1,86,255,255,86,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,2,0,103,160,30,0,90,255,255,90,0,29,160,103,0,2,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,2,0,30,255,255,218,19,73,255,255,73,19,218,255,255,30,0,2,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,1,2,10,189,255,255,204,113,255,255,113,203,255,255,189,9,2,1,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,1,0,13,194,255,255,253,255,255,253,255,255,195,13,0,1,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,1,0,0,2,0,13,194,255,251,255,255,251,255,195,13,0,2,0,0,1,0,0,0,0,0,0,
|
||||
0,0,0,0,0,2,0,1,0,0,2,0,12,194,255,249,249,255,195,13,0,2,0,0,1,0,2,0,0,0,0,0,
|
||||
0,0,0,0,0,0,11,0,0,0,0,2,0,13,194,255,255,195,13,0,2,0,0,0,0,11,0,0,0,0,0,0,
|
||||
0,0,0,3,0,86,230,155,2,2,0,0,2,0,12,164,164,13,0,2,0,0,2,2,156,230,86,0,3,0,0,0,
|
||||
0,0,0,3,0,175,255,243,17,1,2,1,1,2,0,0,0,0,2,1,1,2,1,17,243,255,175,0,3,0,0,0,
|
||||
0,0,0,3,0,166,254,232,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,232,254,166,0,3,0,0,0,
|
||||
0,0,0,3,0,169,254,235,33,17,18,17,17,17,17,17,17,17,17,17,17,18,17,33,235,254,169,0,3,0,0,0,
|
||||
0,0,0,4,0,159,255,246,235,234,234,234,234,234,234,234,234,234,234,234,234,234,234,235,246,255,159,0,4,0,0,0,
|
||||
0,0,0,2,1,55,249,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,249,55,1,2,0,0,0,
|
||||
0,0,0,0,1,0,55,153,167,168,168,168,168,168,168,168,168,168,168,168,168,168,168,167,153,55,0,1,0,0,0,0,
|
||||
0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,
|
||||
0,0,0,0,0,0,2,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,2,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
static const unsigned char SB_ICON_HEART[1024]={
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,2,3,4,4,3,2,0,0,0,0,0,0,2,3,4,4,3,2,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,1,3,0,0,0,0,0,0,3,2,0,0,1,3,0,0,0,0,0,0,3,1,0,0,0,0,0,
|
||||
0,0,0,0,2,0,0,31,84,114,113,81,28,0,0,2,2,0,0,27,81,113,114,84,31,0,0,2,0,0,0,0,
|
||||
0,0,0,2,0,27,162,244,255,255,255,255,241,158,30,0,0,30,158,241,255,255,255,255,244,162,27,0,2,0,0,0,
|
||||
0,0,2,0,46,227,255,255,252,251,251,252,255,255,233,55,55,233,255,255,252,251,251,252,255,255,227,46,0,2,0,0,
|
||||
0,1,2,21,226,255,250,254,255,255,255,255,254,251,255,243,243,255,251,254,255,255,255,255,254,250,255,226,21,2,1,0,
|
||||
0,3,0,147,255,250,255,255,255,255,255,255,255,255,254,255,255,254,255,255,255,255,255,255,255,255,250,255,147,0,3,0,
|
||||
1,0,16,238,254,254,255,255,255,255,255,255,255,255,255,254,254,255,255,255,255,255,255,255,255,255,254,254,237,15,0,1,
|
||||
3,0,61,255,253,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,253,255,60,0,3,
|
||||
4,0,84,254,252,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,252,254,84,0,4,
|
||||
4,0,81,255,252,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,252,255,81,0,4,
|
||||
3,0,55,255,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254,255,55,0,3,
|
||||
1,0,13,235,255,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254,255,235,14,0,1,
|
||||
0,3,0,161,255,251,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,251,255,163,0,3,0,
|
||||
0,2,0,47,254,252,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254,252,254,49,0,3,0,
|
||||
0,0,3,0,141,255,250,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,250,255,146,0,3,0,0,
|
||||
0,0,1,3,6,195,255,251,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,251,255,201,8,3,1,0,0,
|
||||
0,0,0,2,0,19,213,255,252,255,255,255,255,255,255,255,255,255,255,255,255,255,255,252,255,219,24,0,2,0,0,0,
|
||||
0,0,0,0,2,0,29,223,255,252,255,255,255,255,255,255,255,255,255,255,255,255,252,255,229,36,0,3,0,0,0,0,
|
||||
0,0,0,0,0,3,0,36,227,255,252,255,255,255,255,255,255,255,255,255,255,252,255,232,43,0,3,0,0,0,0,0,
|
||||
0,0,0,0,0,0,3,0,39,226,255,251,255,255,255,255,255,255,255,255,252,255,231,45,0,4,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,3,0,37,222,255,251,255,255,255,255,255,255,251,255,226,42,0,4,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,3,0,31,213,255,251,255,255,255,255,251,255,215,34,0,4,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,3,0,21,199,255,250,254,254,250,255,200,22,0,3,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,3,0,12,182,255,255,255,255,183,12,0,3,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,2,0,4,154,242,243,154,4,0,2,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,25,25,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
|
||||
static const sb_icon_t sb_icons[] = {
|
||||
{ SB_ICON_WIFI },
|
||||
{ SB_ICON_BT },
|
||||
@@ -212,11 +732,42 @@ static const sb_icon_t sb_icons[] = {
|
||||
{ SB_ICON_BT_DIS },
|
||||
{ SB_ICON_BT_SEARCH },
|
||||
{ SB_ICON_BATT_CHG },
|
||||
{ SB_ICON_SETTINGS },
|
||||
{ SB_ICON_REFRESH },
|
||||
{ SB_ICON_SEARCH },
|
||||
{ SB_ICON_PLAY_ARROW },
|
||||
{ SB_ICON_PAUSE },
|
||||
{ SB_ICON_SKIP_NEXT },
|
||||
{ SB_ICON_SKIP_PREV },
|
||||
{ SB_ICON_ARROW_BACK },
|
||||
{ SB_ICON_CLOSE },
|
||||
{ SB_ICON_ADD },
|
||||
{ SB_ICON_CHECK },
|
||||
{ SB_ICON_LYRICS },
|
||||
{ SB_ICON_QUEUE_MUSIC },
|
||||
{ SB_ICON_DOWNLOAD },
|
||||
{ SB_ICON_HEART },
|
||||
};
|
||||
|
||||
/* 枚举索引(与 sb_icons[] 对应) */
|
||||
/* 枚举索引(与 sb_icons[] 对应)。状态栏 0-14(勿改序);动作 15-29(TopAppBar 用)。 */
|
||||
enum { SB_I_WIFI=0, SB_I_BT=1, SB_I_BATT=2, SB_I_VOL=3, SB_I_BRIGHT=4,
|
||||
SB_I_WIFI_STRONG=5, SB_I_WIFI_MEDIUM=6, SB_I_WIFI_WEAK=7,
|
||||
SB_I_WIFI_NO=8, SB_I_WIFI_OFF=9, SB_I_BT_NO=10, SB_I_BT_CONN=11,
|
||||
SB_I_BT_DIS=12, SB_I_BT_SEARCH=13, SB_I_BATT_CHG=14 };
|
||||
#endif /* SB_ICONS_H */
|
||||
SB_I_BT_DIS=12, SB_I_BT_SEARCH=13, SB_I_BATT_CHG=14,
|
||||
SB_I_SETTINGS=15,
|
||||
SB_I_REFRESH=16,
|
||||
SB_I_SEARCH=17,
|
||||
SB_I_PLAY_ARROW=18,
|
||||
SB_I_PAUSE=19,
|
||||
SB_I_SKIP_NEXT=20,
|
||||
SB_I_SKIP_PREV=21,
|
||||
SB_I_ARROW_BACK=22,
|
||||
SB_I_CLOSE=23,
|
||||
SB_I_ADD=24,
|
||||
SB_I_CHECK=25,
|
||||
SB_I_LYRICS=26,
|
||||
SB_I_QUEUE_MUSIC=27,
|
||||
SB_I_DOWNLOAD=28,
|
||||
SB_I_HEART=29 };
|
||||
|
||||
#endif /* SB_ICONS_H */
|
||||
@@ -7,7 +7,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -Wall -Wextra -o test_ime_fix test_ime_fix.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_ui_draw.c meow_ui_view.c meow_ui_img.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* (meow_ui_widget.c 新 W_LISTVIEW 引用 meow_ui_img_scaled → 需链 meow_ui_img.c+stb_image.c+-lm)
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/* test_meow_ui_anim.c — §12.2 meow_anim 动画基座宿主验证:
|
||||
* 缓动数值(tween 中点/终点)、生命周期(start/update/done/cancel)、并行多动画、脏区 flush。
|
||||
* 时间推进用真实 usleep(meow_ui_time_ms 是单调钟),对中点用宽容差、终点/取消做精确断言。
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/draw/activity/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_anim test_meow_ui_anim.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_meow_ui_anim
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h" /* meow_ui_back_buf 等(脏区 flush 值域校验) */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int g_pass = 0, g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { g_pass++; } \
|
||||
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
|
||||
} while (0)
|
||||
|
||||
#define NEAR(a, b) (((a)-(b)) < 0.0001f && ((b)-(a)) < 0.0001f)
|
||||
|
||||
/* ---- 缓动数值 ---- */
|
||||
static void test_ease(void)
|
||||
{
|
||||
CHECK(NEAR(meow_ease_apply(MEOW_EASE_LINEAR, 0.0f), 0.0f), "lin 0");
|
||||
CHECK(NEAR(meow_ease_apply(MEOW_EASE_LINEAR, 0.5f), 0.5f), "lin .5");
|
||||
CHECK(NEAR(meow_ease_apply(MEOW_EASE_LINEAR, 1.0f), 1.0f), "lin 1");
|
||||
CHECK(NEAR(meow_ease_apply(MEOW_EASE_OUT, 0.0f), 0.0f), "out 0");
|
||||
CHECK(NEAR(meow_ease_apply(MEOW_EASE_OUT, 1.0f), 1.0f), "out 1");
|
||||
CHECK(meow_ease_apply(MEOW_EASE_OUT, 0.5f) > 0.5f && meow_ease_apply(MEOW_EASE_OUT, 0.5f) < 1.0f, "out .5 between");
|
||||
CHECK(NEAR(meow_ease_apply(MEOW_EASE_IN_OUT, 0.0f), 0.0f), "inout 0");
|
||||
CHECK(NEAR(meow_ease_apply(MEOW_EASE_IN_OUT, 0.5f), 0.5f), "inout .5");
|
||||
CHECK(NEAR(meow_ease_apply(MEOW_EASE_IN_OUT, 1.0f), 1.0f), "inout 1");
|
||||
CHECK(NEAR(meow_ease_apply(MEOW_EASE_OVERSHOOT, 0.0f), 0.0f), "overshoot 0");
|
||||
CHECK(NEAR(meow_ease_apply(MEOW_EASE_OVERSHOOT, 1.0f), 1.0f), "overshoot 1");
|
||||
CHECK(meow_ease_apply(MEOW_EASE_OVERSHOOT, 0.85f) > 1.0f, "overshoot mid >1");
|
||||
}
|
||||
|
||||
/* ---- update/done 记录 ---- */
|
||||
typedef struct { meow_anim *a; float last; int updates; int done; int canceled; } anim_track;
|
||||
static void on_update(meow_anim *a, float v, void *ctx)
|
||||
{ anim_track *t = ctx; t->last = v; t->updates++; }
|
||||
static void on_done(meow_anim *a, int canceled, void *ctx)
|
||||
{ anim_track *t = ctx; t->a = a; t->done++; t->canceled = canceled; }
|
||||
|
||||
static void test_tween_complete(void)
|
||||
{
|
||||
anim_track t; memset(&t, 0, sizeof t);
|
||||
meow_anim_desc d; memset(&d, 0, sizeof d);
|
||||
d.from = 0; d.to = 100; d.duration_ms = 200; d.ease = MEOW_EASE_LINEAR;
|
||||
d.update = on_update; d.done = on_done; d.ctx = &t;
|
||||
meow_anim *a = meow_anim_start(&d);
|
||||
CHECK(a != NULL, "start");
|
||||
CHECK(meow_anim_active(a) == 1, "active at start");
|
||||
CHECK(NEAR(meow_anim_value(a), 0.0f), "value from");
|
||||
CHECK(t.updates == 1 && NEAR(t.last, 0.0f), "update t0");
|
||||
meow_anim_tick(); /* 与 start 同时刻:仍 ~0 */
|
||||
CHECK(t.done == 0, "not done yet");
|
||||
usleep(100000); meow_anim_tick(); /* 中点 */
|
||||
CHECK(t.last > 10.0f && t.last < 90.0f, "mid value %f", t.last);
|
||||
usleep(150000); meow_anim_tick(); /* 超过 duration */
|
||||
CHECK(t.done == 1 && t.canceled == 0, "done once non-cancel");
|
||||
CHECK(NEAR(t.last, 100.0f), "final value 100 got %f", t.last);
|
||||
CHECK(meow_anim_active(a) == 0, "inactive after done");
|
||||
usleep(10000); meow_anim_tick();
|
||||
CHECK(t.done == 1, "no re-done");
|
||||
}
|
||||
|
||||
static void test_cancel(void)
|
||||
{
|
||||
anim_track t; memset(&t, 0, sizeof t);
|
||||
meow_anim_desc d; memset(&d, 0, sizeof d);
|
||||
d.from = 0; d.to = 100; d.duration_ms = 500; d.ease = MEOW_EASE_LINEAR;
|
||||
d.update = on_update; d.done = on_done; d.ctx = &t;
|
||||
meow_anim *a = meow_anim_start(&d);
|
||||
usleep(50000); meow_anim_tick();
|
||||
int updates_before = t.updates;
|
||||
meow_anim_cancel(a);
|
||||
CHECK(t.done == 1 && t.canceled == 1, "cancel done(canceled=1)");
|
||||
CHECK(meow_anim_active(a) == 0, "inactive after cancel");
|
||||
meow_anim_tick();
|
||||
CHECK(t.updates == updates_before, "no update after cancel");
|
||||
}
|
||||
|
||||
static void test_parallel(void)
|
||||
{
|
||||
anim_track t1, t2; memset(&t1, 0, sizeof t1); memset(&t2, 0, sizeof t2);
|
||||
meow_anim_desc a, b; memset(&a, 0, sizeof a); memset(&b, 0, sizeof b);
|
||||
a.from = 0; a.to = 10; a.duration_ms = 120; a.ease = MEOW_EASE_LINEAR; a.update = on_update; a.done = on_done; a.ctx = &t1;
|
||||
b.from = 100; b.to = 0; b.duration_ms = 120; b.ease = MEOW_EASE_LINEAR; b.update = on_update; b.done = on_done; b.ctx = &t2;
|
||||
meow_anim *x = meow_anim_start(&a);
|
||||
meow_anim *y = meow_anim_start(&b);
|
||||
CHECK(x && y, "parallel start");
|
||||
CHECK(meow_anim_count() == 2, "count 2");
|
||||
usleep(150000); meow_anim_tick();
|
||||
CHECK(t1.done == 1 && t2.done == 1, "both done");
|
||||
CHECK(NEAR(t1.last, 10.0f) && NEAR(t2.last, 0.0f), "endpoints");
|
||||
CHECK(meow_anim_count() == 0, "count 0");
|
||||
}
|
||||
|
||||
static void test_region_flush(void)
|
||||
{
|
||||
anim_track t; memset(&t, 0, sizeof t);
|
||||
meow_anim_desc d; memset(&d, 0, sizeof d);
|
||||
d.from = 0; d.to = 1; d.duration_ms = 80; d.ease = MEOW_EASE_LINEAR;
|
||||
d.update = on_update; d.done = on_done; d.ctx = &t;
|
||||
meow_anim *a = meow_anim_start(&d);
|
||||
meow_anim_set_region(a, 100, 200, 60, 40); /* 每 tick 只 flush 此区 */
|
||||
usleep(100000); meow_anim_tick();
|
||||
CHECK(t.done == 1, "region anim done");
|
||||
CHECK(t.updates >= 1, "region updates");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
meow_ui_opts o = { 480, 720 };
|
||||
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
|
||||
test_ease();
|
||||
test_tween_complete();
|
||||
test_cancel();
|
||||
test_parallel();
|
||||
test_region_flush();
|
||||
meow_ui_shutdown();
|
||||
printf("anim: %d pass / %d fail\n", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/* test_meow_ui_box.c — §12.1 meow_box 线性布局引擎宿主验证:
|
||||
* 布局求解矩阵(固定/weight/wrap/嵌套/对齐)、margin/padding/gap、交叉轴对齐、view 集成。
|
||||
* 纯逻辑(只创控件 + arrange + 断言矩形),不依赖 /dev/fb0/字库;不绘制。
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 widget/draw/activity/theme/gfx + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_box test_meow_ui_box.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_meow_ui_box
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h" /* meow_widget_rect:真实几何断言 */
|
||||
#include "meow_ui_view.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int g_pass = 0, g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { g_pass++; } \
|
||||
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
|
||||
} while (0)
|
||||
|
||||
static void wr(meow_widget *w, int out[4]) { meow_widget_rect(w, out); }
|
||||
static void boxr(meow_box *b, int out[4]) { meow_box_rect(b, out); }
|
||||
|
||||
/* 创建固定尺寸按钮(既有无绝对定位;注册到绘制物注册表即可) */
|
||||
static meow_widget *mk(int w, int h)
|
||||
{
|
||||
meow_widget *b = meow_btn_create(0, 0, w, h, "X");
|
||||
return b;
|
||||
}
|
||||
|
||||
/* ---- 竖直列:固定 + padding + gap + CROSS_START ---- */
|
||||
static void test_col_fixed(void)
|
||||
{
|
||||
meow_box *bx = meow_box_new(MEOW_BOX_V);
|
||||
CHECK(bx != NULL, "box_new V");
|
||||
meow_box_set_rect(bx, 0, 0, 480, 720);
|
||||
meow_box_set_padding(bx, 20, 20, 20, 20);
|
||||
meow_box_set_gap(bx, 10);
|
||||
meow_box_set_align(bx, MEOW_BOX_ALIGN_START, MEOW_BOX_CROSS_START);
|
||||
meow_widget *a = mk(100, 50), *b = mk(120, 50), *c = mk(80, 50);
|
||||
CHECK(meow_box_add(bx, a, MEOW_BOX_FIXED, 0) == 0, "add a");
|
||||
CHECK(meow_box_add(bx, b, MEOW_BOX_FIXED, 0) == 0, "add b");
|
||||
CHECK(meow_box_add(bx, c, MEOW_BOX_FIXED, 0) == 0, "add c");
|
||||
CHECK(meow_box_count(bx) == 3, "count");
|
||||
meow_box_arrange(bx);
|
||||
int r[4];
|
||||
wr(a, r); CHECK(r[0] == 20 && r[1] == 20 && r[2] == 100 && r[3] == 50, "A x=20 y=20 w100 h50 got %d,%d,%d,%d", r[0], r[1], r[2], r[3]);
|
||||
wr(b, r); CHECK(r[0] == 20 && r[1] == 80 && r[2] == 120, "B y=80 got %d,%d,%d,%d", r[0], r[1], r[2], r[3]);
|
||||
wr(c, r); CHECK(r[0] == 20 && r[1] == 140 && r[2] == 80, "C y=140 got %d,%d,%d,%d", r[0], r[1], r[2], r[3]);
|
||||
meow_box_free(bx);
|
||||
}
|
||||
|
||||
/* ---- 横向行:END 对齐 + 交叉轴中心 ---- */
|
||||
static void test_row_end_center(void)
|
||||
{
|
||||
meow_box *bx = meow_box_new(MEOW_BOX_H);
|
||||
meow_box_set_rect(bx, 10, 0, 480, 200);
|
||||
meow_box_set_padding(bx, 10, 10, 10, 10);
|
||||
meow_box_set_gap(bx, 5);
|
||||
meow_box_set_align(bx, MEOW_BOX_ALIGN_END, MEOW_BOX_CROSS_CENTER);
|
||||
meow_widget *a = mk(100, 50), *b = mk(100, 50), *c = mk(100, 50);
|
||||
CHECK(meow_box_add(bx, a, MEOW_BOX_FIXED, 0) == 0, "add");
|
||||
CHECK(meow_box_add(bx, b, MEOW_BOX_FIXED, 0) == 0, "addb");
|
||||
CHECK(meow_box_add(bx, c, MEOW_BOX_FIXED, 0) == 0, "addc");
|
||||
meow_box_arrange(bx);
|
||||
/* 内容区 x∈[20,470] cw=460;foot=100*3+5*2=310;leftover=150;END → off0=150 */
|
||||
int r[4];
|
||||
wr(a, r);
|
||||
CHECK(r[0] == 20 + 150, "END first x=170 got %d", r[0]);
|
||||
/* 交叉轴中心:cy=10(pad_t);cross_size=ch=180,cross_base=50 → pad=130 → cross=65 → y=10+65=75 */
|
||||
CHECK(r[1] == 75 && r[3] == 50, "center y=75 got %d,%d", r[1], r[3]);
|
||||
wr(b, r); CHECK(r[0] == 20 + 150 + 100 + 5, "END second x=%d", r[0]);
|
||||
wr(c, r); CHECK(r[0] == 380, "END third x=380 got %d", r[0]); /* 170+100+5+100+5+? = 380 */
|
||||
meow_box_free(bx);
|
||||
}
|
||||
|
||||
/* ---- weight 均分:竖直,两个 weight 1:3 ---- */
|
||||
static void test_weight(void)
|
||||
{
|
||||
meow_box *bx = meow_box_new(MEOW_BOX_H);
|
||||
meow_box_set_rect(bx, 0, 0, 480, 200);
|
||||
/* 内容区 480 宽(无 pad/gap)。child 固定 80 + 两个权重 1:3 */
|
||||
meow_widget *fixed = mk(80, 200), *w1 = mk(0, 200), *w2 = mk(0, 200);
|
||||
CHECK(meow_box_add(bx, fixed, MEOW_BOX_FIXED, 0) == 0, "fixed");
|
||||
CHECK(meow_box_add(bx, w1, MEOW_BOX_WEIGHT, 1) == 0, "w1");
|
||||
CHECK(meow_box_add(bx, w2, MEOW_BOX_WEIGHT, 3) == 0, "w2");
|
||||
meow_box_arrange(bx);
|
||||
/* leftover = 480 - 80 = 400;w1=100, w2=300 */
|
||||
int r[4];
|
||||
wr(fixed, r); CHECK(r[0] == 0 && r[2] == 80, "fixed x=0 w80 got %d,%d", r[0], r[2]);
|
||||
wr(w1, r); CHECK(r[0] == 80 && r[2] == 100, "w1 x=80 w100 got %d,%d", r[0], r[2]);
|
||||
wr(w2, r); CHECK(r[0] == 180 && r[2] == 300, "w2 x=180 w300 got %d,%d", r[0], r[2]);
|
||||
meow_box_free(bx);
|
||||
}
|
||||
|
||||
/* ---- WRAP 内容测量(measure 回调给 min/preferred) ---- */
|
||||
static void wrap_measure(void *ctx, int *mn, int *pf) { (void)ctx; *mn = 40; *pf = 120; }
|
||||
static void test_wrap(void)
|
||||
{
|
||||
meow_box *bx = meow_box_new(MEOW_BOX_H);
|
||||
meow_box_set_rect(bx, 0, 0, 480, 100);
|
||||
meow_box_set_gap(bx, 10);
|
||||
meow_box_set_align(bx, MEOW_BOX_ALIGN_START, MEOW_BOX_CROSS_START);
|
||||
meow_widget *w = mk(5, 40); /* 尺寸无关,wrap 用 measure */
|
||||
CHECK(meow_box_add_ex(bx, w, MEOW_BOX_WRAP, 0, 40, 120, wrap_measure, NULL) == 0, "wrap-add");
|
||||
meow_box_arrange(bx);
|
||||
int r[4]; wr(w, r);
|
||||
/* wrap 主轴向 preferred=120;cross_start 交叉=40(widget h 落 V=40?这是 H box,交叉=h) */
|
||||
CHECK(r[2] == 120, "wrap pref w=120 got %d", r[2]);
|
||||
meow_box_free(bx);
|
||||
}
|
||||
|
||||
/* ---- 嵌套:Row 内嵌 Column ---- */
|
||||
static void test_nested(void)
|
||||
{
|
||||
meow_box *row = meow_box_new(MEOW_BOX_H);
|
||||
meow_box *col = meow_box_new(MEOW_BOX_V);
|
||||
meow_box_set_rect(row, 0, 0, 480, 200);
|
||||
meow_box_set_rect(col, 0, 0, 200, 200);
|
||||
meow_box_set_gap(row, 10);
|
||||
meow_widget *a = mk(100, 50), *b = mk(100, 50), *c = mk(100, 50);
|
||||
meow_box_add(row, a, MEOW_BOX_FIXED, 0);
|
||||
meow_box_add_sub(row, col, MEOW_BOX_FIXED, 0);
|
||||
CHECK(meow_box_add_sub(row, col, MEOW_BOX_FIXED, 0) < 0, "sub dedupe");
|
||||
meow_box_add(col, b, MEOW_BOX_FIXED, 0);
|
||||
meow_box_add(col, c, MEOW_BOX_FIXED, 0);
|
||||
meow_box_arrange(row);
|
||||
int r[4];
|
||||
wr(a, r); CHECK(r[0] == 0 && r[2] == 100, "row a x0 w100 got %d,%d", r[0], r[2]);
|
||||
int cr[4]; boxr(col, cr); CHECK(cr[0] == 110 && cr[1] == 0 && cr[2] == 200, "sub box x=110 got %d", cr[0]);
|
||||
/* col 内 b,c 纵向:y 0,50(col y=0, gap0, cross_start) */
|
||||
meow_widget_rect(b, r); CHECK(r[0] == 110 && r[1] == 0, "col b y0 got %d", r[1]);
|
||||
meow_widget_rect(c, r); CHECK(r[1] == 50, "col c y50 got %d", r[1]);
|
||||
meow_box_free(row); meow_box_free(col);
|
||||
}
|
||||
|
||||
/* ---- margin(widget opts)+ 交叉轴 STRETCH ---- */
|
||||
static void test_margin_stretch(void)
|
||||
{
|
||||
meow_box *bx = meow_box_new(MEOW_BOX_V);
|
||||
meow_box_set_rect(bx, 0, 0, 480, 300);
|
||||
meow_box_set_align(bx, MEOW_BOX_ALIGN_START, MEOW_BOX_CROSS_STRETCH);
|
||||
meow_widget *a = mk(100, 60);
|
||||
meow_widget_opts o; memset(&o, 0, sizeof o);
|
||||
o.margin_x = 30; o.margin_y = 10;
|
||||
CHECK(meow_widget_set_opts(a, &o) == 0, "set opts");
|
||||
meow_box_add(bx, a, MEOW_BOX_FIXED, 0);
|
||||
meow_box_arrange(bx);
|
||||
int r[4]; wr(a, r);
|
||||
/* V:交叉轴=x,STRETCH → w = cw - 2*margin_x = 480 - 60 = 420;x = margin_x = 30。
|
||||
* 主轴 y:lead=margin_y=10 → y=10;ext=h=60。 */
|
||||
CHECK(r[0] == 30 && r[2] == 420, "stretch w=420 x=30 got %d,%d", r[0], r[2]);
|
||||
CHECK(r[1] == 10 && r[3] == 60, "y=10 h=60 got %d,%d", r[1], r[3]);
|
||||
meow_box_free(bx);
|
||||
}
|
||||
|
||||
/* ---- SPACE_BETWEEN ---- */
|
||||
static void test_space_between(void)
|
||||
{
|
||||
meow_box *bx = meow_box_new(MEOW_BOX_H);
|
||||
meow_box_set_rect(bx, 0, 0, 500, 100);
|
||||
meow_box_set_align(bx, MEOW_BOX_ALIGN_SPACE_BETWEEN, MEOW_BOX_CROSS_START);
|
||||
meow_widget *a = mk(100, 50), *b = mk(100, 50), *c = mk(100, 50);
|
||||
meow_box_add(bx, a, MEOW_BOX_FIXED, 0);
|
||||
meow_box_add(bx, b, MEOW_BOX_FIXED, 0);
|
||||
meow_box_add(bx, c, MEOW_BOX_FIXED, 0);
|
||||
meow_box_arrange(bx);
|
||||
/* cw=500;foot=300;leftover=200;n-1=2 → 每 gap += 100 → A.x=0 B.x=200 C.x=400 */
|
||||
int r[4];
|
||||
wr(a, r); CHECK(r[0] == 0, "sb A x0 got %d", r[0]);
|
||||
wr(b, r); CHECK(r[0] == 200, "sb B x200 got %d", r[0]);
|
||||
wr(c, r); CHECK(r[0] == 400, "sb C x400 got %d", r[0]);
|
||||
meow_box_free(bx);
|
||||
}
|
||||
|
||||
/* ---- view 集成:meow_view_set_layout + relayout ---- */
|
||||
static void test_view_layout(void)
|
||||
{
|
||||
meow_view *v = meow_view_create(900, MEOW_VIEW_ACTIVITY, NULL, NULL);
|
||||
CHECK(v != NULL, "view create");
|
||||
v->x = 0; v->y = 0; v->w = 480; v->h = 720;
|
||||
meow_box *bx = meow_box_new(MEOW_BOX_V);
|
||||
meow_box_set_gap(bx, 8);
|
||||
meow_box_set_align(bx, MEOW_BOX_ALIGN_START, MEOW_BOX_CROSS_START);
|
||||
meow_widget *a = mk(300, 50), *b = mk(300, 50);
|
||||
meow_box_add(bx, a, MEOW_BOX_FIXED, 0);
|
||||
meow_box_add(bx, b, MEOW_BOX_FIXED, 0);
|
||||
meow_view_add_widget(v, a);
|
||||
meow_view_add_widget(v, b);
|
||||
meow_view_set_layout(v, bx); /* relayout 立即排一次 */
|
||||
int r[4];
|
||||
wr(a, r); CHECK(r[0] == 0 && r[1] == 0 && r[2] == 300, "view a x0 y0 w300 got %d,%d,%d", r[0], r[1], r[2]);
|
||||
wr(b, r); CHECK(r[1] == 58, "view b y=58 got %d", r[1]); /* 0 + 50 + gap8 */
|
||||
meow_view_free(v);
|
||||
meow_box_free(bx);
|
||||
}
|
||||
|
||||
/* ---- 越界去重 / 池满 ---- */
|
||||
static void test_edge(void)
|
||||
{
|
||||
meow_box *bx = meow_box_new(MEOW_BOX_H);
|
||||
meow_widget *a = mk(10, 10), *dup = mk(10, 10);
|
||||
CHECK(meow_box_add(bx, dup, MEOW_BOX_FIXED, 0) == 0, "add dup");
|
||||
CHECK(meow_box_add(bx, dup, MEOW_BOX_FIXED, 0) < 0, "dedupe");
|
||||
CHECK(meow_box_add(bx, NULL, MEOW_BOX_FIXED, 0) < 0, "null add rejected");
|
||||
meow_box_widget_at(bx, 0) ? 0 : 0;
|
||||
CHECK(meow_box_widget_at(bx, 0) == dup, "widget_at 0");
|
||||
CHECK(meow_box_sub_at(bx, 0) == NULL, "sub_at null");
|
||||
meow_box_free(bx);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test_col_fixed();
|
||||
test_row_end_center();
|
||||
test_weight();
|
||||
test_wrap();
|
||||
test_nested();
|
||||
test_margin_stretch();
|
||||
test_space_between();
|
||||
test_view_layout();
|
||||
test_edge();
|
||||
printf("box: %d pass / %d fail\n", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/* test_meow_ui_dispatch.c — §6.4 事件分发形式化宿主验证(离屏 480x720,16bpp)。
|
||||
*
|
||||
* 覆盖(新穿透矩阵,全部直接驱动 SDK §6.4 仲裁者 API + 真实注册控件):
|
||||
* M1 模态打开点下层不触发:modal_begin 后 meow_ui_hit_topdown 恒返回模态哨兵(不再落到下层
|
||||
* 同坐标控件),gesture_begin 捕获的是模态(哨兵)而非下层按钮——下层绝不消费;
|
||||
* M2 弹层叠开两层不叠黑/层隔离:modal_begin 两次 → depth=2;end(LIFO)逐层递减;期间
|
||||
* hit_topdown 始终返回哨兵,下层收不到任何触摸;
|
||||
* M3 捕获手势滑出不误触:gesture_begin 命中按钮 A 后捕获;任凭概念滑动到远处(不重新命中),
|
||||
* meow_ui_gesture_owner 恒为 A(绝不滑出误触下层/邻控件);end 释放;
|
||||
* M4 list 焦点点外释放:meow_list_tap_at/meow_listview_tap_at 使列表成为唯一焦点;
|
||||
* focus_tap 落在列表内 → 保位;落在列表外(页面内容/空白)→ 释放焦点并触发 lost 回调。
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 widget/draw/input/activity/gfx/sys/theme/vol/log/dialog/ime/notify/ipc +
|
||||
* 打桩 font/global;完整超集便于链接):
|
||||
* gcc -Wall -Wextra -o test_meow_ui_dispatch test_meow_ui_dispatch.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_meow_ui_dispatch (无设备,纯宿主)
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h"
|
||||
#include "meow_ui_view.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond, name) do { \
|
||||
if (cond) { printf(" PASS %s\n", name); } \
|
||||
else { printf(" FAIL %s\n", name); g_fail++; } } while (0)
|
||||
|
||||
/* 焦点丢失回调计数 */
|
||||
static int g_lost_calls = 0;
|
||||
static void *g_lost_last = NULL;
|
||||
static void focus_lost(void *owner, void *ctx)
|
||||
{ (void)ctx; g_lost_calls++; g_lost_last = owner; }
|
||||
|
||||
/* 列表数据源(最小 W_LIST) */
|
||||
static int list_src(int idx, char *label, int len, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (idx < 0 || idx >= 8) return 0;
|
||||
snprintf(label, (size_t)len, "item %d", idx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
setenv("MEOW_UI_DUMP", "/tmp/dispatch.ppm", 1);
|
||||
meow_ui_opts o = { 480, 720 };
|
||||
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
|
||||
meow_ui_activity_push(5); /* 页面控件 owner=5,命中/绘制对齐 */
|
||||
|
||||
/* 场景控件:两个不重叠按钮 A/B + 一个 LazyColumn 列表 */
|
||||
meow_widget *btnA = meow_btn_create(20, 100, 200, 60, "A");
|
||||
meow_widget *btnB = meow_btn_create(240, 100, 200, 60, "B");
|
||||
meow_widget *list = meow_lazycolumn_create(20, 300, 400, 150, 44, 8, list_src, NULL);
|
||||
|
||||
/* ================= M1:模态打开点下层不触发 =================
|
||||
* 无模态:hit_topdown(按钮A中心) == 按钮A;modal_begin 后同一落点返回模态哨兵(≠按钮A),
|
||||
* gesture_begin 捕获的是模态而非下层按钮——下层绝不消费(穿透根治)。 */
|
||||
printf("M1 modal open -> lower not triggered\n");
|
||||
void *h0 = meow_ui_hit_topdown(120, 130);
|
||||
printf(" no-modal hit_topdown(120,130) = %s\n",
|
||||
h0 == (void *)btnA ? "btnA" : (h0 ? "other" : "NULL"));
|
||||
CHECK(h0 == (void *)btnA, "no-modal topdown hits btnA (z-top)");
|
||||
|
||||
meow_ui_modal *m = meow_ui_modal_begin();
|
||||
void *hm = meow_ui_hit_topdown(120, 130);
|
||||
printf(" modal-open hit_topdown(120,130) = %s (sentinel=%d)\n",
|
||||
hm == (void *)btnA ? "btnA(LEAK!)" : "modal-sentinel", hm != NULL && hm != (void *)btnA);
|
||||
CHECK(meow_ui_modal_active(), "modal active");
|
||||
CHECK(hm != NULL && hm != (void *)btnA, "modal present -> hit_topdown never reaches btnA");
|
||||
void *g0 = meow_ui_gesture_begin(120, 130); /* 下层同坐标:捕获的是模态而非按钮 */
|
||||
CHECK(g0 == hm, "gesture_begin during modal captures modal (sentinel), not btnA");
|
||||
meow_ui_gesture_end();
|
||||
meow_ui_modal_end(m);
|
||||
CHECK(meow_ui_hit_topdown(120, 130) == (void *)btnA, "after modal_end topdown returns btnA again");
|
||||
CHECK(!meow_ui_modal_active(), "modal released");
|
||||
|
||||
/* ================= M2:弹层叠开两层(层隔离,depth 语义) =================
|
||||
* 两层弹开:depth 1→2;期间 hit_topdown 恒返回哨兵;LIFO 关内层→1,关外层→0。
|
||||
* 叠黑由 Dialog/Sheet 的「打开前整帧快照 + 关闭还原 + 高亮仅重绘卡片不叠遮罩」保证
|
||||
* (meow_ui_dialog.c,M2.71-bug3),本处验证层隔离 + 栈深。 */
|
||||
printf("M2 stacked modals (two layers)\n");
|
||||
meow_ui_modal *m1 = meow_ui_modal_begin();
|
||||
meow_ui_modal *m2 = meow_ui_modal_begin();
|
||||
CHECK(meow_ui_modal_depth() == 2, "two modals -> depth 2");
|
||||
CHECK(meow_ui_hit_topdown(120, 130) != (void *)btnA, "either modal -> lower btnA blocked");
|
||||
meow_ui_modal_end(m2);
|
||||
CHECK(meow_ui_modal_depth() == 1, "close inner -> depth 1");
|
||||
CHECK(meow_ui_hit_topdown(120, 130) != (void *)btnA, "outer modal still blocks lower");
|
||||
meow_ui_modal_end(m1);
|
||||
CHECK(meow_ui_modal_depth() == 0, "close outer -> depth 0");
|
||||
CHECK(meow_ui_hit_topdown(120, 130) == (void *)btnA, "both closed -> lower reachable");
|
||||
|
||||
/* ================= M3:捕获手势滑出不误触 =================
|
||||
* touch-down 命中按钮 A → 捕获。此后 move(即使滑到按钮 B / 空白)不重新命中,owner 恒为 A;
|
||||
* 绝不滑出误触 B 或下层。抬手(gesture_end)释放。 */
|
||||
printf("M3 gesture capture: slide-out no false-trigger\n");
|
||||
void *g1 = meow_ui_gesture_begin(120, 130);
|
||||
CHECK(g1 == (void *)btnA, "down on A captures A");
|
||||
CHECK(meow_ui_gesture_owner() == (void *)btnA, "owner == A after down");
|
||||
/* 概念滑动到按钮 B 中心 (340,130):捕获不重命中 → 仍是 A(不误触 B) */
|
||||
CHECK(meow_ui_gesture_owner() == (void *)btnA, "slide to B point: owner stays A (no re-hit)");
|
||||
/* 滑出到空白 */
|
||||
CHECK(meow_ui_gesture_owner() == (void *)btnA, "slide to blank: owner stays A");
|
||||
meow_ui_gesture_end();
|
||||
CHECK(meow_ui_gesture_owner() == NULL, "release -> no owner");
|
||||
|
||||
/* ================= M4:list 焦点点外释放 =================
|
||||
* meow_list_tap_at 使列表成为唯一焦点;focus_tap 落在列表内→保位;落在列表外→释放并通知。 */
|
||||
printf("M4 list focus release on tap outside\n");
|
||||
meow_ui_focus_set_lost_cb(focus_lost, NULL);
|
||||
CHECK(meow_ui_focus_owner() == NULL, "no focus initially");
|
||||
int idx = meow_list_tap_at(list, 120, 320); /* 命中列表行0 */
|
||||
CHECK(idx >= 0, "list tap hits a row");
|
||||
CHECK(meow_ui_focus_owner() == (void *)list, "list tap -> list becomes focus owner");
|
||||
meow_ui_focus_tap(120, 320); /* 点列表内:保位 */
|
||||
CHECK(meow_ui_focus_owner() == (void *)list, "tap inside list -> focus retained");
|
||||
CHECK(g_lost_calls == 0, "no lost callback while focus retained");
|
||||
meow_ui_focus_tap(120, 130); /* 命中按钮A(非列表):释放焦点+通知 */
|
||||
CHECK(meow_ui_focus_owner() == NULL, "tap outside list (on A) -> focus released");
|
||||
CHECK(g_lost_calls == 1 && g_lost_last == (void *)list, "lost callback fired with list owner");
|
||||
/* 再次聚焦后再点「空白」(无控件命中):同样释放 */
|
||||
meow_list_tap_at(list, 120, 320);
|
||||
meow_ui_focus_tap(400, 650); /* 列表外空白 */
|
||||
CHECK(meow_ui_focus_owner() == NULL, "tap on blank -> focus released");
|
||||
CHECK(g_lost_calls == 2, "lost callback fired again");
|
||||
(void)btnB;
|
||||
|
||||
/* 清理:控件释放自动注销 z 序绘制物 + 焦点/捕获复位 */
|
||||
meow_widget_free(list);
|
||||
meow_widget_free(btnB);
|
||||
meow_widget_free(btnA);
|
||||
meow_ui_focus_release(NULL);
|
||||
meow_ui_activity_pop();
|
||||
meow_ui_shutdown();
|
||||
|
||||
printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail);
|
||||
return g_fail;
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
/* test_meow_ui_listvirt.c — 宿主验证 §5.5 虚拟化列表(W_LISTVIEW pool 模式):
|
||||
* T1 虚拟化正确性:10000 项滚动窗口内容正确(hit 行号映射)/ bind 次数有界(全量扫过=count)/
|
||||
* 回滚不重绑(scroll 回顶 bind 计数不变)。
|
||||
* T2 多行富 item 渲染:双行(标题+副标题)/ 单行(仅标题)/ 左图标占位 / 右尾件 /
|
||||
* 副标题超宽截断(不越过操作区)。
|
||||
* T3 段头占位行(bind 返回 section=1)+ 空态占位(count=0 置 empty_text)。
|
||||
* T4 完全自绘(set_item_draw 覆盖默认 renderer)。
|
||||
* T5 焦点(tap_at → meow_ui_focus_owner)+ fling 联动(meow_scroll_wrap 包裹 pool 列表,
|
||||
* track+fling 吸附到行边界;滚动只改 viewport 偏移)。
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_listvirt test_meow_ui_listvirt.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_meow_ui_listvirt
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp:真实像素断言 */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int g_pass = 0, g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { g_pass++; } \
|
||||
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
|
||||
} while (0)
|
||||
|
||||
/* ---- back_buf 像素读取(真实渲染断言) ---- */
|
||||
static void rd_px(int x, int y, int *r, int *g, int *b)
|
||||
{
|
||||
unsigned char *bb = meow_ui_back_buf();
|
||||
int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp();
|
||||
if (!bb || x < 0 || y < 0) { *r = *g = *b = 0; return; }
|
||||
if (bpp == 16) {
|
||||
uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2);
|
||||
*r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2;
|
||||
*g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4;
|
||||
*b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2);
|
||||
} else {
|
||||
uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4);
|
||||
*r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF;
|
||||
}
|
||||
}
|
||||
static int near2(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 14; }
|
||||
static int px_near(int x, int y, int r, int g, int b)
|
||||
{
|
||||
int rr, gg, bb; rd_px(x, y, &rr, &gg, &bb);
|
||||
return near2(rr, r) && near2(gg, g) && near2(bb, b);
|
||||
}
|
||||
static int region_has(int x, int y, int w, int h, int r, int g, int b)
|
||||
{
|
||||
int cnt = 0;
|
||||
for (int dy = 0; dy < h; dy++)
|
||||
for (int dx = 0; dx < w; dx++)
|
||||
if (px_near(x + dx, y + dy, r, g, b)) cnt++;
|
||||
return cnt;
|
||||
}
|
||||
#define UNPK(c, rr, gg, bb) do { (rr) = ((c) >> 16) & 0xFF; (gg) = ((c) >> 8) & 0xFF; (bb) = (c) & 0xFF; } while (0)
|
||||
|
||||
/* ---- 虚拟化数据源(10000 项):bind 计数有界 / 内容正确 / 段头 ---- */
|
||||
#define N 10000
|
||||
static int g_bind_total; /* pool bind 总数(经 listv_slot 调用;hit 走独立路径不计数) */
|
||||
static int g_bind_touch_count; /* bind 回调的独立计数(hit 也会调 bind,故仅命中计数用) */
|
||||
static int g_count_override; /* <0 = 用默认 N;>=0 = 覆盖(空态/小列表用) */
|
||||
static int g_section_at; /* 该 index 返回段头(-1=无) */
|
||||
|
||||
static int vcount(void *ctx) { (void)ctx; return g_count_override >= 0 ? g_count_override : N; }
|
||||
static void vbind(int idx, meow_lv_item *it, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
g_bind_total++;
|
||||
g_bind_touch_count++;
|
||||
memset(it, 0, sizeof *it);
|
||||
it->icon_kind = MEOW_LIST_ICON_FOLDER;
|
||||
it->right = MEOW_LIST_RIGHT_ARROW;
|
||||
it->on = 1; it->enabled = 1;
|
||||
if (g_section_at == idx) {
|
||||
it->section = 1;
|
||||
it->title = "SECTION A"; it->subtitle = NULL; it->icon_kind = MEOW_LIST_ICON_NONE;
|
||||
it->right = MEOW_LIST_RIGHT_NONE;
|
||||
return;
|
||||
}
|
||||
static char t[64], s[64];
|
||||
snprintf(t, sizeof t, "title %d", idx);
|
||||
snprintf(s, sizeof s, "sub %d", idx);
|
||||
it->title = t; it->subtitle = s;
|
||||
}
|
||||
|
||||
/* ---- 数据源 A:单行(无副标题/无图标/无尾件),供 T2 单行断言 ---- */
|
||||
static int via_count(void *ctx) { (void)ctx; return 3; }
|
||||
static void via_bind(int idx, meow_lv_item *it, void *ctx)
|
||||
{
|
||||
(void)ctx; (void)idx;
|
||||
memset(it, 0, sizeof *it);
|
||||
static char t[32]; snprintf(t, sizeof t, "single %d", idx);
|
||||
it->title = t; it->subtitle = NULL; it->icon_kind = MEOW_LIST_ICON_NONE;
|
||||
it->right = MEOW_LIST_RIGHT_NONE; it->enabled = 1;
|
||||
}
|
||||
|
||||
/* ---- 数据源:超宽副标题(T2 截断) ---- */
|
||||
static int long_count(void *ctx) { (void)ctx; return 1; }
|
||||
static void long_bind(int idx, meow_lv_item *it, void *ctx)
|
||||
{
|
||||
(void)ctx; (void)idx;
|
||||
memset(it, 0, sizeof *it);
|
||||
it->title = "Title";
|
||||
it->subtitle = "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"; /* 超宽 */
|
||||
it->icon_kind = MEOW_LIST_ICON_NONE;
|
||||
it->right = MEOW_LIST_RIGHT_NONE; it->enabled = 1;
|
||||
}
|
||||
|
||||
/* ---- T4 完全自绘:整行画实心块 + 标题 ---- */
|
||||
static int g_draw_calls;
|
||||
static void mydraw(int idx, int x, int y, int w, int h, int st, void *ctx)
|
||||
{
|
||||
(void)idx; (void)ctx;
|
||||
g_draw_calls++;
|
||||
meow_ui_fill_rect(x, y, w, h, 0x334455); /* 自定义行底 */
|
||||
meow_ui_fill_rect(x + 4, y + 4, 8, 8, 0xFF0000); /* 角标记(自绘内容) */
|
||||
if (st & MEOW_LISTVIEW_ST_SELECTED) meow_ui_fill_rect(x + w - 12, y + 4, 8, 8, 0x00FF00);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
meow_ui_opts o = { 480, 720 };
|
||||
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
|
||||
const meow_theme *th = meow_theme_get();
|
||||
int sb_r, sb_g, sb_b, os_r, os_g, os_b, pc_r, pc_g, pc_b, ospv_r, ospv_g, ospv_b, prim_r, prim_g, prim_b;
|
||||
UNPK(th->bg, sb_r, sb_g, sb_b);
|
||||
UNPK(th->onSurface, os_r, os_g, os_b);
|
||||
UNPK(th->primaryContainer, pc_r, pc_g, pc_b);
|
||||
UNPK(th->onSurfaceVariant, ospv_r, ospv_g, ospv_b);
|
||||
UNPK(th->primary, prim_r, prim_g, prim_b);
|
||||
int thh = meow_ui_text_height(th->font_body);
|
||||
int itemh = 56;
|
||||
|
||||
/* ============ T1 虚拟化正确性 / bind 有界 / 回滚不重绑 ============ */
|
||||
{
|
||||
g_count_override = -1; g_section_at = -1; g_bind_total = 0; g_bind_touch_count = 0;
|
||||
int lvx = 20, lvy = 50, lvw = 440, lvh = 300;
|
||||
meow_widget *lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
CHECK(meow_listview_set_item_h(lv, itemh) == itemh, "item_h");
|
||||
CHECK(meow_listview_set_vsource(lv, vcount, vbind, NULL) == 0, "vsource");
|
||||
CHECK(meow_listview_max_scroll(lv) == N * itemh - lvh, "max_scroll %d", meow_listview_max_scroll(lv)); /* 560000-300=559700 */
|
||||
CHECK(meow_listview_scroll(lv) == 0, "scroll0");
|
||||
|
||||
/* 首帧 draw:绑定视口行(first..last) */
|
||||
meow_widget_draw(lv);
|
||||
int first0 = 0;
|
||||
int last0 = (0 + lvh) / itemh + 1; if (last0 > N) last0 = N;
|
||||
int B0 = g_bind_total;
|
||||
CHECK(B0 == (last0 - first0), "first draw binds %d rows got %d", last0 - first0, B0);
|
||||
|
||||
/* 下滚 1 行:仅新入视口的行被 bind(delta==1),未离屏行不重绑 */
|
||||
meow_listview_set_scroll(lv, itemh);
|
||||
meow_widget_draw(lv);
|
||||
int B1 = g_bind_total;
|
||||
CHECK(B1 == B0 + 1, "scroll+1row binds exactly 1 more got %d (was %d)", B1 - B0, B0);
|
||||
|
||||
/* 回滚到顶:视口行槽位仍在 → 不重绑(delta==0) */
|
||||
meow_listview_set_scroll(lv, 0);
|
||||
meow_widget_draw(lv);
|
||||
CHECK(g_bind_total == B1, "scroll-back no rebind got %d (was %d)", g_bind_total, B1);
|
||||
|
||||
/* 全量扫过:每个 index 进入视口时 bind 恰一次 → 总数 == N(有界,非 per-frame × count) */
|
||||
int prev = 1;
|
||||
while (prev <= N) {
|
||||
int off = prev * itemh;
|
||||
meow_listview_set_scroll(lv, off);
|
||||
meow_widget_draw(lv);
|
||||
if (off >= meow_listview_max_scroll(lv)) break;
|
||||
prev++;
|
||||
}
|
||||
meow_listview_set_scroll(lv, meow_listview_max_scroll(lv));
|
||||
meow_widget_draw(lv);
|
||||
CHECK(g_bind_total == N, "full sweep binds each index once got %d (expect %d)", g_bind_total, N);
|
||||
CHECK(g_bind_touch_count == N, "bind callback called %d times (expect %d)", g_bind_touch_count, N);
|
||||
|
||||
/* 回滚(扫到末尾后拉回顶):池发生整表 wrap(cap<count),回收合法重绑——但**有界**,
|
||||
* 每次 draw 至多重绑当前视口窗口(≤ win),绝不整表 N 重绑(可复用回收语义)。 */
|
||||
int before2 = g_bind_total;
|
||||
int win = (lvh / itemh) + 1; if (win > N) win = N;
|
||||
meow_listview_set_scroll(lv, 0);
|
||||
meow_widget_draw(lv);
|
||||
CHECK((g_bind_total - before2) <= win, "scroll-back rebind bounded (delta %d ≤ %d)",
|
||||
g_bind_total - before2, win);
|
||||
|
||||
/* 内容正确:命中行号 = 滚动换算(视口窗口映射正确) */
|
||||
CHECK(meow_listview_hit(lv, lvx + lvw - 10, lvy + 0 * itemh + 20) == 0, "hit0");
|
||||
meow_listview_set_scroll(lv, 5 * itemh);
|
||||
int idx_mid = meow_listview_hit(lv, lvx + lvw - 10, lvy + itemh / 2);
|
||||
CHECK(idx_mid == 5, "hit maps scroll to row got %d (expect 5)", idx_mid);
|
||||
meow_listview_set_scroll(lv, 888 * itemh);
|
||||
int idx_mid2 = meow_listview_hit(lv, lvx + lvw - 10, lvy + itemh * 2 + itemh / 2);
|
||||
CHECK(idx_mid2 == 890, "hit row 2 of window got %d (expect 890)", idx_mid2);
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ============ T2 多行富 item 渲染:双行 / 单行 / 图标 / 尾件 / 截断 ============ */
|
||||
{
|
||||
/* 双行(lines=2,默认):行 0 title+subtitle + icon(FOLDER) + 尾件(ARROW) */
|
||||
int lvx = 20, lvy = 50, lvw = 440, lvh = 120;
|
||||
meow_widget *lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
CHECK(meow_listview_set_item_h(lv, itemh) == itemh, "item_h");
|
||||
CHECK(meow_listview_set_vsource(lv, vcount, vbind, NULL) == 0, "vsource");
|
||||
CHECK(meow_listview_set_item_renderer(lv, 2, 1, 1) == 0, "renderer");
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv);
|
||||
int text_x = lvx + 16 + 24 + 16; /* show_icon=1 → 76 */
|
||||
int op_start = lvx + (lvw * 2 / 3); /* 20+293=313 */
|
||||
int ty0 = lvy + (itemh - (2 * thh + 2)) / 2; /* 双行标题 y */
|
||||
int sy0 = ty0 + thh + 2; /* 副标题 y */
|
||||
CHECK(region_has(text_x, ty0, 60, thh, os_r, os_g, os_b) > 2, "double-line title drawn");
|
||||
CHECK(region_has(text_x, sy0, 60, thh, ospv_r, ospv_g, ospv_b) > 2, "double-line subtitle drawn");
|
||||
CHECK(region_has(lvx + 16, lvy + (itemh - 24) / 2, 24, 24, prim_r, prim_g, prim_b) > 30, "icon placeholder drawn");
|
||||
int ax = lvx + lvw - 16 - 24;
|
||||
CHECK(region_has(ax, lvy + itemh / 2 - 8, 20, 16, ospv_r, ospv_g, ospv_b) > 2, "right tail (arrow) drawn");
|
||||
|
||||
/* 单行(lines=1):仅标题居中,无副标题带 */
|
||||
g_count_override = 3;
|
||||
meow_widget *lv1 = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
meow_listview_set_item_h(lv1, itemh);
|
||||
meow_listview_set_vsource(lv1, via_count, via_bind, NULL);
|
||||
meow_listview_set_item_renderer(lv1, 1, 0, 0); /* 单行、无图标、无尾件 */
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv1);
|
||||
int t1y = lvy + (itemh - thh) / 2;
|
||||
CHECK(region_has(lvx + 16, t1y, 60, thh, os_r, os_g, os_b) > 2, "single-line title drawn");
|
||||
CHECK(region_has(lvx + 16, t1y + thh + 2, 60, thh, ospv_r, ospv_g, ospv_b) == 0, "single-line no subtitle");
|
||||
/* 单行档位默认行高 48(set_item_tier) */
|
||||
meow_widget *lv2 = meow_listview_create(lvx, lvy, lvw, lvh, 3, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
CHECK(meow_listview_set_item_tier(lv2, 1) == 0, "tier");
|
||||
CHECK(meow_listview_scroll(lv2) == 0 && meow_listview_max_scroll(lv2) == 3 * 48 - lvh, "tier-h %d",
|
||||
meow_listview_max_scroll(lv2));
|
||||
meow_widget_free(lv2);
|
||||
|
||||
/* 副标题超宽截断:不画过 text_maxw(op_start 之前) */
|
||||
g_count_override = -1;
|
||||
meow_widget *lv3 = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
meow_listview_set_item_h(lv3, itemh);
|
||||
meow_listview_set_vsource(lv3, long_count, long_bind, NULL);
|
||||
meow_listview_set_item_renderer(lv3, 2, 0, 0); /* 无图标 → text_x = lvx+16=36 */
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv3);
|
||||
int text_x3 = lvx + 16;
|
||||
int op3 = lvx + lvw - 16; /* show_right=0 → op_start=右缘-pad */
|
||||
int maxw3 = op3 - text_x3; /* 20+440-16-36 = 408 */
|
||||
int ty3 = lvy + (itemh - (2 * thh + 2)) / 2;
|
||||
int sy3 = ty3 + thh + 2;
|
||||
CHECK(region_has(text_x3, sy3, 40, thh, ospv_r, ospv_g, ospv_b) > 2, "long subtitle leading drawn");
|
||||
CHECK(region_has(text_x3 + maxw3 + 4, sy3, 40, thh, ospv_r, ospv_g, ospv_b) == 0, "long subtitle truncated (no overflow)");
|
||||
meow_widget_free(lv3);
|
||||
meow_widget_free(lv1);
|
||||
|
||||
/* 截断不越界 + 双行副标题带(回归:无图标/无尾件时 text_x/pad 正确) */
|
||||
CHECK(region_has(text_x3, ty3, 40, thh, os_r, os_g, os_b) > 2, "truncate-r title drawn");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ============ T3 段头占位 + 空态 ============ */
|
||||
{
|
||||
int lvx = 20, lvy = 50, lvw = 440, lvh = 120;
|
||||
g_count_override = 3; g_section_at = 1;
|
||||
meow_widget *lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
meow_listview_set_item_h(lv, itemh);
|
||||
meow_listview_set_vsource(lv, vcount, vbind, NULL);
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv);
|
||||
int sch_r, sch_g, sch_b; UNPK(th->surfaceContainerHigh, sch_r, sch_g, sch_b);
|
||||
CHECK(region_has(lvx, lvy + itemh, lvw, itemh - 4, sch_r, sch_g, sch_b) > 40, "section header band drawn");
|
||||
CHECK(region_has(lvx + 16, lvy + itemh + (itemh - thh) / 2, 60, thh, prim_r, prim_g, prim_b) > 2,
|
||||
"section header title (primary) drawn");
|
||||
|
||||
/* 空态:count=0 + empty_text → 视口内居中画文案(onSurfaceVariant) */
|
||||
g_count_override = 0; g_section_at = -1;
|
||||
meow_widget *lve = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
meow_listview_set_vsource(lve, vcount, vbind, NULL);
|
||||
meow_listview_set_empty_text(lve, "No data"); /* ASCII(宿主 5x7 点阵仅含 ASCII/CJK 用真机字库) */
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lve);
|
||||
int ety = lvy + (lvh - thh) / 2;
|
||||
CHECK(region_has(lvx + lvw / 2 - 60, ety, 120, thh, ospv_r, ospv_g, ospv_b) > 2, "empty state text drawn");
|
||||
meow_widget_free(lve);
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ============ T4 完全自绘(set_item_draw 覆盖默认 renderer) ============ */
|
||||
{
|
||||
int lvx = 20, lvy = 200, lvw = 440, lvh = 120;
|
||||
g_count_override = 3; g_section_at = -1; g_draw_calls = 0;
|
||||
meow_widget *lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE);
|
||||
meow_listview_set_item_h(lv, itemh);
|
||||
meow_listview_set_vsource(lv, vcount, vbind, NULL);
|
||||
CHECK(meow_listview_set_item_draw(lv, mydraw, NULL) == 0, "set_draw");
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv);
|
||||
int vis = (lvh / itemh) + 1; if (vis > 3) vis = 3;
|
||||
CHECK(g_draw_calls == vis, "draw_item called per visible row got %d (expect %d)", g_draw_calls, vis);
|
||||
CHECK(region_has(lvx + 4, lvy + 4, 8, 8, 0xFF, 0x00, 0x00) > 0, "custom row content drawn");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ============ T5 焦点 + fling 联动(meow_scroll_wrap 包裹 pool 列表) ============ */
|
||||
{
|
||||
int lvx = 20, lvy = 330, lvw = 440, lvh = 300;
|
||||
g_count_override = -1; g_section_at = -1;
|
||||
meow_widget *lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE);
|
||||
meow_listview_set_item_h(lv, itemh);
|
||||
meow_listview_set_vsource(lv, vcount, vbind, NULL);
|
||||
|
||||
/* 焦点:tap_at 命中行 → 列表成为唯一焦点(§6.4) */
|
||||
int tidx = meow_listview_tap_at(lv, lvx + lvw - 10, lvy + itemh / 2);
|
||||
CHECK(tidx >= 0, "tap_at returns row got %d", tidx);
|
||||
CHECK(meow_ui_focus_owner() == (void *)lv, "list acquired focus after tap_at");
|
||||
|
||||
/* fling:track+fling → meow_scroll_wrap 包裹;吸附到行边界(snap=itemh) */
|
||||
meow_scroll *s = meow_scroll_wrap(lv, 1); /* 1=W_LISTVIEW */
|
||||
CHECK(s != NULL, "scroll wrap pool list");
|
||||
meow_scroll_set_snap(s, itemh);
|
||||
uint64_t t = meow_ui_time_ms();
|
||||
for (int i = 0; i < 12; i++) meow_scroll_track(s, 30, t + (uint64_t)i * 16);
|
||||
int start = meow_scroll_offset(s);
|
||||
CHECK(start > 0, "drag advances offset got %d", start);
|
||||
meow_scroll_fling(s);
|
||||
CHECK(meow_scroll_active(s) == 1, "fling active");
|
||||
meow_scroll_stop(s);
|
||||
CHECK(meow_scroll_active(s) == 0, "stop ok");
|
||||
|
||||
/* 再次 fling 到自然停:offset 只改 viewport,且 snap 到行边界 */
|
||||
for (int i = 0; i < 12; i++) meow_scroll_track(s, 30, t + (uint64_t)i * 16);
|
||||
start = meow_scroll_offset(s);
|
||||
meow_scroll_fling(s);
|
||||
int max_off = start, guard = 0;
|
||||
while (meow_scroll_active(s) && guard++ < 300) {
|
||||
meow_anim_tick();
|
||||
int cur = meow_scroll_offset(s);
|
||||
if (max_off < cur) max_off = cur;
|
||||
usleep(10000);
|
||||
}
|
||||
meow_anim_tick();
|
||||
int end = meow_scroll_offset(s);
|
||||
CHECK(end >= start, "fling advances got %d vs %d", end, start);
|
||||
CHECK((end % itemh) == 0, "snap to item boundary got %d (%%%d)", end, itemh);
|
||||
CHECK(end <= meow_listview_max_scroll(lv), "fling in range %d (max %d)", end, meow_listview_max_scroll(lv));
|
||||
/* 滚动只改 viewport:meow_listview_scroll 与物理层一致 */
|
||||
CHECK(meow_listview_scroll(lv) == end, "listview_scroll mirrors scroll offset %d vs %d", meow_listview_scroll(lv), end);
|
||||
/* 停稳后 draw 反映最终窗口(bind 只补新入视口的行,不整表重绑) */
|
||||
int before_draw = g_bind_total;
|
||||
int win = (lvh / itemh) + 1; if (win > N) win = N; /* 视口窗口行数(含底缘 1 行) */
|
||||
meow_widget_draw(lv);
|
||||
CHECK(g_bind_total >= before_draw && (g_bind_total - before_draw) <= win,
|
||||
"settle draw rebinds only window (delta %d ≤ %d)", g_bind_total - before_draw, win);
|
||||
meow_scroll_free(s);
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
meow_ui_shutdown();
|
||||
printf("listvirt: %d pass / %d fail\n", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/* test_meow_ui_scroll.c — §12.3 meow_scroll 滚动物理宿主验证:
|
||||
* 跟手拖动(track 跟随与 clamp)、速度跟踪→松手 fling(位移序列/停止位置/单调/不越界)、
|
||||
* 边界过冲 clamp + 简单回弹、snap。时间推进用真实 usleep(meow_anim_tick 驱动 fling)。
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/draw/activity/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_scroll test_meow_ui_scroll.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_meow_ui_scroll
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int g_pass = 0, g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { g_pass++; } \
|
||||
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
|
||||
} while (0)
|
||||
|
||||
#define LIST_H 400
|
||||
#define ITEM_H 44
|
||||
#define COUNT 50
|
||||
/* max_scroll = COUNT*ITEM_H - LIST_H = 2200 - 400 = 1800 */
|
||||
#define MAXS (COUNT * ITEM_H - LIST_H)
|
||||
|
||||
static int src(int idx, char *label, int len, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (idx < 0 || idx >= COUNT) return 0;
|
||||
snprintf(label, (size_t)len, "row %d", idx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static meow_widget *make_list(void)
|
||||
{
|
||||
meow_widget *lv = meow_list_create(0, 0, 480, LIST_H, COUNT, src, NULL);
|
||||
meow_list_set_item_h(lv, ITEM_H);
|
||||
return lv;
|
||||
}
|
||||
|
||||
/* 喂一串向下(正)速度采样,模拟快速下划 */
|
||||
static void feed_drag(meow_scroll *s, int step, int n)
|
||||
{
|
||||
uint64_t now = meow_ui_time_ms();
|
||||
for (int i = 0; i < n; i++) {
|
||||
meow_scroll_track(s, step, now + (uint64_t)i * 16);
|
||||
}
|
||||
/* 最后用一个真实稍晚时刻的 track 让采样时间戳真实(保持一致钟) */
|
||||
meow_scroll_track(s, 0, meow_ui_time_ms());
|
||||
}
|
||||
|
||||
static void pump_until_idle(meow_scroll *s, int *max_off, int *mono_ok)
|
||||
{
|
||||
int prev = meow_scroll_offset(s);
|
||||
int guard = 0;
|
||||
while (meow_scroll_active(s) && guard++ < 300) {
|
||||
meow_anim_tick();
|
||||
int cur = meow_scroll_offset(s);
|
||||
if (*max_off < cur) *max_off = cur;
|
||||
if (cur < prev) *mono_ok = 0; /* 下划 fling 应单调不减(回弹段除外:此处只测无回弹) */
|
||||
prev = cur;
|
||||
usleep(10000);
|
||||
}
|
||||
meow_anim_tick();
|
||||
}
|
||||
|
||||
static void test_track_follow(void)
|
||||
{
|
||||
meow_widget *lv = make_list();
|
||||
meow_scroll *s = meow_scroll_wrap(lv, 0);
|
||||
CHECK(s != NULL, "wrap");
|
||||
CHECK(meow_scroll_offset(s) == 0, "init offset 0");
|
||||
meow_scroll_track(s, 10, meow_ui_time_ms());
|
||||
CHECK(meow_scroll_offset(s) == 10, "follow +10 got %d", meow_scroll_offset(s));
|
||||
/* 超量拖到底:clamp 到 max */
|
||||
meow_scroll_track(s, 100000, meow_ui_time_ms());
|
||||
CHECK(meow_scroll_offset(s) == MAXS, "clamp to max got %d", meow_scroll_offset(s));
|
||||
meow_scroll_free(s);
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
static void test_fling_no_boundary(void)
|
||||
{
|
||||
meow_widget *lv = make_list();
|
||||
meow_scroll *s = meow_scroll_wrap(lv, 0);
|
||||
/* 从 0 起拖 ~300px,建正初速 */
|
||||
uint64_t t = meow_ui_time_ms();
|
||||
for (int i = 0; i < 12; i++) meow_scroll_track(s, 25, t + (uint64_t)i * 16);
|
||||
int start = meow_scroll_offset(s);
|
||||
CHECK(start > 0, "dragged start>0 got %d", start);
|
||||
meow_scroll_fling(s);
|
||||
CHECK(meow_scroll_active(s) == 1, "fling active");
|
||||
int max_off = start, mono = 1, prev = start;
|
||||
int guard = 0;
|
||||
while (meow_scroll_active(s) && guard++ < 300) {
|
||||
meow_anim_tick();
|
||||
int cur = meow_scroll_offset(s);
|
||||
if (max_off < cur) max_off = cur;
|
||||
if (cur < prev) mono = 0;
|
||||
prev = cur;
|
||||
usleep(10000);
|
||||
}
|
||||
meow_anim_tick();
|
||||
int end = meow_scroll_offset(s);
|
||||
CHECK(mono == 1, "monotonic non-decreasing during fling");
|
||||
CHECK(end >= start, "fling advances end=%d start=%d", end, start);
|
||||
CHECK(end <= MAXS, "no overflow end=%d max=%d", end, MAXS);
|
||||
CHECK(meow_scroll_active(s) == 0, "settles idle");
|
||||
meow_scroll_free(s);
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
static void test_fling_boundary_bounce(void)
|
||||
{
|
||||
meow_widget *lv = make_list();
|
||||
meow_scroll *s = meow_scroll_wrap(lv, 0);
|
||||
/* 先拖到近底(~1700) */
|
||||
meow_list_set_scroll(lv, 1700);
|
||||
/* 快速下划,冲过底界 */
|
||||
uint64_t t = meow_ui_time_ms();
|
||||
for (int i = 0; i < 8; i++) meow_scroll_track(s, 40, t + (uint64_t)i * 16);
|
||||
meow_scroll_fling(s);
|
||||
int max_off = 0, mono = 1;
|
||||
pump_until_idle(s, &max_off, &mono);
|
||||
int end = meow_scroll_offset(s);
|
||||
CHECK(max_off <= MAXS, "never exceed max, max_off=%d max=%d", max_off, MAXS);
|
||||
CHECK(end == MAXS, "bounce settles at bottom end=%d", end); /* 回弹后回到底 */
|
||||
CHECK(meow_scroll_active(s) == 0, "idle after bounce");
|
||||
meow_scroll_free(s);
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
static void test_snap(void)
|
||||
{
|
||||
meow_widget *lv = make_list();
|
||||
meow_scroll *s = meow_scroll_wrap(lv, 0);
|
||||
meow_scroll_set_snap(s, ITEM_H); /* 吸附到行边界 */
|
||||
/* 慢拖到某非边界位置并给 small velocity → fling 结束 snap 到行倍数 */
|
||||
uint64_t t = meow_ui_time_ms();
|
||||
for (int i = 0; i < 6; i++) meow_scroll_track(s, 20, t + (uint64_t)i * 16);
|
||||
meow_scroll_fling(s);
|
||||
int max_off = 0, mono = 1;
|
||||
pump_until_idle(s, &max_off, &mono);
|
||||
int end = meow_scroll_offset(s);
|
||||
CHECK((end % ITEM_H) == 0, "snap to item multiple got %d", end);
|
||||
CHECK(end <= MAXS, "snap in range %d", end);
|
||||
meow_scroll_free(s);
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
static void test_stop(void)
|
||||
{
|
||||
meow_widget *lv = make_list();
|
||||
meow_scroll *s = meow_scroll_wrap(lv, 0);
|
||||
uint64_t t = meow_ui_time_ms();
|
||||
for (int i = 0; i < 6; i++) meow_scroll_track(s, 20, t + (uint64_t)i * 16);
|
||||
meow_scroll_fling(s);
|
||||
CHECK(meow_scroll_active(s) == 1, "fling active (stop test)");
|
||||
meow_scroll_stop(s);
|
||||
CHECK(meow_scroll_active(s) == 0, "stopped immediately");
|
||||
meow_scroll_free(s);
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
meow_ui_opts o = { 480, 720 };
|
||||
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
|
||||
test_track_follow();
|
||||
test_fling_no_boundary();
|
||||
test_fling_boundary_bounce();
|
||||
test_snap();
|
||||
test_stop();
|
||||
meow_ui_shutdown();
|
||||
printf("scroll: %d pass / %d fail\n", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* test_meow_ui_str.c — meow_str_* 宿主单测(2026-09-02,§13.i18n 雏形)。
|
||||
*
|
||||
* 编译(宿主,meow_ui_str 为纯 libc,无需 SDK 其它对象):
|
||||
* gcc -Wall -Wextra -o test_meow_ui_str test_meow_ui_str.c meow_ui_str.c -I.
|
||||
*
|
||||
* 两种模式(覆盖表按 locale 懒加载,分两次跑):
|
||||
* ./test_meow_ui_str tmp → unsetenv MEOW_DATA_DIR,仅验证内嵌双语表(无覆盖表)
|
||||
* ./test_meow_ui_str env → 设 MEOW_DATA_DIR=<mkdtemp>/data,写 <mkdtemp>/strings/en_us.txt,
|
||||
* 验证 TF 覆盖表优先级(覆盖>内嵌)+ locale 即时生效
|
||||
*
|
||||
* 覆盖矩阵:
|
||||
* T1 内嵌回退(默认 zh_cn):common.ok/loading/no_data 等取 zh
|
||||
* T2 切 locale 即时生效:set_locale(en_us) → 内嵌 en;set_locale(zh_cn) → 内嵌 zh(无需重初始化)
|
||||
* T3 未知 id 回退:meow_str("nope.missing") 原样返回 id;meow_str(NULL) 返回 ""
|
||||
* T4 TF 覆盖优先级:override > 内嵌当前语言;未覆盖 id 回落内嵌;自定义 id 命中
|
||||
* T5 查找正确性:一批 id 的 zh/en 值逐一断言
|
||||
* T6 meow_strf:取串并按 snprintf 语义写调用方缓冲(与 meow_str 一致)
|
||||
*
|
||||
* 注:set_locale 的 ui.locale 持久化目标是板载 /usr/data/meow/settings.conf,宿主无该目录
|
||||
* → 静默失败(运行时仍切换,仅不跨启动);不影响本测试断言。
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond, msg) do { \
|
||||
if (cond) { printf(" PASS: %s\n", msg); } \
|
||||
else { printf(" FAIL: %s\n", msg); g_fail = 1; } \
|
||||
} while (0)
|
||||
|
||||
/* 找内嵌某 id 的 zh/en 期望值(供 T5 逐项对照)。不在表内返回 NULL。 */
|
||||
static const char *expect(const char *id, int en)
|
||||
{
|
||||
/* 与 meow_ui_str.c 的 g_embed[] 对齐(此处只列 T5 抽查的子集) */
|
||||
static const struct { const char *id; const char *zh; const char *en; } t[] = {
|
||||
{ "common.ok", "确定", "OK" },
|
||||
{ "common.cancel", "取消", "Cancel" },
|
||||
{ "common.back", "返回", "Back" },
|
||||
{ "common.confirm", "确认", "Confirm" },
|
||||
{ "common.loading", "加载中...", "Loading..." },
|
||||
{ "common.no_data", "暂无数据", "No data" },
|
||||
{ "common.retry_fail", "重试失败", "Retry failed" },
|
||||
{ "common.tap_ok_edit", "点击确定编辑", "tap OK to edit..." },
|
||||
{ "ime.space", "空格", "Space" },
|
||||
{ "ime.enter", "确定", "OK" },
|
||||
{ "ime.esc", "取消", "Cancel" },
|
||||
{ "ime.clear", "清", "Clear" },
|
||||
{ "ime.pinyin_mode", "拼音", "Pinyin" },
|
||||
{ "ime.no_candidate", "(无候选)", "(none)" },
|
||||
};
|
||||
for (size_t i = 0; i < sizeof t / sizeof t[0]; i++)
|
||||
if (strcmp(t[i].id, id) == 0) return en ? t[i].en : t[i].zh;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *mode = (argc > 1) ? argv[1] : "tmp";
|
||||
char *tmpdir = NULL;
|
||||
|
||||
if (strcmp(mode, "env") == 0) {
|
||||
/* 建 <mkdtemp>/data + <mkdtemp>/strings/en_us.txt(meow_str 由 MEOW_DATA_DIR 去尾 /data
|
||||
* 得 <mkdtemp>/,再拼 strings/<locale>.txt)。 */
|
||||
tmpdir = strdup("/tmp/meowstrtestXXXXXX");
|
||||
if (!mkdtemp(tmpdir)) { printf("FATAL: mkdtemp failed\n"); return 2; }
|
||||
char datadir[512], strdir[512];
|
||||
snprintf(datadir, sizeof datadir, "%s/data", tmpdir);
|
||||
snprintf(strdir, sizeof strdir, "%s/strings", tmpdir);
|
||||
mkdir(datadir, 0755);
|
||||
mkdir(strdir, 0755);
|
||||
char file[512];
|
||||
snprintf(file, sizeof file, "%s/strings/en_us.txt", tmpdir);
|
||||
FILE *f = fopen(file, "w");
|
||||
if (!f) { printf("FATAL: cannot write override file\n"); free(tmpdir); return 2; }
|
||||
/* 混合:正常行 / '=' 两侧带空格 / 空行 / 无 '=' 行(应被跳过) */
|
||||
fprintf(f, "# comment line (no equals)\n");
|
||||
fprintf(f, "common.ok=确认OK\n");
|
||||
fprintf(f, "common.custom=自定义覆盖\n");
|
||||
fprintf(f, "common.loading = 加载中覆盖\n");
|
||||
fprintf(f, "badline-without-equals\n");
|
||||
fprintf(f, "\n");
|
||||
fclose(f);
|
||||
setenv("MEOW_DATA_DIR", datadir, 1);
|
||||
} else {
|
||||
unsetenv("MEOW_DATA_DIR");
|
||||
}
|
||||
|
||||
printf("== meow_str test [%s] ==\n", mode);
|
||||
printf(" locale = %s\n", meow_str_locale());
|
||||
|
||||
/* --- T1 内嵌回退(默认 zh_cn,无覆盖表/无 zh_cn.txt)--- */
|
||||
CHECK(strcmp(meow_str("common.ok"), "确定") == 0, "T1 zh common.ok = 确定");
|
||||
CHECK(strcmp(meow_str("common.cancel"), "取消") == 0, "T1 zh common.cancel = 取消");
|
||||
CHECK(strcmp(meow_str("common.loading"), "加载中...") == 0, "T1 zh common.loading = 加载中...");
|
||||
CHECK(strcmp(meow_str("common.no_data"), "暂无数据") == 0, "T1 zh common.no_data = 暂无数据");
|
||||
CHECK(strcmp(meow_str("ime.space"), "空格") == 0, "T1 zh ime.space = 空格");
|
||||
|
||||
/* --- T3 未知 id 回退(永不 NULL)--- */
|
||||
CHECK(strcmp(meow_str("nope.missing"), "nope.missing") == 0, "T3 未知 id 原样返回 id");
|
||||
CHECK(strcmp(meow_str(NULL), "") == 0, "T3 meow_str(NULL) = \"\"");
|
||||
|
||||
/* --- T6 meow_strf:与 meow_str 一致,snprintf 语义 --- */
|
||||
{
|
||||
char buf[128];
|
||||
int r = meow_strf(buf, sizeof buf, "common.ok");
|
||||
CHECK(r == (int)strlen("确定") && strcmp(buf, "确定") == 0, "T6 meow_strf zh common.ok = 确定");
|
||||
meow_strf(buf, sizeof buf, "common.no_data");
|
||||
CHECK(strcmp(buf, meow_str("common.no_data")) == 0, "T6 meow_strf == meow_str");
|
||||
CHECK(meow_strf(NULL, 10, "common.ok") == -1, "T6 meow_strf(out=NULL) = -1");
|
||||
CHECK(meow_strf(buf, 0, "common.ok") == -1, "T6 meow_strf(outsz=0) = -1");
|
||||
}
|
||||
|
||||
/* --- T2 切 locale 即时生效(无需重初始化)---
|
||||
* 用「未在覆盖表」的 id 证明 locale 即时切换(common.ok 等在 env 会被覆盖,留给 T4 验覆盖路径)。 */
|
||||
meow_str_set_locale("en_us");
|
||||
CHECK(strcmp(meow_str_locale(), "en_us") == 0, "T2 locale = en_us");
|
||||
CHECK(strcmp(meow_str("common.cancel"), "Cancel") == 0, "T2 en common.cancel = Cancel");
|
||||
CHECK(strcmp(meow_str("common.no_data"), "No data") == 0, "T2 en common.no_data = No data");
|
||||
CHECK(strcmp(meow_str("ime.space"), "Space") == 0, "T2 en ime.space = Space");
|
||||
|
||||
/* --- T4 TF 覆盖优先级(仅 env 模式)--- */
|
||||
if (strcmp(mode, "env") == 0) {
|
||||
/* ① 覆盖内嵌当前语言(en_us*下 common.ok 应是覆盖值"确认OK"而非内嵌"OK") */
|
||||
CHECK(strcmp(meow_str("common.ok"), "确认OK") == 0, "T4 @en override 覆盖 common.ok");
|
||||
/* ② 覆盖自定义 id(内嵌无 → 覆盖命中) */
|
||||
CHECK(strcmp(meow_str("common.custom"), "自定义覆盖") == 0, "T4 @en override 自定义 id");
|
||||
/* ③ '=' 两侧带空格也可解析 */
|
||||
CHECK(strcmp(meow_str("common.loading"), "加载中覆盖") == 0, "T4 @en override 带空格行");
|
||||
/* ④ 未覆盖 id 回落内嵌当前语言 */
|
||||
CHECK(strcmp(meow_str("common.cancel"), "Cancel") == 0, "T4 @en 未覆盖 → 内嵌 en");
|
||||
CHECK(strcmp(meow_str("ime.clear"), "Clear") == 0, "T4 @en ime.clear 未覆盖 → 内嵌 en");
|
||||
/* ⑤ 自定义 id 在 zh(无 zh_cn.txt,覆盖表清空)→ 内嵌无 → 原样返回 id */
|
||||
meow_str_set_locale("zh_cn");
|
||||
CHECK(strcmp(meow_str("common.ok"), "确定") == 0, "T4 @zh 覆盖清除 → 内嵌 zh");
|
||||
CHECK(strcmp(meow_str("common.custom"), "common.custom") == 0, "T4 @zh 自定义 id 原样回退");
|
||||
meow_str_set_locale("en_us"); /* 回到 en 供 T5 */
|
||||
}
|
||||
|
||||
/* --- T5 查找正确性:一批 id 的 zh/en 逐一断言(仅 tmp 模式——无覆盖干扰,
|
||||
* 才能验证内嵌表本身;env 模式的内嵌 vs 覆盖已由 T2/T4 覆盖,此处跳过多余断言)--- */
|
||||
if (strcmp(mode, "tmp") == 0) {
|
||||
const char *ids[] = { "common.ok", "common.cancel", "common.back", "common.confirm",
|
||||
"common.loading", "common.no_data", "common.retry_fail",
|
||||
"common.tap_ok_edit", "ime.space", "ime.enter", "ime.esc",
|
||||
"ime.clear", "ime.pinyin_mode", "ime.no_candidate" };
|
||||
meow_str_set_locale("zh_cn");
|
||||
for (size_t i = 0; i < sizeof ids / sizeof ids[0]; i++) {
|
||||
if (expect(ids[i], 0)) {
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof msg, "T5 zh %s == 期望", ids[i]);
|
||||
CHECK(strcmp(meow_str(ids[i]), expect(ids[i], 0)) == 0, msg);
|
||||
}
|
||||
}
|
||||
meow_str_set_locale("en_us");
|
||||
for (size_t i = 0; i < sizeof ids / sizeof ids[0]; i++) {
|
||||
if (expect(ids[i], 1)) {
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof msg, "T5 en %s == 期望", ids[i]);
|
||||
CHECK(strcmp(meow_str(ids[i]), expect(ids[i], 1)) == 0, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tmpdir) free(tmpdir);
|
||||
if (g_fail) { printf("\nRESULT: FAIL (%d)\n", g_fail); return 1; }
|
||||
printf("\nRESULT: ALL PASS\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_theme test_meow_ui_theme.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_ui_draw.c meow_ui_view.c meow_ui_img.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_widgets test_meow_ui_widgets.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_ui_draw.c meow_ui_view.c meow_ui_img.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:MEOW_UI_DUMP=/tmp/b2.ppm ./test_meow_ui_widgets (可选,dump 屏幕)
|
||||
@@ -201,6 +201,75 @@ int main(void)
|
||||
assert(meow_topappbar_hit(tab_, 440, 28) == 1); /* 右缘动作 "Edit"(最右) */
|
||||
printf("PASS topappbar: create/back/action/hit\n");
|
||||
|
||||
/* ---------- TopAppBar 位图动作图标(sb_icons[],Material Symbols)----------
|
||||
* 文本项默认;set_item_icon 把动作项改为位图 → 绘制用 meow_gfx_blit_icon_ink(32px 槽
|
||||
* 居中 + ink 垂直对齐栏中点),颜色取主题 onSurface(按压向栏底色淡 12%)。断言用
|
||||
* back_buf 真实像素:画完读亮度,非空 ink + 按压变淡 + 图标项命中。 */
|
||||
{
|
||||
/* 用独立 topbar(y=300,避让其它控件);meow_widget_draw 自填 surfaceContainerHigh 底 */
|
||||
meow_widget *ti = W(meow_topappbar_create(0, 300, 480, 56, ""));
|
||||
int ai = meow_topappbar_add_action(ti, ""); /* 文本占位 → 转图标 */
|
||||
assert(ai == 0);
|
||||
assert(meow_topappbar_set_item_icon(ti, ai, MEOW_ICON_SETTINGS) == 0);
|
||||
assert(meow_topappbar_set_item_icon(ti, 99, MEOW_ICON_SETTINGS) == -1); /* 越界 */
|
||||
assert(meow_topappbar_set_item_icon(ti, ai, -1) == -1); /* 非法图标 */
|
||||
assert(meow_topappbar_set_item_icon(NULL, ai, MEOW_ICON_SETTINGS) == -1);
|
||||
meow_widget_draw(ti);
|
||||
/* 右起动作格(SDK 位图动作项宽 48)命中:几何中心 → 项 0(最右图标) */
|
||||
assert(meow_topappbar_hit(ti, 444, 328) == ai);
|
||||
/* 图标 ink 采样:与栏底色有「绝对对比度」的像素即 ink(日/夜主题都适用——
|
||||
* 日 = 深 onSurface 画浅底、夜 = 浅 onSurface 画深底,对比度都大)。 */
|
||||
int x0 = 480 - 48, y0 = 300 + 10, x1 = 480, y1 = 300 + 46;
|
||||
int bg_b, bg_g, bg_bgv;
|
||||
rd_px(480 - 3, 302, &bg_b, &bg_g, &bg_bgv); /* 栏右上角 ≈ 背景 */
|
||||
int bg_lum = (bg_b + bg_g + bg_bgv) / 3;
|
||||
int sum_ink = 0, cnt_ink = 0;
|
||||
for (int xx = x0; xx < x1; xx++) {
|
||||
for (int yy = y0; yy < y1; yy++) {
|
||||
int rr, gg, bb;
|
||||
rd_px(xx, yy, &rr, &gg, &bb);
|
||||
int lum = (rr + gg + bb) / 3;
|
||||
int d = lum - bg_lum; if (d < 0) d = -d;
|
||||
if (d > 20) { sum_ink += d; cnt_ink++; }
|
||||
}
|
||||
}
|
||||
assert(cnt_ink > 4); /* 位图 ink 非空(为抗锯齿留余量) */
|
||||
int before = sum_ink;
|
||||
/* 按压态 → 图标向栏底色淡 12% → 对比度整体下降 */
|
||||
meow_widget_set_state(ti, MEOW_WS_PRESSED, 1);
|
||||
meow_widget_draw(ti);
|
||||
sum_ink = 0;
|
||||
for (int xx = x0; xx < x1; xx++) {
|
||||
for (int yy = y0; yy < y1; yy++) {
|
||||
int rr, gg, bb;
|
||||
rd_px(xx, yy, &rr, &gg, &bb);
|
||||
int lum = (rr + gg + bb) / 3;
|
||||
int d = lum - bg_lum; if (d < 0) d = -d;
|
||||
if (d > 20) sum_ink += d;
|
||||
}
|
||||
}
|
||||
assert(sum_ink < before); /* 按压变淡 */
|
||||
meow_widget_set_state(ti, MEOW_WS_PRESSED, 0);
|
||||
printf("PASS topappbar: bitmap action icon draw/hit/pressed-fade (ink=%d->%d)\n",
|
||||
before, sum_ink);
|
||||
}
|
||||
|
||||
/* ---------- 状态栏图标 blit 路径回归(meow_gfx_blit_icon 同源核心)----------
|
||||
* 证明 refactor 后状态栏路径(blit + 顶栏脏区)仍写非空像素;复用 SB_ICON_WIFI(0)。 */
|
||||
{
|
||||
meow_ui_fill_rect(0, 0, 480, 28, 0x000000); /* 清顶栏区为黑底 */
|
||||
int bgr, bgg, bgb, bright = 0;
|
||||
meow_gfx_blit_icon(0, 0, 0, 16, 0xFFFFFF, 14); /* idx0=WIFI 槽左缘 0 */
|
||||
for (int xx = 0; xx < 32; xx++) {
|
||||
for (int yy = 4; yy < 24; yy++) {
|
||||
rd_px(xx, yy, &bgr, &bgg, &bgb);
|
||||
if ((bgr + bgg + bgb) / 3 > 40) bright++;
|
||||
}
|
||||
}
|
||||
assert(bright > 4); /* 状态栏 WiFi 位图 ink 非空(黑底 + 白前景 → 亮像素) */
|
||||
printf("PASS statusbar: meow_gfx_blit_icon(0) draws non-empty ink (brt=%d)\n", bright);
|
||||
}
|
||||
|
||||
/* ---------- NavigationBar ---------- */
|
||||
meow_widget *nav = W(meow_navigationbar_create(0, 680, 480, 40));
|
||||
int n0 = meow_navigationbar_add_item(nav, "Home");
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/ime/media/notify_ipc + 打桩 font/global):
|
||||
* gcc -Wall -Wextra -o test_notif_gesture test_notif_gesture.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_ime.c meow_ui_media.c meow_notify_ipc.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_ime.c meow_ui_str.c meow_ui_media.c meow_notify_ipc.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_notif_gesture
|
||||
*/
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -Wall -Wextra -o test_overlay_suppress test_overlay_suppress.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_ui_draw.c meow_ui_view.c meow_notify_ipc.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread
|
||||
*/
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* 2) 编译本文件 + 其余 hello 模块 + SDK 源 + 打桩,链接 app.o:
|
||||
* gcc -Wall -Wextra -DMEOW_UI_HOST_TEST -o test_p1p2p3_repro test_p1p2p3_repro.c app.o \
|
||||
* test_host_stub.c meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_ui_img.c meow_ui_config.c meow_ui_notify.c meow_notify_ipc.c meow_ui_draw.c \
|
||||
* meow_ui_view.c meow_ui_nav.c meow_ui_media.c stb_image.c \
|
||||
* ../hello_app/ui/home/home.c ../hello_app/ui/full.c ../hello_app/ui/demo/demo.c \
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* 编译(宿主 gcc,同 test_ime_fix):
|
||||
* gcc -Wall -Wextra -o test_pinyin test_pinyin.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_img.c meow_ui_media.c \
|
||||
* meow_notify_ipc.c stb_image.c -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -Wall -o test_sdk_fixes test_sdk_fixes.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_ui_draw.c meow_ui_view.c meow_notify_ipc.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread
|
||||
*/
|
||||
|
||||
@@ -27,10 +27,20 @@ _Static_assert(MEOW_THEME_DARK == 1, "theme dark");
|
||||
_Static_assert(MEOW_STYLE_MIUIX == 1, "style miuix");
|
||||
_Static_assert(MEOW_Z_TOP == 1000, "z top");
|
||||
_Static_assert(MEOW_TOPBAR_BACK == (-2), "topbar back");
|
||||
_Static_assert(MEOW_ICON_SETTINGS == 15 && MEOW_ICON_HEART == 29, "topbar icon idx 15..29");
|
||||
_Static_assert(MEOW_ICON_SKIP_PREV == 21 && MEOW_ICON_QUEUE_MUSIC == 27, "topbar icon order");
|
||||
_Static_assert(MEOW_SBAR_HOTZONE_FS == 4, "sbar hotzone fs");
|
||||
_Static_assert(MEOW_WS_PRESSED == (1u << 3), "ws pressed");
|
||||
_Static_assert(MEOW_BTN_ELEVATED == 4, "btn elevated");
|
||||
_Static_assert(MEOW_LAYOUT_BOX == 2, "layout box");
|
||||
/* §12.1 线性布局引擎 / §12.2 动画基座 / §12.3 滚动物理(新增模块,不改变既有值) */
|
||||
_Static_assert(MEOW_BOX_V == 1, "box dir v");
|
||||
_Static_assert(MEOW_BOX_WEIGHT == 1, "box size weight");
|
||||
_Static_assert(MEOW_BOX_WRAP == 2, "box size wrap");
|
||||
_Static_assert(MEOW_BOX_ALIGN_SPACE_BETWEEN == 3, "box align between");
|
||||
_Static_assert(MEOW_BOX_CROSS_STRETCH == 3, "box cross stretch");
|
||||
_Static_assert(MEOW_EASE_OVERSHOOT == 3, "ease overshoot");
|
||||
_Static_assert(MEOW_ANIM_MAX == 16, "anim pool");
|
||||
_Static_assert(MEOW_LIST_SEL_MULTI == 2, "list sel multi");
|
||||
_Static_assert(MEOW_NOTIFY_CAT_MEDIA == 5, "notify cat media");
|
||||
_Static_assert(MEOW_NOTIFY_URG_CRITICAL == 3, "notify urg critical");
|
||||
@@ -74,10 +84,19 @@ static void umbrella_smoke(void)
|
||||
(void)&meow_notify_send; (void)&meow_notify_category_str; (void)&meow_notify_update_progress;
|
||||
/* 配置(§9.2) */
|
||||
(void)&meow_config_dir; (void)&meow_config_int; (void)&meow_config_remove;
|
||||
/* 字符串资源/i18n(§13,追加式) */
|
||||
(void)&meow_str; (void)&meow_strf; (void)&meow_str_set_locale; (void)&meow_str_locale;
|
||||
/* 字体(§5.7/§10) */
|
||||
(void)&meow_font_enumerate; (void)&meow_font_tier; (void)&meow_font_select_by_id;
|
||||
/* 音频(§11) */
|
||||
(void)&meow_audio_open; (void)&meow_audio_query; (void)&meow_audio_on_preempt;
|
||||
/* §12.1 布局 / §12.2 动画 / §12.3 滚动 */
|
||||
(void)&meow_box_new; (void)&meow_box_arrange; (void)&meow_box_add;
|
||||
(void)&meow_box_add_sub; (void)&meow_widget_set_rect;
|
||||
(void)&meow_anim_start; (void)&meow_anim_cancel; (void)&meow_anim_tick;
|
||||
(void)&meow_ease_apply; (void)&meow_anim_set_region;
|
||||
(void)&meow_scroll_create; (void)&meow_scroll_wrap; (void)&meow_scroll_track;
|
||||
(void)&meow_scroll_fling; (void)&meow_scroll_velocity; (void)&meow_scroll_set_snap;
|
||||
}
|
||||
|
||||
/* 供宿主回归驱动直接调用;不链接本库也没关系(纯地址丢弃)。 */
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""gen_action_icons.py — 从 Material Symbols Rounded 官方 SVG 生成 sb_icons.h 位图动作图标。
|
||||
|
||||
背景:MeowOS SDK topbar(W_TOPAPPBAR)动作此前用字体码点(⚙↻⌕)绘制,4k 字库无这些码点→豆腐。
|
||||
正确路线 = 位图图标:与状态栏同一张 sb_icons[](Material Symbols Rounded 32x32 单通道 alpha),
|
||||
动作项经 meow_topappbar_set_item_icon(w, idx, MEOW_ICON_*) 指定索引后即画位图。
|
||||
|
||||
版权/来源:图标位图源自 google/material-design-icons 的 Material Symbols Rounded(Apache-2.0)。
|
||||
本脚本只做下载(或读缓存)+ 栅格化成 32x32 单通道 alpha + 拼接 C 数组。
|
||||
|
||||
生成方式:svg(24px 网格,viewBox 0 -960 960 960)→ cairosvg 512x512 → LANCZOS 降到 32x32
|
||||
取 alpha 通道(每像素 0..255 亮度/alpha,与状态栏同源——绘制颜色由调用方主题表提供)。
|
||||
用 FILL=1(实心)以匹配现有状态栏图标的实心风格(WiFi/BT/BATT 均为实心)。
|
||||
|
||||
可复现性:SVG 缓存于本目录 icons_src/{symbol}_fill1_24px.svg(缺则联网下载,命中缓存即断网可跑);
|
||||
栅格参数(512/32/LANCZOS/FILL=1)固定 → 重跑输出一致。
|
||||
|
||||
用法(在 patch_v2/sdk/ 下):
|
||||
python3 tools/gen_action_icons.py # 用 venv 的 cairosvg 运行
|
||||
依赖:Python3 + Pillow + cairosvg(isolated venv:python3 -m venv <v> && <v>/bin/pip install cairosvg)。
|
||||
输出:重写 patch_v2/sdk/sb_icons.h——保留现有 15 个状态栏位图(0-14,原样),追加 15 个动作
|
||||
图标(15-29),索引兼容(既有索引不变,只追加)。
|
||||
"""
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
try:
|
||||
import cairosvg
|
||||
from PIL import Image
|
||||
except Exception: # noqa
|
||||
sys.exit("缺少 cairosvg / Pillow,请先安装:python3 -m venv <v> && <v>/bin/pip install cairosvg")
|
||||
|
||||
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SDK_DIR = os.path.abspath(os.path.join(THIS_DIR, os.pardir))
|
||||
SB_ICONS_H = os.path.join(SDK_DIR, "sb_icons.h")
|
||||
SRC_DIR = os.path.join(THIS_DIR, "icons_src")
|
||||
|
||||
OUT_RES = 512 # cairosvg 栅格目标边长(先大后小 → 抗锯齿 alpha)
|
||||
TARGET = 32 # 最终位图边长(= SB_ICON_SIZE)
|
||||
FILL = 1 # Material Symbols FILL 轴:1=实心(与状态栏一致)
|
||||
BASE_URL = "https://raw.githubusercontent.com/google/material-design-icons/master/symbols/web/"
|
||||
UA = {"User-Agent": "Mozilla/5.0"}
|
||||
|
||||
# 现有状态栏图标 C 数组名(0-14,按 sb_icons[] 现有顺序;勿改序)。
|
||||
# 本脚本不改动它们(原样保留),仅在生成表格/枚举时按此顺序引用。
|
||||
BASE_ICONS = [
|
||||
"SB_ICON_WIFI", "SB_ICON_BT", "SB_ICON_BATT", "SB_ICON_VOL", "SB_ICON_BRIGHT",
|
||||
"SB_ICON_WIFI_STRONG", "SB_ICON_WIFI_MEDIUM", "SB_ICON_WIFI_WEAK",
|
||||
"SB_ICON_WIFI_NO", "SB_ICON_WIFI_OFF", "SB_ICON_BT_NO", "SB_ICON_BT_CONN",
|
||||
"SB_ICON_BT_DIS", "SB_ICON_BT_SEARCH", "SB_ICON_BATT_CHG",
|
||||
]
|
||||
|
||||
# 动作图标:c_array_name, enum_name, material_symbols 图标名。索引 15-29,勿改序。
|
||||
ACTIONS = [
|
||||
("SB_ICON_SETTINGS", "SB_I_SETTINGS", "settings"),
|
||||
("SB_ICON_REFRESH", "SB_I_REFRESH", "refresh"),
|
||||
("SB_ICON_SEARCH", "SB_I_SEARCH", "search"),
|
||||
("SB_ICON_PLAY_ARROW", "SB_I_PLAY_ARROW", "play_arrow"),
|
||||
("SB_ICON_PAUSE", "SB_I_PAUSE", "pause"),
|
||||
("SB_ICON_SKIP_NEXT", "SB_I_SKIP_NEXT", "skip_next"),
|
||||
("SB_ICON_SKIP_PREV", "SB_I_SKIP_PREV", "skip_previous"),
|
||||
("SB_ICON_ARROW_BACK", "SB_I_ARROW_BACK", "arrow_back"),
|
||||
("SB_ICON_CLOSE", "SB_I_CLOSE", "close"),
|
||||
("SB_ICON_ADD", "SB_I_ADD", "add"),
|
||||
("SB_ICON_CHECK", "SB_I_CHECK", "check"),
|
||||
("SB_ICON_LYRICS", "SB_I_LYRICS", "lyrics"),
|
||||
("SB_ICON_QUEUE_MUSIC", "SB_I_QUEUE_MUSIC", "queue_music"),
|
||||
("SB_ICON_DOWNLOAD", "SB_I_DOWNLOAD", "download"),
|
||||
("SB_ICON_HEART", "SB_I_HEART", "favorite"), # Material Symbols 用 "favorite" 表心形
|
||||
]
|
||||
|
||||
HEADER_COMMENT = """/* sb_icons.h — 状态栏 + 动作位图图标(Material Symbols Rounded, MD3),单通道 alpha 位图 32x32。
|
||||
* 生成自 material-symbols rounded(google/material-design-icons,Apache-2.0)SVG 栅格化:
|
||||
* 图标族 = FILL=1 实心(与状态栏 WiFi/BT/BATT 现有实心风格一致);每像素 0..255(亮度/alpha)。
|
||||
* ★ 为日/夜间预留:位图只存亮度;绘制颜色由调用方主题表提供,换主题仅改配色。
|
||||
* 索引布局(兼容):状态栏 SB_I_WIFI=0..SB_I_BATT_CHG=14(原样保留);TopAppBar 动作
|
||||
* MEOW_ICON_SETTINGS=15..MEOW_ICON_HEART=29(只追加,既有索引不变)。动作图标为 15-29,
|
||||
* 经 meow_topappbar_set_item_icon(w, idx, MEOW_ICON_*) 使 W_TOPAPPBAR 动作项画位图。
|
||||
* 重新生成:python3 tools/gen_action_icons.py(SVG 缓存 tools/icons_src/,可复现)。 */
|
||||
#ifndef SB_ICONS_H
|
||||
#define SB_ICONS_H
|
||||
|
||||
#define SB_ICON_SIZE 32
|
||||
|
||||
typedef struct { const unsigned char *data; } sb_icon_t;
|
||||
|
||||
"""
|
||||
|
||||
ACTION_BLOCK_MARKER = "/* ===== TopAppBar \u52a8\u4f5c\u56fe\u6807" # "/* ===== TopAppBar 动作图标"
|
||||
|
||||
|
||||
def fetch(url, path, retries=6):
|
||||
"""带重试的下载(网络不稳):命中缓存直接返回,失败退避重试。"""
|
||||
if os.path.exists(path) and os.path.getsize(path) > 200:
|
||||
return open(path, "rb").read()
|
||||
for k in range(retries):
|
||||
try:
|
||||
req = urllib.request.Request(url, headers=UA)
|
||||
data = urllib.request.urlopen(req, timeout=25).read()
|
||||
if len(data) < 200:
|
||||
raise ValueError("too small %d" % len(data))
|
||||
open(path, "wb").write(data)
|
||||
return data
|
||||
except Exception as e: # noqa
|
||||
sys.stderr.write(" retry %d %s: %s\n" % (k, path, repr(e)[:80]))
|
||||
time.sleep(1.5 * (k + 1))
|
||||
raise RuntimeError("download failed: " + url)
|
||||
|
||||
|
||||
def svg_path_for(symbol):
|
||||
return os.path.join(SRC_DIR, symbol + "_fill%d_24px.svg" % FILL)
|
||||
|
||||
|
||||
def ensure_svg(symbol):
|
||||
path = svg_path_for(symbol)
|
||||
if not (os.path.exists(path) and os.path.getsize(path) > 200):
|
||||
url = BASE_URL + symbol + "/materialsymbolsrounded/" + symbol + "_fill%d_24px.svg" % FILL
|
||||
fetch(url, path)
|
||||
return path
|
||||
|
||||
|
||||
def rasterize_alpha32(symbol):
|
||||
"""SVG → 32x32 单通道 alpha(0..255)。512 渲大图再 LANCZOS 降采样,得到平滑边缘。"""
|
||||
svg = open(ensure_svg(symbol), "rb").read()
|
||||
png = cairosvg.svg2png(bytestring=svg, output_width=OUT_RES, output_height=OUT_RES)
|
||||
alpha = Image.open(io.BytesIO(png)).convert("RGBA").getchannel("A")
|
||||
return alpha.resize((TARGET, TARGET), Image.LANCZOS)
|
||||
|
||||
|
||||
def fmt_alpha_array(name, alpha):
|
||||
"""把 32x32 alpha 排成 C 数组(每行 32 个像素,便于目检)。"""
|
||||
lines = ["static const unsigned char %s[%d]={" % (name, TARGET * TARGET)]
|
||||
for y in range(TARGET):
|
||||
row = ",".join(str(alpha.getpixel((x, y))) for x in range(TARGET))
|
||||
lines.append(row + ",")
|
||||
lines.append("};")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def build_action_block():
|
||||
outs = [ACTION_BLOCK_MARKER + "(Material Symbols Rounded, MD3, Apache-2.0)====\n"]
|
||||
outs.append(" * 复用状态栏同源 32x32 单通道 alpha;绘制颜色/按压态由 meow_ui_widget.c 主题表提供。 */\n")
|
||||
for cname, _enum, symbol in ACTIONS:
|
||||
a = rasterize_alpha32(symbol)
|
||||
outs.append(fmt_alpha_array(cname, a))
|
||||
return "\n".join(outs)
|
||||
|
||||
|
||||
def build_table_and_enum():
|
||||
table = ["static const sb_icon_t sb_icons[] = {"]
|
||||
for name in BASE_ICONS:
|
||||
table.append(" { %s }," % name)
|
||||
for cname, _enum, _symbol in ACTIONS:
|
||||
table.append(" { %s }," % cname)
|
||||
table.append("};")
|
||||
table = "\n".join(table)
|
||||
|
||||
enum = ["/* 枚举索引(与 sb_icons[] 对应)。状态栏 0-14(勿改序);动作 15-29(TopAppBar 用)。 */"]
|
||||
enum.append("enum { SB_I_WIFI=0, SB_I_BT=1, SB_I_BATT=2, SB_I_VOL=3, SB_I_BRIGHT=4,")
|
||||
enum.append(" SB_I_WIFI_STRONG=5, SB_I_WIFI_MEDIUM=6, SB_I_WIFI_WEAK=7,")
|
||||
enum.append(" SB_I_WIFI_NO=8, SB_I_WIFI_OFF=9, SB_I_BT_NO=10, SB_I_BT_CONN=11,")
|
||||
enum.append(" SB_I_BT_DIS=12, SB_I_BT_SEARCH=13, SB_I_BATT_CHG=14,")
|
||||
# 动作枚举从 15 起,与 meow_ui_widgets.h 的 MEOW_ICON_* 同值(勿改序)
|
||||
for i, (_c, e, _s) in enumerate(ACTIONS):
|
||||
last = (i == len(ACTIONS) - 1)
|
||||
enum.append(" %s=%d%s" % (e, 15 + i, " }; " if last else ","))
|
||||
return table + "\n\n" + "\n".join(enum) + "\n\n#endif /* SB_ICONS_H */\n"
|
||||
|
||||
|
||||
def regenerate_sb_icons_h():
|
||||
old = open(SB_ICONS_H, encoding="utf-8").read()
|
||||
# body = 现有状态栏数组(原样保留)。HEADER_COMMENT 已提供 ifndef/define/size/typedef,
|
||||
# 故从首个状态栏数组起切;终点:优先动作块标记(幂等重跑),缺失则 sb_icons[] 表格(首跑)。
|
||||
body_start = old.find("static const unsigned char " + BASE_ICONS[0] + "[1024]")
|
||||
if body_start < 0:
|
||||
sys.exit("sb_icons.h 缺少首个状态栏数组,拒绝重写")
|
||||
pr = old.find(ACTION_BLOCK_MARKER)
|
||||
if pr < 0:
|
||||
pr = old.find("static const sb_icon_t sb_icons[]")
|
||||
if pr < 0 or pr <= body_start:
|
||||
sys.exit("sb_icons.h 缺少 sb_icons[] 表格,拒绝重写")
|
||||
body = old[body_start:pr]
|
||||
|
||||
out = HEADER_COMMENT + body.rstrip("\n") + "\n\n"
|
||||
out += build_action_block() + "\n\n"
|
||||
out += build_table_and_enum()
|
||||
open(SB_ICONS_H, "w", encoding="utf-8").write(out)
|
||||
print("wrote %s (%d bytes)" % (SB_ICONS_H, len(out)))
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs(SRC_DIR, exist_ok=True)
|
||||
regenerate_sb_icons_h()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M440-440H240q-17 0-28.5-11.5T200-480q0-17 11.5-28.5T240-520h200v-200q0-17 11.5-28.5T480-760q17 0 28.5 11.5T520-720v200h200q17 0 28.5 11.5T760-480q0 17-11.5 28.5T720-440H520v200q0 17-11.5 28.5T480-200q-17 0-28.5-11.5T440-240v-200Z"/></svg>
|
||||
|
After Width: | Height: | Size: 335 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m313-440 196 196q12 12 11.5 28T508-188q-12 11-28 11.5T452-188L188-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l264-264q11-11 27.5-11t28.5 11q12 12 12 28.5T508-715L313-520h447q17 0 28.5 11.5T800-480q0 17-11.5 28.5T760-440H313Z"/></svg>
|
||||
|
After Width: | Height: | Size: 328 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m382-354 339-339q12-12 28-12t28 12q12 12 12 28.5T777-636L410-268q-12 12-28 12t-28-12L182-440q-12-12-11.5-28.5T183-497q12-12 28.5-12t28.5 12l142 143Z"/></svg>
|
||||
|
After Width: | Height: | Size: 254 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-424 284-228q-11 11-28 11t-28-11q-11-11-11-28t11-28l196-196-196-196q-11-11-11-28t11-28q11-11 28-11t28 11l196 196 196-196q11-11 28-11t28 11q11 11 11 28t-11 28L536-480l196 196q11 11 11 28t-11 28q-11 11-28 11t-28-11L480-424Z"/></svg>
|
||||
|
After Width: | Height: | Size: 331 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-337q-8 0-15-2.5t-13-8.5L308-492q-12-12-11.5-28t11.5-28q12-12 28.5-12.5T365-549l75 75v-286q0-17 11.5-28.5T480-800q17 0 28.5 11.5T520-760v286l75-75q12-12 28.5-11.5T652-548q11 12 11.5 28T652-492L508-348q-6 6-13 8.5t-15 2.5ZM240-160q-33 0-56.5-23.5T160-240v-80q0-17 11.5-28.5T200-360q17 0 28.5 11.5T240-320v80h480v-80q0-17 11.5-28.5T760-360q17 0 28.5 11.5T800-320v80q0 33-23.5 56.5T720-160H240Z"/></svg>
|
||||
|
After Width: | Height: | Size: 501 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-147q-14 0-28.5-5T426-168l-69-63q-106-97-191.5-192.5T80-634q0-94 63-157t157-63q53 0 100 22.5t80 61.5q33-39 80-61.5T660-854q94 0 157 63t63 157q0 115-85 211T602-230l-68 62q-11 11-25.5 16t-28.5 5Z"/></svg>
|
||||
|
After Width: | Height: | Size: 303 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M280-400h80q17 0 28.5-11.5T400-440q0-17-11.5-28.5T360-480h-80q-17 0-28.5 11.5T240-440q0 17 11.5 28.5T280-400Zm480-80q-50 0-85-35t-35-85q0-50 35-85t85-35q11 0 21 2t19 5v-167q0-17 11.5-28.5T840-920h80q17 0 28.5 11.5T960-880q0 17-11.5 28.5T920-840h-40v240q0 50-35 85t-85 35Zm-480-40h200q17 0 28.5-11.5T520-560q0-17-11.5-28.5T480-600H280q-17 0-28.5 11.5T240-560q0 17 11.5 28.5T280-520Zm0-120h200q17 0 28.5-11.5T520-680q0-17-11.5-28.5T480-720H280q-17 0-28.5 11.5T240-680q0 17 11.5 28.5T280-640Zm-40 400-92 92q-19 19-43.5 8.5T80-177v-623q0-33 23.5-56.5T160-880h440q31 0 54.5 19t23.5 49q0 14-6.5 25T654-769q-44 27-69 72t-25 97q0 54 26.5 100t73.5 73q22 13 35 34.5t13 47.5q0 45-31.5 75T600-240H240Z"/></svg>
|
||||
|
After Width: | Height: | Size: 795 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M640-200q-33 0-56.5-23.5T560-280v-400q0-33 23.5-56.5T640-760q33 0 56.5 23.5T720-680v400q0 33-23.5 56.5T640-200Zm-320 0q-33 0-56.5-23.5T240-280v-400q0-33 23.5-56.5T320-760q33 0 56.5 23.5T400-680v400q0 33-23.5 56.5T320-200Z"/></svg>
|
||||
|
After Width: | Height: | Size: 327 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M320-273v-414q0-17 12-28.5t28-11.5q5 0 10.5 1.5T381-721l326 207q9 6 13.5 15t4.5 19q0 10-4.5 19T707-446L381-239q-5 3-10.5 4.5T360-233q-16 0-28-11.5T320-273Z"/></svg>
|
||||
|
After Width: | Height: | Size: 261 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M640-160q-50 0-85-35t-35-85q0-50 35-85t85-35q11 0 21 1.5t19 6.5v-288q0-17 11.5-28.5T720-720h120q17 0 28.5 11.5T880-680q0 17-11.5 28.5T840-640h-80v360q0 50-35 85t-85 35ZM160-320q-17 0-28.5-11.5T120-360q0-17 11.5-28.5T160-400h240q17 0 28.5 11.5T440-360q0 17-11.5 28.5T400-320H160Zm0-160q-17 0-28.5-11.5T120-520q0-17 11.5-28.5T160-560h400q17 0 28.5 11.5T600-520q0 17-11.5 28.5T560-480H160Zm0-160q-17 0-28.5-11.5T120-680q0-17 11.5-28.5T160-720h400q17 0 28.5 11.5T600-680q0 17-11.5 28.5T560-640H160Z"/></svg>
|
||||
|
After Width: | Height: | Size: 600 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-70q0-17 11.5-28.5T760-800q17 0 28.5 11.5T800-760v200q0 17-11.5 28.5T760-520H560q-17 0-28.5-11.5T520-560q0-17 11.5-28.5T560-600h128q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q68 0 124.5-34.5T692-367q8-14 22.5-19.5t29.5-.5q16 5 23 21t-1 30q-41 80-117 128t-169 48Z"/></svg>
|
||||
|
After Width: | Height: | Size: 463 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M380-320q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l224 224q11 11 11 28t-11 28q-11 11-28 11t-28-11L532-372q-30 24-69 38t-83 14Zm0-80q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/></svg>
|
||||
|
After Width: | Height: | Size: 387 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M433-80q-27 0-46.5-18T363-142l-9-66q-13-5-24.5-12T307-235l-62 26q-25 11-50 2t-39-32l-47-82q-14-23-8-49t27-43l53-40q-1-7-1-13.5v-27q0-6.5 1-13.5l-53-40q-21-17-27-43t8-49l47-82q14-23 39-32t50 2l62 26q11-8 23-15t24-12l9-66q4-26 23.5-44t46.5-18h94q27 0 46.5 18t23.5 44l9 66q13 5 24.5 12t22.5 15l62-26q25-11 50-2t39 32l47 82q14 23 8 49t-27 43l-53 40q1 7 1 13.5v27q0 6.5-2 13.5l53 40q21 17 27 43t-8 49l-48 82q-14 23-39 32t-50-2l-60-26q-11 8-23 15t-24 12l-9 66q-4 26-23.5 44T527-80h-94Zm49-260q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Z"/></svg>
|
||||
|
After Width: | Height: | Size: 671 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M660-280v-400q0-17 11.5-28.5T700-720q17 0 28.5 11.5T740-680v400q0 17-11.5 28.5T700-240q-17 0-28.5-11.5T660-280Zm-440-35v-330q0-18 12-29t28-11q5 0 11 1t11 5l248 166q9 6 13.5 14.5T548-480q0 10-4.5 18.5T530-447L282-281q-5 4-11 5t-11 1q-16 0-28-11t-12-29Z"/></svg>
|
||||
|
After Width: | Height: | Size: 357 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M220-280v-400q0-17 11.5-28.5T260-720q17 0 28.5 11.5T300-680v400q0 17-11.5 28.5T260-240q-17 0-28.5-11.5T220-280Zm458-1L430-447q-9-6-13.5-14.5T412-480q0-10 4.5-18.5T430-513l248-166q5-4 11-5t11-1q16 0 28 11t12 29v330q0 18-12 29t-28 11q-5 0-11-1t-11-5Z"/></svg>
|
||||
|
After Width: | Height: | Size: 354 B |