Files
MeowOS-SDK/sdk/test_meow_ui_lazygrid.c
T
AskaEth c0396e40ce sync(SDK): 增量同步主仓 M6.37–M6.39 至公开 SDK 仓
sdk/:
- 同步 meow_ui_widget/input/gfx/lazygrid/audio/media/img 等源码与头
  (SDK 默认列表/容器控件默认 MD3 — meow_list_set_container 可回退;
   全局 App 内底部上滑回桌面 — meow_ui_input_is_home_swipe 系统保留手势;
   LazyGrid/LazyColumn/LazyRow MD3 化;叠加让位自抑改 app_id 判定;
   DSD 音频格式等)
- 新增独立模块源码 meow_img_color / meow_lyric / meow_lyric_view 及对应宿主 test_*.c
- 保留公开仓自包含适配:vendored meow_font.c/meow_font.h/meow_notif.h 不动;
  build_sdk.sh 自包含版本不动;meow_ui_gfx.c 仅改 meow_font.h include 路径;
  meow_ui_internal.h 去除 FreeType 内部声明
- meow_ipc.h 同步到主仓 launcher 版本(含 AUDIO_FMT_DOP/媒体歌词/蓝牙输出协议)
- 不含 FreeType 矢量字 meow_ft_*(需 freetype vendor,公开仓以位图 meow_font
  自包含);不含 mipsel 二进制 / .o / .a / 固件 / 密钥 / devdocs

examples/hello/:
- 新增 ui/demo/sdk2/sdk2.c;build_hello.sh 纳入该文件(保持自包含)

docs / README / .gitignore:
- docs/MEOW_APP_SDK.md 补 §12.5(默认 MD3 容器 + 底部上滑回桌面 + 两个新 API)
- README 能力清单补「默认 MD3 容器」与「底部上滑回桌面」
- .gitignore 补新增宿主测试二进制

验证:sdk/meow_ui.a 与 examples/hello/hello 交叉编译均 0 warning;工作区无生成物。
2026-09-04 18:05:36 +08:00

279 lines
14 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_lazygrid.c — 宿主验证 §12.4 2 维虚拟化宫格(meow_lazygrid):
* T1 虚拟化正确性:count/cols/max_scroll;首帧绑定可见行 item;scroll 下滚仅补新入视口的 item;
* 回滚不重绑;全量扫过 = count(每 index 恰 bind 一次);命中把滚动+网格坐标映射回 idx 正确。
* T2 默认 renderer:圆角卡片底 / 标题/副标题 / 选中高亮(primaryContainer)/ 禁用变淡。
* T3 完全自绘 set_item_draw:每可见格调一次 + 自绘内容像素。
* T4 空态占位(count=0 + empty_text 居中)。
* T5 meow_scroll 集成(meow_lazygrid_scroll_attach:track/fling/snap 到行边界)。
*
* 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global):
* gcc -o test_meow_ui_lazygrid test_meow_ui_lazygrid.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_str.c \
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
* meow_lazygrid.c meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
* 运行:./test_meow_ui_lazygrid
*/
#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 <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)
/* ---- 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);
}
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)
/* ---- 网格几何(测试统一) ---- */
#define GX 20
#define GY 50
#define GW 440
#define GH 200
#define COLS 3
#define CW 100
#define CH 80
#define GAP 10
#define ROWH (CH + GAP) /* 90 */
#define N 20 /* 默认总项数 */
static int g_count_override; /* <0 = 用默认 N;>=0 = 覆盖(空态/小列表) */
static int g_bind_total; /* bind 回调计数(含 hit 的临时 bind) */
static int g_draw_total; /* draw_cb(自绘)计数 */
static int g_disabled_at; /* 该 index 返回 enabled=0(-1=无) */
static int g_tap_cb_idx; /* tap_at 回调记录的选中项 */
static void lg_tap_cb(int idx, void *ctx) { (void)ctx; g_tap_cb_idx = idx; }
static int lg_count(void *ctx) { (void)ctx; return g_count_override >= 0 ? g_count_override : N; }
static void lg_bind(int idx, meow_lazygrid_item *it, void *ctx)
{
(void)ctx;
g_bind_total++;
memset(it, 0, sizeof *it);
static char t[32], s[32];
snprintf(t, sizeof t, "cell %d", idx);
snprintf(s, sizeof s, "st %d", idx);
it->title = t; it->subtitle = s;
it->enabled = 1;
if (g_disabled_at == idx) it->enabled = 0;
}
/* ---- T3 完全自绘:每格画实心块 + 角标记 ---- */
static void mydraw(int idx, int x, int y, int w, int h, int st, void *ctx)
{
(void)idx; (void)ctx;
g_draw_total++;
meow_ui_fill_rect(x, y, w, h, 0x334455); /* 自定义格底 */
meow_ui_fill_rect(x + 4, y + 4, 8, 8, 0xFF0000); /* 角标记 */
if (st & MEOW_LAZYGRID_ST_SELECTED) meow_ui_fill_rect(x + w - 12, y + 4, 8, 8, 0x00FF00);
}
int main(void)
{
meow_ui_opts o = { 480, 720 };
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
const meow_theme *th = meow_theme_get();
int sc_r, sc_g, sc_b, on_r, on_g, on_b, pc_r, pc_g, pc_b, osv_r, osv_g, osv_b, opc_r, opc_g, opc_b;
UNPK(th->surfaceContainer, sc_r, sc_g, sc_b);
UNPK(th->onSurface, on_r, on_g, on_b);
UNPK(th->primaryContainer, pc_r, pc_g, pc_b);
UNPK(th->onSurfaceVariant, osv_r, osv_g, osv_b);
UNPK(th->onPrimaryContainer, opc_r, opc_g, opc_b);
g_count_override = -1; g_bind_total = 0; g_disabled_at = -1;
/* ============ T1 虚拟化正确性 ============ */
{
meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP);
CHECK(g != NULL, "create");
CHECK(meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL) == 0, "vsource");
CHECK(meow_lazygrid_max_scroll(g) == 7 * CH + 6 * GAP - GH, "max_scroll %d (expect %d)",
meow_lazygrid_max_scroll(g), 7 * CH + 6 * GAP - GH); /* 620-200=420 */
CHECK(meow_lazygrid_scroll(g) == 0, "scroll0");
meow_ui_clear(th->bg);
meow_lazygrid_draw(g, th);
int B0 = g_bind_total;
int vis_rows = GH / ROWH + 1; /* 3 */
int expect0 = vis_rows * COLS; /* 9(row0..2 × 3 格) */
CHECK(B0 == expect0, "first draw binds %d got %d", expect0, B0);
/* 下滚 1 行(90px):新入视口 = 末 1 行 3 格 → bind +3(其余同槽不重绑) */
meow_lazygrid_set_scroll(g, ROWH);
meow_lazygrid_draw(g, th);
int B1 = g_bind_total;
CHECK(B1 == B0 + COLS, "scroll+1row binds +%d got %d (was %d)", COLS, B1 - B0, B0);
/* 回滚到顶:槽仍在 → 不重绑(此段不调 hit,避免临时 bind 干扰计数) */
meow_lazygrid_set_scroll(g, 0);
meow_lazygrid_draw(g, th);
CHECK(g_bind_total == B1, "scroll-back no rebind got %d (was %d)", g_bind_total, B1);
/* 全量扫过:每个 index 进入视口时 bind 恰一次 → 总 == N(有界,非 per-frame × count) */
int prev = 0;
while (prev <= N) {
int off = prev * ROWH;
meow_lazygrid_set_scroll(g, off);
meow_lazygrid_draw(g, th);
if (off >= meow_lazygrid_max_scroll(g)) break;
prev++;
}
CHECK(g_bind_total == N, "full sweep binds each index once got %d (expect %d)", g_bind_total, N);
/* 命中:索引 = 滚动 + 网格坐标映射(此后的 hit 走临时 bind,不影响上面的总数断言) */
meow_lazygrid_set_scroll(g, 0);
CHECK(meow_lazygrid_hit(g, GX + 10, GY + 10) == 0, "hit cell0 got %d", meow_lazygrid_hit(g, GX + 10, GY + 10));
meow_lazygrid_set_scroll(g, ROWH);
/* scroll=90:GY+10(视口顶)→ 内容 y=100 → row1([90,170])col0 → idx=3 */
CHECK(meow_lazygrid_hit(g, GX + 10, GY + 10) == COLS,
"hit maps scroll+grid got %d (expect 3)", meow_lazygrid_hit(g, GX + 10, GY + 10));
/* gap 区不命中 */
CHECK(meow_lazygrid_hit(g, GX + CW + GAP / 2, GY + 10) == -1, "horizontal gap miss got %d",
meow_lazygrid_hit(g, GX + CW + GAP / 2, GY + 10));
/* 越界列(row0 左侧无格/第 4 列)不命中 */
meow_lazygrid_set_scroll(g, 0);
CHECK(meow_lazygrid_hit(g, GX + COLS * (CW + GAP) + 10, GY + 10) == -1, "col overflow miss got %d",
meow_lazygrid_hit(g, GX + COLS * (CW + GAP) + 10, GY + 10));
CHECK(meow_lazygrid_hit(g, GX + 10, GY + ROWH) == COLS, "hit row1 col0 idx=3 got %d",
meow_lazygrid_hit(g, GX + 10, GY + ROWH));
/* tap_at:命中 → 置选中 + 回调 */
g_tap_cb_idx = -1;
meow_lazygrid_set_on_tap(g, lg_tap_cb, NULL);
int tap_idx = meow_lazygrid_tap_at(g, GX + 50, GY + 40);
CHECK(tap_idx == 0, "tap_at idx0 got %d", tap_idx);
CHECK(meow_lazygrid_sel(g) == 0, "sel set to 0");
CHECK(g_tap_cb_idx == 0, "tap cb idx0 got %d", g_tap_cb_idx);
/* 点 row1,col1(idx=4) → 该格命中 */
int idx4 = meow_lazygrid_hit(g, GX + 1 * (CW + GAP) + 10, GY + ROWH + 10);
CHECK(idx4 == 4, "hit row1col1 idx=%d (expect 4)", idx4);
meow_lazygrid_free(g);
}
/* ============ T2 默认 renderer:卡片底 / 标题 / 选中高亮 / 禁用 ============ */
{
meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP);
meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL);
g_count_override = 3; g_disabled_at = 2; /* 3 格:cell2 禁用 */
meow_ui_clear(th->bg);
meow_lazygrid_set_sel(g, 0);
meow_lazygrid_draw(g, th);
/* 选中 cell0 → primaryContainer 底([GX,GY,100,80]) */
CHECK(region_has(GX + 4, GY + 4, 90, 60, pc_r, pc_g, pc_b) > 40, "selected cell primaryContainer bg");
/* 未选中 cell1 → surfaceContainer 底(MD3 容器:格内对称内缩 m=8 的圆角卡) */
int x1 = GX + 1 * (CW + GAP);
CHECK(region_has(x1 + 4, GY + 4, 90, 60, sc_r, sc_g, sc_b) > 40, "cell1 surfaceContainer bg");
/* 标题画在 cell0 卡内底部(选中 → onPrimaryContainer;副标题在下方 onPrimaryContainer 同色带)。
* 镜像 lg_draw_cell 的 ty:cy=GY+m、chh=CH-2m、pad=8 → ty = GY+CH-m-pad-thh-(thh+2)。 */
int thh = meow_ui_text_height(th->font_body);
int title_y = GY + CH - 8 - 8 - thh - (thh + 2);
int c0cx = GX + CW / 2;
CHECK(region_has(c0cx - 30, title_y, 60, thh, opc_r, opc_g, opc_b) > 2, "cell0 title drawn (onPrimaryContainer)");
/* 禁用 cell2 变淡:卡内左上角(图标/文本区之外)不再是纯 surfaceContainer(被 onSurface α 叠暗) */
int x2 = GX + 2 * (CW + GAP);
CHECK(!px_near(x2 + 8 + 4, GY + 8 + 4, sc_r, sc_g, sc_b), "disabled cell overlaid (bg dimmed)");
meow_lazygrid_free(g);
}
/* ============ T3 完全自绘(set_item_draw) ============ */
{
meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP);
meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL);
g_count_override = 3; g_disabled_at = -1; g_draw_total = 0;
CHECK(meow_lazygrid_set_item_draw(g, mydraw, NULL) == 0, "set_item_draw");
meow_ui_clear(th->bg);
meow_lazygrid_draw(g, th);
CHECK(g_draw_total == 3, "draw_cb per visible cell got %d (expect 3)", g_draw_total);
CHECK(region_has(GX + 4, GY + 4, 8, 8, 0xFF, 0x00, 0x00) > 0, "custom cell content drawn");
meow_lazygrid_free(g);
}
/* ============ T4 空态 ============ */
{
meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP);
meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL);
g_count_override = 0;
meow_lazygrid_set_empty_text(g, "No data");
const meow_theme *t2 = meow_theme_get();
meow_ui_clear(th->bg);
meow_lazygrid_draw(g, t2);
int thh = meow_ui_text_height(t2->font_body);
int ety = GY + (GH - thh) / 2;
CHECK(region_has(GX + GW / 2 - 60, ety, 120, thh, osv_r, osv_g, osv_b) > 2, "empty state text drawn");
meow_lazygrid_free(g);
}
/* ============ T5 meow_scroll 集成(fling/snap) ============ */
{
g_count_override = -1; g_disabled_at = -1;
meow_lazygrid *g = meow_lazygrid_create(GX, GY, GW, GH, COLS, CW, CH, GAP);
meow_lazygrid_set_vsource(g, lg_count, lg_bind, NULL);
meow_scroll *s = meow_lazygrid_scroll_attach(g);
CHECK(s != NULL, "scroll attach");
meow_scroll_set_snap(s, ROWH);
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, "drag advances offset got %d", start);
meow_scroll_fling(s);
CHECK(meow_scroll_active(s) == 1, "fling active");
int max_off = start, guard = 0;
while (meow_scroll_active(s) && guard++ < 300) {
meow_anim_tick();
int cur = meow_scroll_offset(s);
if (max_off < cur) max_off = cur;
usleep(10000);
}
meow_anim_tick();
int end = meow_scroll_offset(s);
CHECK(end >= start, "fling advances %d vs %d", end, start);
/* snap 到行边界;若撞底(max_scroll=420 非 row_h 的整数倍)则停在边界(clamp 优先) */
CHECK((end % ROWH) == 0 || end == meow_lazygrid_max_scroll(g),
"fling ends snapped-or-boundary got %d (%%%d, max %d)", end, end % ROWH, meow_lazygrid_max_scroll(g));
CHECK(end <= meow_lazygrid_max_scroll(g), "fling in range %d (max %d)", end, meow_lazygrid_max_scroll(g));
CHECK(meow_lazygrid_scroll(g) == end, "grid scroll mirrors offset");
meow_scroll_free(s);
meow_lazygrid_free(g);
}
meow_ui_shutdown();
printf("lazygrid: %d pass / %d fail\n", g_pass, g_fail);
return g_fail ? 1 : 0;
}