Merge pull request #13323 from jordan-woyak/iterative-mapping
DolphinQt: Add setting to enable iterative input mappings.
This commit is contained in:
commit
ce0f9139e9
|
@ -11,8 +11,6 @@
|
||||||
#include "DolphinQt/Config/Mapping/IOWindow.h"
|
#include "DolphinQt/Config/Mapping/IOWindow.h"
|
||||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||||
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
||||||
#include "DolphinQt/QtUtils/BlockUserInputFilter.h"
|
|
||||||
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
|
|
||||||
|
|
||||||
#include "InputCommon/ControlReference/ControlReference.h"
|
#include "InputCommon/ControlReference/ControlReference.h"
|
||||||
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||||
|
@ -67,15 +65,11 @@ static QString RefToDisplayString(ControlReference* ref)
|
||||||
return expression;
|
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();
|
if (m_reference->IsInput())
|
||||||
}
|
|
||||||
|
|
||||||
MappingButton::MappingButton(MappingWidget* parent, ControlReference* ref)
|
|
||||||
: ElidedButton(RefToDisplayString(ref)), m_mapping_window(parent->GetParent()), m_reference(ref)
|
|
||||||
{
|
|
||||||
if (IsInput())
|
|
||||||
{
|
{
|
||||||
setToolTip(
|
setToolTip(
|
||||||
tr("Left-click to detect input.\nMiddle-click to clear.\nRight-click for more options."));
|
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(this, &MappingButton::clicked, this, &MappingButton::Clicked);
|
||||||
|
|
||||||
connect(parent, &MappingWidget::ConfigChanged, this, &MappingButton::ConfigChanged);
|
connect(parent, &MappingWidget::ConfigChanged, this, &MappingButton::ConfigChanged);
|
||||||
connect(this, &MappingButton::ConfigChanged, [this] {
|
connect(this, &MappingButton::ConfigChanged,
|
||||||
setText(RefToDisplayString(m_reference));
|
[this] { setText(RefToDisplayString(m_reference)); });
|
||||||
m_is_mapping = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MappingButton::AdvancedPressed()
|
void MappingButton::AdvancedPressed()
|
||||||
|
@ -114,7 +106,6 @@ void MappingButton::Clicked()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_is_mapping = true;
|
|
||||||
m_mapping_window->QueueInputDetection(this);
|
m_mapping_window->QueueInputDetection(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,14 +122,6 @@ void MappingButton::Clear()
|
||||||
m_mapping_window->UnQueueInputDetection(this);
|
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)
|
void MappingButton::mouseReleaseEvent(QMouseEvent* event)
|
||||||
{
|
{
|
||||||
switch (event->button())
|
switch (event->button())
|
||||||
|
@ -159,3 +142,8 @@ ControlReference* MappingButton::GetControlReference()
|
||||||
{
|
{
|
||||||
return m_reference;
|
return m_reference;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto MappingButton::GetControlType() const -> ControlType
|
||||||
|
{
|
||||||
|
return m_control_type;
|
||||||
|
}
|
||||||
|
|
|
@ -15,14 +15,21 @@ class MappingButton : public ElidedButton
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
MappingButton(MappingWidget* widget, ControlReference* ref);
|
enum class ControlType
|
||||||
|
{
|
||||||
|
NormalInput,
|
||||||
|
ModifierInput,
|
||||||
|
Output,
|
||||||
|
};
|
||||||
|
|
||||||
|
MappingButton(MappingWidget* widget, ControlReference* ref, ControlType type);
|
||||||
|
|
||||||
bool IsInput() const;
|
|
||||||
ControlReference* GetControlReference();
|
ControlReference* GetControlReference();
|
||||||
void StartMapping();
|
ControlType GetControlType() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void ConfigChanged();
|
void ConfigChanged();
|
||||||
|
void QueueNextButtonMapping();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Clear();
|
void Clear();
|
||||||
|
@ -33,5 +40,5 @@ private:
|
||||||
|
|
||||||
MappingWindow* const m_mapping_window;
|
MappingWindow* const m_mapping_window;
|
||||||
ControlReference* const m_reference;
|
ControlReference* const m_reference;
|
||||||
bool m_is_mapping = false;
|
const ControlType m_control_type;
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include "DolphinQt/Config/Mapping/MappingButton.h"
|
#include "DolphinQt/Config/Mapping/MappingButton.h"
|
||||||
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
||||||
|
#include "DolphinQt/QtUtils/BlockUserInputFilter.h"
|
||||||
|
|
||||||
#include "InputCommon/ControlReference/ControlReference.h"
|
#include "InputCommon/ControlReference/ControlReference.h"
|
||||||
#include "InputCommon/ControllerEmu/ControllerEmu.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.
|
// Ignore the mouse-click when queuing more buttons with "alternate mappings" enabled.
|
||||||
constexpr auto INPUT_DETECT_ENDING_IGNORE_TIME = std::chrono::milliseconds(50);
|
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
|
class MappingProcessor : public QWidget
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -49,7 +55,10 @@ public:
|
||||||
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();
|
||||||
|
|
||||||
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()};
|
std::vector device_strings{default_device.ToString()};
|
||||||
if (m_parent->IsCreateOtherDeviceMappingsEnabled())
|
if (m_parent->IsCreateOtherDeviceMappingsEnabled())
|
||||||
|
@ -71,16 +80,40 @@ public:
|
||||||
m_input_detector->Update(INPUT_DETECT_INITIAL_TIME, confirmation_time,
|
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())
|
||||||
|
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.
|
// 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))
|
if (!ciface::MappingCommon::ContainsCompleteDetection(detections))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -88,18 +121,22 @@ public:
|
||||||
|
|
||||||
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();
|
||||||
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);
|
UnQueueInputDetection(button);
|
||||||
return true;
|
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()
|
void UpdateInputDetectionStartTimer()
|
||||||
{
|
{
|
||||||
m_input_detector.reset();
|
m_input_detector.reset();
|
||||||
|
@ -110,22 +147,20 @@ public:
|
||||||
m_input_detection_start_timer->start(INPUT_DETECT_INITIAL_DELAY);
|
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();
|
button->ConfigChanged();
|
||||||
UpdateInputDetectionStartTimer();
|
UpdateInputDetectionStartTimer();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueueInputDetection(MappingButton* button)
|
void QueueInputDetection(MappingButton* button)
|
||||||
{
|
{
|
||||||
// UnQueue if already queued.
|
// Just UnQueue if already queued.
|
||||||
if (std::erase(m_clicked_mapping_buttons, button))
|
if (UnQueueInputDetection(button))
|
||||||
{
|
|
||||||
button->ConfigChanged();
|
|
||||||
UpdateInputDetectionStartTimer();
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
button->setText(QStringLiteral("[ ... ]"));
|
button->setText(QStringLiteral("[ ... ]"));
|
||||||
m_clicked_mapping_buttons.push_back(button);
|
m_clicked_mapping_buttons.push_back(button);
|
||||||
|
@ -136,7 +171,7 @@ public:
|
||||||
auto results = m_input_detector->TakeResults();
|
auto results = m_input_detector->TakeResults();
|
||||||
ciface::MappingCommon::RemoveDetectionsAfterTimePoint(
|
ciface::MappingCommon::RemoveDetectionsAfterTimePoint(
|
||||||
&results, ciface::Core::DeviceContainer::Clock::now() - INPUT_DETECT_ENDING_IGNORE_TIME);
|
&results, ciface::Core::DeviceContainer::Clock::now() - INPUT_DETECT_ENDING_IGNORE_TIME);
|
||||||
FinalizeMapping(std::move(results));
|
FinalizeMapping(&results);
|
||||||
}
|
}
|
||||||
UpdateInputDetectionStartTimer();
|
UpdateInputDetectionStartTimer();
|
||||||
}
|
}
|
||||||
|
|
|
@ -313,7 +313,27 @@ QGroupBox* MappingWidget::CreateControlsBox(const QString& name, ControllerEmu::
|
||||||
void MappingWidget::CreateControl(const ControllerEmu::Control* control, QFormLayout* layout,
|
void MappingWidget::CreateControl(const ControllerEmu::Control* control, QFormLayout* layout,
|
||||||
bool indicator)
|
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->setMinimumWidth(100);
|
||||||
button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
class InputConfig;
|
class InputConfig;
|
||||||
|
class MappingButton;
|
||||||
class MappingNumeric;
|
class MappingNumeric;
|
||||||
class MappingWindow;
|
class MappingWindow;
|
||||||
class QFormLayout;
|
class QFormLayout;
|
||||||
|
@ -55,4 +56,5 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MappingWindow* m_parent;
|
MappingWindow* m_parent;
|
||||||
|
MappingButton* m_previous_mapping_button = nullptr;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 = new QAction(tr("Wait for Alternate Input Mappings"), options);
|
||||||
m_wait_for_alternate_mappings->setCheckable(true);
|
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(refresh_action);
|
||||||
options->addAction(m_other_device_mappings);
|
options->addAction(m_other_device_mappings);
|
||||||
options->addAction(m_wait_for_alternate_mappings);
|
options->addAction(m_wait_for_alternate_mappings);
|
||||||
|
options->addAction(m_iterative_mapping);
|
||||||
options->setDefaultAction(refresh_action);
|
options->setDefaultAction(refresh_action);
|
||||||
|
|
||||||
m_devices_combo->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
m_devices_combo->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||||
|
@ -368,6 +372,11 @@ bool MappingWindow::IsWaitForAlternateMappingsEnabled() const
|
||||||
return m_wait_for_alternate_mappings->isChecked();
|
return m_wait_for_alternate_mappings->isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MappingWindow::IsIterativeMappingEnabled() const
|
||||||
|
{
|
||||||
|
return m_iterative_mapping->isChecked();
|
||||||
|
}
|
||||||
|
|
||||||
void MappingWindow::RefreshDevices()
|
void MappingWindow::RefreshDevices()
|
||||||
{
|
{
|
||||||
g_controller_interface.RefreshDevices();
|
g_controller_interface.RefreshDevices();
|
||||||
|
|
|
@ -53,6 +53,7 @@ public:
|
||||||
ControllerEmu::EmulatedController* GetController() const;
|
ControllerEmu::EmulatedController* GetController() const;
|
||||||
bool IsCreateOtherDeviceMappingsEnabled() const;
|
bool IsCreateOtherDeviceMappingsEnabled() const;
|
||||||
bool IsWaitForAlternateMappingsEnabled() const;
|
bool IsWaitForAlternateMappingsEnabled() const;
|
||||||
|
bool IsIterativeMappingEnabled() const;
|
||||||
void ShowExtensionMotionTabs(bool show);
|
void ShowExtensionMotionTabs(bool show);
|
||||||
void ActivateExtensionTab();
|
void ActivateExtensionTab();
|
||||||
|
|
||||||
|
@ -106,6 +107,7 @@ private:
|
||||||
QComboBox* m_devices_combo;
|
QComboBox* m_devices_combo;
|
||||||
QAction* m_other_device_mappings;
|
QAction* m_other_device_mappings;
|
||||||
QAction* m_wait_for_alternate_mappings;
|
QAction* m_wait_for_alternate_mappings;
|
||||||
|
QAction* m_iterative_mapping;
|
||||||
|
|
||||||
// Profiles
|
// Profiles
|
||||||
QGroupBox* m_profiles_box;
|
QGroupBox* m_profiles_box;
|
||||||
|
|
Loading…
Reference in New Issue