K1Guitar/UI/UI_global.c

1563 lines
52 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "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 停更,须显式恢复全绿(旧 GUI_TAB_INDEX 路径曾做) */
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;
}
}