Files
MeowOS-For-R3P2/patch_v2/sdk/meow_ui_scroll.c
T
MeowOS 5a5e995ebd SDK 冲刺批①+②+⑤: meow_box 线性布局 + meow_anim 动画基座 + meow_scroll 滚动物理
- 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,由主会话统一处理)。
2026-09-01 23:49:24 +08:00

294 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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; }