Confine Mouse (cross platform)

* This pull request can confine the mouse cursor cross platform

* In bootmanager.cpp the mouse cursor will be warp in the touchscreen area if it exits limits

* The mouse cursor is warped too slowly and by clicking the mouse can change the focus window

* The transparent QWidget "window_frame" prevent this to happen by showing it at fullscreen
This commit is contained in:
luc-git 2023-06-05 20:54:17 +02:00
parent 3d0a3c2c45
commit 466073de1e
10 changed files with 109 additions and 28 deletions

View File

@ -12,6 +12,7 @@
#include <QWindow> #include <QWindow>
#include "citra_qt/bootmanager.h" #include "citra_qt/bootmanager.h"
#include "citra_qt/main.h" #include "citra_qt/main.h"
#include "citra_qt/uisettings.h"
#include "common/color.h" #include "common/color.h"
#include "common/microprofile.h" #include "common/microprofile.h"
#include "common/scm_rev.h" #include "common/scm_rev.h"
@ -48,17 +49,6 @@ EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(cor
EmuThread::~EmuThread() = default; EmuThread::~EmuThread() = default;
static GMainWindow* GetMainWindow() {
const auto widgets = qApp->topLevelWidgets();
for (QWidget* w : widgets) {
if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) {
return main;
}
}
return nullptr;
}
void EmuThread::run() { void EmuThread::run() {
MicroProfileOnThreadCreate("EmuThread"); MicroProfileOnThreadCreate("EmuThread");
const auto scope = core_context.Acquire(); const auto scope = core_context.Acquire();
@ -386,6 +376,8 @@ GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread_, Core::Sys
bool is_secondary_) bool is_secondary_)
: QWidget(parent_), EmuWindow(is_secondary_), emu_thread(emu_thread_), system{system_} { : QWidget(parent_), EmuWindow(is_secondary_), emu_thread(emu_thread_), system{system_} {
window_frame = new QDialog(this);
window_frame->setWindowOpacity(0.004);
setWindowTitle(QStringLiteral("Citra %1 | %2-%3") setWindowTitle(QStringLiteral("Citra %1 | %2-%3")
.arg(QString::fromUtf8(Common::g_build_name), .arg(QString::fromUtf8(Common::g_build_name),
QString::fromUtf8(Common::g_scm_branch), QString::fromUtf8(Common::g_scm_branch),
@ -398,8 +390,8 @@ GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread_, Core::Sys
this->setMouseTracking(true); this->setMouseTracking(true);
strict_context_required = QGuiApplication::platformName() == QStringLiteral("wayland"); strict_context_required = QGuiApplication::platformName() == QStringLiteral("wayland");
GMainWindow* parent = GetMainWindow(); main_window = qobject_cast<GMainWindow*>(parent_);
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); connect(this, &GRenderWindow::FirstFrameDisplayed, main_window, &GMainWindow::OnLoadComplete);
} }
GRenderWindow::~GRenderWindow() = default; GRenderWindow::~GRenderWindow() = default;
@ -486,9 +478,25 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
return; // touch input is handled in TouchBeginEvent return; // touch input is handled in TouchBeginEvent
} }
if (UISettings::values.confine_mouse_to_the_touchscreen.GetValue() && !confined) {
foreground_window = window();
child_widget->grabMouse();
window_frame->showFullScreen();
foreground_window->move(main_window->pos());
foreground_window->setFixedSize(main_window->size());
foreground_window->setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
foreground_window->show();
if (parentWidget() == nullptr) {
main_window->hide();
}
confined = true;
}
auto pos = event->pos(); auto pos = event->pos();
if (event->button() == Qt::LeftButton) { if (event->button() == Qt::LeftButton) {
const auto [x, y] = ScaleTouch(pos); const auto [x, y] = ScaleTouch(pos);
if (confined) {
ConfineMouse();
}
this->TouchPressed(x, y); this->TouchPressed(x, y);
} else if (event->button() == Qt::RightButton) { } else if (event->button() == Qt::RightButton) {
InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
@ -503,6 +511,9 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
auto pos = event->pos(); auto pos = event->pos();
const auto [x, y] = ScaleTouch(pos); const auto [x, y] = ScaleTouch(pos);
if (confined) {
ConfineMouse();
}
this->TouchMoved(x, y); this->TouchMoved(x, y);
InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
emit MouseActivity(); emit MouseActivity();
@ -725,6 +736,7 @@ void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
void GRenderWindow::OnEmulationStopping() { void GRenderWindow::OnEmulationStopping() {
emu_thread = nullptr; emu_thread = nullptr;
child_widget->releaseMouse();
} }
void GRenderWindow::showEvent(QShowEvent* event) { void GRenderWindow::showEvent(QShowEvent* event) {
@ -744,3 +756,32 @@ std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext()
#endif #endif
return std::make_unique<DummyContext>(); return std::make_unique<DummyContext>();
} }
void GRenderWindow::ConfineMouse() {
auto layout = GetFramebufferLayout();
auto posi = QCursor::pos();
qint32 x_limit =
qBound(child_widget->mapToGlobal(QPoint(layout.bottom_screen.left, 0)).x(), posi.x(),
child_widget->mapToGlobal(QPoint(layout.bottom_screen.right, 0)).x());
qint32 y_limit =
qBound(child_widget->mapToGlobal(QPoint(0, layout.bottom_screen.top)).y(), posi.y(),
child_widget->mapToGlobal(QPoint(0, layout.bottom_screen.bottom)).y());
if (x_limit != posi.x() || y_limit != posi.y()) {
QCursor::setPos(x_limit, y_limit);
}
}
void GRenderWindow::UnconfineMouse() {
if (!confined) {
return;
}
child_widget->releaseMouse();
confined = false;
window_frame->close();
foreground_window->setWindowFlags(Qt::Window);
foreground_window->setFixedSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
foreground_window->show();
if (parentWidget() == nullptr) {
main_window->show();
}
}

View File

@ -17,6 +17,7 @@ class QKeyEvent;
class QTouchEvent; class QTouchEvent;
class GRenderWindow; class GRenderWindow;
class GMainWindow;
namespace VideoCore { namespace VideoCore {
enum class LoadCallbackStage; enum class LoadCallbackStage;
@ -161,6 +162,7 @@ public slots:
void OnEmulationStarting(EmuThread* emu_thread); void OnEmulationStarting(EmuThread* emu_thread);
void OnEmulationStopping(); void OnEmulationStopping();
void OnFramebufferSizeChanged(); void OnFramebufferSizeChanged();
void UnconfineMouse();
signals: signals:
/// Emitted when the window is closed /// Emitted when the window is closed
@ -178,12 +180,15 @@ 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 ConfineMouse();
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override; void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
bool InitializeOpenGL(); bool InitializeOpenGL();
void InitializeSoftware(); void InitializeSoftware();
bool LoadOpenGL(); bool LoadOpenGL();
GMainWindow* main_window;
QDialog* window_frame;
QWidget* child_widget = nullptr; QWidget* child_widget = nullptr;
@ -200,6 +205,8 @@ private:
QByteArray geometry; QByteArray geometry;
bool first_frame = false; bool first_frame = false;
bool has_focus = false; bool has_focus = false;
bool confined = false;
QWidget* foreground_window;
protected: protected:
void showEvent(QShowEvent* event) override; void showEvent(QShowEvent* event) override;

View File

@ -6,6 +6,7 @@
#include <array> #include <array>
#include <QKeySequence> #include <QKeySequence>
#include <QSettings> #include <QSettings>
#include "citra_qt/bootmanager.h"
#include "citra_qt/configuration/config.h" #include "citra_qt/configuration/config.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "common/settings.h" #include "common/settings.h"
@ -54,7 +55,7 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config:
// This must be in alphabetical order according to action name as it must have the same order as // This must be in alphabetical order according to action name as it must have the same order as
// UISetting::values.shortcuts, which is alphabetically ordered. // UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off // clang-format off
const std::array<UISettings::Shortcut, 28> Config::default_hotkeys {{ const std::array<UISettings::Shortcut, 29> Config::default_hotkeys {{
{QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}}, {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
@ -83,6 +84,7 @@ const std::array<UISettings::Shortcut, 28> Config::default_hotkeys {{
{QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}}, {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
{QStringLiteral("Toggle Texture Dumping"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Texture Dumping"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Custom Textures"), QStringLiteral("Main Window"), {QStringLiteral("F7"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Custom Textures"), QStringLiteral("Main Window"), {QStringLiteral("F7"), Qt::ApplicationShortcut}},
{QStringLiteral("Unconfine Mouse Cursor"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), Qt::ApplicationShortcut}}
}}; }};
// clang-format on // clang-format on
@ -651,6 +653,7 @@ void Config::ReadShortcutValues() {
qt_config->beginGroup(QStringLiteral("Shortcuts")); qt_config->beginGroup(QStringLiteral("Shortcuts"));
for (const auto& [name, group, shortcut] : default_hotkeys) { for (const auto& [name, group, shortcut] : default_hotkeys) {
auto [keyseq, context] = shortcut;
qt_config->beginGroup(group); qt_config->beginGroup(group);
qt_config->beginGroup(name); qt_config->beginGroup(name);
// No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1 // No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1
@ -759,6 +762,7 @@ void Config::ReadUIValues() {
ReadBasicSetting(UISettings::values.pause_when_in_background); ReadBasicSetting(UISettings::values.pause_when_in_background);
ReadBasicSetting(UISettings::values.hide_mouse); ReadBasicSetting(UISettings::values.hide_mouse);
} }
UISettings::values.hide_mouse = ReadSetting(QStringLiteral("ConfineMouse"), false).toBool();
qt_config->endGroup(); qt_config->endGroup();
} }
@ -1216,6 +1220,7 @@ void Config::SaveUIValues() {
WriteBasicSetting(UISettings::values.show_console); WriteBasicSetting(UISettings::values.show_console);
WriteBasicSetting(UISettings::values.pause_when_in_background); WriteBasicSetting(UISettings::values.pause_when_in_background);
WriteBasicSetting(UISettings::values.hide_mouse); WriteBasicSetting(UISettings::values.hide_mouse);
WriteBasicSetting(UISettings::values.confine_mouse_to_the_touchscreen);
} }
qt_config->endGroup(); qt_config->endGroup();

View File

@ -26,7 +26,7 @@ public:
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs; static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
static const std::array<UISettings::Shortcut, 28> default_hotkeys; static const std::array<UISettings::Shortcut, 29> default_hotkeys;
private: private:
void Initialize(const std::string& config_name); void Initialize(const std::string& config_name);

View File

@ -2,6 +2,14 @@
<ui version="4.0"> <ui version="4.0">
<class>ConfigureDialog</class> <class>ConfigureDialog</class>
<widget class="QDialog" name="ConfigureDialog"> <widget class="QDialog" name="ConfigureDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>382</width>
<height>242</height>
</rect>
</property>
<property name="windowTitle"> <property name="windowTitle">
<string>Citra Configuration</string> <string>Citra Configuration</string>
</property> </property>
@ -9,19 +17,12 @@
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QListWidget" name="selectorList"> <widget class="QListWidget" name="selectorList"/>
<property name="minimumWidth">
<number>150</number>
</property>
<property name="maximumWidth">
<number>150</number>
</property>
</widget>
</item> </item>
<item> <item>
<widget class="QTabWidget" name="tabWidget"> <widget class="QTabWidget" name="tabWidget">
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>6</number>
</property> </property>
<widget class="ConfigureGeneral" name="generalTab"> <widget class="ConfigureGeneral" name="generalTab">
<attribute name="title"> <attribute name="title">
@ -49,9 +50,9 @@
</attribute> </attribute>
</widget> </widget>
<widget class="ConfigureEnhancements" name="enhancementsTab"> <widget class="ConfigureEnhancements" name="enhancementsTab">
<attribute name="title"> <attribute name="title">
<string>Enhancements</string> <string>Enhancements</string>
</attribute> </attribute>
</widget> </widget>
<widget class="ConfigureAudio" name="audioTab"> <widget class="ConfigureAudio" name="audioTab">
<attribute name="title"> <attribute name="title">
@ -127,7 +128,7 @@
<header>configuration/configure_debug.h</header> <header>configuration/configure_debug.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget> <customwidget>
<class>ConfigureStorage</class> <class>ConfigureStorage</class>
<extends>QWidget</extends> <extends>QWidget</extends>
<header>configuration/configure_storage.h</header> <header>configuration/configure_storage.h</header>

View File

@ -75,6 +75,8 @@ void ConfigureGeneral::SetConfiguration() {
ui->toggle_update_check->setChecked( ui->toggle_update_check->setChecked(
UISettings::values.check_for_update_on_start.GetValue()); UISettings::values.check_for_update_on_start.GetValue());
ui->toggle_auto_update->setChecked(UISettings::values.update_on_close.GetValue()); ui->toggle_auto_update->setChecked(UISettings::values.update_on_close.GetValue());
ui->toggle_confine_mouse_touchscreen->setChecked(
UISettings::values.confine_mouse_to_the_touchscreen.GetValue());
} }
if (Settings::values.frame_limit.GetValue() == 0) { if (Settings::values.frame_limit.GetValue() == 0) {
@ -171,6 +173,8 @@ void ConfigureGeneral::ApplyConfiguration() {
UISettings::values.check_for_update_on_start = ui->toggle_update_check->isChecked(); UISettings::values.check_for_update_on_start = ui->toggle_update_check->isChecked();
UISettings::values.update_on_close = ui->toggle_auto_update->isChecked(); UISettings::values.update_on_close = ui->toggle_auto_update->isChecked();
UISettings::values.confine_mouse_to_the_touchscreen =
ui->toggle_confine_mouse_touchscreen->isChecked();
} }
} }

View File

@ -43,6 +43,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="toggle_confine_mouse_touchscreen">
<property name="text">
<string>confine mouse touchscreen</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -525,6 +525,7 @@ void GMainWindow::InitializeHotkeys() {
const QString main_window = QStringLiteral("Main Window"); const QString main_window = QStringLiteral("Main Window");
const QString fullscreen = QStringLiteral("Fullscreen"); const QString fullscreen = QStringLiteral("Fullscreen");
const QString unconfine_mouse = QStringLiteral("Unconfine Mouse Cursor");
const QString toggle_screen_layout = QStringLiteral("Toggle Screen Layout"); const QString toggle_screen_layout = QStringLiteral("Toggle Screen Layout");
const QString swap_screens = QStringLiteral("Swap Screens"); const QString swap_screens = QStringLiteral("Swap Screens");
const QString rotate_screens = QStringLiteral("Rotate Screens Upright"); const QString rotate_screens = QStringLiteral("Rotate Screens Upright");
@ -655,6 +656,8 @@ void GMainWindow::InitializeHotkeys() {
UpdateStatusBar(); UpdateStatusBar();
} }
}); });
connect(hotkey_registry.GetHotkey(main_window, unconfine_mouse, render_window),
&QShortcut::activated, ui->action_Unconfine_Mouse, &QAction::trigger);
} }
void GMainWindow::SetDefaultUIGeometry() { void GMainWindow::SetDefaultUIGeometry() {
@ -749,6 +752,8 @@ void GMainWindow::ConnectWidgetEvents() {
connect(this, &GMainWindow::CIAInstallFinished, this, &GMainWindow::OnCIAInstallFinished); connect(this, &GMainWindow::CIAInstallFinished, this, &GMainWindow::OnCIAInstallFinished);
connect(this, &GMainWindow::UpdateThemedIcons, multiplayer_state, connect(this, &GMainWindow::UpdateThemedIcons, multiplayer_state,
&MultiplayerState::UpdateThemedIcons); &MultiplayerState::UpdateThemedIcons);
connect(ui->action_Unconfine_Mouse, &QAction::triggered, render_window,
&GRenderWindow::UnconfineMouse);
} }
void GMainWindow::ConnectMenuEvents() { void GMainWindow::ConnectMenuEvents() {

View File

@ -601,6 +601,14 @@
<enum>QAction::NoRole</enum> <enum>QAction::NoRole</enum>
</property> </property>
</action> </action>
<action name="action_Unconfine_Mouse">
<property name="text">
<string>Unconfine Mouse</string>
</property>
<property name="toolTip">
<string>Unconfine the mouse cursor</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -133,6 +133,9 @@ struct Values {
// logging // logging
Settings::Setting<bool> show_console{false, "showConsole"}; Settings::Setting<bool> show_console{false, "showConsole"};
// ConfineMouse
Settings::Setting<bool> confine_mouse_to_the_touchscreen{false, "ConfineMouse"};
}; };
extern Values values; extern Values values;