diff --git a/lang/english/css/main_window.css b/lang/english/css/main_window.css index 9b91b89d..5f673ee3 100644 --- a/lang/english/css/main_window.css +++ b/lang/english/css/main_window.css @@ -284,6 +284,17 @@ rombrowser .games-grid[data-size="4"] .rom-card margin-bottom: *; } +#LoadingPanel .loading-main.no-game-icon .loading-text-column +{ + margin-left: *; + text-align: center; +} + +#LoadingPanel .loading-main.no-game-icon .loading-progress-wrap +{ + width: 20rem; +} + #LoadingCornerBanner { horizontal-align: right; diff --git a/src/nxemu-loader/loader_settings.cpp b/src/nxemu-loader/loader_settings.cpp index 86c28e4f..9c54a019 100644 --- a/src/nxemu-loader/loader_settings.cpp +++ b/src/nxemu-loader/loader_settings.cpp @@ -13,12 +13,14 @@ namespace enum class SettingType { BooleanValue, + IntegerValue, }; class LoaderSetting { public: LoaderSetting(const char * id, const char * path, bool * val, bool defaultValue); + LoaderSetting(const char * id, int defaultValue); const char * identifier; const char * json_path; @@ -26,15 +28,19 @@ namespace union { bool * boolValue; + int * intValue; } setting; union { bool boolValue; + int intValue; } defaultValue; }; static LoaderSetting settings[] = { {NXLoaderSetting::CheckForUpdatedFirmware, "CheckForUpdatedFirmware", &loaderSettings.checkForUpdatedFirmware, true}, + {NXLoaderSetting::FirmwareInstallCurrent, 0}, + {NXLoaderSetting::FirmwareInstallTotal, 0}, }; }; @@ -56,6 +62,12 @@ void LoaderSettingChanged(const char * setting, void * /*userData*/) case SettingType::BooleanValue: *loaderSetting.setting.boolValue = g_settings->GetBool(setting); break; + case SettingType::IntegerValue: + if (loaderSetting.setting.intValue != nullptr) + { + *loaderSetting.setting.intValue = g_settings->GetInt(setting); + } + break; default: UNIMPLEMENTED(); } @@ -71,6 +83,12 @@ void SetupLoaderSetting(void) case SettingType::BooleanValue: *loaderSetting.setting.boolValue = loaderSetting.defaultValue.boolValue; break; + case SettingType::IntegerValue: + if (loaderSetting.setting.intValue != nullptr) + { + *loaderSetting.setting.intValue = loaderSetting.defaultValue.intValue; + } + break; default: UNIMPLEMENTED(); } @@ -84,6 +102,11 @@ void SetupLoaderSetting(void) { for (const LoaderSetting & loaderSetting : settings) { + if (loaderSetting.json_path == nullptr) + { + continue; + } + const JsonValue value = JsonGetNestedValue(root, loaderSetting.json_path); switch (loaderSetting.settingType) { @@ -93,6 +116,8 @@ void SetupLoaderSetting(void) *loaderSetting.setting.boolValue = value.asBool(); } break; + case SettingType::IntegerValue: + break; default: UNIMPLEMENTED(); } @@ -112,6 +137,10 @@ void SetupLoaderSetting(void) g_settings->SetDefaultBool(loaderSetting.identifier, loaderSetting.defaultValue.boolValue); g_settings->SetBool(loaderSetting.identifier, *loaderSetting.setting.boolValue); break; + case SettingType::IntegerValue: + g_settings->SetDefaultInt(loaderSetting.identifier, loaderSetting.defaultValue.intValue); + g_settings->SetInt(loaderSetting.identifier, loaderSetting.setting.intValue != nullptr ? *loaderSetting.setting.intValue : loaderSetting.defaultValue.intValue); + break; default: UNIMPLEMENTED(); } @@ -133,6 +162,8 @@ void SaveLoaderSettings(void) JsonSetNestedValue(root, loaderSetting.json_path, *loaderSetting.setting.boolValue != 0); } break; + case SettingType::IntegerValue: + break; default: UNIMPLEMENTED(); } @@ -148,3 +179,12 @@ LoaderSetting::LoaderSetting(const char * id, const char * path, bool * val, boo setting.boolValue = val; defaultValue.boolValue = defaultValue_; } + +LoaderSetting::LoaderSetting(const char * id, int defaultValue_) : + identifier(id), + json_path(nullptr), + settingType(SettingType::IntegerValue) +{ + setting.intValue = nullptr; + defaultValue.intValue = defaultValue_; +} diff --git a/src/nxemu-loader/loader_settings_identifiers.h b/src/nxemu-loader/loader_settings_identifiers.h index ca8122d4..e41fde7e 100644 --- a/src/nxemu-loader/loader_settings_identifiers.h +++ b/src/nxemu-loader/loader_settings_identifiers.h @@ -3,4 +3,6 @@ namespace NXLoaderSetting { constexpr const char * CheckForUpdatedFirmware = "nxloader:CheckForUpdatedFirmware"; +constexpr const char * FirmwareInstallCurrent = "nxloader:FirmwareInstallCurrent"; +constexpr const char * FirmwareInstallTotal = "nxloader:FirmwareInstallTotal"; } // namespace NXLoaderSetting diff --git a/src/nxemu-loader/system_loader.cpp b/src/nxemu-loader/system_loader.cpp index 7276987a..c386b68f 100644 --- a/src/nxemu-loader/system_loader.cpp +++ b/src/nxemu-loader/system_loader.cpp @@ -17,6 +17,7 @@ #include "core/loader/loader.h" #include "firmware_zip.h" #include "loader_settings.h" +#include "loader_settings_identifiers.h" #include "rom_info.h" #include #include @@ -257,6 +258,12 @@ std::filesystem::path CreateFirmwareExtractDirectory() return {}; } +void ClearFirmwareInstallProgress() +{ + g_settings->SetInt(NXLoaderSetting::FirmwareInstallCurrent, 0); + g_settings->SetInt(NXLoaderSetting::FirmwareInstallTotal, 0); +} + void CollectFirmwareSourcesFromPath(const std::filesystem::path & path, const FileSys::VirtualFilesystem & vfs, std::vector & sources) { std::error_code ec; @@ -376,10 +383,15 @@ bool InstallFirmwareFilesToRegistered(::FileSystemController & fs_controller, co return false; } + int32_t copied = 0; + g_settings->SetInt(NXLoaderSetting::FirmwareInstallTotal, static_cast(sources.size())); + g_settings->SetInt(NXLoaderSetting::FirmwareInstallCurrent, 0); + for (const FirmwareInstallSource & source : sources) { if (!source.file) { + ClearFirmwareInstallProgress(); g_notify->DisplayError("Copying one or more firmware files failed. See the log for details.", "Firmware install failed"); return false; } @@ -389,11 +401,14 @@ bool InstallFirmwareFilesToRegistered(::FileSystemController & fs_controller, co if (!dst_file || !FileSys::VfsRawCopy(source.file, dst_file)) { LOG_ERROR(Core, "Firmware install: copy failed for {}", filename); + ClearFirmwareInstallProgress(); g_notify->DisplayError("Copying one or more firmware files failed. See the log for details.", "Firmware install failed"); return false; } + g_settings->SetInt(NXLoaderSetting::FirmwareInstallCurrent, ++copied); } + ClearFirmwareInstallProgress(); return true; } @@ -1192,6 +1207,8 @@ bool Systemloader::InstallFirmwarePackage(const char * utf8_path) return false; } + ClearFirmwareInstallProgress(); + FirmwareVersionFormat package_firmware{}; if (!impl->QueryFirmwarePackage(utf8_path, &package_firmware)) { @@ -1232,5 +1249,7 @@ bool Systemloader::InstallFirmwarePackage(const char * utf8_path) { g_notify->DisplayError("Firmware installed successfully.", "Firmware installed"); } + + ClearFirmwareInstallProgress(); return installed; } diff --git a/src/nxemu/user_interface/sciter_main_window.cpp b/src/nxemu/user_interface/sciter_main_window.cpp index 3f5751ba..0b57acfb 100644 --- a/src/nxemu/user_interface/sciter_main_window.cpp +++ b/src/nxemu/user_interface/sciter_main_window.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,9 @@ enum EVENT_EMULATION_STOPPED = 0x2002, EVENT_EMULATION_FIRST_FRAME = 0x2003, EVENT_DISK_CACHE_STATUS = 0x2004, + EVENT_FIRMWARE_INSTALL_DONE = 0x2005, + EVENT_FIRMWARE_INSTALL_ACTIVE = 0x2006, + EVENT_FIRMWARE_INSTALL_FINISHED = 0x2007, }; const uint32_t kKeyboardStateControl = 0x0040u | 0x0080u; @@ -196,8 +200,7 @@ std::string GetGameTitleLoadingHtml(ISystemloader & loader, const char * verb) return stdstr_f("%s ...", verb); } const std::string titleEsc = HtmlEscapeForHtmlContent(std::string(buf.data())); - return stdstr_f("%s %s", verb, - titleEsc.c_str()); + return stdstr_f("%s %s", verb, titleEsc.c_str()); } } // namespace @@ -213,7 +216,9 @@ SciterMainWindow::SciterMainWindow(ISciterUI & sciterUI, const char * windowTitl m_lastDiskCacheStatusPostMs(0), m_lastPostedDiskCacheStage(0), m_shownFirstFrame(false), - m_win32Fullscreen(std::make_unique()) + m_win32Fullscreen(std::make_unique()), + m_firmwareInstallInProgress(false), + m_firmwareInstallUiActive(false) { SettingsStore & settings = SettingsStore::GetInstance(); settings.RegisterCallback(NXCoreSetting::EmulationRunning, SciterMainWindow::EmulationRunning, this); @@ -222,6 +227,7 @@ SciterMainWindow::SciterMainWindow(ISciterUI & sciterUI, const char * windowTitl settings.RegisterCallback(NXCoreSetting::GameName, SciterMainWindow::GameNameChanged, this); settings.RegisterCallback(NXCoreSetting::DisplayedFrames, SciterMainWindow::DisplayedFramesChanged, this); settings.RegisterCallback(NXCoreSetting::DiskCacheLoadTick, SciterMainWindow::DiskCacheLoadChanged, this); + settings.RegisterCallback(NXLoaderSetting::FirmwareInstallTotal, SciterMainWindow::FirmwareInstallTotalChanged, this); settings.RegisterCallback(NXOsSetting::AudioMuted, SciterMainWindow::SettingChanged, this); settings.RegisterCallback(NXOsSetting::AudioVolume, SciterMainWindow::SettingChanged, this); settings.RegisterCallback(NXOsSetting::ResolutionUpFactor, SciterMainWindow::SettingChanged, this); @@ -307,6 +313,7 @@ SciterMainWindow::~SciterMainWindow() settings.UnregisterCallback(NXCoreSetting::GameName, SciterMainWindow::GameNameChanged, this); settings.UnregisterCallback(NXCoreSetting::DisplayedFrames, SciterMainWindow::DisplayedFramesChanged, this); settings.UnregisterCallback(NXCoreSetting::DiskCacheLoadTick, SciterMainWindow::DiskCacheLoadChanged, this); + settings.UnregisterCallback(NXLoaderSetting::FirmwareInstallTotal, SciterMainWindow::FirmwareInstallTotalChanged, this); settings.UnregisterCallback(NXOsSetting::AudioMuted, SciterMainWindow::SettingChanged, this); settings.UnregisterCallback(NXOsSetting::AudioVolume, SciterMainWindow::SettingChanged, this); settings.UnregisterCallback(NXOsSetting::ResolutionUpFactor, SciterMainWindow::SettingChanged, this); @@ -316,6 +323,11 @@ SciterMainWindow::~SciterMainWindow() settings.UnregisterCallback(NXOsSetting::DockedMode, SciterMainWindow::SettingChanged, this); settings.UnregisterCallback(NXUISetting::Hotkeys, SciterMainWindow::HotKeysChanged, this); + m_rootElement.SetTimer(0, (uint32_t *)TIMER_UPDATE_INSTALL_FIRMWARE); + if (m_firmwareInstallThread.joinable()) + { + m_firmwareInstallThread.join(); + } m_systemConfig.reset(nullptr); m_inputConfig.reset(nullptr); @@ -406,7 +418,7 @@ void SciterMainWindow::ResetMenu() optionsMenu.push_back(MenuBarItem(static_cast(GuiAction::OpenControllersDialog), "&Controllers...", nullptr, HotkeyAccelerator(Hotkey::Controllers), MenuBarItem::CheckState::None, MenuIconSvg(GuiAction::OpenControllersDialog))); optionsMenu.push_back(MenuBarItem(static_cast(GuiAction::OpenSystemConfiguration), "Confi&gure...", nullptr, HotkeyAccelerator(Hotkey::Configure), MenuBarItem::CheckState::None, MenuIconSvg(GuiAction::OpenSystemConfiguration))); MenuBarItemList installFirmwareMenu; - if (!m_emulationRunning && m_modules.IsValid()) + if (!m_emulationRunning && !m_firmwareInstallInProgress && m_modules.IsValid()) { optionsMenu.push_back(MenuBarItem(MenuBarItem::SPLITER)); const std::string * installFirmwareSvg = MenuIconSvg(GuiAction::InstallFirmwareFromFile); @@ -642,6 +654,12 @@ void SciterMainWindow::EmulationStateChanged(const char * /*setting*/, void * us if (state == EmulationState::RomLoaded) { impl->UpdateLoadingScreenDetails(); + SciterElement loadingMain(impl->m_rootElement.FindFirst("#LoadingPanel .loading-main")); + if (loadingMain.IsValid()) + { + loadingMain.RemoveClassName("no-game-icon"); + } + impl->RefreshDiskCacheLoadingText(); impl->m_shownFirstFrame = false; impl->ShowPanel(Panel::Loading); } @@ -837,7 +855,7 @@ void SciterMainWindow::OnOpenFile() void SciterMainWindow::OnInstallFirmwareFromFile() { - if (m_window == nullptr || m_emulationRunning || !m_modules.IsValid()) + if (m_window == nullptr || m_emulationRunning || !m_modules.IsValid() || m_firmwareInstallInProgress) { return; } @@ -850,13 +868,12 @@ void SciterMainWindow::OnInstallFirmwareFromFile() return; } - m_modules.Modules().Systemloader().InstallFirmwarePackage(file); - UpdateEmulationStatusText(); + BeginFirmwareInstall(file); } void SciterMainWindow::OnInstallFirmwareFromFolder() { - if (m_window == nullptr || m_emulationRunning || !m_modules.IsValid()) + if (m_window == nullptr || m_emulationRunning || !m_modules.IsValid() || m_firmwareInstallInProgress) { return; } @@ -868,8 +885,166 @@ void SciterMainWindow::OnInstallFirmwareFromFolder() return; } - m_modules.Modules().Systemloader().InstallFirmwarePackage((const char *)folder); + BeginFirmwareInstall((const char *)folder); +} + +void SciterMainWindow::RefreshFirmwareInstallLoading() +{ + SettingsStore & settings = SettingsStore::GetInstance(); + const uint32_t current = static_cast(settings.GetInt(NXLoaderSetting::FirmwareInstallCurrent)); + const uint32_t total = static_cast(settings.GetInt(NXLoaderSetting::FirmwareInstallTotal)); + + SciterElement fillEl(m_rootElement.GetElementByID("LoadingProgressFill")); + SciterElement status(m_rootElement.GetElementByID("loadingText")); + + std::string detail; + if (total > 0) + { + detail = stdstr_f("%u / %u files", current, total); + if (fillEl.IsValid()) + { + const int pct = static_cast((100.0 * static_cast(current)) / static_cast(total)); + UpdateLoadingProgressBar(fillEl, false, pct, false); + } + } + else if (fillEl.IsValid()) + { + UpdateLoadingProgressBar(fillEl, true, 0, false); + } + + if (status.IsValid()) + { + const std::string text = stdstr_f("Installing firmware %s",HtmlEscapeForHtmlContent(detail).c_str()); + status.SetHTML(reinterpret_cast(text.c_str()), text.size()); + } + m_sciterUI.UpdateWindow(m_rootElement.GetElementHwnd(true)); +} + +void SciterMainWindow::StartFirmwareInstallUi() +{ + if (m_firmwareInstallUiActive) + { + return; + } + + m_firmwareInstallUiActive = true; + + SciterElement fillEl(m_rootElement.GetElementByID("LoadingProgressFill")); + SciterElement status(m_rootElement.GetElementByID("loadingText")); + if (fillEl.IsValid()) + { + UpdateLoadingProgressBar(fillEl, true, 0, false); + } + if (status.IsValid()) + { + const std::string text = "Installing firmware"; + status.SetHTML(reinterpret_cast(text.c_str()), text.size()); + } + LoadImageToElement(m_rootElement.GetElementByID("LoadingCornerLogo"), {}); + LoadImageToElement(m_rootElement.GetElementByID("LoadingCornerBanner"), {}); + LoadImageToElement(m_rootElement.GetElementByID("LoadingGameIcon"), {}); + SciterElement loadingMain(m_rootElement.FindFirst("#LoadingPanel .loading-main")); + if (loadingMain.IsValid()) + { + loadingMain.AddClassName("no-game-icon"); + } + ShowPanel(Panel::Loading); + m_sciterUI.UpdateWindow(m_rootElement.GetElementHwnd(true)); + + if (!m_emulationRunning) + { + ResetMenu(); + } + m_rootElement.SetTimer(25, (uint32_t *)TIMER_UPDATE_INSTALL_FIRMWARE); + RefreshFirmwareInstallLoading(); +} + +void SciterMainWindow::StopFirmwareInstallUi() +{ + if (!m_firmwareInstallUiActive) + { + return; + } + + m_firmwareInstallUiActive = false; + m_rootElement.SetTimer(0, (uint32_t *)TIMER_UPDATE_INSTALL_FIRMWARE); + + SciterElement loadingMain(m_rootElement.FindFirst("#LoadingPanel .loading-main")); + if (loadingMain.IsValid()) + { + loadingMain.RemoveClassName("no-game-icon"); + } + + const EmulationState state = static_cast(SettingsStore::GetInstance().GetInt(NXCoreSetting::EmulationState)); + if (state == EmulationState::Running && m_shownFirstFrame) + { + ShowPanel(Panel::Renderer); + } + else if (state == EmulationState::Paused) + { + ShowPanel(Panel::Pause); + } + else if (state == EmulationState::RomLoaded) + { + ShowPanel(Panel::Loading); + RefreshDiskCacheLoadingText(); + } + else + { + ShowPanel(Panel::RomBrowser); + } + UpdateEmulationStatusText(); + if (!m_emulationRunning) + { + ResetMenu(); + } +} + +void SciterMainWindow::FinishFirmwareInstall() +{ + if (m_firmwareInstallThread.joinable()) + { + m_firmwareInstallThread.join(); + } + m_firmwareInstallInProgress = false; + StopFirmwareInstallUi(); + UpdateEmulationStatusText(); + if (!m_emulationRunning) + { + ResetMenu(); + } +} + +void SciterMainWindow::BeginFirmwareInstall(const char * utf8_path) +{ + if (utf8_path == nullptr || utf8_path[0] == '\0' || m_firmwareInstallInProgress || !m_modules.IsValid()) + { + return; + } + + m_firmwareInstallInProgress = true; + m_rootElement.PostEvent(EVENT_FIRMWARE_INSTALL_ACTIVE); + + const std::string path = utf8_path; + m_firmwareInstallThread = std::thread([this, path]() { + m_modules.Modules().Systemloader().InstallFirmwarePackage(path.c_str()); + m_rootElement.PostEvent(EVENT_FIRMWARE_INSTALL_DONE); + }); +} + +void SciterMainWindow::FirmwareInstallTotalChanged(const char * /*setting*/, void * userData) +{ + SciterMainWindow * impl = static_cast(userData); + const int32_t total = SettingsStore::GetInstance().GetInt(NXLoaderSetting::FirmwareInstallTotal); + if (total > 0) + { + impl->m_rootElement.PostEvent(EVENT_FIRMWARE_INSTALL_ACTIVE); + } + else if (impl->m_firmwareInstallUiActive) + { + impl->m_rootElement.PostEvent(EVENT_FIRMWARE_INSTALL_FINISHED); + } } void SciterMainWindow::OnFileExit() @@ -1314,6 +1489,12 @@ void SciterMainWindow::UpdateLoadingScreenDetails() LoadImageToElement(m_rootElement.GetElementByID("LoadingCornerLogo"), logoData); LoadImageToElement(m_rootElement.GetElementByID("LoadingCornerBanner"), bannerData); LoadImageToElement(m_rootElement.GetElementByID("LoadingGameIcon"), iconData); + + SciterElement loadingMain(m_rootElement.FindFirst("#LoadingPanel .loading-main")); + if (loadingMain.IsValid()) + { + loadingMain.RemoveClassName("no-game-icon"); + } } void SciterMainWindow::UpdateUIVisibility() @@ -1618,6 +1799,13 @@ bool SciterMainWindow::OnTimer(SCITER_ELEMENT /*element*/, uint32_t * timerId) { UpdateEmulationStatusText(); } + else if (timerId == (uint32_t *)TIMER_UPDATE_INSTALL_FIRMWARE) + { + if (m_firmwareInstallUiActive) + { + RefreshFirmwareInstallLoading(); + } + } return true; } @@ -1670,5 +1858,17 @@ bool SciterMainWindow::OnEvent(SCITER_ELEMENT element, SCITER_ELEMENT /*source*/ { RefreshDiskCacheLoadingText(); } + else if (event_code == EVENT_FIRMWARE_INSTALL_ACTIVE) + { + StartFirmwareInstallUi(); + } + else if (event_code == EVENT_FIRMWARE_INSTALL_FINISHED) + { + StopFirmwareInstallUi(); + } + else if (event_code == EVENT_FIRMWARE_INSTALL_DONE) + { + FinishFirmwareInstall(); + } return false; } diff --git a/src/nxemu/user_interface/sciter_main_window.h b/src/nxemu/user_interface/sciter_main_window.h index 8db34a31..e6ccecad 100644 --- a/src/nxemu/user_interface/sciter_main_window.h +++ b/src/nxemu/user_interface/sciter_main_window.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +67,7 @@ class SciterMainWindow : TIMER_UPDATE_UI = 5000, TIMER_UPDATE_INPUT, TIMER_UPDATE_STATUS, + TIMER_UPDATE_INSTALL_FIRMWARE, }; public: @@ -94,6 +96,7 @@ private: static void GameNameChanged(const char * setting, void * userData); static void DisplayedFramesChanged(const char * setting, void * userData); static void DiskCacheLoadChanged(const char * setting, void * userData); + static void FirmwareInstallTotalChanged(const char * setting, void * userData); static void HotKeysChanged(const char * setting, void * userData); void UpdateStatusWidgets(); void UpdateInputDrivers(); @@ -108,6 +111,11 @@ private: void OnInputConfig(); void OnInstallFirmwareFromFile(); void OnInstallFirmwareFromFolder(); + void BeginFirmwareInstall(const char * utf8_path); + void StartFirmwareInstallUi(); + void StopFirmwareInstallUi(); + void RefreshFirmwareInstallLoading(); + void FinishFirmwareInstall(); void OnRecetGame(uint32_t fileIndex); void OnToggleDockedMode(); void OnToggleStartGamesInFullscreen(); @@ -187,4 +195,7 @@ private: int m_lastPostedDiskCacheStage; bool m_shownFirstFrame; std::unique_ptr m_win32Fullscreen; + bool m_firmwareInstallInProgress; + bool m_firmwareInstallUiActive; + std::thread m_firmwareInstallThread; };