From b712088daa6433cd706206124d9c30f9c4b0fdbc Mon Sep 17 00:00:00 2001 From: AskaEth Date: Fri, 4 Sep 2026 11:49:59 +0800 Subject: [PATCH] =?UTF-8?q?style(md3):=20=E5=85=A8=E9=9D=A2MD3=E5=AF=B9?= =?UTF-8?q?=E9=BD=90P0-P3=20=E2=80=94=20P0=E6=89=81=E5=B9=B3=E8=A1=8C?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E5=AE=B9=E5=99=A8=E5=8C=96(meow=5Flistview?= =?UTF-8?q?=5Fset=5Fcontainer=E5=AE=B9=E5=99=A8=E6=A8=A1=E5=BC=8F:surfaceC?= =?UTF-8?q?ontainer=E5=9C=86=E8=A7=92=E5=8D=A112dp+tonal=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E5=B1=82+=E5=8E=BB=E5=88=86=E9=9A=94=E7=BA=BF;sub=5Fpage/setti?= =?UTF-8?q?ngs=5Fpage/bg=5Fpage=E5=9C=86=E8=A7=92=E5=AE=B9=E5=99=A8)=20P1?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E8=83=B6=E5=9B=8A=E5=8C=96+shape=E4=BB=A4?= =?UTF-8?q?=E7=89=8C(extrasmall/small/medium/large/full)=20P2=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=B1=82=E8=A1=A5=E9=BD=90=20P3=E5=AD=90=E6=8E=A7?= =?UTF-8?q?=E4=BB=B6(Switch=20off-track/NavBar=20pill64=C3=9732/Checkbox24?= =?UTF-8?q?/=E5=AF=B9=E8=AF=9D=E6=A1=8628);=E5=AE=BF=E4=B8=BBtest=5Fmeow?= =?UTF-8?q?=5Fmd3=E7=AD=89=E5=85=A8=E8=BF=87,0warning;meow=5Fui.a=E5=8F=98?= =?UTF-8?q?M6.36=E9=9C=80=E9=87=8D=E7=BC=96+=E5=9B=9BApp=E9=87=8D=E9=93=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- patch_v2/meow_launcherd/bg_page.c | 4 +- patch_v2/meow_launcherd/settings_page.c | 54 +++++--- patch_v2/meow_launcherd/subpage.c | 5 +- patch_v2/sdk/include/meow_ui_widgets.h | 15 +++ patch_v2/sdk/meow_ui_widget.c | 169 ++++++++++++++++------- patch_v2/sdk/test_meow_md3.c | 171 ++++++++++++++++++++++++ 6 files changed, 343 insertions(+), 75 deletions(-) create mode 100644 patch_v2/sdk/test_meow_md3.c diff --git a/patch_v2/meow_launcherd/bg_page.c b/patch_v2/meow_launcherd/bg_page.c index 94c9e5f..0e5b756 100644 --- a/patch_v2/meow_launcherd/bg_page.c +++ b/patch_v2/meow_launcherd/bg_page.c @@ -226,9 +226,9 @@ static void bgm_row_draw(void *ctx, int idx, sub_row_t *r, int ry = cy + (ch - (th + tsub + 4)) / 2; /* 内容在卡内垂直居中(行中心=卡中心) */ if (r->label) meow_ui_text(rx, ry, r->label, 20, mt->onSurface); if (r->sub) meow_ui_text(rx, ry + th + 4, r->sub, 16, mt->onSurfaceVariant); - /* 行内杀按钮(圆角框 + 「杀」;error=红=杀) */ + /* 行内杀按钮(全形=胶囊(bh/2);「杀」;error=红=杀)——P1 按钮胶囊化 */ int bx, by, bw, bh; bgm_btn_rect(x, y, w, h, &bx, &by, &bw, &bh); - meow_ui_draw_rect_round(bx, by, bw, bh, 10, mt->error); + meow_ui_draw_rect_round(bx, by, bw, bh, bh / 2, mt->error); int bth = meow_ui_text_height(16); if (bth > bh) bth = bh; meow_ui_text_center(bx + bw / 2, by + (bh - bth) / 2, "杀", 16, mt->error); diff --git a/patch_v2/meow_launcherd/settings_page.c b/patch_v2/meow_launcherd/settings_page.c index e5d1fa6..401af4f 100644 --- a/patch_v2/meow_launcherd/settings_page.c +++ b/patch_v2/meow_launcherd/settings_page.c @@ -780,8 +780,8 @@ static void dd_modal_paint(const meow_ui_theme_t *th, int W, int H, int d_x, int int content_top, int content_bot, int dd_scroll) { meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150); - meow_gfx_fill_round(d_x, d_y, d_w, d_h, 16, th->surface_container); - meow_ui_draw_rect_round(d_x, d_y, d_w, d_h, 16, th->outline); + meow_gfx_fill_round(d_x, d_y, d_w, d_h, 28, th->surface_container); /* MD3 extraLarge 28(原 16 偏小) */ + meow_ui_draw_rect_round(d_x, d_y, d_w, d_h, 28, th->outline); if (mfont_ready) ui_draw(d_x + 18, d_y + 8, title, th->on_surface, 20); else draw_text(d_x + 18, d_y + 8, title, 2, th->on_surface); for (int i = 0; i < n; i++) { @@ -1094,6 +1094,11 @@ static int s_font_dropdown(const srow_t *r, char *out_val, int vlen) #define SET_TRACK_CY 48 /* 滑条 track 中心相对行顶 y(=SET_SLIDER_YOFF+3,与绘制对齐) */ #define SET_SLIDER_LY 12 /* 滑条行 label 顶相对行顶 y(上移给 track 留位) */ #define SET_SWITCH_POOL 12 +/* MD3 容器化(P0):设置页行=surface 上 surfaceContainer 圆角卡 + 行间空隙(去分隔线); + * 卡横向外边距=SET_CARD_X(8),卡在行高内上下对称内缩(行中心=卡中心,同主界面/ncm 歌单手法)。 */ +#define SET_ROW_CARD_X SET_CARD_X /* 8:卡左缘 = 内容区左缘 */ +#define SET_ROW_CARD_G 8 /* 卡纵向间隙(px) */ +#define SET_ROW_CARD_R 12 /* 卡圆角(12dp) */ static meow_widget *g_set_lv = NULL; /* meow_listview(虚拟化) */ static meow_scroll *g_set_sc = NULL; /* 滚动物理 */ @@ -1155,24 +1160,30 @@ static void set_lv_draw(int idx, int x, int y, int w, int h, int state, void *ct uint32_t textcol = sel ? mt->onPrimaryContainer : mt->onSurface; uint32_t subcol = sel ? mt->onPrimaryContainer : mt->onSurfaceVariant; + /* MD3 容器化(P0):行=surface 上 surfaceContainer 圆角卡;段头=surface 上的 primary 段标签(无卡)。 */ + int cx = x + SET_ROW_CARD_X; /* 卡左缘 */ + int cw = w - 2 * SET_ROW_CARD_X; /* 卡宽 */ + int cy = y + SET_ROW_CARD_G / 2; /* 卡顶(行内对称内缩) */ + int ch = SET_ITEM_H - SET_ROW_CARD_G; /* 卡高 */ + if (r->ctrl == SC_HEADER) { - meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->surfaceContainerHigh); int ty = y + (SET_ITEM_H - thh) / 2; - if (ty >= 0) meow_ui_text(x + 16, ty, s_row_label(r), mt->font_body, mt->primary); - meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant); + if (ty >= 0) meow_ui_text(cx + 16, ty, s_row_label(r), mt->font_body, mt->primary); return; } - if (sel) meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->primaryContainer); - else if (press) meow_ui_fill_rect_alpha(x, y, w, SET_ITEM_H, mt->primary, 26); + meow_gfx_fill_round(cx, cy, cw, ch, SET_ROW_CARD_R, mt->surfaceContainer); + if (sel) meow_gfx_fill_round(cx, cy, cw, ch, SET_ROW_CARD_R, mt->primaryContainer); + else if (press) meow_gfx_fill_round_alpha(cx, cy, cw, ch, SET_ROW_CARD_R, mt->primary, 26); int ly = y + (SET_ITEM_H - thh) / 2; /* label 单行垂直居中 */ + int rgt = cx + cw - 16; /* 卡内右缘(内容右对齐锚点) */ switch (r->ctrl) { case SC_SWITCH: { - meow_ui_text(x + 16, ly, s_row_label(r), mt->font_body, textcol); + meow_ui_text(cx + 16, ly, s_row_label(r), mt->font_body, textcol); meow_widget *sw = set_sw_take(); if (sw) { int on = 0; meow_settings_get_switch(r->key, &on); - meow_widget_set_rect(sw, x + w - 16 - 56, y + (SET_ITEM_H - 32) / 2, 56, 32); + meow_widget_set_rect(sw, rgt - 56, y + (SET_ITEM_H - 32) / 2, 56, 32); meow_switch_set(sw, on); meow_widget_draw(sw); } @@ -1180,9 +1191,9 @@ static void set_lv_draw(int idx, int x, int y, int w, int h, int state, void *ct } case SC_SLIDER: { /* label 上移一行,track 放行下方(中心=SET_SLIDER_YOFF+3=48),与拖动命中带对齐 */ - meow_ui_text(x + 16, y + SET_SLIDER_LY, s_row_label(r), mt->font_body, textcol); + meow_ui_text(cx + 16, y + SET_SLIDER_LY, s_row_label(r), mt->font_body, textcol); char vb[32]; s_fmt_value(r, vb, sizeof vb); - meow_ui_text(x + w - 16 - meow_ui_text_width(vb, mt->font_body), y + SET_SLIDER_LY, vb, mt->font_body, subcol); + meow_ui_text(rgt - meow_ui_text_width(vb, mt->font_body), y + SET_SLIDER_LY, vb, mt->font_body, subcol); int v = 0; meow_settings_get_int(r->key, &v); /* 息屏时间为档位索引型(秒→索引 等距画);其余 clamp 到 [min,max] */ if (!strcmp(r->key, "display.screen_off_timeout")) v = s_time_tier_idx(v); @@ -1191,30 +1202,29 @@ static void set_lv_draw(int idx, int x, int y, int w, int h, int state, void *ct break; } case SC_INFO: { - meow_ui_text(x + 16, ly, s_row_label(r), mt->font_body, textcol); + meow_ui_text(cx + 16, ly, s_row_label(r), mt->font_body, textcol); char vb[48]; s_net_get(r, vb, sizeof vb); - meow_ui_text(x + w - 16 - meow_ui_text_width(vb, mt->font_body), ly, vb, mt->font_body, subcol); + meow_ui_text(rgt - meow_ui_text_width(vb, mt->font_body), ly, vb, mt->font_body, subcol); break; } case SC_LIST: { - meow_ui_text(x + 16, ly, s_row_label(r), mt->font_body, textcol); + meow_ui_text(cx + 16, ly, s_row_label(r), mt->font_body, textcol); char vb[48]; s_net_get(r, vb, sizeof vb); - int vx = x + w - 16 - 12 - meow_ui_text_width(vb, mt->font_body); + int vx = rgt - 12 - meow_ui_text_width(vb, mt->font_body); meow_ui_text(vx, ly, vb, mt->font_body, subcol); - s_draw_chevron(x + w - 16, y + SET_ITEM_H / 2, subcol); + s_draw_chevron(rgt, y + SET_ITEM_H / 2, subcol); break; } default: { /* SC_ENUM / SC_PRESET */ - meow_ui_text(x + 16, ly, s_row_label(r), mt->font_body, textcol); + meow_ui_text(cx + 16, ly, s_row_label(r), mt->font_body, textcol); char vb[40]; s_display_value(r, vb, sizeof vb); - int vx = x + w - 16 - meow_ui_text_width(vb, mt->font_body); + int vx = rgt - meow_ui_text_width(vb, mt->font_body); meow_ui_text(vx, ly, vb, mt->font_body, subcol); s_draw_chevron(vx - 8, y + SET_ITEM_H / 2, subcol); break; } } - meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant); - if (dis) meow_ui_fill_rect_alpha(x, y, w, SET_ITEM_H, mt->onSurface, 90); + if (dis) meow_gfx_fill_round_alpha(cx, cy, cw, ch, SET_ROW_CARD_R, mt->onSurface, 90); } /* ---- meow_scroll 回调(包 listview)---- */ @@ -1229,7 +1239,7 @@ static void set_redraw_region(void *ctx) int lvx, lvy, lvw, lvh; set_lv_rect(&lvx, &lvy, &lvw, &lvh); if (lvh <= 0) return; const meow_ui_theme_t *th = s_ui_theme(); - meow_ui_fill_rect(lvx, lvy, lvw, lvh, th->surface_variant); + meow_ui_fill_rect(lvx, lvy, lvw, lvh, th->surface); /* P0:内容底改 surface(卡在其上抬升) */ if (g_set_lv) { g_set_sw_use = 0; meow_widget_draw(g_set_lv); } meow_dirty_add(lvx, lvy, lvw, lvh); meow_ui_flush(); @@ -1274,7 +1284,7 @@ static void set_paint_page(int sel) if (mfont_ready) ui_draw(24, 50, meow_str_zh("menu.settings", "系统设置"), th->on_surface, 28); else draw_text(24, 30, "SETTINGS", 3, th->on_surface); fill_rect(0, 50 + (mfont_ready ? meow_font_line_height(28) : 24) + 2, W, 2, th->outline); - fill_rect(0, SET_CARD_Y, W, H - SET_CARD_Y, th->surface_variant); + fill_rect(0, SET_CARD_Y, W, H - SET_CARD_Y, th->surface); /* P0:内容底 surface,行用圆角卡抬升 */ if (g_set_lv) { g_set_sw_use = 0; int p = s_vis_of(sel); if (p >= 0) meow_listview_set_sel(g_set_lv, p); diff --git a/patch_v2/meow_launcherd/subpage.c b/patch_v2/meow_launcherd/subpage.c index 7766880..35b9f8a 100644 --- a/patch_v2/meow_launcherd/subpage.c +++ b/patch_v2/meow_launcherd/subpage.c @@ -619,7 +619,7 @@ static void sp_paint_top_action(void) if (w <= 0 || h <= 0) return; const meow_theme *mt = meow_theme_get(); uint32_t col = mt->primary; - meow_ui_draw_rect_round(x, y, w, h, 10, col); /* 圆角描边按钮 */ + meow_ui_draw_rect_round(x, y, w, h, h / 2, col); /* 全形=胶囊描边按钮(P1) */ int th = meow_ui_text_height(20); if (th > h) th = h; meow_ui_text_center(x + w / 2, y + (h - th) / 2, g_sp->top_action, 20, col); @@ -658,6 +658,9 @@ static void sp_widgets_create(void) /* 双行富 item + 无左图标 + 右尾件(SWITCH/ARROW);行高 56 */ meow_listview_set_item_renderer(g_lv, 2, 0, 1); meow_listview_set_item_h(g_lv, SUB_ITEM_H); + /* MD3(P0):默认 renderer 容器模式——行=surfaceContainer 圆角卡+行间空隙, + * 选中/按压 tonal(primaryContainer/primary α26)、去冗余分隔线;段头=surface 上的 primary 段标签。 */ + meow_listview_set_container(g_lv, 1); } /* 注:刻意不 set_snap —— SDK meow_scroll 的 snap 以「fling 起点」为吸附目标,快速滑动时会 * 从自然停点往回拉一整行(实测 697→336),UX 不佳;默认 ease-out 惯性停(无 snap)更顺滑。 diff --git a/patch_v2/sdk/include/meow_ui_widgets.h b/patch_v2/sdk/include/meow_ui_widgets.h index 1e3c091..fbbe544 100644 --- a/patch_v2/sdk/include/meow_ui_widgets.h +++ b/patch_v2/sdk/include/meow_ui_widgets.h @@ -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); diff --git a/patch_v2/sdk/meow_ui_widget.c b/patch_v2/sdk/meow_ui_widget.c index 5c0fc47..89d89a0 100644 --- a/patch_v2/sdk/meow_ui_widget.c +++ b/patch_v2/sdk/meow_ui_widget.c @@ -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); } diff --git a/patch_v2/sdk/test_meow_md3.c b/patch_v2/sdk/test_meow_md3.c new file mode 100644 index 0000000..918964e --- /dev/null +++ b/patch_v2/sdk/test_meow_md3.c @@ -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 +#include +#include +#include + +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; +}