Skip to content

Commit

Permalink
DolphinQt: Make input mapping and output testing non-blocking.
Browse files Browse the repository at this point in the history
  • Loading branch information
jordan-woyak committed Nov 2, 2024
1 parent c25e400 commit 1244b2d
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 195 deletions.
143 changes: 104 additions & 39 deletions Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include "DolphinQt/Config/Mapping/IOWindow.h"

#include <optional>
#include <thread>

#include <QBrush>
#include <QColor>
Expand All @@ -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"
Expand All @@ -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;
Expand Down Expand Up @@ -228,15 +229,17 @@ class InputStateLineEdit : public QLineEdit
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"));
Expand All @@ -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));
Expand Down Expand Up @@ -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();

Expand All @@ -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()
Expand All @@ -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);
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
13 changes: 8 additions & 5 deletions Source/Core/DolphinQt/Config/Mapping/IOWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -66,9 +65,13 @@ class IOWindow final : public QDialog
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;

Expand All @@ -79,8 +82,6 @@ class IOWindow final : public QDialog

void OnDialogButtonPressed(QAbstractButton* button);
void OnDeviceChanged();
void OnDetectButtonPressed();
void OnTestButtonPressed();
void OnRangeChanged(int range);

void AppendSelectedOption();
Expand Down Expand Up @@ -115,10 +116,12 @@ class IOWindow final : public QDialog

// 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;
Expand Down
Loading

0 comments on commit 1244b2d

Please sign in to comment.