Files
MeowOS-For-R3P2/patch_v2/sdk/test_meow_ui_widgets.c
T

316 lines
15 KiB
C
Raw 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_meow_ui_widgets.c — 宿主验证 §5.8.6 Material 子集纯逻辑(create/hit/state/滚动/选中)+
* 各组件绘制(5x7 字库兜底)dump PPM 目检。不依赖 /dev/fb0/字库(meow_font 打桩)。
*
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
* gcc -o test_meow_ui_widgets test_meow_ui_widgets.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_draw.c meow_ui_view.c meow_ui_img.c stb_image.c \
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
* 运行:MEOW_UI_DUMP=/tmp/b2.ppm ./test_meow_ui_widgets (可选,dump 屏幕)
*
* 注:W_LISTVIEW 的 MEOW_LIST_ICON_IMG 分支调用 meow_ui_img_scaled → 宿主链 meow_ui_img.c+stb_image.c+-lm。
* 结构化列表断言用 back_buf 像素(region_has/px_near),画完即读,非目检。
*/
#include "meow_ui.h"
#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp:真实像素断言 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/* ---- back_buf 像素读取(断言真实渲染:画完读像素,不依赖目检) ---- */
static void rd_px(int x, int y, int *r, int *g, int *b)
{
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) { *r = *g = *b = 0; return; }
if (bpp == 16) {
uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2);
*r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2;
*g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4;
*b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2);
} else {
uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4);
*r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF;
}
}
static int near2(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 14; }
static int px_near(int x, int y, int r, int g, int b)
{
int rr, gg, bb; rd_px(x, y, &rr, &gg, &bb);
return near2(rr, r) && near2(gg, g) && near2(bb, b);
}
/* 区域内含「接近 target 色」的像素数(>0 即该色被真实绘出)。 */
static int region_has(int x, int y, int w, int h, int r, int g, int b)
{
int cnt = 0;
for (int dy = 0; dy < h; dy++)
for (int dx = 0; dx < w; dx++)
if (px_near(x + dx, y + dy, r, g, b)) cnt++;
return cnt;
}
#define UNPK(c, rr, gg, bb) do { (rr) = ((c) >> 16) & 0xFF; (gg) = ((c) >> 8) & 0xFF; (bb) = (c) & 0xFF; } while (0)
/* 结构化列表(W_LISTVIEW)测试数据源 */
typedef struct { const char *title; const char *sub; int ik; int right; int enabled; } lvrow_t;
static lvrow_t g_lvrows[4] = {
{ "Row A", "sub A", MEOW_LIST_ICON_FOLDER, MEOW_LIST_RIGHT_MORE, 1 },
{ "Row B", "sub B", MEOW_LIST_ICON_FILE, MEOW_LIST_RIGHT_NONE, 1 },
{ "Row C", "sub C", MEOW_LIST_ICON_NONE, MEOW_LIST_RIGHT_ARROW, 0 }, /* 禁用 */
{ "Row D", "sub D", MEOW_LIST_ICON_FOLDER, MEOW_LIST_RIGHT_CHECK, 1 },
};
static int lv_src(int idx, meow_list_item *out, void *ctx)
{
(void)ctx;
if (idx < 0 || idx >= 4) return 0;
out->icon_kind = g_lvrows[idx].ik;
out->title = g_lvrows[idx].title;
out->subtitle = g_lvrows[idx].sub;
out->right = g_lvrows[idx].right;
out->on = 1;
out->enabled = g_lvrows[idx].enabled;
return 1;
}
/* 测试数据源(列表容器) */
static int list_src(int idx, char *label, int len, void *ctx)
{
(void)ctx;
if (idx < 0 || idx >= 100) return 0;
snprintf(label, (size_t)len, "item %d", idx);
return 1;
}
static meow_widget *g_w[64];
static int g_wn = 0;
static meow_widget *W(meow_widget *w) { g_w[g_wn++] = w; return w; }
static void free_all(void)
{
for (int i = 0; i < g_wn; i++) meow_widget_free(g_w[i]);
g_wn = 0;
}
int main(void)
{
setenv("MEOW_UI_DUMP", "/tmp/b2.ppm", 1);
meow_ui_opts o = { 480, 720 };
assert(meow_ui_init(&o) == 0);
int xres = meow_ui_width(), yres = meow_ui_height();
assert(xres == 480 && yres == 720);
/* ---------- Button 五型:create_variant/set_variant + 命中 ---------- */
meow_widget *bf = W(meow_btn_create_variant(10, 10, 200, 56, "FILLED", MEOW_BTN_FILLED));
meow_widget *bt = W(meow_btn_create_variant(10, 80, 200, 56, "TONAL", MEOW_BTN_TONAL));
meow_widget *bo = W(meow_btn_create_variant(10, 150, 200, 56, "OUTLINED", MEOW_BTN_OUTLINED));
meow_widget *bx = W(meow_btn_create_variant(10, 220, 200, 56, "TEXT", MEOW_BTN_TEXT));
meow_widget *be = W(meow_btn_create_variant(10, 290, 200, 56, "ELEV", MEOW_BTN_ELEVATED));
assert(meow_btn_set_variant(bt, MEOW_BTN_TONAL) == 0); /* 同值 */
assert(meow_btn_set_variant(bo, MEOW_BTN_OUTLINED) == 0);
assert(meow_widget_hit(bf, 100, 30) == 1);
assert(meow_widget_hit(bf, 5, 5) == 0); /* 越界 */
meow_widget_set_enabled(bf, 0);
assert(meow_widget_hit(bf, 100, 30) == 0); /* 禁用不命中 */
meow_widget_set_enabled(bf, 1);
meow_widget_set_selected(be, 1);
assert(meow_widget_selected(be) == 1);
printf("PASS button-variant: create/set/hit/disabled/selected\n");
/* ---------- TextField ---------- */
char tfbuf[32] = "";
meow_widget *tf = W(meow_textfield_create(10, 360, 300, 44, "Type here...", tfbuf, 32, MEOW_IME_ASCII));
strcpy(tfbuf, "abc");
assert(strcmp(meow_textfield_text(tf), "abc") == 0);
assert(meow_widget_hit(tf, 20, 380) == 1);
meow_textfield_set_placeholder(tf, "placeholder");
printf("PASS textfield: create/text/placeholder/hit\n");
/* ---------- Card + content ---------- */
meow_widget *card = W(meow_card_create(240, 10, 220, 180));
meow_widget *inner = W(meow_btn_create_variant(260, 40, 180, 44, "IN CARD", MEOW_BTN_FILLED));
meow_card_set_content(card, inner);
assert(meow_widget_hit(card, 250, 20) == 1);
printf("PASS card: create/content/hit\n");
/* ---------- Divider / Badge ---------- */
meow_widget *dv = W(meow_divider_create(10, 470, 200, 0));
meow_widget *dv2 = W(meow_divider_create(230, 470, 8, 1));
meow_widget *bd = W(meow_badge_create(400, 100));
meow_badge_set(bd, 3);
meow_badge_set(bd, 0);
printf("PASS divider/badge: create/set\n");
/* ---------- Checkbox / Radio ---------- */
meow_widget *ck = W(meow_checkbox_create(300, 200));
meow_widget *rd = W(meow_radio_create(300, 250));
meow_widget_set_selected(ck, 1);
meow_widget_set_selected(rd, 1);
assert(meow_widget_selected(ck) == 1);
assert(meow_widget_hit(rd, 315, 265) == 1);
printf("PASS checkbox/radio: create/select/hit\n");
/* ---------- ProgressBar ---------- */
meow_widget *pg = W(meow_progress_create(10, 520, 300, 6, 40));
meow_progress_set(pg, 200); /* clamp 100(无 getter,仅不崩) */
meow_widget *pgc = W(meow_progress_circular_create(420, 520, 20, 66));
meow_progress_set(pgc, -1); /* 不定 */
printf("PASS progress: create/set linear+circular\n");
/* ---------- FAB ---------- */
meow_widget *fab = W(meow_fab_create(410, 620, 56, "+"));
assert(meow_widget_hit(fab, 430, 640) == 1);
printf("PASS fab: create/hit\n");
/* ---------- TopAppBar ---------- */
meow_widget *tab_ = W(meow_topappbar_create(0, 0, 480, 56, "Title"));
meow_topappbar_set_back(tab_, 1);
int a0 = meow_topappbar_add_action(tab_, "Save");
int a1 = meow_topappbar_add_action(tab_, "Edit");
assert(a0 == 0 && a1 == 1);
assert(meow_topappbar_hit(tab_, 10, 28) == MEOW_TOPBAR_BACK); /* 返回区 */
assert(meow_topappbar_hit(tab_, 440, 28) == 1); /* 右缘动作 "Edit"(最右) */
printf("PASS topappbar: create/back/action/hit\n");
/* ---------- NavigationBar ---------- */
meow_widget *nav = W(meow_navigationbar_create(0, 680, 480, 40));
int n0 = meow_navigationbar_add_item(nav, "Home");
int n1 = meow_navigationbar_add_item(nav, "Library");
int n2 = meow_navigationbar_add_item(nav, "Settings");
assert(n0 == 0 && n1 == 1 && n2 == 2);
meow_navigationbar_set_selected(nav, 2);
assert(meow_navigationbar_selected(nav) == 2);
assert(meow_navigationbar_hit(nav, 420, 700) == 2); /* 第 3 项 */
assert(meow_navigationbar_hit(nav, 10, 700) == 0);
printf("PASS navigationbar: add/select/hit\n");
/* ---------- TabBar ---------- */
meow_widget *tbb = W(meow_tabbar_create(0, 660, 480, 36));
meow_tabbar_add_tab(tbb, "Playlist");
meow_tabbar_add_tab(tbb, "Queue");
meow_tabbar_set_selected(tbb, 1);
assert(meow_tabbar_hit(tbb, 300, 678) == 1);
printf("PASS tabbar: add/select/hit\n");
/* ---------- 列表容器(LazyColumn 虚拟化 + 滚动) ---------- */
meow_widget *lc = W(meow_lazycolumn_create(20, 460, 260, 180, 40, 100, list_src, NULL));
assert(meow_list_scroll(lc) == 0);
assert(meow_list_max_scroll(lc) == 100 * 40 - 180); /* 4000-180=3820 */
meow_list_set_scroll(lc, 5000);
assert(meow_list_scroll(lc) == 3820); /* clamp 到 max */
meow_list_scroll_by(lc, -320);
assert(meow_list_scroll(lc) == 3500);
assert(meow_list_hit_index(lc, 30, 480) == ((480 - 460 + 3500) / 40)); /* 可见项换算 */
meow_list_set_item_h(lc, 50);
assert(meow_widget_hit(lc, 30, 470) == 1);
/* M2.71-bug8:行选中 + 命中点击回调 */
meow_list_set_selected(lc, 5);
assert(meow_list_selected(lc) == 5);
/* 验证 tap_at 返回命中下标并置选中 */
int hidx = meow_list_tap_at(lc, 30, 480);
assert(hidx == ((480 - 460 + meow_list_scroll(lc)) / 50));
assert(meow_list_selected(lc) == hidx);
/* LazyRow 横向 */
meow_widget *lr = W(meow_lazyrow_create(20, 640, 380, 40, 60, 100, list_src, NULL));
meow_list_scroll_by(lr, 200);
assert(meow_list_hit_index(lr, 40, 660) == ((40 - 20 + 200) / 60));
printf("PASS lazy list: scroll clamp/virtualize/hit_index/tap_at/select (col+row)\n");
/* ---------- 结构化列表(W_LISTVIEW):行渲染像素 + 命中 + 滚动裁剪 ----------
* lv 位于 (20,240,320,112)item_h=56 → 行0 y240..296、行1 296..352。
* 断言全部「画完读 back_buf 像素」(region_has/px_near),非目检。 */
meow_widget *lv = W(meow_listview_create(20, 240, 320, 112, 4, lv_src, NULL, MEOW_LIST_SEL_SINGLE));
assert(meow_listview_set_item_h(lv, 56) == 56);
const meow_theme *lth = meow_theme_get();
int lvx = 20, lvy = 240, lvw = 320, lvh = 112, itemh = 56;
int textx = lvx + 16 + 24 + 16; /* 文本起 x = 76 */
int op_start = lvx + (lvw * 2 / 3); /* 行右 1/3 操作区起 = 20+213=233 */
int sb_r, sb_g, sb_b, os_r, os_g, os_b, pc_r, pc_g, pc_b;
int ospv_r, ospv_g, ospv_b, prim_r, prim_g, prim_b;
UNPK(lth->bg, sb_r, sb_g, sb_b);
UNPK(lth->onSurface, os_r, os_g, os_b);
UNPK(lth->primaryContainer, pc_r, pc_g, pc_b);
UNPK(lth->onSurfaceVariant, ospv_r, ospv_g, ospv_b);
UNPK(lth->primary, prim_r, prim_g, prim_b);
int thh = meow_ui_text_height(lth->font_body); /* 20(宿主 5x7) */
/* 行 0 的标题/副标题 y(含 subtitle 的两行居中):ty=240+(56-42)/2=247 */
int r0_ty = lvy + (itemh - (2 * thh + 2)) / 2;
int r0_sy = r0_ty + thh + 2;
/* 1) 行内容渲染:清底 + 只画列表,断言图标/标题/副标题/右侧 ⋯ 真实像素 */
meow_ui_clear(lth->bg);
meow_widget_draw(lv);
assert(region_has(lvx + 16, lvy + (itemh - 24) / 2, 24, 24, prim_r, prim_g, prim_b) > 100);
assert(region_has(textx, r0_ty, 80, thh, os_r, os_g, os_b) > 2);
assert(region_has(textx, r0_sy, 80, thh, ospv_r, ospv_g, ospv_b) > 2);
assert(region_has(lvx + lvw - 16 - 24, lvy + (itemh - 20) / 2, 8, 20, ospv_r, ospv_g, ospv_b) > 20);
printf("PASS listview: row render (icon/title/subtitle/right-more) pixels\n");
/* 2) 选中态:行 1 → primaryContainer 高亮;行 0 无高亮(bg) */
meow_listview_set_sel(lv, 1);
meow_ui_clear(lth->bg);
meow_widget_draw(lv);
assert(px_near(28, lvy + itemh + 48, pc_r, pc_g, pc_b)); /* 行1带底部空白 = primaryContainer */
assert(px_near(28, lvy + 48, sb_r, sb_g, sb_b)); /* 行0带空白 = bg(未选中无高亮) */
int selout[4];
assert(meow_listview_get_sel(lv, selout, 4) == 1 && selout[0] == 1);
assert(meow_listview_sel_count(lv) == 1);
printf("PASS listview: selected row highlight + get_sel/sel_count\n");
/* 3) 命中:行号 + 右侧操作区分 + 禁用行不命中 */
assert(meow_listview_hit(lv, 28, lvy + 20) == 0); /* 行0 体 */
assert(meow_listview_hit_op(lv, 28, lvy + 20) == 0); /* 体非操作区 */
assert(meow_listview_hit_op(lv, op_start + 10, lvy + 20) == 1); /* 行0 右1/3=操作区 */
meow_listview_set_sel(lv, -1);
/* 滚动到 3、4 行行内命中(scroll=112 → 行2 y240、行3 y296) */
meow_listview_set_scroll(lv, 112);
assert(meow_listview_scroll(lv) == 112);
assert(meow_listview_max_scroll(lv) == 4 * 56 - 112);
assert(meow_listview_hit(lv, 28, lvy + 28) == -1); /* 行2 禁用 → 不命中 */
assert(meow_listview_hit(lv, 28, lvy + itemh + 28) == 3); /* 行3 命中 */
printf("PASS listview: hit row/hit_op/disabled-not-hit\n");
/* 4) 滚动视口裁剪:滚到 112 后画,视口外(上缘/下缘)无泄漏(保持 bg) */
meow_ui_clear(lth->bg);
meow_widget_draw(lv);
assert(px_near(21, lvy - 1, sb_r, sb_g, sb_b)); /* 视口上缘外 = bg(无上漏) */
assert(px_near(21, lvy + lvh, sb_r, sb_g, sb_b)); /* 视口下缘外 = bg(无下漏) */
/* 视口内 行3 标题真实渲染(行3 y=296,标题 y=303) */
assert(region_has(textx, lvy + itemh + (itemh - (2 * thh + 2)) / 2,
80, thh, os_r, os_g, os_b) > 2);
/* 行 2 禁用行「变淡」:draw 后其左 padding 像素应被 dim 覆盖(偏离纯 bg) */
assert(!px_near(28, lvy + 28, sb_r, sb_g, sb_b));
printf("PASS listview: scroll clip no-leak + disabled-row dim\n");
meow_listview_set_scroll(lv, 0);
meow_listview_set_sel(lv, -1);
/* ---------- Dialog / BottomSheet 纯逻辑(create/set/add;show 阻塞故不调) ---------- */
meow_dialog *dlg = meow_dialog_create(MEOW_DLG_SELECT, "Pick one");
const char *items[] = { "Alpha", "Beta", "Gamma" };
meow_dialog_set_items(dlg, items, 3, 1);
assert(meow_dialog_result(dlg) == 0); /* 未 show 默认 */
meow_dialog_free(dlg);
meow_dialog *dc = meow_dialog_create(MEOW_DLG_CONFIRM, "Sure?");
assert(meow_dialog_result(dc) == 0);
meow_dialog_free(dc);
meow_sheet *sh = meow_sheet_create("Options", 0);
int s0 = meow_sheet_add_item(sh, "Copy");
int s1 = meow_sheet_add_item(sh, "Delete");
assert(s0 == 0 && s1 == 1);
meow_sheet_free(sh);
printf("PASS dialog/sheet: create/set_items/add_item (blocking show skipped)\n");
/* ---------- 绘制所有控件到 back_buf(5x7 兜底),flush 出屏幕 ---------- */
const meow_theme *th = meow_theme_get();
meow_ui_clear(th->bg);
for (int i = 0; i < g_wn; i++) meow_widget_draw(g_w[i]);
meow_ui_flush();
printf("PASS draw: all %d widgets rendered, screen dumps to /tmp/b2.ppm\n", g_wn);
free_all();
meow_ui_shutdown();
printf("ALL PASS\n");
return 0;
}