feat: M6.10–M6.19 批量收编 + M6.19 推卡验收
- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘) - OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复 - ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640 - 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫) - 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
This commit is contained in:
1 parent
c2ee7626a4
commit
3d70a3dd2c
176 files changed
+15507
-597
No files matched your search
@@ -120,6 +120,27 @@ static void home_draw(void *ctx)
|
||||
meow_ui_text(pc.x + asz + 28, pc.y + 6, g_st.nickname[0] ? g_st.nickname : "我的歌单",
|
||||
th->font_body, th->onSurface);
|
||||
meow_ui_text(pc.x + asz + 28, pc.y + 6 + th->font_body + 4, idtxt, th->font_body, th->onSurfaceVariant);
|
||||
/* §22 会员等级小字:account_vip() 枚举(用户语义 0=无 / 1=畅听会员 / 2=黑胶 VIP /
|
||||
* 3=超级会员);昵称右侧画小红徽章(red_ctr 底 + 红字),无会员则不画。 */
|
||||
{
|
||||
int vip = account_vip();
|
||||
if (vip != NCM_VIP_NONE) {
|
||||
const char *vip_lab = (vip == NCM_VIP_CVIP) ? "畅听会员"
|
||||
: (vip == NCM_VIP_SVIP) ? "黑胶 VIP"
|
||||
: (vip == NCM_VIP_SUPER) ? "超级会员" : "";
|
||||
const char *nm = g_st.nickname[0] ? g_st.nickname : "我的歌单";
|
||||
int nmw = meow_ui_text_width(nm, th->font_body);
|
||||
int bs = th->font_body - 2; /* 徽章字号略小 */
|
||||
if (bs < 16) bs = 16;
|
||||
int bw = meow_ui_text_width(vip_lab, bs) + 12;
|
||||
int bx = pc.x + asz + 28 + nmw + 8;
|
||||
int by = pc.y + 6 + (th->font_body - (bs + 4)) / 2;
|
||||
if (bx + bw <= pc.x + pc.w - 24) { /* 不越卡片右缘(避开“>”) */
|
||||
ncm_fill_round(bx, by, bw, bs + 4, 6, pa->red_ctr);
|
||||
meow_ui_text_center(bx + bw / 2, by + 2, vip_lab, bs, pa->red);
|
||||
}
|
||||
}
|
||||
}
|
||||
meow_ui_text_right(pc.x + pc.w - 16, pc.y + (pc.h - 28) / 2, ">", 28, th->onSurfaceVariant);
|
||||
|
||||
/* 双页签(g_tabs=meow_tabbar widget 建)——红下划线/红选中字叠层。 */
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
* - 封面页:方形大图+圆角(cover.c LRU,无旋转动画)+ 进度条(可点 seek)+ MD3 矢量控制钮
|
||||
* (prev/play-pause/next,ncm_icons.h 32x32 单通道 alpha,参照 sb_icons.h 范式)
|
||||
* + 元信息 + 实际音质档 + 页面指示点;缓冲/错误态沿用(「缓冲中…N%」/中文错误条+重试)。
|
||||
* - 歌词页:LRC 滚动(当前行高亮居中,上下行渐隐)+ 点行跳转;默认=封面模糊置底,
|
||||
* 省电=封面背景+黑色半透明叠加层(设置 ncm.player_bg_mode: default/power_save)。
|
||||
* - 歌词页:LRC 滚动(活动行纯白偏上锚定,上下行逐级灰阶)+ 点行跳转;默认=封面模糊置底
|
||||
* (低透明非纯黑中性叠加层,需求1 加深),省电=封面背景+黑色半透明叠加层
|
||||
* (设置 ncm.player_bg_mode: default/power_save)。
|
||||
* - 日夜间切换:主题配色只映射到半透明叠加层(歌词页 scrim)与 accent(红),
|
||||
* 不动沉浸底(恒 NCM_PLAYER_BG)。
|
||||
*
|
||||
@@ -39,6 +40,35 @@
|
||||
#define PLR_EDGE_W 24 /* 左缘返回热区(与 SDK EDGE_W 一致,防抢返回) */
|
||||
#define PLR_SWIPE_MS 65 /* 中部横滑判页阈值(与 SDK SWIPE_THRESH 一致) */
|
||||
|
||||
/* 歌词页布局(§22 重做批):左边距 / 子行高 / 可见行数上限 / 折行段缓冲。
|
||||
* 超宽歌词折行显示(中西文兼容)+ 活动行高亮(纯白)偏上锚定 + 上下行逐级灰阶,
|
||||
* 顶部元信息与歌词正文同左边距。 */
|
||||
#define LYR_PAD 24 /* 歌词页左边距(顶部元信息与歌词正文一致) */
|
||||
#define LYR_SUB_LH 44 /* 一个歌词条目(含折行)的子行高 */
|
||||
#define LYR_NAME_SZ 28 /* 顶部歌名字号 */
|
||||
#define LYR_ART_SZ 20 /* 顶部歌手字号 */
|
||||
#define LYR_CUR_SZ 28 /* 当前歌词行字号 */
|
||||
#define LYR_OTH_SZ 24 /* 上下行歌词字号 */
|
||||
#define LYR_TL_SZ 20 /* 当前行翻译字号 */
|
||||
#define LYR_MAX_ROWS 9 /* 歌词页可见视觉行数上限(含折行,≤9) */
|
||||
#define LYR_SEG_SZ 256 /* 折行段缓冲 */
|
||||
/* 需求3 活动行锚定:块中心相对屏中线偏上 LYR_ABOVE_OFF px;上方至少 LYR_ABOVE_MIN 行已过歌词。 */
|
||||
#define LYR_ABOVE_OFF 50
|
||||
#define LYR_ABOVE_MIN 2
|
||||
/* 需求1 叠加层加深(非纯黑,保留封面微透出 → 歌词清晰、封面隐约):
|
||||
* default=近黑中性,alpha 132→176、色值略暗;power_save=纯黑 alpha 195→200。 */
|
||||
#define LYR_SCRIM_D 0x10101A /* 默认层暗色 scrim */
|
||||
#define LYR_SCRIM_L 0x201A16 /* 默认层亮色 scrim(暖黑) */
|
||||
#define LYR_SCRIM_PS 0x000000 /* 省电层 scrim(纯黑) */
|
||||
#define LYR_SCRIM_A 176
|
||||
#define LYR_SCRIM_PS_A 200
|
||||
|
||||
/* 需求2 歌词行灰阶:按距活动行行距 0..5 分级(0=活动行纯白,越远越灰;最远可见 0x555555)。
|
||||
* 锚点对照用户需求:±1 行 0xBBBBBB、中深 0x808080、最远 0x555555,逐级单调变灰。 */
|
||||
static const uint32_t lyr_gray_tbl[6] = {
|
||||
0xFFFFFF, 0xBBBBBB, 0x929292, 0x808080, 0x6A6A6A, 0x555555
|
||||
};
|
||||
|
||||
typedef struct { meow_view *view; int active; } player_ctrl;
|
||||
static player_ctrl g_plc;
|
||||
|
||||
@@ -63,8 +93,14 @@ static int g_ctl_y, g_prev_x, g_play_x, g_next_x, g_r_side, g_r_play, g_dots_y;
|
||||
|
||||
/* ---- 歌词页几何 ---- */
|
||||
static int g_lyr_cx; /* 歌词垂直中心(屏中线) */
|
||||
static int g_lyr_lh; /* 行高 */
|
||||
static int g_lyr_meta_y; /* 顶部元信息 y */
|
||||
static int g_lyr_anchor; /* 活动行块中心 y(屏中线偏上,需求3) */
|
||||
static int g_lyr_meta_y; /* 顶部元信息 y(避开状态栏) */
|
||||
/* 歌词布局暂存(每次 plr_draw_lyrics 重建;UI 线程串行,静态复用) */
|
||||
static char s_lyr_seg[LYR_MAX_ROWS][LYR_SEG_SZ]; /* 折行段复用缓冲(一次画一个条目) */
|
||||
static int s_lyr_ent[LYR_MAX_ROWS]; /* 可见条目 → 歌词行下标 */
|
||||
static int s_lyr_rw[LYR_MAX_ROWS]; /* 条目折行段数 */
|
||||
static int s_lyr_sz[LYR_MAX_ROWS]; /* 条目标题字号 */
|
||||
static int s_lyr_y[LYR_MAX_ROWS]; /* 条目块顶 y */
|
||||
|
||||
#define CTL_RR (g_r_side + 8) /* 侧钮命中容差(圆外一点仍可点) */
|
||||
|
||||
@@ -90,6 +126,108 @@ static void fmt_ms(int64_t ms, char *out, size_t sz)
|
||||
snprintf(out, sz, "%d:%02d", sec / 60, sec % 60);
|
||||
}
|
||||
|
||||
/* 折行:把 txt 按「词/字符」折成 ≤max_rows 段,每段 ≤maxw px。中文按字符、英文按词断行;
|
||||
* 宽测量用 meow_ui_text_width(宿主/生产同口径)。段写入 seg[][LYR_SEG_SZ],返回段数(≥1)。 */
|
||||
static int lyr_wrap_lines(const char *txt, int size, int maxw,
|
||||
char seg[][LYR_SEG_SZ], int max_rows)
|
||||
{
|
||||
if (!txt || !txt[0] || max_rows <= 0) { seg[0][0] = 0; return 1; }
|
||||
int n = 0;
|
||||
size_t len = strlen(txt), pos = 0;
|
||||
while (pos < len && n < max_rows) {
|
||||
size_t start = pos;
|
||||
int last_ws = -1; /* 本行最后一个空格(相对 start 的字节偏移) */
|
||||
int overflow = 0;
|
||||
while (pos < len) {
|
||||
unsigned char c = (unsigned char)txt[pos];
|
||||
int ch = (c < 0x80) ? 1 : (c < 0xE0) ? 2 : (c < 0xF0) ? 3 : 4;
|
||||
if (pos + (size_t)ch > len) ch = (int)(len - pos);
|
||||
size_t cand = (pos + (size_t)ch) - start;
|
||||
size_t cp = cand < LYR_SEG_SZ - 1 ? cand : LYR_SEG_SZ - 1;
|
||||
memcpy(seg[n], txt + start, cp); seg[n][cp] = 0;
|
||||
if (meow_ui_text_width(seg[n], size) > maxw && cand > 0) { overflow = 1; break; }
|
||||
if (txt[pos] == ' ' || txt[pos] == '\t') last_ws = (int)(pos - start);
|
||||
pos += (size_t)ch;
|
||||
}
|
||||
if (overflow) {
|
||||
size_t linelen = (last_ws > 0) ? (size_t)last_ws : (pos - start);
|
||||
if (linelen == 0) { /* 单字符超宽(罕见):至少放一个字符防死循环 */
|
||||
unsigned char c = (unsigned char)txt[start];
|
||||
int ch = (c < 0x80) ? 1 : (c < 0xE0) ? 2 : (c < 0xF0) ? 3 : 4;
|
||||
linelen = (size_t)ch;
|
||||
}
|
||||
size_t cp = linelen < LYR_SEG_SZ - 1 ? linelen : LYR_SEG_SZ - 1;
|
||||
memcpy(seg[n], txt + start, cp); seg[n][cp] = 0;
|
||||
pos = start + linelen;
|
||||
if (last_ws > 0) {
|
||||
size_t sp = start + (size_t)last_ws + 1;
|
||||
if (sp > pos) pos = sp; /* 跳过断点后的一个空格 */
|
||||
}
|
||||
n++;
|
||||
} else {
|
||||
size_t cp = (len - start) < LYR_SEG_SZ - 1 ? (len - start) : LYR_SEG_SZ - 1;
|
||||
memcpy(seg[n], txt + start, cp); seg[n][cp] = 0;
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (n == 0) { seg[0][0] = 0; n = 1; }
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 歌词可见条目布局:活动行块中心锚定在 g_lyr_anchor(屏中线偏上,需求3 中间偏上),
|
||||
* 活动行上方保底 ≥LYR_ABOVE_MIN 行已过歌词,下方填满,总视觉行数 ≤ LYR_MAX_ROWS(≤9)。
|
||||
* 结果写 s_lyr_ent/rw/sz/y(s_lyr_y 自上向下排好;当前行块中心 = g_lyr_anchor)。 */
|
||||
static int lyr_layout(int n, int cur, int maxw)
|
||||
{
|
||||
/* 清静态暂存(防帧间残留:上帧条目数 > 本周,绘制循环读到陈旧 s_lyr_* 越界复用) */
|
||||
for (int k = 0; k < LYR_MAX_ROWS; k++) { s_lyr_rw[k] = 0; s_lyr_ent[k] = -1; s_lyr_y[k] = 0; }
|
||||
if (cur < 0 || cur >= n) return 0;
|
||||
|
||||
int rc = lyr_wrap_lines(lyric_line_text(cur), LYR_CUR_SZ, maxw, s_lyr_seg, LYR_MAX_ROWS);
|
||||
if (rc > LYR_MAX_ROWS) rc = LYR_MAX_ROWS;
|
||||
|
||||
int cap = LYR_MAX_ROWS - rc; /* 当前行之外可用的视觉行数 */
|
||||
int above_want = (cap * 2) / 5; /* 偏上锚定:约 40% 行给上方 */
|
||||
if (above_want > cur) above_want = cur; /* 上方行数上限 = 已过行数 */
|
||||
if (above_want < LYR_ABOVE_MIN) above_want = (cur >= LYR_ABOVE_MIN) ? LYR_ABOVE_MIN : cur;
|
||||
if (above_want > cap) above_want = cap; /* 总行数 ≤ LYR_MAX_ROWS */
|
||||
|
||||
/* 1) 上方(cur-1 向上)前插到头部,保持自顶向下 */
|
||||
int a_cnt = 0, usedA = 0;
|
||||
for (int i = cur - 1; i >= 0 && usedA < above_want; i--) {
|
||||
int rr = lyr_wrap_lines(lyric_line_text(i), LYR_OTH_SZ, maxw, s_lyr_seg, LYR_MAX_ROWS);
|
||||
if (usedA + rr > above_want) break;
|
||||
for (int k = a_cnt; k > 0; k--) {
|
||||
s_lyr_ent[k] = s_lyr_ent[k - 1]; s_lyr_rw[k] = s_lyr_rw[k - 1];
|
||||
s_lyr_sz[k] = s_lyr_sz[k - 1];
|
||||
}
|
||||
s_lyr_ent[0] = i; s_lyr_rw[0] = rr; s_lyr_sz[0] = LYR_OTH_SZ; a_cnt++; usedA += rr;
|
||||
}
|
||||
/* 2) 当前行(下标 a_cnt) */
|
||||
s_lyr_ent[a_cnt] = cur; s_lyr_rw[a_cnt] = rc; s_lyr_sz[a_cnt] = LYR_CUR_SZ;
|
||||
/* 3) 下方(cur+1 向下):余量 = cap - actual 上方行数 */
|
||||
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 = lyr_wrap_lines(lyric_line_text(i), LYR_OTH_SZ, maxw, s_lyr_seg, LYR_MAX_ROWS);
|
||||
if (usedB + rr > below_want) break;
|
||||
int k = a_cnt + 1 + b_cnt;
|
||||
s_lyr_ent[k] = i; s_lyr_rw[k] = rr; s_lyr_sz[k] = LYR_OTH_SZ; b_cnt++; usedB += rr;
|
||||
}
|
||||
int cnt = a_cnt + 1 + b_cnt;
|
||||
if (cnt == 0) return 0;
|
||||
|
||||
/* 4) y 位置:活动行块中心 = g_lyr_anchor;上方向上加、下方向下加(块间无缝隙) */
|
||||
s_lyr_y[a_cnt] = g_lyr_anchor - rc * LYR_SUB_LH / 2;
|
||||
for (int k = a_cnt + 1; k < cnt; k++)
|
||||
s_lyr_y[k] = s_lyr_y[k - 1] + s_lyr_rw[k - 1] * LYR_SUB_LH;
|
||||
for (int k = a_cnt - 1; k >= 0; k--)
|
||||
s_lyr_y[k] = s_lyr_y[k + 1] - s_lyr_rw[k] * LYR_SUB_LH;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/* 颜色插值(t=0..255 混向 b)。播放页歌词渐隐/叠加用(无 GPU,逐通道整数混合)。 */
|
||||
static uint32_t mix_c(uint32_t a, uint32_t b, int t)
|
||||
{
|
||||
@@ -177,19 +315,22 @@ static void player_compute_geom(void)
|
||||
g_title_y = g_cover_r.y + g_cover_r.h + 32; /* 400 */
|
||||
g_artist_y = g_title_y + 32; /* 432 */
|
||||
g_qual_y = g_artist_y + 26; /* 458 */
|
||||
g_times_y = g_qual_y + 26; /* 484 */
|
||||
g_bar = NR(48, g_times_y + 20, W - 96, 4); /* 504 (h=4) */
|
||||
g_ctl_y = g_bar.y + 64; /* 568 */
|
||||
/* §22 时间行:放大字号并下移到进度条下方,不与进度条相交。
|
||||
* 布局:进度条(484,h=4) → 时间行(500, font 24) → 控制钮(564)。 */
|
||||
g_bar = NR(48, g_qual_y + 26, W - 96, 4); /* 484 (h=4) */
|
||||
g_times_y = g_bar.y + g_bar.h + 12; /* 500 */
|
||||
g_ctl_y = g_times_y + 64; /* 564 */
|
||||
g_prev_x = W / 2 - 92; g_play_x = W / 2; g_next_x = W / 2 + 92;
|
||||
g_r_side = 24; g_r_play = 34;
|
||||
g_dots_y = g_ctl_y + 62; /* 630 */
|
||||
g_dots_y = g_ctl_y + 62; /* 626 */
|
||||
|
||||
/* 动态带 = 封面下方(元信息/进度/控制/指示点),与封面静态带不重叠。 */
|
||||
g_dyn_r = NR(0, g_cover_r.y + g_cover_r.h, W, H - (g_cover_r.y + g_cover_r.h));
|
||||
|
||||
g_lyr_cx = H / 2; /* 360 */
|
||||
g_lyr_lh = 30;
|
||||
g_lyr_meta_y = 22;
|
||||
/* 歌词页几何:中心线 / 活动行块中心(屏中线偏上,需求3)/ 顶部元信息 y。 */
|
||||
g_lyr_cx = H / 2; /* 360 */
|
||||
g_lyr_anchor = g_lyr_cx - LYR_ABOVE_OFF; /* 310 活动行偏上锚定 */
|
||||
g_lyr_meta_y = ncm_statusbar() + 20;
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
@@ -280,8 +421,9 @@ static void plr_draw_cover_dyn(void *ctx)
|
||||
}
|
||||
meow_ui_text_center(W / 2, g_times_y - 8, bbuf, 20, PLR_TEXT_DIM);
|
||||
} else {
|
||||
meow_ui_text(48, g_times_y, lp, 20, PLR_TEXT_DIM);
|
||||
meow_ui_text_right(W - 48, g_times_y, rp, 20, PLR_TEXT_DIM);
|
||||
/* §22 时间字号放大(20→24)且下移到进度条下方,不再与进度条相交 */
|
||||
meow_ui_text(48, g_times_y, lp, 24, PLR_TEXT_DIM);
|
||||
meow_ui_text_right(W - 48, g_times_y, rp, 24, PLR_TEXT_DIM);
|
||||
}
|
||||
meow_ui_fill_rect(g_bar.x, g_bar.y, g_bar.w, g_bar.h, PLR_TRACK_BG);
|
||||
meow_ui_fill_rect(g_bar.x, g_bar.y, (int)((int64_t)g_bar.w * frac / 1000), g_bar.h, pa->red);
|
||||
@@ -297,73 +439,75 @@ static void plr_draw_cover_dyn(void *ctx)
|
||||
draw_page_dots(g_dots_y);
|
||||
}
|
||||
|
||||
/* 歌词页(整屏带状物):封面模糊置底 + scrim + 顶部元信息 + 滚动歌词(当前行高亮居中 +
|
||||
* 上下行渐隐)+ 指示点。整屏在 进入/切歌/封面变化/换背景模式/换行 时重画;pos tick 不画。 */
|
||||
/* 歌词页(整屏带状物):封面模糊置底 + 加深 scrim + 顶部元信息 + 滚动歌词(活动行纯白偏上锚定 +
|
||||
* 上下行逐级灰阶)+ 指示点。整屏在 进入/切歌/封面变化/换背景模式/换行 时重画;pos tick 不画。 */
|
||||
static void plr_draw_lyrics(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
if (meow_ui_activity_active() != ACT_PLAYER || g_ppage != 1) return;
|
||||
const meow_theme *th = meow_theme_get();
|
||||
const ncm_pal_t *pa = ncm_palette(g_dark);
|
||||
int W = g_W, H = g_H;
|
||||
|
||||
/* 底:封面等比居中放大(contain:等比缩放到屏内最大、居中;四周主题底),无封面 → 沉浸底。
|
||||
* 不拉伸铺满(方图硬拉 480×720 变形丑,2026-09-02 用户报)。
|
||||
* ⚠️ cover_get() 持锁返回,勿在绘前 return(防 g_cm 泄漏 → 下次 paint 自锁死,§18)。 */
|
||||
/* 底:封面铺满全屏(cover-fit,四周无黑边;无 GPU 高斯模糊,用低透明中性叠层近似「模糊底」),
|
||||
* 无封面 → 主题中性底(非纯黑)。⚠️ cover_get() 持锁返回,勿在绘前 return(防 g_cm 泄漏,
|
||||
* 下次 paint 自锁死,§18)。§22:default=封面+半透明中性叠层;power_save=封面+黑色更重叠层。 */
|
||||
meow_img *cov = cover_get();
|
||||
meow_ui_fill_rect(0, 0, W, H, NCM_PLAYER_BG);
|
||||
meow_ui_fill_rect(0, 0, W, H, 0x1A1A20); /* 无封面兜底主题底(中性深灰,非纯黑) */
|
||||
if (cov) {
|
||||
int cw = meow_img_w(cov), ch = meow_img_h(cov);
|
||||
if (cw > 0 && ch > 0) {
|
||||
int dw = W, dh = (int)((long)ch * W / cw);
|
||||
if (dh > H) { dh = H; dw = (int)((long)cw * H / ch); }
|
||||
meow_ui_img_scaled((W - dw) / 2, (H - dh) / 2, dw, dh, cov);
|
||||
long dw = W, dh = (long)ch * W / cw; /* cover-fit:铺满宽度,高按等比超出裁剪 */
|
||||
if (dh < H) { dh = H; dw = (long)cw * H / ch; }
|
||||
meow_ui_img_scaled((W - (int)dw) / 2, (H - (int)dh) / 2, (int)dw, (int)dh, cov);
|
||||
}
|
||||
}
|
||||
cover_put();
|
||||
/* 半透明叠加层:default=主题 scrim(日/夜跟随主题);power_save=黑色更重(§22)。 */
|
||||
/* 半透明叠加层:default=主题中性(近黑但不纯黑,封面色彩微透出 → 模糊底观感);
|
||||
* power_save=黑色更重(§22 保留原「封面背景+黑叠加」语义)。需求1 加深:alpha/色值加暗,
|
||||
* 但仍保留「封面隐约」而非纯黑整体吞掉。 */
|
||||
if (g_st.player_bg_mode == NCM_BG_POWER_SAVE)
|
||||
meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 195);
|
||||
meow_ui_fill_rect_alpha(0, 0, W, H, LYR_SCRIM_PS, LYR_SCRIM_PS_A);
|
||||
else
|
||||
meow_ui_fill_rect_alpha(0, 0, W, H, th->scrim, 150);
|
||||
meow_ui_fill_rect_alpha(0, 0, W, H, g_dark ? LYR_SCRIM_D : LYR_SCRIM_L, LYR_SCRIM_A);
|
||||
|
||||
/* 顶部元信息(title · artist,Apple Music 式顶部小字) */
|
||||
/* 顶部元信息(两行居左):第一行歌名(大字)、第二行歌手名(小字,多歌手保留已拼串)。
|
||||
* 与歌词正文同左边距 LYR_PAD;下移避开状态栏(g_lyr_meta_y = statusbar + 20)。 */
|
||||
ncm_song_t sm;
|
||||
if (cur_song_meta(&sm) == 0) {
|
||||
char mb[300];
|
||||
snprintf(mb, sizeof mb, "%s / %s", sm.name, sm.art_name);
|
||||
meow_ui_text_center(W / 2, g_lyr_meta_y, ncm_text_fit(mb, W - 48, 18), 18, PLR_TEXT);
|
||||
meow_ui_text(LYR_PAD, g_lyr_meta_y,
|
||||
ncm_text_fit(sm.name, W - 2 * LYR_PAD, LYR_NAME_SZ), LYR_NAME_SZ, PLR_TEXT);
|
||||
meow_ui_text(LYR_PAD, g_lyr_meta_y + LYR_NAME_SZ + 6,
|
||||
ncm_text_fit(sm.art_name, W - 2 * LYR_PAD, LYR_ART_SZ), LYR_ART_SZ, PLR_TEXT_DIM);
|
||||
}
|
||||
|
||||
/* 歌词:当前行居中高亮;上下行渐隐(距中心越远越接近底色)。 */
|
||||
/* 歌词:活动行纯白居中(含折行,块中心偏上锚定);上下行按行距逐级灰阶;≤LYR_MAX_ROWS。 */
|
||||
int n = lyric_count();
|
||||
int64_t ps = player_manager_pos_ms();
|
||||
int cur = (n > 0) ? lyric_line_at(ps) : -1;
|
||||
int cy = g_lyr_cx;
|
||||
int lo = g_lyr_meta_y + 44, hi = g_dots_y - 6; /* 歌词窗口(避顶部元信息/底部指示点) */
|
||||
for (int i = cur - 7; i <= cur + 6; i++) {
|
||||
if (i < 0 || i >= n) continue;
|
||||
int yy = cy + (i - cur) * g_lyr_lh;
|
||||
if (yy < lo || yy + g_lyr_lh > hi) continue;
|
||||
int maxw = W - 2 * LYR_PAD;
|
||||
int cnt = lyr_layout(n, cur, maxw);
|
||||
/* 渐隐目标色 = 歌词页背底层(与 default/power_save 叠层一致),供翻译行融入背景 */
|
||||
uint32_t bgcol = (g_st.player_bg_mode == NCM_BG_POWER_SAVE) ? LYR_SCRIM_PS
|
||||
: (g_dark ? LYR_SCRIM_D : LYR_SCRIM_L);
|
||||
for (int k = 0; k < cnt; k++) {
|
||||
int i = s_lyr_ent[k], rows = s_lyr_rw[k], sz = s_lyr_sz[k], y = s_lyr_y[k];
|
||||
int is_cur = (i == cur);
|
||||
int dist = (i - cur) < 0 ? (cur - i) : (i - cur);
|
||||
int fade = dist * 42; /* 0..255,越远越淡 */
|
||||
if (fade > 214) fade = 214; /* 远行仍隐约可见,不全隐 */
|
||||
uint32_t base = is_cur ? pa->red_lit : PLR_TEXT;
|
||||
uint32_t bgcol = (g_st.player_bg_mode == NCM_BG_POWER_SAVE) ? 0x000000 : th->scrim;
|
||||
uint32_t col = mix_c(base, bgcol, fade);
|
||||
int size = is_cur ? 24 : 20;
|
||||
meow_ui_text_center(W / 2, yy, ncm_text_fit(lyric_line_text(i), W - 24, size), size, col);
|
||||
int dist = is_cur ? 0 : ((i - cur) < 0 ? (cur - i) : (i - cur)); /* 距活动行行距 */
|
||||
uint32_t col = lyr_gray_tbl[dist > 5 ? 5 : dist]; /* 0=纯白,越远越灰 */
|
||||
int rr = lyr_wrap_lines(lyric_line_text(i), sz, maxw, s_lyr_seg, rows);
|
||||
if (rr > rows) rr = rows;
|
||||
for (int r = 0; r < rr; r++)
|
||||
meow_ui_text(LYR_PAD, y + r * LYR_SUB_LH, s_lyr_seg[r], sz, col);
|
||||
if (is_cur) {
|
||||
const char *tl = lyric_line_tl(i);
|
||||
if (tl && tl[0])
|
||||
meow_ui_text_center(W / 2, yy + 28, ncm_text_fit(tl, W - 24, 18), 18,
|
||||
mix_c(pa->red_lit, bgcol, 120));
|
||||
meow_ui_text(LYR_PAD, y + rows * LYR_SUB_LH + 2,
|
||||
ncm_text_fit(tl, W - 2 * LYR_PAD, LYR_TL_SZ), LYR_TL_SZ,
|
||||
mix_c(0xFFFFFF, bgcol, 120));
|
||||
}
|
||||
}
|
||||
if (n == 0) {
|
||||
meow_ui_text_center(W / 2, cy - 10,
|
||||
lyric_state() == 2 ? "暂无歌词" : "歌词加载中…", 20, PLR_TEXT_DIM);
|
||||
meow_ui_text(LYR_PAD, g_lyr_cx - 10,
|
||||
lyric_state() == 2 ? "暂无歌词" : "歌词加载中…", 20, PLR_TEXT_DIM);
|
||||
}
|
||||
|
||||
draw_page_dots(g_dots_y);
|
||||
@@ -466,21 +610,27 @@ static void player_reset_touch(void)
|
||||
g_touch_on = 0; g_drag_latched = 0; g_drag_dx = 0;
|
||||
}
|
||||
|
||||
/* 歌词页:点行 seek(把 tap y 归到最近一行的下标)。 */
|
||||
/* 歌词页:点行 seek(用与绘制一致的可见条目布局把 tap y 命中最临近行;
|
||||
* 活动行已偏上锚定,非屏中线,故按 s_lyr_y/rw 逐块扫描而非 cx 偏移)。 */
|
||||
static int lyrics_line_tap(int y)
|
||||
{
|
||||
int n = lyric_count();
|
||||
if (n <= 0) return -1;
|
||||
/* 仅当 tap 落在歌词窗口内才响应(顶部元信息/底部指示点区不误触)。
|
||||
* 元信息块 = 歌名(28)+6+歌手(20)+边距 → g_lyr_meta_y+66。 */
|
||||
if (y < g_lyr_meta_y + 66 || y > g_dots_y - 6) return -1;
|
||||
int64_t ps = player_manager_pos_ms();
|
||||
int cur = lyric_line_at(ps);
|
||||
int off = y - g_lyr_cx;
|
||||
int dioff = (off >= 0) ? (off + g_lyr_lh / 2) / g_lyr_lh : (off - g_lyr_lh / 2) / g_lyr_lh;
|
||||
int i = cur + dioff;
|
||||
if (i < 0) i = 0;
|
||||
if (i >= n) i = n - 1;
|
||||
/* 仅当 tap 落在歌词窗口内才响应(顶部元信息/底部指示点区不误触)。 */
|
||||
if (y < g_lyr_meta_y + 44 || y > g_dots_y - 6) return -1;
|
||||
return i;
|
||||
int maxw = g_W - 2 * LYR_PAD;
|
||||
int cnt = lyr_layout(n, cur, maxw);
|
||||
if (cnt <= 0) return -1;
|
||||
for (int k = 0; k < cnt; k++) {
|
||||
int yb = s_lyr_y[k];
|
||||
int yh = s_lyr_rw[k] * LYR_SUB_LH;
|
||||
if (y >= yb && y < yb + yh) return s_lyr_ent[k];
|
||||
}
|
||||
/* 落在可见块之外 → 就近取整(顶部元信息/底部指示点已在上面排除) */
|
||||
return (y < s_lyr_y[0]) ? s_lyr_ent[0] : s_lyr_ent[cnt - 1];
|
||||
}
|
||||
|
||||
static int in_circle(int cx, int cy, int r, int x, int y)
|
||||
|
||||
@@ -268,6 +268,17 @@ static void *playlist_worker(void *arg)
|
||||
/* 首屏:快照式加载(detail 全量 trackIds + 物化首段)。ctx 校验代次/目标,过期自动丢弃。 */
|
||||
if (tidx < 0) rc = NCM_ERR_ARG;
|
||||
else rc = appstate_load_playlist_songs_ctx(&tpl, tsearch, tgen, tidx, 0, NCM_SONG_BATCH);
|
||||
/* §22 整张歌单逐渐物化:首屏成功后把「能装进滑动窗口」的歌单一次性补填入窗
|
||||
* (≤NCM_SONG_WINDOW 的歌单整张物化,末叶行不再恒「.../加载中」——真机反馈
|
||||
* 「曲目稍多时行一直加载中,点开播放这首才回填」);超窗口的歌单仍按视口滑动补载
|
||||
* (内存纪律:窗口 ≤256 首)。窗口基址=0 覆盖 [0,min(track,WINDOW)),不滑窗不挤占视口。 */
|
||||
if (rc == NCM_OK) {
|
||||
int full = (g_st.track_ids_count < NCM_SONG_WINDOW) ? g_st.track_ids_count : NCM_SONG_WINDOW;
|
||||
if (full > NCM_SONG_BATCH) {
|
||||
int r2 = appstate_song_ensure_range(0, full);
|
||||
if (r2 != NCM_OK && rc == NCM_OK) rc = r2; /* 补填失败非致命:已物化槽保留 */
|
||||
}
|
||||
}
|
||||
/* REPORT §30(真机四连症状①):首屏结果打日志——tidx/track_ids_count/物化数/rc,
|
||||
* 供「162 首歌单所有条目占位 加载中」定位(首屏 song_detail 失败/部分失败=非致命
|
||||
* g_ens_err=rc,窗口空 → 行占位;g_st.song_count 为诊断竞态读,仅日志用途)。 */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* ui/settings.c — 设置页(ACT_SETTINGS)。
|
||||
*
|
||||
* 行项:音质(§20 八档:标准/极高 HQ/无损 SQ/Hi-Res/高清臻音/臻音全景声/超清母带/沉浸环绕声,
|
||||
* 默认极高 HQ;每档标注规格+会员门槛 CVIP/SVIP;超权限档允许选定,播放时钳顶+逐级降级,
|
||||
* 默认极高 HQ;每档标注规格+会员门槛(畅听会员/黑胶 VIP/超级会员);超权限档允许选定,播放时钳顶+逐级降级,
|
||||
* 见 player_manager.c song_local_file)+ 缓存上限(默认 10GB,上限 100GB,支持预设与
|
||||
* 「自定义」数字输入)+ 清理缓存(显示当前占用)+ 退出登录 + 关于/版本。
|
||||
* 设置持久化到 MeowData/dev.player.ncm/data(meow_config_*,与 core
|
||||
@@ -80,9 +80,12 @@ static void settings_draw(void *ctx)
|
||||
rows[4].title = "退出登录"; rows[4].red = 1;
|
||||
rows[5].title = "关于网易云"; rows[5].red = 0;
|
||||
{
|
||||
/* 当前档 + 会员门槛标注;超权限(未解锁)时提示将降级(不硬拒,§20) */
|
||||
/* 当前档 + 会员门槛标注;超权限(未解锁)时提示将降级(不硬拒,§20)。
|
||||
* 门槛串按用户语义对齐:1=畅听会员 / 2=黑胶 VIP / 3=超级会员(门槛恒 ≤2,SUPER 兜底)。 */
|
||||
int gate = ncm_api_level_gate(g_st.level);
|
||||
const char *gate_s = (gate == NCM_VIP_SVIP) ? "SVIP" : (gate == NCM_VIP_CVIP) ? "CVIP" : "";
|
||||
const char *gate_s = (gate == NCM_VIP_SVIP) ? "黑胶 VIP"
|
||||
: (gate == NCM_VIP_CVIP) ? "畅听会员"
|
||||
: (gate == NCM_VIP_SUPER) ? "超级会员" : "";
|
||||
int locked = (account_vip() < gate);
|
||||
if (gate_s[0])
|
||||
snprintf(rows[0].sub, sizeof rows[0].sub, "当前: %s (%s) / 需 %s%s",
|
||||
@@ -112,7 +115,7 @@ static void settings_draw(void *ctx)
|
||||
|
||||
/* 品牌红页脚小字。 */
|
||||
int ny = y + SETTINGS_ROWS * (g_row_h + 8) + 4;
|
||||
meow_ui_text(x, ny, "无损/Hi-Res 需 CVIP ・ 臻音/母带需 SVIP,不支持时自动降级", 20, th->onSurfaceVariant);
|
||||
meow_ui_text(x, ny, "无损/Hi-Res 需畅听会员 ・ 臻音/母带需黑胶 VIP,不支持时自动降级", 20, th->onSurfaceVariant);
|
||||
meow_ui_text(x, ny + 24, "网易云音乐 / MeowOS 移植版", 20, pa->red);
|
||||
}
|
||||
|
||||
@@ -170,9 +173,11 @@ int settings_on_tap(int x, int y)
|
||||
int lv = g_ui_levels[i];
|
||||
int gate = ncm_api_level_gate(lv);
|
||||
char lbl[96];
|
||||
/* 每档标注 规格+会员门槛;未解锁档标注「需 CVIP/SVIP」(sheet 无置灰能力,
|
||||
/* 每档标注 规格+会员门槛;未解锁档标注「需 畅听会员/黑胶 VIP」(sheet 无置灰能力,
|
||||
* 以文字标注代替——允许选定,实际播放逐级降级到可用档) */
|
||||
const char *gate_s = (gate == NCM_VIP_SVIP) ? "SVIP" : (gate == NCM_VIP_CVIP) ? "CVIP" : NULL;
|
||||
const char *gate_s = (gate == NCM_VIP_SVIP) ? "黑胶 VIP"
|
||||
: (gate == NCM_VIP_CVIP) ? "畅听会员"
|
||||
: (gate == NCM_VIP_SUPER) ? "超级会员" : NULL;
|
||||
if (gate_s)
|
||||
snprintf(lbl, sizeof lbl, "%s (%s) / %s%s", ncm_level_name(lv),
|
||||
ncm_level_spec(lv), gate_s, (vip < gate) ? "/未解锁" : "");
|
||||
|
||||
Reference in new issue
Block a user