From 9600f1035270d59da69ec368a33e35dfbff27260 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Thu, 5 Nov 2026 20:40:51 +0800 Subject: [PATCH] =?UTF-8?q?fix(hello):=20demo=20=E5=B7=A6=E6=BB=91?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E8=A1=8C=E9=AB=98=E6=8C=89=E5=AD=97=E4=BD=93?= =?UTF-8?q?=E8=A1=8C=E9=AB=98=E6=B4=BE=E7=94=9F=20=E2=80=94=20=E9=A6=96?= =?UTF-8?q?=E8=A1=8C=E6=A0=87=E9=A2=98=E4=B8=8D=E5=86=8D=E4=B8=A2=E5=A4=B1?= =?UTF-8?q?=EF=BC=88M17=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因(承接 M12/M15 finding):SDK meow_listview 双行 renderer 把 block = 2*meow_ui_text_height(font_body) + MEOW_LISTV_SUB_GAP(2) 在行内垂直居中 (ty = 行顶 + (item - block)/2),标题只在 ty >= 视口顶时才画。demo 的 swipe 列表 显式 set_item_renderer(2,1,0)(双行档位)却 set_item_h(48):真机 source_han_serif 20px 行高 30 → block 62 → ty = 行顶 - 7,首行「notif 0」标题被裁剪守卫整条跳过, 只剩 swipe left 副标题。 修复: - hello_internal.h:新增 HELLO_ROW_H_MIN(56) / HELLO_ROW_SUB_GAP(2,与 SDK MEOW_LISTV_SUB_GAP 同口径) + hello_row_h_for_line_h(line_h) = max(56, 2*line_h + 2) + hello_row_h()(取当前主题 font_body 的 meow_ui_text_height)。本地派生,不依赖 M15 新增导出 meow_listview_item_h; 显式传值在 M15 之前的 SDK(默认 48/56 档位)与 M15 之后(默认已派生 62)都成立且一致。 - ui/demo/sdk2/sdk2.c:set_item_h(48) 改传 hello_row_h()(行高 30 → 62)。 列表视口 LV_H / 三节布局推进量不变(行高只影响行内几何与滚动量),其它 demo 区块 (LazyColumn 列表、宫格、选择器)不受影响。 验证(宿主,未碰真机): - zig-mipsel-cc -Os -static -Wall -Wextra(另用 -Werror 复验)交叉编译 hello:0 warning; - 新宿主 test/test_row_text_fit.c(32 断言,纯头文件派生、不链 SDK): 字库行高矩阵 14/21/26/27/30 下 block <= item、首行 ty >= 0;行高 30 → 62; 旧 48 → ty=-7 的 bug 形态哨兵;全 PASS。 影响:meow_ui.a 未改 → 无需重编 SDK;本 App 需重链重部署(热更 hello 二进制)。 --- patch_v2/hello_app/hello_internal.h | 25 ++++++ patch_v2/hello_app/test/test_row_text_fit.c | 87 +++++++++++++++++++++ patch_v2/hello_app/ui/demo/sdk2/sdk2.c | 2 +- 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 patch_v2/hello_app/test/test_row_text_fit.c diff --git a/patch_v2/hello_app/hello_internal.h b/patch_v2/hello_app/hello_internal.h index fde5f96..1db5b1e 100644 --- a/patch_v2/hello_app/hello_internal.h +++ b/patch_v2/hello_app/hello_internal.h @@ -33,6 +33,31 @@ extern meow_sheet *g_sheet; /* BottomSheet(阻塞模态;main 创建 typedef struct { int x, y, w, h; } rect_t; #define RX(x,y,w,h) ((rect_t){ (x), (y), (w), (h) }) +/* ---- 两行列表行高:按字体行高派生(M17,首行标题不显示根因修复)---- + * SDK meow_listview 的双行渲染把「标题+副标题」文本块 + * block = 2 * meow_ui_text_height(font_body) + MEOW_LISTV_SUB_GAP(2) + * 在行内垂直居中(ty = 行顶 + (item - block) / 2),标题只在 ty >= 视口顶时才画。 + * item < block 时 ty = 行顶 - Δ:第一行(行顶 == 视口顶)标题被整条跳过、只剩副标题。 + * 真机 source_han_serif 20px(ascent 24 + descent 6)行高 30 → block 62;demo 的 swipe 列表 + * 是双行档位(set_item_renderer(2,...)),旧的显式 48 比 block 小 14 → 首行标题必丢。 + * 故行高按当前主题正文行高派生:max(HELLO_ROW_H_MIN, 2*line_h + 2)(行高 30 → 62)。 + * 行几何(draw / hit / max_scroll)全由 SDK 按传入的这一个值换算,滚动与命中天然同步。 */ +#define HELLO_ROW_H_MIN 56 +#define HELLO_ROW_SUB_GAP 2 /* 与 SDK MEOW_LISTV_SUB_GAP 同口径(标题/副标题行距) */ + +static inline int hello_row_h_for_line_h(int line_h) +{ + if (line_h < 1) line_h = 1; /* 字体未就绪 → 退回下限 */ + int h = 2 * line_h + HELLO_ROW_SUB_GAP; + return (h > HELLO_ROW_H_MIN) ? h : HELLO_ROW_H_MIN; +} +/* 当前主题正文行高下的两行列表行高(建列表时取一次;字体档位切换走内容重建)。 */ +static inline int hello_row_h(void) +{ + const meow_theme *th = meow_theme_get(); + return hello_row_h_for_line_h(th ? meow_ui_text_height(th->font_body) : 0); +} + /* ---- 视图控制器(ctx = biz-UI 绑定点)---- * 本批控制器为薄壳:持 meow_view 回指针;页面细粒度视图态(g_ds/g_drag_* /scroll 等)留在各页 * 模块作用域全局(设计定案 B:demo 视图态不迁 logic)。控制器未来可吸收这些状态。 */ diff --git a/patch_v2/hello_app/test/test_row_text_fit.c b/patch_v2/hello_app/test/test_row_text_fit.c new file mode 100644 index 0000000..284e738 --- /dev/null +++ b/patch_v2/hello_app/test/test_row_text_fit.c @@ -0,0 +1,87 @@ +/* test/test_row_text_fit.c — 宿主回归(M17):demo 的 swipe 列表行高必须容纳双行文本块。 + * + * 背景(真机根因,承接 M12/M15):SDK meow_listview 的双行渲染把 + * block = 2 * meow_ui_text_height(font_body) + 2 (标题行 + 2px 行距 + 副标题行) + * 在行内垂直居中:ty = 行顶 + (item - block) / 2;且标题只在 `ty >= 视口顶` 时才画(越界钳制)。 + * 行高 item < block 时 ty < 行顶 → **第一行(行顶 == 视口顶)标题被整条跳过**、副标题仍在。 + * 真机字库实测(source_han_serif 20px:ascent 24 + descent 6 → 行高 30)block = 62; + * demo 的 swipe 列表是双行档位(set_item_renderer(2,...))却显式 set_item_h(48) → ty = 行顶 - 7, + * 「notif 0」首行标题在真机上必然空白。 + * + * 本测试只验 app 侧行高派生(hello_row_h_for_line_h,纯整数、不链 SDK): + * 行高 = max(HELLO_ROW_H_MIN, 2*行高 + HELLO_ROW_SUB_GAP) → block ≤ item,首行标题不被钳掉。 + * + * 编译/运行(宿主 gcc,只需 SDK 头,不需要 meow_ui.a): + * cd patch_v2/hello_app + * gcc -O2 -Wall -Wextra -I. -I../sdk test/test_row_text_fit.c -o "$TMPDIR/test_row_text_fit" \ + * && "$TMPDIR/test_row_text_fit" + * + * 断言矩阵 = 真机已装字库 20px 面实测行高:source_han_serif 30(当前生效)、 + * misans/default 27、google_sans/meslo 26、source_han_sans 21;14 = 宿主 5x7 回退。 + * ① block ≤ item(否则居中偏移为负) + * ② 首行 ty ≥ 0 → 绝对 ty_abs ≥ 视口顶(标题不被裁剪守卫跳过) + * ③ 副标题底边 ty + block ≤ item(两行都不越行) + * ④ item ≥ HELLO_ROW_H_MIN 下限(小字库/字体未就绪不缩回固定小值) + * ⑤ 旧固定 48 在行高 30 下 ty < 0(bug 形态复现,防止再写死 48) + */ +#include "../hello_internal.h" +#include + +static int g_checks, g_fail; + +static void chk(int cond, int line_h, const char *what) +{ + g_checks++; + if (!cond) { + g_fail++; + printf(" FAIL lh=%d: %s\n", line_h, what); + } +} + +int main(void) +{ + static const int lh_all[] = { 14, 21, 26, 27, 30 }; + const int n = (int)(sizeof lh_all / sizeof lh_all[0]); + const int list_y = 400; /* 任意列表顶(只验相对几何) */ + + for (int k = 0; k < n; k++) { + int lh = lh_all[k]; + int item = hello_row_h_for_line_h(lh); + int block = 2 * lh + HELLO_ROW_SUB_GAP; + int ty = (item - block) / 2; /* SDK 双行文本块居中偏移 */ + + printf("lh=%2d item=%3d block=%3d ty=%d\n", lh, item, block, ty); + + chk(block <= item, lh, "文本块必须 <= 行高(否则首行标题被视口顶裁剪)"); + chk(ty >= 0, lh, "首行标题 y 不得高于行顶/视口顶"); + chk(ty + block <= item, lh, "副标题底边仍在行内"); + chk(item >= HELLO_ROW_H_MIN, lh, "行高不得小于 HELLO_ROW_H_MIN 下限"); + + /* 首行(scroll=0 → 行顶 == 视口顶)的绝对坐标 */ + chk(list_y + ty >= list_y, lh, "首行标题绝对 y 不得被视口顶裁掉"); + } + + /* 行高随字体行高单调不减 */ + chk(hello_row_h_for_line_h(30) >= hello_row_h_for_line_h(26), 30, "行高随字体行高单调不减"); + + /* 真机当前生效字库(source_han_serif 20px,行高 30):block 62 → item 62 */ + chk(hello_row_h_for_line_h(30) == 62, 30, "行高 30 → 派生行高 62"); + chk(2 * 30 + HELLO_ROW_SUB_GAP <= hello_row_h_for_line_h(30), 30, "派生行高容纳 62px 文本块"); + + /* 下限:小字库 / 字体未就绪(line_h=0)仍不缩回固定小值 */ + chk(hello_row_h_for_line_h(14) == HELLO_ROW_H_MIN, 14, "小字库退回 HELLO_ROW_H_MIN 下限"); + chk(hello_row_h_for_line_h(0) == HELLO_ROW_H_MIN, 0, "字体未就绪(行高 0)退回下限"); + + /* 回归哨兵:旧固定行高 48 + 当前默认字体(行高 30)就是 bug 形态 */ + { + const int lh = 30; + int block = 2 * lh + HELLO_ROW_SUB_GAP; /* 62 */ + int ty_legacy = (48 - block) / 2; /* -7 */ + chk(ty_legacy < 0, lh, "旧行高 48 + 行高 30 → ty<0(首行标题被跳过,bug 复现)"); + chk(hello_row_h_for_line_h(lh) > 48, lh, "默认字体下行高必须大于旧值 48"); + } + + printf("%s: %d checks, %d fail (font line-height matrix: %d)\n", + g_fail ? "FAIL" : "PASS", g_checks, g_fail, n); + return g_fail ? 1 : 0; +} diff --git a/patch_v2/hello_app/ui/demo/sdk2/sdk2.c b/patch_v2/hello_app/ui/demo/sdk2/sdk2.c index f98f172..5809818 100644 --- a/patch_v2/hello_app/ui/demo/sdk2/sdk2.c +++ b/patch_v2/hello_app/ui/demo/sdk2/sdk2.c @@ -266,7 +266,7 @@ int sdk2_build_at(int yb_base, int ct, int scroll) g_lv = meow_listview_create(r_lv.x, r_lv.y, r_lv.w, r_lv.h, 0, NULL, NULL, MEOW_LIST_SEL_NONE); if (g_lv) { - meow_listview_set_item_h(g_lv, 48); + meow_listview_set_item_h(g_lv, hello_row_h()); /* M17:双行文本块按字体行高派生 */ meow_listview_set_vsource(g_lv, lv_count, lv_bind, NULL); meow_listview_set_item_renderer(g_lv, 2, 1, 0); /* 双行、左图标、无右尾件 */ meow_listview_set_empty_text(g_lv, "All rows deleted");