K1Guitar/UI/UI_global.c

1564 lines
52 KiB
C
Raw Normal View History

#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);
}
// ==============================================
// <20><><EFBFBD><EFBFBD>ָ<EFBFBD>붨<EFBFBD><EBB6A8>
// ==============================================
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);
// ==============================================
// <20><>ʼ<EFBFBD><CABC><EFBFBD>˵<EFBFBD>UI
// ==============================================
void InitMenuUI(void) {
MenuPage_Idle_Proc = CallUI_Idle; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
MenuPage_SongMode_Proc = CallUI_SongMode; // <20><><EFBFBD><EFBFBD>ģʽ
MenuPage_ExpertMode_Proc = CallUI_ExpertMode; // ר<><D7A8>ģʽ
MenuPage_FreeMode_Proc = CallUI_FreeMode; // <20><><EFBFBD><EFBFBD>ģʽ
MenuPage_Setting_Proc = CallUI_Setting; // <20><><EFBFBD><EFBFBD>
MenuPage_SystemSetting_Proc = CallUI_SystemSetting; // ϵͳ<CFB5><CDB3><EFBFBD><EFBFBD>
MenuPage_Mixer_Proc = CallUI_Mixer; //<2F><><EFBFBD><EFBFBD>̨
MenuPage_Restore_Proc = CallUI_RestoreSelect;
MenuPage_Language_Proc = CallUI_Language;
MenuPage_UsbConfirm_Proc = CallUI_UsbConfirm;
}
// ==============================================
// <20><><EFBFBD><EFBFBD> UI <20><><EFBFBD>ú<EFBFBD><C3BA><EFBFBD>
// ==============================================
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, // 坐标<E59D90>?
// 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;
// 重点:根<EFBC9A>?当前选中的模式(2/3/4)找到<E689BE>?<3F>应数据结构<E7BB93>?
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
);
// 显示当前选中模式的真实数<E5AE9E>?
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);
}
}
void UI_NavBar_Action(uint8_t value)
{
/* 离开万能后 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;
case UI0902_NAV_SETTING:
MenuPage_SystemSetting_Proc(); /* 0902-11 设置入口:调音台/系统设置 */
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 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֽ<EFBFBD>4-4
case 1: strcpy(Str,"\xBC\xAA\xCB\xFB\xBE\xAD\xB5\xE4\xC9\xA8\xCF\xD2 4-4\0"); break; // 01 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɨ<EFBFBD><C9A8>4-4
case 2: strcpy(Str,"\xB8\xB4\xB9\xC5\xBC\xAA\xCB\xFB\xC1\xF7\xD0\xD0 4-4\0"); break; // 02 <20><><EFBFBD>ż<EFBFBD><C5BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>4-4
case 3: strcpy(Str,"\xC1\xF7\xD0\xD0\xD2\xA1\xB9\xF6 4-4\0"); break; // 03 <20><><EFBFBD><EFBFBD>ҡ<EFBFBD><D2A1>4-4
case 4: strcpy(Str,"\xBE\xAD\xB5\xE4\xC7\xE9\xB8\xE8 4-4\0"); break; // 04 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>4-4
case 5: strcpy(Str,"\xC1\xB5\xC8\xCB\xC7\xE9\xB8\xE8 2-4\0"); break; // 05 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2-4
case 6: strcpy(Str,"\xCC\xF0\xC3\xDB\xC7\xE9\xB8\xE8 4-4\0"); break; // 06 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>4-4
case 7: strcpy(Str,"\xCE\xC2\xC7\xE9 8 \xC5\xC4 4-4\0"); break; // 07 <20><><EFBFBD><EFBFBD>8<EFBFBD><38>4-4
case 8: strcpy(Str,"\xCE\xC2\xC8\xE1 8 \xC5\xC4 4-4\0"); break; // 08 <20><><EFBFBD><EFBFBD>8<EFBFBD><38>4-4
case 9: strcpy(Str,"\xCF\xA6\xD1\xF4\xC1\xF7\xD0\xD0 4-4\0"); break; // 09 Ϧ<><CFA6><EFBFBD><EFBFBD><EFBFBD><EFBFBD>4-4
case 10: strcpy(Str,"\xCE\xF7\xB7\xBD\xCA\xEF\xB9\xE2 4-4\0"); break; // 10 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>4-4
case 11: strcpy(Str,"\xCE\xC2\xDC\xB0\xC3\xF1\xD2\xA5 4-4\0"); break; // 11 <20><>ܰ<EFBFBD><DCB0>ҥ4-4
case 12: strcpy(Str,"\xD6\xD0\xCB\xD9\xD2\xA1\xB9\xF6 4-4\0"); break; // 12 <20><><EFBFBD><EFBFBD>ҡ<EFBFBD><D2A1>4-4
case 13: strcpy(Str,"\xD6\xD0\xCB\xD9\xC7\xE9\xB8\xE8 4-4\0"); break; // 13 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>4-4
case 14: strcpy(Str,"\xCC\xF0\xC3\xC0 8 \xC5\xC4 4-4\0"); break; // 14 <20><><EFBFBD><EFBFBD>8<EFBFBD><38>4-4
case 15: strcpy(Str,"\xC0\xD6\xB6\xD3 8 \xC5\xC4 4-4\0"); break; // 15 <20>ֶ<EFBFBD>8<EFBFBD><38>4-4
case 16: strcpy(Str,"8 \xC5\xC4\xBB\xB3\xBE\xC9 4-4\0"); break; // 16 8<>Ļ<EFBFBD><C4BB><EFBFBD>4-4
case 17: strcpy(Str,"\xCF\xC4\xC8\xD5\xB2\xA8\xC8\xF8 4-4\0"); break; // 17 <20><><EFBFBD>ղ<EFBFBD><D5B2><EFBFBD>4-4
case 18: strcpy(Str,"\xB7\xC5\xBF\xCB\xC2\xC9\xB6\xAF 4-4\0"); break; // 18 <20>ſ<EFBFBD><C5BF>ɶ<EFBFBD>4-4
case 19: strcpy(Str,"\xB4\xF2\xB0\xF1\xD2\xA1\xB9\xF6\xC0\xD6 4-4\0"); break; // 19 <20><><EFBFBD>ҡ<EFBFBD><D2A1><EFBFBD><EFBFBD>4-4
case 20: strcpy(Str,"\xC1\xF7\xD0\xD0\xD6\xD0\xCB\xD9\xB5\xAF\xB3\xAA 4-4\0"); break; // 20 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ٵ<EFBFBD><D9B5><EFBFBD>4-4
case 21: strcpy(Str,"\xC8\xF8\xBF\xCB\xCB\xB9 8 \xC5\xC4 4-4\0"); break; // 21 <20><><EFBFBD><EFBFBD>˹8<CBB9><38>4-4
case 22: strcpy(Str,"\xB8\xD6\xC7\xD9\xC2\xC9\xB6\xAF 4-4\0"); break; // 22 <20><><EFBFBD><EFBFBD><EFBFBD>ɶ<EFBFBD>4-4
case 23: strcpy(Str,"\xBF\xE1\xEC\xC5\xD0\xF0\xCA\xC2\xC7\xFA 4-4\0"); break; // 23 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>4-4
case 24: strcpy(Str,"\xBF\xEC\xC0\xD6\xC7\xA1\xC7\xA1 4-4\0"); break; // 24 <20><><EFBFBD><EFBFBD>ǡǡ4-4
case 25: strcpy(Str,"16 \xC5\xC4\xC7\xE9\xB8\xE8 4-4\0"); break; // 25 16<31><36><EFBFBD><EFBFBD><EFBFBD>4-4
case 26: strcpy(Str,"\xB1\xEA\xD7\xBC\xD0\xF0\xCA\xC2\xC7\xFA 6-8\0"); break; // 26 <20><>׼<EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>6-8
case 27: strcpy(Str,"\xCC\xA8\xCD\xE5\xC1\xF7\xD0\xD0\xC0\xD6 4-4\0"); break; // 27 ̨<><CCA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>4-4
case 28: strcpy(Str,"\xB5\xE7\xD3\xB0\xD0\xF0\xCA\xC2 6-8\0"); break; // 28 <20><>Ӱ<EFBFBD><D3B0><EFBFBD><EFBFBD>6-8
case 29: strcpy(Str,"\xCA\xA5\xB5\xAE\xD6\xAE\xD2\xB9 3-4\0"); break; // 29 ʥ<><CAA5>֮ҹ3-4
case 30: strcpy(Str,"\xBC\xAA\xCB\xFB\xB3\xAA\xD7\xF7\xD5\xDF 4-4\0"); break; // 30 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>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 音乐会钢弦吉<E5BCA6>?
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 复古失真电吉<E794B5>?
case 4: memcpy(Str,"\xB5\xE7\xB1\xB4\xCB\xBE\x00", 7); break; // 05 电贝<E794B5>?
}
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;
}
}