subtree pull sdk 来自主仓 pub/sdk(主仓 commit 68ce46a 等)。新增: - 布局引擎 meow_ui_box / 动画基座 meow_ui_anim / 滚动物理 meow_ui_scroll - §6.4 事件分发形式化 / W_LISTVIEW 虚拟化多行 / meow_str 字符串资源 i18n - sb_icons 动作图标扩充 + topbar 位图图标 / meow_img_downscale - tools/ 图标生成脚本与 SVG 源 - 对应宿主 test_*.c 与 demo_box_scroll.c build_sdk.sh 冲突解析:保留公开仓自包含版(vendored meow_font + -Iinclude -I.), OBJS 手工并入 4 个新模块(meow_ui_box/anim/scroll/str)。保留 vendored meow_font.c/ meow_ipc.h/meow_font.h/meow_notif.h,剔除宿主测试二进制与编译产物。
294 lines
11 KiB
C
294 lines
11 KiB
C
/*
|
||
* meow_ui_scroll.c — 滚动物理(§12.3)。
|
||
*
|
||
* 速度采样:track 时把 (pos, t) 记进定长环形(≤MEOW_SCROLL_SAMPLES),fling 时取
|
||
* 最近 MEOW_SCROLL_WINDOW ms 窗口,按「间隔速度 × 近者权重」加权得出初速(px/s)。
|
||
* 位置模型:ease-out quad 的位移曲线(末速恰为 0),初速与输入 v 对齐,时长 = 2×tau。
|
||
* 边界:free 目标越界 → clamp 到 [0,max];若越界速度大 → 补两段短 IN_OUT 回弹
|
||
* (overshoot 超过边界方向的内侧再回位),不做弹簧。停止后可选 snap 到 step 倍数。
|
||
*
|
||
* 不引 libm(SDK 约定,免 -lm);只用整数 + 纯浮点四则。
|
||
*/
|
||
#include "meow_ui.h"
|
||
#include "include/meow_ui_scroll.h"
|
||
#include "meow_ui_internal.h" /* meow_widget_rect(定位 redraw 视口区) */
|
||
#include "include/meow_ui_anim.h"
|
||
#include <string.h>
|
||
|
||
#define MEOW_SCROLL_POOL 8
|
||
#define MEOW_SCROLL_SAMPLES 8 /* 速度采样点数(最近 N 加权) */
|
||
#define MEOW_SCROLL_WINDOW 110 /* 速度窗口(ms) */
|
||
#define MEOW_SCROLL_TAU 180 /* 默认摩擦时间常数(ms) */
|
||
#define MEOW_SCROLL_MIN_SPEED 60 /* 低于此的初速不 fling(px/s) */
|
||
#define MEOW_SCROLL_MAX_DUR 900 /* fling 时长上限(ms) */
|
||
#define MEOW_SCROLL_BOUNCE_MS 110 /* 每段回弹时长(ms) */
|
||
#define MEOW_SCROLL_SNAP_MS 120
|
||
|
||
typedef enum {
|
||
MEOW_SCROLL_IDLE = 0,
|
||
MEOW_SCROLL_FLING,
|
||
MEOW_SCROLL_BOUNCE1,
|
||
MEOW_SCROLL_BOUNCE2,
|
||
MEOW_SCROLL_SNAP,
|
||
} meow_scroll_phase;
|
||
|
||
struct meow_scroll {
|
||
meow_scroll_set_fn set;
|
||
meow_scroll_max_fn max;
|
||
meow_scroll_get_fn get;
|
||
meow_scroll_redraw_fn redraw;
|
||
void *ctx;
|
||
|
||
int snap_step; /* >0 = 吸附步长 */
|
||
int tau_ms; /* 摩擦时间常数 */
|
||
/* 速度采样(顺序数组,满则前移,≤SAMPLES) */
|
||
int v_pos[MEOW_SCROLL_SAMPLES];
|
||
uint64_t v_t[MEOW_SCROLL_SAMPLES];
|
||
int v_n;
|
||
|
||
meow_scroll_phase phase;
|
||
meow_anim *anim;
|
||
int maxv; /* fling 开始时缓存 max_scroll */
|
||
int bounce; /* fling 边界回弹幅度(带符号;0=不回弹) */
|
||
int bounce_start; /* 回弹回位起点(= clamp 边界) */
|
||
int snap_to; /* snap 目标 */
|
||
};
|
||
|
||
static meow_scroll g_pool[MEOW_SCROLL_POOL];
|
||
static unsigned char g_used[MEOW_SCROLL_POOL];
|
||
|
||
/* ---- 生命周期 ---- */
|
||
meow_scroll *meow_scroll_create(meow_scroll_set_fn set, meow_scroll_max_fn max,
|
||
meow_scroll_get_fn get, meow_scroll_redraw_fn redraw,
|
||
void *ctx)
|
||
{
|
||
if (!set || !max || !get) return NULL;
|
||
for (int i = 0; i < MEOW_SCROLL_POOL; i++) {
|
||
if (!g_used[i]) {
|
||
g_used[i] = 1;
|
||
meow_scroll *s = &g_pool[i];
|
||
memset(s, 0, sizeof *s);
|
||
s->set = set; s->max = max; s->get = get; s->redraw = redraw; s->ctx = ctx;
|
||
s->tau_ms = MEOW_SCROLL_TAU;
|
||
return s;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
void meow_scroll_free(meow_scroll *s)
|
||
{
|
||
if (!s) return;
|
||
if (s->anim) meow_anim_cancel(s->anim);
|
||
s->anim = NULL;
|
||
for (int i = 0; i < MEOW_SCROLL_POOL; i++)
|
||
if (&g_pool[i] == s) { g_used[i] = 0; return; }
|
||
}
|
||
|
||
/* ---- wrap:包一个 meow_widget 列表 ---- */
|
||
typedef struct { meow_widget *w; int lv; } wrap_ctx;
|
||
static void wrap_set(void *c, int off)
|
||
{ wrap_ctx *wc = c; if (wc->lv) meow_listview_set_scroll(wc->w, off); else meow_list_set_scroll(wc->w, off); }
|
||
static int wrap_max(void *c)
|
||
{ wrap_ctx *wc = c; return wc->lv ? meow_listview_max_scroll(wc->w) : meow_list_max_scroll(wc->w); }
|
||
static int wrap_get(void *c)
|
||
{ wrap_ctx *wc = c; return wc->lv ? meow_listview_scroll(wc->w) : meow_list_scroll(wc->w); }
|
||
static void wrap_redraw(void *c)
|
||
{
|
||
wrap_ctx *wc = c;
|
||
if (!wc->w) return;
|
||
int r[4]; meow_widget_rect(wc->w, r);
|
||
if (r[2] > 0 && r[3] > 0) meow_ui_flush_region(r[0], r[1], r[2], r[3]); /* 只刷视口区 */
|
||
}
|
||
|
||
static wrap_ctx g_wrap_ctx[MEOW_SCROLL_POOL];
|
||
static int g_wrap_used[MEOW_SCROLL_POOL];
|
||
|
||
meow_scroll *meow_scroll_wrap(meow_widget *list, int is_listview)
|
||
{
|
||
if (!list) return NULL;
|
||
meow_scroll *s = meow_scroll_create(wrap_set, wrap_max, wrap_get, wrap_redraw, NULL);
|
||
if (!s) return NULL;
|
||
for (int i = 0; i < MEOW_SCROLL_POOL; i++) {
|
||
if (!g_wrap_used[i]) {
|
||
g_wrap_used[i] = 1;
|
||
g_wrap_ctx[i].w = list; g_wrap_ctx[i].lv = is_listview ? 1 : 0;
|
||
s->ctx = &g_wrap_ctx[i];
|
||
return s;
|
||
}
|
||
}
|
||
meow_scroll_free(s);
|
||
return NULL;
|
||
}
|
||
|
||
/* ---- 速度采样 ---- */
|
||
static void sample_push(meow_scroll *s, int pos, uint64_t t)
|
||
{
|
||
if (s->v_n >= MEOW_SCROLL_SAMPLES) { /* 满:前移丢弃最旧 */
|
||
for (int i = 1; i < s->v_n; i++) { s->v_pos[i - 1] = s->v_pos[i]; s->v_t[i - 1] = s->v_t[i]; }
|
||
s->v_n--;
|
||
}
|
||
s->v_pos[s->v_n] = pos; s->v_t[s->v_n] = t; s->v_n++;
|
||
}
|
||
|
||
/* 加权初速:取窗口内采样,按间隔速度 × 近者权重加权平均(px/s)。 */
|
||
static int sample_velocity(meow_scroll *s, uint64_t now)
|
||
{
|
||
int lo = 0;
|
||
while (lo < s->v_n && now > s->v_t[lo] && (now - s->v_t[lo]) > MEOW_SCROLL_WINDOW) lo++;
|
||
if (s->v_n - lo < 2) return 0;
|
||
long long sum_w = 0, sum_vw = 0;
|
||
for (int i = lo; i < s->v_n - 1; i++) {
|
||
uint64_t dt = s->v_t[i + 1] - s->v_t[i];
|
||
if (dt == 0) continue;
|
||
long long v = (long long)(s->v_pos[i + 1] - s->v_pos[i]) * 1000LL / (long long)dt;
|
||
long long w = (i - lo + 1); /* 近者权重高 */
|
||
sum_vw += v * w; sum_w += w;
|
||
}
|
||
if (sum_w == 0) return 0;
|
||
return (int)(sum_vw / sum_w);
|
||
}
|
||
|
||
int meow_scroll_velocity(meow_scroll *s)
|
||
{
|
||
if (!s) return 0;
|
||
return sample_velocity(s, meow_ui_time_ms());
|
||
}
|
||
|
||
void meow_scroll_track(meow_scroll *s, int dy, uint64_t t_ms)
|
||
{
|
||
if (!s || dy == 0) return;
|
||
int off = s->get(s->ctx) + dy; /* set 会 clamp 到 [0,max](与 scroll_by 一致) */
|
||
s->set(s->ctx, off);
|
||
int got = s->get(s->ctx); /* clamp 后的真实 offset */
|
||
sample_push(s, got, t_ms);
|
||
}
|
||
|
||
/* ---- 动画驱动回调 ---- */
|
||
static void fling_update(meow_anim *a, float v, void *ctx)
|
||
{
|
||
(void)a;
|
||
meow_scroll *s = (meow_scroll *)ctx;
|
||
s->set(s->ctx, (int)v);
|
||
if (s->redraw) s->redraw(s->ctx);
|
||
}
|
||
static void fling_done(meow_anim *a, int canceled, void *ctx)
|
||
{
|
||
(void)a;
|
||
meow_scroll *s = (meow_scroll *)ctx;
|
||
s->anim = NULL;
|
||
if (canceled) { s->phase = MEOW_SCROLL_IDLE; return; }
|
||
if (s->phase == MEOW_SCROLL_FLING) {
|
||
if (s->bounce != 0) {
|
||
/* 边界回弹段1:向内侧过冲 → 段2 回位(bounce 已带符号;顶界 +,底界 -) */
|
||
int from = s->get(s->ctx);
|
||
int to = from + s->bounce; /* bounce 已带符号 */
|
||
if (to < 0) to = 0;
|
||
if (to > s->maxv) to = s->maxv;
|
||
if (to == from) { s->phase = MEOW_SCROLL_IDLE; return; }
|
||
s->phase = MEOW_SCROLL_BOUNCE1;
|
||
meow_anim_desc d; memset(&d, 0, sizeof d);
|
||
d.from = (float)from; d.to = (float)to; d.duration_ms = MEOW_SCROLL_BOUNCE_MS;
|
||
d.ease = MEOW_EASE_IN_OUT; d.update = fling_update; d.done = fling_done; d.ctx = s;
|
||
s->anim = meow_anim_start(&d);
|
||
return;
|
||
}
|
||
/* 无回弹 → snap 或收尾 */
|
||
if (s->snap_step > 0 && s->get(s->ctx) != s->snap_to) {
|
||
int from = s->get(s->ctx);
|
||
int to = s->snap_to;
|
||
if (to < 0) to = 0;
|
||
if (to > s->maxv) to = s->maxv;
|
||
if (to != from) {
|
||
s->phase = MEOW_SCROLL_SNAP;
|
||
meow_anim_desc d; memset(&d, 0, sizeof d);
|
||
d.from = (float)from; d.to = (float)to; d.duration_ms = MEOW_SCROLL_SNAP_MS;
|
||
d.ease = MEOW_EASE_IN_OUT; d.update = fling_update; d.done = fling_done; d.ctx = s;
|
||
s->anim = meow_anim_start(&d);
|
||
return;
|
||
}
|
||
}
|
||
s->phase = MEOW_SCROLL_IDLE;
|
||
return;
|
||
}
|
||
if (s->phase == MEOW_SCROLL_BOUNCE1) {
|
||
int from = s->get(s->ctx);
|
||
/* 回位到 bounce 起点(= clamp 边界) */
|
||
int to = s->bounce_start;
|
||
if (to < 0) to = 0;
|
||
if (to > s->maxv) to = s->maxv;
|
||
s->phase = MEOW_SCROLL_BOUNCE2;
|
||
if (to == from) { s->phase = MEOW_SCROLL_IDLE; return; }
|
||
meow_anim_desc d; memset(&d, 0, sizeof d);
|
||
d.from = (float)from; d.to = (float)to; d.duration_ms = MEOW_SCROLL_BOUNCE_MS;
|
||
d.ease = MEOW_EASE_IN_OUT; d.update = fling_update; d.done = fling_done; d.ctx = s;
|
||
s->anim = meow_anim_start(&d);
|
||
return;
|
||
}
|
||
if (s->phase == MEOW_SCROLL_BOUNCE2 || s->phase == MEOW_SCROLL_SNAP) {
|
||
s->phase = MEOW_SCROLL_IDLE;
|
||
return;
|
||
}
|
||
s->phase = MEOW_SCROLL_IDLE;
|
||
}
|
||
|
||
/* ---- fling ---- */
|
||
void meow_scroll_fling(meow_scroll *s)
|
||
{
|
||
if (!s) return;
|
||
if (s->anim) { meow_anim_cancel(s->anim); s->anim = NULL; }
|
||
s->phase = MEOW_SCROLL_IDLE;
|
||
int v = sample_velocity(s, meow_ui_time_ms());
|
||
if (v > -MEOW_SCROLL_MIN_SPEED && v < MEOW_SCROLL_MIN_SPEED) return; /* 太慢不 fling */
|
||
int max = s->max(s->ctx);
|
||
if (max < 0) max = 0;
|
||
s->maxv = max;
|
||
int pos0 = s->get(s->ctx);
|
||
int dist = (int)((long long)v * s->tau_ms / 1000LL); /* 摩擦滑行总位移(px) */
|
||
int free_target = pos0 + dist;
|
||
int clamped = free_target, over = 0;
|
||
if (clamped > max) { over = clamped - max; clamped = max; }
|
||
if (clamped < 0) { over = -clamped; clamped = 0; }
|
||
if (clamped == pos0) return; /* 无位移 */
|
||
|
||
int dir; /* 回弹方向:底界向 -1(往内上),顶界向 +1(往内下) */
|
||
if (free_target > max) dir = -1;
|
||
else if (free_target < 0) dir = 1;
|
||
else dir = 0;
|
||
|
||
int dur = 2 * s->tau_ms; /* ease-out quad:初速与 v 对齐,末速为 0 */
|
||
if (dur > MEOW_SCROLL_MAX_DUR) dur = MEOW_SCROLL_MAX_DUR;
|
||
|
||
s->bounce = 0;
|
||
s->snap_to = pos0;
|
||
if (s->snap_step > 0) {
|
||
int half = s->snap_step / 2;
|
||
s->snap_to = ((pos0 + half) / s->snap_step) * s->snap_step;
|
||
if (s->snap_to > max) s->snap_to = max;
|
||
}
|
||
if (over > 12) { /* 快速撞墙:clamp + 简单回弹 */
|
||
int amt = over / 10; if (amt < 3) amt = 3; if (amt > 14) amt = 14;
|
||
s->bounce = dir * amt; /* 带符号的向内幅度 */
|
||
/* 记录回位起点 = clamp 边界 */
|
||
s->bounce_start = clamped;
|
||
}
|
||
|
||
s->phase = MEOW_SCROLL_FLING;
|
||
meow_anim_desc d; memset(&d, 0, sizeof d);
|
||
d.from = (float)pos0; d.to = (float)clamped; d.duration_ms = (uint32_t)dur;
|
||
d.ease = MEOW_EASE_OUT; d.update = fling_update; d.done = fling_done; d.ctx = s;
|
||
s->anim = meow_anim_start(&d);
|
||
}
|
||
|
||
void meow_scroll_stop(meow_scroll *s)
|
||
{
|
||
if (!s) return;
|
||
if (s->anim) { meow_anim_cancel(s->anim); s->anim = NULL; }
|
||
s->phase = MEOW_SCROLL_IDLE;
|
||
}
|
||
|
||
int meow_scroll_active(meow_scroll *s) { return (s && s->phase != MEOW_SCROLL_IDLE) ? 1 : 0; }
|
||
int meow_scroll_offset(meow_scroll *s) { return s ? s->get(s->ctx) : 0; }
|
||
void meow_scroll_set_snap(meow_scroll *s, int step) { if (s) s->snap_step = step > 0 ? step : 0; }
|
||
void meow_scroll_set_friction(meow_scroll *s, int tau_ms)
|
||
{ if (s) s->tau_ms = (tau_ms > 20 && tau_ms <= 2000) ? tau_ms : MEOW_SCROLL_TAU; }
|