Files
MeowOS-For-R3P2/patch_v2/sdk/meow_ui_widget.c
T

1219 lines
53 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 };
/* 布局子项数组上限(固定尺寸子项先做;超出静默拒收)。 */
#define MEOW_LAYOUT_MAX_CHILD 32
/* 容器(TopAppBar 动作 / NavigationBar 项 / TabBar 页签)子项上限。 */
#define MEOW_CONTAINER_MAX 8
/* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层)。
* 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */
#define MEOW_BTN_HOLD_MS 500u
/* 圆形 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.8 batch1 内部辅助:opts 取值(opts_used==0 时按控件内建默认)----
* 注意:必须先定义 struct meow_widget,才能访问其成员(C 要求类型完整)。 */
static uint32_t widget_bg(const meow_widget *w, const meow_theme *th, uint32_t def)
{ (void)th; return (w->opts_used && w->opts.bg) ? w->opts.bg : def; }
static uint32_t widget_fg(const meow_widget *w, const meow_theme *th, uint32_t def)
{ (void)th; return (w->opts_used && w->opts.fg) ? w->opts.fg : def; }
static int widget_radius(const meow_widget *w, int def)
{ return (w->opts_used && w->opts.radius >= 0) ? w->opts.radius : def; }
static int widget_pady(const meow_widget *w)
{ return (w->opts_used && w->opts.pad_y > 0) ? w->opts.pad_y : 0; }
/* 是否禁用(状态机:MEOW_WS_ENABLED 未置位)。 */
static int widget_disabled(const meow_widget *w) { return !(w->state & MEOW_WS_ENABLED); }
/* MD3 状态层叠色:把基色向 onSurface 叠 t% 调出按下/按住/聚焦状态层(§5.8.4)。 */
static uint32_t state_tint(uint32_t base, const meow_theme *th, int t)
{ return blend_color(base, th->onSurface, t); }
/* ---- §5.8.2/§5.8.3 公共 API:Options + 状态机 ---- */
int meow_widget_set_opts(meow_widget *w, const meow_widget_opts *opts)
{
if (!w || !opts) return -1;
w->opts = *opts;
w->opts_used = 1;
/* disabled/selected 立即反映到状态机位(§5.8.3)。 */
if (opts->disabled) w->state &= ~MEOW_WS_ENABLED;
else w->state |= MEOW_WS_ENABLED;
if (opts->selected) w->state |= MEOW_WS_SELECTED;
else w->state &= ~MEOW_WS_SELECTED;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); /* 挂载会改变绘制:标区域脏 */
return 0;
}
int meow_widget_opts_get(const meow_widget *w, meow_widget_opts *out)
{
if (!w || !out) return -1;
if (!w->opts_used) {
/* 默认视图:radius=-1(控件默认)、disabled/selected 取状态位。 */
memset(out, 0, sizeof *out);
out->radius = -1;
out->disabled = widget_disabled(w) ? 1 : 0;
out->selected = (w->state & MEOW_WS_SELECTED) ? 1 : 0;
return 0;
}
*out = w->opts;
return 0;
}
void meow_widget_set_state(meow_widget *w, unsigned bits, int on)
{
if (!w) return;
if (on) w->state |= bits;
else w->state &= ~bits;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); /* 状态变化会改变绘制 */
}
unsigned meow_widget_state(const meow_widget *w) { return w ? w->state : MEOW_WS_ENABLED; }
int meow_widget_enabled(const meow_widget *w) { return w ? (w->state & MEOW_WS_ENABLED) != 0 : 0; }
void meow_widget_set_enabled(meow_widget *w, int on) { meow_widget_set_state(w, MEOW_WS_ENABLED, on ? 1 : 0); }
int meow_widget_selected(const meow_widget *w) { return w ? (w->state & MEOW_WS_SELECTED) != 0 : 0; }
void meow_widget_set_selected(meow_widget *w, int on) { meow_widget_set_state(w, MEOW_WS_SELECTED, on ? 1 : 0); }
/* 一键叠状态层(自定义画布可选;标准控件由 SDK 自动叠,不调用):半透明状态层 + 焦点描边。 */
void meow_widget_draw_state_layer(meow_widget *w, const meow_theme *th)
{
if (!w || !th) return;
if (widget_disabled(w)) {
meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
return; /* 禁用不再叠按下/聚焦 */
}
if (w->state & MEOW_WS_PRESSED) {
/* 向 onSurface 叠半透明状态层铺满控件矩形(按下/按住) */
int t = (w->opts_used && w->opts.bg) ? 24 : 12;
meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, t * 255 / 100);
}
if (w->state & MEOW_WS_FOCUSED) {
int r = widget_radius(w, 10);
meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary);
}
}
/* ---- 实时按压即时刷新(agent-119)----
* 按压/按住视觉由 meow_widget_draw 读实时触摸(meow_input_touch_*)呈现,但 app 只在
* on_tick(1s)/on_tap 重绘 → 快速点/按住时按压态刷新滞后/漏。此处在 SDK 触摸按下/释放/
* 按住达阈值时主动重绘命中的按钮区域并局部上屏(不等 app 重绘),即时反映按下变深/
* 释放恢复,连续快速点击每按都反映。仅针对 SDK 控件按钮(W_BTN);app 纯自绘不适用。
* 语义不变:on_tap / meow_btn_pressed / meow_widget_hit 零改动;非按钮区域不动。 */
#define MEOW_LIVE_BTN_MAX 64
static meow_widget *g_live_btns[MEOW_LIVE_BTN_MAX];
static int g_live_btn_n = 0;
static meow_widget *g_live_btn = NULL; /* 实时按压中最上层按钮(无 = NULL) */
static int g_live_prev_hold = 0; /* 该按钮上次渲染是否为按住态 */
static void live_btn_register(meow_widget *w)
{
if (!w || w->type != W_BTN) return;
for (int i = 0; i < g_live_btn_n; i++)
if (g_live_btns[i] == w) return;
if (g_live_btn_n < MEOW_LIVE_BTN_MAX) g_live_btns[g_live_btn_n++] = w;
}
static void live_btn_unregister(meow_widget *w)
{
for (int i = 0; i < g_live_btn_n; i++) {
if (g_live_btns[i] == w) {
g_live_btns[i] = g_live_btns[--g_live_btn_n];
if (g_live_btn == w) g_live_btn = NULL;
return;
}
}
if (g_live_btn == w) g_live_btn = NULL;
}
/* 命中:返回坐标所在的「当前 active Activity」的最上层按钮(创建序 = 绘制序,最后者在上)。
* §6.1 ①:仅扫描 owner == 当前 active Activity 的按钮 —— 非 active Activity 的控件
* 绝不参与命中(根治 agent-119 遗留的「全屏页触摸到主页按钮位置 → 闪现 accent 底」)。 */
static meow_widget *live_btn_hit(int x, int y)
{
int active = meow_ui_activity_active();
meow_widget *r = NULL;
for (int i = 0; i < g_live_btn_n; i++) {
meow_widget *w = g_live_btns[i];
if (w->owner != active) continue; /* 非 active activity:跳过 */
if (x >= w->x && x < w->x + w->w && y >= w->y && y < w->y + w->h)
r = w; /* 越后创建越上:保留最后命中 */
}
return r;
}
/* 重绘并局部刷新一个按钮区(绘制内部调 meow_gfx_fill_round 已标脏;flush_region 合区+上屏)。 */
static void live_btn_redraw(meow_widget *w)
{
if (!w) return;
meow_widget_draw(w);
meow_ui_flush_region(w->x, w->y, w->w, w->h);
}
/* 由 meow_ui_run 每帧调用(主线程):检测实时触摸状态迁移,即时重绘命中按钮的按压/按住/
* 释放,不等 app on_tick/on_tap。仅在触摸状态相对上次渲染发生变化时动作(非全屏、省性能)。 */
void meow_widget_touch_refresh(void)
{
int active = meow_ui_activity_active();
/* §6.1 ①:若当前按住按钮已不属于 active activity(按压期间发生了 Activity 切换,
* 例如点 FULLSCREEN 后进入全屏),静默清引用——不绘制、不闪现(否则会在新页面底上
* 画出旧按钮的 accent 残留)。 */
if (g_live_btn && g_live_btn->owner != active) {
g_live_btn->state &= ~MEOW_WS_PRESSED; /* §5.8.3:清除按压态(跨页面不残留) */
g_live_btn = NULL;
g_live_prev_hold = 0;
}
int down = meow_input_touch_down();
meow_widget *hit = down ? live_btn_hit(meow_input_touch_x(), meow_input_touch_y()) : NULL;
int hold = down && hit && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS;
if (!down) {
/* 释放:把最近按压按钮恢复为常态 */
if (g_live_btn) {
g_live_btn->state &= ~MEOW_WS_PRESSED;
live_btn_redraw(g_live_btn);
g_live_btn = NULL;
}
g_live_prev_hold = 0;
return;
}
if (hit != g_live_btn) {
/* 按下/滑至新按钮:先恢复旧(若手指滑离),再按压新 */
if (g_live_btn) {
g_live_btn->state &= ~MEOW_WS_PRESSED;
live_btn_redraw(g_live_btn);
}
g_live_btn = hit;
g_live_prev_hold = hold;
if (hit) {
if (widget_disabled(hit)) { g_live_btn = NULL; } /* 禁用不响应按压 */
else { hit->state |= MEOW_WS_PRESSED; live_btn_redraw(hit); }
}
} else if (hit && hold != g_live_prev_hold) {
/* 同一按钮按住达阈值:升级到更强态 */
hit->state |= MEOW_WS_PRESSED;
live_btn_redraw(hit);
g_live_prev_hold = hold;
}
}
meow_widget *meow_btn_create(int x, int y, int w, int h, const char *label)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_BTN; t->x = x; t->y = y; t->w = w; t->h = h;
t->owner = meow_ui_activity_active(); /* §6.1 ①:绑定当前 active Activity */
t->state = MEOW_WS_ENABLED; /* §5.8.3:默认 ENABLED */
if (label) { strncpy(t->label, label, sizeof t->label - 1); t->label[sizeof t->label - 1] = 0; }
live_btn_register(t);
return t;
}
meow_widget *meow_slider_create(int x, int y, int w, int min, int max, int val)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_SLIDER; t->x = x; t->y = y; t->w = w; t->h = 40;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->min = min; t->max = (max > min) ? max : min + 1; t->val = (val < min) ? min : (val > t->max) ? t->max : val;
return t;
}
meow_widget *meow_switch_create(int x, int y, int on)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_SWITCH; t->x = x; t->y = y; t->w = 56; t->h = 32;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->on = on ? 1 : 0;
if (t->on) t->state |= MEOW_WS_SELECTED; /* §5.8.3:开关 on → selected */
return t;
}
meow_widget *meow_list_create(int x, int y, int w, int h, int count,
meow_list_source src, void *ctx)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_LIST; t->x = x; t->y = y; t->w = w; t->h = h;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->count = count; t->src = src; t->ctx = ctx; t->sel = 0;
return t;
}
void meow_widget_free(meow_widget *w)
{
if (!w) return;
live_btn_unregister(w);
free(w);
}
/* ---- §5.8.1 自定义画布:App 提供 render/hit 回调,同一个 meow_widget 基座 ---- */
meow_widget *meow_widget_custom_create(int x, int y, int w, int h,
meow_widget_render_fn render_cb,
meow_widget_hit_fn hit_cb,
void *ctx)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_CUSTOM; t->x = x; t->y = y; t->w = w; t->h = h;
t->owner = meow_ui_activity_active(); /* §6.1 ①:同既有控件绑当前 active Activity */
t->state = MEOW_WS_ENABLED;
t->render_cb = render_cb;
t->hit_cb = hit_cb;
t->ctx = ctx;
return t;
}
/* ---- §5.8.5 可组合布局:Row / Column / Box + Spacer ---- */
meow_widget *meow_row_create(int x, int y, int w, int h, int spacing)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_LAYOUT; t->kind = MEOW_LAYOUT_ROW; t->x = x; t->y = y; t->w = w; t->h = h;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->spacing = spacing > 0 ? spacing : 0;
return t;
}
meow_widget *meow_column_create(int x, int y, int w, int h, int spacing)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_LAYOUT; t->kind = MEOW_LAYOUT_COLUMN; t->x = x; t->y = y; t->w = w; t->h = h;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->spacing = spacing > 0 ? spacing : 0;
return t;
}
meow_widget *meow_box_create(int x, int y, int w, int h)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_LAYOUT; t->kind = MEOW_LAYOUT_BOX; t->x = x; t->y = y; t->w = w; t->h = h;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->spacing = 0;
return t;
}
meow_widget *meow_layout_spacer(int w, int h)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_SPACER; t->x = 0; t->y = 0; t->w = w > 0 ? w : 0; t->h = h > 0 ? h : 0;
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
return t;
}
int meow_layout_add(meow_widget *layout, meow_widget *child)
{
if (!layout || layout->type != W_LAYOUT || !child) return -1;
if (layout->nchildren >= MEOW_LAYOUT_MAX_CHILD) return -1;
for (int i = 0; i < layout->nchildren; i++)
if (layout->children[i] == child) return -1; /* 同子项去重 */
layout->children[layout->nchildren++] = child;
return 0;
}
/* 把子控件算成绝对坐标(写进各 child 的 x,y;w/h 固定尺寸,先不做弹性)。
* Row:子项水平排(垂直居中于容器 h);Column:垂直排(水平居中);Box:叠放(各放容器左上角)。
* 子项的 margin_x/margin_y(Options)参与排布;子项为布局时递归 arrange。 */
void meow_layout_arrange(meow_widget *layout)
{
if (!layout || layout->type != W_LAYOUT) return;
int kind = layout->kind;
int cur = (kind == MEOW_LAYOUT_COLUMN) ? layout->y : layout->x;
for (int i = 0; i < layout->nchildren; i++) {
meow_widget *c = layout->children[i];
if (!c) continue;
int mx = (c->opts_used && c->opts.margin_x > 0) ? c->opts.margin_x : 0;
int my = (c->opts_used && c->opts.margin_y > 0) ? c->opts.margin_y : 0;
if (kind == MEOW_LAYOUT_ROW) {
cur += mx;
c->x = cur;
c->y = layout->y + (layout->h - c->h) / 2; /* 垂直居中 */
if (c->y < layout->y) c->y = layout->y;
cur += c->w + mx + layout->spacing;
} else if (kind == MEOW_LAYOUT_COLUMN) {
cur += my;
c->y = cur;
c->x = layout->x + (layout->w - c->w) / 2; /* 水平居中 */
if (c->x < layout->x) c->x = layout->x;
cur += c->h + my + layout->spacing;
} else { /* BOX:叠放,各放容器左上角 */
c->x = layout->x;
c->y = layout->y;
}
if (c->type == W_LAYOUT) meow_layout_arrange(c); /* 嵌套递归 */
}
}
/* ============================================================ */
/* §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;
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 文本同步到字段缓冲 + 只刷字段区(字段在顶部,IME 键盘在底部覆盖层) */
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;
meow_gfx_mark_dirty(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;
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;
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; /* 红点 */
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;
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;
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);
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);
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; }
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; }
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;
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;
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;
}
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() >= MEOW_BTN_HOLD_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_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;
}
}