Files
MeowOS-SDK/meow_ui_view.c
T
MeowOS 962888b96f 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

283 lines
10 KiB
C
Raw 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_view.c — meow_view 视图/页面框架骨架(§A 视图数据结构/嵌套/生命周期/页面导航)。
*
* meow_view 是**薄壳**:容器 + 生命周期 + 事件路由;叶子仍是 meow_widget(不改)。绘制走
* z 序绘制物注册表(on_draw 回调,非保留——不缓存像素)。opt-in:不调 meow_view_* 的 App
* 完全照旧(widget/activity/flush 不动)。页面模型 = 定案 b(顶层页切走 activity 栈,
* page_push/pop/replace 包装;不做页内子导航栈,sub_push/pop 为预留扩展位 a 桩)。
*/
#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>
#define MEOW_VIEW_BY_ID_MAX 32
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);
}
/* 登记/清除 id→view 映射(幂等)。 */
static void view_map_set(meow_view *v)
{
if (!v) return;
int id = v->id;
if (id >= 0 && id < MEOW_VIEW_BY_ID_MAX) g_view_by_id[id] = v;
}
static void view_map_clear(meow_view *v)
{
if (!v) return;
int id = v->id;
if (id >= 0 && id < MEOW_VIEW_BY_ID_MAX && g_view_by_id[id] == v) g_view_by_id[id] = NULL;
}
static meow_view *view_map_get(int id)
{
return (id >= 0 && id < MEOW_VIEW_BY_ID_MAX) ? g_view_by_id[id] : NULL;
}
/* 进入一个 view(置 ENTERED + on_enter),**递归 children**(子视图随父进出,§A.4)。
* 进入顺序:父 on_enter 先(记录顺序 home.enter→demo.enter),随后子视图依递归逐个进入;
* 子视图的 on_draw 经 meow_draw_view_entered(含祖先已进入判定)保证父 on_enter 重绘时可见。 */
static void view_enter(meow_view *v)
{
if (!v || (v->state & MEOW_VIEW_STATE_ENTERED)) return;
v->state |= MEOW_VIEW_STATE_ENTERED;
run_fn(v->hooks.on_enter, v);
for (int i = 0; i < v->nchildren; i++)
view_enter(v->children[i]);
}
/* 离开一个 view(清 ENTERED + on_leave;默认不销毁子 view/widget——弹回零重建),
* **递归 children**:先子后父(demo.leave→home.leave)。 */
static void view_leave(meow_view *v)
{
if (!v || !(v->state & MEOW_VIEW_STATE_ENTERED)) return;
for (int i = 0; i < v->nchildren; i++)
view_leave(v->children[i]);
v->state &= ~MEOW_VIEW_STATE_ENTERED;
run_fn(v->hooks.on_leave, v);
}
/* ============================================================ */
/* 生命周期 */
/* ============================================================ */
meow_view *meow_view_create(int id, meow_view_type type,
const meow_view_hooks *hooks, void *ctx)
{
meow_view *v = (meow_view *)calloc(1, sizeof *v);
if (!v) return NULL;
v->id = id;
v->type = (type == MEOW_VIEW_CONTAINER) ? MEOW_VIEW_CONTAINER : MEOW_VIEW_ACTIVITY;
v->x = v->y = v->w = v->h = 0;
v->z = 0;
v->state = MEOW_VIEW_STATE_CREATED;
if (hooks) v->hooks = *hooks;
v->ctx = ctx;
view_map_set(v);
meow_draw_view_register(v); /* 登记为绘制物(on_draw 经 z 序重画触发) */
run_fn(v->hooks.on_create, v);
if (v->hooks.on_layout) run_fn(v->hooks.on_layout, v); /* 布局:create 后跑一次 */
return v;
}
void meow_view_free(meow_view *v)
{
if (!v) return;
if (v->state & MEOW_VIEW_STATE_ENTERED) view_leave(v);
run_fn(v->hooks.on_destroy, v); /* 释放视图态资源 */
view_map_clear(v);
meow_draw_view_unregister(v); /* 注销 z 序绘制物 */
for (int i = 0; i < v->nwidgets; i++) /* 叶子控件 */
meow_widget_free(v->widgets[i]);
v->nwidgets = 0;
for (int i = 0; i < v->nchildren; i++) /* 子 view 递归 */
meow_view_free(v->children[i]);
v->nchildren = 0;
free(v);
}
/* ============================================================ */
/* 嵌套(组合视图树) */
/* ============================================================ */
int meow_view_add_child(meow_view *parent, meow_view *child)
{
if (!parent || !child || child == parent) return -1;
for (int i = 0; i < parent->nchildren; i++)
if (parent->children[i] == child) return -1; /* 去重 */
if (parent->nchildren >= MEOW_VIEW_MAX_CHILD) return -1;
parent->children[parent->nchildren++] = child;
child->parent = parent;
return 0;
}
int meow_view_add_widget(meow_view *v, meow_widget *w)
{
if (!v || !w) return -1;
for (int i = 0; i < v->nwidgets; i++)
if (v->widgets[i] == w) return -1;
if (v->nwidgets >= MEOW_VIEW_MAX_WIDGET) return -1;
v->widgets[v->nwidgets++] = w;
meow_draw_widget_set_view(w, v); /* z 深度源 = view 树;重算 auto z */
return 0;
}
void meow_view_set_z(meow_view *v, int z)
{
if (!v) return;
v->z = (z < 0) ? 0 : z;
meow_draw_view_set_z(v, v->z);
}
int meow_view_z(const meow_view *v)
{
return v ? v->z : 0;
}
/* ============================================================ */
/* 页面导航(包装 activity 栈——唯一驱动 on_enter/on_leave) */
/* ============================================================ */
void meow_view_page_push(int id)
{
meow_view *cur = g_top_view;
meow_view *next = view_map_get(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;
}
void meow_view_page_pop(void)
{
meow_view *cur = g_top_view;
view_leave(cur);
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;
}
/* 说明(诚实):replace 假定目标页已由 App 先 meow_view_create 指定 id 建好;本批不销毁旧页
* 对象(保留弹回),仅 leave+enter 切换——与设计「leave+destroy 当前」的差异见汇报。 */
void meow_view_page_replace(int id)
{
meow_view *cur = g_top_view;
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;
}
/* ---- 页内子导航(预留扩展位 a 桩:本批不实现,仅返回占位)---- */
int meow_view_sub_push(meow_view *v, meow_view *sub)
{
(void)v; (void)sub;
return 0; /* 预留:将来在 ACTIVITY view 内维护子栈 */
}
meow_view *meow_view_sub_pop(meow_view *v)
{
(void)v;
return NULL; /* 预留 */
}
/* ============================================================ */
/* 事件路由(§A.7:每个 view 的 on_event 先把事件发给后代(按 z 降序),命中即消费,
* 再落本 view 的控件)。SDK 提供递归树走;叶子 on_event 负责本 view 的 widget 命中。 */
/* ============================================================ */
static int view_dispatch_event(meow_view *v, int ev, int a, int b)
{
if (!v || !(v->state & MEOW_VIEW_STATE_ENTERED)) return 0; /* 仅已进入的视图参与 */
if (v->nchildren > 0) {
meow_view *kids[MEOW_VIEW_MAX_CHILD];
int nn = v->nchildren;
for (int i = 0; i < nn; i++) kids[i] = v->children[i];
/* 按 z 降序(同 z 保持挂载序稳定);z 同值时树序,靠后挂的在先。 */
for (int i = 1; i < nn; i++) {
meow_view *key = kids[i];
int j = i - 1;
while (j >= 0 && meow_view_z(kids[j]) < meow_view_z(key)) { kids[j + 1] = kids[j]; j--; }
kids[j + 1] = key;
}
for (int i = 0; i < nn; i++)
if (view_dispatch_event(kids[i], ev, a, b)) return 1; /* 子命中即消费 */
}
if (v->hooks.on_event && v->hooks.on_event(v, ev, a, b, v->ctx)) return 1; /* 落到本 view 事件 */
return 0;
}
int meow_view_dispatch_event(int ev, int a, int b)
{
return view_dispatch_event(g_top_view, ev, a, b);
}
/* ============================================================ */
/* 坐标 / 查询 */
/* ============================================================ */
int meow_view_screen_x(const meow_view *v)
{
int x = v ? v->x : 0;
const meow_view *p = v ? v->parent : NULL;
while (p) { x += p->x; p = p->parent; }
return x;
}
int meow_view_screen_y(const meow_view *v)
{
int y = v ? v->y : 0;
const meow_view *p = v ? v->parent : NULL;
while (p) { y += p->y; p = p->parent; }
return y;
}
meow_view *meow_view_parent(const meow_view *v) { return v ? v->parent : NULL; }
int meow_view_get_state(const meow_view *v) { return v ? (int)v->state : 0; }