Files
MeowOS-For-R3P2/patch_v2/sdk/meow_ui_widget.c
T
MeowOS 2cb6a7c16f feat(sdk): 控件库第一批通用基座(agent-150) + launcher/hello 重链
- meow_widget 基座收敛(尾部追加 Options+状态机位+自定义回调+布局子项), W_CUSTOM/W_LAYOUT/W_SPACER
- meow_theme 尾部追加 MD3 语义色+风格参数(旧字段 bg/fg/accent/dim 映射兼容), g_theme[2][2](style×mode×明暗), display.background 背景源深浅成对, 图片路径回退
- meow_widget_opts(Modifier式, 不改create签名)/MEOW_WS_*状态机(enabled阻断hit,pressed)/meow_widget_custom_create(render/hit_cb)/meow_row/column/box+arrange(算绝对坐标兼容)
- meow_ui_set_style/style/meow_theme_get 完整主题(MD3默认/MIUIx占位)
- meow_ui.a=29c5078d hello=24116e6b(向后兼容外观零变化) launcher重链=626546f9;build_sdk 0warning; 宿主 test_sdk_controls T1-T10 PASS + test_sb_statusbar ALL PASS
- 未做(第二批/后续): Material子集/实时按压刷新扩展/物理键焦点移动运行时(留草案)
2026-08-30 21:57:46 +08:00

538 lines
24 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 <stdlib.h>
#include <string.h>
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;
}
}