Files
MeowOS-SDK/meow_ui_widget.c
T
MeowOS 5d44128357 feat(sdk): 通用长按事件 (agent-191, 用户定案) — MEOW_EV_LONG_PRESS 坐标式事件+meow_ui_set_long_press_cb/ms(opt-in,默认500ms,移动>12px取消,长按后抬手抑制TAP/SWIPE); 按钮按住视觉阈值同源; files 长按从App层迁入 SDK
meow_ui.a 4335c33a→6de89c27; hello→72fb20da; files→b1c4f58f
宿主 widgets(含6条长按断言)/p1p2p3/ime_fix/test_fsx 全 PASS; 未改 meow_ui_events 结构(防 -Wmissing-field-initializers 破坏既有App)
2026-08-31 05:04:26 +08:00

1620 lines
71 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.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 多选位图 */
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;
}
/* ============================================================ */
/* §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);
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; }
/* 文本截断:超出 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;
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; 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;
if (w->sel >= w->count) w->sel = -1;
if (w->sel_mode == MEOW_LIST_SEL_MULTI) { /* 重分配多选位图 */
free(w->sel_bits); w->sel_bits = NULL; w->sel_bits_n = 0;
int need = (count + 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;
int ms = w->count * listv_item_h(w) - w->h;
return ms < 0 ? 0 : 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;
if (w->count <= 0) { w->sel = -1; return; }
if (idx >= w->count) idx = w->count - 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;
if (w->sel_mode == MEOW_LIST_SEL_MULTI) {
for (int i = 0; i < w->count && 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;
for (int i = 0; i < w->count; 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;
if (idx < 0 || idx >= w->count) 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;
}
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;
}
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);
if (w->lv_tap_cb) w->lv_tap_cb(idx, op, w->lv_tap_ctx);
return idx;
}
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;
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 结构化列表(纵向 LazyColumn):只画可见行(虚拟化)+ 视口裁剪(M2.73 纪律)。
* 行 = 图标(左) + 标题 + 副标题(次要色) + 右侧操作区;选中行 = primaryContainer /
* onPrimaryContainer(§5.8.3);禁用行变淡不响应(命中由 hit 层保证)。全语义色。 */
int item = listv_item_h(w);
int vp = w->h; /* 纵向 */
int off = w->scroll;
int maxs = w->count * item - vp;
if (maxs < 0) maxs = 0;
if (off < 0) off = 0;
if (off > maxs) off = maxs;
int first = (item > 0) ? off / item : 0;
int last = (item > 0) ? (off + vp) / item + 1 : 0;
if (first < 0) first = 0;
if (last > w->count) last = w->count;
int vx0 = w->x, vy0 = w->y, vy1 = w->y + w->h;
int thh = meow_ui_text_height(th->font_body);
int pad = MEOW_LISTV_PAD, isz = MEOW_LISTV_ICON;
int text_x = vx0 + pad + isz + pad;
int op_start = vx0 + (w->w * 2 / 3); /* 行右 1/3 = 操作区(§5.5 命中) */
int text_maxw = op_start - pad - text_x; /* 标题/副标题可用宽(不压操作区) */
if (text_maxw < 8) text_maxw = 8;
for (int i = first; i < last; i++) {
meow_list_item it;
memset(&it, 0, sizeof it);
if (!w->src2 || !w->src2(i, &it, w->ctx)) continue;
int ry = w->y + (i * item - off);
if (ry >= vy1) break;
if (ry + item <= vy0) continue;
int cl = ry > vy0 ? ry : vy0; /* 可见顶界(裁剪用) */
int cr = (ry + item) < vy1 ? (ry + item) : vy1; /* 可见底界 */
int sel = (i == w->sel);
uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface;
uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
if (sel) meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->primaryContainer);
/* 图标(左) */
uint32_t iccol = sel ? th->onPrimaryContainer
: ((it.icon_kind == MEOW_LIST_ICON_FOLDER) ? th->primary
: th->onSurfaceVariant);
int icy = ry + (item - isz) / 2;
if (icy + isz >= vy0 && icy < vy1) /* 图标不越视口边界才画(防泄漏) */
listv_draw_icon(th, vx0 + pad, icy, it.icon_kind, it.icon, iccol);
/* 标题 + 副标题(两行居中;无副标题=单行居中;越界不画防泄漏) */
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;
}
}