KT25-1015_AC695x_SDK310/apps/ui/ui_music.c
2026-03-19 05:52:10 +08:00

660 lines
20 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.

#include "ui_music.h"
#include "img_bg.h"
#include "kt_ui.h"
#include "kt.h"
#include "key_event_deal.h"
#include "lvgl.h"
#include "drv_st7789.h"
#include "system/fs/fs.h"
#include "dev_manager/dev_manager.h"
#include "system/app_msg.h"
#include "app_config.h"
#include "music_player.h"
#include "eye_led.h"
#define MUSIC_SCAN_PARAM "-tMP1MP2MP3 -sn -r"
#define FILE_NAME_BUF_SIZE 128
#define SHORT_NAME_LEN 13 /* 8.3 + null */
#define LED_MODE_COUNT 8
#define LIST_TOP 36
#define LIST_ROWS 6
#define ROW_GAP 1
#define LIST_AVAIL_H (LCD_H - 50 - LIST_TOP) /* BAR_Y - LIST_TOP = 154 */
#define ROW_H ((LIST_AVAIL_H - (LIST_ROWS - 1) * ROW_GAP) / LIST_ROWS) /* 每行约 24px保证6行容纳 */
#define LIST_H (LIST_ROWS * ROW_H + (LIST_ROWS - 1) * ROW_GAP)
#define LIST_W ((LCD_W - 16) / 2 - 4) /* 左右各一半,中间留缝 */
#define BAR_W (LCD_W - 48)
#define BAR_X 24
#define BAR_H 14
#define BAR_Y (LCD_H - 45)
#define TIME_Y (BAR_Y + BAR_H + 7)
static lv_obj_t *file_list = NULL;
static lv_obj_t *bar_progress = NULL;
static lv_obj_t *label_curr_time = NULL;
static lv_obj_t *label_total_time = NULL;
static lv_obj_t *led_list = NULL;
static struct vfscan *file_scan_fs = NULL;
#if defined(TCFG_LFN_EN) && TCFG_LFN_EN
static u8 lfn_buf[512];
#endif
/* 设置模式:长按 PP 进入;先选 list红框再 ENTER 进入行选择 */
static u8 setting_flag = 0;
#define FOCUS_LIST_FILE 0
#define FOCUS_LIST_LED 1
#define ROW_SELECT_LIST 0 /* 选择列表(红框) */
#define ROW_SELECT_ROW 1 /* 选择行(行内循环) */
static u8 row_select_mode = ROW_SELECT_LIST; /* 0=list 选择1=行选择 */
static u8 focus_list_id = FOCUS_LIST_FILE; /* 当前选中的列表 */
static int focus_idx = 0; /* 行选择时当前列表内的索引 */
void ui_music_update_led_mode_item(u8 mode);
static int get_file_count(void)
{
int n = 0;
if (file_list)
{
uint32_t cnt = lv_obj_get_child_cnt(file_list);
for (uint32_t i = 0; i < cnt; i++)
{
lv_obj_t *c = lv_obj_get_child(file_list, i);
if (lv_obj_get_user_data(c) != NULL)
n++;
}
}
return n;
}
static void clear_focus_style(lv_obj_t *btn)
{
lv_obj_set_style_bg_opa(btn, LV_OPA_TRANSP, 0);
}
static void set_focus_style(lv_obj_t *btn)
{
lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0);
lv_obj_set_style_bg_color(btn, lv_color_hex(0x333333), 0); /* 反显:深色底 */
lv_obj_scroll_to_view(btn, LV_ANIM_OFF); /* 滚动到可见 */
}
#define BORDER_NORMAL 0xCCCCCC
#define BORDER_SELECT 0xFF0000
/* 更新 list 外框颜色list 选择模式下,选中项红框 */
static void update_list_border_style(void)
{
if (!file_list || !led_list)
return;
lv_color_t file_c = (row_select_mode == ROW_SELECT_LIST && focus_list_id == FOCUS_LIST_FILE)
? lv_color_hex(BORDER_SELECT) : lv_color_hex(BORDER_NORMAL);
lv_color_t led_c = (row_select_mode == ROW_SELECT_LIST && focus_list_id == FOCUS_LIST_LED)
? lv_color_hex(BORDER_SELECT) : lv_color_hex(BORDER_NORMAL);
lv_obj_set_style_border_color(file_list, file_c, 0);
lv_obj_set_style_border_color(led_list, led_c, 0);
}
/* 根据 focus_list_id + focus_idx 更新焦点样式(行选择模式才显示行焦点) */
static void update_focus_style(void)
{
update_list_border_style();
if (row_select_mode != ROW_SELECT_ROW)
{
/* list 选择模式:清除所有行焦点 */
if (file_list)
{
uint32_t cnt = lv_obj_get_child_cnt(file_list);
for (uint32_t i = 0; i < cnt; i++)
clear_focus_style(lv_obj_get_child(file_list, i));
}
if (led_list)
{
for (int i = 0; i < LED_MODE_COUNT; i++)
{
lv_obj_t *c = lv_obj_get_child(led_list, i);
if (c)
clear_focus_style(c);
}
}
return;
}
/* 行选择模式:修正 focus_idx标亮当前行 */
int file_cnt = get_file_count();
if (focus_list_id == FOCUS_LIST_FILE)
{
if (file_cnt > 0 && focus_idx >= file_cnt)
focus_idx = 0;
}
else
{
if (focus_idx >= LED_MODE_COUNT)
focus_idx = 0;
}
int file_i = 0;
if (file_list)
{
uint32_t cnt = lv_obj_get_child_cnt(file_list);
for (uint32_t i = 0; i < cnt; i++)
{
lv_obj_t *c = lv_obj_get_child(file_list, i);
if (lv_obj_get_user_data(c) != NULL)
{
if (focus_list_id == FOCUS_LIST_FILE && focus_idx == file_i)
set_focus_style(c);
else
clear_focus_style(c);
file_i++;
}
}
}
if (led_list)
{
for (int i = 0; i < LED_MODE_COUNT; i++)
{
lv_obj_t *c = lv_obj_get_child(led_list, i);
if (c)
{
if (focus_list_id == FOCUS_LIST_LED && focus_idx == i)
set_focus_style(c);
else
clear_focus_style(c);
}
}
}
}
/* NEXT/PREV: list 选择模式下切换 list行选择模式下仅在当前 list 内循环 */
static void focus_next(void)
{
if (row_select_mode == ROW_SELECT_LIST)
{
focus_list_id = (focus_list_id == FOCUS_LIST_FILE) ? FOCUS_LIST_LED : FOCUS_LIST_FILE;
}
else
{
if (focus_list_id == FOCUS_LIST_FILE)
{
int n = get_file_count();
if (n <= 0)
return;
focus_idx = (focus_idx + 1) % n;
}
else
focus_idx = (focus_idx + 1) % LED_MODE_COUNT;
}
update_focus_style();
}
static void focus_prev(void)
{
if (row_select_mode == ROW_SELECT_LIST)
{
focus_list_id = (focus_list_id == FOCUS_LIST_FILE) ? FOCUS_LIST_LED : FOCUS_LIST_FILE;
}
else
{
if (focus_list_id == FOCUS_LIST_FILE)
{
int n = get_file_count();
if (n <= 0)
return;
focus_idx = (focus_idx - 1 + n) % n;
}
else
focus_idx = (focus_idx - 1 + LED_MODE_COUNT) % LED_MODE_COUNT;
}
update_focus_style();
}
static void focus_exit_setting(void);
/* 确认键list 选择时进入行选择;行选择时确认并退出设置 */
static void focus_confirm(void)
{
if (row_select_mode == ROW_SELECT_LIST)
{
row_select_mode = ROW_SELECT_ROW;
focus_idx = 0;
if (focus_list_id == FOCUS_LIST_FILE)
{
int n = get_file_count();
if (n <= 0)
row_select_mode = ROW_SELECT_LIST; /* 空列表不进入 */
}
}
else
{
/* 行选择确认file 播放选中曲目LED 仅退出 */
if (focus_list_id == FOCUS_LIST_FILE)
{
int file_i = 0;
uint32_t cnt = file_list ? lv_obj_get_child_cnt(file_list) : 0;
for (uint32_t i = 0; i < cnt; i++)
{
lv_obj_t *c = lv_obj_get_child(file_list, i);
if (lv_obj_get_user_data(c) != NULL)
{
if (file_i == focus_idx)
{
u32 sclust = (u32)(uintptr_t)lv_obj_get_user_data(c);
app_task_put_key_msg(KEY_MUSIC_PLAYE_BY_DEV_SCLUST, (int)sclust);
break;
}
file_i++;
}
}
}
else
{
eye_led_stop();
eye_led_set_mode((u8)(focus_idx + 1));
eye_led_start();
ui_music_update_led_mode_item((u8)(focus_idx + 1));
}
setting_flag = 0;
focus_exit_setting();
return;
}
update_focus_style();
}
static void focus_enter_setting(void)
{
row_select_mode = ROW_SELECT_LIST;
focus_list_id = FOCUS_LIST_FILE; /* 默认选中 file_list */
focus_idx = 0;
int file_cnt = get_file_count();
if (file_cnt <= 0)
focus_list_id = FOCUS_LIST_LED;
update_focus_style();
}
static void focus_exit_setting(void)
{
row_select_mode = ROW_SELECT_LIST;
if (file_list)
lv_obj_set_style_border_color(file_list, lv_color_hex(BORDER_NORMAL), 0);
if (led_list)
lv_obj_set_style_border_color(led_list, lv_color_hex(BORDER_NORMAL), 0);
if (file_list)
{
uint32_t cnt = lv_obj_get_child_cnt(file_list);
for (uint32_t i = 0; i < cnt; i++)
clear_focus_style(lv_obj_get_child(file_list, i));
}
if (led_list)
{
for (int i = 0; i < LED_MODE_COUNT; i++)
{
lv_obj_t *c = lv_obj_get_child(led_list, i);
if (c)
clear_focus_style(c);
}
}
}
static void file_btn_click_cb(lv_event_t *e)
{
lv_obj_t *btn = lv_event_get_target(e);
u32 sclust = (u32)(uintptr_t)lv_obj_get_user_data(btn);
app_task_put_key_msg(KEY_MUSIC_PLAYE_BY_DEV_SCLUST, (int)sclust);
}
static void refresh_file_list_content(lv_obj_t *list)
{
if (!list)
return;
lv_obj_t *child;
while ((child = lv_obj_get_child(list, 0)) != NULL)
{
lv_obj_del(child);
}
void *dev = dev_manager_find_active(1);
if (!dev)
{
lv_list_add_text(list, "No storage device");
return;
}
char *path = dev_manager_get_root_path(dev);
if (!path)
{
lv_list_add_text(list, "No storage device");
return;
}
if (file_scan_fs)
{
fscan_release(file_scan_fs);
file_scan_fs = NULL;
}
file_scan_fs = fscan(path, MUSIC_SCAN_PARAM, 9);
#if defined(TCFG_LFN_EN) && TCFG_LFN_EN
if (file_scan_fs)
{
fset_lfn_buf(file_scan_fs, lfn_buf);
}
#endif
if (!file_scan_fs || file_scan_fs->file_number == 0)
{
lv_list_add_text(list, "No music files");
return;
}
u8 *name_buf = (u8 *)lv_mem_alloc(FILE_NAME_BUF_SIZE);
if (!name_buf)
{
lv_list_add_text(list, "Out of memory");
return;
}
int add_cnt = 0;
for (u32 i = 0; i < file_scan_fs->file_number; i++)
{
FILE *f = fselect(file_scan_fs, FSEL_BY_NUMBER, i + 1);
if (!f)
continue;
struct vfs_attr attr;
fget_attrs(f, &attr);
if (attr.attr & F_ATTR_DIR)
{
fclose(f);
continue;
}
int len = fget_name(f, name_buf, SHORT_NAME_LEN);
fclose(f);
if (len <= 0)
continue;
if (len >= SHORT_NAME_LEN)
len = SHORT_NAME_LEN - 1;
name_buf[len] = '\0';
int need_fallback = 0;
if (len >= 2 && name_buf[0] == '\\' && name_buf[1] == 'U')
{
need_fallback = 1;
}
else
{
for (int j = 0; j < len; j++)
{
if ((u8)name_buf[j] < 0x20 || (u8)name_buf[j] > 0x7E)
{
need_fallback = 1;
break;
}
}
}
const char *disp_text;
char fallback[16];
if (need_fallback)
{
int n = add_cnt + 1;
memcpy(fallback, "Music ", 6);
if (n >= 10)
{
fallback[6] = '0' + n / 10;
fallback[7] = '0' + n % 10;
fallback[8] = '\0';
}
else
{
fallback[6] = '0' + n;
fallback[7] = '\0';
}
disp_text = fallback;
}
else
{
disp_text = (const char *)name_buf;
}
lv_obj_t *btn = lv_list_add_btn(list, LV_SYMBOL_AUDIO, disp_text);
lv_obj_set_height(btn, ROW_H);
lv_obj_set_style_pad_all(btn, 2, 0); /* 压缩按钮内边距适配12号字体 */
lv_obj_set_style_bg_opa(btn, LV_OPA_TRANSP, 0); /* 行背景透明 */
lv_obj_set_style_text_color(btn, lv_color_hex(0xFFFFFF), 0); /* 白色文字,需设在按钮上才生效 */
lv_obj_set_style_border_width(btn, 1, 0);
lv_obj_set_style_border_side(btn, LV_BORDER_SIDE_BOTTOM, 0);
lv_obj_set_style_border_color(btn, lv_color_hex(0xAAAAAA), 0); /* 浅色行分隔线 */
lv_obj_set_user_data(btn, (void *)(uintptr_t)attr.sclust);
lv_obj_add_event_cb(btn, file_btn_click_cb, LV_EVENT_CLICKED, NULL);
add_cnt++;
}
lv_mem_free(name_buf);
}
static void create_led_mode_list(lv_obj_t *list)
{
const char *mode_text[] = {"Mode 1", "Mode 2", "Mode 3", "Mode 4", "Mode 5", "Mode 6", "Mode 7", "Mode 8"};
for (int i = 0; i < LED_MODE_COUNT; i++)
{
lv_obj_t *btn = lv_list_add_btn(list, LV_SYMBOL_CHARGE, mode_text[i]);
lv_obj_set_height(btn, ROW_H);
lv_obj_set_style_pad_all(btn, 2, 0); /* 压缩按钮内边距适配12号字体 */
lv_obj_set_style_bg_opa(btn, LV_OPA_TRANSP, 0); /* 行背景透明 */
lv_obj_set_style_text_color(btn, lv_color_hex(0xFFFFFF), 0); /* 白色文字,需设在按钮上才生效 */
lv_obj_set_style_border_width(btn, 1, 0);
lv_obj_set_style_border_side(btn, LV_BORDER_SIDE_BOTTOM, 0);
lv_obj_set_style_border_color(btn, lv_color_hex(0xAAAAAA), 0); /* 浅色行分隔线 */
}
}
void ui_music_update_playing_item(u32 sclust);
void ui_music_refresh_file_list(void)
{
if (file_list)
{
refresh_file_list_content(file_list);
/* 若正在播放,恢复高亮对应列表项 */
if (music_player_get_play_status() == 1) /* FILE_DEC_STATUS_PLAY */
ui_music_update_playing_item(music_player_get_file_sclust());
}
/* 若 eye LED 正在运行,恢复高亮对应模式 */
ui_music_update_led_mode_item(eye_led_get_mode());
}
/* 刷新播放进度music_player 返回秒,与 file_dec_get_cur_time 一致) */
static void ui_music_refresh_play_time(void)
{
if (!bar_progress || !label_curr_time || !label_total_time)
return;
int cur_sec = music_player_get_dec_cur_time();
int total_sec = music_player_get_dec_total_time();
u32 cur_min = (u32)(cur_sec / 60);
u32 cur_s = (u32)(cur_sec % 60);
u32 total_min = (u32)(total_sec / 60);
u32 total_s = (u32)(total_sec % 60);
char buf[16];
lv_snprintf(buf, sizeof(buf), "%" LV_PRIu32 ":%02" LV_PRIu32, cur_min, cur_s);
lv_label_set_text(label_curr_time, buf);
lv_snprintf(buf, sizeof(buf), "%" LV_PRIu32 ":%02" LV_PRIu32, total_min, total_s);
lv_label_set_text(label_total_time, buf);
int val = (total_sec > 0) ? (int)((u32)cur_sec * 100 / (u32)total_sec) : 0;
if (val > 100)
val = 100;
lv_bar_set_value(bar_progress, val, LV_ANIM_OFF);
}
void ui_music_update_play_time(void)
{
ui_music_refresh_play_time();
}
#define COLOR_PLAYING 0x00FF00 /* 正在播放:绿色 */
#define COLOR_NORMAL 0xFFFFFF /* 普通:白色 */
/* 根据当前 eye LED 模式高亮对应列表项绿色mode 0 表示全部恢复白色 */
void ui_music_update_led_mode_item(u8 mode)
{
if (!led_list)
return;
for (int i = 0; i < LED_MODE_COUNT; i++)
{
lv_obj_t *c = lv_obj_get_child(led_list, i);
if (c)
{
lv_color_t color = (mode != 0 && (int)(mode - 1) == i)
? lv_color_hex(COLOR_PLAYING) : lv_color_hex(COLOR_NORMAL);
lv_obj_set_style_text_color(c, color, 0);
}
}
}
/* 根据播放文件的 sclust 高亮对应列表项(绿色) */
void ui_music_update_playing_item(u32 sclust)
{
if (!file_list)
return;
uint32_t cnt = lv_obj_get_child_cnt(file_list);
for (uint32_t i = 0; i < cnt; i++)
{
lv_obj_t *c = lv_obj_get_child(file_list, i);
void *ud = lv_obj_get_user_data(c);
if (ud != NULL)
{
u32 item_sclust = (u32)(uintptr_t)ud;
lv_color_t color = (item_sclust == sclust) ? lv_color_hex(COLOR_PLAYING) : lv_color_hex(COLOR_NORMAL);
lv_obj_set_style_text_color(c, color, 0);
}
}
}
lv_obj_t *ui_music_create(void)
{
lv_obj_t *scr = lv_obj_create(NULL);
lv_obj_set_style_bg_img_src(scr, &img_bg, 0);
lv_obj_set_style_pad_all(scr, 0, 0);
lv_obj_clear_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
/* 顶部标题 */
lv_obj_t *title = lv_label_create(scr);
lv_label_set_text(title, "Music");
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_pos(title, 0, 8);
lv_obj_set_width(title, LCD_W);
lv_obj_set_style_text_align(title, LV_TEXT_ALIGN_CENTER, 0);
/* 左侧:文件列表 */
file_list = lv_list_create(scr);
lv_obj_set_size(file_list, LIST_W, LIST_H);
lv_obj_set_pos(file_list, 4, LIST_TOP);
lv_obj_set_style_bg_opa(file_list, LV_OPA_TRANSP, 0);
lv_obj_set_style_border_width(file_list, 1, 0);
lv_obj_set_style_border_color(file_list, lv_color_hex(0xCCCCCC), 0); /* 浅色边框 */
lv_obj_set_style_text_color(file_list, lv_color_hex(0xFFFFFF), 0); /* 白色前景 */
lv_obj_set_style_text_font(file_list, &lv_font_montserrat_12, 0);
lv_obj_set_style_pad_row(file_list, ROW_GAP, 0);
refresh_file_list_content(file_list);
/* 右侧LED 模式列表 */
led_list = lv_list_create(scr);
lv_obj_set_size(led_list, LIST_W, LIST_H);
lv_obj_set_pos(led_list, 12 + LIST_W, LIST_TOP);
lv_obj_set_style_bg_opa(led_list, LV_OPA_TRANSP, 0);
lv_obj_set_style_border_width(led_list, 1, 0);
lv_obj_set_style_border_color(led_list, lv_color_hex(0xCCCCCC), 0); /* 浅色边框 */
lv_obj_set_style_text_color(led_list, lv_color_hex(0xFFFFFF), 0); /* 白色前景 */
lv_obj_set_style_text_font(led_list, &lv_font_montserrat_12, 0);
lv_obj_set_style_pad_row(led_list, ROW_GAP, 0);
create_led_mode_list(led_list);
/* 底部进度条(同 BT 页) */
bar_progress = lv_bar_create(scr);
lv_obj_set_size(bar_progress, BAR_W, BAR_H);
lv_obj_set_pos(bar_progress, BAR_X, BAR_Y);
lv_bar_set_range(bar_progress, 0, 100);
lv_bar_set_value(bar_progress, 0, LV_ANIM_OFF);
lv_obj_set_style_bg_color(bar_progress, lv_color_hex(0xAAAAAA), LV_PART_MAIN);
lv_obj_set_style_bg_opa(bar_progress, LV_OPA_70, LV_PART_MAIN);
lv_obj_set_style_bg_color(bar_progress, lv_color_hex(0x2196F3), LV_PART_INDICATOR);
lv_obj_set_style_radius(bar_progress, 7, 0);
lv_obj_set_style_radius(bar_progress, 7, LV_PART_INDICATOR);
label_curr_time = lv_label_create(scr);
lv_label_set_text(label_curr_time, "0:00");
lv_obj_set_style_text_color(label_curr_time, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(label_curr_time, &lv_font_montserrat_12, 0);
lv_obj_set_pos(label_curr_time, BAR_X, TIME_Y);
label_total_time = lv_label_create(scr);
lv_label_set_text(label_total_time, "0:00");
lv_obj_set_style_text_color(label_total_time, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_text_font(label_total_time, &lv_font_montserrat_12, 0);
lv_obj_set_pos(label_total_time, BAR_X + BAR_W - 30, TIME_Y);
ui_music_refresh_play_time();
return scr;
}
void ui_music_on_key(int key_event, int key_value)
{
switch (key_event)
{
case KEY_MUSIC_PREV:
//case KEY_USER_PREV:
if (setting_flag)
focus_prev();
break;
case KEY_MUSIC_NEXT:
//case KEY_USER_NEXT:
if (setting_flag)
focus_next();
break;
//case KEY_USER_ENTER:
case KEY_MUSIC_PP: /* 设置模式下 PP 单击 = 确认 */
if (setting_flag)
{
focus_confirm(); /* 确认:音乐列表 <-> LED 列表切换焦点 */
}
break;
case KEY_USER_PLAY_TIME_UPDATE:
ui_music_refresh_play_time();
break;
case KEY_USER_MUSIC_PLAYING:
ui_music_update_playing_item((u32)key_value);
break;
case KEY_USER_EYE_LED_STOP:
ui_music_update_led_mode_item(0);
break;
case KEY_USER_SETTING:
setting_flag = !setting_flag;
if (setting_flag)
{
focus_enter_setting(); /* 进入设置,默认焦点到文件列表第一项 */
}
else
{
focus_exit_setting();
}
break;
default:
break;
}
}
u8 ui_music_get_setting_flag(void)
{
return setting_flag;
}