feat(sdk): 控件库第二批 Material 子集(agent-152)

- 新增控件: Button五型(filled/tonal/outlined/text/elevated)/TextField(接IME: round外框idle-outline/focused-primary+占位+光标)/Card/Divider/Badge/Checkbox/Radio/ProgressBar(线圆, value=-1不定)/FAB/TopAppBar(标题+‹返回+动作)/NavigationBar(TabBar pageSwitcher)+TabBar(下划线)/LazyColumn/LazyRow(虚拟化+scroll clamp)
- Dialog(B③同构阻塞模态)/BottomSheet(B④), meow_ui_dialog.c
- meow_gfx 新增 fill_circle/line/ring_arc(无-lm, COS90象限查表)
- 结构体尾缀(对外opaque/calloc归零), 旧字段与create签名零破坏; meow_widget 类型扩到W_TEXTFIELD..W_FAB(17种)
- meow_ui.a=18c21da5 hello=5e31d919 launcher=4952261d(重编重链无回归); build_sdk 0warning; 宿主 test_meow_ui_widgets 全PASS+test_sb_collapse PASS+PPM目检正确
- 与 §5.8/§5.2-5.7 一致; 遗留真机观感/性能
This commit is contained in:
MeowOS committed 2026-08-30 22:33:51 +08:00
1 parent 26074e47ac
commit 7f36b6f069
8 files changed
+1360 -24

No files matched your search

+642 -22
View File
@@ -8,13 +8,20 @@
#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_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
@@ -51,6 +58,21 @@ struct meow_widget {
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 选中下标 */
};
/* ---- §5.8 batch1 内部辅助:opts 取值(opts_used==0 时按控件内建默认)----
@@ -391,6 +413,384 @@ void meow_layout_arrange(meow_widget *layout)
}
}
/* ============================================================ */
/* §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;
t->type = W_PROGRESS; t->x = cx; t->y = cy; t->w = r; t->h = r;
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;
}
static int hit(meow_widget *w, int x, int y)
{
if (!w) return 0;
@@ -453,27 +853,50 @@ void meow_widget_draw(meow_widget *w)
int press = (w->state & MEOW_WS_PRESSED) || w->pressed || live;
int hold = live && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS;
if (disabled) press = 0;
/* 底色:选中态用 primaryContainer(onPrimaryContainer 文字);否则 primary(onPrimary 文字)。
* 可被 opts.bg 覆盖(此时文字用 opts.fg 或 onPrimary)。 */
/* §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);
uint32_t base, default_fg;
if (selected && !(w->opts_used && w->opts.bg)) { base = th->primaryContainer; default_fg = th->onPrimaryContainer; }
else { base = widget_bg(w, th, th->primary); default_fg = th->onPrimary; }
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 (hold) bg = state_tint(base, th, 24); /* 按住 = 深状态层 */
else if (press) bg = state_tint(base, th, 12); /* 按下 = 状态层 */
else bg = base;
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);
meow_gfx_fill_round(w->x, w->y, w->w, w->h, r, bg);
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 = widget_fg(w, th, default_fg);
uint32_t tc = default_fg;
meow_ui_text_center(cx, cy, w->label, th->font_body, tc);
/* opts 描边 / 焦点外圈 */
if (w->opts_used && w->opts.stroke && w->opts.stroke_w > 0)
meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, w->opts.stroke);
/* 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);
/* 禁用:整体变暗(状态层向 onSurface 叠 α) */
@@ -503,18 +926,41 @@ void meow_widget_draw(meow_widget *w)
break;
}
case W_LIST: {
int rowh = 44, pad = 16;
for (int i = 0; i < w->count; i++) {
int ry = w->y + i * rowh;
if (ry >= w->y + w->h) break;
if (i == w->sel) meow_ui_fill_rect(w->x, ry, w->w, rowh, th->primary);
/* §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;
for (int i = first; i < last; i++) {
char label[64] = "";
if (w->src && w->src(i, label, (int)sizeof label, w->ctx)) {
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 >= w->x + w->w) break;
if (i == w->sel) meow_ui_fill_rect(rx, w->y, item, w->h, th->primary);
uint32_t col = (i == w->sel) ? 0xFFFFFF : th->onSurface;
meow_ui_text(w->x + pad, ry + (rowh - meow_ui_text_height(th->font_body)) / 2,
meow_ui_text_center(rx + item / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2,
label, th->font_body, col);
meow_ui_fill_rect(rx + item - 1, w->y, 1, w->h, th->onSurfaceVariant);
} else {
int ry = w->y + pos;
if (ry >= w->y + w->h) break;
if (i == w->sel) meow_ui_fill_rect(w->x, ry, w->w, item, th->primary);
uint32_t col = (i == w->sel) ? 0xFFFFFF : th->onSurface;
meow_ui_text(w->x + pad, ry + (item - meow_ui_text_height(th->font_body)) / 2,
label, th->font_body, col);
meow_ui_fill_rect(w->x, ry + item - 1, w->w, 1, th->onSurfaceVariant);
}
meow_ui_fill_rect(w->x, ry + rowh - 1, w->w, 1, th->onSurfaceVariant);
}
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
break;
@@ -529,6 +975,180 @@ void meow_widget_draw(meow_widget *w)
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_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, 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_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, 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_ui_fill_rect_alpha(w->x, w->y, sz, sz, 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_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
break;
}
case W_PROGRESS: {
if (w->variant == 1) { /* 圆形 */
int ccx = w->x, ccy = w->y, rr = w->w;
int thk = 4;
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_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, 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: