#include "includes.h" extern uint8_t level; /* 0902 控件-11/12 左右实心三角;x 为图标左缘,cy 为垂直中心 */ static void UI_DrawTriLeft(uint16_t x, uint16_t cy) { uint16_t y = (cy >= UI0902_TRI_L_H / 2) ? (uint16_t)(cy - UI0902_TRI_L_H / 2) : 0; LCD_WR_PIC_Trans(x, y, UI0902_TRI_L_W, UI0902_TRI_L_H, gImage_UI0902_TriLeft_22x22, BLACK); } static void UI_DrawTriRight(uint16_t x, uint16_t cy) { uint16_t y = (cy >= UI0902_TRI_R_H / 2) ? (uint16_t)(cy - UI0902_TRI_R_H / 2) : 0; LCD_WR_PIC_Trans(x, y, UI0902_TRI_R_W, UI0902_TRI_R_H, gImage_UI0902_TriRight_23x22, BLACK); } // ============================================== // ����ָ�붨�� // ============================================== void (*MenuPage_Idle_Proc)(void); void (*MenuPage_SongMode_Proc)(void); void (*MenuPage_ExpertMode_Proc)(void); void (*MenuPage_FreeMode_Proc)(void); void (*MenuPage_Setting_Proc)(void); void (*MenuPage_SystemSetting_Proc)(void); void (*MenuPage_Mixer_Proc)(void); void (*MenuPage_Restore_Proc)(void); void (*MenuPage_Language_Proc)(void); void (*MenuPage_UsbConfirm_Proc)(void); // ============================================== // ��ʼ���˵�UI // ============================================== void InitMenuUI(void) { MenuPage_Idle_Proc = CallUI_Idle; // ���������� MenuPage_SongMode_Proc = CallUI_SongMode; // ����ģʽ MenuPage_ExpertMode_Proc = CallUI_ExpertMode; // ר��ģʽ MenuPage_FreeMode_Proc = CallUI_FreeMode; // ����ģʽ MenuPage_Setting_Proc = CallUI_Setting; // ���� MenuPage_SystemSetting_Proc = CallUI_SystemSetting; // ϵͳ���� MenuPage_Mixer_Proc = CallUI_Mixer; //����̨ MenuPage_Restore_Proc = CallUI_RestoreSelect; MenuPage_Language_Proc = CallUI_Language; MenuPage_UsbConfirm_Proc = CallUI_UsbConfirm; } // ============================================== // ���� UI ���ú��� // ============================================== void CallUI_Idle(void) { CurrUIProcress = IdleProcess; app_log_set_ui_page("Idle"); LOG_I("UI", "page Idle"); } void CallUI_SongMode(void) { app_touch_suppress(500); MasterVolume_Reapply(); /* 进模式补发主音量,防开机 SysEx 丢失后实际音量与图标不符 */ UI_SongMode_Init(); CurrUIProcress = UI_SongMode_Process; app_log_set_ui_page("Song"); LOG_I("UI", "page SongMode tab=%u", (unsigned)mGuiData[GUI_TAB_INDEX].Current); } void CallUI_ExpertMode(void) { app_touch_suppress(500); MasterVolume_Reapply(); UI_ExpertMode_Init(); // CurrUIProcress = UI_ExpertMode_Process; CurrUIProcress = UI_SongMode_Process; app_log_set_ui_page("Expert"); LOG_I("UI", "page ExpertMode"); } void CallUI_FreeMode(void) { app_touch_suppress(500); MasterVolume_Reapply(); UI_FreeMode_Init(); // CurrUIProcress = UI_FreeMode_Process; CurrUIProcress = UI_SongMode_Process; app_log_set_ui_page("Free"); LOG_I("UI", "page FreeMode"); } void CallUI_Setting(void) { app_touch_suppress(500); CurrUIProcress = UI_Setting_Process; UI_Setting_Init(); app_log_set_ui_page("Setting"); LOG_I("UI", "page Setting"); } void CallUI_Mixer(void) { app_touch_suppress(500); CurrUIProcress = UI_Mixer_Process; UI_Mixer_Init(); app_log_set_ui_page("Mixer"); LOG_I("UI", "page Mixer"); } void CallUI_SystemSetting(void) { app_touch_suppress(500); CurrUIProcress = UI_SystemSet_Process; UI_SystemSet_Init(); app_log_set_ui_page("SysSet"); LOG_I("UI", "page SystemSetting"); } void CallUI_RestoreSelect(void) { CurrUIProcress = UI_Restore_Select_Process; UI_Restore_Select_Init(); app_log_set_ui_page("Restore"); LOG_I("UI", "page Restore"); } void CallUI_Language(void) { CurrUIProcress = UI_Language_Process; UI_Language_Init(); app_log_set_ui_page("Lang"); LOG_I("UI", "page Language"); } void CallUI_UsbConfirm(void) { CurrUIProcress = UI_UsbModeConfirm_Process; UI_UsbModeConfirm_Init(); app_log_set_ui_page("UsbCfm"); LOG_I("UI", "page UsbConfirm"); } /* 音师:万能→3.bin;普通/专业:节奏→1.bin、本地曲目→2.bin */ void UI_ApplyToneAddress(void) { uint32_t expect = 0; const char *map_name = "?"; switch (mGuiData[GUI_TAB_INDEX].Current) { case 0: /* 万能:和弦走向库 */ ADDRESS = FLASH_ADDR_MODE_UNIVERSAL; expect = EXTFLASH_BIN3_UNIVERSAL_ADDR; map_name = "BIN3_UNIVERSAL"; break; case 1: /* 专业 */ if (mGuiData[GUI_AUTOBAND_SW].Current) { ADDRESS = FLASH_ADDR_SONG_HAITIAN; expect = EXTFLASH_BIN2_SONG_HAITIAN_ADDR; map_name = "BIN2_SONG_HAITIAN"; } else { ADDRESS = FLASH_ADDR_MODE_EXPERT; expect = EXTFLASH_BIN1_RHYTHM_ADDR; map_name = "BIN1_RHYTHM"; } break; case 2: /* 普通 */ if (mGuiData[GUI_AUTOBAND_SW].Current) { ADDRESS = FLASH_ADDR_SONG_HAITIAN; expect = EXTFLASH_BIN2_SONG_HAITIAN_ADDR; map_name = "BIN2_SONG_HAITIAN"; } else { ADDRESS = FLASH_ADDR_MODE_NORMAL; expect = EXTFLASH_BIN1_RHYTHM_ADDR; map_name = "BIN1_RHYTHM"; } break; default: ADDRESS = FLASH_ADDR_MODE_UNIVERSAL; expect = EXTFLASH_BIN3_UNIVERSAL_ADDR; map_name = "BIN3_UNIVERSAL"; break; } LOG_I("tone", "ADDRESS=0x%08X map=%s expect=0x%08X tab=%u sw=%u %s", (unsigned)ADDRESS, map_name, (unsigned)expect, (unsigned)mGuiData[GUI_TAB_INDEX].Current, (unsigned)mGuiData[GUI_AUTOBAND_SW].Current, (ADDRESS == expect) ? "OK" : "MISMATCH"); } /* Doc/默认出厂设置:万能三条都是 92;节奏 31 条。第 20 条打榜摇滚乐 = 91。 * 本地曲目不在表内,切过去不改当前速度。 */ static const uint16_t kRhythmBpm[31] = { 79, 105, 115, 89, 61, 78, 74, 111, 60, 99, 65, 76, 75, 86, 72, 90, 92, 125, 94, 91, 82, 115, 92, 86, 127, 86, 59, 112, 92, 86, 69 }; static uint8_t s_track_valid; static uint8_t s_track_tab; static uint8_t s_track_local; static uint16_t s_track_idx; static void Track_Read(uint8_t *tab, uint8_t *local, uint16_t *idx) { *tab = (uint8_t)mGuiData[GUI_TAB_INDEX].Current; if (*tab == 0) { *local = 0; *idx = ParamGuiData[ALL_MODE_PARAM].Current; } else if (mGuiData[GUI_AUTOBAND_SW].Current) { *local = 1; *idx = ParamGuiData[EXPRESS_MODE_PARAM].Current; } else { *local = 0; *idx = ParamGuiData[SONG_MODE_PARAM].Current; } } static uint8_t Track_LookupBpm(uint8_t tab, uint8_t local, uint16_t idx, uint16_t *bpm) { if (tab == 0) { *bpm = 92; return 1; } if (local) return 0; if (idx >= (uint16_t)(sizeof(kRhythmBpm) / sizeof(kRhythmBpm[0]))) return 0; *bpm = kRhythmBpm[idx]; return 1; } void TrackBpm_Invalidate(void) { s_track_valid = 0; } uint8_t TrackBpm_SyncGui(void) { uint8_t tab; uint8_t local; uint16_t idx; uint16_t bpm; Track_Read(&tab, &local, &idx); if (s_track_valid && s_track_tab == tab && s_track_local == local && s_track_idx == idx) return 0; s_track_valid = 1; s_track_tab = tab; s_track_local = local; s_track_idx = idx; if (!Track_LookupBpm(tab, local, idx, &bpm)) return 0; if (mGuiData[GUI_SPEED].Current != bpm) { mGuiData[GUI_SPEED].Current = bpm; LOG_I("BPM", "track bpm=%u tab=%u local=%u idx=%u", (unsigned)bpm, (unsigned)tab, (unsigned)local, (unsigned)idx); } return 1; } uint16_t TrackBpm_Peek(void) { uint8_t tab; uint8_t local; uint16_t idx; uint16_t bpm; Track_Read(&tab, &local, &idx); /* 还是这一首:手调速度保持,不要被默认表盖掉 */ if (s_track_valid && s_track_tab == tab && s_track_local == local && s_track_idx == idx) return mGuiData[GUI_SPEED].Current; if (!Track_LookupBpm(tab, local, idx, &bpm)) return mGuiData[GUI_SPEED].Current; return bpm; } void TrackBpm_OnPresetLoaded(void) { uint8_t changed = TrackBpm_SyncGui(); AutoBandTop1_ChangeBPM((int)mGuiData[GUI_SPEED].Current); if (changed && CurrUIProcress == UI_SongMode_Process) Refresh_Transpose(&UI_Label[GUI_SPEED], &mGuiData[GUI_SPEED]); } void Session_ApplyPowerOnDefaults(void) { NvmParam_Type *nvm = drv_nvm_param_ptr(); nvm->param.BluetoothSW = 1; nvm->param.Param1 = 0; nvm->param.Param2 = 0; nvm->param.Param3 = 0; mGuiData[GUI_BL_SW].Current = 1; mGuiData[GUI_PLAY_SECTION].Current = 0; mGuiData[GUI_AUTOBAND_SW].Current = 0; ParamGuiData[SONG_MODE_PARAM].Current = 0; ParamGuiData[EXPRESS_MODE_PARAM].Current = 0; ParamGuiData[ALL_MODE_PARAM].Current = 0; TrackBpm_Invalidate(); TrackBpm_SyncGui(); nvm->param.BPM = mGuiData[GUI_SPEED].Current; } void UI_ReloadTonePreset(void) { UI_ApplyToneAddress(); /* 换库/切模式前完整停止伴奏(引擎停 + 全通道 AllNotesOff) */ Stop_AutoBand(); /* 万能:和弦走向 */ if (mGuiData[GUI_TAB_INDEX].Current == 0) { int ret = App_Auto_LoadPresetFromFlash(ParamGuiData[ALL_MODE_PARAM].Current); if (ret != 0) { uint8_t hdr[16]; W25Q128_Read(hdr, (uint32_t)ADDRESS, 16); LOG_E("tone", "load universal idx=%d ret=%d magic=%02X%02X%02X%02X cnt=%lu addr=0x%08X", ParamGuiData[ALL_MODE_PARAM].Current, ret, hdr[0], hdr[1], hdr[2], hdr[3], (unsigned long)(hdr[12] | (hdr[13] << 8) | (hdr[14] << 16) | (hdr[15] << 24)), (unsigned)ADDRESS); } else { LOG_I("tone", "load universal idx=%d ret=%d name=%s addr=0x%08X map=BIN3@0x%08X", ParamGuiData[ALL_MODE_PARAM].Current, ret, AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?", (unsigned)ADDRESS, (unsigned)EXTFLASH_BIN3_UNIVERSAL_ADDR); } TrackBpm_OnPresetLoaded(); return; } /* 普通/专业:本地曲目或节奏类型 */ if (mGuiData[GUI_AUTOBAND_SW].Current) { GUI_SWITCH *local = &ParamGuiData[EXPRESS_MODE_PARAM]; local->Max = (LOCAL_SONG_COUNT > 0) ? (uint16_t)(LOCAL_SONG_COUNT - 1) : 0; if (local->Current > local->Max) local->Current = local->Max; int ret = App_Auto_LoadPresetFromFlash(local->Current); if (ret != 0) LOG_E("tone", "load local song idx=%d failed ret=%d addr=0x%08X map=BIN2@0x%08X", local->Current, ret, (unsigned)ADDRESS, (unsigned)EXTFLASH_BIN2_SONG_HAITIAN_ADDR); else LOG_I("tone", "local song idx=%d name=%s addr=0x%08X map=BIN2@0x%08X %s", local->Current, AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?", (unsigned)ADDRESS, (unsigned)EXTFLASH_BIN2_SONG_HAITIAN_ADDR, (ADDRESS == EXTFLASH_BIN2_SONG_HAITIAN_ADDR) ? "OK" : "MISMATCH"); { int bin_n = AutoBandTop1_GetPresetItemCount(); if (bin_n != LOCAL_SONG_COUNT) LOG_W("tone", "local song count csv=%d bin=%d", LOCAL_SONG_COUNT, bin_n); } TrackBpm_OnPresetLoaded(); return; } { int ret = App_Auto_LoadPresetFromFlash(ParamGuiData[SONG_MODE_PARAM].Current); LOG_I("tone", "load rhythm idx=%d ret=%d name=%s addr=0x%08X map=BIN1@0x%08X %s", ParamGuiData[SONG_MODE_PARAM].Current, ret, AutoBandTop1_GetPresetName() ? AutoBandTop1_GetPresetName() : "?", (unsigned)ADDRESS, (unsigned)EXTFLASH_BIN1_RHYTHM_ADDR, (ADDRESS == EXTFLASH_BIN1_RHYTHM_ADDR) ? "OK" : "MISMATCH"); (void)ret; } TrackBpm_OnPresetLoaded(); } static uint8_t s_ui_focus_id = UI_FOCUS_NONE; static uint8_t UI_NormalizeFocusId(uint8_t widget_id) { if (widget_id == GUI_AUTOBAND_SW) return GUI_MODE_PARAM; return widget_id; } static void UI_RedrawWidget(uint8_t id) { if (CurrUIProcress == UI_SongMode_Process) { switch (id) { case GUI_TRANSPOSE: label_Tranzpose_ui(&UI_Label[GUI_TRANSPOSE], &mGuiData[GUI_TRANSPOSE]); break; case GUI_SPEED: label_Speed_ui(&UI_Label[GUI_SPEED], &mGuiData[GUI_SPEED]); break; case GUI_MODE_PARAM: label_ModeSelece_ui(&UI_Label[GUI_MODE_PARAM], &mGuiData[GUI_MODE_PARAM]); break; case GUI_TIMBRE_SELECT: label_TimbreSelece_ui(&UI_Label[GUI_TIMBRE_SELECT], &mGuiData[GUI_TIMBRE_SELECT]); break; case GUI_PLAY_SECTION: label_PlaySection_ui(&UI_Label[GUI_PLAY_SECTION], &mGuiData[GUI_PLAY_SECTION]); break; default: break; } } else if (CurrUIProcress == UI_Setting_Process) { if (id <= GUI_RESTORE) Draw_System_Item_List(&SystemSetting[id], &mGuiData_SystemSETTING[id]); } } void UI_ClearFocus(void) { s_ui_focus_id = UI_FOCUS_NONE; } uint8_t UI_IsFocused(uint8_t widget_id) { return (s_ui_focus_id == UI_NormalizeFocusId(widget_id)) ? 1 : 0; } void UI_GetFocusGrad(uint8_t widget_id, uint16_t *c_top, uint16_t *c_bot) { /* 0902:行背景保持深色;选中态由 TAB/段落按钮自身高亮,避免整行变蓝 */ (void)widget_id; *c_top = UI0902_ROW_BG; *c_bot = UI0902_ROW_BG; } uint8_t UI_SetFocus(uint8_t widget_id) { uint8_t prev = s_ui_focus_id; widget_id = UI_NormalizeFocusId(widget_id); if (prev == widget_id) return 0; s_ui_focus_id = widget_id; if (prev != UI_FOCUS_NONE) UI_RedrawWidget(prev); if (widget_id != UI_FOCUS_NONE) UI_RedrawWidget(widget_id); return 1; } void UI_DrawStatusSepLine(void) { /* 0924:去掉状态栏分割线 */ } void label_top() { LCD_FillByColor(0, 0, 240, UI_STATUS_BAR_H, UI0902_BG_COLOR); /* 音量条:渐变 + 圆角(见 Draw_Volume_Bar) */ Draw_Volume_Bar(UI0902_VOL_X, UI0902_VOL_Y, level, 1, WHITE, GRAY); Show_BL_Icon(mGuiData[GUI_BL_SW].Current); Show_Battery_Icon(usb_charging_state); UI_DrawStatusSepLine(); } /* 小节选择刷新(占位,实际绘制在 UI_SongMode 内部) */ void Refresh_SectionSelect(uint16_t last_value, const LEBEL_UI *UI, GUI_SWITCH *Group) { Draw_Tab_Section_Menu(UI,(TAB_SECTION_INDEX)Group->Current); } /* TAB 选择刷新:tab 未变时不切页,避免误触/phantom touch 整页重绘闪烁 */ void Refresh_TabSelect_ui(const LEBEL_UI *UI, GUI_SWITCH *Group) { static uint8_t s_last_tab = 0xFFU; uint8_t tab = (uint8_t)Group->Current; (void)UI; if (tab == s_last_tab) { return; } s_last_tab = tab; switch (tab) { case 0: MenuPage_SongMode_Proc(); break; case 1: MenuPage_ExpertMode_Proc(); break; case 2: MenuPage_FreeMode_Proc(); break; case 3: MenuPage_SystemSetting_Proc(); break; default: break; } } /* 返回当前 TAB 对应的模式页(设置/调音台页的“返回”用) */ void UI_GotoCurrentModePage(void) { switch (mGuiData[GUI_TAB_INDEX].Current) { case 0: MenuPage_SongMode_Proc(); /* Init 内 Reload */ break; case 1: MenuPage_ExpertMode_Proc(); break; case 2: MenuPage_FreeMode_Proc(); break; default: MenuPage_SongMode_Proc(); break; } } /* Bluetooth status icons from 0923 pack (on 13x20 / off 19x20) */ void Show_BL_Icon(uint8_t value) { static int8_t s_bt_pwr = -1; uint8_t on = value ? 1u : 0u; uint16_t draw_w; uint16_t draw_h; uint16_t draw_x; const unsigned char *pic; LCD_FillByColor(UI0902_BT_X, UI0902_BT_Y, UI0902_BT_X + UI0902_BT_W, UI0902_BT_Y + UI0902_BT_H, UI0902_BG_COLOR); if (on) { draw_w = UI0902_BT_ON_W; draw_h = UI0902_BT_ON_H; pic = gImage_UI0902_Bluetooth_On_13x20; } else { draw_w = UI0902_BT_OFF_W; draw_h = UI0902_BT_OFF_H; pic = gImage_UI0902_Bluetooth_Off_19x20; } draw_x = (uint16_t)((240 - draw_w) / 2); LCD_WR_PIC_Trans(draw_x, UI0902_BT_Y, draw_w, draw_h, pic, BLACK); if (s_bt_pwr != (int8_t)on) { s_bt_pwr = (int8_t)on; BSP_BlueToothPowerEnable(on); } UI_DrawStatusSepLine(); } void UI_StatusBar_ToggleBluetooth(void) { NvmParam_Type *nvm = drv_nvm_param_ptr(); uint8_t on = mGuiData[GUI_BL_SW].Current ? 0u : 1u; mGuiData[GUI_BL_SW].Current = on; nvm->param.BluetoothSW = on; drv_nvm_save_to_flash(); Show_BL_Icon(on); } void Draw_Status_Battery_Icon(uint8_t battery_percent) { uint8_t Info[8]; uint16_t fill_w; uint16_t actual_w; uint16_t max_w; uint16_t group_w; uint16_t clear_x; uint16_t bat_x; uint16_t text_x; uint16_t text_y; uint16_t fill_x1, fill_y1, fill_x2, fill_y2; uint8_t charging = (usb_charging_state != 0) ? 1 : 0; uint16_t icon_w = charging ? UI0902_BATTERY_CHG_W : UI0902_BATTERY_W; uint16_t icon_h = charging ? UI0902_BATTERY_CHG_H : UI0902_BATTERY_H; uint16_t icon_y; uint8_t adv = UI0902_ASCII_W(UI0902_BATTERY_TEXT_SIZE); if (battery_percent > 100) battery_percent = 100; sprintf((char *)Info, "%d%%", battery_percent); actual_w = (uint16_t)(strlen((char *)Info) * adv); /* 图标与百分比紧贴;整组右对齐。「100%」变位数时图标左移,不拉开字距 */ max_w = (uint16_t)(4u * adv); group_w = (uint16_t)(icon_w + UI0902_BATTERY_TEXT_GAP + actual_w); bat_x = (uint16_t)(240 - UI0902_BATTERY_RIGHT_MARGIN - group_w); text_x = (uint16_t)(bat_x + icon_w + UI0902_BATTERY_TEXT_GAP); /* 充电图 13px 比普通外壳 12px 高 1px:同中心上提,保持与音量/蓝牙对齐 */ icon_y = (uint16_t)(UI0902_BATTERY_Y + ((int16_t)UI0902_BATTERY_H - (int16_t)icon_h) / 2); text_y = (uint16_t)(UI0902_BATTERY_Y + ((int16_t)UI0902_BATTERY_H - (int16_t)UI0902_BATTERY_TEXT_SIZE) / 2); if ((int16_t)text_y < 0) text_y = 0; clear_x = (uint16_t)(240 - UI0902_BATTERY_RIGHT_MARGIN - UI0902_BATTERY_W - UI0902_BATTERY_TEXT_GAP - max_w); if (clear_x > 2) clear_x = (uint16_t)(clear_x - 2); else clear_x = 0; LCD_FillByColor(clear_x, 0, 240, UI0902_STATUS_SEP_Y, UI0902_BG_COLOR); if (charging) { /* 内腔按实际电量填黄,再盖外壳和闪电(图标里不再烘焙固定黄条) */ fill_w = (uint16_t)(((uint32_t)UI0902_BATTERY_CHG_FILL_MAX * battery_percent) / 100u); if (fill_w > UI0902_BATTERY_CHG_FILL_MAX) fill_w = UI0902_BATTERY_CHG_FILL_MAX; if (fill_w > 0) { fill_x1 = (uint16_t)(bat_x + UI0902_BATTERY_CHG_FILL_DX); fill_y1 = (uint16_t)(icon_y + UI0902_BATTERY_CHG_FILL_DY); fill_x2 = (uint16_t)(fill_x1 + fill_w); fill_y2 = (uint16_t)(fill_y1 + UI0902_BATTERY_CHG_FILL_H); LCD_FillByColor(fill_x1, fill_y1, fill_x2, fill_y2, UI0902_BATTERY_CHG_FILL); } LCD_WR_PIC_Trans(bat_x, icon_y, UI0902_BATTERY_CHG_W, UI0902_BATTERY_CHG_H, gImage_UI0902_BatteryCharging_40x13, BLACK); } else { /* 内腔约 y+2..y+9 / x+3..x+23;上下左右各留 1px,电量不贴外壳 */ fill_w = (uint16_t)((19UL * battery_percent) / 100); if (fill_w > 0) { fill_x1 = (uint16_t)(bat_x + 4); fill_y1 = (uint16_t)(UI0902_BATTERY_Y + 3); fill_x2 = (uint16_t)(fill_x1 + fill_w); fill_y2 = (uint16_t)(fill_y1 + 6); LCD_FillByColor(fill_x1, fill_y1, fill_x2, fill_y2, WHITE); } LCD_WR_PIC_Trans(bat_x, UI0902_BATTERY_Y, UI0902_BATTERY_W, UI0902_BATTERY_H, gImage_UI0902_BatteryOutline_29x12, BLACK); } LCD_ShowString(text_x, text_y, Info, WHITE, UI0902_BG_COLOR, UI0902_BATTERY_TEXT_SIZE, 0); UI_DrawStatusSepLine(); } void Show_Battery_Icon(uint8_t flag) { (void)flag; /* 充电态由全局 usb_charging_state 决定(MSG_ID_CHARG 发送前已更新) */ Draw_Status_Battery_Icon(percentage); } /* 中英文混排像素宽(与 LCD_GetMixedStringWidth 规则一致:CJK=sizey,ASCII=UI0902_ASCII_W) */ static uint16_t UI_MixedTextWidth(const uint8_t *str, uint8_t sizey) { uint16_t width = 0; uint8_t ascii_w = UI0902_ASCII_W(sizey); if (str == NULL) return 0; while (*str != '\0') { if (*str > 0x80) { width = (uint16_t)(width + sizey); str += 2; } else { width = (uint16_t)(width + ascii_w); str += 1; } } return width; } /* 曲目/音色共用:序号与名称间距(与「1.名称」拼接一致,保证两行视觉间距相同) */ #define UI0902_INDEX_NAME_GAP 0 /* 在 [x1,x2] 内整体居中绘制「N.」+ 名称(无渐变,show_Param 用) */ static void UI_DrawIndexedName_Mixed(uint16_t x1, uint16_t x2, uint16_t text_y, const char *num, const char *name, uint16_t fc, uint16_t bc, uint8_t sizey) { char line[48] = {0}; snprintf(line, sizeof(line), "%s%s", num, name); LCD_ShowMixedString(x1, x2, text_y, (uint8_t *)line, LCD_ALIGN_CENTER, fc, bc, sizey, 1); } /* 在 [x1,x2] 内整体居中绘制「N.」+ 中文名(渐变底,音色行用);间隙与曲目行一致 */ static void UI_DrawIndexedName_Grad(uint16_t x1, uint16_t x2, uint16_t text_y, const char *num, const uint8_t *name, uint16_t fc, uint16_t c_top, uint16_t c_bot, uint8_t sizey) { uint16_t num_w = (uint16_t)(strlen(num) * UI0902_ASCII_W(sizey)); uint16_t name_w = UI_MixedTextWidth(name, sizey); uint16_t total_w = (uint16_t)(num_w + UI0902_INDEX_NAME_GAP + name_w); uint16_t area_w = (x2 > x1) ? (uint16_t)(x2 - x1) : 0; uint16_t start_x = (total_w >= area_w) ? x1 : (uint16_t)(x1 + (area_w - total_w) / 2); uint16_t name_x = (uint16_t)(start_x + num_w + UI0902_INDEX_NAME_GAP); LCD_ShowString_AutoAlign_Gradient(start_x, (uint16_t)(start_x + num_w), text_y, (const uint8_t *)num, LCD_ALIGN_LEFT, fc, c_top, c_bot, sizey); LCD_ShowChinese_AutoAlign_Gradient(name_x, x2, text_y, name, LCD_ALIGN_LEFT, fc, c_top, c_bot, sizey); } void show_Param(const LEBEL_UI *UI, GUI_SWITCH *Group) { char Textstr_new[40] = {0}; char NumBuf[8] = {0}; /* 0902 比例:TAB 行高 18 在区块顶,曲目行在其下,正文在行内垂直居中 */ const uint16_t row_y = UI->y + UI0902_TAB_H + 4; /* 曲目行顶 = 130 */ const uint16_t row_h = UI->h - UI0902_TAB_H - 4; /* 31 */ const uint16_t text_y = row_y + (row_h - UI->Text_size) / 2 + 1; uint8_t idx = Group->Current + 1; uint16_t c_top, c_bot; uint16_t x1, x2; UI_GetFocusGrad(GUI_MODE_PARAM, &c_top, &c_bot); x1 = (uint16_t)(UI->x + 10); x2 = (uint16_t)(UI->x + UI->w - 10); /* 修复曲目名尾部残留:旧名比新名长时,只填渐变胶囊(x+25 ~ x+w-40) 盖不住文字区(x+10 ~ x+w-10)内的旧笔画,切到短名后尾部留残影。 先按行底色清整个文字区,再补渐变与文字;整行清除会盖住两侧三角, 文字绘制后统一补画。 */ LCD_FillByColor(x1, row_y, x2, (uint16_t)(row_y + row_h), c_top); LCD_FillRoundRectGradient( UI->x+25, row_y, UI->x + UI->w-40-(UI->x+25), row_h, 0, c_top, c_bot ); /* 序号+名称整体居中(约等于标注「右移7px」的视觉效果) */ sprintf(NumBuf, "%d.", idx); if (Group->Id == EXPRESS_MODE_PARAM && Group->Current < LOCAL_SONG_COUNT) strcpy(Textstr_new, LocalSongNameGbk[Group->Current]); else GET_ParamNum_Str(Group->Current, 0, Textstr_new, Group->Id); UI_DrawIndexedName_Mixed(x1, x2, text_y, NumBuf, Textstr_new, WHITE, BLACK, UI->Text_size); /* 补画左右三角(几何与 label_ModeSelece_ui 一致) */ { uint16_t row_cy = (uint16_t)(row_y + row_h / 2); UI_DrawTriLeft((uint16_t)(UI->x + 5), row_cy); UI_DrawTriRight((uint16_t)(UI->x + UI->w - 5 - UI0902_TRI_R_W), row_cy); } } //void Refresh_Transpose(uint8_t last_data,const LEBEL_UI *UI, GUI_SWITCH * Group) //{ // LCD_FillRoundRectGradient( // UI->x+30, // UI->y + 20, // 25 + 10+10+5, // 20, // 0, // START_COLOR, END_COLOR // ); // char Textstr1[20] = {0,}; // GET_Num_Str(Group->Current,0,Textstr1,Group->Id); // // LCD_ShowString_AutoAlign_Gradient( // UI->x, // 坐标�? // UI->x+UI->w, // UI->y + 17, // (const uint8_t*)Textstr1, // UI->Text_Align, // UI->Text_fc, // START_COLOR, // END_COLOR, // UI->Text_size // ); //} /* 稿面文本框:以 cx 为水平中心,宽 box_w;Y 为框顶。 * 行高有效(lh >= font)时在 max(lh, box_h) 内垂直居中;否则顶对齐画字。 */ static uint16_t UI_CardTextBoxX1(uint16_t cx, uint16_t box_w, uint16_t content_w) { uint16_t w = box_w; if (content_w > w) w = content_w; if (cx < w / 2) return 0; return (uint16_t)(cx - w / 2); } static uint16_t UI_CardTextBoxX2(uint16_t cx, uint16_t box_w, uint16_t content_w) { uint16_t w = box_w; if (content_w > w) w = content_w; return (uint16_t)(cx - w / 2 + w - 1); } static uint16_t UI_CardTextDrawY(uint16_t box_y, uint8_t box_h, uint8_t line_h, uint8_t font_h) { uint8_t cell = box_h; if (line_h >= font_h && line_h > cell) cell = line_h; if (cell < font_h) return box_y; return (uint16_t)(box_y + (cell - font_h) / 2); } void Refresh_Transpose(const LEBEL_UI *UI, GUI_SWITCH * Group) { uint16_t c_top, c_bot; uint16_t cx = (UI->Id == GUI_SPEED) ? UI0902_CARD_TEXT_CX_R : UI0902_CARD_TEXT_CX_L; uint16_t value_y, value_y0, x1, x2, content_w, clear_w, clear_h; char Textstr1[20] = {0,}; UI_GetFocusGrad(UI->Id, &c_top, &c_bot); GET_Num_Str(Group->Current,0,Textstr1,Group->Id); content_w = (uint16_t)(strlen(Textstr1) * UI0902_ASCII_W(UI0902_CARD_VALUE_SIZE)); /* 清区按最大位数:移调最多2字符(Db),速度最多3位(260) */ clear_w = (uint16_t)(((UI->Id == GUI_SPEED) ? 3u : 2u) * UI0902_ASCII_W(UI0902_CARD_VALUE_SIZE)); if (content_w > clear_w) clear_w = content_w; if (clear_w < UI0902_CARD_VALUE_W) clear_w = UI0902_CARD_VALUE_W; value_y0 = (uint16_t)(UI0902_CARD_CTRL_CY - UI0902_CARD_VALUE_SIZE / 2); value_y = value_y0; clear_h = (uint16_t)(UI0902_CARD_VALUE_H + 1); if (clear_h < (uint16_t)(UI0902_CARD_VALUE_SIZE + 1)) clear_h = (uint16_t)(UI0902_CARD_VALUE_SIZE + 1); x1 = UI_CardTextBoxX1(cx, clear_w, clear_w); x2 = UI_CardTextBoxX2(cx, clear_w, clear_w); LCD_FillRoundRectGradient( x1, value_y, (uint16_t)(x2 - x1 + 1), clear_h, 0, c_top, c_bot); x1 = UI_CardTextBoxX1(cx, UI0902_CARD_VALUE_W, content_w); x2 = UI_CardTextBoxX2(cx, UI0902_CARD_VALUE_W, content_w); LCD_ShowString_AutoAlign_Gradient( x1, x2, value_y, (const uint8_t*)Textstr1, LCD_ALIGN_CENTER, UI->Text_fc, c_top, c_bot, UI0902_CARD_VALUE_SIZE); } void Refresh_StringTimbre(uint8_t last_data,const LEBEL_UI *UI, GUI_SWITCH * Group) { (void)last_data; /* 与 label_TimbreSelece_ui 同一套居中布局;旧版 UI->y+25 在 TIMBRE_H=30 时会画到行外,叠出第二行 */ label_TimbreSelece_ui(UI, Group); if (Group->Id == GUI_TIMBRE_SELECT) { uint8_t AllNoteOff_buff[3] = {0xB0, 0x7B, 0x00}; SendMidiDataToDreamDSP(AllNoteOff_buff, sizeof(AllNoteOff_buff)); } } void Refresh_ModeSelect(uint8_t last_data, const LEBEL_UI *UI, GUI_SWITCH *Group) { Draw_Tab_Mode_Menu(UI,Group); } void Refresh_Mode1Select(uint8_t last_data, LEBEL_UI *UI, GUI_SWITCH *Group) { char Textstr1[20] = {0}; char Textstr2[20] = {0}; GUI_SWITCH *pCurData =NULL; // 重点:根�?当前选中的模式(2/3/4)找到�?�应数据结构�? switch(mGuiData[GUI_TAB_INDEX].Current) { case 0: pCurData = &ParamGuiData[ALL_MODE_PARAM]; break; /* 万能:和弦走向 */ case 1: /* 专业 */ case 2: /* 普通 */ if (mGuiData[GUI_AUTOBAND_SW].Current) pCurData = &ParamGuiData[EXPRESS_MODE_PARAM]; else pCurData = &ParamGuiData[SONG_MODE_PARAM]; break; //default: pCurData = &mGuiData[GUI_MODE1_SELECT]; } // 清除旧数字(清除的是旧值,不是当前值) GET_ParamNum_Str(last_data, 0, Textstr1, pCurData->Id); LCD_ClearString_AutoAlign( UI->x, UI->x + UI->w, UI->y + 25, (const uint8_t*)Textstr1, UI->Text_Align, UI->Text_fc, UI->Text_bc, 0, UI->Text_size ); // 显示当前选中模式的真实数�? GET_ParamNum_Str(pCurData->Current, 0, Textstr2, pCurData->Id); LCD_ShowString_AutoAlign( UI->x, UI->x + UI->w, UI->y + 25, (const uint8_t*)Textstr2, UI->Text_Align, UI->Text_fc, UI->Text_bc, 0, UI->Text_size ); } void label_Tranzpose_ui(const LEBEL_UI *UI,GUI_SWITCH * Group) { uint8_t Textstr[20] = {0}; uint16_t c_top, c_bot; uint16_t cx = UI0902_CARD_TEXT_CX_L; uint16_t label_y, value_y, chev_cy; uint16_t x1, x2, content_w; UI_GetFocusGrad(UI->Id, &c_top, &c_bot); LCD_FillRoundRectGradient( UI->x, UI->y, UI->w, UI->h, UI0902_RADIUS_CARD, c_top, c_bot); /* 移调:11 / LH13 / 框22×11 / (64,60) */ label_y = UI_CardTextDrawY(UI0902_CARD_LABEL_Y, UI0902_CARD_LABEL_H, UI0902_CARD_LABEL_LH, UI0902_CARD_LABEL_SIZE); x1 = UI_CardTextBoxX1(cx, UI0902_CARD_LABEL_W, UI0902_CARD_LABEL_W); x2 = UI_CardTextBoxX2(cx, UI0902_CARD_LABEL_W, UI0902_CARD_LABEL_W); LCD_ShowChinese_AutoAlign_Gradient( x1, x2, label_y, UI->p, LCD_ALIGN_CENTER, UI->Tab_fc, c_top, c_bot, UI0902_CARD_LABEL_SIZE); GET_Num_Str(mGuiData[UI->Id].Current, 0, (char*)Textstr, UI->Id); content_w = (uint16_t)(strlen((char*)Textstr) * UI0902_ASCII_W(UI0902_CARD_VALUE_SIZE)); /* 主值、左右三角色块共用稿面中线 y=74 */ value_y = (uint16_t)(UI0902_CARD_CTRL_CY - UI0902_CARD_VALUE_SIZE / 2); x1 = UI_CardTextBoxX1(cx, UI0902_CARD_VALUE_W, content_w); x2 = UI_CardTextBoxX2(cx, UI0902_CARD_VALUE_W, content_w); chev_cy = UI0902_CARD_CTRL_CY; UI_DrawTriLeft(UI->x + 5, chev_cy); UI_DrawTriRight((uint16_t)(UI->x + UI->w - 5 - UI0902_TRI_R_W), chev_cy); LCD_ShowString_AutoAlign_Gradient( x1, x2, value_y, Textstr, LCD_ALIGN_CENTER, UI->Text_fc, c_top, c_bot, UI0902_CARD_VALUE_SIZE); } void label_Speed_ui(const LEBEL_UI *UI,GUI_SWITCH * Group) { uint8_t Textstr[20] = {0}; uint16_t c_top, c_bot; uint16_t cx = UI0902_CARD_TEXT_CX_R; uint16_t label_y, value_y; uint16_t x1, x2, content_w; int16_t btn_y; UI_GetFocusGrad(UI->Id, &c_top, &c_bot); LCD_FillRoundRectGradient( UI->x, UI->y, UI->w, UI->h, UI0902_RADIUS_CARD, c_top, c_bot); label_y = UI_CardTextDrawY(UI0902_CARD_LABEL_Y, UI0902_CARD_LABEL_H, UI0902_CARD_LABEL_LH, UI0902_CARD_LABEL_SIZE); x1 = UI_CardTextBoxX1(cx, UI0902_CARD_LABEL_W, UI0902_CARD_LABEL_W); x2 = UI_CardTextBoxX2(cx, UI0902_CARD_LABEL_W, UI0902_CARD_LABEL_W); LCD_ShowChinese_AutoAlign_Gradient( x1, x2, label_y, UI->p, LCD_ALIGN_CENTER, UI->Tab_fc, c_top, c_bot, UI0902_CARD_LABEL_SIZE); GET_Num_Str(mGuiData[UI->Id].Current, 0, (char*)Textstr, UI->Id); content_w = (uint16_t)(strlen((char*)Textstr) * UI0902_ASCII_W(UI0902_CARD_VALUE_SIZE)); value_y = (uint16_t)(UI0902_CARD_CTRL_CY - UI0902_CARD_VALUE_SIZE / 2); x1 = UI_CardTextBoxX1(cx, UI0902_CARD_VALUE_W, content_w); x2 = UI_CardTextBoxX2(cx, UI0902_CARD_VALUE_W, content_w); /* ± 色块与主值同一水平中线 */ btn_y = (int16_t)UI0902_CARD_CTRL_CY - (int16_t)UI0902_BTN_MINUS_H / 2; if (btn_y < (int16_t)UI->y + 2) btn_y = (int16_t)UI->y + 2; LCD_WR_PIC_Trans(UI->x + 5, (uint16_t)btn_y, UI0902_BTN_MINUS_W, UI0902_BTN_MINUS_H, gImage_UI0902_BtnMinus_23x22, BLACK); LCD_WR_PIC_Trans(UI->x + UI->w - 5 - UI0902_BTN_PLUS_W, (uint16_t)btn_y, UI0902_BTN_PLUS_W, UI0902_BTN_PLUS_H, gImage_UI0902_BtnPlus_23x22, BLACK); LCD_ShowString_AutoAlign_Gradient( x1, x2, value_y, Textstr, LCD_ALIGN_CENTER, UI->Text_fc, c_top, c_bot, UI0902_CARD_VALUE_SIZE); } // 万能:和弦走向;普通/专业:节奏类型 | 本地曲目 void label_ModeSelece_ui(const LEBEL_UI *UI,GUI_SWITCH * Group) { char *str; GUI_SWITCH *paramCurData = NULL; uint16_t c_top, c_bot; uint8_t tab = (uint8_t)mGuiData[GUI_TAB_INDEX].Current; UI_GetFocusGrad(GUI_MODE_PARAM, &c_top, &c_bot); (void)Group; LCD_FillRoundRectGradient(UI->x, UI->y+UI0902_TAB_H+4, UI->w, UI->h-UI0902_TAB_H-4, UI0902_RADIUS_CARD, c_top, c_bot); if (tab == 0) { /* 万能:无 TAB,标题「和弦走向」 */ str = (char*)"\xBA\xCD\xCF\xD2\xD7\xDF\xCF\xF2"; { uint16_t row_y = (uint16_t)(UI->y + UI0902_TAB_H + 4); uint16_t lab_y = (row_y > 15) ? (uint16_t)(row_y - 15) : 0; LCD_FillByColor(UI->x, lab_y, UI->x + UI->w, (uint16_t)(row_y - 1), UI0902_BG_COLOR); LCD_ShowChinese_AutoAlign( UI->x, UI->x+UI->w, lab_y, (uint8_t *)str, UI->Tab_Align, UI->Tab_fc, UI0902_BG_COLOR, 0, UI->Tab_size ); } paramCurData = &ParamGuiData[ALL_MODE_PARAM]; } else { /* 普通/专业:节奏类型 | 本地曲目 */ Draw_Tab_Mode_Menu(UI, &mGuiData[GUI_AUTOBAND_SW]); if (mGuiData[GUI_AUTOBAND_SW].Current) paramCurData = &ParamGuiData[EXPRESS_MODE_PARAM]; else paramCurData = &ParamGuiData[SONG_MODE_PARAM]; } /* 左右三角由 show_Param 末尾统一补画(整行清除后必须重画) */ show_Param(&UI_Label[GUI_MODE_PARAM], paramCurData); } void label_TimbreSelece_ui(const LEBEL_UI *UI, GUI_SWITCH * Group) { uint8_t Textstr[20] = {0}; char NumBuf[8] = {0}; /* 0902 比例:音色行高 30,正文在行内垂直居中 */ uint16_t text_y = UI->y + (UI->h - UI->Text_size) / 2 + 1; uint8_t idx = mGuiData[UI->Id].Current + 1; uint16_t c_top, c_bot; uint16_t chev_cy = UI->y + UI->h / 2; UI_GetFocusGrad(UI->Id, &c_top, &c_bot); LCD_FillRoundRectGradient( UI->x, UI->y, UI->w, UI->h, UI0902_RADIUS_CARD, c_top, c_bot ); LCD_ShowChinese_AutoAlign( UI->x, UI->x+UI->w, UI->y - 15, UI->p, UI->Tab_Align, UI->Tab_fc, UI0902_BG_COLOR, 1, UI->Tab_size ); UI_DrawTriLeft(UI->x + 5, chev_cy); UI_DrawTriRight((uint16_t)(UI->x + UI->w - 5 - UI0902_TRI_R_W), chev_cy); LCD_FillRoundRectGradient( UI->x + 40, UI->y, UI->x + UI->w - 40 - UI->x - 40 , UI->h, 0, c_top, c_bot ); sprintf(NumBuf, "%d.", idx); GET_Num_Str(mGuiData[UI->Id].Current, 0, (char*)Textstr, UI->Id); UI_DrawIndexedName_Grad( (uint16_t)(UI->x + 10), (uint16_t)(UI->x + UI->w - 10), text_y, NumBuf, Textstr, UI->Text_fc, c_top, c_bot, UI->Text_size ); switch(Group->Id) { case GUI_TIMBRE_SELECT: /* 双音色库:主弦走 Bank1,先选 bank 再 PC */ SendProgramWithBankMidiEvent(0, MIDI_BANK_STRING, Timbre_index[Group->Current]); break; default: break; } } void label_PlaySection_ui(const LEBEL_UI *UI,GUI_SWITCH * Group) { /* 0902 比例:标签在按钮上方 15px,先画按钮再补标签底,避免色块盖住 */ Draw_Tab_Section_Menu(UI,(TAB_SECTION_INDEX)Group->Current); LCD_FillByColor(UI->x, UI->y - 15, UI->x + UI->w, UI->y - 1, UI0902_BG_COLOR); LCD_ShowChinese_AutoAlign( UI->x, UI->x+UI->w, UI->y - 14, UI->p, UI->Tab_Align, UI->Tab_fc, UI0902_BG_COLOR, 1, UI->Tab_size ); } void UI_DrawSubPageHeader(const uint8_t *title_gbk) { /* 0904:左返回位图(调音台控件-1)+ 居中标题 + 细分隔线(06/07/08 一致) */ LCD_WR_PIC_Trans(UI0902_SUB_BACK_X, UI0902_SUB_BACK_Y, UI0902_SUB_BACK_W, UI0902_SUB_BACK_H, gImage_UI0902_TabBack_30x24, BLACK); LCD_ShowChinese_AutoAlign(0, 240, UI0902_SUB_TITLE_Y, title_gbk, LCD_ALIGN_CENTER, WHITE, UI0902_BG_COLOR, 0, UI0902_SUB_TITLE_SIZE); } void Draw_Bottom_Bar(uint8_t sel, uint8_t first_back) { /* 0924 完整图底栏:图标+两字合成图,左上角对齐图稿 */ const uint8_t *icons[4]; const uint16_t xs[4] = { 23, 76, 130, 183 }; const uint8_t ws[4] = { 33, 34, 33, 34 }; uint8_t i; (void)first_back; LCD_FillByColor(0, (uint16_t)(UI0902_SECTION_Y + UI0902_SECTION_BTN_H), 240, 320, UI0902_BG_COLOR); icons[0] = (sel == UI0902_NAV_UNIVERSAL) ? gImage_Nav_Universal_Sel : gImage_Nav_Universal_Not; icons[1] = (sel == UI0902_NAV_NORMAL) ? gImage_Nav_Normal_Sel : gImage_Nav_Normal_Not; icons[2] = (sel == UI0902_NAV_EXPERT) ? gImage_Nav_Expert_Sel : gImage_Nav_Expert_Not; icons[3] = (sel == UI0902_NAV_SETTING) ? gImage_Nav_Setting_Sel : gImage_Nav_Setting_Not; for (i = 0; i < 4; i++) { LCD_WR_PIC_Trans(xs[i], UI0902_NAV_ICON_Y, ws[i], UI0902_NAV_ICON_H, icons[i], BLACK); } } uint8_t UI_IsSettingsPage(void) { return (CurrUIProcress == UI_SystemSet_Process || CurrUIProcress == UI_Setting_Process || CurrUIProcress == UI_Mixer_Process || CurrUIProcress == UI_Language_Process || CurrUIProcress == UI_Restore_Select_Process || CurrUIProcress == UI_UsbModeConfirm_Process) ? 1u : 0u; } void UI_NavBar_Action(uint8_t value) { uint8_t want = 0xFFu; /* 进入设置:不停播、不改灯、不换库。底栏仍高亮「设置」。 */ if (value == UI0902_NAV_SETTING) { MenuPage_SystemSetting_Proc(); return; } if (value == UI0902_NAV_UNIVERSAL) want = 0; else if (value == UI0902_NAV_EXPERT) want = 1; else if (value == UI0902_NAV_NORMAL) want = 2; /* 从设置回到进入前的同一模式:只重绘演奏页 */ if (UI_IsSettingsPage() && want == (uint8_t)mGuiData[GUI_TAB_INDEX].Current) { switch (want) { case 0: MenuPage_SongMode_Proc(); break; case 1: MenuPage_ExpertMode_Proc(); break; case 2: MenuPage_FreeMode_Proc(); break; default: break; } return; } /* 离开万能后 LOOP_LIGHT 停更。未连 App 时普通/专业保持全绿;App 引导中不改灯。 */ if (!AppNeckLed_IsGuide()) app_tm1629_all_on(LED_COLOR_G); /* 先记住当前专业/普通的指板与移调,再停;勿再强制一级(会盖掉记忆供普通无指板拨片) */ Accomp_RememberPlayContext(); Stop_AutoBand(); switch (value) { case UI0902_NAV_UNIVERSAL: /* Init 内会设 TAB 并 ReloadTonePreset,勿在此前加载(会仍用旧 TAB) */ Accomp_ApplyDefaultIChord(); /* 万能拨片默认一级 */ MenuPage_SongMode_Proc(); break; case UI0902_NAV_NORMAL: MenuPage_FreeMode_Proc(); break; case UI0902_NAV_EXPERT: MenuPage_ExpertMode_Proc(); break; default: break; } } void label_Tab_Select_ui(const LEBEL_UI *UI,GUI_SWITCH * Group) { uint8_t sel = UI0902_NAV_NONE; (void)UI; (void)Group; /* GUI_TAB_INDEX:0万能 1专业 2普通 */ switch (mGuiData[GUI_TAB_INDEX].Current) { case 0: sel = UI0902_NAV_UNIVERSAL; break; case 1: sel = UI0902_NAV_EXPERT; break; case 2: sel = UI0902_NAV_NORMAL; break; default: break; } Draw_Bottom_Bar(sel, 0); } void Draw_Tab_Section_Menu(const LEBEL_UI *UI, TAB_SECTION_INDEX select_tab) { const uint16_t tab_w = UI0902_SECTION_BTN_W; const uint16_t tab_h = UI0902_SECTION_BTN_H; const uint8_t tab_r = UI0902_RADIUS_BTN; const uint8_t pitch = UI0902_SECTION_PITCH; const uint16_t start_x = UI0902_MARGIN_X; /* 0902-04:12/69/126/183 */ const uint16_t start_y = UI->y; /* 0819:演奏段落数字与标题同为 13,按钮内垂直居中(视觉重心 +1);稿面数字整体右移 2px */ const uint16_t text_y = start_y + (tab_h - UI0902_FONT_BODY) / 2 + 1; const char *nums[4] = {"1", "2", "3", "4"}; uint8_t i; for (i = 0; i < 4; i++) { uint16_t x = start_x + i * pitch; uint16_t bg = (select_tab == i) ? COLOR_GRAD_MID : UI0902_ROW_BG; LCD_FillRoundRect(x, start_y, tab_w, tab_h, tab_r, bg); LCD_ShowString_AutoAlign( (uint16_t)(x + 2), (uint16_t)(x + tab_w - 1 + 2), text_y, (const uint8_t*)nums[i], UI->Text_Align, WHITE, bg, 0, UI0902_FONT_BODY ); } } void GET_ParamNum_Str(int16_t NUM, uint8_t ValidLen, char* Str, uint8_t hasSign) { // int16_t Value; switch(hasSign) { case 0: switch(NUM) { case 0: strcpy(Str,"\xBC\xAA\xCB\xFB\xBE\xAD\xB5\xE4\xB7\xD6\xBD\xE2 4-4\0"); break; // 00 ��������ֽ�4-4 case 1: strcpy(Str,"\xBC\xAA\xCB\xFB\xBE\xAD\xB5\xE4\xC9\xA8\xCF\xD2 4-4\0"); break; // 01 ��������ɨ��4-4 case 2: strcpy(Str,"\xB8\xB4\xB9\xC5\xBC\xAA\xCB\xFB\xC1\xF7\xD0\xD0 4-4\0"); break; // 02 ���ż�������4-4 case 3: strcpy(Str,"\xC1\xF7\xD0\xD0\xD2\xA1\xB9\xF6 4-4\0"); break; // 03 ����ҡ��4-4 case 4: strcpy(Str,"\xBE\xAD\xB5\xE4\xC7\xE9\xB8\xE8 4-4\0"); break; // 04 �������4-4 case 5: strcpy(Str,"\xC1\xB5\xC8\xCB\xC7\xE9\xB8\xE8 2-4\0"); break; // 05 �������2-4 case 6: strcpy(Str,"\xCC\xF0\xC3\xDB\xC7\xE9\xB8\xE8 4-4\0"); break; // 06 �������4-4 case 7: strcpy(Str,"\xCE\xC2\xC7\xE9 8 \xC5\xC4 4-4\0"); break; // 07 ����8��4-4 case 8: strcpy(Str,"\xCE\xC2\xC8\xE1 8 \xC5\xC4 4-4\0"); break; // 08 ����8��4-4 case 9: strcpy(Str,"\xCF\xA6\xD1\xF4\xC1\xF7\xD0\xD0 4-4\0"); break; // 09 Ϧ������4-4 case 10: strcpy(Str,"\xCE\xF7\xB7\xBD\xCA\xEF\xB9\xE2 4-4\0"); break; // 10 �������4-4 case 11: strcpy(Str,"\xCE\xC2\xDC\xB0\xC3\xF1\xD2\xA5 4-4\0"); break; // 11 ��ܰ��ҥ4-4 case 12: strcpy(Str,"\xD6\xD0\xCB\xD9\xD2\xA1\xB9\xF6 4-4\0"); break; // 12 ����ҡ��4-4 case 13: strcpy(Str,"\xD6\xD0\xCB\xD9\xC7\xE9\xB8\xE8 4-4\0"); break; // 13 �������4-4 case 14: strcpy(Str,"\xCC\xF0\xC3\xC0 8 \xC5\xC4 4-4\0"); break; // 14 ����8��4-4 case 15: strcpy(Str,"\xC0\xD6\xB6\xD3 8 \xC5\xC4 4-4\0"); break; // 15 �ֶ�8��4-4 case 16: strcpy(Str,"8 \xC5\xC4\xBB\xB3\xBE\xC9 4-4\0"); break; // 16 8�Ļ���4-4 case 17: strcpy(Str,"\xCF\xC4\xC8\xD5\xB2\xA8\xC8\xF8 4-4\0"); break; // 17 ���ղ���4-4 case 18: strcpy(Str,"\xB7\xC5\xBF\xCB\xC2\xC9\xB6\xAF 4-4\0"); break; // 18 �ſ��ɶ�4-4 case 19: strcpy(Str,"\xB4\xF2\xB0\xF1\xD2\xA1\xB9\xF6\xC0\xD6 4-4\0"); break; // 19 ���ҡ����4-4 case 20: strcpy(Str,"\xC1\xF7\xD0\xD0\xD6\xD0\xCB\xD9\xB5\xAF\xB3\xAA 4-4\0"); break; // 20 �������ٵ���4-4 case 21: strcpy(Str,"\xC8\xF8\xBF\xCB\xCB\xB9 8 \xC5\xC4 4-4\0"); break; // 21 ����˹8��4-4 case 22: strcpy(Str,"\xB8\xD6\xC7\xD9\xC2\xC9\xB6\xAF 4-4\0"); break; // 22 �����ɶ�4-4 case 23: strcpy(Str,"\xBF\xE1\xEC\xC5\xD0\xF0\xCA\xC2\xC7\xFA 4-4\0"); break; // 23 ����������4-4 case 24: strcpy(Str,"\xBF\xEC\xC0\xD6\xC7\xA1\xC7\xA1 4-4\0"); break; // 24 ����ǡǡ4-4 case 25: strcpy(Str,"16 \xC5\xC4\xC7\xE9\xB8\xE8 4-4\0"); break; // 25 16�����4-4 case 26: strcpy(Str,"\xB1\xEA\xD7\xBC\xD0\xF0\xCA\xC2\xC7\xFA 6-8\0"); break; // 26 ��׼������6-8 case 27: strcpy(Str,"\xCC\xA8\xCD\xE5\xC1\xF7\xD0\xD0\xC0\xD6 4-4\0"); break; // 27 ̨��������4-4 case 28: strcpy(Str,"\xB5\xE7\xD3\xB0\xD0\xF0\xCA\xC2 6-8\0"); break; // 28 ��Ӱ����6-8 case 29: strcpy(Str,"\xCA\xA5\xB5\xAE\xD6\xAE\xD2\xB9 3-4\0"); break; // 29 ʥ��֮ҹ3-4 case 30: strcpy(Str,"\xBC\xAA\xCB\xFB\xB3\xAA\xD7\xF7\xD5\xDF 4-4\0"); break; // 30 ����������4-4 } return; break; case 1: /* EXPRESS_MODE_PARAM:本地曲目显示名(CSV→LocalSongNames.h) */ if (NUM >= 0 && NUM < LOCAL_SONG_COUNT) strcpy(Str, LocalSongNameGbk[NUM]); else sprintf(Str, "%d", NUM); return; break; case 2: switch(NUM) { case 0: memcpy(Str,"1645", 4); break; case 1: memcpy(Str,"1564", 4); break; case 2: memcpy(Str,"1364", 4); break; } return; break; } } void GET_Num_Str(int16_t NUM, uint8_t ValidLen, char* Str, uint8_t hasSign) { switch(hasSign) { case 0: switch(NUM) { case 0: memcpy(Str,"C\0", 2); break; case 1: memcpy(Str,"Db\0",3); break; case 2: memcpy(Str,"D\0", 2); break; case 3: memcpy(Str,"Eb\0",3); break; case 4: memcpy(Str,"E\0", 2); break; case 5: memcpy(Str,"F\0", 2); break; case 6: memcpy(Str,"Gb\0",3); break; case 7: memcpy(Str,"G\0", 2); break; case 8: memcpy(Str,"Ab\0",3); break; case 9: memcpy(Str,"A\0", 2); break; case 10: memcpy(Str,"Bb\0",3); break; case 11: memcpy(Str,"B\0", 2); break; } return; break; case 1: sprintf(Str, "%d", NUM); return; break; case 3: switch(NUM) { case 0: memcpy(Str,"\xD2\xF4\xC0\xD6\xBB\xE1\xB8\xD6\xCF\xD2\xBC\xAA\xCB\xFB\x00", 15); break; // 01 音乐会钢弦吉�? case 1: memcpy(Str,"\xBE\xAD\xB5\xE4\xB8\xD6\xCF\xD2\xBC\xAA\xCB\xFB\x00", 13); break; // 02 经典钢弦吉他 case 2: memcpy(Str,"\xBE\xAD\xB5\xE4\xC7\xE5\xD2\xF4\xBC\xAA\xCB\xFB\x00", 13); break; // 03 经典轻音吉他 case 3: memcpy(Str,"\xB8\xB4\xB9\xC5\xCA\xA7\xD5\xE6\xB5\xE7\xBC\xAA\xCB\xFB\x00",15); break; // 04 复古失真电吉�? case 4: memcpy(Str,"\xB5\xE7\xB1\xB4\xCB\xBE\x00", 7); break; // 05 电贝�? } return; break; case 5: sprintf(Str, "%d", NUM); return; break; case 6: sprintf(Str, "%d", NUM); return; break; case 7: sprintf(Str, "%d", NUM); return; break; } } void RefreshSystemSetting(uint8_t last_data,const LEBEL_UI *UI,GUI_SWITCH * Group) { Draw_System_Item_List(UI,Group); } /* 将自动关机索引应用到运行时计数器(手册:15/30/45/60/不自动) */ static void ApplyAutoCloseIndex(uint16_t idx) { AutoCloseFlag = true; switch (idx) { case 0: AutoCloseTime = 15; mAutoPowerOffCount = (uint16_t)(AutoCloseTime * 60); break; case 1: AutoCloseTime = 30; mAutoPowerOffCount = (uint16_t)(AutoCloseTime * 60); break; case 2: AutoCloseTime = 45; mAutoPowerOffCount = (uint16_t)(AutoCloseTime * 60); break; case 3: AutoCloseTime = 60; mAutoPowerOffCount = (uint16_t)(AutoCloseTime * 60); break; case 4: AutoCloseTime = 0; AutoCloseFlag = false; break; default: AutoCloseTime = 15; mAutoPowerOffCount = (uint16_t)(AutoCloseTime * 60); break; } } void RestoreSetting(void) { NvmParam_Type *nvm = drv_nvm_param_ptr(); /* 写入出厂静态参数到 RAM,并同步 GUI / 推子 */ drv_nvm_load_default_calibration_static_value(nvm, 1); mGuiData[GUI_PLAY_SECTION].Current = 0; mGuiData[GUI_AUTOBAND_SW].Current = 0; /* 出厂默认蓝牙开(已由 static_value 写入 NVM/GUI) */ mGuiData[GUI_BL_SW].Current = 1; nvm->param.BluetoothSW = 1; nvm->param.Language = NVM_LANG_CN; nvm->param.UsbMode = NVM_USB_MIDI; mGuiData_SystemSETTING[GUI_USB_MODE].Current = NVM_USB_MIDI; BSP_BlueToothPowerEnable(1); /* 出厂后的曲目是当前模式下的第 1 条,BPM 跟那一条 */ TrackBpm_Invalidate(); TrackBpm_SyncGui(); nvm->param.BPM = mGuiData[GUI_SPEED].Current; ApplyAutoCloseIndex(mGuiData_SystemSETTING[GUI_AUTOCLOSE].Current); /* 音色 / 音量 / 混响等到 DSP */ LoadConfig(); AutoBandTop1_ChangeBPM((int)mGuiData[GUI_SPEED].Current); /* 持久化到内部 Flash */ drv_nvm_save_to_flash(); LOG_I("NVM", "factory restore applied and saved"); } /* 文字是 1 位 MiSans(省内部 Flash);左右三角仍是 RGB565 */ static void UI_BlitSys(uint16_t x, uint16_t y, const SysSetGlyph *g) { uint16_t row, col, stride; if (!g || !g->data || !g->w || !g->h) return; if (g->mode) { LCD_WR_PIC_Trans(x, y, g->w, g->h, g->data, BLACK); return; } stride = (uint16_t)((g->w + 7u) >> 3); for (row = 0; row < g->h; row++) { const uint8_t *p = g->data + (uint32_t)row * stride; for (col = 0; col < g->w; col++) { if (p[col >> 3] & (uint8_t)(0x80u >> (col & 7))) LCD_DrawPoint((uint16_t)(x + col), (uint16_t)(y + row), WHITE); } } } static uint16_t UI_SysRowTextY(uint16_t row_y, uint8_t glyph_h) { if (glyph_h >= UI0902_SET_ROW_H) return row_y; return (uint16_t)(row_y + (UI0902_SET_ROW_H - glyph_h) / 2); } static const SysSetGlyph *UI_SysDigitGlyph(char ch) { switch (ch) { case '0': return &g_SysSet_D0; case '1': return &g_SysSet_D1; case '2': return &g_SysSet_D2; case '3': return &g_SysSet_D3; case '4': return &g_SysSet_D4; case '5': return &g_SysSet_D5; case '6': return &g_SysSet_D6; case '7': return &g_SysSet_D7; case '8': return &g_SysSet_D8; case '9': return &g_SysSet_D9; case '.': return &g_SysSet_Dot; default: return 0; } } static uint16_t UI_SysMeasure(const char *s) { uint16_t w = 0; while (s && *s) { const SysSetGlyph *g = UI_SysDigitGlyph(*s++); if (g) w = (uint16_t)(w + g->adv); } return w; } static void UI_SysDrawNum(uint16_t x, uint16_t y, const char *s) { while (s && *s) { const SysSetGlyph *g = UI_SysDigitGlyph(*s++); int16_t bx; if (!g) continue; bx = (int16_t)g->bear; if (bx < 0) bx = 0; UI_BlitSys((uint16_t)(x + bx), y, g); x = (uint16_t)(x + g->adv); } } static void UI_SysCenterInSlot(uint16_t row_y, const SysSetGlyph *g) { uint16_t x; uint16_t y; if (!g) return; x = SYSSET_VAL_X; if (g->w < SYSSET_VAL_W) x = (uint16_t)(SYSSET_VAL_X + (SYSSET_VAL_W - g->w) / 2); y = UI_SysRowTextY(row_y, g->h); UI_BlitSys(x, y, g); } static void UI_SysDrawSteppers(uint16_t row_y) { UI_BlitSys(SYSSET_TRIL_X, (uint16_t)(row_y + SYSSET_TRIL_DY), &g_SysSet_TriL); UI_BlitSys(SYSSET_TRIR_X, (uint16_t)(row_y + SYSSET_TRIR_DY), &g_SysSet_TriR); } void UI_DrawSystemSetHeader(void) { uint16_t tx = 0; if (g_SysSet_Title.w < 240) tx = (uint16_t)((240 - g_SysSet_Title.w) / 2); UI_BlitSys(SYSSET_BACK_X, SYSSET_BACK_Y, &g_SysSet_Back); UI_BlitSys(tx, SYSSET_TITLE_Y, &g_SysSet_Title); } void Draw_System_Item_List(const LEBEL_UI *UI, GUI_SWITCH *Group) { NvmParam_Type * nvm = drv_nvm_param_ptr(); char Info[16]; uint16_t row_w = UI->w ? UI->w : UI0902_SET_ROW_W; const SysSetGlyph *label = 0; /* 先清整块外接矩形为页面底色,避免焦点蓝圆角残留四角描边 */ LCD_FillByColor(UI->x, UI->y, (uint16_t)(UI->x + row_w), (uint16_t)(UI->y + UI->h), UI0902_BG_COLOR); if (UI_IsFocused(Group->Id)) { LCD_FillRoundRectGradient(UI->x, UI->y, row_w, UI->h, SYSSET_CARD_R, START_COLOR, END_COLOR); } else { LCD_FillRoundRect(UI->x, UI->y, row_w, UI->h, SYSSET_CARD_R, UI0902_ROW_BG); } switch (Group->Id) { case GUI_VERSION: label = &g_SysSet_LblVer; break; case GUI_USB_MODE: label = &g_SysSet_LblUsb; break; case GUI_AUTOCLOSE: label = &g_SysSet_LblAuto; break; case GUI_RESTORE: label = &g_SysSet_LblRestore; break; default: break; } if (label) UI_BlitSys((uint16_t)(UI->x + SYSSET_LABEL_DX), UI_SysRowTextY(UI->y, label->h), label); switch(Group->Id) { case GUI_VERSION: sprintf(Info, "%d.%d.%d", Version[0], Version[1], Version[2]); { const uint16_t pill_x = (uint16_t)(UI->x + SYSSET_PILL_DX); const uint16_t pill_y = (uint16_t)(UI->y + SYSSET_PILL_DY); uint16_t tw = UI_SysMeasure(Info); uint16_t tx = pill_x; if (tw < SYSSET_PILL_W) tx = (uint16_t)(pill_x + (SYSSET_PILL_W - tw) / 2); LCD_FillRoundRect(pill_x, pill_y, SYSSET_PILL_W, SYSSET_PILL_H, SYSSET_PILL_R, UI0902_BG_COLOR); UI_SysDrawNum(tx, (uint16_t)(pill_y + (SYSSET_PILL_H - g_SysSet_D0.h) / 2), Info); } break; case GUI_USB_MODE: UI_SysDrawSteppers(UI->y); UI_SysCenterInSlot(UI->y, Group->Current ? &g_SysSet_UsbUpdate : &g_SysSet_UsbMidi); break; case GUI_AUTOCLOSE: ApplyAutoCloseIndex(Group->Current); UI_SysDrawSteppers(UI->y); if (Group->Current != 4) { uint16_t tw; uint16_t tx = SYSSET_VAL_X; sprintf(Info, "%d", AutoCloseTime); tw = UI_SysMeasure(Info); if (tw < SYSSET_VAL_W) tx = (uint16_t)(SYSSET_VAL_X + (SYSSET_VAL_W - tw) / 2); UI_SysDrawNum(tx, UI_SysRowTextY(UI->y, g_SysSet_D0.h), Info); } else { UI_SysCenterInSlot(UI->y, &g_SysSet_Off); } nvm->param.AutoCloseTime = Group->Current; drv_nvm_save_to_flash(); break; case GUI_RESTORE: UI_BlitSys(SYSSET_CHEV_X, SYSSET_CHEV_Y, &g_SysSet_Chevron); break; default: break; } }