feat(sdk): M6.39批 — LazyGrid/LazyColumn/LazyRow MD3容器化 + SDK全局App内底部上滑回桌面(系统手势优先)

- meow_ui_widget.c: W_LIST/meow_list/LazyColumn/LazyRow默认container=1(MD3圆角卡surfaceContainer+tonal选中+卡内按压+去分隔线) + 新增meow_list_set_container; 平坦回退set_container(0)
- meow_lazygrid.c: lg_draw_cell容器化(卡内缩8,surfaceContainer圆角+tonal选中+按压)
- meow_ui_input.c: 新增meow_ui_input_is_home_swipe(镜像np_home_gesture:屏底28px热区+上移65px+竖向主导,与左缘返回/列表中滑互斥) + MEOW_EV_HOME分发→meow_ui_nav_request_exit(系统保留手势**优先于App on_swipe**,任意App内生效→MSG_NAV EXIT→批①保活回桌面; 不调request_exit避免进程退出断送保活)
- 声明: meow_ui_input.h / meow_ui_widgets.h 各追加
- 宿主测试: test_meow_ui_home_gesture 19-0 / test_meow_ui_lazygrid 32-0, 0warning
This commit is contained in:
AskaEth committed 2026-09-04 13:56:11 +08:00
1 parent d0a5901ffb
commit 501867b362
7 files changed
+350 -32

No files matched your search

+7
View File
@@ -36,6 +36,13 @@ void meow_ui_request_exit(void); /* 请求退出(相当于返回/
* x<=0 时清掉此前画的条(填回背景色)。不发 flush,由调用方 meow_ui_flush_region 上屏。 */
void meow_ui_swipe_hint(int x);
/* 批②(2026-09-04):底部热区上滑 → 回桌面(home)判定,镜像 launcher np_gesture.h
* np_home_gesture 同口径。down_x/down_y=抬手原点,dx/dy=抬手位移(释放-原点),
* fb_h=屏高(SDK 传 meow_ui_height())。命中=起点在底部热区(最后 HOME_EDGE_H≈28px)且
* **不在左缘**(down_x>=EDGE_W,与左缘返回互斥)+ 上移 ≥HOME_SWIPE(65px) + 竖向主导(adx<-dy)。
* 供宿主单测直调;触摸状态机(meow_ui_input.c touch_release)与 App 内判据共用,避免两端漂移。 */
int meow_ui_input_is_home_swipe(int down_x, int down_y, int dx, int dy, int fb_h);
/* ============================================================ */
/* §6.3 通用长按(2026-08-31) */
/* ============================================================ */
+4
View File
@@ -266,6 +266,10 @@ int meow_list_selected(meow_widget *w);
typedef void (*meow_list_tap_fn)(int idx, void *ctx);
void meow_list_set_on_tap(meow_widget *w, meow_list_tap_fn cb, void *ctx); /* 命中行→回调 */
int meow_list_tap_at(meow_widget *w, int x, int y); /* 命中行:置选中+回调,返回 idx/-1 */
/* MD3 容器模式(W_LIST 共用,§5.5/§5.8.6):0=扁平满宽项+分隔线(回退);1=每项外套 surfaceContainer
* 圆角卡+项间空隙(去分隔线),选中=primaryContainer/按压=primary α26(卡内 tonal)。
* 覆盖 meow_list_create / meow_lazycolumn_create / meow_lazyrow_create。返回 0=成功;无效=负值。 */
int meow_list_set_container(meow_widget *w, int on);
/* ============================================================ */
/* §5.5 设计模板 A② 结构化列表(W_LISTVIEW):行=图标+标题+副标题+右侧操作 + 选中
+24 -13
View File
@@ -268,35 +268,46 @@ int meow_lazygrid_hit(meow_lazygrid *g, int x, int y)
}
/* ---- 绘制(只画可见行;clip 钳到宫格视口)---- */
/* MD3 容器(默认 renderer P0):格=格内对称内缩 surfaceContainer 圆角卡(radius_card)+ 卡内 tonal
* 选中(primaryContainer)/ 按压(primary α26);画布=整格(触控目标),卡在其内对称内缩(同
* meow_listview 行>卡范式:卡片外仍命中整格,视觉卡留出呼吸空隙)。完全自绘(set_item_draw)
* 覆盖本 renderer,App 自理容器观感。 */
#define MEOW_LAZYGRID_CARD_MARGIN 8 /* 格内对称边距(卡内缩;与 cell gap 叠加出卡间空隙) */
static void lg_draw_cell(meow_lazygrid *g, const meow_theme *th, int index,
int cell_x, int cell_y, meow_lazygrid_item *it)
{
int sel = (index == g->sel);
int txtcol = sel ? th->onPrimaryContainer : th->onSurface;
int subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
uint32_t bg = sel ? th->primaryContainer : th->surfaceContainerLow;
meow_gfx_fill_round(cell_x, cell_y, g->cell_w, g->cell_h, th->radius_card, bg);
int m = MEOW_LAZYGRID_CARD_MARGIN;
int cw = g->cell_w - 2 * m, chh = g->cell_h - 2 * m; /* 卡尺寸(格内对称内缩) */
int cx = cell_x + m, cy = cell_y + m; /* 卡左上 */
int press = meow_input_touch_down() &&
meow_input_touch_x() >= cell_x && meow_input_touch_x() < cell_x + g->cell_w &&
meow_input_touch_y() >= cell_y && meow_input_touch_y() < cell_y + g->cell_h;
meow_gfx_fill_round(cx, cy, cw, chh, th->radius_card, th->surfaceContainer);
if (sel) meow_gfx_fill_round(cx, cy, cw, chh, th->radius_card, th->primaryContainer);
else if (press) meow_gfx_fill_round_alpha(cx, cy, cw, chh, th->radius_card, th->primary, 26);
int pad = 8;
int thh = meow_ui_text_height(th->font_body);
int chh = g->cell_h, cw = g->cell_w;
/* 图标(可选):居中于格顶 ~50%。 */
/* 图标(可选):居中于卡顶 ~50%(卡中心=格中心,因对称内缩)。 */
if (it->icon) {
int isz = cw * 6 / 10; if (isz > chh * 5 / 10) isz = chh * 5 / 10;
int isz = g->cell_w * 6 / 10; if (isz > g->cell_h * 5 / 10) isz = g->cell_h * 5 / 10;
if (isz < 16) isz = 16;
int icx = cell_x + cw / 2, icy = cell_y + pad + isz / 2;
int icx = cx + cw / 2, icy = cy + pad + isz / 2;
meow_ui_img_scaled(icx - isz / 2, icy - isz / 2, isz, isz, (meow_img *)it->icon);
}
/* 标题 + 副标题(主界面:名 + 状态)。底部对齐。 */
/* 标题 + 副标题(主界面:名 + 状态)。卡内底部对齐。 */
int has_sub = (it->subtitle && it->subtitle[0]);
int ty = cell_y + chh - pad - thh - (has_sub ? (thh + 2) : 0);
if (it->title) meow_ui_text_center(cell_x + cw / 2, ty, it->title, th->font_body, txtcol);
if (has_sub) meow_ui_text_center(cell_x + cw / 2, ty + thh + 2, it->subtitle, th->font_body, subcol);
int ty = cy + chh - pad - thh - (has_sub ? (thh + 2) : 0);
if (it->title) meow_ui_text_center(cx + cw / 2, ty, it->title, th->font_body, txtcol);
if (has_sub) meow_ui_text_center(cx + cw / 2, ty + thh + 2, it->subtitle, th->font_body, subcol);
/* 选中描边(轮廓);禁用整体变淡。 */
if (sel) meow_ui_draw_rect_round(cell_x, cell_y, cw, chh, th->radius_card, th->primary);
/* 选中描边(轮廓);禁用卡内变淡(圆角 α,防圆角外叠出灰方块)。 */
if (sel) meow_ui_draw_rect_round(cx, cy, cw, chh, th->radius_card, th->primary);
if (!it->enabled)
meow_ui_fill_rect_alpha(cell_x, cell_y, cw, chh, th->onSurface, 90);
meow_gfx_fill_round_alpha(cx, cy, cw, chh, th->radius_card, th->onSurface, 90);
}
void meow_lazygrid_draw(meow_lazygrid *g, const meow_theme *th)
+47 -1
View File
@@ -27,6 +27,7 @@
#include "meow_ui_vol.h"
#include "meow_ui_media.h"
#include "meow_ui_view.h" /* M7.0a 缺口#7:把 meow_view_dispatch_event 接入 meow_ui_run */
#include "meow_ui_nav.h" /* 批②:底部上滑回桌面→meow_ui_nav_request_exit(MSG_NAV EXIT→批①保活) */
#include <stdio.h>
#include <stdlib.h>
@@ -57,6 +58,8 @@
#define SWIPE_THRESH 65 /* px:抬起位移超过视为滑动(GESTURE_BACK: 90→65,用户实测 90 偏多,65 更顺手) */
#define EDGE_W 24 /* 左滑返回的「左缘起点」宽度(t_dx < EDGE_W 才认返回手势) */
#define HOME_EDGE_H 28 /* 批②:底部起滑区高(自屏底向上;镜像 np_gesture.h NP_HOME_EDGE_H) */
#define HOME_SWIPE 65 /* 批②:底部上滑位移阈值(镜像 np_gesture.h NP_HOME_SWIPE;= SWIPE_THRESH) */
#define MEOW_LONG_PRESS_MS 500u /* §6.3 通用长按默认阈值(ms;meow_ui_set_long_press_ms 可配) */
#define MEOW_LONG_PRESS_SLOP 12 /* §6.3 按下后「仍算按住」的最大位移(px);超此取消长按 */
#define QUEUE_N 128
@@ -71,6 +74,10 @@ void meow_ui_gesture_end(void);
* (路由 meow_ui_sys_notif_pull),不下发给 APP 的 on_swipe(顶部热区为系统保留手势)。
* 与左缘返回(SWIPE_LEFT)/ meow_ui_swipe_hint 互不影响——后者仍全屏生效,详见下方触摸机。 */
#define MEOW_EV_NOTIF_PULL 5
/* 批②:底部热区上滑 → 回桌面(后台保活,走批①链路)。系统手势,a=MEOW_SWIPE_UP(供
* on_swipe 接管、view 树消费),b=位移(px)。定义在 input.c 内部(同 MEOW_EV_NOTIF_PULL
* 未入公共头),宿主单测按同值声明(见 test_meow_ui_home_gesture.c)。 */
#define MEOW_EV_HOME 9
/* 顶部下滑 → 通知中心判定:纵向 DOWN 手势的起点(origin_y)是否落在顶部热区内。
* 热区由 meow_ui_statusbar_hotzone() 给出(非全屏=状态栏高、全屏=MEOW_SBAR_HOTZONE_FS、
@@ -80,6 +87,20 @@ int meow_ui_input_is_notif_pull(int origin_y, int hotzone)
return origin_y >= 0 && origin_y < hotzone;
}
/* 批② 底部热区上滑 → 回桌面(home)判定:镜像 launcher np_gesture.h np_home_gesture 同口径
* (热区 28px / 上移 ≥65px / 竖向主导 / 避开左缘返回列),SDK 侧 fb_h=屏幕高(复用
* meow_ui_height())。供宿主单测直调(判据与触摸状态机 touch_release 共用)。本判定把
* 「底部热区起滑 + 上移超阈值且竖向主导」从普通上滑中剥离——中部上滑/滚动仍归 App
* (LazyColumn/LazyGrid 滚动不受影响),底部热区的横向滑仍走原有左缘返回/横向逻辑。 */
int meow_ui_input_is_home_swipe(int down_x, int down_y, int dx, int dy, int fb_h)
{
int adx = dx < 0 ? -dx : dx;
if (down_x >= EDGE_W && down_y >= fb_h - HOME_EDGE_H && down_y >= 0 &&
dy <= -HOME_SWIPE && adx < -dy)
return 1;
return 0;
}
static int g_thread_started = 0;
static pthread_t g_thread;
static volatile int g_thread_run = 1;
@@ -326,7 +347,16 @@ static void touch_release(void)
}
} else {
int dir = mdy > 0 ? MEOW_SWIPE_DOWN : MEOW_SWIPE_UP;
push_ev(MEOW_EV_SWIPE, dir, ady > adx ? ady : adx);
int dist = ady > adx ? ady : adx;
/* 批②:竖向主导且上移(dir==UP)时,判「底部热区上滑 → 回桌面」。仅当起点在底部
* 热区(最后 HOME_EDGE_H px)且**不在左缘**(down_x>=EDGE_W,与左缘返回互斥)且
* 上移 ≥HOME_SWIPE 且竖向主导(adx<-dy)才命中;否则照常 SWIPE_UP(中部上滑/滚动交
* 由 App)。命中改入队 MEOW_EV_HOME(区别于普通上滑,供 run 循环按兜底回桌面)。 */
if (dir == MEOW_SWIPE_UP &&
meow_ui_input_is_home_swipe(t_dx, t_dy, mdx, mdy, meow_ui_height()))
push_ev(MEOW_EV_HOME, MEOW_SWIPE_UP, dist);
else
push_ev(MEOW_EV_SWIPE, dir, dist);
}
} else {
push_ev(MEOW_EV_TAP, t_cx, t_cy);
@@ -735,6 +765,22 @@ void meow_ui_run(const meow_ui_events *ev)
} else if (e.a == MEOW_SWIPE_LEFT && sb_active) {
meow_ui_request_exit();
}
} else if (e.type == MEOW_EV_HOME) {
/* 系统级「底部热区上滑回桌面」(用户 2026-09-04 批②补全,任意 App 内生效):
* 屏底边缘热区+竖向主导上滑已由 meow_ui_input_is_home_swipe 过滤(与 App 中部
* 滚动、底部横向滑、左缘返回互斥),故作为**系统保留手势**优先于 App on_swipe /
* view 树——否则现有 App(ncm/hello/files/reader 均注册 on_swipe)会接管导致
* App 内上滑不回桌面。命中调 meow_ui_nav_request_exit()(MSG_NAV EXIT → launcher
* 批① meow_bg_add 保活,进程保留转后台、SDK present 自抑);**不**调
* meow_ui_request_exit()——那会断送保活(进程 exit(0))。launcher 据 app 声明
* (background_keepalive/background_audio+媒体活跃)决定无限期/宽限。 */
if (in_startup) continue; /* 启动窗口误触残留:丢弃 */
meow_ui_swipe_hint(-1);
meow_ui_gesture_end(); /* §6.4:抬手边沿,释放手势捕获 */
/* IME 打开时软键盘覆盖底部:键盘拥有底部区,把事件交给 IME 且**绝不**回桌面
* (防输入时误退)。 */
if (meow_ime_active()) { (void)meow_ime_handle_event(&e); continue; }
meow_ui_nav_request_exit();
} else if (e.type == MEOW_EV_NOTIF_PULL) {
if (in_startup) continue; /* 启动窗口顶区误触残留:丢弃,防进入即开通知中心 */
/* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发
+63 -10
View File
@@ -407,6 +407,10 @@ meow_widget *meow_list_create(int x, int y, int w, int h, int count,
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->count = count; t->src = src; t->ctx = ctx; t->sel = 0;
/* M6.37 P1:SDK 默认列表(meow_list_create / meow_lazycolumn_create / meow_lazyrow_create,皆 W_LIST)
* 走 MD3 容器模式——每项=surfaceContainer 圆角卡 + 项间空隙 + 去分隔线 + 卡内 tonal 选中/按压,
* 与 meow_listview 容器同范式(全局统一)。需扁平的宿主可显式 meow_list_set_container(w,0) 回退。 */
t->container = 1;
meow_draw_widget_register(t);
return t;
}
@@ -1014,6 +1018,17 @@ int meow_list_tap_at(meow_widget *w, int x, int y)
return idx;
}
/* MD3 容器模式(W_LIST 共用,§5.5/§5.8.6):0=扁平满宽项+分隔线(回退);1=每项外套 surfaceContainer
* 圆角卡+项间空隙(去分隔线),选中=primaryContainer/按压=primary α26(卡内 tonal)。
* 覆盖 meow_list_create / meow_lazycolumn_create / meow_lazyrow_create。返回 0=成功;无效=负值。 */
int meow_list_set_container(meow_widget *w, int on)
{
if (!w || w->type != W_LIST) return -1;
w->container = on ? 1 : 0;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
return 0;
}
/* ============================================================ */
/* §5.5 结构化列表(W_LISTVIEW):行=图标+标题+副标题+右侧操作+选中
* ------------------------------------------------------------
@@ -1730,7 +1745,11 @@ void meow_widget_draw(meow_widget *w)
}
case W_LIST: {
/* §5.8.6 列表容器(LazyColumn/LazyRow):只画可见项(虚拟化)+ 滚动偏移。
* 纵向=行高 item_h(默认 44,兼容既有 meow_list_create);横向=LazyRow(列宽=item_h)。 */
* 纵向=行高 item_h(默认 44,兼容既有 meow_list_create);横向=LazyRow(列宽=item_h)。
* MD3 容器模式(w->container=1,默认):每项外套 surfaceContainer 圆角卡(12dp)+ 项间空隙
* (去分隔线),选中=primaryContainer/按压=primary α26(卡内 tonal),与 meow_listview 容器同范式;
* 卡在项内交叉轴内缩 MEOW_LISTV_CARD_MARGIN、滚动轴对称内缩 MEOW_LISTV_CARD_GAP(行中心=卡中心)。
* 非容器(meow_list_set_container(0) 回退):满幅扁平行 + primary 选中 + onSurfaceVariant 分隔线。 */
int pad = 16;
int item = list_item(w);
int vp = w->horizontal ? w->w : w->h;
@@ -1747,36 +1766,70 @@ void meow_widget_draw(meow_widget *w)
* 画到视口外 → 标脏并上屏 → 标题栏/底部残留脏区,P3)。 */
int vx0 = w->x, vy0 = w->y, vx1 = w->x + w->w, vy1 = w->y + w->h;
int thh = meow_ui_text_height(th->font_body);
int cm = w->container ? MEOW_LISTV_CARD_MARGIN : 0; /* 卡交叉轴内缩(纵向=水平→横向=垂直) */
int cgap = w->container ? MEOW_LISTV_CARD_GAP : 0; /* 卡滚动轴对称内缩(去分隔线、留空隙) */
for (int i = first; i < last; i++) {
char label[64] = "";
if (!w->src || !w->src(i, label, (int)sizeof label, w->ctx)) continue;
int pos = i * item - off; /* 相对容器起点的可视位置 */
int sel = (i == w->sel);
if (w->horizontal) {
int rx = w->x + pos;
if (rx >= vx1) break; /* 完全在右缘外 */
if (rx + item <= vx0) continue; /* 完全在左缘外(滚出残余) */
int cl = rx > vx0 ? rx : vx0; /* 可见左界 */
int cr = (rx + item) < vx1 ? (rx + item) : vx1; /* 可见右界 */
if (i == w->sel) meow_ui_fill_rect(cl, w->y, cr - cl, w->h, th->primary);
uint32_t col = (i == w->sel) ? 0xFFFFFF : th->onSurface;
int ctop = w->y + cm; /* 卡顶(交叉轴内缩) */
int chh = w->h - 2 * cm; /* 卡高 */
int clft = rx + cgap / 2; /* 卡左(滚动轴对称内缩) */
int cwid = item - cgap; /* 卡宽 */
int press = meow_input_touch_down() &&
meow_input_touch_x() >= rx && meow_input_touch_x() < rx + item &&
meow_input_touch_y() >= w->y && meow_input_touch_y() < w->y + w->h;
if (w->container) {
meow_gfx_fill_round(clft, ctop, cwid, chh, MEOW_LISTV_CARD_RADIUS, th->surfaceContainer);
if (sel) meow_gfx_fill_round(clft, ctop, cwid, chh, MEOW_LISTV_CARD_RADIUS, th->primaryContainer);
else if (press) meow_gfx_fill_round_alpha(clft, ctop, cwid, chh, MEOW_LISTV_CARD_RADIUS, th->primary, 26);
} else {
if (sel) meow_ui_fill_rect(cl, w->y, cr - cl, w->h, th->primary);
}
uint32_t col = sel ? (w->container ? th->onPrimaryContainer : 0xFFFFFF) : th->onSurface;
int ty = w->y + (w->h - thh) / 2;
if (rx >= vx0 && rx + item <= vx1) /* 仅整项可见才画文本(防泄漏) */
meow_ui_text_center(rx + item / 2, ty, label, th->font_body, col);
int sxp = rx + item - 1; /* 分隔线:仅当落在可见区 */
if (sxp >= cl && sxp < cr) meow_ui_fill_rect(sxp, w->y, 1, w->h, th->onSurfaceVariant);
if (!w->container) { /* 非容器才画分隔线(容器模式去冗余) */
int sxp = rx + item - 1; /* 分隔线:仅当落在可见区 */
if (sxp >= cl && sxp < cr) meow_ui_fill_rect(sxp, w->y, 1, w->h, th->onSurfaceVariant);
}
} else {
int ry = w->y + pos;
if (ry >= vy1) break; /* 完全在底缘外 */
if (ry + item <= vy0) continue; /* 完全在顶缘外(滚出残余) */
int cl = ry > vy0 ? ry : vy0; /* 可见顶界 */
int cr = (ry + item) < vy1 ? (ry + item) : vy1; /* 可见底界 */
if (i == w->sel) meow_ui_fill_rect(w->x, cl, w->w, cr - cl, th->primary);
uint32_t col = (i == w->sel) ? 0xFFFFFF : th->onSurface;
int clft = w->x + cm; /* 卡左(交叉轴内缩) */
int cwid = w->w - 2 * cm; /* 卡宽 */
int ctop = ry + cgap / 2; /* 卡顶(滚动轴对称内缩) */
int chh = item - cgap; /* 卡高 */
int press = meow_input_touch_down() &&
meow_input_touch_x() >= w->x && meow_input_touch_x() < w->x + w->w &&
meow_input_touch_y() >= ry && meow_input_touch_y() < ry + item;
if (w->container) {
meow_gfx_fill_round(clft, ctop, cwid, chh, MEOW_LISTV_CARD_RADIUS, th->surfaceContainer);
if (sel) meow_gfx_fill_round(clft, ctop, cwid, chh, MEOW_LISTV_CARD_RADIUS, th->primaryContainer);
else if (press) meow_gfx_fill_round_alpha(clft, ctop, cwid, chh, MEOW_LISTV_CARD_RADIUS, th->primary, 26);
} else {
if (sel) meow_ui_fill_rect(w->x, cl, w->w, cr - cl, th->primary);
}
uint32_t col = sel ? (w->container ? th->onPrimaryContainer : 0xFFFFFF) : th->onSurface;
int txx = (w->container ? clft : w->x) + pad; /* 卡内文本左缘 */
int ty = ry + (item - thh) / 2;
if (ty >= vy0 && ty + thh <= vy1) /* 文本不越界才画(防标题/底部泄漏) */
meow_ui_text(w->x + pad, ty, label, th->font_body, col);
int sy = ry + item - 1; /* 分隔线:仅当落在可见区 */
if (sy >= cl && sy < cr) meow_ui_fill_rect(w->x, sy, w->w, 1, th->onSurfaceVariant);
meow_ui_text(txx, ty, label, th->font_body, col);
if (!w->container) { /* 非容器才画分隔线(容器模式去冗余) */
int sy = ry + item - 1; /* 分隔线:仅当落在可见区 */
if (sy >= cl && sy < cr) meow_ui_fill_rect(w->x, sy, w->w, 1, th->onSurfaceVariant);
}
}
}
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
+196
View File
@@ -0,0 +1,196 @@
/* test_meow_ui_home_gesture.c — 批② SDK 全局「App 内底部热区上滑回桌面」宿主验证。
*
* 覆盖(并同 meow_ui_input_is_home_swipe 判据、touch_release 分类、meow_ui_run 分发三层):
* T1 判定矩阵(meow_ui_input_is_home_swipe,镜像 launcher np_gesture.h np_home_gesture):
* 底部热区上滑=1;底部左缘上滑=0(左缘返回互斥);中部上滑=0(App 滚动);底部横向=0;
* 底部下滑=0;上移不足=0;横向主导=0;屏底边界(down_y=fb_h-HOME_EDGE_H)之外=0。
* T2 触摸状态机分类(真实 touch_release → 队列 pop 断言):
* 底部热区上滑→MEOW_EV_HOME(a=MEOW_SWIPE_UP);中部上滑→MEOW_EV_SWIPE(UP)(不被剥回桌面);
* 底部左缘横滑→MEOW_EV_SWIPE(LEFT)(原左缘返回,不被剥)。
* T3 分发(meow_ui_run 主循环,run-loop 复用 test_meow_ui_view_event 的 driver 线程范式):
* 3a 未注册 on_swipe:注入 MEOW_EV_HOME → meow_ui_nav_request_exit 被触发(一次)——回桌面;
* 3b 未注册 on_swipe:注入 MEOW_EV_SWIPE(UP)(中部上滑)→ nav_request_exit 不触发(App 滚动);
* 3c 注册 on_swipe:注入 MEOW_EV_HOME → nav_request_exit 触发(系统级手势优先于 App on_swipe),
* on_swipe 不被调用——屏底热区上滑是系统保留手势,任意 App 内生效(用户 2026-09-04 批②)。
*
* 编译(宿主 gcc,链真实 SDK 源全集 + 打桩 font/global;**不链 meow_ui_nav.c**——本测以测试内
* meow_ui_nav_request_exit 桩计数验证触发):
* gcc -Wall -Wextra -o test_meow_ui_home_gesture test_meow_ui_home_gesture.c test_host_stub.c \
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
* 运行:./test_meow_ui_home_gesture
*/
#include "meow_ui.h"
#include "meow_ui_internal.h" /* meow_input_touch_set/move/release + meow_input_pop + MEOW_EV_* */
#include "meow_ui_nav.h" /* meow_ui_nav_request_exit 原型(本测以桩计数验证) */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <assert.h>
/* MEOW_EV_HOME 是 meow_ui_input.c 内部常量(未入公共头,同 MEOW_EV_NOTIF_PULL 惯例),按同值声明。 */
#ifndef MEOW_EV_HOME
#define MEOW_EV_HOME 9
#endif
static int g_pass = 0, g_fail = 0;
#define CHECK(cond, ...) do { \
if (cond) { g_pass++; } \
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
} while (0)
/* fb 高度(宿主 480x720) */
#define FBW 480
#define FBH 720
/* 判据常量镜像(np_gesture.h NP_HOME_EDGE_H=28 / NP_HOME_SWIPE=65 / NP_EDGE_W=24)。 */
#define H_EDGE 28
#define H_SWIPE 65
#define EDGE_W 24
/* ---- nav_request_exit 桩(不链 meow_ui_nav.c;计数验证 SDK 触发) ---- */
static int g_nav_exit_calls = 0;
void meow_ui_nav_request_exit(void) { g_nav_exit_calls++; }
/* ---- on_swipe 桩(App 接管验证) ---- */
static int g_swipe_calls = 0;
static int g_swipe_dir = -1;
static int g_swipe_dx = 0;
static void app_on_swipe(int dir, int dx, int dy) { (void)dy; g_swipe_calls++; g_swipe_dir = dir; g_swipe_dx = dx; }
/* 从队列 pop 一个事件并回填 (type,a,b);无事件返回 0。 */
static int pop_ev(int *type, int *a, int *b)
{
meow_input_ev e;
if (!meow_input_pop(&e)) return 0;
*type = e.type; *a = e.a; *b = e.b;
return 1;
}
/* ============ T1 判定矩阵(meow_ui_input_is_home_swipe) ============ */
static void test_predicate(void)
{
printf("T1 meow_ui_input_is_home_swipe matrix\n");
int bz = FBH - H_EDGE; /* 底部热区上边界 y = 720-28 = 692 */
CHECK(meow_ui_input_is_home_swipe(240, bz + 10, 0, -120, FBH) == 1, "T1 bottom-hotzone up -> 1");
CHECK(meow_ui_input_is_home_swipe(100, FBH - 1, 20, -80, FBH) == 1, "T1 screen-bottom up (x>EDGE_W) -> 1");
CHECK(meow_ui_input_is_home_swipe(240, bz + 10, 0, -H_SWIPE, FBH) == 1, "T1 exactly threshold up -> 1");
CHECK(meow_ui_input_is_home_swipe(240, bz + 10, -30, -70, FBH) == 1, "T1 up w/ small left dx (adx<dy) -> 1");
/* 左缘互斥(down_x<EDGE_W) */
CHECK(meow_ui_input_is_home_swipe(10, bz + 10, 0, -120, FBH) == 0, "T1 left-edge origin -> 0 (edge-back reserved)");
/* 中部上滑(App 滚动) */
CHECK(meow_ui_input_is_home_swipe(240, 300, 0, -120, FBH) == 0, "T1 mid-screen up -> 0 (App scroll)");
/* 底部横向(左缘返回/横向逻辑保留) */
CHECK(meow_ui_input_is_home_swipe(240, bz + 10, 120, 0, FBH) == 0, "T1 bottom horizontal -> 0");
/* 底部下滑 */
CHECK(meow_ui_input_is_home_swipe(240, bz + 10, 0, 120, FBH) == 0, "T1 bottom down -> 0");
/* 上移不足(<H_SWIPE) */
CHECK(meow_ui_input_is_home_swipe(240, bz + 10, 0, -(H_SWIPE - 1), FBH) == 0, "T1 up below threshold -> 0");
/* 竖向未主导(横向位移≥上移量,adx>=dy) */
CHECK(meow_ui_input_is_home_swipe(240, bz + 10, -120, -80, FBH) == 0, "T1 up w/ big dx (not vertical-dominant) -> 0");
/* 热区边界之外(down_y < bz) */
CHECK(meow_ui_input_is_home_swipe(240, bz - 1, 0, -120, FBH) == 0, "T1 just above hotzone -> 0");
}
/* ============ T2 触摸状态机分类(touch_release → 队列) ============ */
static void test_touch_classify(void)
{
printf("T2 touch_release classification\n");
meow_input_drain();
/* 底部热区上滑 → MEOW_EV_HOME */
meow_input_touch_set(1, 240, FBH - 5, 0); /* 起点 y=715 ∈ [692,720) */
meow_input_touch_move(240, FBH - 5 - 130); /* 上移 130 */
meow_input_touch_release();
{
int t, a, b;
CHECK(pop_ev(&t, &a, &b) == 1 && t == MEOW_EV_HOME && a == MEOW_SWIPE_UP,
"T2 bottom-hotzone up -> MEOW_EV_HOME(UP) got type=%d a=%d", t, a);
}
/* 中部上滑 → 普通 MEOW_EV_SWIPE(UP)(不被剥回桌面) */
meow_input_touch_set(1, 240, 300, 0);
meow_input_touch_move(240, 300 - 130);
meow_input_touch_release();
{
int t, a, b;
CHECK(pop_ev(&t, &a, &b) == 1 && t == MEOW_EV_SWIPE && a == MEOW_SWIPE_UP,
"T2 mid up -> SWIPE(UP) got type=%d a=%d", t, a);
}
/* 底部左缘横滑 → 原有左缘返回(不被剥) */
meow_input_touch_set(1, 5, FBH - 5, 0);
meow_input_touch_move(5 + 100, FBH - 5);
meow_input_touch_release();
{
int t, a, b;
CHECK(pop_ev(&t, &a, &b) == 1 && t == MEOW_EV_SWIPE && a == MEOW_SWIPE_LEFT,
"T2 bottom-left horizontal -> SWIPE(LEFT) got type=%d a=%d", t, a);
}
}
/* ============ T3 分发(meow_ui_run 主循环) ============ */
static int g_inj_type, g_inj_a, g_inj_b;
static void *driver(void *arg)
{
(void)arg;
usleep(700000); /* 等 startup cooldown(500ms) 过后再注入 */
meow_input_inject(g_inj_type, g_inj_a, g_inj_b);
usleep(80000);
meow_ui_request_exit();
return NULL;
}
static void run_scenario(int inj_type, int inj_a, int inj_b, int register_swipe)
{
g_inj_type = inj_type; g_inj_a = inj_a; g_inj_b = inj_b;
g_nav_exit_calls = 0;
g_swipe_calls = 0; g_swipe_dir = -1; g_swipe_dx = 0;
meow_input_drain(); /* 防上一次残留事件影响本次分发 */
meow_ui_events ev; memset(&ev, 0, sizeof ev);
if (register_swipe) ev.on_swipe = app_on_swipe;
pthread_t thd; pthread_create(&thd, NULL, driver, NULL);
meow_ui_run(&ev);
pthread_join(thd, NULL);
}
static void test_dispatch(void)
{
printf("T3 meow_ui_run dispatch (home = nav_request_exit fallback)\n");
/* 3a 未注册 on_swipe:底部热区上滑 → nav_request_exit 触发一次(回桌面) */
run_scenario(MEOW_EV_HOME, MEOW_SWIPE_UP, 120, 0);
CHECK(g_nav_exit_calls == 1, "T3a no-on_swipe MEOW_EV_HOME -> nav_request_exit fired (got %d)", g_nav_exit_calls);
CHECK(g_swipe_calls == 0, "T3a no-on_swipe -> on_swipe NOT called");
/* 3b 未注册 on_swipe:中部上滑(普通 MEOW_EV_SWIPE UP)→ nav_request_exit 不触发(App 滚动) */
run_scenario(MEOW_EV_SWIPE, MEOW_SWIPE_UP, 120, 0);
CHECK(g_nav_exit_calls == 0, "T3b mid-swipe-up -> nav_request_exit NOT fired (App scroll), got %d", g_nav_exit_calls);
/* 3c 注册 on_swipe:底部热区上滑 → 系统优先,nav_request_exit 触发(回桌面),on_swipe 不被调用 */
run_scenario(MEOW_EV_HOME, MEOW_SWIPE_UP, 120, 1);
CHECK(g_nav_exit_calls == 1, "T3c on_swipe registered -> nav_request_exit STILL fired (system gesture wins), got %d", g_nav_exit_calls);
CHECK(g_swipe_calls == 0, "T3c system-priority home -> on_swipe NOT called");
}
int main(void)
{
meow_ui_opts o = { FBW, FBH };
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
test_predicate();
test_touch_classify();
test_dispatch();
meow_ui_shutdown();
printf("home_gesture: %d pass / %d fail\n", g_pass, g_fail);
return g_fail ? 1 : 0;
}
+9 -8
View File
@@ -109,8 +109,8 @@ int main(void)
meow_ui_opts o = { 480, 720 };
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
const meow_theme *th = meow_theme_get();
int scl_r, scl_g, scl_b, on_r, on_g, on_b, pc_r, pc_g, pc_b, osv_r, osv_g, osv_b, opc_r, opc_g, opc_b;
UNPK(th->surfaceContainerLow, scl_r, scl_g, scl_b);
int sc_r, sc_g, sc_b, on_r, on_g, on_b, pc_r, pc_g, pc_b, osv_r, osv_g, osv_b, opc_r, opc_g, opc_b;
UNPK(th->surfaceContainer, sc_r, sc_g, sc_b);
UNPK(th->onSurface, on_r, on_g, on_b);
UNPK(th->primaryContainer, pc_r, pc_g, pc_b);
UNPK(th->onSurfaceVariant, osv_r, osv_g, osv_b);
@@ -196,17 +196,18 @@ int main(void)
meow_lazygrid_draw(g, th);
/* 选中 cell0 → primaryContainer 底([GX,GY,100,80]) */
CHECK(region_has(GX + 4, GY + 4, 90, 60, pc_r, pc_g, pc_b) > 40, "selected cell primaryContainer bg");
/* 未选中 cell1 → surfaceContainerLow 底 */
/* 未选中 cell1 → surfaceContainer 底(MD3 容器:格内对称内缩 m=8 的圆角卡) */
int x1 = GX + 1 * (CW + GAP);
CHECK(region_has(x1 + 4, GY + 4, 90, 60, scl_r, scl_g, scl_b) > 40, "cell1 surfaceContainerLow bg");
/* 标题画在 cell0 底部(选中 → onPrimaryContainer;副标题在下方 onPrimaryContainer 同色带) */
CHECK(region_has(x1 + 4, GY + 4, 90, 60, sc_r, sc_g, sc_b) > 40, "cell1 surfaceContainer bg");
/* 标题画在 cell0 卡内底部(选中 → onPrimaryContainer;副标题在下方 onPrimaryContainer 同色带)。
* 镜像 lg_draw_cell 的 ty:cy=GY+m、chh=CH-2m、pad=8 → ty = GY+CH-m-pad-thh-(thh+2)。 */
int thh = meow_ui_text_height(th->font_body);
int title_y = GY + CH - 8 - thh - (thh + 2); /* = lg_draw_cell 的 ty */
int title_y = GY + CH - 8 - 8 - thh - (thh + 2);
int c0cx = GX + CW / 2;
CHECK(region_has(c0cx - 30, title_y, 60, thh, opc_r, opc_g, opc_b) > 2, "cell0 title drawn (onPrimaryContainer)");
/* 禁用 cell2 变淡:左上角(图标/文本区之外)不再是纯 surfaceContainerLow(被 onSurface α 叠暗) */
/* 禁用 cell2 变淡:卡内左上角(图标/文本区之外)不再是纯 surfaceContainer(被 onSurface α 叠暗) */
int x2 = GX + 2 * (CW + GAP);
CHECK(!px_near(x2 + 4, GY + 4, scl_r, scl_g, scl_b), "disabled cell overlaid (bg dimmed)");
CHECK(!px_near(x2 + 8 + 4, GY + 8 + 4, sc_r, sc_g, sc_b), "disabled cell overlaid (bg dimmed)");
meow_lazygrid_free(g);
}