公开 SDK 落后主仓的一批 bugfix(主仓是固件构建源,这些修复已在设备上跑过)。 本次只做「主仓 → 公开仓」单向同步,图像栈方向由主仓侧另一单处理。 时钟 ABI(M16 第二弹,原文件自认「残余未修」): - meow_ui_time.c 147→345 行:futex 相对超时接管(__timedwait/_cp)、 timerfd_create/settime/gettime 的 itimerspec 64↔32 双向转换、 __clock_settime64 越界返 ENOTSUP,另有 4 个纯换算函数 - meow_ui_internal.h:struct meow_time32 / meow_itimerspec32 契约移入内部头 - CLOCK_ABI.md:补接管机制、验证配方与 objdump 证据 - 新增 test_meow_time64.c IME bug3(成对才有意义): - meow_ui_ime.c:IME_TAP_COOLDOWN_MS 200 冷却窗 + 打开时二次 drain - meow_ui_input.c:meow_input_touch_reset() 并在 drain 内调用(复位触摸状态机) - test_ime_fix.c:新增用例④ stray-tap 列表行高(M15,修用户可见 bug): - meow_ui_widget.c:listv_item_h() 由主题字体行高派生,不再写死 56 (两行列表 62 > 56 会让首行标题被裁剪守卫整条跳过) - include/meow_ui_widgets.h:新增 meow_listview_item_h() - 新增 test_meow_ui_listview_rowh.c 其余:meow_select.c / meow_ui_dialog.c 统一 MD3 形状 token; include/meow_ipc.h 补 MSG_KEY_PAGEUP/PAGEDOWN 枚举; meow_ui_sys.c / meow_cachegov.c 取时统一走 meow_ui_time_ms()。 测试辅助函数标定:tuku/test/test_settings_lock_host.c 的注入前等待 150ms→350ms。 该等待按修复前 IME 标定,新冷却窗下首键 dt_open≈114ms 会被当迟到抬起吞掉, 只录到 "68" 两位。仅改等待时长,断言原样保留。 验证:tuku 四套脚本全绿(run_tests 644 PASS / zip 106 / cbz 296 / webtoon PASS); 12 个源文件与主仓逐字节一致;sdk/build_sdk.sh 34 成员; tuku/build_tuku.sh 产出静态 MIPS32r2 ELF。
249 lines
12 KiB
C
249 lines
12 KiB
C
/* 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 \
|
||
* meow_jpeg.c meow_png_stream.c \
|
||
* -I. -Iinclude -DMEOW_UI_HOST_TEST -pthread -lm -lz \
|
||
* -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;
|
||
}
|