diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 27eda16cb4e0..a64912b56aba 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -1,10 +1,14 @@ -find_package(Qt5 5.9 REQUIRED COMPONENTS Gui Widgets) +find_package(Qt5 5.9 REQUIRED COMPONENTS Gui Widgets Quick QuickWidgets) set_property(TARGET Qt5::Core PROPERTY INTERFACE_COMPILE_FEATURES "") message(STATUS "Found Qt version ${Qt5Core_VERSION}") set(CMAKE_AUTOMOC ON) +find_package(Qt5QuickCompiler) +qtquick_compiler_add_resources(RCC_SOURCES Resources/Resources.qrc) +set_property(SOURCE ${RCC_SOURCES} PROPERTY SKIP_AUTOMOC ON) + add_executable(dolphin-emu AboutDialog.cpp CheatsManager.cpp @@ -69,6 +73,7 @@ add_executable(dolphin-emu Config/Mapping/MappingNumeric.cpp Config/Mapping/MappingWidget.cpp Config/Mapping/MappingWindow.cpp + Config/Mapping/Visual/GCMappingWidget.cpp Config/Mapping/WiimoteEmuExtension.cpp Config/Mapping/WiimoteEmuGeneral.cpp Config/Mapping/WiimoteEmuMotionControl.cpp @@ -128,6 +133,7 @@ add_executable(dolphin-emu TAS/StickWidget.cpp TAS/IRWidget.cpp Updater.cpp + ${RCC_SOURCES} ) target_compile_definitions(dolphin-emu @@ -141,12 +147,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 ) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp index bc582e1bd93f..35cd5b5c6ba3 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp @@ -83,6 +83,9 @@ void MappingButton::Detect() QString expression; + const auto old_text = text(); + setText(QStringLiteral("...")); + if (m_parent->GetParent()->IsMappingAllDevices()) { expression = MappingCommon::DetectExpression(this, g_controller_interface, @@ -96,6 +99,8 @@ void MappingButton::Detect() 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 01aa95d3a051..2b4932cc3713 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(); @@ -73,19 +72,17 @@ QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& dev std::tie(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 5e213b32215b..cc403965187f 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 { @@ -33,7 +36,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 72abaa2fd123..9e5ff42a5562 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp @@ -23,7 +23,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" #include "DolphinQt/Settings.h" diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp index d99b198ea29a..9eee3602ea37 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp @@ -11,6 +11,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 d611aa974926..48d21c174292 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h @@ -33,8 +33,6 @@ class Device; } } // namespace ciface -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 0d280d5b7554..b016becc658c 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -32,6 +32,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/WiimoteEmuGeneral.h" #include "DolphinQt/Config/Mapping/WiimoteEmuMotionControl.h" @@ -309,10 +310,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..8fd81ebe4db4 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Mapping/Visual/GCMappingWidget.cpp @@ -0,0 +1,278 @@ +// 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" + +#include + +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/GCPad.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)), this, SLOT(DetectInput(QString)), + Qt::QueuedConnection); + + setLayout(layout); +} + +void VisualGCMappingWidget::DetectInput(QString name) +{ + std::cout << "NAME: " << name.toStdString() << std::endl; + + auto* buttons = Pad::GetGroup(m_port, PadGroup::Buttons); + + ControllerEmu::Control* map_control = nullptr; + + for (auto& control : buttons->controls) + { + if (control->name == name.toStdString()) + { + map_control = control.get(); + break; + } + } + + auto* dpad = Pad::GetGroup(m_port, PadGroup::DPad); + for (auto& control : dpad->controls) + { + if ("DPad_" + control->name == name.toStdString()) + { + map_control = control.get(); + break; + } + } + + auto* triggers = Pad::GetGroup(m_port, PadGroup::Triggers); + for (auto& control : triggers->controls) + { + if ("Trigger_" + control->name == name.toStdString()) + { + map_control = control.get(); + break; + } + } + + auto* main_stick = Pad::GetGroup(m_port, PadGroup::MainStick); + for (auto& control : main_stick->controls) + { + if ("Stick_Main_" + control->name == name.toStdString()) + { + map_control = control.get(); + break; + } + } + + auto* c_stick = Pad::GetGroup(m_port, PadGroup::CStick); + for (auto& control : c_stick->controls) + { + if ("Stick_C_" + control->name == name.toStdString()) + { + map_control = control.get(); + break; + } + } + + if (map_control == nullptr) + return; + + auto* thread = QThread::create([this, map_control, name] { + const auto default_device_qualifier = m_parent->GetController()->GetDefaultDevice(); + + QString expression; + + if (m_parent->IsMappingAllDevices()) + { + expression = MappingCommon::DetectExpression(this, g_controller_interface, + g_controller_interface.GetAllDeviceStrings(), + default_device_qualifier); + } + else + { + expression = MappingCommon::DetectExpression(this, g_controller_interface, + {default_device_qualifier.ToString()}, + default_device_qualifier); + } + + QMetaObject::invokeMethod(GetQMLController(), "detectedInput", Qt::QueuedConnection, + Q_ARG(QVariant, name)); + + if (expression.isEmpty()) + return; + + map_control->control_ref->SetExpression(expression.toStdString()); + m_parent->GetController()->UpdateReferences(g_controller_interface); + + Pad::GetConfig()->SaveConfig(); + }); + + thread->setParent(this); + thread->start(); +} + +QObject* VisualGCMappingWidget::GetQMLController() +{ + return m_widget->rootObject()->findChild(QStringLiteral("controller")); +} + +void VisualGCMappingWidget::RefreshControls() +{ + if (!isActiveWindow()) + return; + + auto* buttons = Pad::GetGroup(m_port, PadGroup::Buttons); + + QObject* object = m_widget->rootObject()->findChild(QStringLiteral("controller")); + + const auto lock = m_parent->GetController()->GetStateLock(); + Settings::Instance().SetControllerStateNeeded(true); + + for (auto& control : buttons->controls) + { + auto state = control->control_ref->State(); + const bool activated = state > ControllerEmu::Buttons::ACTIVATION_THRESHOLD; + const std::string expression = control->control_ref->GetExpression(); + + QMetaObject::invokeMethod(object, "updateButton", Qt::QueuedConnection, + Q_ARG(QVariant, QString::fromStdString(control->name)), + Q_ARG(QVariant, activated), + Q_ARG(QVariant, QString::fromStdString(expression))); + } + + auto* dpad = Pad::GetGroup(m_port, PadGroup::DPad); + + for (auto& control : dpad->controls) + { + auto state = control->control_ref->State(); + const bool activated = state > ControllerEmu::Buttons::ACTIVATION_THRESHOLD; + const std::string expression = control->control_ref->GetExpression(); + + QMetaObject::invokeMethod(object, "updateButton", Qt::QueuedConnection, + Q_ARG(QVariant, QString::fromStdString("DPad_" + control->name)), + Q_ARG(QVariant, activated), + Q_ARG(QVariant, QString::fromStdString(expression))); + } + + auto* triggers = Pad::GetGroup(m_port, PadGroup::Triggers); + + for (size_t i = 2; i < 4; i++) + { + auto& control = triggers->controls[i]; + + auto state = control->control_ref->State(); + + const auto name = control->name.substr(0, 1); + + QMetaObject::invokeMethod(object, "updateTrigger", Qt::QueuedConnection, + Q_ARG(QVariant, QString::fromStdString("Trigger_" + name)), + Q_ARG(QVariant, state), Q_ARG(QVariant, QVariant())); + } + + static bool once = true; + + for (size_t i = 0; i < 2; i++) + { + auto& control = triggers->controls[i]; + + const bool activated = + control->control_ref->State() > ControllerEmu::Buttons::ACTIVATION_THRESHOLD; + + if (!activated && !once) + continue; + + const std::string expression = control->control_ref->GetExpression(); + + QMetaObject::invokeMethod(object, "updateTrigger", Qt::QueuedConnection, + Q_ARG(QVariant, QString::fromStdString("Trigger_" + control->name)), + Q_ARG(QVariant, activated), + Q_ARG(QVariant, QString::fromStdString(expression))); + } + + once = false; + + auto* main_stick = + static_cast(Pad::GetGroup(m_port, PadGroup::MainStick)); + + auto main_state = main_stick->GetReshapableState(true); + + QMetaObject::invokeMethod(object, "updateStick", + Q_ARG(QVariant, QString::fromStdString("Stick_Main")), + Q_ARG(QVariant, main_state.x), Q_ARG(QVariant, 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(); + QMetaObject::invokeMethod(object, "updateStickExpression", + Q_ARG(QVariant, QString::fromStdString("Stick_Main")), + Q_ARG(QVariant, QString::fromStdString(control->name)), + Q_ARG(QVariant, QString::fromStdString(expression))); + } + + auto* c_stick = + static_cast(Pad::GetGroup(m_port, PadGroup::CStick)); + + auto c_state = c_stick->GetReshapableState(true); + + QMetaObject::invokeMethod(object, "updateStick", + Q_ARG(QVariant, QString::fromStdString("Stick_C")), + Q_ARG(QVariant, c_state.x), Q_ARG(QVariant, 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(); + QMetaObject::invokeMethod(object, "updateStickExpression", + Q_ARG(QVariant, QString::fromStdString("Stick_C")), + Q_ARG(QVariant, QString::fromStdString(control->name)), + Q_ARG(QVariant, QString::fromStdString(expression))); + } + + Settings::Instance().SetControllerStateNeeded(false); +} 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..9bfcff0961b3 --- /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 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 f5f1742c6727..65c9b55fc2fe 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -44,7 +44,7 @@ avrt.lib;iphlpapi.lib;winmm.lib;setupapi.lib;opengl32.lib;glu32.lib;rpcrt4.lib;comctl32.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;Shlwapi.lib;discord-rpc.lib;%(AdditionalDependencies) - $(ProjectDir)VideoInterface;$(ProjectDir)GameList;$(ProjectDir)Debugger;$(ProjectDir)Settings;$(ProjectDir)Config;$(ProjectDir)Config\Mapping;$(ProjectDir)Config\Graphics;$(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)NetPlay;$(ProjectDir)QtUtils;$(ProjectDir)TAS;$(ProjectDir)FIFO;%(AdditionalIncludeDirectories) DolphinQt.manifest;%(AdditionalManifestFiles) @@ -86,6 +86,7 @@ + @@ -168,8 +169,12 @@ + + + + @@ -195,6 +200,7 @@ + @@ -324,6 +330,7 @@ + diff --git a/Source/Core/DolphinQt/Main.cpp b/Source/Core/DolphinQt/Main.cpp index 038fed9c8944..8e0c11990991 100644 --- a/Source/Core/DolphinQt/Main.cpp +++ b/Source/Core/DolphinQt/Main.cpp @@ -5,6 +5,7 @@ #ifdef _WIN32 #include #include +#include #include "Common/StringUtil.h" #endif @@ -88,6 +89,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/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/Button.qml b/Source/Core/DolphinQt/Resources/GCPad/Button.qml new file mode 100644 index 000000000000..726e95884f33 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/Button.qml @@ -0,0 +1,45 @@ +import QtQuick 2.0 +import QtGraphicalEffects 1.0 +import QtQuick.Controls 2.0 + +Image { + property string name + property int box_x : image.width / 2 + property int box_y : image.height - 10 + + signal detectInput(string name) + + id: image + source: name+".svg" + + objectName: "button" + + ColorOverlay + { + id: hover_overlay + anchors.fill: image + source: image + color: "transparent" + } + + function buttonPressed(pressed, expression) + { + input_box.setValue(expression) + input_box.setPressed(pressed) + hover_overlay.color = pressed ? Qt.rgba(1, 1, 0, 0.6) : "transparent"; + } + + function detectedInput() + { + input_box.detectedInput(); + } + + InputBox + { + onDetectInput: image.detectInput(image.name) + id: input_box + x: box_x - width/2 + y: box_y + 10 + z: 1 + } +} 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..7e86182a2226 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/GCPad.qml @@ -0,0 +1,205 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +import QtQuick 2.0 + +Rectangle +{ + id: rect + color: "transparent" + + Rectangle + { + objectName: "controller" + id: controller + + signal detectInput(string name) + + function updateButton(name, value, expression) + { + for (var i = 0; i < controller.children.length; i++) + { + var item = controller.children[i]; + + if (item.name == name && item.objectName == "button") + { + item.buttonPressed(value, expression); + return; + } + } + } + + function updateTrigger(name, value, expression) + { + for (var i = 0; i < controller.children.length; i++) + { + var item = controller.children[i]; + + if (item.name == name && item.objectName == "trigger") + { + item.triggerPressed(value, expression); + return; + } + } + } + + function updateStick(name, x, y) + { + for (var i = 0; i < controller.children.length; i++) + { + var item = controller.children[i]; + + if (item.name == name && item.objectName == "stick") + { + item.setPosition(x, y); + return; + } + } + } + + function updateStickExpression(name, direction, expression) + { + for (var i = 0; i < controller.children.length; i++) + { + var item = controller.children[i]; + + if (item.name == name && item.objectName == "stick") + { + item.setExpression(direction, expression); + return; + } + } + } + + function detectedInput(name) + { + if (name.startsWith("Stick_Main_")) { + stick_main.detectedInput(name.substr(11)) + return; + } + if (name.startsWith("Stick_C_")) { + stick_c.detectedInput(name.substr(8)) + return; + } + for (var i = 0; i < controller.children.length; i++) + { + var item = controller.children[i]; + if (item.name == name) + { + item.detectedInput(); + return; + } + } + } + + Image { + source: "base.svg" + } + + x: (rect.width - 650) / 2 + y: (rect.height - 500) / 2 + + Trigger { + name: "Trigger_R" + x: 505 + y: 20 + z: -2 + onDetectInput: controller.detectInput(name) + } + Button { + name: "Z" + x: 495 + y: 30 + z: -1 + box_x: 155 + box_y: 20 + onDetectInput: controller.detectInput(name) + } + Trigger { + name: "Trigger_L" + x: 60 + y: 30 + z: -1 + onDetectInput: controller.detectInput(name) + } + Button { + name: "A" + x: 510 + y: 135 + onDetectInput: controller.detectInput(name) + } + Button { + name: "B" + x: 460 + y: 190 + onDetectInput: controller.detectInput(name) + } + Button { + name: "X" + x: 605 + y: 120 + onDetectInput: controller.detectInput(name) + + } + Button { + name: "Y" + x: 500 + y: 85 + onDetectInput: controller.detectInput(name) + } + Button { + name: "Start" + x: 320 + y: 165 + onDetectInput: controller.detectInput(name) + } + Stick { + id: stick_c + name: "Stick_C" + radius: 50 + x: 420 + y: 300 + shift_input_x: -5 + onDetectInput: controller.detectInput(name) + } + Stick { + id: stick_main + name: "Stick_Main" + radius: 60 + x: 75 + y: 128 + onDetectInput: controller.detectInput(name) + } + Button { + name: "DPad_Up" + x: 214 + y: 293 + box_y: -45 + onDetectInput: controller.detectInput(name) + } + Button { + onDetectInput: controller.detectInput(name) + name: "DPad_Down" + x: 214 + y: 358 + box_y: 15 + } + Button { + name: "DPad_Left" + x: 185 + y: 323 + box_x: -50 + box_y: -12 + onDetectInput: controller.detectInput(name) + } + Button { + name: "DPad_Right" + x: 245 + y: 323 + box_x: 70 + box_y: -12 + onDetectInput: controller.detectInput(name) + } + } +} diff --git a/Source/Core/DolphinQt/Resources/GCPad/InputBox.qml b/Source/Core/DolphinQt/Resources/GCPad/InputBox.qml new file mode 100644 index 000000000000..f44e7f20c25a --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/InputBox.qml @@ -0,0 +1,60 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +import QtQuick 2.0 + +Rectangle +{ + signal detectInput() + + property bool detecting_input : false + + id: input_box + color: Qt.rgba(0, 0, 0, 0.5) + radius: 15 + width: 80 + height: 25 + z: 1 + + function setValue(value) + { + if (input_box.detecting_input) + return; + + expression.text = value == "" ? "" : value; + } + + + function detectedInput() + { + input_box.detecting_input = false; + } + + function setPressed(pressed) + { + expression.font.bold = pressed; + } + + Text { + id: expression + anchors.centerIn: input_box + width: input_box.width - 10 + text: "" + color: "white" + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } + + + MouseArea + { + anchors.fill: input_box + onClicked: { + input_box.detecting_input = true; + expression.text = "..."; + input_box.detectInput(); + } + } +} 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.qml b/Source/Core/DolphinQt/Resources/GCPad/Stick.qml new file mode 100644 index 000000000000..fa604ed0fee1 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/Stick.qml @@ -0,0 +1,106 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +import QtQuick 2.0 +import QtGraphicalEffects 1.0 +Rectangle { + id: base + + property string name + property int radius + + property int org_x: x + property int org_y: y + + property int shift_input_x: 0 + property int shift_input_y: 0 + + signal detectInput(string name) + + objectName: "stick" + + Component.onCompleted: { org_x = org_x; org_y = org_y; } + + Image { + id: image + + opacity: 0.5 + + source: base.name+".svg" + + ColorOverlay + { + id: hover_overlay + anchors.fill: image + source: image + color: "transparent" + } + } + + function setPosition(new_x, new_y) + { + image.x = new_x * radius; + image.y = -new_y * radius; + } + + function detectedInput(direction) + { + if (direction == "Up") + input_box_up.detectedInput(); + if (direction == "Down") + input_box_down.detectedInput(); + if (direction == "Left") + input_box_left.detectedInput(); + if (direction == "Right") + input_box_right.detectedInput(); + if (direction == "Down") + input_box_down.detectedInput(); + } + + function setExpression(direction, expression) + { + if (direction == "Up") + input_box_up.setValue(expression) + if (direction == "Down") + input_box_down.setValue(expression) + if (direction == "Left") + input_box_left.setValue(expression) + if (direction == "Right") + input_box_right.setValue(expression) + if (direction == "Down") + input_box_down.setValue(expression) + } + + InputBox + { + id: input_box_up + x: base.radius - width/2 - 10 + shift_input_x + y: -30 + shift_input_y + onDetectInput: base.detectInput(base.name+"_Up") + } + + InputBox + { + id: input_box_down + x: base.radius - width/2 - 10 + shift_input_x + y: base.radius*2 + shift_input_y + onDetectInput: base.detectInput(base.name+"_Down") + } + + InputBox + { + id: input_box_left + x: -10 - width + shift_input_x + y: base.radius - height + shift_input_y + onDetectInput: base.detectInput(base.name+"_Left") + } + + InputBox + { + id: input_box_right + x: base.radius*2 - 10 + shift_input_x + y: base.radius - height + shift_input_y + onDetectInput: base.detectInput(base.name+"_Right") + } +} 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.qml b/Source/Core/DolphinQt/Resources/GCPad/Trigger.qml new file mode 100644 index 000000000000..5c0269c11eb7 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/Trigger.qml @@ -0,0 +1,63 @@ +import QtQuick 2.0 +import QtGraphicalEffects 1.0 +import QtQuick.Controls 2.0 + +Image { + property string name + property int box_x : image.width / 2 + property int box_y : 0 + + id: image + + source: name+".svg" + + objectName: "trigger" + + signal detectInput(string name) + + ColorOverlay + { + id: hover_overlay + anchors.fill: image + source: image + color: "transparent" + } + + ProgressBar + { + id: progress + value: 0 + y: -25 + anchors.horizontalCenter: image.horizontalCenter + width: image.width + } + + function detectedInput() + { + input_box.detectedInput(); + } + + function triggerPressed(value, expression) + { + if (expression != null) + input_box.setValue(expression) + + progress.value = value; + + var pressed = value == 1 + + hover_overlay.color = pressed ? Qt.rgba(1, 1, 0, 0.6) : "transparent"; + + input_box.setPressed(value) + } + + InputBox + { + onDetectInput: image.detectInput(image.name) + id: input_box + x: box_x - width/2 + y: box_y - height - 20 + z: 1 + } + +} 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..7c9190cfe8e0 --- /dev/null +++ b/Source/Core/DolphinQt/Resources/GCPad/base.svg @@ -0,0 +1,356 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..ae570f4177bd --- /dev/null +++ b/Source/Core/DolphinQt/Resources/Resources.qrc @@ -0,0 +1,26 @@ + + + + GCPad/GCPad.qml + GCPad/Button.qml + GCPad/InputBox.qml + GCPad/Stick.qml + GCPad/Trigger.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 a09f0d11780e..38a6d0c37d46 100644 --- a/Source/VSProps/QtCompile.props +++ b/Source/VSProps/QtCompile.props @@ -27,6 +27,9 @@ $(QtIncludeDir)QtCore;%(AdditionalIncludeDirectories) $(QtIncludeDir)QtGui;%(AdditionalIncludeDirectories) $(QtIncludeDir)QtWidgets;%(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) + + + + + + + + @@ -77,17 +92,27 @@ + + + + + + + QtMoc + + + QtResource - +