Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/ui/queue.c
T

385 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.
/* ui/queue.c — 当前实时播放队列页(ACT_QUEUE,§NEW:播放页「队列」钮进入)。
*
* 展示**当前播放队列**(player_manager 的 queue_src —— 歌单滑动窗口 或 搜索单曲结果集),
* 含当前曲高亮、可点某行切到那首播放。数据源与分页复用歌单滑动窗口(REPORT §17):
* - count = appstate_queue_count()(全量:track_ids_count 或 search_song_count,可 5194 首);
* - 行内容 = src2 逐行按 queue_src 取(PLAYLIST→滑窗拷贝,SEARCH→搜索数组),未物化槽显示
* ".../加载中" 占位,视口 ±24 行未物化 → 后台 worker ensure_range 补载(盘缓存优先,批≤NCM_SONG_BATCH);
* - 列表本身由 meow_listview 虚拟化(只画视口内行),不整歌单常驻内存 → 任意长度不卡死。
* - SEARCH 队列行全在内存(≤256),无需 ensure;PLAYLIST 队列走滑窗+ensure(含网络)。
* 当前曲高亮 = 播放页三态钮旁队列页以红色左条 + 红色轻染标记 player_manager_playing_song()
* 所在行(queue idx 与播放队列下标一致,两种 queue_src 通用)。
*
* 布局(无封面头块,纯列表):topbar「播放列表」+ 曲目列表 + 底部 `first-last / N 首` 页脚。
* 左缘返回 = 弹回上一页(播放页,app.c);点击行 → player_play_queue_song(保持当前 queue_src)。
*/
#include "../ncm_app_internal.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
typedef struct { meow_view *view; int active; } queue_ctrl;
static queue_ctrl g_qc;
static meow_widget *g_tb;
static meow_widget *g_list;
static ncm_rect_t r_cont, r_list;
static int g_bg_id, g_draw_id, g_empty_id, g_footer_id;
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
static int g_last_count, g_last_cur;
/* 队列补载(PLAYLIST)后台 worker 参数:目标区间 [lo,hi)(全局下标)。SEARCH 无需补载。 */
static pthread_mutex_t g_qmu = PTHREAD_MUTEX_INITIALIZER;
static pthread_t g_qthr;
static int g_qthr_on;
static volatile int g_ens_lo, g_ens_hi;
static volatile int g_ens_err;
static volatile int g_ens_done; /* worker 已完成的代次(tick 消费) */
static volatile int g_ens_acked;
static void q_drag_reset(void)
{
g_press_row = -1; g_press_delta = 0; g_dragging = 0; g_drag_last_y = 0;
}
/* 队列第 idx 行的 ncm_song_t:按 queue_src 取。返回 0=已物化(out 有效);-1=未物化/占位。
* PLAYLIST:滑窗拷贝(UI 线程不触发网络);SEARCH:搜索数组(内存)。 */
static int q_song_get(int idx, ncm_song_t *out)
{
if (appstate_get_queue() == NCM_QUEUE_SEARCH)
return (appstate_search_song(idx, out) == 0) ? 0 : -1;
return (appstate_song_copy(idx, out) == 0) ? 0 : -1;
}
/* 列表数据源(meow_list2_source):逐行按需填充(不做 IO,§5.5)。未物化槽显示占位行。 */
static int q_src2(int idx, meow_list_item *out, void *ctx)
{
(void)ctx;
int cnt = appstate_queue_count();
if (idx < 0 || idx >= cnt) return 0;
static ncm_song_t s_s;
static char sub[256];
if (q_song_get(idx, &s_s) != 0) {
out->icon_kind = MEOW_LIST_ICON_NONE;
out->title = "...";
out->subtitle = "加载中";
out->right = MEOW_LIST_RIGHT_NONE;
out->on = 0; out->enabled = 1;
return 1;
}
if (s_s.art_name[0] && s_s.album_name[0]) snprintf(sub, sizeof sub, "%s / %s", s_s.art_name, s_s.album_name);
else if (s_s.art_name[0]) snprintf(sub, sizeof sub, "%s", s_s.art_name);
else if (s_s.album_name[0]) snprintf(sub, sizeof sub, "%s", s_s.album_name);
else snprintf(sub, sizeof sub, "-");
out->icon_kind = MEOW_LIST_ICON_NONE;
out->title = s_s.name;
out->subtitle = sub;
out->right = MEOW_LIST_RIGHT_NONE;
out->on = 0; out->enabled = 1;
return 1;
}
/* 行背景(z=5,画在 listview 文本之下):每行 surfaceContainer 圆角卡 + 「当前播放行」红色左条/轻染。 */
static void q_row_bg(void *ctx)
{
(void)ctx;
if (meow_ui_activity_active() != ACT_QUEUE) return;
int cnt = appstate_queue_count();
if (cnt <= 0) return;
const meow_theme *th = meow_theme_get();
const ncm_pal_t *pa = ncm_palette(g_dark);
int item = APP_ROW_H;
int off = meow_listview_scroll(g_list);
int first = off / item, last = (off + r_list.h) / item + 1;
if (last > cnt) last = cnt;
int cur = player_manager_playing_song();
for (int i = first; i < last; i++) {
int ry = ncm_row_card_y(item, i, off, r_list.y);
int th2 = ncm_row_card_h(item);
if (ry < r_list.y || ry + th2 > r_list.y + r_list.h) continue; /* 只画完整落在视口内的行 */
if (i == cur) {
/* 当前播放行:红色左条 + 红色轻染(高亮),底用 red_ctr 浅红 */
ncm_fill_round(r_list.x, ry, r_list.w, th2, 12, pa->red_ctr);
meow_ui_fill_rect(r_list.x + 2, ry + 4, 4, th2 - 8, pa->red);
} else {
ncm_fill_round(r_list.x, ry, r_list.w, th2, 12, th->surfaceContainer);
}
}
}
/* 主绘制(z=60):当前行红色 mini-vinyl 缩略座标(文本由 listview z=10 画)。 */
static void q_draw(void *ctx)
{
(void)ctx;
if (meow_ui_activity_active() != ACT_QUEUE) return;
const meow_theme *th = meow_theme_get();
const ncm_pal_t *pa = ncm_palette(g_dark);
int cnt = appstate_queue_count();
if (cnt <= 0) return;
int item = APP_ROW_H, isz = 24;
int off = meow_listview_scroll(g_list);
int first = off / item, last = (off + r_list.h) / item + 1;
if (last > cnt) last = cnt;
for (int i = first; i < last; i++) {
int ry = ncm_row_card_y(item, i, off, r_list.y);
int th2 = ncm_row_card_h(item);
if (ry < r_list.y || ry + th2 > r_list.y + r_list.h) continue;
int icy = ry + (th2 - isz) / 2, bx = r_list.x + 16;
ncm_fill_round(bx, icy, isz, isz, 6, pa->red);
meow_ui_fill_rect(bx + isz / 2 - 2, icy + isz / 2 - 2, 4, 4, pa->on_red);
if (g_press_row == i) {
meow_ui_fill_rect(r_list.x, ry, 4, th2, pa->red);
meow_ui_fill_rect_alpha(r_list.x, ry, r_list.w, th2, pa->red, 26);
}
/* 擦除 z=10 的 SDK 行分隔线(现落进卡片间空隙):以页底色填空隙带;不越出列表视口底。 */
int gy = ry + th2, gh = NCM_ROW_GAP;
if (gy + gh > r_list.y + r_list.h) gh = r_list.y + r_list.h - gy;
if (gh > 0) meow_ui_fill_rect(r_list.x, gy, r_list.w, gh, th->bg);
}
}
static void q_draw_empty(void *ctx)
{
(void)ctx;
if (meow_ui_activity_active() != ACT_QUEUE) return;
if (appstate_queue_count() != 0) return;
ncm_draw_empty(r_list, "暂无播放队列", "播放一首歌曲后在此查看列表");
}
static void q_draw_footer(void *ctx)
{
(void)ctx;
if (meow_ui_activity_active() != ACT_QUEUE) return;
int total = appstate_queue_count();
if (total <= 0) return;
int first = meow_listview_scroll(g_list) / APP_ROW_H + 1;
int last = first + r_list.h / APP_ROW_H;
if (last > total) last = total;
char foot[96];
snprintf(foot, sizeof foot, "%d-%d / %d 首", first, last, total);
ncm_draw_footer(r_cont, foot);
}
static void q_apply(void)
{
if (g_list) {
int cnt = appstate_queue_count();
meow_listview_set_count(g_list, cnt);
if (cnt != g_last_count) { g_last_count = cnt; meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h); }
}
const meow_theme *th = meow_theme_get();
meow_ui_set_page_bg(th->bg);
}
static void *queue_worker(void *arg)
{
(void)arg;
int lo, hi;
pthread_mutex_lock(&g_qmu);
lo = g_ens_lo; hi = g_ens_hi;
pthread_mutex_unlock(&g_qmu);
int rc = 0;
if (!g_host_mock) {
/* §26:滑窗补载(盘缓存优先,批网格补载);非致命(部分失败留占位行) */
rc = (lo < hi) ? appstate_song_ensure_range(lo, hi) : NCM_OK;
}
pthread_mutex_lock(&g_qmu);
g_ens_err = rc;
g_ens_done++;
pthread_mutex_unlock(&g_qmu);
return NULL;
}
/* 视口驱动滑窗补载(仅 PLAYLIST 队列):视口 ±24 行有未物化槽 → 发 ensure worker。
* SEARCH 队列全在内存,无需补载。 */
static void q_maybe_ensure(void)
{
if (appstate_get_queue() != NCM_QUEUE_PLAYLIST) return;
if (g_qthr_on || g_host_mock) return;
int total = appstate_queue_count();
if (total <= 0) return;
int scroll = meow_listview_scroll(g_list);
int first = scroll / APP_ROW_H;
int last = (scroll + r_list.h) / APP_ROW_H + 1;
int lo = first - 24, hi = last + 24;
if (lo < 0) lo = 0;
if (hi > total) hi = total;
if (lo >= hi) return;
if (appstate_range_loaded(lo, hi)) return; /* 已物化:无操作 */
pthread_mutex_lock(&g_qmu);
g_ens_lo = lo; g_ens_hi = hi;
pthread_mutex_unlock(&g_qmu);
g_qthr_on = 1;
if (ncm_thread_create(&g_qthr, queue_worker, NULL) != 0) {
g_qthr_on = 0; ncm_snack("加载失败");
}
}
void queue_open(void)
{
/* 从播放页「队列」钮进入:push 本页后按当前 queue 刷新(首屏立刻有 count/行)。 */
meow_view_page_push(ACT_QUEUE);
q_apply();
meow_ui_paint_full();
q_maybe_ensure();
}
int queue_is_active(void) { return g_qc.active; }
int queue_on_tap(int x, int y)
{
if (g_tb) {
int ah = meow_topappbar_hit(g_tb, x, y);
if (ah == MEOW_TOPBAR_BACK) { q_drag_reset(); meow_view_page_pop(); return 1; }
}
if (meow_widget_hit(g_list, x, y)) {
int was_drag = (g_press_delta > 10);
int row = (g_press_row >= 0) ? g_press_row : meow_listview_hit(g_list, x, y);
q_drag_reset();
if (row < 0 || was_drag) return 1;
player_play_queue_song(row); /* 点播:保持当前 queue_src,占位行由播放线程物化 */
return 1;
}
return 0;
}
void queue_touch_down(int x, int y)
{
q_drag_reset();
if (appstate_queue_count() <= 0) return;
if (meow_widget_hit(g_list, x, y)) {
int row = meow_listview_hit(g_list, x, y);
if (row >= 0) g_press_row = row;
g_dragging = 1; g_drag_last_y = y;
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
}
}
void queue_touch_move(int x, int y)
{
(void)x;
if (!g_dragging) return;
if (g_press_row >= 0) {
int d = y - g_drag_last_y; if (d < 0) d = -d;
g_press_delta += d;
if (g_press_delta > 10) g_press_row = -1;
}
int dy = y - g_drag_last_y; g_drag_last_y = y;
if (dy == 0) return;
int before = meow_listview_scroll(g_list);
meow_listview_scroll_by(g_list, -dy);
if (meow_listview_scroll(g_list) != before) {
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
q_maybe_ensure();
}
}
void queue_swipe(int dir, int dist)
{
if (dir != MEOW_SWIPE_UP && dir != MEOW_SWIPE_DOWN) return;
if (appstate_queue_count() <= 0) return;
int del = (dir == MEOW_SWIPE_UP) ? dist : -dist;
int before = meow_listview_scroll(g_list);
meow_listview_scroll_by(g_list, del);
if (meow_listview_scroll(g_list) != before) {
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
q_maybe_ensure();
}
}
void queue_tick(uint64_t now)
{
(void)now;
chrome_tick();
if (!g_qc.active) return;
/* worker 补载完成 → 刷新列表(占位行自愈) */
if (g_qthr_on) {
int done, acked;
pthread_mutex_lock(&g_qmu);
done = g_ens_done; acked = g_ens_acked;
pthread_mutex_unlock(&g_qmu);
if (done != acked) {
pthread_mutex_lock(&g_qmu);
g_ens_acked = done;
pthread_mutex_unlock(&g_qmu);
g_qthr_on = 0;
if (g_ens_err != 0) ncm_snack("加载失败");
q_apply();
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
}
}
/* 当前播放曲变化(切歌/顺延)→ 重画高亮行 */
int cur = player_manager_playing_song();
if (cur != g_last_cur) {
g_last_cur = cur;
meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h);
}
}
static void q_page_create(meow_view *v, void *ctx)
{
(void)v;
((queue_ctrl *)ctx)->view = v;
r_cont = ncm_content_rect();
r_list = NR(16, r_cont.y + 8, r_cont.w - 32,
r_cont.y + r_cont.h - APP_FOOTER_H - (r_cont.y + 8));
if (r_list.h < 0) r_list.h = 0;
meow_ui_activity_push(ACT_QUEUE);
g_tb = ncm_topbar_create("播放列表", 1, NULL);
g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0,
q_src2, NULL, MEOW_LIST_SEL_NONE);
meow_listview_set_item_h(g_list, APP_ROW_H);
meow_ui_activity_pop();
meow_widget_set_z(g_list, 10);
g_bg_id = meow_draw_custom_register(5, r_list.x, r_list.y, r_list.w, r_list.h,
q_row_bg, NULL);
g_draw_id = meow_draw_custom_register(60, r_cont.x, r_cont.y, r_cont.w, r_cont.h,
q_draw, NULL);
g_empty_id = meow_draw_custom_register(1, r_list.x, r_list.y, r_list.w, r_list.h,
q_draw_empty, NULL);
g_footer_id = meow_draw_custom_register(1, r_cont.x, r_cont.y + r_cont.h - APP_FOOTER_H,
r_cont.w, APP_FOOTER_H, q_draw_footer, NULL);
q_drag_reset();
}
static void q_page_enter(meow_view *v, void *ctx)
{
(void)v;
((queue_ctrl *)ctx)->active = 1;
const meow_theme *th = meow_theme_get();
meow_ui_set_page_bg(th->bg);
g_last_count = -1; g_last_cur = -2;
q_apply();
meow_ui_paint_full();
q_maybe_ensure();
}
static void q_page_leave(meow_view *v, void *ctx)
{
(void)v;
((queue_ctrl *)ctx)->active = 0;
}
static void q_page_destroy(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
if (g_bg_id) { meow_draw_unregister(g_bg_id); g_bg_id = 0; }
if (g_draw_id) { meow_draw_unregister(g_draw_id); g_draw_id = 0; }
if (g_empty_id) { meow_draw_unregister(g_empty_id); g_empty_id = 0; }
if (g_footer_id){ meow_draw_unregister(g_footer_id);g_footer_id = 0; }
if (g_list) { meow_widget_free(g_list); g_list = NULL; }
if (g_tb) { meow_widget_free(g_tb); g_tb = NULL; }
}
meow_view *queue_view_create(void)
{
static const meow_view_hooks h = {
.on_create = q_page_create,
.on_enter = q_page_enter,
.on_leave = q_page_leave,
.on_destroy = q_page_destroy,
};
return meow_view_create(ACT_QUEUE, MEOW_VIEW_ACTIVITY, &h, &g_qc);
}