- 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,由主会话统一处理)。
308 lines
12 KiB
C
308 lines
12 KiB
C
/*
|
||
* 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;
|
||
}
|
||
}
|
||
}
|