From 143c11ac94bd7ba034cc8952ad990d81317b5fc3 Mon Sep 17 00:00:00 2001 From: MeowOS Date: Sun, 30 Aug 2026 15:27:10 +0800 Subject: [PATCH] =?UTF-8?q?M2.60=E2=86=92M2.61=20=E9=AA=A8=E6=9E=B6:=20SDK?= =?UTF-8?q?=20Activity=20=E6=A0=88=20+=20=E6=8E=A7=E4=BB=B6=E5=BD=92?= =?UTF-8?q?=E5=B1=9E(agent-122)=20+=20=E6=8C=89=E9=92=AE=E5=8E=BB=E5=9C=88?= =?UTF-8?q?/=E5=8D=B3=E6=97=B6=E9=87=8D=E7=BB=98(agent-119)=20+=20SDK=20?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E6=96=87=E6=A1=A3(agent-121)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SDK §6.1 三层控件模型: Activity栈(push/pop/active) + 页面控件归属 owner + meow_ui_global_* 骨架(②) + 系统级仅内部接口(③) - meow_widget 加 owner; live_btn_hit/meow_widget_hit 仅 active activity 命中; touch_refresh stale 保护 - 根治"全屏页触摸主页按钮闪现"(跨页面控件残留); 控件对象复用弹栈不重建 - hello 迁移双 Activity(home/full): 全屏左滑=弹栈回主页, 根左滑=退出 - agent-119 按钮去圈(纯色accent+黑12/24%)+即时局部重绘保留; agent-121 SDK模板§5.3-5.7 --- build_sdk.sh | 2 +- meow_ui.h | 37 ++++++++++++ meow_ui_activity.c | 33 +++++++++++ meow_ui_gfx.c | 4 ++ meow_ui_global.c | 61 ++++++++++++++++++++ meow_ui_input.c | 7 +++ meow_ui_internal.h | 13 +++++ meow_ui_widget.c | 140 +++++++++++++++++++++++++++++++++++++++------ 8 files changed, 277 insertions(+), 20 deletions(-) create mode 100644 meow_ui_activity.c create mode 100644 meow_ui_global.c diff --git a/build_sdk.sh b/build_sdk.sh index 6589f06..b0fe874 100755 --- a/build_sdk.sh +++ b/build_sdk.sh @@ -10,7 +10,7 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架 rm -f meow_ui.a *.o # -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移) -OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol stb_image" +OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global stb_image" CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" for f in $OBJS; do echo "== cc $f ==" diff --git a/meow_ui.h b/meow_ui.h index e7372c3..04120d9 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -156,6 +156,43 @@ void meow_ui_request_exit(void); /* 请求退出(相当于返回/ * x<=0 时清掉此前画的条(填回背景色)。不发 flush,由调用方 meow_ui_flush_region 上屏。 */ void meow_ui_swipe_hint(int x); +/* ============================================================ */ +/* §6.1 控件生命周期与归属(Activity 栈 + 三层控件模型) [源:2026-08-30] */ +/* ============================================================ */ +/* 根治「跨页面控件残留」:控件命中/绘制必须严格绑定其所属页面,否则会出现 + * "按钮已不可见但仍被触摸命中"(如全屏页触摸到主页按钮位置闪现 accent 底)。 + * + * 三层职责(从 App 到系统,风险从低到高): + * ① 页面控件:归属唯一 owner Activity;命中/实时按压只在「当前 active Activity + * 的控件集」里生效。对象不销毁(同 Activity 内复用),弹栈回来自动恢复 → + * 回页面零重建。 + * ② App 跨 Activity 控件(SDK 托管):钉在 App 的 Activity 栈之上、跨 Activity 常驻, + * 逻辑/渲染由 SDK 调度。本轮为「可用骨架」(见上 API 注释),待 B2 细化。 + * ③ 系统级控件(状态栏/通知中心/返回滑条/电源菜单):仅框架内部,无公共 API(不开放)。 + * + * 触摸命中分流顺序:③ 系统全局 → ② App 全局 → ① 当前 active Activity 页面控件; + * 某层命中即消费,否则落到下一层。 */ + +/* ① Activity 栈。控件创建时绑定「当前 active Activity」为 owner;弹栈回来自动恢复。 + * 栈空时 active() 返回 0(根/default Activity),故未显式管理栈的 App 默认所有控件 + * 归属根 Activity 0。push 新 Activity(进子页/全屏)、pop 回上级(回根)。 */ +void meow_ui_activity_push(int id); +void meow_ui_activity_pop(void); +int meow_ui_activity_active(void); /* 当前 active activity id(栈空=0) */ + +/* ② App 跨 Activity 控件(SDK 托管,骨架)。SDK 维护全局控件列表: + * - 渲染:每帧(有脏区需上屏时,meow_ui_gfx present())在 active activity 内容之上、 + * 全局状态栏之下调用 render_cb(跨 Activity 常驻); + * - 触摸:TAP 先给 global hit_cb(返回 1=消费),否则落到 active activity 页面控件。 + * 逻辑/渲染由 SDK 调度,App 只需声明。注:骨架,待 B2 细化。 */ +typedef void (*meow_global_render_fn)(void *ctx); /* 每次渲染帧回调(SDK 调用) */ +typedef int (*meow_global_hit_fn)(void *ctx, int x, int y); /* 命中返回1=消费 */ +void meow_ui_global_add(int id, meow_global_render_fn r, meow_global_hit_fn h, void *ctx); +void meow_ui_global_remove(int id); + +/* ③ 系统级控件:状态栏/通知中心/左缘返回滑条/电源菜单等全局叠加层——仅 SDK 内部接口, + * 不向用户 App 开放任何公共悬浮/叠加层 API。既有状态栏/返回滑条逻辑(§3.1)不动。 */ + /* ============================================================ */ /* §3.1 全局状态栏 / 全屏(A-0,SDK 系统级组件) */ /* ============================================================ */ diff --git a/meow_ui_activity.c b/meow_ui_activity.c new file mode 100644 index 0000000..205d215 --- /dev/null +++ b/meow_ui_activity.c @@ -0,0 +1,33 @@ +/* + * meow_ui_activity.c — Activity 栈(§6.1 ①)。 + * + * 控件严格归属创建它的 Activity;命中/实时按压只在「当前 active Activity」的控件集里 + * 生效(见 meow_ui_widget.c 的 owner 绑定 + live_btn_hit/meow_widget_hit 过滤)。 + * 栈空时 active() 返回 0(根/default Activity)。 + * + * 依赖:无(纯静态数组 + 无外部符号),宿主/真机均可链入,便于单测。 + * 语义:push = 压栈(新 Activity 成为 active); pop = 弹栈(回上级,栈空则停在根 0)。 + */ +#include "meow_ui.h" + +#define MEOW_ACT_MAX 32 + +static int g_act[MEOW_ACT_MAX]; +static int g_act_n = 0; + +void meow_ui_activity_push(int id) +{ + if (g_act_n >= MEOW_ACT_MAX) + return; /* 栈满:静默钳制(骨架级,不崩) */ + g_act[g_act_n++] = id; +} + +void meow_ui_activity_pop(void) +{ + if (g_act_n > 0) g_act_n--; +} + +int meow_ui_activity_active(void) +{ + return g_act_n > 0 ? g_act[g_act_n - 1] : 0; +} diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index a31e971..a056516 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -652,6 +652,10 @@ static void present(void) if (!g_refresh_always_max) { if (g_last_present_ms && now - g_last_present_ms < (unsigned long long)period) return; } + /* §6.1 ②:App 跨 Activity 全局控件(SDK 托管骨架)。在 active activity 内容之上、 + * 全局状态栏之下绘制——跨 Activity 常驻,仅在有帧需上屏(已过帧率门控)时调用。 + * render_cb 内的 meow_ui_* 绘制会自行标脏,随下方 dirty 列表一并上屏。 */ + meow_ui_global_render(); /* 全局状态栏(A-0)+ 上收(collapsed)基座:上屏前把状态栏画进 back_buf 顶部行。 * 展开态=整条(MUI_SBAR_H 行);收合态只占/清「提示线」区域(MUI_SBAR_COLLAPSE_H 行), * app 内容(inset=提示线高)可上移到提示线下缘,无 28px 空条。全屏/隐藏时 active()==0 diff --git a/meow_ui_global.c b/meow_ui_global.c new file mode 100644 index 0000000..d91dc20 --- /dev/null +++ b/meow_ui_global.c @@ -0,0 +1,61 @@ +/* + * meow_ui_global.c — App 跨 Activity 控件(§6.1 ②,SDK 托管骨架)。 + * + * SDK 维护一个全局控件列表(钉在 App 的 Activity 栈之上、跨 Activity 常驻): + * - 渲染:meow_ui_gfx present() 每帧在 active activity 内容之上、全局状态栏之下调 + * meow_ui_global_render()(遍历列表调 render_cb); + * - 触摸:meow_ui_input TAP 分发先给 meow_ui_global_hit(x,y)——命中返回 1=消费, + * 否则落到 active activity 页面控件(①)。 + * + * 骨架(待 B2 细化):无 z 序细分/无按压态管理/无命中区排布——App 的 render/hit 回调 + * 自行决定绘制与命中。跨 Activity 常驻由「列表不随 Activity 栈变化」天然保证。 + */ +#include "meow_ui.h" + +#define MEOW_GLOBAL_MAX 16 + +typedef struct { + int id; + meow_global_render_fn render; + meow_global_hit_fn hit; + void *ctx; +} gentry; + +static gentry g_globals[MEOW_GLOBAL_MAX]; +static int g_global_n = 0; + +void meow_ui_global_add(int id, meow_global_render_fn r, meow_global_hit_fn h, void *ctx) +{ + for (int i = 0; i < g_global_n; i++) + if (g_globals[i].id == id) return; /* 同 id 幂等(重复 add 不新增,骨架) */ + if (g_global_n >= MEOW_GLOBAL_MAX) return; + g_globals[g_global_n].id = id; + g_globals[g_global_n].render = r; + g_globals[g_global_n].hit = h; + g_globals[g_global_n].ctx = ctx; + g_global_n++; +} + +void meow_ui_global_remove(int id) +{ + for (int i = 0; i < g_global_n; i++) { + if (g_globals[i].id == id) { + g_globals[i] = g_globals[--g_global_n]; + return; + } + } +} + +void meow_ui_global_render(void) +{ + for (int i = 0; i < g_global_n; i++) + if (g_globals[i].render) g_globals[i].render(g_globals[i].ctx); +} + +int meow_ui_global_hit(int x, int y) +{ + for (int i = 0; i < g_global_n; i++) + if (g_globals[i].hit && g_globals[i].hit(g_globals[i].ctx, x, y)) + return 1; + return 0; +} diff --git a/meow_ui_input.c b/meow_ui_input.c index aa19944..c234ca7 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -310,6 +310,10 @@ void meow_ui_run(const meow_ui_events *ev) startup_cooldown_until = now_ms() + STARTUP_COOLDOWN_MS; /* M2.22(C):启动窗口丢弃 SWIPE */ while (!g_exit) { + /* agent-119:实时触摸按压/按住/释放 → 即时重绘命中按钮区(不等 app 重绘)。 + * IME 打开时软键盘为覆盖层且拦截触摸,不驱动背后按钮视觉,跳过。 */ + if (!meow_ime_active()) meow_widget_touch_refresh(); + meow_input_ev e; int got = 0; while (meow_input_pop(&e)) { @@ -323,6 +327,9 @@ void meow_ui_run(const meow_ui_events *ev) meow_ui_swipe_hint(-1); meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6); if (meow_ime_active() && meow_ime_handle_tap(e.a, e.b)) continue; + /* §6.1 触摸分流:③ 系统全局(IME/状态栏…已在上面消费) → ② App 全局控件 + * (命中返回 1=消费,否则落到 ① active Activity 页面控件)。 */ + if (meow_ui_global_hit(e.a, e.b)) continue; if (g_events_set && g_events.on_tap) g_events.on_tap(e.a, e.b); } else if (e.type == MEOW_EV_KEY) { if (g_events_set && g_events.on_key) g_events.on_key(e.a, e.b); diff --git a/meow_ui_internal.h b/meow_ui_internal.h index 7767568..ff151e2 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -64,6 +64,19 @@ unsigned int meow_input_touch_hold_ms(void); /* 按下已持续毫秒 * (把按下时刻回拨),便于测按住态;down=0 复位。设备运行时无调用方。 */ void meow_input_touch_set(int down, int x, int y, unsigned hold_ms); +/* ---- 控件层实时刷新(meow_ui_widget.c)---- + * 每帧由 meow_ui_run 调用(主线程):实时触摸按下/释放/按住达阈值时,重绘命中的按钮区 + * 并局部上屏,让按压/按住/释放视觉即时反映,无需 app 在 on_tick/on_tap 重绘(agent-119)。 + * 仅作用于 SDK 控件按钮(meow_widget_draw 的 W_BTN);app 纯自绘不适用。 */ +void meow_widget_touch_refresh(void); + +/* ---- App 跨 Activity 全局控件(meow_ui_global.c,§6.1 ②骨架)---- + * 渲染帧(meow_ui_gfx present())在 active activity 内容之上、全局状态栏之下调用 + * meow_ui_global_render;触摸 TAP 分发先给 meow_ui_global_hit(命中返回 1=消费, + * 否则落到 active activity 页面控件)。跨 Activity 常驻,由 SDK 调度。 */ +void meow_ui_global_render(void); +int meow_ui_global_hit(int x, int y); + /* ---- IME(meow_ui_ime.c)---- */ /* 当前是否显示软键盘。 */ int meow_ime_active(void); diff --git a/meow_ui_widget.c b/meow_ui_widget.c index 58c2f4d..00c8f77 100644 --- a/meow_ui_widget.c +++ b/meow_ui_widget.c @@ -11,7 +11,7 @@ enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3 }; -/* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层 + 描边环)。 +/* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层)。 * 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */ #define MEOW_BTN_HOLD_MS 500u @@ -28,6 +28,7 @@ static uint32_t blend_color(uint32_t c, uint32_t o, int t) struct meow_widget { int type; int x, y, w, h; + int owner; /* §6.1 ①:创建时绑定的 Activity;命中只在 owner==active 才生效 */ char label[128]; int pressed; /* 按钮按下视觉态 */ int min, max, val; /* 滑条 */ @@ -37,12 +38,105 @@ struct meow_widget { void *ctx; }; +/* ---- 实时按压即时刷新(agent-119)---- + * 按压/按住视觉由 meow_widget_draw 读实时触摸(meow_input_touch_*)呈现,但 app 只在 + * on_tick(1s)/on_tap 重绘 → 快速点/按住时按压态刷新滞后/漏。此处在 SDK 触摸按下/释放/ + * 按住达阈值时主动重绘命中的按钮区域并局部上屏(不等 app 重绘),即时反映按下变深/ + * 释放恢复,连续快速点击每按都反映。仅针对 SDK 控件按钮(W_BTN);app 纯自绘不适用。 + * 语义不变:on_tap / meow_btn_pressed / meow_widget_hit 零改动;非按钮区域不动。 */ +#define MEOW_LIVE_BTN_MAX 64 +static meow_widget *g_live_btns[MEOW_LIVE_BTN_MAX]; +static int g_live_btn_n = 0; +static meow_widget *g_live_btn = NULL; /* 实时按压中最上层按钮(无 = NULL) */ +static int g_live_prev_hold = 0; /* 该按钮上次渲染是否为按住态 */ + +static void live_btn_register(meow_widget *w) +{ + if (!w || w->type != W_BTN) return; + for (int i = 0; i < g_live_btn_n; i++) + if (g_live_btns[i] == w) return; + if (g_live_btn_n < MEOW_LIVE_BTN_MAX) g_live_btns[g_live_btn_n++] = w; +} + +static void live_btn_unregister(meow_widget *w) +{ + for (int i = 0; i < g_live_btn_n; i++) { + if (g_live_btns[i] == w) { + g_live_btns[i] = g_live_btns[--g_live_btn_n]; + if (g_live_btn == w) g_live_btn = NULL; + return; + } + } + if (g_live_btn == w) g_live_btn = NULL; +} + +/* 命中:返回坐标所在的「当前 active Activity」的最上层按钮(创建序 = 绘制序,最后者在上)。 + * §6.1 ①:仅扫描 owner == 当前 active Activity 的按钮 —— 非 active Activity 的控件 + * 绝不参与命中(根治 agent-119 遗留的「全屏页触摸到主页按钮位置 → 闪现 accent 底」)。 */ +static meow_widget *live_btn_hit(int x, int y) +{ + int active = meow_ui_activity_active(); + meow_widget *r = NULL; + for (int i = 0; i < g_live_btn_n; i++) { + meow_widget *w = g_live_btns[i]; + if (w->owner != active) continue; /* 非 active activity:跳过 */ + if (x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h) + r = w; /* 越后创建越上:保留最后命中 */ + } + return r; +} + +/* 重绘并局部刷新一个按钮区(绘制内部调 meow_gfx_fill_round 已标脏;flush_region 合区+上屏)。 */ +static void live_btn_redraw(meow_widget *w) +{ + if (!w) return; + meow_widget_draw(w); + meow_ui_flush_region(w->x, w->y, w->w, w->h); +} + +/* 由 meow_ui_run 每帧调用(主线程):检测实时触摸状态迁移,即时重绘命中按钮的按压/按住/ + * 释放,不等 app on_tick/on_tap。仅在触摸状态相对上次渲染发生变化时动作(非全屏、省性能)。 */ +void meow_widget_touch_refresh(void) +{ + int active = meow_ui_activity_active(); + /* §6.1 ①:若当前按住按钮已不属于 active activity(按压期间发生了 Activity 切换, + * 例如点 FULLSCREEN 后进入全屏),静默清引用——不绘制、不闪现(否则会在新页面底上 + * 画出旧按钮的 accent 残留)。 */ + if (g_live_btn && g_live_btn->owner != active) { + g_live_btn = NULL; + g_live_prev_hold = 0; + } + int down = meow_input_touch_down(); + meow_widget *hit = down ? live_btn_hit(meow_input_touch_x(), meow_input_touch_y()) : NULL; + int hold = down && hit && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS; + + if (!down) { + /* 释放:把最近按压按钮恢复为常态 */ + if (g_live_btn) { live_btn_redraw(g_live_btn); g_live_btn = NULL; } + g_live_prev_hold = 0; + return; + } + if (hit != g_live_btn) { + /* 按下/滑至新按钮:先恢复旧(若手指滑离),再按压新 */ + if (g_live_btn) live_btn_redraw(g_live_btn); + g_live_btn = hit; + g_live_prev_hold = hold; + if (hit) live_btn_redraw(hit); + } else if (hit && hold != g_live_prev_hold) { + /* 同一按钮按住达阈值:升级到更强态 */ + live_btn_redraw(hit); + g_live_prev_hold = hold; + } +} + meow_widget *meow_btn_create(int x, int y, int w, int h, const char *label) { meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; t->type = W_BTN; t->x = x; t->y = y; t->w = w; t->h = h; + t->owner = meow_ui_activity_active(); /* §6.1 ①:绑定当前 active Activity */ if (label) { strncpy(t->label, label, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; } + live_btn_register(t); return t; } @@ -51,6 +145,7 @@ meow_widget *meow_slider_create(int x, int y, int w, int min, int max, int val) meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; t->type = W_SLIDER; t->x = x; t->y = y; t->w = w; t->h = 40; + t->owner = meow_ui_activity_active(); t->min = min; t->max = (max > min) ? max : min + 1; t->val = (val < min) ? min : (val > t->max) ? t->max : val; return t; } @@ -60,6 +155,7 @@ meow_widget *meow_switch_create(int x, int y, int on) meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; t->type = W_SWITCH; t->x = x; t->y = y; t->w = 56; t->h = 32; + t->owner = meow_ui_activity_active(); t->on = on ? 1 : 0; return t; } @@ -70,11 +166,17 @@ meow_widget *meow_list_create(int x, int y, int w, int h, int count, meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; t->type = W_LIST; t->x = x; t->y = y; t->w = w; t->h = h; + t->owner = meow_ui_activity_active(); t->count = count; t->src = src; t->ctx = ctx; t->sel = 0; return t; } -void meow_widget_free(meow_widget *w) { if (w) free(w); } +void meow_widget_free(meow_widget *w) +{ + if (!w) return; + live_btn_unregister(w); + free(w); +} static int hit(meow_widget *w, int x, int y) { @@ -82,7 +184,13 @@ static int hit(meow_widget *w, int x, int y) return x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h; } -int meow_widget_hit(meow_widget *w, int x, int y) { return hit(w, x, y); } +int meow_widget_hit(meow_widget *w, int x, int y) +{ + if (!w) return 0; + /* §6.1 ①:页面控件只对自身 Activity 负责,不跨页面复用命中。 */ + if (w->owner != meow_ui_activity_active()) return 0; + return hit(w, x, y); +} int meow_btn_pressed(meow_widget *w) { return w && w->type == W_BTN ? w->pressed : 0; } void meow_slider_set(meow_widget *w, int val) @@ -112,22 +220,16 @@ void meow_widget_draw(meow_widget *w) meow_input_touch_y() >= w->y && meow_input_touch_y() < w->y + w->h; int press = w->pressed || live; int hold = live && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS; - if (hold) { - /* 按住:更强态——更深状态层 + 内缩圆角描边环(浅 accent),贴 MD3 强调; - * 动效取舍:状态层按规范即时切换(MD3 ripple 属额外动画,依赖 on_tick 逐帧, - * 非本「加视觉态」范围),不做 1-2 帧插值。 */ - uint32_t hbg = blend_color(th->accent, 0x000000, 24); - meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, hbg); - if (w->w > 6 && w->h > 6) - meow_ui_draw_rect_round(w->x + 1, w->y + 1, w->w - 2, w->h - 2, 9, - blend_color(th->accent, 0xFFFFFF, 45)); - } else if (press) { - /* 按下:轻状态层(accent 向黑调 12%,即变深/偏移),释放恢复;MD3 表面按压。 */ - meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, - blend_color(th->accent, 0x000000, 12)); - } else { - meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, th->accent); - } + /* 纯色状态层(无描边环、无分层):一层即满,深浅二态均由 accent 向黑调出。 + * 常态=accent、按下=accent+黑12%、按住=accent+黑24%。整体一层不「先底色再套圈」。 */ + uint32_t bg; + if (hold) + bg = blend_color(th->accent, 0x000000, 24); + else if (press) + bg = blend_color(th->accent, 0x000000, 12); + else + bg = th->accent; + meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, bg); meow_ui_text_center(w->x + w->w / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2, w->label, th->font_body, 0xFFFFFF); break;