summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar FearlessTobi2020-07-14 19:01:36 +0200
committerGravatar FearlessTobi2020-08-29 18:56:34 +0200
commite6bd1fd1b8487e421f71d43b6073ee56de1a043d (patch)
tree53b383906fae814a67ae270b9b510a60f1b5df9d
parentMerge pull request #4604 from lioncash/lifetime (diff)
downloadyuzu-e6bd1fd1b8487e421f71d43b6073ee56de1a043d.tar.gz
yuzu-e6bd1fd1b8487e421f71d43b6073ee56de1a043d.tar.xz
yuzu-e6bd1fd1b8487e421f71d43b6073ee56de1a043d.zip
yuzu: Add motion and touch configuration
-rw-r--r--dist/qt_themes/qdarkstyle/style.qss5
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.cpp12
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h1
-rw-r--r--src/core/settings.h12
-rw-r--r--src/input_common/CMakeLists.txt2
-rw-r--r--src/input_common/main.cpp9
-rw-r--r--src/input_common/main.h3
-rw-r--r--src/input_common/touch_from_button.cpp49
-rw-r--r--src/input_common/touch_from_button.h25
-rw-r--r--src/yuzu/CMakeLists.txt7
-rw-r--r--src/yuzu/configuration/config.cpp68
-rw-r--r--src/yuzu/configuration/configure_input.cpp6
-rw-r--r--src/yuzu/configuration/configure_motion_touch.cpp304
-rw-r--r--src/yuzu/configuration/configure_motion_touch.h77
-rw-r--r--src/yuzu/configuration/configure_motion_touch.ui327
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.cpp612
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.h86
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.ui231
-rw-r--r--src/yuzu/configuration/configure_touch_widget.h61
19 files changed, 1894 insertions, 3 deletions
diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss
index 7755426f8..16218f0c2 100644
--- a/dist/qt_themes/qdarkstyle/style.qss
+++ b/dist/qt_themes/qdarkstyle/style.qss
@@ -1371,3 +1371,8 @@ QGroupBox#vibrationGroup::title {
1371 padding-left: 1px; 1371 padding-left: 1px;
1372 padding-right: 1px; 1372 padding-right: 1px;
1373} 1373}
1374
1375/* touchscreen mapping widget */
1376TouchScreenPreview {
1377 qproperty-dotHighlightColor: #3daee9;
1378}
diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp
index e326f8f5c..0df395e85 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.cpp
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -40,9 +40,14 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
40 cur_entry.sampling_number = last_entry.sampling_number + 1; 40 cur_entry.sampling_number = last_entry.sampling_number + 1;
41 cur_entry.sampling_number2 = cur_entry.sampling_number; 41 cur_entry.sampling_number2 = cur_entry.sampling_number;
42 42
43 const auto [x, y, pressed] = touch_device->GetStatus(); 43 bool pressed = false;
44 float x, y;
45 std::tie(x, y, pressed) = touch_device->GetStatus();
44 auto& touch_entry = cur_entry.states[0]; 46 auto& touch_entry = cur_entry.states[0];
45 touch_entry.attribute.raw = 0; 47 touch_entry.attribute.raw = 0;
48 if (!pressed && touch_btn_device) {
49 std::tie(x, y, pressed) = touch_btn_device->GetStatus();
50 }
46 if (pressed && Settings::values.touchscreen.enabled) { 51 if (pressed && Settings::values.touchscreen.enabled) {
47 touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width); 52 touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
48 touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height); 53 touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
@@ -63,5 +68,10 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
63 68
64void Controller_Touchscreen::OnLoadInputDevices() { 69void Controller_Touchscreen::OnLoadInputDevices() {
65 touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touchscreen.device); 70 touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touchscreen.device);
71 if (Settings::values.use_touch_from_button) {
72 touch_btn_device = Input::CreateDevice<Input::TouchDevice>("engine:touch_from_button");
73 } else {
74 touch_btn_device.reset();
75 }
66} 76}
67} // namespace Service::HID 77} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index a1d97269e..4d9042adc 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -68,6 +68,7 @@ private:
68 "TouchScreenSharedMemory is an invalid size"); 68 "TouchScreenSharedMemory is an invalid size");
69 TouchScreenSharedMemory shared_memory{}; 69 TouchScreenSharedMemory shared_memory{};
70 std::unique_ptr<Input::TouchDevice> touch_device; 70 std::unique_ptr<Input::TouchDevice> touch_device;
71 std::unique_ptr<Input::TouchDevice> touch_btn_device;
71 s64_le last_touch{}; 72 s64_le last_touch{};
72}; 73};
73} // namespace Service::HID 74} // namespace Service::HID
diff --git a/src/core/settings.h b/src/core/settings.h
index 732c6a894..80f0d95a7 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -67,6 +67,11 @@ private:
67 Type local{}; 67 Type local{};
68}; 68};
69 69
70struct TouchFromButtonMap {
71 std::string name;
72 std::vector<std::string> buttons;
73};
74
70struct Values { 75struct Values {
71 // Audio 76 // Audio
72 std::string audio_device_id; 77 std::string audio_device_id;
@@ -145,15 +150,18 @@ struct Values {
145 ButtonsRaw debug_pad_buttons; 150 ButtonsRaw debug_pad_buttons;
146 AnalogsRaw debug_pad_analogs; 151 AnalogsRaw debug_pad_analogs;
147 152
148 std::string motion_device;
149
150 bool vibration_enabled; 153 bool vibration_enabled;
151 154
155 std::string motion_device;
156 std::string touch_device;
152 TouchscreenInput touchscreen; 157 TouchscreenInput touchscreen;
153 std::atomic_bool is_device_reload_pending{true}; 158 std::atomic_bool is_device_reload_pending{true};
159 bool use_touch_from_button;
160 int touch_from_button_map_index;
154 std::string udp_input_address; 161 std::string udp_input_address;
155 u16 udp_input_port; 162 u16 udp_input_port;
156 u8 udp_pad_index; 163 u8 udp_pad_index;
164 std::vector<TouchFromButtonMap> touch_from_button_maps;
157 165
158 // Data Storage 166 // Data Storage
159 bool use_virtual_sd; 167 bool use_virtual_sd;
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 56267c8a8..32433df25 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -9,6 +9,8 @@ add_library(input_common STATIC
9 motion_emu.h 9 motion_emu.h
10 settings.cpp 10 settings.cpp
11 settings.h 11 settings.h
12 touch_from_button.cpp
13 touch_from_button.h
12 gcadapter/gc_adapter.cpp 14 gcadapter/gc_adapter.cpp
13 gcadapter/gc_adapter.h 15 gcadapter/gc_adapter.h
14 gcadapter/gc_poller.cpp 16 gcadapter/gc_poller.cpp
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 57e7a25fe..f9d7b408f 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -11,6 +11,7 @@
11#include "input_common/keyboard.h" 11#include "input_common/keyboard.h"
12#include "input_common/main.h" 12#include "input_common/main.h"
13#include "input_common/motion_emu.h" 13#include "input_common/motion_emu.h"
14#include "input_common/touch_from_button.h"
14#include "input_common/udp/udp.h" 15#include "input_common/udp/udp.h"
15#ifdef HAVE_SDL2 16#ifdef HAVE_SDL2
16#include "input_common/sdl/sdl.h" 17#include "input_common/sdl/sdl.h"
@@ -32,6 +33,8 @@ struct InputSubsystem::Impl {
32 std::make_shared<AnalogFromButton>()); 33 std::make_shared<AnalogFromButton>());
33 motion_emu = std::make_shared<MotionEmu>(); 34 motion_emu = std::make_shared<MotionEmu>();
34 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); 35 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
36 Input::RegisterFactory<Input::TouchDevice>("touch_from_button",
37 std::make_shared<TouchFromButtonFactory>());
35 38
36#ifdef HAVE_SDL2 39#ifdef HAVE_SDL2
37 sdl = SDL::Init(); 40 sdl = SDL::Init();
@@ -46,6 +49,7 @@ struct InputSubsystem::Impl {
46 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); 49 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
47 Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); 50 Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
48 motion_emu.reset(); 51 motion_emu.reset();
52 Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
49#ifdef HAVE_SDL2 53#ifdef HAVE_SDL2
50 sdl.reset(); 54 sdl.reset();
51#endif 55#endif
@@ -171,6 +175,11 @@ const GCButtonFactory* InputSubsystem::GetGCButtons() const {
171 return impl->gcbuttons.get(); 175 return impl->gcbuttons.get();
172} 176}
173 177
178void ReloadInputDevices() {
179 if (udp)
180 udp->ReloadUDPClient();
181}
182
174std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers( 183std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers(
175 Polling::DeviceType type) const { 184 Polling::DeviceType type) const {
176#ifdef HAVE_SDL2 185#ifdef HAVE_SDL2
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 58e5dc250..269735c43 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -21,6 +21,9 @@ namespace Settings::NativeButton {
21enum Values : int; 21enum Values : int;
22} 22}
23 23
24/// Reloads the input devices
25void ReloadInputDevices();
26
24namespace InputCommon { 27namespace InputCommon {
25namespace Polling { 28namespace Polling {
26 29
diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp
new file mode 100644
index 000000000..8e7f90253
--- /dev/null
+++ b/src/input_common/touch_from_button.cpp
@@ -0,0 +1,49 @@
1// Copyright 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/settings.h"
6#include "input_common/touch_from_button.h"
7
8namespace InputCommon {
9
10class TouchFromButtonDevice final : public Input::TouchDevice {
11public:
12 TouchFromButtonDevice() {
13 for (const auto& config_entry :
14 Settings::values.touch_from_button_maps[Settings::values.touch_from_button_map_index]
15 .buttons) {
16 const Common::ParamPackage package{config_entry};
17 map.emplace_back(
18 Input::CreateDevice<Input::ButtonDevice>(config_entry),
19 std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)),
20 std::clamp(package.Get("y", 0), 0,
21 static_cast<int>(Layout::ScreenUndocked::Height)));
22 }
23 }
24
25 std::tuple<float, float, bool> GetStatus() const override {
26 for (const auto& m : map) {
27 const bool state = std::get<0>(m)->GetStatus();
28 if (state) {
29 const float x = static_cast<float>(std::get<1>(m)) /
30 static_cast<int>(Layout::ScreenUndocked::Width);
31 const float y = static_cast<float>(std::get<2>(m)) /
32 static_cast<int>(Layout::ScreenUndocked::Height);
33 return std::make_tuple(x, y, true);
34 }
35 }
36 return std::make_tuple(0.0f, 0.0f, false);
37 }
38
39private:
40 std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map; // button, x, y
41};
42
43std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(
44 const Common::ParamPackage& params) {
45
46 return std::make_unique<TouchFromButtonDevice>();
47}
48
49} // namespace InputCommon
diff --git a/src/input_common/touch_from_button.h b/src/input_common/touch_from_button.h
new file mode 100644
index 000000000..cfb82f108
--- /dev/null
+++ b/src/input_common/touch_from_button.h
@@ -0,0 +1,25 @@
1// Copyright 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/framebuffer_layout.h"
9#include "core/frontend/input.h"
10
11namespace InputCommon {
12
13/**
14 * A touch device factory that takes a list of button devices and combines them into a touch device.
15 */
16class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> {
17public:
18 /**
19 * Creates a touch device from a list of button devices
20 * @param unused
21 */
22 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
23};
24
25} // namespace InputCommon
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 6987e85e1..3ea4e5601 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -68,6 +68,9 @@ add_executable(yuzu
68 configuration/configure_input_advanced.cpp 68 configuration/configure_input_advanced.cpp
69 configuration/configure_input_advanced.h 69 configuration/configure_input_advanced.h
70 configuration/configure_input_advanced.ui 70 configuration/configure_input_advanced.ui
71 configuration/configure_motion_touch.cpp
72 configuration/configure_motion_touch.h
73 configuration/configure_motion_touch.ui
71 configuration/configure_mouse_advanced.cpp 74 configuration/configure_mouse_advanced.cpp
72 configuration/configure_mouse_advanced.h 75 configuration/configure_mouse_advanced.h
73 configuration/configure_mouse_advanced.ui 76 configuration/configure_mouse_advanced.ui
@@ -86,9 +89,13 @@ add_executable(yuzu
86 configuration/configure_system.cpp 89 configuration/configure_system.cpp
87 configuration/configure_system.h 90 configuration/configure_system.h
88 configuration/configure_system.ui 91 configuration/configure_system.ui
92 configuration/configure_touch_from_button.cpp
93 configuration/configure_touch_from_button.h
94 configuration/configure_touch_from_button.ui
89 configuration/configure_touchscreen_advanced.cpp 95 configuration/configure_touchscreen_advanced.cpp
90 configuration/configure_touchscreen_advanced.h 96 configuration/configure_touchscreen_advanced.h
91 configuration/configure_touchscreen_advanced.ui 97 configuration/configure_touchscreen_advanced.ui
98 configuration/configure_touch_widget.h
92 configuration/configure_ui.cpp 99 configuration/configure_ui.cpp
93 configuration/configure_ui.h 100 configuration/configure_ui.h
94 configuration/configure_ui.ui 101 configuration/configure_ui.ui
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 588bbd677..ead19a870 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -423,11 +423,54 @@ void Config::ReadControlValues() {
423 423
424 Settings::values.vibration_enabled = 424 Settings::values.vibration_enabled =
425 ReadSetting(QStringLiteral("vibration_enabled"), true).toBool(); 425 ReadSetting(QStringLiteral("vibration_enabled"), true).toBool();
426
427 int num_touch_from_button_maps =
428 qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
429
430 if (num_touch_from_button_maps > 0) {
431 const auto append_touch_from_button_map = [this] {
432 Settings::TouchFromButtonMap map;
433 map.name = ReadSetting(QStringLiteral("name"), QStringLiteral("default"))
434 .toString()
435 .toStdString();
436 const int num_touch_maps = qt_config->beginReadArray(QStringLiteral("entries"));
437 map.buttons.reserve(num_touch_maps);
438 for (int i = 0; i < num_touch_maps; i++) {
439 qt_config->setArrayIndex(i);
440 std::string touch_mapping =
441 ReadSetting(QStringLiteral("bind")).toString().toStdString();
442 map.buttons.emplace_back(std::move(touch_mapping));
443 }
444 qt_config->endArray(); // entries
445 Settings::values.touch_from_button_maps.emplace_back(std::move(map));
446 };
447
448 for (int i = 0; i < num_touch_from_button_maps; ++i) {
449 qt_config->setArrayIndex(i);
450 append_touch_from_button_map();
451 }
452 } else {
453 Settings::values.touch_from_button_maps.emplace_back(
454 Settings::TouchFromButtonMap{"default", {}});
455 num_touch_from_button_maps = 1;
456 }
457 qt_config->endArray();
458
426 Settings::values.motion_device = 459 Settings::values.motion_device =
427 ReadSetting(QStringLiteral("motion_device"), 460 ReadSetting(QStringLiteral("motion_device"),
428 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")) 461 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))
429 .toString() 462 .toString()
430 .toStdString(); 463 .toStdString();
464 Settings::values.touch_device =
465 ReadSetting(QStringLiteral("touch_device"), QStringLiteral("engine:emu_window"))
466 .toString()
467 .toStdString();
468 Settings::values.use_touch_from_button =
469 ReadSetting(QStringLiteral("use_touch_from_button"), false).toBool();
470 Settings::values.touch_from_button_map_index =
471 ReadSetting(QStringLiteral("touch_from_button_map"), 0).toInt();
472 Settings::values.touch_from_button_map_index =
473 std::clamp(Settings::values.touch_from_button_map_index, 0, num_touch_from_button_maps - 1);
431 Settings::values.udp_input_address = 474 Settings::values.udp_input_address =
432 ReadSetting(QStringLiteral("udp_input_address"), 475 ReadSetting(QStringLiteral("udp_input_address"),
433 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)) 476 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR))
@@ -981,7 +1024,14 @@ void Config::SaveControlValues() {
981 WriteSetting(QStringLiteral("motion_device"), 1024 WriteSetting(QStringLiteral("motion_device"),
982 QString::fromStdString(Settings::values.motion_device), 1025 QString::fromStdString(Settings::values.motion_device),
983 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); 1026 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
1027 WriteSetting(QStringLiteral("touch_device"),
1028 QString::fromStdString(Settings::values.touch_device),
1029 QStringLiteral("engine:emu_window"));
984 WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false); 1030 WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
1031 WriteSetting(QStringLiteral("use_touch_from_button"), Settings::values.use_touch_from_button,
1032 false);
1033 WriteSetting(QStringLiteral("touch_from_button_map"),
1034 Settings::values.touch_from_button_map_index, 0);
985 WriteSetting(QStringLiteral("udp_input_address"), 1035 WriteSetting(QStringLiteral("udp_input_address"),
986 QString::fromStdString(Settings::values.udp_input_address), 1036 QString::fromStdString(Settings::values.udp_input_address),
987 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)); 1037 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
@@ -990,6 +1040,24 @@ void Config::SaveControlValues() {
990 WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0); 1040 WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
991 WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false); 1041 WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
992 1042
1043 qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps"));
1044 for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
1045 qt_config->setArrayIndex(static_cast<int>(p));
1046 WriteSetting(QStringLiteral("name"),
1047 QString::fromStdString(Settings::values.touch_from_button_maps[p].name),
1048 QStringLiteral("default"));
1049 qt_config->beginWriteArray(QStringLiteral("entries"));
1050 for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size();
1051 ++q) {
1052 qt_config->setArrayIndex(static_cast<int>(q));
1053 WriteSetting(
1054 QStringLiteral("bind"),
1055 QString::fromStdString(Settings::values.touch_from_button_maps[p].buttons[q]));
1056 }
1057 qt_config->endArray();
1058 }
1059 qt_config->endArray();
1060
993 qt_config->endGroup(); 1061 qt_config->endGroup();
994} 1062}
995 1063
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 5223eed1d..62c504286 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -20,6 +20,7 @@
20#include "yuzu/configuration/configure_input.h" 20#include "yuzu/configuration/configure_input.h"
21#include "yuzu/configuration/configure_input_advanced.h" 21#include "yuzu/configuration/configure_input_advanced.h"
22#include "yuzu/configuration/configure_input_player.h" 22#include "yuzu/configuration/configure_input_player.h"
23#include "yuzu/configuration/configure_motion_touch.h"
23#include "yuzu/configuration/configure_mouse_advanced.h" 24#include "yuzu/configuration/configure_mouse_advanced.h"
24#include "yuzu/configuration/configure_touchscreen_advanced.h" 25#include "yuzu/configuration/configure_touchscreen_advanced.h"
25 26
@@ -131,6 +132,11 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) {
131 connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); 132 connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
132 connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); 133 connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
133 134
135 connect(ui->buttonMotionTouch, &QPushButton::clicked, [this] {
136 QDialog* motion_touch_dialog = new ConfigureMotionTouch(this);
137 return motion_touch_dialog->exec();
138 });
139
134 RetranslateUI(); 140 RetranslateUI();
135 LoadConfiguration(); 141 LoadConfiguration();
136} 142}
diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp
new file mode 100644
index 000000000..cb79e47ce
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.cpp
@@ -0,0 +1,304 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <array>
6#include <QCloseEvent>
7#include <QLabel>
8#include <QMessageBox>
9#include <QPushButton>
10#include <QVBoxLayout>
11#include "input_common/main.h"
12#include "ui_configure_motion_touch.h"
13#include "yuzu/configuration/configure_motion_touch.h"
14#include "yuzu/configuration/configure_touch_from_button.h"
15
16CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent,
17 const std::string& host, u16 port,
18 u8 pad_index, u16 client_id)
19 : QDialog(parent) {
20 layout = new QVBoxLayout;
21 status_label = new QLabel(tr("Communicating with the server..."));
22 cancel_button = new QPushButton(tr("Cancel"));
23 connect(cancel_button, &QPushButton::clicked, this, [this] {
24 if (!completed)
25 job->Stop();
26 accept();
27 });
28 layout->addWidget(status_label);
29 layout->addWidget(cancel_button);
30 setLayout(layout);
31
32 using namespace InputCommon::CemuhookUDP;
33 job = std::make_unique<CalibrationConfigurationJob>(
34 host, port, pad_index, client_id,
35 [this](CalibrationConfigurationJob::Status status) {
36 QString text;
37 switch (status) {
38 case CalibrationConfigurationJob::Status::Ready:
39 text = tr("Touch the top left corner <br>of your touchpad.");
40 break;
41 case CalibrationConfigurationJob::Status::Stage1Completed:
42 text = tr("Now touch the bottom right corner <br>of your touchpad.");
43 break;
44 case CalibrationConfigurationJob::Status::Completed:
45 text = tr("Configuration completed!");
46 break;
47 }
48 QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text));
49 if (status == CalibrationConfigurationJob::Status::Completed) {
50 QMetaObject::invokeMethod(this, "UpdateButtonText", Q_ARG(QString, tr("OK")));
51 }
52 },
53 [this](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) {
54 completed = true;
55 min_x = min_x_;
56 min_y = min_y_;
57 max_x = max_x_;
58 max_y = max_y_;
59 });
60}
61
62CalibrationConfigurationDialog::~CalibrationConfigurationDialog() = default;
63
64void CalibrationConfigurationDialog::UpdateLabelText(QString text) {
65 status_label->setText(text);
66}
67
68void CalibrationConfigurationDialog::UpdateButtonText(QString text) {
69 cancel_button->setText(text);
70}
71
72const std::array<std::pair<const char*, const char*>, 2> MotionProviders = {
73 {{"motion_emu", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Mouse (Right Click)")},
74 {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}}};
75
76const std::array<std::pair<const char*, const char*>, 2> TouchProviders = {
77 {{"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")},
78 {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}}};
79
80ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent)
81 : QDialog(parent), ui(std::make_unique<Ui::ConfigureMotionTouch>()) {
82 ui->setupUi(this);
83 for (auto [provider, name] : MotionProviders) {
84 ui->motion_provider->addItem(tr(name), QString::fromUtf8(provider));
85 }
86 for (auto [provider, name] : TouchProviders) {
87 ui->touch_provider->addItem(tr(name), QString::fromUtf8(provider));
88 }
89
90 ui->udp_learn_more->setOpenExternalLinks(true);
91 ui->udp_learn_more->setText(
92 tr("<a "
93 "href='https://citra-emu.org/wiki/"
94 "using-a-controller-or-android-phone-for-motion-or-touch-input'><span "
95 "style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>"));
96
97 SetConfiguration();
98 UpdateUiDisplay();
99 ConnectEvents();
100}
101
102ConfigureMotionTouch::~ConfigureMotionTouch() = default;
103
104void ConfigureMotionTouch::SetConfiguration() {
105 Common::ParamPackage motion_param(Settings::values.motion_device);
106 Common::ParamPackage touch_param(Settings::values.touch_device);
107 std::string motion_engine = motion_param.Get("engine", "motion_emu");
108 std::string touch_engine = touch_param.Get("engine", "emu_window");
109
110 ui->motion_provider->setCurrentIndex(
111 ui->motion_provider->findData(QString::fromStdString(motion_engine)));
112 ui->touch_provider->setCurrentIndex(
113 ui->touch_provider->findData(QString::fromStdString(touch_engine)));
114 ui->touch_from_button_checkbox->setChecked(Settings::values.use_touch_from_button);
115 touch_from_button_maps = Settings::values.touch_from_button_maps;
116 for (const auto& touch_map : touch_from_button_maps) {
117 ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
118 }
119 ui->touch_from_button_map->setCurrentIndex(Settings::values.touch_from_button_map_index);
120 ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f));
121
122 min_x = touch_param.Get("min_x", 100);
123 min_y = touch_param.Get("min_y", 50);
124 max_x = touch_param.Get("max_x", 1800);
125 max_y = touch_param.Get("max_y", 850);
126
127 ui->udp_server->setText(QString::fromStdString(Settings::values.udp_input_address));
128 ui->udp_port->setText(QString::number(Settings::values.udp_input_port));
129 ui->udp_pad_index->setCurrentIndex(Settings::values.udp_pad_index);
130}
131
132void ConfigureMotionTouch::UpdateUiDisplay() {
133 std::string motion_engine = ui->motion_provider->currentData().toString().toStdString();
134 std::string touch_engine = ui->touch_provider->currentData().toString().toStdString();
135
136 if (motion_engine == "motion_emu") {
137 ui->motion_sensitivity_label->setVisible(true);
138 ui->motion_sensitivity->setVisible(true);
139 } else {
140 ui->motion_sensitivity_label->setVisible(false);
141 ui->motion_sensitivity->setVisible(false);
142 }
143
144 if (touch_engine == "cemuhookudp") {
145 ui->touch_calibration->setVisible(true);
146 ui->touch_calibration_config->setVisible(true);
147 ui->touch_calibration_label->setVisible(true);
148 ui->touch_calibration->setText(QStringLiteral("(%1, %2) - (%3, %4)")
149 .arg(QString::number(min_x), QString::number(min_y),
150 QString::number(max_x), QString::number(max_y)));
151 } else {
152 ui->touch_calibration->setVisible(false);
153 ui->touch_calibration_config->setVisible(false);
154 ui->touch_calibration_label->setVisible(false);
155 }
156
157 if (motion_engine == "cemuhookudp" || touch_engine == "cemuhookudp") {
158 ui->udp_config_group_box->setVisible(true);
159 } else {
160 ui->udp_config_group_box->setVisible(false);
161 }
162}
163
164void ConfigureMotionTouch::ConnectEvents() {
165 connect(ui->motion_provider,
166 static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
167 [this](int index) { UpdateUiDisplay(); });
168 connect(ui->touch_provider,
169 static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
170 [this](int index) { UpdateUiDisplay(); });
171 connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest);
172 connect(ui->touch_calibration_config, &QPushButton::clicked, this,
173 &ConfigureMotionTouch::OnConfigureTouchCalibration);
174 connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this,
175 &ConfigureMotionTouch::OnConfigureTouchFromButton);
176 connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
177 if (CanCloseDialog())
178 reject();
179 });
180}
181
182void ConfigureMotionTouch::OnCemuhookUDPTest() {
183 ui->udp_test->setEnabled(false);
184 ui->udp_test->setText(tr("Testing"));
185 udp_test_in_progress = true;
186 InputCommon::CemuhookUDP::TestCommunication(
187 ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toInt()),
188 static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872,
189 [this] {
190 LOG_INFO(Frontend, "UDP input test success");
191 QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, true));
192 },
193 [this] {
194 LOG_ERROR(Frontend, "UDP input test failed");
195 QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, false));
196 });
197}
198
199void ConfigureMotionTouch::OnConfigureTouchCalibration() {
200 ui->touch_calibration_config->setEnabled(false);
201 ui->touch_calibration_config->setText(tr("Configuring"));
202 CalibrationConfigurationDialog* dialog = new CalibrationConfigurationDialog(
203 this, ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toUInt()),
204 static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872);
205 dialog->exec();
206 if (dialog->completed) {
207 min_x = dialog->min_x;
208 min_y = dialog->min_y;
209 max_x = dialog->max_x;
210 max_y = dialog->max_y;
211 LOG_INFO(Frontend,
212 "UDP touchpad calibration config success: min_x={}, min_y={}, max_x={}, max_y={}",
213 min_x, min_y, max_x, max_y);
214 UpdateUiDisplay();
215 } else {
216 LOG_ERROR(Frontend, "UDP touchpad calibration config failed");
217 }
218 ui->touch_calibration_config->setEnabled(true);
219 ui->touch_calibration_config->setText(tr("Configure"));
220}
221
222void ConfigureMotionTouch::closeEvent(QCloseEvent* event) {
223 if (CanCloseDialog())
224 event->accept();
225 else
226 event->ignore();
227}
228
229void ConfigureMotionTouch::ShowUDPTestResult(bool result) {
230 udp_test_in_progress = false;
231 if (result) {
232 QMessageBox::information(this, tr("Test Successful"),
233 tr("Successfully received data from the server."));
234 } else {
235 QMessageBox::warning(this, tr("Test Failed"),
236 tr("Could not receive valid data from the server.<br>Please verify "
237 "that the server is set up correctly and "
238 "the address and port are correct."));
239 }
240 ui->udp_test->setEnabled(true);
241 ui->udp_test->setText(tr("Test"));
242}
243
244void ConfigureMotionTouch::OnConfigureTouchFromButton() {
245 ConfigureTouchFromButton dialog{this, touch_from_button_maps,
246 ui->touch_from_button_map->currentIndex()};
247 if (dialog.exec() != QDialog::Accepted) {
248 return;
249 }
250 touch_from_button_maps = dialog.GetMaps();
251
252 while (ui->touch_from_button_map->count() > 0) {
253 ui->touch_from_button_map->removeItem(0);
254 }
255 for (const auto& touch_map : touch_from_button_maps) {
256 ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
257 }
258 ui->touch_from_button_map->setCurrentIndex(dialog.GetSelectedIndex());
259}
260
261bool ConfigureMotionTouch::CanCloseDialog() {
262 if (udp_test_in_progress) {
263 QMessageBox::warning(this, tr("Citra"),
264 tr("UDP Test or calibration configuration is in progress.<br>Please "
265 "wait for them to finish."));
266 return false;
267 }
268 return true;
269}
270
271void ConfigureMotionTouch::ApplyConfiguration() {
272 if (!CanCloseDialog())
273 return;
274
275 std::string motion_engine = ui->motion_provider->currentData().toString().toStdString();
276 std::string touch_engine = ui->touch_provider->currentData().toString().toStdString();
277
278 Common::ParamPackage motion_param{}, touch_param{};
279 motion_param.Set("engine", motion_engine);
280 touch_param.Set("engine", touch_engine);
281
282 if (motion_engine == "motion_emu") {
283 motion_param.Set("sensitivity", static_cast<float>(ui->motion_sensitivity->value()));
284 }
285
286 if (touch_engine == "cemuhookudp") {
287 touch_param.Set("min_x", min_x);
288 touch_param.Set("min_y", min_y);
289 touch_param.Set("max_x", max_x);
290 touch_param.Set("max_y", max_y);
291 }
292
293 Settings::values.motion_device = motion_param.Serialize();
294 Settings::values.touch_device = touch_param.Serialize();
295 Settings::values.use_touch_from_button = ui->touch_from_button_checkbox->isChecked();
296 Settings::values.touch_from_button_map_index = ui->touch_from_button_map->currentIndex();
297 Settings::values.touch_from_button_maps = touch_from_button_maps;
298 Settings::values.udp_input_address = ui->udp_server->text().toStdString();
299 Settings::values.udp_input_port = static_cast<u16>(ui->udp_port->text().toInt());
300 Settings::values.udp_pad_index = static_cast<u8>(ui->udp_pad_index->currentIndex());
301 InputCommon::ReloadInputDevices();
302
303 accept();
304}
diff --git a/src/yuzu/configuration/configure_motion_touch.h b/src/yuzu/configuration/configure_motion_touch.h
new file mode 100644
index 000000000..1a4f50022
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.h
@@ -0,0 +1,77 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QDialog>
9#include "common/param_package.h"
10#include "core/settings.h"
11#include "input_common/udp/client.h"
12#include "input_common/udp/udp.h"
13
14class QVBoxLayout;
15class QLabel;
16class QPushButton;
17
18namespace Ui {
19class ConfigureMotionTouch;
20}
21
22/// A dialog for touchpad calibration configuration.
23class CalibrationConfigurationDialog : public QDialog {
24 Q_OBJECT
25public:
26 explicit CalibrationConfigurationDialog(QWidget* parent, const std::string& host, u16 port,
27 u8 pad_index, u16 client_id);
28 ~CalibrationConfigurationDialog();
29
30private:
31 Q_INVOKABLE void UpdateLabelText(QString text);
32 Q_INVOKABLE void UpdateButtonText(QString text);
33
34 QVBoxLayout* layout;
35 QLabel* status_label;
36 QPushButton* cancel_button;
37 std::unique_ptr<InputCommon::CemuhookUDP::CalibrationConfigurationJob> job;
38
39 // Configuration results
40 bool completed{};
41 u16 min_x, min_y, max_x, max_y;
42
43 friend class ConfigureMotionTouch;
44};
45
46class ConfigureMotionTouch : public QDialog {
47 Q_OBJECT
48
49public:
50 explicit ConfigureMotionTouch(QWidget* parent = nullptr);
51 ~ConfigureMotionTouch() override;
52
53public slots:
54 void ApplyConfiguration();
55
56private slots:
57 void OnCemuhookUDPTest();
58 void OnConfigureTouchCalibration();
59 void OnConfigureTouchFromButton();
60
61private:
62 void closeEvent(QCloseEvent* event) override;
63 Q_INVOKABLE void ShowUDPTestResult(bool result);
64 void SetConfiguration();
65 void UpdateUiDisplay();
66 void ConnectEvents();
67 bool CanCloseDialog();
68
69 std::unique_ptr<Ui::ConfigureMotionTouch> ui;
70
71 // Coordinate system of the CemuhookUDP touch provider
72 int min_x, min_y, max_x, max_y;
73
74 bool udp_test_in_progress{};
75
76 std::vector<Settings::TouchFromButtonMap> touch_from_button_maps;
77};
diff --git a/src/yuzu/configuration/configure_motion_touch.ui b/src/yuzu/configuration/configure_motion_touch.ui
new file mode 100644
index 000000000..602cf8cd8
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.ui
@@ -0,0 +1,327 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureMotionTouch</class>
4 <widget class="QDialog" name="ConfigureMotionTouch">
5 <property name="windowTitle">
6 <string>Configure Motion / Touch</string>
7 </property>
8 <property name="geometry">
9 <rect>
10 <x>0</x>
11 <y>0</y>
12 <width>500</width>
13 <height>450</height>
14 </rect>
15 </property>
16 <layout class="QVBoxLayout">
17 <item>
18 <widget class="QGroupBox" name="motion_group_box">
19 <property name="title">
20 <string>Motion</string>
21 </property>
22 <layout class="QVBoxLayout">
23 <item>
24 <layout class="QHBoxLayout">
25 <item>
26 <widget class="QLabel" name="motion_provider_label">
27 <property name="text">
28 <string>Motion Provider:</string>
29 </property>
30 </widget>
31 </item>
32 <item>
33 <widget class="QComboBox" name="motion_provider"/>
34 </item>
35 </layout>
36 </item>
37 <item>
38 <layout class="QHBoxLayout">
39 <item>
40 <widget class="QLabel" name="motion_sensitivity_label">
41 <property name="text">
42 <string>Sensitivity:</string>
43 </property>
44 </widget>
45 </item>
46 <item>
47 <widget class="QDoubleSpinBox" name="motion_sensitivity">
48 <property name="alignment">
49 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
50 </property>
51 <property name="decimals">
52 <number>4</number>
53 </property>
54 <property name="minimum">
55 <double>0.010000000000000</double>
56 </property>
57 <property name="maximum">
58 <double>10.000000000000000</double>
59 </property>
60 <property name="singleStep">
61 <double>0.001000000000000</double>
62 </property>
63 <property name="value">
64 <double>0.010000000000000</double>
65 </property>
66 </widget>
67 </item>
68 </layout>
69 </item>
70 </layout>
71 </widget>
72 </item>
73 <item>
74 <widget class="QGroupBox" name="touch_group_box">
75 <property name="title">
76 <string>Touch</string>
77 </property>
78 <layout class="QVBoxLayout">
79 <item>
80 <layout class="QHBoxLayout">
81 <item>
82 <widget class="QLabel" name="touch_provider_label">
83 <property name="text">
84 <string>Touch Provider:</string>
85 </property>
86 </widget>
87 </item>
88 <item>
89 <widget class="QComboBox" name="touch_provider"/>
90 </item>
91 </layout>
92 </item>
93 <item>
94 <layout class="QHBoxLayout">
95 <item>
96 <widget class="QLabel" name="touch_calibration_label">
97 <property name="text">
98 <string>Calibration:</string>
99 </property>
100 </widget>
101 </item>
102 <item>
103 <widget class="QLabel" name="touch_calibration">
104 <property name="text">
105 <string>(100, 50) - (1800, 850)</string>
106 </property>
107 <property name="alignment">
108 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
109 </property>
110 </widget>
111 </item>
112 <item>
113 <widget class="QPushButton" name="touch_calibration_config">
114 <property name="sizePolicy">
115 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
116 <horstretch>0</horstretch>
117 <verstretch>0</verstretch>
118 </sizepolicy>
119 </property>
120 <property name="text">
121 <string>Configure</string>
122 </property>
123 </widget>
124 </item>
125 </layout>
126 </item>
127 <item>
128 <layout class="QHBoxLayout">
129 <item>
130 <widget class="QCheckBox" name="touch_from_button_checkbox">
131 <property name="sizePolicy">
132 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
133 <horstretch>0</horstretch>
134 <verstretch>0</verstretch>
135 </sizepolicy>
136 </property>
137 <property name="text">
138 <string>Use button mapping:</string>
139 </property>
140 </widget>
141 </item>
142 <item>
143 <widget class="QComboBox" name="touch_from_button_map"/>
144 </item>
145 <item>
146 <widget class="QPushButton" name="touch_from_button_config_btn">
147 <property name="sizePolicy">
148 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
149 <horstretch>0</horstretch>
150 <verstretch>0</verstretch>
151 </sizepolicy>
152 </property>
153 <property name="text">
154 <string>Configure</string>
155 </property>
156 </widget>
157 </item>
158 </layout>
159 </item>
160 </layout>
161 </widget>
162 </item>
163 <item>
164 <widget class="QGroupBox" name="udp_config_group_box">
165 <property name="title">
166 <string>CemuhookUDP Config</string>
167 </property>
168 <layout class="QVBoxLayout">
169 <item>
170 <widget class="QLabel" name="udp_help">
171 <property name="text">
172 <string>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</string>
173 </property>
174 <property name="alignment">
175 <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
176 </property>
177 <property name="wordWrap">
178 <bool>true</bool>
179 </property>
180 </widget>
181 </item>
182 <item>
183 <layout class="QHBoxLayout">
184 <item>
185 <widget class="QLabel" name="udp_server_label">
186 <property name="text">
187 <string>Server:</string>
188 </property>
189 </widget>
190 </item>
191 <item>
192 <widget class="QLineEdit" name="udp_server">
193 <property name="sizePolicy">
194 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
195 <horstretch>0</horstretch>
196 <verstretch>0</verstretch>
197 </sizepolicy>
198 </property>
199 </widget>
200 </item>
201 </layout>
202 </item>
203 <item>
204 <layout class="QHBoxLayout">
205 <item>
206 <widget class="QLabel" name="udp_port_label">
207 <property name="text">
208 <string>Port:</string>
209 </property>
210 </widget>
211 </item>
212 <item>
213 <widget class="QLineEdit" name="udp_port">
214 <property name="sizePolicy">
215 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
216 <horstretch>0</horstretch>
217 <verstretch>0</verstretch>
218 </sizepolicy>
219 </property>
220 </widget>
221 </item>
222 </layout>
223 </item>
224 <item>
225 <layout class="QHBoxLayout">
226 <item>
227 <widget class="QLabel" name="udp_pad_index_label">
228 <property name="text">
229 <string>Pad:</string>
230 </property>
231 </widget>
232 </item>
233 <item>
234 <widget class="QComboBox" name="udp_pad_index">
235 <item>
236 <property name="text">
237 <string>Pad 1</string>
238 </property>
239 </item>
240 <item>
241 <property name="text">
242 <string>Pad 2</string>
243 </property>
244 </item>
245 <item>
246 <property name="text">
247 <string>Pad 3</string>
248 </property>
249 </item>
250 <item>
251 <property name="text">
252 <string>Pad 4</string>
253 </property>
254 </item>
255 </widget>
256 </item>
257 </layout>
258 </item>
259 <item>
260 <layout class="QHBoxLayout">
261 <item>
262 <widget class="QLabel" name="udp_learn_more">
263 <property name="text">
264 <string>Learn More</string>
265 </property>
266 </widget>
267 </item>
268 <item>
269 <widget class="QPushButton" name="udp_test">
270 <property name="sizePolicy">
271 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
272 <horstretch>0</horstretch>
273 <verstretch>0</verstretch>
274 </sizepolicy>
275 </property>
276 <property name="text">
277 <string>Test</string>
278 </property>
279 </widget>
280 </item>
281 </layout>
282 </item>
283 </layout>
284 </widget>
285 </item>
286 <item>
287 <spacer>
288 <property name="orientation">
289 <enum>Qt::Vertical</enum>
290 </property>
291 <property name="sizeHint" stdset="0">
292 <size>
293 <width>167</width>
294 <height>55</height>
295 </size>
296 </property>
297 </spacer>
298 </item>
299 <item>
300 <widget class="QDialogButtonBox" name="buttonBox">
301 <property name="standardButtons">
302 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
303 </property>
304 </widget>
305 </item>
306 </layout>
307 </widget>
308 <resources/>
309 <connections>
310 <connection>
311 <sender>buttonBox</sender>
312 <signal>accepted()</signal>
313 <receiver>ConfigureMotionTouch</receiver>
314 <slot>ApplyConfiguration()</slot>
315 <hints>
316 <hint type="sourcelabel">
317 <x>220</x>
318 <y>380</y>
319 </hint>
320 <hint type="destinationlabel">
321 <x>220</x>
322 <y>200</y>
323 </hint>
324 </hints>
325 </connection>
326 </connections>
327</ui>
diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp
new file mode 100644
index 000000000..0a0448cea
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.cpp
@@ -0,0 +1,612 @@
1// Copyright 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QInputDialog>
6#include <QKeyEvent>
7#include <QMessageBox>
8#include <QMouseEvent>
9#include <QResizeEvent>
10#include <QStandardItemModel>
11#include <QTimer>
12#include "common/param_package.h"
13#include "input_common/main.h"
14#include "ui_configure_touch_from_button.h"
15#include "yuzu/configuration/configure_touch_from_button.h"
16#include "yuzu/configuration/configure_touch_widget.h"
17
18static QString GetKeyName(int key_code) {
19 switch (key_code) {
20 case Qt::Key_Shift:
21 return QObject::tr("Shift");
22 case Qt::Key_Control:
23 return QObject::tr("Ctrl");
24 case Qt::Key_Alt:
25 return QObject::tr("Alt");
26 case Qt::Key_Meta:
27 return QString{};
28 default:
29 return QKeySequence(key_code).toString();
30 }
31}
32
33static QString ButtonToText(const Common::ParamPackage& param) {
34 if (!param.Has("engine")) {
35 return QObject::tr("[not set]");
36 }
37
38 if (param.Get("engine", "") == "keyboard") {
39 return GetKeyName(param.Get("code", 0));
40 }
41
42 if (param.Get("engine", "") == "sdl") {
43 if (param.Has("hat")) {
44 const QString hat_str = QString::fromStdString(param.Get("hat", ""));
45 const QString direction_str = QString::fromStdString(param.Get("direction", ""));
46
47 return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
48 }
49
50 if (param.Has("axis")) {
51 const QString axis_str = QString::fromStdString(param.Get("axis", ""));
52 const QString direction_str = QString::fromStdString(param.Get("direction", ""));
53
54 return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
55 }
56
57 if (param.Has("button")) {
58 const QString button_str = QString::fromStdString(param.Get("button", ""));
59
60 return QObject::tr("Button %1").arg(button_str);
61 }
62
63 return {};
64 }
65
66 return QObject::tr("[unknown]");
67}
68
69ConfigureTouchFromButton::ConfigureTouchFromButton(
70 QWidget* parent, const std::vector<Settings::TouchFromButtonMap>& touch_maps,
71 const int default_index)
72 : QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchFromButton>()), touch_maps(touch_maps),
73 selected_index(default_index), timeout_timer(std::make_unique<QTimer>()),
74 poll_timer(std::make_unique<QTimer>()) {
75
76 ui->setupUi(this);
77 binding_list_model = std::make_unique<QStandardItemModel>(0, 3, this);
78 binding_list_model->setHorizontalHeaderLabels({tr("Button"), tr("X"), tr("Y")});
79 ui->binding_list->setModel(binding_list_model.get());
80 ui->bottom_screen->SetCoordLabel(ui->coord_label);
81
82 SetConfiguration();
83 UpdateUiDisplay();
84 ConnectEvents();
85}
86
87ConfigureTouchFromButton::~ConfigureTouchFromButton() = default;
88
89void ConfigureTouchFromButton::showEvent(QShowEvent* ev) {
90 QWidget::showEvent(ev);
91
92 // width values are not valid in the constructor
93 const int w =
94 ui->binding_list->viewport()->contentsRect().width() / binding_list_model->columnCount();
95 if (w > 0) {
96 ui->binding_list->setColumnWidth(0, w);
97 ui->binding_list->setColumnWidth(1, w);
98 ui->binding_list->setColumnWidth(2, w);
99 }
100}
101
102void ConfigureTouchFromButton::SetConfiguration() {
103 for (const auto& touch_map : touch_maps) {
104 ui->mapping->addItem(QString::fromStdString(touch_map.name));
105 }
106
107 ui->mapping->setCurrentIndex(selected_index);
108}
109
110void ConfigureTouchFromButton::UpdateUiDisplay() {
111 ui->button_delete->setEnabled(touch_maps.size() > 1);
112 ui->button_delete_bind->setEnabled(false);
113
114 binding_list_model->removeRows(0, binding_list_model->rowCount());
115
116 for (const auto& button_str : touch_maps[selected_index].buttons) {
117 Common::ParamPackage package{button_str};
118 QStandardItem* button = new QStandardItem(ButtonToText(package));
119 button->setData(QString::fromStdString(button_str));
120 button->setEditable(false);
121 QStandardItem* xcoord = new QStandardItem(QString::number(package.Get("x", 0)));
122 QStandardItem* ycoord = new QStandardItem(QString::number(package.Get("y", 0)));
123 binding_list_model->appendRow({button, xcoord, ycoord});
124
125 int dot = ui->bottom_screen->AddDot(package.Get("x", 0), package.Get("y", 0));
126 button->setData(dot, DataRoleDot);
127 }
128}
129
130void ConfigureTouchFromButton::ConnectEvents() {
131 connect(ui->mapping, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
132 SaveCurrentMapping();
133 selected_index = index;
134 UpdateUiDisplay();
135 });
136 connect(ui->button_new, &QPushButton::clicked, this, &ConfigureTouchFromButton::NewMapping);
137 connect(ui->button_delete, &QPushButton::clicked, this,
138 &ConfigureTouchFromButton::DeleteMapping);
139 connect(ui->button_rename, &QPushButton::clicked, this,
140 &ConfigureTouchFromButton::RenameMapping);
141 connect(ui->button_delete_bind, &QPushButton::clicked, this,
142 &ConfigureTouchFromButton::DeleteBinding);
143 connect(ui->binding_list, &QTreeView::doubleClicked, this,
144 &ConfigureTouchFromButton::EditBinding);
145 connect(ui->binding_list->selectionModel(), &QItemSelectionModel::selectionChanged, this,
146 &ConfigureTouchFromButton::OnBindingSelection);
147 connect(binding_list_model.get(), &QStandardItemModel::itemChanged, this,
148 &ConfigureTouchFromButton::OnBindingChanged);
149 connect(ui->binding_list->model(), &QStandardItemModel::rowsAboutToBeRemoved, this,
150 &ConfigureTouchFromButton::OnBindingDeleted);
151 connect(ui->bottom_screen, &TouchScreenPreview::DotAdded, this,
152 &ConfigureTouchFromButton::NewBinding);
153 connect(ui->bottom_screen, &TouchScreenPreview::DotSelected, this,
154 &ConfigureTouchFromButton::SetActiveBinding);
155 connect(ui->bottom_screen, &TouchScreenPreview::DotMoved, this,
156 &ConfigureTouchFromButton::SetCoordinates);
157 connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
158 &ConfigureTouchFromButton::ApplyConfiguration);
159
160 connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
161
162 connect(poll_timer.get(), &QTimer::timeout, [this]() {
163 Common::ParamPackage params;
164 for (auto& poller : device_pollers) {
165 params = poller->GetNextInput();
166 if (params.Has("engine")) {
167 SetPollingResult(params, false);
168 return;
169 }
170 }
171 });
172}
173
174void ConfigureTouchFromButton::SaveCurrentMapping() {
175 auto& map = touch_maps[selected_index];
176 map.buttons.clear();
177 for (int i = 0, rc = binding_list_model->rowCount(); i < rc; ++i) {
178 const auto bind_str = binding_list_model->index(i, 0)
179 .data(Qt::ItemDataRole::UserRole + 1)
180 .toString()
181 .toStdString();
182 if (bind_str.empty()) {
183 continue;
184 }
185 Common::ParamPackage params{bind_str};
186 if (!params.Has("engine")) {
187 continue;
188 }
189 params.Set("x", binding_list_model->index(i, 1).data().toInt());
190 params.Set("y", binding_list_model->index(i, 2).data().toInt());
191 map.buttons.emplace_back(params.Serialize());
192 }
193}
194
195void ConfigureTouchFromButton::NewMapping() {
196 const QString name =
197 QInputDialog::getText(this, tr("New Profile"), tr("Enter the name for the new profile."));
198 if (name.isEmpty()) {
199 return;
200 }
201 touch_maps.emplace_back(Settings::TouchFromButtonMap{name.toStdString(), {}});
202 ui->mapping->addItem(name);
203 ui->mapping->setCurrentIndex(ui->mapping->count() - 1);
204}
205
206void ConfigureTouchFromButton::DeleteMapping() {
207 const auto answer = QMessageBox::question(
208 this, tr("Delete Profile"), tr("Delete profile %1?").arg(ui->mapping->currentText()));
209 if (answer != QMessageBox::Yes) {
210 return;
211 }
212 const bool blocked = ui->mapping->blockSignals(true);
213 ui->mapping->removeItem(selected_index);
214 ui->mapping->blockSignals(blocked);
215 touch_maps.erase(touch_maps.begin() + selected_index);
216 selected_index = ui->mapping->currentIndex();
217 UpdateUiDisplay();
218}
219
220void ConfigureTouchFromButton::RenameMapping() {
221 const QString new_name = QInputDialog::getText(this, tr("Rename Profile"), tr("New name:"));
222 if (new_name.isEmpty()) {
223 return;
224 }
225 ui->mapping->setItemText(selected_index, new_name);
226 touch_maps[selected_index].name = new_name.toStdString();
227}
228
229void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is_new) {
230 binding_list_model->item(row_index, 0)->setText(tr("[press key]"));
231
232 input_setter = [this, row_index, is_new](const Common::ParamPackage& params,
233 const bool cancel) {
234 auto cell = binding_list_model->item(row_index, 0);
235 if (cancel) {
236 if (is_new) {
237 binding_list_model->removeRow(row_index);
238 } else {
239 cell->setText(
240 ButtonToText(Common::ParamPackage{cell->data().toString().toStdString()}));
241 }
242 } else {
243 cell->setText(ButtonToText(params));
244 cell->setData(QString::fromStdString(params.Serialize()));
245 }
246 };
247
248 device_pollers = InputCommon::Polling::GetPollers(InputCommon::Polling::DeviceType::Button);
249
250 for (auto& poller : device_pollers) {
251 poller->Start();
252 }
253
254 grabKeyboard();
255 grabMouse();
256 qApp->setOverrideCursor(QCursor(Qt::CursorShape::ArrowCursor));
257 timeout_timer->start(5000); // Cancel after 5 seconds
258 poll_timer->start(200); // Check for new inputs every 200ms
259}
260
261void ConfigureTouchFromButton::NewBinding(const QPoint& pos) {
262 QStandardItem* button = new QStandardItem();
263 button->setEditable(false);
264 QStandardItem* xcoord = new QStandardItem(QString::number(pos.x()));
265 QStandardItem* ycoord = new QStandardItem(QString::number(pos.y()));
266
267 const int dot_id = ui->bottom_screen->AddDot(pos.x(), pos.y());
268 button->setData(dot_id, DataRoleDot);
269
270 binding_list_model->appendRow({button, xcoord, ycoord});
271 ui->binding_list->setFocus();
272 ui->binding_list->setCurrentIndex(button->index());
273
274 GetButtonInput(binding_list_model->rowCount() - 1, true);
275}
276
277void ConfigureTouchFromButton::EditBinding(const QModelIndex& qi) {
278 if (qi.row() >= 0 && qi.column() == 0) {
279 GetButtonInput(qi.row(), false);
280 }
281}
282
283void ConfigureTouchFromButton::DeleteBinding() {
284 const int row_index = ui->binding_list->currentIndex().row();
285 if (row_index >= 0) {
286 ui->bottom_screen->RemoveDot(
287 binding_list_model->index(row_index, 0).data(DataRoleDot).toInt());
288 binding_list_model->removeRow(row_index);
289 }
290}
291
292void ConfigureTouchFromButton::OnBindingSelection(const QItemSelection& selected,
293 const QItemSelection& deselected) {
294 ui->button_delete_bind->setEnabled(!selected.isEmpty());
295 if (!selected.isEmpty()) {
296 const auto dot_data = selected.indexes().first().data(DataRoleDot);
297 if (dot_data.isValid()) {
298 ui->bottom_screen->HighlightDot(dot_data.toInt());
299 }
300 }
301 if (!deselected.isEmpty()) {
302 const auto dot_data = deselected.indexes().first().data(DataRoleDot);
303 if (dot_data.isValid()) {
304 ui->bottom_screen->HighlightDot(dot_data.toInt(), false);
305 }
306 }
307}
308
309void ConfigureTouchFromButton::OnBindingChanged(QStandardItem* item) {
310 if (item->column() == 0) {
311 return;
312 }
313
314 const bool blocked = binding_list_model->blockSignals(true);
315 item->setText(QString::number(
316 std::clamp(item->text().toInt(), 0,
317 static_cast<int>((item->column() == 1 ? Layout::ScreenUndocked::Width
318 : Layout::ScreenUndocked::Height) -
319 1))));
320 binding_list_model->blockSignals(blocked);
321
322 const auto dot_data = binding_list_model->index(item->row(), 0).data(DataRoleDot);
323 if (dot_data.isValid()) {
324 ui->bottom_screen->MoveDot(dot_data.toInt(),
325 binding_list_model->item(item->row(), 1)->text().toInt(),
326 binding_list_model->item(item->row(), 2)->text().toInt());
327 }
328}
329
330void ConfigureTouchFromButton::OnBindingDeleted(const QModelIndex& parent, int first, int last) {
331 for (int i = first; i <= last; ++i) {
332 auto ix = binding_list_model->index(i, 0);
333 if (!ix.isValid()) {
334 return;
335 }
336 const auto dot_data = ix.data(DataRoleDot);
337 if (dot_data.isValid()) {
338 ui->bottom_screen->RemoveDot(dot_data.toInt());
339 }
340 }
341}
342
343void ConfigureTouchFromButton::SetActiveBinding(const int dot_id) {
344 for (int i = 0; i < binding_list_model->rowCount(); ++i) {
345 if (binding_list_model->index(i, 0).data(DataRoleDot) == dot_id) {
346 ui->binding_list->setCurrentIndex(binding_list_model->index(i, 0));
347 ui->binding_list->setFocus();
348 return;
349 }
350 }
351}
352
353void ConfigureTouchFromButton::SetCoordinates(const int dot_id, const QPoint& pos) {
354 for (int i = 0; i < binding_list_model->rowCount(); ++i) {
355 if (binding_list_model->item(i, 0)->data(DataRoleDot) == dot_id) {
356 binding_list_model->item(i, 1)->setText(QString::number(pos.x()));
357 binding_list_model->item(i, 2)->setText(QString::number(pos.y()));
358 return;
359 }
360 }
361}
362
363void ConfigureTouchFromButton::SetPollingResult(const Common::ParamPackage& params,
364 const bool cancel) {
365 releaseKeyboard();
366 releaseMouse();
367 qApp->restoreOverrideCursor();
368 timeout_timer->stop();
369 poll_timer->stop();
370 for (auto& poller : device_pollers) {
371 poller->Stop();
372 }
373 if (input_setter) {
374 (*input_setter)(params, cancel);
375 input_setter.reset();
376 }
377}
378
379void ConfigureTouchFromButton::keyPressEvent(QKeyEvent* event) {
380 if (!input_setter && event->key() == Qt::Key_Delete) {
381 DeleteBinding();
382 return;
383 }
384
385 if (!input_setter) {
386 return QDialog::keyPressEvent(event);
387 }
388
389 if (event->key() != Qt::Key_Escape) {
390 SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
391 false);
392 } else {
393 SetPollingResult({}, true);
394 }
395}
396
397void ConfigureTouchFromButton::ApplyConfiguration() {
398 SaveCurrentMapping();
399 accept();
400}
401
402int ConfigureTouchFromButton::GetSelectedIndex() const {
403 return selected_index;
404}
405
406std::vector<Settings::TouchFromButtonMap> ConfigureTouchFromButton::GetMaps() const {
407 return touch_maps;
408}
409
410TouchScreenPreview::TouchScreenPreview(QWidget* parent) : QFrame(parent) {
411 setBackgroundRole(QPalette::ColorRole::Base);
412}
413
414TouchScreenPreview::~TouchScreenPreview() = default;
415
416void TouchScreenPreview::SetCoordLabel(QLabel* const label) {
417 coord_label = label;
418}
419
420int TouchScreenPreview::AddDot(const int device_x, const int device_y) {
421 QFont dot_font{QStringLiteral("monospace")};
422 dot_font.setStyleHint(QFont::Monospace);
423 dot_font.setPointSize(20);
424
425 QLabel* dot = new QLabel(this);
426 dot->setAttribute(Qt::WA_TranslucentBackground);
427 dot->setFont(dot_font);
428 dot->setText(QChar(0xD7)); // U+00D7 Multiplication Sign
429 dot->setAlignment(Qt::AlignmentFlag::AlignCenter);
430 dot->setProperty(PropId, ++max_dot_id);
431 dot->setProperty(PropX, device_x);
432 dot->setProperty(PropY, device_y);
433 dot->setCursor(Qt::CursorShape::PointingHandCursor);
434 dot->setMouseTracking(true);
435 dot->installEventFilter(this);
436 dot->show();
437 PositionDot(dot, device_x, device_y);
438 dots.emplace_back(max_dot_id, dot);
439 return max_dot_id;
440}
441
442void TouchScreenPreview::RemoveDot(const int id) {
443 for (auto dot_it = dots.begin(); dot_it != dots.end(); ++dot_it) {
444 if (dot_it->first == id) {
445 dot_it->second->deleteLater();
446 dots.erase(dot_it);
447 return;
448 }
449 }
450}
451
452void TouchScreenPreview::HighlightDot(const int id, const bool active) const {
453 for (const auto& dot : dots) {
454 if (dot.first == id) {
455 // use color property from the stylesheet, or fall back to the default palette
456 if (dot_highlight_color.isValid()) {
457 dot.second->setStyleSheet(
458 active ? QStringLiteral("color: %1").arg(dot_highlight_color.name())
459 : QString{});
460 } else {
461 dot.second->setForegroundRole(active ? QPalette::ColorRole::LinkVisited
462 : QPalette::ColorRole::NoRole);
463 }
464 if (active) {
465 dot.second->raise();
466 }
467 return;
468 }
469 }
470}
471
472void TouchScreenPreview::MoveDot(const int id, const int device_x, const int device_y) const {
473 for (const auto& dot : dots) {
474 if (dot.first == id) {
475 dot.second->setProperty(PropX, device_x);
476 dot.second->setProperty(PropY, device_y);
477 PositionDot(dot.second, device_x, device_y);
478 return;
479 }
480 }
481}
482
483void TouchScreenPreview::resizeEvent(QResizeEvent* event) {
484 if (ignore_resize) {
485 return;
486 }
487
488 const int target_width = std::min(width(), height() * 4 / 3);
489 const int target_height = std::min(height(), width() * 3 / 4);
490 if (target_width == width() && target_height == height()) {
491 return;
492 }
493 ignore_resize = true;
494 setGeometry((parentWidget()->contentsRect().width() - target_width) / 2, y(), target_width,
495 target_height);
496 ignore_resize = false;
497
498 if (event->oldSize().width() != target_width || event->oldSize().height() != target_height) {
499 for (const auto& dot : dots) {
500 PositionDot(dot.second);
501 }
502 }
503}
504
505void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) {
506 if (!coord_label) {
507 return;
508 }
509 const auto pos = MapToDeviceCoords(event->x(), event->y());
510 if (pos) {
511 coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y()));
512 } else {
513 coord_label->clear();
514 }
515}
516
517void TouchScreenPreview::leaveEvent(QEvent* event) {
518 if (coord_label) {
519 coord_label->clear();
520 }
521}
522
523void TouchScreenPreview::mousePressEvent(QMouseEvent* event) {
524 if (event->button() == Qt::MouseButton::LeftButton) {
525 const auto pos = MapToDeviceCoords(event->x(), event->y());
526 if (pos) {
527 emit DotAdded(*pos);
528 }
529 }
530}
531
532bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
533 switch (event->type()) {
534 case QEvent::Type::MouseButtonPress: {
535 const auto mouse_event = static_cast<QMouseEvent*>(event);
536 if (mouse_event->button() != Qt::MouseButton::LeftButton) {
537 break;
538 }
539 emit DotSelected(obj->property(PropId).toInt());
540
541 drag_state.dot = qobject_cast<QLabel*>(obj);
542 drag_state.start_pos = mouse_event->globalPos();
543 return true;
544 }
545 case QEvent::Type::MouseMove: {
546 if (!drag_state.dot) {
547 break;
548 }
549 const auto mouse_event = static_cast<QMouseEvent*>(event);
550 if (!drag_state.active) {
551 drag_state.active =
552 (mouse_event->globalPos() - drag_state.start_pos).manhattanLength() >=
553 QApplication::startDragDistance();
554 if (!drag_state.active) {
555 break;
556 }
557 }
558 auto current_pos = mapFromGlobal(mouse_event->globalPos());
559 current_pos.setX(std::clamp(current_pos.x(), contentsMargins().left(),
560 contentsMargins().left() + contentsRect().width() - 1));
561 current_pos.setY(std::clamp(current_pos.y(), contentsMargins().top(),
562 contentsMargins().top() + contentsRect().height() - 1));
563 const auto device_coord = MapToDeviceCoords(current_pos.x(), current_pos.y());
564 if (device_coord) {
565 drag_state.dot->setProperty(PropX, device_coord->x());
566 drag_state.dot->setProperty(PropY, device_coord->y());
567 PositionDot(drag_state.dot, device_coord->x(), device_coord->y());
568 emit DotMoved(drag_state.dot->property(PropId).toInt(), *device_coord);
569 if (coord_label) {
570 coord_label->setText(
571 QStringLiteral("X: %1, Y: %2").arg(device_coord->x()).arg(device_coord->y()));
572 }
573 }
574 return true;
575 }
576 case QEvent::Type::MouseButtonRelease: {
577 drag_state.dot.clear();
578 drag_state.active = false;
579 return true;
580 }
581 default:
582 break;
583 }
584 return obj->eventFilter(obj, event);
585}
586
587std::optional<QPoint> TouchScreenPreview::MapToDeviceCoords(const int screen_x,
588 const int screen_y) const {
589 const float t_x = 0.5f + static_cast<float>(screen_x - contentsMargins().left()) *
590 (Layout::ScreenUndocked::Width - 1) / (contentsRect().width() - 1);
591 const float t_y = 0.5f + static_cast<float>(screen_y - contentsMargins().top()) *
592 (Layout::ScreenUndocked::Height - 1) /
593 (contentsRect().height() - 1);
594 if (t_x >= 0.5f && t_x < Layout::ScreenUndocked::Width && t_y >= 0.5f &&
595 t_y < Layout::ScreenUndocked::Height) {
596
597 return QPoint{static_cast<int>(t_x), static_cast<int>(t_y)};
598 }
599 return std::nullopt;
600}
601
602void TouchScreenPreview::PositionDot(QLabel* const dot, const int device_x,
603 const int device_y) const {
604 dot->move(static_cast<int>(
605 static_cast<float>(device_x >= 0 ? device_x : dot->property(PropX).toInt()) *
606 (contentsRect().width() - 1) / (Layout::ScreenUndocked::Width - 1) +
607 contentsMargins().left() - static_cast<float>(dot->width()) / 2 + 0.5f),
608 static_cast<int>(
609 static_cast<float>(device_y >= 0 ? device_y : dot->property(PropY).toInt()) *
610 (contentsRect().height() - 1) / (Layout::ScreenUndocked::Height - 1) +
611 contentsMargins().top() - static_cast<float>(dot->height()) / 2 + 0.5f));
612}
diff --git a/src/yuzu/configuration/configure_touch_from_button.h b/src/yuzu/configuration/configure_touch_from_button.h
new file mode 100644
index 000000000..c926db012
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.h
@@ -0,0 +1,86 @@
1// Copyright 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <memory>
9#include <optional>
10#include <vector>
11#include <QDialog>
12#include "core/frontend/framebuffer_layout.h"
13#include "core/settings.h"
14
15class QItemSelection;
16class QModelIndex;
17class QStandardItemModel;
18class QStandardItem;
19class QTimer;
20
21namespace Common {
22class ParamPackage;
23}
24
25namespace InputCommon {
26namespace Polling {
27class DevicePoller;
28}
29} // namespace InputCommon
30
31namespace Ui {
32class ConfigureTouchFromButton;
33}
34
35class ConfigureTouchFromButton : public QDialog {
36 Q_OBJECT
37
38public:
39 explicit ConfigureTouchFromButton(QWidget* parent,
40 const std::vector<Settings::TouchFromButtonMap>& touch_maps,
41 int default_index = 0);
42 ~ConfigureTouchFromButton() override;
43
44 int GetSelectedIndex() const;
45 std::vector<Settings::TouchFromButtonMap> GetMaps() const;
46
47public slots:
48 void ApplyConfiguration();
49 void NewBinding(const QPoint& pos);
50 void SetActiveBinding(int dot_id);
51 void SetCoordinates(int dot_id, const QPoint& pos);
52
53protected:
54 virtual void showEvent(QShowEvent* ev) override;
55 virtual void keyPressEvent(QKeyEvent* event) override;
56
57private slots:
58 void NewMapping();
59 void DeleteMapping();
60 void RenameMapping();
61 void EditBinding(const QModelIndex& qi);
62 void DeleteBinding();
63 void OnBindingSelection(const QItemSelection& selected, const QItemSelection& deselected);
64 void OnBindingChanged(QStandardItem* item);
65 void OnBindingDeleted(const QModelIndex& parent, int first, int last);
66
67private:
68 void SetConfiguration();
69 void UpdateUiDisplay();
70 void ConnectEvents();
71 void GetButtonInput(int row_index, bool is_new);
72 void SetPollingResult(const Common::ParamPackage& params, bool cancel);
73 void SaveCurrentMapping();
74
75 std::unique_ptr<Ui::ConfigureTouchFromButton> ui;
76 std::unique_ptr<QStandardItemModel> binding_list_model;
77 std::vector<Settings::TouchFromButtonMap> touch_maps;
78 int selected_index;
79
80 std::unique_ptr<QTimer> timeout_timer;
81 std::unique_ptr<QTimer> poll_timer;
82 std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
83 std::optional<std::function<void(const Common::ParamPackage&, bool)>> input_setter;
84
85 static constexpr int DataRoleDot = Qt::ItemDataRole::UserRole + 2;
86};
diff --git a/src/yuzu/configuration/configure_touch_from_button.ui b/src/yuzu/configuration/configure_touch_from_button.ui
new file mode 100644
index 000000000..d0598bdbd
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.ui
@@ -0,0 +1,231 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureTouchFromButton</class>
4 <widget class="QDialog" name="ConfigureTouchFromButton">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>500</width>
10 <height>500</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Configure Touchscreen Mappings</string>
15 </property>
16 <layout class="QVBoxLayout">
17 <item>
18 <layout class="QHBoxLayout" name="horizontalLayout">
19 <item>
20 <widget class="QLabel" name="label">
21 <property name="text">
22 <string>Mapping:</string>
23 </property>
24 <property name="textFormat">
25 <enum>Qt::PlainText</enum>
26 </property>
27 </widget>
28 </item>
29 <item>
30 <widget class="QComboBox" name="mapping">
31 <property name="sizePolicy">
32 <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
33 <horstretch>0</horstretch>
34 <verstretch>0</verstretch>
35 </sizepolicy>
36 </property>
37 </widget>
38 </item>
39 <item>
40 <widget class="QPushButton" name="button_new">
41 <property name="sizePolicy">
42 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
43 <horstretch>0</horstretch>
44 <verstretch>0</verstretch>
45 </sizepolicy>
46 </property>
47 <property name="text">
48 <string>New</string>
49 </property>
50 </widget>
51 </item>
52 <item>
53 <widget class="QPushButton" name="button_delete">
54 <property name="sizePolicy">
55 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
56 <horstretch>0</horstretch>
57 <verstretch>0</verstretch>
58 </sizepolicy>
59 </property>
60 <property name="text">
61 <string>Delete</string>
62 </property>
63 </widget>
64 </item>
65 <item>
66 <widget class="QPushButton" name="button_rename">
67 <property name="sizePolicy">
68 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
69 <horstretch>0</horstretch>
70 <verstretch>0</verstretch>
71 </sizepolicy>
72 </property>
73 <property name="text">
74 <string>Rename</string>
75 </property>
76 </widget>
77 </item>
78 </layout>
79 </item>
80 <item>
81 <widget class="Line" name="line">
82 <property name="orientation">
83 <enum>Qt::Horizontal</enum>
84 </property>
85 </widget>
86 </item>
87 <item>
88 <layout class="QHBoxLayout" name="horizontalLayout_2">
89 <item>
90 <widget class="QLabel" name="label_2">
91 <property name="text">
92 <string>Click the bottom area to add a point, then press a button to bind.
93Drag points to change position, or double-click table cells to edit values.</string>
94 </property>
95 <property name="textFormat">
96 <enum>Qt::PlainText</enum>
97 </property>
98 </widget>
99 </item>
100 <item>
101 <spacer name="horizontalSpacer">
102 <property name="orientation">
103 <enum>Qt::Horizontal</enum>
104 </property>
105 <property name="sizeHint" stdset="0">
106 <size>
107 <width>40</width>
108 <height>20</height>
109 </size>
110 </property>
111 </spacer>
112 </item>
113 <item>
114 <widget class="QPushButton" name="button_delete_bind">
115 <property name="text">
116 <string>Delete Point</string>
117 </property>
118 </widget>
119 </item>
120 </layout>
121 </item>
122 <item>
123 <widget class="QTreeView" name="binding_list">
124 <property name="sizePolicy">
125 <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
126 <horstretch>0</horstretch>
127 <verstretch>0</verstretch>
128 </sizepolicy>
129 </property>
130 <property name="rootIsDecorated">
131 <bool>false</bool>
132 </property>
133 <property name="uniformRowHeights">
134 <bool>true</bool>
135 </property>
136 <property name="itemsExpandable">
137 <bool>false</bool>
138 </property>
139 </widget>
140 </item>
141 <item>
142 <widget class="TouchScreenPreview" name="bottom_screen">
143 <property name="sizePolicy">
144 <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
145 <horstretch>0</horstretch>
146 <verstretch>0</verstretch>
147 </sizepolicy>
148 </property>
149 <property name="minimumSize">
150 <size>
151 <width>160</width>
152 <height>120</height>
153 </size>
154 </property>
155 <property name="baseSize">
156 <size>
157 <width>320</width>
158 <height>240</height>
159 </size>
160 </property>
161 <property name="cursor">
162 <cursorShape>CrossCursor</cursorShape>
163 </property>
164 <property name="mouseTracking">
165 <bool>true</bool>
166 </property>
167 <property name="autoFillBackground">
168 <bool>true</bool>
169 </property>
170 <property name="frameShape">
171 <enum>QFrame::StyledPanel</enum>
172 </property>
173 <property name="frameShadow">
174 <enum>QFrame::Sunken</enum>
175 </property>
176 </widget>
177 </item>
178 <item>
179 <layout class="QHBoxLayout" name="horizontalLayout_3">
180 <item>
181 <widget class="QLabel" name="coord_label">
182 <property name="sizePolicy">
183 <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
184 <horstretch>0</horstretch>
185 <verstretch>0</verstretch>
186 </sizepolicy>
187 </property>
188 <property name="textFormat">
189 <enum>Qt::PlainText</enum>
190 </property>
191 </widget>
192 </item>
193 <item>
194 <widget class="QDialogButtonBox" name="buttonBox">
195 <property name="standardButtons">
196 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
197 </property>
198 </widget>
199 </item>
200 </layout>
201 </item>
202 </layout>
203 </widget>
204 <customwidgets>
205 <customwidget>
206 <class>TouchScreenPreview</class>
207 <extends>QFrame</extends>
208 <header>citra_qt/configuration/configure_touch_widget.h</header>
209 <container>1</container>
210 </customwidget>
211 </customwidgets>
212 <resources/>
213 <connections>
214 <connection>
215 <sender>buttonBox</sender>
216 <signal>rejected()</signal>
217 <receiver>ConfigureTouchFromButton</receiver>
218 <slot>reject()</slot>
219 <hints>
220 <hint type="sourcelabel">
221 <x>249</x>
222 <y>428</y>
223 </hint>
224 <hint type="destinationlabel">
225 <x>249</x>
226 <y>224</y>
227 </hint>
228 </hints>
229 </connection>
230 </connections>
231</ui>
diff --git a/src/yuzu/configuration/configure_touch_widget.h b/src/yuzu/configuration/configure_touch_widget.h
new file mode 100644
index 000000000..c85960f82
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_widget.h
@@ -0,0 +1,61 @@
1// Copyright 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <optional>
8#include <utility>
9#include <vector>
10#include <QFrame>
11#include <QPointer>
12
13class QLabel;
14
15// Widget for representing touchscreen coordinates
16class TouchScreenPreview : public QFrame {
17 Q_OBJECT
18 Q_PROPERTY(QColor dotHighlightColor MEMBER dot_highlight_color)
19
20public:
21 explicit TouchScreenPreview(QWidget* parent);
22 ~TouchScreenPreview() override;
23
24 void SetCoordLabel(QLabel*);
25 int AddDot(int device_x, int device_y);
26 void RemoveDot(int id);
27 void HighlightDot(int id, bool active = true) const;
28 void MoveDot(int id, int device_x, int device_y) const;
29
30signals:
31 void DotAdded(const QPoint& pos);
32 void DotSelected(int dot_id);
33 void DotMoved(int dot_id, const QPoint& pos);
34
35protected:
36 virtual void resizeEvent(QResizeEvent*) override;
37 virtual void mouseMoveEvent(QMouseEvent*) override;
38 virtual void leaveEvent(QEvent*) override;
39 virtual void mousePressEvent(QMouseEvent*) override;
40 virtual bool eventFilter(QObject*, QEvent*) override;
41
42private:
43 std::optional<QPoint> MapToDeviceCoords(int screen_x, int screen_y) const;
44 void PositionDot(QLabel* dot, int device_x = -1, int device_y = -1) const;
45
46 bool ignore_resize = false;
47 QPointer<QLabel> coord_label;
48
49 std::vector<std::pair<int, QLabel*>> dots;
50 int max_dot_id = 0;
51 QColor dot_highlight_color;
52 static constexpr char PropId[] = "dot_id";
53 static constexpr char PropX[] = "device_x";
54 static constexpr char PropY[] = "device_y";
55
56 struct {
57 bool active = false;
58 QPointer<QLabel> dot;
59 QPoint start_pos;
60 } drag_state;
61};