From ef51f04d4febab13cf7f475e54573b1b31017ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=ADo?= Date: Wed, 5 Feb 2025 16:36:30 -0300 Subject: [PATCH] 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> --- UnleashedRecomp/install/installer.cpp | 31 +- UnleashedRecomp/install/installer.h | 5 +- UnleashedRecomp/locale/locale.cpp | 32 ++ UnleashedRecomp/main.cpp | 6 +- UnleashedRecomp/res/credits.h | 11 +- UnleashedRecomp/ui/button_guide.cpp | 15 +- UnleashedRecomp/ui/button_guide.h | 3 +- UnleashedRecomp/ui/installer_wizard.cpp | 377 +++++++++++++++++------- UnleashedRecomp/ui/message_window.cpp | 29 +- UnleashedRecompResources | 2 +- 10 files changed, 368 insertions(+), 143 deletions(-) diff --git a/UnleashedRecomp/install/installer.cpp b/UnleashedRecomp/install/installer.cpp index 8e077d8..8a3e7b3 100644 --- a/UnleashedRecomp/install/installer.cpp +++ b/UnleashedRecomp/install/installer.cpp @@ -67,7 +67,7 @@ static std::unique_ptr 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 &fileData, Journal &journal, const std::function &progressCallback) { +static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, bool skipHashChecks, std::vector &fileData, Journal &journal, const std::function &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 filePairs, const uint return true; } -bool Installer::copyFiles(std::span filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function &progressCallback) +bool Installer::copyFiles(std::span filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function &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 &progressCallback) +bool Installer::install(const Sources &sources, const std::filesystem::path &targetDirectory, bool skipHashChecks, Journal &journal, std::chrono::seconds endWaitTime, const std::function &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; } diff --git a/UnleashedRecomp/install/installer.h b/UnleashedRecomp/install/installer.h index c61beda..198076d 100644 --- a/UnleashedRecomp/install/installer.h +++ b/UnleashedRecomp/install/installer.h @@ -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 filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, Journal &journal, uint64_t &totalSize); - static bool copyFiles(std::span filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function &progressCallback); + static bool copyFiles(std::span filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function &progressCallback); static bool parseContent(const std::filesystem::path &sourcePath, std::unique_ptr &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 &progressCallback); + static bool install(const Sources &sources, const std::filesystem::path &targetDirectory, bool skipHashChecks, Journal &journal, std::chrono::seconds endWaitTime, const std::function &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. diff --git a/UnleashedRecomp/locale/locale.cpp b/UnleashedRecomp/locale/locale.cpp index 016d599..c2c8680 100644 --- a/UnleashedRecomp/locale/locale.cpp +++ b/UnleashedRecomp/locale/locale.cpp @@ -259,6 +259,12 @@ std::unordered_map> g_lo { ELanguage::Italian, "SALTA" } } }, + { + "Installer_Button_Retry", + { + { ELanguage::English, "RETRY" } + } + }, { "Installer_Button_AddFiles", { @@ -339,6 +345,20 @@ std::unordered_map> 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> g_lo { ELanguage::Italian, "Indietro" } } }, + { + "Common_Quit", + { + { ELanguage::English, "Quit" }, + } + }, + { + "Common_Cancel", + { + { ELanguage::English, "Cancel" } + } + }, { "Common_Reset", { diff --git a/UnleashedRecomp/main.cpp b/UnleashedRecomp/main.cpp index 012b9b2..18507c3 100644 --- a/UnleashedRecomp/main.cpp +++ b/UnleashedRecomp/main.cpp @@ -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); } } diff --git a/UnleashedRecomp/res/credits.h b/UnleashedRecomp/res/credits.h index fbc5952..5643f2e 100644 --- a/UnleashedRecomp/res/credits.h +++ b/UnleashedRecomp/res/credits.h @@ -1,20 +1,19 @@ #pragma once -inline const char* g_credits[] = +inline std::array 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; diff --git a/UnleashedRecomp/ui/button_guide.cpp b/UnleashedRecomp/ui/button_guide.cpp index 252fb08..d81db82 100644 --- a/UnleashedRecomp/ui/button_guide.cpp +++ b/UnleashedRecomp/ui/button_guide.cpp @@ -38,7 +38,8 @@ std::unordered_map g_iconWidths = { EButtonIcon::Start, 40 }, { EButtonIcon::Back, 40 }, { EButtonIcon::LMB, 40 }, - { EButtonIcon::Enter, 40 } + { EButtonIcon::Enter, 40 }, + { EButtonIcon::Escape, 40 }, }; std::unordered_map g_iconHeights = @@ -56,7 +57,8 @@ std::unordered_map g_iconHeights = { EButtonIcon::Start, 40 }, { EButtonIcon::Back, 40 }, { EButtonIcon::LMB, 40 }, - { EButtonIcon::Enter, 40 } + { EButtonIcon::Enter, 40 }, + { EButtonIcon::Escape, 40 }, }; std::tuple, GuestTexture*> GetButtonIcon(EButtonIcon icon) @@ -124,12 +126,17 @@ std::tuple, 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; } diff --git a/UnleashedRecomp/ui/button_guide.h b/UnleashedRecomp/ui/button_guide.h index 50d26ef..0849314 100644 --- a/UnleashedRecomp/ui/button_guide.h +++ b/UnleashedRecomp/ui/button_guide.h @@ -20,7 +20,8 @@ enum class EButtonIcon // Keyboard + Mouse (temporary) LMB, - Enter + Enter, + Escape }; enum class EButtonAlignment diff --git a/UnleashedRecomp/ui/installer_wizard.cpp b/UnleashedRecomp/ui/installer_wizard.cpp index 622bfb8..55295ea 100644 --- a/UnleashedRecomp/ui/installer_wizard.cpp +++ b/UnleashedRecomp/ui/installer_wizard.cpp @@ -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 g_installerProgressRatioTarget = 0.0f; static std::atomic g_installerFinished = false; +static std::atomic g_installerHalted = false; +static std::atomic 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 g_currentPickerResults; static std::atomic 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> 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 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 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; } diff --git a/UnleashedRecomp/ui/message_window.cpp b/UnleashedRecomp/ui/message_window.cpp index fe05b8b..a1bc63e 100644 --- a/UnleashedRecomp/ui/message_window.cpp +++ b/UnleashedRecomp/ui/message_window.cpp @@ -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 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 buttons = + { + Button(Localise("Common_Select"), EButtonIcon::LMB), + Button(Localise("Common_Back"), EButtonIcon::Escape), + }; + + ButtonGuide::Open(buttons); } if (g_selectedRowIndex != -1 && g_isAccepted) diff --git a/UnleashedRecompResources b/UnleashedRecompResources index b2026bb..20dc53c 160000 --- a/UnleashedRecompResources +++ b/UnleashedRecompResources @@ -1 +1 @@ -Subproject commit b2026bbc8e1ac96858659aff76c77c5945c15ff2 +Subproject commit 20dc53cf544eeb573be3df9406720f3fb003ad9d