Make configure dialog non-blocking

Currently, when the configure dialog is open, it prevents inputs to the main
window. This is annoying if you are trying to change your keybinds as you have
to constantly reopen the configure dialog to test your keys.

To make the window non-blocking, we use .show() instead of .exec() on the
configure dialog, and have a slot handle when the confirmation buttons (ok,
cancel, x) are pressed.

However, entering the configuration dialog disables inputs to prevent sending
inputs to the running game while changing inputs. We modify this by preventing
inputs from being sent to the game only when focus is not on the main menu. The
steps are as follows:
- Determine focus in/focus out on main window with QGuiApplication::focusWindowChanged
- Send focus in/focus out signal to ConfigureDialog slot
- ConfigureDialog slot calls EnableConfiguration/DisableConfiguration for ConfigureInput
- ConfigureInput calls EnableConfiguration/DisableConfiguration for ConfigureInputPlayer

Added:
- Signal FocusIn/FocusOut for GMainWindow
- EnableConfiguration()/DisableConfiguration() for ConfigureInputPlayer and
  ConfigureInput to control whether inputs to the game should be blocked.
  ConfigureInput::EnableConfiguration/DisableConfiguration simply forwards the
  call to ConfigureInputPlayer.
This commit is contained in:
Johnny Cai 2022-11-26 08:33:28 -05:00
parent e16d1b85f1
commit 75f00f756f
No known key found for this signature in database
GPG key ID: 30B3A6D0576E4905
8 changed files with 126 additions and 30 deletions

View file

@ -117,6 +117,14 @@ void ConfigureDialog::ApplyConfiguration() {
Settings::LogSettings();
}
void ConfigureDialog::MainWindowFocusIn() {
input_tab->DisableConfiguration();
}
void ConfigureDialog::MainWindowFocusOut() {
input_tab->EnableConfiguration();
}
void ConfigureDialog::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();

View file

@ -46,6 +46,10 @@ public:
void ApplyConfiguration();
public slots:
void MainWindowFocusIn();
void MainWindowFocusOut();
private slots:
void OnLanguageChanged(const QString& locale);

View file

@ -181,6 +181,18 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
LoadConfiguration();
}
void ConfigureInput::EnableConfiguration() {
for (auto controller : player_controllers) {
controller->EnableConfiguration();
}
}
void ConfigureInput::DisableConfiguration() {
for (auto controller : player_controllers) {
controller->DisableConfiguration();
}
}
QList<QWidget*> ConfigureInput::GetSubTabs() const {
return {
ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,

View file

@ -43,6 +43,12 @@ public:
/// Initializes the input dialog with the given input subsystem.
void Initialize(InputCommon::InputSubsystem* input_subsystem_, std::size_t max_players = 8);
/// Enables configuration on controllers
void EnableConfiguration();
/// Disables configuration on controllers
void DisableConfiguration();
/// Save all button configurations to settings file.
void ApplyConfiguration();

View file

@ -274,26 +274,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
is_powered_on{is_powered_on_}, input_subsystem{input_subsystem_}, profiles(profiles_),
timeout_timer(std::make_unique<QTimer>()),
poll_timer(std::make_unique<QTimer>()), bottom_row{bottom_row_}, hid_core{hid_core_} {
if (player_index == 0) {
auto* emulated_controller_p1 =
hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
auto* emulated_controller_handheld =
hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
emulated_controller_p1->SaveCurrentConfig();
emulated_controller_p1->EnableConfiguration();
emulated_controller_handheld->SaveCurrentConfig();
emulated_controller_handheld->EnableConfiguration();
if (emulated_controller_handheld->IsConnected(true)) {
emulated_controller_p1->Disconnect();
emulated_controller = emulated_controller_handheld;
} else {
emulated_controller = emulated_controller_p1;
}
} else {
emulated_controller = hid_core.GetEmulatedControllerByIndex(player_index);
emulated_controller->SaveCurrentConfig();
emulated_controller->EnableConfiguration();
}
EnableConfiguration();
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
@ -771,6 +752,33 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
}
ConfigureInputPlayer::~ConfigureInputPlayer() {
DisableConfiguration();
}
void ConfigureInputPlayer::EnableConfiguration() {
if (player_index == 0) {
auto* emulated_controller_p1 =
hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
auto* emulated_controller_handheld =
hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
emulated_controller_p1->SaveCurrentConfig();
emulated_controller_p1->EnableConfiguration();
emulated_controller_handheld->SaveCurrentConfig();
emulated_controller_handheld->EnableConfiguration();
if (emulated_controller_handheld->IsConnected(true)) {
emulated_controller_p1->Disconnect();
emulated_controller = emulated_controller_handheld;
} else {
emulated_controller = emulated_controller_p1;
}
} else {
emulated_controller = hid_core.GetEmulatedControllerByIndex(player_index);
emulated_controller->SaveCurrentConfig();
emulated_controller->EnableConfiguration();
}
}
void ConfigureInputPlayer::DisableConfiguration() {
if (player_index == 0) {
auto* emulated_controller_p1 =
hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);

View file

@ -56,6 +56,12 @@ public:
bool is_powered_on_, bool debug = false);
~ConfigureInputPlayer() override;
/// Enables configuration on controllers
void EnableConfiguration();
/// Disables configuration on controllers
void DisableConfiguration();
/// Save all button configurations to settings file.
void ApplyConfiguration();

View file

@ -72,6 +72,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QString>
#include <QSysInfo>
#include <QUrl>
#include <QWindow>
#include <QtConcurrent/QtConcurrent>
#ifdef HAVE_SDL2
@ -1246,6 +1247,16 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
}
}
void GMainWindow::FocusWindowChanged(QWindow* focusWindow) {
if (focusWindow && focusWindow->winId() == winId() && !main_window_in_focus) {
emit FocusIn();
main_window_in_focus = true;
} else if (main_window_in_focus) {
emit FocusOut();
main_window_in_focus = false;
}
}
void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::BootGame, this, &GMainWindow::BootGame);
connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
@ -3023,16 +3034,33 @@ void GMainWindow::ResetWindowSize1080() {
}
void GMainWindow::OnConfigure() {
const auto old_theme = UISettings::values.theme;
const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue();
old_configure_value = {UISettings::values.theme,
UISettings::values.enable_discord_presence.GetValue()};
Settings::SetConfiguringGlobal(true);
ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system,
!multiplayer_state->IsHostingPublicRoom());
connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this,
&GMainWindow::OnLanguageChanged);
const auto result = configure_dialog.exec();
configure_dialog.reset(); // The destructor must run first
configure_dialog =
std::make_unique<ConfigureDialog>(this, hotkey_registry, input_subsystem.get(), *system,
!multiplayer_state->IsHostingPublicRoom());
connect(configure_dialog.get(), &ConfigureDialog::LanguageChanged, this,
&GMainWindow::OnLanguageChanged);
connect(configure_dialog.get(), &ConfigureDialog::finished, this,
&GMainWindow::OnConfigureFinished);
configure_dialog->show();
connect(this, &GMainWindow::FocusIn, configure_dialog.get(),
&ConfigureDialog::MainWindowFocusIn);
connect(this, &GMainWindow::FocusOut, configure_dialog.get(),
&ConfigureDialog::MainWindowFocusOut);
}
void GMainWindow::OnConfigureFinished(int result) {
// Configure dialog needs to be deleted after this function finishes to run the appropriate
// destructors
std::unique_ptr<ConfigureDialog> config_dialog(std::move(configure_dialog));
if (result != QDialog::Accepted && !UISettings::values.configuration_applied &&
!UISettings::values.reset_to_defaults) {
// Runs if the user hit Cancel or closed the window, and did not ever press the Apply button
@ -3042,7 +3070,7 @@ void GMainWindow::OnConfigure() {
// Only apply new changes if user hit Okay
// This is here to avoid applying changes if the user hit Apply, made some changes, then hit
// Cancel
configure_dialog.ApplyConfiguration();
config_dialog->ApplyConfiguration();
} else if (UISettings::values.reset_to_defaults) {
LOG_INFO(Frontend, "Resetting all settings to defaults");
if (!Common::FS::RemoveFile(config->GetConfigFilePath())) {
@ -3079,10 +3107,11 @@ void GMainWindow::OnConfigure() {
}
InitializeHotkeys();
if (UISettings::values.theme != old_theme) {
if (UISettings::values.theme != old_configure_value.theme) {
UpdateUITheme();
}
if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) {
if (UISettings::values.enable_discord_presence.GetValue() !=
old_configure_value.discord_presence) {
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
}
@ -4232,6 +4261,8 @@ int main(int argc, char* argv[]) {
QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window,
&GMainWindow::OnAppFocusStateChanged);
QObject::connect(&app, &QGuiApplication::focusWindowChanged, &main_window,
&GMainWindow::FocusWindowChanged);
int result = app.exec();
detached_tasks.WaitForAllTasks();

View file

@ -23,6 +23,7 @@
class Config;
class ClickableLabel;
class ConfigureDialog;
class EmuThread;
class GameList;
class GImageInfo;
@ -122,6 +123,12 @@ class GMainWindow : public QMainWindow {
UI_EMU_STOPPING,
};
// Used to save previous configure values to check for changes
struct OldConfigureValue {
QString theme;
bool discord_presence;
};
public:
void filterBarSetChecked(bool state);
void UpdateUITheme();
@ -132,6 +139,11 @@ public:
void AcceptDropEvent(QDropEvent* event);
signals:
/// Emitted when this main window receives focus
void FocusIn();
/// Emitted when this main window loses focus
void FocusOut();
/**
* Signal that is emitted when a new EmuThread has been created and an emulation session is
@ -195,6 +207,7 @@ public slots:
void WebBrowserOpenWebPage(const std::string& main_url, const std::string& additional_args,
bool is_local);
void OnAppFocusStateChanged(Qt::ApplicationState state);
void FocusWindowChanged(QWindow* focusWindow);
void OnTasStateChanged();
private:
@ -303,6 +316,7 @@ private slots:
void OnMenuInstallToNAND();
void OnMenuRecentFile();
void OnConfigure();
void OnConfigureFinished(int result);
void OnConfigureTas();
void OnTasStartStop();
void OnTasRecord();
@ -430,6 +444,11 @@ private:
// Install progress dialog
QProgressDialog* install_progress;
// Configuration dialog, use unique_ptr as it is possible for the user to open the configure
// menu multiple times, thus we must delete the old menu
std::unique_ptr<ConfigureDialog> configure_dialog;
OldConfigureValue old_configure_value;
// Last game booted, used for multi-process apps
QString last_filename_booted;
@ -445,6 +464,8 @@ private:
// True if TAS recording dialog is visible
bool is_tas_recording_dialog_active{};
bool main_window_in_focus = false;
#ifdef __unix__
QSocketNotifier* sig_interrupt_notifier;
static std::array<int, 3> sig_interrupt_fds;