/* 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 #include #include #include 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; }