mirror of
				https://github.com/hedge-dev/UnleashedRecomp.git
				synced 2025-10-30 07:11:05 +00:00 
			
		
		
		
	Add back button functionality to the installer. (#279)
* Add back button functionality to the installer. * Nuclear exits. * Adjust error code. * Rework waiting time into the installer process instead. * Extra waiting time during quitting. * Restore button max widths. * button_guide: set up Esc key texture * Update installer_wizard.cpp * Update resources submodule * installer_wizard: decrease nav button margin --------- Co-authored-by: Hyper <34012267+hyperbx@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									b68dbec612
								
							
						
					
					
						commit
						ef51f04d4f
					
				
					 10 changed files with 368 additions and 143 deletions
				
			
		|  | @ -67,7 +67,7 @@ static std::unique_ptr<VirtualFileSystem> createFileSystemFromPath(const std::fi | |||
|     } | ||||
| } | ||||
| 
 | ||||
| static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, bool skipHashChecks, std::vector<uint8_t> &fileData, Journal &journal, const std::function<void()> &progressCallback) { | ||||
| static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, bool skipHashChecks, std::vector<uint8_t> &fileData, Journal &journal, const std::function<bool()> &progressCallback) { | ||||
|     const std::string filename(pair.first); | ||||
|     const uint32_t hashCount = pair.second; | ||||
|     if (!sourceVfs.exists(filename)) | ||||
|  | @ -139,7 +139,13 @@ static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFi | |||
|     } | ||||
| 
 | ||||
|     journal.progressCounter += fileData.size(); | ||||
|     progressCallback(); | ||||
|      | ||||
|     if (!progressCallback()) | ||||
|     { | ||||
|         journal.lastResult = Journal::Result::Cancelled; | ||||
|         journal.lastErrorMessage = "Installation was cancelled."; | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
|  | @ -266,7 +272,7 @@ bool Installer::computeTotalSize(std::span<const FilePair> filePairs, const uint | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool Installer::copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<void()> &progressCallback) | ||||
| bool Installer::copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<bool()> &progressCallback) | ||||
| { | ||||
|     std::error_code ec; | ||||
|     if (!std::filesystem::exists(targetDirectory) && !std::filesystem::create_directories(targetDirectory, ec)) | ||||
|  | @ -429,7 +435,7 @@ bool Installer::parseSources(const Input &input, Journal &journal, Sources &sour | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool Installer::install(const Sources &sources, const std::filesystem::path &targetDirectory, bool skipHashChecks, Journal &journal, const std::function<void()> &progressCallback) | ||||
| bool Installer::install(const Sources &sources, const std::filesystem::path &targetDirectory, bool skipHashChecks, Journal &journal, std::chrono::seconds endWaitTime, const std::function<bool()> &progressCallback) | ||||
| { | ||||
|     // Install files in reverse order of importance. In case of a process crash or power outage, this will increase the likelihood of the installation
 | ||||
|     // missing critical files required for the game to run. These files are used as the way to detect if the game is installed.
 | ||||
|  | @ -497,7 +503,22 @@ bool Installer::install(const Sources &sources, const std::filesystem::path &tar | |||
| 
 | ||||
|     // Update the progress with the artificial amount attributed to the patching.
 | ||||
|     journal.progressCounter += PatcherContribution; | ||||
|     progressCallback(); | ||||
|      | ||||
|     for (uint32_t i = 0; i < 2; i++) | ||||
|     { | ||||
|         if (!progressCallback()) | ||||
|         { | ||||
|             journal.lastResult = Journal::Result::Cancelled; | ||||
|             journal.lastErrorMessage = "Installation was cancelled."; | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         if (i == 0) | ||||
|         { | ||||
|             // Wait the specified amount of time to allow the consumer of the callbacks to animate, halt or cancel the installation for a while after it's finished.
 | ||||
|             std::this_thread::sleep_for(endWaitTime); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ struct Journal | |||
|     enum class Result | ||||
|     { | ||||
|         Success, | ||||
|         Cancelled, | ||||
|         VirtualFileSystemFailed, | ||||
|         DirectoryCreationFailed, | ||||
|         FileMissing, | ||||
|  | @ -75,10 +76,10 @@ struct Installer | |||
|     static bool checkDLCInstall(const std::filesystem::path &baseDirectory, DLC dlc); | ||||
|     static bool checkAllDLC(const std::filesystem::path &baseDirectory); | ||||
|     static bool computeTotalSize(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, Journal &journal, uint64_t &totalSize); | ||||
|     static bool copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<void()> &progressCallback); | ||||
|     static bool copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<bool()> &progressCallback); | ||||
|     static bool parseContent(const std::filesystem::path &sourcePath, std::unique_ptr<VirtualFileSystem> &targetVfs, Journal &journal); | ||||
|     static bool parseSources(const Input &input, Journal &journal, Sources &sources); | ||||
|     static bool install(const Sources &sources, const std::filesystem::path &targetDirectory, bool skipHashChecks, Journal &journal, const std::function<void()> &progressCallback); | ||||
|     static bool install(const Sources &sources, const std::filesystem::path &targetDirectory, bool skipHashChecks, Journal &journal, std::chrono::seconds endWaitTime, const std::function<bool()> &progressCallback); | ||||
|     static void rollback(Journal &journal); | ||||
| 
 | ||||
|     // Convenience method for checking if the specified file contains the game. This should be used when the user selects the file.
 | ||||
|  |  | |||
|  | @ -259,6 +259,12 @@ std::unordered_map<std::string, std::unordered_map<ELanguage, std::string>> g_lo | |||
|             { ELanguage::Italian, "SALTA" } | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         "Installer_Button_Retry", | ||||
|         { | ||||
|             { ELanguage::English, "RETRY" } | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         "Installer_Button_AddFiles", | ||||
|         { | ||||
|  | @ -339,6 +345,20 @@ std::unordered_map<std::string, std::unordered_map<ELanguage, std::string>> g_lo | |||
|             { ELanguage::Italian, "Questa opzione riavviera il gioco\nper farti installare qualsiasi DLC\nche non hai installato.\n\nHai già installato tutti i DLC.\n\nVuoi procedere comunque?" } | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         // Notes: message appears when user chooses "Quit" on the first available installation screen.
 | ||||
|         "Installer_Message_Quit", | ||||
|         { | ||||
|             { ELanguage::English, "Are you sure you want to quit?" }, | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         // Notes: message appears when user chooses "Cancel" during installation.
 | ||||
|         "Installer_Message_Cancel", | ||||
|         { | ||||
|             { ELanguage::English, "Are you sure you want to cancel the installation?" }, | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         // Notes: message appears when pressing B at the title screen.
 | ||||
|         "Title_Message_Quit", | ||||
|  | @ -446,6 +466,18 @@ std::unordered_map<std::string, std::unordered_map<ELanguage, std::string>> g_lo | |||
|             { ELanguage::Italian, "Indietro" } | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         "Common_Quit", | ||||
|         { | ||||
|             { ELanguage::English, "Quit" }, | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         "Common_Cancel", | ||||
|         { | ||||
|             { ELanguage::English, "Cancel" } | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         "Common_Reset", | ||||
|         { | ||||
|  |  | |||
|  | @ -203,12 +203,12 @@ int main(int argc, char *argv[]) | |||
|         if (!Video::CreateHostDevice(sdlVideoDriver)) | ||||
|         { | ||||
|             SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), Localise("Video_BackendError").c_str(), GameWindow::s_pWindow); | ||||
|             return 1; | ||||
|             std::_Exit(1); | ||||
|         } | ||||
| 
 | ||||
|         if (!InstallerWizard::Run(GAME_INSTALL_DIRECTORY, isGameInstalled && forceDLCInstaller)) | ||||
|         { | ||||
|             return 1; | ||||
|             std::_Exit(0); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -223,7 +223,7 @@ int main(int argc, char *argv[]) | |||
|         if (!Video::CreateHostDevice(sdlVideoDriver)) | ||||
|         { | ||||
|             SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), Localise("Video_BackendError").c_str(), GameWindow::s_pWindow); | ||||
|             return 1; | ||||
|             std::_Exit(1); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,20 +1,19 @@ | |||
| #pragma once | ||||
| 
 | ||||
| inline const char* g_credits[] = | ||||
| inline std::array<const char*, 14> g_credits = | ||||
| { | ||||
|     "Skyth", | ||||
|     "Hyper", | ||||
|     "Darío", | ||||
|     "Sajid", | ||||
|     "DeaThProj", | ||||
|     "RadiantDerg", | ||||
|     "PTKay", | ||||
|     "DeaThProj", | ||||
|     "SuperSonic16", | ||||
|     "NextinHKRY", | ||||
|     "M&M", | ||||
|     "saguinee", | ||||
|     "LadyLunanova", | ||||
|     "LJSTAR" | ||||
|     "LJSTAR", | ||||
|     "Goalringmod27", | ||||
|     "M&M" | ||||
| }; | ||||
| 
 | ||||
| inline size_t g_creditsSize = 12; | ||||
|  |  | |||
|  | @ -38,7 +38,8 @@ std::unordered_map<EButtonIcon, float> g_iconWidths = | |||
|     { EButtonIcon::Start, 40 }, | ||||
|     { EButtonIcon::Back, 40 }, | ||||
|     { EButtonIcon::LMB, 40 }, | ||||
|     { EButtonIcon::Enter, 40 } | ||||
|     { EButtonIcon::Enter, 40 }, | ||||
|     { EButtonIcon::Escape, 40 }, | ||||
| }; | ||||
| 
 | ||||
| std::unordered_map<EButtonIcon, float> g_iconHeights = | ||||
|  | @ -56,7 +57,8 @@ std::unordered_map<EButtonIcon, float> g_iconHeights = | |||
|     { EButtonIcon::Start, 40 }, | ||||
|     { EButtonIcon::Back, 40 }, | ||||
|     { EButtonIcon::LMB, 40 }, | ||||
|     { EButtonIcon::Enter, 40 } | ||||
|     { EButtonIcon::Enter, 40 }, | ||||
|     { EButtonIcon::Escape, 40 }, | ||||
| }; | ||||
| 
 | ||||
| std::tuple<std::tuple<ImVec2, ImVec2>, GuestTexture*> GetButtonIcon(EButtonIcon icon) | ||||
|  | @ -124,12 +126,17 @@ std::tuple<std::tuple<ImVec2, ImVec2>, GuestTexture*> GetButtonIcon(EButtonIcon | |||
|             break; | ||||
| 
 | ||||
|         case EButtonIcon::LMB: | ||||
|             btn = PIXELS_TO_UV_COORDS(256, 128, 0, 0, 128, 128); | ||||
|             btn = PIXELS_TO_UV_COORDS(384, 128, 0, 0, 128, 128); | ||||
|             texture = g_upKBMIcons.get(); | ||||
|             break; | ||||
| 
 | ||||
|         case EButtonIcon::Enter: | ||||
|             btn = PIXELS_TO_UV_COORDS(256, 128, 128, 0, 128, 128); | ||||
|             btn = PIXELS_TO_UV_COORDS(384, 128, 128, 0, 128, 128); | ||||
|             texture = g_upKBMIcons.get(); | ||||
|             break; | ||||
| 
 | ||||
|         case EButtonIcon::Escape: | ||||
|             btn = PIXELS_TO_UV_COORDS(384, 128, 256, 0, 128, 128); | ||||
|             texture = g_upKBMIcons.get(); | ||||
|             break; | ||||
|     } | ||||
|  |  | |||
|  | @ -20,7 +20,8 @@ enum class EButtonIcon | |||
| 
 | ||||
|     // Keyboard + Mouse (temporary)
 | ||||
|     LMB, | ||||
|     Enter | ||||
|     Enter, | ||||
|     Escape | ||||
| }; | ||||
| 
 | ||||
| enum class EButtonAlignment | ||||
|  |  | |||
|  | @ -54,6 +54,7 @@ static constexpr double CONTAINER_INNER_TIME = SCANLINES_ANIMATION_DURATION + CO | |||
| static constexpr double CONTAINER_INNER_DURATION = 15.0; | ||||
| 
 | ||||
| static constexpr double ALL_ANIMATIONS_FULL_DURATION = CONTAINER_INNER_TIME + CONTAINER_INNER_DURATION; | ||||
| static constexpr double QUITTING_EXTRA_DURATION = 60.0; | ||||
| 
 | ||||
| static constexpr double INSTALL_ICONS_FADE_IN_ANIMATION_TIME = 0.0; | ||||
| static constexpr double INSTALL_ICONS_FADE_IN_ANIMATION_DURATION = 15.0; | ||||
|  | @ -77,7 +78,7 @@ constexpr float CONTAINER_HEIGHT = 246.0f; | |||
| constexpr float SIDE_CONTAINER_WIDTH = CONTAINER_WIDTH / 2.0f; | ||||
| 
 | ||||
| constexpr float BOTTOM_X_GAP = 4.0f; | ||||
| constexpr float BOTTOM_Y_GAP = 6.0f; | ||||
| constexpr float BOTTOM_Y_GAP = 5.0f; | ||||
| constexpr float CONTAINER_BUTTON_WIDTH = 250.0f; | ||||
| constexpr float CONTAINER_BUTTON_GAP = 9.0f; | ||||
| constexpr float BUTTON_HEIGHT = 22.0f; | ||||
|  | @ -98,6 +99,7 @@ static double g_arrowCircleCurrentRotation = 0.0; | |||
| static double g_appearTime = 0.0; | ||||
| static double g_disappearTime = DBL_MAX; | ||||
| static bool g_isDisappearing = false; | ||||
| static bool g_isQuitting = false; | ||||
| 
 | ||||
| static std::filesystem::path g_installPath; | ||||
| static std::filesystem::path g_gameSourcePath; | ||||
|  | @ -118,6 +120,8 @@ static double g_installerEndTime = DBL_MAX; | |||
| static float g_installerProgressRatioCurrent = 0.0f; | ||||
| static std::atomic<float> g_installerProgressRatioTarget = 0.0f; | ||||
| static std::atomic<bool> g_installerFinished = false; | ||||
| static std::atomic<bool> g_installerHalted = false; | ||||
| static std::atomic<bool> g_installerCancelled = false; | ||||
| static bool g_installerFailed = false; | ||||
| static std::string g_installerErrorMessage; | ||||
| 
 | ||||
|  | @ -133,9 +137,17 @@ enum class WizardPage | |||
|     InstallFailed, | ||||
| }; | ||||
| 
 | ||||
| enum class MessagePromptSource | ||||
| { | ||||
|     Unknown, | ||||
|     Next, | ||||
|     Back | ||||
| }; | ||||
| 
 | ||||
| static WizardPage g_firstPage = WizardPage::SelectLanguage; | ||||
| static WizardPage g_currentPage = g_firstPage; | ||||
| static std::string g_currentMessagePrompt = ""; | ||||
| static MessagePromptSource g_currentMessagePromptSource = MessagePromptSource::Unknown; | ||||
| static bool g_currentMessagePromptConfirmation = false; | ||||
| static std::list<std::filesystem::path> g_currentPickerResults; | ||||
| static std::atomic<bool> g_currentPickerResultsReady = false; | ||||
|  | @ -151,6 +163,7 @@ static ImVec2 g_joypadAxis = {}; | |||
| static int g_currentCursorIndex = -1; | ||||
| static int g_currentCursorDefault = 0; | ||||
| static bool g_currentCursorAccepted = false; | ||||
| static bool g_currentCursorBack = false; | ||||
| static std::vector<std::pair<ImVec2, ImVec2>> g_currentCursorRects; | ||||
| static std::string g_creditsStr; | ||||
| 
 | ||||
|  | @ -185,6 +198,9 @@ public: | |||
|                     case SDL_SCANCODE_KP_ENTER: | ||||
|                         g_currentCursorAccepted = (g_currentCursorIndex >= 0); | ||||
|                         break; | ||||
|                     case SDL_SCANCODE_ESCAPE: | ||||
|                         g_currentCursorBack = true; | ||||
|                         break; | ||||
|                 } | ||||
| 
 | ||||
|                 break; | ||||
|  | @ -209,6 +225,9 @@ public: | |||
|                     case SDL_CONTROLLER_BUTTON_A: | ||||
|                         g_currentCursorAccepted = (g_currentCursorIndex >= 0); | ||||
|                         break; | ||||
|                     case SDL_CONTROLLER_BUTTON_B: | ||||
|                         g_currentCursorBack = true; | ||||
|                         break; | ||||
|                 } | ||||
| 
 | ||||
|                 break; | ||||
|  | @ -807,15 +826,47 @@ static void DrawDescriptionContainer() | |||
|     DrawContainer(sideMin, sideMax, false); | ||||
|     drawList->PopClipRect(); | ||||
| 
 | ||||
|     if (g_currentPage != WizardPage::Installing && textAlpha >= 1.0) | ||||
|     EButtonIcon backIcon; | ||||
|     EButtonIcon selectIcon; | ||||
|     if (hid::IsInputDeviceController()) | ||||
|     { | ||||
|         auto icon = hid::IsInputDeviceController() | ||||
|             ? EButtonIcon::A | ||||
|             : hid::g_inputDevice == hid::EInputDevice::Keyboard | ||||
|                 ? EButtonIcon::Enter | ||||
|                 : EButtonIcon::LMB; | ||||
|         backIcon = EButtonIcon::B; | ||||
|         selectIcon = EButtonIcon::A; | ||||
|     } | ||||
|     else if (hid::g_inputDevice == hid::EInputDevice::Keyboard) | ||||
|     { | ||||
|         backIcon = EButtonIcon::Escape; | ||||
|         selectIcon = EButtonIcon::Enter; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         backIcon = EButtonIcon::Escape; | ||||
|         selectIcon = EButtonIcon::LMB; | ||||
|     } | ||||
| 
 | ||||
|         ButtonGuide::Open(Button(Localise("Common_Select"), icon)); | ||||
|     if (g_currentPage == WizardPage::InstallSucceeded && textAlpha >= 1.0) | ||||
|     { | ||||
|         ButtonGuide::Open(Button(Localise("Common_Select"), selectIcon)); | ||||
|     } | ||||
|     else if (g_currentPage != WizardPage::Installing && textAlpha >= 1.0) | ||||
|     { | ||||
|         const char *backKey = "Common_Back"; | ||||
|         if ((g_currentPage == g_firstPage) || (g_currentPage == WizardPage::InstallFailed)) | ||||
|         { | ||||
|             backKey = "Common_Quit"; | ||||
|         } | ||||
| 
 | ||||
|         std::array<Button, 2> buttons = | ||||
|         { | ||||
|             Button(Localise("Common_Select"), selectIcon), | ||||
|             Button(Localise(backKey), backIcon) | ||||
|         }; | ||||
| 
 | ||||
|         ButtonGuide::Open(buttons); | ||||
|     } | ||||
|     else if (g_currentPage == WizardPage::Installing) | ||||
|     { | ||||
|         ButtonGuide::Open(Button(Localise("Common_Cancel"), backIcon)); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|  | @ -966,9 +1017,9 @@ static void DrawProgressBar(float progressRatio) | |||
|     float alpha = 1.0; | ||||
|     const uint32_t innerColor0 = IM_COL32(0, 65, 0, 255 * alpha); | ||||
|     const uint32_t innerColor1 = IM_COL32(0, 32, 0, 255 * alpha); | ||||
|     float xPadding = Scale(6.0f); | ||||
|     float yPadding = Scale(3.0f); | ||||
|     ImVec2 min = { g_aspectRatioOffsetX + Scale(CONTAINER_X) + BOTTOM_X_GAP, g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP) }; | ||||
|     float xPadding = Scale(4); | ||||
|     float yPadding = Scale(3); | ||||
|     ImVec2 min = { g_aspectRatioOffsetX + Scale(CONTAINER_X) + BOTTOM_X_GAP + Scale(1), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP)}; | ||||
|     ImVec2 max = { g_aspectRatioOffsetX + Scale(CONTAINER_X + CONTAINER_WIDTH - BOTTOM_X_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; | ||||
| 
 | ||||
|     DrawButtonContainer(min, max, 0, 0, alpha); | ||||
|  | @ -985,8 +1036,8 @@ static void DrawProgressBar(float progressRatio) | |||
| 
 | ||||
|     const uint32_t sliderColor0 = IM_COL32(57, 241, 0, 255 * alpha); | ||||
|     const uint32_t sliderColor1 = IM_COL32(2, 106, 0, 255 * alpha); | ||||
|     xPadding += Scale(1.0f); | ||||
|     yPadding += Scale(1.0f); | ||||
|     xPadding += Scale(1.5f); | ||||
|     yPadding += Scale(1.5f); | ||||
| 
 | ||||
|     ImVec2 sliderMin = { min.x + xPadding, min.y + yPadding }; | ||||
|     ImVec2 sliderMax = { max.x - xPadding, max.y - yPadding }; | ||||
|  | @ -1178,27 +1229,23 @@ static void DrawSourcePickers() | |||
|     std::list<std::filesystem::path> paths; | ||||
|     if (g_currentPage == WizardPage::SelectGameAndUpdate || g_currentPage == WizardPage::SelectDLC) | ||||
|     { | ||||
|         constexpr float ADD_BUTTON_MAX_TEXT_WIDTH = 160.0f; | ||||
|         constexpr float ADD_BUTTON_MAX_TEXT_WIDTH = 168.0f; | ||||
|         const std::string &addFilesText = Localise("Installer_Button_AddFiles"); | ||||
|         float squashRatio; | ||||
|         ImVec2 textSize = ComputeTextSize(g_dfsogeistdFont, addFilesText.c_str(), 20.0f, squashRatio, ADD_BUTTON_MAX_TEXT_WIDTH); | ||||
|         textSize.x += BUTTON_TEXT_GAP; | ||||
| 
 | ||||
|         ImVec2 min = { g_aspectRatioOffsetX + Scale(CONTAINER_X + BOTTOM_X_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP) }; | ||||
|         ImVec2 max = { g_aspectRatioOffsetX + Scale(CONTAINER_X + BOTTOM_X_GAP + textSize.x * squashRatio), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; | ||||
|         ImVec2 max = { g_aspectRatioOffsetX + Scale(CONTAINER_X + BOTTOM_X_GAP + textSize.x * squashRatio + BUTTON_TEXT_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; | ||||
|         DrawButton(min, max, addFilesText.c_str(), false, true, buttonPressed, ADD_BUTTON_MAX_TEXT_WIDTH); | ||||
|         if (buttonPressed) | ||||
|         { | ||||
|             PickerShow(false); | ||||
|         } | ||||
| 
 | ||||
|         min.x += Scale(BOTTOM_X_GAP + textSize.x * squashRatio); | ||||
|         min.x += Scale(BOTTOM_X_GAP + textSize.x * squashRatio + BUTTON_TEXT_GAP); | ||||
| 
 | ||||
|         const std::string &addFolderText = Localise("Installer_Button_AddFolder"); | ||||
|         textSize = ComputeTextSize(g_dfsogeistdFont, addFolderText.c_str(), 20.0f, squashRatio, ADD_BUTTON_MAX_TEXT_WIDTH); | ||||
|         textSize.x += BUTTON_TEXT_GAP; | ||||
| 
 | ||||
|         max.x = min.x + Scale(textSize.x * squashRatio); | ||||
|         max.x = min.x + Scale(textSize.x * squashRatio + BUTTON_TEXT_GAP); | ||||
|         DrawButton(min, max, addFolderText.c_str(), false, true, buttonPressed, ADD_BUTTON_MAX_TEXT_WIDTH); | ||||
|         if (buttonPressed) | ||||
|         { | ||||
|  | @ -1244,8 +1291,14 @@ static void DrawInstallingProgress() | |||
| 
 | ||||
| static void InstallerThread() | ||||
| { | ||||
|     if (!Installer::install(g_installerSources, g_installPath, false, g_installerJournal, [&]() { | ||||
|     if (!Installer::install(g_installerSources, g_installPath, false, g_installerJournal, std::chrono::seconds(1), [&]() { | ||||
|         g_installerProgressRatioTarget = float(double(g_installerJournal.progressCounter) / double(g_installerJournal.progressTotal)); | ||||
| 
 | ||||
|         // If user is being asked for confirmation on cancelling the installation, halt the installer from progressing further.
 | ||||
|         g_installerHalted.wait(true); | ||||
| 
 | ||||
|         // If user has confirmed they wish to cancel the installation, return false to indicate the installer should fail and stop.
 | ||||
|         return !g_installerCancelled.load(); | ||||
|     })) | ||||
|     { | ||||
|         g_installerFailed = true; | ||||
|  | @ -1255,9 +1308,8 @@ static void InstallerThread() | |||
|         Installer::rollback(g_installerJournal); | ||||
|     } | ||||
| 
 | ||||
|     // Rest for a bit after finishing the installation, the device is tired
 | ||||
|     std::this_thread::sleep_for(std::chrono::seconds(1)); | ||||
|     g_installerFinished = true; | ||||
|     g_installerCancelled = false; | ||||
| } | ||||
| 
 | ||||
| static void InstallerStart() | ||||
|  | @ -1298,104 +1350,162 @@ static bool InstallerParseSources(std::string &errorMessage) | |||
|     return sourcesParsed; | ||||
| } | ||||
| 
 | ||||
| static void DrawNextButton() | ||||
| static void DrawNavigationButton() | ||||
| { | ||||
|     if (g_currentPage != WizardPage::Installing) | ||||
|     if (g_currentPage == WizardPage::Installing) | ||||
|     { | ||||
|         bool nextButtonEnabled = !g_isDisappearing; | ||||
|         if (nextButtonEnabled && g_currentPage == WizardPage::SelectGameAndUpdate) | ||||
|         // Navigation buttons are not offered during installation at the moment.
 | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     bool nextButtonEnabled = !g_isDisappearing && (g_currentPage != WizardPage::Installing); | ||||
|     if (nextButtonEnabled && g_currentPage == WizardPage::SelectGameAndUpdate) | ||||
|     { | ||||
|         nextButtonEnabled = !g_gameSourcePath.empty() && !g_updateSourcePath.empty(); | ||||
|     } | ||||
| 
 | ||||
|     bool skipButton = false; | ||||
|     if (g_currentPage == WizardPage::SelectDLC) | ||||
|     { | ||||
|         skipButton = std::all_of(g_dlcSourcePaths.begin(), g_dlcSourcePaths.end(), [](const std::filesystem::path &path) { return path.empty(); }); | ||||
|     } | ||||
| 
 | ||||
|     float squashRatio; | ||||
|     constexpr float NAV_BUTTON_MAX_TEXT_WIDTH = 90.0f; | ||||
|     const char *nextButtonKey = "Installer_Button_Next"; | ||||
|     if (skipButton) | ||||
|     { | ||||
|         nextButtonKey = "Installer_Button_Skip"; | ||||
|     } | ||||
|     else if (g_currentPage == WizardPage::InstallFailed) | ||||
|     { | ||||
|         nextButtonKey = "Installer_Button_Retry"; | ||||
|     } | ||||
| 
 | ||||
|     const std::string &nextButtonText = Localise(nextButtonKey); | ||||
|     ImVec2 nextTextSize = ComputeTextSize(g_newRodinFont, nextButtonText.c_str(), 20.0f, squashRatio, NAV_BUTTON_MAX_TEXT_WIDTH); | ||||
|     ImVec2 min = { g_aspectRatioOffsetX + Scale(CONTAINER_X + CONTAINER_WIDTH - nextTextSize.x * squashRatio - BOTTOM_X_GAP - BUTTON_TEXT_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP) }; | ||||
|     ImVec2 max = { g_aspectRatioOffsetX + Scale(CONTAINER_X + CONTAINER_WIDTH - BOTTOM_X_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; | ||||
| 
 | ||||
|     bool buttonPressed = false; | ||||
|     DrawButton(min, max, nextButtonText.c_str(), false, nextButtonEnabled, buttonPressed, NAV_BUTTON_MAX_TEXT_WIDTH); | ||||
| 
 | ||||
|     if (buttonPressed) | ||||
|     { | ||||
|         XexPatcher::Result patcherResult; | ||||
|         if (g_currentPage == WizardPage::SelectGameAndUpdate && (patcherResult = Installer::checkGameUpdateCompatibility(g_gameSourcePath, g_updateSourcePath), patcherResult != XexPatcher::Result::Success)) | ||||
|         { | ||||
|             nextButtonEnabled = !g_gameSourcePath.empty() && !g_updateSourcePath.empty(); | ||||
|             g_currentMessagePrompt = Localise("Installer_Message_IncompatibleGameData"); | ||||
|             g_currentMessagePromptConfirmation = false; | ||||
|         } | ||||
| 
 | ||||
|         bool skipButton = false; | ||||
|         if (g_currentPage == WizardPage::SelectDLC) | ||||
|         else if (g_currentPage == WizardPage::SelectDLC) | ||||
|         { | ||||
|             skipButton = std::all_of(g_dlcSourcePaths.begin(), g_dlcSourcePaths.end(), [](const std::filesystem::path &path) { return path.empty(); }); | ||||
|         } | ||||
| 
 | ||||
|         float squashRatio; | ||||
|         constexpr float NEXT_BUTTON_MAX_TEXT_WIDTH = 100.0f; | ||||
|         const std::string &buttonText = Localise(skipButton ? "Installer_Button_Skip" : "Installer_Button_Next"); | ||||
|         ImVec2 textSize = ComputeTextSize(g_newRodinFont, buttonText.c_str(), 20.0f, squashRatio, NEXT_BUTTON_MAX_TEXT_WIDTH); | ||||
|         textSize.x += BUTTON_TEXT_GAP; | ||||
| 
 | ||||
|         ImVec2 min = { g_aspectRatioOffsetX + Scale(CONTAINER_X + CONTAINER_WIDTH - textSize.x * squashRatio - BOTTOM_X_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP) }; | ||||
|         ImVec2 max = { g_aspectRatioOffsetX + Scale(CONTAINER_X + CONTAINER_WIDTH - BOTTOM_X_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; | ||||
| 
 | ||||
|         bool buttonPressed = false; | ||||
|         DrawButton(min, max, buttonText.c_str(), false, nextButtonEnabled, buttonPressed, NEXT_BUTTON_MAX_TEXT_WIDTH); | ||||
| 
 | ||||
|         if (buttonPressed) | ||||
|         { | ||||
|             XexPatcher::Result patcherResult; | ||||
|             if (g_currentPage == WizardPage::SelectGameAndUpdate && (patcherResult = Installer::checkGameUpdateCompatibility(g_gameSourcePath, g_updateSourcePath), patcherResult != XexPatcher::Result::Success)) | ||||
|             // Check if any of the DLC was not specified.
 | ||||
|             bool dlcIncomplete = false; | ||||
|             for (int i = 0; (i < int(DLC::Count)) && !dlcIncomplete; i++) | ||||
|             { | ||||
|                 g_currentMessagePrompt = Localise("Installer_Message_IncompatibleGameData"); | ||||
|                 if (g_dlcSourcePaths[i].empty() && !g_dlcInstalled[i]) | ||||
|                 { | ||||
|                     dlcIncomplete = true; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             bool dlcInstallerMode = g_gameSourcePath.empty(); | ||||
|             std::string sourcesErrorMessage; | ||||
|             if (!InstallerParseSources(sourcesErrorMessage)) | ||||
|             { | ||||
|                 // Some of the sources that were provided to the installer are not valid. Restart the file selection process.
 | ||||
|                 std::stringstream stringStream; | ||||
|                 stringStream << Localise("Installer_Message_InvalidFiles"); | ||||
|                 if (!sourcesErrorMessage.empty()) { | ||||
|                     stringStream << std::endl << std::endl << sourcesErrorMessage; | ||||
|                 } | ||||
| 
 | ||||
|                 g_currentMessagePrompt = stringStream.str(); | ||||
|                 g_currentMessagePromptConfirmation = false; | ||||
|                 g_currentPage = dlcInstallerMode ? WizardPage::SelectDLC : WizardPage::SelectGameAndUpdate; | ||||
|             } | ||||
|             else if (g_currentPage == WizardPage::SelectDLC) | ||||
|             else if (dlcIncomplete && !dlcInstallerMode) | ||||
|             { | ||||
|                 // Check if any of the DLC was not specified.
 | ||||
|                 bool dlcIncomplete = false; | ||||
|                 for (int i = 0; (i < int(DLC::Count)) && !dlcIncomplete; i++) | ||||
|                 { | ||||
|                     if (g_dlcSourcePaths[i].empty() && !g_dlcInstalled[i]) | ||||
|                     { | ||||
|                         dlcIncomplete = true; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 bool dlcInstallerMode = g_gameSourcePath.empty(); | ||||
|                 std::string sourcesErrorMessage; | ||||
|                 if (!InstallerParseSources(sourcesErrorMessage)) | ||||
|                 { | ||||
|                     // Some of the sources that were provided to the installer are not valid. Restart the file selection process.
 | ||||
|                     std::stringstream stringStream; | ||||
|                     stringStream << Localise("Installer_Message_InvalidFiles"); | ||||
|                     if (!sourcesErrorMessage.empty()) { | ||||
|                         stringStream << std::endl << std::endl << sourcesErrorMessage; | ||||
|                     } | ||||
| 
 | ||||
|                     g_currentMessagePrompt = stringStream.str(); | ||||
|                     g_currentMessagePromptConfirmation = false; | ||||
|                     g_currentPage = dlcInstallerMode ? WizardPage::SelectDLC : WizardPage::SelectGameAndUpdate; | ||||
|                 } | ||||
|                 else if (dlcIncomplete && !dlcInstallerMode) | ||||
|                 { | ||||
|                     // Not all the DLC was specified, we show a prompt and await a confirmation before starting the installer.
 | ||||
|                     g_currentMessagePrompt = Localise("Installer_Message_DLCWarning"); | ||||
|                     g_currentMessagePromptConfirmation = true; | ||||
|                 } | ||||
|                 else if (skipButton && dlcInstallerMode) | ||||
|                 { | ||||
|                     // Nothing was selected and the installer was in DLC mode, just close it.
 | ||||
|                     g_isDisappearing = true; | ||||
|                     g_disappearTime = ImGui::GetTime(); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     g_currentPage = WizardPage::CheckSpace; | ||||
|                 } | ||||
|                 // Not all the DLC was specified, we show a prompt and await a confirmation before starting the installer.
 | ||||
|                 g_currentMessagePrompt = Localise("Installer_Message_DLCWarning"); | ||||
|                 g_currentMessagePromptSource = MessagePromptSource::Next; | ||||
|                 g_currentMessagePromptConfirmation = true; | ||||
|             } | ||||
|             else if (g_currentPage == WizardPage::CheckSpace) | ||||
|             { | ||||
|                 InstallerStart(); | ||||
|             } | ||||
|             else if (g_currentPage == WizardPage::InstallSucceeded) | ||||
|             else if (skipButton && dlcInstallerMode) | ||||
|             { | ||||
|                 // Nothing was selected and the installer was in DLC mode, just close it.
 | ||||
|                 g_isDisappearing = true; | ||||
|                 g_disappearTime = ImGui::GetTime(); | ||||
|             } | ||||
|             else if (g_currentPage == WizardPage::InstallFailed) | ||||
|             { | ||||
|                 g_currentPage = g_firstPage; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 g_currentPage = WizardPage(int(g_currentPage) + 1); | ||||
|                 g_currentPage = WizardPage::CheckSpace; | ||||
|             } | ||||
|         } | ||||
|         else if (g_currentPage == WizardPage::CheckSpace) | ||||
|         { | ||||
|             InstallerStart(); | ||||
|         } | ||||
|         else if (g_currentPage == WizardPage::InstallSucceeded) | ||||
|         { | ||||
|             g_isDisappearing = true; | ||||
|             g_disappearTime = ImGui::GetTime(); | ||||
|         } | ||||
|         else if (g_currentPage == WizardPage::InstallFailed) | ||||
|         { | ||||
|             g_currentPage = g_firstPage; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             g_currentPage = WizardPage(int(g_currentPage) + 1); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void CheckCancelAction() | ||||
| { | ||||
|     if (!g_currentCursorBack) | ||||
|     { | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     g_currentCursorBack = false; | ||||
| 
 | ||||
|     if (g_currentPage == WizardPage::InstallSucceeded) | ||||
|     { | ||||
|         // Nothing to back out on this page.
 | ||||
|         return; | ||||
|     } | ||||
|     if (g_currentPage == WizardPage::Installing && g_installerCancelled) | ||||
|     { | ||||
|         // Installer's already been cancelled, no need for more confirmations.
 | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     Game_PlaySound("sys_actstg_pausecansel"); | ||||
| 
 | ||||
|     if (g_currentPage == g_firstPage || g_currentPage == WizardPage::InstallFailed) | ||||
|     { | ||||
|         // Ask for confirmation if user wants to quit the installer.
 | ||||
|         g_currentMessagePrompt = Localise("Installer_Message_Quit"); | ||||
|         g_currentMessagePromptSource = MessagePromptSource::Back; | ||||
|         g_currentMessagePromptConfirmation = true; | ||||
|     } | ||||
|     else if (g_currentPage == WizardPage::Installing) | ||||
|     { | ||||
|         // Ask for confirmation if the user wants to cancel the installation.
 | ||||
|         g_currentMessagePrompt = Localise("Installer_Message_Cancel"); | ||||
|         g_currentMessagePromptSource = MessagePromptSource::Back; | ||||
|         g_currentMessagePromptConfirmation = true; | ||||
| 
 | ||||
|         // Indicate to the installer that all progress should stop until the user confirms if they wish to cancel.
 | ||||
|         g_installerHalted = true; | ||||
|     } | ||||
|     else if (int(g_currentPage) > 0) | ||||
|     { | ||||
|         // Just go back to the previous page.
 | ||||
|         g_currentPage = WizardPage(int(g_currentPage) - 1); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -1492,17 +1602,52 @@ static void DrawMessagePrompt() | |||
| 
 | ||||
|     if (messageWindowReturned) | ||||
|     { | ||||
|         if (g_currentMessagePromptConfirmation && (g_currentMessageResult == 0) && (g_currentPage == WizardPage::SelectDLC)) | ||||
|         if (g_currentMessagePromptConfirmation && (g_currentMessageResult == 0)) | ||||
|         { | ||||
|             // If user confirms the message prompt that they wish to skip installing the DLC, proceed to the next step.
 | ||||
|             g_currentPage = WizardPage::CheckSpace; | ||||
|             if (g_currentMessagePromptSource == MessagePromptSource::Back) | ||||
|             { | ||||
|                 if (g_currentPage == WizardPage::Installing) | ||||
|                 { | ||||
|                     // If user confirms they wish to cancel the installation, notify the installation thread it must finish as soon as possible.
 | ||||
|                     g_installerCancelled = true; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     // In all cases, proceed to just quit the application.
 | ||||
|                     g_isQuitting = true; | ||||
|                     g_isDisappearing = true; | ||||
|                     g_disappearTime = ImGui::GetTime(); | ||||
|                 } | ||||
|             } | ||||
|             else if (g_currentPage == WizardPage::SelectDLC) | ||||
|             { | ||||
|                 // If user confirms the message prompt that they wish to skip installing the DLC, proceed to the next step.
 | ||||
|                 g_currentPage = WizardPage::CheckSpace; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (g_currentMessagePromptSource == MessagePromptSource::Back) | ||||
|         { | ||||
|             // Regardless of the confirmation, the installation thread must be resumed.
 | ||||
|             g_installerHalted = false; | ||||
|             g_installerHalted.notify_all(); | ||||
|         } | ||||
| 
 | ||||
|         g_currentMessagePrompt.clear(); | ||||
|         g_currentMessagePromptSource = MessagePromptSource::Unknown; | ||||
|         g_currentMessageResult = -1; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void PickerDrawForeground() | ||||
| { | ||||
|     if (g_currentPickerVisible) | ||||
|     { | ||||
|         auto drawList = ImGui::GetForegroundDrawList(); | ||||
|         drawList->AddRectFilled({ 0.0f, 0.0f }, ImGui::GetIO().DisplaySize, IM_COL32(0, 0, 0, 190)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void PickerCheckTutorial() | ||||
| { | ||||
|     if (!g_pickerTutorialTriggered || !g_currentMessagePrompt.empty()) | ||||
|  | @ -1557,10 +1702,10 @@ void InstallerWizard::Init() | |||
|     g_pulseInstall = LOAD_ZSTD_TEXTURE(g_pulse_install); | ||||
|     g_upHedgeDev = LOAD_ZSTD_TEXTURE(g_hedgedev); | ||||
| 
 | ||||
|     for (int i = 0; i < g_creditsSize; i++) | ||||
|     for (int i = 0; i < g_credits.size(); i++) | ||||
|     { | ||||
|         g_creditsStr += g_credits[i]; | ||||
|         g_creditsStr += "   "; | ||||
|         g_creditsStr += "  "; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -1580,15 +1725,23 @@ void InstallerWizard::Draw() | |||
|     DrawSourcePickers(); | ||||
|     DrawSources(); | ||||
|     DrawInstallingProgress(); | ||||
|     DrawNextButton(); | ||||
|     DrawNavigationButton(); | ||||
|     CheckCancelAction(); | ||||
|     DrawBorders(); | ||||
|     DrawMessagePrompt(); | ||||
|     PickerDrawForeground(); | ||||
|     PickerCheckTutorial(); | ||||
|     PickerCheckResults(); | ||||
| 
 | ||||
|     if (g_isDisappearing) | ||||
|     { | ||||
|         const double disappearDuration = ALL_ANIMATIONS_FULL_DURATION / 60.0; | ||||
|         double disappearDuration = ALL_ANIMATIONS_FULL_DURATION / 60.0; | ||||
|         if (g_isQuitting) | ||||
|         { | ||||
|             // Add some extra waiting time when quitting the application altogether.
 | ||||
|             disappearDuration += QUITTING_EXTRA_DURATION / 60.0; | ||||
|         } | ||||
| 
 | ||||
|         if (ImGui::GetTime() > (g_disappearTime + disappearDuration)) | ||||
|         { | ||||
|             s_isVisible = false; | ||||
|  | @ -1670,5 +1823,5 @@ bool InstallerWizard::Run(std::filesystem::path installPath, bool skipGame) | |||
|     InstallerWizard::Shutdown(); | ||||
|     EmbeddedPlayer::Shutdown(); | ||||
| 
 | ||||
|     return true; | ||||
|     return !g_isQuitting; | ||||
| } | ||||
|  |  | |||
|  | @ -296,7 +296,7 @@ void MessageWindow::Draw() | |||
| 
 | ||||
|         textY += Scale(lines.size() % 2 == 0 ? 1.5f : 8.0f); | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     bool isController = hid::IsInputDeviceController(); | ||||
|     bool isKeyboard = hid::g_inputDevice == hid::EInputDevice::Keyboard; | ||||
| 
 | ||||
|  | @ -397,20 +397,25 @@ void MessageWindow::Draw() | |||
|                     g_upWasHeld = upIsHeld; | ||||
|                     g_downWasHeld = downIsHeld; | ||||
| 
 | ||||
|                     if (isController || (isKeyboard && App::s_isInit)) | ||||
|                     EButtonIcon selectIcon = EButtonIcon::A; | ||||
|                     EButtonIcon backIcon = EButtonIcon::B; | ||||
|                     if (isController || isKeyboard) | ||||
|                     { | ||||
|                         if (isKeyboard && !App::s_isInit) | ||||
|                         { | ||||
|                             // Only display keyboard prompt during installer.
 | ||||
|                             selectIcon = EButtonIcon::Enter; | ||||
|                             backIcon = EButtonIcon::Escape; | ||||
|                         } | ||||
| 
 | ||||
|                         std::array<Button, 2> buttons = | ||||
|                         { | ||||
|                             Button(Localise("Common_Select"), EButtonIcon::A), | ||||
|                             Button(Localise("Common_Back"), EButtonIcon::B), | ||||
|                             Button(Localise("Common_Select"), selectIcon), | ||||
|                             Button(Localise("Common_Back"), backIcon), | ||||
|                         }; | ||||
| 
 | ||||
|                         ButtonGuide::Open(buttons); | ||||
|                     } | ||||
|                     else // Only display keyboard prompt during installer.
 | ||||
|                     { | ||||
|                         ButtonGuide::Open(Button(Localise("Common_Select"), EButtonIcon::Enter)); | ||||
|                     } | ||||
| 
 | ||||
|                     if (g_isDeclined) | ||||
|                     { | ||||
|  | @ -448,7 +453,13 @@ void MessageWindow::Draw() | |||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     ButtonGuide::Open(Button(Localise("Common_Select"), EButtonIcon::LMB)); | ||||
|                     std::array<Button, 2> buttons = | ||||
|                     { | ||||
|                         Button(Localise("Common_Select"), EButtonIcon::LMB), | ||||
|                         Button(Localise("Common_Back"), EButtonIcon::Escape), | ||||
|                     }; | ||||
| 
 | ||||
|                     ButtonGuide::Open(buttons); | ||||
|                 } | ||||
| 
 | ||||
|                 if (g_selectedRowIndex != -1 && g_isAccepted) | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| Subproject commit b2026bbc8e1ac96858659aff76c77c5945c15ff2 | ||||
| Subproject commit 20dc53cf544eeb573be3df9406720f3fb003ad9d | ||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Darío
						Darío