From 6e7e808b6614f98674dba7290bea2ea044c4d5d2 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Wed, 29 Jan 2025 02:02:46 -0600 Subject: [PATCH] DolphinQt/Mapping: Add setting to enable waiting for alternate mappings using the OR-operator. --- .../Config/Mapping/MappingCommon.cpp | 57 ++++++++++++------- .../Config/Mapping/MappingWindow.cpp | 19 +++++-- .../DolphinQt/Config/Mapping/MappingWindow.h | 6 +- .../ControllerInterface/CoreDevice.cpp | 6 +- .../ControllerInterface/CoreDevice.h | 7 ++- .../ControllerInterface/MappingCommon.cpp | 27 +++++++-- .../ControllerInterface/MappingCommon.h | 13 +++-- 7 files changed, 91 insertions(+), 44 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp index edce169c28a1..37407f229843 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp @@ -19,8 +19,10 @@ namespace MappingCommon { constexpr auto INPUT_DETECT_INITIAL_TIME = std::chrono::seconds(3); -constexpr auto INPUT_DETECT_CONFIRMATION_TIME = std::chrono::milliseconds(0); +constexpr auto INPUT_DETECT_CONFIRMATION_TIME = std::chrono::milliseconds(750); 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); class MappingProcessor : public QWidget { @@ -50,7 +52,7 @@ class MappingProcessor : public QWidget button->StartMapping(); std::vector device_strings{default_device.ToString()}; - if (m_parent->IsMappingAllDevices()) + if (m_parent->IsCreateOtherDeviceMappingsEnabled()) device_strings = g_controller_interface.GetAllDeviceStrings(); m_input_detector = std::make_unique(); @@ -63,33 +65,39 @@ class MappingProcessor : public QWidget if (!m_input_detector) return; - m_input_detector->Update(INPUT_DETECT_INITIAL_TIME, INPUT_DETECT_CONFIRMATION_TIME, + const auto confirmation_time = + INPUT_DETECT_CONFIRMATION_TIME * (m_parent->IsWaitForAlternateMappingsEnabled() ? 1 : 0); + + m_input_detector->Update(INPUT_DETECT_INITIAL_TIME, confirmation_time, INPUT_DETECT_MAXIMUM_TIME); if (m_input_detector->IsComplete()) { - auto detections = m_input_detector->TakeResults(); - ciface::MappingCommon::RemoveSpuriousTriggerCombinations(&detections); - // No inputs detected. Cancel this and any other queued mappings. - if (detections.empty()) - { + if (!FinalizeMapping(m_input_detector->TakeResults())) CancelMapping(); - return; - } + } + } - const auto& default_device = m_parent->GetController()->GetDefaultDevice(); - auto& button = m_clicked_mapping_buttons.front(); - auto* const control_reference = button->GetControlReference(); + bool FinalizeMapping(ciface::Core::InputDetector::Results detections) + { + if (!ciface::MappingCommon::ContainsCompleteDetection(detections)) + return false; - control_reference->SetExpression( - BuildExpression(detections, default_device, ciface::MappingCommon::Quote::On)); - m_parent->Save(); + ciface::MappingCommon::RemoveSpuriousTriggerCombinations(&detections); - m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, - control_reference); - UnQueueInputDetection(button); - } + const auto& default_device = m_parent->GetController()->GetDefaultDevice(); + auto& button = m_clicked_mapping_buttons.front(); + auto* const control_reference = button->GetControlReference(); + + 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 UpdateInputDetectionStartTimer() @@ -121,6 +129,15 @@ class MappingProcessor : public QWidget button->setText(QStringLiteral("[ ... ]")); m_clicked_mapping_buttons.push_back(button); + + if (m_input_detector) + { + // Ignore the mouse-click that queued this new detection and finalize the current mapping. + auto results = m_input_detector->TakeResults(); + ciface::MappingCommon::RemoveDetectionsAfterTimePoint( + &results, ciface::Core::DeviceContainer::Clock::now() - INPUT_DETECT_ENDING_IGNORE_TIME); + FinalizeMapping(std::move(results)); + } UpdateInputDetectionStartTimer(); } diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index 46b4b1f30211..665d5e965925 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -111,11 +111,15 @@ void MappingWindow::CreateDevicesLayout() const auto refresh_action = new QAction(tr("Refresh"), options); connect(refresh_action, &QAction::triggered, this, &MappingWindow::RefreshDevices); - m_all_devices_action = new QAction(tr("Create mappings for other devices"), options); - m_all_devices_action->setCheckable(true); + m_other_device_mappings = new QAction(tr("Create Mappings for Other Devices"), options); + m_other_device_mappings->setCheckable(true); + + m_wait_for_alternate_mappings = new QAction(tr("Wait for Alternate Input Mappings"), options); + m_wait_for_alternate_mappings->setCheckable(true); options->addAction(refresh_action); - options->addAction(m_all_devices_action); + options->addAction(m_other_device_mappings); + options->addAction(m_wait_for_alternate_mappings); options->setDefaultAction(refresh_action); m_devices_combo->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); @@ -354,9 +358,14 @@ void MappingWindow::OnSelectDevice(int) m_controller->UpdateReferences(g_controller_interface); } -bool MappingWindow::IsMappingAllDevices() const +bool MappingWindow::IsCreateOtherDeviceMappingsEnabled() const +{ + return m_other_device_mappings->isChecked(); +} + +bool MappingWindow::IsWaitForAlternateMappingsEnabled() const { - return m_all_devices_action->isChecked(); + return m_wait_for_alternate_mappings->isChecked(); } void MappingWindow::RefreshDevices() diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h index e51df977f357..10bf590f352f 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h @@ -51,7 +51,8 @@ class MappingWindow final : public QDialog int GetPort() const; ControllerEmu::EmulatedController* GetController() const; - bool IsMappingAllDevices() const; + bool IsCreateOtherDeviceMappingsEnabled() const; + bool IsWaitForAlternateMappingsEnabled() const; void ShowExtensionMotionTabs(bool show); void ActivateExtensionTab(); @@ -103,7 +104,8 @@ class MappingWindow final : public QDialog QGroupBox* m_devices_box; QHBoxLayout* m_devices_layout; QComboBox* m_devices_combo; - QAction* m_all_devices_action; + QAction* m_other_device_mappings; + QAction* m_wait_for_alternate_mappings; // Profiles QGroupBox* m_profiles_box; diff --git a/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp b/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp index b9141658e633..3a29d4c98f0c 100644 --- a/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp +++ b/Source/Core/InputCommon/ControllerInterface/CoreDevice.cpp @@ -491,7 +491,7 @@ void InputDetector::Update(std::chrono::milliseconds initial_wait, Detection new_detection; new_detection.device = device_state.device; new_detection.input = input_state.input; - new_detection.press_time = Clock::now(); + new_detection.press_time = now; new_detection.smoothness = smoothness; // We found an input. Add it to our detections. @@ -516,12 +516,12 @@ bool InputDetector::IsComplete() const return !m_state; } -auto InputDetector::GetResults() const -> const std::vector& +auto InputDetector::GetResults() const -> const Results& { return m_detections; } -auto InputDetector::TakeResults() -> std::vector +auto InputDetector::TakeResults() -> Results { return std::move(m_detections); } diff --git a/Source/Core/InputCommon/ControllerInterface/CoreDevice.h b/Source/Core/InputCommon/ControllerInterface/CoreDevice.h index 0370fce8942a..92f53df9fa13 100644 --- a/Source/Core/InputCommon/ControllerInterface/CoreDevice.h +++ b/Source/Core/InputCommon/ControllerInterface/CoreDevice.h @@ -250,6 +250,7 @@ class InputDetector { public: using Detection = DeviceContainer::InputDetection; + using Results = std::vector; InputDetector(); ~InputDetector(); @@ -259,16 +260,16 @@ class InputDetector std::chrono::milliseconds maximum_wait); bool IsComplete() const; - const std::vector& GetResults() const; + const Results& GetResults() const; // move-return'd to prevent copying. - std::vector TakeResults(); + Results TakeResults(); private: struct Impl; Clock::time_point m_start_time; - std::vector m_detections; + Results m_detections; std::unique_ptr m_state; }; diff --git a/Source/Core/InputCommon/ControllerInterface/MappingCommon.cpp b/Source/Core/InputCommon/ControllerInterface/MappingCommon.cpp index cb06a7ed3088..3765ef24a68a 100644 --- a/Source/Core/InputCommon/ControllerInterface/MappingCommon.cpp +++ b/Source/Core/InputCommon/ControllerInterface/MappingCommon.cpp @@ -51,11 +51,10 @@ std::string GetExpressionForControl(const std::string& control_name, return expr; } -std::string -BuildExpression(const std::vector& detections, - const ciface::Core::DeviceQualifier& default_device, Quote quote) +std::string BuildExpression(const Core::InputDetector::Results& detections, + const ciface::Core::DeviceQualifier& default_device, Quote quote) { - std::vector pressed_inputs; + std::vector pressed_inputs; std::vector alternations; @@ -135,8 +134,7 @@ BuildExpression(const std::vector return fmt::to_string(fmt::join(alternations, "|")); } -void RemoveSpuriousTriggerCombinations( - std::vector* detections) +void RemoveSpuriousTriggerCombinations(Core::InputDetector::Results* detections) { const auto is_spurious = [&](const auto& detection) { return std::ranges::any_of(*detections, [&](const auto& d) { @@ -149,4 +147,21 @@ void RemoveSpuriousTriggerCombinations( std::erase_if(*detections, is_spurious); } +void RemoveDetectionsAfterTimePoint(Core::InputDetector::Results* results, + Core::DeviceContainer::Clock::time_point after) +{ + const auto is_after_time = [&](const Core::InputDetector::Detection& detection) { + return detection.release_time.value_or(after) >= after; + }; + + std::erase_if(*results, is_after_time); +} + +bool ContainsCompleteDetection(const Core::InputDetector::Results& results) +{ + return std::ranges::any_of(results, [](const Core::InputDetector::Detection& detection) { + return detection.release_time.has_value(); + }); +} + } // namespace ciface::MappingCommon diff --git a/Source/Core/InputCommon/ControllerInterface/MappingCommon.h b/Source/Core/InputCommon/ControllerInterface/MappingCommon.h index 182212555737..f58813a50c85 100644 --- a/Source/Core/InputCommon/ControllerInterface/MappingCommon.h +++ b/Source/Core/InputCommon/ControllerInterface/MappingCommon.h @@ -17,13 +17,16 @@ enum class Quote }; std::string GetExpressionForControl(const std::string& control_name, - const ciface::Core::DeviceQualifier& control_device, - const ciface::Core::DeviceQualifier& default_device, + const Core::DeviceQualifier& control_device, + const Core::DeviceQualifier& default_device, Quote quote = Quote::On); -std::string BuildExpression(const std::vector&, - const ciface::Core::DeviceQualifier& default_device, Quote quote); +std::string BuildExpression(const Core::InputDetector::Results&, + const Core::DeviceQualifier& default_device, Quote quote); -void RemoveSpuriousTriggerCombinations(std::vector*); +void RemoveSpuriousTriggerCombinations(Core::InputDetector::Results*); +void RemoveDetectionsAfterTimePoint(Core::InputDetector::Results*, + Core::DeviceContainer::Clock::time_point after); +bool ContainsCompleteDetection(const Core::InputDetector::Results&); } // namespace ciface::MappingCommon