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

+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);
}