公开 SDK 前把 SDK/App 硬编码文案外置:新增 meow_str/meow_strf/meow_str_set_locale/ meow_str_locale,按当前 locale 取串(永不 NULL),第三方开发者可做多语言。 - 新增 include/meow_ui_str.h + meow_ui_str.c:取串优先级 = TF 覆盖表 > 内嵌当前语言 (en_us→en 其余→zh) > 内嵌内置默认(zh_cn) > 原样返回 id。内嵌双语表(common.* 21 条 + ime.* 9 条);热路径零 malloc(编译期静态表 + ≤256 静态覆盖数组);纯 libc 宿主可单测。 - locale 存系统设置 settings.conf 的 ui.locale(默认 zh_cn,首次调用惰性读取); meow_str_set_locale 即时切换 + 重载覆盖表 + best-effort 持久化(流式改写保留其它行)。 - TF 覆盖表 MeowData/<pkg>/strings/<locale>.txt(id=text 行,UTF-8,'=' 两侧去空白); 无卡/无文件/无该 id → 回落内嵌。meow_strf 用调用方缓冲(snprintf 语义,重入安全)。 - SDK 控件接入:dialog 默认按钮 OK/CANCEL + INPUT 占位、IME 键面/徽标(空格/确定/拼英/ ?123/清/拼音/(无候选)/中)改走 meow_str;空态/加载态给 App 提供 common.no_data/loading。 - 测试:新增 test_meow_ui_str.c(内嵌回退/TF 覆盖优先级/未知 id 回退/切 locale 即时生效/ 查找正确性/meow_strf,tmp+env 两模式);全量宿主回归(widgets/dispatch/box/anim/scroll/ listvirt/theme/ime_fix/pinyin/overlay/notif/sdk_input_keys/p1p2p3/config/vol/sb_collapse/ notify_*) 0 回归;交叉 zig-mipsel -Wall -Wextra 0 warning。 - 文档:MEOW_APP_SDK.md 新增 §13(字符串资源/i18n,遗留顺延为 §14);docs/APP_DEV_GUIDE.md 新增 §7.11。各宿主 test_*.c 头注释补 meow_ui_str.c 编译依赖。 边界(§13.7):仅 zh_cn/en_us 整串取用;不做复数规则 / BiDi / RTL / 动态字体回退(雏形)。
366 lines
19 KiB
C
366 lines
19 KiB
C
/* test_meow_ui_listvirt.c — 宿主验证 §5.5 虚拟化列表(W_LISTVIEW pool 模式):
|
||
* T1 虚拟化正确性:10000 项滚动窗口内容正确(hit 行号映射)/ bind 次数有界(全量扫过=count)/
|
||
* 回滚不重绑(scroll 回顶 bind 计数不变)。
|
||
* T2 多行富 item 渲染:双行(标题+副标题)/ 单行(仅标题)/ 左图标占位 / 右尾件 /
|
||
* 副标题超宽截断(不越过操作区)。
|
||
* T3 段头占位行(bind 返回 section=1)+ 空态占位(count=0 置 empty_text)。
|
||
* T4 完全自绘(set_item_draw 覆盖默认 renderer)。
|
||
* T5 焦点(tap_at → meow_ui_focus_owner)+ fling 联动(meow_scroll_wrap 包裹 pool 列表,
|
||
* track+fling 吸附到行边界;滚动只改 viewport 偏移)。
|
||
*
|
||
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
|
||
* gcc -o test_meow_ui_listvirt test_meow_ui_listvirt.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_ui_listvirt
|
||
*/
|
||
#include "meow_ui.h"
|
||
#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp:真实像素断言 */
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
|
||
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)
|
||
|
||
/* ---- 虚拟化数据源(10000 项):bind 计数有界 / 内容正确 / 段头 ---- */
|
||
#define N 10000
|
||
static int g_bind_total; /* pool bind 总数(经 listv_slot 调用;hit 走独立路径不计数) */
|
||
static int g_bind_touch_count; /* bind 回调的独立计数(hit 也会调 bind,故仅命中计数用) */
|
||
static int g_count_override; /* <0 = 用默认 N;>=0 = 覆盖(空态/小列表用) */
|
||
static int g_section_at; /* 该 index 返回段头(-1=无) */
|
||
|
||
static int vcount(void *ctx) { (void)ctx; return g_count_override >= 0 ? g_count_override : N; }
|
||
static void vbind(int idx, meow_lv_item *it, void *ctx)
|
||
{
|
||
(void)ctx;
|
||
g_bind_total++;
|
||
g_bind_touch_count++;
|
||
memset(it, 0, sizeof *it);
|
||
it->icon_kind = MEOW_LIST_ICON_FOLDER;
|
||
it->right = MEOW_LIST_RIGHT_ARROW;
|
||
it->on = 1; it->enabled = 1;
|
||
if (g_section_at == idx) {
|
||
it->section = 1;
|
||
it->title = "SECTION A"; it->subtitle = NULL; it->icon_kind = MEOW_LIST_ICON_NONE;
|
||
it->right = MEOW_LIST_RIGHT_NONE;
|
||
return;
|
||
}
|
||
static char t[64], s[64];
|
||
snprintf(t, sizeof t, "title %d", idx);
|
||
snprintf(s, sizeof s, "sub %d", idx);
|
||
it->title = t; it->subtitle = s;
|
||
}
|
||
|
||
/* ---- 数据源 A:单行(无副标题/无图标/无尾件),供 T2 单行断言 ---- */
|
||
static int via_count(void *ctx) { (void)ctx; return 3; }
|
||
static void via_bind(int idx, meow_lv_item *it, void *ctx)
|
||
{
|
||
(void)ctx; (void)idx;
|
||
memset(it, 0, sizeof *it);
|
||
static char t[32]; snprintf(t, sizeof t, "single %d", idx);
|
||
it->title = t; it->subtitle = NULL; it->icon_kind = MEOW_LIST_ICON_NONE;
|
||
it->right = MEOW_LIST_RIGHT_NONE; it->enabled = 1;
|
||
}
|
||
|
||
/* ---- 数据源:超宽副标题(T2 截断) ---- */
|
||
static int long_count(void *ctx) { (void)ctx; return 1; }
|
||
static void long_bind(int idx, meow_lv_item *it, void *ctx)
|
||
{
|
||
(void)ctx; (void)idx;
|
||
memset(it, 0, sizeof *it);
|
||
it->title = "Title";
|
||
it->subtitle = "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"; /* 超宽 */
|
||
it->icon_kind = MEOW_LIST_ICON_NONE;
|
||
it->right = MEOW_LIST_RIGHT_NONE; it->enabled = 1;
|
||
}
|
||
|
||
/* ---- T4 完全自绘:整行画实心块 + 标题 ---- */
|
||
static int g_draw_calls;
|
||
static void mydraw(int idx, int x, int y, int w, int h, int st, void *ctx)
|
||
{
|
||
(void)idx; (void)ctx;
|
||
g_draw_calls++;
|
||
meow_ui_fill_rect(x, y, w, h, 0x334455); /* 自定义行底 */
|
||
meow_ui_fill_rect(x + 4, y + 4, 8, 8, 0xFF0000); /* 角标记(自绘内容) */
|
||
if (st & MEOW_LISTVIEW_ST_SELECTED) meow_ui_fill_rect(x + w - 12, y + 4, 8, 8, 0x00FF00);
|
||
}
|
||
|
||
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 sb_r, sb_g, sb_b, os_r, os_g, os_b, pc_r, pc_g, pc_b, ospv_r, ospv_g, ospv_b, prim_r, prim_g, prim_b;
|
||
UNPK(th->bg, sb_r, sb_g, sb_b);
|
||
UNPK(th->onSurface, os_r, os_g, os_b);
|
||
UNPK(th->primaryContainer, pc_r, pc_g, pc_b);
|
||
UNPK(th->onSurfaceVariant, ospv_r, ospv_g, ospv_b);
|
||
UNPK(th->primary, prim_r, prim_g, prim_b);
|
||
int thh = meow_ui_text_height(th->font_body);
|
||
int itemh = 56;
|
||
|
||
/* ============ T1 虚拟化正确性 / bind 有界 / 回滚不重绑 ============ */
|
||
{
|
||
g_count_override = -1; g_section_at = -1; g_bind_total = 0; g_bind_touch_count = 0;
|
||
int lvx = 20, lvy = 50, lvw = 440, lvh = 300;
|
||
meow_widget *lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||
CHECK(meow_listview_set_item_h(lv, itemh) == itemh, "item_h");
|
||
CHECK(meow_listview_set_vsource(lv, vcount, vbind, NULL) == 0, "vsource");
|
||
CHECK(meow_listview_max_scroll(lv) == N * itemh - lvh, "max_scroll %d", meow_listview_max_scroll(lv)); /* 560000-300=559700 */
|
||
CHECK(meow_listview_scroll(lv) == 0, "scroll0");
|
||
|
||
/* 首帧 draw:绑定视口行(first..last) */
|
||
meow_widget_draw(lv);
|
||
int first0 = 0;
|
||
int last0 = (0 + lvh) / itemh + 1; if (last0 > N) last0 = N;
|
||
int B0 = g_bind_total;
|
||
CHECK(B0 == (last0 - first0), "first draw binds %d rows got %d", last0 - first0, B0);
|
||
|
||
/* 下滚 1 行:仅新入视口的行被 bind(delta==1),未离屏行不重绑 */
|
||
meow_listview_set_scroll(lv, itemh);
|
||
meow_widget_draw(lv);
|
||
int B1 = g_bind_total;
|
||
CHECK(B1 == B0 + 1, "scroll+1row binds exactly 1 more got %d (was %d)", B1 - B0, B0);
|
||
|
||
/* 回滚到顶:视口行槽位仍在 → 不重绑(delta==0) */
|
||
meow_listview_set_scroll(lv, 0);
|
||
meow_widget_draw(lv);
|
||
CHECK(g_bind_total == B1, "scroll-back no rebind got %d (was %d)", g_bind_total, B1);
|
||
|
||
/* 全量扫过:每个 index 进入视口时 bind 恰一次 → 总数 == N(有界,非 per-frame × count) */
|
||
int prev = 1;
|
||
while (prev <= N) {
|
||
int off = prev * itemh;
|
||
meow_listview_set_scroll(lv, off);
|
||
meow_widget_draw(lv);
|
||
if (off >= meow_listview_max_scroll(lv)) break;
|
||
prev++;
|
||
}
|
||
meow_listview_set_scroll(lv, meow_listview_max_scroll(lv));
|
||
meow_widget_draw(lv);
|
||
CHECK(g_bind_total == N, "full sweep binds each index once got %d (expect %d)", g_bind_total, N);
|
||
CHECK(g_bind_touch_count == N, "bind callback called %d times (expect %d)", g_bind_touch_count, N);
|
||
|
||
/* 回滚(扫到末尾后拉回顶):池发生整表 wrap(cap<count),回收合法重绑——但**有界**,
|
||
* 每次 draw 至多重绑当前视口窗口(≤ win),绝不整表 N 重绑(可复用回收语义)。 */
|
||
int before2 = g_bind_total;
|
||
int win = (lvh / itemh) + 1; if (win > N) win = N;
|
||
meow_listview_set_scroll(lv, 0);
|
||
meow_widget_draw(lv);
|
||
CHECK((g_bind_total - before2) <= win, "scroll-back rebind bounded (delta %d ≤ %d)",
|
||
g_bind_total - before2, win);
|
||
|
||
/* 内容正确:命中行号 = 滚动换算(视口窗口映射正确) */
|
||
CHECK(meow_listview_hit(lv, lvx + lvw - 10, lvy + 0 * itemh + 20) == 0, "hit0");
|
||
meow_listview_set_scroll(lv, 5 * itemh);
|
||
int idx_mid = meow_listview_hit(lv, lvx + lvw - 10, lvy + itemh / 2);
|
||
CHECK(idx_mid == 5, "hit maps scroll to row got %d (expect 5)", idx_mid);
|
||
meow_listview_set_scroll(lv, 888 * itemh);
|
||
int idx_mid2 = meow_listview_hit(lv, lvx + lvw - 10, lvy + itemh * 2 + itemh / 2);
|
||
CHECK(idx_mid2 == 890, "hit row 2 of window got %d (expect 890)", idx_mid2);
|
||
meow_widget_free(lv);
|
||
}
|
||
|
||
/* ============ T2 多行富 item 渲染:双行 / 单行 / 图标 / 尾件 / 截断 ============ */
|
||
{
|
||
/* 双行(lines=2,默认):行 0 title+subtitle + icon(FOLDER) + 尾件(ARROW) */
|
||
int lvx = 20, lvy = 50, lvw = 440, lvh = 120;
|
||
meow_widget *lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||
CHECK(meow_listview_set_item_h(lv, itemh) == itemh, "item_h");
|
||
CHECK(meow_listview_set_vsource(lv, vcount, vbind, NULL) == 0, "vsource");
|
||
CHECK(meow_listview_set_item_renderer(lv, 2, 1, 1) == 0, "renderer");
|
||
meow_ui_clear(th->bg);
|
||
meow_widget_draw(lv);
|
||
int text_x = lvx + 16 + 24 + 16; /* show_icon=1 → 76 */
|
||
int op_start = lvx + (lvw * 2 / 3); /* 20+293=313 */
|
||
int ty0 = lvy + (itemh - (2 * thh + 2)) / 2; /* 双行标题 y */
|
||
int sy0 = ty0 + thh + 2; /* 副标题 y */
|
||
CHECK(region_has(text_x, ty0, 60, thh, os_r, os_g, os_b) > 2, "double-line title drawn");
|
||
CHECK(region_has(text_x, sy0, 60, thh, ospv_r, ospv_g, ospv_b) > 2, "double-line subtitle drawn");
|
||
CHECK(region_has(lvx + 16, lvy + (itemh - 24) / 2, 24, 24, prim_r, prim_g, prim_b) > 30, "icon placeholder drawn");
|
||
int ax = lvx + lvw - 16 - 24;
|
||
CHECK(region_has(ax, lvy + itemh / 2 - 8, 20, 16, ospv_r, ospv_g, ospv_b) > 2, "right tail (arrow) drawn");
|
||
|
||
/* 单行(lines=1):仅标题居中,无副标题带 */
|
||
g_count_override = 3;
|
||
meow_widget *lv1 = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||
meow_listview_set_item_h(lv1, itemh);
|
||
meow_listview_set_vsource(lv1, via_count, via_bind, NULL);
|
||
meow_listview_set_item_renderer(lv1, 1, 0, 0); /* 单行、无图标、无尾件 */
|
||
meow_ui_clear(th->bg);
|
||
meow_widget_draw(lv1);
|
||
int t1y = lvy + (itemh - thh) / 2;
|
||
CHECK(region_has(lvx + 16, t1y, 60, thh, os_r, os_g, os_b) > 2, "single-line title drawn");
|
||
CHECK(region_has(lvx + 16, t1y + thh + 2, 60, thh, ospv_r, ospv_g, ospv_b) == 0, "single-line no subtitle");
|
||
/* 单行档位默认行高 48(set_item_tier) */
|
||
meow_widget *lv2 = meow_listview_create(lvx, lvy, lvw, lvh, 3, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||
CHECK(meow_listview_set_item_tier(lv2, 1) == 0, "tier");
|
||
CHECK(meow_listview_scroll(lv2) == 0 && meow_listview_max_scroll(lv2) == 3 * 48 - lvh, "tier-h %d",
|
||
meow_listview_max_scroll(lv2));
|
||
meow_widget_free(lv2);
|
||
|
||
/* 副标题超宽截断:不画过 text_maxw(op_start 之前) */
|
||
g_count_override = -1;
|
||
meow_widget *lv3 = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||
meow_listview_set_item_h(lv3, itemh);
|
||
meow_listview_set_vsource(lv3, long_count, long_bind, NULL);
|
||
meow_listview_set_item_renderer(lv3, 2, 0, 0); /* 无图标 → text_x = lvx+16=36 */
|
||
meow_ui_clear(th->bg);
|
||
meow_widget_draw(lv3);
|
||
int text_x3 = lvx + 16;
|
||
int op3 = lvx + lvw - 16; /* show_right=0 → op_start=右缘-pad */
|
||
int maxw3 = op3 - text_x3; /* 20+440-16-36 = 408 */
|
||
int ty3 = lvy + (itemh - (2 * thh + 2)) / 2;
|
||
int sy3 = ty3 + thh + 2;
|
||
CHECK(region_has(text_x3, sy3, 40, thh, ospv_r, ospv_g, ospv_b) > 2, "long subtitle leading drawn");
|
||
CHECK(region_has(text_x3 + maxw3 + 4, sy3, 40, thh, ospv_r, ospv_g, ospv_b) == 0, "long subtitle truncated (no overflow)");
|
||
meow_widget_free(lv3);
|
||
meow_widget_free(lv1);
|
||
|
||
/* 截断不越界 + 双行副标题带(回归:无图标/无尾件时 text_x/pad 正确) */
|
||
CHECK(region_has(text_x3, ty3, 40, thh, os_r, os_g, os_b) > 2, "truncate-r title drawn");
|
||
meow_widget_free(lv);
|
||
}
|
||
|
||
/* ============ T3 段头占位 + 空态 ============ */
|
||
{
|
||
int lvx = 20, lvy = 50, lvw = 440, lvh = 120;
|
||
g_count_override = 3; g_section_at = 1;
|
||
meow_widget *lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||
meow_listview_set_item_h(lv, itemh);
|
||
meow_listview_set_vsource(lv, vcount, vbind, NULL);
|
||
meow_ui_clear(th->bg);
|
||
meow_widget_draw(lv);
|
||
int sch_r, sch_g, sch_b; UNPK(th->surfaceContainerHigh, sch_r, sch_g, sch_b);
|
||
CHECK(region_has(lvx, lvy + itemh, lvw, itemh - 4, sch_r, sch_g, sch_b) > 40, "section header band drawn");
|
||
CHECK(region_has(lvx + 16, lvy + itemh + (itemh - thh) / 2, 60, thh, prim_r, prim_g, prim_b) > 2,
|
||
"section header title (primary) drawn");
|
||
|
||
/* 空态:count=0 + empty_text → 视口内居中画文案(onSurfaceVariant) */
|
||
g_count_override = 0; g_section_at = -1;
|
||
meow_widget *lve = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||
meow_listview_set_vsource(lve, vcount, vbind, NULL);
|
||
meow_listview_set_empty_text(lve, "No data"); /* ASCII(宿主 5x7 点阵仅含 ASCII/CJK 用真机字库) */
|
||
meow_ui_clear(th->bg);
|
||
meow_widget_draw(lve);
|
||
int ety = lvy + (lvh - thh) / 2;
|
||
CHECK(region_has(lvx + lvw / 2 - 60, ety, 120, thh, ospv_r, ospv_g, ospv_b) > 2, "empty state text drawn");
|
||
meow_widget_free(lve);
|
||
meow_widget_free(lv);
|
||
}
|
||
|
||
/* ============ T4 完全自绘(set_item_draw 覆盖默认 renderer) ============ */
|
||
{
|
||
int lvx = 20, lvy = 200, lvw = 440, lvh = 120;
|
||
g_count_override = 3; g_section_at = -1; g_draw_calls = 0;
|
||
meow_widget *lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE);
|
||
meow_listview_set_item_h(lv, itemh);
|
||
meow_listview_set_vsource(lv, vcount, vbind, NULL);
|
||
CHECK(meow_listview_set_item_draw(lv, mydraw, NULL) == 0, "set_draw");
|
||
meow_ui_clear(th->bg);
|
||
meow_widget_draw(lv);
|
||
int vis = (lvh / itemh) + 1; if (vis > 3) vis = 3;
|
||
CHECK(g_draw_calls == vis, "draw_item called per visible row got %d (expect %d)", g_draw_calls, vis);
|
||
CHECK(region_has(lvx + 4, lvy + 4, 8, 8, 0xFF, 0x00, 0x00) > 0, "custom row content drawn");
|
||
meow_widget_free(lv);
|
||
}
|
||
|
||
/* ============ T5 焦点 + fling 联动(meow_scroll_wrap 包裹 pool 列表) ============ */
|
||
{
|
||
int lvx = 20, lvy = 330, lvw = 440, lvh = 300;
|
||
g_count_override = -1; g_section_at = -1;
|
||
meow_widget *lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE);
|
||
meow_listview_set_item_h(lv, itemh);
|
||
meow_listview_set_vsource(lv, vcount, vbind, NULL);
|
||
|
||
/* 焦点:tap_at 命中行 → 列表成为唯一焦点(§6.4) */
|
||
int tidx = meow_listview_tap_at(lv, lvx + lvw - 10, lvy + itemh / 2);
|
||
CHECK(tidx >= 0, "tap_at returns row got %d", tidx);
|
||
CHECK(meow_ui_focus_owner() == (void *)lv, "list acquired focus after tap_at");
|
||
|
||
/* fling:track+fling → meow_scroll_wrap 包裹;吸附到行边界(snap=itemh) */
|
||
meow_scroll *s = meow_scroll_wrap(lv, 1); /* 1=W_LISTVIEW */
|
||
CHECK(s != NULL, "scroll wrap pool list");
|
||
meow_scroll_set_snap(s, itemh);
|
||
uint64_t t = meow_ui_time_ms();
|
||
for (int i = 0; i < 12; i++) meow_scroll_track(s, 30, t + (uint64_t)i * 16);
|
||
int start = meow_scroll_offset(s);
|
||
CHECK(start > 0, "drag advances offset got %d", start);
|
||
meow_scroll_fling(s);
|
||
CHECK(meow_scroll_active(s) == 1, "fling active");
|
||
meow_scroll_stop(s);
|
||
CHECK(meow_scroll_active(s) == 0, "stop ok");
|
||
|
||
/* 再次 fling 到自然停:offset 只改 viewport,且 snap 到行边界 */
|
||
for (int i = 0; i < 12; i++) meow_scroll_track(s, 30, t + (uint64_t)i * 16);
|
||
start = meow_scroll_offset(s);
|
||
meow_scroll_fling(s);
|
||
int max_off = start, guard = 0;
|
||
while (meow_scroll_active(s) && guard++ < 300) {
|
||
meow_anim_tick();
|
||
int cur = meow_scroll_offset(s);
|
||
if (max_off < cur) max_off = cur;
|
||
usleep(10000);
|
||
}
|
||
meow_anim_tick();
|
||
int end = meow_scroll_offset(s);
|
||
CHECK(end >= start, "fling advances got %d vs %d", end, start);
|
||
CHECK((end % itemh) == 0, "snap to item boundary got %d (%%%d)", end, itemh);
|
||
CHECK(end <= meow_listview_max_scroll(lv), "fling in range %d (max %d)", end, meow_listview_max_scroll(lv));
|
||
/* 滚动只改 viewport:meow_listview_scroll 与物理层一致 */
|
||
CHECK(meow_listview_scroll(lv) == end, "listview_scroll mirrors scroll offset %d vs %d", meow_listview_scroll(lv), end);
|
||
/* 停稳后 draw 反映最终窗口(bind 只补新入视口的行,不整表重绑) */
|
||
int before_draw = g_bind_total;
|
||
int win = (lvh / itemh) + 1; if (win > N) win = N; /* 视口窗口行数(含底缘 1 行) */
|
||
meow_widget_draw(lv);
|
||
CHECK(g_bind_total >= before_draw && (g_bind_total - before_draw) <= win,
|
||
"settle draw rebinds only window (delta %d ≤ %d)", g_bind_total - before_draw, win);
|
||
meow_scroll_free(s);
|
||
meow_widget_free(lv);
|
||
}
|
||
|
||
meow_ui_shutdown();
|
||
printf("listvirt: %d pass / %d fail\n", g_pass, g_fail);
|
||
return g_fail ? 1 : 0;
|
||
}
|