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,由主会话统一处理)。
This commit is contained in:
MeowOS committed 2026-09-01 23:49:24 +08:00
1 parent 553a7d5d9e
commit 5a5e995ebd
19 files changed
+1839 -2

No files matched your search

+48
View File
@@ -9,6 +9,7 @@
#include "meow_ui.h"
#include "meow_ui_internal.h"
#include "meow_ui_view.h"
#include "include/meow_ui_box.h"
#include <stdlib.h>
#include <string.h>
@@ -17,6 +18,50 @@
static meow_view *g_view_by_id[MEOW_VIEW_BY_ID_MAX]; /* id → 顶层页 view(page_push/pop 查找) */
static meow_view *g_top_view = NULL; /* 当前进入的顶层页 view(生命周期跟踪) */
/* ---- 布局提供者注册表(view → meow_box,opt-in) ---- */
static struct { meow_view *v; meow_box *box; } g_view_layout[MEOW_VIEW_BY_ID_MAX];
static int g_view_layout_n = 0;
static void layout_reg_set(meow_view *v, meow_box *box)
{
for (int i = 0; i < g_view_layout_n; i++) {
if (g_view_layout[i].v == v) {
g_view_layout[i].box = box;
return;
}
}
if (g_view_layout_n < MEOW_VIEW_BY_ID_MAX) {
g_view_layout[g_view_layout_n].v = v;
g_view_layout[g_view_layout_n].box = box;
g_view_layout_n++;
}
}
static meow_box *layout_reg_get(const meow_view *v)
{
for (int i = 0; i < g_view_layout_n; i++)
if (g_view_layout[i].v == v) return g_view_layout[i].box;
return NULL;
}
/* 重排:把 view 的布局 box 解算一次(box 矩形未设时自动跟 view 内容矩形)。 */
void meow_view_relayout(meow_view *v)
{
if (!v) return;
meow_box *b = layout_reg_get(v);
if (!b) return;
int r[4]; meow_box_rect(b, r);
if (r[2] <= 0 || r[3] <= 0) /* w/h 未设 → 跟 view 内容矩形 */
meow_box_set_rect(b, v->x, v->y, v->w, v->h);
meow_box_arrange(b);
}
void meow_view_set_layout(meow_view *v, struct meow_box *box)
{
if (!v) return;
if (!box) { layout_reg_set(v, NULL); return; }
layout_reg_set(v, box);
meow_view_relayout(v); /* 立即按当前尺寸排一次 */
}
static void run_fn(meow_view_fn f, meow_view *v)
{
if (f) f(v, v->ctx);
@@ -146,6 +191,7 @@ void meow_view_page_push(int id)
if (cur && cur != next) view_leave(cur);
meow_ui_activity_push(id);
view_enter(next);
meow_view_relayout(next); /* 布局提供者(meow_box)随页进入自动排一次 */
g_top_view = next ? next : cur;
}
@@ -156,6 +202,7 @@ void meow_view_page_pop(void)
meow_ui_activity_pop();
meow_view *next = view_map_get(meow_ui_activity_active());
view_enter(next);
meow_view_relayout(next);
g_top_view = next ? next : NULL;
}
@@ -167,6 +214,7 @@ void meow_view_page_replace(int id)
meow_view *next = view_map_get(id);
if (cur && cur != next) view_leave(cur);
view_enter(next);
meow_view_relayout(next);
g_top_view = next ? next : cur;
}