sdk: 增量同步主仓新模块(box/anim/scroll/dispatch/listvirt/str/sb_icons/img)

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,剔除宿主测试二进制与编译产物。
This commit is contained in:
AskaEth committed 2026-09-02 09:08:51 +08:00
commit 4b094d62a5
56 files changed
+4327 -117

No files matched your search

+293
View File
@@ -0,0 +1,293 @@
/*
* 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; }