Files
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

308 lines
12 KiB
C
Raw Permalink 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_box.c — 线性布局引擎(§12.1 meow_box)。
*
* meow_box 是**纯布局计算器**(非控件):静态池创建、定长子项数组、arrange 单遍求解,
* 把每个子控件的 x/y/w/h 写进 meow_widget(meow_widget_set_rect);嵌套 sub-box 递归求解。
* 用显式静态栈(深度≤MEOW_BOX_MAX_DEPTH)做嵌套展开,无 C 递归,杜绝栈爆炸。
*
* 求解模型(主轴向):
* - 每个子项占位 = lead(margin) + extent + trail(margin);项间再叠 box->gap。
* - FIXED/WRAP 的 extent 先定;WEIGHT 的 extent=0(待均分)。
* - leftover = content_main - foot;若权重和>0 → 均分 leftover;否则按主轴对齐分配。
* 交叉轴:按 START/CENTER/END/STRETCH 对齐,STRETCH 填满容器交叉向减 margin。
*/
#include "meow_ui.h"
#include "include/meow_ui_box.h"
#include "meow_ui_internal.h" /* meow_widget_rect(只读几何) */
#include <string.h>
#define MEOW_BOX_POOL 16 /* 静态池大小(并发 box 数) */
struct meow_box {
int dir; /* MEOW_BOX_H / MEOW_BOX_V */
int x, y, w, h; /* 容器矩形(绝对坐标) */
int pad_l, pad_t, pad_r, pad_b;
int gap;
int main_align;
int cross_align;
meow_box_item items[MEOW_BOX_MAX_CHILD];
int n;
};
static meow_box g_pool[MEOW_BOX_POOL];
static unsigned char g_used[MEOW_BOX_POOL];
/* ---- 静态池存取 ---- */
meow_box *meow_box_new(meow_box_dir dir)
{
for (int i = 0; i < MEOW_BOX_POOL; i++) {
if (!g_used[i]) {
g_used[i] = 1;
meow_box *b = &g_pool[i];
memset(b, 0, sizeof *b);
b->dir = (dir == MEOW_BOX_H) ? MEOW_BOX_H : MEOW_BOX_V;
return b;
}
}
return NULL;
}
void meow_box_free(meow_box *b)
{
if (!b) return;
for (int i = 0; i < MEOW_BOX_POOL; i++)
if (&g_pool[i] == b) { g_used[i] = 0; return; }
}
void meow_box_set_rect(meow_box *b, int x, int y, int w, int h)
{
if (!b) return;
b->x = x; b->y = y;
b->w = w > 0 ? w : 0; b->h = h > 0 ? h : 0;
}
void meow_box_set_padding(meow_box *b, int l, int t, int r, int btm)
{
if (!b) return;
b->pad_l = l; b->pad_t = t; b->pad_r = r; b->pad_b = btm;
}
void meow_box_set_gap(meow_box *b, int gap)
{
if (!b) return;
b->gap = gap > 0 ? gap : 0;
}
void meow_box_set_align(meow_box *b, int main_align, int cross_align)
{
if (!b) return;
b->main_align = main_align;
b->cross_align = cross_align;
}
/* ---- 子项 ---- */
int meow_box_add(meow_box *b, meow_widget *w, int size, int weight)
{
return meow_box_add_ex(b, w, size, weight, 0, 0, NULL, NULL);
}
int meow_box_add_ex(meow_box *b, meow_widget *w, int size, int weight,
int min, int preferred, meow_box_measure_fn m, void *mctx)
{
if (!b || !w || b->n >= MEOW_BOX_MAX_CHILD) return -1;
if (size != MEOW_BOX_FIXED && size != MEOW_BOX_WEIGHT && size != MEOW_BOX_WRAP) return -1;
if (size == MEOW_BOX_WEIGHT && weight <= 0) return -1;
for (int i = 0; i < b->n; i++)
if (b->items[i].w == w) return -1; /* 同控件去重 */
meow_box_item *it = &b->items[b->n++];
memset(it, 0, sizeof *it);
it->w = w; it->size = size; it->weight = weight > 0 ? weight : 1;
it->min = min > 0 ? min : 0;
it->preferred = preferred > 0 ? preferred : 0;
it->measure = m; it->mctx = mctx;
return 0;
}
int meow_box_add_sub(meow_box *b, meow_box *sub, int size, int weight)
{
if (!b || !sub || b->n >= MEOW_BOX_MAX_CHILD) return -1;
if (size != MEOW_BOX_FIXED && size != MEOW_BOX_WEIGHT && size != MEOW_BOX_WRAP) return -1;
if (size == MEOW_BOX_WEIGHT && weight <= 0) return -1;
for (int i = 0; i < b->n; i++)
if (b->items[i].sub == sub) return -1;
meow_box_item *it = &b->items[b->n++];
memset(it, 0, sizeof *it);
it->sub = sub; it->size = size; it->weight = weight > 0 ? weight : 1;
return 0;
}
/* ---- 查询 ---- */
int meow_box_count(const meow_box *b) { return b ? b->n : 0; }
meow_widget *meow_box_widget_at(const meow_box *b, int idx)
{ return (b && idx >= 0 && idx < b->n) ? b->items[idx].w : NULL; }
meow_box *meow_box_sub_at(const meow_box *b, int idx)
{ return (b && idx >= 0 && idx < b->n) ? b->items[idx].sub : NULL; }
void meow_box_rect(const meow_box *b, int out[4])
{
if (!out) return;
if (!b) { out[0] = out[1] = out[2] = out[3] = 0; return; }
out[0] = b->x; out[1] = b->y; out[2] = b->w; out[3] = b->h;
}
/* ---- 内部:取子项 margin(widget opts;sub 无 margin)---- */
static void item_margin(const meow_box_item *it, meow_box_dir dir,
int *main_before, int *main_after, int *cross)
{
*main_before = *main_after = *cross = 0;
if (!it->w) return;
meow_widget_opts o;
if (meow_widget_opts_get(it->w, &o) != 0) return;
int mx = o.margin_x > 0 ? o.margin_x : 0;
int my = o.margin_y > 0 ? o.margin_y : 0;
if (dir == MEOW_BOX_H) { *main_before = mx; *main_after = mx; *cross = my; }
else { *main_before = my; *main_after = my; *cross = mx; }
}
/* ---- 内部:子项主轴向 extent ----
* FIXED → 控件/嵌套盒当前主轴向尺寸
* WRAP → measure 回调(若有)否则控件当前尺寸;下限 clamp 到 min
* WEIGHT → 0(由均分阶段填充) */
static int item_extent(const meow_box_item *it, int main_is_w)
{
int extent = 0;
if (it->w) {
int r[4]; meow_widget_rect(it->w, r);
extent = main_is_w ? r[2] : r[3];
} else if (it->sub) {
extent = main_is_w ? it->sub->w : it->sub->h;
}
if (it->size == MEOW_BOX_WRAP && it->measure) {
int mn = 0, pf = 0;
it->measure(it->mctx, &mn, &pf);
if (pf > 0) extent = pf;
else if (mn > 0) extent = mn;
if (mn > 0 && extent < mn) extent = mn; /* clamp 到下限 */
}
if (extent < 0) extent = 0;
return extent;
}
/* ---- 单容器求解(一次 pass:算 extent + 位置,写全部子项矩形)----
* 子项为嵌套 meow_box 时只写其矩形(父求结果),后续由 meow_box_arrange 的
* 显式栈递归求解其内部。 */
static void box_layout_one(meow_box *b)
{
int n = b->n;
if (n <= 0) return;
int cx = b->x + b->pad_l;
int cy = b->y + b->pad_t;
int cw = b->w - b->pad_l - b->pad_r; if (cw < 0) cw = 0;
int ch = b->h - b->pad_t - b->pad_b; if (ch < 0) ch = 0;
int main_is_w = (b->dir == MEOW_BOX_H);
int main_size = main_is_w ? cw : ch;
int cross_size = main_is_w ? ch : cw;
int gap = b->gap;
int ext[MEOW_BOX_MAX_CHILD], lead[MEOW_BOX_MAX_CHILD],
trail[MEOW_BOX_MAX_CHILD], wgt[MEOW_BOX_MAX_CHILD];
int nw = 0, wsum = 0;
long long foot = 0;
for (int i = 0; i < n; i++) {
const meow_box_item *it = &b->items[i];
int mb, ma, cr;
item_margin(it, (meow_box_dir)b->dir, &mb, &ma, &cr);
lead[i] = mb; trail[i] = ma; (void)cr;
wgt[i] = (it->size == MEOW_BOX_WEIGHT) ? it->weight : 0;
if (it->size == MEOW_BOX_WEIGHT) {
ext[i] = 0; nw++; wsum += it->weight;
} else {
ext[i] = item_extent(it, main_is_w);
}
foot += (long long)lead[i] + trail[i] + ext[i];
}
foot += (long long)gap * (n - 1);
long long leftover = (long long)main_size - foot;
if (nw > 0 && leftover > 0) {
/* 均分剩余到权重项;余数塞给最后一个权重项铺满精确 */
long long consumed = 0;
for (int i = 0; i < n; i++) {
if (wgt[i] > 0) { ext[i] = (int)(leftover * wgt[i] / wsum); consumed += ext[i]; }
}
for (int i = n - 1; i >= 0; i--) {
if (wgt[i] > 0) { ext[i] += (int)(leftover - consumed); break; }
}
leftover = 0;
}
/* 主轴对齐(仅当无权重、leftover != 0 有意义;有权重时 leftover≈0 → START 等效) */
int off0 = 0;
int intergap = gap;
if (nw == 0) {
if (leftover > 0) {
switch (b->main_align) {
case MEOW_BOX_ALIGN_CENTER: off0 = (int)(leftover / 2); break;
case MEOW_BOX_ALIGN_END: off0 = (int)leftover; break;
case MEOW_BOX_ALIGN_SPACE_BETWEEN: off0 = 0;
intergap = (n > 1) ? gap + (int)(leftover / (n - 1)) : gap; break;
case MEOW_BOX_ALIGN_SPACE_AROUND: off0 = (int)(leftover / (2 * n));
intergap = gap + (int)(leftover / n); break;
case MEOW_BOX_ALIGN_SPACE_EVENLY: off0 = (int)(leftover / (n + 1));
intergap = gap + (int)(leftover / (n + 1)); break;
default: off0 = 0; break; /* START */
}
} else {
off0 = 0; intergap = gap; /* 溢出:从起点排(可越界,交滚动容器) */
}
}
/* 主轴定位 + 交叉轴定位,写子控件矩形(主轴起点 = 内容原点 cx/cy)。
* rel = 相对内容原点的主轴游标;content_rel = 本项内容相对起点;content_pos = 绝对主轴坐标。 */
int main_base = main_is_w ? cx : cy;
int rel = off0;
for (int i = 0; i < n; i++) {
const meow_box_item *it = &b->items[i];
int content_rel = rel + lead[i];
int content_pos = main_base + content_rel;
if (it->w) {
int r[4]; meow_widget_rect(it->w, r);
meow_widget_opts o; int has = (meow_widget_opts_get(it->w, &o) == 0);
int cmx = (has && o.margin_x > 0) ? o.margin_x : 0;
int cmy = (has && o.margin_y > 0) ? o.margin_y : 0;
int cm = main_is_w ? cmy : cmx;
int base = main_is_w ? r[3] : r[2];
int cross = 0, cross_ext = 0;
if (b->cross_align == MEOW_BOX_CROSS_STRETCH) {
cross_ext = cross_size - 2 * cm; if (cross_ext < 0) cross_ext = 0;
cross = cm;
} else {
cross_ext = base;
int pad = cross_size - 2 * cm - cross_ext; if (pad < 0) pad = 0;
if (b->cross_align == MEOW_BOX_CROSS_CENTER) cross = cm + pad / 2;
else if (b->cross_align == MEOW_BOX_CROSS_END) cross = cm + pad;
else cross = cm; /* START */
}
if (main_is_w)
meow_widget_set_rect(it->w, content_pos, cy + cross, ext[i], cross_ext);
else
meow_widget_set_rect(it->w, cx + cross, content_pos, cross_ext, ext[i]);
} else if (it->sub) {
meow_box *sb = it->sub;
int base = main_is_w ? sb->h : sb->w;
int cross = 0, cross_ext = 0;
if (b->cross_align == MEOW_BOX_CROSS_STRETCH) {
cross_ext = cross_size; if (cross_ext < 0) cross_ext = 0;
cross = 0;
} else {
cross_ext = base;
int pad = cross_size - cross_ext; if (pad < 0) pad = 0;
if (b->cross_align == MEOW_BOX_CROSS_CENTER) cross = pad / 2;
else if (b->cross_align == MEOW_BOX_CROSS_END) cross = pad;
else cross = 0;
}
if (main_is_w) { sb->x = content_pos; sb->y = cy + cross; sb->w = ext[i]; sb->h = cross_ext; }
else { sb->x = cx + cross; sb->y = content_pos; sb->w = cross_ext; sb->h = ext[i]; }
}
if (i < n - 1)
rel = content_rel + ext[i] + trail[i] + intergap;
else
rel = content_rel + ext[i] + trail[i];
}
}
/* ---- 求解:单遍,显式静态栈展开嵌套(深度≤MEOW_BOX_MAX_DEPTH)---- */
static meow_box *g_stack[MEOW_BOX_MAX_DEPTH];
void meow_box_arrange(meow_box *b)
{
if (!b) return;
int top = 0;
g_stack[0] = b;
while (top >= 0) {
meow_box *cur = g_stack[top--];
box_layout_one(cur); /* 写全部直接子项矩形(含 sub->rect) */
for (int i = 0; i < cur->n; i++) {
meow_box *sb = cur->items[i].sub;
if (!sb) continue;
if (top + 1 >= MEOW_BOX_MAX_DEPTH) continue; /* 深度上限:跳过(矩形已被父写定) */
g_stack[++top] = sb;
}
}
}