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;工作区无生成物。
279 lines
14 KiB
C
279 lines
14 KiB
C
/* 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;
|
||
}
|