/* * meow_ui_widget.c — 控件层(B1.3):按钮/滑条/开关/列表最小集。 * * 控件只负责绘制与状态;命中分发由应用在 on_tap 里调 meow_widget_hit 完成 * (与 MEOW_APP_SDK.md §5 一致)。绘制读当前主题(meow_theme_get)走 back_buf。 */ #include "meow_ui.h" #include "meow_ui_internal.h" #include #include enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3, W_CUSTOM = 4, W_LAYOUT = 5, W_SPACER = 6 }; /* 布局子项数组上限(固定尺寸子项先做;超出静默拒收)。 */ #define MEOW_LAYOUT_MAX_CHILD 32 /* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层)。 * 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */ #define MEOW_BTN_HOLD_MS 500u /* MD3 状态层混色:c 为基色、o 为状态色、t 为 o 的占比(0-100)。 * 用于把主题 accent 向黑/白调出「按下/按住」状态层(日/夜主题各配色)。 */ static uint32_t blend_color(uint32_t c, uint32_t o, int t) { int rr = (((c >> 16) & 0xFF) * (100 - t) + ((o >> 16) & 0xFF) * t) / 100; int gg = (((c >> 8) & 0xFF) * (100 - t) + ((o >> 8) & 0xFF) * t) / 100; int bb = ((c & 0xFF) * (100 - t) + (o & 0xFF) * t) / 100; return ((uint32_t)rr << 16) | ((uint32_t)gg << 8) | (uint32_t)bb; } struct meow_widget { int type; int x, y, w, h; int owner; /* §6.1 ①:创建时绑定的 Activity;命中只在 owner==active 才生效 */ char label[128]; int pressed; /* 按钮按下视觉态(遗留字面;状态机用 MEOW_WS_PRESSED 位) */ int min, max, val; /* 滑条 */ int on; /* 开关 */ int count, sel; /* 列表 */ meow_list_source src; void *ctx; /* §5.8 batch1:统一状态机(§5.8.3)+ 通用 Options(§5.8.2)+ 自定义回调(§5.8.1)+ 布局(§5.8.5) */ unsigned state; /* MEOW_WS_* 位掩码(默认 MEOW_WS_ENABLED) */ meow_widget_opts opts; /* 通用修饰(浅拷;opts_used==0 时按控件内建默认) */ int opts_used; /* 1=set_opts 显式挂载过 */ meow_widget_render_fn render_cb; /* W_CUSTOM:自绘回调 */ meow_widget_hit_fn hit_cb; /* W_CUSTOM:自定义命中回调 */ int kind; /* W_LAYOUT:MEOW_LAYOUT_ROW/COLUMN/BOX */ int spacing; /* W_LAYOUT:项间间距 */ meow_widget *children[MEOW_LAYOUT_MAX_CHILD]; /* W_LAYOUT:子控件 */ int nchildren; }; /* ---- §5.8 batch1 内部辅助:opts 取值(opts_used==0 时按控件内建默认)---- * 注意:必须先定义 struct meow_widget,才能访问其成员(C 要求类型完整)。 */ static uint32_t widget_bg(const meow_widget *w, const meow_theme *th, uint32_t def) { (void)th; return (w->opts_used && w->opts.bg) ? w->opts.bg : def; } static uint32_t widget_fg(const meow_widget *w, const meow_theme *th, uint32_t def) { (void)th; return (w->opts_used && w->opts.fg) ? w->opts.fg : def; } static int widget_radius(const meow_widget *w, int def) { return (w->opts_used && w->opts.radius >= 0) ? w->opts.radius : def; } static int widget_pady(const meow_widget *w) { return (w->opts_used && w->opts.pad_y > 0) ? w->opts.pad_y : 0; } /* 是否禁用(状态机:MEOW_WS_ENABLED 未置位)。 */ static int widget_disabled(const meow_widget *w) { return !(w->state & MEOW_WS_ENABLED); } /* MD3 状态层叠色:把基色向 onSurface 叠 t% 调出按下/按住/聚焦状态层(§5.8.4)。 */ static uint32_t state_tint(uint32_t base, const meow_theme *th, int t) { return blend_color(base, th->onSurface, t); } /* ---- §5.8.2/§5.8.3 公共 API:Options + 状态机 ---- */ int meow_widget_set_opts(meow_widget *w, const meow_widget_opts *opts) { if (!w || !opts) return -1; w->opts = *opts; w->opts_used = 1; /* disabled/selected 立即反映到状态机位(§5.8.3)。 */ if (opts->disabled) w->state &= ~MEOW_WS_ENABLED; else w->state |= MEOW_WS_ENABLED; if (opts->selected) w->state |= MEOW_WS_SELECTED; else w->state &= ~MEOW_WS_SELECTED; meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); /* 挂载会改变绘制:标区域脏 */ return 0; } int meow_widget_opts_get(const meow_widget *w, meow_widget_opts *out) { if (!w || !out) return -1; if (!w->opts_used) { /* 默认视图:radius=-1(控件默认)、disabled/selected 取状态位。 */ memset(out, 0, sizeof *out); out->radius = -1; out->disabled = widget_disabled(w) ? 1 : 0; out->selected = (w->state & MEOW_WS_SELECTED) ? 1 : 0; return 0; } *out = w->opts; return 0; } void meow_widget_set_state(meow_widget *w, unsigned bits, int on) { if (!w) return; if (on) w->state |= bits; else w->state &= ~bits; meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); /* 状态变化会改变绘制 */ } unsigned meow_widget_state(const meow_widget *w) { return w ? w->state : MEOW_WS_ENABLED; } int meow_widget_enabled(const meow_widget *w) { return w ? (w->state & MEOW_WS_ENABLED) != 0 : 0; } void meow_widget_set_enabled(meow_widget *w, int on) { meow_widget_set_state(w, MEOW_WS_ENABLED, on ? 1 : 0); } int meow_widget_selected(const meow_widget *w) { return w ? (w->state & MEOW_WS_SELECTED) != 0 : 0; } void meow_widget_set_selected(meow_widget *w, int on) { meow_widget_set_state(w, MEOW_WS_SELECTED, on ? 1 : 0); } /* 一键叠状态层(自定义画布可选;标准控件由 SDK 自动叠,不调用):半透明状态层 + 焦点描边。 */ void meow_widget_draw_state_layer(meow_widget *w, const meow_theme *th) { if (!w || !th) return; if (widget_disabled(w)) { meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); return; /* 禁用不再叠按下/聚焦 */ } if (w->state & MEOW_WS_PRESSED) { /* 向 onSurface 叠半透明状态层铺满控件矩形(按下/按住) */ int t = (w->opts_used && w->opts.bg) ? 24 : 12; meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, t * 255 / 100); } if (w->state & MEOW_WS_FOCUSED) { int r = widget_radius(w, 10); meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary); } } /* ---- 实时按压即时刷新(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->state &= ~MEOW_WS_PRESSED; /* §5.8.3:清除按压态(跨页面不残留) */ 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) { g_live_btn->state &= ~MEOW_WS_PRESSED; 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) { g_live_btn->state &= ~MEOW_WS_PRESSED; live_btn_redraw(g_live_btn); } g_live_btn = hit; g_live_prev_hold = hold; if (hit) { if (widget_disabled(hit)) { g_live_btn = NULL; } /* 禁用不响应按压 */ else { hit->state |= MEOW_WS_PRESSED; live_btn_redraw(hit); } } } else if (hit && hold != g_live_prev_hold) { /* 同一按钮按住达阈值:升级到更强态 */ hit->state |= MEOW_WS_PRESSED; 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 */ t->state = MEOW_WS_ENABLED; /* §5.8.3:默认 ENABLED */ if (label) { strncpy(t->label, label, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; } live_btn_register(t); return t; } 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->state = MEOW_WS_ENABLED; t->min = min; t->max = (max > min) ? max : min + 1; t->val = (val < min) ? min : (val > t->max) ? t->max : val; return t; } 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->state = MEOW_WS_ENABLED; t->on = on ? 1 : 0; if (t->on) t->state |= MEOW_WS_SELECTED; /* §5.8.3:开关 on → selected */ return t; } meow_widget *meow_list_create(int x, int y, int w, int h, int count, meow_list_source src, void *ctx) { 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->state = MEOW_WS_ENABLED; t->count = count; t->src = src; t->ctx = ctx; t->sel = 0; return t; } void meow_widget_free(meow_widget *w) { if (!w) return; live_btn_unregister(w); free(w); } /* ---- §5.8.1 自定义画布:App 提供 render/hit 回调,同一个 meow_widget 基座 ---- */ meow_widget *meow_widget_custom_create(int x, int y, int w, int h, meow_widget_render_fn render_cb, meow_widget_hit_fn hit_cb, void *ctx) { meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; t->type = W_CUSTOM; t->x = x; t->y = y; t->w = w; t->h = h; t->owner = meow_ui_activity_active(); /* §6.1 ①:同既有控件绑当前 active Activity */ t->state = MEOW_WS_ENABLED; t->render_cb = render_cb; t->hit_cb = hit_cb; t->ctx = ctx; return t; } /* ---- §5.8.5 可组合布局:Row / Column / Box + Spacer ---- */ meow_widget *meow_row_create(int x, int y, int w, int h, int spacing) { meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; t->type = W_LAYOUT; t->kind = MEOW_LAYOUT_ROW; t->x = x; t->y = y; t->w = w; t->h = h; t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->spacing = spacing > 0 ? spacing : 0; return t; } meow_widget *meow_column_create(int x, int y, int w, int h, int spacing) { meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; t->type = W_LAYOUT; t->kind = MEOW_LAYOUT_COLUMN; t->x = x; t->y = y; t->w = w; t->h = h; t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->spacing = spacing > 0 ? spacing : 0; return t; } meow_widget *meow_box_create(int x, int y, int w, int h) { meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; t->type = W_LAYOUT; t->kind = MEOW_LAYOUT_BOX; t->x = x; t->y = y; t->w = w; t->h = h; t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->spacing = 0; return t; } meow_widget *meow_layout_spacer(int w, int h) { meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; t->type = W_SPACER; t->x = 0; t->y = 0; t->w = w > 0 ? w : 0; t->h = h > 0 ? h : 0; t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; return t; } int meow_layout_add(meow_widget *layout, meow_widget *child) { if (!layout || layout->type != W_LAYOUT || !child) return -1; if (layout->nchildren >= MEOW_LAYOUT_MAX_CHILD) return -1; for (int i = 0; i < layout->nchildren; i++) if (layout->children[i] == child) return -1; /* 同子项去重 */ layout->children[layout->nchildren++] = child; return 0; } /* 把子控件算成绝对坐标(写进各 child 的 x,y;w/h 固定尺寸,先不做弹性)。 * Row:子项水平排(垂直居中于容器 h);Column:垂直排(水平居中);Box:叠放(各放容器左上角)。 * 子项的 margin_x/margin_y(Options)参与排布;子项为布局时递归 arrange。 */ void meow_layout_arrange(meow_widget *layout) { if (!layout || layout->type != W_LAYOUT) return; int kind = layout->kind; int cur = (kind == MEOW_LAYOUT_COLUMN) ? layout->y : layout->x; for (int i = 0; i < layout->nchildren; i++) { meow_widget *c = layout->children[i]; if (!c) continue; int mx = (c->opts_used && c->opts.margin_x > 0) ? c->opts.margin_x : 0; int my = (c->opts_used && c->opts.margin_y > 0) ? c->opts.margin_y : 0; if (kind == MEOW_LAYOUT_ROW) { cur += mx; c->x = cur; c->y = layout->y + (layout->h - c->h) / 2; /* 垂直居中 */ if (c->y < layout->y) c->y = layout->y; cur += c->w + mx + layout->spacing; } else if (kind == MEOW_LAYOUT_COLUMN) { cur += my; c->y = cur; c->x = layout->x + (layout->w - c->w) / 2; /* 水平居中 */ if (c->x < layout->x) c->x = layout->x; cur += c->h + my + layout->spacing; } else { /* BOX:叠放,各放容器左上角 */ c->x = layout->x; c->y = layout->y; } if (c->type == W_LAYOUT) meow_layout_arrange(c); /* 嵌套递归 */ } } static int hit(meow_widget *w, int x, int y) { if (!w) return 0; 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) { if (!w) return 0; /* §6.1 ①:页面控件只对自身 Activity 负责,不跨页面复用命中。 */ if (w->owner != meow_ui_activity_active()) return 0; /* §5.8.3:禁用不响应命中(也不进 pressed)。 */ if (widget_disabled(w)) return 0; /* §5.8.1:自定义画布用 App 回调判定命中(可非矩形);其余矩形内含。 */ if (w->type == W_CUSTOM) return w->hit_cb ? w->hit_cb(w, x, y, w->ctx) : hit(w, x, y); return hit(w, x, y); } int meow_btn_pressed(meow_widget *w) { return w && w->type == W_BTN ? (w->pressed || (w->state & MEOW_WS_PRESSED)) : 0; } 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; w->val = val; } int meow_slider_get(meow_widget *w) { return w && w->type == W_SLIDER ? w->val : 0; } void meow_switch_set(meow_widget *w, int on) { if (w && w->type == W_SWITCH) { w->on = on ? 1 : 0; if (w->on) w->state |= MEOW_WS_SELECTED; /* §5.8.3:on → selected */ else w->state &= ~MEOW_WS_SELECTED; meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); } } int meow_switch_get(meow_widget *w) { return w && w->type == W_SWITCH ? w->on : 0; } /* 供应用在 on_tap 里对外可控回馈:这里不存命中态(由 app 分发),仅暴露绘制。 * §5.8 升级:统一取语义色(meow_theme),读 opts(背景/圆角/描边/pad/fg)与状态机位 * (disabled/selected/pressed/focused),标准控件由 SDK 自动叠状态层;自定义画布(W_CUSTOM) * 由 App render_cb 全权自绘(SDK 只注入主题、不画背景/状态层);布局(W_LAYOUT)递归绘制子控件。 */ void meow_widget_draw(meow_widget *w) { if (!w) return; const meow_theme *th = meow_theme_get(); int disabled = widget_disabled(w); switch (w->type) { case W_BTN: { /* 按下/按住 = 视觉态(不改 on_tap/btn_pressed/hit 语义)。按下来源三路: * - 状态机 MEOW_WS_PRESSED(App 经 set_state 或 SDK 触摸层置位); * - w->pressed(既有字面,保留); * - 实时触摸命中:SDK 输入层当前正按在该控件内(无需 app 追踪)。 * 按住=实时触摸命中且持续 ≥MEOW_BTN_HOLD_MS;按下=任一来源。禁用时不叠按下底。 */ int live = meow_input_touch_down() && meow_input_touch_x() >= w->x && meow_input_touch_x() < w->x + w->w && meow_input_touch_y() >= w->y && meow_input_touch_y() < w->y + w->h; int press = (w->state & MEOW_WS_PRESSED) || w->pressed || live; int hold = live && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS; if (disabled) press = 0; /* 底色:选中态用 primaryContainer(onPrimaryContainer 文字);否则 primary(onPrimary 文字)。 * 可被 opts.bg 覆盖(此时文字用 opts.fg 或 onPrimary)。 */ int selected = (w->state & MEOW_WS_SELECTED); uint32_t base, default_fg; if (selected && !(w->opts_used && w->opts.bg)) { base = th->primaryContainer; default_fg = th->onPrimaryContainer; } else { base = widget_bg(w, th, th->primary); default_fg = th->onPrimary; } uint32_t bg; if (hold) bg = state_tint(base, th, 24); /* 按住 = 深状态层 */ else if (press) bg = state_tint(base, th, 12); /* 按下 = 状态层 */ else bg = base; int r = widget_radius(w, 10); meow_gfx_fill_round(w->x, w->y, w->w, w->h, r, bg); /* 内容区受 pad 垂直内缩(水平居中文本,pad_x 不影响中心) */ int pady = widget_pady(w); int cx = w->x + w->w / 2; int cy = w->y + pady + (w->h - 2 * pady - meow_ui_text_height(th->font_body)) / 2; uint32_t tc = widget_fg(w, th, default_fg); meow_ui_text_center(cx, cy, w->label, th->font_body, tc); /* opts 描边 / 焦点外圈 */ if (w->opts_used && w->opts.stroke && w->opts.stroke_w > 0) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, w->opts.stroke); if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary); /* 禁用:整体变暗(状态层向 onSurface 叠 α) */ if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); break; } case W_SLIDER: { /* 语义色:轨道=onSurfaceVariant(dim)、填充=primary(accent)、滑块=fg(onSurface)。 */ int trace_y = w->y + w->h / 2 - 3; meow_ui_fill_rect(w->x, trace_y, w->w, 6, th->onSurfaceVariant); int range = w->max - w->min; int frac = range ? (w->val - w->min) * w->w / range : 0; if (frac > 0) meow_ui_fill_rect(w->x, trace_y, frac, 6, th->primary); int tx = w->x + frac; int ty = w->y + w->h / 2; meow_gfx_fill_round(tx - 10, ty - 10, 20, 20, 10, th->onSurface); if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); break; } case W_SWITCH: { uint32_t bg = w->on ? th->primary : th->onSurfaceVariant; meow_gfx_fill_round(w->x, w->y, w->w, w->h, w->h / 2, bg); int knob_r = w->h / 2 - 3; int kx = w->on ? w->x + w->w - knob_r * 2 - 3 : w->x + 3; meow_gfx_fill_round(kx, w->y + 3, knob_r * 2, knob_r * 2, knob_r, 0xFFFFFF); if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); break; } case W_LIST: { int rowh = 44, pad = 16; for (int i = 0; i < w->count; i++) { int ry = w->y + i * rowh; if (ry >= w->y + w->h) break; if (i == w->sel) meow_ui_fill_rect(w->x, ry, w->w, rowh, th->primary); char label[64] = ""; if (w->src && w->src(i, label, (int)sizeof label, w->ctx)) { uint32_t col = (i == w->sel) ? 0xFFFFFF : th->onSurface; meow_ui_text(w->x + pad, ry + (rowh - meow_ui_text_height(th->font_body)) / 2, label, th->font_body, col); } meow_ui_fill_rect(w->x, ry + rowh - 1, w->w, 1, th->onSurfaceVariant); } if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); break; } case W_CUSTOM: /* SDK 不替自定义画布画背景/状态层;App 在 render_cb 里自绘(th 供取色), * 可读 meow_widget_state(状态位)/ meow_widget_opts_get(通用修饰)自行叠。 */ if (w->render_cb) w->render_cb(w, th, w->ctx); break; case W_LAYOUT: /* 递归绘制子控件(子控件坐标已由 meow_layout_arrange 算成绝对坐标)。 */ for (int i = 0; i < w->nchildren; i++) if (w->children[i]) meow_widget_draw(w->children[i]); break; case W_SPACER: break; /* 不可见空白 */ default: break; } }