Add back button functionality to the installer.

This commit is contained in:
Dario 2025-02-04 19:50:27 -03:00 committed by Hyper
parent b68dbec612
commit 728ff5a45d
7 changed files with 326 additions and 118 deletions

View file

@ -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 std::string filename(pair.first);
const uint32_t hashCount = pair.second; const uint32_t hashCount = pair.second;
if (!sourceVfs.exists(filename)) if (!sourceVfs.exists(filename))
@ -139,7 +139,13 @@ static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFi
} }
journal.progressCounter += fileData.size(); journal.progressCounter += fileData.size();
progressCallback();
if (!progressCallback())
{
journal.lastResult = Journal::Result::Cancelled;
journal.lastErrorMessage = "Installation was cancelled.";
return false;
}
return true; return true;
} }
@ -266,7 +272,7 @@ bool Installer::computeTotalSize(std::span<const FilePair> filePairs, const uint
return true; 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; std::error_code ec;
if (!std::filesystem::exists(targetDirectory) && !std::filesystem::create_directories(targetDirectory, 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; 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, 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 // 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. // 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,13 @@ bool Installer::install(const Sources &sources, const std::filesystem::path &tar
// Update the progress with the artificial amount attributed to the patching. // Update the progress with the artificial amount attributed to the patching.
journal.progressCounter += PatcherContribution; journal.progressCounter += PatcherContribution;
progressCallback();
if (!progressCallback())
{
journal.lastResult = Journal::Result::Cancelled;
journal.lastErrorMessage = "Installation was cancelled.";
return false;
}
return true; return true;
} }

View file

@ -22,6 +22,7 @@ struct Journal
enum class Result enum class Result
{ {
Success, Success,
Cancelled,
VirtualFileSystemFailed, VirtualFileSystemFailed,
DirectoryCreationFailed, DirectoryCreationFailed,
FileMissing, FileMissing,
@ -75,10 +76,10 @@ struct Installer
static bool checkDLCInstall(const std::filesystem::path &baseDirectory, DLC dlc); static bool checkDLCInstall(const std::filesystem::path &baseDirectory, DLC dlc);
static bool checkAllDLC(const std::filesystem::path &baseDirectory); 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 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 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 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, const std::function<bool()> &progressCallback);
static void rollback(Journal &journal); 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. // Convenience method for checking if the specified file contains the game. This should be used when the user selects the file.

View file

@ -259,6 +259,12 @@ std::unordered_map<std::string, std::unordered_map<ELanguage, std::string>> g_lo
{ ELanguage::Italian, "SALTA" } { ELanguage::Italian, "SALTA" }
} }
}, },
{
"Installer_Button_Retry",
{
{ ELanguage::English, "RETRY" }
}
},
{ {
"Installer_Button_AddFiles", "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?" } { 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. // Notes: message appears when pressing B at the title screen.
"Title_Message_Quit", "Title_Message_Quit",
@ -446,6 +466,18 @@ std::unordered_map<std::string, std::unordered_map<ELanguage, std::string>> g_lo
{ ELanguage::Italian, "Indietro" } { ELanguage::Italian, "Indietro" }
} }
}, },
{
"Common_Quit",
{
{ ELanguage::English, "Quit" },
}
},
{
"Common_Cancel",
{
{ ELanguage::English, "Cancel" }
}
},
{ {
"Common_Reset", "Common_Reset",
{ {

View file

@ -38,7 +38,8 @@ std::unordered_map<EButtonIcon, float> g_iconWidths =
{ EButtonIcon::Start, 40 }, { EButtonIcon::Start, 40 },
{ EButtonIcon::Back, 40 }, { EButtonIcon::Back, 40 },
{ EButtonIcon::LMB, 40 }, { EButtonIcon::LMB, 40 },
{ EButtonIcon::Enter, 40 } { EButtonIcon::Enter, 40 },
{ EButtonIcon::Escape, 40 },
}; };
std::unordered_map<EButtonIcon, float> g_iconHeights = std::unordered_map<EButtonIcon, float> g_iconHeights =
@ -56,7 +57,8 @@ std::unordered_map<EButtonIcon, float> g_iconHeights =
{ EButtonIcon::Start, 40 }, { EButtonIcon::Start, 40 },
{ EButtonIcon::Back, 40 }, { EButtonIcon::Back, 40 },
{ EButtonIcon::LMB, 40 }, { EButtonIcon::LMB, 40 },
{ EButtonIcon::Enter, 40 } { EButtonIcon::Enter, 40 },
{ EButtonIcon::Escape, 40 },
}; };
std::tuple<std::tuple<ImVec2, ImVec2>, GuestTexture*> GetButtonIcon(EButtonIcon icon) std::tuple<std::tuple<ImVec2, ImVec2>, GuestTexture*> GetButtonIcon(EButtonIcon icon)
@ -132,6 +134,11 @@ std::tuple<std::tuple<ImVec2, ImVec2>, GuestTexture*> GetButtonIcon(EButtonIcon
btn = PIXELS_TO_UV_COORDS(256, 128, 128, 0, 128, 128); btn = PIXELS_TO_UV_COORDS(256, 128, 128, 0, 128, 128);
texture = g_upKBMIcons.get(); texture = g_upKBMIcons.get();
break; break;
case EButtonIcon::Escape:
btn = PIXELS_TO_UV_COORDS(256, 128, 128, 0, 128, 128);
texture = g_upKBMIcons.get();
break;
} }
return std::make_tuple(btn, texture); return std::make_tuple(btn, texture);

View file

@ -20,7 +20,8 @@ enum class EButtonIcon
// Keyboard + Mouse (temporary) // Keyboard + Mouse (temporary)
LMB, LMB,
Enter Enter,
Escape
}; };
enum class EButtonAlignment enum class EButtonAlignment

View file

@ -98,6 +98,7 @@ static double g_arrowCircleCurrentRotation = 0.0;
static double g_appearTime = 0.0; static double g_appearTime = 0.0;
static double g_disappearTime = DBL_MAX; static double g_disappearTime = DBL_MAX;
static bool g_isDisappearing = false; static bool g_isDisappearing = false;
static bool g_isQuitting = false;
static std::filesystem::path g_installPath; static std::filesystem::path g_installPath;
static std::filesystem::path g_gameSourcePath; static std::filesystem::path g_gameSourcePath;
@ -118,6 +119,8 @@ static double g_installerEndTime = DBL_MAX;
static float g_installerProgressRatioCurrent = 0.0f; static float g_installerProgressRatioCurrent = 0.0f;
static std::atomic<float> g_installerProgressRatioTarget = 0.0f; static std::atomic<float> g_installerProgressRatioTarget = 0.0f;
static std::atomic<bool> g_installerFinished = false; 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 bool g_installerFailed = false;
static std::string g_installerErrorMessage; static std::string g_installerErrorMessage;
@ -133,9 +136,17 @@ enum class WizardPage
InstallFailed, InstallFailed,
}; };
enum class MessagePromptSource
{
Unknown,
Next,
Back
};
static WizardPage g_firstPage = WizardPage::SelectLanguage; static WizardPage g_firstPage = WizardPage::SelectLanguage;
static WizardPage g_currentPage = g_firstPage; static WizardPage g_currentPage = g_firstPage;
static std::string g_currentMessagePrompt = ""; static std::string g_currentMessagePrompt = "";
static MessagePromptSource g_currentMessagePromptSource = MessagePromptSource::Unknown;
static bool g_currentMessagePromptConfirmation = false; static bool g_currentMessagePromptConfirmation = false;
static std::list<std::filesystem::path> g_currentPickerResults; static std::list<std::filesystem::path> g_currentPickerResults;
static std::atomic<bool> g_currentPickerResultsReady = false; static std::atomic<bool> g_currentPickerResultsReady = false;
@ -151,6 +162,7 @@ static ImVec2 g_joypadAxis = {};
static int g_currentCursorIndex = -1; static int g_currentCursorIndex = -1;
static int g_currentCursorDefault = 0; static int g_currentCursorDefault = 0;
static bool g_currentCursorAccepted = false; static bool g_currentCursorAccepted = false;
static bool g_currentCursorBack = false;
static std::vector<std::pair<ImVec2, ImVec2>> g_currentCursorRects; static std::vector<std::pair<ImVec2, ImVec2>> g_currentCursorRects;
static std::string g_creditsStr; static std::string g_creditsStr;
@ -185,6 +197,9 @@ public:
case SDL_SCANCODE_KP_ENTER: case SDL_SCANCODE_KP_ENTER:
g_currentCursorAccepted = (g_currentCursorIndex >= 0); g_currentCursorAccepted = (g_currentCursorIndex >= 0);
break; break;
case SDL_SCANCODE_ESCAPE:
g_currentCursorBack = true;
break;
} }
break; break;
@ -209,6 +224,9 @@ public:
case SDL_CONTROLLER_BUTTON_A: case SDL_CONTROLLER_BUTTON_A:
g_currentCursorAccepted = (g_currentCursorIndex >= 0); g_currentCursorAccepted = (g_currentCursorIndex >= 0);
break; break;
case SDL_CONTROLLER_BUTTON_B:
g_currentCursorBack = true;
break;
} }
break; break;
@ -807,15 +825,43 @@ static void DrawDescriptionContainer()
DrawContainer(sideMin, sideMax, false); DrawContainer(sideMin, sideMax, false);
drawList->PopClipRect(); drawList->PopClipRect();
EButtonIcon backIcon;
EButtonIcon selectIcon;
if (hid::IsInputDeviceController())
{
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;
}
if (g_currentPage != WizardPage::Installing && textAlpha >= 1.0) if (g_currentPage != WizardPage::Installing && textAlpha >= 1.0)
{ {
auto icon = hid::IsInputDeviceController() const char *backKey = "Common_Back";
? EButtonIcon::A if ((g_currentPage == g_firstPage) || (g_currentPage == WizardPage::InstallFailed))
: hid::g_inputDevice == hid::EInputDevice::Keyboard {
? EButtonIcon::Enter backKey = "Common_Quit";
: EButtonIcon::LMB; }
ButtonGuide::Open(Button(Localise("Common_Select"), icon)); 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 else
{ {
@ -1178,27 +1224,23 @@ static void DrawSourcePickers()
std::list<std::filesystem::path> paths; std::list<std::filesystem::path> paths;
if (g_currentPage == WizardPage::SelectGameAndUpdate || g_currentPage == WizardPage::SelectDLC) 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 = 124.0f;
const std::string &addFilesText = Localise("Installer_Button_AddFiles"); const std::string &addFilesText = Localise("Installer_Button_AddFiles");
float squashRatio; float squashRatio;
ImVec2 textSize = ComputeTextSize(g_dfsogeistdFont, addFilesText.c_str(), 20.0f, squashRatio, ADD_BUTTON_MAX_TEXT_WIDTH); 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 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); DrawButton(min, max, addFilesText.c_str(), false, true, buttonPressed, ADD_BUTTON_MAX_TEXT_WIDTH);
if (buttonPressed) if (buttonPressed)
{ {
PickerShow(false); 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"); const std::string &addFolderText = Localise("Installer_Button_AddFolder");
textSize = ComputeTextSize(g_dfsogeistdFont, addFolderText.c_str(), 20.0f, squashRatio, ADD_BUTTON_MAX_TEXT_WIDTH); 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 + BUTTON_TEXT_GAP);
max.x = min.x + Scale(textSize.x * squashRatio);
DrawButton(min, max, addFolderText.c_str(), false, true, buttonPressed, ADD_BUTTON_MAX_TEXT_WIDTH); DrawButton(min, max, addFolderText.c_str(), false, true, buttonPressed, ADD_BUTTON_MAX_TEXT_WIDTH);
if (buttonPressed) if (buttonPressed)
{ {
@ -1246,6 +1288,12 @@ static void InstallerThread()
{ {
if (!Installer::install(g_installerSources, g_installPath, false, g_installerJournal, [&]() { if (!Installer::install(g_installerSources, g_installPath, false, g_installerJournal, [&]() {
g_installerProgressRatioTarget = float(double(g_installerJournal.progressCounter) / double(g_installerJournal.progressTotal)); 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; g_installerFailed = true;
@ -1258,6 +1306,7 @@ static void InstallerThread()
// Rest for a bit after finishing the installation, the device is tired // Rest for a bit after finishing the installation, the device is tired
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
g_installerFinished = true; g_installerFinished = true;
g_installerCancelled = false;
} }
static void InstallerStart() static void InstallerStart()
@ -1298,104 +1347,162 @@ static bool InstallerParseSources(std::string &errorMessage)
return sourcesParsed; return sourcesParsed;
} }
static void DrawNextButton() static void DrawNavigationButton()
{ {
if (g_currentPage != WizardPage::Installing) if (g_currentPage == WizardPage::Installing)
{ {
bool nextButtonEnabled = !g_isDisappearing; // Navigation buttons are not offered during installation at the moment.
if (nextButtonEnabled && g_currentPage == WizardPage::SelectGameAndUpdate) 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 = 72.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;
} }
else if (g_currentPage == WizardPage::SelectDLC)
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(); }); // Check if any of the DLC was not specified.
} bool dlcIncomplete = false;
for (int i = 0; (i < int(DLC::Count)) && !dlcIncomplete; i++)
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))
{ {
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_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. // Not all the DLC was specified, we show a prompt and await a confirmation before starting the installer.
bool dlcIncomplete = false; g_currentMessagePrompt = Localise("Installer_Message_DLCWarning");
for (int i = 0; (i < int(DLC::Count)) && !dlcIncomplete; i++) g_currentMessagePromptSource = MessagePromptSource::Next;
{ g_currentMessagePromptConfirmation = true;
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;
}
} }
else if (g_currentPage == WizardPage::CheckSpace) else if (skipButton && dlcInstallerMode)
{
InstallerStart();
}
else if (g_currentPage == WizardPage::InstallSucceeded)
{ {
// Nothing was selected and the installer was in DLC mode, just close it.
g_isDisappearing = true; g_isDisappearing = true;
g_disappearTime = ImGui::GetTime(); g_disappearTime = ImGui::GetTime();
} }
else if (g_currentPage == WizardPage::InstallFailed)
{
g_currentPage = g_firstPage;
}
else 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 +1599,52 @@ static void DrawMessagePrompt()
if (messageWindowReturned) 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. if (g_currentMessagePromptSource == MessagePromptSource::Back)
g_currentPage = WizardPage::CheckSpace; {
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_currentMessagePrompt.clear();
g_currentMessagePromptSource = MessagePromptSource::Unknown;
g_currentMessageResult = -1; 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() static void PickerCheckTutorial()
{ {
if (!g_pickerTutorialTriggered || !g_currentMessagePrompt.empty()) if (!g_pickerTutorialTriggered || !g_currentMessagePrompt.empty())
@ -1580,9 +1722,11 @@ void InstallerWizard::Draw()
DrawSourcePickers(); DrawSourcePickers();
DrawSources(); DrawSources();
DrawInstallingProgress(); DrawInstallingProgress();
DrawNextButton(); DrawNavigationButton();
CheckCancelAction();
DrawBorders(); DrawBorders();
DrawMessagePrompt(); DrawMessagePrompt();
PickerDrawForeground();
PickerCheckTutorial(); PickerCheckTutorial();
PickerCheckResults(); PickerCheckResults();
@ -1670,5 +1814,5 @@ bool InstallerWizard::Run(std::filesystem::path installPath, bool skipGame)
InstallerWizard::Shutdown(); InstallerWizard::Shutdown();
EmbeddedPlayer::Shutdown(); EmbeddedPlayer::Shutdown();
return true; return !g_isQuitting;
} }

View file

@ -296,7 +296,7 @@ void MessageWindow::Draw()
textY += Scale(lines.size() % 2 == 0 ? 1.5f : 8.0f); textY += Scale(lines.size() % 2 == 0 ? 1.5f : 8.0f);
} }
bool isController = hid::IsInputDeviceController(); bool isController = hid::IsInputDeviceController();
bool isKeyboard = hid::g_inputDevice == hid::EInputDevice::Keyboard; bool isKeyboard = hid::g_inputDevice == hid::EInputDevice::Keyboard;
@ -397,20 +397,25 @@ void MessageWindow::Draw()
g_upWasHeld = upIsHeld; g_upWasHeld = upIsHeld;
g_downWasHeld = downIsHeld; 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 = std::array<Button, 2> buttons =
{ {
Button(Localise("Common_Select"), EButtonIcon::A), Button(Localise("Common_Select"), selectIcon),
Button(Localise("Common_Back"), EButtonIcon::B), Button(Localise("Common_Back"), backIcon),
}; };
ButtonGuide::Open(buttons); ButtonGuide::Open(buttons);
} }
else // Only display keyboard prompt during installer.
{
ButtonGuide::Open(Button(Localise("Common_Select"), EButtonIcon::Enter));
}
if (g_isDeclined) 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) if (g_selectedRowIndex != -1 && g_isAccepted)