From f139762c6450c5f4fb961e5f4757c35680a5cec0 Mon Sep 17 00:00:00 2001 From: Exhigh Date: Tue, 8 Oct 2024 10:47:42 +0400 Subject: [PATCH] imgui/renderer: Hide Cursor on Idle Implementation (#1266) Implement hide cursor on idle w/ idle timeout duration (configurable via GUI). While at it add always and never to hide the cursor options as well. * Revert commit #1211 as to not interfere with the cursor states. * Make hide cursor on idle as the default setting w/ timeout duration of 5 seconds to hide. * Add an input tab in the settings page to add the hide cursor setting, with hiding the idle timeout box with respect to the cursor hide option. Co-authored-by: georgemoralis --- src/common/config.cpp | 24 +++ src/common/config.h | 8 + src/imgui/renderer/imgui_impl_sdl3.cpp | 42 +++++- src/qt_gui/settings_dialog.cpp | 31 ++++ src/qt_gui/settings_dialog.h | 1 + src/qt_gui/settings_dialog.ui | 196 ++++++++++++++++++++++++- src/sdl_window.cpp | 6 - 7 files changed, 296 insertions(+), 12 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 8c1c4f79..a7d01f53 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -57,6 +57,8 @@ static bool vkValidationGpu = false; static bool rdocEnable = false; static bool vkMarkers = false; static bool vkCrashDiagnostic = false; +static s16 cursorState = HideCursorState::Idle; +static int cursorHideTimeout = 5; // 5 seconds (default) // Gui std::filesystem::path settings_install_dir = {}; @@ -96,6 +98,14 @@ int getBGMvolume() { return BGMvolume; } +s16 getCursorState() { + return cursorState; +} + +int getCursorHideTimeout() { + return cursorHideTimeout; +} + u32 getScreenWidth() { return screenWidth; } @@ -256,6 +266,14 @@ void setBGMvolume(int volume) { BGMvolume = volume; } +void setCursorState(s16 newCursorState) { + cursorState = newCursorState; +} + +void setCursorHideTimeout(int newcursorHideTimeout) { + cursorHideTimeout = newcursorHideTimeout; +} + void setLanguage(u32 language) { m_language = language; } @@ -450,6 +468,8 @@ void load(const std::filesystem::path& path) { if (data.contains("Input")) { const toml::value& input = data.at("Input"); + cursorState = toml::find_or(input, "cursorState", HideCursorState::Idle); + cursorHideTimeout = toml::find_or(input, "cursorHideTimeout", 5); useSpecialPad = toml::find_or(input, "useSpecialPad", false); specialPadClass = toml::find_or(input, "specialPadClass", 1); } @@ -543,6 +563,8 @@ void save(const std::filesystem::path& path) { data["General"]["updateChannel"] = updateChannel; data["General"]["showSplash"] = isShowSplash; data["General"]["autoUpdate"] = isAutoUpdate; + data["Input"]["cursorState"] = cursorState; + data["Input"]["cursorHideTimeout"] = cursorHideTimeout; data["General"]["backButtonBehavior"] = backButtonBehavior; data["Input"]["useSpecialPad"] = useSpecialPad; data["Input"]["specialPadClass"] = specialPadClass; @@ -592,6 +614,8 @@ void setDefaultValues() { isFullscreen = false; playBGM = false; BGMvolume = 50; + cursorState = HideCursorState::Idle; + cursorHideTimeout = 5; screenWidth = 1280; screenHeight = 720; logFilter = ""; diff --git a/src/common/config.h b/src/common/config.h index 4aad7bed..32cfc605 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -8,6 +8,9 @@ #include "types.h" namespace Config { + +enum HideCursorState : s16 { Never, Idle, Always }; + void load(const std::filesystem::path& path); void save(const std::filesystem::path& path); @@ -16,6 +19,9 @@ bool isFullscreenMode(); bool getPlayBGM(); int getBGMvolume(); +s16 getCursorState(); +int getCursorHideTimeout(); + std::string getLogFilter(); std::string getLogType(); std::string getUserName(); @@ -51,6 +57,8 @@ void setScreenHeight(u32 height); void setFullscreenMode(bool enable); void setPlayBGM(bool enable); void setBGMvolume(int volume); +void setCursorState(s16 cursorState); +void setCursorHideTimeout(int newcursorHideTimeout); void setLanguage(u32 language); void setNeoMode(bool enable); void setUserName(const std::string& type); diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp index cecf504a..230d396f 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.cpp +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -4,6 +4,7 @@ // Based on imgui_impl_sdl3.cpp from Dear ImGui repository #include +#include "common/config.h" #include "imgui_impl_sdl3.h" // SDL @@ -36,6 +37,8 @@ struct SdlData { SDL_Cursor* mouse_cursors[ImGuiMouseCursor_COUNT]{}; SDL_Cursor* mouse_last_cursor{}; int mouse_pending_leave_frame{}; + ImVec2 prev_mouse_pos{0, 0}; + Uint64 lastCursorMoveTime{}; // Gamepad handling ImVector gamepads{}; @@ -371,6 +374,13 @@ bool ProcessEvent(const SDL_Event* event) { ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse); io.AddMousePosEvent(mouse_pos.x, mouse_pos.y); + if (mouse_pos.x != bd->prev_mouse_pos.x || mouse_pos.y != bd->prev_mouse_pos.y) { + bd->prev_mouse_pos.x = mouse_pos.x; + bd->prev_mouse_pos.y = mouse_pos.y; + if (Config::getCursorState() == Config::HideCursorState::Idle) { + bd->lastCursorMoveTime = bd->time; + } + } return true; } case SDL_EVENT_MOUSE_WHEEL: { @@ -447,6 +457,7 @@ bool ProcessEvent(const SDL_Event* event) { return false; bd->mouse_window_id = event->window.windowID; bd->mouse_pending_leave_frame = 0; + bd->lastCursorMoveTime = bd->time; return true; } // - In some cases, when detaching a window from main viewport SDL may send @@ -459,6 +470,7 @@ bool ProcessEvent(const SDL_Event* event) { if (GetViewportForWindowId(event->window.windowID) == NULL) return false; bd->mouse_pending_leave_frame = ImGui::GetFrameCount() + 1; + bd->lastCursorMoveTime = bd->time; return true; } case SDL_EVENT_WINDOW_FOCUS_GAINED: @@ -600,7 +612,18 @@ static void UpdateMouseData() { int window_x, window_y; SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global); SDL_GetWindowPosition(focused_window, &window_x, &window_y); - io.AddMousePosEvent(mouse_x_global - (float)window_x, mouse_y_global - (float)window_y); + mouse_x_global -= (float)window_x; + mouse_y_global -= (float)window_y; + io.AddMousePosEvent(mouse_x_global, mouse_y_global); + // SDL_EVENT_MOUSE_MOTION isn't triggered before the first frame is rendered + // force update the prev_cursor coords + if (mouse_x_global != bd->prev_mouse_pos.x || mouse_y_global != bd->prev_mouse_pos.y && + bd->prev_mouse_pos.y == 0 && + bd->prev_mouse_pos.x == 0) { + bd->prev_mouse_pos.x = mouse_x_global; + bd->prev_mouse_pos.y = mouse_y_global; + bd->lastCursorMoveTime = bd->time; + } } } } @@ -611,10 +634,25 @@ static void UpdateMouseCursor() { return; SdlData* bd = GetBackendData(); + s16 cursorState = Config::getCursorState(); ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); - if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) { + if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None || + cursorState == Config::HideCursorState::Always) { // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor SDL_HideCursor(); + + } else if (cursorState == Config::HideCursorState::Idle && + bd->time - bd->lastCursorMoveTime >= + Config::getCursorHideTimeout() * SDL_GetPerformanceFrequency()) { + + bool wasCursorVisible = SDL_CursorVisible(); + SDL_HideCursor(); + + if (wasCursorVisible) { + SDL_WarpMouseInWindow(SDL_GetKeyboardFocus(), bd->prev_mouse_pos.x, + bd->prev_mouse_pos.y); // Force refresh the cursor state + } + } else { // Show OS mouse cursor SDL_Cursor* expected_cursor = bd->mouse_cursors[imgui_cursor] diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index bcf5762f..3845ab0c 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -47,6 +47,8 @@ QStringList languageNames = {"Arabic", const QVector languageIndexes = {21, 23, 14, 6, 18, 1, 12, 22, 2, 4, 25, 24, 29, 5, 0, 9, 15, 16, 17, 7, 26, 8, 11, 20, 3, 13, 27, 10, 19, 28}; +QStringList hideCursorStates = {"Never", "Idle", "Always"}; + SettingsDialog::SettingsDialog(std::span physical_devices, QWidget* parent) : QDialog(parent), ui(new Ui::SettingsDialog) { ui->setupUi(this); @@ -67,6 +69,8 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge completer->setCaseSensitivity(Qt::CaseInsensitive); ui->consoleLanguageComboBox->setCompleter(completer); + ui->hideCursorComboBox->addItems(hideCursorStates); + InitializeEmulatorLanguages(); LoadValuesFromConfig(); @@ -170,6 +174,18 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge }); } + // Input TAB + { + connect(ui->hideCursorComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](s16 index) { + Config::setCursorState(index); + OnCursorStateChanged(index); + }); + + connect(ui->idleTimeoutSpinBox, &QSpinBox::valueChanged, this, + [](int index) { Config::setCursorHideTimeout(index); }); + } + // GPU TAB { // First options is auto selection -1, so gpuId on the GUI will always have to subtract 1 @@ -246,6 +262,9 @@ void SettingsDialog::LoadValuesFromConfig() { std::find(languageIndexes.begin(), languageIndexes.end(), Config::GetLanguage())) % languageIndexes.size()); ui->emulatorLanguageComboBox->setCurrentIndex(languages[Config::getEmulatorLanguage()]); + ui->hideCursorComboBox->setCurrentIndex(Config::getCursorState()); + OnCursorStateChanged(Config::getCursorState()); + ui->idleTimeoutSpinBox->setValue(Config::getCursorHideTimeout()); ui->graphicsAdapterBox->setCurrentIndex(Config::getGpuId() + 1); ui->widthSpinBox->setValue(Config::getScreenWidth()); ui->heightSpinBox->setValue(Config::getScreenHeight()); @@ -311,6 +330,18 @@ void SettingsDialog::OnLanguageChanged(int index) { emit LanguageChanged(ui->emulatorLanguageComboBox->itemData(index).toString().toStdString()); } +void SettingsDialog::OnCursorStateChanged(s16 index) { + if (index == -1) + return; + if (index == Config::HideCursorState::Idle) { + ui->idleTimeoutGroupBox->show(); + } else { + if (!ui->idleTimeoutGroupBox->isHidden()) { + ui->idleTimeoutGroupBox->hide(); + } + } +} + int SettingsDialog::exec() { return QDialog::exec(); } diff --git a/src/qt_gui/settings_dialog.h b/src/qt_gui/settings_dialog.h index 85ae39aa..d09617ec 100644 --- a/src/qt_gui/settings_dialog.h +++ b/src/qt_gui/settings_dialog.h @@ -33,6 +33,7 @@ private: void LoadValuesFromConfig(); void InitializeEmulatorLanguages(); void OnLanguageChanged(int index); + void OnCursorStateChanged(s16 index); std::unique_ptr ui; diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index a6264af6..b686c3f8 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -34,9 +34,18 @@ :/images/shadps4.ico:/images/shadps4.ico + + false + + + false + + + true + QFrame::Shape::NoFrame @@ -51,8 +60,8 @@ 0 0 - 836 - 446 + 832 + 431 @@ -62,7 +71,7 @@ - 0 + 1 @@ -369,7 +378,7 @@ 10 30 241 - 71 + 92 @@ -485,6 +494,185 @@ + + + Input + + + + + + + + + + Cursor + + + + + + Hide Cursor + + + + + + + + + + + + true + + + + 0 + 85 + + + + Hide Cursor Idle Timeout + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + false + + + + 70 + + + 11 + + + + + true + + + + 0 + 0 + + + + + 80 + 30 + + + + + 16777215 + 16777215 + + + + Qt::LayoutDirection::LeftToRight + + + false + + + Qt::AlignmentFlag::AlignCenter + + + QAbstractSpinBox::ButtonSymbols::UpDownArrows + + + + + + 3600 + + + 5 + + + 10 + + + + + + + s + + + + + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 20 + 20 + + + + + + + + Graphics diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 470f12de..bf29b37f 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -313,9 +313,6 @@ void WindowSDL::onKeyPress(const SDL_Event* event) { if (axis != Input::Axis::AxisMax) { controller->Axis(0, axis, ax); } - if (SDL_GetCursor() != NULL) { - SDL_HideCursor(); - } } void WindowSDL::onGamepadEvent(const SDL_Event* event) { @@ -354,9 +351,6 @@ void WindowSDL::onGamepadEvent(const SDL_Event* event) { controller->CheckButton(0, button, event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN); } } - if (SDL_GetCursor() != NULL) { - SDL_HideCursor(); - } break; case SDL_EVENT_GAMEPAD_AXIS_MOTION: axis = event->gaxis.axis == SDL_GAMEPAD_AXIS_LEFTX ? Input::Axis::LeftX