style(md3): 全面MD3对齐P0-P3 — P0扁平行列表容器化(meow_listview_set_container容器模式:surfaceContainer圆角卡12dp+tonal状态层+去分隔线;sub_page/settings_page/bg_page圆角容器) P1按钮胶囊化+shape令牌(extrasmall/small/medium/large/full) P2状态层补齐 P3子控件(Switch off-track/NavBar pill64×32/Checkbox24/对话框28);宿主test_meow_md3等全过,0warning;meow_ui.a变M6.36需重编+四App重链

This commit is contained in:
AskaEth committed 2026-09-04 11:49:59 +08:00
1 parent 3fea237306
commit b712088daa
6 files changed
+343 -75

No files matched your search

+15
View File
@@ -146,6 +146,16 @@ enum {
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=成功 */
/* ---- MD3 shape 层级(§5.8.6,Material You 圆角体系,单位 px)----
* 组件按角色落到四档 + 胶囊:按钮=full(全高一半)、卡=large(16)、输入框=small(8)、
* 勾选=extrasmall(4)。theme.radius/radius_card 为风格族默认;App 可经 opts.radius 逐控件覆盖。
* 全高一半用 MEOW_SHAPE_FULL(h) 宏(控件的胶囊圆角)。 */
#define MEOW_SHAPE_EXTRASMALL 4 /* 勾选 Checkbox/Radio */
#define MEOW_SHAPE_SMALL 8 /* 输入框 TextField */
#define MEOW_SHAPE_MEDIUM 12 /* 主题默认 radius(列表卡/通用控件) */
#define MEOW_SHAPE_LARGE 16 /* 卡片/容器(theme.radius_card 默认) */
#define MEOW_SHAPE_FULL(h) ((h) / 2) /* 全形=胶囊(按钮/导航等) */
/* ---- 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,
@@ -384,6 +394,11 @@ int meow_listview_set_item_tier(meow_widget *lv, int lines);
/* 完全自绘:设 draw_item 回调(覆盖默认 renderer);NULL=用默认 renderer。返回 0=成功。 */
int meow_listview_set_item_draw(meow_widget *lv, meow_listview_draw_fn draw,
void *ctx);
/* MD3 容器模式(默认 renderer 可选,P0 对齐):0=扁平满宽行+分隔线(默认,兼容);
* 1=每行外套 surfaceContainer 圆角卡(12dp)+行间空隙,选中=primaryContainer/按压=primary α
* (tonal,非满幅实心),去冗余分隔线;段头=surface 上的 primary 段标签(无卡无分隔线)。
* 注意:仅对默认 renderer(未 set_item_draw)生效;完全自绘行由 App self-draw 自理。 */
int meow_listview_set_container(meow_widget *lv, int on);
/* 空态占位:count==0 时视口内居中画 empty_text(NULL 不画)。返回 0=成功。 */
int meow_listview_set_empty_text(meow_widget *lv, const char *text);
+119 -50
View File
@@ -116,6 +116,7 @@ struct meow_widget {
int show_icon; /* 1=画左图标/缩略图(默认 1) */
int show_right;/* 1=画右尾件(默认 1) */
char empty_text[64]; /* 空态占位文案(首字符 0=不画) */
int container; /* MD3 容器模式(默认 renderer):1=行=surfaceContainer 圆角卡+空隙 (P0) */
/* §5.5 左滑删除(列表行横向手势:滑动偏移/删除动画/回弹;calloc 归零,安全)。
* 手势由 App 在 on_touch_move 喂(swipe_begin/move/end);横向小位移判 swipe、纵向滚动优先;
@@ -141,6 +142,12 @@ static uint32_t widget_fg(const meow_widget *w, const meow_theme *th, uint32_t d
{ (void)th; return (w->opts_used && w->opts.fg) ? w->opts.fg : def; }
static int widget_radius(const meow_widget *w, int def)
{ return (w->opts_used && w->opts.radius >= 0) ? w->opts.radius : def; }
/* MD3 全形按钮圆角 = 胶囊(全高一半);opts.radius 显式优先(P1 shape 对齐,替代硬编码 10)。 */
static int widget_btn_radius(const meow_widget *w)
{
if (w->opts_used && w->opts.radius >= 0) return w->opts.radius;
return MEOW_SHAPE_FULL(w->h);
}
static int widget_pady(const meow_widget *w)
{ return (w->opts_used && w->opts.pad_y > 0) ? w->opts.pad_y : 0; }
@@ -1017,6 +1024,11 @@ int meow_list_tap_at(meow_widget *w, int x, int y)
#define MEOW_LISTV_ICON 24 /* 图标盒尺寸(px) */
#define MEOW_LISTV_PAD 16 /* 行内左右 padding(px) */
#define MEOW_LISTV_SUB_GAP 2 /* 标题与副标题间隙(px) */
/* MD3 容器模式(P0):行=surfaceContainer 圆角卡 + 行间空隙(去分隔线);卡图元与主界面/
* ncm 歌单同手法(main_menu.c:639 / playlist.c)。卡在行高内上下对称内缩(行中心=卡中心)。 */
#define MEOW_LISTV_CARD_MARGIN 8 /* 卡横向外边距(px) */
#define MEOW_LISTV_CARD_GAP 8 /* 卡纵向间隙(px) */
#define MEOW_LISTV_CARD_RADIUS 12 /* 卡圆角(px,12dp) */
/* meow_ui_img_scaled 弱引用:W_LISTVIEW 的 MEOW_LIST_ICON_IMG 分支用它绘制图片图标。
* 弱引用让「只用内置符号图标、不解码图片」的 App 不被牵连进 meow_ui_img.o/stb_image.o
@@ -1395,6 +1407,14 @@ int meow_listview_set_item_draw(meow_widget *w, meow_listview_draw_fn draw,
return 0;
}
int meow_listview_set_container(meow_widget *w, int on)
{
if (!w || w->type != W_LISTVIEW) return -1;
w->container = on ? 1 : 0;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
return 0;
}
int meow_listview_set_empty_text(meow_widget *w, const char *text)
{
if (!w || w->type != W_LISTVIEW) return -1;
@@ -1631,7 +1651,7 @@ void meow_widget_draw(meow_widget *w)
uint32_t bg;
if (fill) bg = (hold) ? state_tint(base, th, 24) : (press ? state_tint(base, th, 12) : base);
else bg = 0;
int r = widget_radius(w, 10);
int r = widget_btn_radius(w);
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 不影响中心) */
@@ -1693,11 +1713,15 @@ void meow_widget_draw(meow_widget *w)
break;
}
case W_SWITCH: {
uint32_t bg = w->on ? th->primary : th->onSurfaceVariant;
/* MD3(P3):on-track=primary;off-track=surfaceContainerHighest(原 onSurfaceVariant 过暗);
* on-thumb=onPrimary;off-thumb=surface + outline(原恒白)。 */
uint32_t bg = w->on ? th->primary : th->surfaceContainerHighest;
meow_gfx_fill_round(w->x, w->y, w->w, w->h, w->h / 2, bg);
int knob_r = w->h / 2 - 3;
int kx = w->on ? w->x + w->w - knob_r * 2 - 3 : w->x + 3;
meow_gfx_fill_round(kx, w->y + 3, knob_r * 2, knob_r * 2, knob_r, 0xFFFFFF);
uint32_t knob = w->on ? th->onPrimary : th->surface; /* off-thumb 表面色 */
meow_gfx_fill_round(kx, w->y + 3, knob_r * 2, knob_r * 2, knob_r, knob);
if (!w->on) meow_ui_draw_rect_round(kx, w->y + 3, knob_r * 2, knob_r * 2, knob_r, th->outline);
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, w->h / 2, th->onSurface, 90);
break;
}
@@ -1777,11 +1801,16 @@ void meow_widget_draw(meow_widget *w)
int vx0 = w->x, vy0 = w->y, vy1 = w->y + w->h;
int thh = meow_ui_text_height(th->font_body);
int pad = MEOW_LISTV_PAD, isz = MEOW_LISTV_ICON;
int text_x = vx0 + pad + ((w->show_icon) ? (isz + pad) : 0); /* 无图标则无图标间隙 */
int op_start = (w->show_right) ? (vx0 + (w->w * 2 / 3)) /* 行右 1/3 = 操作区 */
: (vx0 + w->w - pad);
/* 容器模式(P0):行=surfaceContainer 圆角卡,卡横向内缩 cm;内容左缘/操作区/可用宽
* 都在卡内计算(非容器 cm=0 → 与既有行为逐字一致)。 */
int cm = w->container ? MEOW_LISTV_CARD_MARGIN : 0;
int cw = w->w - 2 * cm; /* 内容/卡宽 */
int cx0 = vx0 + cm; /* 内容左缘(卡内) */
int text_x = cx0 + pad + ((w->show_icon) ? (isz + pad) : 0); /* 无图标则无图标间隙 */
int op_start = (w->show_right) ? (cx0 + (cw * 2 / 3)) /* 行右 1/3 = 操作区 */
: (cx0 + cw - pad);
int text_maxw = (w->show_right) ? (op_start - pad - text_x) /* 标题/副标题可用宽(不压操作区) */
: ((vx0 + w->w - pad) - text_x);
: ((cx0 + cw - pad) - text_x);
if (text_maxw < 8) text_maxw = 8;
/* 空态占位(§5.5):count==0 且设了 empty_text → 视口内居中画文案。 */
@@ -1841,26 +1870,36 @@ void meow_widget_draw(meow_widget *w)
meow_str("common.delete"), th->font_body, th->onError);
shift = w->swipe_dx;
}
int rlx = vx0 + shift; /* 行内容左移后的 x */
int rlx = (w->container ? cx0 : vx0) + shift; /* 行内容/卡左缘(随行左移) */
int rtx = text_x + shift; /* 文本 x(随行左移) */
int rop = op_start + shift; /* 操作区分界 x */
int card_y = ry + MEOW_LISTV_CARD_GAP / 2; /* 容器模式:卡顶(行内对称内缩) */
int card_h = item - MEOW_LISTV_CARD_GAP;
if (it->section) { /* 段头占位行(非粘性):surfaceContainerHigh 底 + primary 段名 */
meow_ui_fill_rect(rlx, cl, w->w, cr - cl, th->surfaceContainerHigh);
if (it->section) { /* 段头占位行(非粘性):容器=surface 上的 primary 段标签;非容器=surfaceContainerHigh 底 */
if (!w->container) meow_ui_fill_rect(rlx, cl, w->w, cr - cl, th->surfaceContainerHigh);
int sty = ry + (item - thh) / 2;
if (it->title && sty >= vy0 && sty + thh <= vy1)
listv_text_trunc(rtx, sty, it->title, text_maxw, th->font_body, th->primary);
int sy = ry + item - 1;
if (sy >= cl && sy < cr)
meow_ui_fill_rect(rlx, sy, w->w, 1, th->outlineVariant);
if (!it->enabled)
meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->onSurface, 90);
if (!w->container) {
int sy = ry + item - 1;
if (sy >= cl && sy < cr)
meow_ui_fill_rect(rlx, sy, w->w, 1, th->outlineVariant);
if (!it->enabled)
meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->onSurface, 90);
}
continue;
}
/* 选中/按压背景(primaryContainer / 半透明主题色按压层) */
if (sel) meow_ui_fill_rect(rlx, cl, w->w, cr - cl, th->primaryContainer);
else if (press) meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->primary, 26);
/* 选中/按压背景(容器=卡内 tonal;非容器=满幅实心,原行为) */
if (w->container) {
meow_gfx_fill_round(rlx, card_y, cw, card_h, MEOW_LISTV_CARD_RADIUS, th->surfaceContainer);
if (sel) meow_gfx_fill_round(rlx, card_y, cw, card_h, MEOW_LISTV_CARD_RADIUS, th->primaryContainer);
else if (press) meow_gfx_fill_round_alpha(rlx, card_y, cw, card_h, MEOW_LISTV_CARD_RADIUS, th->primary, 26);
} else {
if (sel) meow_ui_fill_rect(rlx, cl, w->w, cr - cl, th->primaryContainer);
else if (press) meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->primary, 26);
}
uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface;
uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
@@ -1890,19 +1929,25 @@ void meow_widget_draw(meow_widget *w)
/* 右尾件(show_right=1:箭头/开关/复选/⋯) */
if (w->show_right && it->right != MEOW_LIST_RIGHT_NONE) {
int ax = rlx + w->w - pad - 24; /* 右缘动作槽(约 24px) */
int ax = (cx0 + cw - pad - 24) + shift; /* 右缘动作槽(约 24px;容器=卡内右缘) */
if (ax >= rop)
listv_draw_right(th, ax, ry, item, it->right, it->on, subcol);
}
/* 分隔线(仅落在可见带内才画) */
int sy = ry + item - 1;
if (sy >= cl && sy < cr)
meow_ui_fill_rect(rlx, sy, w->w, 1, th->outlineVariant);
/* 分隔线(仅非容器画;容器模式去冗余分隔线,行间用空隙替代) */
if (!w->container) {
int sy = ry + item - 1;
if (sy >= cl && sy < cr)
meow_ui_fill_rect(rlx, sy, w->w, 1, th->outlineVariant);
}
/* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */
if (!it->enabled)
meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->onSurface, 90);
/* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗;容器=卡内淡) */
if (!it->enabled) {
if (w->container)
meow_gfx_fill_round_alpha(rlx, card_y, cw, card_h, MEOW_LISTV_CARD_RADIUS, th->onSurface, 90);
else
meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->onSurface, 90);
}
}
}
} else {
@@ -1918,10 +1963,13 @@ void meow_widget_draw(meow_widget *w)
int cl = ry > vy0 ? ry : vy0; /* 可见顶界(裁剪用) */
int cr = (ry + item) < vy1 ? (ry + item) : vy1; /* 可见底界 */
int sel = (i == w->sel);
int press = meow_input_touch_down() &&
meow_input_touch_x() >= vx0 && meow_input_touch_x() < vx0 + w->w &&
meow_input_touch_y() >= ry && meow_input_touch_y() < ry + item;
uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface;
uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
/* 左滑删除(M7.3):本行被滑动 → 右露删除区 + 行 x 左移。非滑动行 shift=0 → 输出不变。 */
int shift = 0, rlx = vx0, rtx = text_x, rop = op_start;
int shift = 0, rlx = (w->container ? cx0 : vx0), rtx = text_x, rop = op_start;
if (w->swipe_active && w->swipe_idx == i && w->swipe_dx < 0) {
meow_ui_fill_rect(vx0 + w->w + w->swipe_dx, ry, -w->swipe_dx, item, th->error);
int thh2 = meow_ui_text_height(th->font_body);
@@ -1929,9 +1977,19 @@ void meow_widget_draw(meow_widget *w)
if (dly >= vy0 && dly + thh2 <= vy1)
meow_ui_text_center(vx0 + w->w + w->swipe_dx + (-w->swipe_dx) / 2, dly,
meow_str("common.delete"), th->font_body, th->onError);
shift = w->swipe_dx; rlx = vx0 + shift; rtx = text_x + shift; rop = op_start + shift;
shift = w->swipe_dx; rlx = (w->container ? cx0 : vx0) + shift;
rtx = text_x + shift; rop = op_start + shift;
}
int card_y = ry + MEOW_LISTV_CARD_GAP / 2;
int card_h = item - MEOW_LISTV_CARD_GAP;
if (w->container) {
meow_gfx_fill_round(rlx, card_y, cw, card_h, MEOW_LISTV_CARD_RADIUS, th->surfaceContainer);
if (sel) meow_gfx_fill_round(rlx, card_y, cw, card_h, MEOW_LISTV_CARD_RADIUS, th->primaryContainer);
else if (press) meow_gfx_fill_round_alpha(rlx, card_y, cw, card_h, MEOW_LISTV_CARD_RADIUS, th->primary, 26);
} else {
if (sel) meow_ui_fill_rect(rlx, cl, w->w, cr - cl, th->primaryContainer);
else if (press) meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->primary, 26);
}
if (sel) meow_ui_fill_rect(rlx, cl, w->w, cr - cl, th->primaryContainer);
/* 图标(左) */
uint32_t iccol = sel ? th->onPrimaryContainer
@@ -1958,19 +2016,25 @@ void meow_widget_draw(meow_widget *w)
/* 右侧操作区 */
if (it.right != MEOW_LIST_RIGHT_NONE) {
int ax = rlx + w->w - pad - 24; /* 右缘动作槽(约 24px) */
int ax = (cx0 + cw - pad - 24) + shift; /* 右缘动作槽(约 24px;容器=卡内右缘) */
if (ax >= rop)
listv_draw_right(th, ax, ry, item, it.right, it.on, subcol);
}
/* 分隔线(仅落在可见带内才画) */
int sy = ry + item - 1;
if (sy >= cl && sy < cr)
meow_ui_fill_rect(rlx, sy, w->w, 1, th->outlineVariant);
/* 分隔线(仅非容器画;容器模式去冗余分隔线,行间用空隙替代) */
if (!w->container) {
int sy = ry + item - 1;
if (sy >= cl && sy < cr)
meow_ui_fill_rect(rlx, sy, w->w, 1, th->outlineVariant);
}
/* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */
if (!it.enabled)
meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->onSurface, 90);
/* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗;容器=卡内淡) */
if (!it.enabled) {
if (w->container)
meow_gfx_fill_round_alpha(rlx, card_y, cw, card_h, MEOW_LISTV_CARD_RADIUS, th->onSurface, 90);
else
meow_ui_fill_rect_alpha(rlx, cl, w->w, cr - cl, th->onSurface, 90);
}
}
}
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
@@ -1988,7 +2052,7 @@ void meow_widget_draw(meow_widget *w)
break;
case W_TEXTFIELD: {
/* MD3 输入框:圆角外框(surfaceContainerLowest 底 + outline 描边;focused=primary 2px)+ 提示/文本 + 光标。 */
int r = widget_radius(w, 8);
int r = widget_radius(w, MEOW_SHAPE_SMALL);
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;
@@ -2045,26 +2109,30 @@ void meow_widget_draw(meow_widget *w)
break;
}
case W_CHECKBOX: {
/* MD3(P3):视觉字 24dp 于控件矩形内居中(触摸目标不变);修正原 34 视觉过大。 */
int sel = (w->state & MEOW_WS_SELECTED) != 0;
int sz = w->w;
int r = widget_radius(w, 4);
int sz = 24;
if (w->w < sz) sz = w->w; /* 控件小于视觉尺寸时跟随 */
int bx = w->x + (w->w - sz) / 2, by = w->y + (w->h - sz) / 2;
int r = widget_radius(w, MEOW_SHAPE_EXTRASMALL);
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);
meow_gfx_fill_round(bx, by, sz, sz, r, th->primary);
int ax = bx + 7, ay1 = by + 13, ay2 = by + 17, b2 = bx + 11, cx = bx + 18, cy = by + 7;
meow_gfx_line(ax, ay1, b2, ay2, 2, th->onPrimary);
meow_gfx_line(b2, ay2, cx, cy, 2, th->onPrimary);
} else {
meow_ui_draw_rect_round(w->x, w->y, sz, sz, r, th->outline);
meow_ui_draw_rect_round(bx, by, sz, sz, r, th->outline);
}
if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, sz, sz, r, th->primary);
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, sz, sz, r, th->onSurface, 90);
if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(bx, by, sz, sz, r, th->primary);
if (disabled) meow_gfx_fill_round_alpha(bx, by, sz, sz, r, th->onSurface, 90);
break;
}
case W_RADIO: {
/* MD3(P3):视觉 20dp(环半径 10)于控件内居中;原环半径 12 略大。 */
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);
meow_gfx_ring_arc(ccx, ccy, 10, 2, 0, 360, sel ? th->primary : th->outline);
if (sel) meow_gfx_fill_circle(ccx, ccy, 5, th->primary);
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, w->w / 2, th->onSurface, 90);
break;
}
@@ -2154,7 +2222,8 @@ void meow_widget_draw(meow_widget *w)
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);
if (sel) /* MD3 active pill 64×32(原 48×22 偏小);垂直居中于栏 */
meow_gfx_fill_round(ix + iw / 2 - 32, w->y + (w->h - 32) / 2, 64, 32, 16, 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);
}
+171
View File
@@ -0,0 +1,171 @@
/* test_meow_md3.c — 宿主验证 MD3(Material You)对齐修复(agent-434 P0/P1/P3):
* P0 listview 容器模式(默认 renderer:行=surfaceContainer 圆角卡 + 行间空隙 + 去分隔线);
* P1 按钮胶囊化(圆角=h/2);
* P3 Switch off-track/off-thumb、NavigationBar active pill 64×32、Checkbox/Radio 视觉尺寸。
* 断言全部「画完读 back_buf 像素」(region_has/px_near),不依赖 /dev/fb0/字库(meow_font 打桩)。
*
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
* gcc -Wall -Wextra -o test_meow_md3 test_meow_md3.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 meow_ui_str.c \
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
* 运行:./test_meow_md3
*/
#include "meow_ui.h"
#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
static void rd_px(int x, int y, int *r, int *g, int *b)
{
unsigned char *bb = meow_ui_back_buf();
int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp();
if (!bb || x < 0 || y < 0) { *r = *g = *b = 0; return; }
if (bpp == 16) {
uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2);
*r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2;
*g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4;
*b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2);
} else {
uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4);
*r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF;
}
}
static int near2(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 12; }
static int px_near(int x, int y, int r, int g, int b)
{
int rr, gg, bb; rd_px(x, y, &rr, &gg, &bb);
return near2(rr, r) && near2(gg, g) && near2(bb, b);
}
#define UNPK(c, rr, gg, bb) do { (rr) = ((c) >> 16) & 0xFF; (gg) = ((c) >> 8) & 0xFF; (bb) = (c) & 0xFF; } while (0)
/* Listview src2 数据源:2 行普通富 item(无图标、无右尾件,label-only)。 */
typedef struct { const char *title; const char *sub; int right; int enabled; } trow_t;
static trow_t g_rows[2] = {
{ "Row 0", NULL, MEOW_LIST_RIGHT_NONE, 1 },
{ "Row 1", NULL, MEOW_LIST_RIGHT_NONE, 1 },
};
static int trow_src(int idx, meow_list_item *out, void *ctx)
{
(void)ctx;
if (idx < 0 || idx >= 2) return 0;
out->icon_kind = MEOW_LIST_ICON_NONE;
out->title = g_rows[idx].title;
out->subtitle = g_rows[idx].sub;
out->right = g_rows[idx].right;
out->on = 0;
out->enabled = g_rows[idx].enabled;
return 1;
}
int main(void)
{
setenv("MEOW_UI_DUMP", "/tmp/md3.ppm", 1);
meow_ui_opts o = { 480, 720 };
assert(meow_ui_init(&o) == 0);
assert(meow_ui_width() == 480 && meow_ui_height() == 720);
const meow_theme *th = meow_theme_get();
int sf_r, sf_g, sf_b, sc_r, sc_g, sc_b, p_r, p_g, p_b;
int pc_r, pc_g, pc_b, ov_r, ov_g, ov_b, sch_r, sch_g, sch_b, scs_r, scs_g, scs_b;
UNPK(th->surface, sf_r, sf_g, sf_b);
UNPK(th->surfaceContainer, sc_r, sc_g, sc_b);
UNPK(th->primary, p_r, p_g, p_b);
UNPK(th->primaryContainer, pc_r, pc_g, pc_b);
UNPK(th->outline, ov_r, ov_g, ov_b);
UNPK(th->surfaceContainerHighest, sch_r, sch_g, sch_b);
UNPK(th->secondaryContainer, scs_r, scs_g, scs_b);
/* ---- P0 listview 容器模式:行=surfaceContainer 圆角卡 + 空隙,去分隔线 ---- */
meow_widget *lv = meow_listview_create(20, 240, 320, 112, 2, trow_src, NULL, MEOW_LIST_SEL_SINGLE);
assert(lv);
assert(meow_listview_set_item_h(lv, 56) == 56);
assert(meow_listview_set_container(lv, 1) == 0);
meow_ui_clear(th->surface);
meow_widget_draw(lv);
/* 行 0 卡(x=28,y=244,w=304,h=48):中心=surfaceContainer;行 1 同。 */
assert(px_near(180, 268, sc_r, sc_g, sc_b));
assert(px_near(180, 324, sc_r, sc_g, sc_b));
/* 卡间空隙(y=292..300):surface(行间分隔用空隙)。 */
assert(px_near(180, 296, sf_r, sf_g, sf_b));
/* 去分隔线:行边界不再画 outlineVariant(改空隙代替)。 */
assert(!px_near(180, 292, ov_r, ov_g, ov_b));
/* 选中行 0 → primaryContainer(tonal,非满幅实心)。 */
meow_listview_set_sel(lv, 0);
meow_ui_clear(th->surface);
meow_widget_draw(lv);
assert(px_near(180, 268, pc_r, pc_g, pc_b));
assert(px_near(180, 324, sc_r, sc_g, sc_b)); /* 行 1 未选仍卡 */
printf("PASS listview container: rounded card + gap + no-divider + tonal select\n");
/* ---- P1 按钮胶囊化:圆角=h/2(56/2=28)---- */
meow_widget *btn = meow_btn_create_variant(10, 10, 200, 56, "FILLED", MEOW_BTN_FILLED);
assert(btn);
meow_ui_clear(th->surface);
meow_widget_draw(btn);
assert(px_near(110, 22, p_r, p_g, p_b)); /* 上部(避开文字)=primary */
/* 顶缘中心在圆角胶囊内(仍 primary);角落(圆角外)回 surface。 */
assert(px_near(110, 13, p_r, p_g, p_b));
assert(px_near(12, 12, sf_r, sf_g, sf_b)); /* 角落被圆角切掉 */
/* 同高矮按钮(h=40 → 圆角 20):上部仍 primary,角落 surface。 */
meow_widget *btn2 = meow_btn_create_variant(10, 120, 120, 40, "B", MEOW_BTN_FILLED);
assert(btn2);
meow_ui_clear(th->surface);
meow_widget_draw(btn2);
assert(px_near(70, 125, p_r, p_g, p_b));
assert(px_near(12, 122, sf_r, sf_g, sf_b));
printf("PASS button: capsule radius = h/2\n");
/* ---- P3 Switch off-track/off-thumb ---- */
meow_widget *sw = meow_switch_create(20, 300, 0); /* off */
assert(sw);
meow_ui_clear(th->surface);
meow_widget_draw(sw);
assert(px_near(60, 316, sch_r, sch_g, sch_b)); /* off-track=surfaceContainerHighest */
assert(px_near(36, 316, sf_r, sf_g, sf_b)); /* off-thumb=surface(表面色,非恒白) */
meow_switch_set(sw, 1);
meow_ui_clear(th->surface);
meow_widget_draw(sw);
assert(px_near(28, 316, p_r, p_g, p_b)); /* on-track=primary(thumb 左侧) */
printf("PASS switch: off-track surfaceContainerHighest / off-thumb surface\n");
/* ---- P3 NavigationBar active pill 64×32 ---- */
meow_widget *nb = meow_navigationbar_create(0, 600, 480, 80);
assert(nb);
meow_navigationbar_add_item(nb, "A");
meow_navigationbar_add_item(nb, "B");
meow_navigationbar_add_item(nb, "C");
meow_navigationbar_add_item(nb, "D");
meow_navigationbar_set_selected(nb, 0);
meow_ui_clear(th->surface);
meow_widget_draw(nb);
/* item0 pill 中心 (ix+60, y+40)=(60,640) — 64×32 居中于栏;pill 内=secondaryContainer。 */
assert(px_near(60, 640, scs_r, scs_g, scs_b));
/* pill 外(栏上部)= surfaceContainer(navbar 容器底)。 */
assert(px_near(60, 610, sc_r, sc_g, sc_b));
printf("PASS navbar: active pill 64x32 (was 48x22)\n");
/* ---- P3 Checkbox 视觉 24dp(原 34 偏大):勾选框视觉居中,角落回 surface ---- */
meow_widget *ck = meow_checkbox_create(150, 60);
assert(ck);
meow_ui_clear(th->surface);
meow_widget_draw(ck);
/* 未选:24dp 描边框(左缘=150+5=155);框边=outline;内部=surface(空心)。 */
assert(px_near(155, 77, ov_r, ov_g, ov_b)); /* 框左缘 stroke */
assert(px_near(167, 77, sf_r, sf_g, sf_b)); /* 内部空心 */
meow_widget_set_selected(ck, 1);
meow_ui_clear(th->surface);
meow_widget_draw(ck);
/* 选中:24dp 实心 primary 居中(左缘 155..179 顶 65..89);控件矩形角落(视觉 24 外)不填。 */
assert(px_near(150, 60, sf_r, sf_g, sf_b)); /* 视觉 24 外 → surface */
assert(px_near(167, 77, p_r, p_g, p_b)); /* 视觉中心=primary */
printf("PASS checkbox: 24dp visual (was 34)\n");
meow_widget_free(lv); meow_widget_free(btn); meow_widget_free(btn2);
meow_widget_free(sw); meow_widget_free(nb); meow_widget_free(ck);
printf("ALL MD3 PASS\n");
return 0;
}