KT25-1015_AC695x_SDK310/apps/ui/ui_music.c

660 lines
20 KiB
C
Raw Normal View History

2026-02-13 13:49:31 +00:00
#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"
2026-03-18 21:52:10 +00:00
#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);
}
}
}
2026-02-13 13:49:31 +00:00
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);
2026-03-18 21:52:10 +00:00
lv_label_set_text(title, "Music");
2026-02-13 13:49:31 +00:00
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);
2026-03-18 21:52:10 +00:00
/* 左侧:文件列表 */
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();
2026-02-13 13:49:31 +00:00
return scr;
}
void ui_music_on_key(int key_event, int key_value)
{
switch (key_event)
{
case KEY_MUSIC_PREV:
2026-03-18 21:52:10 +00:00
//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();
}
2026-02-13 13:49:31 +00:00
break;
default:
break;
}
}
2026-03-18 21:52:10 +00:00
u8 ui_music_get_setting_flag(void)
{
return setting_flag;
}