diff options
Diffstat (limited to 'src/yuzu')
-rw-r--r-- | src/yuzu/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/yuzu/applets/qt_controller.cpp | 39 | ||||
-rw-r--r-- | src/yuzu/applets/qt_controller.h | 6 | ||||
-rw-r--r-- | src/yuzu/applets/qt_controller.ui | 54 | ||||
-rw-r--r-- | src/yuzu/configuration/config.cpp | 4 | ||||
-rw-r--r-- | src/yuzu/configuration/configure_input.cpp | 36 | ||||
-rw-r--r-- | src/yuzu/configuration/configure_input.h | 1 | ||||
-rw-r--r-- | src/yuzu/configuration/shared_translation.cpp | 8 | ||||
-rw-r--r-- | src/yuzu/game_list.cpp | 5 | ||||
-rw-r--r-- | src/yuzu/game_list_worker.cpp | 7 | ||||
-rw-r--r-- | src/yuzu/game_list_worker.h | 5 | ||||
-rw-r--r-- | src/yuzu/main.cpp | 183 | ||||
-rw-r--r-- | src/yuzu/main.h | 18 | ||||
-rw-r--r-- | src/yuzu/uisettings.h | 11 | ||||
-rw-r--r-- | src/yuzu/util/util.cpp | 81 |
15 files changed, 335 insertions, 125 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 9ebece907..34208ed74 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -384,7 +384,7 @@ if (USE_DISCORD_PRESENCE) discord_impl.cpp discord_impl.h ) - target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib) + target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib Qt${QT_MAJOR_VERSION}::Network) target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) endif() diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp index d15559518..ca0e14fad 100644 --- a/src/yuzu/applets/qt_controller.cpp +++ b/src/yuzu/applets/qt_controller.cpp @@ -23,6 +23,7 @@ #include "yuzu/configuration/configure_vibration.h" #include "yuzu/configuration/input_profiles.h" #include "yuzu/main.h" +#include "yuzu/util/controller_navigation.h" namespace { @@ -132,6 +133,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, }; + ui->labelError->setVisible(false); + // Setup/load everything prior to setting up connections. // This avoids unintentionally changing the states of elements while loading them in. SetSupportedControllers(); @@ -143,6 +146,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( LoadConfiguration(); + controller_navigation = new ControllerNavigation(system.HIDCore(), this); + for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { SetExplainText(i); UpdateControllerIcon(i); @@ -151,6 +156,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { if (checked) { + // Hide eventual error message about number of controllers + ui->labelError->setVisible(false); for (std::size_t index = 0; index <= i; ++index) { connected_controller_checkboxes[index]->setChecked(checked); } @@ -199,6 +206,12 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QtControllerSelectorDialog::ApplyConfiguration); + connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, + [this](Qt::Key key) { + QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); + QCoreApplication::postEvent(this, event); + }); + // Enhancement: Check if the parameters have already been met before disconnecting controllers. // If all the parameters are met AND only allows a single player, // stop the constructor here as we do not need to continue. @@ -217,6 +230,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( } QtControllerSelectorDialog::~QtControllerSelectorDialog() { + controller_navigation->UnloadController(); system.HIDCore().DisableAllControllerConfiguration(); } @@ -291,6 +305,31 @@ void QtControllerSelectorDialog::CallConfigureInputProfileDialog() { dialog.exec(); } +void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) { + const auto num_connected_players = static_cast<int>( + std::count_if(player_groupboxes.begin(), player_groupboxes.end(), + [](const QGroupBox* player) { return player->isChecked(); })); + + const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; + const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + + if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) { + // Display error message when trying to validate using "Enter" and "OK" button is disabled + ui->labelError->setVisible(true); + return; + } else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) { + // Remove a player if possible + connected_controller_checkboxes[num_connected_players - 1]->setChecked(false); + return; + } else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) { + // Add a player, if possible + ui->labelError->setVisible(false); + connected_controller_checkboxes[num_connected_players]->setChecked(true); + return; + } + QDialog::keyPressEvent(evt); +} + bool QtControllerSelectorDialog::CheckIfParametersMet() { // Here, we check and validate the current configuration against all applicable parameters. const auto num_connected_players = static_cast<int>( diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h index 2fdc35857..7f0673d06 100644 --- a/src/yuzu/applets/qt_controller.h +++ b/src/yuzu/applets/qt_controller.h @@ -34,6 +34,8 @@ class HIDCore; enum class NpadStyleIndex : u8; } // namespace Core::HID +class ControllerNavigation; + class QtControllerSelectorDialog final : public QDialog { Q_OBJECT @@ -46,6 +48,8 @@ public: int exec() override; + void keyPressEvent(QKeyEvent* evt) override; + private: // Applies the current configuration. void ApplyConfiguration(); @@ -110,6 +114,8 @@ private: Core::System& system; + ControllerNavigation* controller_navigation = nullptr; + // This is true if and only if all parameters are met. Otherwise, this is false. // This determines whether the "OK" button can be clicked to exit the applet. bool parameters_met{false}; diff --git a/src/yuzu/applets/qt_controller.ui b/src/yuzu/applets/qt_controller.ui index 729e921ee..6f7cb3c13 100644 --- a/src/yuzu/applets/qt_controller.ui +++ b/src/yuzu/applets/qt_controller.ui @@ -2624,13 +2624,53 @@ </spacer> </item> <item alignment="Qt::AlignBottom"> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> + <widget class="QWidget" name="closeButtons" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_46"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="labelError"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="styleSheet"> + <string notr="true">QLabel { color : red; }</string> + </property> + <property name="text"> + <string>Not enough controllers</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="margin"> + <number>0</number> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> </widget> </item> </layout> diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 1de093447..d5157c502 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -128,8 +128,8 @@ const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut, false}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral("R+Plus+Minus"), Qt::WindowShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral("L+Plus+Minus"), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index e8f9ebfd8..5a48e388b 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -115,17 +115,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, for (std::size_t i = 0; i < player_tabs.size(); ++i) { player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); player_tabs[i]->layout()->addWidget(player_controllers[i]); - connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) { + connect(player_connected[i], &QCheckBox::clicked, [this, i](int checked) { // Ensures that the controllers are always connected in sequential order - if (is_connected) { - for (std::size_t index = 0; index <= i; ++index) { - player_connected[index]->setChecked(is_connected); - } - } else { - for (std::size_t index = i; index < player_tabs.size(); ++index) { - player_connected[index]->setChecked(is_connected); - } - } + this->propagateMouseClickOnPlayers(i, checked, true); }); connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this, &ConfigureInput::UpdateAllInputDevices); @@ -183,6 +175,30 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, LoadConfiguration(); } +void ConfigureInput::propagateMouseClickOnPlayers(size_t player_index, bool checked, bool origin) { + // Origin has already been toggled + if (!origin) { + player_connected[player_index]->setChecked(checked); + } + + if (checked) { + // Check all previous buttons when checked + if (player_index > 0) { + propagateMouseClickOnPlayers(player_index - 1, checked, false); + } + } else { + // Unchecked all following buttons when unchecked + if (player_index < player_tabs.size() - 1) { + // Reconnect current player if it was the last one checked + // (player number was reduced by more than one) + if (origin && player_connected[player_index + 1]->checkState() == Qt::Checked) { + player_connected[player_index]->setCheckState(Qt::Checked); + } + propagateMouseClickOnPlayers(player_index + 1, checked, false); + } + } +} + QList<QWidget*> ConfigureInput::GetSubTabs() const { return { ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5, diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index c89189c36..abb7f7089 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -56,6 +56,7 @@ private: void UpdateDockedState(bool is_handheld); void UpdateAllInputDevices(); void UpdateAllInputProfiles(std::size_t player_index); + void propagateMouseClickOnPlayers(size_t player_index, bool origin, bool checked); /// Load configuration settings. void LoadConfiguration(); diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp index a4e8af1b4..3fe448f27 100644 --- a/src/yuzu/configuration/shared_translation.cpp +++ b/src/yuzu/configuration/shared_translation.cpp @@ -157,6 +157,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) { INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", ""); INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", ""); INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", ""); + INSERT(UISettings, confirm_before_stopping, "Confirm before stopping emulation", ""); INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", ""); INSERT(UISettings, controller_applet_disabled, "Disable controller applet", ""); @@ -383,6 +384,13 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) { translations->insert( {Settings::EnumMetadata<Settings::ConsoleMode>::Index(), {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}}); + translations->insert( + {Settings::EnumMetadata<Settings::ConfirmStop>::Index(), + { + PAIR(ConfirmStop, Ask_Always, "Always ask (Default)"), + PAIR(ConfirmStop, Ask_Based_On_Game, "Only if game specifies not to stop"), + PAIR(ConfirmStop, Ask_Never, "Never ask"), + }}); #undef PAIR #undef CTX_PAIR diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 74f48031a..2bb1a0239 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -826,12 +826,13 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); + // Before deleting rows, cancel the worker so that it is not using them + emit ShouldCancelWorker(); + // Delete any rows that might already exist if we're repopulating item_model->removeRows(0, item_model->rowCount()); search_field->clear(); - emit ShouldCancelWorker(); - GameListWorker* worker = new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system); diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 588f1dd6e..077ced12b 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -293,7 +293,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, GameListDir* parent_dir) { const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool { - if (stop_processing) { + if (stop_requested) { // Breaks the callback loop. return false; } @@ -399,7 +399,6 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa } void GameListWorker::run() { - stop_processing = false; provider->ClearAllEntries(); for (UISettings::GameDir& game_dir : game_dirs) { @@ -427,9 +426,11 @@ void GameListWorker::run() { } emit Finished(watch_list); + processing_completed.Set(); } void GameListWorker::Cancel() { this->disconnect(); - stop_processing = true; + stop_requested.store(true); + processing_completed.Wait(); } diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 2bb0a0cb6..54dc05e30 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h @@ -12,6 +12,7 @@ #include <QRunnable> #include <QString> +#include "common/thread.h" #include "yuzu/compatibility_list.h" #include "yuzu/play_time_manager.h" @@ -82,7 +83,9 @@ private: const PlayTime::PlayTimeManager& play_time_manager; QStringList watch_list; - std::atomic_bool stop_processing; + + Common::Event processing_completed; + std::atomic_bool stop_requested = false; Core::System& system; }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 5427758c1..1431cf2fe 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -67,6 +67,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #define QT_NO_OPENGL #include <QClipboard> #include <QDesktopServices> +#include <QDir> #include <QFile> #include <QFileDialog> #include <QInputDialog> @@ -76,6 +77,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include <QPushButton> #include <QScreen> #include <QShortcut> +#include <QStandardPaths> #include <QStatusBar> #include <QString> #include <QSysInfo> @@ -209,7 +211,7 @@ void GMainWindow::ShowTelemetryCallout() { tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous " "data is collected</a> to help improve yuzu. " "<br/><br/>Would you like to share your usage data with us?"); - if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) { + if (!question(this, tr("Telemetry"), telemetry_message)) { Settings::values.enable_telemetry = false; system->ApplySettings(); } @@ -2418,9 +2420,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT } }(); - if (QMessageBox::question(this, tr("Remove Entry"), entry_question, - QMessageBox::Yes | QMessageBox::No, - QMessageBox::No) != QMessageBox::Yes) { + if (!question(this, tr("Remove Entry"), entry_question, QMessageBox::Yes | QMessageBox::No, + QMessageBox::No)) { return; } @@ -2519,8 +2520,8 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ } }(); - if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No, - QMessageBox::No) != QMessageBox::Yes) { + if (!GMainWindow::question(this, tr("Remove File"), question, + QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) { return; } @@ -2869,44 +2870,50 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga #endif // __linux__ std::filesystem::path target_directory{}; - // Determine target directory for shortcut -#if defined(WIN32) - const char* home = std::getenv("USERPROFILE"); -#else - const char* home = std::getenv("HOME"); -#endif - const std::filesystem::path home_path = (home == nullptr ? "~" : home); - const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); - if (target == GameListShortcutTarget::Desktop) { - target_directory = home_path / "Desktop"; - if (!Common::FS::IsDir(target_directory)) { - QMessageBox::critical( - this, tr("Create Shortcut"), - tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.") - .arg(QString::fromStdString(target_directory.generic_string())), - QMessageBox::StandardButton::Ok); - return; - } - } else if (target == GameListShortcutTarget::Applications) { - target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) / - "applications"; - if (!Common::FS::CreateDirs(target_directory)) { - QMessageBox::critical( - this, tr("Create Shortcut"), - tr("Cannot create shortcut in applications menu. Path \"%1\" " - "does not exist and cannot be created.") - .arg(QString::fromStdString(target_directory.generic_string())), - QMessageBox::StandardButton::Ok); - return; + switch (target) { + case GameListShortcutTarget::Desktop: { + const QString desktop_path = + QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + target_directory = desktop_path.toUtf8().toStdString(); + break; + } + case GameListShortcutTarget::Applications: { + const QString applications_path = + QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); + if (applications_path.isEmpty()) { + const char* home = std::getenv("HOME"); + if (home != nullptr) { + target_directory = std::filesystem::path(home) / ".local/share/applications"; + } + } else { + target_directory = applications_path.toUtf8().toStdString(); } + break; + } + default: + return; + } + + const QDir dir(QString::fromStdString(target_directory.generic_string())); + if (!dir.exists()) { + QMessageBox::critical(this, tr("Create Shortcut"), + tr("Cannot create shortcut. Path \"%1\" does not exist.") + .arg(QString::fromStdString(target_directory.generic_string())), + QMessageBox::StandardButton::Ok); + return; } const std::string game_file_name = std::filesystem::path(game_path).filename().string(); // Determine full paths for icon and shortcut #if defined(__linux__) || defined(__FreeBSD__) + const char* home = std::getenv("HOME"); + const std::filesystem::path home_path = (home == nullptr ? "~" : home); + const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); + std::filesystem::path system_icons_path = - (xdg_data_home == nullptr ? home_path / ".local/share/" : xdg_data_home) / + (xdg_data_home == nullptr ? home_path / ".local/share/" + : std::filesystem::path(xdg_data_home)) / "icons/hicolor/256x256"; if (!Common::FS::CreateDirs(system_icons_path)) { QMessageBox::critical( @@ -3401,10 +3408,13 @@ void GMainWindow::OnRestartGame() { if (!system->IsPoweredOn()) { return; } - // Make a copy since ShutdownGame edits game_path - const auto current_game = QString(current_game_path); - ShutdownGame(); - BootGame(current_game); + + if (ConfirmShutdownGame()) { + // Make a copy since ShutdownGame edits game_path + const auto current_game = QString(current_game_path); + ShutdownGame(); + BootGame(current_game); + } } void GMainWindow::OnPauseGame() { @@ -3426,18 +3436,39 @@ void GMainWindow::OnPauseContinueGame() { } void GMainWindow::OnStopGame() { - if (system->GetExitLocked() && !ConfirmForceLockedExit()) { - return; + if (ConfirmShutdownGame()) { + play_time_manager->Stop(); + // Update game list to show new play time + game_list->PopulateAsync(UISettings::values.game_dirs); + if (OnShutdownBegin()) { + OnShutdownBeginDialog(); + } else { + OnEmulationStopped(); + } } +} - play_time_manager->Stop(); - // Update game list to show new play time - game_list->PopulateAsync(UISettings::values.game_dirs); - if (OnShutdownBegin()) { - OnShutdownBeginDialog(); +bool GMainWindow::ConfirmShutdownGame() { + if (UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Always) { + if (system->GetExitLocked()) { + if (!ConfirmForceLockedExit()) { + return false; + } + } else { + if (!ConfirmChangeGame()) { + return false; + } + } } else { - OnEmulationStopped(); + if (UISettings::values.confirm_before_stopping.GetValue() == + ConfirmStop::Ask_Based_On_Game && + system->GetExitLocked()) { + if (!ConfirmForceLockedExit()) { + return false; + } + } } + return true; } void GMainWindow::OnLoadComplete() { @@ -3817,22 +3848,11 @@ void GMainWindow::OnTasRecord() { const bool is_recording = input_subsystem->GetTas()->Record(); if (!is_recording) { is_tas_recording_dialog_active = true; - ControllerNavigation* controller_navigation = - new ControllerNavigation(system->HIDCore(), this); - // Use QMessageBox instead of question so we can link controller navigation - QMessageBox* box_dialog = new QMessageBox(); - box_dialog->setWindowTitle(tr("TAS Recording")); - box_dialog->setText(tr("Overwrite file of player 1?")); - box_dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No); - box_dialog->setDefaultButton(QMessageBox::Yes); - connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, - [box_dialog](Qt::Key key) { - QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); - QCoreApplication::postEvent(box_dialog, event); - }); - int res = box_dialog->exec(); - controller_navigation->UnloadController(); - input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes); + + bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + + input_subsystem->GetTas()->SaveRecording(answer); is_tas_recording_dialog_active = false; } OnTasStateChanged(); @@ -4073,6 +4093,29 @@ void GMainWindow::OnLoadAmiibo() { LoadAmiibo(filename); } +bool GMainWindow::question(QWidget* parent, const QString& title, const QString& text, + QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton) { + + QMessageBox* box_dialog = new QMessageBox(parent); + box_dialog->setWindowTitle(title); + box_dialog->setText(text); + box_dialog->setStandardButtons(buttons); + box_dialog->setDefaultButton(defaultButton); + + ControllerNavigation* controller_navigation = + new ControllerNavigation(system->HIDCore(), box_dialog); + connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, + [box_dialog](Qt::Key key) { + QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); + QCoreApplication::postEvent(box_dialog, event); + }); + int res = box_dialog->exec(); + + controller_navigation->UnloadController(); + return res == QMessageBox::Yes; +} + void GMainWindow::LoadAmiibo(const QString& filename) { auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); const QString title = tr("Error loading Amiibo data"); @@ -4806,8 +4849,7 @@ bool GMainWindow::ConfirmClose() { return true; } const auto text = tr("Are you sure you want to close yuzu?"); - const auto answer = QMessageBox::question(this, tr("yuzu"), text); - return answer != QMessageBox::No; + return question(this, tr("yuzu"), text); } void GMainWindow::closeEvent(QCloseEvent* event) { @@ -4900,11 +4942,11 @@ bool GMainWindow::ConfirmChangeGame() { if (emu_thread == nullptr) return true; - const auto answer = QMessageBox::question( + // Use custom question to link controller navigation + return question( this, tr("yuzu"), tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - return answer != QMessageBox::No; + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); } bool GMainWindow::ConfirmForceLockedExit() { @@ -4914,8 +4956,7 @@ bool GMainWindow::ConfirmForceLockedExit() { const auto text = tr("The currently running application has requested yuzu to not exit.\n\n" "Would you like to bypass this and exit anyway?"); - const auto answer = QMessageBox::question(this, tr("yuzu"), text); - return answer != QMessageBox::No; + return question(this, tr("yuzu"), text); } void GMainWindow::RequestGameExit() { diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 2346eb3bd..270a40c5f 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -7,6 +7,7 @@ #include <optional> #include <QMainWindow> +#include <QMessageBox> #include <QTimer> #include <QTranslator> @@ -15,6 +16,7 @@ #include "input_common/drivers/tas_input.h" #include "yuzu/compatibility_list.h" #include "yuzu/hotkeys.h" +#include "yuzu/util/controller_navigation.h" #ifdef __unix__ #include <QVariant> @@ -424,6 +426,11 @@ private: bool CheckSystemArchiveDecryption(); bool CheckFirmwarePresence(); void ConfigureFilesystemProvider(const std::string& filepath); + /** + * Open (or not) the right confirm dialog based on current setting and game exit lock + * @returns true if the player confirmed or the settings do no require it + */ + bool ConfirmShutdownGame(); QString GetTasStateDescription() const; bool CreateShortcut(const std::string& shortcut_path, const std::string& title, @@ -431,6 +438,17 @@ private: const std::string& command, const std::string& arguments, const std::string& categories, const std::string& keywords); + /** + * Mimic the behavior of QMessageBox::question but link controller navigation to the dialog + * The only difference is that it returns a boolean. + * + * @returns true if buttons contains QMessageBox::Yes and the user clicks on the "Yes" button. + */ + bool question(QWidget* parent, const QString& title, const QString& text, + QMessageBox::StandardButtons buttons = + QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); + std::unique_ptr<Ui::MainWindow> ui; std::unique_ptr<Core::System> system; diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 975008159..b62ff620c 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -16,7 +16,9 @@ #include "common/settings_enums.h" using Settings::Category; +using Settings::ConfirmStop; using Settings::Setting; +using Settings::SwitchableSetting; #ifndef CANNOT_EXPLICITLY_INSTANTIATE namespace Settings { @@ -94,6 +96,15 @@ struct Values { Setting<bool> confirm_before_closing{ linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default, true, true}; + + SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage, + ConfirmStop::Ask_Always, + "confirmStop", + Category::UiGeneral, + Settings::Specialization::Default, + true, + true}; + Setting<bool> first_start{linkage, true, "firstStart", Category::Ui}; Setting<bool> pause_when_in_background{linkage, false, diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp index 61cf00176..f2854c8ec 100644 --- a/src/yuzu/util/util.cpp +++ b/src/yuzu/util/util.cpp @@ -63,25 +63,15 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) { }; #pragma pack(pop) - QImage source_image = image.convertToFormat(QImage::Format_RGB32); + const QImage source_image = image.convertToFormat(QImage::Format_RGB32); + constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16}; constexpr int bytes_per_pixel = 4; - const int image_size = source_image.width() * source_image.height() * bytes_per_pixel; - - BITMAPINFOHEADER info_header{}; - info_header.biSize = sizeof(BITMAPINFOHEADER), info_header.biWidth = source_image.width(), - info_header.biHeight = source_image.height() * 2, info_header.biPlanes = 1, - info_header.biBitCount = bytes_per_pixel * 8, info_header.biCompression = BI_RGB; - - const IconDir icon_dir{.id_reserved = 0, .id_type = 1, .id_count = 1}; - const IconDirEntry icon_entry{.width = static_cast<BYTE>(source_image.width()), - .height = static_cast<BYTE>(source_image.height() * 2), - .color_count = 0, - .reserved = 0, - .planes = 1, - .bit_count = bytes_per_pixel * 8, - .bytes_in_res = - static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size), - .image_offset = sizeof(IconDir) + sizeof(IconDirEntry)}; + + const IconDir icon_dir{ + .id_reserved = 0, + .id_type = 1, + .id_count = static_cast<WORD>(scale_sizes.size()), + }; Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write, Common::FS::FileType::BinaryFile); @@ -92,20 +82,55 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) { if (!icon_file.Write(icon_dir)) { return false; } - if (!icon_file.Write(icon_entry)) { - return false; - } - if (!icon_file.Write(info_header)) { - return false; + + std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size()); + for (std::size_t i = 0; i < scale_sizes.size(); i++) { + const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel; + const IconDirEntry icon_entry{ + .width = static_cast<BYTE>(scale_sizes[i]), + .height = static_cast<BYTE>(scale_sizes[i]), + .color_count = 0, + .reserved = 0, + .planes = 1, + .bit_count = bytes_per_pixel * 8, + .bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size), + .image_offset = static_cast<DWORD>(image_offset), + }; + image_offset += icon_entry.bytes_in_res; + if (!icon_file.Write(icon_entry)) { + return false; + } } - for (int y = 0; y < image.height(); y++) { - const auto* line = source_image.scanLine(source_image.height() - 1 - y); - std::vector<u8> line_data(source_image.width() * bytes_per_pixel); - std::memcpy(line_data.data(), line, line_data.size()); - if (!icon_file.Write(line_data)) { + for (std::size_t i = 0; i < scale_sizes.size(); i++) { + const QImage scaled_image = source_image.scaled( + scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + const BITMAPINFOHEADER info_header{ + .biSize = sizeof(BITMAPINFOHEADER), + .biWidth = scaled_image.width(), + .biHeight = scaled_image.height() * 2, + .biPlanes = 1, + .biBitCount = bytes_per_pixel * 8, + .biCompression = BI_RGB, + .biSizeImage{}, + .biXPelsPerMeter{}, + .biYPelsPerMeter{}, + .biClrUsed{}, + .biClrImportant{}, + }; + + if (!icon_file.Write(info_header)) { return false; } + + for (int y = 0; y < scaled_image.height(); y++) { + const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y); + std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel); + std::memcpy(line_data.data(), line, line_data.size()); + if (!icon_file.Write(line_data)) { + return false; + } + } } icon_file.Close(); |