summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/frontend/framebuffer_layout.h1
-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.cpp11
-rw-r--r--src/input_common/main.h3
-rw-r--r--src/input_common/touch_from_button.cpp50
-rw-r--r--src/input_common/touch_from_button.h23
-rw-r--r--src/yuzu/CMakeLists.txt7
-rw-r--r--src/yuzu/configuration/config.cpp101
-rw-r--r--src/yuzu/configuration/config.h2
-rw-r--r--src/yuzu/configuration/configure_input.cpp5
-rw-r--r--src/yuzu/configuration/configure_input_advanced.cpp2
-rw-r--r--src/yuzu/configuration/configure_input_advanced.h1
-rw-r--r--src/yuzu/configuration/configure_motion_touch.cpp314
-rw-r--r--src/yuzu/configuration/configure_motion_touch.h90
-rw-r--r--src/yuzu/configuration/configure_motion_touch.ui327
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.cpp623
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.h92
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.ui231
-rw-r--r--src/yuzu/configuration/configure_touch_widget.h62
22 files changed, 1959 insertions, 13 deletions
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index 91ecc30ab..e2e3bbbb3 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include "common/common_types.h"
7#include "common/math_util.h" 8#include "common/math_util.h"
8 9
9namespace Layout { 10namespace Layout {
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..ea1a1cee6 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,13 @@ const GCButtonFactory* InputSubsystem::GetGCButtons() const {
171 return impl->gcbuttons.get(); 175 return impl->gcbuttons.get();
172} 176}
173 177
178void InputSubsystem::ReloadInputDevices() {
179 if (!impl->udp) {
180 return;
181 }
182 impl->udp->ReloadUDPClient();
183}
184
174std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers( 185std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers(
175 Polling::DeviceType type) const { 186 Polling::DeviceType type) const {
176#ifdef HAVE_SDL2 187#ifdef HAVE_SDL2
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 58e5dc250..f3fbf696e 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -115,6 +115,9 @@ public:
115 /// Retrieves the underlying GameCube button handler. 115 /// Retrieves the underlying GameCube button handler.
116 [[nodiscard]] const GCButtonFactory* GetGCButtons() const; 116 [[nodiscard]] const GCButtonFactory* GetGCButtons() const;
117 117
118 /// Reloads the input devices
119 void ReloadInputDevices();
120
118 /// Get all DevicePoller from all backends for a specific device type 121 /// Get all DevicePoller from all backends for a specific device type
119 [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers( 122 [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers(
120 Polling::DeviceType type) const; 123 Polling::DeviceType type) const;
diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp
new file mode 100644
index 000000000..98da0ef1a
--- /dev/null
+++ b/src/input_common/touch_from_button.cpp
@@ -0,0 +1,50 @@
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/frontend/framebuffer_layout.h"
6#include "core/settings.h"
7#include "input_common/touch_from_button.h"
8
9namespace InputCommon {
10
11class TouchFromButtonDevice final : public Input::TouchDevice {
12public:
13 TouchFromButtonDevice() {
14 for (const auto& config_entry :
15 Settings::values.touch_from_button_maps[Settings::values.touch_from_button_map_index]
16 .buttons) {
17 const Common::ParamPackage package{config_entry};
18 map.emplace_back(
19 Input::CreateDevice<Input::ButtonDevice>(config_entry),
20 std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)),
21 std::clamp(package.Get("y", 0), 0,
22 static_cast<int>(Layout::ScreenUndocked::Height)));
23 }
24 }
25
26 std::tuple<float, float, bool> GetStatus() const override {
27 for (const auto& m : map) {
28 const bool state = std::get<0>(m)->GetStatus();
29 if (state) {
30 const float x = static_cast<float>(std::get<1>(m)) /
31 static_cast<int>(Layout::ScreenUndocked::Width);
32 const float y = static_cast<float>(std::get<2>(m)) /
33 static_cast<int>(Layout::ScreenUndocked::Height);
34 return {x, y, true};
35 }
36 }
37 return {};
38 }
39
40private:
41 // A vector of the mapped button, its x and its y-coordinate
42 std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map;
43};
44
45std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(
46 const Common::ParamPackage& params) {
47 return std::make_unique<TouchFromButtonDevice>();
48}
49
50} // 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..8b4d1aa96
--- /dev/null
+++ b/src/input_common/touch_from_button.h
@@ -0,0 +1,23 @@
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/input.h"
9
10namespace InputCommon {
11
12/**
13 * A touch device factory that takes a list of button devices and combines them into a touch device.
14 */
15class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> {
16public:
17 /**
18 * Creates a touch device from a list of button devices
19 */
20 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
21};
22
23} // 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..2bc55a26a 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -420,14 +420,64 @@ void Config::ReadControlValues() {
420 ReadKeyboardValues(); 420 ReadKeyboardValues();
421 ReadMouseValues(); 421 ReadMouseValues();
422 ReadTouchscreenValues(); 422 ReadTouchscreenValues();
423 ReadMotionTouchValues();
423 424
424 Settings::values.vibration_enabled = 425 Settings::values.vibration_enabled =
425 ReadSetting(QStringLiteral("vibration_enabled"), true).toBool(); 426 ReadSetting(QStringLiteral("vibration_enabled"), true).toBool();
427 Settings::values.use_docked_mode =
428 ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
429
430 qt_config->endGroup();
431}
432
433void Config::ReadMotionTouchValues() {
434 int num_touch_from_button_maps =
435 qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
436
437 if (num_touch_from_button_maps > 0) {
438 const auto append_touch_from_button_map = [this] {
439 Settings::TouchFromButtonMap map;
440 map.name = ReadSetting(QStringLiteral("name"), QStringLiteral("default"))
441 .toString()
442 .toStdString();
443 const int num_touch_maps = qt_config->beginReadArray(QStringLiteral("entries"));
444 map.buttons.reserve(num_touch_maps);
445 for (int i = 0; i < num_touch_maps; i++) {
446 qt_config->setArrayIndex(i);
447 std::string touch_mapping =
448 ReadSetting(QStringLiteral("bind")).toString().toStdString();
449 map.buttons.emplace_back(std::move(touch_mapping));
450 }
451 qt_config->endArray(); // entries
452 Settings::values.touch_from_button_maps.emplace_back(std::move(map));
453 };
454
455 for (int i = 0; i < num_touch_from_button_maps; ++i) {
456 qt_config->setArrayIndex(i);
457 append_touch_from_button_map();
458 }
459 } else {
460 Settings::values.touch_from_button_maps.emplace_back(
461 Settings::TouchFromButtonMap{"default", {}});
462 num_touch_from_button_maps = 1;
463 }
464 qt_config->endArray();
465
426 Settings::values.motion_device = 466 Settings::values.motion_device =
427 ReadSetting(QStringLiteral("motion_device"), 467 ReadSetting(QStringLiteral("motion_device"),
428 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")) 468 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))
429 .toString() 469 .toString()
430 .toStdString(); 470 .toStdString();
471 Settings::values.touch_device =
472 ReadSetting(QStringLiteral("touch_device"), QStringLiteral("engine:emu_window"))
473 .toString()
474 .toStdString();
475 Settings::values.use_touch_from_button =
476 ReadSetting(QStringLiteral("use_touch_from_button"), false).toBool();
477 Settings::values.touch_from_button_map_index =
478 ReadSetting(QStringLiteral("touch_from_button_map"), 0).toInt();
479 Settings::values.touch_from_button_map_index =
480 std::clamp(Settings::values.touch_from_button_map_index, 0, num_touch_from_button_maps - 1);
431 Settings::values.udp_input_address = 481 Settings::values.udp_input_address =
432 ReadSetting(QStringLiteral("udp_input_address"), 482 ReadSetting(QStringLiteral("udp_input_address"),
433 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)) 483 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR))
@@ -438,10 +488,6 @@ void Config::ReadControlValues() {
438 .toInt()); 488 .toInt());
439 Settings::values.udp_pad_index = 489 Settings::values.udp_pad_index =
440 static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt()); 490 static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt());
441 Settings::values.use_docked_mode =
442 ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
443
444 qt_config->endGroup();
445} 491}
446 492
447void Config::ReadCoreValues() { 493void Config::ReadCoreValues() {
@@ -934,6 +980,43 @@ void Config::SaveTouchscreenValues() {
934 WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15); 980 WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);
935} 981}
936 982
983void Config::SaveMotionTouchValues() {
984 WriteSetting(QStringLiteral("motion_device"),
985 QString::fromStdString(Settings::values.motion_device),
986 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
987 WriteSetting(QStringLiteral("touch_device"),
988 QString::fromStdString(Settings::values.touch_device),
989 QStringLiteral("engine:emu_window"));
990 WriteSetting(QStringLiteral("use_touch_from_button"), Settings::values.use_touch_from_button,
991 false);
992 WriteSetting(QStringLiteral("touch_from_button_map"),
993 Settings::values.touch_from_button_map_index, 0);
994 WriteSetting(QStringLiteral("udp_input_address"),
995 QString::fromStdString(Settings::values.udp_input_address),
996 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
997 WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
998 InputCommon::CemuhookUDP::DEFAULT_PORT);
999 WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
1000
1001 qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps"));
1002 for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
1003 qt_config->setArrayIndex(static_cast<int>(p));
1004 WriteSetting(QStringLiteral("name"),
1005 QString::fromStdString(Settings::values.touch_from_button_maps[p].name),
1006 QStringLiteral("default"));
1007 qt_config->beginWriteArray(QStringLiteral("entries"));
1008 for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size();
1009 ++q) {
1010 qt_config->setArrayIndex(static_cast<int>(q));
1011 WriteSetting(
1012 QStringLiteral("bind"),
1013 QString::fromStdString(Settings::values.touch_from_button_maps[p].buttons[q]));
1014 }
1015 qt_config->endArray();
1016 }
1017 qt_config->endArray();
1018}
1019
937void Config::SaveValues() { 1020void Config::SaveValues() {
938 if (global) { 1021 if (global) {
939 SaveControlValues(); 1022 SaveControlValues();
@@ -976,18 +1059,16 @@ void Config::SaveControlValues() {
976 SaveDebugValues(); 1059 SaveDebugValues();
977 SaveMouseValues(); 1060 SaveMouseValues();
978 SaveTouchscreenValues(); 1061 SaveTouchscreenValues();
1062 SaveMotionTouchValues();
979 1063
980 WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true); 1064 WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true);
981 WriteSetting(QStringLiteral("motion_device"), 1065 WriteSetting(QStringLiteral("motion_device"),
982 QString::fromStdString(Settings::values.motion_device), 1066 QString::fromStdString(Settings::values.motion_device),
983 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); 1067 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
1068 WriteSetting(QStringLiteral("touch_device"),
1069 QString::fromStdString(Settings::values.touch_device),
1070 QStringLiteral("engine:emu_window"));
984 WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false); 1071 WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
985 WriteSetting(QStringLiteral("udp_input_address"),
986 QString::fromStdString(Settings::values.udp_input_address),
987 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
988 WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
989 InputCommon::CemuhookUDP::DEFAULT_PORT);
990 WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
991 WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false); 1072 WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
992 1073
993 qt_config->endGroup(); 1074 qt_config->endGroup();
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index aa929d134..ca0d29c6c 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -38,6 +38,7 @@ private:
38 void ReadKeyboardValues(); 38 void ReadKeyboardValues();
39 void ReadMouseValues(); 39 void ReadMouseValues();
40 void ReadTouchscreenValues(); 40 void ReadTouchscreenValues();
41 void ReadMotionTouchValues();
41 42
42 // Read functions bases off the respective config section names. 43 // Read functions bases off the respective config section names.
43 void ReadAudioValues(); 44 void ReadAudioValues();
@@ -64,6 +65,7 @@ private:
64 void SaveDebugValues(); 65 void SaveDebugValues();
65 void SaveMouseValues(); 66 void SaveMouseValues();
66 void SaveTouchscreenValues(); 67 void SaveTouchscreenValues();
68 void SaveMotionTouchValues();
67 69
68 // Save functions based off the respective config section names. 70 // Save functions based off the respective config section names.
69 void SaveAudioValues(); 71 void SaveAudioValues();
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 5223eed1d..ae3e31762 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
@@ -127,6 +128,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) {
127 }); 128 });
128 connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog, 129 connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog,
129 [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); }); 130 [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); });
131 connect(advanced, &ConfigureInputAdvanced::CallMotionTouchConfigDialog,
132 [this, input_subsystem] {
133 CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
134 });
130 135
131 connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); 136 connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
132 connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); 137 connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
index db42b826b..81f9dc16c 100644
--- a/src/yuzu/configuration/configure_input_advanced.cpp
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -86,6 +86,8 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
86 connect(ui->mouse_advanced, &QPushButton::clicked, this, [this] { CallMouseConfigDialog(); }); 86 connect(ui->mouse_advanced, &QPushButton::clicked, this, [this] { CallMouseConfigDialog(); });
87 connect(ui->touchscreen_advanced, &QPushButton::clicked, this, 87 connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
88 [this] { CallTouchscreenConfigDialog(); }); 88 [this] { CallTouchscreenConfigDialog(); });
89 connect(ui->buttonMotionTouch, &QPushButton::clicked, this,
90 &ConfigureInputAdvanced::CallMotionTouchConfigDialog);
89 91
90 LoadConfiguration(); 92 LoadConfiguration();
91} 93}
diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h
index d8fcec52d..50bb87768 100644
--- a/src/yuzu/configuration/configure_input_advanced.h
+++ b/src/yuzu/configuration/configure_input_advanced.h
@@ -28,6 +28,7 @@ signals:
28 void CallDebugControllerDialog(); 28 void CallDebugControllerDialog();
29 void CallMouseConfigDialog(); 29 void CallMouseConfigDialog();
30 void CallTouchscreenConfigDialog(); 30 void CallTouchscreenConfigDialog();
31 void CallMotionTouchConfigDialog();
31 32
32private: 33private:
33 void changeEvent(QEvent* event) override; 34 void changeEvent(QEvent* event) override;
diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp
new file mode 100644
index 000000000..c7d085151
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.cpp
@@ -0,0 +1,314 @@
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 "common/logging/log.h"
12#include "core/settings.h"
13#include "input_common/main.h"
14#include "input_common/udp/client.h"
15#include "input_common/udp/udp.h"
16#include "ui_configure_motion_touch.h"
17#include "yuzu/configuration/configure_motion_touch.h"
18#include "yuzu/configuration/configure_touch_from_button.h"
19
20CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent,
21 const std::string& host, u16 port,
22 u8 pad_index, u16 client_id)
23 : QDialog(parent) {
24 layout = new QVBoxLayout;
25 status_label = new QLabel(tr("Communicating with the server..."));
26 cancel_button = new QPushButton(tr("Cancel"));
27 connect(cancel_button, &QPushButton::clicked, this, [this] {
28 if (!completed) {
29 job->Stop();
30 }
31 accept();
32 });
33 layout->addWidget(status_label);
34 layout->addWidget(cancel_button);
35 setLayout(layout);
36
37 using namespace InputCommon::CemuhookUDP;
38 job = std::make_unique<CalibrationConfigurationJob>(
39 host, port, pad_index, client_id,
40 [this](CalibrationConfigurationJob::Status status) {
41 QString text;
42 switch (status) {
43 case CalibrationConfigurationJob::Status::Ready:
44 text = tr("Touch the top left corner <br>of your touchpad.");
45 break;
46 case CalibrationConfigurationJob::Status::Stage1Completed:
47 text = tr("Now touch the bottom right corner <br>of your touchpad.");
48 break;
49 case CalibrationConfigurationJob::Status::Completed:
50 text = tr("Configuration completed!");
51 break;
52 }
53 QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text));
54 if (status == CalibrationConfigurationJob::Status::Completed) {
55 QMetaObject::invokeMethod(this, "UpdateButtonText", Q_ARG(QString, tr("OK")));
56 }
57 },
58 [this](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) {
59 completed = true;
60 min_x = min_x_;
61 min_y = min_y_;
62 max_x = max_x_;
63 max_y = max_y_;
64 });
65}
66
67CalibrationConfigurationDialog::~CalibrationConfigurationDialog() = default;
68
69void CalibrationConfigurationDialog::UpdateLabelText(const QString& text) {
70 status_label->setText(text);
71}
72
73void CalibrationConfigurationDialog::UpdateButtonText(const QString& text) {
74 cancel_button->setText(text);
75}
76
77constexpr std::array<std::pair<const char*, const char*>, 2> MotionProviders = {{
78 {"motion_emu", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Mouse (Right Click)")},
79 {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")},
80}};
81
82constexpr std::array<std::pair<const char*, const char*>, 2> TouchProviders = {{
83 {"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")},
84 {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")},
85}};
86
87ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent,
88 InputCommon::InputSubsystem* input_subsystem_)
89 : QDialog(parent), input_subsystem{input_subsystem_},
90 ui(std::make_unique<Ui::ConfigureMotionTouch>()) {
91 ui->setupUi(this);
92 for (const auto& [provider, name] : MotionProviders) {
93 ui->motion_provider->addItem(tr(name), QString::fromUtf8(provider));
94 }
95 for (const auto& [provider, name] : TouchProviders) {
96 ui->touch_provider->addItem(tr(name), QString::fromUtf8(provider));
97 }
98
99 ui->udp_learn_more->setOpenExternalLinks(true);
100 ui->udp_learn_more->setText(
101 tr("<a "
102 "href='https://yuzu-emu.org/wiki/"
103 "using-a-controller-or-android-phone-for-motion-or-touch-input'><span "
104 "style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>"));
105
106 SetConfiguration();
107 UpdateUiDisplay();
108 ConnectEvents();
109}
110
111ConfigureMotionTouch::~ConfigureMotionTouch() = default;
112
113void ConfigureMotionTouch::SetConfiguration() {
114 const Common::ParamPackage motion_param(Settings::values.motion_device);
115 const Common::ParamPackage touch_param(Settings::values.touch_device);
116 const std::string motion_engine = motion_param.Get("engine", "motion_emu");
117 const std::string touch_engine = touch_param.Get("engine", "emu_window");
118
119 ui->motion_provider->setCurrentIndex(
120 ui->motion_provider->findData(QString::fromStdString(motion_engine)));
121 ui->touch_provider->setCurrentIndex(
122 ui->touch_provider->findData(QString::fromStdString(touch_engine)));
123 ui->touch_from_button_checkbox->setChecked(Settings::values.use_touch_from_button);
124 touch_from_button_maps = Settings::values.touch_from_button_maps;
125 for (const auto& touch_map : touch_from_button_maps) {
126 ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
127 }
128 ui->touch_from_button_map->setCurrentIndex(Settings::values.touch_from_button_map_index);
129 ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f));
130
131 min_x = touch_param.Get("min_x", 100);
132 min_y = touch_param.Get("min_y", 50);
133 max_x = touch_param.Get("max_x", 1800);
134 max_y = touch_param.Get("max_y", 850);
135
136 ui->udp_server->setText(QString::fromStdString(Settings::values.udp_input_address));
137 ui->udp_port->setText(QString::number(Settings::values.udp_input_port));
138 ui->udp_pad_index->setCurrentIndex(Settings::values.udp_pad_index);
139}
140
141void ConfigureMotionTouch::UpdateUiDisplay() {
142 const QString motion_engine = ui->motion_provider->currentData().toString();
143 const QString touch_engine = ui->touch_provider->currentData().toString();
144 const QString cemuhook_udp = QStringLiteral("cemuhookudp");
145
146 if (motion_engine == QStringLiteral("motion_emu")) {
147 ui->motion_sensitivity_label->setVisible(true);
148 ui->motion_sensitivity->setVisible(true);
149 } else {
150 ui->motion_sensitivity_label->setVisible(false);
151 ui->motion_sensitivity->setVisible(false);
152 }
153
154 if (touch_engine == cemuhook_udp) {
155 ui->touch_calibration->setVisible(true);
156 ui->touch_calibration_config->setVisible(true);
157 ui->touch_calibration_label->setVisible(true);
158 ui->touch_calibration->setText(
159 QStringLiteral("(%1, %2) - (%3, %4)").arg(min_x).arg(min_y).arg(max_x).arg(max_y));
160 } else {
161 ui->touch_calibration->setVisible(false);
162 ui->touch_calibration_config->setVisible(false);
163 ui->touch_calibration_label->setVisible(false);
164 }
165
166 if (motion_engine == cemuhook_udp || touch_engine == cemuhook_udp) {
167 ui->udp_config_group_box->setVisible(true);
168 } else {
169 ui->udp_config_group_box->setVisible(false);
170 }
171}
172
173void ConfigureMotionTouch::ConnectEvents() {
174 connect(ui->motion_provider, qOverload<int>(&QComboBox::currentIndexChanged), this,
175 [this](int index) { UpdateUiDisplay(); });
176 connect(ui->touch_provider, qOverload<int>(&QComboBox::currentIndexChanged), this,
177 [this](int index) { UpdateUiDisplay(); });
178 connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest);
179 connect(ui->touch_calibration_config, &QPushButton::clicked, this,
180 &ConfigureMotionTouch::OnConfigureTouchCalibration);
181 connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this,
182 &ConfigureMotionTouch::OnConfigureTouchFromButton);
183 connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
184 if (CanCloseDialog()) {
185 reject();
186 }
187 });
188}
189
190void ConfigureMotionTouch::OnCemuhookUDPTest() {
191 ui->udp_test->setEnabled(false);
192 ui->udp_test->setText(tr("Testing"));
193 udp_test_in_progress = true;
194 InputCommon::CemuhookUDP::TestCommunication(
195 ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toInt()),
196 static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872,
197 [this] {
198 LOG_INFO(Frontend, "UDP input test success");
199 QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, true));
200 },
201 [this] {
202 LOG_ERROR(Frontend, "UDP input test failed");
203 QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, false));
204 });
205}
206
207void ConfigureMotionTouch::OnConfigureTouchCalibration() {
208 ui->touch_calibration_config->setEnabled(false);
209 ui->touch_calibration_config->setText(tr("Configuring"));
210 CalibrationConfigurationDialog dialog(
211 this, ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toUInt()),
212 static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872);
213 dialog.exec();
214 if (dialog.completed) {
215 min_x = dialog.min_x;
216 min_y = dialog.min_y;
217 max_x = dialog.max_x;
218 max_y = dialog.max_y;
219 LOG_INFO(Frontend,
220 "UDP touchpad calibration config success: min_x={}, min_y={}, max_x={}, max_y={}",
221 min_x, min_y, max_x, max_y);
222 UpdateUiDisplay();
223 } else {
224 LOG_ERROR(Frontend, "UDP touchpad calibration config failed");
225 }
226 ui->touch_calibration_config->setEnabled(true);
227 ui->touch_calibration_config->setText(tr("Configure"));
228}
229
230void ConfigureMotionTouch::closeEvent(QCloseEvent* event) {
231 if (CanCloseDialog()) {
232 event->accept();
233 } else {
234 event->ignore();
235 }
236}
237
238void ConfigureMotionTouch::ShowUDPTestResult(bool result) {
239 udp_test_in_progress = false;
240 if (result) {
241 QMessageBox::information(this, tr("Test Successful"),
242 tr("Successfully received data from the server."));
243 } else {
244 QMessageBox::warning(this, tr("Test Failed"),
245 tr("Could not receive valid data from the server.<br>Please verify "
246 "that the server is set up correctly and "
247 "the address and port are correct."));
248 }
249 ui->udp_test->setEnabled(true);
250 ui->udp_test->setText(tr("Test"));
251}
252
253void ConfigureMotionTouch::OnConfigureTouchFromButton() {
254 ConfigureTouchFromButton dialog{this, touch_from_button_maps, input_subsystem,
255 ui->touch_from_button_map->currentIndex()};
256 if (dialog.exec() != QDialog::Accepted) {
257 return;
258 }
259 touch_from_button_maps = dialog.GetMaps();
260
261 while (ui->touch_from_button_map->count() > 0) {
262 ui->touch_from_button_map->removeItem(0);
263 }
264 for (const auto& touch_map : touch_from_button_maps) {
265 ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
266 }
267 ui->touch_from_button_map->setCurrentIndex(dialog.GetSelectedIndex());
268}
269
270bool ConfigureMotionTouch::CanCloseDialog() {
271 if (udp_test_in_progress) {
272 QMessageBox::warning(this, tr("Citra"),
273 tr("UDP Test or calibration configuration is in progress.<br>Please "
274 "wait for them to finish."));
275 return false;
276 }
277 return true;
278}
279
280void ConfigureMotionTouch::ApplyConfiguration() {
281 if (!CanCloseDialog()) {
282 return;
283 }
284
285 std::string motion_engine = ui->motion_provider->currentData().toString().toStdString();
286 std::string touch_engine = ui->touch_provider->currentData().toString().toStdString();
287
288 Common::ParamPackage motion_param{}, touch_param{};
289 motion_param.Set("engine", std::move(motion_engine));
290 touch_param.Set("engine", std::move(touch_engine));
291
292 if (motion_engine == "motion_emu") {
293 motion_param.Set("sensitivity", static_cast<float>(ui->motion_sensitivity->value()));
294 }
295
296 if (touch_engine == "cemuhookudp") {
297 touch_param.Set("min_x", min_x);
298 touch_param.Set("min_y", min_y);
299 touch_param.Set("max_x", max_x);
300 touch_param.Set("max_y", max_y);
301 }
302
303 Settings::values.motion_device = motion_param.Serialize();
304 Settings::values.touch_device = touch_param.Serialize();
305 Settings::values.use_touch_from_button = ui->touch_from_button_checkbox->isChecked();
306 Settings::values.touch_from_button_map_index = ui->touch_from_button_map->currentIndex();
307 Settings::values.touch_from_button_maps = touch_from_button_maps;
308 Settings::values.udp_input_address = ui->udp_server->text().toStdString();
309 Settings::values.udp_input_port = static_cast<u16>(ui->udp_port->text().toInt());
310 Settings::values.udp_pad_index = static_cast<u8>(ui->udp_pad_index->currentIndex());
311 input_subsystem->ReloadInputDevices();
312
313 accept();
314}
diff --git a/src/yuzu/configuration/configure_motion_touch.h b/src/yuzu/configuration/configure_motion_touch.h
new file mode 100644
index 000000000..3d4b5d659
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.h
@@ -0,0 +1,90 @@
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
11class QLabel;
12class QPushButton;
13class QVBoxLayout;
14
15namespace InputCommon {
16class InputSubsystem;
17}
18
19namespace InputCommon::CemuhookUDP {
20class CalibrationConfigurationJob;
21}
22
23namespace Ui {
24class ConfigureMotionTouch;
25}
26
27/// A dialog for touchpad calibration configuration.
28class CalibrationConfigurationDialog : public QDialog {
29 Q_OBJECT
30public:
31 explicit CalibrationConfigurationDialog(QWidget* parent, const std::string& host, u16 port,
32 u8 pad_index, u16 client_id);
33 ~CalibrationConfigurationDialog() override;
34
35private:
36 Q_INVOKABLE void UpdateLabelText(const QString& text);
37 Q_INVOKABLE void UpdateButtonText(const QString& text);
38
39 QVBoxLayout* layout;
40 QLabel* status_label;
41 QPushButton* cancel_button;
42 std::unique_ptr<InputCommon::CemuhookUDP::CalibrationConfigurationJob> job;
43
44 // Configuration results
45 bool completed{};
46 u16 min_x{};
47 u16 min_y{};
48 u16 max_x{};
49 u16 max_y{};
50
51 friend class ConfigureMotionTouch;
52};
53
54class ConfigureMotionTouch : public QDialog {
55 Q_OBJECT
56
57public:
58 explicit ConfigureMotionTouch(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_);
59 ~ConfigureMotionTouch() override;
60
61public slots:
62 void ApplyConfiguration();
63
64private slots:
65 void OnCemuhookUDPTest();
66 void OnConfigureTouchCalibration();
67 void OnConfigureTouchFromButton();
68
69private:
70 void closeEvent(QCloseEvent* event) override;
71 Q_INVOKABLE void ShowUDPTestResult(bool result);
72 void SetConfiguration();
73 void UpdateUiDisplay();
74 void ConnectEvents();
75 bool CanCloseDialog();
76
77 InputCommon::InputSubsystem* input_subsystem;
78
79 std::unique_ptr<Ui::ConfigureMotionTouch> ui;
80
81 // Coordinate system of the CemuhookUDP touch provider
82 int min_x{};
83 int min_y{};
84 int max_x{};
85 int max_y{};
86
87 bool udp_test_in_progress{};
88
89 std::vector<Settings::TouchFromButtonMap> touch_from_button_maps;
90};
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..15557e4b8
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.cpp
@@ -0,0 +1,623 @@
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 "core/frontend/framebuffer_layout.h"
14#include "core/settings.h"
15#include "input_common/main.h"
16#include "ui_configure_touch_from_button.h"
17#include "yuzu/configuration/configure_touch_from_button.h"
18#include "yuzu/configuration/configure_touch_widget.h"
19
20static QString GetKeyName(int key_code) {
21 switch (key_code) {
22 case Qt::Key_Shift:
23 return QObject::tr("Shift");
24 case Qt::Key_Control:
25 return QObject::tr("Ctrl");
26 case Qt::Key_Alt:
27 return QObject::tr("Alt");
28 case Qt::Key_Meta:
29 return QString{};
30 default:
31 return QKeySequence(key_code).toString();
32 }
33}
34
35static QString ButtonToText(const Common::ParamPackage& param) {
36 if (!param.Has("engine")) {
37 return QObject::tr("[not set]");
38 }
39
40 if (param.Get("engine", "") == "keyboard") {
41 return GetKeyName(param.Get("code", 0));
42 }
43
44 if (param.Get("engine", "") == "sdl") {
45 if (param.Has("hat")) {
46 const QString hat_str = QString::fromStdString(param.Get("hat", ""));
47 const QString direction_str = QString::fromStdString(param.Get("direction", ""));
48
49 return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
50 }
51
52 if (param.Has("axis")) {
53 const QString axis_str = QString::fromStdString(param.Get("axis", ""));
54 const QString direction_str = QString::fromStdString(param.Get("direction", ""));
55
56 return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
57 }
58
59 if (param.Has("button")) {
60 const QString button_str = QString::fromStdString(param.Get("button", ""));
61
62 return QObject::tr("Button %1").arg(button_str);
63 }
64
65 return {};
66 }
67
68 return QObject::tr("[unknown]");
69}
70
71ConfigureTouchFromButton::ConfigureTouchFromButton(
72 QWidget* parent, const std::vector<Settings::TouchFromButtonMap>& touch_maps,
73 InputCommon::InputSubsystem* input_subsystem_, const int default_index)
74 : QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchFromButton>()),
75 touch_maps(touch_maps), input_subsystem{input_subsystem_}, selected_index(default_index),
76 timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
77 ui->setupUi(this);
78 binding_list_model = new QStandardItemModel(0, 3, this);
79 binding_list_model->setHorizontalHeaderLabels(
80 {tr("Button"), tr("X", "X axis"), tr("Y", "Y axis")});
81 ui->binding_list->setModel(binding_list_model);
82 ui->bottom_screen->SetCoordLabel(ui->coord_label);
83
84 SetConfiguration();
85 UpdateUiDisplay();
86 ConnectEvents();
87}
88
89ConfigureTouchFromButton::~ConfigureTouchFromButton() = default;
90
91void ConfigureTouchFromButton::showEvent(QShowEvent* ev) {
92 QWidget::showEvent(ev);
93
94 // width values are not valid in the constructor
95 const int w =
96 ui->binding_list->viewport()->contentsRect().width() / binding_list_model->columnCount();
97 if (w <= 0) {
98 return;
99 }
100 ui->binding_list->setColumnWidth(0, w);
101 ui->binding_list->setColumnWidth(1, w);
102 ui->binding_list->setColumnWidth(2, w);
103}
104
105void ConfigureTouchFromButton::SetConfiguration() {
106 for (const auto& touch_map : touch_maps) {
107 ui->mapping->addItem(QString::fromStdString(touch_map.name));
108 }
109
110 ui->mapping->setCurrentIndex(selected_index);
111}
112
113void ConfigureTouchFromButton::UpdateUiDisplay() {
114 ui->button_delete->setEnabled(touch_maps.size() > 1);
115 ui->button_delete_bind->setEnabled(false);
116
117 binding_list_model->removeRows(0, binding_list_model->rowCount());
118
119 for (const auto& button_str : touch_maps[selected_index].buttons) {
120 Common::ParamPackage package{button_str};
121 QStandardItem* button = new QStandardItem(ButtonToText(package));
122 button->setData(QString::fromStdString(button_str));
123 button->setEditable(false);
124 QStandardItem* xcoord = new QStandardItem(QString::number(package.Get("x", 0)));
125 QStandardItem* ycoord = new QStandardItem(QString::number(package.Get("y", 0)));
126 binding_list_model->appendRow({button, xcoord, ycoord});
127
128 const int dot = ui->bottom_screen->AddDot(package.Get("x", 0), package.Get("y", 0));
129 button->setData(dot, DataRoleDot);
130 }
131}
132
133void ConfigureTouchFromButton::ConnectEvents() {
134 connect(ui->mapping, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
135 SaveCurrentMapping();
136 selected_index = index;
137 UpdateUiDisplay();
138 });
139 connect(ui->button_new, &QPushButton::clicked, this, &ConfigureTouchFromButton::NewMapping);
140 connect(ui->button_delete, &QPushButton::clicked, this,
141 &ConfigureTouchFromButton::DeleteMapping);
142 connect(ui->button_rename, &QPushButton::clicked, this,
143 &ConfigureTouchFromButton::RenameMapping);
144 connect(ui->button_delete_bind, &QPushButton::clicked, this,
145 &ConfigureTouchFromButton::DeleteBinding);
146 connect(ui->binding_list, &QTreeView::doubleClicked, this,
147 &ConfigureTouchFromButton::EditBinding);
148 connect(ui->binding_list->selectionModel(), &QItemSelectionModel::selectionChanged, this,
149 &ConfigureTouchFromButton::OnBindingSelection);
150 connect(binding_list_model, &QStandardItemModel::itemChanged, this,
151 &ConfigureTouchFromButton::OnBindingChanged);
152 connect(ui->binding_list->model(), &QStandardItemModel::rowsAboutToBeRemoved, this,
153 &ConfigureTouchFromButton::OnBindingDeleted);
154 connect(ui->bottom_screen, &TouchScreenPreview::DotAdded, this,
155 &ConfigureTouchFromButton::NewBinding);
156 connect(ui->bottom_screen, &TouchScreenPreview::DotSelected, this,
157 &ConfigureTouchFromButton::SetActiveBinding);
158 connect(ui->bottom_screen, &TouchScreenPreview::DotMoved, this,
159 &ConfigureTouchFromButton::SetCoordinates);
160 connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
161 &ConfigureTouchFromButton::ApplyConfiguration);
162
163 connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
164
165 connect(poll_timer.get(), &QTimer::timeout, [this]() {
166 Common::ParamPackage params;
167 for (auto& poller : device_pollers) {
168 params = poller->GetNextInput();
169 if (params.Has("engine")) {
170 SetPollingResult(params, false);
171 return;
172 }
173 }
174 });
175}
176
177void ConfigureTouchFromButton::SaveCurrentMapping() {
178 auto& map = touch_maps[selected_index];
179 map.buttons.clear();
180 for (int i = 0, rc = binding_list_model->rowCount(); i < rc; ++i) {
181 const auto bind_str = binding_list_model->index(i, 0)
182 .data(Qt::ItemDataRole::UserRole + 1)
183 .toString()
184 .toStdString();
185 if (bind_str.empty()) {
186 continue;
187 }
188 Common::ParamPackage params{bind_str};
189 if (!params.Has("engine")) {
190 continue;
191 }
192 params.Set("x", binding_list_model->index(i, 1).data().toInt());
193 params.Set("y", binding_list_model->index(i, 2).data().toInt());
194 map.buttons.emplace_back(params.Serialize());
195 }
196}
197
198void ConfigureTouchFromButton::NewMapping() {
199 const QString name =
200 QInputDialog::getText(this, tr("New Profile"), tr("Enter the name for the new profile."));
201 if (name.isEmpty()) {
202 return;
203 }
204 touch_maps.emplace_back(Settings::TouchFromButtonMap{name.toStdString(), {}});
205 ui->mapping->addItem(name);
206 ui->mapping->setCurrentIndex(ui->mapping->count() - 1);
207}
208
209void ConfigureTouchFromButton::DeleteMapping() {
210 const auto answer = QMessageBox::question(
211 this, tr("Delete Profile"), tr("Delete profile %1?").arg(ui->mapping->currentText()));
212 if (answer != QMessageBox::Yes) {
213 return;
214 }
215 const bool blocked = ui->mapping->blockSignals(true);
216 ui->mapping->removeItem(selected_index);
217 ui->mapping->blockSignals(blocked);
218 touch_maps.erase(touch_maps.begin() + selected_index);
219 selected_index = ui->mapping->currentIndex();
220 UpdateUiDisplay();
221}
222
223void ConfigureTouchFromButton::RenameMapping() {
224 const QString new_name = QInputDialog::getText(this, tr("Rename Profile"), tr("New name:"));
225 if (new_name.isEmpty()) {
226 return;
227 }
228 ui->mapping->setItemText(selected_index, new_name);
229 touch_maps[selected_index].name = new_name.toStdString();
230}
231
232void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is_new) {
233 binding_list_model->item(row_index, 0)->setText(tr("[press key]"));
234
235 input_setter = [this, row_index, is_new](const Common::ParamPackage& params,
236 const bool cancel) {
237 auto* cell = binding_list_model->item(row_index, 0);
238 if (cancel) {
239 if (is_new) {
240 binding_list_model->removeRow(row_index);
241 } else {
242 cell->setText(
243 ButtonToText(Common::ParamPackage{cell->data().toString().toStdString()}));
244 }
245 } else {
246 cell->setText(ButtonToText(params));
247 cell->setData(QString::fromStdString(params.Serialize()));
248 }
249 };
250
251 device_pollers = input_subsystem->GetPollers(InputCommon::Polling::DeviceType::Button);
252
253 for (auto& poller : device_pollers) {
254 poller->Start();
255 }
256
257 grabKeyboard();
258 grabMouse();
259 qApp->setOverrideCursor(QCursor(Qt::CursorShape::ArrowCursor));
260 timeout_timer->start(5000); // Cancel after 5 seconds
261 poll_timer->start(200); // Check for new inputs every 200ms
262}
263
264void ConfigureTouchFromButton::NewBinding(const QPoint& pos) {
265 auto* button = new QStandardItem();
266 button->setEditable(false);
267 auto* x_coord = new QStandardItem(QString::number(pos.x()));
268 auto* y_coord = new QStandardItem(QString::number(pos.y()));
269
270 const int dot_id = ui->bottom_screen->AddDot(pos.x(), pos.y());
271 button->setData(dot_id, DataRoleDot);
272
273 binding_list_model->appendRow({button, x_coord, y_coord});
274 ui->binding_list->setFocus();
275 ui->binding_list->setCurrentIndex(button->index());
276
277 GetButtonInput(binding_list_model->rowCount() - 1, true);
278}
279
280void ConfigureTouchFromButton::EditBinding(const QModelIndex& qi) {
281 if (qi.row() >= 0 && qi.column() == 0) {
282 GetButtonInput(qi.row(), false);
283 }
284}
285
286void ConfigureTouchFromButton::DeleteBinding() {
287 const int row_index = ui->binding_list->currentIndex().row();
288 if (row_index < 0) {
289 return;
290 }
291 ui->bottom_screen->RemoveDot(binding_list_model->index(row_index, 0).data(DataRoleDot).toInt());
292 binding_list_model->removeRow(row_index);
293}
294
295void ConfigureTouchFromButton::OnBindingSelection(const QItemSelection& selected,
296 const QItemSelection& deselected) {
297 ui->button_delete_bind->setEnabled(!selected.isEmpty());
298 if (!selected.isEmpty()) {
299 const auto dot_data = selected.indexes().first().data(DataRoleDot);
300 if (dot_data.isValid()) {
301 ui->bottom_screen->HighlightDot(dot_data.toInt());
302 }
303 }
304 if (!deselected.isEmpty()) {
305 const auto dot_data = deselected.indexes().first().data(DataRoleDot);
306 if (dot_data.isValid()) {
307 ui->bottom_screen->HighlightDot(dot_data.toInt(), false);
308 }
309 }
310}
311
312void ConfigureTouchFromButton::OnBindingChanged(QStandardItem* item) {
313 if (item->column() == 0) {
314 return;
315 }
316
317 const bool blocked = binding_list_model->blockSignals(true);
318 item->setText(QString::number(
319 std::clamp(item->text().toInt(), 0,
320 static_cast<int>((item->column() == 1 ? Layout::ScreenUndocked::Width
321 : Layout::ScreenUndocked::Height) -
322 1))));
323 binding_list_model->blockSignals(blocked);
324
325 const auto dot_data = binding_list_model->index(item->row(), 0).data(DataRoleDot);
326 if (dot_data.isValid()) {
327 ui->bottom_screen->MoveDot(dot_data.toInt(),
328 binding_list_model->item(item->row(), 1)->text().toInt(),
329 binding_list_model->item(item->row(), 2)->text().toInt());
330 }
331}
332
333void ConfigureTouchFromButton::OnBindingDeleted(const QModelIndex& parent, int first, int last) {
334 for (int i = first; i <= last; ++i) {
335 const auto ix = binding_list_model->index(i, 0);
336 if (!ix.isValid()) {
337 return;
338 }
339 const auto dot_data = ix.data(DataRoleDot);
340 if (dot_data.isValid()) {
341 ui->bottom_screen->RemoveDot(dot_data.toInt());
342 }
343 }
344}
345
346void ConfigureTouchFromButton::SetActiveBinding(const int dot_id) {
347 for (int i = 0; i < binding_list_model->rowCount(); ++i) {
348 if (binding_list_model->index(i, 0).data(DataRoleDot) == dot_id) {
349 ui->binding_list->setCurrentIndex(binding_list_model->index(i, 0));
350 ui->binding_list->setFocus();
351 return;
352 }
353 }
354}
355
356void ConfigureTouchFromButton::SetCoordinates(const int dot_id, const QPoint& pos) {
357 for (int i = 0; i < binding_list_model->rowCount(); ++i) {
358 if (binding_list_model->item(i, 0)->data(DataRoleDot) == dot_id) {
359 binding_list_model->item(i, 1)->setText(QString::number(pos.x()));
360 binding_list_model->item(i, 2)->setText(QString::number(pos.y()));
361 return;
362 }
363 }
364}
365
366void ConfigureTouchFromButton::SetPollingResult(const Common::ParamPackage& params,
367 const bool cancel) {
368 releaseKeyboard();
369 releaseMouse();
370 qApp->restoreOverrideCursor();
371 timeout_timer->stop();
372 poll_timer->stop();
373 for (auto& poller : device_pollers) {
374 poller->Stop();
375 }
376 if (input_setter) {
377 (*input_setter)(params, cancel);
378 input_setter.reset();
379 }
380}
381
382void ConfigureTouchFromButton::keyPressEvent(QKeyEvent* event) {
383 if (!input_setter && event->key() == Qt::Key_Delete) {
384 DeleteBinding();
385 return;
386 }
387
388 if (!input_setter) {
389 return QDialog::keyPressEvent(event);
390 }
391
392 if (event->key() != Qt::Key_Escape) {
393 SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
394 false);
395 } else {
396 SetPollingResult({}, true);
397 }
398}
399
400void ConfigureTouchFromButton::ApplyConfiguration() {
401 SaveCurrentMapping();
402 accept();
403}
404
405int ConfigureTouchFromButton::GetSelectedIndex() const {
406 return selected_index;
407}
408
409std::vector<Settings::TouchFromButtonMap> ConfigureTouchFromButton::GetMaps() const {
410 return touch_maps;
411}
412
413TouchScreenPreview::TouchScreenPreview(QWidget* parent) : QFrame(parent) {
414 setBackgroundRole(QPalette::ColorRole::Base);
415}
416
417TouchScreenPreview::~TouchScreenPreview() = default;
418
419void TouchScreenPreview::SetCoordLabel(QLabel* const label) {
420 coord_label = label;
421}
422
423int TouchScreenPreview::AddDot(const int device_x, const int device_y) {
424 QFont dot_font{QStringLiteral("monospace")};
425 dot_font.setStyleHint(QFont::Monospace);
426 dot_font.setPointSize(20);
427
428 auto* dot = new QLabel(this);
429 dot->setAttribute(Qt::WA_TranslucentBackground);
430 dot->setFont(dot_font);
431 dot->setText(QChar(0xD7)); // U+00D7 Multiplication Sign
432 dot->setAlignment(Qt::AlignmentFlag::AlignCenter);
433 dot->setProperty(PropId, ++max_dot_id);
434 dot->setProperty(PropX, device_x);
435 dot->setProperty(PropY, device_y);
436 dot->setCursor(Qt::CursorShape::PointingHandCursor);
437 dot->setMouseTracking(true);
438 dot->installEventFilter(this);
439 dot->show();
440 PositionDot(dot, device_x, device_y);
441 dots.emplace_back(max_dot_id, dot);
442 return max_dot_id;
443}
444
445void TouchScreenPreview::RemoveDot(const int id) {
446 const auto iter = std::find_if(dots.begin(), dots.end(),
447 [id](const auto& entry) { return entry.first == id; });
448 if (iter == dots.cend()) {
449 return;
450 }
451
452 iter->second->deleteLater();
453 dots.erase(iter);
454}
455
456void TouchScreenPreview::HighlightDot(const int id, const bool active) const {
457 for (const auto& dot : dots) {
458 if (dot.first == id) {
459 // use color property from the stylesheet, or fall back to the default palette
460 if (dot_highlight_color.isValid()) {
461 dot.second->setStyleSheet(
462 active ? QStringLiteral("color: %1").arg(dot_highlight_color.name())
463 : QString{});
464 } else {
465 dot.second->setForegroundRole(active ? QPalette::ColorRole::LinkVisited
466 : QPalette::ColorRole::NoRole);
467 }
468 if (active) {
469 dot.second->raise();
470 }
471 return;
472 }
473 }
474}
475
476void TouchScreenPreview::MoveDot(const int id, const int device_x, const int device_y) const {
477 const auto iter = std::find_if(dots.begin(), dots.end(),
478 [id](const auto& entry) { return entry.first == id; });
479 if (iter == dots.cend()) {
480 return;
481 }
482
483 iter->second->setProperty(PropX, device_x);
484 iter->second->setProperty(PropY, device_y);
485 PositionDot(iter->second, device_x, device_y);
486}
487
488void TouchScreenPreview::resizeEvent(QResizeEvent* event) {
489 if (ignore_resize) {
490 return;
491 }
492
493 const int target_width = std::min(width(), height() * 4 / 3);
494 const int target_height = std::min(height(), width() * 3 / 4);
495 if (target_width == width() && target_height == height()) {
496 return;
497 }
498 ignore_resize = true;
499 setGeometry((parentWidget()->contentsRect().width() - target_width) / 2, y(), target_width,
500 target_height);
501 ignore_resize = false;
502
503 if (event->oldSize().width() != target_width || event->oldSize().height() != target_height) {
504 for (const auto& dot : dots) {
505 PositionDot(dot.second);
506 }
507 }
508}
509
510void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) {
511 if (!coord_label) {
512 return;
513 }
514 const auto pos = MapToDeviceCoords(event->x(), event->y());
515 if (pos) {
516 coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y()));
517 } else {
518 coord_label->clear();
519 }
520}
521
522void TouchScreenPreview::leaveEvent(QEvent* event) {
523 if (coord_label) {
524 coord_label->clear();
525 }
526}
527
528void TouchScreenPreview::mousePressEvent(QMouseEvent* event) {
529 if (event->button() != Qt::MouseButton::LeftButton) {
530 return;
531 }
532 const auto pos = MapToDeviceCoords(event->x(), event->y());
533 if (pos) {
534 emit DotAdded(*pos);
535 }
536}
537
538bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
539 switch (event->type()) {
540 case QEvent::Type::MouseButtonPress: {
541 const auto mouse_event = static_cast<QMouseEvent*>(event);
542 if (mouse_event->button() != Qt::MouseButton::LeftButton) {
543 break;
544 }
545 emit DotSelected(obj->property(PropId).toInt());
546
547 drag_state.dot = qobject_cast<QLabel*>(obj);
548 drag_state.start_pos = mouse_event->globalPos();
549 return true;
550 }
551 case QEvent::Type::MouseMove: {
552 if (!drag_state.dot) {
553 break;
554 }
555 const auto mouse_event = static_cast<QMouseEvent*>(event);
556 if (!drag_state.active) {
557 drag_state.active =
558 (mouse_event->globalPos() - drag_state.start_pos).manhattanLength() >=
559 QApplication::startDragDistance();
560 if (!drag_state.active) {
561 break;
562 }
563 }
564 auto current_pos = mapFromGlobal(mouse_event->globalPos());
565 current_pos.setX(std::clamp(current_pos.x(), contentsMargins().left(),
566 contentsMargins().left() + contentsRect().width() - 1));
567 current_pos.setY(std::clamp(current_pos.y(), contentsMargins().top(),
568 contentsMargins().top() + contentsRect().height() - 1));
569 const auto device_coord = MapToDeviceCoords(current_pos.x(), current_pos.y());
570 if (device_coord) {
571 drag_state.dot->setProperty(PropX, device_coord->x());
572 drag_state.dot->setProperty(PropY, device_coord->y());
573 PositionDot(drag_state.dot, device_coord->x(), device_coord->y());
574 emit DotMoved(drag_state.dot->property(PropId).toInt(), *device_coord);
575 if (coord_label) {
576 coord_label->setText(
577 QStringLiteral("X: %1, Y: %2").arg(device_coord->x()).arg(device_coord->y()));
578 }
579 }
580 return true;
581 }
582 case QEvent::Type::MouseButtonRelease: {
583 drag_state.dot.clear();
584 drag_state.active = false;
585 return true;
586 }
587 default:
588 break;
589 }
590 return obj->eventFilter(obj, event);
591}
592
593std::optional<QPoint> TouchScreenPreview::MapToDeviceCoords(const int screen_x,
594 const int screen_y) const {
595 const float t_x = 0.5f + static_cast<float>(screen_x - contentsMargins().left()) *
596 (Layout::ScreenUndocked::Width - 1) / (contentsRect().width() - 1);
597 const float t_y = 0.5f + static_cast<float>(screen_y - contentsMargins().top()) *
598 (Layout::ScreenUndocked::Height - 1) /
599 (contentsRect().height() - 1);
600 if (t_x >= 0.5f && t_x < Layout::ScreenUndocked::Width && t_y >= 0.5f &&
601 t_y < Layout::ScreenUndocked::Height) {
602
603 return QPoint{static_cast<int>(t_x), static_cast<int>(t_y)};
604 }
605 return std::nullopt;
606}
607
608void TouchScreenPreview::PositionDot(QLabel* const dot, const int device_x,
609 const int device_y) const {
610 const float device_coord_x =
611 static_cast<float>(device_x >= 0 ? device_x : dot->property(PropX).toInt());
612 int x_coord = static_cast<int>(
613 device_coord_x * (contentsRect().width() - 1) / (Layout::ScreenUndocked::Width - 1) +
614 contentsMargins().left() - static_cast<float>(dot->width()) / 2 + 0.5f);
615
616 const float device_coord_y =
617 static_cast<float>(device_y >= 0 ? device_y : dot->property(PropY).toInt());
618 const int y_coord = static_cast<int>(
619 device_coord_y * (contentsRect().height() - 1) / (Layout::ScreenUndocked::Height - 1) +
620 contentsMargins().top() - static_cast<float>(dot->height()) / 2 + 0.5f);
621
622 dot->move(x_coord, y_coord);
623}
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..d9513e3bc
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.h
@@ -0,0 +1,92 @@
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
13class QItemSelection;
14class QModelIndex;
15class QStandardItemModel;
16class QStandardItem;
17class QTimer;
18
19namespace Common {
20class ParamPackage;
21}
22
23namespace InputCommon {
24class InputSubsystem;
25}
26
27namespace InputCommon::Polling {
28class DevicePoller;
29}
30
31namespace Settings {
32struct TouchFromButtonMap;
33}
34
35namespace Ui {
36class ConfigureTouchFromButton;
37}
38
39class ConfigureTouchFromButton : public QDialog {
40 Q_OBJECT
41
42public:
43 explicit ConfigureTouchFromButton(QWidget* parent,
44 const std::vector<Settings::TouchFromButtonMap>& touch_maps,
45 InputCommon::InputSubsystem* input_subsystem_,
46 int default_index = 0);
47 ~ConfigureTouchFromButton() override;
48
49 int GetSelectedIndex() const;
50 std::vector<Settings::TouchFromButtonMap> GetMaps() const;
51
52public slots:
53 void ApplyConfiguration();
54 void NewBinding(const QPoint& pos);
55 void SetActiveBinding(int dot_id);
56 void SetCoordinates(int dot_id, const QPoint& pos);
57
58protected:
59 void showEvent(QShowEvent* ev) override;
60 void keyPressEvent(QKeyEvent* event) override;
61
62private slots:
63 void NewMapping();
64 void DeleteMapping();
65 void RenameMapping();
66 void EditBinding(const QModelIndex& qi);
67 void DeleteBinding();
68 void OnBindingSelection(const QItemSelection& selected, const QItemSelection& deselected);
69 void OnBindingChanged(QStandardItem* item);
70 void OnBindingDeleted(const QModelIndex& parent, int first, int last);
71
72private:
73 void SetConfiguration();
74 void UpdateUiDisplay();
75 void ConnectEvents();
76 void GetButtonInput(int row_index, bool is_new);
77 void SetPollingResult(const Common::ParamPackage& params, bool cancel);
78 void SaveCurrentMapping();
79
80 std::unique_ptr<Ui::ConfigureTouchFromButton> ui;
81 std::vector<Settings::TouchFromButtonMap> touch_maps;
82 QStandardItemModel* binding_list_model;
83 InputCommon::InputSubsystem* input_subsystem;
84 int selected_index;
85
86 std::unique_ptr<QTimer> timeout_timer;
87 std::unique_ptr<QTimer> poll_timer;
88 std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
89 std::optional<std::function<void(const Common::ParamPackage&, bool)>> input_setter;
90
91 static constexpr int DataRoleDot = Qt::ItemDataRole::UserRole + 2;
92};
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..f581e27e0
--- /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>yuzu/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..347b46583
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_widget.h
@@ -0,0 +1,62 @@
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 void resizeEvent(QResizeEvent*) override;
37 void mouseMoveEvent(QMouseEvent*) override;
38 void leaveEvent(QEvent*) override;
39 void mousePressEvent(QMouseEvent*) override;
40 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 DragState {
57 bool active = false;
58 QPointer<QLabel> dot;
59 QPoint start_pos;
60 };
61 DragState drag_state;
62};