/* test_subpage_widgets.c — M7.1a 子页渲染层迁移(SDK 控件化)宿主验证。 * * 复现 subpage.c 所建控件集(topappbar + 顶开关 meow_switch + listview 虚拟化 vsource + * meow_scroll 包裹 fling),验证: * P1 listview 虚拟化渲染:section 段头 + 普通/开关/动作行(right=SWITCH/ARROW)非背景像素; * P2 listview 命中:行号(含滚动换算 + 禁用行不响应)+ right 操作区命中(hit_op); * P3 topappbar 命中:返回箭头区 → MEOW_TOPBAR_BACK; * P4 meow_scroll 包裹 listview:跟手 track + 松手 fling(meow_anim_tick 驱动)+ snap 到行边界; * P5 SDK 输入态(meow_input_touch_set)→ listview 按压态像素变化; * P6 主题四态(MD3/MIUIx × 明暗)下 topappbar + listview 渲染非背景。 * * 几何常量与 subpage.c / meow_launcher_internal.h 镜像;控件创建方式与 sp_widgets_create 同 * (owner=哨兵 0x52,listview 用 set_vsource + set_item_renderer(2,0,1) + set_item_h 56)。 * * 编译(在 patch_v2/meow_launcherd 下): * gcc -Wall -Wextra -o test_subpage_widgets test_subpage_widgets.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_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_subpage_widgets */ #include "meow_ui.h" #include "meow_ui_internal.h" /* meow_ui_back_buf / meow_gfx_mark_dirty / meow_input_touch_set */ #include #include #include #include static int g_fail = 0; #define CHECK(cond, ...) do { \ if (cond) { printf(" PASS: " __VA_ARGS__); printf("\n"); } \ else { printf(" FAIL: " __VA_ARGS__); printf("\n"); g_fail++; } } while (0) /* ---- meow_lv_item 大列表数据源(P4 滚动测用:40 行) ---- */ static int big_count(void *ctx) { (void)ctx; return 40; } static void big_bind(int idx, meow_lv_item *out, void *ctx) { (void)ctx; memset(out, 0, sizeof *out); out->icon_kind = MEOW_LIST_ICON_NONE; out->enabled = 1; out->title = "行"; out->subtitle = "sub"; out->right = MEOW_LIST_RIGHT_ARROW; (void)idx; } /* ---- 与 subpage.c / meow_launcher_internal.h 同源几何(镜像) ---- */ #define SUB_TB_Y 28 #define SUB_TB_H 68 #define SUB_TB_BOT (SUB_TB_Y + SUB_TB_H) /* 96 */ #define SUB_CONT_TOP SUB_TB_BOT #define SUB_FOOT_H 44 #define SUB_ITEM_H 56 #define SUB_SENTINEL 0x52 #define XRES 480 #define YRES 720 #define LV_H (YRES - SUB_CONT_TOP - SUB_FOOT_H) /* 580 */ /* ---- sub_row_t 行缓冲镜像(sub_page_t/sub_row_t 结构在 meow_launcher_internal.h) ---- */ enum { SUB_HDR, SUB_ROW, SUB_SW, SUB_ACT }; typedef struct { int kind; const char *label; const char *sub; int on; int act; } sub_row_t; static sub_row_t g_rows[16]; static int g_nrows = 0; static void rows_fill(void) { g_nrows = 0; g_rows[g_nrows].kind = SUB_HDR; g_rows[g_nrows].label = "当前连接"; g_rows[g_nrows].sub = NULL; g_nrows++; g_rows[g_nrows].kind = SUB_ROW; g_rows[g_nrows].label = "已连接"; g_rows[g_nrows].sub = "MyWiFi"; g_rows[g_nrows].act = -1; g_nrows++; g_rows[g_nrows].kind = SUB_HDR; g_rows[g_nrows].label = "已保存网络"; g_rows[g_nrows].sub = NULL; g_nrows++; g_rows[g_nrows].kind = SUB_ACT; g_rows[g_nrows].label = "MyWiFi"; g_rows[g_nrows].sub = "已连接"; g_rows[g_nrows].act = 0; g_nrows++; g_rows[g_nrows].kind = SUB_HDR; g_rows[g_nrows].label = "附近网络"; g_rows[g_nrows].sub = NULL; g_nrows++; g_rows[g_nrows].kind = SUB_SW; g_rows[g_nrows].label = "WLAN"; g_rows[g_nrows].sub = NULL; g_rows[g_nrows].on = 1; g_nrows++; } /* ---- meow_lv_item 映射(镜像 subpage.c sp_lv_bind) ---- */ static int sp_lv_count(void *ctx) { (void)ctx; return g_nrows; } static void sp_lv_bind(int idx, meow_lv_item *out, void *ctx) { (void)ctx; memset(out, 0, sizeof *out); out->icon_kind = MEOW_LIST_ICON_NONE; out->enabled = 1; if (idx < 0 || idx >= g_nrows) { out->title = ""; return; } const sub_row_t *r = &g_rows[idx]; switch (r->kind) { case SUB_HDR: out->section = 1; out->title = r->label ? r->label : ""; out->right = MEOW_LIST_RIGHT_NONE; out->enabled = 0; break; case SUB_ROW: out->title = r->label ? r->label : ""; out->subtitle = r->sub; out->right = MEOW_LIST_RIGHT_NONE; break; case SUB_SW: out->title = r->label ? r->label : ""; out->subtitle = r->sub; out->right = MEOW_LIST_RIGHT_SWITCH; out->on = r->on; break; case SUB_ACT: out->title = r->label ? r->label : ""; out->subtitle = r->sub; out->right = MEOW_LIST_RIGHT_ARROW; break; } } static meow_widget *tb = NULL, *sw = NULL, *lv = NULL; static void rd_px(int x, int y, unsigned short *p) { unsigned char *bb = meow_ui_back_buf(); int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); if (!bb || x < 0 || y < 0) { *p = 0; return; } if (bpp == 16) *p = *(unsigned short *)(bb + (size_t)y * line + (size_t)x * 2); else *p = (unsigned short)(*(unsigned int *)(bb + (size_t)y * line + (size_t)x * 4) >> 16); } static unsigned short px565(int x, int y) { unsigned short p; rd_px(x, y, &p); return p; } static int region_nonbg(int x, int y, int w, int h, uint32_t bg) { unsigned short bg565 = ((bg >> 8) & 0xF800) | ((bg >> 5) & 0x07E0) | ((bg >> 3) & 0x001F); int cnt = 0; for (int dy = 0; dy < h; dy++) for (int dx = 0; dx < w; dx++) if (px565(x + dx, y + dy) != bg565) cnt++; return cnt; } static void mk_widgets(void) { meow_ui_activity_push(SUB_SENTINEL); tb = meow_topappbar_create(0, SUB_TB_Y, XRES, SUB_TB_H, "WiFi"); meow_topappbar_set_back(tb, 1); lv = meow_listview_create(0, SUB_CONT_TOP, XRES, LV_H, 0, NULL, NULL, MEOW_LIST_SEL_NONE); meow_listview_set_vsource(lv, sp_lv_count, sp_lv_bind, NULL); meow_listview_set_item_renderer(lv, 2, 0, 1); meow_listview_set_item_h(lv, SUB_ITEM_H); sw = meow_switch_create(XRES - 16 - 56, SUB_TB_Y + (SUB_TB_H - 32) / 2, 1); meow_ui_activity_pop(); } static void draw_all(void) { const meow_theme *th = meow_theme_get(); meow_ui_fill_rect(0, 0, XRES, YRES, th->surface); if (tb) meow_widget_draw(tb); if (sw) meow_widget_draw(sw); meow_ui_fill_rect(0, SUB_CONT_TOP, XRES, LV_H, th->surface); if (lv) meow_widget_draw(lv); } int main(void) { setbuf(stdout, NULL); setenv("MEOW_UI_DUMP", "/tmp/subpage.ppm", 1); meow_ui_opts o = { XRES, YRES }; if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } rows_fill(); mk_widgets(); /* ============ P1 listview 虚拟化渲染(section 段头 + 开关/箭头行) ============ */ printf("P1 listview virtualized render\n"); meow_ui_activity_push(SUB_SENTINEL); const meow_theme *th = meow_theme_get(); draw_all(); int hdrcnt = region_nonbg(2, SUB_CONT_TOP + 4, XRES - 4, SUB_ITEM_H - 8, th->surface); int rowcnt = region_nonbg(2, SUB_CONT_TOP + SUB_ITEM_H + 4, XRES - 4, SUB_ITEM_H - 8, th->surface); char nm[80]; snprintf(nm, sizeof nm, "section header renders (band=%d)", hdrcnt); CHECK(hdrcnt > 0, "%s", nm); snprintf(nm, sizeof nm, "row renders (title hdr/switch/arrow) cnt=%d", rowcnt); CHECK(rowcnt > 40, "%s", nm); /* ============ P2 listview 命中(行号 + right 操作区) ============ */ printf("P2 listview hit / hit_op\n"); int lvy = SUB_CONT_TOP; CHECK(meow_listview_hit(lv, 10, lvy + SUB_ITEM_H / 2) == -1, "row0 (hdr) disabled -> -1"); CHECK(meow_listview_hit(lv, 10, lvy + SUB_ITEM_H + SUB_ITEM_H / 2) == 1, "row1 (已连接) hits idx1"); /* 行右 1/3 = 操作区:row1 是 SUB_ROW(right none) → hit_op 0 */ CHECK(meow_listview_hit_op(lv, XRES - 20, lvy + SUB_ITEM_H + SUB_ITEM_H / 2) == 0, "row1 right-none -> hit_op 0"); /* row3 = SUB_ACT(MyWiFi) 右箭头;row5 = SUB_SW(WLAN) 右开关 → hit_op 1 */ CHECK(meow_listview_hit_op(lv, XRES - 20, lvy + 3 * SUB_ITEM_H + SUB_ITEM_H / 2) == 1, "row3 (act arrow) right hits op"); CHECK(meow_listview_hit_op(lv, XRES - 20, lvy + 5 * SUB_ITEM_H + SUB_ITEM_H / 2) == 1, "row5 (sw) right hits op"); /* ============ P3 topappbar 命中(返回箭头) ============ */ printf("P3 topappbar back hit\n"); CHECK(meow_topappbar_hit(tb, 10, SUB_TB_Y + SUB_TB_H / 2) == MEOW_TOPBAR_BACK, "back arrow region -> MEOW_TOPBAR_BACK"); CHECK(meow_topappbar_hit(tb, XRES - 5, SUB_TB_Y + SUB_TB_H / 2) == -1, "right region (no action) -> -1"); /* ============ P4 meow_scroll 包裹 listview + fling(惯性,ease-out,无 snap 回拉) ============ */ printf("P4 meow_scroll wrap listview + fling\n"); /* 40 行大列表(独立数据源),撑起滚动 */ meow_widget *scroll_lv = meow_listview_create(0, SUB_CONT_TOP, XRES, LV_H, 0, NULL, NULL, MEOW_LIST_SEL_NONE); meow_listview_set_vsource(scroll_lv, big_count, big_bind, NULL); meow_listview_set_item_renderer(scroll_lv, 2, 0, 1); meow_listview_set_item_h(scroll_lv, SUB_ITEM_H); meow_scroll *s = meow_scroll_wrap(scroll_lv, 1); /* 1=W_LISTVIEW */ CHECK(s != NULL, "meow_scroll_wrap(listview)"); int maxs = meow_listview_max_scroll(scroll_lv); CHECK(maxs > 0, "max_scroll > 0 (list taller than viewport) got %d", maxs); /* 快速下划(正步进)建初速 → fling 前进(单调不减 + 不越界 + snap 到行边界) */ uint64_t t = meow_ui_time_ms(); for (int i = 0; i < 12; i++) meow_scroll_track(s, 30, t + (uint64_t)i * 16); int start = meow_scroll_offset(s); CHECK(start > 0, "dragged start>0 got %d", start); meow_scroll_fling(s); int prev = start, mono = 1, maxoff = start, guard = 0; while (meow_scroll_active(s) && guard++ < 400) { meow_anim_tick(); int cur = meow_scroll_offset(s); if (maxoff < cur) maxoff = cur; if (cur < prev) mono = 0; prev = cur; usleep(8000); } meow_anim_tick(); int end = meow_scroll_offset(s); CHECK(mono == 1, "fling monotonic non-decreasing"); CHECK(end >= start, "fling advances end=%d start=%d", end, start); CHECK(end <= maxs, "no overflow end=%d max=%d", end, maxs); meow_scroll_free(s); meow_widget_free(scroll_lv); /* ============ P5 SDK 输入态 → listview 按压态像素变化 ============ */ printf("P5 meow_input_touch_set pressed state\n"); meow_ui_activity_push(SUB_SENTINEL); draw_all(); int px_y = SUB_CONT_TOP + SUB_ITEM_H + SUB_ITEM_H / 2; /* row1 中心 */ unsigned short p0 = px565(10, px_y); meow_input_touch_set(1, 10, px_y, 0); draw_all(); unsigned short p1 = px565(10, px_y); meow_input_touch_set(0, 0, 0, 0); CHECK(p1 != p0, "pressed row -> pixel changes"); meow_ui_activity_pop(); /* ============ P6 主题四态(MD3/MIUIx × 明暗)渲染非背景 ============ */ printf("P6 theme 4-state render\n"); const int styles[2] = { MEOW_STYLE_MD3, MEOW_STYLE_MIUIX }; const int modes[2] = { MEOW_THEME_LIGHT, MEOW_THEME_DARK }; for (int si = 0; si < 2; si++) { for (int mi = 0; mi < 2; mi++) { meow_ui_set_style(styles[si]); meow_theme_set_mode((meow_theme_mode)modes[mi]); th = meow_theme_get(); draw_all(); int tbcnt = region_nonbg(0, SUB_TB_Y, XRES, SUB_TB_H, th->surface); int lvcnt = region_nonbg(0, SUB_CONT_TOP, XRES, LV_H, th->surface); snprintf(nm, sizeof nm, "style=%s mode=%s topbar+list render", styles[si] ? "MIUIx" : "MD3", modes[mi] ? "dark" : "light"); CHECK(tbcnt > 0 && lvcnt > 40, "%s", nm); } } meow_ui_activity_pop(); meow_ui_shutdown(); printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail); return g_fail; }