253 lines
13 KiB
C
253 lines
13 KiB
C
/* test_now_playing_widgets.c — M7.0b Now Playing 控件化宿主验证。
|
||
*
|
||
* 复现 run_now_playing() 所建控件集(np_widgets_create:meow_progress 进度条 + meow_iconbtn
|
||
* 三钮,play/pause 同位双钮按状态只画其一),验证:
|
||
* N1 进度条:value clamp 0-100 + track/fill 渲染(填充分界像素不同);
|
||
* N2 三钮圆形命中:圆心命中、角部不命中;
|
||
* N3 play/pause 同位切换:两钮图标位图不同 → 中心像素不同(画 PAUSE vs PLAY_ARROW);
|
||
* N4 主题四态(MD3/MIUIx × 明暗)下进度条+三钮渲染非背景像素。
|
||
*
|
||
* 几何常量与 now_playing.c 同源(镜像);控件创建方式与 np_widgets_create 同。
|
||
* 编译(在 patch_v2/meow_launcherd 下):
|
||
* gcc -Wall -Wextra -o test_now_playing_widgets test_now_playing_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 \
|
||
* ../sdk/meow_lyric.c ../sdk/meow_lyric_view.c \
|
||
* -I../sdk -I. -DMEOW_UI_HOST_TEST -pthread -lm
|
||
* 运行:./test_now_playing_widgets
|
||
*/
|
||
#include "meow_ui.h"
|
||
#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp */
|
||
#include "np_gesture.h" /* 批4:双页方向感知手势决策(纯逻辑,宿主可单测) */
|
||
#include "meow_lyric_view.h" /* 批2:歌词页控件(批4接入,数据批3 meow_media_lyric_*) */
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <assert.h>
|
||
|
||
static int g_fail = 0;
|
||
#define CHECK(cond, name) do { \
|
||
if (cond) printf(" PASS %s\n", name); \
|
||
else { printf(" FAIL %s\n", name); g_fail++; } } while (0)
|
||
|
||
/* ---- 与 now_playing.c 同源几何(镜像) ---- */
|
||
#define NP_PROG_X 40
|
||
#define NP_PROG_W (480 - 80) /* 400 */
|
||
#define NP_PROG_Y 518
|
||
#define NP_TIME_Y 492
|
||
#define NP_BTN_Y 568
|
||
#define NP_PLAY_CX 240
|
||
#define NP_PREV_CX (NP_PLAY_CX - 78) /* 162 */
|
||
#define NP_NEXT_CX (NP_PLAY_CX + 78) /* 318 */
|
||
#define NP_PLAY_R 30
|
||
#define NP_PREV_R 22
|
||
#define NP_SENTINEL 0x51
|
||
|
||
static meow_widget *prog = NULL, *prev = NULL, *play = NULL, *pause = NULL, *next = 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(NP_SENTINEL);
|
||
prog = meow_progress_create(NP_PROG_X, NP_PROG_Y, NP_PROG_W, 6, 0);
|
||
prev = meow_iconbtn_create(NP_PREV_CX - NP_PREV_R, NP_BTN_Y - NP_PREV_R,
|
||
NP_PREV_R * 2, MEOW_ICON_SKIP_PREV);
|
||
play = meow_iconbtn_create(NP_PLAY_CX - NP_PLAY_R, NP_BTN_Y - NP_PLAY_R,
|
||
NP_PLAY_R * 2, MEOW_ICON_PLAY_ARROW);
|
||
pause = meow_iconbtn_create(NP_PLAY_CX - NP_PLAY_R, NP_BTN_Y - NP_PLAY_R,
|
||
NP_PLAY_R * 2, MEOW_ICON_PAUSE);
|
||
next = meow_iconbtn_create(NP_NEXT_CX - NP_PREV_R, NP_BTN_Y - NP_PREV_R,
|
||
NP_PREV_R * 2, MEOW_ICON_SKIP_NEXT);
|
||
/* 主播放钮强调:primary/onPrimary(镜像 np_widgets_create) */
|
||
const meow_theme *th = meow_theme_get();
|
||
meow_widget_opts op; memset(&op, 0, sizeof op);
|
||
op.bg = th->primary; op.fg = th->onPrimary;
|
||
meow_widget_set_opts(play, &op);
|
||
meow_widget_set_opts(pause, &op);
|
||
meow_ui_activity_pop();
|
||
}
|
||
|
||
/* 歌词视图宽度测量(宿主:字节数近似,够用不裂屏;launcher 侧为 ui_width)。 */
|
||
static int lv_test_width(const char *u, int size, void *ctx)
|
||
{
|
||
(void)size; (void)ctx;
|
||
return (int)strlen(u);
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
setbuf(stdout, NULL);
|
||
setenv("MEOW_UI_DUMP", "/tmp/nowplaying.ppm", 1);
|
||
meow_ui_opts o = { 480, 720 };
|
||
assert(meow_ui_init(&o) == 0);
|
||
|
||
meow_ui_activity_push(NP_SENTINEL);
|
||
mk_widgets();
|
||
const meow_theme *th = meow_theme_get();
|
||
meow_ui_set_style(MEOW_STYLE_MD3);
|
||
meow_theme_set_mode(MEOW_THEME_DARK);
|
||
th = meow_theme_get();
|
||
|
||
/* ============ N1 进度条 clamp + track/fill 渲染 ============ */
|
||
printf("N1 progress value + track/fill\n");
|
||
meow_widget_set_opts(prog, &(meow_widget_opts){ .bg = 0 });
|
||
meow_ui_clear(0);
|
||
meow_progress_set(prog, 150); /* clamp → 100 */
|
||
meow_widget_draw(prog);
|
||
unsigned short fill_end = px565(NP_PROG_X + NP_PROG_W - 2, NP_PROG_Y + 3);
|
||
unsigned short track_mid = px565(NP_PROG_X + NP_PROG_W / 2, NP_PROG_Y + 3);
|
||
CHECK(fill_end != 0 && track_mid != 0, "value 150 clamp 100 -> full fill renders");
|
||
meow_progress_set(prog, 50);
|
||
meow_widget_draw(prog);
|
||
/* 50% 填充区 [40,240) 内像素应为主色;240 之后的轨道区应为轨道色(surfaceVariant) */
|
||
unsigned short infill = px565(NP_PROG_X + NP_PROG_W / 4, NP_PROG_Y + 3); /* x=140,填充区 */
|
||
unsigned short empty = px565(NP_PROG_X + NP_PROG_W - 4, NP_PROG_Y + 3); /* x=436,轨道区 */
|
||
CHECK(infill != empty, "value 50 -> fill region differs from empty tail");
|
||
|
||
/* ============ N2 三钮圆形命中 ============ */
|
||
printf("N2 circular hit\n");
|
||
CHECK(meow_widget_hit(prev, NP_PREV_CX, NP_BTN_Y), "prev center hits");
|
||
CHECK(!meow_widget_hit(prev, NP_PREV_CX - NP_PREV_R, NP_BTN_Y - NP_PREV_R), "prev corner MISSES");
|
||
CHECK(meow_widget_hit(play, NP_PLAY_CX, NP_BTN_Y), "play center hits");
|
||
CHECK(meow_widget_hit(pause, NP_PLAY_CX, NP_BTN_Y), "pause(overlay) center hits");
|
||
CHECK(!meow_widget_hit(play, NP_PLAY_CX - NP_PLAY_R, NP_BTN_Y - NP_PLAY_R), "play corner MISSES");
|
||
CHECK(meow_widget_hit(next, NP_NEXT_CX, NP_BTN_Y), "next center hits");
|
||
|
||
/* ============ N3 play/pause 同位切换(两钮位图不同 → 像素不同) ============ */
|
||
printf("N3 play/pause overlay icon switch\n");
|
||
meow_ui_clear(0);
|
||
meow_widget_draw(play); meow_ui_clear(0); /* 只画 play */
|
||
meow_widget_draw(play);
|
||
unsigned short p_play = px565(NP_PLAY_CX, NP_BTN_Y); /* 中心=图标墨色 */
|
||
meow_ui_clear(0);
|
||
meow_widget_draw(pause);
|
||
unsigned short p_pause = px565(NP_PLAY_CX, NP_BTN_Y);
|
||
CHECK(p_play != p_pause, "play vs pause icon differs at centre (overlay switch works)");
|
||
/* 圆心区域非页面底色(画出来了) */
|
||
meow_ui_clear(0); meow_widget_draw(pause);
|
||
CHECK(region_nonbg(NP_PLAY_CX - NP_PLAY_R, NP_BTN_Y - NP_PLAY_R,
|
||
NP_PLAY_R * 2, NP_PLAY_R * 2, th->surface) > 0,
|
||
"pause button renders non-bg in circle");
|
||
|
||
/* ============ N4 主题四态:进度条+三钮渲染非背景 ============ */
|
||
printf("N4 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();
|
||
meow_theme ex; meow_theme_export(&ex);
|
||
CHECK(ex.primary == th->primary && ex.surface == th->surface &&
|
||
ex.surfaceVariant == th->surfaceVariant, "export == get (spot roles)");
|
||
meow_ui_clear(0);
|
||
meow_progress_set(prog, 40); meow_widget_draw(prog);
|
||
meow_widget_draw(prev);
|
||
meow_widget_draw(pause);
|
||
meow_widget_draw(next);
|
||
char nm[80];
|
||
int progpx = region_nonbg(NP_PROG_X, NP_PROG_Y, NP_PROG_W, 6, th->surface);
|
||
int btnpx = region_nonbg(NP_PLAY_CX - NP_PLAY_R, NP_BTN_Y - NP_PLAY_R,
|
||
NP_PLAY_R * 2, NP_PLAY_R * 2, th->surface);
|
||
snprintf(nm, sizeof nm, "style=%s mode=%s prog+btn render",
|
||
styles[si] ? "MIUIx" : "MD3", modes[mi] ? "dark" : "light");
|
||
CHECK(progpx > 0 && btnpx > 0, nm);
|
||
}
|
||
}
|
||
|
||
/* ============ G5 双页手势方向感知(np_gesture.h,纯逻辑) ============ */
|
||
printf("G5 dual-page direction-aware gestures\n");
|
||
/* 播放页:右→左滑 → 进歌词页 */
|
||
CHECK(np_gesture_decide(NP_PAGE_PLAY, 200, 100, -50, 5) == NP_GEST_NEXT,
|
||
"play: left-swipe -> lyric");
|
||
/* 播放页:左缘右滑 → 回桌面(保留 M6.26) */
|
||
CHECK(np_gesture_decide(NP_PAGE_PLAY, 8, 100, 80, 5) == NP_GEST_EXIT,
|
||
"play: edge right-swipe -> exit");
|
||
/* 播放页:非左缘右滑 → 播放页无上一页,忽略(不误切/不误退) */
|
||
CHECK(np_gesture_decide(NP_PAGE_PLAY, 200, 100, 80, 5) == NP_GEST_NONE,
|
||
"play: non-edge right-swipe -> none");
|
||
/* 歌词页:左→右滑 → 回播放页(不是回桌面) */
|
||
CHECK(np_gesture_decide(NP_PAGE_LYRIC, 200, 100, 60, 5) == NP_GEST_PREV,
|
||
"lyric: right-swipe -> play");
|
||
/* 歌词页:左缘右滑 → 回播放页(不接桌面,避免误退) */
|
||
CHECK(np_gesture_decide(NP_PAGE_LYRIC, 8, 100, 80, 5) == NP_GEST_PREV,
|
||
"lyric: edge right-swipe -> play (not exit)");
|
||
/* 歌词页:右→左滑 → 歌词页是末页,忽略 */
|
||
CHECK(np_gesture_decide(NP_PAGE_LYRIC, 200, 100, -60, 5) == NP_GEST_NONE,
|
||
"lyric: left-swipe -> none");
|
||
/* 顶部热区下滑 → 通知中心(两个页面同语义) */
|
||
CHECK(np_gesture_decide(NP_PAGE_PLAY, 100, 10, 0, 70) == NP_GEST_TOP,
|
||
"top swipe-down -> notif");
|
||
/* 近点按 → TAP;纵向主导 → NONE(不误切) */
|
||
CHECK(np_gesture_decide(NP_PAGE_PLAY, 200, 300, 5, 5) == NP_GEST_TAP,
|
||
"near tap -> TAP");
|
||
CHECK(np_gesture_decide(NP_PAGE_PLAY, 200, 300, 5, 80) == NP_GEST_NONE,
|
||
"vertical dominant -> NONE");
|
||
|
||
/* ============ G6 歌词页:空态 + 渲染(批2 控件 + 批3 数据语义) ============ */
|
||
printf("G6 lyric view: empty + render\n");
|
||
meow_ui_activity_push(NP_SENTINEL);
|
||
meow_lyric_view *lv = meow_lyric_view_create(0, 28, 480, 720 - 28, lv_test_width, NULL);
|
||
meow_lyric_view_set_empty(lv, "暂无歌词");
|
||
meow_ui_activity_pop();
|
||
CHECK(lv != NULL, "lyric_view created");
|
||
if (lv) {
|
||
/* 无歌词空态:无解析 → 0 行、visible=0(视图内建空态文案) */
|
||
meow_lyric_clear();
|
||
CHECK(meow_lyric_count() == 0, "clear -> 0 lines");
|
||
CHECK(meow_lyric_view_visible(lv, 0, NULL, 0) == 0, "empty -> 0 visible entries");
|
||
/* 有词:解析两行 → count/line_at/visible/当前行色 */
|
||
int np = meow_lyric_parse("[00:01.00]第一行歌词\n[00:05.00]第二行歌词", "");
|
||
CHECK(np == 2, "parse -> 2 lines");
|
||
CHECK(meow_lyric_count() == 2, "count == 2");
|
||
CHECK(meow_lyric_line_at(0) == 0, "line_at(0) -> first");
|
||
CHECK(meow_lyric_line_at(3000) == 0, "line_at(3000) -> first");
|
||
CHECK(meow_lyric_line_at(6000) == 1, "line_at(6000) -> second");
|
||
CHECK(strcmp(meow_lyric_line_text(0), "第一行歌词") == 0, "text(0) == 第一行歌词");
|
||
meow_lyric_view_vis vb[9];
|
||
int vn = meow_lyric_view_visible(lv, 3000, vb, 9);
|
||
CHECK(vn > 0, "visible(3000) > 0");
|
||
CHECK(meow_lyric_view_cur_color(lv) == meow_theme_get()->onSurface,
|
||
"cur_color == theme onSurface");
|
||
/* 批5 取色预留:set_colors 可接入(覆盖活动行为主题 accent 后再还原默认) */
|
||
meow_lyric_view_colors c; memset(&c, 0, sizeof c);
|
||
c.cur = meow_theme_get()->accent;
|
||
meow_lyric_view_set_colors(lv, &c);
|
||
CHECK(meow_lyric_view_cur_color(lv) == meow_theme_get()->accent,
|
||
"set_colors cur override works");
|
||
meow_lyric_view_set_colors(lv, NULL); /* 回主题默认 */
|
||
CHECK(meow_lyric_view_cur_color(lv) == meow_theme_get()->onSurface,
|
||
"set_colors NULL -> theme default");
|
||
meow_lyric_view_free(lv);
|
||
}
|
||
|
||
meow_ui_activity_pop();
|
||
meow_ui_shutdown();
|
||
printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail);
|
||
return g_fail;
|
||
}
|