diff --git a/patch_v2/sdk/include/meow_ui_input.h b/patch_v2/sdk/include/meow_ui_input.h index 9f2e8e1..00d45f5 100644 --- a/patch_v2/sdk/include/meow_ui_input.h +++ b/patch_v2/sdk/include/meow_ui_input.h @@ -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) */ /* ============================================================ */ diff --git a/patch_v2/sdk/include/meow_ui_widgets.h b/patch_v2/sdk/include/meow_ui_widgets.h index 0f654eb..f6088fc 100644 --- a/patch_v2/sdk/include/meow_ui_widgets.h +++ b/patch_v2/sdk/include/meow_ui_widgets.h @@ -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):行=图标+标题+副标题+右侧操作 + 选中 diff --git a/patch_v2/sdk/meow_lazygrid.c b/patch_v2/sdk/meow_lazygrid.c index f965078..1758e21 100644 --- a/patch_v2/sdk/meow_lazygrid.c +++ b/patch_v2/sdk/meow_lazygrid.c @@ -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) diff --git a/patch_v2/sdk/meow_ui_input.c b/patch_v2/sdk/meow_ui_input.c index fe51246..b3648e7 100644 --- a/patch_v2/sdk/meow_ui_input.c +++ b/patch_v2/sdk/meow_ui_input.c @@ -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 #include @@ -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; /* 启动窗口顶区误触残留:丢弃,防进入即开通知中心 */ /* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发 diff --git a/patch_v2/sdk/meow_ui_widget.c b/patch_v2/sdk/meow_ui_widget.c index 2ef37ce..2a2bb5b 100644 --- a/patch_v2/sdk/meow_ui_widget.c +++ b/patch_v2/sdk/meow_ui_widget.c @@ -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); diff --git a/patch_v2/sdk/test_meow_ui_home_gesture.c b/patch_v2/sdk/test_meow_ui_home_gesture.c new file mode 100644 index 0000000..7bc97aa --- /dev/null +++ b/patch_v2/sdk/test_meow_ui_home_gesture.c @@ -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 +#include +#include +#include +#include + +/* 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 1"); + + /* 左缘互斥(down_x 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"); + /* 上移不足( 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; +} diff --git a/patch_v2/sdk/test_meow_ui_lazygrid.c b/patch_v2/sdk/test_meow_ui_lazygrid.c index fb1766f..806bc67 100644 --- a/patch_v2/sdk/test_meow_ui_lazygrid.c +++ b/patch_v2/sdk/test_meow_ui_lazygrid.c @@ -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); }