/* test_settings_widgets.c — M7.2 设置页主列表迁 SDK 控件(宿主验证)。 * * 复现 settings_page.c M7.2 SDK 路径所建控件集(meow_listview 虚拟化 vsource + section 段头 + * set_item_draw 自绘 + 行内真 meow_switch + 拖拽同布局 meow_slider),验证: * P1 listview 虚拟化渲染:section 段头 + 开关/枚举/滑条/幂等行非背景像素; * P2 meow_listview 命中:行号(含滚动换算 + 段头行 enabled=1 可命中); * P3 开关写回:行 tap → meow_settings_set_switch(key,!on) → settings.conf 键正确(读改写单键); * P4 滑条写回:track 映射 x→值 → meow_settings_set_int(key,v)(含息屏时间档位索引→秒)→ 键正确; * P5 枚举写回:enum 选项 → meow_settings_set_enum(key,idx) → settings.conf 键值正确。 * 写回全部走 meow_settings_set_*(单键读改写,勿整文件覆盖——音量存盘教训)+ meow_settings_save。 * * 几何/控件创建与 settings_page.c「M7.2 SDK 路径」镜像(owner=哨兵 0x53,listview 用 set_vsource * + set_item_draw + set_item_h 68)。SMENU 行表取代表性子集(键/类型/min/max 与真表一致)。 * * 编译(在 patch_v2/meow_launcherd 下): * gcc -Wall -Wextra -o test_settings_widgets test_settings_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 \ * -DMEOW_SETTINGS_FILE='"/tmp/meow_set_test/settings.conf"' -pthread -lm * 运行:./test_settings_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 #include #include /* 覆盖存储路径到临时目录(需父目录存在;test 内初始化时 mkdir)。 * 注意:须在 include "meow_settings.c" 前定义(SDK meow_ui_sys/theme 自带默认,勿用 -D 以免重定义)。 */ #define MEOW_SETTINGS_FILE "/tmp/meow_set_test/settings.conf" #include "meow_settings.c" 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) /* ---- 与 settings_page.c / meow_launcher_internal.h 同源几何(镜像) ---- */ #define XRES 480 #define YRES 720 #define SET_CARD_X 8 #define SET_CARD_W (XRES - 16) #define SET_CONTENT_W (SET_CARD_W - 24) #define S_SLIDER_X (SET_CARD_X + 16) /* 24 */ #define S_SLIDER_W SET_CONTENT_W /* 440 */ #define SET_LIST_Y 100 #define SET_FOOT_H 40 #define SET_ITEM_H 68 #define SET_SLIDER_YOFF 32 #define SET_TRACK_CY 52 #define SET_SENTINEL 0x53 #define LV_H (YRES - SET_LIST_Y - SET_FOOT_H) /* 580 */ /* 息屏时间档位(镜像 settings_page.c s_time_tiers) */ static const int s_time_tiers[] = { 0, 5, 10, 15, 20, 30, 45, 60, 90, 120, 180, 300, 600, 900, 1800, 3600, 7200 }; #define S_TIME_TIERS_N ((int)(sizeof s_time_tiers / sizeof s_time_tiers[0])) /* ---- srow_t 行表镜像(键/类型/min/max 与 settings_page.c SMENU 代表行一致) ---- */ typedef enum { SC_HEADER, SC_SWITCH, SC_SLIDER, SC_ENUM, SC_PRESET, SC_INFO, SC_LIST } sctrl_t; typedef struct { const char *label; const char *key; sctrl_t ctrl; int min, max, step; } srow_t; static const srow_t ROWS[] = { { "显示", NULL, SC_HEADER, 0, 0, 0 }, { "亮度", "display.brightness", SC_SLIDER, 1, 100, 1 }, { "最高刷新率", "display.refresh_always_max", SC_SWITCH, 0, 0, 0 }, { "息屏模式", "display.screen_off_mode", SC_ENUM, 0, 0, 0 }, { "息屏时间", "display.screen_off_timeout", SC_SLIDER, 0, S_TIME_TIERS_N - 1, 1 }, { "UI风格", "ui.style", SC_ENUM, 0, 0, 0 }, { "滑动方向", "input.swipe_invert", SC_SWITCH, 0, 0, 0 }, { "网络", NULL, SC_HEADER, 0, 0, 0 }, { "WiFi", NULL, SC_LIST, 0, 0, 0 }, }; #define NROWS ((int)(sizeof ROWS / sizeof ROWS[0])) static meow_widget *lv = NULL; /* ---- 值格式化/映射(镜像 settings_page.c s_fmt_value/s_time_tier_idx/s_time_tier_val) ---- */ static int s_time_tier_idx(int sec) { if (sec <= s_time_tiers[0]) return 0; for (int i = 1; i < S_TIME_TIERS_N; i++) { if (sec == s_time_tiers[i]) return i; if (sec < s_time_tiers[i]) { int lo = s_time_tiers[i-1], hi = s_time_tiers[i]; return (sec - lo < hi - sec) ? i - 1 : i; } } return S_TIME_TIERS_N - 1; } static int s_time_tier_val(int idx) { if (idx < 0) idx = 0; if (idx >= S_TIME_TIERS_N) idx = S_TIME_TIERS_N - 1; return s_time_tiers[idx]; } static void s_fmt_time(int sec, char *buf, int len) { if (sec <= 0) { snprintf(buf, len, "常亮"); return; } if (sec < 60) { snprintf(buf, len, "%d 秒", sec); return; } if (sec < 3600) { snprintf(buf, len, "%d 分", sec / 60); return; } snprintf(buf, len, "%d 时", sec / 3600); } static void s_fmt_value(const srow_t *r, char *buf, int len) { int v = 0; if (meow_settings_get_int(r->key, &v) != 0) { snprintf(buf, len, "-"); return; } if (!strcmp(r->key, "display.screen_off_timeout")) { s_fmt_time(v, buf, len); return; } snprintf(buf, len, "%d", v); } static void s_display_value(const srow_t *r, char *buf, int len) { int idx = 0; meow_settings_get_enum(r->key, &idx); if (meow_settings_enum_label(r->key, idx, buf, len) != 0) snprintf(buf, len, "-"); } static int s_slider_val_from_x(const srow_t *r, int px) { int range = r->max - r->min; int v = r->min + (range ? (px - S_SLIDER_X) * range / S_SLIDER_W : 0); if (v < r->min) v = r->min; if (v > r->max) v = r->max; return v; } /* 滑条落值(含息屏时间 挡位→秒 换算;镜像 s_slider_set_from_track) */ static void s_slider_set(const srow_t *r, int sl_v, int save) { (void)save; if (!strcmp(r->key, "display.screen_off_timeout")) sl_v = s_time_tier_val(sl_v); meow_settings_set_int(r->key, sl_v); } /* 开关 tap:读改写单键(镜像 run_settings_page SC_SWITCH 分支) */ static void s_switch_toggle(const srow_t *r) { int on = 0; meow_settings_get_switch(r->key, &on); meow_settings_set_switch(r->key, !on); } /* ---- listview 虚拟化数据源(镜像 settings_page.c set_lv_bind) ---- */ static int lv_count(void *ctx) { (void)ctx; return NROWS; } static void 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 >= NROWS) { out->title = ""; return; } const srow_t *r = &ROWS[idx]; out->title = r->label ? r->label : ""; out->section = (r->ctrl == SC_HEADER) ? 1 : 0; if (r->ctrl == SC_SWITCH) { int on = 0; meow_settings_get_switch(r->key, &on); out->right = MEOW_LIST_RIGHT_SWITCH; out->on = on; } } /* 行内开关池(镜像 settings_page.c g_set_sw pool) */ #define SET_SWITCH_POOL 12 static meow_widget *g_set_sw[SET_SWITCH_POOL]; static int g_set_sw_n = 0, g_set_sw_use = 0; static meow_widget *set_sw_take(void) { if (g_set_sw_use < g_set_sw_n) return g_set_sw[g_set_sw_use++]; if (g_set_sw_n >= SET_SWITCH_POOL) return NULL; meow_widget *w = meow_switch_create(0, 0, 0); if (w) { g_set_sw[g_set_sw_n++] = w; return g_set_sw[g_set_sw_use++]; } return NULL; } /* ---- 整行自绘(镜像 settings_page.c set_lv_draw) ---- */ static void lv_draw(int idx, int x, int y, int w, int h, int state, void *ctx) { (void)ctx; (void)h; const meow_theme *mt = meow_theme_get(); if (idx < 0) return; const srow_t *r = &ROWS[idx]; int sel = (state & MEOW_LISTVIEW_ST_SELECTED); int press = (state & MEOW_LISTVIEW_ST_PRESSED); int thh = meow_ui_text_height(mt->font_body); uint32_t textcol = sel ? mt->onPrimaryContainer : mt->onSurface; uint32_t subcol = sel ? mt->onPrimaryContainer : mt->onSurfaceVariant; if (r->ctrl == SC_HEADER) { meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->surfaceContainerHigh); int ty = y + (SET_ITEM_H - thh) / 2; if (ty >= 0) meow_ui_text(x + 16, ty, r->label, mt->font_body, mt->primary); meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant); return; } if (sel) meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->primaryContainer); else if (press) meow_ui_fill_rect_alpha(x, y, w, SET_ITEM_H, mt->primary, 26); int ly = y + (SET_ITEM_H - thh) / 2; switch (r->ctrl) { case SC_SWITCH: { meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol); meow_widget *sw = set_sw_take(); if (sw) { int on = 0; meow_settings_get_switch(r->key, &on); meow_widget_set_rect(sw, x + w - 16 - 56, y + (SET_ITEM_H - 32) / 2, 56, 32); meow_switch_set(sw, on); meow_widget_draw(sw); } break; } case SC_SLIDER: { /* 宿主测试不引入 launcher s_draw_slider/s_ui_theme:只验证行文字渲染 + 命中,track 视觉由 * 真机 s_draw_slider 呈现(settings_page.c set_lv_draw 已复用)。 */ meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol); char vb[32]; s_fmt_value(r, vb, sizeof vb); meow_ui_text(x + w - 16 - meow_ui_text_width(vb, mt->font_body), ly, vb, mt->font_body, subcol); break; } case SC_LIST: { meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol); meow_ui_text(x + w - 16 - meow_ui_text_width("已连接", mt->font_body), ly, "已连接", mt->font_body, subcol); break; } default: { /* SC_ENUM / SC_PRESET */ meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol); char vb[40]; s_display_value(r, vb, sizeof vb); int vx = x + w - 16 - meow_ui_text_width(vb, mt->font_body); meow_ui_text(vx, ly, vb, mt->font_body, subcol); break; } } meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant); if (state & MEOW_LISTVIEW_ST_DISABLED) meow_ui_fill_rect_alpha(x, y, w, SET_ITEM_H, mt->onSurface, 90); } static void mk_widgets(void) { meow_ui_activity_push(SET_SENTINEL); lv = meow_listview_create(0, SET_LIST_Y, XRES, LV_H, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE); meow_listview_set_vsource(lv, lv_count, lv_bind, NULL); meow_listview_set_item_draw(lv, lv_draw, NULL); meow_listview_set_item_h(lv, SET_ITEM_H); 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); meow_ui_fill_rect(0, SET_LIST_Y, XRES, LV_H, th->surfaceVariant); if (lv) { g_set_sw_use = 0; meow_widget_draw(lv); } } /* ---- 像素探测 ---- */ 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; } /* ---- meow_settings 写回验证辅助:清盘后 save,再 grep conf(格式 `key = value`,去空格比较) ---- */ static int conf_has(const char *key, const char *val) { char line[256], expect[256]; FILE *f = fopen(MEOW_SETTINGS_FILE, "r"); if (!f) return 0; snprintf(expect, sizeof expect, "%s=%s", key, val); int found = 0; while (fgets(line, sizeof line, f)) { char stripped[256]; int k = 0; for (int i = 0; line[i] && k < 255; i++) if (line[i] != ' ' && line[i] != '\n' && line[i] != '\r') stripped[k++] = line[i]; stripped[k] = 0; if (!strcmp(stripped, expect)) { found = 1; break; } } fclose(f); return found; } int main(void) { setbuf(stdout, NULL); mkdir("/tmp/meow_set_test", 0777); setenv("MEOW_UI_DUMP", "/tmp/settings.ppm", 1); if (meow_settings_init() != 0) printf("settings init (defaults; file may be absent)\n"); meow_ui_opts o = { XRES, YRES }; if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } mk_widgets(); /* ============ P1 listview 虚拟化渲染(section 段头 + 各型行非背景) ============ */ printf("P1 listview virtualized render (settings rows)\n"); meow_ui_activity_push(SET_SENTINEL); const meow_theme *th = meow_theme_get(); draw_all(); /* 段头行整体填 surfaceContainerHigh(覆盖列表底 surfaceVariant)→ 对列表底背景即非背景; * 注意宿主无 mfont 时 CJK 标题回退点阵不显形,故以「段头底 vs 列表底」判渲染而非文字像素。 */ int hdrcnt = region_nonbg(2, SET_LIST_Y + 4, XRES - 4, SET_ITEM_H - 8, th->surfaceVariant); int rowcnt = region_nonbg(2, SET_LIST_Y + SET_ITEM_H + 4, XRES - 4, SET_ITEM_H - 8, th->surfaceVariant); CHECK(hdrcnt > 0, "section header band renders (hdr=%d)", hdrcnt); CHECK(rowcnt > 40, "row band renders (title/value/switch) cnt=%d", rowcnt); /* P1b:开关行(ROWS[2]=最高刷新率 right switch)中心右缘区非背景 */ { int swy = SET_LIST_Y + 2 * SET_ITEM_H + SET_ITEM_H / 2; int swc = region_nonbg(XRES - 16 - 56, swy - 4, 56, 8, th->surfaceVariant); CHECK(swc > 0, "switch row right tail (meow_switch) renders swc=%d", swc); } /* ============ P2 meow_listview 命中(行号 + 段头可命中) ============ */ printf("P2 meow_listview hit\n"); CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + 0 * SET_ITEM_H + SET_ITEM_H / 2) == 0, "row0 (header 显示) hit idx0 (enabled=1)"); CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + 1 * SET_ITEM_H + SET_ITEM_H / 2) == 1, "row1 (亮度 slider) hit idx1"); CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + 3 * SET_ITEM_H + SET_ITEM_H / 2) == 3, "row3 (息屏模式 enum) hit idx3"); CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + 8 * SET_ITEM_H + SET_ITEM_H / 2) == 8, "row8 (WiFi list) hit idx8"); CHECK(meow_listview_hit(lv, 10, SET_LIST_Y - 2) == -1, "above list -> -1"); /* 滚动后命中(listview set_scroll 换算):scroll=20(<9*68-视口=32)时点 row1 中心 → idx1 */ meow_listview_set_scroll(lv, 20); CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + SET_ITEM_H + SET_ITEM_H / 2) == 1, "scrolled +20px: row1 center hits idx1"); CHECK(meow_listview_max_scroll(lv) > 0, "max_scroll > 0 (list taller than viewport) got %d", meow_listview_max_scroll(lv)); meow_listview_set_scroll(lv, 0); /* ============ P3 开关写回(读改写单键 → settings.conf) ============ */ printf("P3 switch write-back to settings.conf\n"); meow_settings_set_switch("input.swipe_invert", 0); /* reset */ meow_settings_save(); int before_on = 0; meow_settings_get_switch("input.swipe_invert", &before_on); { /* 模拟 tap ROWS[6]=滑动方向 */ const srow_t *r = &ROWS[6]; s_switch_toggle(r); meow_settings_save(); } int after_on = 0; meow_settings_get_switch("input.swipe_invert", &after_on); CHECK(before_on == 0 && after_on == 1, "input.swipe_invert toggled 0->1 (read-modify-write single key)"); CHECK(conf_has("input.swipe_invert", "1"), "settings.conf has input.swipe_invert=1"); CHECK(!conf_has("display.refresh_always_max", "1"), "untouched sibling key unchanged in conf"); /* ============ P4 滑条写回(x→值 → settings.conf;含息屏时间 挡位→秒) ============ */ printf("P4 slider write-back to settings.conf\n"); meow_settings_set_int("display.brightness", 50); meow_settings_save(); { const srow_t *r = &ROWS[1]; /* 亮度 1..100 */ int v = s_slider_val_from_x(r, S_SLIDER_X + S_SLIDER_W / 2); /* 中点 ≈ 50 */ s_slider_set(r, v, 1); meow_settings_save(); int got = 0; meow_settings_get_int("display.brightness", &got); CHECK(got == v, "display.brightness set via track -> %d (expect %d)", got, v); char kv[64]; snprintf(kv, sizeof kv, "%d", v); CHECK(conf_has("display.brightness", kv), "settings.conf has display.brightness=%d", v); } { /* 息屏时间:秒→挡位索引(显示用)与 挡位索引→秒(落盘用)双向映射 */ const srow_t *r = &ROWS[4]; /* 0..S_TIME_TIERS_N-1 */ int idx = 5; /* 第 5 挡 = 30 秒 */ int sec = s_time_tier_val(idx); CHECK(s_time_tier_idx(30) == idx, "screen_off_timeout sec 30 -> tier idx %d", idx); s_slider_set(r, idx, 1); meow_settings_save(); int got = 0; meow_settings_get_int("display.screen_off_timeout", &got); CHECK(sec == 30 && got == 30, "screen_off_timeout tier idx5 -> 30s (got %d)", got); CHECK(conf_has("display.screen_off_timeout", "30"), "settings.conf has display.screen_off_timeout=30"); } /* ============ P5 枚举写回(enum 选项 → settings.conf 键值正确) ============ */ printf("P5 enum write-back to settings.conf\n"); { const char *names[8]; int kn = 0; meow_settings_enum_names("display.screen_off_mode", names, &kn); CHECK(kn > 0, "display.screen_off_mode enum names count=%d", kn); int pick = 1; /* "black"(黑屏) */ meow_settings_set_enum("display.screen_off_mode", pick); meow_settings_save(); int got = 0; meow_settings_get_enum("display.screen_off_mode", &got); CHECK(got == pick, "display.screen_off_mode enum -> %d (expect %d)", got, pick); CHECK(conf_has("display.screen_off_mode", "black"), "settings.conf has display.screen_off_mode=black"); } meow_ui_activity_pop(); meow_ui_shutdown(); printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail); return g_fail; }