Files
MeowOS-SDK/meow_ui_widget.c
T
AskaEth 2d974bed62 sdk(ui): W_LISTVIEW 虚拟化层 — 固定 item 池 + 多行富 item + 完全自绘 (§5.5)
在既有 W_LISTVIEW (src2 每帧取行) 之上叠加虚拟化层 (API 追加式, 旧 API 一字不改):
- 数据源 count_fn + bind_fn (index, meow_lv_item*, ctx); count 每帧实时查 (任意长度≥65535,
  不常驻 item 对象); bind 仅在行进入视口时被调一次
- 固定 item 池: 视口行数+2 静态槽 (有界 ≤96), 回滚不重绑 (同 index 命中同槽); 整表扫过后
  wrap 回收合法但每次 draw 有界 (≤视口窗口)
- 多行富 item: 主标题/副标题/左图标·缩略图/右尾件; 行高 set_item_h(px) 或 set_item_tier(1→48/2→56);
  默认 renderer 主题语义色绘制; set_item_draw 完全自绘; section 段头占位行(非粘性); 空态占位
- 集成: tap_at→meow_ui_focus_acquire (§6.4); meow_scroll_wrap(lv,1) track+fling+snap(行边界);
  选中/按压态走主题; enabled=0 变淡不响应
- 兼容: src2==NULL 走既有路径; W_LIST/W_LISTVIEW 单行用法零改动

验证(宿主): 新测试 test_meow_ui_listvirt T1-T5 全 44 PASS (10000 项滚动窗口内容正确/hit 映射/
bind 有界=count/回滚不重绑/多行渲染/截断/图标占位/段头/空态/自绘/焦点/fling 吸附);
全量回归 widgets/dispatch/box/anim/scroll/ime/pinyin/overlay/notif/sdk_input_keys/p1p2p3 全 PASS;
交叉 zig-mipsel -Wall -Wextra 全 SDK TU 0 warning; meow_ui.a 新 md5=2a2d8000b95dd211d09dc736d5538ad0

文档: MEOW_APP_SDK.md §5.5 同步虚拟化层(API 一览/池语义/集成/回归).
2026-09-02 01:36:48 +08:00

1899 lines
87 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>
#include <stdio.h>
enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3,
W_CUSTOM = 4, W_LAYOUT = 5, W_SPACER = 6,
W_TEXTFIELD = 7, W_CARD = 8, W_DIVIDER = 9, W_BADGE = 10,
W_CHECKBOX = 11, W_RADIO = 12, W_PROGRESS = 13,
W_TOPAPPBAR = 14, W_NAVBAR = 15, W_TABBAR = 16, W_FAB = 17,
W_LISTVIEW = 18 }; /* §5.5 结构化列表(行=图标+标题+副标题+右侧操作+选中) */
/* 布局子项数组上限(固定尺寸子项先做;超出静默拒收)。 */
#define MEOW_LAYOUT_MAX_CHILD 32
/* 容器(TopAppBar 动作 / NavigationBar 项 / TabBar 页签)子项上限。 */
#define MEOW_CONTAINER_MAX 8
/* 按钮「按住」判定阈值:视觉上区分「短按按下」与「按住」(更深状态层)。
* 阈值取 meow_ui_long_press_ms()(§6.3 通用长按默认 500ms,App 可配)——与长按事件同源,
* 避免「长按已触发但视觉未加深」的不一致。触发语义不变(on_tap 仍按抬手判定)。 */
/* 圆形 ProgressBar 环带厚 / 外扩边距(bgg 修复 M2.71-bug1:控件几何必须覆盖真实像素)。
* 实际像素范围 = 中心 ± (r + thk/2);margin = thk/2 + 1 保证外接框覆盖环带外缘与圆帽。 */
#define MEOW_PROGRESS_ARC_THK 4
#define MEOW_PROGRESS_ARC_MARGIN (MEOW_PROGRESS_ARC_THK / 2 + 1)
/* 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.6 batch2 Material 子集(尾缀追加;calloc 归零,安全) */
int variant; /* W_BTN:MEOW_BTN_* 变体; W_PROGRESS:0=线性/1=圆形 */
char placeholder[64]; /* W_TEXTFIELD 提示文案 */
char *buf; int buflen; /* W_TEXTFIELD 文本缓冲(App 持有)+ 当前长 */
int maxlen; /* W_TEXTFIELD 上限(含 NUL) */
meow_ime_mode ime_mode; /* W_TEXTFIELD IME 模式 */
int value; /* W_PROGRESS:0-100 / -1=不定; W_BADGE:计数 */
int item_h; /* W_LIST:行高/列宽(lazy),默认 44 */
int scroll; /* W_LIST:滚动偏移(lazy) */
int horizontal; /* W_LIST:1=横向(LazyRow) */
int back_on; /* W_TOPAPPBAR:显示返回箭头 */
char items[MEOW_CONTAINER_MAX][24]; /* W_TOPAPPBAR 动作 / W_NAVBAR 项 / W_TABBAR 页签 */
int nitems; /* 上述项数 */
int selected; /* W_NAVBAR / W_TABBAR 选中下标 */
/* M2.71-bug8:W_LIST 行点击回调(可选)。 */
meow_list_tap_fn tap_cb; /* 命中行回调(可 NULL) */
void *tap_ctx; /* 回调 ctx */
/* §5.5 结构化列表(W_LISTVIEW):行=图标+标题+副标题+右侧操作+选中(calloc 归零,安全) */
meow_list2_source src2; /* 行数据源(meow_list_item) */
int sel_mode; /* MEOW_LIST_SEL_* */
uint32_t *sel_bits; /* 多选位图(sel_bits_n 个字;SINGLE 不分配) */
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 时按控件内建默认)----
* 注意:必须先定义 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;
}
/* 重绘并局部刷新一个按钮区。
* §6.2 收敛:若按钮已注册绘制物 → 走 meow_ui_invalidate(清底 + 按 z 重画重叠区完整 z 栈),
* 避免按钮压上层 chrome 时被单控件 flush 抹掉(P1);未注册则回退单控件绘制+区域上屏。 */
static void live_btn_redraw(meow_widget *w)
{
if (!w) return;
if (meow_draw_widget_registered(w)) {
meow_ui_invalidate(w->x, w->y, w->w, w->h);
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() >= (unsigned)meow_ui_long_press_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);
meow_draw_widget_register(t); /* §6.2 自动注册(创建即注册,App 零调用) */
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;
meow_draw_widget_register(t);
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 */
meow_draw_widget_register(t);
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;
meow_draw_widget_register(t);
return t;
}
void meow_widget_free(meow_widget *w)
{
if (!w) return;
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);
}
/* ---- §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;
meow_draw_widget_register(t);
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;
meow_draw_widget_register(t);
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;
meow_draw_widget_register(t);
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;
meow_draw_widget_register(t);
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;
meow_draw_widget_register(t);
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); /* 嵌套递归 */
}
}
/* ---- 布局深度反推访问器(meow_ui_draw.c 用,读本 TU 的 struct meow_widget 内部字段)---- */
int meow_widget_is_layout(const meow_widget *w)
{ return w && w->type == W_LAYOUT; }
int meow_widget_nchild(const meow_widget *w)
{ return (w && w->type == W_LAYOUT) ? w->nchildren : 0; }
const meow_widget *meow_widget_child_at(const meow_widget *w, int idx)
{ return (w && w->type == W_LAYOUT && idx >= 0 && idx < w->nchildren) ? w->children[idx] : NULL; }
/* ---- 几何/归属访问器(meow_ui_draw.c 用:绘制物 rect 实时读 + owner 过滤)---- */
int meow_widget_owner(const meow_widget *w)
{ return w ? w->owner : 0; }
void meow_widget_rect(const meow_widget *w, int out[4])
{
if (!out) return;
if (!w) { out[0] = out[1] = out[2] = out[3] = 0; return; }
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) */
/* ============================================================ */
/* ---- Button 五型(既有 meow_btn_create = FILLED) ---- */
meow_widget *meow_btn_create_variant(int x, int y, int w, int h, const char *label, int variant)
{
meow_widget *t = meow_btn_create(x, y, w, h, label);
if (!t) return NULL;
if (variant < MEOW_BTN_FILLED || variant > MEOW_BTN_ELEVATED) variant = MEOW_BTN_FILLED;
t->variant = variant;
return t;
}
int meow_btn_set_variant(meow_widget *w, int variant)
{
if (!w || w->type != W_BTN) return -1;
if (variant < MEOW_BTN_FILLED || variant > MEOW_BTN_ELEVATED) return -1;
if (variant == w->variant) return 0;
w->variant = variant;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
return 0;
}
/* ---- TextField ---- */
meow_widget *meow_textfield_create(int x, int y, int w, int h, const char *placeholder,
char *buf, int maxlen, meow_ime_mode mode)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_TEXTFIELD; t->x = x; t->y = y; t->w = w; t->h = h;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
if (placeholder) {
strncpy(t->placeholder, placeholder, sizeof t->placeholder - 1);
t->placeholder[sizeof t->placeholder - 1] = 0;
}
t->buf = buf; t->buflen = buf ? (int)strlen(buf) : 0;
t->maxlen = (maxlen > 0 && maxlen <= 256) ? maxlen : 256;
t->ime_mode = mode;
meow_draw_widget_register(t);
return t;
}
void meow_textfield_set_placeholder(meow_widget *w, const char *placeholder)
{
if (!w || w->type != W_TEXTFIELD) return;
if (placeholder) {
strncpy(w->placeholder, placeholder, sizeof w->placeholder - 1);
w->placeholder[sizeof w->placeholder - 1] = 0;
}
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
}
const char *meow_textfield_text(meow_widget *w) { return (w && w->type == W_TEXTFIELD && w->buf) ? w->buf : ""; }
/* 输入中实时把 IME 文本同步到字段缓冲 + 重画字段区(invalidate 重画 z 栈相交绘制物;
* flush 在 IME 阻塞循环内被挂起(meow_gfx_flush_hold),随本周期末一次原子上屏) */
static void textfield_change_cb(const char *text, void *ctx)
{
meow_widget *w = (meow_widget *)ctx;
if (!w || w->type != W_TEXTFIELD) return;
if (w->buf && text) {
strncpy(w->buf, text, w->maxlen - 1);
w->buf[w->maxlen - 1] = 0;
}
w->buflen = text ? (int)strlen(text) : 0;
/* 只标脏不重画的话 back_buf 仍是旧文本,flush 上去的还是旧的——必须真重画 */
meow_ui_invalidate(w->x, w->y, w->w, w->h);
}
int meow_textfield_activate(meow_widget *w)
{
if (!w || w->type != W_TEXTFIELD) return 0;
w->state |= MEOW_WS_FOCUSED;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
meow_ime_ctx ctx;
memset(&ctx, 0, sizeof ctx);
ctx.mode = w->ime_mode;
ctx.maxlen = w->maxlen;
ctx.on_change = textfield_change_cb;
ctx.ctx = w;
if (w->buf) strncpy(ctx.buf, w->buf, sizeof ctx.buf - 1); /* 预填当前文本 */
int r = meow_ui_text_input(&ctx);
if (r && w->buf) {
strncpy(w->buf, ctx.buf, w->maxlen - 1);
w->buf[w->maxlen - 1] = 0;
}
w->buflen = w->buf ? (int)strlen(w->buf) : 0;
w->state &= ~MEOW_WS_FOCUSED;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
return r;
}
/* ---- Card ---- */
meow_widget *meow_card_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_CARD; t->x = x; t->y = y; t->w = w; t->h = h;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
meow_draw_widget_register(t);
return t;
}
void meow_card_set_content(meow_widget *card, meow_widget *child)
{
if (!card || card->type != W_CARD || !child) return;
card->children[0] = child;
card->nchildren = 1;
}
/* ---- Divider ---- */
meow_widget *meow_divider_create(int x, int y, int len, int vertical)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_DIVIDER; t->x = x; t->y = y;
t->w = vertical ? 1 : (len > 0 ? len : 1);
t->h = vertical ? (len > 0 ? len : 1) : 1;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->variant = vertical ? 1 : 0;
meow_draw_widget_register(t);
return t;
}
/* ---- Badge ---- */
meow_widget *meow_badge_create(int cx, int cy)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_BADGE; t->x = cx; t->y = cy; t->w = 24; t->h = 24;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->value = 0; /* 红点 */
meow_draw_widget_register(t);
return t;
}
void meow_badge_set(meow_widget *w, int count)
{
if (!w || w->type != W_BADGE) return;
w->value = count;
meow_gfx_mark_dirty(w->x - 16, w->y - 16, 32, 32);
}
/* ---- Checkbox / Radio ---- */
meow_widget *meow_checkbox_create(int x, int y)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_CHECKBOX; t->x = x; t->y = y; t->w = 34; t->h = 34;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
meow_draw_widget_register(t);
return t;
}
meow_widget *meow_radio_create(int x, int y)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_RADIO; t->x = x; t->y = y; t->w = 34; t->h = 34;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
meow_draw_widget_register(t);
return t;
}
/* ---- ProgressBar ---- */
meow_widget *meow_progress_create(int x, int y, int w, int h, int value)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_PROGRESS; t->x = x; t->y = y; t->w = w; t->h = h > 0 ? h : 4;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->variant = 0; /* 线性 */
t->value = value < 0 ? -1 : (value > 100 ? 100 : value);
meow_draw_widget_register(t);
return t;
}
meow_widget *meow_progress_circular_create(int cx, int cy, int r, int value)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
/* bbox = 整圆像素外接框(中心 ± (r + margin)):使 w->x/y/w/h 即真实绘制范围,
* 于是 meow_progress_set 的 mark_dirty 与 App 的 flush_region(w->x,w->y,w->w,w->h)
* 都天然对齐,不漏刷/不多刷(修复 bgg M2.71 圆形进度脏区)。 */
int m = MEOW_PROGRESS_ARC_MARGIN;
t->type = W_PROGRESS;
t->x = cx - r - m; t->y = cy - r - m;
t->w = 2 * (r + m); t->h = 2 * (r + m);
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->variant = 1; /* 圆形 */
t->value = value < 0 ? -1 : (value > 100 ? 100 : value);
meow_draw_widget_register(t);
return t;
}
void meow_progress_set(meow_widget *w, int value)
{
if (!w || w->type != W_PROGRESS) return;
w->value = value < 0 ? -1 : (value > 100 ? 100 : value);
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
}
/* ---- FAB ---- */
meow_widget *meow_fab_create(int x, int y, int size, const char *label)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_FAB; t->x = x; t->y = y; t->w = size > 0 ? size : 48; t->h = t->w;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
if (label) { strncpy(t->label, label, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; }
meow_draw_widget_register(t);
return t;
}
/* ---- TopAppBar ---- */
meow_widget *meow_topappbar_create(int x, int y, int w, int h, const char *title)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_TOPAPPBAR; t->x = x; t->y = y; t->w = w; t->h = h;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->back_on = 0; t->nitems = 0;
if (title) { strncpy(t->label, title, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; }
meow_draw_widget_register(t);
return t;
}
void meow_topappbar_set_title(meow_widget *w, const char *title)
{
if (!w || w->type != W_TOPAPPBAR) return;
if (title) { strncpy(w->label, title, sizeof w->label - 1); w->label[sizeof w->label - 1] = 0; }
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
}
void meow_topappbar_set_back(meow_widget *w, int on)
{
if (!w || w->type != W_TOPAPPBAR) return;
w->back_on = on ? 1 : 0;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
}
int meow_topappbar_add_action(meow_widget *w, const char *label)
{
if (!w || w->type != W_TOPAPPBAR || w->nitems >= MEOW_CONTAINER_MAX) return -1;
if (label) strncpy(w->items[w->nitems], label, 23);
w->items[w->nitems][23] = 0;
int idx = w->nitems;
w->nitems++;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
return idx;
}
int meow_topappbar_hit(meow_widget *w, int x, int y)
{
if (!w || w->type != W_TOPAPPBAR) return -1;
if (x < w->x || x >= w->x + w->w || y < w->y || y >= w->y + w->h) return -1;
if (w->back_on && x >= w->x && x < w->x + 44) return MEOW_TOPBAR_BACK;
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;
ax -= aw;
if (x >= ax && x < ax + aw && x >= w->x + 100) return i; /* 不压标题区/返回区 */
}
return -1;
}
/* ---- NavigationBar ---- */
meow_widget *meow_navigationbar_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_NAVBAR; t->x = x; t->y = y; t->w = w; t->h = h;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->nitems = 0; t->selected = 0;
meow_draw_widget_register(t);
return t;
}
int meow_navigationbar_add_item(meow_widget *w, const char *label)
{
if (!w || w->type != W_NAVBAR || w->nitems >= MEOW_CONTAINER_MAX) return -1;
if (label) strncpy(w->items[w->nitems], label, 23);
w->items[w->nitems][23] = 0;
int idx = w->nitems;
w->nitems++;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
return idx;
}
void meow_navigationbar_set_selected(meow_widget *w, int idx)
{
if (!w || w->type != W_NAVBAR) return;
if (idx < 0) idx = 0;
if (idx >= w->nitems) idx = w->nitems - 1;
w->selected = idx;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
}
int meow_navigationbar_selected(meow_widget *w) { return (w && w->type == W_NAVBAR) ? w->selected : 0; }
int meow_navigationbar_hit(meow_widget *w, int x, int y)
{
if (!w || w->type != W_NAVBAR || w->nitems <= 0) return -1;
if (x < w->x || x >= w->x + w->w || y < w->y || y >= w->y + w->h) return -1;
int iw = w->w / w->nitems;
int idx = iw > 0 ? (x - w->x) / iw : 0;
return (idx >= 0 && idx < w->nitems) ? idx : -1;
}
/* ---- TabBar ---- */
meow_widget *meow_tabbar_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_TABBAR; t->x = x; t->y = y; t->w = w; t->h = h;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->nitems = 0; t->selected = 0;
meow_draw_widget_register(t);
return t;
}
int meow_tabbar_add_tab(meow_widget *w, const char *label)
{
if (!w || w->type != W_TABBAR || w->nitems >= MEOW_CONTAINER_MAX) return -1;
if (label) strncpy(w->items[w->nitems], label, 23);
w->items[w->nitems][23] = 0;
int idx = w->nitems;
w->nitems++;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
return idx;
}
void meow_tabbar_set_selected(meow_widget *w, int idx)
{
if (!w || w->type != W_TABBAR) return;
if (idx < 0) idx = 0;
if (idx >= w->nitems) idx = w->nitems - 1;
w->selected = idx;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
}
int meow_tabbar_hit(meow_widget *w, int x, int y)
{
if (!w || w->type != W_TABBAR || w->nitems <= 0) return -1;
if (x < w->x || x >= w->x + w->w || y < w->y || y >= w->y + w->h) return -1;
int iw = w->w / w->nitems;
int idx = iw > 0 ? (x - w->x) / iw : 0;
return (idx >= 0 && idx < w->nitems) ? idx : -1;
}
/* ---- 列表容器(LazyColumn/LazyRow:虚拟化 + 滚动;复用 W_LIST 与 meow_list_source)---- */
meow_widget *meow_lazycolumn_create(int x, int y, int w, int h, int item_h, int count,
meow_list_source src, void *ctx)
{
meow_widget *t = meow_list_create(x, y, w, h, count, src, ctx);
if (!t) return NULL;
t->item_h = item_h > 0 ? item_h : 44;
t->scroll = 0; t->horizontal = 0;
return t;
}
meow_widget *meow_lazyrow_create(int x, int y, int w, int h, int item_w, int count,
meow_list_source src, void *ctx)
{
meow_widget *t = meow_list_create(x, y, w, h, count, src, ctx);
if (!t) return NULL;
t->item_h = item_w > 0 ? item_w : 60;
t->scroll = 0; t->horizontal = 1;
return t;
}
static int list_item(const meow_widget *w) { return (w->item_h > 0) ? w->item_h : 44; }
int meow_list_max_scroll(meow_widget *w)
{
if (!w || w->type != W_LIST) return 0;
int vp = w->horizontal ? w->w : w->h;
int ms = w->count * list_item(w) - vp;
return ms < 0 ? 0 : ms;
}
void meow_list_set_scroll(meow_widget *w, int offset)
{
if (!w || w->type != W_LIST) return;
int maxs = meow_list_max_scroll(w);
if (offset < 0) offset = 0;
if (offset > maxs) offset = maxs;
if (offset == w->scroll) return;
w->scroll = offset;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
}
int meow_list_scroll(meow_widget *w) { return (w && w->type == W_LIST) ? w->scroll : 0; }
void meow_list_scroll_by(meow_widget *w, int dy) { if (w) meow_list_set_scroll(w, w->scroll + dy); }
int meow_list_set_item_h(meow_widget *w, int item)
{
if (!w || w->type != W_LIST) return -1;
if (item > 0) { w->item_h = item; meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); }
return w->item_h;
}
int meow_list_hit_index(meow_widget *w, int x, int y)
{
if (!w || w->type != W_LIST) return -1;
if (x < w->x || x >= w->x + w->w || y < w->y || y >= w->y + w->h) return -1;
int item = list_item(w);
int idx = w->horizontal ? (x - w->x + w->scroll) / item
: (y - w->y + w->scroll) / item;
return (idx >= 0 && idx < w->count) ? idx : -1;
}
/* M2.71-bug8:选中行(越界 clamp)+ 标脏(高亮刷新)。 */
void meow_list_set_selected(meow_widget *w, int idx)
{
if (!w || w->type != W_LIST) return;
if (idx < 0) idx = 0;
if (w->count > 0 && idx >= w->count) idx = w->count - 1;
if (idx == w->sel) return;
w->sel = idx;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
}
int meow_list_selected(meow_widget *w) { return (w && w->type == W_LIST) ? w->sel : -1; }
void meow_list_set_on_tap(meow_widget *w, meow_list_tap_fn cb, void *ctx)
{
if (!w || w->type != W_LIST) return;
w->tap_cb = cb; w->tap_ctx = ctx;
}
/* 命中行:置选中 + 回调(若设)+ 返回 idx;未命中返回 -1(不置选中)。 */
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;
}
/* ============================================================ */
/* §5.5 结构化列表(W_LISTVIEW):行=图标+标题+副标题+右侧操作+选中
* ------------------------------------------------------------
* 行数据源(meow_list2_source)逐行填充 meow_list_item;容器只画可见行(虚拟化)+ 视口裁剪
* (M2.73 纪律:只画视口内行/分隔线/文本,无标题栏/底部泄漏);局部 dirty;行高固定;首/末行
* 钳制。颜色全部取 meow_theme_get() 语义色(日/夜/style 自动),禁止硬编码。 */
/* ============================================================ */
#define MEOW_LISTV_ICON 24 /* 图标盒尺寸(px) */
#define MEOW_LISTV_PAD 16 /* 行内左右 padding(px) */
#define MEOW_LISTV_SUB_GAP 2 /* 标题与副标题间隙(px) */
/* meow_ui_img_scaled 弱引用:W_LISTVIEW 的 MEOW_LIST_ICON_IMG 分支用它绘制图片图标。
* 弱引用让「只用内置符号图标、不解码图片」的 App 不被牵连进 meow_ui_img.o/stb_image.o
* (静态库惰性链接:无强引用则不拉入 stb 解码器,避免所有 App 无谓膨胀 ~77KB)。
* 宿主/设备需图片图标的 App 经 meow_ui_img_load_* 强引用 meow_ui_img.o → 此处自动解析。 */
extern void meow_ui_img_scaled(int x, int y, int w, int h, meow_img *img) __attribute__((weak));
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)
{
if (!s) return;
int full = meow_ui_text_width(s, size);
if (full <= maxw) { meow_ui_text(x, y, s, size, col); return; }
const char *dots = "...";
int dw = meow_ui_text_width(dots, size);
char tmp[128];
int i;
for (i = 0; s[i] && i < (int)sizeof tmp - 4; i++) {
tmp[i] = s[i];
tmp[i + 1] = '\0';
if (meow_ui_text_width(tmp, size) > maxw - dw) break;
}
memcpy(tmp + i, dots, 4);
meow_ui_text(x, y, tmp, size, col);
}
/* 行图标绘制(内置符号 / meow_img)。col=图标前景(选中/未选中由调用方定)。 */
static void listv_draw_icon(const meow_theme *th, int x, int y, int kind,
const meow_img *img, uint32_t col)
{
(void)th;
int s = MEOW_LISTV_ICON;
if (kind == MEOW_LIST_ICON_IMG && img && meow_ui_img_scaled) { /* 弱符号非空才画(未链 stb 则跳过) */
meow_ui_img_scaled(x, y, s, s, (meow_img *)img); /* img 为 opaque 句柄,const 丢弃 */
return;
}
if (kind == MEOW_LIST_ICON_FOLDER) {
meow_ui_fill_rect(x, y + 6, s, s - 6, col); /* 主体 */
meow_ui_fill_rect(x, y + 2, 12, 6, col); /* 页签 */
} else if (kind == MEOW_LIST_ICON_FILE) {
meow_ui_draw_rect(x + 4, y + 2, s - 8, s - 4, col);/* 页体 */
meow_ui_fill_rect(x + 4, y + 2, 7, 4, col); /* 文件名横条 */
}
}
/* 行右侧操作区绘制(箭头/开关/复选/⋯)。x=动作槽左缘;col=前景。 */
static void listv_draw_right(const meow_theme *th, int x, int y, int row_h,
int right, int on, uint32_t col)
{
switch (right) {
case MEOW_LIST_RIGHT_ARROW: {
int cx = x + 4, cy = y + row_h / 2;
meow_gfx_line(cx - 4, cy - 6, cx + 4, cy, 2, col);
meow_gfx_line(cx + 4, cy, cx - 4, cy + 6, 2, col);
break;
}
case MEOW_LIST_RIGHT_SWITCH: {
int sw = 36, sh = 20;
int sx = x, sy = y + (row_h - sh) / 2;
meow_gfx_fill_round(sx, sy, sw, sh, sh / 2, on ? th->primary : th->onSurfaceVariant);
int kr = sh / 2 - 3;
int kx = on ? sx + sw - kr * 2 - 3 : sx + 3;
meow_gfx_fill_round(kx, sy + 3, kr * 2, kr * 2, kr, on ? th->onPrimary : th->surface);
break;
}
case MEOW_LIST_RIGHT_CHECK: {
int cx = x + 2, cy = y + row_h / 2;
meow_gfx_line(cx - 6, cy + 2, cx - 1, cy + 7, 2, col);
meow_gfx_line(cx - 1, cy + 7, cx + 7, cy - 6, 2, col);
break;
}
case MEOW_LIST_RIGHT_MORE: {
int cx = x;
int cy0 = y + row_h / 2 - 8;
for (int d = 0; d < 3; d++)
meow_ui_fill_rect(cx, cy0 + d * 6, 4, 4, col);
break;
}
default: break;
}
}
/* 多选位图扩容(MULTI 且 idx 超出当前容量时;SINGLE 不分配)。 */
static void listv_bitmap_grow(meow_widget *w, int idx)
{
int need = (idx + 1 + 31) / 32;
if (need <= w->sel_bits_n) return;
uint32_t *nb = (uint32_t *)calloc((size_t)need, sizeof(uint32_t));
if (!nb) return;
if (w->sel_bits && w->sel_bits_n)
memcpy(nb, w->sel_bits, (size_t)w->sel_bits_n * sizeof(uint32_t));
free(w->sel_bits);
w->sel_bits = nb;
w->sel_bits_n = need;
}
meow_widget *meow_listview_create(int x, int y, int w, int h, int count,
meow_list2_source src, void *ctx, meow_list_sel mode)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_LISTVIEW; 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 > 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));
}
meow_draw_widget_register(t);
return t;
}
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;
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)
{
if (!w || w->type != W_LISTVIEW) return;
if (count < 0) count = 0;
if (count == w->count) return;
w->count = count;
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 = (n + 31) / 32;
if (need > 0) {
w->sel_bits_n = need;
w->sel_bits = (uint32_t *)calloc((size_t)need, sizeof(uint32_t));
}
}
int ms = meow_listview_max_scroll(w); /* 行数减少时 clamp scroll */
if (w->scroll > ms) w->scroll = ms;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
}
int meow_listview_max_scroll(meow_widget *w)
{
if (!w || w->type != W_LISTVIEW) return 0;
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)
{
if (!w || w->type != W_LISTVIEW) return;
int ms = meow_listview_max_scroll(w);
if (offset < 0) offset = 0;
if (offset > ms) offset = ms;
if (offset == w->scroll) return;
w->scroll = offset;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
}
void meow_listview_scroll_by(meow_widget *w, int dy) { if (w) meow_listview_set_scroll(w, w->scroll + dy); }
int meow_listview_scroll(meow_widget *w) { return (w && w->type == W_LISTVIEW) ? w->scroll : 0; }
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;
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 = 清除 */
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
} else { /* MULTI:翻转 idx */
if (idx < 0) {
if (w->sel_bits) memset(w->sel_bits, 0, (size_t)w->sel_bits_n * sizeof(uint32_t));
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
return;
}
listv_bitmap_grow(w, idx);
if (!w->sel_bits) return;
w->sel_bits[idx >> 5] ^= (1u << (idx & 31));
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
}
}
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, total = listv_count(w);
if (w->sel_mode == MEOW_LIST_SEL_MULTI) {
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;
}
return n;
}
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, 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;
}
return (w->sel >= 0) ? 1 : 0;
}
/* 命中:返回行号(含滚动换算;禁用行/越界 -1)。抬手用最新坐标(goodix 免疫,调用方传抬手落点)。 */
int meow_listview_hit(meow_widget *w, int x, int y)
{
if (!w || w->type != W_LISTVIEW) return -1;
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;
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;
}
int meow_listview_hit_op(meow_widget *w, int x, int y)
{
int idx = meow_listview_hit(w, x, y);
if (idx < 0) return 0;
int opx = w->x + (w->w * 2 / 3); /* 行右 1/3 = 操作区(§5.5) */
if (x < opx) return 0;
if (w->src2) {
meow_list_item it;
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;
}
void meow_listview_set_on_tap(meow_widget *w, meow_listview_tap_fn cb, void *ctx)
{
if (!w || w->type != W_LISTVIEW) return;
w->lv_tap_cb = cb; w->lv_tap_ctx = ctx;
}
int meow_listview_tap_at(meow_widget *w, int x, int y)
{
int idx = meow_listview_hit(w, x, 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;
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;
if (val == w->val) return;
w->val = val;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); /* 滑块/轨道随值变化:标脏 */
}
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;
/* 绘制期裁剪(§3):把绘制 clip 设为该控件矩形,画完恢复——任何控件(含 W_CUSTOM
* 自定义画布)不可能画出自身边界,从根上杜绝越界脏区。可嵌套(W_LAYOUT/W_CARD 递归
* 子控件会再 push 子控件矩形);顶层 clip=全屏,故 IME/模态直绘不受影响。 */
meow_gfx_clip_push(w->x, w->y, w->w, w->h);
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() >= (unsigned)meow_ui_long_press_ms();
if (disabled) press = 0;
/* §5.8.6 五型:filled=primary/onPrimary;tonal=secondaryContainer/onSecondaryContainer;
* outlined=透明底+primary 文+outline 描边;text=透明底+primary 文;elevated=surfaceContainerLow+primary 文。
* 选中态优先生效(primaryContainer / onPrimaryContainer);opts.bg/fg 可覆盖。 */
int selected = (w->state & MEOW_WS_SELECTED);
int variant = w->variant;
if (variant < MEOW_BTN_FILLED || variant > MEOW_BTN_ELEVATED) variant = MEOW_BTN_FILLED;
int fill = 1, stroke_draw = 0, stroke_w = 1;
uint32_t base, default_fg, stroke_col = 0;
if (selected && !(w->opts_used && w->opts.bg)) {
base = th->primaryContainer; default_fg = th->onPrimaryContainer;
} else {
switch (variant) {
case MEOW_BTN_TONAL:
base = th->secondaryContainer; default_fg = th->onSecondaryContainer; break;
case MEOW_BTN_OUTLINED:
fill = 0; base = 0; default_fg = th->primary;
stroke_draw = 1; stroke_col = th->outline; stroke_w = 1; break;
case MEOW_BTN_TEXT:
fill = 0; base = 0; default_fg = th->primary; break;
case MEOW_BTN_ELEVATED:
base = th->surfaceContainerLow; default_fg = th->primary;
stroke_draw = 1; stroke_col = th->outlineVariant; stroke_w = 1; break;
default:
base = th->primary; default_fg = th->onPrimary; break;
}
}
if (w->opts_used && w->opts.bg) { base = w->opts.bg; fill = 1; }
if (w->opts_used && w->opts.fg) default_fg = w->opts.fg;
uint32_t bg;
if (fill) bg = (hold) ? state_tint(base, th, 24) : (press ? state_tint(base, th, 12) : base);
else bg = 0;
int r = widget_radius(w, 10);
if (fill) meow_gfx_fill_round(w->x, w->y, w->w, w->h, r, bg);
else if (press) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->primary, 12); /* transparent ripple */
/* 内容区受 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 = default_fg;
meow_ui_text_center(cx, cy, w->label, th->font_body, tc);
/* opts 描边(优先)/ 变体内建描边 / 焦点外圈 */
int sw = (w->opts_used && w->opts.stroke && w->opts.stroke_w > 0) ? w->opts.stroke_w : (stroke_draw ? stroke_w : 0);
uint32_t sc = (w->opts_used && w->opts.stroke) ? w->opts.stroke : stroke_col;
if (sw > 0 && sc) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, sc);
if (w->state & MEOW_WS_FOCUSED)
meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary);
/* 禁用:整体变暗(圆角 α,防在圆角外叠出灰方块——M2.71-bug4) */
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, 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_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, w->h / 2, th->onSurface, 90);
break;
}
case W_LIST: {
/* §5.8.6 列表容器(LazyColumn/LazyRow):只画可见项(虚拟化)+ 滚动偏移。
* 纵向=行高 item_h(默认 44,兼容既有 meow_list_create);横向=LazyRow(列宽=item_h)。 */
int pad = 16;
int item = list_item(w);
int vp = w->horizontal ? w->w : w->h;
int off = w->scroll;
if (off < 0) off = 0;
int maxs = w->count * item - vp; /* max_scroll(与 meow_list_max_scroll 同式) */
if (maxs < 0) maxs = 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;
/* 视口绝对边界:列表只在其内绘制,杜绝越界泄漏(部分行滚出视口时把填充/分隔线/文本
* 画到视口外 → 标脏并上屏 → 标题栏/底部残留脏区,P3)。 */
int vx0 = w->x, vy0 = w->y, vx1 = w->x + w->w, vy1 = w->y + w->h;
int thh = meow_ui_text_height(th->font_body);
for (int i = first; i < last; i++) {
char label[64] = "";
if (!w->src || !w->src(i, label, (int)sizeof label, w->ctx)) continue;
int pos = i * item - off; /* 相对容器起点的可视位置 */
if (w->horizontal) {
int rx = w->x + pos;
if (rx >= vx1) break; /* 完全在右缘外 */
if (rx + item <= vx0) continue; /* 完全在左缘外(滚出残余) */
int cl = rx > vx0 ? rx : vx0; /* 可见左界 */
int cr = (rx + item) < vx1 ? (rx + item) : vx1; /* 可见右界 */
if (i == w->sel) meow_ui_fill_rect(cl, w->y, cr - cl, w->h, th->primary);
uint32_t col = (i == w->sel) ? 0xFFFFFF : th->onSurface;
int ty = w->y + (w->h - thh) / 2;
if (rx >= vx0 && rx + item <= vx1) /* 仅整项可见才画文本(防泄漏) */
meow_ui_text_center(rx + item / 2, ty, label, th->font_body, col);
int sxp = rx + item - 1; /* 分隔线:仅当落在可见区 */
if (sxp >= cl && sxp < cr) meow_ui_fill_rect(sxp, w->y, 1, w->h, th->onSurfaceVariant);
} else {
int ry = w->y + pos;
if (ry >= vy1) break; /* 完全在底缘外 */
if (ry + item <= vy0) continue; /* 完全在顶缘外(滚出残余) */
int cl = ry > vy0 ? ry : vy0; /* 可见顶界 */
int cr = (ry + item) < vy1 ? (ry + item) : vy1; /* 可见底界 */
if (i == w->sel) meow_ui_fill_rect(w->x, cl, w->w, cr - cl, th->primary);
uint32_t col = (i == w->sel) ? 0xFFFFFF : th->onSurface;
int ty = ry + (item - thh) / 2;
if (ty >= vy0 && ty + thh <= vy1) /* 文本不越界才画(防标题/底部泄漏) */
meow_ui_text(w->x + pad, ty, label, th->font_body, col);
int sy = ry + item - 1; /* 分隔线:仅当落在可见区 */
if (sy >= cl && sy < cr) meow_ui_fill_rect(w->x, sy, 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_LISTVIEW: {
/* §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 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 > 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 + ((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;
/* 空态占位(§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;
}
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);
/* 图标(左) */
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);
}
}
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_TEXTFIELD: {
/* MD3 输入框:圆角外框(surfaceContainerLowest 底 + outline 描边;focused=primary 2px)+ 提示/文本 + 光标。 */
int r = widget_radius(w, 8);
int focused = (w->state & MEOW_WS_FOCUSED) != 0;
meow_gfx_fill_round(w->x, w->y, w->w, w->h, r, th->surfaceContainerLowest);
uint32_t bcol = focused ? th->primary : th->outline;
int bw = focused ? 2 : 1;
if (bw >= 2) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, bcol);
else meow_ui_draw_rect_round(w->x + 1, w->y + 1, w->w - 2, w->h - 2,
r > 1 ? r - 1 : 0, bcol);
int has = w->buf && w->buf[0];
uint32_t col = has ? th->onSurface : th->onSurfaceVariant;
const char *s = has ? w->buf : (w->placeholder[0] ? w->placeholder : "");
int tx = w->x + 12;
int ty = w->y + (w->h - meow_ui_text_height(th->font_body)) / 2;
meow_ui_text(tx, ty, s, th->font_body, col);
if (focused && has) {
int tw = meow_ui_text_width(w->buf, th->font_body);
meow_ui_fill_rect(tx + tw, ty, 2, meow_ui_text_height(th->font_body), th->primary);
}
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90);
break;
}
case W_CARD: {
int r = widget_radius(w, th->radius_card);
uint32_t base = widget_bg(w, th, th->surfaceContainerLow);
meow_gfx_fill_round(w->x, w->y, w->w, w->h, r, base);
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_SELECTED)
meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->primary, 24);
if (w->nchildren > 0 && w->children[0]) meow_widget_draw(w->children[0]);
if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary);
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90);
break;
}
case W_DIVIDER: {
uint32_t c = widget_bg(w, th, th->outlineVariant);
if (w->variant) meow_ui_fill_rect(w->x, w->y, 1, w->h, c);
else meow_ui_fill_rect(w->x, w->y, w->w, 1, c);
break;
}
case W_BADGE: {
int count = w->value;
if (count < 0) break; /* 隐藏 */
uint32_t bgc = th->error; /* MD3 角标用 error */
if (count == 0) {
meow_gfx_fill_circle(w->x, w->y, 5, bgc);
} else {
char s[16];
snprintf(s, sizeof s, "%d", count);
int tw = meow_ui_text_width(s, th->font_body);
int pw = tw + 12; if (pw < 18) pw = 18;
meow_gfx_fill_round(w->x - pw / 2, w->y - 11, pw, 22, 11, bgc);
meow_ui_text_center(w->x, w->y - meow_ui_text_height(th->font_body) / 2, s, th->font_body, th->onError);
}
break;
}
case W_CHECKBOX: {
int sel = (w->state & MEOW_WS_SELECTED) != 0;
int sz = w->w;
int r = widget_radius(w, 4);
if (sel) {
meow_gfx_fill_round(w->x, w->y, sz, sz, r, th->primary);
int ax = w->x + 9, ay1 = w->y + 17, ay2 = w->y + 23, bx = w->x + 15, cx = w->x + 25, cy = w->y + 11;
meow_gfx_line(ax, ay1, bx, ay2, 3, th->onPrimary);
meow_gfx_line(bx, ay2, cx, cy, 3, th->onPrimary);
} else {
meow_ui_draw_rect_round(w->x, w->y, sz, sz, r, th->outline);
}
if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, sz, sz, r, th->primary);
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, sz, sz, r, th->onSurface, 90);
break;
}
case W_RADIO: {
int sel = (w->state & MEOW_WS_SELECTED) != 0;
int ccx = w->x + w->w / 2, ccy = w->y + w->h / 2;
meow_gfx_ring_arc(ccx, ccy, 12, 2, 0, 360, sel ? th->primary : th->outline);
if (sel) meow_gfx_fill_circle(ccx, ccy, 6, th->primary);
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, w->w / 2, th->onSurface, 90);
break;
}
case W_PROGRESS: {
if (w->variant == 1) { /* 圆形(几何 = 整圆像素外接框;由此解回圆心/半径) */
int thk = MEOW_PROGRESS_ARC_THK;
int m = MEOW_PROGRESS_ARC_MARGIN;
int ccx = w->x + w->w / 2, ccy = w->y + w->h / 2;
int rr = w->w / 2 - m; /* 与 create 一致:回到调用方传入的半径 r */
if (rr <= 0) break;
meow_gfx_ring_arc(ccx, ccy, rr, thk, 0, 360, th->surfaceVariant);
if (w->value < 0) { /* 不定:按时间推进一段弧(App 在 on_tick 里重绘驱动) */
int start = (int)((meow_ui_time_ms() / 30) % 360);
meow_gfx_ring_arc(ccx, ccy, rr, thk, start - 90, 90, th->primary);
} else if (w->value > 0) {
int sweep = w->value * 360 / 100;
if (sweep > 0) meow_gfx_ring_arc(ccx, ccy, rr, thk, -90, sweep, th->primary);
}
} else { /* 线性 */
int thk = w->h > 0 ? w->h : 4;
int yy = w->y + w->h / 2 - thk / 2;
meow_ui_fill_rect(w->x, yy, w->w, thk, th->surfaceVariant);
if (w->value < 0) { /* 不定:移动块 */
int fw = w->w / 3;
int pos = (int)((meow_ui_time_ms() / 12) % (w->w + fw)) - fw;
meow_ui_fill_rect(w->x + pos, yy, fw, thk, th->primary);
} else if (w->value > 0) {
int fw = w->value * w->w / 100;
if (fw > 0) meow_ui_fill_rect(w->x, yy, fw, thk, th->primary);
}
}
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
break;
}
case W_FAB: {
int r = widget_radius(w, w->w / 2);
uint32_t base = widget_bg(w, th, th->primaryContainer);
if (w->state & MEOW_WS_PRESSED) base = state_tint(base, th, 12);
meow_gfx_fill_round(w->x, w->y, w->w, w->h, r, base);
uint32_t tc = widget_fg(w, th, th->onPrimaryContainer);
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, tc);
if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary);
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90);
break;
}
case W_TOPAPPBAR: {
meow_ui_fill_rect(w->x, w->y, w->w, w->h, th->surfaceContainerHigh);
int mid = w->y + w->h / 2;
int tx = w->x + 16;
int ty = mid - meow_ui_text_height(th->font_title) / 2;
if (w->back_on) {
int bx = w->x + 22, by = mid;
meow_gfx_line(bx + 4, by - 7, bx - 4, by, 2, th->onSurface);
meow_gfx_line(bx - 4, by, bx + 4, by + 7, 2, th->onSurface);
tx = w->x + 48;
}
if (w->label[0]) meow_ui_text(tx, ty, w->label, th->font_title, th->onSurface);
int ay = mid - meow_ui_text_height(th->font_body) / 2;
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;
ax -= aw;
meow_ui_text_center(ax + aw / 2, ay, w->items[i], th->font_body, th->onSurface);
}
meow_ui_fill_rect(w->x, w->y + w->h - 1, w->w, 1, th->outlineVariant);
break;
}
case W_NAVBAR: {
meow_ui_fill_rect(w->x, w->y, w->w, w->h, th->surfaceContainer);
if (w->nitems <= 0) break;
int iw = w->w / w->nitems;
int ay = w->y + (w->h - meow_ui_text_height(th->font_body)) / 2 + 12;
for (int i = 0; i < w->nitems; i++) {
int ix = w->x + i * iw;
int sel = (i == w->selected);
if (sel) meow_gfx_fill_round(ix + iw / 2 - 24, w->y + 8, 48, 22, 11, th->secondaryContainer);
uint32_t col = sel ? th->onSecondaryContainer : th->onSurfaceVariant;
meow_ui_text_center(ix + iw / 2, ay, w->items[i], th->font_body, col);
}
meow_ui_fill_rect(w->x, w->y, w->w, 1, th->outlineVariant);
break;
}
case W_TABBAR: {
meow_ui_fill_rect(w->x, w->y, w->w, w->h, th->surface);
if (w->nitems <= 0) break;
int iw = w->w / w->nitems;
int ay = w->y + (w->h - meow_ui_text_height(th->font_body)) / 2;
for (int i = 0; i < w->nitems; i++) {
int ix = w->x + i * iw;
int sel = (i == w->selected);
meow_ui_text_center(ix + iw / 2, ay, w->items[i], th->font_body,
sel ? th->primary : th->onSurfaceVariant);
if (sel) meow_ui_fill_rect(ix, w->y + w->h - 2, iw, 2, th->primary);
}
meow_ui_fill_rect(w->x, w->y + w->h - 1, w->w, 1, th->outlineVariant);
break;
}
case W_SPACER:
break; /* 不可见空白 */
default:
break;
}
meow_gfx_clip_pop(); /* 恢复进入前的 clip(嵌套:回到父控件矩形/全屏) */
}