Files
AskaEth 3d70a3dd2c feat: M6.10–M6.19 批量收编 + M6.19 推卡验收
- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘)
- OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复
- ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640
- 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫)
- 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
2026-09-03 12:16:01 +08:00

361 lines
21 KiB
C
Raw Permalink 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.
/* test_menu_sdk.c — M7.4 主界面迁 SDK(meow_lazygrid 宫格 + meow_listview 列表 + meow_scroll)宿主验证。
*
* 复现 main_menu.c M7.4 SDK 路径所用几何/数据源(与 main_menu.c 同源常量镜像):
* app 区视口 = menu_app_region(apps_top, layout):宫格 x=12(PAD) w=xres-2*PAD / 列表 x=8 w=xres-16,
* 顶=apps_top(=ROW_TOP=96,宿主无字库→mfont_ready=0),底=EXIT_ROW-ROW_H-4=584;
* 宫格 cell_w=(xres-2*PAD-gap)/cols=223,cell_h=CARD_H=112,gap=10,row_h=122;
* 列表 row_h=LIST_ROW_H=60。应用数 MAX_APPS=8。
*
* 验证(M7 §4 验收点:双布局可切 / 图标·缺失·后台标注正确 / 空态 / 滚动惯性):
* G1 宫格虚拟化渲染:每可见格调自绘一次;格位置 = menu_app_region/menu_grid_cell_w 公式映射
* (480×720 校准:cell0=12,96 cell1=245,96 cell2=12,218 … 不被截断、不越视口)。
* G2 宫格命中:格中心→正确 idx;水平/垂直 gap→-1;越界→-1;滚动后命中换算正确。
* G3 宫格滚动:8 格(MAX_APPS)装得下 → max_scroll==0;12 格超屏 → max_scroll>0;scroll_attach 可建。
* L1 列表渲染+命中:视口 x=8,w=464;row 矩形与公式一致;8 行装得下 / 12 行超屏。
* E 空态:count==0 → 不画任何格/行卡(空态路径),无回退。
* M 图标·缺失·后台标注:自绘按 app->ok/meow_bg_is_backgrounded 决策「[缺失]」「·后台」/均无。
*
* 注:main_menu.c 的 menu_draw_grid_cell/menu_draw_list_cell 是 static(依赖 launcher 全局面),
* 宿主以同源公式/决策规则镜像复现其契约;SDK 控件行为(虚拟化/命中/滚动/空态)为真实验证。
*
* 编译(在 patch_v2/meow_launcherd 下,宿主 gcc):
* gcc -Wall -Wextra -o test_menu_sdk test_menu_sdk.c \
* ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \
* ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \
* ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \
* ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \
* ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \
* ../sdk/meow_ui_scroll.c ../sdk/meow_lazygrid.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \
* ../sdk/meow_notify_ipc.c ../sdk/stb_image.c -I../sdk -I. -DMEOW_UI_HOST_TEST -pthread -lm
* 运行:./test_menu_sdk
*/
#include "meow_ui.h"
#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp + meow_gfx_clip */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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)
/* ---- 与 main_menu.c / meow_launcher_internal.h 同源几何(镜像) ---- */
#define XRES 480
#define YRES 720
#define MENU_GRID_PAD 12
#define MENU_GRID_GAP 10
#define MENU_GRID_COLS 2
#define MENU_GRID_CARD_H 112
#define MENU_GRID_ICON 44
#define MENU_LIST_ROW_H 60
#define MENU_LIST_ICON 40
#define MENU_LIST_ICON_X 24
#define ROW_H 52
#define ROW_TOP 96
#define EXIT_ROW 640
#define APPS_TOP ROW_TOP /* 宿主无字库 → mfont_ready=0 → menu_apps_top()=ROW_TOP */
#define APPS_BOT (EXIT_ROW - ROW_H - 4) /* 584:设置行顶 */
#define SDK_ACT 0x55
/* menu_app_region 镜像:宫格 vx=12 w=xres-24;列表 vx=8 w=xres-16;vh=底-顶。 */
static int grid_cell_w(void) { return (XRES - 2 * MENU_GRID_PAD - (MENU_GRID_COLS - 1) * MENU_GRID_GAP) / MENU_GRID_COLS; }
static int grid_row_h(void) { return MENU_GRID_CARD_H + MENU_GRID_GAP; }
static void grid_region(int *vx, int *vy, int *vw, int *vh) { *vx = MENU_GRID_PAD; *vy = APPS_TOP; *vw = XRES - 2 * MENU_GRID_PAD; *vh = APPS_BOT - APPS_TOP; }
static void list_region(int *vx, int *vy, int *vw, int *vh) { *vx = 8; *vy = APPS_TOP; *vw = XRES - 16; *vh = APPS_BOT - APPS_TOP; }
/* ---- mock 应用(镜像 struct app 的关键字段) ---- */
typedef struct { const char *name; int ok; int bg; const char *pkg; } tapp_t;
static tapp_t g_apps[16];
static int g_napps = 0;
static int g_draw_cnt = 0; /* 自绘回调调用次数(窗口虚拟化) */
static int g_cell_x[16], g_cell_y[16], g_cell_w[16], g_cell_h[16]; /* 自绘收到的几何 */
static int g_cell_idx[16];
static char g_label[16][16]; /* 格/行自绘决策出的标注("" / "[缺失]" / "·后台") */
/* bind:读 mock app,title=名,enabled=1(命中即启动语义,同 main_menu menu_grid_bind)。 */
static void grid_bind(int idx, meow_lazygrid_item *it, void *ctx)
{
(void)ctx;
memset(it, 0, sizeof *it);
if (idx < 0 || idx >= g_napps) return;
it->title = g_apps[idx].name;
it->subtitle = NULL;
it->icon = NULL; /* 宿主无图标位图;自绘用文字/色块近似 */
it->enabled = 1;
it->user = &g_apps[idx];
}
static int grid_count(void *ctx) { (void)ctx; return g_napps; }
static void list_bind(int idx, meow_lv_item *it, void *ctx)
{
(void)ctx;
memset(it, 0, sizeof *it);
if (idx < 0 || idx >= g_napps) return;
it->title = g_apps[idx].name;
it->subtitle = NULL;
it->icon_kind = MEOW_LIST_ICON_NONE;
it->enabled = 1;
it->user = &g_apps[idx];
}
static int list_count(void *ctx) { (void)ctx; return g_napps; }
/* 镜像 menu_draw_grid_cell 的标注决策(ok→缺失[缺失];bg→·后台;均无→无)。 */
static void decide_label(int idx, char *out, int olen)
{
const tapp_t *a = &g_apps[idx];
if (!a->ok) snprintf(out, olen, "[缺失]");
else if (a->bg) snprintf(out, olen, "·后台");
else out[0] = 0;
}
/* 宫格自绘(镜像 menu_sdk_grid_draw → 记录几何 + 标注决策;宿主无字体用色块近似)。 */
static void grid_draw(int idx, int x, int y, int w, int h, int st, void *ctx)
{
(void)st; (void)ctx;
if (idx < 0 || idx >= g_napps) return;
if (g_draw_cnt < 16) {
g_cell_idx[g_draw_cnt] = idx; g_cell_x[g_draw_cnt] = x; g_cell_y[g_draw_cnt] = y;
g_cell_w[g_draw_cnt] = w; g_cell_h[g_draw_cnt] = h;
g_draw_cnt++;
}
decide_label(idx, g_label[idx], sizeof g_label[idx]);
meow_gfx_fill_round(x, y, w, h, 16, meow_theme_get()->surfaceContainer);
meow_gfx_fill_round(x + w / 2 - 22, y + 12, 44, 44, 13, meow_theme_get()->primary);
}
/* 列表自绘(镜像 menu_sdk_list_draw) */
static void list_draw(int idx, int x, int y, int w, int h, int st, void *ctx)
{
(void)st; (void)ctx;
if (idx < 0 || idx >= g_napps) return;
if (g_draw_cnt < 16) {
g_cell_idx[g_draw_cnt] = idx; g_cell_x[g_draw_cnt] = x; g_cell_y[g_draw_cnt] = y;
g_cell_w[g_draw_cnt] = w; g_cell_h[g_draw_cnt] = h;
g_draw_cnt++;
}
decide_label(idx, g_label[idx], sizeof g_label[idx]);
meow_gfx_fill_round(x, y, w, h, 12, meow_theme_get()->surfaceContainer);
meow_gfx_fill_round(x + MENU_LIST_ICON_X, y + (h - MENU_LIST_ICON) / 2, MENU_LIST_ICON, MENU_LIST_ICON, 12,
meow_theme_get()->primary);
}
static void seed_apps(int n)
{
static const char *nm[16] = {"应用一","应用二","应用三","应用四","应用五","应用六","应用七","应用八",
"应用九","应用十","应用十一","应用十二","应用十三","应用十四","应用十五","应用十六"};
for (int i = 0; i < n; i++) {
g_apps[i].name = nm[i]; g_apps[i].ok = 1; g_apps[i].bg = 0; g_apps[i].pkg = "os.meow.test";
}
/* 2 号缺失、3 号后台(用 0/1 锚定到前几格便于断言) */
if (n > 1) g_apps[1].ok = 0;
if (n > 2) g_apps[2].bg = 1;
g_napps = n;
}
int main(void)
{
meow_ui_opts o = { XRES, YRES };
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
const meow_theme *th = meow_theme_get();
int gx, gy, gw, gh, lx, ly, lw, lh;
grid_region(&gx, &gy, &gw, &gh); list_region(&lx, &ly, &lw, &lh);
int cw = grid_cell_w(), rh = grid_row_h();
CHECK(gx == MENU_GRID_PAD && gw == XRES - 2 * MENU_GRID_PAD, "grid viewport x=%d w=%d (expect %d,%d)", gx, gw, MENU_GRID_PAD, XRES - 2 * MENU_GRID_PAD);
CHECK(lx == 8 && lw == XRES - 16, "list viewport x=%d w=%d", lx, lw);
CHECK(gh == APPS_BOT - APPS_TOP, "grid viewport h=%d (expect %d)", gh, APPS_BOT - APPS_TOP);
/* ============ G1 宫格虚拟化渲染 + 几何校准 ============ */
printf("G1 lazygrid render @480x720\n");
{
seed_apps(8);
meow_ui_activity_push(SDK_ACT);
meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP);
CHECK(g != NULL, "lazygrid create");
CHECK(meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL) == 0, "vsource");
CHECK(meow_lazygrid_set_item_draw(g, grid_draw, NULL) == 0, "set_item_draw");
CHECK(meow_lazygrid_set_empty_text(g, "没有找到应用") == 0, "empty_text");
meow_ui_clear(th->bg);
g_draw_cnt = 0;
meow_lazygrid_draw(g, th);
/* 8 格(MAX_APPS)全可见:每格恰一次自绘 */
CHECK(g_draw_cnt == 8, "8 apps -> each visible cell drawn once got %d", g_draw_cnt);
/* 几何校准:cell0=12,96 cell1=245,96 cell2(行1col0)=12,218 cell3=245,218 */
CHECK(g_cell_idx[0] == 0 && g_cell_x[0] == 12 && g_cell_y[0] == 96, "cell0 geom idx=%d x=%d y=%d (expect 0,12,96)", g_cell_idx[0], g_cell_x[0], g_cell_y[0]);
CHECK(g_cell_idx[1] == 1 && g_cell_x[1] == 12 + cw + MENU_GRID_GAP && g_cell_y[1] == 96, "cell1 geom idx=%d x=%d y=%d", g_cell_idx[1], g_cell_x[1], g_cell_y[1]);
CHECK(g_cell_idx[2] == 2 && g_cell_x[2] == 12 && g_cell_y[2] == 96 + rh, "cell2 geom idx=%d x=%d y=%d", g_cell_idx[2], g_cell_x[2], g_cell_y[2]);
CHECK(g_cell_idx[7] == 7 && g_cell_y[7] == 96 + 3 * rh, "cell7 (row3col1) y=%d (expect %d)", g_cell_y[7], 96 + 3 * rh);
/* 全部格/行不越视口(右侧 ≤ gx+gw,底部 ≤ gy+gh) */
int bad = 0;
for (int i = 0; i < g_draw_cnt; i++)
if (g_cell_x[i] + g_cell_w[i] > gx + gw || g_cell_y[i] + g_cell_h[i] > gy + gh) bad++;
CHECK(bad == 0, "all cells within grid viewport (bad=%d, vp=[%d,%d]x[%d,%d])", bad, gx, gx + gw, gy, gy + gh);
/* 8 格(MAX_APPS)装得下 → max_scroll==0 */
CHECK(meow_lazygrid_max_scroll(g) == 0, "8 apps fit -> max_scroll=0 got %d", meow_lazygrid_max_scroll(g));
meow_lazygrid_free(g);
meow_ui_activity_pop();
}
/* ============ G2 宫格命中(含滚动换算:用 12 应用使超屏可滚) ============ */
printf("G2 lazygrid hit\n");
{
seed_apps(12);
meow_ui_activity_push(SDK_ACT);
meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP);
meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL);
/* cell0 中心(123,152) → idx0;cell1 中心(356,152) → idx1;行1col0 中心(123,274=96+122+56) → idx2 */
CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2) == 0, "hit cell0 center got %d", meow_lazygrid_hit(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2));
CHECK(meow_lazygrid_hit(g, 12 + cw + MENU_GRID_GAP + cw / 2, 96 + MENU_GRID_CARD_H / 2) == 1, "hit cell1 center got %d", meow_lazygrid_hit(g, 12 + cw + MENU_GRID_GAP + cw / 2, 96 + MENU_GRID_CARD_H / 2));
CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 96 + rh + MENU_GRID_CARD_H / 2) == 2, "hit row1col0 got %d", meow_lazygrid_hit(g, 12 + cw / 2, 96 + rh + MENU_GRID_CARD_H / 2));
/* 水平 gap x=240(col0[12,235) col1[245,468) 之间)→ -1 */
CHECK(meow_lazygrid_hit(g, 240, 96 + MENU_GRID_CARD_H / 2) == -1, "horizontal gap miss got %d", meow_lazygrid_hit(g, 240, 96 + MENU_GRID_CARD_H / 2));
/* 垂直 gap y=212(row0[96,208) row1[218,320) 之间)→ -1 */
CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 212) == -1, "vertical gap miss got %d", meow_lazygrid_hit(g, 12 + cw / 2, 212));
/* 越界(右侧 x=475 / 列表区外 y=92)→ -1 */
CHECK(meow_lazygrid_hit(g, 475, 96 + MENU_GRID_CARD_H / 2) == -1, "right-out miss got %d", meow_lazygrid_hit(g, 475, 96 + MENU_GRID_CARD_H / 2));
CHECK(meow_lazygrid_hit(g, 12 + cw / 2, APPS_TOP - 4) == -1, "above viewport -> -1 got %d", meow_lazygrid_hit(g, 12 + cw / 2, APPS_TOP - 4));
/* 滚 1 行(rh=122)后,视口顶 y=96 中心(x)落在行1 → idx2 */
meow_lazygrid_set_scroll(g, rh);
CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2) == 2, "scroll+122: top center hits row1 -> idx2 got %d", meow_lazygrid_hit(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2));
/* tap_at:命中 → 选中 + 回调 */
meow_lazygrid_set_scroll(g, 0);
CHECK(meow_lazygrid_tap_at(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2) == 0, "tap_at idx0 got %d", meow_lazygrid_tap_at(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2));
CHECK(meow_lazygrid_sel(g) == 0, "sel=0");
meow_lazygrid_free(g);
meow_ui_activity_pop();
}
/* ============ G3 宫格滚动(超屏 + scroll_attach) ============ */
printf("G3 lazygrid overflow scroll\n");
{
seed_apps(12); /* 6 行 → 内容高 722 > 视口 488 */
meow_ui_activity_push(SDK_ACT);
meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP);
meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL);
int ms = meow_lazygrid_max_scroll(g);
CHECK(ms > 0, "12 apps overflow max_scroll=%d (>0)", ms);
meow_lazygrid_set_item_draw(g, grid_draw, NULL);
meow_ui_clear(th->bg);
g_draw_cnt = 0;
meow_lazygrid_draw(g, th);
int win_rows = gh / rh + 1; /* 可见行窗口上界 */
CHECK(g_draw_cnt <= win_rows * MENU_GRID_COLS, "virtualized window: drew %d <= %d (not 12)", g_draw_cnt, win_rows * MENU_GRID_COLS);
/* scroll_by 往返 */
int before = meow_lazygrid_scroll(g);
meow_lazygrid_scroll_by(g, rh);
CHECK(meow_lazygrid_scroll(g) == before + rh, "scroll_by +row got %d (expect %d)", meow_lazygrid_scroll(g), before + rh);
meow_lazygrid_set_scroll(g, 0);
CHECK(meow_lazygrid_scroll(g) == 0, "set_scroll 0");
/* scroll_attach 可建(fling/snap 惯性滚动接线) */
meow_scroll *s = meow_lazygrid_scroll_attach(g);
CHECK(s != NULL, "scroll_attach non-NULL");
if (s) {
meow_scroll_set_snap(s, rh);
uint64_t t = meow_ui_time_ms();
for (int i = 0; i < 10; i++) meow_scroll_track(s, 40, t + (uint64_t)i * 16);
int st = meow_scroll_offset(s);
CHECK(st > 0, "drag advances offset got %d", st);
meow_scroll_fling(s);
int guard = 0;
while (meow_scroll_active(s) && guard++ < 300) { meow_anim_tick(); usleep(10000); }
meow_anim_tick();
int en = meow_scroll_offset(s);
CHECK(en >= st, "fling advances %d >= %d", en, st);
CHECK(en <= meow_lazygrid_max_scroll(g), "fling in range %d (max %d)", en, meow_lazygrid_max_scroll(g));
CHECK(meow_lazygrid_scroll(g) == en, "grid scroll mirrors offset");
meow_scroll_free(s);
}
meow_lazygrid_free(g);
meow_ui_activity_pop();
}
/* ============ L1 列表渲染 + 命中 + 滚动 ============ */
printf("L1 listview render + hit + scroll\n");
{
seed_apps(8);
meow_ui_activity_push(SDK_ACT);
meow_widget *lv = meow_listview_create(lx, ly, lw, lh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE);
CHECK(lv != NULL, "listview create");
meow_listview_set_vsource(lv, list_count, list_bind, NULL);
meow_listview_set_item_draw(lv, list_draw, NULL);
meow_listview_set_item_h(lv, MENU_LIST_ROW_H);
meow_ui_fill_rect(0, 0, XRES, YRES, th->bg);
meow_ui_fill_rect(lx, ly, lw, lh, th->surfaceVariant);
g_draw_cnt = 0;
meow_widget_draw(lv);
CHECK(g_draw_cnt == 8, "8 rows drawn got %d", g_draw_cnt);
CHECK(g_cell_x[0] == 8 && g_cell_w[0] == XRES - 16, "row0 x=%d w=%d (expect 8,%d)", g_cell_x[0], g_cell_w[0], XRES - 16);
CHECK(g_cell_y[0] == APPS_TOP && g_cell_y[1] == APPS_TOP + MENU_LIST_ROW_H, "row0/1 y=%d/%d", g_cell_y[0], g_cell_y[1]);
CHECK(meow_listview_max_scroll(lv) == 0, "8 rows fit -> max_scroll=0 got %d", meow_listview_max_scroll(lv));
CHECK(meow_listview_hit(lv, 100, APPS_TOP + MENU_LIST_ROW_H / 2) == 0, "hit row0 got %d", meow_listview_hit(lv, 100, APPS_TOP + MENU_LIST_ROW_H / 2));
CHECK(meow_listview_hit(lv, 100, APPS_TOP + MENU_LIST_ROW_H + MENU_LIST_ROW_H / 2) == 1, "hit row1 got %d", meow_listview_hit(lv, 100, APPS_TOP + MENU_LIST_ROW_H + MENU_LIST_ROW_H / 2));
CHECK(meow_listview_hit(lv, 4, APPS_TOP + MENU_LIST_ROW_H / 2) == -1, "left of list x=4 -> -1 got %d", meow_listview_hit(lv, 4, APPS_TOP + MENU_LIST_ROW_H / 2));
CHECK(meow_listview_hit(lv, 100, APPS_TOP - 4) == -1, "above list -> -1 got %d", meow_listview_hit(lv, 100, APPS_TOP - 4));
meow_widget_free(lv);
meow_ui_activity_pop();
/* 12 行超屏滚动 */
seed_apps(12);
meow_ui_activity_push(SDK_ACT);
meow_widget *lv2 = meow_listview_create(lx, ly, lw, lh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE);
meow_listview_set_vsource(lv2, list_count, list_bind, NULL);
meow_listview_set_item_draw(lv2, list_draw, NULL);
meow_listview_set_item_h(lv2, MENU_LIST_ROW_H);
CHECK(meow_listview_max_scroll(lv2) > 0, "12 rows overflow max_scroll=%d", meow_listview_max_scroll(lv2));
meow_listview_set_scroll(lv2, MENU_LIST_ROW_H);
/* 滚 1 行:视口顶 row 中心落在 idx1 */
CHECK(meow_listview_hit(lv2, 100, APPS_TOP + MENU_LIST_ROW_H / 2) == 1, "scrolled row0->idx1 got %d", meow_listview_hit(lv2, 100, APPS_TOP + MENU_LIST_ROW_H / 2));
meow_widget_free(lv2);
meow_ui_activity_pop();
seed_apps(8); /* 还原 */
}
/* ============ E 空态(count==0 → 空态路径,无格/行卡) ============ */
printf("E empty state\n");
{
seed_apps(0);
meow_ui_activity_push(SDK_ACT);
meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP);
meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL);
meow_lazygrid_set_item_draw(g, grid_draw, NULL);
meow_lazygrid_set_empty_text(g, "没有找到应用");
meow_ui_clear(th->bg);
g_draw_cnt = 0;
meow_lazygrid_draw(g, th);
CHECK(g_draw_cnt == 0, "empty grid: 0 cells drawn got %d", g_draw_cnt);
meow_lazygrid_free(g);
/* 列表空态 */
meow_widget *lv = meow_listview_create(lx, ly, lw, lh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
meow_listview_set_vsource(lv, list_count, list_bind, NULL);
meow_listview_set_item_draw(lv, list_draw, NULL);
meow_listview_set_empty_text(lv, "没有找到应用");
meow_ui_fill_rect(lx, ly, lw, lh, th->surfaceVariant);
g_draw_cnt = 0;
meow_widget_draw(lv);
CHECK(g_draw_cnt == 0, "empty list: 0 rows drawn got %d", g_draw_cnt);
meow_widget_free(lv);
meow_ui_activity_pop();
seed_apps(8);
}
/* ============ M 图标·缺失·后台标注(镜像 menu_draw_grid_cell 决策) ============ */
printf("M missing / background label\n");
{
seed_apps(8); /* app1 ok=0 → [缺失];app2 bg=1 → ·后台;app0 → 无 */
CHECK(strcmp(g_label[0], "") == 0, "app0 (ok,no bg) no label got '%s'", g_label[0]);
CHECK(memcmp(g_label[1], "[缺失]", 6) == 0, "app1 (missing) -> '[缺失]' got '%s'", g_label[1]);
CHECK(memcmp(g_label[2], "·后台", 6) == 0, "app2 (backgrounded) -> '·后台' got '%s'", g_label[2]);
/* 命中语义:enabled=1 恒响应 —— 缺 app 亦可命中(启动→失败 toast,同旧 draw_menu 行为) */
meow_ui_activity_push(SDK_ACT);
meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP);
meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL);
CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 96 + rh + MENU_GRID_CARD_H / 2) == 2, "missing app1 tappable (enabled=1) -> hit row0col? got %d", meow_lazygrid_hit(g, 12 + cw / 2, 96 + rh + MENU_GRID_CARD_H / 2));
meow_lazygrid_free(g);
meow_ui_activity_pop();
}
meow_ui_shutdown();
printf("menu_sdk: %d pass / %d fail\n", g_pass, g_fail);
return g_fail ? 1 : 0;
}