test(sdk): 宿主回归 — --wrap 行高 30 下首行标题必须画出(listview 行高派生)
新 test_meow_ui_listview_rowh.c(35 断言)用链接期 -Wl,--wrap=meow_ui_text_height 把宿主 5x7 回退行高 14 换成真机 source_han_serif 20px 实测行高 30,驱动真实 SDK 渲染(同 M12 app/test/test_first_row_title_render.c 手法),断言全部读 back_buf 像素: - T0 wrap 生效(meow_ui_text_height(font_body) == 30); - T1 默认(不设 item_h):item_h == 62 = max(56, 2*30+2) ≥ 2*行高+间距; max_scroll/hit 按 62 换算;scroll=0 时首行标题带与副标题带都有文字像素; 视口上方无标题泄漏; - T2 裸 create(不调 renderer)同样派生;set_item_h(0) 忽略并保持派生; - T3 显式 set_item_h(70)(ncm 语义):item_h/hit/max_scroll 全按 70,首行标题可见; - T4 档位:tier(2) 恢复派生 62、tier(1) 48,单行档位不画副标题且标题可见; - T5 pool 模式(set_vsource + 默认 renderer):同样派生 62,首行标题可见; - T6 旧行为哨兵:显式 56 < block 62 时首行标题带 0 像素、副标题仍有 → 证明 像素断言能区分「画出/被跳过」,即 A/B 复现 bug 形态。 回归(宿主 gcc -O1 -Wall -Wextra,全部 0 warning): - 新 test:35 pass / 0 fail; - listvirt 44 / widgets ALL PASS / md3 ALL / scroll 17 / select 6 / swipe 35 / lazygrid 32 全 PASS; - 交叉 build_sdk_host.sh --verify:76 成员全 MIPS32r2 rel2,meow_ui_widget.c -Wall -Wextra 0 warning(meow_ui.a 变更 → 下游 App 重链)。
This commit is contained in:
1 parent
4d9ba46a7b
commit
31c12892d6
1 file changed
+247
@@ -0,0 +1,247 @@
|
||||
/* test_meow_ui_listview_rowh.c — 宿主回归(M15):listview 默认行高必须由字体行高派生,
|
||||
* 首行(scroll=0)标题必须真正画出来。
|
||||
*
|
||||
* 根因(M12 finding):双行 renderer 把「标题+副标题」文本块
|
||||
* block = 2 * meow_ui_text_height(font_body) + MEOW_LISTV_SUB_GAP(2)
|
||||
* 在行内垂直居中(ty = 行顶 + (item - block)/2),且文本只在 `ty >= 视口顶` 时才画。
|
||||
* 真机 source_han_serif 20px(ascent 24 + descent 6)行高 = 30 → block = 62;
|
||||
* 旧 SDK 默认行高固定 56 < 62 → ty = 行顶 - 3,首行(行顶 == 视口顶)标题被整条跳过,
|
||||
* 只剩副标题 → 所有 App 的两行列表首行标题消失。
|
||||
*
|
||||
* 宿主 5x7 字体行高是 14,照原样永远复现不了。故用链接期 `-Wl,--wrap=meow_ui_text_height`
|
||||
* 把行高替换成真机实测的 30,驱动同一份 SDK 代码(同 M12 app/test/test_first_row_title_render.c):
|
||||
* A 行高 30 + 显式 item_h=56 → 首行标题带 0 像素(bug 形态,防回归哨兵)
|
||||
* B 行高 30 + 不设 item_h → 派生 62 = block,首行标题/副标题都有像素
|
||||
* C 行高 30 + 显式 item_h=70 → ncm 语义(App 自管卡高)原样生效,标题可见
|
||||
* 断言全部读 back_buf 真实像素(region_has),非目检。
|
||||
*
|
||||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||||
* gcc -o test_meow_ui_listview_rowh test_meow_ui_listview_rowh.c test_host_stub.c \
|
||||
* meow_ui_gfx.c meow_ui_time.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 meow_ui_nav.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm \
|
||||
* -Wl,--wrap=meow_ui_text_height
|
||||
* 运行:./test_meow_ui_listview_rowh
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp:真实像素断言 */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* --wrap 替换实现:真机 source_han_serif 20px 实测行高(font.idx ascent+descent)。 */
|
||||
int __wrap_meow_ui_text_height(int size)
|
||||
{
|
||||
(void)size;
|
||||
return 30;
|
||||
}
|
||||
|
||||
#define LH 30 /* 真机正文行高 */
|
||||
#define BLOCK (2 * LH + 2) /* 双行文本块 = 62(与 MEOW_LISTV_SUB_GAP 同口径) */
|
||||
#define LV_X 24
|
||||
#define LV_Y 40
|
||||
#define LV_W 432
|
||||
#define LV_H 180
|
||||
#define NR 5 /* 行数 */
|
||||
|
||||
static int g_pass = 0, g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { g_pass++; } \
|
||||
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
|
||||
} while (0)
|
||||
|
||||
/* ---- back_buf 像素读取(画完即读,真实渲染断言) ---- */
|
||||
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 <= 14; }
|
||||
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);
|
||||
}
|
||||
static int region_has(int x, int y, int w, int h, int r, int g, int b)
|
||||
{
|
||||
int cnt = 0;
|
||||
for (int dy = 0; dy < h; dy++)
|
||||
for (int dx = 0; dx < w; dx++)
|
||||
if (px_near(x + dx, y + dy, r, g, b)) cnt++;
|
||||
return cnt;
|
||||
}
|
||||
#define UNPK(c, rr, gg, bb) do { (rr) = ((c) >> 16) & 0xFF; (gg) = ((c) >> 8) & 0xFF; (bb) = (c) & 0xFF; } while (0)
|
||||
|
||||
/* ---- 数据源:src2(每帧取行)与 pool(count/bind)各一套,行内容相同 ---- */
|
||||
static const char *const k_title[NR] = { "T1", "T2", "T3", "T4", "T5" };
|
||||
static const char *const k_sub[NR] = { "S1", "S2", "S3", "S4", "S5" };
|
||||
|
||||
static int src2_cb(int idx, meow_list_item *out, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (idx < 0 || idx >= NR) return 0;
|
||||
memset(out, 0, sizeof *out);
|
||||
out->title = k_title[idx];
|
||||
out->subtitle = k_sub[idx];
|
||||
out->right = MEOW_LIST_RIGHT_NONE;
|
||||
out->enabled = 1;
|
||||
return 1;
|
||||
}
|
||||
static int pool_count(void *ctx) { (void)ctx; return NR; }
|
||||
static void pool_bind(int idx, meow_lv_item *it, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
memset(it, 0, sizeof *it);
|
||||
if (idx < 0 || idx >= NR) return;
|
||||
it->title = k_title[idx];
|
||||
it->subtitle = k_sub[idx];
|
||||
it->enabled = 1;
|
||||
}
|
||||
|
||||
/* 造一个 src2 双行列表(扁平基线,便于按颜色断言行内文本带)。不设 item_h → 走派生。 */
|
||||
static meow_widget *make_lv(void)
|
||||
{
|
||||
meow_widget *lv = meow_listview_create(LV_X, LV_Y, LV_W, LV_H, NR,
|
||||
src2_cb, NULL, MEOW_LIST_SEL_NONE);
|
||||
if (!lv) return NULL;
|
||||
meow_listview_set_container(lv, 0);
|
||||
meow_listview_set_item_renderer(lv, 2, 0, 0); /* 双行、无图标、无尾件 → text_x = LV_X+16 */
|
||||
return lv;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
meow_ui_opts o = { 480, 720 };
|
||||
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
|
||||
const meow_theme *th = meow_theme_get();
|
||||
int os_r, os_g, os_b, osv_r, osv_g, osv_b;
|
||||
UNPK(th->onSurface, os_r, os_g, os_b);
|
||||
UNPK(th->onSurfaceVariant, osv_r, osv_g, osv_b);
|
||||
const int text_x = LV_X + 16; /* show_icon=0, container=0 */
|
||||
|
||||
/* ---- T0 --wrap 生效:宿主行高被替换为真机 30 ---- */
|
||||
CHECK(meow_ui_text_height(th->font_body) == LH, "wrap line_h=%d (expect %d)",
|
||||
meow_ui_text_height(th->font_body), LH);
|
||||
|
||||
/* ---- T1 默认(不设 item_h):行高按字体行高派生,首行标题可见 ---- */
|
||||
{
|
||||
meow_widget *lv = make_lv();
|
||||
CHECK(lv != NULL, "create");
|
||||
CHECK(meow_listview_item_h(lv) == BLOCK, "derived item_h=%d (expect %d = 2*%d+2)",
|
||||
meow_listview_item_h(lv), BLOCK, LH);
|
||||
CHECK(meow_listview_item_h(lv) >= 2 * LH + 2, "item_h >= 2*line_h + gap");
|
||||
CHECK(meow_listview_scroll(lv) == 0, "scroll=0");
|
||||
CHECK(meow_listview_max_scroll(lv) == NR * BLOCK - LV_H, "max_scroll=%d",
|
||||
meow_listview_max_scroll(lv));
|
||||
CHECK(meow_listview_hit(lv, LV_X + 10, LV_Y) == 0, "hit row0");
|
||||
CHECK(meow_listview_hit(lv, LV_X + 10, LV_Y + BLOCK) == 1, "hit row1 (item_h=%d)",
|
||||
meow_listview_item_h(lv));
|
||||
|
||||
int ty = LV_Y + (BLOCK - BLOCK) / 2; /* 62-62 → 首行标题顶 == 视口顶 */
|
||||
int sy = ty + LH + 2;
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv);
|
||||
CHECK(region_has(text_x, ty, 60, LH, os_r, os_g, os_b) > 2,
|
||||
"首行标题可见(scroll=0, ty=%d)", ty);
|
||||
CHECK(region_has(text_x, sy, 60, LH, osv_r, osv_g, osv_b) > 2,
|
||||
"首行副标题可见(sy=%d)", sy);
|
||||
CHECK(region_has(text_x, LV_Y - 20, 60, 20, os_r, os_g, os_b) == 0,
|
||||
"视口上方无标题泄漏");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ---- T2 裸 create(不调 renderer):默认 lines=2 同样派生 ---- */
|
||||
{
|
||||
meow_widget *lv = meow_listview_create(LV_X, LV_Y, LV_W, LV_H, NR,
|
||||
src2_cb, NULL, MEOW_LIST_SEL_NONE);
|
||||
CHECK(lv != NULL && meow_listview_item_h(lv) == BLOCK, "bare create item_h=%d",
|
||||
lv ? meow_listview_item_h(lv) : -1);
|
||||
/* set_item_h(<=0) 忽略:仍返回派生值 */
|
||||
CHECK(meow_listview_set_item_h(lv, 0) == BLOCK, "set_item_h(0) keeps derived");
|
||||
CHECK(meow_listview_item_h(lv) == BLOCK, "item_h unchanged after set_item_h(0)");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ---- T3 显式 set_item_h 优先(ncm 语义:App 按自身卡高传 70)---- */
|
||||
{
|
||||
meow_widget *lv = make_lv();
|
||||
CHECK(meow_listview_set_item_h(lv, 70) == 70, "explicit item_h returns 70");
|
||||
CHECK(meow_listview_item_h(lv) == 70, "explicit item_h=70 wins over derived %d", BLOCK);
|
||||
CHECK(meow_listview_max_scroll(lv) == NR * 70 - LV_H, "max_scroll uses 70");
|
||||
int ty = LV_Y + (70 - BLOCK) / 2; /* 4 */
|
||||
int sy = ty + LH + 2;
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv);
|
||||
CHECK(region_has(text_x, ty, 60, LH, os_r, os_g, os_b) > 2, "显式 70 首行标题可见");
|
||||
CHECK(region_has(text_x, sy, 60, LH, osv_r, osv_g, osv_b) > 2, "显式 70 首行副标题可见");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ---- T4 档位:tier 恢复派生默认(2→62 / 1→48),单行不画副标题 ---- */
|
||||
{
|
||||
meow_widget *lv = make_lv();
|
||||
CHECK(meow_listview_set_item_h(lv, 70) == 70, "pre-set explicit 70");
|
||||
CHECK(meow_listview_set_item_tier(lv, 2) == 0, "tier(2)");
|
||||
CHECK(meow_listview_item_h(lv) == BLOCK, "tier(2) 派生 item_h=%d", meow_listview_item_h(lv));
|
||||
CHECK(meow_listview_set_item_tier(lv, 1) == 0, "tier(1)");
|
||||
CHECK(meow_listview_item_h(lv) == 48, "tier(1) item_h=%d (expect 48)",
|
||||
meow_listview_item_h(lv));
|
||||
int ty = LV_Y + (48 - LH) / 2; /* 9 */
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv);
|
||||
CHECK(region_has(text_x, ty, 60, LH, os_r, os_g, os_b) > 2, "单行档位标题可见");
|
||||
CHECK(region_has(text_x, LV_Y + 41, 60, 6, osv_r, osv_g, osv_b) == 0,
|
||||
"单行档位不画副标题");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ---- T5 pool 模式(set_vsource + 默认 renderer):派生行高 + 首行标题可见 ---- */
|
||||
{
|
||||
meow_widget *lv = meow_listview_create(LV_X, LV_Y, LV_W, LV_H, 0,
|
||||
NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
meow_listview_set_container(lv, 0);
|
||||
CHECK(meow_listview_set_vsource(lv, pool_count, pool_bind, NULL) == 0, "vsource");
|
||||
CHECK(meow_listview_set_item_renderer(lv, 2, 0, 0) == 0, "renderer(2,0,0)");
|
||||
CHECK(meow_listview_item_h(lv) == BLOCK, "pool item_h=%d", meow_listview_item_h(lv));
|
||||
int ty = LV_Y + (BLOCK - BLOCK) / 2;
|
||||
int sy = ty + LH + 2;
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv);
|
||||
CHECK(region_has(text_x, ty, 60, LH, os_r, os_g, os_b) > 2, "pool 首行标题可见");
|
||||
CHECK(region_has(text_x, sy, 60, LH, osv_r, osv_g, osv_b) > 2, "pool 首行副标题可见");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
/* ---- T6 旧行为哨兵:显式 56(< block 62)→ 首行标题仍被裁剪守卫跳过 ----
|
||||
* 记录 bug 形态,证明上面的像素断言确实能区分「画出」与「跳过」;
|
||||
* 派生默认生效后这条路径只剩「App 明确要 56」时才会走到。 */
|
||||
{
|
||||
meow_widget *lv = make_lv();
|
||||
CHECK(meow_listview_set_item_h(lv, 56) == 56, "explicit 56");
|
||||
CHECK(meow_listview_item_h(lv) == 56, "explicit 56 stays 56");
|
||||
meow_ui_clear(th->bg);
|
||||
meow_widget_draw(lv);
|
||||
int ty = LV_Y + (56 - BLOCK) / 2; /* -3 → 行顶上方 */
|
||||
int sy = ty + LH + 2; /* 29 */
|
||||
CHECK(region_has(text_x, LV_Y, 60, LH, os_r, os_g, os_b) == 0,
|
||||
"旧 56 行高:首行标题被跳过(bug 形态)");
|
||||
CHECK(region_has(text_x, sy, 60, LH, osv_r, osv_g, osv_b) > 2,
|
||||
"旧 56 行高:首行副标题仍画(症状「只有副标题」)");
|
||||
meow_widget_free(lv);
|
||||
}
|
||||
|
||||
printf("%s: %d passed, %d failed\n", g_fail ? "FAIL" : "PASS", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Reference in new issue
Block a user