Files
MeowOS-For-R3P2/patch_v2/sdk/meow_lyric_view.c
T

359 lines
15 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.
/*
* meow_lyric_view.c — SDK 通用歌词页渲染控件实现(见 meow_lyric_view.h 的契约说明)。
*
* 上收自 ncm_app/app/ui/player.c:lyr_layout(活动行块中心偏上锚定、上方 ≥LYR_ABOVE_MIN 行已过、
* 下填满、总 ≤LYR_MAX_ROWS)+ plr_draw_lyrics(活动行纯白、上下行逐级灰阶、翻译紧跟随不叠行,
* 块高含翻译行是 agent-380 修好 s_lyr_bh 的关键)+ lyrics_line_tap(命中=布局逐块扫描)。折行复用
* 批1 meow_lyric_wrap(=ncm lyr_wrap_lines 的通用化),宽度测量经注入的 meow_lyric_width_fn。
*
* 控件形态:W_CUSTOM 自绘画布(meow_widget_custom_create),render_cb/hit_cb 由本 TU 实现。
* 布局(lv_compute_layout)计算结果缓存在 lv->vis[0..vis_n),绘制/命中/公开查询共用,三者一致。
*
* 内存纪律:per-instance struct(calloc);布局数组定长 [MEOW_LYRIC_VIEW_MAX_ROWS];折行复用缓冲
* 一块 [MEOW_LYRIC_VIEW_MAX_ROWS][MEOW_LYRIC_SEG_SZ](逐条目复用,同 ncm s_lyr_seg 模式);
* 零临时堆分配(meow_lyric_wrap 静态缓冲)。
*/
#include "meow_lyric_view.h"
#include "meow_ui_internal.h" /* meow_widget_rect:widget 为不透明类型,取几何用访问器 */
#include <stdlib.h>
#include <string.h>
/* 折行宽测量的兜底:字节数近似(仅当使用方未注入 width 时;非像素精确)。
* 保证无渲染引擎下行为确定(maxw 作字节预算,仍按中西文断行)——同 meow_lyric_wrap 的 NULL 语义。 */
static int lv_default_width(const char *u, int size, void *ctx)
{
(void)size; (void)ctx;
return (int)strlen(u);
}
/* 逐通道比例缩放(t=0..255,255=原色)。灰度递减用:对 cur 逐级缩灰(对纯白即复现 ncm 灰阶表)。 */
static uint32_t lv_scale_col(uint32_t c, int t)
{
int r = ((int)((c >> 16) & 255) * t) / 255;
int g = ((int)((c >> 8) & 255) * t) / 255;
int b = ((int)( c & 255) * t) / 255;
return ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b;
}
/* 灰阶比例(距活动行 0..5):0xFFFFFF→{FFFFFF,BBBBBB,929292,808080,6A6A6A,555555}(ncm lyr_gray_tbl)。
* 对非纯白 cur 按同比例缩灰,仍单调变灰。 */
static const int lv_gray_fac[6] = { 255, 187, 146, 128, 106, 85 };
/* 两色线性混合(t=0..255,0=纯 a、255=纯 b)。翻译行融入背景用(无 GPU,逐通道整数混)。 */
static uint32_t lv_mix(uint32_t a, uint32_t b, int t)
{
if (t < 0) t = 0;
if (t > 255) t = 255;
int ar = (a >> 16) & 255, ag = (a >> 8) & 255, ab = a & 255;
int br = (b >> 16) & 255, bg = (b >> 8) & 255, bb = b & 255;
int r = ar + ((br - ar) * t + 128) / 255;
int g = ag + ((bg - ag) * t + 128) / 255;
int bl = ab + ((bb - ab) * t + 128) / 255;
return ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)bl;
}
struct meow_lyric_view {
meow_widget *w;
/* 宽度测量(复用批1 meow_lyric_wrap 的 width_fn 约定;NULL→字节近似兜底) */
meow_lyric_width_fn width;
void *width_ctx;
int64_t pos_ms; /* 当前播放位置(UI 线程写) */
meow_lyric_view_colors colors; /* 用户覆盖(has_colors=1 生效;否则主题默认) */
int has_colors;
meow_lyric_view_layout l; /* 布局参数(内建默认/用户覆盖) */
char empty[64]; /* 无歌词占位文案 */
meow_lyric_seek_fn on_seek;
void *seek_ctx;
/* 布局暂存(每次 lv_compute_layout 重建;UI 线程串行,per-instance 复用) */
meow_lyric_view_vis vis[MEOW_LYRIC_VIEW_MAX_ROWS];
int vis_n;
char seg[MEOW_LYRIC_VIEW_MAX_ROWS][MEOW_LYRIC_SEG_SZ]; /* 折行复用缓冲(逐条目覆盖) */
};
/* 内建默认布局(对齐 ncm LYR_PAD/SUB_LH/CUR_SZ/OTH_SZ/TL_SZ/MAX_ROWS/ABOVE_MIN/ABOVE_OFF)。 */
static const meow_lyric_view_layout k_def_layout = {
24, 44, 28, 24, 20, MEOW_LYRIC_VIEW_MAX_ROWS, 2, 50
};
static const char k_def_empty[] = "歌词加载中…";
/* 解析生效色:!has_colors 或字段 0/scrim_alpha<0 → 主题默认;否则用覆盖。out 必填。 */
static void lv_resolve_colors(const meow_lyric_view *lv, const meow_theme *th,
meow_lyric_view_colors *out)
{
/* 内建默认(theme 驱动) */
uint32_t scrim = th->scrim;
if (scrim == 0) scrim = th->surfaceDim; /* scrim 缺省为黑:用 surfaceDim 兜底(非纯黑) */
uint32_t cur = th->onSurface; /* dark=0xFFFFFF 纯白(活动行高亮) */
out->scrim = scrim;
out->cur = cur;
out->scrim_alpha = 176;
out->gray[0] = cur;
for (int i = 1; i < 6; i++) out->gray[i] = lv_scale_col(cur, lv_gray_fac[i]);
if (!lv->has_colors) return;
/* 用户覆盖(0 字段=沿用上面内建默认;scrim_alpha<0=沿用默认 176) */
if (lv->colors.scrim != 0) out->scrim = lv->colors.scrim;
if (lv->colors.cur != 0) { out->cur = lv->colors.cur; out->gray[0] = lv->colors.cur; }
if (lv->colors.scrim_alpha >= 0) out->scrim_alpha = lv->colors.scrim_alpha;
for (int i = 0; i < 6; i++)
if (lv->colors.gray[i] != 0) out->gray[i] = lv->colors.gray[i];
}
/* 距活动行 dist 的生效行色(dist 越界 clamp 0..5)。 */
static uint32_t lv_line_color(const meow_lyric_view *lv, const meow_theme *th, int dist)
{
meow_lyric_view_colors c;
lv_resolve_colors(lv, th, &c);
if (dist < 0) dist = 0;
if (dist > 5) dist = 5;
return c.gray[dist];
}
/* 折行封套(width 缺省兜底;越界 max_rows 内 clamp 到缓冲容量)。返回段数(≥1)。 */
static int lv_wrap(const meow_lyric_view *lv, const char *txt, int size, int maxw,
char seg[][MEOW_LYRIC_SEG_SZ], int max_rows)
{
meow_lyric_width_fn w = lv->width ? lv->width : lv_default_width;
if (max_rows > MEOW_LYRIC_VIEW_MAX_ROWS) max_rows = MEOW_LYRIC_VIEW_MAX_ROWS;
int rc = meow_lyric_wrap(txt, size, maxw, seg, max_rows, w, lv->width_ctx);
if (rc > max_rows) rc = max_rows;
return rc;
}
/* 核心布局:按 pos_ms 计算可见条目(vis[0..n)。算法与 ncm lyr_layout 逐行为一致)——
* 活动行块中心锚定在视图中线 - above_off(偏上);上方保底 ≥above_min 行已过;下填满;总 ≤max_rows;
* 块高 bh = 原文折行数 rw + 当前行翻译占 1(其余条目无翻译,bh=rw;翻译不叠行,点行命中一致)。 */
static int lv_compute_layout(meow_lyric_view *lv, int64_t pos_ms, int maxw)
{
int n = meow_lyric_count();
int cur = (n > 0) ? meow_lyric_line_at(pos_ms) : -1;
memset(lv->vis, 0, sizeof(lv->vis));
lv->vis_n = 0;
if (cur < 0 || cur >= n) return 0;
int MAX = lv->l.max_rows;
if (MAX > MEOW_LYRIC_VIEW_MAX_ROWS) MAX = MEOW_LYRIC_VIEW_MAX_ROWS;
if (MAX < 1) MAX = 1;
int rc = lv_wrap(lv, meow_lyric_line_text(cur), lv->l.cur_sz, maxw, lv->seg, MAX);
const char *tl = meow_lyric_line_tl(cur);
int has_tl = (tl && tl[0]) ? 1 : 0;
int rc_tot = rc + has_tl; /* 活动行块总子行数(原文折行 + 翻译行) */
int cap = MAX - rc_tot; /* 活动行之外可用的视觉行数 */
if (cap < 0) cap = 0;
int above_want = (cap * 2) / 5; /* 偏上锚定:约 40% 行给上方 */
if (above_want > cur) above_want = cur; /* 上方行数上限 = 已过行数 */
if (above_want < lv->l.above_min) above_want = (cur >= lv->l.above_min) ? lv->l.above_min : cur;
if (above_want > cap) above_want = cap; /* 总行数 ≤ MAX */
/* 1) 上方(cur-1 向上)前插到头部,保持自顶向下 */
int a_cnt = 0, usedA = 0;
for (int i = cur - 1; i >= 0 && usedA < above_want; i--) {
int rr = lv_wrap(lv, meow_lyric_line_text(i), lv->l.oth_sz, maxw, lv->seg, MAX);
if (usedA + rr > above_want) break;
for (int k = a_cnt; k > 0; k--) lv->vis[k] = lv->vis[k - 1];
lv->vis[0].ent = i; lv->vis[0].rw = rr; lv->vis[0].bh = rr; lv->vis[0].sz = lv->l.oth_sz;
a_cnt++; usedA += rr;
}
/* 2) 当前行 */
lv->vis[a_cnt].ent = cur; lv->vis[a_cnt].rw = rc; lv->vis[a_cnt].bh = rc_tot; lv->vis[a_cnt].sz = lv->l.cur_sz;
/* 3) 下方(cur+1 向下) */
int below_want = cap - usedA;
if (below_want > (n - 1 - cur)) below_want = n - 1 - cur;
int b_cnt = 0, usedB = 0;
for (int i = cur + 1; i < n && usedB < below_want; i++) {
int rr = lv_wrap(lv, meow_lyric_line_text(i), lv->l.oth_sz, maxw, lv->seg, MAX);
if (usedB + rr > below_want) break;
int k = a_cnt + 1 + b_cnt;
lv->vis[k].ent = i; lv->vis[k].rw = rr; lv->vis[k].bh = rr; lv->vis[k].sz = lv->l.oth_sz;
b_cnt++; usedB += rr;
}
int cnt = a_cnt + 1 + b_cnt;
if (cnt == 0) return 0;
/* 4) y 定位:活动行原文块中心 = 视图中线 - above_off(偏上);上方向上、下方向下(块间无缝隙) */
int rect[4]; meow_widget_rect(lv->w, rect);
int anchor = rect[1] + rect[3] / 2 - lv->l.above_off; /* rect[1]=vy, rect[3]=vh */
lv->vis[a_cnt].y = anchor - rc * lv->l.sub_lh / 2;
for (int k = a_cnt + 1; k < cnt; k++)
lv->vis[k].y = lv->vis[k - 1].y + lv->vis[k - 1].bh * lv->l.sub_lh;
for (int k = a_cnt - 1; k >= 0; k--)
lv->vis[k].y = lv->vis[k + 1].y - lv->vis[k].bh * lv->l.sub_lh;
lv->vis_n = cnt;
return cnt;
}
/* 自绘回调:scrim 底 + 滚动歌词(活动行纯白偏上锚定 + 上下行逐级灰阶)+ 翻译行(不叠行)+ 空态占位。 */
static void lv_render(meow_widget *w, const meow_theme *th, void *ctx)
{
meow_lyric_view *lv = (meow_lyric_view *)ctx;
int rect[4]; meow_widget_rect(w, rect);
int x = rect[0], vy = rect[1], W = rect[2], H = rect[3];
meow_lyric_view_colors col;
lv_resolve_colors(lv, th, &col);
/* 背景 scrim(半透明叠在封面之上;scrim_alpha==0=不画,App 已自绘叠加层)。 */
if (col.scrim_alpha > 0) meow_ui_fill_rect_alpha(x, vy, W, H, col.scrim, col.scrim_alpha);
int n = meow_lyric_count();
int cur = (n > 0) ? meow_lyric_line_at(lv->pos_ms) : -1;
int maxw = W - 2 * lv->l.pad; if (maxw < 1) maxw = 1;
int cnt = lv_compute_layout(lv, lv->pos_ms, maxw);
uint32_t bgcol = col.scrim; /* 翻译行融入背景(渐隐) */
for (int k = 0; k < cnt; k++) {
int i = lv->vis[k].ent, rows = lv->vis[k].rw, sz = lv->vis[k].sz, vsy = lv->vis[k].y;
int is_cur = (i == cur);
int dist = is_cur ? 0 : ((i - cur) < 0 ? (cur - i) : (i - cur));
uint32_t c = lv_line_color(lv, th, dist);
int rr = lv_wrap(lv, meow_lyric_line_text(i), sz, maxw, lv->seg, rows);
if (rr > rows) rr = rows;
for (int r = 0; r < rr; r++)
meow_ui_text(x + lv->l.pad, vsy + r * lv->l.sub_lh, lv->seg[r], sz, c);
if (is_cur) {
const char *tl = meow_lyric_line_tl(i);
if (tl && tl[0])
meow_ui_text(x + lv->l.pad, vsy + rows * lv->l.sub_lh + 2, tl,
lv->l.tl_sz, lv_mix(0xFFFFFF, bgcol, 120));
}
}
if (n == 0)
meow_ui_text(x + lv->l.pad, vy + H / 2 - 10, lv->empty, 20, col.cur);
}
/* 命中回调:点行 seek(用与绘制一致的布局逐块扫描;块外就近取整)。
* 命中某行 → 存 last 行 + 回调 on_seek(ms)(若有),返回 1=消费;否则 0。 */
static int lv_hit_cb(meow_widget *w, int gx, int gy, void *ctx)
{
meow_lyric_view *lv = (meow_lyric_view *)ctx;
int rect[4]; meow_widget_rect(w, rect);
int rx = rect[0], ry = rect[1], rw = rect[2], rh = rect[3];
/* 视图矩形内含判定(含边缘;点必须在控件内)。 */
if (gx < rx || gx >= rx + rw || gy < ry || gy >= ry + rh) return 0;
int n = meow_lyric_count();
if (n <= 0) return 0;
int maxw = rw - 2 * lv->l.pad; if (maxw < 1) maxw = 1;
int cnt = lv_compute_layout(lv, lv->pos_ms, maxw);
if (cnt <= 0) return 0;
int line = -1;
for (int k = 0; k < cnt; k++) {
int yb = lv->vis[k].y;
int yh = lv->vis[k].bh * lv->l.sub_lh; /* 块高含当前行翻译,点翻译行归本行 */
if (gy >= yb && gy < yb + yh) { line = lv->vis[k].ent; break; }
}
if (line < 0) /* 落在可见块之外 → 就近取整(顶部/底部可见行) */
line = (gy < lv->vis[0].y) ? lv->vis[0].ent : lv->vis[cnt - 1].ent;
if (lv->on_seek) {
int64_t t = meow_lyric_line_time(line);
if (t >= 0) lv->on_seek(t, lv->seek_ctx);
}
return 1;
}
/* ---- 生命周期 ---- */
meow_lyric_view *meow_lyric_view_create(int x, int y, int w, int h,
meow_lyric_width_fn width, void *width_ctx)
{
meow_lyric_view *lv = (meow_lyric_view *)calloc(1, sizeof *lv);
if (!lv) return NULL;
lv->w = meow_widget_custom_create(x, y, w, h, lv_render, lv_hit_cb, lv);
if (!lv->w) { free(lv); return NULL; }
lv->width = width;
lv->width_ctx = width_ctx;
lv->l = k_def_layout;
memcpy(lv->empty, k_def_empty, sizeof k_def_empty);
return lv;
}
void meow_lyric_view_free(meow_lyric_view *lv)
{
if (!lv) return;
if (lv->w) meow_widget_free(lv->w);
free(lv);
}
meow_widget *meow_lyric_view_widget(const meow_lyric_view *lv)
{
return lv ? lv->w : NULL;
}
void meow_lyric_view_set_pos(meow_lyric_view *lv, int64_t pos_ms)
{
if (lv) lv->pos_ms = pos_ms;
}
void meow_lyric_view_set_colors(meow_lyric_view *lv, const meow_lyric_view_colors *c)
{
if (!lv) return;
if (!c) { lv->has_colors = 0; return; }
lv->colors = *c;
lv->has_colors = 1;
}
void meow_lyric_view_set_layout(meow_lyric_view *lv, const meow_lyric_view_layout *ls)
{
if (!lv) return;
lv->l = ls ? *ls : k_def_layout;
}
void meow_lyric_view_set_empty(meow_lyric_view *lv, const char *txt)
{
if (!lv) return;
if (!txt || !txt[0]) { lv->empty[0] = 0; return; }
strncpy(lv->empty, txt, sizeof lv->empty - 1);
lv->empty[sizeof lv->empty - 1] = 0;
}
void meow_lyric_view_set_on_seek(meow_lyric_view *lv, meow_lyric_seek_fn cb, void *ctx)
{
if (!lv) return;
lv->on_seek = cb;
lv->seek_ctx = ctx;
}
/* ---- 查询 ---- */
uint32_t meow_lyric_view_cur_color(const meow_lyric_view *lv)
{
if (!lv) return 0;
return lv_line_color(lv, meow_theme_get(), 0);
}
uint32_t meow_lyric_view_line_color(const meow_lyric_view *lv, int dist)
{
if (!lv) return 0;
return lv_line_color(lv, meow_theme_get(), dist);
}
int meow_lyric_view_hit_line(meow_lyric_view *lv, int x, int y)
{
if (!lv || !lv->w) return -1;
int rect[4]; meow_widget_rect(lv->w, rect);
int rx = rect[0], ry = rect[1], rw = rect[2], rh = rect[3];
if (x < rx || x >= rx + rw || y < ry || y >= ry + rh) return -1;
int n = meow_lyric_count();
if (n <= 0) return -1;
int maxw = rw - 2 * lv->l.pad; if (maxw < 1) maxw = 1;
int cnt = lv_compute_layout(lv, lv->pos_ms, maxw);
if (cnt <= 0) return -1;
for (int k = 0; k < cnt; k++) {
int yb = lv->vis[k].y, yh = lv->vis[k].bh * lv->l.sub_lh;
if (y >= yb && y < yb + yh) return lv->vis[k].ent;
}
return (y < lv->vis[0].y) ? lv->vis[0].ent : lv->vis[cnt - 1].ent;
}
int meow_lyric_view_visible(meow_lyric_view *lv, int64_t pos_ms,
meow_lyric_view_vis *out, int cap)
{
if (!lv || !lv->w) return 0;
int rect[4]; meow_widget_rect(lv->w, rect);
int maxw = rect[2] - 2 * lv->l.pad; if (maxw < 1) maxw = 1;
int cnt = lv_compute_layout(lv, pos_ms, maxw);
if (out && cnt > 0) {
int n = (cnt < cap) ? cnt : cap;
for (int i = 0; i < n; i++) out[i] = lv->vis[i];
}
return cnt;
}