#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; }