Merge pull request #13320 from jordan-woyak/detect-alternations

DolphinQt/Mapping: Add setting to enable waiting for alternate mappings.
This commit is contained in:
JMC47 2025-02-15 12:59:23 -05:00 committed by GitHub
commit a8fae9b826
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 93 additions and 46 deletions

View File

@ -19,8 +19,10 @@
namespace MappingCommon namespace MappingCommon
{ {
constexpr auto INPUT_DETECT_INITIAL_TIME = std::chrono::seconds(3); 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); 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 class MappingProcessor : public QWidget
{ {
@ -50,7 +52,7 @@ public:
button->StartMapping(); button->StartMapping();
std::vector device_strings{default_device.ToString()}; std::vector device_strings{default_device.ToString()};
if (m_parent->IsMappingAllDevices()) if (m_parent->IsCreateOtherDeviceMappingsEnabled())
device_strings = g_controller_interface.GetAllDeviceStrings(); device_strings = g_controller_interface.GetAllDeviceStrings();
m_input_detector = std::make_unique<ciface::Core::InputDetector>(); m_input_detector = std::make_unique<ciface::Core::InputDetector>();
@ -63,20 +65,26 @@ public:
if (!m_input_detector) if (!m_input_detector)
return; 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); INPUT_DETECT_MAXIMUM_TIME);
if (m_input_detector->IsComplete()) 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. // No inputs detected. Cancel this and any other queued mappings.
if (detections.empty()) if (!FinalizeMapping(m_input_detector->TakeResults()))
{
CancelMapping(); CancelMapping();
return;
} }
}
bool FinalizeMapping(ciface::Core::InputDetector::Results detections)
{
if (!ciface::MappingCommon::ContainsCompleteDetection(detections))
return false;
ciface::MappingCommon::RemoveSpuriousTriggerCombinations(&detections);
const auto& default_device = m_parent->GetController()->GetDefaultDevice(); const auto& default_device = m_parent->GetController()->GetDefaultDevice();
auto& button = m_clicked_mapping_buttons.front(); auto& button = m_clicked_mapping_buttons.front();
@ -89,7 +97,7 @@ public:
m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, m_parent->GetController()->UpdateSingleControlReference(g_controller_interface,
control_reference); control_reference);
UnQueueInputDetection(button); UnQueueInputDetection(button);
} return true;
} }
void UpdateInputDetectionStartTimer() void UpdateInputDetectionStartTimer()
@ -121,6 +129,15 @@ public:
button->setText(QStringLiteral("[ ... ]")); button->setText(QStringLiteral("[ ... ]"));
m_clicked_mapping_buttons.push_back(button); 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(); UpdateInputDetectionStartTimer();
} }

View File

@ -111,11 +111,15 @@ void MappingWindow::CreateDevicesLayout()
const auto refresh_action = new QAction(tr("Refresh"), options); const auto refresh_action = new QAction(tr("Refresh"), options);
connect(refresh_action, &QAction::triggered, this, &MappingWindow::RefreshDevices); connect(refresh_action, &QAction::triggered, this, &MappingWindow::RefreshDevices);
m_all_devices_action = new QAction(tr("Create mappings for other devices"), options); m_other_device_mappings = new QAction(tr("Create Mappings for Other Devices"), options);
m_all_devices_action->setCheckable(true); 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(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); options->setDefaultAction(refresh_action);
m_devices_combo->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); m_devices_combo->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
@ -354,9 +358,14 @@ void MappingWindow::OnSelectDevice(int)
m_controller->UpdateReferences(g_controller_interface); m_controller->UpdateReferences(g_controller_interface);
} }
bool MappingWindow::IsMappingAllDevices() const bool MappingWindow::IsCreateOtherDeviceMappingsEnabled() const
{ {
return m_all_devices_action->isChecked(); return m_other_device_mappings->isChecked();
}
bool MappingWindow::IsWaitForAlternateMappingsEnabled() const
{
return m_wait_for_alternate_mappings->isChecked();
} }
void MappingWindow::RefreshDevices() void MappingWindow::RefreshDevices()

View File

@ -51,7 +51,8 @@ public:
int GetPort() const; int GetPort() const;
ControllerEmu::EmulatedController* GetController() const; ControllerEmu::EmulatedController* GetController() const;
bool IsMappingAllDevices() const; bool IsCreateOtherDeviceMappingsEnabled() const;
bool IsWaitForAlternateMappingsEnabled() const;
void ShowExtensionMotionTabs(bool show); void ShowExtensionMotionTabs(bool show);
void ActivateExtensionTab(); void ActivateExtensionTab();
@ -103,7 +104,8 @@ private:
QGroupBox* m_devices_box; QGroupBox* m_devices_box;
QHBoxLayout* m_devices_layout; QHBoxLayout* m_devices_layout;
QComboBox* m_devices_combo; QComboBox* m_devices_combo;
QAction* m_all_devices_action; QAction* m_other_device_mappings;
QAction* m_wait_for_alternate_mappings;
// Profiles // Profiles
QGroupBox* m_profiles_box; QGroupBox* m_profiles_box;

View File

@ -491,7 +491,7 @@ void InputDetector::Update(std::chrono::milliseconds initial_wait,
Detection new_detection; Detection new_detection;
new_detection.device = device_state.device; new_detection.device = device_state.device;
new_detection.input = input_state.input; new_detection.input = input_state.input;
new_detection.press_time = Clock::now(); new_detection.press_time = now;
new_detection.smoothness = smoothness; new_detection.smoothness = smoothness;
// We found an input. Add it to our detections. // We found an input. Add it to our detections.
@ -516,12 +516,12 @@ bool InputDetector::IsComplete() const
return !m_state; return !m_state;
} }
auto InputDetector::GetResults() const -> const std::vector<Detection>& auto InputDetector::GetResults() const -> const Results&
{ {
return m_detections; return m_detections;
} }
auto InputDetector::TakeResults() -> std::vector<Detection> auto InputDetector::TakeResults() -> Results
{ {
return std::move(m_detections); return std::move(m_detections);
} }

View File

@ -250,6 +250,7 @@ class InputDetector
{ {
public: public:
using Detection = DeviceContainer::InputDetection; using Detection = DeviceContainer::InputDetection;
using Results = std::vector<Detection>;
InputDetector(); InputDetector();
~InputDetector(); ~InputDetector();
@ -259,16 +260,16 @@ public:
std::chrono::milliseconds maximum_wait); std::chrono::milliseconds maximum_wait);
bool IsComplete() const; bool IsComplete() const;
const std::vector<Detection>& GetResults() const; const Results& GetResults() const;
// move-return'd to prevent copying. // move-return'd to prevent copying.
std::vector<Detection> TakeResults(); Results TakeResults();
private: private:
struct Impl; struct Impl;
Clock::time_point m_start_time; Clock::time_point m_start_time;
std::vector<Detection> m_detections; Results m_detections;
std::unique_ptr<Impl> m_state; std::unique_ptr<Impl> m_state;
}; };

View File

@ -51,11 +51,10 @@ std::string GetExpressionForControl(const std::string& control_name,
return expr; return expr;
} }
std::string std::string BuildExpression(const Core::InputDetector::Results& detections,
BuildExpression(const std::vector<ciface::Core::DeviceContainer::InputDetection>& detections,
const ciface::Core::DeviceQualifier& default_device, Quote quote) const ciface::Core::DeviceQualifier& default_device, Quote quote)
{ {
std::vector<const ciface::Core::DeviceContainer::InputDetection*> pressed_inputs; std::vector<const Core::InputDetector::Detection*> pressed_inputs;
std::vector<std::string> alternations; std::vector<std::string> alternations;
@ -135,8 +134,7 @@ BuildExpression(const std::vector<ciface::Core::DeviceContainer::InputDetection>
return fmt::to_string(fmt::join(alternations, "|")); return fmt::to_string(fmt::join(alternations, "|"));
} }
void RemoveSpuriousTriggerCombinations( void RemoveSpuriousTriggerCombinations(Core::InputDetector::Results* detections)
std::vector<ciface::Core::DeviceContainer::InputDetection>* detections)
{ {
const auto is_spurious = [&](const auto& detection) { const auto is_spurious = [&](const auto& detection) {
return std::ranges::any_of(*detections, [&](const auto& d) { return std::ranges::any_of(*detections, [&](const auto& d) {
@ -149,4 +147,21 @@ void RemoveSpuriousTriggerCombinations(
std::erase_if(*detections, is_spurious); 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 } // namespace ciface::MappingCommon

View File

@ -17,13 +17,16 @@ enum class Quote
}; };
std::string GetExpressionForControl(const std::string& control_name, std::string GetExpressionForControl(const std::string& control_name,
const ciface::Core::DeviceQualifier& control_device, const Core::DeviceQualifier& control_device,
const ciface::Core::DeviceQualifier& default_device, const Core::DeviceQualifier& default_device,
Quote quote = Quote::On); Quote quote = Quote::On);
std::string BuildExpression(const std::vector<ciface::Core::DeviceContainer::InputDetection>&, std::string BuildExpression(const Core::InputDetector::Results&,
const ciface::Core::DeviceQualifier& default_device, Quote quote); const Core::DeviceQualifier& default_device, Quote quote);
void RemoveSpuriousTriggerCombinations(std::vector<ciface::Core::DeviceContainer::InputDetection>*); 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 } // namespace ciface::MappingCommon