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 2cb6a7c16f
commit 9c733fcf73
10 files changed
+1360 -24

No files matched your search

Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -10,7 +10,7 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架
rm -f meow_ui.a *.o
# -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移)
OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global stb_image"
OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog stb_image"
CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd"
for f in $OBJS; do
echo "== cc $f =="
+114
View File
@@ -403,6 +403,120 @@ void meow_layout_arrange(meow_widget *layout);
/* 固定尺寸空白(也作撑余量;不可见,hit 恒 0)。 */
meow_widget *meow_layout_spacer(int w, int h);
/* ============================================================ */
/* §5.8.6 Material 3 组件子集(第二批,基于第一批基座:Options + 状态机 + MD3 色板 + 布局)
* 每个组件 = 新增 meow_widget 类型或组合既有模板的便捷函数;复用主题语义色(th->primary /
* surfaceContainer / outline / ...)+ 风格参数(radius/radius_card/spacing/stroke_w)+ 通用
* Options(meow_widget_set_opts 对它们同样生效)+ 状态机(enabled/selected/pressed/focused)。
* 向后兼容:既有 create 签名一字不改;本批只在其上扩展。 */
/* ============================================================ */
/* ---- Button 五型(meow_btn 扩展;既有 meow_btn_create = FILLED)---- */
enum {
MEOW_BTN_FILLED = 0, /* primary 底 / onPrimary 文 */
MEOW_BTN_TONAL = 1, /* secondaryContainer 底 / onSecondaryContainer 文 */
MEOW_BTN_OUTLINED = 2, /* 透明底 / primary 文 / primary 描边 */
MEOW_BTN_TEXT = 3, /* 透明底 / primary 文 / 无描边 */
MEOW_BTN_ELEVATED = 4, /* surfaceContainerLow 底 / primary 文 */
};
meow_widget *meow_btn_create_variant(int x, int y, int w, int h, const char *label, int variant);
int meow_btn_set_variant(meow_widget *w, int variant); /* 越界 clamp;0=成功 */
/* ---- TextField(单行文本输入,接 IME §5.1)---- */
/* placeholder=提示文案;buf=App 持有的文本缓冲(组件不回拷,只绘制/编辑其上);maxlen=含 NUL 预算 */
meow_widget *meow_textfield_create(int x, int y, int w, int h, const char *placeholder,
char *buf, int maxlen, meow_ime_mode mode);
void meow_textfield_set_placeholder(meow_widget *w, const char *placeholder);
/* 聚焦并打开**阻塞** IME(同 meow_ui_text_input 内层轮询);返回 1=确认 0=取消;buf 已回填 */
int meow_textfield_activate(meow_widget *w);
const char *meow_textfield_text(meow_widget *w);
/* ---- Card(圆角容器卡片;surfaceContainerLow 底 + 可选描边/状态层)---- */
meow_widget *meow_card_create(int x, int y, int w, int h);
/* 内容子控件:绘制时递归绘到卡片内(卡片 hit 仍为矩形;子控件独立命中) */
void meow_card_set_content(meow_widget *card, meow_widget *child);
/* ---- Divider(分隔线;vertical=1 纵向)---- */
meow_widget *meow_divider_create(int x, int y, int len, int vertical);
/* ---- Badge(角标;count=0 红点 / >0 数字 / <0 隐藏;cx,cy=锚点=角标中心)---- */
meow_widget *meow_badge_create(int cx, int cy);
void meow_badge_set(meow_widget *w, int count);
/* ---- Checkbox / Radio(勾选态走 meow_widget_set_selected/meow_widget_selected)---- */
meow_widget *meow_checkbox_create(int x, int y);
meow_widget *meow_radio_create(int x, int y);
/* ---- ProgressBar(线性/圆形;value=0-100 确定性,-1=不定)---- */
meow_widget *meow_progress_create(int x, int y, int w, int h, int value); /* 线性 */
meow_widget *meow_progress_circular_create(int cx, int cy, int r, int value); /* 圆形 */
void meow_progress_set(meow_widget *w, int value); /* -1=不定 */
/* ---- FloatingActionButton(悬浮按钮;圆角胶囊,无投影仅状态层近似)---- */
meow_widget *meow_fab_create(int x, int y, int size, const char *label);
/* ---- TopAppBar(顶部标题栏:标题 + 返回 + 动作)---- */
meow_widget *meow_topappbar_create(int x, int y, int w, int h, const char *title);
void meow_topappbar_set_title(meow_widget *w, const char *title);
void meow_topappbar_set_back(meow_widget *w, int on); /* 1=显示返回箭头 */
int meow_topappbar_add_action(meow_widget *w, const char *label); /* 返回动作下标 */
#define MEOW_TOPBAR_BACK (-2)
int meow_topappbar_hit(meow_widget *w, int x, int y); /* MEOW_TOPBAR_BACK=返回 idx>=0=动作 -1=无 */
/* ---- NavigationBar(底部导航栏)---- */
meow_widget *meow_navigationbar_create(int x, int y, int w, int h);
int meow_navigationbar_add_item(meow_widget *w, const char *label);
void meow_navigationbar_set_selected(meow_widget *w, int idx);
int meow_navigationbar_selected(meow_widget *w);
int meow_navigationbar_hit(meow_widget *w, int x, int y); /* 返回项下标/-1 */
/* ---- TabBar(顶部分页页签;swipe 联动 §5.2 由 App 在 on_swipe 驱动)---- */
meow_widget *meow_tabbar_create(int x, int y, int w, int h);
int meow_tabbar_add_tab(meow_widget *w, const char *label);
void meow_tabbar_set_selected(meow_widget *w, int idx);
int meow_tabbar_hit(meow_widget *w, int x, int y); /* 返回页签下标/-1 */
/* ---- 列表容器(LazyColumn/LazyRow:数据源回调 + 可见区虚拟化;复用 meow_list_source)
* meow_lazycolumn_create=纵向(item_h=行高);meow_lazyrow_create=横向(item_w=列宽)。
* 滚动由 App 在 on_swipe 里用 meow_list_scroll_by/set_scroll 驱动。 */
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 *meow_lazyrow_create(int x, int y, int w, int h, int item_w, int count,
meow_list_source src, void *ctx);
void meow_list_set_scroll(meow_widget *w, int offset); /* clamp 到 [0, max_scroll] */
int meow_list_scroll(meow_widget *w);
int meow_list_max_scroll(meow_widget *w); /* max(0, count*item - viewport) */
void meow_list_scroll_by(meow_widget *w, int dy); /* 相对滚动(App on_swipe 用) */
int meow_list_set_item_h(meow_widget *w, int item); /* 改行高/列宽 */
int meow_list_hit_index(meow_widget *w, int x, int y); /* 命中项下标(含滚动换算);-1=未命中项/越界 */
/* ============================================================ */
/* §5.8.6 模态(App 层,非系统叠加):Dialog / BottomSheet
* 复用 §5.6 B③ 弹窗 / §5.7 B④ 底部面板模板(与 launcher s_dd_modal 同构),控件化。
* 阻塞式(内层 meow_input_pop 轮询,与 meow_ui_text_input 同构);打开前排空残留触摸。
* 关闭途径:点卡片内选项=确认、点卡片外=取消、左缘返回手势=取消。不压状态栏(y 钳制)。 */
/* ============================================================ */
/* ---- Dialog(弹窗:确认/选择/输入/信息)---- */
typedef enum { MEOW_DLG_CONFIRM, MEOW_DLG_SELECT, MEOW_DLG_INPUT, MEOW_DLG_INFO } meow_dlg_t;
typedef struct meow_dialog meow_dialog;
meow_dialog *meow_dialog_create(meow_dlg_t t, const char *title);
void meow_dialog_set_items(meow_dialog *d, const char *const *item, int n, int cur); /* SELECT 型 */
void meow_dialog_set_ime(meow_dialog *d, meow_ime_ctx *ime); /* INPUT 型 */
void meow_dialog_show(meow_dialog *d); /* 阻塞到确认/取消 */
int meow_dialog_result(meow_dialog *d); /* SELECT=选项下标;CONFIRM/INFO 确认=1、取消=0 */
void meow_dialog_close(meow_dialog *d); /* 程序化关闭(App 亦可驱动) */
void meow_dialog_free(meow_dialog *d);
/* CONFIRM 型便捷:点「确定」返回 1,取消/遮罩=0 */
int meow_dialog_confirm(meow_dialog *d);
/* ---- BottomSheet(底部面板:标题 + 选项列表;open 阻塞返回选中 idx/-1)---- */
typedef struct meow_sheet meow_sheet;
meow_sheet *meow_sheet_create(const char *title, int h);
int meow_sheet_add_item(meow_sheet *s, const char *label); /* 返回项下标 */
int meow_sheet_open(meow_sheet *s); /* 阻塞;返回选中 idx / -1=取消 */
void meow_sheet_free(meow_sheet *s);
/* ============================================================ */
/* §9 应用日志(统一日志 API,M2.51 最小可用版) */
/* ============================================================ */
+293
View File
@@ -0,0 +1,293 @@
/*
* meow_ui_dialog.c — §5.8.6 模态控件化(App 层,非系统叠加):Dialog / BottomSheet。
*
* 复用 §5.6 B③ 弹窗 / §5.7 B④ 底部面板模板(与 launcher s_dd_modal 同构):遮罩压暗 +
* 圆角卡片 + 标题 + 内容 + 操作;返回手势=取消;不压状态栏。阻塞式(内层 meow_input_pop
* 轮询,与 meow_ui_text_input 同构),打开前排空残留触摸(goodix 长尾免疫)。
* 命中一律用抬手最新坐标;即画即显(打开一次性绘制,仅高亮变化才重绘,逐帧不重画静态内容)。
*
* 属 App 层模态:不注册进系统顶条/通知中心渲染(系统级控件归属不动)。物理键在 B1 未下发
* 给 App(on_key 无键),故本层关闭途径=触摸 + 左缘返回手势;音量/电源高亮导航为后续批次。
*/
#include "meow_ui.h"
#include "meow_ui_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MEOW_MODAL_MAX_ITEMS 32
#define MEOW_MODAL_ROW 48
#define MEOW_MODAL_BTN 44
/* ---- Dialog ---- */
struct meow_dialog {
meow_dlg_t type;
char title[96];
char items[MEOW_MODAL_MAX_ITEMS][64];
int nitems;
int cur; /* SELECT 预选/高亮项 */
int result;
int done;
meow_ime_ctx ime; /* INPUT 型(拷贝) */
int has_ime;
};
/* 卡片几何:返回标题之后内容起点(行/按钮相对坐标)。 */
typedef struct { int dx,dy,dw,dh; int row_y,rowh; int btn_y,btn_w,btn_h; } dlg_geom;
static void dlg_calc(meow_dialog *d, const meow_theme *th, dlg_geom *g)
{
int W = meow_ui_width(), H = meow_ui_height();
int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0;
int pad = 24;
int tH = meow_ui_text_height(th->font_title);
int bH = meow_ui_text_height(th->font_body);
int select = (d->type == MEOW_DLG_SELECT);
int nrows = select ? d->nitems : 0;
int nmsg = (d->type == MEOW_DLG_INFO) ? (d->nitems > 0 ? d->nitems : 1)
: (select ? 0 : (d->nitems > 0 ? d->nitems : 1));
int have_btns = !select;
g->dw = 348; if (g->dw > W - 40) g->dw = W - 40;
g->dx = (W - g->dw) / 2;
int content = nrows * MEOW_MODAL_ROW + nmsg * (bH + 8);
g->dh = pad + tH + 12 + content + pad + (have_btns ? 16 + MEOW_MODAL_BTN : 0);
int avail = H - sb - 20;
if (g->dh > avail) g->dh = avail;
g->dy = (H - g->dh) / 2; if (g->dy < sb) g->dy = sb;
g->row_y = g->dy + pad + tH + 12;
g->rowh = (MEOW_MODAL_ROW < 0) ? 40 : MEOW_MODAL_ROW;
g->btn_y = g->row_y + content + 16;
g->btn_h = MEOW_MODAL_BTN;
g->btn_w = (g->dw - 2 * pad - 12) / 2;
}
static void dlg_paint(meow_dialog *d, const meow_theme *th)
{
int W = meow_ui_width(), H = meow_ui_height();
meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150); /* 遮罩压暗 */
dlg_geom g;
dlg_calc(d, th, &g);
int pad = 24;
meow_gfx_fill_round(g.dx, g.dy, g.dw, g.dh, th->radius_card, th->surfaceContainerHighest);
meow_ui_draw_rect_round(g.dx, g.dy, g.dw, g.dh, th->radius_card, th->outline);
meow_ui_text(g.dx + pad, g.dy + pad, d->title, th->font_title, th->onSurface);
if (d->type == MEOW_DLG_SELECT) {
for (int i = 0; i < d->nitems; i++) {
int ry = g.row_y + i * g.rowh;
if (ry + g.rowh > g.dy + g.dh - 12) break;
if (i == d->cur) {
meow_ui_fill_rect(g.dx + 8, ry, g.dw - 16, g.rowh, th->primary);
meow_ui_text(g.dx + pad, ry + (g.rowh - meow_ui_text_height(th->font_body)) / 2,
d->items[i], th->font_body, 0xFFFFFF);
} else {
meow_ui_text(g.dx + pad, ry + (g.rowh - meow_ui_text_height(th->font_body)) / 2,
d->items[i], th->font_body, th->onSurface);
}
meow_ui_fill_rect(g.dx + pad, ry + g.rowh - 1, g.dw - 2 * pad, 1, th->outlineVariant);
}
} else {
/* INFO/CONFIRM:正文行;INPUT:文本预览 */
int nmsg = (d->type == MEOW_DLG_INFO) ? (d->nitems > 0 ? d->nitems : 1)
: (d->nitems > 0 ? d->nitems : 1);
int y = g.row_y;
if (d->type == MEOW_DLG_INPUT) {
char buf[300];
snprintf(buf, sizeof buf, "%s", d->ime.buf[0] ? d->ime.buf : "(tap OK to edit...)");
meow_ui_text(g.dx + pad, y, buf, th->font_body, th->onSurfaceVariant);
} else {
for (int i = 0; i < nmsg; i++) {
if (i < d->nitems && d->items[i][0])
meow_ui_text(g.dx + pad, y, d->items[i], th->font_body, th->onSurfaceVariant);
y += meow_ui_text_height(th->font_body) + 8;
}
}
/* 取消(左,text 型)/ 确定(右,filled) */
int bx = g.dx + pad, cx = bx + g.btn_w + 12, by = g.btn_y;
meow_ui_draw_rect_round(bx, by, g.btn_w, g.btn_h, 10, th->primary);
meow_ui_text_center(bx + g.btn_w / 2, by + (g.btn_h - meow_ui_text_height(th->font_body)) / 2,
"CANCEL", th->font_body, th->primary);
meow_gfx_fill_round(cx, by, g.btn_w, g.btn_h, 10, th->primary);
meow_ui_text_center(cx + g.btn_w / 2, by + (g.btn_h - meow_ui_text_height(th->font_body)) / 2,
"OK", th->font_body, th->onPrimary);
}
}
meow_dialog *meow_dialog_create(meow_dlg_t t, const char *title)
{
meow_dialog *d = (meow_dialog *)calloc(1, sizeof *d);
if (!d) return NULL;
d->type = t;
if (title) { strncpy(d->title, title, sizeof d->title - 1); d->title[sizeof d->title - 1] = 0; }
d->cur = -1; d->result = 0; d->done = 0;
return d;
}
void meow_dialog_set_items(meow_dialog *d, const char *const *item, int n, int cur)
{
if (!d) return;
d->nitems = 0;
if (item) for (int i = 0; i < n && i < MEOW_MODAL_MAX_ITEMS; i++)
if (item[i]) { strncpy(d->items[d->nitems], item[i], 63); d->items[d->nitems][63] = 0; d->nitems++; }
d->cur = cur;
}
void meow_dialog_set_ime(meow_dialog *d, meow_ime_ctx *ime)
{
if (!d || !ime) return;
d->ime = *ime;
d->has_ime = 1;
}
void meow_dialog_close(meow_dialog *d)
{
if (d) d->done = 1;
}
void meow_dialog_show(meow_dialog *d)
{
if (!d) return;
const meow_theme *th = meow_theme_get();
/* INPUT 型若未带 ime,用空 ctx 兜底 */
if (d->type == MEOW_DLG_INPUT && !d->has_ime) {
memset(&d->ime, 0, sizeof d->ime);
d->ime.maxlen = 255;
}
meow_input_start();
meow_input_drain(); /* 排空残留触摸(goodix 长尾) */
d->done = 0; d->result = 0;
dlg_paint(d, th);
meow_ui_flush();
while (!d->done) {
meow_input_ev e;
while (meow_input_pop(&e)) {
if (e.type == MEOW_EV_TAP) {
int x = e.a, y = e.b;
dlg_geom g;
dlg_calc(d, th, &g);
int in_card = x >= g.dx && x < g.dx + g.dw && y >= g.dy && y < g.dy + g.dh;
if (!in_card) { d->done = 1; d->result = 0; break; } /* 遮罩外=取消 */
if (d->type == MEOW_DLG_SELECT) {
int idx = (y - g.row_y) / g.rowh;
if (idx >= 0 && idx < d->nitems) { d->cur = idx; d->result = idx; d->done = 1; }
break;
}
/* 非选择:按钮命中 */
int pad = 24, by = g.btn_y;
int bx = g.dx + pad, cx = bx + g.btn_w + 12;
if (y >= by && y < by + g.btn_h) {
if (x >= bx && x < bx + g.btn_w) { /* CANCEL */
d->done = 1; d->result = 0;
} else if (x >= cx && x < cx + g.btn_w) { /* OK */
if (d->type == MEOW_DLG_INPUT) {
d->result = meow_ui_text_input(&d->ime); /* 阻塞编辑 */
} else {
d->result = (d->type == MEOW_DLG_CONFIRM) ? 1 : 1;
}
d->done = 1;
}
break;
}
} else if (e.type == MEOW_EV_SWIPE && e.a == MEOW_SWIPE_LEFT) {
d->done = 1; d->result = 0; /* 返回=取消 */
}
}
if (d->done) break;
usleep(16000);
}
}
int meow_dialog_result(meow_dialog *d) { return d ? d->result : 0; }
int meow_dialog_confirm(meow_dialog *d)
{
if (!d || d->type != MEOW_DLG_CONFIRM) return 0;
meow_dialog_show(d);
return d->result == 1;
}
void meow_dialog_free(meow_dialog *d) { if (d) free(d); }
/* ---- BottomSheet ---- */
struct meow_sheet {
char title[96];
char items[MEOW_MODAL_MAX_ITEMS][64];
int nitems;
int h;
int result;
int done;
};
static int sheet_h(meow_sheet *s, const meow_theme *th)
{
int H = meow_ui_height();
int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0;
int avail = H - sb - 20;
int title_h = meow_ui_text_height(th->font_title);
int min_h = 24 + title_h + 12 + MEOW_MODAL_ROW + 20;
int want = (s->h > 0) ? s->h : min_h;
int est = 24 + title_h + 12 + s->nitems * MEOW_MODAL_ROW + 20;
if (est > want) want = est;
if (want > avail) want = avail;
if (want < min_h) want = min_h < avail ? min_h : avail;
return want;
}
meow_sheet *meow_sheet_create(const char *title, int h)
{
meow_sheet *s = (meow_sheet *)calloc(1, sizeof *s);
if (!s) return NULL;
if (title) { strncpy(s->title, title, sizeof s->title - 1); s->title[sizeof s->title - 1] = 0; }
s->h = h > 0 ? h : 0;
return s;
}
int meow_sheet_add_item(meow_sheet *s, const char *label)
{
if (!s || s->nitems >= MEOW_MODAL_MAX_ITEMS) return -1;
if (label) { strncpy(s->items[s->nitems], label, 63); s->items[s->nitems][63] = 0; }
int idx = s->nitems;
s->nitems++;
return idx;
}
int meow_sheet_open(meow_sheet *s)
{
if (!s) return -1;
const meow_theme *th = meow_theme_get();
meow_input_start();
meow_input_drain();
s->done = 0; s->result = -1;
int W = meow_ui_width(), H = meow_ui_height();
int ph = sheet_h(s, th);
int py = H - ph, pad = 24;
/* 首次绘制:遮罩 + 底部面板(顶部圆角) */
meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150);
meow_gfx_fill_round(0, py, W, ph, th->radius_card, th->surfaceContainerHighest);
meow_ui_text(pad, py + pad, s->title, th->font_title, th->onSurface);
for (int i = 0; i < s->nitems; i++) {
int ry = py + pad + meow_ui_text_height(th->font_title) + 12 + i * MEOW_MODAL_ROW;
if (ry + MEOW_MODAL_ROW > H - 8) break;
meow_ui_text(pad, ry + (MEOW_MODAL_ROW - meow_ui_text_height(th->font_body)) / 2,
s->items[i], th->font_body, th->onSurface);
meow_ui_fill_rect(pad, ry + MEOW_MODAL_ROW - 1, W - 2 * pad, 1, th->outlineVariant);
}
meow_ui_flush();
while (!s->done) {
meow_input_ev e;
while (meow_input_pop(&e)) {
if (e.type == MEOW_EV_TAP) {
int y = e.b;
if (y < py) { s->done = 1; s->result = -1; break; } /* 点面板外=取消 */
int title_h = meow_ui_text_height(th->font_title);
int cy = py + pad + title_h + 12;
int idx = (y - cy) / MEOW_MODAL_ROW;
if (idx >= 0 && idx < s->nitems) { s->result = idx; s->done = 1; }
break;
} else if (e.type == MEOW_EV_SWIPE && e.a == MEOW_SWIPE_LEFT) {
s->done = 1; s->result = -1;
}
}
if (s->done) break;
usleep(16000);
}
return s->result;
}
void meow_sheet_free(meow_sheet *s) { if (s) free(s); }
+99 -1
View File
@@ -311,7 +311,6 @@ void meow_ui_fill_rect(int x, int y, int w, int h, uint32_t color)
{
if (!g_init_ok) return;
if (w <= 0 || h <= 0) return;
int x0 = x, y0 = y;
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
if (x >= g_xres || y >= g_yres) return;
@@ -394,6 +393,105 @@ void meow_ui_draw_rect_round(int x, int y, int w, int h, int r, uint32_t color)
dirty_add(x1, y1, x2 - x1, y2 - y1);
}
/* ================= §5.8.6 batch2:圆形/线段/圆弧环(软渲,无 -lm) =================
* 无 GPU 合成、构建只链 libz+pthread(无 -lm),故不用 math.h:COS 用象限对称查表、
* 圆用 bbox 距离测试、弧带用「沿弧采样成粗线(圆帽)」近似。 */
static const int m_cos90[91] = {
1024,1024,1023,1023,1022,1020,1018,1016,
1014,1011,1008,1005,1002,998,994,989,
984,979,974,968,962,956,949,943,
935,928,920,912,904,896,887,878,
868,859,849,839,828,818,807,796,
784,773,761,749,737,724,711,698,
685,672,658,644,630,616,602,587,
573,558,543,527,512,496,481,465,
449,433,416,400,384,367,350,333,
316,299,282,265,248,230,213,195,
178,160,143,125,107,89,71,54,
36,18,0,
};
static int m_cos_deg(int deg) /* 任意角度(可负)→ cos(deg)*1024 */
{
int a = deg % 360;
if (a < 0) a += 360;
if (a <= 90) return m_cos90[a];
if (a <= 180) return -m_cos90[180 - a];
if (a <= 270) return -m_cos90[a - 180];
return m_cos90[360 - a];
}
static int m_sin_deg(int deg) { return m_cos_deg(90 - deg); }
void meow_gfx_fill_circle(int cx, int cy, int r, uint32_t color)
{
if (!g_init_ok || r <= 0) return;
int x1 = cx - r, y1 = cy - r, x2 = cx + r + 1, y2 = cy + r + 1;
if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0;
if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres;
if (x2 <= x1 || y2 <= y1) return;
int r2 = r * r;
for (int py = y1; py < y2; py++) {
int dy = py - cy;
for (int px = x1; px < x2; px++) {
int dx = px - cx;
if (dx * dx + dy * dy <= r2) put_px(px, py, color);
}
}
dirty_add(x1, y1, x2 - x1, y2 - y1);
}
void meow_gfx_line(int x0, int y0, int x1, int y1, int th, uint32_t color)
{
if (!g_init_ok) return;
int dx = x1 - x0, dy = y1 - y0;
int adx = dx < 0 ? -dx : dx, ady = dy < 0 ? -dy : dy;
int steps = (adx > ady ? adx : ady) + 1;
if (steps <= 1) { meow_gfx_fill_circle(x0, y0, th / 2 + 1, color); return; }
int cap = th / 2;
if (cap < 1) cap = 1;
for (int i = 0; i <= steps; i++) {
int x = x0 + dx * i / steps;
int y = y0 + dy * i / steps;
if (cap <= 1) put_px(x, y, color);
else meow_gfx_fill_circle(x, y, cap, color);
}
}
void meow_gfx_ring_arc(int cx, int cy, int r, int th, int start_deg, int sweep_deg, uint32_t color)
{
if (!g_init_ok || r <= 0) return;
int sw = sweep_deg % 360;
if (sw < 0) sw += 360;
if (sw >= 360) { /* 整环:bbox 距离带(无角度判定,最快) */
int r1 = r - th / 2, r2 = r + th / 2;
if (r1 < 0) r1 = 0;
int x1 = cx - r2, y1 = cy - r2, x2 = cx + r2 + 1, y2 = cy + r2 + 1;
if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0;
if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres;
if (x2 <= x1 || y2 <= y1) return;
for (int py = y1; py < y2; py++)
for (int px = x1; px < x2; px++) {
int dx = px - cx, dy = py - cy;
int d2 = dx * dx + dy * dy;
if (d2 >= r1 * r1 && d2 <= r2 * r2) put_px(px, py, color);
}
dirty_add(x1, y1, x2 - x1, y2 - y1);
return;
}
/* 弧带:沿弧采样相邻点、粗线连接(圆帽)近似。方向:deg 右(0)→上(90)→左(180)→下(270),顺时针。 */
int n = sw / 2 + 1;
if (n < 2) n = 2;
int px = cx + (m_cos_deg(start_deg) * r) / 1024;
int py = cy - (m_sin_deg(start_deg) * r) / 1024; /* 屏幕 y 向下 → sin 取负 */
for (int i = 1; i <= n; i++) {
int deg = start_deg + sw * i / n;
int nx = cx + (m_cos_deg(deg) * r) / 1024;
int ny = cy - (m_sin_deg(deg) * r) / 1024;
meow_gfx_line(px, py, nx, ny, th, color);
px = nx; py = ny;
}
}
void meow_ui_fill_rect_alpha(int x, int y, int w, int h, uint32_t color, int alpha)
{
if (!g_init_ok) return;
+7
View File
@@ -112,6 +112,13 @@ unsigned char *meow_ui_back_buf(void);
/* 实心圆角矩形(控件按钮底色用;公共 §3 只有空心 draw_rect_round)。[D3] */
void meow_gfx_fill_round(int x, int y, int w, int h, int r, uint32_t color);
/* §5.8.6 batch2:软渲圆形/线段/圆弧环原语(无 GPU、无 -lm,内部整数/查表实现)。
* 供 Radio(外圈/内点)、Badge(红点)、ProgressBar(环形轨道/进度弧)、Checkbox(勾线段)使用。 */
void meow_gfx_fill_circle(int cx, int cy, int r, uint32_t color); /* 实心圆(bbox 距离测试) */
void meow_gfx_line(int x0, int y0, int x1, int y1, int th, uint32_t color); /* 粗线(Bresenham + 圆帽) */
void meow_gfx_ring_arc(int cx, int cy, int r, int th, int start_deg,
int sweep_deg, uint32_t color); /* 圆弧环(查表采样成粗线) */
/* 是否运行于真实 fb 之上(0 = 头less/离屏,qemu/宿主)。 */
int meow_gfx_is_device(void);
+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:
+24
View File
@@ -0,0 +1,24 @@
/* test_host_stub.c — 宿主逻辑测试打桩:meow_font_* / meow_ui_global_render。
* 供 test_sb_collapse.c 链真实 meow_ui_gfx.o/meow_ui_sys.o 时提供(无 /dev/fb0、无字库的宿主环境)。
* 仅为宿主单测编译;不进 meow_ui.a(build_sdk.sh 的 OBJS 不含本文件)。 */
#include "meow_ui.h"
#include "meow_font.h" /* 原型(meow_ui_gfx.c include 的同款头) */
#include <string.h>
void meow_font_set_fb(unsigned w, unsigned h, unsigned pitch) { (void)w;(void)h;(void)pitch; }
int meow_font_load(const char *p, const char *i) { (void)p;(void)i; return -1; } /* 无库:g_mfont_ready=0 → 5x7 回退 */
void meow_font_unload(void) {}
int meow_font_reload_tier(int t, const char *p, const char *i) { (void)t;(void)p;(void)i; return -1; }
void meow_font_drawtext(uint16_t *fb, int x, int y, const char *s, uint16_t c, int size) { (void)fb;(void)x;(void)y;(void)s;(void)c;(void)size; }
int meow_font_width_text(const char *s, int size) { (void)size; return (int)strlen(s) * 6; }
int meow_font_line_height(int size) { (void)size; return 20; }
int meow_font_config_multifont(int on, const char *gp, const char *gi,
const meow_font_class_cfg cfg[MEOW_FCLS_COUNT],
int *distinct, int *truncated)
{ (void)on;(void)gp;(void)gi;(void)cfg;(void)distinct;(void)truncated; return -1; }
/* present() 的 App 跨 Activity 全局渲染骨架(宿主无控件,no-op) */
void meow_ui_global_render(void) {}
/* meow_ui_run 触摸分流调用 meow_ui_global_hit(全局控件命中;宿主无全局控件 → 不消费) */
int meow_ui_global_hit(int x, int y) { (void)x; (void)y; return 0; }
+180
View File
@@ -0,0 +1,180 @@
/* test_meow_ui_widgets.c — 宿主验证 §5.8.6 Material 子集纯逻辑(create/hit/state/滚动/选中)+
* 各组件绘制(5x7 字库兜底)dump PPM 目检。不依赖 /dev/fb0/字库(meow_font 打桩)。
*
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
* gcc -o test_meow_ui_widgets test_meow_ui_widgets.c test_host_stub.c \
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread
* 运行:MEOW_UI_DUMP=/tmp/b2.ppm ./test_meow_ui_widgets (可选,dump 屏幕)
*/
#include "meow_ui.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/* 测试数据源(列表容器) */
static int list_src(int idx, char *label, int len, void *ctx)
{
(void)ctx;
if (idx < 0 || idx >= 100) return 0;
snprintf(label, (size_t)len, "item %d", idx);
return 1;
}
static meow_widget *g_w[64];
static int g_wn = 0;
static meow_widget *W(meow_widget *w) { g_w[g_wn++] = w; return w; }
static void free_all(void)
{
for (int i = 0; i < g_wn; i++) meow_widget_free(g_w[i]);
g_wn = 0;
}
int main(void)
{
setenv("MEOW_UI_DUMP", "/tmp/b2.ppm", 1);
meow_ui_opts o = { 480, 720 };
assert(meow_ui_init(&o) == 0);
int xres = meow_ui_width(), yres = meow_ui_height();
assert(xres == 480 && yres == 720);
/* ---------- Button 五型:create_variant/set_variant + 命中 ---------- */
meow_widget *bf = W(meow_btn_create_variant(10, 10, 200, 56, "FILLED", MEOW_BTN_FILLED));
meow_widget *bt = W(meow_btn_create_variant(10, 80, 200, 56, "TONAL", MEOW_BTN_TONAL));
meow_widget *bo = W(meow_btn_create_variant(10, 150, 200, 56, "OUTLINED", MEOW_BTN_OUTLINED));
meow_widget *bx = W(meow_btn_create_variant(10, 220, 200, 56, "TEXT", MEOW_BTN_TEXT));
meow_widget *be = W(meow_btn_create_variant(10, 290, 200, 56, "ELEV", MEOW_BTN_ELEVATED));
assert(meow_btn_set_variant(bt, MEOW_BTN_TONAL) == 0); /* 同值 */
assert(meow_btn_set_variant(bo, MEOW_BTN_OUTLINED) == 0);
assert(meow_widget_hit(bf, 100, 30) == 1);
assert(meow_widget_hit(bf, 5, 5) == 0); /* 越界 */
meow_widget_set_enabled(bf, 0);
assert(meow_widget_hit(bf, 100, 30) == 0); /* 禁用不命中 */
meow_widget_set_enabled(bf, 1);
meow_widget_set_selected(be, 1);
assert(meow_widget_selected(be) == 1);
printf("PASS button-variant: create/set/hit/disabled/selected\n");
/* ---------- TextField ---------- */
char tfbuf[32] = "";
meow_widget *tf = W(meow_textfield_create(10, 360, 300, 44, "Type here...", tfbuf, 32, MEOW_IME_ASCII));
strcpy(tfbuf, "abc");
assert(strcmp(meow_textfield_text(tf), "abc") == 0);
assert(meow_widget_hit(tf, 20, 380) == 1);
meow_textfield_set_placeholder(tf, "placeholder");
printf("PASS textfield: create/text/placeholder/hit\n");
/* ---------- Card + content ---------- */
meow_widget *card = W(meow_card_create(240, 10, 220, 180));
meow_widget *inner = W(meow_btn_create_variant(260, 40, 180, 44, "IN CARD", MEOW_BTN_FILLED));
meow_card_set_content(card, inner);
assert(meow_widget_hit(card, 250, 20) == 1);
printf("PASS card: create/content/hit\n");
/* ---------- Divider / Badge ---------- */
meow_widget *dv = W(meow_divider_create(10, 470, 200, 0));
meow_widget *dv2 = W(meow_divider_create(230, 470, 8, 1));
meow_widget *bd = W(meow_badge_create(400, 100));
meow_badge_set(bd, 3);
meow_badge_set(bd, 0);
printf("PASS divider/badge: create/set\n");
/* ---------- Checkbox / Radio ---------- */
meow_widget *ck = W(meow_checkbox_create(300, 200));
meow_widget *rd = W(meow_radio_create(300, 250));
meow_widget_set_selected(ck, 1);
meow_widget_set_selected(rd, 1);
assert(meow_widget_selected(ck) == 1);
assert(meow_widget_hit(rd, 315, 265) == 1);
printf("PASS checkbox/radio: create/select/hit\n");
/* ---------- ProgressBar ---------- */
meow_widget *pg = W(meow_progress_create(10, 520, 300, 6, 40));
meow_progress_set(pg, 200); /* clamp 100(无 getter,仅不崩) */
meow_widget *pgc = W(meow_progress_circular_create(420, 520, 20, 66));
meow_progress_set(pgc, -1); /* 不定 */
printf("PASS progress: create/set linear+circular\n");
/* ---------- FAB ---------- */
meow_widget *fab = W(meow_fab_create(410, 620, 56, "+"));
assert(meow_widget_hit(fab, 430, 640) == 1);
printf("PASS fab: create/hit\n");
/* ---------- TopAppBar ---------- */
meow_widget *tab_ = W(meow_topappbar_create(0, 0, 480, 56, "Title"));
meow_topappbar_set_back(tab_, 1);
int a0 = meow_topappbar_add_action(tab_, "Save");
int a1 = meow_topappbar_add_action(tab_, "Edit");
assert(a0 == 0 && a1 == 1);
assert(meow_topappbar_hit(tab_, 10, 28) == MEOW_TOPBAR_BACK); /* 返回区 */
assert(meow_topappbar_hit(tab_, 440, 28) == 1); /* 右缘动作 "Edit"(最右) */
printf("PASS topappbar: create/back/action/hit\n");
/* ---------- NavigationBar ---------- */
meow_widget *nav = W(meow_navigationbar_create(0, 680, 480, 40));
int n0 = meow_navigationbar_add_item(nav, "Home");
int n1 = meow_navigationbar_add_item(nav, "Library");
int n2 = meow_navigationbar_add_item(nav, "Settings");
assert(n0 == 0 && n1 == 1 && n2 == 2);
meow_navigationbar_set_selected(nav, 2);
assert(meow_navigationbar_selected(nav) == 2);
assert(meow_navigationbar_hit(nav, 420, 700) == 2); /* 第 3 项 */
assert(meow_navigationbar_hit(nav, 10, 700) == 0);
printf("PASS navigationbar: add/select/hit\n");
/* ---------- TabBar ---------- */
meow_widget *tbb = W(meow_tabbar_create(0, 660, 480, 36));
meow_tabbar_add_tab(tbb, "Playlist");
meow_tabbar_add_tab(tbb, "Queue");
meow_tabbar_set_selected(tbb, 1);
assert(meow_tabbar_hit(tbb, 300, 678) == 1);
printf("PASS tabbar: add/select/hit\n");
/* ---------- 列表容器(LazyColumn 虚拟化 + 滚动) ---------- */
meow_widget *lc = W(meow_lazycolumn_create(20, 460, 260, 180, 40, 100, list_src, NULL));
assert(meow_list_scroll(lc) == 0);
assert(meow_list_max_scroll(lc) == 100 * 40 - 180); /* 4000-180=3820 */
meow_list_set_scroll(lc, 5000);
assert(meow_list_scroll(lc) == 3820); /* clamp 到 max */
meow_list_scroll_by(lc, -320);
assert(meow_list_scroll(lc) == 3500);
assert(meow_list_hit_index(lc, 30, 480) == ((480 - 460 + 3500) / 40)); /* 可见项换算 */
meow_list_set_item_h(lc, 50);
assert(meow_widget_hit(lc, 30, 470) == 1);
/* LazyRow 横向 */
meow_widget *lr = W(meow_lazyrow_create(20, 640, 380, 40, 60, 100, list_src, NULL));
meow_list_scroll_by(lr, 200);
assert(meow_list_hit_index(lr, 40, 660) == ((40 - 20 + 200) / 60));
printf("PASS lazy list: scroll clamp/virtualize/hit_index (col+row)\n");
/* ---------- Dialog / BottomSheet 纯逻辑(create/set/add;show 阻塞故不调) ---------- */
meow_dialog *dlg = meow_dialog_create(MEOW_DLG_SELECT, "Pick one");
const char *items[] = { "Alpha", "Beta", "Gamma" };
meow_dialog_set_items(dlg, items, 3, 1);
assert(meow_dialog_result(dlg) == 0); /* 未 show 默认 */
meow_dialog_free(dlg);
meow_dialog *dc = meow_dialog_create(MEOW_DLG_CONFIRM, "Sure?");
assert(meow_dialog_result(dc) == 0);
meow_dialog_free(dc);
meow_sheet *sh = meow_sheet_create("Options", 0);
int s0 = meow_sheet_add_item(sh, "Copy");
int s1 = meow_sheet_add_item(sh, "Delete");
assert(s0 == 0 && s1 == 1);
meow_sheet_free(sh);
printf("PASS dialog/sheet: create/set_items/add_item (blocking show skipped)\n");
/* ---------- 绘制所有控件到 back_buf(5x7 兜底),flush 出屏幕 ---------- */
const meow_theme *th = meow_theme_get();
meow_ui_clear(th->bg);
for (int i = 0; i < g_wn; i++) meow_widget_draw(g_w[i]);
meow_ui_flush();
printf("PASS draw: all %d widgets rendered, screen dumps to /tmp/b2.ppm\n", g_wn);
free_all();
meow_ui_shutdown();
printf("ALL PASS\n");
return 0;
}