diff --git a/src/core/Frontend.cpp b/src/core/Frontend.cpp index d82c5df4..0c813cbb 100644 --- a/src/core/Frontend.cpp +++ b/src/core/Frontend.cpp @@ -36,6 +36,7 @@ #include "Stats.h" #include "Messages.h" #include "FileLoader.h" +#include "frontendoption.h" #define TIDY_UP_PBP // ProcessButtonPresses #define MAX_VISIBLE_LIST_ROW 30 @@ -431,12 +432,46 @@ CMenuManager::ThingsToDoBeforeGoingBack() if ((m_nCurrScreen == MENUPAGE_SKIN_SELECT) || (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS)) { m_nTotalListRow = 0; } + +#ifdef CUSTOM_FRONTEND_OPTIONS + for (int i = 0; i < numCustomFrontendOptions; i++) { + FrontendOption &option = customFrontendOptions[i]; + if (option.type != FEOPTION_REDIRECT && option.type != FEOPTION_GOBACK && m_nCurrScreen == option.screen) { + if (option.returnPrevPageFunc) + option.returnPrevPageFunc(); + + if (m_nCurrOption == option.screenOptionOrder && option.type == FEOPTION_DYNAMIC) + option.buttonPressFunc(FEOPTION_ACTION_FOCUSLOSS); + + if (option.onlyApplyOnEnter) + option.displayedValue = *option.value; + } + } +#endif } int8 CMenuManager::GetPreviousPageOption() { +#ifndef CUSTOM_FRONTEND_OPTIONS return !m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_ParentEntry[1] : aScreens[m_nCurrScreen].m_ParentEntry[0]; +#else + int8 prevPage = !m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_PreviousPage[1] : aScreens[m_nCurrScreen].m_PreviousPage[0]; + + if (prevPage == -1) // Game also does same + return 0; + + prevPage = prevPage == MENUPAGE_NONE ? (!m_bGameNotLoaded ? MENUPAGE_PAUSE_MENU : MENUPAGE_START_MENU) : prevPage; + + for (int i = 0; i < NUM_MENUROWS; i++) { + if (aScreens[prevPage].m_aEntries[i].m_TargetMenu == m_nCurrScreen) { + return i; + } + } + + // Couldn't find current screen option on previous page, use default behaviour (maybe save-related screen?) + return !m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_ParentEntry[1] : aScreens[m_nCurrScreen].m_ParentEntry[0]; +#endif } // ------ Functions not in the game/inlined ends @@ -955,7 +990,14 @@ CMenuManager::Draw() } #endif +#ifdef CUSTOM_FRONTEND_OPTIONS + static int lastOption = m_nCurrOption; +#endif + for (int i = 0; i < NUM_MENUROWS; ++i) { +#ifdef CUSTOM_FRONTEND_OPTIONS + bool isOptionDisabled = false; +#endif if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action != MENUACTION_LABEL && aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName[0] != '\0') { wchar *rightText = nil; wchar *leftText; @@ -1232,6 +1274,29 @@ CMenuManager::Draw() rightText = TheText.Get(gPS2alphaTest ? "FEM_ON" : "FEM_OFF"); break; #endif +#ifdef CUSTOM_FRONTEND_OPTIONS + case MENUACTION_TRIGGERFUNC: + FrontendOption& option = customFrontendOptions[aScreens[m_nCurrScreen].m_aEntries[i].m_TargetMenu]; + if (m_nCurrScreen == option.screen && i == option.screenOptionOrder) { + leftText = (wchar*)option.leftText; + if (option.type == FEOPTION_SELECT) { + if (option.displayedValue >= option.numRightTexts || option.displayedValue < 0) + option.displayedValue = 0; + + rightText = (wchar*)option.rightTexts[option.displayedValue]; + + } else if (option.type == FEOPTION_DYNAMIC) { + if (option.drawFunc) { + rightText = option.drawFunc(&isOptionDisabled); + } + } + } else { + debug("A- screen:%d option:%d - totalCo: %d, coId: %d, coScreen:%d, coOption:%d\n", m_nCurrScreen, i, numCustomFrontendOptions, aScreens[m_nCurrScreen].m_aEntries[i].m_TargetMenu, option.screen, option.screenOptionOrder); + assert(0 && "Custom frontend options is borked"); + } + + break; +#endif } float nextItemY = headerHeight + nextYToUse; @@ -1318,7 +1383,11 @@ CMenuManager::Draw() || !strcmp(aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName, "FED_AAS") #endif ) - && !m_bGameNotLoaded) + && !m_bGameNotLoaded +#ifdef CUSTOM_FRONTEND_OPTIONS + || isOptionDisabled +#endif + ) CFont::SetColor(CRGBA(155, 117, 6, FadeIn(255))); CFont::PrintString(MENU_X_RIGHT_ALIGNED(columnWidth - textLayer), itemY, rightText); @@ -1428,6 +1497,20 @@ CMenuManager::Draw() } #endif +#ifdef CUSTOM_FRONTEND_OPTIONS + if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action == MENUACTION_TRIGGERFUNC) { + FrontendOption &option = customFrontendOptions[aScreens[m_nCurrScreen].m_aEntries[i].m_TargetMenu]; + if (option.onlyApplyOnEnter && m_nCurrOption != i) + option.displayedValue = *option.value; + + if (m_nCurrOption != lastOption && lastOption == i) { + FrontendOption &oldOption = customFrontendOptions[aScreens[m_nCurrScreen].m_aEntries[lastOption].m_TargetMenu]; + if (oldOption.type == FEOPTION_DYNAMIC) + oldOption.buttonPressFunc(FEOPTION_ACTION_FOCUSLOSS); + } + } +#endif + // Sliders int lastActiveBarX; switch (aScreens[m_nCurrScreen].m_aEntries[i].m_Action) { @@ -1471,6 +1554,10 @@ CMenuManager::Draw() } } +#ifdef CUSTOM_FRONTEND_OPTIONS + lastOption = m_nCurrOption; +#endif + switch (m_nCurrScreen) { case MENUPAGE_CONTROLLER_SETTINGS: case MENUPAGE_SOUND_SETTINGS: @@ -3128,6 +3215,10 @@ CMenuManager::InitialiseChangedLanguageSettings() default: break; } + +#ifdef CUSTOM_FRONTEND_OPTIONS + CustomFrontendOptionsPopulate(); +#endif } } @@ -5005,6 +5096,33 @@ CMenuManager::ProcessButtonPresses(void) RequestFrontEndShutDown(); RetryMission(2, 0); return; +#endif +#ifdef CUSTOM_FRONTEND_OPTIONS + case MENUACTION_TRIGGERFUNC: + FrontendOption& option = customFrontendOptions[aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu]; + if (m_nCurrScreen == option.screen && m_nCurrOption == option.screenOptionOrder) { + if (option.type == FEOPTION_SELECT) { + if (!option.onlyApplyOnEnter) { + option.displayedValue++; + if (option.displayedValue >= option.numRightTexts || option.displayedValue < 0) + option.displayedValue = 0; + } + option.changeFunc(option.displayedValue); + *option.value = option.displayedValue; + + } else if (option.type == FEOPTION_DYNAMIC) { + option.buttonPressFunc(FEOPTION_ACTION_SELECT); + } else if (option.type == FEOPTION_REDIRECT) { + ChangeScreen(option.to, option.option, true, option.fadeIn); + } else if (option.type == FEOPTION_GOBACK) { + goBack = true; + } + } else { + debug("B- screen:%d option:%d - totalCo: %d, coId: %d, coScreen:%d, coOption:%d\n", m_nCurrScreen, m_nCurrOption, numCustomFrontendOptions, aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu, option.screen, option.screenOptionOrder); + assert(0 && "Custom frontend options are borked"); + } + + break; #endif } } @@ -5236,6 +5354,36 @@ CMenuManager::ProcessButtonPresses(void) DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); SaveSettings(); break; +#ifdef CUSTOM_FRONTEND_OPTIONS + case MENUACTION_TRIGGERFUNC: + FrontendOption& option = customFrontendOptions[aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu]; + if (m_nCurrScreen == option.screen && m_nCurrOption == option.screenOptionOrder) { + if (option.type == FEOPTION_SELECT) { + if (changeValueBy > 0) { + option.displayedValue++; + if (option.displayedValue >= option.numRightTexts) + option.displayedValue = 0; + } else { + option.displayedValue--; + if (option.displayedValue < 0) + option.displayedValue = option.numRightTexts - 1; + } + if (!option.onlyApplyOnEnter) { + option.changeFunc(option.displayedValue); + *option.value = option.displayedValue; + } + } else if (option.type == FEOPTION_DYNAMIC) { + option.buttonPressFunc(changeValueBy > 0 ? FEOPTION_ACTION_RIGHT : FEOPTION_ACTION_LEFT); + } + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); + } + else { + debug("C- screen:%d option:%d - totalCo: %d, coId: %d, coScreen:%d, coOption:%d\n", m_nCurrScreen, m_nCurrOption, numCustomFrontendOptions, aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu, option.screen, option.screenOptionOrder); + assert(0 && "Custom frontend options are borked"); + } + + break; +#endif } ProcessOnOffMenuOptions(); if (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) { diff --git a/src/core/Frontend.h b/src/core/Frontend.h index cf112b3d..fa3652a1 100644 --- a/src/core/Frontend.h +++ b/src/core/Frontend.h @@ -381,6 +381,9 @@ enum eMenuAction #ifdef CUTSCENE_BORDERS_SWITCH MENUACTION_CUTSCENEBORDERS, #endif +#ifdef CUSTOM_FRONTEND_OPTIONS + MENUACTION_TRIGGERFUNC +#endif }; enum eCheckHover @@ -475,7 +478,7 @@ struct CMenuScreen int32 m_Action; // eMenuAction char m_EntryName[8]; int32 m_SaveSlot; // eSaveSlot - int32 m_TargetMenu; // eMenuScreen + int32 m_TargetMenu; // eMenuScreen // FrontendOption ID if it's a custom option } m_aEntries[NUM_MENUROWS]; }; diff --git a/src/core/Game.cpp b/src/core/Game.cpp index a95c479a..c0530709 100644 --- a/src/core/Game.cpp +++ b/src/core/Game.cpp @@ -86,6 +86,7 @@ #include "ZoneCull.h" #include "Zones.h" #include "debugmenu.h" +#include "frontendoption.h" #include "postfx.h" #include "custompipes.h" @@ -292,6 +293,10 @@ bool CGame::InitialiseOnceAfterRW(void) DMAudio.SetEffectsFadeVol(127); DMAudio.SetMusicFadeVol(127); CWorld::Players[0].SetPlayerSkin(CMenuManager::m_PrefsSkinFile); + +#ifdef CUSTOM_FRONTEND_OPTIONS + CustomFrontendOptionsPopulate(); +#endif return true; } diff --git a/src/core/MenuScreens.cpp b/src/core/MenuScreens.cpp index 5dfcc8fe..02c004b3 100644 --- a/src/core/MenuScreens.cpp +++ b/src/core/MenuScreens.cpp @@ -2,6 +2,8 @@ #include "Frontend.h" #ifdef PC_MENU +// If you want to add new options, please don't do that here and see CustomFrontendOptionsPopulate in re3.cpp. + #ifdef CUTSCENE_BORDERS_SWITCH #define MENU_CUTSCENE_BORDERS_SWITCH(screen) MENUACTION_CUTSCENEBORDERS, "FEM_CSB", SAVESLOT_NONE, screen, #else @@ -45,11 +47,7 @@ CMenuScreen aScreens[] = { { "", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, }, // MENUPAGE_STATS = 1 -#ifdef MENU_MAP - { "FET_STA", 1, MENUPAGE_NONE, MENUPAGE_NONE, 5, 3, -#else { "FET_STA", 1, MENUPAGE_NONE, MENUPAGE_NONE, 5, 2, -#endif MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, }, @@ -62,11 +60,7 @@ CMenuScreen aScreens[] = { }, // MENUPAGE_BRIEFS = 3 -#ifdef MENU_MAP - { "FET_BRE", 1, MENUPAGE_NONE, MENUPAGE_NONE, 6, 4, -#else { "FET_BRE", 1, MENUPAGE_NONE, MENUPAGE_NONE, 6, 3, -#endif MENUACTION_CHANGEMENU, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, }, @@ -381,11 +375,7 @@ CMenuScreen aScreens[] = { }, // MENUPAGE_OPTIONS = 41 -#ifdef MENU_MAP - { "FET_OPT", 1, MENUPAGE_NONE, MENUPAGE_NONE, 1, 5, -#else { "FET_OPT", 1, MENUPAGE_NONE, MENUPAGE_NONE, 1, 4, -#endif MENUACTION_CHANGEMENU, "FET_CTL", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, MENUACTION_LOADRADIO, "FET_AUD", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, MENUACTION_CHANGEMENU, "FET_DIS", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, @@ -398,11 +388,7 @@ CMenuScreen aScreens[] = { }, // MENUPAGE_EXIT = 42 -#ifdef MENU_MAP - { "FET_QG", 1, MENUPAGE_NONE, MENUPAGE_NONE, 2, 6, -#else { "FET_QG", 1, MENUPAGE_NONE, MENUPAGE_NONE, 2, 5, -#endif MENUACTION_LABEL, "FEQ_SRE", SAVESLOT_NONE, MENUPAGE_NONE, MENUACTION_DONTCANCEL, "FEM_NO", SAVESLOT_NONE, MENUPAGE_NONE, MENUACTION_CANCELGAME, "FEM_YES", SAVESLOT_NONE, MENUPAGE_NONE, @@ -463,9 +449,6 @@ CMenuScreen aScreens[] = { { "FET_PAU", 1, MENUPAGE_DISABLED, MENUPAGE_DISABLED, 0, 0, MENUACTION_RESUME, "FEM_RES", SAVESLOT_NONE, MENUPAGE_NONE, MENUACTION_CHANGEMENU, "FEN_STA", SAVESLOT_NONE, MENUPAGE_NEW_GAME, -#ifdef MENU_MAP - MENUACTION_CHANGEMENU, "FEG_MAP", SAVESLOT_NONE, MENUPAGE_MAP, -#endif MENUACTION_CHANGEMENU, "FEP_STA", SAVESLOT_NONE, MENUPAGE_STATS, MENUACTION_CHANGEMENU, "FEP_BRI", SAVESLOT_NONE, MENUPAGE_BRIEFS, MENUACTION_CHANGEMENU, "FET_OPT", SAVESLOT_NONE, MENUPAGE_OPTIONS, diff --git a/src/core/config.h b/src/core/config.h index 8e91853d..ac3ba109 100644 --- a/src/core/config.h +++ b/src/core/config.h @@ -248,6 +248,7 @@ enum Config { # define SCROLLABLE_STATS_PAGE // only draggable by mouse atm # define TRIANGLE_BACK_BUTTON //# define CIRCLE_BACK_BUTTON +# define CUSTOM_FRONTEND_OPTIONS # define GRAPHICS_MENU_OPTIONS #endif diff --git a/src/core/re3.cpp b/src/core/re3.cpp index 27ec336d..dd116e27 100644 --- a/src/core/re3.cpp +++ b/src/core/re3.cpp @@ -71,6 +71,115 @@ mysrand(unsigned int seed) myrand_seed = seed; } +#ifdef CUSTOM_FRONTEND_OPTIONS +#include "frontendoption.h" +#include "platform.h" + +void ReloadFrontendOptions(void) +{ + CustomFrontendOptionsPopulate(); +} + +#ifdef MORE_LANGUAGES +void LangPolSelect(int8 action) +{ + if (action == FEOPTION_ACTION_SELECT) { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_POLISH; + FrontEndMenuManager.m_bFrontEnd_ReloadObrTxtGxt = true; + FrontEndMenuManager.InitialiseChangedLanguageSettings(); + FrontEndMenuManager.SaveSettings(); + } +} + +void LangRusSelect(int8 action) +{ + if (action == FEOPTION_ACTION_SELECT) { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_RUSSIAN; + FrontEndMenuManager.m_bFrontEnd_ReloadObrTxtGxt = true; + FrontEndMenuManager.InitialiseChangedLanguageSettings(); + FrontEndMenuManager.SaveSettings(); + } +} + +void LangJapSelect(int8 action) +{ + if (action == FEOPTION_ACTION_SELECT) { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_JAPANESE; + FrontEndMenuManager.m_bFrontEnd_ReloadObrTxtGxt = true; + FrontEndMenuManager.InitialiseChangedLanguageSettings(); + FrontEndMenuManager.SaveSettings(); + } +} +#endif + +/*#ifdef IMPROVED_VIDEOMODE +void ScreenModeChange(int8 displayedValue) +{ + if (displayedValue != FrontEndMenuManager.m_nPrefsWindowed) { + FrontEndMenuManager.m_nPrefsWindowed = displayedValue; + _psSelectScreenVM(FrontEndMenuManager.m_nPrefsVideoMode); // apply same resolution + FrontEndMenuManager.SetHelperText(0); + FrontEndMenuManager.SaveSettings(); + } +} +#endif*/ + +#ifdef FREE_CAM +void ToggleFreeCam(int8 action) +{ + if (action == FEOPTION_ACTION_SELECT) { + TheCamera.bFreeCam = !TheCamera.bFreeCam; + FrontEndMenuManager.SaveSettings(); + } +} +#endif + +//#ifdef CUTSCENE_BORDERS_SWITCH +//void BorderModeChange(int8 displayedValue) +//{ +// CMenuManager::m_PrefsCutsceneBorders = !!displayedValue; +// FrontEndMenuManager.SaveSettings(); +//} +//#endif + +// Reloaded on language change, so you can use hardcoded wchar* and TheText.Get with peace of mind +void +CustomFrontendOptionsPopulate(void) +{ + RemoveCustomFrontendOptions(); // if exist + +#ifdef MORE_LANGUAGES + FrontendOptionSetPosition(MENUPAGE_LANGUAGE_SETTINGS); + FrontendOptionAddDynamic(TheText.Get("FEL_POL"), nil, LangPolSelect, nil); + FrontendOptionAddDynamic(TheText.Get("FEL_RUS"), nil, LangRusSelect, nil); + FrontendOptionAddDynamic(TheText.Get("FEL_JAP"), nil, LangJapSelect, nil); +#endif + +/*#ifdef IMPROVED_VIDEOMODE + static const wchar *screenModes[] = { (wchar*)L"FULLSCREEN", (wchar*)L"WINDOWED" }; + FrontendOptionSetPosition(MENUPAGE_GRAPHICS_SETTINGS, 8); + FrontendOptionAddSelect(TheText.Get("SCRFOR"), screenModes, 2, (int8*)&FrontEndMenuManager.m_nPrefsWindowed, true, ScreenModeChange, nil); +#endif*/ + +#ifdef MENU_MAP + FrontendOptionSetPosition(MENUPAGE_PAUSE_MENU, 2); + FrontendOptionAddRedirect(TheText.Get("FEG_MAP"), MENUPAGE_MAP); +#endif + +#ifdef FREE_CAM + static const wchar *text = (wchar*)L"TOGGLE FREE CAM"; + FrontendOptionSetPosition(MENUPAGE_CONTROLLER_PC, 1); + FrontendOptionAddDynamic(text, nil, ToggleFreeCam, nil); +#endif + +/*#ifdef CUTSCENE_BORDERS_SWITCH + static const wchar *off_on[] = { TheText.Get("FEM_OFF"), TheText.Get("FEM_ON") }; + FrontendOptionSetPosition(MENUPAGE_DISPLAY_SETTINGS, 3); + FrontendOptionAddSelect((const wchar *)L"CUTSCENE BORDERS", off_on, 2, (int8 *)&CMenuManager::m_PrefsCutsceneBorders, false, BorderModeChange, nil); +#endif*/ +} +#endif + #ifdef DEBUGMENU void WeaponCheat(); void HealthCheat(); @@ -405,6 +514,9 @@ DebugMenuPopulate(void) DebugMenuAddCmd("Debug", "Catalina Fly Away", CHeli::MakeCatalinaHeliFlyAway); DebugMenuAddVarBool8("Debug", "Script Heli On", &CHeli::ScriptHeliOn, nil); +#ifdef CUSTOM_FRONTEND_OPTIONS + DebugMenuAddCmd("Debug", "Reload custom frontend options", ReloadFrontendOptions); +#endif DebugMenuAddVarBool8("Debug", "Toggle popping heads on headshot", &CPed::bPopHeadsOnHeadshot, nil); DebugMenuAddCmd("Debug", "Start Credits", CCredits::Start); DebugMenuAddCmd("Debug", "Stop Credits", CCredits::Stop); diff --git a/src/extras/frontendoption.cpp b/src/extras/frontendoption.cpp new file mode 100644 index 00000000..51814f35 --- /dev/null +++ b/src/extras/frontendoption.cpp @@ -0,0 +1,173 @@ +#include "common.h" + +#ifdef CUSTOM_FRONTEND_OPTIONS +#include "frontendoption.h" + +int numCustomFrontendOptions = 0; +FrontendOption *customFrontendOptions; + +int optionCursor = -1; +eMenuScreen currentMenu; + +void ChangeScreen(eMenuScreen screen, int option, bool fadeIn) +{ + FrontEndMenuManager.m_nPrevScreen = FrontEndMenuManager.m_nCurrScreen; + FrontEndMenuManager.m_nCurrScreen = screen; + FrontEndMenuManager.m_nCurrOption = option; + if (fadeIn) + FrontEndMenuManager.m_nMenuFadeAlpha = 0; +} + +void GoBack(bool fadeIn) +{ + int screen = !FrontEndMenuManager.m_bGameNotLoaded ? + aScreens[FrontEndMenuManager.m_nCurrScreen].m_PreviousPage[1] : aScreens[FrontEndMenuManager.m_nCurrScreen].m_PreviousPage[0]; + int option = !FrontEndMenuManager.m_bGameNotLoaded ? + aScreens[FrontEndMenuManager.m_nCurrScreen].m_ParentEntry[1] : aScreens[FrontEndMenuManager.m_nCurrScreen].m_ParentEntry[0]; + + FrontEndMenuManager.ThingsToDoBeforeGoingBack(); + + ChangeScreen((eMenuScreen)screen, option, fadeIn); +} + +uint8 +GetNumberOfMenuOptions(int screen) +{ + uint8 Rows = 0; + for (int i = 0; i < NUM_MENUROWS; i++) { + if (aScreens[screen].m_aEntries[i].m_Action == MENUACTION_NOTHING) + break; + + ++Rows; + } + return Rows; +} + +// Used before populating options, but effective in InitialiseChangedLanguageSettings and debugmenu +void +RemoveCustomFrontendOptions() +{ + if (numCustomFrontendOptions == 0) + return; + + for (int i = 0; i < MENUPAGES; i++) { + for (int j = 0; j < NUM_MENUROWS; j++) { + if (aScreens[i].m_aEntries[j].m_Action == MENUACTION_TRIGGERFUNC) { + int k; + for (k = j; k < NUM_MENUROWS-1; k++) { + memcpy(&aScreens[i].m_aEntries[k], &aScreens[i].m_aEntries[k+1], sizeof(CMenuScreen::CMenuEntry)); + } + aScreens[i].m_aEntries[k].m_Action = MENUACTION_NOTHING; + aScreens[i].m_aEntries[k].m_EntryName[0] = '\0'; + j--; + } + } + } + free(customFrontendOptions); + numCustomFrontendOptions = 0; +} + +int8 RegisterNewOption(int screen) +{ + numCustomFrontendOptions++; + if (numCustomFrontendOptions == 1) + customFrontendOptions = (FrontendOption*)malloc(5 * sizeof(FrontendOption)); + else if (numCustomFrontendOptions % 5 == 1) + customFrontendOptions = (FrontendOption*)realloc(customFrontendOptions, (numCustomFrontendOptions + 4) * sizeof(FrontendOption)); + + assert(customFrontendOptions != nil && "Custom frontend options can't be allocated"); + + uint8 nth = GetNumberOfMenuOptions(screen); + if (optionCursor < 0) { + if (optionCursor == -1) { + if (!strcmp(aScreens[screen].m_aEntries[nth - 1].m_EntryName, "FEDS_TB") || !strcmp(aScreens[screen].m_aEntries[nth - 1].m_EntryName, "FESZ_CA")) { + // Move back button one below + memcpy(&aScreens[screen].m_aEntries[nth], &aScreens[screen].m_aEntries[nth - 1], sizeof(CMenuScreen::CMenuEntry)); + nth--; + } + } + } else { + if (aScreens[screen].m_aEntries[optionCursor].m_Action != MENUACTION_NOTHING) { + for (int i = nth - 1; i >= optionCursor; i--) { + memcpy(&aScreens[screen].m_aEntries[i + 1], &aScreens[screen].m_aEntries[i], sizeof(CMenuScreen::CMenuEntry)); + } + } + nth = optionCursor; + optionCursor++; + } + + aScreens[screen].m_aEntries[nth].m_Action = MENUACTION_TRIGGERFUNC; + aScreens[screen].m_aEntries[nth].m_TargetMenu = numCustomFrontendOptions - 1; + aScreens[screen].m_aEntries[nth].m_EntryName[0] = 1; // just something to fool it + return nth; +} + +void FrontendOptionSetPosition(eMenuScreen screen, int8 option) +{ + currentMenu = screen; + optionCursor = option; +} + +void FrontendOptionAddSelect(const wchar* leftText, const wchar** rightTexts, int8 numRightTexts, int8 *var, bool onlyApplyOnEnter, ChangeFunc changeFunc, ReturnPrevPageFunc returnPrevPageFunc) +{ + int8 screenOptionOrder = RegisterNewOption(currentMenu); + + FrontendOption& option = customFrontendOptions[numCustomFrontendOptions - 1]; + option.screen = currentMenu; + option.type = FEOPTION_SELECT; + option.leftText = leftText; + option.rightTexts = rightTexts; + option.numRightTexts = numRightTexts; + option.value = var; + option.displayedValue = *var; + option.onlyApplyOnEnter = onlyApplyOnEnter; + option.changeFunc = changeFunc; + option.screenOptionOrder = screenOptionOrder; + option.returnPrevPageFunc = returnPrevPageFunc; +} + +void FrontendOptionAddDynamic(const wchar* leftText, DrawFunc drawFunc, ButtonPressFunc buttonPressFunc, ReturnPrevPageFunc returnPrevPageFunc) +{ + int8 screenOptionOrder = RegisterNewOption(currentMenu); + + FrontendOption& option = customFrontendOptions[numCustomFrontendOptions - 1]; + option.screen = currentMenu; + option.type = FEOPTION_DYNAMIC; + option.drawFunc = drawFunc; + option.buttonPressFunc = buttonPressFunc; + option.leftText = leftText; + option.onlyApplyOnEnter = false; + option.screenOptionOrder = screenOptionOrder; + option.returnPrevPageFunc = returnPrevPageFunc; +} + +void FrontendOptionAddRedirect(const wchar* text, eMenuScreen to, int8 selectedOption, bool fadeIn) +{ + int8 screenOptionOrder = RegisterNewOption(currentMenu); + + FrontendOption &option = customFrontendOptions[numCustomFrontendOptions - 1]; + option.screen = currentMenu; + option.type = FEOPTION_REDIRECT; + option.to = to; + option.option = selectedOption; + option.fadeIn = fadeIn; + option.leftText = text; + option.onlyApplyOnEnter = false; + option.screenOptionOrder = screenOptionOrder; + option.returnPrevPageFunc = nil; +} + +void FrontendOptionAddBackButton(const wchar* text, bool fadeIn) +{ + int8 screenOptionOrder = RegisterNewOption(currentMenu); + + FrontendOption& option = customFrontendOptions[numCustomFrontendOptions - 1]; + option.screen = currentMenu; + option.type = FEOPTION_GOBACK; + option.fadeIn = fadeIn; + option.leftText = text; + option.onlyApplyOnEnter = false; + option.screenOptionOrder = screenOptionOrder; + option.returnPrevPageFunc = nil; +} +#endif \ No newline at end of file diff --git a/src/extras/frontendoption.h b/src/extras/frontendoption.h new file mode 100644 index 00000000..7cfc09a7 --- /dev/null +++ b/src/extras/frontendoption.h @@ -0,0 +1,87 @@ +#pragma once +#include "common.h" + +#ifdef CUSTOM_FRONTEND_OPTIONS +#include "Frontend.h" + +// Warning: All of the code relies on that you won't use more then NUM_MENUROWS(18) options on one page. Also congrats if you can make 18 options visible at once. + + +// Static/select: User allocates variable, passes it to function and it's set automatically from input among the strings given to function, +// then you can handle ChangeFunc and ReturnPrevPageFunc if needed. +// +// Dynamic: Function doesn't accept value pointer, user should do operations with handling ButtonPressFunc. +// Right-side text can be set via DrawFunc, which is called on every draw. ReturnPrevPageFunc is also here if needed. + +#define FEOPTION_SELECT 0 +#define FEOPTION_DYNAMIC 1 +#define FEOPTION_REDIRECT 2 +#define FEOPTION_GOBACK 3 + +#define FEOPTION_ACTION_LEFT 0 +#define FEOPTION_ACTION_RIGHT 1 +#define FEOPTION_ACTION_SELECT 2 +#define FEOPTION_ACTION_FOCUSLOSS 3 + +void RemoveCustomFrontendOptions(); +void CustomFrontendOptionsPopulate(); + +// for static and dynamic options +typedef void (*ReturnPrevPageFunc)(); + +// for static options +typedef void (*ChangeFunc)(int8 displayedValue); // called before updating the value + +// for dynamic options +typedef wchar* (*DrawFunc)(bool* disabled); // should return pointer to right text. *disabled = true will make it dark yellow +typedef void (*ButtonPressFunc)(int8 action); // see FEOPTION_ACTIONs above + +struct FrontendOption +{ + int8 type; + int8 screenOptionOrder; + eMenuScreen screen; + const wchar* leftText; + ReturnPrevPageFunc returnPrevPageFunc; + + union { + // Only for dynamic + struct { + DrawFunc drawFunc; + ButtonPressFunc buttonPressFunc; + }; + + // Only for static/select + struct { + const wchar** rightTexts; + int8 numRightTexts; + int8 *value; + int8 displayedValue; // if onlyApplyOnEnter enabled + bool onlyApplyOnEnter; + ChangeFunc changeFunc; + }; + + // Only for redirect + struct { + eMenuScreen to; + int8 option; + bool fadeIn; + }; + }; +}; + +extern int numCustomFrontendOptions; +extern FrontendOption* customFrontendOptions; + +// To be used in ButtonPressFunc / ChangeFunc(but that would be weird): +void ChangeScreen(eMenuScreen screen, int option = 0, bool fadeIn = true); +void GoBack(bool fadeIn = true); + +// If option is positive number, all calls will increase it before using it (you can think it as cursor). -1 means before the back button, -2 is end of page +void FrontendOptionSetPosition(eMenuScreen screen, int8 option = -1); + +void FrontendOptionAddSelect(const wchar* leftText, const wchar** rightTexts, int8 numRightTexts, int8 *var, bool onlyApplyOnEnter, ChangeFunc changeFunc, ReturnPrevPageFunc returnPrevPageFunc); +void FrontendOptionAddDynamic(const wchar* leftText, DrawFunc rightTextDrawFunc, ButtonPressFunc buttonPressFunc, ReturnPrevPageFunc returnPrevPageFunc); +void FrontendOptionAddRedirect(const wchar* text, eMenuScreen to, int8 selectedOption = 0, bool fadeIn = true); +void FrontendOptionAddBackButton(const wchar* text, bool fadeIn = true); +#endif \ No newline at end of file