Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/test_now_playing_widgets.c
T

353 lines
18 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_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 "meow_img_color.h" /* 批5b:封面取色主题派生(局部重绘进度条/按钮带取色用例) */
#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 unsigned short rgb565(uint32_t c)
{ return (unsigned short)(((c >> 8) & 0xF800) | ((c >> 5) & 0x07E0) | ((c >> 3) & 0x001F)); }
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);
}
}
/* ============ R1 批5b:局部重绘取色——进度条 fill=accent/track=fg_dim + 默认回退 ============
* 镜像 np_theme_widgets(取色有效→进度条 opts.bg=fg_dim/opts.fg=accent)+ np_draw_progress。
* 用「直接 fill_rect 参考像素」对比,绕开 565 精度差异(同一渲染器同色 → 同 565)。 */
printf("R1 progress cover-theme fill/track + default fallback\n");
meow_ui_set_style(MEOW_STYLE_MD3);
meow_theme_set_mode(MEOW_THEME_DARK);
th = meow_theme_get();
meow_img_color_theme cc; memset(&cc, 0, sizeof cc);
meow_img_color_derive(0xE53935u, 0x1E88E5u, th, &cc); /* 红/蓝两色 → 派生取色主题 */
/* 覆盖取色:进度条 fill=accent / track=fg_dim */
{
meow_widget_opts op; memset(&op, 0, sizeof op);
op.bg = cc.fg_dim; op.fg = cc.accent;
meow_widget_set_opts(prog, &op);
meow_ui_clear(0);
meow_ui_fill_rect(NP_PROG_X, NP_PROG_Y, 5, 6, cc.accent); /* fill 参考 */
unsigned short ref_fill = px565(NP_PROG_X + 2, NP_PROG_Y + 3);
meow_ui_clear(0);
meow_ui_fill_rect(NP_PROG_X + NP_PROG_W - 4, NP_PROG_Y, 3, 6, cc.fg_dim); /* track 参考 */
unsigned short ref_track = px565(NP_PROG_X + NP_PROG_W - 3, NP_PROG_Y + 3);
meow_ui_clear(0);
meow_progress_set(prog, 50);
meow_widget_draw(prog);
int fill_x = NP_PROG_X + NP_PROG_W / 4; /* 50% 填充区内 */
int track_x = NP_PROG_X + NP_PROG_W - 4; /* 尾部轨道区 */
CHECK(px565(fill_x, NP_PROG_Y + 3) == ref_fill, "cover: progress fill == accent");
CHECK(px565(track_x, NP_PROG_Y + 3) == ref_track, "cover: progress track == fg_dim");
/* 默认回退:清 opts → 回到主题 primary/surfaceVariant(无取色回退默认) */
memset(&op, 0, sizeof op);
meow_widget_set_opts(prog, &op);
meow_ui_clear(0);
meow_ui_fill_rect(NP_PROG_X, NP_PROG_Y, 5, 6, th->primary);
unsigned short ref_pri = px565(NP_PROG_X + 2, NP_PROG_Y + 3);
meow_ui_clear(0);
meow_progress_set(prog, 50);
meow_widget_draw(prog);
CHECK(px565(fill_x, NP_PROG_Y + 3) == ref_pri, "default: progress fill == theme primary");
CHECK(px565(fill_x, NP_PROG_Y + 3) != ref_fill, "default vs cover fill differ");
}
/* ============ R2 批5b:进度/按钮带底色随取色渐变(非默认 surface) ============
* 镜像 np_repaint_progress 的带底色分支(np_page_bg_row 按整页 span 插值)。
* 旧 bug:本地重绘用 th->surface 清带 → 带内被刷成默认色;现应取色渐变且 != surface。 */
printf("R2 band background uses cover gradient (not default surface)\n");
{
#define NP_SB_H 28
int H = 720;
meow_ui_clear(0);
for (int yy = NP_TIME_Y; yy < NP_BTN_Y + NP_PLAY_R; yy++) {
int t = (H - NP_SB_H <= 1) ? 0 : (yy - NP_SB_H) * 255 / (H - NP_SB_H - 1);
if (t < 0) t = 0; else if (t > 255) t = 255;
int tr = (cc.bg_top >> 16) & 255, tg = (cc.bg_top >> 8) & 255, tb = cc.bg_top & 255;
int br = (cc.bg_bottom >> 16) & 255, bg = (cc.bg_bottom >> 8) & 255, bb = cc.bg_bottom & 255;
int r = tr + ((br - tr) * t + 128) / 255;
int g = tg + ((bg - tg) * t + 128) / 255;
int b = tb + ((bb - tb) * t + 128) / 255;
meow_ui_fill_rect(0, yy, 480, 1, ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b);
}
unsigned short bandpx = px565(10, NP_TIME_Y + 55);
CHECK(bandpx != rgb565(th->surface), "cover: band bg != default surface");
CHECK(bandpx != 0, "cover: band bg rendered");
}
/* ============ R3 批5b:控制三钮(含 prev/next)随取色 accent + 默认回退 ============
* 镜像 np_theme_widgets:取色有效→四钮 opts.bg=accent/opts.fg=accent_fg(prev/next 不再默认底色)。 */
printf("R3 control buttons follow cover accent\n");
{
meow_widget_opts op; memset(&op, 0, sizeof op);
op.bg = cc.accent; op.fg = cc.accent_fg;
meow_widget_set_opts(prev, &op);
meow_widget_set_opts(next, &op);
meow_widget_set_opts(play, &op);
meow_widget_set_opts(pause, &op);
meow_ui_clear(0);
meow_ui_fill_rect(NP_PREV_CX - 2, NP_BTN_Y - 18, 4, 2, cc.accent); /* accent 参考(圆底上方、图标外) */
unsigned short ref_acct = px565(NP_PREV_CX, NP_BTN_Y - 17);
meow_ui_clear(0);
meow_widget_draw(prev); /* r=22,cy-17 在圆内且高于图标(cy-13) */
CHECK(px565(NP_PREV_CX, NP_BTN_Y - 17) == ref_acct, "cover: prev bg == accent");
meow_ui_clear(0);
meow_widget_draw(play); /* r=30,cy-26 在圆内且高于图标(cy-18) */
CHECK(px565(NP_PLAY_CX, NP_BTN_Y - 26) == ref_acct, "cover: play bg == accent");
/* 默认回退:清 prev/next opts → 回 surfaceContainerLow(无取色不回归旧形态) */
memset(&op, 0, sizeof op);
meow_widget_set_opts(prev, &op);
meow_widget_set_opts(next, &op);
meow_ui_clear(0);
meow_ui_fill_rect(NP_PREV_CX - 2, NP_BTN_Y - 18, 4, 2, th->surfaceContainerLow);
unsigned short ref_def = px565(NP_PREV_CX, NP_BTN_Y - 17);
meow_ui_clear(0);
meow_widget_draw(prev);
CHECK(px565(NP_PREV_CX, NP_BTN_Y - 17) == ref_def, "default: prev bg == surfaceContainerLow");
}
/* ============ 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");
/* 播放页:任意右滑 → 返回桌面(用户定案「播放页任意右滑回桌面」,含非左缘;np_gesture.h 恒 EXIT) */
CHECK(np_gesture_decide(NP_PAGE_PLAY, 200, 100, 80, 5) == NP_GEST_EXIT,
"play: non-edge right-swipe -> EXIT");
/* 歌词页:左→右滑 → 回播放页(不是回桌面) */
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;
}