feat(launcher): 批4 Now Playing双页(播放页⇄歌词页) — 方向感知手势(播放页右→左进歌词/左缘右滑回桌面;歌词页左→右回播放)+歌词页用批3数据+批1/2控件实时滚动+空态;宿主G5/G6全过,0warning;meow_ui.a未重编

This commit is contained in:
AskaEth committed 2026-09-03 20:00:38 +08:00
1 parent 2e5b814059
commit 8c677f127f
4 files changed
+310 -39

No files matched your search

+146 -38
View File
@@ -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() 重绘——否则时间冻结在进入播放页那一刻)。 */