summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJMC47 <JMC4789@gmail.com>2025-02-15 16:19:02 -0500
committerGitHub <noreply@github.com>2025-02-15 16:19:02 -0500
commitce0f9139e9d7f1af4f863a9e0ba48c620a4fdbe8 (patch)
treea4891e0b75f4ed1bac9be690f7883dc57f17d8f5
parenta8fae9b82683dd0ea766cb8c282b414707962255 (diff)
parent66624abb124b2b1aa0fc7381d14402014ef86f66 (diff)
Merge pull request #13323 from jordan-woyak/iterative-mapping
DolphinQt: Add setting to enable iterative input mappings.
-rw-r--r--Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp34
-rw-r--r--Source/Core/DolphinQt/Config/Mapping/MappingButton.h15
-rw-r--r--Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp75
-rw-r--r--Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp22
-rw-r--r--Source/Core/DolphinQt/Config/Mapping/MappingWidget.h2
-rw-r--r--Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp9
-rw-r--r--Source/Core/DolphinQt/Config/Mapping/MappingWindow.h2
7 files changed, 111 insertions, 48 deletions
diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp
index 3af4c2e00e..24e7be9bf8 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,11 @@ static QString RefToDisplayString(ControlReference* ref)
return expression;
}
-bool MappingButton::IsInput() const
+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}
{
- 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 +82,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 +106,6 @@ void MappingButton::Clicked()
return;
}
- m_is_mapping = true;
m_mapping_window->QueueInputDetection(this);
}
@@ -131,14 +122,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())
@@ -159,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 76bb412079..7cac489ad0 100644
--- a/Source/Core/DolphinQt/Config/Mapping/MappingButton.h
+++ b/Source/Core/DolphinQt/Config/Mapping/MappingButton.h
@@ -15,14 +15,21 @@ 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);
- bool IsInput() const;
ControlReference* GetControlReference();
- void StartMapping();
+ ControlType GetControlType() const;
signals:
void ConfigChanged();
+ void QueueNextButtonMapping();
private:
void Clear();
@@ -33,5 +40,5 @@ private:
MappingWindow* const m_mapping_window;
ControlReference* const m_reference;
- bool m_is_mapping = false;
+ const ControlType m_control_type;
};
diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp
index 37407f2298..d94502b104 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"
@@ -24,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:
@@ -49,7 +55,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())
@@ -71,16 +80,40 @@ public:
m_input_detector->Update(INPUT_DETECT_INITIAL_TIME, confirmation_time,
INPUT_DETECT_MAXIMUM_TIME);
- if (m_input_detector->IsComplete())
+ if (!m_input_detector->IsComplete())
+ return;
+
+ 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.
- if (!FinalizeMapping(m_input_detector->TakeResults()))
- CancelMapping();
+ 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))
+ {
+ // 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;
@@ -88,16 +121,20 @@ 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();
+ 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);
- UnQueueInputDetection(button);
- return true;
}
void UpdateInputDetectionStartTimer()
@@ -110,22 +147,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);
@@ -136,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 82fe042f9a..036e9c512e 100644
--- a/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp
+++ b/Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp
@@ -313,7 +313,27 @@ 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())
+ {
+ 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 <QWidget>
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;