- meow_box(include/meow_ui_box.h + meow_ui_box.c):竖直/水平容器;子项=固定|weight|wrap; margin/padding/gap;主轴对齐 start/center/end/space-between/around/evenly;交叉轴 start/center/end/stretch; 嵌套(深度≤16 静态栈,单遍求解,无递归爆炸)。可作 meow_view 布局提供者 (meow_view_set_layout/relayout,opt-in;未设布局的 view 保持现有绝对坐标行为)。 新增 meow_widget_set_rect(只写字段不标脏)作为 box 写坐标的通道。 - meow_anim(include/meow_ui_anim.h + meow_ui_anim.c):时间驱动 tween(from/to/duration/easing: linear/ease-out/ease-in-out/overshoot)+ update/done 回调;静态池≤16、取消、并行多动画; meow_anim_set_region 让每帧只 flush 动画影响区(不整屏)。meow_anim_tick 挂主循环 on_tick, 无动画零开销(只查计数即返回)。 - meow_scroll(include/meow_ui_scroll.h + meow_ui_scroll.c):速度跟踪(最近 N 采样加权)+ 松手 fling (减速度曲线,用 anim 基座驱动)+ 边界过冲 clamp + 简单回弹(不做 spring)+ 可选 snap; 跟手拖动行为不变(track 内部调既有 set_scroll);每帧只 redraw 列表视口区;不引 libm。 - 追加式:既有 meow_row/column/box 与全部 meow_*_create/hit/scroll 签名一行未改;旧绝对坐标永远有效; 既有 App 免改源码。 - 验证:宿主新测试 box(42)/anim(32)/scroll(17) 全 PASS;既存 9 回归(widgets/sdk_input_keys/ime_fix/ pinyin/overlay_suppress/notif_gesture/sdk_fixes/sb_collapse/p1p2p3_repro)全 PASS; 离屏 demo(demo_box_scroll.c)渲染 box 布局 + 列表 fling 正常;zig-mipsel -Wall -Wextra 全 TU 0 warning; meow_ui.a md5 83dd6a2d0d24cc748a8659c29f1de2b5(未重链/部署 hello/files/ncm/reader,由主会话统一处理)。
185 lines
6.5 KiB
C
185 lines
6.5 KiB
C
/* 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_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 <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)
|
||
|
||
#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;
|
||
}
|