From 8c677f127f62f3e9f71f90186f02d75db03d2fa1 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Thu, 3 Sep 2026 20:00:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(launcher):=20=E6=89=B94=20Now=20Playing?= =?UTF-8?q?=E5=8F=8C=E9=A1=B5(=E6=92=AD=E6=94=BE=E9=A1=B5=E2=87=84?= =?UTF-8?q?=E6=AD=8C=E8=AF=8D=E9=A1=B5)=20=E2=80=94=20=E6=96=B9=E5=90=91?= =?UTF-8?q?=E6=84=9F=E7=9F=A5=E6=89=8B=E5=8A=BF(=E6=92=AD=E6=94=BE?= =?UTF-8?q?=E9=A1=B5=E5=8F=B3=E2=86=92=E5=B7=A6=E8=BF=9B=E6=AD=8C=E8=AF=8D?= =?UTF-8?q?/=E5=B7=A6=E7=BC=98=E5=8F=B3=E6=BB=91=E5=9B=9E=E6=A1=8C?= =?UTF-8?q?=E9=9D=A2;=E6=AD=8C=E8=AF=8D=E9=A1=B5=E5=B7=A6=E2=86=92?= =?UTF-8?q?=E5=8F=B3=E5=9B=9E=E6=92=AD=E6=94=BE)+=E6=AD=8C=E8=AF=8D?= =?UTF-8?q?=E9=A1=B5=E7=94=A8=E6=89=B93=E6=95=B0=E6=8D=AE+=E6=89=B91/2?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6=E5=AE=9E=E6=97=B6=E6=BB=9A=E5=8A=A8+?= =?UTF-8?q?=E7=A9=BA=E6=80=81;=E5=AE=BF=E4=B8=BBG5/G6=E5=85=A8=E8=BF=87,0w?= =?UTF-8?q?arning;meow=5Fui.a=E6=9C=AA=E9=87=8D=E7=BC=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- patch_v2/meow_launcherd/build_launcher.sh | 1 + patch_v2/meow_launcherd/now_playing.c | 184 ++++++++++++++---- patch_v2/meow_launcherd/np_gesture.h | 85 ++++++++ .../meow_launcherd/test_now_playing_widgets.c | 79 +++++++- 4 files changed, 310 insertions(+), 39 deletions(-) create mode 100644 patch_v2/meow_launcherd/np_gesture.h diff --git a/patch_v2/meow_launcherd/build_launcher.sh b/patch_v2/meow_launcherd/build_launcher.sh index 94a0001..bff4d6e 100755 --- a/patch_v2/meow_launcherd/build_launcher.sh +++ b/patch_v2/meow_launcherd/build_launcher.sh @@ -29,6 +29,7 @@ SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf" font_theme.c keys.c util.c meow_dep.c \ meow_touch.c meow_settings.c meow_notif.c meow_media.c meow_bgplay.c \ meow_ams_start.c lock_screen.c lock_screen_ui.c lock_settings.c \ + "../sdk/meow_lyric.c" "../sdk/meow_lyric_view.c" \ "../sdk/meow_ui.a" "$SYSROOT/lib/libz.a" -lpthread -o meow_launcherd echo "== meow_launcherd ==" ls -la meow_launcherd diff --git a/patch_v2/meow_launcherd/now_playing.c b/patch_v2/meow_launcherd/now_playing.c index fa2249a..6865a7f 100644 --- a/patch_v2/meow_launcherd/now_playing.c +++ b/patch_v2/meow_launcherd/now_playing.c @@ -10,6 +10,8 @@ * 元数据/源变化立即整页重画;交互只重绘按钮/进度带。 */ #include "meow_launcher_internal.h" +#include "np_gesture.h" /* 批4:双页方向感知手势决策(header-only,宿主可单测) */ +#include "meow_lyric_view.h" /* 批2控件:歌词页渲染(批4接入,数据批3 meow_media_lyric_*) */ #define NP_COVER 300 /* 封面边长(方,居中) */ @@ -33,6 +35,23 @@ static meow_img *g_np_cover = NULL; static char g_np_cover_path[200] = ""; +/* ---- 批4:Now Playing 双页状态 + 歌词页控件 ---- + * 同一容器两页:NP_PAGE_PLAY(播放页,原有内容)+ NP_PAGE_LYRIC(歌词页,批2 meow_lyric_view)。 + * 页面切换靠方向感知手势(np_gesture.h);歌词数据批3(meow_media_lyric_*),主题先走 meow_theme + * 默认;meow_lyric_view_set_colors 为批5 封面取色动态主题预留(本批不取色,见 np_lyric_refresh)。 */ +static int g_np_page = NP_PAGE_PLAY; +static meow_lyric_view *g_np_lyric = NULL; /* 歌词视图(lazy 建一次,跨进入复用) */ +static uint32_t g_np_lyric_seq = 0xFFFFFFFFu; /* 上次解析的歌曲代际(新 seq 才重解析) */ +static char g_np_lyric_app[MEOW_APP_ID_MAX] = ""; /* 上次解析的源 app_id */ +static int g_np_lyric_last_valid = -1; /* -1=未初始化 0=无词 1=有词(防重复 clear) */ + +/* 批4:歌词页宽度测量(launcher ui_width 包装成 meow_lyric_width_fn;meow_lyric_view 需要)。 */ +static int np_lyric_width(const char *u, int size, void *ctx) +{ + (void)ctx; + return ui_width(u, size); +} + /* 封面解码缓存(artwork 路径变化才重载)。 */ static void np_cover_load(const meow_media_session_t *s) { @@ -137,6 +156,12 @@ static void np_widgets_create(void) op.bg = th->primary; op.fg = th->onPrimary; if (g_np_play) meow_widget_set_opts(g_np_play, &op); if (g_np_pause) meow_widget_set_opts(g_np_pause, &op); + /* 批4:歌词页视图(覆盖状态栏下整页;宽测量走 launcher ui_width 包装)。 + * set_colors 留作批5 封面取色覆盖入口;本批用 meow_theme 默认(不调用)。 */ + g_np_lyric = meow_lyric_view_create(0, SB_BAR_H, fb_xres, fb_yres - SB_BAR_H, + np_lyric_width, NULL); + if (g_np_lyric) + meow_lyric_view_set_empty(g_np_lyric, meow_str_zh("np.no_lyric", "暂无歌词")); meow_ui_activity_pop(); g_np_init = 1; } @@ -180,12 +205,64 @@ static void np_draw_progress(const meow_media_session_t *s, uint32_t pos, const ui_draw(NP_PROG_X + NP_PROG_W - ui_width(rbuf, 16), NP_TIME_Y, rbuf, th->on_surface_variant, 16); } +/* ================================================================== + * 批4:歌词页渲染(批2 meow_lyric_view + 数据批3 meow_media_lyric_*)。 + * - 有词:meow_lyric_parse(lrc,tlrc)→meow_lyric_view_set_pos(display_pos) 实时滚动, + * 当前行由 meow_lyric_view 内部 meow_lyric_line_at(pos_ms) 计算(与绘制/点跳同源)。 + * - 无词/无源:清空并置 pos=0,视图内建空态文案(np.no_lyric→"暂无歌词")。 + * 主题:先走 meow_theme 默认(视图自绘 scrim/活动行纯白/灰阶递减);批5 封面取色动态主题的可 + * 接入点为 meow_lyric_view_set_colors(本批不取色,故不调用;接入只需在下方填 meow_lyric_view_colors + * 后调用一次,字段 0=回主题默认)。 + * ================================================================== */ +static void np_lyric_refresh(const meow_media_session_t *s) +{ + if (!g_np_lyric) return; + int valid = meow_media_lyric_valid(s); + if (valid) { + /* 换歌/换源(新 lyric_seq/app_id)才重解析;否则沿用,只推进位置。 */ + if (g_np_lyric_seq != s->lyric_seq || strcmp(g_np_lyric_app, s->app_id) != 0) { + meow_lyric_parse(meow_media_lyric_lrc(s), meow_media_lyric_tlrc(s)); + g_np_lyric_seq = s->lyric_seq; + snprintf(g_np_lyric_app, sizeof g_np_lyric_app, "%s", s->app_id); + } + meow_lyric_view_set_pos(g_np_lyric, meow_media_display_pos(s)); + } else { + /* 无词/无源:仅当上次仍有词才清空(省冗余 memset),置 pos=0。 */ + if (g_np_lyric_last_valid != 0) { + meow_lyric_clear(); + g_np_lyric_seq = 0xFFFFFFFFu; + g_np_lyric_app[0] = 0; + } + meow_lyric_view_set_pos(g_np_lyric, 0); + } + g_np_lyric_last_valid = valid; +} + +/* 歌词页整页绘制:铺 theme surface 底 → lyric_view(自绘 scrim + 歌词/空态)。 */ +static void np_draw_lyric_page(int redraw_sb) +{ + const meow_ui_theme_t *th = s_ui_theme(); + int W = fb_xres, H = fb_yres; + if (!g_np_init) np_widgets_create(); + if (redraw_sb) fill_rect(0, 0, W, SB_BAR_H, th->surface); + fill_rect(0, SB_BAR_H, W, H - SB_BAR_H, th->surface); + np_lyric_refresh(meow_media_current()); + if (g_np_lyric) + meow_widget_draw(meow_lyric_view_widget(g_np_lyric)); + if (redraw_sb) draw_statusbar(); +} + /* 整页绘制(空态占位)。redraw_sb=1 时喂/标脏状态栏(打开/主题变化),否则保留上次顶条。 */ static void np_draw_page(int redraw_sb) { const meow_ui_theme_t *th = s_ui_theme(); int W = fb_xres, H = fb_yres; if (!g_np_init) np_widgets_create(); /* M7.0b:lazy 建控件(跨进入复用) */ + /* 批4:歌词页整页走 lyric_view(无源/无词空态由视图内建)。 */ + if (g_np_page == NP_PAGE_LYRIC) { + np_draw_lyric_page(redraw_sb); + return; + } if (redraw_sb) fill_rect(0, 0, W, SB_BAR_H, th->surface); fill_rect(0, SB_BAR_H, W, H - SB_BAR_H, th->surface); const meow_media_session_t *s = meow_media_current(); @@ -299,10 +376,14 @@ void run_now_playing(struct app *apps, int napps) uint32_t last_state = 0xFFFFFFFFu; char last_app[MEOW_APP_ID_MAX] = ""; - meow_ui_activity_push(NP_SENTINEL_ACT); /* M7.0b:进入 push 哨兵(与函数尾 449 行 pop 配对)。 + meow_ui_activity_push(NP_SENTINEL_ACT); /* M7.0b:进入 push 哨兵(与函数尾 pop 配对)。 * 控件 owner 一直钉哨兵,命中要走 meow_widget_hit 的 * owner==active 闸门——漏 push 则 active()!=0x51, * 三钮 meow_widget_hit 恒 false(「播放页三键无效」根因)。 */ + g_np_page = NP_PAGE_PLAY; /* 批4:每次进入 Now Playing 从播放页起(上次退出页不残留) */ + g_np_lyric_last_valid = -1; /* 批4:重置歌词「是否有词」过渡态,防首帧多清一次 */ + g_np_lyric_seq = 0xFFFFFFFFu; /* 批4:强制首次进歌词页重解析(防跨进入残留缓存) */ + g_np_lyric_app[0] = 0; np_draw_page(1); meow_dirty_full(); meow_ui_flush(); @@ -335,43 +416,61 @@ void run_now_playing(struct app *apps, int napps) if (td_ok) { int dx = tlx - tdx, dy = tly - tdy; int adx = dx < 0 ? -dx : dx, ady = dy < 0 ? -dy : dy; - /* M6.21 诊断:打印该次抬手手势的完整输入与各判定结果(真机定位 - * 「返回滑不出」根因——状态栏/通知中心已生效,惟返回未触发)。 */ - int np_is_top = mnp_top_swipe_down(tdx, tdy, dx, dy, SB_BAR_H, 65); - int np_edge = meow_edge_back(tdx, dx, dy); - int np_anyright = (dx >= 40 && adx > ady); - logmsg("np up: down=(%d,%d) d=(%d,%d) adx=%d ady=%d top=%d edge=%d anyright=%d", - tdx, tdy, dx, dy, adx, ady, np_is_top, np_edge, np_anyright); - /* 顶部热区下滑 → 打开通知中心(与主界面同语义;mnp_top_swipe_down - * 内部已排除左缘 down_x<24,与下方返回手势互斥)。run_notif_center - * 为阻塞模态,返回后整页重画播放页。 */ - if (np_is_top) { - notif_click_t click; - memset(&click, 0, sizeof click); - click.app_idx = -1; - click.status = NOTIF_CLICK_STATUS_NONE; - run_notif_center(apps, napps, &click, 0, 0, 0); - need_redraw = 1; - meow_dirty_full(); - meow_ui_flush(); - } - /* 左缘右滑返回(全局手势语义),或任意横向右滑返回——与主界面 - * 「左滑进入 Now Playing」对称。返回阈值 65→40 放宽(防用户右滑 - * 幅度不足 65 判不中);播放页无横向分页内容、无进度条拖动 - * (进度=点按,幅度<60 走 tap 分支),故任意位置横向右滑恒判返回。 */ - else if (np_edge || np_anyright) { - logmsg("np: BACK break (edge=%d anyright=%d dx=%d)", np_edge, np_anyright, dx); - np_exit = 1; - break; /* 仅跳出内层读事件 while;外层主循环靠 np_exit 退出 */ - } - if (adx < 60 && ady < 60) { - int cmd = np_btn_tap(tlx, tly); - if (cmd) { meow_media_playctl((uint32_t)cmd, 0); need_redraw = 1; } - else { - int64_t seek = np_progress_seek(tlx, tly); - if (seek > 0) meow_media_playctl(5, seek); + /* M6.21 诊断 + 批4 页感知:打印该次抬手手势的完整输入与按当前页 + * 的方向感知判定(真机定位「返回滑不出」根因曾依赖此日志; + * 保留,键改为 ges=np_gesture_decide 返回值)。 */ + np_gest_t gnp = np_gesture_decide(g_np_page, tdx, tdy, dx, dy); + logmsg("np up: page=%d down=(%d,%d) d=(%d,%d) adx=%d ady=%d ges=%d", + g_np_page, tdx, tdy, dx, dy, adx, ady, (int)gnp); + switch (gnp) { + case NP_GEST_TOP: + /* 顶部热区下滑 → 打开通知中心(与主界面同语义;mnp_top_swipe_down + * 内部已排除左缘 down_x<24,与下方返回手势互斥)。run_notif_center + * 为阻塞模态,返回后整页重画。 */ + { + notif_click_t click; + memset(&click, 0, sizeof click); + click.app_idx = -1; + click.status = NOTIF_CLICK_STATUS_NONE; + run_notif_center(apps, napps, &click, 0, 0, 0); + need_redraw = 1; + meow_dirty_full(); + meow_ui_flush(); } + break; + case NP_GEST_EXIT: + /* 播放页左缘右滑 → 回桌面(保留 M6.26 全局手势语义)。 */ + logmsg("np: BACK break (exit, page=%d)", g_np_page); + np_exit = 1; + break; + case NP_GEST_NEXT: + /* 播放页 右→左滑 → 进歌词页。 */ + g_np_page = NP_PAGE_LYRIC; + need_redraw = 1; + logmsg("np: -> lyric page"); + break; + case NP_GEST_PREV: + /* 歌词页 左→右滑 → 回播放页(含左缘;不接桌面,避免误退)。 */ + g_np_page = NP_PAGE_PLAY; + need_redraw = 1; + logmsg("np: -> play page"); + break; + case NP_GEST_TAP: + /* 近点按(按页分派):播放页=三钮/进度看点;歌词页预留点跳 + * (本批不启用,见 batch5/后续:借 meow_lyric_view_hit_line + on_seek)。 */ + if (g_np_page == NP_PAGE_PLAY) { + int cmd = np_btn_tap(tlx, tly); + if (cmd) { meow_media_playctl((uint32_t)cmd, 0); need_redraw = 1; } + else { + int64_t seek = np_progress_seek(tlx, tly); + if (seek > 0) meow_media_playctl(5, seek); + } + } + break; + default: + break; /* NP_GEST_NONE:反向/纵向主导,忽略不误切 */ } + if (np_exit) break; /* M6.22:返回手势已命中 → 跳出内层读事件 while */ tdx = tdy = -1; } else { tdx = tdy = -1; } } @@ -438,9 +537,18 @@ void run_now_playing(struct app *apps, int napps) need_redraw = 0; last_prog = now; } else if (s && (now - last_prog >= 1000)) { - /* 进度推进:仅重绘进度带 + 按钮 */ + /* 进度推进(按页分流):播放页=进度带+按钮;歌词页=歌词实时滚动重绘(当前行随 pos)。 */ last_prog = now; - np_repaint_progress(); + if (g_np_page == NP_PAGE_LYRIC) { + np_lyric_refresh(s); + if (g_np_lyric) { + meow_widget_draw(meow_lyric_view_widget(g_np_lyric)); + meow_dirty_add(0, SB_BAR_H, fb_xres, fb_yres - SB_BAR_H); + meow_ui_flush(); + } + } else { + np_repaint_progress(); + } } /* M6.20:播放页状态栏时钟 1s 刷新(与主界面/子页同语义;draw_clock 喂 SDK 全局 * 状态栏 + 标脏顶条,SDK present() 重绘——否则时间冻结在进入播放页那一刻)。 */ diff --git a/patch_v2/meow_launcherd/np_gesture.h b/patch_v2/meow_launcherd/np_gesture.h new file mode 100644 index 0000000..e5f8395 --- /dev/null +++ b/patch_v2/meow_launcherd/np_gesture.h @@ -0,0 +1,85 @@ +/* np_gesture.h — Now Playing 双页手势方向感知(header-only 纯逻辑,宿主可单测)。 + * + * 定案(用户,批4):Now Playing 同一容器两页——播放页 + 歌词页,手势按当前页方向感知: + * - 播放页:右→左滑 → 进歌词页;左缘右滑 → 回桌面(保留 M6.26 语义)。 + * - 歌词页:左→右滑 → 回播放页(不是回桌面);左缘右滑不接桌面(避免误退)。 + * 本头为 now_playing.c 与宿主单测(test_now_playing_widgets.c)共用的唯一手势决策,避免两端漂移。 + * + * 依赖:仅 + meow_notif_panel.h(mnp_top_swipe_down,纯函数)。左缘返回逻辑自 + * (re)实现(镜像 meow_touch.c meow_edge_back 同口径,宿主编译无需链 meow_touch.c)。 + */ +#ifndef NP_GESTURE_H +#define NP_GESTURE_H + +#include +#include "meow_notif_panel.h" /* mnp_top_swipe_down(顶部下滑→通知中心) */ + +/* ---- 页面 ---- */ +#define NP_PAGE_PLAY 0 +#define NP_PAGE_LYRIC 1 + +/* ---- 手势动作 ---- */ +typedef enum { + NP_GEST_NONE = 0, /* 无(横向反向滑/纵向主导不匹配;调用方忽略,不误切页) */ + NP_GEST_TOP, /* 顶部热区下滑 → 打开通知中心 */ + NP_GEST_NEXT, /* 播放页 右→左滑 → 进入歌词页 */ + NP_GEST_PREV, /* 歌词页 左→右滑 → 回到播放页 */ + NP_GEST_EXIT, /* 播放页 左缘右滑 → 返回桌面 */ + NP_GEST_TAP, /* 近点按(按当前页分派:播放页=按钮/进度看点) */ +} np_gest_t; + +/* ---- 常量(对齐 launcher 同口径;#ifndef 防与 meow_touch.h 等重复定义冲突)---- */ +#ifndef NP_SWIPE_THRESH +#define NP_SWIPE_THRESH 65 /* 顶部下滑触发位移阈值(对齐 launcher MENU_SWIPE_THRESH) */ +#endif +#ifndef NP_PAGE_SWIPE +#define NP_PAGE_SWIPE 40 /* 横向切页最小位移(对齐 M6.26 np_anyright 的 dx>=40) */ +#endif +#ifndef NP_TAP_MAX +#define NP_TAP_MAX 60 /* 点按位移上限(对齐现有 adx<60 && ady<60) */ +#endif +#ifndef NP_TOP_HOTZONE +#define NP_TOP_HOTZONE 28 /* 顶部热区高(= launcher SB_BAR_H;通知中心/播放页同) */ +#endif +#ifndef NP_EDGE_W +#define NP_EDGE_W 24 /* 左缘起点宽度(镜像 meow_touch.h MEOW_EDGE_W) */ +#endif +#ifndef NP_EDGE_SWIPE +#define NP_EDGE_SWIPE 65 /* 左缘右滑位移阈值(镜像 meow_touch.h MEOW_EDGE_SWIPE) */ +#endif + +/* 左缘返回(镜像 meow_touch.c meow_edge_back:down_x∈[0,NP_EDGE_W) 且 dx>=NP_EDGE_SWIPE + * 且横向主导)。为宿主测试免链 meow_touch.c,此处 static inline 复刻同口径。 */ +static inline int np_edge_back(int down_x, int dx, int dy) +{ + int ady = dy < 0 ? -dy : dy; + if (down_x >= 0 && down_x < NP_EDGE_W && dx >= NP_EDGE_SWIPE && ady < dx) + return 1; + return 0; +} + +/* 按当前页把手势几何 → 动作。down_x/down_y=抬手原点;dx/dy=抬手位移(释放-原点,即 tlx-tdx)。 + * 优先级:顶部下滑 > 横向主导切页/返回 > 点按。横向反向或无主导 → NONE(调用方忽略,不误切)。 */ +static inline np_gest_t np_gesture_decide(int page, int down_x, int down_y, int dx, int dy) +{ + int adx = dx < 0 ? -dx : dx, ady = dy < 0 ? -dy : dy; + /* 顶部热区下滑 → 通知中心(与主界面同语义;mnp_top_swipe_down 内部排除左缘起点) */ + if (mnp_top_swipe_down(down_x, down_y, dx, dy, NP_TOP_HOTZONE, NP_SWIPE_THRESH)) + return NP_GEST_TOP; + /* 横向主导(adx>=NP_PAGE_SWIPE 且横向位移>竖向)→ 按当前页分流切页/返回 */ + if (adx >= NP_PAGE_SWIPE && adx > ady) { + if (page == NP_PAGE_PLAY) { + if (np_edge_back(down_x, dx, dy)) return NP_GEST_EXIT; /* 左缘右滑 → 回桌面 */ + if (dx <= -NP_PAGE_SWIPE) return NP_GEST_NEXT; /* 右→左滑 → 进歌词页 */ + return NP_GEST_NONE; /* 非左缘右滑=播放页无上一页,忽略 */ + } else { /* NP_PAGE_LYRIC */ + if (dx >= NP_PAGE_SWIPE) return NP_GEST_PREV; /* 左→右滑 → 回播放页(含左缘,不接桌面) */ + return NP_GEST_NONE; /* 右→左滑=歌词页是末页,忽略 */ + } + } + /* 非横向主导:近点按 */ + if (adx < NP_TAP_MAX && ady < NP_TAP_MAX) return NP_GEST_TAP; + return NP_GEST_NONE; +} + +#endif /* NP_GESTURE_H */ diff --git a/patch_v2/meow_launcherd/test_now_playing_widgets.c b/patch_v2/meow_launcherd/test_now_playing_widgets.c index 716b566..e197827 100644 --- a/patch_v2/meow_launcherd/test_now_playing_widgets.c +++ b/patch_v2/meow_launcherd/test_now_playing_widgets.c @@ -16,11 +16,15 @@ * ../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 -I../sdk -I. -DMEOW_UI_HOST_TEST -pthread -lm + * ../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 #include #include @@ -86,6 +90,13 @@ static void mk_widgets(void) 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); @@ -168,6 +179,72 @@ int main(void) } } + /* ============ 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);