From ca42d027acfd8c362d727bd78e0a8fd5b79c8f49 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sun, 27 Dec 2020 14:08:13 +1000 Subject: [PATCH] Qt: Implement relative mouse mode --- src/core/controller.cpp | 2 +- src/core/controller.h | 2 +- src/core/host_interface.cpp | 12 +++++- src/core/host_interface.h | 3 ++ src/core/namco_guncon.cpp | 3 +- src/core/namco_guncon.h | 2 +- src/core/playstation_mouse.h | 2 + src/duckstation-qt/mainwindow.cpp | 26 +++++++++++++ src/duckstation-qt/mainwindow.h | 2 + src/duckstation-qt/qtdisplaywidget.cpp | 54 ++++++++++++++++++++++++-- src/duckstation-qt/qtdisplaywidget.h | 7 ++++ src/duckstation-qt/qthostinterface.cpp | 6 +++ src/duckstation-qt/qthostinterface.h | 3 ++ 13 files changed, 116 insertions(+), 8 deletions(-) diff --git a/src/core/controller.cpp b/src/core/controller.cpp index 9ec931ed3..9feae5593 100644 --- a/src/core/controller.cpp +++ b/src/core/controller.cpp @@ -47,7 +47,7 @@ float Controller::GetVibrationMotorStrength(u32 motor) void Controller::LoadSettings(const char* section) {} -bool Controller::GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale) +bool Controller::GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale, bool* relative_mode) { return false; } diff --git a/src/core/controller.h b/src/core/controller.h index 7c51f9d1b..5c7a9a9b4 100644 --- a/src/core/controller.h +++ b/src/core/controller.h @@ -64,7 +64,7 @@ public: virtual void LoadSettings(const char* section); /// Returns the software cursor to use for this controller, if any. - virtual bool GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale); + virtual bool GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale, bool* relative_mode); /// Creates a new controller of the specified type. static std::unique_ptr Create(ControllerType type, u32 index); diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index aec841acb..ec3c6e86d 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -917,20 +917,28 @@ void HostInterface::UpdateSoftwareCursor() { if (System::IsShutdown()) { + SetMouseMode(false, false); m_display->ClearSoftwareCursor(); return; } const Common::RGBA8Image* image = nullptr; float image_scale = 1.0f; + bool relative_mode = false; + bool hide_cursor = false; for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { Controller* controller = System::GetController(i); - if (controller && controller->GetSoftwareCursor(&image, &image_scale)) + if (controller && controller->GetSoftwareCursor(&image, &image_scale, &relative_mode)) + { + hide_cursor = true; break; + } } + SetMouseMode(relative_mode, hide_cursor); + if (image && image->IsValid()) { m_display->SetSoftwareCursor(image->GetPixels(), image->GetWidth(), image->GetHeight(), image->GetByteStride(), @@ -967,6 +975,8 @@ void HostInterface::RecreateSystem() System::ResetPerformanceCounters(); } +void HostInterface::SetMouseMode(bool relative, bool hide_cursor) {} + void HostInterface::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/, int progress_max /*= -1*/, int progress_value /*= -1*/) { diff --git a/src/core/host_interface.h b/src/core/host_interface.h index d9956b7f6..c984f7d91 100644 --- a/src/core/host_interface.h +++ b/src/core/host_interface.h @@ -175,6 +175,9 @@ protected: /// Switches the GPU renderer by saving state, recreating the display window, and restoring state (if needed). virtual void RecreateSystem(); + /// Enables "relative" mouse mode, locking the cursor position and returning relative coordinates. + virtual void SetMouseMode(bool relative, bool hide_cursor); + /// Sets the user directory to the program directory, i.e. "portable mode". void SetUserDirectoryToProgramDirectory(); diff --git a/src/core/namco_guncon.cpp b/src/core/namco_guncon.cpp index 35edca366..8e508e572 100644 --- a/src/core/namco_guncon.cpp +++ b/src/core/namco_guncon.cpp @@ -280,12 +280,13 @@ void NamcoGunCon::LoadSettings(const char* section) m_x_scale = g_host_interface->GetFloatSettingValue(section, "XScale", 1.0f); } -bool NamcoGunCon::GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale) +bool NamcoGunCon::GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale, bool* relative_mode) { if (!m_crosshair_image.IsValid()) return false; *image = &m_crosshair_image; *image_scale = m_crosshair_image_scale; + *relative_mode = false; return true; } diff --git a/src/core/namco_guncon.h b/src/core/namco_guncon.h index 210996119..a316f8b55 100644 --- a/src/core/namco_guncon.h +++ b/src/core/namco_guncon.h @@ -34,7 +34,7 @@ public: void Reset() override; bool DoState(StateWrapper& sw, bool apply_input_state) override; void LoadSettings(const char* section) override; - bool GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale) override; + bool GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale, bool* relative_mode) override; void SetAxisState(s32 axis_code, float value) override; void SetButtonState(s32 button_code, bool pressed) override; diff --git a/src/core/playstation_mouse.h b/src/core/playstation_mouse.h index 3ffcafb60..65740019e 100644 --- a/src/core/playstation_mouse.h +++ b/src/core/playstation_mouse.h @@ -39,6 +39,8 @@ public: void SetButtonState(Button button, bool pressed); + bool GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale, bool* relative_mode) override; + private: void UpdatePosition(); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 84a233577..9bfcc461a 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -290,6 +290,22 @@ void MainWindow::focusDisplayWidget() m_display_widget->setFocus(); } +void MainWindow::onMouseModeRequested(bool relative_mode, bool hide_cursor) +{ + if (!m_display_widget) + return; + + const bool paused = System::IsPaused(); + + if (hide_cursor) + m_display_widget->setCursor(Qt::BlankCursor); + else + m_display_widget->unsetCursor(); + + m_relative_mouse_mode = relative_mode; + m_display_widget->setRelativeMode(!paused && relative_mode); +} + void MainWindow::onEmulationStarting() { m_emulation_running = true; @@ -327,6 +343,9 @@ void MainWindow::onEmulationPaused(bool paused) { QSignalBlocker blocker(m_ui.actionPause); m_ui.actionPause->setChecked(paused); + + if (m_display_widget) + m_display_widget->setRelativeMode(!paused && m_relative_mouse_mode); } void MainWindow::onStateSaved(const QString& game_code, bool global, qint32 slot) @@ -372,6 +391,9 @@ void MainWindow::onApplicationStateChanged(Qt::ApplicationState state) { m_host_interface->pauseSystem(true); m_was_paused_by_focus_loss = true; + + if (m_display_widget) + m_display_widget->setRelativeMode(false); } } else @@ -381,6 +403,9 @@ void MainWindow::onApplicationStateChanged(Qt::ApplicationState state) if (System::IsPaused()) m_host_interface->pauseSystem(false); m_was_paused_by_focus_loss = false; + + if (m_display_widget) + m_display_widget->setRelativeMode(m_relative_mouse_mode); } } } @@ -974,6 +999,7 @@ void MainWindow::connectSignals() &MainWindow::onSystemPerformanceCountersUpdated); connect(m_host_interface, &QtHostInterface::runningGameChanged, this, &MainWindow::onRunningGameChanged); connect(m_host_interface, &QtHostInterface::exitRequested, this, &MainWindow::close); + connect(m_host_interface, &QtHostInterface::mouseModeRequested, this, &MainWindow::onMouseModeRequested); // These need to be queued connections to stop crashing due to menus opening/closing and switching focus. connect(m_game_list_widget, &GameListWidget::entrySelected, this, &MainWindow::onGameListEntrySelected, diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index 3d0fd1d66..ef7ba98ae 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -52,6 +52,7 @@ private Q_SLOTS: void displaySizeRequested(qint32 width, qint32 height); void destroyDisplay(); void focusDisplayWidget(); + void onMouseModeRequested(bool relative_mode, bool hide_cursor); void setTheme(const QString& theme); void updateTheme(); @@ -148,6 +149,7 @@ private: bool m_emulation_running = false; bool m_was_paused_by_focus_loss = false; bool m_open_debugger_on_start = false; + bool m_relative_mouse_mode = false; GDBServer* m_gdb_server = nullptr; }; diff --git a/src/duckstation-qt/qtdisplaywidget.cpp b/src/duckstation-qt/qtdisplaywidget.cpp index 1854fcde1..9470a090a 100644 --- a/src/duckstation-qt/qtdisplaywidget.cpp +++ b/src/duckstation-qt/qtdisplaywidget.cpp @@ -92,6 +92,29 @@ std::optional QtDisplayWidget::getWindowInfo() const return wi; } +void QtDisplayWidget::setRelativeMode(bool enabled) +{ + if (m_relative_mouse_enabled == enabled) + return; + + if (enabled) + { + m_relative_mouse_start_position = QCursor::pos(); + + const QPoint center_pos = mapToGlobal(QPoint(width() / 2, height() / 2)); + QCursor::setPos(center_pos); + m_relative_mouse_last_position = center_pos; + grabMouse(); + } + else + { + QCursor::setPos(m_relative_mouse_start_position); + releaseMouse(); + } + + m_relative_mouse_enabled = enabled; +} + QPaintEngine* QtDisplayWidget::paintEngine() const { return nullptr; @@ -113,10 +136,35 @@ bool QtDisplayWidget::event(QEvent* event) case QEvent::MouseMove: { - const qreal dpr = devicePixelRatioFromScreen(); const QMouseEvent* mouse_event = static_cast(event); - emit windowMouseMoveEvent(static_cast(static_cast(mouse_event->x()) * dpr), - static_cast(static_cast(mouse_event->y()) * dpr)); + + if (!m_relative_mouse_enabled) + { + const qreal dpr = devicePixelRatioFromScreen(); + const int scaled_x = static_cast(static_cast(mouse_event->x()) * dpr); + const int scaled_y = static_cast(static_cast(mouse_event->y()) * dpr); + + windowMouseMoveEvent(scaled_x, scaled_y); + } + else + { + const QPoint center_pos = mapToGlobal(QPoint((width() + 1) / 2, (height() + 1) / 2)); + const QPoint mouse_pos = mapToGlobal(mouse_event->pos()); + + const int dx = mouse_pos.x() - center_pos.x(); + const int dy = mouse_pos.y() - center_pos.y(); + m_relative_mouse_last_position.setX(m_relative_mouse_last_position.x() + dx); + m_relative_mouse_last_position.setY(m_relative_mouse_last_position.y() + dy); + windowMouseMoveEvent(m_relative_mouse_last_position.x(), m_relative_mouse_last_position.y()); + QCursor::setPos(center_pos); + +#if 0 + qCritical() << "center" << center_pos.x() << "," << center_pos.y(); + qCritical() << "mouse" << mouse_pos.x() << "," << mouse_pos.y(); + qCritical() << "dxdy" << dx << "," << dy; +#endif + } + return true; } diff --git a/src/duckstation-qt/qtdisplaywidget.h b/src/duckstation-qt/qtdisplaywidget.h index 7d15c8cc5..49e79f699 100644 --- a/src/duckstation-qt/qtdisplaywidget.h +++ b/src/duckstation-qt/qtdisplaywidget.h @@ -20,6 +20,8 @@ public: std::optional getWindowInfo() const; + void setRelativeMode(bool enabled); + Q_SIGNALS: void windowResizedEvent(int width, int height); void windowRestoredEvent(); @@ -30,4 +32,9 @@ Q_SIGNALS: protected: bool event(QEvent* event) override; + +private: + QPoint m_relative_mouse_start_position{}; + QPoint m_relative_mouse_last_position{}; + bool m_relative_mouse_enabled = false; }; diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 273cd2618..17ea20ea9 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -577,6 +577,7 @@ void QtHostInterface::updateDisplayState() if (!System::IsShutdown()) { g_gpu->UpdateResolutionScale(); + UpdateSoftwareCursor(); redrawDisplayWindow(); } UpdateSpeedLimiterState(); @@ -736,6 +737,11 @@ void QtHostInterface::UpdateInputMap() updateInputMap(); } +void QtHostInterface::SetMouseMode(bool relative, bool hide_cursor) +{ + emit mouseModeRequested(relative, hide_cursor); +} + void QtHostInterface::updateInputMap() { if (!isOnWorkerThread()) diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index 2cb3e6827..349622f43 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -139,6 +139,7 @@ Q_SIGNALS: void runningGameChanged(const QString& filename, const QString& game_code, const QString& game_title); void exitRequested(); void inputProfileLoaded(); + void mouseModeRequested(bool relative, bool hide_cursor); public Q_SLOTS: void setDefaultSettings(); @@ -203,6 +204,8 @@ protected: void SetDefaultSettings(SettingsInterface& si) override; void UpdateInputMap() override; + void SetMouseMode(bool relative, bool hide_cursor) override; + private: enum : u32 {