From 3189de6c7af808f62f622c744832b051f0f885ff Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 1 Feb 2025 14:33:03 -0600 Subject: [PATCH 1/2] DolphinQt: Add setting to enable iterative input mappings. --- .../Config/Mapping/MappingButton.cpp | 24 ++------------- .../DolphinQt/Config/Mapping/MappingButton.h | 4 +-- .../Config/Mapping/MappingCommon.cpp | 30 ++++++++++++------- .../Config/Mapping/MappingWidget.cpp | 11 +++++++ .../DolphinQt/Config/Mapping/MappingWidget.h | 2 ++ .../Config/Mapping/MappingWindow.cpp | 9 ++++++ .../DolphinQt/Config/Mapping/MappingWindow.h | 2 ++ 7 files changed, 48 insertions(+), 34 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp index 3af4c2e00e..d0e1ce4bbe 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp @@ -11,8 +11,6 @@ #include "DolphinQt/Config/Mapping/IOWindow.h" #include "DolphinQt/Config/Mapping/MappingWidget.h" #include "DolphinQt/Config/Mapping/MappingWindow.h" -#include "DolphinQt/QtUtils/BlockUserInputFilter.h" -#include "DolphinQt/QtUtils/SetWindowDecorations.h" #include "InputCommon/ControlReference/ControlReference.h" #include "InputCommon/ControllerEmu/ControllerEmu.h" @@ -67,15 +65,10 @@ static QString RefToDisplayString(ControlReference* ref) return expression; } -bool MappingButton::IsInput() const -{ - return m_reference->IsInput(); -} - MappingButton::MappingButton(MappingWidget* parent, ControlReference* ref) : ElidedButton(RefToDisplayString(ref)), m_mapping_window(parent->GetParent()), m_reference(ref) { - if (IsInput()) + if (m_reference->IsInput()) { setToolTip( tr("Left-click to detect input.\nMiddle-click to clear.\nRight-click for more options.")); @@ -88,10 +81,8 @@ MappingButton::MappingButton(MappingWidget* parent, ControlReference* ref) connect(this, &MappingButton::clicked, this, &MappingButton::Clicked); connect(parent, &MappingWidget::ConfigChanged, this, &MappingButton::ConfigChanged); - connect(this, &MappingButton::ConfigChanged, [this] { - setText(RefToDisplayString(m_reference)); - m_is_mapping = false; - }); + connect(this, &MappingButton::ConfigChanged, + [this] { setText(RefToDisplayString(m_reference)); }); } void MappingButton::AdvancedPressed() @@ -114,7 +105,6 @@ void MappingButton::Clicked() return; } - m_is_mapping = true; m_mapping_window->QueueInputDetection(this); } @@ -131,14 +121,6 @@ void MappingButton::Clear() m_mapping_window->UnQueueInputDetection(this); } -void MappingButton::StartMapping() -{ - // Focus just makes it more clear which button is currently being mapped. - setFocus(); - setText(tr("[ Press Now ]")); - QtUtils::InstallKeyboardBlocker(this, this, &MappingButton::ConfigChanged); -} - void MappingButton::mouseReleaseEvent(QMouseEvent* event) { switch (event->button()) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.h b/Source/Core/DolphinQt/Config/Mapping/MappingButton.h index 76bb412079..733bdf3e9e 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.h @@ -17,12 +17,11 @@ class MappingButton : public ElidedButton public: MappingButton(MappingWidget* widget, ControlReference* ref); - bool IsInput() const; ControlReference* GetControlReference(); - void StartMapping(); signals: void ConfigChanged(); + void QueueNextButtonMapping(); private: void Clear(); @@ -33,5 +32,4 @@ private: MappingWindow* const m_mapping_window; ControlReference* const m_reference; - bool m_is_mapping = false; }; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp index 37407f2298..54a0db59ef 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp @@ -10,6 +10,7 @@ #include "DolphinQt/Config/Mapping/MappingButton.h" #include "DolphinQt/Config/Mapping/MappingWindow.h" +#include "DolphinQt/QtUtils/BlockUserInputFilter.h" #include "InputCommon/ControlReference/ControlReference.h" #include "InputCommon/ControllerEmu/ControllerEmu.h" @@ -49,7 +50,10 @@ public: const auto& default_device = m_parent->GetController()->GetDefaultDevice(); auto& button = m_clicked_mapping_buttons.front(); - button->StartMapping(); + // Focus just makes it more clear which button is currently being mapped. + button->setFocus(); + button->setText(tr("[ Press Now ]")); + QtUtils::InstallKeyboardBlocker(button, button, &MappingButton::ConfigChanged); std::vector device_strings{default_device.ToString()}; if (m_parent->IsCreateOtherDeviceMappingsEnabled()) @@ -73,9 +77,17 @@ public: if (m_input_detector->IsComplete()) { - // No inputs detected. Cancel this and any other queued mappings. + auto* const button = m_clicked_mapping_buttons.back(); + if (!FinalizeMapping(m_input_detector->TakeResults())) + { + // No inputs detected. Cancel this and any other queued mappings. CancelMapping(); + } + else if (m_parent->IsIterativeMappingEnabled() && m_clicked_mapping_buttons.empty()) + { + button->QueueNextButtonMapping(); + } } } @@ -110,22 +122,20 @@ public: m_input_detection_start_timer->start(INPUT_DETECT_INITIAL_DELAY); } - void UnQueueInputDetection(MappingButton* button) + bool UnQueueInputDetection(MappingButton* button) { - std::erase(m_clicked_mapping_buttons, button); + if (!std::erase(m_clicked_mapping_buttons, button)) + return false; button->ConfigChanged(); UpdateInputDetectionStartTimer(); + return true; } void QueueInputDetection(MappingButton* button) { - // UnQueue if already queued. - if (std::erase(m_clicked_mapping_buttons, button)) - { - button->ConfigChanged(); - UpdateInputDetectionStartTimer(); + // Just UnQueue if already queued. + if (UnQueueInputDetection(button)) return; - } button->setText(QStringLiteral("[ ... ]")); m_clicked_mapping_buttons.push_back(button); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp index 82fe042f9a..442fff464f 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp @@ -314,6 +314,17 @@ void MappingWidget::CreateControl(const ControllerEmu::Control* control, QFormLa bool indicator) { auto* const button = new MappingButton(this, control->control_ref.get()); + + if (control->control_ref->IsInput()) + { + if (m_previous_mapping_button) + { + connect(m_previous_mapping_button, &MappingButton::QueueNextButtonMapping, + [this, button]() { m_parent->QueueInputDetection(button); }); + } + m_previous_mapping_button = button; + } + button->setMinimumWidth(100); button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h index 31cf964fc3..f993968590 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.h @@ -7,6 +7,7 @@ #include class InputConfig; +class MappingButton; class MappingNumeric; class MappingWindow; class QFormLayout; @@ -55,4 +56,5 @@ protected: private: MappingWindow* m_parent; + MappingButton* m_previous_mapping_button = nullptr; }; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 665d5e9659..d8eb6ec408 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -117,9 +117,13 @@ void MappingWindow::CreateDevicesLayout() m_wait_for_alternate_mappings = new QAction(tr("Wait for Alternate Input Mappings"), options); m_wait_for_alternate_mappings->setCheckable(true); + m_iterative_mapping = new QAction(tr("Enable Iterative Input Mapping"), options); + m_iterative_mapping->setCheckable(true); + options->addAction(refresh_action); options->addAction(m_other_device_mappings); options->addAction(m_wait_for_alternate_mappings); + options->addAction(m_iterative_mapping); options->setDefaultAction(refresh_action); m_devices_combo->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); @@ -368,6 +372,11 @@ bool MappingWindow::IsWaitForAlternateMappingsEnabled() const return m_wait_for_alternate_mappings->isChecked(); } +bool MappingWindow::IsIterativeMappingEnabled() const +{ + return m_iterative_mapping->isChecked(); +} + void MappingWindow::RefreshDevices() { g_controller_interface.RefreshDevices(); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h index 10bf590f35..f07aac7fd4 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h @@ -53,6 +53,7 @@ public: ControllerEmu::EmulatedController* GetController() const; bool IsCreateOtherDeviceMappingsEnabled() const; bool IsWaitForAlternateMappingsEnabled() const; + bool IsIterativeMappingEnabled() const; void ShowExtensionMotionTabs(bool show); void ActivateExtensionTab(); @@ -106,6 +107,7 @@ private: QComboBox* m_devices_combo; QAction* m_other_device_mappings; QAction* m_wait_for_alternate_mappings; + QAction* m_iterative_mapping; // Profiles QGroupBox* m_profiles_box; From 66624abb124b2b1aa0fc7381d14402014ef86f66 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 7 Feb 2025 00:36:52 -0600 Subject: [PATCH 2/2] DolphinQt/Mapping: Clear and skip "Modifier" iterative input mappings when using analog inputs. --- .../Config/Mapping/MappingButton.cpp | 10 ++- .../DolphinQt/Config/Mapping/MappingButton.h | 11 +++- .../Config/Mapping/MappingCommon.cpp | 63 +++++++++++++------ .../Config/Mapping/MappingWidget.cpp | 11 +++- 4 files changed, 72 insertions(+), 23 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp index d0e1ce4bbe..24e7be9bf8 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp @@ -65,8 +65,9 @@ static QString RefToDisplayString(ControlReference* ref) return expression; } -MappingButton::MappingButton(MappingWidget* parent, ControlReference* ref) - : ElidedButton(RefToDisplayString(ref)), m_mapping_window(parent->GetParent()), m_reference(ref) +MappingButton::MappingButton(MappingWidget* parent, ControlReference* ref, ControlType control_type) + : ElidedButton{RefToDisplayString(ref)}, m_mapping_window{parent->GetParent()}, + m_reference{ref}, m_control_type{control_type} { if (m_reference->IsInput()) { @@ -141,3 +142,8 @@ ControlReference* MappingButton::GetControlReference() { return m_reference; } + +auto MappingButton::GetControlType() const -> ControlType +{ + return m_control_type; +} diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.h b/Source/Core/DolphinQt/Config/Mapping/MappingButton.h index 733bdf3e9e..7cac489ad0 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.h @@ -15,9 +15,17 @@ class MappingButton : public ElidedButton { Q_OBJECT public: - MappingButton(MappingWidget* widget, ControlReference* ref); + enum class ControlType + { + NormalInput, + ModifierInput, + Output, + }; + + MappingButton(MappingWidget* widget, ControlReference* ref, ControlType type); ControlReference* GetControlReference(); + ControlType GetControlType() const; signals: void ConfigChanged(); @@ -32,4 +40,5 @@ private: MappingWindow* const m_mapping_window; ControlReference* const m_reference; + const ControlType m_control_type; }; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp index 54a0db59ef..d94502b104 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp @@ -25,6 +25,11 @@ constexpr auto INPUT_DETECT_MAXIMUM_TIME = std::chrono::seconds(5); // Ignore the mouse-click when queuing more buttons with "alternate mappings" enabled. constexpr auto INPUT_DETECT_ENDING_IGNORE_TIME = std::chrono::milliseconds(50); +bool ContainsAnalogInput(const ciface::Core::InputDetector::Results& results) +{ + return std::ranges::any_of(results, [](auto& detection) { return detection.smoothness > 1; }); +} + class MappingProcessor : public QWidget { public: @@ -75,24 +80,40 @@ public: m_input_detector->Update(INPUT_DETECT_INITIAL_TIME, confirmation_time, INPUT_DETECT_MAXIMUM_TIME); - if (m_input_detector->IsComplete()) - { - auto* const button = m_clicked_mapping_buttons.back(); + if (!m_input_detector->IsComplete()) + return; - if (!FinalizeMapping(m_input_detector->TakeResults())) + auto* const button = m_clicked_mapping_buttons.front(); + + auto results = m_input_detector->TakeResults(); + if (!FinalizeMapping(&results)) + { + // No inputs detected. Cancel this and any other queued mappings. + CancelMapping(); + } + else if (m_parent->IsIterativeMappingEnabled() && m_clicked_mapping_buttons.empty()) + { + button->QueueNextButtonMapping(); + + if (m_clicked_mapping_buttons.empty()) + return; + + // Skip "Modifier" mappings when using analog inputs. + auto* next_button = m_clicked_mapping_buttons.front(); + if (next_button->GetControlType() == MappingButton::ControlType::ModifierInput && + ContainsAnalogInput(results)) { - // No inputs detected. Cancel this and any other queued mappings. - CancelMapping(); - } - else if (m_parent->IsIterativeMappingEnabled() && m_clicked_mapping_buttons.empty()) - { - button->QueueNextButtonMapping(); + // Clear "Modifier" mapping and queue the next button. + SetButtonExpression(next_button, ""); + UnQueueInputDetection(next_button); + next_button->QueueNextButtonMapping(); } } } - bool FinalizeMapping(ciface::Core::InputDetector::Results detections) + bool FinalizeMapping(ciface::Core::InputDetector::Results* detections_ptr) { + auto& detections = *detections_ptr; if (!ciface::MappingCommon::ContainsCompleteDetection(detections)) return false; @@ -100,18 +121,22 @@ public: const auto& default_device = m_parent->GetController()->GetDefaultDevice(); auto& button = m_clicked_mapping_buttons.front(); - auto* const control_reference = button->GetControlReference(); + SetButtonExpression( + button, BuildExpression(detections, default_device, ciface::MappingCommon::Quote::On)); - control_reference->SetExpression( - BuildExpression(detections, default_device, ciface::MappingCommon::Quote::On)); - m_parent->Save(); - - m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, - control_reference); UnQueueInputDetection(button); return true; } + void SetButtonExpression(MappingButton* button, const std::string& expression) + { + auto* const control_reference = button->GetControlReference(); + control_reference->SetExpression(expression); + m_parent->Save(); + m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, + control_reference); + } + void UpdateInputDetectionStartTimer() { m_input_detector.reset(); @@ -146,7 +171,7 @@ public: auto results = m_input_detector->TakeResults(); ciface::MappingCommon::RemoveDetectionsAfterTimePoint( &results, ciface::Core::DeviceContainer::Clock::now() - INPUT_DETECT_ENDING_IGNORE_TIME); - FinalizeMapping(std::move(results)); + FinalizeMapping(&results); } UpdateInputDetectionStartTimer(); } diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp index 442fff464f..036e9c512e 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp @@ -313,7 +313,16 @@ QGroupBox* MappingWidget::CreateControlsBox(const QString& name, ControllerEmu:: void MappingWidget::CreateControl(const ControllerEmu::Control* control, QFormLayout* layout, bool indicator) { - auto* const button = new MappingButton(this, control->control_ref.get()); + // I know this check is terrible, but it's just UI code. + const bool is_modifier = control->name == "Modifier"; + + using ControlType = MappingButton::ControlType; + const auto control_type = + control->control_ref->IsInput() ? + (is_modifier ? ControlType::ModifierInput : ControlType::NormalInput) : + ControlType::Output; + + auto* const button = new MappingButton(this, control->control_ref.get(), control_type); if (control->control_ref->IsInput()) {