DolphinQt: Make input mapping and output testing non-blocking.
This commit is contained in:
parent
c25e400377
commit
1244b2d047
|
@ -4,7 +4,6 @@
|
|||
#include "DolphinQt/Config/Mapping/IOWindow.h"
|
||||
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
#include <QBrush>
|
||||
#include <QColor>
|
||||
|
@ -20,16 +19,15 @@
|
|||
#include <QSlider>
|
||||
#include <QSpinBox>
|
||||
#include <QTableWidget>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/Core.h"
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingCommon.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingIndicator.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
||||
#include "DolphinQt/QtUtils/BlockUserInputFilter.h"
|
||||
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "InputCommon/ControlReference/ControlReference.h"
|
||||
|
@ -40,6 +38,9 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
constexpr auto INPUT_DETECT_TIME = std::chrono::seconds(2);
|
||||
constexpr auto OUTPUT_TEST_TIME = std::chrono::seconds(2);
|
||||
|
||||
QTextCharFormat GetSpecialCharFormat()
|
||||
{
|
||||
QTextCharFormat format;
|
||||
|
@ -228,15 +229,17 @@ private:
|
|||
bool m_should_paint_state_indicator = false;
|
||||
};
|
||||
|
||||
IOWindow::IOWindow(MappingWidget* parent, ControllerEmu::EmulatedController* controller,
|
||||
IOWindow::IOWindow(MappingWindow* window, ControllerEmu::EmulatedController* controller,
|
||||
ControlReference* ref, IOWindow::Type type)
|
||||
: QDialog(parent), m_reference(ref), m_original_expression(ref->GetExpression()),
|
||||
: QDialog(window), m_reference(ref), m_original_expression(ref->GetExpression()),
|
||||
m_controller(controller), m_type(type)
|
||||
{
|
||||
SetQWidgetWindowDecorations(this);
|
||||
|
||||
CreateMainLayout();
|
||||
|
||||
connect(parent, &MappingWidget::Update, this, &IOWindow::Update);
|
||||
connect(parent->GetParent(), &MappingWindow::ConfigChanged, this, &IOWindow::ConfigChanged);
|
||||
connect(window, &MappingWindow::Update, this, &IOWindow::Update);
|
||||
connect(window, &MappingWindow::ConfigChanged, this, &IOWindow::ConfigChanged);
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, &IOWindow::ConfigChanged);
|
||||
|
||||
setWindowTitle(type == IOWindow::Type::Input ? tr("Configure Input") : tr("Configure Output"));
|
||||
|
@ -258,18 +261,24 @@ void IOWindow::CreateMainLayout()
|
|||
|
||||
m_devices_combo = new QComboBox();
|
||||
m_option_list = new QTableWidget();
|
||||
m_select_button = new QPushButton(tr("Select"));
|
||||
m_detect_button = new QPushButton(tr("Detect"), this);
|
||||
m_test_button = new QPushButton(tr("Test"), this);
|
||||
|
||||
m_select_button =
|
||||
new QPushButton(m_type == IOWindow::Type::Input ? tr("Insert Input") : tr("Insert Output"));
|
||||
m_detect_button = new QPushButton(tr("Detect Input"), this);
|
||||
m_test_button = new QPushButton(tr("Test Output"), this);
|
||||
m_button_box = new QDialogButtonBox();
|
||||
m_clear_button = new QPushButton(tr("Clear"));
|
||||
m_scalar_spinbox = new QSpinBox();
|
||||
|
||||
m_parse_text = new InputStateLineEdit([this] {
|
||||
const auto lock = m_controller->GetStateLock();
|
||||
return m_reference->GetState<ControlState>();
|
||||
});
|
||||
m_parse_text->setReadOnly(true);
|
||||
if (m_type == Type::Input)
|
||||
{
|
||||
m_parse_text = new InputStateLineEdit([this] { return m_reference->GetState<ControlState>(); });
|
||||
}
|
||||
else
|
||||
{
|
||||
m_parse_text = new InputStateLineEdit(
|
||||
[this] { return m_output_test_timer->isActive() * m_reference->range; });
|
||||
}
|
||||
|
||||
m_expression_text = new QPlainTextEdit();
|
||||
m_expression_text->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
|
||||
|
@ -419,11 +428,17 @@ void IOWindow::CreateMainLayout()
|
|||
m_button_box->addButton(m_clear_button, QDialogButtonBox::ActionRole);
|
||||
m_button_box->addButton(QDialogButtonBox::Ok);
|
||||
|
||||
m_output_test_timer = new QTimer(this);
|
||||
m_output_test_timer->setSingleShot(true);
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
void IOWindow::ConfigChanged()
|
||||
{
|
||||
emit DetectInputComplete();
|
||||
emit TestOutputComplete();
|
||||
|
||||
const QSignalBlocker blocker(this);
|
||||
const auto lock = ControllerEmu::EmulatedController::GetStateLock();
|
||||
|
||||
|
@ -444,6 +459,31 @@ void IOWindow::Update()
|
|||
{
|
||||
m_option_list->viewport()->update();
|
||||
m_parse_text->update();
|
||||
|
||||
if (!m_input_detector)
|
||||
return;
|
||||
|
||||
if (m_input_detector->IsComplete())
|
||||
{
|
||||
const auto results = m_input_detector->TakeResults();
|
||||
|
||||
emit DetectInputComplete();
|
||||
|
||||
if (results.empty())
|
||||
return;
|
||||
|
||||
// Select the first detected input.
|
||||
auto list = m_option_list->findItems(QString::fromStdString(results.front().input->GetName()),
|
||||
Qt::MatchFixedString);
|
||||
if (list.empty())
|
||||
return;
|
||||
|
||||
m_option_list->setCurrentItem(list.front());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_input_detector->Update(INPUT_DETECT_TIME, {}, INPUT_DETECT_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
void IOWindow::ConnectWidgets()
|
||||
|
@ -453,8 +493,50 @@ void IOWindow::ConnectWidgets()
|
|||
connect(&Settings::Instance(), &Settings::ReleaseDevices, this, &IOWindow::ReleaseDevices);
|
||||
connect(&Settings::Instance(), &Settings::DevicesChanged, this, &IOWindow::UpdateDeviceList);
|
||||
|
||||
connect(m_detect_button, &QPushButton::clicked, this, &IOWindow::OnDetectButtonPressed);
|
||||
connect(m_test_button, &QPushButton::clicked, this, &IOWindow::OnTestButtonPressed);
|
||||
// Input detection:
|
||||
// Clicking "Detect" button starts a timer before the actual detection.
|
||||
auto* const input_detect_start_timer = new QTimer(this);
|
||||
input_detect_start_timer->setSingleShot(true);
|
||||
connect(m_detect_button, &QPushButton::clicked, [this, input_detect_start_timer] {
|
||||
m_detect_button->setText(tr("[ ... ]"));
|
||||
input_detect_start_timer->start(MappingCommon::INPUT_DETECT_INITIAL_DELAY);
|
||||
});
|
||||
connect(input_detect_start_timer, &QTimer::timeout, [this] {
|
||||
m_detect_button->setText(tr("[ Press Now ]"));
|
||||
m_input_detector = std::make_unique<ciface::Core::InputDetector>();
|
||||
const auto lock = m_controller->GetStateLock();
|
||||
m_input_detector->Start(g_controller_interface, {m_devq.ToString()});
|
||||
QtUtils::InstallKeyboardBlocker(m_detect_button, this, &IOWindow::DetectInputComplete);
|
||||
});
|
||||
connect(this, &IOWindow::DetectInputComplete,
|
||||
[this, initial_text = m_detect_button->text(), input_detect_start_timer] {
|
||||
input_detect_start_timer->stop();
|
||||
m_input_detector.reset();
|
||||
m_detect_button->setText(initial_text);
|
||||
});
|
||||
|
||||
// Rumble testing:
|
||||
connect(m_test_button, &QPushButton::clicked, [this] {
|
||||
// Stop if already started.
|
||||
if (m_output_test_timer->isActive())
|
||||
{
|
||||
emit IOWindow::TestOutputComplete();
|
||||
return;
|
||||
}
|
||||
m_test_button->setText(QStringLiteral("[ ... ]"));
|
||||
m_output_test_timer->start(OUTPUT_TEST_TIME);
|
||||
const auto lock = m_controller->GetStateLock();
|
||||
m_reference->State(1.0);
|
||||
});
|
||||
connect(m_output_test_timer, &QTimer::timeout,
|
||||
[this, initial_text = m_test_button->text()] { emit TestOutputComplete(); });
|
||||
connect(this, &IOWindow::TestOutputComplete, [this, initial_text = m_test_button->text()] {
|
||||
m_output_test_timer->stop();
|
||||
m_test_button->setText(initial_text);
|
||||
const auto lock = m_controller->GetStateLock();
|
||||
m_reference->State(0.0);
|
||||
});
|
||||
connect(this, &QWidget::destroyed, this, &IOWindow::TestOutputComplete);
|
||||
|
||||
connect(m_button_box, &QDialogButtonBox::clicked, this, &IOWindow::OnDialogButtonPressed);
|
||||
connect(m_devices_combo, &QComboBox::currentTextChanged, this, &IOWindow::OnDeviceChanged);
|
||||
|
@ -546,30 +628,10 @@ void IOWindow::OnDialogButtonPressed(QAbstractButton* button)
|
|||
}
|
||||
}
|
||||
|
||||
void IOWindow::OnDetectButtonPressed()
|
||||
{
|
||||
const auto expression =
|
||||
MappingCommon::DetectExpression(m_detect_button, g_controller_interface, {m_devq.ToString()},
|
||||
m_devq, ciface::MappingCommon::Quote::Off);
|
||||
|
||||
if (expression.isEmpty())
|
||||
return;
|
||||
|
||||
const auto list = m_option_list->findItems(expression, Qt::MatchFixedString);
|
||||
|
||||
// Try to select the first. If this fails, the last selected item would still appear as such
|
||||
if (!list.empty())
|
||||
m_option_list->setCurrentItem(list[0]);
|
||||
}
|
||||
|
||||
void IOWindow::OnTestButtonPressed()
|
||||
{
|
||||
MappingCommon::TestOutput(m_test_button, static_cast<OutputReference*>(m_reference));
|
||||
}
|
||||
|
||||
void IOWindow::OnRangeChanged(int value)
|
||||
{
|
||||
m_reference->range = value / 100.0;
|
||||
emit TestOutputComplete();
|
||||
}
|
||||
|
||||
void IOWindow::ReleaseDevices()
|
||||
|
@ -670,6 +732,8 @@ void IOWindow::UpdateDeviceList()
|
|||
|
||||
void IOWindow::UpdateExpression(std::string new_expression, UpdateMode mode)
|
||||
{
|
||||
emit TestOutputComplete();
|
||||
|
||||
const auto lock = m_controller->GetStateLock();
|
||||
if (mode != UpdateMode::Force && new_expression == m_reference->GetExpression())
|
||||
return;
|
||||
|
@ -719,6 +783,7 @@ InputStateDelegate::InputStateDelegate(IOWindow* parent, int column,
|
|||
InputStateLineEdit::InputStateLineEdit(std::function<ControlState()> state_evaluator)
|
||||
: m_state_evaluator(std::move(state_evaluator))
|
||||
{
|
||||
setReadOnly(true);
|
||||
}
|
||||
|
||||
static void PaintStateIndicator(QPainter& painter, const QRect& region, ControlState state)
|
||||
|
|
|
@ -12,11 +12,10 @@
|
|||
#include <QString>
|
||||
#include <QSyntaxHighlighter>
|
||||
|
||||
#include "Common/Flag.h"
|
||||
#include "InputCommon/ControllerInterface/CoreDevice.h"
|
||||
|
||||
class ControlReference;
|
||||
class MappingWidget;
|
||||
class MappingWindow;
|
||||
class QAbstractButton;
|
||||
class QDialogButtonBox;
|
||||
class QLineEdit;
|
||||
|
@ -66,9 +65,13 @@ public:
|
|||
Output
|
||||
};
|
||||
|
||||
explicit IOWindow(MappingWidget* parent, ControllerEmu::EmulatedController* m_controller,
|
||||
explicit IOWindow(MappingWindow* window, ControllerEmu::EmulatedController* m_controller,
|
||||
ControlReference* ref, Type type);
|
||||
|
||||
signals:
|
||||
void DetectInputComplete();
|
||||
void TestOutputComplete();
|
||||
|
||||
private:
|
||||
std::shared_ptr<ciface::Core::Device> GetSelectedDevice() const;
|
||||
|
||||
|
@ -79,8 +82,6 @@ private:
|
|||
|
||||
void OnDialogButtonPressed(QAbstractButton* button);
|
||||
void OnDeviceChanged();
|
||||
void OnDetectButtonPressed();
|
||||
void OnTestButtonPressed();
|
||||
void OnRangeChanged(int range);
|
||||
|
||||
void AppendSelectedOption();
|
||||
|
@ -115,10 +116,12 @@ private:
|
|||
|
||||
// Input actions
|
||||
QPushButton* m_detect_button;
|
||||
std::unique_ptr<ciface::Core::InputDetector> m_input_detector;
|
||||
QComboBox* m_functions_combo;
|
||||
|
||||
// Output actions
|
||||
QPushButton* m_test_button;
|
||||
QTimer* m_output_test_timer;
|
||||
|
||||
// Textarea
|
||||
QPlainTextEdit* m_expression_text;
|
||||
|
|
|
@ -9,13 +9,12 @@
|
|||
#include <QString>
|
||||
|
||||
#include "DolphinQt/Config/Mapping/IOWindow.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingCommon.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/ControlGroup/Buttons.h"
|
||||
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
|
@ -74,7 +73,7 @@ bool MappingButton::IsInput() const
|
|||
}
|
||||
|
||||
MappingButton::MappingButton(MappingWidget* parent, ControlReference* ref, bool indicator)
|
||||
: ElidedButton(RefToDisplayString(ref)), m_parent(parent), m_reference(ref)
|
||||
: ElidedButton(RefToDisplayString(ref)), m_mapping_window(parent->GetParent()), m_reference(ref)
|
||||
{
|
||||
if (IsInput())
|
||||
{
|
||||
|
@ -92,17 +91,22 @@ MappingButton::MappingButton(MappingWidget* parent, ControlReference* ref, bool
|
|||
connect(parent, &MappingWidget::Update, this, &MappingButton::UpdateIndicator);
|
||||
|
||||
connect(parent, &MappingWidget::ConfigChanged, this, &MappingButton::ConfigChanged);
|
||||
connect(this, &MappingButton::ConfigChanged, [this] {
|
||||
setText(RefToDisplayString(m_reference));
|
||||
m_is_mapping = false;
|
||||
});
|
||||
}
|
||||
|
||||
void MappingButton::AdvancedPressed()
|
||||
{
|
||||
IOWindow io(m_parent, m_parent->GetController(), m_reference,
|
||||
m_mapping_window->CancelMapping();
|
||||
|
||||
IOWindow io(m_mapping_window, m_mapping_window->GetController(), m_reference,
|
||||
m_reference->IsInput() ? IOWindow::Type::Input : IOWindow::Type::Output);
|
||||
SetQWidgetWindowDecorations(&io);
|
||||
io.exec();
|
||||
|
||||
ConfigChanged();
|
||||
m_parent->SaveSettings();
|
||||
m_mapping_window->Save();
|
||||
}
|
||||
|
||||
void MappingButton::Clicked()
|
||||
|
@ -113,31 +117,8 @@ void MappingButton::Clicked()
|
|||
return;
|
||||
}
|
||||
|
||||
const auto default_device_qualifier = m_parent->GetController()->GetDefaultDevice();
|
||||
|
||||
QString expression;
|
||||
|
||||
if (m_parent->GetParent()->IsMappingAllDevices())
|
||||
{
|
||||
expression = MappingCommon::DetectExpression(this, g_controller_interface,
|
||||
g_controller_interface.GetAllDeviceStrings(),
|
||||
default_device_qualifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
expression = MappingCommon::DetectExpression(this, g_controller_interface,
|
||||
{default_device_qualifier.ToString()},
|
||||
default_device_qualifier);
|
||||
}
|
||||
|
||||
if (expression.isEmpty())
|
||||
return;
|
||||
|
||||
m_reference->SetExpression(expression.toStdString());
|
||||
m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, m_reference);
|
||||
|
||||
ConfigChanged();
|
||||
m_parent->SaveSettings();
|
||||
m_is_mapping = true;
|
||||
m_mapping_window->QueueInputDetection(this);
|
||||
}
|
||||
|
||||
void MappingButton::Clear()
|
||||
|
@ -145,22 +126,21 @@ void MappingButton::Clear()
|
|||
m_reference->range = 100.0 / SLIDER_TICK_COUNT;
|
||||
|
||||
m_reference->SetExpression("");
|
||||
m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, m_reference);
|
||||
m_mapping_window->GetController()->UpdateSingleControlReference(g_controller_interface,
|
||||
m_reference);
|
||||
|
||||
m_parent->SaveSettings();
|
||||
ConfigChanged();
|
||||
m_mapping_window->Save();
|
||||
|
||||
m_mapping_window->UnQueueInputDetection(this);
|
||||
}
|
||||
|
||||
void MappingButton::UpdateIndicator()
|
||||
{
|
||||
if (!isActiveWindow())
|
||||
return;
|
||||
QFont f = m_mapping_window->font();
|
||||
|
||||
QFont f = m_parent->font();
|
||||
|
||||
// If the input state is "true" (we can't know the state of outputs), show it in bold.
|
||||
if (m_reference->IsInput() && m_reference->GetState<bool>())
|
||||
if (isActiveWindow() && m_reference->IsInput() && m_reference->GetState<bool>() && !m_is_mapping)
|
||||
f.setBold(true);
|
||||
|
||||
// If the expression has failed to parse, show it in italic.
|
||||
// Some expressions still work even the failed to parse so don't prevent the GetState() above.
|
||||
if (m_reference->GetParseStatus() == ciface::ExpressionParser::ParseStatus::SyntaxError)
|
||||
|
@ -169,9 +149,12 @@ void MappingButton::UpdateIndicator()
|
|||
setFont(f);
|
||||
}
|
||||
|
||||
void MappingButton::ConfigChanged()
|
||||
void MappingButton::StartMapping()
|
||||
{
|
||||
setText(RefToDisplayString(m_reference));
|
||||
// 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)
|
||||
|
@ -189,3 +172,8 @@ void MappingButton::mouseReleaseEvent(QMouseEvent* event)
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ControlReference* MappingButton::GetControlReference()
|
||||
{
|
||||
return m_reference;
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "Common/Flag.h"
|
||||
#include "DolphinQt/QtUtils/ElidedButton.h"
|
||||
|
||||
class ControlReference;
|
||||
class MappingWidget;
|
||||
class MappingWindow;
|
||||
class QEvent;
|
||||
class QMouseEvent;
|
||||
|
||||
|
@ -18,16 +18,21 @@ public:
|
|||
MappingButton(MappingWidget* widget, ControlReference* ref, bool indicator);
|
||||
|
||||
bool IsInput() const;
|
||||
ControlReference* GetControlReference();
|
||||
void StartMapping();
|
||||
|
||||
signals:
|
||||
void ConfigChanged();
|
||||
|
||||
private:
|
||||
void Clear();
|
||||
void UpdateIndicator();
|
||||
void ConfigChanged();
|
||||
void AdvancedPressed();
|
||||
|
||||
void Clicked();
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
|
||||
MappingWidget* m_parent;
|
||||
ControlReference* m_reference;
|
||||
MappingWindow* const m_mapping_window;
|
||||
ControlReference* const m_reference;
|
||||
bool m_is_mapping = false;
|
||||
};
|
||||
|
|
|
@ -3,19 +3,18 @@
|
|||
|
||||
#include "DolphinQt/Config/Mapping/MappingCommon.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
#include "DolphinQt/QtUtils/BlockUserInputFilter.h"
|
||||
#include "InputCommon/ControlReference/ControlReference.h"
|
||||
#include "InputCommon/ControllerInterface/MappingCommon.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingButton.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
||||
|
||||
#include "Common/Thread.h"
|
||||
#include "InputCommon/ControlReference/ControlReference.h"
|
||||
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
#include "InputCommon/ControllerInterface/MappingCommon.h"
|
||||
|
||||
namespace MappingCommon
|
||||
{
|
||||
|
@ -23,65 +22,128 @@ constexpr auto INPUT_DETECT_INITIAL_TIME = std::chrono::seconds(3);
|
|||
constexpr auto INPUT_DETECT_CONFIRMATION_TIME = std::chrono::milliseconds(0);
|
||||
constexpr auto INPUT_DETECT_MAXIMUM_TIME = std::chrono::seconds(5);
|
||||
|
||||
constexpr auto OUTPUT_TEST_TIME = std::chrono::seconds(2);
|
||||
|
||||
QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& device_container,
|
||||
const std::vector<std::string>& device_strings,
|
||||
const ciface::Core::DeviceQualifier& default_device,
|
||||
ciface::MappingCommon::Quote quote)
|
||||
class MappingProcessor : public QWidget
|
||||
{
|
||||
const auto filter = new BlockUserInputFilter(button);
|
||||
public:
|
||||
MappingProcessor(MappingWindow* parent) : QWidget{parent}, m_parent{parent}
|
||||
{
|
||||
using MW = MappingWindow;
|
||||
using MP = MappingProcessor;
|
||||
|
||||
button->installEventFilter(filter);
|
||||
button->grabKeyboard();
|
||||
button->grabMouse();
|
||||
connect(parent, &MW::Update, this, &MP::ProcessMappingButtons);
|
||||
connect(parent, &MW::ConfigChanged, this, &MP::CancelMapping);
|
||||
|
||||
const auto old_text = button->text();
|
||||
button->setText(QStringLiteral("..."));
|
||||
connect(parent, &MW::UnQueueInputDetection, this, &MP::UnQueueInputDetection);
|
||||
connect(parent, &MW::QueueInputDetection, this, &MP::QueueInputDetection);
|
||||
connect(parent, &MW::CancelMapping, this, &MP::CancelMapping);
|
||||
|
||||
// The button text won't be updated if we don't process events here
|
||||
QApplication::processEvents();
|
||||
m_input_detection_start_timer = new QTimer(this);
|
||||
m_input_detection_start_timer->setSingleShot(true);
|
||||
connect(m_input_detection_start_timer, &QTimer::timeout, this, &MP::StartInputDetection);
|
||||
}
|
||||
|
||||
// Avoid that the button press itself is registered as an event
|
||||
Common::SleepCurrentThread(50);
|
||||
void StartInputDetection()
|
||||
{
|
||||
const auto& default_device = m_parent->GetController()->GetDefaultDevice();
|
||||
auto& button = m_clicked_mapping_buttons.front();
|
||||
|
||||
auto detections =
|
||||
device_container.DetectInput(device_strings, INPUT_DETECT_INITIAL_TIME,
|
||||
INPUT_DETECT_CONFIRMATION_TIME, INPUT_DETECT_MAXIMUM_TIME);
|
||||
button->StartMapping();
|
||||
|
||||
ciface::MappingCommon::RemoveSpuriousTriggerCombinations(&detections);
|
||||
std::vector device_strings{default_device.ToString()};
|
||||
if (m_parent->IsMappingAllDevices())
|
||||
device_strings = g_controller_interface.GetAllDeviceStrings();
|
||||
|
||||
const auto timer = new QTimer(button);
|
||||
m_input_detector = std::make_unique<ciface::Core::InputDetector>();
|
||||
const auto lock = m_parent->GetController()->GetStateLock();
|
||||
m_input_detector->Start(g_controller_interface, device_strings);
|
||||
}
|
||||
|
||||
timer->setSingleShot(true);
|
||||
void ProcessMappingButtons()
|
||||
{
|
||||
if (!m_input_detector)
|
||||
return;
|
||||
|
||||
button->connect(timer, &QTimer::timeout, [button, filter] {
|
||||
button->releaseMouse();
|
||||
button->releaseKeyboard();
|
||||
button->removeEventFilter(filter);
|
||||
});
|
||||
m_input_detector->Update(INPUT_DETECT_INITIAL_TIME, INPUT_DETECT_CONFIRMATION_TIME,
|
||||
INPUT_DETECT_MAXIMUM_TIME);
|
||||
|
||||
// Prevent mappings of "space", "return", or mouse clicks from re-activating detection.
|
||||
timer->start(500);
|
||||
if (m_input_detector->IsComplete())
|
||||
{
|
||||
auto detections = m_input_detector->TakeResults();
|
||||
ciface::MappingCommon::RemoveSpuriousTriggerCombinations(&detections);
|
||||
|
||||
button->setText(old_text);
|
||||
// No inputs detected. Cancel this and any other queued mappings.
|
||||
if (detections.empty())
|
||||
{
|
||||
CancelMapping();
|
||||
return;
|
||||
}
|
||||
|
||||
return QString::fromStdString(BuildExpression(detections, default_device, quote));
|
||||
}
|
||||
const auto& default_device = m_parent->GetController()->GetDefaultDevice();
|
||||
auto& button = m_clicked_mapping_buttons.front();
|
||||
auto* const control_reference = button->GetControlReference();
|
||||
|
||||
void TestOutput(QPushButton* button, OutputReference* reference)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateInputDetectionStartTimer()
|
||||
{
|
||||
m_input_detector.reset();
|
||||
|
||||
if (m_clicked_mapping_buttons.empty())
|
||||
m_input_detection_start_timer->stop();
|
||||
else
|
||||
m_input_detection_start_timer->start(INPUT_DETECT_INITIAL_DELAY);
|
||||
}
|
||||
|
||||
void UnQueueInputDetection(MappingButton* button)
|
||||
{
|
||||
std::erase(m_clicked_mapping_buttons, button);
|
||||
button->ConfigChanged();
|
||||
UpdateInputDetectionStartTimer();
|
||||
}
|
||||
|
||||
void QueueInputDetection(MappingButton* button)
|
||||
{
|
||||
// UnQueue if already queued.
|
||||
if (std::erase(m_clicked_mapping_buttons, button))
|
||||
{
|
||||
button->ConfigChanged();
|
||||
UpdateInputDetectionStartTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
button->setText(QStringLiteral("[ ... ]"));
|
||||
m_clicked_mapping_buttons.push_back(button);
|
||||
UpdateInputDetectionStartTimer();
|
||||
}
|
||||
|
||||
void CancelMapping()
|
||||
{
|
||||
// Signal buttons to take on their proper input expression text.
|
||||
for (auto* button : m_clicked_mapping_buttons)
|
||||
button->ConfigChanged();
|
||||
|
||||
m_clicked_mapping_buttons = {};
|
||||
UpdateInputDetectionStartTimer();
|
||||
}
|
||||
|
||||
private:
|
||||
std::deque<MappingButton*> m_clicked_mapping_buttons;
|
||||
std::unique_ptr<ciface::Core::InputDetector> m_input_detector;
|
||||
QTimer* m_input_detection_start_timer;
|
||||
MappingWindow* const m_parent;
|
||||
};
|
||||
|
||||
void CreateMappingProcessor(MappingWindow* window)
|
||||
{
|
||||
const auto old_text = button->text();
|
||||
button->setText(QStringLiteral("..."));
|
||||
|
||||
// The button text won't be updated if we don't process events here
|
||||
QApplication::processEvents();
|
||||
|
||||
reference->State(1.0);
|
||||
std::this_thread::sleep_for(OUTPUT_TEST_TIME);
|
||||
reference->State(0.0);
|
||||
|
||||
button->setText(old_text);
|
||||
new MappingProcessor{window};
|
||||
}
|
||||
|
||||
} // namespace MappingCommon
|
||||
|
|
|
@ -3,23 +3,14 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
#include "InputCommon/ControllerInterface/CoreDevice.h"
|
||||
#include "InputCommon/ControllerInterface/MappingCommon.h"
|
||||
|
||||
class QString;
|
||||
class OutputReference;
|
||||
class QPushButton;
|
||||
class MappingWindow;
|
||||
|
||||
namespace MappingCommon
|
||||
{
|
||||
QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& device_container,
|
||||
const std::vector<std::string>& device_strings,
|
||||
const ciface::Core::DeviceQualifier& default_device,
|
||||
ciface::MappingCommon::Quote quote = ciface::MappingCommon::Quote::On);
|
||||
|
||||
void TestOutput(QPushButton* button, OutputReference* reference);
|
||||
// A slight delay improves behavior when "clicking" the detect button via key-press.
|
||||
static constexpr auto INPUT_DETECT_INITIAL_DELAY = std::chrono::milliseconds{100};
|
||||
|
||||
void CreateMappingProcessor(MappingWindow*);
|
||||
} // namespace MappingCommon
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
||||
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
|
||||
|
||||
#include "InputCommon/ControlReference/ControlReference.h"
|
||||
#include "InputCommon/ControllerEmu/Control/Control.h"
|
||||
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
|
||||
#include "InputCommon/ControllerEmu/ControlGroup/MixedTriggers.h"
|
||||
|
@ -338,10 +337,10 @@ MappingWidget::CreateSettingAdvancedMappingButton(ControllerEmu::NumericSettingB
|
|||
setting.SetExpressionFromValue();
|
||||
|
||||
// Ensure the UI has the game-controller indicator while editing the expression.
|
||||
// And cancel in-progress mappings.
|
||||
ConfigChanged();
|
||||
|
||||
IOWindow io(this, GetController(), &setting.GetInputReference(), IOWindow::Type::Input);
|
||||
SetQWidgetWindowDecorations(&io);
|
||||
IOWindow io(GetParent(), GetController(), &setting.GetInputReference(), IOWindow::Type::Input);
|
||||
io.exec();
|
||||
|
||||
setting.SimplifyIfPossible();
|
||||
|
|
|
@ -3,15 +3,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
|
||||
class ControlGroupBox;
|
||||
class InputConfig;
|
||||
class MappingButton;
|
||||
class MappingNumeric;
|
||||
class MappingWindow;
|
||||
class QFormLayout;
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include <QToolButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/Core.h"
|
||||
#include "Core/HotkeyManager.h"
|
||||
|
||||
#include "Common/CommonPaths.h"
|
||||
|
@ -41,6 +40,7 @@
|
|||
#include "DolphinQt/Config/Mapping/HotkeyTAS.h"
|
||||
#include "DolphinQt/Config/Mapping/HotkeyUSBEmu.h"
|
||||
#include "DolphinQt/Config/Mapping/HotkeyWii.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingCommon.h"
|
||||
#include "DolphinQt/Config/Mapping/WiimoteEmuExtension.h"
|
||||
#include "DolphinQt/Config/Mapping/WiimoteEmuExtensionMotionInput.h"
|
||||
#include "DolphinQt/Config/Mapping/WiimoteEmuExtensionMotionSimulation.h"
|
||||
|
@ -90,6 +90,8 @@ MappingWindow::MappingWindow(QWidget* parent, Type type, int port_num)
|
|||
[] { HotkeyManagerEmu::Enable(true); });
|
||||
filter->connect(filter, &WindowActivationEventFilter::windowActivated,
|
||||
[] { HotkeyManagerEmu::Enable(false); });
|
||||
|
||||
MappingCommon::CreateMappingProcessor(this);
|
||||
}
|
||||
|
||||
void MappingWindow::CreateDevicesLayout()
|
||||
|
@ -182,9 +184,8 @@ void MappingWindow::CreateMainLayout()
|
|||
|
||||
void MappingWindow::ConnectWidgets()
|
||||
{
|
||||
connect(&Settings::Instance(), &Settings::DevicesChanged, this,
|
||||
&MappingWindow::OnGlobalDevicesChanged);
|
||||
connect(this, &MappingWindow::ConfigChanged, this, &MappingWindow::OnGlobalDevicesChanged);
|
||||
connect(&Settings::Instance(), &Settings::DevicesChanged, this, &MappingWindow::ConfigChanged);
|
||||
connect(this, &MappingWindow::ConfigChanged, this, &MappingWindow::UpdateDeviceList);
|
||||
connect(m_devices_combo, &QComboBox::currentIndexChanged, this, &MappingWindow::OnSelectDevice);
|
||||
|
||||
connect(m_reset_clear, &QPushButton::clicked, this, &MappingWindow::OnClearFieldsPressed);
|
||||
|
@ -200,6 +201,8 @@ void MappingWindow::ConnectWidgets()
|
|||
// We currently use the "Close" button as an "Accept" button so we must save on reject.
|
||||
connect(this, &QDialog::rejected, [this] { emit Save(); });
|
||||
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
connect(m_tab_widget, &QTabWidget::currentChanged, this, &MappingWindow::CancelMapping);
|
||||
}
|
||||
|
||||
void MappingWindow::UpdateProfileIndex()
|
||||
|
@ -342,6 +345,8 @@ void MappingWindow::OnSelectDevice(int)
|
|||
const auto device = m_devices_combo->currentData().toString().toStdString();
|
||||
|
||||
m_controller->SetDefaultDevice(device);
|
||||
|
||||
emit ConfigChanged();
|
||||
m_controller->UpdateReferences(g_controller_interface);
|
||||
}
|
||||
|
||||
|
@ -355,7 +360,7 @@ void MappingWindow::RefreshDevices()
|
|||
g_controller_interface.RefreshDevices();
|
||||
}
|
||||
|
||||
void MappingWindow::OnGlobalDevicesChanged()
|
||||
void MappingWindow::UpdateDeviceList()
|
||||
{
|
||||
const QSignalBlocker blocker(m_devices_combo);
|
||||
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
|
||||
#include <QDialog>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
|
||||
#include "InputCommon/ControllerInterface/CoreDevice.h"
|
||||
|
||||
namespace ControllerEmu
|
||||
{
|
||||
|
@ -15,6 +12,8 @@ class EmulatedController;
|
|||
}
|
||||
|
||||
class InputConfig;
|
||||
class MappingButton;
|
||||
|
||||
class QComboBox;
|
||||
class QDialogButtonBox;
|
||||
class QEvent;
|
||||
|
@ -58,10 +57,14 @@ public:
|
|||
signals:
|
||||
// Emitted when config has changed so widgets can update to reflect the change.
|
||||
void ConfigChanged();
|
||||
// Emitted at 30hz for real-time indicators to be updated.
|
||||
// Emitted at INDICATOR_UPDATE_FREQ Hz for real-time indicators to be updated.
|
||||
void Update();
|
||||
void Save();
|
||||
|
||||
void UnQueueInputDetection(MappingButton*);
|
||||
void QueueInputDetection(MappingButton*);
|
||||
void CancelMapping();
|
||||
|
||||
private:
|
||||
void SetMappingType(Type type);
|
||||
void CreateDevicesLayout();
|
||||
|
@ -82,11 +85,11 @@ private:
|
|||
void UpdateProfileIndex();
|
||||
void UpdateProfileButtonState();
|
||||
void PopulateProfileSelection();
|
||||
void UpdateDeviceList();
|
||||
|
||||
void OnDefaultFieldsPressed();
|
||||
void OnClearFieldsPressed();
|
||||
void OnSelectDevice(int index);
|
||||
void OnGlobalDevicesChanged();
|
||||
|
||||
ControllerEmu::EmulatedController* m_controller = nullptr;
|
||||
|
||||
|
|
|
@ -3,12 +3,34 @@
|
|||
|
||||
#include "DolphinQt/QtUtils/BlockUserInputFilter.h"
|
||||
|
||||
#include <QEvent>
|
||||
#include <chrono>
|
||||
|
||||
bool BlockUserInputFilter::eventFilter(QObject* object, QEvent* event)
|
||||
#include <QEvent>
|
||||
#include <QTimer>
|
||||
|
||||
namespace QtUtils
|
||||
{
|
||||
const QEvent::Type event_type = event->type();
|
||||
return event_type == QEvent::KeyPress || event_type == QEvent::KeyRelease ||
|
||||
event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonRelease ||
|
||||
event_type == QEvent::MouseButtonDblClick;
|
||||
|
||||
// Leave filter active for a bit to prevent Return/Space detection from reactivating the button.
|
||||
constexpr auto REMOVAL_DELAY = std::chrono::milliseconds{100};
|
||||
|
||||
BlockKeyboardInputFilter::BlockKeyboardInputFilter(QObject* parent) : QObject{parent}
|
||||
{
|
||||
parent->installEventFilter(this);
|
||||
}
|
||||
|
||||
void BlockKeyboardInputFilter::ScheduleRemoval()
|
||||
{
|
||||
auto* const timer = new QTimer(this);
|
||||
timer->setSingleShot(true);
|
||||
connect(timer, &QTimer::timeout, [this] { delete this; });
|
||||
timer->start(REMOVAL_DELAY);
|
||||
}
|
||||
|
||||
bool BlockKeyboardInputFilter::eventFilter(QObject* object, QEvent* event)
|
||||
{
|
||||
const auto event_type = event->type();
|
||||
return event_type == QEvent::KeyPress || event_type == QEvent::KeyRelease;
|
||||
}
|
||||
|
||||
} // namespace QtUtils
|
||||
|
|
|
@ -5,14 +5,26 @@
|
|||
|
||||
#include <QObject>
|
||||
|
||||
class QEvent;
|
||||
namespace QtUtils
|
||||
{
|
||||
|
||||
class BlockUserInputFilter : public QObject
|
||||
class BlockKeyboardInputFilter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
using QObject::QObject;
|
||||
BlockKeyboardInputFilter(QObject* parent);
|
||||
void ScheduleRemoval();
|
||||
|
||||
private:
|
||||
bool eventFilter(QObject* object, QEvent* event) override;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void InstallKeyboardBlocker(QObject* obj, T* removal_signal_object, void (T::*removal_signal)())
|
||||
{
|
||||
removal_signal_object->connect(removal_signal_object, removal_signal,
|
||||
new QtUtils::BlockKeyboardInputFilter{obj},
|
||||
&QtUtils::BlockKeyboardInputFilter::ScheduleRemoval);
|
||||
}
|
||||
|
||||
} // namespace QtUtils
|
||||
|
|
Loading…
Reference in New Issue