yuzu: Enable to use controller to close a game

- Add General setting to choose if a confirm dialog is shown when stopping
- Show the right confirm dialog if wanted
  - Reuse dialog window that ask to close the game
- Add "L + Plus + Minus" default shortcut to Stop emulation
- Create generic question dialog based on TAS dialog
  - It allows controller interaction on most dialogs
This commit is contained in:
flodavid 2023-07-25 22:31:39 +02:00
parent da6824d9fd
commit a34565727b
5 changed files with 75 additions and 32 deletions

View file

@ -129,7 +129,7 @@ const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{
{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 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", "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", "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", "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 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 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}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}},

View file

@ -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, select_user_on_boot, "Prompt for user on game boot", "");
INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", ""); 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_closing, "Confirm exit while emulation is running", "");
INSERT(UISettings, confirm_before_stopping, "Confirm stopping emulation", "");
INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", ""); INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", "");
INSERT(UISettings, controller_applet_disabled, "Disable controller applet", ""); INSERT(UISettings, controller_applet_disabled, "Disable controller applet", "");

View file

@ -209,7 +209,7 @@ void GMainWindow::ShowTelemetryCallout() {
tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous " tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous "
"data is collected</a> to help improve yuzu. " "data is collected</a> to help improve yuzu. "
"<br/><br/>Would you like to share your usage data with us?"); "<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; Settings::values.enable_telemetry = false;
system->ApplySettings(); system->ApplySettings();
} }
@ -2418,9 +2418,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT
} }
}(); }();
if (QMessageBox::question(this, tr("Remove Entry"), entry_question, if (!question(this, tr("Remove Entry"), entry_question, QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) {
QMessageBox::No) != QMessageBox::Yes) {
return; return;
} }
@ -2519,8 +2518,8 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
} }
}(); }();
if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No, if (!GMainWindow::question(this, tr("Remove File"), question,
QMessageBox::No) != QMessageBox::Yes) { QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) {
return; return;
} }
@ -3426,9 +3425,26 @@ void GMainWindow::OnPauseContinueGame() {
} }
void GMainWindow::OnStopGame() { void GMainWindow::OnStopGame() {
if (system->GetExitLocked() && !ConfirmForceLockedExit()) { // Open (or not) the right confirm dialog based on current setting and game exit lock
if (UISettings::values.confirm_before_stopping.GetValue() == UISettings::AskStopIndex::Always) {
if (system->GetExitLocked()) {
if (!ConfirmForceLockedExit()) {
return; return;
} }
} else {
if (!ConfirmChangeGame()) {
return;
}
}
} else {
if (UISettings::values.confirm_before_stopping.GetValue() ==
UISettings::AskStopIndex::Game &&
system->GetExitLocked()) {
if (!ConfirmForceLockedExit()) {
return;
}
}
}
play_time_manager->Stop(); play_time_manager->Stop();
// Update game list to show new play time // Update game list to show new play time
@ -3817,22 +3833,11 @@ void GMainWindow::OnTasRecord() {
const bool is_recording = input_subsystem->GetTas()->Record(); const bool is_recording = input_subsystem->GetTas()->Record();
if (!is_recording) { if (!is_recording) {
is_tas_recording_dialog_active = true; is_tas_recording_dialog_active = true;
ControllerNavigation* controller_navigation =
new ControllerNavigation(system->HIDCore(), this); bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
// Use QMessageBox instead of question so we can link controller navigation QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
QMessageBox* box_dialog = new QMessageBox();
box_dialog->setWindowTitle(tr("TAS Recording")); input_subsystem->GetTas()->SaveRecording(answer);
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);
is_tas_recording_dialog_active = false; is_tas_recording_dialog_active = false;
} }
OnTasStateChanged(); OnTasStateChanged();
@ -4073,6 +4078,27 @@ void GMainWindow::OnLoadAmiibo() {
LoadAmiibo(filename); LoadAmiibo(filename);
} }
bool GMainWindow::question(QWidget* parent, const QString& title, const QString& text,
QMessageBox::StandardButtons buttons,
QMessageBox::StandardButton defaultButton) {
ControllerNavigation* controller_navigation = new ControllerNavigation(system->HIDCore(), this);
QMessageBox* box_dialog = new QMessageBox(parent);
box_dialog->setWindowTitle(title);
box_dialog->setText(text);
box_dialog->setStandardButtons(buttons);
box_dialog->setDefaultButton(defaultButton);
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) { void GMainWindow::LoadAmiibo(const QString& filename) {
auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
const QString title = tr("Error loading Amiibo data"); const QString title = tr("Error loading Amiibo data");
@ -4806,8 +4832,7 @@ bool GMainWindow::ConfirmClose() {
return true; return true;
} }
const auto text = tr("Are you sure you want to close yuzu?"); const auto text = tr("Are you sure you want to close yuzu?");
const auto answer = QMessageBox::question(this, tr("yuzu"), text); return question(this, tr("yuzu"), text);
return answer != QMessageBox::No;
} }
void GMainWindow::closeEvent(QCloseEvent* event) { void GMainWindow::closeEvent(QCloseEvent* event) {
@ -4900,11 +4925,11 @@ bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr) if (emu_thread == nullptr)
return true; return true;
const auto answer = QMessageBox::question( // Use custom question to link controller navigation
return question(
this, tr("yuzu"), this, tr("yuzu"),
tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No); QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
return answer != QMessageBox::No;
} }
bool GMainWindow::ConfirmForceLockedExit() { bool GMainWindow::ConfirmForceLockedExit() {
@ -4914,8 +4939,7 @@ bool GMainWindow::ConfirmForceLockedExit() {
const auto text = tr("The currently running application has requested yuzu to not exit.\n\n" 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?"); "Would you like to bypass this and exit anyway?");
const auto answer = QMessageBox::question(this, tr("yuzu"), text); return question(this, tr("yuzu"), text);
return answer != QMessageBox::No;
} }
void GMainWindow::RequestGameExit() { void GMainWindow::RequestGameExit() {

View file

@ -7,6 +7,7 @@
#include <optional> #include <optional>
#include <QMainWindow> #include <QMainWindow>
#include <QMessageBox>
#include <QTimer> #include <QTimer>
#include <QTranslator> #include <QTranslator>
@ -15,6 +16,7 @@
#include "input_common/drivers/tas_input.h" #include "input_common/drivers/tas_input.h"
#include "yuzu/compatibility_list.h" #include "yuzu/compatibility_list.h"
#include "yuzu/hotkeys.h" #include "yuzu/hotkeys.h"
#include "yuzu/util/controller_navigation.h"
#ifdef __unix__ #ifdef __unix__
#include <QVariant> #include <QVariant>
@ -431,6 +433,17 @@ private:
const std::string& command, const std::string& arguments, const std::string& command, const std::string& arguments,
const std::string& categories, const std::string& keywords); 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<Ui::MainWindow> ui;
std::unique_ptr<Core::System> system; std::unique_ptr<Core::System> system;

View file

@ -56,6 +56,8 @@ enum class Theme {
MidnightBlueColorful, MidnightBlueColorful,
}; };
enum AskStopIndex : int { Always, Game, Never };
using Themes = std::array<std::pair<const char*, const char*>, 6>; using Themes = std::array<std::pair<const char*, const char*>, 6>;
extern const Themes themes; extern const Themes themes;
@ -94,6 +96,9 @@ struct Values {
Setting<bool> confirm_before_closing{ Setting<bool> confirm_before_closing{
linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default, linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default,
true, true}; true, true};
Setting<bool> confirm_before_stopping{
linkage, true, "confirmStop", Category::UiGeneral, Settings::Specialization::Default,
true, true};
Setting<bool> first_start{linkage, true, "firstStart", Category::Ui}; Setting<bool> first_start{linkage, true, "firstStart", Category::Ui};
Setting<bool> pause_when_in_background{linkage, Setting<bool> pause_when_in_background{linkage,
false, false,