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 "citra_qt/bootmanager.h"
#include "citra_qt/main.h"
#include "citra_qt/uisettings.h"
#include "common/color.h"
#include "common/microprofile.h"
#include "common/scm_rev.h"
@ -48,17 +49,6 @@ EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(cor
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() {
MicroProfileOnThreadCreate("EmuThread");
const auto scope = core_context.Acquire();
@ -386,6 +376,8 @@ GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread_, Core::Sys
bool is_secondary_)
: 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")
.arg(QString::fromUtf8(Common::g_build_name),
QString::fromUtf8(Common::g_scm_branch),
@ -398,8 +390,8 @@ GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread_, Core::Sys
this->setMouseTracking(true);
strict_context_required = QGuiApplication::platformName() == QStringLiteral("wayland");
GMainWindow* parent = GetMainWindow();
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
main_window = qobject_cast<GMainWindow*>(parent_);
connect(this, &GRenderWindow::FirstFrameDisplayed, main_window, &GMainWindow::OnLoadComplete);
}
GRenderWindow::~GRenderWindow() = default;
@ -486,9 +478,25 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
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();
if (event->button() == Qt::LeftButton) {
const auto [x, y] = ScaleTouch(pos);
if (confined) {
ConfineMouse();
}
this->TouchPressed(x, y);
} else if (event->button() == Qt::RightButton) {
InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
@ -503,6 +511,9 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
auto pos = event->pos();
const auto [x, y] = ScaleTouch(pos);
if (confined) {
ConfineMouse();
}
this->TouchMoved(x, y);
InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
emit MouseActivity();
@ -725,6 +736,7 @@ void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
void GRenderWindow::OnEmulationStopping() {
emu_thread = nullptr;
child_widget->releaseMouse();
}
void GRenderWindow::showEvent(QShowEvent* event) {
@ -744,3 +756,32 @@ std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext()
#endif
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 GRenderWindow;
class GMainWindow;
namespace VideoCore {
enum class LoadCallbackStage;
@ -161,6 +162,7 @@ public slots:
void OnEmulationStarting(EmuThread* emu_thread);
void OnEmulationStopping();
void OnFramebufferSizeChanged();
void UnconfineMouse();
signals:
/// Emitted when the window is closed
@ -178,12 +180,15 @@ private:
void TouchBeginEvent(const QTouchEvent* event);
void TouchUpdateEvent(const QTouchEvent* event);
void TouchEndEvent();
void ConfineMouse();
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
bool InitializeOpenGL();
void InitializeSoftware();
bool LoadOpenGL();
GMainWindow* main_window;
QDialog* window_frame;
QWidget* child_widget = nullptr;
@ -200,6 +205,8 @@ private:
QByteArray geometry;
bool first_frame = false;
bool has_focus = false;
bool confined = false;
QWidget* foreground_window;
protected:
void showEvent(QShowEvent* event) override;

View file

@ -6,6 +6,7 @@
#include <array>
#include <QKeySequence>
#include <QSettings>
#include "citra_qt/bootmanager.h"
#include "citra_qt/configuration/config.h"
#include "common/file_util.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
// UISetting::values.shortcuts, which is alphabetically ordered.
// 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("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
{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 Texture Dumping"), QStringLiteral("Main Window"), {QStringLiteral(""), 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
@ -651,6 +653,7 @@ void Config::ReadShortcutValues() {
qt_config->beginGroup(QStringLiteral("Shortcuts"));
for (const auto& [name, group, shortcut] : default_hotkeys) {
auto [keyseq, context] = shortcut;
qt_config->beginGroup(group);
qt_config->beginGroup(name);
// 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.hide_mouse);
}
UISettings::values.hide_mouse = ReadSetting(QStringLiteral("ConfineMouse"), false).toBool();
qt_config->endGroup();
}
@ -1216,6 +1220,7 @@ void Config::SaveUIValues() {
WriteBasicSetting(UISettings::values.show_console);
WriteBasicSetting(UISettings::values.pause_when_in_background);
WriteBasicSetting(UISettings::values.hide_mouse);
WriteBasicSetting(UISettings::values.confine_mouse_to_the_touchscreen);
}
qt_config->endGroup();

View file

@ -26,7 +26,7 @@ public:
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<UISettings::Shortcut, 28> default_hotkeys;
static const std::array<UISettings::Shortcut, 29> default_hotkeys;
private:
void Initialize(const std::string& config_name);

View file

@ -2,6 +2,14 @@
<ui version="4.0">
<class>ConfigureDialog</class>
<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">
<string>Citra Configuration</string>
</property>
@ -9,19 +17,12 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QListWidget" name="selectorList">
<property name="minimumWidth">
<number>150</number>
</property>
<property name="maximumWidth">
<number>150</number>
</property>
</widget>
<widget class="QListWidget" name="selectorList"/>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
<number>6</number>
</property>
<widget class="ConfigureGeneral" name="generalTab">
<attribute name="title">

View file

@ -75,6 +75,8 @@ void ConfigureGeneral::SetConfiguration() {
ui->toggle_update_check->setChecked(
UISettings::values.check_for_update_on_start.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) {
@ -171,6 +173,8 @@ void ConfigureGeneral::ApplyConfiguration() {
UISettings::values.check_for_update_on_start = ui->toggle_update_check->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>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_confine_mouse_touchscreen">
<property name="text">
<string>confine mouse touchscreen</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View file

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

View file

@ -601,6 +601,14 @@
<enum>QAction::NoRole</enum>
</property>
</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>
<resources/>
<connections/>

View file

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