19 changed files with 1894 additions and 3 deletions
-
5dist/qt_themes/qdarkstyle/style.qss
-
12src/core/hle/service/hid/controllers/touchscreen.cpp
-
1src/core/hle/service/hid/controllers/touchscreen.h
-
12src/core/settings.h
-
2src/input_common/CMakeLists.txt
-
9src/input_common/main.cpp
-
3src/input_common/main.h
-
49src/input_common/touch_from_button.cpp
-
25src/input_common/touch_from_button.h
-
7src/yuzu/CMakeLists.txt
-
68src/yuzu/configuration/config.cpp
-
6src/yuzu/configuration/configure_input.cpp
-
304src/yuzu/configuration/configure_motion_touch.cpp
-
77src/yuzu/configuration/configure_motion_touch.h
-
327src/yuzu/configuration/configure_motion_touch.ui
-
612src/yuzu/configuration/configure_touch_from_button.cpp
-
86src/yuzu/configuration/configure_touch_from_button.h
-
231src/yuzu/configuration/configure_touch_from_button.ui
-
61src/yuzu/configuration/configure_touch_widget.h
@ -0,0 +1,49 @@ |
|||||
|
// Copyright 2020 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include "core/settings.h"
|
||||
|
#include "input_common/touch_from_button.h"
|
||||
|
|
||||
|
namespace InputCommon { |
||||
|
|
||||
|
class TouchFromButtonDevice final : public Input::TouchDevice { |
||||
|
public: |
||||
|
TouchFromButtonDevice() { |
||||
|
for (const auto& config_entry : |
||||
|
Settings::values.touch_from_button_maps[Settings::values.touch_from_button_map_index] |
||||
|
.buttons) { |
||||
|
const Common::ParamPackage package{config_entry}; |
||||
|
map.emplace_back( |
||||
|
Input::CreateDevice<Input::ButtonDevice>(config_entry), |
||||
|
std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)), |
||||
|
std::clamp(package.Get("y", 0), 0, |
||||
|
static_cast<int>(Layout::ScreenUndocked::Height))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
std::tuple<float, float, bool> GetStatus() const override { |
||||
|
for (const auto& m : map) { |
||||
|
const bool state = std::get<0>(m)->GetStatus(); |
||||
|
if (state) { |
||||
|
const float x = static_cast<float>(std::get<1>(m)) / |
||||
|
static_cast<int>(Layout::ScreenUndocked::Width); |
||||
|
const float y = static_cast<float>(std::get<2>(m)) / |
||||
|
static_cast<int>(Layout::ScreenUndocked::Height); |
||||
|
return std::make_tuple(x, y, true); |
||||
|
} |
||||
|
} |
||||
|
return std::make_tuple(0.0f, 0.0f, false); |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map; // button, x, y
|
||||
|
}; |
||||
|
|
||||
|
std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create( |
||||
|
const Common::ParamPackage& params) { |
||||
|
|
||||
|
return std::make_unique<TouchFromButtonDevice>(); |
||||
|
} |
||||
|
|
||||
|
} // namespace InputCommon
|
||||
@ -0,0 +1,25 @@ |
|||||
|
// Copyright 2020 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <memory> |
||||
|
#include "core/frontend/framebuffer_layout.h" |
||||
|
#include "core/frontend/input.h" |
||||
|
|
||||
|
namespace InputCommon { |
||||
|
|
||||
|
/** |
||||
|
* A touch device factory that takes a list of button devices and combines them into a touch device. |
||||
|
*/ |
||||
|
class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> { |
||||
|
public: |
||||
|
/** |
||||
|
* Creates a touch device from a list of button devices |
||||
|
* @param unused |
||||
|
*/ |
||||
|
std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override; |
||||
|
}; |
||||
|
|
||||
|
} // namespace InputCommon |
||||
@ -0,0 +1,304 @@ |
|||||
|
// Copyright 2018 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <array>
|
||||
|
#include <QCloseEvent>
|
||||
|
#include <QLabel>
|
||||
|
#include <QMessageBox>
|
||||
|
#include <QPushButton>
|
||||
|
#include <QVBoxLayout>
|
||||
|
#include "input_common/main.h"
|
||||
|
#include "ui_configure_motion_touch.h"
|
||||
|
#include "yuzu/configuration/configure_motion_touch.h"
|
||||
|
#include "yuzu/configuration/configure_touch_from_button.h"
|
||||
|
|
||||
|
CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent, |
||||
|
const std::string& host, u16 port, |
||||
|
u8 pad_index, u16 client_id) |
||||
|
: QDialog(parent) { |
||||
|
layout = new QVBoxLayout; |
||||
|
status_label = new QLabel(tr("Communicating with the server...")); |
||||
|
cancel_button = new QPushButton(tr("Cancel")); |
||||
|
connect(cancel_button, &QPushButton::clicked, this, [this] { |
||||
|
if (!completed) |
||||
|
job->Stop(); |
||||
|
accept(); |
||||
|
}); |
||||
|
layout->addWidget(status_label); |
||||
|
layout->addWidget(cancel_button); |
||||
|
setLayout(layout); |
||||
|
|
||||
|
using namespace InputCommon::CemuhookUDP; |
||||
|
job = std::make_unique<CalibrationConfigurationJob>( |
||||
|
host, port, pad_index, client_id, |
||||
|
[this](CalibrationConfigurationJob::Status status) { |
||||
|
QString text; |
||||
|
switch (status) { |
||||
|
case CalibrationConfigurationJob::Status::Ready: |
||||
|
text = tr("Touch the top left corner <br>of your touchpad."); |
||||
|
break; |
||||
|
case CalibrationConfigurationJob::Status::Stage1Completed: |
||||
|
text = tr("Now touch the bottom right corner <br>of your touchpad."); |
||||
|
break; |
||||
|
case CalibrationConfigurationJob::Status::Completed: |
||||
|
text = tr("Configuration completed!"); |
||||
|
break; |
||||
|
} |
||||
|
QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text)); |
||||
|
if (status == CalibrationConfigurationJob::Status::Completed) { |
||||
|
QMetaObject::invokeMethod(this, "UpdateButtonText", Q_ARG(QString, tr("OK"))); |
||||
|
} |
||||
|
}, |
||||
|
[this](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) { |
||||
|
completed = true; |
||||
|
min_x = min_x_; |
||||
|
min_y = min_y_; |
||||
|
max_x = max_x_; |
||||
|
max_y = max_y_; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
CalibrationConfigurationDialog::~CalibrationConfigurationDialog() = default; |
||||
|
|
||||
|
void CalibrationConfigurationDialog::UpdateLabelText(QString text) { |
||||
|
status_label->setText(text); |
||||
|
} |
||||
|
|
||||
|
void CalibrationConfigurationDialog::UpdateButtonText(QString text) { |
||||
|
cancel_button->setText(text); |
||||
|
} |
||||
|
|
||||
|
const std::array<std::pair<const char*, const char*>, 2> MotionProviders = { |
||||
|
{{"motion_emu", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Mouse (Right Click)")}, |
||||
|
{"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}}}; |
||||
|
|
||||
|
const std::array<std::pair<const char*, const char*>, 2> TouchProviders = { |
||||
|
{{"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")}, |
||||
|
{"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}}}; |
||||
|
|
||||
|
ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent) |
||||
|
: QDialog(parent), ui(std::make_unique<Ui::ConfigureMotionTouch>()) { |
||||
|
ui->setupUi(this); |
||||
|
for (auto [provider, name] : MotionProviders) { |
||||
|
ui->motion_provider->addItem(tr(name), QString::fromUtf8(provider)); |
||||
|
} |
||||
|
for (auto [provider, name] : TouchProviders) { |
||||
|
ui->touch_provider->addItem(tr(name), QString::fromUtf8(provider)); |
||||
|
} |
||||
|
|
||||
|
ui->udp_learn_more->setOpenExternalLinks(true); |
||||
|
ui->udp_learn_more->setText( |
||||
|
tr("<a " |
||||
|
"href='https://citra-emu.org/wiki/" |
||||
|
"using-a-controller-or-android-phone-for-motion-or-touch-input'><span " |
||||
|
"style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>")); |
||||
|
|
||||
|
SetConfiguration(); |
||||
|
UpdateUiDisplay(); |
||||
|
ConnectEvents(); |
||||
|
} |
||||
|
|
||||
|
ConfigureMotionTouch::~ConfigureMotionTouch() = default; |
||||
|
|
||||
|
void ConfigureMotionTouch::SetConfiguration() { |
||||
|
Common::ParamPackage motion_param(Settings::values.motion_device); |
||||
|
Common::ParamPackage touch_param(Settings::values.touch_device); |
||||
|
std::string motion_engine = motion_param.Get("engine", "motion_emu"); |
||||
|
std::string touch_engine = touch_param.Get("engine", "emu_window"); |
||||
|
|
||||
|
ui->motion_provider->setCurrentIndex( |
||||
|
ui->motion_provider->findData(QString::fromStdString(motion_engine))); |
||||
|
ui->touch_provider->setCurrentIndex( |
||||
|
ui->touch_provider->findData(QString::fromStdString(touch_engine))); |
||||
|
ui->touch_from_button_checkbox->setChecked(Settings::values.use_touch_from_button); |
||||
|
touch_from_button_maps = Settings::values.touch_from_button_maps; |
||||
|
for (const auto& touch_map : touch_from_button_maps) { |
||||
|
ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name)); |
||||
|
} |
||||
|
ui->touch_from_button_map->setCurrentIndex(Settings::values.touch_from_button_map_index); |
||||
|
ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f)); |
||||
|
|
||||
|
min_x = touch_param.Get("min_x", 100); |
||||
|
min_y = touch_param.Get("min_y", 50); |
||||
|
max_x = touch_param.Get("max_x", 1800); |
||||
|
max_y = touch_param.Get("max_y", 850); |
||||
|
|
||||
|
ui->udp_server->setText(QString::fromStdString(Settings::values.udp_input_address)); |
||||
|
ui->udp_port->setText(QString::number(Settings::values.udp_input_port)); |
||||
|
ui->udp_pad_index->setCurrentIndex(Settings::values.udp_pad_index); |
||||
|
} |
||||
|
|
||||
|
void ConfigureMotionTouch::UpdateUiDisplay() { |
||||
|
std::string motion_engine = ui->motion_provider->currentData().toString().toStdString(); |
||||
|
std::string touch_engine = ui->touch_provider->currentData().toString().toStdString(); |
||||
|
|
||||
|
if (motion_engine == "motion_emu") { |
||||
|
ui->motion_sensitivity_label->setVisible(true); |
||||
|
ui->motion_sensitivity->setVisible(true); |
||||
|
} else { |
||||
|
ui->motion_sensitivity_label->setVisible(false); |
||||
|
ui->motion_sensitivity->setVisible(false); |
||||
|
} |
||||
|
|
||||
|
if (touch_engine == "cemuhookudp") { |
||||
|
ui->touch_calibration->setVisible(true); |
||||
|
ui->touch_calibration_config->setVisible(true); |
||||
|
ui->touch_calibration_label->setVisible(true); |
||||
|
ui->touch_calibration->setText(QStringLiteral("(%1, %2) - (%3, %4)") |
||||
|
.arg(QString::number(min_x), QString::number(min_y), |
||||
|
QString::number(max_x), QString::number(max_y))); |
||||
|
} else { |
||||
|
ui->touch_calibration->setVisible(false); |
||||
|
ui->touch_calibration_config->setVisible(false); |
||||
|
ui->touch_calibration_label->setVisible(false); |
||||
|
} |
||||
|
|
||||
|
if (motion_engine == "cemuhookudp" || touch_engine == "cemuhookudp") { |
||||
|
ui->udp_config_group_box->setVisible(true); |
||||
|
} else { |
||||
|
ui->udp_config_group_box->setVisible(false); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureMotionTouch::ConnectEvents() { |
||||
|
connect(ui->motion_provider, |
||||
|
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, |
||||
|
[this](int index) { UpdateUiDisplay(); }); |
||||
|
connect(ui->touch_provider, |
||||
|
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, |
||||
|
[this](int index) { UpdateUiDisplay(); }); |
||||
|
connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest); |
||||
|
connect(ui->touch_calibration_config, &QPushButton::clicked, this, |
||||
|
&ConfigureMotionTouch::OnConfigureTouchCalibration); |
||||
|
connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this, |
||||
|
&ConfigureMotionTouch::OnConfigureTouchFromButton); |
||||
|
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] { |
||||
|
if (CanCloseDialog()) |
||||
|
reject(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
void ConfigureMotionTouch::OnCemuhookUDPTest() { |
||||
|
ui->udp_test->setEnabled(false); |
||||
|
ui->udp_test->setText(tr("Testing")); |
||||
|
udp_test_in_progress = true; |
||||
|
InputCommon::CemuhookUDP::TestCommunication( |
||||
|
ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toInt()), |
||||
|
static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872, |
||||
|
[this] { |
||||
|
LOG_INFO(Frontend, "UDP input test success"); |
||||
|
QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, true)); |
||||
|
}, |
||||
|
[this] { |
||||
|
LOG_ERROR(Frontend, "UDP input test failed"); |
||||
|
QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, false)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
void ConfigureMotionTouch::OnConfigureTouchCalibration() { |
||||
|
ui->touch_calibration_config->setEnabled(false); |
||||
|
ui->touch_calibration_config->setText(tr("Configuring")); |
||||
|
CalibrationConfigurationDialog* dialog = new CalibrationConfigurationDialog( |
||||
|
this, ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toUInt()), |
||||
|
static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872); |
||||
|
dialog->exec(); |
||||
|
if (dialog->completed) { |
||||
|
min_x = dialog->min_x; |
||||
|
min_y = dialog->min_y; |
||||
|
max_x = dialog->max_x; |
||||
|
max_y = dialog->max_y; |
||||
|
LOG_INFO(Frontend, |
||||
|
"UDP touchpad calibration config success: min_x={}, min_y={}, max_x={}, max_y={}", |
||||
|
min_x, min_y, max_x, max_y); |
||||
|
UpdateUiDisplay(); |
||||
|
} else { |
||||
|
LOG_ERROR(Frontend, "UDP touchpad calibration config failed"); |
||||
|
} |
||||
|
ui->touch_calibration_config->setEnabled(true); |
||||
|
ui->touch_calibration_config->setText(tr("Configure")); |
||||
|
} |
||||
|
|
||||
|
void ConfigureMotionTouch::closeEvent(QCloseEvent* event) { |
||||
|
if (CanCloseDialog()) |
||||
|
event->accept(); |
||||
|
else |
||||
|
event->ignore(); |
||||
|
} |
||||
|
|
||||
|
void ConfigureMotionTouch::ShowUDPTestResult(bool result) { |
||||
|
udp_test_in_progress = false; |
||||
|
if (result) { |
||||
|
QMessageBox::information(this, tr("Test Successful"), |
||||
|
tr("Successfully received data from the server.")); |
||||
|
} else { |
||||
|
QMessageBox::warning(this, tr("Test Failed"), |
||||
|
tr("Could not receive valid data from the server.<br>Please verify " |
||||
|
"that the server is set up correctly and " |
||||
|
"the address and port are correct.")); |
||||
|
} |
||||
|
ui->udp_test->setEnabled(true); |
||||
|
ui->udp_test->setText(tr("Test")); |
||||
|
} |
||||
|
|
||||
|
void ConfigureMotionTouch::OnConfigureTouchFromButton() { |
||||
|
ConfigureTouchFromButton dialog{this, touch_from_button_maps, |
||||
|
ui->touch_from_button_map->currentIndex()}; |
||||
|
if (dialog.exec() != QDialog::Accepted) { |
||||
|
return; |
||||
|
} |
||||
|
touch_from_button_maps = dialog.GetMaps(); |
||||
|
|
||||
|
while (ui->touch_from_button_map->count() > 0) { |
||||
|
ui->touch_from_button_map->removeItem(0); |
||||
|
} |
||||
|
for (const auto& touch_map : touch_from_button_maps) { |
||||
|
ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name)); |
||||
|
} |
||||
|
ui->touch_from_button_map->setCurrentIndex(dialog.GetSelectedIndex()); |
||||
|
} |
||||
|
|
||||
|
bool ConfigureMotionTouch::CanCloseDialog() { |
||||
|
if (udp_test_in_progress) { |
||||
|
QMessageBox::warning(this, tr("Citra"), |
||||
|
tr("UDP Test or calibration configuration is in progress.<br>Please " |
||||
|
"wait for them to finish.")); |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void ConfigureMotionTouch::ApplyConfiguration() { |
||||
|
if (!CanCloseDialog()) |
||||
|
return; |
||||
|
|
||||
|
std::string motion_engine = ui->motion_provider->currentData().toString().toStdString(); |
||||
|
std::string touch_engine = ui->touch_provider->currentData().toString().toStdString(); |
||||
|
|
||||
|
Common::ParamPackage motion_param{}, touch_param{}; |
||||
|
motion_param.Set("engine", motion_engine); |
||||
|
touch_param.Set("engine", touch_engine); |
||||
|
|
||||
|
if (motion_engine == "motion_emu") { |
||||
|
motion_param.Set("sensitivity", static_cast<float>(ui->motion_sensitivity->value())); |
||||
|
} |
||||
|
|
||||
|
if (touch_engine == "cemuhookudp") { |
||||
|
touch_param.Set("min_x", min_x); |
||||
|
touch_param.Set("min_y", min_y); |
||||
|
touch_param.Set("max_x", max_x); |
||||
|
touch_param.Set("max_y", max_y); |
||||
|
} |
||||
|
|
||||
|
Settings::values.motion_device = motion_param.Serialize(); |
||||
|
Settings::values.touch_device = touch_param.Serialize(); |
||||
|
Settings::values.use_touch_from_button = ui->touch_from_button_checkbox->isChecked(); |
||||
|
Settings::values.touch_from_button_map_index = ui->touch_from_button_map->currentIndex(); |
||||
|
Settings::values.touch_from_button_maps = touch_from_button_maps; |
||||
|
Settings::values.udp_input_address = ui->udp_server->text().toStdString(); |
||||
|
Settings::values.udp_input_port = static_cast<u16>(ui->udp_port->text().toInt()); |
||||
|
Settings::values.udp_pad_index = static_cast<u8>(ui->udp_pad_index->currentIndex()); |
||||
|
InputCommon::ReloadInputDevices(); |
||||
|
|
||||
|
accept(); |
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
// Copyright 2018 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <memory> |
||||
|
#include <QDialog> |
||||
|
#include "common/param_package.h" |
||||
|
#include "core/settings.h" |
||||
|
#include "input_common/udp/client.h" |
||||
|
#include "input_common/udp/udp.h" |
||||
|
|
||||
|
class QVBoxLayout; |
||||
|
class QLabel; |
||||
|
class QPushButton; |
||||
|
|
||||
|
namespace Ui { |
||||
|
class ConfigureMotionTouch; |
||||
|
} |
||||
|
|
||||
|
/// A dialog for touchpad calibration configuration. |
||||
|
class CalibrationConfigurationDialog : public QDialog { |
||||
|
Q_OBJECT |
||||
|
public: |
||||
|
explicit CalibrationConfigurationDialog(QWidget* parent, const std::string& host, u16 port, |
||||
|
u8 pad_index, u16 client_id); |
||||
|
~CalibrationConfigurationDialog(); |
||||
|
|
||||
|
private: |
||||
|
Q_INVOKABLE void UpdateLabelText(QString text); |
||||
|
Q_INVOKABLE void UpdateButtonText(QString text); |
||||
|
|
||||
|
QVBoxLayout* layout; |
||||
|
QLabel* status_label; |
||||
|
QPushButton* cancel_button; |
||||
|
std::unique_ptr<InputCommon::CemuhookUDP::CalibrationConfigurationJob> job; |
||||
|
|
||||
|
// Configuration results |
||||
|
bool completed{}; |
||||
|
u16 min_x, min_y, max_x, max_y; |
||||
|
|
||||
|
friend class ConfigureMotionTouch; |
||||
|
}; |
||||
|
|
||||
|
class ConfigureMotionTouch : public QDialog { |
||||
|
Q_OBJECT |
||||
|
|
||||
|
public: |
||||
|
explicit ConfigureMotionTouch(QWidget* parent = nullptr); |
||||
|
~ConfigureMotionTouch() override; |
||||
|
|
||||
|
public slots: |
||||
|
void ApplyConfiguration(); |
||||
|
|
||||
|
private slots: |
||||
|
void OnCemuhookUDPTest(); |
||||
|
void OnConfigureTouchCalibration(); |
||||
|
void OnConfigureTouchFromButton(); |
||||
|
|
||||
|
private: |
||||
|
void closeEvent(QCloseEvent* event) override; |
||||
|
Q_INVOKABLE void ShowUDPTestResult(bool result); |
||||
|
void SetConfiguration(); |
||||
|
void UpdateUiDisplay(); |
||||
|
void ConnectEvents(); |
||||
|
bool CanCloseDialog(); |
||||
|
|
||||
|
std::unique_ptr<Ui::ConfigureMotionTouch> ui; |
||||
|
|
||||
|
// Coordinate system of the CemuhookUDP touch provider |
||||
|
int min_x, min_y, max_x, max_y; |
||||
|
|
||||
|
bool udp_test_in_progress{}; |
||||
|
|
||||
|
std::vector<Settings::TouchFromButtonMap> touch_from_button_maps; |
||||
|
}; |
||||
@ -0,0 +1,327 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<ui version="4.0"> |
||||
|
<class>ConfigureMotionTouch</class> |
||||
|
<widget class="QDialog" name="ConfigureMotionTouch"> |
||||
|
<property name="windowTitle"> |
||||
|
<string>Configure Motion / Touch</string> |
||||
|
</property> |
||||
|
<property name="geometry"> |
||||
|
<rect> |
||||
|
<x>0</x> |
||||
|
<y>0</y> |
||||
|
<width>500</width> |
||||
|
<height>450</height> |
||||
|
</rect> |
||||
|
</property> |
||||
|
<layout class="QVBoxLayout"> |
||||
|
<item> |
||||
|
<widget class="QGroupBox" name="motion_group_box"> |
||||
|
<property name="title"> |
||||
|
<string>Motion</string> |
||||
|
</property> |
||||
|
<layout class="QVBoxLayout"> |
||||
|
<item> |
||||
|
<layout class="QHBoxLayout"> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="motion_provider_label"> |
||||
|
<property name="text"> |
||||
|
<string>Motion Provider:</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QComboBox" name="motion_provider"/> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
<item> |
||||
|
<layout class="QHBoxLayout"> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="motion_sensitivity_label"> |
||||
|
<property name="text"> |
||||
|
<string>Sensitivity:</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QDoubleSpinBox" name="motion_sensitivity"> |
||||
|
<property name="alignment"> |
||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> |
||||
|
</property> |
||||
|
<property name="decimals"> |
||||
|
<number>4</number> |
||||
|
</property> |
||||
|
<property name="minimum"> |
||||
|
<double>0.010000000000000</double> |
||||
|
</property> |
||||
|
<property name="maximum"> |
||||
|
<double>10.000000000000000</double> |
||||
|
</property> |
||||
|
<property name="singleStep"> |
||||
|
<double>0.001000000000000</double> |
||||
|
</property> |
||||
|
<property name="value"> |
||||
|
<double>0.010000000000000</double> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QGroupBox" name="touch_group_box"> |
||||
|
<property name="title"> |
||||
|
<string>Touch</string> |
||||
|
</property> |
||||
|
<layout class="QVBoxLayout"> |
||||
|
<item> |
||||
|
<layout class="QHBoxLayout"> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="touch_provider_label"> |
||||
|
<property name="text"> |
||||
|
<string>Touch Provider:</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QComboBox" name="touch_provider"/> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
<item> |
||||
|
<layout class="QHBoxLayout"> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="touch_calibration_label"> |
||||
|
<property name="text"> |
||||
|
<string>Calibration:</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="touch_calibration"> |
||||
|
<property name="text"> |
||||
|
<string>(100, 50) - (1800, 850)</string> |
||||
|
</property> |
||||
|
<property name="alignment"> |
||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QPushButton" name="touch_calibration_config"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Fixed"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
<property name="text"> |
||||
|
<string>Configure</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
<item> |
||||
|
<layout class="QHBoxLayout"> |
||||
|
<item> |
||||
|
<widget class="QCheckBox" name="touch_from_button_checkbox"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Fixed"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
<property name="text"> |
||||
|
<string>Use button mapping:</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QComboBox" name="touch_from_button_map"/> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QPushButton" name="touch_from_button_config_btn"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Fixed"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
<property name="text"> |
||||
|
<string>Configure</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QGroupBox" name="udp_config_group_box"> |
||||
|
<property name="title"> |
||||
|
<string>CemuhookUDP Config</string> |
||||
|
</property> |
||||
|
<layout class="QVBoxLayout"> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="udp_help"> |
||||
|
<property name="text"> |
||||
|
<string>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</string> |
||||
|
</property> |
||||
|
<property name="alignment"> |
||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> |
||||
|
</property> |
||||
|
<property name="wordWrap"> |
||||
|
<bool>true</bool> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<layout class="QHBoxLayout"> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="udp_server_label"> |
||||
|
<property name="text"> |
||||
|
<string>Server:</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QLineEdit" name="udp_server"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Fixed"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
<item> |
||||
|
<layout class="QHBoxLayout"> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="udp_port_label"> |
||||
|
<property name="text"> |
||||
|
<string>Port:</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QLineEdit" name="udp_port"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Fixed"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
<item> |
||||
|
<layout class="QHBoxLayout"> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="udp_pad_index_label"> |
||||
|
<property name="text"> |
||||
|
<string>Pad:</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QComboBox" name="udp_pad_index"> |
||||
|
<item> |
||||
|
<property name="text"> |
||||
|
<string>Pad 1</string> |
||||
|
</property> |
||||
|
</item> |
||||
|
<item> |
||||
|
<property name="text"> |
||||
|
<string>Pad 2</string> |
||||
|
</property> |
||||
|
</item> |
||||
|
<item> |
||||
|
<property name="text"> |
||||
|
<string>Pad 3</string> |
||||
|
</property> |
||||
|
</item> |
||||
|
<item> |
||||
|
<property name="text"> |
||||
|
<string>Pad 4</string> |
||||
|
</property> |
||||
|
</item> |
||||
|
</widget> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
<item> |
||||
|
<layout class="QHBoxLayout"> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="udp_learn_more"> |
||||
|
<property name="text"> |
||||
|
<string>Learn More</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QPushButton" name="udp_test"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Fixed"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
<property name="text"> |
||||
|
<string>Test</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<spacer> |
||||
|
<property name="orientation"> |
||||
|
<enum>Qt::Vertical</enum> |
||||
|
</property> |
||||
|
<property name="sizeHint" stdset="0"> |
||||
|
<size> |
||||
|
<width>167</width> |
||||
|
<height>55</height> |
||||
|
</size> |
||||
|
</property> |
||||
|
</spacer> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QDialogButtonBox" name="buttonBox"> |
||||
|
<property name="standardButtons"> |
||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</widget> |
||||
|
<resources/> |
||||
|
<connections> |
||||
|
<connection> |
||||
|
<sender>buttonBox</sender> |
||||
|
<signal>accepted()</signal> |
||||
|
<receiver>ConfigureMotionTouch</receiver> |
||||
|
<slot>ApplyConfiguration()</slot> |
||||
|
<hints> |
||||
|
<hint type="sourcelabel"> |
||||
|
<x>220</x> |
||||
|
<y>380</y> |
||||
|
</hint> |
||||
|
<hint type="destinationlabel"> |
||||
|
<x>220</x> |
||||
|
<y>200</y> |
||||
|
</hint> |
||||
|
</hints> |
||||
|
</connection> |
||||
|
</connections> |
||||
|
</ui> |
||||
@ -0,0 +1,612 @@ |
|||||
|
// Copyright 2020 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <QInputDialog>
|
||||
|
#include <QKeyEvent>
|
||||
|
#include <QMessageBox>
|
||||
|
#include <QMouseEvent>
|
||||
|
#include <QResizeEvent>
|
||||
|
#include <QStandardItemModel>
|
||||
|
#include <QTimer>
|
||||
|
#include "common/param_package.h"
|
||||
|
#include "input_common/main.h"
|
||||
|
#include "ui_configure_touch_from_button.h"
|
||||
|
#include "yuzu/configuration/configure_touch_from_button.h"
|
||||
|
#include "yuzu/configuration/configure_touch_widget.h"
|
||||
|
|
||||
|
static QString GetKeyName(int key_code) { |
||||
|
switch (key_code) { |
||||
|
case Qt::Key_Shift: |
||||
|
return QObject::tr("Shift"); |
||||
|
case Qt::Key_Control: |
||||
|
return QObject::tr("Ctrl"); |
||||
|
case Qt::Key_Alt: |
||||
|
return QObject::tr("Alt"); |
||||
|
case Qt::Key_Meta: |
||||
|
return QString{}; |
||||
|
default: |
||||
|
return QKeySequence(key_code).toString(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static QString ButtonToText(const Common::ParamPackage& param) { |
||||
|
if (!param.Has("engine")) { |
||||
|
return QObject::tr("[not set]"); |
||||
|
} |
||||
|
|
||||
|
if (param.Get("engine", "") == "keyboard") { |
||||
|
return GetKeyName(param.Get("code", 0)); |
||||
|
} |
||||
|
|
||||
|
if (param.Get("engine", "") == "sdl") { |
||||
|
if (param.Has("hat")) { |
||||
|
const QString hat_str = QString::fromStdString(param.Get("hat", "")); |
||||
|
const QString direction_str = QString::fromStdString(param.Get("direction", "")); |
||||
|
|
||||
|
return QObject::tr("Hat %1 %2").arg(hat_str, direction_str); |
||||
|
} |
||||
|
|
||||
|
if (param.Has("axis")) { |
||||
|
const QString axis_str = QString::fromStdString(param.Get("axis", "")); |
||||
|
const QString direction_str = QString::fromStdString(param.Get("direction", "")); |
||||
|
|
||||
|
return QObject::tr("Axis %1%2").arg(axis_str, direction_str); |
||||
|
} |
||||
|
|
||||
|
if (param.Has("button")) { |
||||
|
const QString button_str = QString::fromStdString(param.Get("button", "")); |
||||
|
|
||||
|
return QObject::tr("Button %1").arg(button_str); |
||||
|
} |
||||
|
|
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
return QObject::tr("[unknown]"); |
||||
|
} |
||||
|
|
||||
|
ConfigureTouchFromButton::ConfigureTouchFromButton( |
||||
|
QWidget* parent, const std::vector<Settings::TouchFromButtonMap>& touch_maps, |
||||
|
const int default_index) |
||||
|
: QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchFromButton>()), touch_maps(touch_maps), |
||||
|
selected_index(default_index), timeout_timer(std::make_unique<QTimer>()), |
||||
|
poll_timer(std::make_unique<QTimer>()) { |
||||
|
|
||||
|
ui->setupUi(this); |
||||
|
binding_list_model = std::make_unique<QStandardItemModel>(0, 3, this); |
||||
|
binding_list_model->setHorizontalHeaderLabels({tr("Button"), tr("X"), tr("Y")}); |
||||
|
ui->binding_list->setModel(binding_list_model.get()); |
||||
|
ui->bottom_screen->SetCoordLabel(ui->coord_label); |
||||
|
|
||||
|
SetConfiguration(); |
||||
|
UpdateUiDisplay(); |
||||
|
ConnectEvents(); |
||||
|
} |
||||
|
|
||||
|
ConfigureTouchFromButton::~ConfigureTouchFromButton() = default; |
||||
|
|
||||
|
void ConfigureTouchFromButton::showEvent(QShowEvent* ev) { |
||||
|
QWidget::showEvent(ev); |
||||
|
|
||||
|
// width values are not valid in the constructor
|
||||
|
const int w = |
||||
|
ui->binding_list->viewport()->contentsRect().width() / binding_list_model->columnCount(); |
||||
|
if (w > 0) { |
||||
|
ui->binding_list->setColumnWidth(0, w); |
||||
|
ui->binding_list->setColumnWidth(1, w); |
||||
|
ui->binding_list->setColumnWidth(2, w); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::SetConfiguration() { |
||||
|
for (const auto& touch_map : touch_maps) { |
||||
|
ui->mapping->addItem(QString::fromStdString(touch_map.name)); |
||||
|
} |
||||
|
|
||||
|
ui->mapping->setCurrentIndex(selected_index); |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::UpdateUiDisplay() { |
||||
|
ui->button_delete->setEnabled(touch_maps.size() > 1); |
||||
|
ui->button_delete_bind->setEnabled(false); |
||||
|
|
||||
|
binding_list_model->removeRows(0, binding_list_model->rowCount()); |
||||
|
|
||||
|
for (const auto& button_str : touch_maps[selected_index].buttons) { |
||||
|
Common::ParamPackage package{button_str}; |
||||
|
QStandardItem* button = new QStandardItem(ButtonToText(package)); |
||||
|
button->setData(QString::fromStdString(button_str)); |
||||
|
button->setEditable(false); |
||||
|
QStandardItem* xcoord = new QStandardItem(QString::number(package.Get("x", 0))); |
||||
|
QStandardItem* ycoord = new QStandardItem(QString::number(package.Get("y", 0))); |
||||
|
binding_list_model->appendRow({button, xcoord, ycoord}); |
||||
|
|
||||
|
int dot = ui->bottom_screen->AddDot(package.Get("x", 0), package.Get("y", 0)); |
||||
|
button->setData(dot, DataRoleDot); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::ConnectEvents() { |
||||
|
connect(ui->mapping, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) { |
||||
|
SaveCurrentMapping(); |
||||
|
selected_index = index; |
||||
|
UpdateUiDisplay(); |
||||
|
}); |
||||
|
connect(ui->button_new, &QPushButton::clicked, this, &ConfigureTouchFromButton::NewMapping); |
||||
|
connect(ui->button_delete, &QPushButton::clicked, this, |
||||
|
&ConfigureTouchFromButton::DeleteMapping); |
||||
|
connect(ui->button_rename, &QPushButton::clicked, this, |
||||
|
&ConfigureTouchFromButton::RenameMapping); |
||||
|
connect(ui->button_delete_bind, &QPushButton::clicked, this, |
||||
|
&ConfigureTouchFromButton::DeleteBinding); |
||||
|
connect(ui->binding_list, &QTreeView::doubleClicked, this, |
||||
|
&ConfigureTouchFromButton::EditBinding); |
||||
|
connect(ui->binding_list->selectionModel(), &QItemSelectionModel::selectionChanged, this, |
||||
|
&ConfigureTouchFromButton::OnBindingSelection); |
||||
|
connect(binding_list_model.get(), &QStandardItemModel::itemChanged, this, |
||||
|
&ConfigureTouchFromButton::OnBindingChanged); |
||||
|
connect(ui->binding_list->model(), &QStandardItemModel::rowsAboutToBeRemoved, this, |
||||
|
&ConfigureTouchFromButton::OnBindingDeleted); |
||||
|
connect(ui->bottom_screen, &TouchScreenPreview::DotAdded, this, |
||||
|
&ConfigureTouchFromButton::NewBinding); |
||||
|
connect(ui->bottom_screen, &TouchScreenPreview::DotSelected, this, |
||||
|
&ConfigureTouchFromButton::SetActiveBinding); |
||||
|
connect(ui->bottom_screen, &TouchScreenPreview::DotMoved, this, |
||||
|
&ConfigureTouchFromButton::SetCoordinates); |
||||
|
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, |
||||
|
&ConfigureTouchFromButton::ApplyConfiguration); |
||||
|
|
||||
|
connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); }); |
||||
|
|
||||
|
connect(poll_timer.get(), &QTimer::timeout, [this]() { |
||||
|
Common::ParamPackage params; |
||||
|
for (auto& poller : device_pollers) { |
||||
|
params = poller->GetNextInput(); |
||||
|
if (params.Has("engine")) { |
||||
|
SetPollingResult(params, false); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::SaveCurrentMapping() { |
||||
|
auto& map = touch_maps[selected_index]; |
||||
|
map.buttons.clear(); |
||||
|
for (int i = 0, rc = binding_list_model->rowCount(); i < rc; ++i) { |
||||
|
const auto bind_str = binding_list_model->index(i, 0) |
||||
|
.data(Qt::ItemDataRole::UserRole + 1) |
||||
|
.toString() |
||||
|
.toStdString(); |
||||
|
if (bind_str.empty()) { |
||||
|
continue; |
||||
|
} |
||||
|
Common::ParamPackage params{bind_str}; |
||||
|
if (!params.Has("engine")) { |
||||
|
continue; |
||||
|
} |
||||
|
params.Set("x", binding_list_model->index(i, 1).data().toInt()); |
||||
|
params.Set("y", binding_list_model->index(i, 2).data().toInt()); |
||||
|
map.buttons.emplace_back(params.Serialize()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::NewMapping() { |
||||
|
const QString name = |
||||
|
QInputDialog::getText(this, tr("New Profile"), tr("Enter the name for the new profile.")); |
||||
|
if (name.isEmpty()) { |
||||
|
return; |
||||
|
} |
||||
|
touch_maps.emplace_back(Settings::TouchFromButtonMap{name.toStdString(), {}}); |
||||
|
ui->mapping->addItem(name); |
||||
|
ui->mapping->setCurrentIndex(ui->mapping->count() - 1); |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::DeleteMapping() { |
||||
|
const auto answer = QMessageBox::question( |
||||
|
this, tr("Delete Profile"), tr("Delete profile %1?").arg(ui->mapping->currentText())); |
||||
|
if (answer != QMessageBox::Yes) { |
||||
|
return; |
||||
|
} |
||||
|
const bool blocked = ui->mapping->blockSignals(true); |
||||
|
ui->mapping->removeItem(selected_index); |
||||
|
ui->mapping->blockSignals(blocked); |
||||
|
touch_maps.erase(touch_maps.begin() + selected_index); |
||||
|
selected_index = ui->mapping->currentIndex(); |
||||
|
UpdateUiDisplay(); |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::RenameMapping() { |
||||
|
const QString new_name = QInputDialog::getText(this, tr("Rename Profile"), tr("New name:")); |
||||
|
if (new_name.isEmpty()) { |
||||
|
return; |
||||
|
} |
||||
|
ui->mapping->setItemText(selected_index, new_name); |
||||
|
touch_maps[selected_index].name = new_name.toStdString(); |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is_new) { |
||||
|
binding_list_model->item(row_index, 0)->setText(tr("[press key]")); |
||||
|
|
||||
|
input_setter = [this, row_index, is_new](const Common::ParamPackage& params, |
||||
|
const bool cancel) { |
||||
|
auto cell = binding_list_model->item(row_index, 0); |
||||
|
if (cancel) { |
||||
|
if (is_new) { |
||||
|
binding_list_model->removeRow(row_index); |
||||
|
} else { |
||||
|
cell->setText( |
||||
|
ButtonToText(Common::ParamPackage{cell->data().toString().toStdString()})); |
||||
|
} |
||||
|
} else { |
||||
|
cell->setText(ButtonToText(params)); |
||||
|
cell->setData(QString::fromStdString(params.Serialize())); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
device_pollers = InputCommon::Polling::GetPollers(InputCommon::Polling::DeviceType::Button); |
||||
|
|
||||
|
for (auto& poller : device_pollers) { |
||||
|
poller->Start(); |
||||
|
} |
||||
|
|
||||
|
grabKeyboard(); |
||||
|
grabMouse(); |
||||
|
qApp->setOverrideCursor(QCursor(Qt::CursorShape::ArrowCursor)); |
||||
|
timeout_timer->start(5000); // Cancel after 5 seconds
|
||||
|
poll_timer->start(200); // Check for new inputs every 200ms
|
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::NewBinding(const QPoint& pos) { |
||||
|
QStandardItem* button = new QStandardItem(); |
||||
|
button->setEditable(false); |
||||
|
QStandardItem* xcoord = new QStandardItem(QString::number(pos.x())); |
||||
|
QStandardItem* ycoord = new QStandardItem(QString::number(pos.y())); |
||||
|
|
||||
|
const int dot_id = ui->bottom_screen->AddDot(pos.x(), pos.y()); |
||||
|
button->setData(dot_id, DataRoleDot); |
||||
|
|
||||
|
binding_list_model->appendRow({button, xcoord, ycoord}); |
||||
|
ui->binding_list->setFocus(); |
||||
|
ui->binding_list->setCurrentIndex(button->index()); |
||||
|
|
||||
|
GetButtonInput(binding_list_model->rowCount() - 1, true); |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::EditBinding(const QModelIndex& qi) { |
||||
|
if (qi.row() >= 0 && qi.column() == 0) { |
||||
|
GetButtonInput(qi.row(), false); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::DeleteBinding() { |
||||
|
const int row_index = ui->binding_list->currentIndex().row(); |
||||
|
if (row_index >= 0) { |
||||
|
ui->bottom_screen->RemoveDot( |
||||
|
binding_list_model->index(row_index, 0).data(DataRoleDot).toInt()); |
||||
|
binding_list_model->removeRow(row_index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::OnBindingSelection(const QItemSelection& selected, |
||||
|
const QItemSelection& deselected) { |
||||
|
ui->button_delete_bind->setEnabled(!selected.isEmpty()); |
||||
|
if (!selected.isEmpty()) { |
||||
|
const auto dot_data = selected.indexes().first().data(DataRoleDot); |
||||
|
if (dot_data.isValid()) { |
||||
|
ui->bottom_screen->HighlightDot(dot_data.toInt()); |
||||
|
} |
||||
|
} |
||||
|
if (!deselected.isEmpty()) { |
||||
|
const auto dot_data = deselected.indexes().first().data(DataRoleDot); |
||||
|
if (dot_data.isValid()) { |
||||
|
ui->bottom_screen->HighlightDot(dot_data.toInt(), false); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::OnBindingChanged(QStandardItem* item) { |
||||
|
if (item->column() == 0) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const bool blocked = binding_list_model->blockSignals(true); |
||||
|
item->setText(QString::number( |
||||
|
std::clamp(item->text().toInt(), 0, |
||||
|
static_cast<int>((item->column() == 1 ? Layout::ScreenUndocked::Width |
||||
|
: Layout::ScreenUndocked::Height) - |
||||
|
1)))); |
||||
|
binding_list_model->blockSignals(blocked); |
||||
|
|
||||
|
const auto dot_data = binding_list_model->index(item->row(), 0).data(DataRoleDot); |
||||
|
if (dot_data.isValid()) { |
||||
|
ui->bottom_screen->MoveDot(dot_data.toInt(), |
||||
|
binding_list_model->item(item->row(), 1)->text().toInt(), |
||||
|
binding_list_model->item(item->row(), 2)->text().toInt()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::OnBindingDeleted(const QModelIndex& parent, int first, int last) { |
||||
|
for (int i = first; i <= last; ++i) { |
||||
|
auto ix = binding_list_model->index(i, 0); |
||||
|
if (!ix.isValid()) { |
||||
|
return; |
||||
|
} |
||||
|
const auto dot_data = ix.data(DataRoleDot); |
||||
|
if (dot_data.isValid()) { |
||||
|
ui->bottom_screen->RemoveDot(dot_data.toInt()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::SetActiveBinding(const int dot_id) { |
||||
|
for (int i = 0; i < binding_list_model->rowCount(); ++i) { |
||||
|
if (binding_list_model->index(i, 0).data(DataRoleDot) == dot_id) { |
||||
|
ui->binding_list->setCurrentIndex(binding_list_model->index(i, 0)); |
||||
|
ui->binding_list->setFocus(); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::SetCoordinates(const int dot_id, const QPoint& pos) { |
||||
|
for (int i = 0; i < binding_list_model->rowCount(); ++i) { |
||||
|
if (binding_list_model->item(i, 0)->data(DataRoleDot) == dot_id) { |
||||
|
binding_list_model->item(i, 1)->setText(QString::number(pos.x())); |
||||
|
binding_list_model->item(i, 2)->setText(QString::number(pos.y())); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::SetPollingResult(const Common::ParamPackage& params, |
||||
|
const bool cancel) { |
||||
|
releaseKeyboard(); |
||||
|
releaseMouse(); |
||||
|
qApp->restoreOverrideCursor(); |
||||
|
timeout_timer->stop(); |
||||
|
poll_timer->stop(); |
||||
|
for (auto& poller : device_pollers) { |
||||
|
poller->Stop(); |
||||
|
} |
||||
|
if (input_setter) { |
||||
|
(*input_setter)(params, cancel); |
||||
|
input_setter.reset(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::keyPressEvent(QKeyEvent* event) { |
||||
|
if (!input_setter && event->key() == Qt::Key_Delete) { |
||||
|
DeleteBinding(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!input_setter) { |
||||
|
return QDialog::keyPressEvent(event); |
||||
|
} |
||||
|
|
||||
|
if (event->key() != Qt::Key_Escape) { |
||||
|
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, |
||||
|
false); |
||||
|
} else { |
||||
|
SetPollingResult({}, true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureTouchFromButton::ApplyConfiguration() { |
||||
|
SaveCurrentMapping(); |
||||
|
accept(); |
||||
|
} |
||||
|
|
||||
|
int ConfigureTouchFromButton::GetSelectedIndex() const { |
||||
|
return selected_index; |
||||
|
} |
||||
|
|
||||
|
std::vector<Settings::TouchFromButtonMap> ConfigureTouchFromButton::GetMaps() const { |
||||
|
return touch_maps; |
||||
|
} |
||||
|
|
||||
|
TouchScreenPreview::TouchScreenPreview(QWidget* parent) : QFrame(parent) { |
||||
|
setBackgroundRole(QPalette::ColorRole::Base); |
||||
|
} |
||||
|
|
||||
|
TouchScreenPreview::~TouchScreenPreview() = default; |
||||
|
|
||||
|
void TouchScreenPreview::SetCoordLabel(QLabel* const label) { |
||||
|
coord_label = label; |
||||
|
} |
||||
|
|
||||
|
int TouchScreenPreview::AddDot(const int device_x, const int device_y) { |
||||
|
QFont dot_font{QStringLiteral("monospace")}; |
||||
|
dot_font.setStyleHint(QFont::Monospace); |
||||
|
dot_font.setPointSize(20); |
||||
|
|
||||
|
QLabel* dot = new QLabel(this); |
||||
|
dot->setAttribute(Qt::WA_TranslucentBackground); |
||||
|
dot->setFont(dot_font); |
||||
|
dot->setText(QChar(0xD7)); // U+00D7 Multiplication Sign
|
||||
|
dot->setAlignment(Qt::AlignmentFlag::AlignCenter); |
||||
|
dot->setProperty(PropId, ++max_dot_id); |
||||
|
dot->setProperty(PropX, device_x); |
||||
|
dot->setProperty(PropY, device_y); |
||||
|
dot->setCursor(Qt::CursorShape::PointingHandCursor); |
||||
|
dot->setMouseTracking(true); |
||||
|
dot->installEventFilter(this); |
||||
|
dot->show(); |
||||
|
PositionDot(dot, device_x, device_y); |
||||
|
dots.emplace_back(max_dot_id, dot); |
||||
|
return max_dot_id; |
||||
|
} |
||||
|
|
||||
|
void TouchScreenPreview::RemoveDot(const int id) { |
||||
|
for (auto dot_it = dots.begin(); dot_it != dots.end(); ++dot_it) { |
||||
|
if (dot_it->first == id) { |
||||
|
dot_it->second->deleteLater(); |
||||
|
dots.erase(dot_it); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void TouchScreenPreview::HighlightDot(const int id, const bool active) const { |
||||
|
for (const auto& dot : dots) { |
||||
|
if (dot.first == id) { |
||||
|
// use color property from the stylesheet, or fall back to the default palette
|
||||
|
if (dot_highlight_color.isValid()) { |
||||
|
dot.second->setStyleSheet( |
||||
|
active ? QStringLiteral("color: %1").arg(dot_highlight_color.name()) |
||||
|
: QString{}); |
||||
|
} else { |
||||
|
dot.second->setForegroundRole(active ? QPalette::ColorRole::LinkVisited |
||||
|
: QPalette::ColorRole::NoRole); |
||||
|
} |
||||
|
if (active) { |
||||
|
dot.second->raise(); |
||||
|
} |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void TouchScreenPreview::MoveDot(const int id, const int device_x, const int device_y) const { |
||||
|
for (const auto& dot : dots) { |
||||
|
if (dot.first == id) { |
||||
|
dot.second->setProperty(PropX, device_x); |
||||
|
dot.second->setProperty(PropY, device_y); |
||||
|
PositionDot(dot.second, device_x, device_y); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void TouchScreenPreview::resizeEvent(QResizeEvent* event) { |
||||
|
if (ignore_resize) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const int target_width = std::min(width(), height() * 4 / 3); |
||||
|
const int target_height = std::min(height(), width() * 3 / 4); |
||||
|
if (target_width == width() && target_height == height()) { |
||||
|
return; |
||||
|
} |
||||
|
ignore_resize = true; |
||||
|
setGeometry((parentWidget()->contentsRect().width() - target_width) / 2, y(), target_width, |
||||
|
target_height); |
||||
|
ignore_resize = false; |
||||
|
|
||||
|
if (event->oldSize().width() != target_width || event->oldSize().height() != target_height) { |
||||
|
for (const auto& dot : dots) { |
||||
|
PositionDot(dot.second); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) { |
||||
|
if (!coord_label) { |
||||
|
return; |
||||
|
} |
||||
|
const auto pos = MapToDeviceCoords(event->x(), event->y()); |
||||
|
if (pos) { |
||||
|
coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y())); |
||||
|
} else { |
||||
|
coord_label->clear(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void TouchScreenPreview::leaveEvent(QEvent* event) { |
||||
|
if (coord_label) { |
||||
|
coord_label->clear(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void TouchScreenPreview::mousePressEvent(QMouseEvent* event) { |
||||
|
if (event->button() == Qt::MouseButton::LeftButton) { |
||||
|
const auto pos = MapToDeviceCoords(event->x(), event->y()); |
||||
|
if (pos) { |
||||
|
emit DotAdded(*pos); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) { |
||||
|
switch (event->type()) { |
||||
|
case QEvent::Type::MouseButtonPress: { |
||||
|
const auto mouse_event = static_cast<QMouseEvent*>(event); |
||||
|
if (mouse_event->button() != Qt::MouseButton::LeftButton) { |
||||
|
break; |
||||
|
} |
||||
|
emit DotSelected(obj->property(PropId).toInt()); |
||||
|
|
||||
|
drag_state.dot = qobject_cast<QLabel*>(obj); |
||||
|
drag_state.start_pos = mouse_event->globalPos(); |
||||
|
return true; |
||||
|
} |
||||
|
case QEvent::Type::MouseMove: { |
||||
|
if (!drag_state.dot) { |
||||
|
break; |
||||
|
} |
||||
|
const auto mouse_event = static_cast<QMouseEvent*>(event); |
||||
|
if (!drag_state.active) { |
||||
|
drag_state.active = |
||||
|
(mouse_event->globalPos() - drag_state.start_pos).manhattanLength() >= |
||||
|
QApplication::startDragDistance(); |
||||
|
if (!drag_state.active) { |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
auto current_pos = mapFromGlobal(mouse_event->globalPos()); |
||||
|
current_pos.setX(std::clamp(current_pos.x(), contentsMargins().left(), |
||||
|
contentsMargins().left() + contentsRect().width() - 1)); |
||||
|
current_pos.setY(std::clamp(current_pos.y(), contentsMargins().top(), |
||||
|
contentsMargins().top() + contentsRect().height() - 1)); |
||||
|
const auto device_coord = MapToDeviceCoords(current_pos.x(), current_pos.y()); |
||||
|
if (device_coord) { |
||||
|
drag_state.dot->setProperty(PropX, device_coord->x()); |
||||
|
drag_state.dot->setProperty(PropY, device_coord->y()); |
||||
|
PositionDot(drag_state.dot, device_coord->x(), device_coord->y()); |
||||
|
emit DotMoved(drag_state.dot->property(PropId).toInt(), *device_coord); |
||||
|
if (coord_label) { |
||||
|
coord_label->setText( |
||||
|
QStringLiteral("X: %1, Y: %2").arg(device_coord->x()).arg(device_coord->y())); |
||||
|
} |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
case QEvent::Type::MouseButtonRelease: { |
||||
|
drag_state.dot.clear(); |
||||
|
drag_state.active = false; |
||||
|
return true; |
||||
|
} |
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
return obj->eventFilter(obj, event); |
||||
|
} |
||||
|
|
||||
|
std::optional<QPoint> TouchScreenPreview::MapToDeviceCoords(const int screen_x, |
||||
|
const int screen_y) const { |
||||
|
const float t_x = 0.5f + static_cast<float>(screen_x - contentsMargins().left()) * |
||||
|
(Layout::ScreenUndocked::Width - 1) / (contentsRect().width() - 1); |
||||
|
const float t_y = 0.5f + static_cast<float>(screen_y - contentsMargins().top()) * |
||||
|
(Layout::ScreenUndocked::Height - 1) / |
||||
|
(contentsRect().height() - 1); |
||||
|
if (t_x >= 0.5f && t_x < Layout::ScreenUndocked::Width && t_y >= 0.5f && |
||||
|
t_y < Layout::ScreenUndocked::Height) { |
||||
|
|
||||
|
return QPoint{static_cast<int>(t_x), static_cast<int>(t_y)}; |
||||
|
} |
||||
|
return std::nullopt; |
||||
|
} |
||||
|
|
||||
|
void TouchScreenPreview::PositionDot(QLabel* const dot, const int device_x, |
||||
|
const int device_y) const { |
||||
|
dot->move(static_cast<int>( |
||||
|
static_cast<float>(device_x >= 0 ? device_x : dot->property(PropX).toInt()) * |
||||
|
(contentsRect().width() - 1) / (Layout::ScreenUndocked::Width - 1) + |
||||
|
contentsMargins().left() - static_cast<float>(dot->width()) / 2 + 0.5f), |
||||
|
static_cast<int>( |
||||
|
static_cast<float>(device_y >= 0 ? device_y : dot->property(PropY).toInt()) * |
||||
|
(contentsRect().height() - 1) / (Layout::ScreenUndocked::Height - 1) + |
||||
|
contentsMargins().top() - static_cast<float>(dot->height()) / 2 + 0.5f)); |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
// Copyright 2020 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <functional> |
||||
|
#include <memory> |
||||
|
#include <optional> |
||||
|
#include <vector> |
||||
|
#include <QDialog> |
||||
|
#include "core/frontend/framebuffer_layout.h" |
||||
|
#include "core/settings.h" |
||||
|
|
||||
|
class QItemSelection; |
||||
|
class QModelIndex; |
||||
|
class QStandardItemModel; |
||||
|
class QStandardItem; |
||||
|
class QTimer; |
||||
|
|
||||
|
namespace Common { |
||||
|
class ParamPackage; |
||||
|
} |
||||
|
|
||||
|
namespace InputCommon { |
||||
|
namespace Polling { |
||||
|
class DevicePoller; |
||||
|
} |
||||
|
} // namespace InputCommon |
||||
|
|
||||
|
namespace Ui { |
||||
|
class ConfigureTouchFromButton; |
||||
|
} |
||||
|
|
||||
|
class ConfigureTouchFromButton : public QDialog { |
||||
|
Q_OBJECT |
||||
|
|
||||
|
public: |
||||
|
explicit ConfigureTouchFromButton(QWidget* parent, |
||||
|
const std::vector<Settings::TouchFromButtonMap>& touch_maps, |
||||
|
int default_index = 0); |
||||
|
~ConfigureTouchFromButton() override; |
||||
|
|
||||
|
int GetSelectedIndex() const; |
||||
|
std::vector<Settings::TouchFromButtonMap> GetMaps() const; |
||||
|
|
||||
|
public slots: |
||||
|
void ApplyConfiguration(); |
||||
|
void NewBinding(const QPoint& pos); |
||||
|
void SetActiveBinding(int dot_id); |
||||
|
void SetCoordinates(int dot_id, const QPoint& pos); |
||||
|
|
||||
|
protected: |
||||
|
virtual void showEvent(QShowEvent* ev) override; |
||||
|
virtual void keyPressEvent(QKeyEvent* event) override; |
||||
|
|
||||
|
private slots: |
||||
|
void NewMapping(); |
||||
|
void DeleteMapping(); |
||||
|
void RenameMapping(); |
||||
|
void EditBinding(const QModelIndex& qi); |
||||
|
void DeleteBinding(); |
||||
|
void OnBindingSelection(const QItemSelection& selected, const QItemSelection& deselected); |
||||
|
void OnBindingChanged(QStandardItem* item); |
||||
|
void OnBindingDeleted(const QModelIndex& parent, int first, int last); |
||||
|
|
||||
|
private: |
||||
|
void SetConfiguration(); |
||||
|
void UpdateUiDisplay(); |
||||
|
void ConnectEvents(); |
||||
|
void GetButtonInput(int row_index, bool is_new); |
||||
|
void SetPollingResult(const Common::ParamPackage& params, bool cancel); |
||||
|
void SaveCurrentMapping(); |
||||
|
|
||||
|
std::unique_ptr<Ui::ConfigureTouchFromButton> ui; |
||||
|
std::unique_ptr<QStandardItemModel> binding_list_model; |
||||
|
std::vector<Settings::TouchFromButtonMap> touch_maps; |
||||
|
int selected_index; |
||||
|
|
||||
|
std::unique_ptr<QTimer> timeout_timer; |
||||
|
std::unique_ptr<QTimer> poll_timer; |
||||
|
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; |
||||
|
std::optional<std::function<void(const Common::ParamPackage&, bool)>> input_setter; |
||||
|
|
||||
|
static constexpr int DataRoleDot = Qt::ItemDataRole::UserRole + 2; |
||||
|
}; |
||||
@ -0,0 +1,231 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<ui version="4.0"> |
||||
|
<class>ConfigureTouchFromButton</class> |
||||
|
<widget class="QDialog" name="ConfigureTouchFromButton"> |
||||
|
<property name="geometry"> |
||||
|
<rect> |
||||
|
<x>0</x> |
||||
|
<y>0</y> |
||||
|
<width>500</width> |
||||
|
<height>500</height> |
||||
|
</rect> |
||||
|
</property> |
||||
|
<property name="windowTitle"> |
||||
|
<string>Configure Touchscreen Mappings</string> |
||||
|
</property> |
||||
|
<layout class="QVBoxLayout"> |
||||
|
<item> |
||||
|
<layout class="QHBoxLayout" name="horizontalLayout"> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="label"> |
||||
|
<property name="text"> |
||||
|
<string>Mapping:</string> |
||||
|
</property> |
||||
|
<property name="textFormat"> |
||||
|
<enum>Qt::PlainText</enum> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QComboBox" name="mapping"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QPushButton" name="button_new"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Fixed"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
<property name="text"> |
||||
|
<string>New</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QPushButton" name="button_delete"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Fixed"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
<property name="text"> |
||||
|
<string>Delete</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QPushButton" name="button_rename"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Fixed"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
<property name="text"> |
||||
|
<string>Rename</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="Line" name="line"> |
||||
|
<property name="orientation"> |
||||
|
<enum>Qt::Horizontal</enum> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2"> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="label_2"> |
||||
|
<property name="text"> |
||||
|
<string>Click the bottom area to add a point, then press a button to bind. |
||||
|
Drag points to change position, or double-click table cells to edit values.</string> |
||||
|
</property> |
||||
|
<property name="textFormat"> |
||||
|
<enum>Qt::PlainText</enum> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<spacer name="horizontalSpacer"> |
||||
|
<property name="orientation"> |
||||
|
<enum>Qt::Horizontal</enum> |
||||
|
</property> |
||||
|
<property name="sizeHint" stdset="0"> |
||||
|
<size> |
||||
|
<width>40</width> |
||||
|
<height>20</height> |
||||
|
</size> |
||||
|
</property> |
||||
|
</spacer> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QPushButton" name="button_delete_bind"> |
||||
|
<property name="text"> |
||||
|
<string>Delete Point</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QTreeView" name="binding_list"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
<property name="rootIsDecorated"> |
||||
|
<bool>false</bool> |
||||
|
</property> |
||||
|
<property name="uniformRowHeights"> |
||||
|
<bool>true</bool> |
||||
|
</property> |
||||
|
<property name="itemsExpandable"> |
||||
|
<bool>false</bool> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="TouchScreenPreview" name="bottom_screen"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
<property name="minimumSize"> |
||||
|
<size> |
||||
|
<width>160</width> |
||||
|
<height>120</height> |
||||
|
</size> |
||||
|
</property> |
||||
|
<property name="baseSize"> |
||||
|
<size> |
||||
|
<width>320</width> |
||||
|
<height>240</height> |
||||
|
</size> |
||||
|
</property> |
||||
|
<property name="cursor"> |
||||
|
<cursorShape>CrossCursor</cursorShape> |
||||
|
</property> |
||||
|
<property name="mouseTracking"> |
||||
|
<bool>true</bool> |
||||
|
</property> |
||||
|
<property name="autoFillBackground"> |
||||
|
<bool>true</bool> |
||||
|
</property> |
||||
|
<property name="frameShape"> |
||||
|
<enum>QFrame::StyledPanel</enum> |
||||
|
</property> |
||||
|
<property name="frameShadow"> |
||||
|
<enum>QFrame::Sunken</enum> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3"> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="coord_label"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
<property name="textFormat"> |
||||
|
<enum>Qt::PlainText</enum> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QDialogButtonBox" name="buttonBox"> |
||||
|
<property name="standardButtons"> |
||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</widget> |
||||
|
<customwidgets> |
||||
|
<customwidget> |
||||
|
<class>TouchScreenPreview</class> |
||||
|
<extends>QFrame</extends> |
||||
|
<header>citra_qt/configuration/configure_touch_widget.h</header> |
||||
|
<container>1</container> |
||||
|
</customwidget> |
||||
|
</customwidgets> |
||||
|
<resources/> |
||||
|
<connections> |
||||
|
<connection> |
||||
|
<sender>buttonBox</sender> |
||||
|
<signal>rejected()</signal> |
||||
|
<receiver>ConfigureTouchFromButton</receiver> |
||||
|
<slot>reject()</slot> |
||||
|
<hints> |
||||
|
<hint type="sourcelabel"> |
||||
|
<x>249</x> |
||||
|
<y>428</y> |
||||
|
</hint> |
||||
|
<hint type="destinationlabel"> |
||||
|
<x>249</x> |
||||
|
<y>224</y> |
||||
|
</hint> |
||||
|
</hints> |
||||
|
</connection> |
||||
|
</connections> |
||||
|
</ui> |
||||
@ -0,0 +1,61 @@ |
|||||
|
// Copyright 2020 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <optional> |
||||
|
#include <utility> |
||||
|
#include <vector> |
||||
|
#include <QFrame> |
||||
|
#include <QPointer> |
||||
|
|
||||
|
class QLabel; |
||||
|
|
||||
|
// Widget for representing touchscreen coordinates |
||||
|
class TouchScreenPreview : public QFrame { |
||||
|
Q_OBJECT |
||||
|
Q_PROPERTY(QColor dotHighlightColor MEMBER dot_highlight_color) |
||||
|
|
||||
|
public: |
||||
|
explicit TouchScreenPreview(QWidget* parent); |
||||
|
~TouchScreenPreview() override; |
||||
|
|
||||
|
void SetCoordLabel(QLabel*); |
||||
|
int AddDot(int device_x, int device_y); |
||||
|
void RemoveDot(int id); |
||||
|
void HighlightDot(int id, bool active = true) const; |
||||
|
void MoveDot(int id, int device_x, int device_y) const; |
||||
|
|
||||
|
signals: |
||||
|
void DotAdded(const QPoint& pos); |
||||
|
void DotSelected(int dot_id); |
||||
|
void DotMoved(int dot_id, const QPoint& pos); |
||||
|
|
||||
|
protected: |
||||
|
virtual void resizeEvent(QResizeEvent*) override; |
||||
|
virtual void mouseMoveEvent(QMouseEvent*) override; |
||||
|
virtual void leaveEvent(QEvent*) override; |
||||
|
virtual void mousePressEvent(QMouseEvent*) override; |
||||
|
virtual bool eventFilter(QObject*, QEvent*) override; |
||||
|
|
||||
|
private: |
||||
|
std::optional<QPoint> MapToDeviceCoords(int screen_x, int screen_y) const; |
||||
|
void PositionDot(QLabel* dot, int device_x = -1, int device_y = -1) const; |
||||
|
|
||||
|
bool ignore_resize = false; |
||||
|
QPointer<QLabel> coord_label; |
||||
|
|
||||
|
std::vector<std::pair<int, QLabel*>> dots; |
||||
|
int max_dot_id = 0; |
||||
|
QColor dot_highlight_color; |
||||
|
static constexpr char PropId[] = "dot_id"; |
||||
|
static constexpr char PropX[] = "device_x"; |
||||
|
static constexpr char PropY[] = "device_y"; |
||||
|
|
||||
|
struct { |
||||
|
bool active = false; |
||||
|
QPointer<QLabel> dot; |
||||
|
QPoint start_pos; |
||||
|
} drag_state; |
||||
|
}; |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue