feat(sdk): 绘制期通用clip + view递归/on_event接线 (agent-195) — ①栈式clip rect(g_clip_stack[16]), 所有写像素原语过clip, meow_widget_draw push/pop控件矩形(含W_CUSTOM, 根治越界脏区), IME/模态顶层全屏不受影响 ②view enter/leave递归children + meow_view_dispatch_event(z降序子>父命中即消费); hello App层显式驱动退役

meow_ui.a→da4d3a7d; hello→c655ba43; 宿主4套件全PASS(含clip越界断言+view递归断言)
出入: W_SLIDER拇指在max处被裁(既有几何瑕疵可见化,待修); on_draw祖先已进入即画
This commit is contained in:
MeowOS committed 2026-08-31 05:24:43 +08:00
1 parent b2e7cc8c70
commit 32a6a8ed1e
14 files changed
+257 -44

No files matched your search

+72
View File
@@ -14,6 +14,7 @@
*/
#include "meow_ui.h"
#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp:真实像素断言 */
#include "meow_ui_view.h" /* 任务② view 递归 + on_event 路由断言 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -95,6 +96,31 @@ static void free_all(void)
/* §6.3 通用长按测试回调(开闸长按判定用;事件本身经 meow_input_pop 断言)。 */
static void lp_recv(int x, int y) { (void)x; (void)y; }
/* ---- 任务① 绘制期 clip:W_CUSTOM 越界绘制被裁掉(render 回调经 flag 切换越界)----
* 控件几何硬编码为 (40,40,80,40)(与下方创建一致):meow_widget 是对外不透明类型,
* 测试经 meow_widget_custom_create(40,40,80,40,...) 建在同址,回调按常量绘制。 */
static int g_custom_oob = 1; /* 1=画越界实心块+越界文本;0=只画控件内 */
static void clip_oob_render(meow_widget *w, const meow_theme *th, void *ctx)
{
(void)w; (void)th; (void)ctx;
meow_ui_fill_rect(45, 45, 20, 20, 0xFF0000); /* 控件 (40,40,80,40) 内:必画红块 */
if (g_custom_oob) {
/* 越界(右/下,出控件矩形 [40..120)×[40..80)):应被 widget clip 裁掉 */
meow_ui_fill_rect(140, 40, 60, 40, 0xFF0000);
meow_ui_text(160, 100, "OOB", 20, 0xFF0000);
}
}
/* ---- 任务② view 递归 enter/leave + on_event 子>父:trace 顺序 + 命中计数 ---- */
static int g_vtrace[16]; static int g_vtrace_n = 0;
static void vtrace_enter(meow_view *v, void *ctx) { (void)ctx; g_vtrace[g_vtrace_n++] = v->id; }
static void vtrace_leave(meow_view *v, void *ctx) { (void)ctx; g_vtrace[g_vtrace_n++] = -v->id; }
static int g_ev_child_hits = 0, g_ev_parent_hits = 0;
static int vev_child(meow_view *v, int ev, int a, int b, void *ctx)
{ (void)v; (void)ctx; if (ev == 7 && a == 10 && b == 10) { g_ev_child_hits++; return 1; } return 0; }
static int vev_parent(meow_view *v, int ev, int a, int b, void *ctx)
{ (void)v; (void)ctx; if (ev == 7) { g_ev_parent_hits++; return 1; } return 0; }
int main(void)
{
setenv("MEOW_UI_DUMP", "/tmp/b2.ppm", 1);
@@ -361,6 +387,52 @@ int main(void)
printf("PASS long-press: trigger/suppress-tap/short-tap/cancel-move/configurable/opt-in\n");
}
/* ---------- 任务① 绘制期 clip:W_CUSTOM 越界绘制被裁掉 ---------- */
{
const meow_theme *thc = meow_theme_get();
int bg_r, bg_g, bg_b; UNPK(thc->bg, bg_r, bg_g, bg_b);
meow_widget *cw = W(meow_widget_custom_create(40, 40, 80, 40, clip_oob_render, NULL, NULL));
/* a) clip 生效:经 meow_widget_draw(push 控件矩形)→ 越界块/文本被裁掉 */
g_custom_oob = 1;
meow_ui_clear(thc->bg);
meow_widget_draw(cw);
assert(region_has(45, 45, 20, 20, 0xFF, 0x00, 0x00) > 30); /* 控件内红块画出 */
assert(px_near(40 + 80 + 25, 40 + 10, bg_r, bg_g, bg_b)); /* 越界实心块被裁 → bg */
assert(px_near(40 + 80 + 45, 40 + 65, bg_r, bg_g, bg_b)); /* 越界文本被裁 → bg */
/* b) 对照:无 clip 直绘同尺寸块会画出(证明是 clip 在裁) */
meow_ui_clear(thc->bg);
meow_ui_fill_rect(40 + 80 + 20, 40, 60, 40, 0x00FF00);
assert(region_has(40 + 80 + 20, 40, 60, 40, 0x00, 0xFF, 0x00) > 100);
printf("PASS clip: W_CUSTOM out-of-bounds fill+text clipped by widget clip\n");
}
/* ---------- 任务② view 递归 enter/leave + on_event 子>父命中 ---------- */
{
static const meow_view_hooks ph = { .on_enter = vtrace_enter, .on_leave = vtrace_leave,
.on_event = vev_parent };
static const meow_view_hooks ch = { .on_enter = vtrace_enter, .on_leave = vtrace_leave,
.on_event = vev_child };
/* 页 id 须 < MEOW_VIEW_BY_ID_MAX(32) 才能进 g_view_by_id(page_push 查找) */
meow_view *pv = meow_view_create(3, MEOW_VIEW_ACTIVITY, &ph, NULL);
meow_view *cv = meow_view_create(4, MEOW_VIEW_CONTAINER, &ch, NULL);
assert(meow_view_add_child(pv, cv) == 0);
g_vtrace_n = 0; g_ev_child_hits = 0; g_ev_parent_hits = 0;
meow_view_page_push(3); /* 进入 pv → 递归进 cv:pv.on_enter→cv.on_enter */
assert(g_vtrace_n == 2 && g_vtrace[0] == 3 && g_vtrace[1] == 4);
/* 命中 (10,10):先发子(按 z 降序),子命中即消费,父不再收 */
assert(meow_view_dispatch_event(7, 10, 10) == 1);
assert(g_ev_child_hits == 1 && g_ev_parent_hits == 0);
/* 命中 (20,20):子不命中 → 落父 on_event */
assert(meow_view_dispatch_event(7, 20, 20) == 1);
assert(g_ev_child_hits == 1 && g_ev_parent_hits == 1);
/* 离开 pv → 递归离开:cv.on_leave(-4) → pv.on_leave(-3) */
g_vtrace_n = 0;
meow_view_page_pop();
assert(g_vtrace_n == 2 && g_vtrace[0] == -4 && g_vtrace[1] == -3);
meow_view_free(pv); /* 递归释放 cv */
printf("PASS view: recursive enter/leave order + on_event child>parent hit\n");
}
/* ---------- 绘制所有控件到 back_buf(5x7 兜底),flush 出屏幕 ---------- */
const meow_theme *th = meow_theme_get();
meow_ui_clear(th->bg);