Files
MeowOS-SDK/sdk/meow_ui_scroll.c
T
AskaEth c85877b447 SDK: 修 meow_scroll_wrap 池槽泄漏(free 归还 wrap 上下文槽)+ 64 次回收/交错/池满回归(M19)
meow_scroll_wrap 从 MEOW_SCROLL_POOL(=8) 个 wrap 上下文槽占一个,meow_scroll_free 只归还
会话槽、从不归还 wrap 槽也不清 ctx.w → 进程内最多成功 wrap 8 次,之后恒 NULL。图库
list_rebuild/load_dir 每次重建,进 8 个目录后列表丢惯性(g_scroll==NULL 走 scroll_by 兜底),
os.meow.files 同样每次 reload 重建。

- meow_scroll 结构内记 wrap_slot(创建置 -1),wrap 时写入槽下标;free 时按该下标清
  g_wrap_ctx[].w/lv 并归还 g_wrap_used,随后置 -1(二次 free 不触碰任何槽)。
  直接 create 的实例 wrap_slot=-1,free 不误清别人的槽。
- 池满语义不变:8 个同时活跃时第 9 个 wrap 仍返回 NULL(不越界);free(NULL) 安全。
- test_meow_ui_scroll 补回归:64 次 wrap/free 全可用(修复前 8/64,修复后 64/64)、free 后
  再 wrap、8 满 + 第 9 个 NULL + 释放其一即复用、两 wrap 交错 free 其一不串槽、直接 create
  不误清槽、list→listview→list 槽复用不残留 kind、free(NULL)/二次 free。
2026-11-05 19:38:55 +08:00

309 lines
12 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 目标 */
int wrap_slot; /* >=0:占用的 g_wrap_ctx 槽下标(-1=非 wrap 实例) */
};
static meow_scroll g_pool[MEOW_SCROLL_POOL];
static unsigned char g_used[MEOW_SCROLL_POOL];
/* wrap 上下文槽:meow_scroll_wrap 占用一个,meow_scroll_free 必须归还(否则进程内最多 wrap
* MEOW_SCROLL_POOL 次后恒 NULL)。槽由 wrap 实例独占,free 时按 wrap_slot 精确归还。 */
typedef struct { meow_widget *w; int lv; } wrap_ctx;
static wrap_ctx g_wrap_ctx[MEOW_SCROLL_POOL];
static int g_wrap_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;
s->wrap_slot = -1;
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) {
int ws = s->wrap_slot;
if (ws >= 0 && ws < MEOW_SCROLL_POOL) { /* wrap 实例:归还上下文槽 */
g_wrap_ctx[ws].w = NULL;
g_wrap_ctx[ws].lv = 0;
g_wrap_used[ws] = 0;
}
s->wrap_slot = -1; /* 二次 free 时不再触碰任何槽 */
g_used[i] = 0;
return;
}
}
/* ---- wrap:包一个 meow_widget 列表 ---- */
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]); /* 只刷视口区 */
}
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];
s->wrap_slot = 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; }