/* test_meow_ui_scroll.c — §12.3 meow_scroll 滚动物理宿主验证: * 跟手拖动(track 跟随与 clamp)、速度跟踪→松手 fling(位移序列/停止位置/单调/不越界)、 * 边界过冲 clamp + 简单回弹、snap。时间推进用真实 usleep(meow_anim_tick 驱动 fling)。 * * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/draw/activity/ime + 打桩 font/global): * gcc -o test_meow_ui_scroll test_meow_ui_scroll.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_scroll */ #include "meow_ui.h" #include "meow_ui_internal.h" #include #include #include #include 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) #define LIST_H 400 #define ITEM_H 44 #define COUNT 50 /* max_scroll = COUNT*ITEM_H - LIST_H = 2200 - 400 = 1800 */ #define MAXS (COUNT * ITEM_H - LIST_H) static int src(int idx, char *label, int len, void *ctx) { (void)ctx; if (idx < 0 || idx >= COUNT) return 0; snprintf(label, (size_t)len, "row %d", idx); return 1; } static meow_widget *make_list(void) { meow_widget *lv = meow_list_create(0, 0, 480, LIST_H, COUNT, src, NULL); meow_list_set_item_h(lv, ITEM_H); return lv; } /* 喂一串向下(正)速度采样,模拟快速下划 */ static void feed_drag(meow_scroll *s, int step, int n) { uint64_t now = meow_ui_time_ms(); for (int i = 0; i < n; i++) { meow_scroll_track(s, step, now + (uint64_t)i * 16); } /* 最后用一个真实稍晚时刻的 track 让采样时间戳真实(保持一致钟) */ meow_scroll_track(s, 0, meow_ui_time_ms()); } static void pump_until_idle(meow_scroll *s, int *max_off, int *mono_ok) { int prev = meow_scroll_offset(s); int 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; if (cur < prev) *mono_ok = 0; /* 下划 fling 应单调不减(回弹段除外:此处只测无回弹) */ prev = cur; usleep(10000); } meow_anim_tick(); } static void test_track_follow(void) { meow_widget *lv = make_list(); meow_scroll *s = meow_scroll_wrap(lv, 0); CHECK(s != NULL, "wrap"); CHECK(meow_scroll_offset(s) == 0, "init offset 0"); meow_scroll_track(s, 10, meow_ui_time_ms()); CHECK(meow_scroll_offset(s) == 10, "follow +10 got %d", meow_scroll_offset(s)); /* 超量拖到底:clamp 到 max */ meow_scroll_track(s, 100000, meow_ui_time_ms()); CHECK(meow_scroll_offset(s) == MAXS, "clamp to max got %d", meow_scroll_offset(s)); meow_scroll_free(s); meow_widget_free(lv); } static void test_fling_no_boundary(void) { meow_widget *lv = make_list(); meow_scroll *s = meow_scroll_wrap(lv, 0); /* 从 0 起拖 ~300px,建正初速 */ uint64_t t = meow_ui_time_ms(); for (int i = 0; i < 12; i++) meow_scroll_track(s, 25, t + (uint64_t)i * 16); int start = meow_scroll_offset(s); CHECK(start > 0, "dragged start>0 got %d", start); meow_scroll_fling(s); CHECK(meow_scroll_active(s) == 1, "fling active"); int max_off = start, mono = 1, prev = start; int 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; if (cur < prev) mono = 0; prev = cur; usleep(10000); } meow_anim_tick(); int end = meow_scroll_offset(s); CHECK(mono == 1, "monotonic non-decreasing during fling"); CHECK(end >= start, "fling advances end=%d start=%d", end, start); CHECK(end <= MAXS, "no overflow end=%d max=%d", end, MAXS); CHECK(meow_scroll_active(s) == 0, "settles idle"); meow_scroll_free(s); meow_widget_free(lv); } static void test_fling_boundary_bounce(void) { meow_widget *lv = make_list(); meow_scroll *s = meow_scroll_wrap(lv, 0); /* 先拖到近底(~1700) */ meow_list_set_scroll(lv, 1700); /* 快速下划,冲过底界 */ uint64_t t = meow_ui_time_ms(); for (int i = 0; i < 8; i++) meow_scroll_track(s, 40, t + (uint64_t)i * 16); meow_scroll_fling(s); int max_off = 0, mono = 1; pump_until_idle(s, &max_off, &mono); int end = meow_scroll_offset(s); CHECK(max_off <= MAXS, "never exceed max, max_off=%d max=%d", max_off, MAXS); CHECK(end == MAXS, "bounce settles at bottom end=%d", end); /* 回弹后回到底 */ CHECK(meow_scroll_active(s) == 0, "idle after bounce"); meow_scroll_free(s); meow_widget_free(lv); } static void test_snap(void) { meow_widget *lv = make_list(); meow_scroll *s = meow_scroll_wrap(lv, 0); meow_scroll_set_snap(s, ITEM_H); /* 吸附到行边界 */ /* 慢拖到某非边界位置并给 small velocity → fling 结束 snap 到行倍数 */ uint64_t t = meow_ui_time_ms(); for (int i = 0; i < 6; i++) meow_scroll_track(s, 20, t + (uint64_t)i * 16); meow_scroll_fling(s); int max_off = 0, mono = 1; pump_until_idle(s, &max_off, &mono); int end = meow_scroll_offset(s); CHECK((end % ITEM_H) == 0, "snap to item multiple got %d", end); CHECK(end <= MAXS, "snap in range %d", end); meow_scroll_free(s); meow_widget_free(lv); } static void test_stop(void) { meow_widget *lv = make_list(); meow_scroll *s = meow_scroll_wrap(lv, 0); uint64_t t = meow_ui_time_ms(); for (int i = 0; i < 6; i++) meow_scroll_track(s, 20, t + (uint64_t)i * 16); meow_scroll_fling(s); CHECK(meow_scroll_active(s) == 1, "fling active (stop test)"); meow_scroll_stop(s); CHECK(meow_scroll_active(s) == 0, "stopped immediately"); meow_scroll_free(s); meow_widget_free(lv); } int main(void) { meow_ui_opts o = { 480, 720 }; if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } test_track_follow(); test_fling_no_boundary(); test_fling_boundary_bounce(); test_snap(); test_stop(); meow_ui_shutdown(); printf("scroll: %d pass / %d fail\n", g_pass, g_fail); return g_fail ? 1 : 0; }