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,剔除宿主测试二进制与编译产物。
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;
|
||
}
|
||
}
|
||
}
|