From 82f267e6094c07e4a62a5771503785270e50f7d8 Mon Sep 17 00:00:00 2001 From: spycrab Date: Thu, 25 Apr 2019 15:07:03 +0200 Subject: [PATCH] Qt/Mapping: Visual GCPad configuration --- Source/Core/DolphinQt/CMakeLists.txt | 16 +- .../Config/Mapping/MappingButton.cpp | 5 + .../Config/Mapping/MappingCommon.cpp | 29 +- .../DolphinQt/Config/Mapping/MappingCommon.h | 7 +- .../Config/Mapping/MappingIndicator.cpp | 2 +- .../Config/Mapping/MappingWidget.cpp | 1 + .../DolphinQt/Config/Mapping/MappingWidget.h | 2 - .../Config/Mapping/MappingWindow.cpp | 6 + .../Config/Mapping/Visual/GCMappingWidget.cpp | 240 ++++++++++++ .../Config/Mapping/Visual/GCMappingWidget.h | 37 ++ Source/Core/DolphinQt/DolphinQt.vcxproj | 9 +- Source/Core/DolphinQt/Main.cpp | 3 + .../DolphinQt/Resources/Controller/Button.qml | 135 +++++++ .../Resources/Controller/Controller.qml | 45 +++ .../DolphinQt/Resources/Controller/Stick.qml | 103 +++++ .../Resources/Controller/Trigger.qml | 10 + Source/Core/DolphinQt/Resources/GCPad/A.svg | 81 ++++ Source/Core/DolphinQt/Resources/GCPad/B.svg | 81 ++++ .../DolphinQt/Resources/GCPad/DPad_Down.svg | 74 ++++ .../DolphinQt/Resources/GCPad/DPad_Left.svg | 254 +++++++++++++ .../DolphinQt/Resources/GCPad/DPad_Right.svg | 74 ++++ .../DolphinQt/Resources/GCPad/DPad_Up.svg | 74 ++++ .../Core/DolphinQt/Resources/GCPad/GCPad.qml | 169 +++++++++ .../DolphinQt/Resources/GCPad/GCPadView.qml | 16 + .../Core/DolphinQt/Resources/GCPad/Start.svg | 75 ++++ .../DolphinQt/Resources/GCPad/Stick_C.svg | 79 ++++ .../DolphinQt/Resources/GCPad/Stick_Main.svg | 97 +++++ .../DolphinQt/Resources/GCPad/Trigger_L.svg | 74 ++++ .../DolphinQt/Resources/GCPad/Trigger_R.svg | 74 ++++ Source/Core/DolphinQt/Resources/GCPad/X.svg | 80 ++++ Source/Core/DolphinQt/Resources/GCPad/Y.svg | 80 ++++ Source/Core/DolphinQt/Resources/GCPad/Z.svg | 75 ++++ .../Core/DolphinQt/Resources/GCPad/base.svg | 355 ++++++++++++++++++ Source/Core/DolphinQt/Resources/Resources.qrc | 27 ++ Source/VSProps/QtCompile.props | 30 +- 35 files changed, 2493 insertions(+), 26 deletions(-) create mode 100644 Source/Core/DolphinQt/Config/Mapping/Visual/GCMappingWidget.cpp create mode 100644 Source/Core/DolphinQt/Config/Mapping/Visual/GCMappingWidget.h create mode 100644 Source/Core/DolphinQt/Resources/Controller/Button.qml create mode 100644 Source/Core/DolphinQt/Resources/Controller/Controller.qml create mode 100644 Source/Core/DolphinQt/Resources/Controller/Stick.qml create mode 100644 Source/Core/DolphinQt/Resources/Controller/Trigger.qml create mode 100644 Source/Core/DolphinQt/Resources/GCPad/A.svg create mode 100644 Source/Core/DolphinQt/Resources/GCPad/B.svg create mode 100644 Source/Core/DolphinQt/Resources/GCPad/DPad_Down.svg create mode 100644 Source/Core/DolphinQt/Resources/GCPad/DPad_Left.svg create mode 100644 Source/Core/DolphinQt/Resources/GCPad/DPad_Right.svg create mode 100644 Source/Core/DolphinQt/Resources/GCPad/DPad_Up.svg create mode 100644 Source/Core/DolphinQt/Resources/GCPad/GCPad.qml create mode 100644 Source/Core/DolphinQt/Resources/GCPad/GCPadView.qml create mode 100644 Source/Core/DolphinQt/Resources/GCPad/Start.svg create mode 100644 Source/Core/DolphinQt/Resources/GCPad/Stick_C.svg create mode 100644 Source/Core/DolphinQt/Resources/GCPad/Stick_Main.svg create mode 100644 Source/Core/DolphinQt/Resources/GCPad/Trigger_L.svg create mode 100644 Source/Core/DolphinQt/Resources/GCPad/Trigger_R.svg create mode 100644 Source/Core/DolphinQt/Resources/GCPad/X.svg create mode 100644 Source/Core/DolphinQt/Resources/GCPad/Y.svg create mode 100644 Source/Core/DolphinQt/Resources/GCPad/Z.svg create mode 100644 Source/Core/DolphinQt/Resources/GCPad/base.svg create mode 100644 Source/Core/DolphinQt/Resources/Resources.qrc diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index f0951cb3b5d0..2c6caa91aa72 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -6,13 +6,20 @@ if (NOT Qt5_DIR AND MSVC) endif() endif() -find_package(Qt5 5.9 REQUIRED COMPONENTS Gui Widgets) +find_package(Qt5 5.9 REQUIRED COMPONENTS Gui Widgets Quick QuickWidgets) +find_package(Qt5 CONFIG QUIET OPTIONAL_COMPONENTS QuickCompiler) set_property(TARGET Qt5::Core PROPERTY INTERFACE_COMPILE_FEATURES "") message(STATUS "Found Qt version ${Qt5Core_VERSION}") set(CMAKE_AUTOMOC ON) +if (NOT Qt5QuickCompiler_DIR STREQUAL "Qt5QuickCompiler_DIR-NOTFOUND") + message(STATUS "Found Qt Quick Compiler") + qtquick_compiler_add_resources(RCC_SOURCES Resources/Resources.qrc) +endif() +set_property(SOURCE ${RCC_SOURCES} PROPERTY SKIP_AUTOMOC ON) + add_executable(dolphin-emu AboutDialog.cpp AboutDialog.h @@ -142,6 +149,7 @@ add_executable(dolphin-emu Config/Mapping/MappingWidget.h Config/Mapping/MappingWindow.cpp Config/Mapping/MappingWindow.h + Config/Mapping/Visual/GCMappingWidget.cpp Config/Mapping/WiimoteEmuExtension.cpp Config/Mapping/WiimoteEmuExtension.h Config/Mapping/WiimoteEmuExtensionMotionInput.cpp @@ -266,6 +274,7 @@ add_executable(dolphin-emu TAS/IRWidget.h Updater.cpp Updater.h + ${RCC_SOURCES} ) if (NOT WIN32) @@ -286,12 +295,16 @@ target_include_directories(dolphin-emu PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${Qt5Gui_PRIVATE_INCLUDE_DIRS} + ${Qt5Quick_PRIVATE_INCLUDE_DIRS} + ${Qt5QuickWidgets_PRIVATE_INCLUDE_DIRS} ) target_link_libraries(dolphin-emu PRIVATE core Qt5::Widgets + Qt5::Quick + Qt5::QuickWidgets uicommon imgui ) @@ -348,6 +361,7 @@ if(WIN32) COMMAND "${CMAKE_COMMAND}" -E env PATH="${QT_BINARY_DIRECTORY}" "${WINDEPLOYQT_EXE}" --libdir="${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" --plugindir="${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/QtPlugins" + --qmldir="${CMAKE_CURRENT_SOURCE_DIR}/Resources" $,--debug,--release> --no-translations --no-compiler-runtime diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp index f6f68d15ff38..d7955af6c2e2 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp @@ -93,6 +93,9 @@ void MappingButton::Clicked() QString expression; + const auto old_text = text(); + setText(QStringLiteral("...")); + if (m_parent->GetParent()->IsMappingAllDevices()) { expression = MappingCommon::DetectExpression(this, g_controller_interface, @@ -106,6 +109,8 @@ void MappingButton::Clicked() default_device_qualifier); } + setText(old_text); + if (expression.isEmpty()) return; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp index bbd15ad83196..c159c40ee279 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp @@ -13,6 +13,7 @@ #include #include "DolphinQt/QtUtils/BlockUserInputFilter.h" +#include "DolphinQt/QtUtils/QueueOnObject.h" #include "InputCommon/ControlReference/ControlReference.h" #include "InputCommon/ControllerInterface/Device.h" @@ -49,18 +50,16 @@ QString GetExpressionForControl(const QString& control_name, return expr; } -QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& device_container, +QString DetectExpression(QWidget* widget, ciface::Core::DeviceContainer& device_container, const std::vector& device_strings, const ciface::Core::DeviceQualifier& default_device, Quote quote) { - const auto filter = new BlockUserInputFilter(button); - - button->installEventFilter(filter); - button->grabKeyboard(); - button->grabMouse(); - - const auto old_text = button->text(); - button->setText(QStringLiteral("...")); + const auto filter = new BlockUserInputFilter(widget); + QueueOnObject(widget, [widget, filter] { + widget->installEventFilter(filter); + widget->grabKeyboard(); + widget->grabMouse(); + }); // The button text won't be updated if we don't process events here QApplication::processEvents(); @@ -70,19 +69,17 @@ QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& dev const auto [device, input] = device_container.DetectInput(INPUT_DETECT_TIME, device_strings); - const auto timer = new QTimer(button); + const auto timer = new QTimer(widget); - button->connect(timer, &QTimer::timeout, [button, filter] { - button->releaseMouse(); - button->releaseKeyboard(); - button->removeEventFilter(filter); + widget->connect(timer, &QTimer::timeout, [widget, filter] { + widget->releaseMouse(); + widget->releaseKeyboard(); + widget->removeEventFilter(filter); }); // Prevent mappings of "space", "return", or mouse clicks from re-activating detection. timer->start(500); - button->setText(old_text); - if (!input) return {}; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.h b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.h index f3372bdb27d8..16d8cda47761 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.h @@ -7,9 +7,12 @@ #include #include -class QString; class OutputReference; class QPushButton; +class QString; +class QWidget; + +constexpr int INDICATOR_UPDATE_FREQ = 30; namespace ciface::Core { @@ -30,7 +33,7 @@ QString GetExpressionForControl(const QString& control_name, const ciface::Core::DeviceQualifier& default_device, Quote quote = Quote::On); -QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& device_container, +QString DetectExpression(QWidget* widget, ciface::Core::DeviceContainer& device_container, const std::vector& device_strings, const ciface::Core::DeviceQualifier& default_device, Quote quote = Quote::On); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp index f29555c2efc7..21616319b465 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp @@ -25,7 +25,7 @@ #include "InputCommon/ControllerEmu/Setting/NumericSetting.h" #include "InputCommon/ControllerInterface/Device.h" -#include "DolphinQt/Config/Mapping/MappingWidget.h" +#include "DolphinQt/Config/Mapping/MappingCommon.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" namespace diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp index 09d136d0f9f0..fb4459822a54 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp @@ -12,6 +12,7 @@ #include "DolphinQt/Config/Mapping/IOWindow.h" #include "DolphinQt/Config/Mapping/MappingButton.h" +#include "DolphinQt/Config/Mapping/MappingCommon.h" #include "DolphinQt/Config/Mapping/MappingIndicator.h" #include "DolphinQt/Config/Mapping/MappingNumeric.h" #include "DolphinQt/Config/Mapping/MappingWindow.h" diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h index 851d2c12ce17..ab2d4c254892 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h @@ -28,8 +28,6 @@ class EmulatedController; class NumericSettingBase; } // namespace ControllerEmu -constexpr int INDICATOR_UPDATE_FREQ = 30; - class MappingWidget : public QWidget { Q_OBJECT diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 0052278b908b..6c7055f3e292 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -33,6 +33,7 @@ #include "DolphinQt/Config/Mapping/HotkeyStatesOther.h" #include "DolphinQt/Config/Mapping/HotkeyTAS.h" #include "DolphinQt/Config/Mapping/HotkeyWii.h" +#include "DolphinQt/Config/Mapping/Visual/GCMappingWidget.h" #include "DolphinQt/Config/Mapping/WiimoteEmuExtension.h" #include "DolphinQt/Config/Mapping/WiimoteEmuExtensionMotionInput.h" #include "DolphinQt/Config/Mapping/WiimoteEmuExtensionMotionSimulation.h" @@ -49,6 +50,7 @@ #include "InputCommon/InputConfig.h" constexpr const char* PROFILES_DIR = "Profiles/"; +constexpr int INDICATOR_UPDATE_FREQ = 30; MappingWindow::MappingWindow(QWidget* parent, Type type, int port_num) : QDialog(parent), m_port(port_num) @@ -346,10 +348,14 @@ void MappingWindow::SetMappingType(MappingWindow::Type type) case Type::MAPPING_GC_STEERINGWHEEL: case Type::MAPPING_GC_DANCEMAT: case Type::MAPPING_GCPAD: + { widget = new GCPadEmu(this); + auto* visual = new VisualGCMappingWidget(this, GetPort()); setWindowTitle(tr("GameCube Controller at Port %1").arg(GetPort() + 1)); AddWidget(tr("GameCube Controller"), widget); + AddWidget(tr("Visual Configuration"), visual); break; + } case Type::MAPPING_GC_MICROPHONE: widget = new GCMicrophone(this); setWindowTitle(tr("GameCube Microphone Slot %1") diff --git a/Source/Core/DolphinQt/Config/Mapping/Visual/GCMappingWidget.cpp b/Source/Core/DolphinQt/Config/Mapping/Visual/GCMappingWidget.cpp new file mode 100644 index 000000000000..66d9b64656fb --- /dev/null +++ b/Source/Core/DolphinQt/Config/Mapping/Visual/GCMappingWidget.cpp @@ -0,0 +1,240 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Config/Mapping/Visual/GCMappingWidget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "Common/FileUtil.h" + +#include "Core/HW/GCPad.h" +#include "Core/HW/GCPadEmu.h" + +#include "DolphinQt/Config/Mapping/MappingCommon.h" +#include "DolphinQt/Config/Mapping/MappingWindow.h" +#include "DolphinQt/Settings.h" + +#include "InputCommon/ControllerEmu/Control/Control.h" +#include "InputCommon/ControllerEmu/ControlGroup/Buttons.h" +#include "InputCommon/ControllerEmu/ControllerEmu.h" +#include "InputCommon/ControllerEmu/StickGate.h" +#include "InputCommon/ControllerInterface/ControllerInterface.h" +#include "InputCommon/InputConfig.h" + +VisualGCMappingWidget::VisualGCMappingWidget(MappingWindow* parent, int port) + : QWidget(parent), m_parent(parent), m_port(port) +{ + CreateWidgets(); + + m_timer = new QTimer(this); + connect(m_timer, &QTimer::timeout, this, &VisualGCMappingWidget::RefreshControls); + m_timer->start(1000 / INDICATOR_UPDATE_FREQ); +} + +void VisualGCMappingWidget::CreateWidgets() +{ + auto* layout = new QVBoxLayout; + + m_widget = new QQuickWidget; + m_widget->setSource(QUrl(QStringLiteral("qrc:/GCPad/GCPadView.qml"))); + m_widget->setResizeMode(QQuickWidget::SizeRootObjectToView); + m_widget->setClearColor(Qt::transparent); + m_widget->setAttribute(Qt::WA_TranslucentBackground); + m_widget->setAttribute(Qt::WA_AlwaysStackOnTop); + + layout->addWidget(m_widget); + + // We need to use the old signal / slot syntax here in order to access the QML signal + connect(GetQMLController(), SIGNAL(detectInput(QString, QString)), this, + SLOT(DetectInput(QString, QString)), Qt::QueuedConnection); + + setLayout(layout); +} + +void VisualGCMappingWidget::DetectInput(QString group, QString name) +{ + auto* buttons = Pad::GetGroup(m_port, PadGroup::Buttons); + + ControllerEmu::Control* map_control = nullptr; + + if (group == QStringLiteral("Button")) + { + for (auto& control : buttons->controls) + { + if (control->name == name.toStdString()) + { + map_control = control.get(); + break; + } + } + } + else if (group == QStringLiteral("DPad")) + { + auto* dpad = Pad::GetGroup(m_port, PadGroup::DPad); + for (auto& control : dpad->controls) + { + if (control->name == name.toStdString()) + { + map_control = control.get(); + break; + } + } + } + else if (group == QStringLiteral("Trigger")) + { + auto* triggers = Pad::GetGroup(m_port, PadGroup::Triggers); + for (auto& control : triggers->controls) + { + if (control->name == name.toStdString()) + { + map_control = control.get(); + break; + } + } + } + else if (group == QStringLiteral("Stick_Main")) + { + auto* main_stick = Pad::GetGroup(m_port, PadGroup::MainStick); + for (auto& control : main_stick->controls) + { + if (control->name == name.toStdString()) + { + map_control = control.get(); + break; + } + } + } + else if (group == QStringLiteral("Stick_C")) + { + auto* c_stick = Pad::GetGroup(m_port, PadGroup::CStick); + for (auto& control : c_stick->controls) + { + if (control->name == name.toStdString()) + { + map_control = control.get(); + break; + } + } + } + + if (map_control == nullptr) + return; + + const auto default_device_qualifier = m_parent->GetController()->GetDefaultDevice(); + + QString expression; + + auto device_strings = m_parent->IsMappingAllDevices() ? + g_controller_interface.GetAllDeviceStrings() : + std::vector{default_device_qualifier.ToString()}; + + expression = MappingCommon::DetectExpression(this, g_controller_interface, device_strings, + default_device_qualifier); + + QMetaObject::invokeMethod(GetQMLController(), "detectedInput", Qt::QueuedConnection, + Q_ARG(QString, group), Q_ARG(QString, name), + Q_ARG(QString, expression)); + + if (expression.isEmpty()) + return; + + map_control->control_ref->SetExpression(expression.toStdString()); + m_parent->GetController()->UpdateReferences(g_controller_interface); + + Pad::GetConfig()->SaveConfig(); +} + +QObject* VisualGCMappingWidget::GetQMLController() +{ + return m_widget->rootObject()->findChild(QStringLiteral("gcpad")); +} + +void VisualGCMappingWidget::RefreshControls() +{ + if (!isActiveWindow()) + return; + + auto* buttons = Pad::GetGroup(m_port, PadGroup::Buttons); + + QObject* object = GetQMLController(); + + const auto lock = m_parent->GetController()->GetStateLock(); + + auto update_control = [this](std::string group, std::string name, std::string expression, + double value) { + QMetaObject::invokeMethod( + GetQMLController(), "updateControl", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(group)), Q_ARG(QString, QString::fromStdString(name)), + Q_ARG(QString, QString::fromStdString(expression)), Q_ARG(double, value)); + }; + + for (auto& control : buttons->controls) + { + const bool activated = control->GetState(); + const std::string expression = control->control_ref->GetExpression(); + + update_control("Button", control->name, expression, activated); + } + + auto* dpad = Pad::GetGroup(m_port, PadGroup::DPad); + + for (auto& control : dpad->controls) + { + const bool activated = control->GetState(); + const std::string expression = control->control_ref->GetExpression(); + + update_control("DPad", control->name, expression, activated); + } + + auto* triggers = Pad::GetGroup(m_port, PadGroup::Triggers); + + for (size_t i = 0; i < 2; i++) + { + auto& control = triggers->controls[i]; + + const auto name = control->name; + const std::string expression = control->control_ref->GetExpression(); + const double value = std::max(std::min(control->GetState(), 1.0), 0.0); + + update_control("Trigger", control->name, expression, value); + } + + auto* main_stick = + static_cast(Pad::GetGroup(m_port, PadGroup::MainStick)); + + auto main_state = main_stick->GetReshapableState(true); + + QMetaObject::invokeMethod(object, "updateStick", Q_ARG(QString, QString::fromStdString("Main")), + Q_ARG(double, main_state.x), Q_ARG(double, main_state.y)); + + auto* main_stick_group = Pad::GetGroup(m_port, PadGroup::MainStick); + + for (auto& control : main_stick_group->controls) + { + const std::string expression = control->control_ref->GetExpression(); + update_control("Stick_Main", control->name, expression, 0); + } + + auto* c_stick = + static_cast(Pad::GetGroup(m_port, PadGroup::CStick)); + + auto c_state = c_stick->GetReshapableState(true); + + QMetaObject::invokeMethod(object, "updateStick", Q_ARG(QString, QString::fromStdString("C")), + Q_ARG(double, c_state.x), Q_ARG(double, c_state.y)); + + auto* c_stick_group = Pad::GetGroup(m_port, PadGroup::CStick); + + for (auto& control : c_stick_group->controls) + { + const std::string expression = control->control_ref->GetExpression(); + update_control("Stick_C", control->name, expression, 0); + } +} diff --git a/Source/Core/DolphinQt/Config/Mapping/Visual/GCMappingWidget.h b/Source/Core/DolphinQt/Config/Mapping/Visual/GCMappingWidget.h new file mode 100644 index 000000000000..e5147ae2d791 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Mapping/Visual/GCMappingWidget.h @@ -0,0 +1,37 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +class QTimer; +class QQuickWidget; + +class MappingWindow; + +class VisualGCMappingWidget : public QWidget +{ + Q_OBJECT +public: + explicit VisualGCMappingWidget(MappingWindow* parent, int port); + +private slots: + void DetectInput(QString group, QString name); + +private: + void CreateWidgets(); + void ConnectWidgets(); + void RefreshControls(); + + QObject* GetQMLController(); + + QTimer* m_timer; + + QQuickWidget* m_widget; + + MappingWindow* m_parent; + int m_port; +}; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index de228eba5f86..9946d5f76ef3 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -60,7 +60,7 @@ $(ExternalsDir)ffmpeg\lib;%(AdditionalLibraryDirectories) - $(ProjectDir)VideoInterface;$(ProjectDir)GameList;$(ProjectDir)Debugger;$(ProjectDir)Settings;$(ProjectDir)Config;$(ProjectDir)Config\Mapping;$(ProjectDir)Config\Graphics;$(ProjectDir)Config\ControllerInterface;$(ProjectDir)NetPlay;$(ProjectDir)QtUtils;$(ProjectDir)TAS;$(ProjectDir)FIFO;%(AdditionalIncludeDirectories) + $(ProjectDir)VideoInterface;$(ProjectDir)GameList;$(ProjectDir)Debugger;$(ProjectDir)Settings;$(ProjectDir)Config;$(ProjectDir)Config\Mapping;$(ProjectDir)Config\Mapping\Visual;$(ProjectDir)Config\Graphics;$(ProjectDir)Config\ControllerInterface;$(ProjectDir)NetPlay;$(ProjectDir)QtUtils;$(ProjectDir)TAS;$(ProjectDir)FIFO;%(AdditionalIncludeDirectories) DolphinQt.manifest;%(AdditionalManifestFiles) @@ -102,6 +102,7 @@ + @@ -191,8 +192,12 @@ + + + + @@ -221,6 +226,7 @@ + @@ -357,6 +363,7 @@ + diff --git a/Source/Core/DolphinQt/Main.cpp b/Source/Core/DolphinQt/Main.cpp index afb79aaf9758..0889e1059204 100644 --- a/Source/Core/DolphinQt/Main.cpp +++ b/Source/Core/DolphinQt/Main.cpp @@ -4,6 +4,7 @@ #ifdef _WIN32 #include +#include #include #endif @@ -90,6 +91,8 @@ int main(int argc, char* argv[]) freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); } + + Q_INIT_RESOURCE(Resources); #endif QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); diff --git a/Source/Core/DolphinQt/Resources/Controller/Button.qml b/Source/Core/DolphinQt/Resources/Controller/Button.qml new file mode 100644 index 000000000000..a4bceff4ff89 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/Controller/Button.qml @@ -0,0 +1,135 @@ +import QtQuick 2.0 +import QtGraphicalEffects 1.13 + +Item { + id: button + + property string group : "Button" + property string name + property bool analog : false + + property Controller controller : parent; + + property string image + property bool showImage: true + + property string orientation: "top" + property int margin: 5 + + property int labelWidth: 0 + + property bool detecting_input : false + + // Input + signal detectedInput(string expression); + signal update(string expression, variant value); + + function setPressed(pressed) + { + buttonExpression.font.bold = pressed; + colorOverlay.color = pressed ? Qt.rgba(0, 1, 0, 0.5) : "transparent"; + } + + function detect() + { + button.detecting_input = true; + buttonExpression.text = "..."; + + // We need to call the signal this way, otherwise the text update will not occur + detectInputTimer.start(); + } + + function setExpression(expression) + { + if (button.detecting_input) + return; + + buttonExpression.text = expression === "" ? "" : expression; + } + + Component.onCompleted: { + controller.registerControl(button.group, button.name, button); + } + + onDetectedInput: { + button.detecting_input = false; + setExpression(expression); + } + + onUpdate: { + setExpression(expression); + setPressed(value === 1.0); + + if (button.analog) + progressBar.progress = value; + } + + Timer { + id: detectInputTimer + interval: 100 + repeat: false + onTriggered: controller.detectInput(button.group, button.name) + } + + Image { + id: buttonImage + source: button.image + anchors.horizontalCenter: parent.horizontalCenter + visible: button.showImage + + MouseArea + { + anchors.fill: parent + onClicked: button.detect() + } + } + + ColorOverlay { + id: colorOverlay + visible: button.showImage + anchors.fill: buttonImage + source: buttonImage + color: "transparent" + } + + Rectangle { + id: buttonRectangle + + Rectangle { + property double progress : 0 + + id: progressBar + color: "lime" + height: parent.height + width: parent.width * progress + } + + Text { + id: buttonExpression + text: "" + anchors.centerIn: parent + color: "white" + } + + width: labelWidth == 0 ? buttonExpression.width + 5 : labelWidth + height: buttonExpression.height + 5 + + color: "black" + radius: 3 + + anchors.horizontalCenter: button.orientation == "bottom" || button.orientation == "top" ? buttonImage.horizontalCenter : undefined + anchors.verticalCenter: button.orientation == "left" || button.orientation == "right" ? buttonImage.verticalCenter : undefined + anchors.top: button.orientation == "bottom" ? buttonImage.bottom : undefined + anchors.bottom: button.orientation == "top" ? buttonImage.top : undefined + anchors.left: button.orientation == "right" ? buttonImage.right : undefined + anchors.right: button.orientation == "left" ? buttonImage.left : undefined + + anchors.margins: button.margin + + MouseArea + { + anchors.fill: parent + onClicked: button.detect() + } + } +} diff --git a/Source/Core/DolphinQt/Resources/Controller/Controller.qml b/Source/Core/DolphinQt/Resources/Controller/Controller.qml new file mode 100644 index 000000000000..150f37d0051b --- /dev/null +++ b/Source/Core/DolphinQt/Resources/Controller/Controller.qml @@ -0,0 +1,45 @@ +import QtQuick 2.0 + +Item { + id: controller + + // Output + signal detectInput(string group, string name) + + // Input + signal registerControl(string group, string name, variant control) + signal registerStick(string name, variant control) + + signal detectedInput(string group, string name, string value) + signal updateControl(string group, string name, string expression, double value) + signal updateStick(string name, double x, double y) + + property variant controls : ({}) + property variant sticks : ({}) + + onDetectedInput: { + controller.controls[group][name].detectedInput(value); + } + + onUpdateControl: { + if (!controller.controls[group].hasOwnProperty(name)) + return; + + controller.controls[group][name].update(expression, value); + } + + onUpdateStick: { + controller.sticks[name].update(x, y); + } + + onRegisterStick: function(name, control) { + controller.sticks[name] = control; + } + + onRegisterControl: function(group, name, control) { + if (!controller.controls.hasOwnProperty(group)) + controller.controls[group] = {}; + + controller.controls[group][name] = control; + } +} diff --git a/Source/Core/DolphinQt/Resources/Controller/Stick.qml b/Source/Core/DolphinQt/Resources/Controller/Stick.qml new file mode 100644 index 000000000000..f559e438e417 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/Controller/Stick.qml @@ -0,0 +1,103 @@ +import QtQuick 2.0 + +Item { + id: stick + + property string name + + property Controller controller : parent + + property string image + property int radius: stickImage.width / 2 + + property int radiusMargin: radius * 0.75 + + signal update(double x, double y); + + function setPosition(x, y) + { + stickImage.x = -stickImage.width/2 + x * radius; + stickImage.y = -y * radius; + } + + Image + { + id: stickImage + source: stick.image + x: -stickImage.width/2 + } + + onUpdate: { + setPosition(x, y); + } + + Component.onCompleted: { + controller.registerStick(stick.name, stick) + } + + Button + { + id: buttonUp + + name: "Up" + group: "Stick_" + stick.name + + controller: stick.controller + image: stick.image + showImage: false + orientation: "top" + margin: radiusMargin + x: 0 + y: 0 + } + + Button + { + id: buttonDown + + name: "Down" + group: "Stick_" + stick.name + + controller: stick.controller + image: stick.image + showImage: false + orientation: "bottom" + margin: radiusMargin + x: 0 + y: 0 + } + + Button + { + id: buttonLeft + + controller: stick.controller + + name: "Left" + group: "Stick_" + stick.name + + image: stick.image + showImage: false + orientation: "left" + margin: radiusMargin + x: 0 + y: 0 + } + + Button + { + id: buttonRight + + controller: stick.controller + + name: "Right" + group: "Stick_" + stick.name + + image: stick.image + showImage: false + orientation: "right" + margin: radiusMargin + x: 0 + y: 0 + } +} diff --git a/Source/Core/DolphinQt/Resources/Controller/Trigger.qml b/Source/Core/DolphinQt/Resources/Controller/Trigger.qml new file mode 100644 index 000000000000..6b07c0d19601 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/Controller/Trigger.qml @@ -0,0 +1,10 @@ +import QtQuick 2.0 + +Button { + orientation: "top" + group: "Trigger" + + labelWidth: 125 + + analog: true +} diff --git a/Source/Core/DolphinQt/Resources/GCPad/A.svg b/Source/Core/DolphinQt/Resources/GCPad/A.svg new file mode 100644 index 000000000000..b49888e488c5 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/A.svg @@ -0,0 +1,81 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/GCPad/B.svg b/Source/Core/DolphinQt/Resources/GCPad/B.svg new file mode 100644 index 000000000000..4b7e17df86d3 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/B.svg @@ -0,0 +1,81 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/GCPad/DPad_Down.svg b/Source/Core/DolphinQt/Resources/GCPad/DPad_Down.svg new file mode 100644 index 000000000000..24521d62fbf5 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/DPad_Down.svg @@ -0,0 +1,74 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/GCPad/DPad_Left.svg b/Source/Core/DolphinQt/Resources/GCPad/DPad_Left.svg new file mode 100644 index 000000000000..73819a0b1bcb --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/DPad_Left.svg @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/GCPad/DPad_Right.svg b/Source/Core/DolphinQt/Resources/GCPad/DPad_Right.svg new file mode 100644 index 000000000000..efac443fae5a --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/DPad_Right.svg @@ -0,0 +1,74 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/GCPad/DPad_Up.svg b/Source/Core/DolphinQt/Resources/GCPad/DPad_Up.svg new file mode 100644 index 000000000000..2e3be487b5ff --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/DPad_Up.svg @@ -0,0 +1,74 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/GCPad/GCPad.qml b/Source/Core/DolphinQt/Resources/GCPad/GCPad.qml new file mode 100644 index 000000000000..884cad49964c --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/GCPad.qml @@ -0,0 +1,169 @@ +import QtQuick 2.0 + +import "../Controller" + +Controller +{ + width: 650 + height: 500 + + Image { + id: image + anchors.centerIn: parent + source: "base.svg" + z: -1 + } + + // Buttons + Button { + name: "Start" + + orientation: "bottom" + + image: "qrc:/GCPad/Start.svg" + + x: 325 + y: 175 + } + + Button { + name: "A" + + orientation: "bottom" + + image: "qrc:/GCPad/A.svg" + + x: 535 + y: 150 + } + + Button { + name: "B" + + image: "qrc:/GCPad/B.svg" + + x: 465 + y: 195 + } + + Button { + name: "X" + + orientation: "bottom" + + image: "qrc:/GCPad/X.svg" + + x: 610 + y: 125 + } + + Button { + name: "Y" + + image: "qrc:/GCPad/Y.svg" + + x: 520 + y: 100 + } + + Button { + name: "Z" + + orientation: "right" + + image: "qrc:/GCPad/Z.svg" + + x: 535 + y: 35 + z: -2 + } + + // D-Pad + Button { + group: "DPad" + name: "Up" + + image: "qrc:/GCPad/DPad_Up.svg" + + x: 211 + y: 304 + } + + Button { + group: "DPad" + name: "Down" + + orientation: "bottom" + + image: "qrc:/GCPad/DPad_Down.svg" + + x: 211 + y: 367 + } + + Button { + group: "DPad" + name: "Left" + + orientation: "left" + + image: "qrc:/GCPad/DPad_Left.svg" + + x: 179 + y: 335 + } + + Button { + group: "DPad" + name: "Right" + orientation: "right" + + image: "qrc:/GCPad/DPad_Right.svg" + + x: 243 + y: 335 + } + + // Sticks + Stick { + name: "Main" + + image: "qrc:/GCPad/Stick_Main.svg" + + x: 110 + y: 140 + } + + Stick { + name: "C" + + image: "qrc:/GCPad/Stick_C.svg" + + radius: 65 + radiusMargin: 10 + + x: 440 + y: 315 + } + + // Triggers + Trigger { + name: "L" + + image: "qrc:/GCPad/Trigger_L.svg" + + x: 80 + y: 46 + z: -3 + } + + Trigger { + name: "R" + + image: "qrc:/GCPad/Trigger_R.svg" + + x: 540 + y: 25 + z: -3 + } +} diff --git a/Source/Core/DolphinQt/Resources/GCPad/GCPadView.qml b/Source/Core/DolphinQt/Resources/GCPad/GCPadView.qml new file mode 100644 index 000000000000..5c0a7128db63 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/GCPadView.qml @@ -0,0 +1,16 @@ +import QtQuick 2.0 + +Rectangle { + width: gcpad.width + 75 + height: gcpad.height + 75 + + color: "transparent" + + GCPad { + id: gcpad + anchors.centerIn: parent + scale: Math.min(parent.width / gcpad.width, parent.height / gcpad.height) * 0.85 + objectName: "gcpad" + } + +} diff --git a/Source/Core/DolphinQt/Resources/GCPad/Start.svg b/Source/Core/DolphinQt/Resources/GCPad/Start.svg new file mode 100644 index 000000000000..7699b195507c --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/Start.svg @@ -0,0 +1,75 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/GCPad/Stick_C.svg b/Source/Core/DolphinQt/Resources/GCPad/Stick_C.svg new file mode 100644 index 000000000000..61d6baf3593a --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/Stick_C.svg @@ -0,0 +1,79 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/GCPad/Stick_Main.svg b/Source/Core/DolphinQt/Resources/GCPad/Stick_Main.svg new file mode 100644 index 000000000000..b92acc9f8491 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/Stick_Main.svg @@ -0,0 +1,97 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/GCPad/Trigger_L.svg b/Source/Core/DolphinQt/Resources/GCPad/Trigger_L.svg new file mode 100644 index 000000000000..a661196ae25b --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/Trigger_L.svg @@ -0,0 +1,74 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/GCPad/Trigger_R.svg b/Source/Core/DolphinQt/Resources/GCPad/Trigger_R.svg new file mode 100644 index 000000000000..6f91b637614f --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/Trigger_R.svg @@ -0,0 +1,74 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/GCPad/X.svg b/Source/Core/DolphinQt/Resources/GCPad/X.svg new file mode 100644 index 000000000000..a0d79ca4c369 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/X.svg @@ -0,0 +1,80 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/GCPad/Y.svg b/Source/Core/DolphinQt/Resources/GCPad/Y.svg new file mode 100644 index 000000000000..302b808ef18a --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/Y.svg @@ -0,0 +1,80 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/GCPad/Z.svg b/Source/Core/DolphinQt/Resources/GCPad/Z.svg new file mode 100644 index 000000000000..d5b4c48694b6 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/Z.svg @@ -0,0 +1,75 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/GCPad/base.svg b/Source/Core/DolphinQt/Resources/GCPad/base.svg new file mode 100644 index 000000000000..196bcfbe0b3b --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/base.svg @@ -0,0 +1,355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Core/DolphinQt/Resources/Resources.qrc b/Source/Core/DolphinQt/Resources/Resources.qrc new file mode 100644 index 000000000000..95c0e1c09906 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/Resources.qrc @@ -0,0 +1,27 @@ + + + + Controller/Controller.qml + Controller/Button.qml + Controller/Stick.qml + Controller/Trigger.qml + GCPad/GCPad.qml + GCPad/GCPadView.qml + + GCPad/base.svg + GCPad/A.svg + GCPad/B.svg + GCPad/X.svg + GCPad/Y.svg + GCPad/Z.svg + GCPad/Start.svg + GCPad/Trigger_L.svg + GCPad/Trigger_R.svg + GCPad/Stick_Main.svg + GCPad/Stick_C.svg + GCPad/DPad_Up.svg + GCPad/DPad_Down.svg + GCPad/DPad_Left.svg + GCPad/DPad_Right.svg + + diff --git a/Source/VSProps/QtCompile.props b/Source/VSProps/QtCompile.props index 2b819aab62fa..dd70dc3a7d0a 100644 --- a/Source/VSProps/QtCompile.props +++ b/Source/VSProps/QtCompile.props @@ -29,6 +29,9 @@ $(QtIncludeDir)QtGui;%(AdditionalIncludeDirectories) $(QtIncludeDir)QtWidgets;%(AdditionalIncludeDirectories) $(QtIncludeDir)QtANGLE;%(AdditionalIncludeDirectories) + $(QtIncludeDir)QtQml;%(AdditionalIncludeDirectories) + $(QtIncludeDir)QtQuick;%(AdditionalIncludeDirectories) + $(QtIncludeDir)QtQuickWidgets;%(AdditionalIncludeDirectories) - "-I$(QtIncludeDir)QtWidgets" "-I$(QtIncludeDir)QtGui" "-I$(QtIncludeDir)QtCore" "-I$(QtIncludeDir) " "-I$(QtToolOutDir) " -I. + "-I$(QtIncludeDir)QtWidgets" "-I$(QtIncludeDir)QtGui" "-I$(QtIncludeDir)QtCore" "-I$(QtIncludeDir) " "-I$(QtToolOutDir) " "-I$(QtIncludeDir)QtQML" "-I$(QtIncludeDir)QtQuick" "-I$(QtIncludeDir)QtQuickWidgets" -I. "-I$(ExternalsDir)xxhash" "-I$(ExternalsDir)zlib" "-I$(ExternalsDir)SFML\include" "-I$(ExternalsDir)mbedtls\include" "-I$(ExternalsDir)miniupnpc\src" "-I$(ExternalsDir)LZO" "-I$(ExternalsDir)libusbx\libusb" "-I$(ExternalsDir)libpng" "-I$(ExternalsDir)GL" "-I$(ExternalsDir)Bochs_disasm" "-I$(ExternalsDir) " "-I$(CoreDir) " $(MocIncludes) + + + + + + + @@ -79,17 +93,27 @@ + + + + + + + QtMoc + + + QtResource - +