Merge pull request #12160 from german77/mouse_constrain

yuzu: Constrain mouse in render window when emulated
This commit is contained in:
liamwhite 2023-11-27 11:56:24 -05:00 committed by GitHub
commit 5a96c525e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 65 additions and 54 deletions

View file

@ -167,6 +167,11 @@ protected:
*/ */
std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const; std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const;
/**
* Clip the provided coordinates to be inside the touchscreen area.
*/
std::pair<u32, u32> ClipToTouchScreen(u32 new_x, u32 new_y) const;
WindowSystemInfo window_info; WindowSystemInfo window_info;
bool strict_context_required = false; bool strict_context_required = false;
@ -181,11 +186,6 @@ private:
// By default, ignore this request and do nothing. // By default, ignore this request and do nothing.
} }
/**
* Clip the provided coordinates to be inside the touchscreen area.
*/
std::pair<u32, u32> ClipToTouchScreen(u32 new_x, u32 new_y) const;
Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout
u32 client_area_width; ///< Current client width, should be set by window impl. u32 client_area_width; ///< Current client width, should be set by window impl.

View file

@ -30,7 +30,6 @@
#include <QSize> #include <QSize>
#include <QStringLiteral> #include <QStringLiteral>
#include <QSurfaceFormat> #include <QSurfaceFormat>
#include <QTimer>
#include <QWindow> #include <QWindow>
#include <QtCore/qobjectdefs.h> #include <QtCore/qobjectdefs.h>
@ -66,6 +65,8 @@ class QObject;
class QPaintEngine; class QPaintEngine;
class QSurface; class QSurface;
constexpr int default_mouse_constrain_timeout = 10;
EmuThread::EmuThread(Core::System& system) : m_system{system} {} EmuThread::EmuThread(Core::System& system) : m_system{system} {}
EmuThread::~EmuThread() = default; EmuThread::~EmuThread() = default;
@ -304,6 +305,9 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
Qt::QueuedConnection); Qt::QueuedConnection);
connect(this, &GRenderWindow::ExitSignal, parent, &GMainWindow::OnExit, Qt::QueuedConnection); connect(this, &GRenderWindow::ExitSignal, parent, &GMainWindow::OnExit, Qt::QueuedConnection);
connect(this, &GRenderWindow::TasPlaybackStateChanged, parent, &GMainWindow::OnTasStateChanged); connect(this, &GRenderWindow::TasPlaybackStateChanged, parent, &GMainWindow::OnTasStateChanged);
mouse_constrain_timer.setInterval(default_mouse_constrain_timeout);
connect(&mouse_constrain_timer, &QTimer::timeout, this, &GRenderWindow::ConstrainMouse);
} }
void GRenderWindow::ExecuteProgram(std::size_t program_index) { void GRenderWindow::ExecuteProgram(std::size_t program_index) {
@ -393,6 +397,22 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
QWidget::closeEvent(event); QWidget::closeEvent(event);
} }
void GRenderWindow::leaveEvent(QEvent* event) {
if (Settings::values.mouse_panning) {
const QRect& rect = QWidget::geometry();
QPoint position = QCursor::pos();
qint32 x = qBound(rect.left(), position.x(), rect.right());
qint32 y = qBound(rect.top(), position.y(), rect.bottom());
// Only start the timer if the mouse has left the window bound.
// The leave event is also triggered when the window looses focus.
if (x != position.x() || y != position.y()) {
mouse_constrain_timer.start();
}
event->accept();
}
}
int GRenderWindow::QtKeyToSwitchKey(Qt::Key qt_key) { int GRenderWindow::QtKeyToSwitchKey(Qt::Key qt_key) {
static constexpr std::array<std::pair<Qt::Key, Settings::NativeKeyboard::Keys>, 106> key_map = { static constexpr std::array<std::pair<Qt::Key, Settings::NativeKeyboard::Keys>, 106> key_map = {
std::pair<Qt::Key, Settings::NativeKeyboard::Keys>{Qt::Key_A, Settings::NativeKeyboard::A}, std::pair<Qt::Key, Settings::NativeKeyboard::Keys>{Qt::Key_A, Settings::NativeKeyboard::A},
@ -658,10 +678,19 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
input_subsystem->GetMouse()->TouchMove(touch_x, touch_y); input_subsystem->GetMouse()->TouchMove(touch_x, touch_y);
input_subsystem->GetMouse()->Move(pos.x(), pos.y(), center_x, center_y); input_subsystem->GetMouse()->Move(pos.x(), pos.y(), center_x, center_y);
// Center mouse for mouse panning
if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) { if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
QCursor::setPos(mapToGlobal(QPoint{center_x, center_y})); QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
} }
// Constrain mouse for mouse emulation with mouse panning
if (Settings::values.mouse_panning && Settings::values.mouse_enabled) {
const auto [clamped_mouse_x, clamped_mouse_y] = ClipToTouchScreen(x, y);
QCursor::setPos(mapToGlobal(
QPoint{static_cast<int>(clamped_mouse_x), static_cast<int>(clamped_mouse_y)}));
}
mouse_constrain_timer.stop();
emit MouseActivity(); emit MouseActivity();
} }
@ -675,6 +704,31 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
input_subsystem->GetMouse()->ReleaseButton(button); input_subsystem->GetMouse()->ReleaseButton(button);
} }
void GRenderWindow::ConstrainMouse() {
if (emu_thread == nullptr || !Settings::values.mouse_panning) {
mouse_constrain_timer.stop();
return;
}
if (!this->isActiveWindow()) {
mouse_constrain_timer.stop();
return;
}
if (Settings::values.mouse_enabled) {
const auto pos = mapFromGlobal(QCursor::pos());
const int new_pos_x = std::clamp(pos.x(), 0, width());
const int new_pos_y = std::clamp(pos.y(), 0, height());
QCursor::setPos(mapToGlobal(QPoint{new_pos_x, new_pos_y}));
return;
}
const int center_x = width() / 2;
const int center_y = height() / 2;
QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
}
void GRenderWindow::wheelEvent(QWheelEvent* event) { void GRenderWindow::wheelEvent(QWheelEvent* event) {
const int x = event->angleDelta().x(); const int x = event->angleDelta().x();
const int y = event->angleDelta().y(); const int y = event->angleDelta().y();

View file

@ -17,6 +17,7 @@
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
#include <QThread> #include <QThread>
#include <QTimer>
#include <QWidget> #include <QWidget>
#include <qglobal.h> #include <qglobal.h>
#include <qnamespace.h> #include <qnamespace.h>
@ -38,7 +39,6 @@ class QMouseEvent;
class QObject; class QObject;
class QResizeEvent; class QResizeEvent;
class QShowEvent; class QShowEvent;
class QTimer;
class QTouchEvent; class QTouchEvent;
class QWheelEvent; class QWheelEvent;
@ -166,6 +166,7 @@ public:
std::pair<u32, u32> ScaleTouch(const QPointF& pos) const; std::pair<u32, u32> ScaleTouch(const QPointF& pos) const;
void closeEvent(QCloseEvent* event) override; void closeEvent(QCloseEvent* event) override;
void leaveEvent(QEvent* event) override;
void resizeEvent(QResizeEvent* event) override; void resizeEvent(QResizeEvent* event) override;
@ -229,6 +230,7 @@ private:
void TouchBeginEvent(const QTouchEvent* event); void TouchBeginEvent(const QTouchEvent* event);
void TouchUpdateEvent(const QTouchEvent* event); void TouchUpdateEvent(const QTouchEvent* event);
void TouchEndEvent(); void TouchEndEvent();
void ConstrainMouse();
void RequestCameraCapture(); void RequestCameraCapture();
void OnCameraCapture(int requestId, const QImage& img); void OnCameraCapture(int requestId, const QImage& img);
@ -268,6 +270,8 @@ private:
std::unique_ptr<QTimer> camera_timer; std::unique_ptr<QTimer> camera_timer;
#endif #endif
QTimer mouse_constrain_timer;
Core::System& system; Core::System& system;
protected: protected:

View file

@ -187,7 +187,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
#endif #endif
constexpr int default_mouse_hide_timeout = 2500; constexpr int default_mouse_hide_timeout = 2500;
constexpr int default_mouse_center_timeout = 10;
constexpr int default_input_update_timeout = 1; constexpr int default_input_update_timeout = 1;
constexpr size_t CopyBufferSize = 1_MiB; constexpr size_t CopyBufferSize = 1_MiB;
@ -437,9 +436,6 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor); connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor); connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor);
mouse_center_timer.setInterval(default_mouse_center_timeout);
connect(&mouse_center_timer, &QTimer::timeout, this, &GMainWindow::CenterMouseCursor);
update_input_timer.setInterval(default_input_update_timeout); update_input_timer.setInterval(default_input_update_timeout);
connect(&update_input_timer, &QTimer::timeout, this, &GMainWindow::UpdateInputDrivers); connect(&update_input_timer, &QTimer::timeout, this, &GMainWindow::UpdateInputDrivers);
update_input_timer.start(); update_input_timer.start();
@ -1376,14 +1372,6 @@ void GMainWindow::InitializeHotkeys() {
} }
}); });
connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
if (Settings::values.mouse_enabled) {
Settings::values.mouse_panning = false;
QMessageBox::warning(
this, tr("Emulated mouse is enabled"),
tr("Real mouse input and mouse panning are incompatible. Please disable the "
"emulated mouse in input advanced settings to allow mouse panning."));
return;
}
Settings::values.mouse_panning = !Settings::values.mouse_panning; Settings::values.mouse_panning = !Settings::values.mouse_panning;
if (Settings::values.mouse_panning) { if (Settings::values.mouse_panning) {
render_window->installEventFilter(render_window); render_window->installEventFilter(render_window);
@ -4711,26 +4699,10 @@ void GMainWindow::ShowMouseCursor() {
} }
} }
void GMainWindow::CenterMouseCursor() {
if (emu_thread == nullptr || !Settings::values.mouse_panning) {
mouse_center_timer.stop();
return;
}
if (!this->isActiveWindow()) {
mouse_center_timer.stop();
return;
}
const int center_x = render_window->width() / 2;
const int center_y = render_window->height() / 2;
QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
}
void GMainWindow::OnMouseActivity() { void GMainWindow::OnMouseActivity() {
if (!Settings::values.mouse_panning) { if (!Settings::values.mouse_panning) {
ShowMouseCursor(); ShowMouseCursor();
} }
mouse_center_timer.stop();
} }
void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
@ -5031,22 +5003,6 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
AcceptDropEvent(event); AcceptDropEvent(event);
} }
void GMainWindow::leaveEvent(QEvent* event) {
if (Settings::values.mouse_panning) {
const QRect& rect = geometry();
QPoint position = QCursor::pos();
qint32 x = qBound(rect.left(), position.x(), rect.right());
qint32 y = qBound(rect.top(), position.y(), rect.bottom());
// Only start the timer if the mouse has left the window bound.
// The leave event is also triggered when the window looses focus.
if (x != position.x() || y != position.y()) {
mouse_center_timer.start();
}
event->accept();
}
}
bool GMainWindow::ConfirmChangeGame() { bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr) if (emu_thread == nullptr)
return true; return true;

View file

@ -451,7 +451,6 @@ private:
void UpdateInputDrivers(); void UpdateInputDrivers();
void HideMouseCursor(); void HideMouseCursor();
void ShowMouseCursor(); void ShowMouseCursor();
void CenterMouseCursor();
void OpenURL(const QUrl& url); void OpenURL(const QUrl& url);
void LoadTranslation(); void LoadTranslation();
void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
@ -535,7 +534,6 @@ private:
bool auto_paused = false; bool auto_paused = false;
bool auto_muted = false; bool auto_muted = false;
QTimer mouse_hide_timer; QTimer mouse_hide_timer;
QTimer mouse_center_timer;
QTimer update_input_timer; QTimer update_input_timer;
QString startup_icon_theme; QString startup_icon_theme;
@ -592,5 +590,4 @@ protected:
void dropEvent(QDropEvent* event) override; void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override;
void dragMoveEvent(QDragMoveEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override;
void leaveEvent(QEvent* event) override;
}; };