291 lines
11 KiB
C
291 lines
11 KiB
C
/*
|
||
* 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;
|
||
/* 递归修正(§A.4「子视图随父进出」):父已进入时挂上的子视图**立即**进入
|
||
* (on_enter 不遗漏、参与事件分发)。页在 push/pop 时仍由 view_enter/view_leave 递归驱动,
|
||
* 此处只补「运行期动态挂子」的间隙(hello 目录嵌套实为 create 后 page_push 前 add,不受影响)。 */
|
||
if (parent->state & MEOW_VIEW_STATE_ENTERED) view_enter(child);
|
||
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,**标注废弃**)----
|
||
* 设计定案 b(SDK_VIEW_FRAMEWORK_DESIGN.md §F.2:只做子视图容器嵌套,顶层页切仍走 activity
|
||
* 栈,不做页内子导航栈):本函数对为「将来 a」预留的桩,**不实现、不供依赖**——调用方不得
|
||
* 认为会建立/弹出子导航。页内「子页」请用 meow_view_add_child 挂 CONTAINER 子视图(组合嵌套),
|
||
* 或以 activity 栈驱动页切换(page_push/pop/replace)。保留符号仅为未来 a 的占位。 */
|
||
int meow_view_sub_push(meow_view *v, meow_view *sub)
|
||
{
|
||
(void)v; (void)sub;
|
||
return 0; /* 预留:将来在 ACTIVITY view 内维护子栈(见设计定案 b,不实现) */
|
||
}
|
||
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; }
|