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:
1 parent
26074e47ac
commit
7f36b6f069
8 files changed
+1360
-24
No files matched your search
+642
-22
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user