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 e553497
Show file tree
Hide file tree
Showing 14 changed files with 316 additions and 211 deletions.
2 changes: 0 additions & 2 deletions Source/Core/DolphinQt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,6 @@ add_executable(dolphin-emu
Config/Mapping/IOWindow.cpp
Config/Mapping/MappingButton.cpp
Config/Mapping/MappingButton.h
Config/Mapping/MappingCommon.cpp
Config/Mapping/MappingCommon.h
Config/Mapping/MappingIndicator.cpp
Config/Mapping/MappingIndicator.h
Config/Mapping/MappingNumeric.cpp
Expand Down
133 changes: 98 additions & 35 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,11 +19,9 @@
#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"
Expand All @@ -38,8 +35,12 @@
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/MappingCommon.h"

constexpr auto INPUT_DETECT_TIME = std::chrono::seconds(2);
constexpr auto OUTPUT_TEST_TIME = std::chrono::seconds(2);

namespace
{

QTextCharFormat GetSpecialCharFormat()
{
QTextCharFormat format;
Expand Down Expand Up @@ -258,18 +259,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 +426,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 +457,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 +491,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(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 +626,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 +730,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 +781,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
12 changes: 9 additions & 3 deletions Source/Core/DolphinQt/Config/Mapping/IOWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#include <QString>
#include <QSyntaxHighlighter>

#include "Common/Flag.h"
#include "InputCommon/ControllerInterface/CoreDevice.h"

class ControlReference;
Expand All @@ -35,6 +34,9 @@ class EmulatedController;

class InputStateLineEdit;

// A slight delay improves behavior when "clicking" the detect button via key-press.
static constexpr auto INPUT_DETECT_INITIAL_DELAY = std::chrono::milliseconds{100};

class ControlExpressionSyntaxHighlighter final : public QSyntaxHighlighter
{
Q_OBJECT
Expand Down Expand Up @@ -69,6 +71,10 @@ class IOWindow final : public QDialog
explicit IOWindow(MappingWidget* parent, 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 +85,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 +119,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
58 changes: 23 additions & 35 deletions Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -92,10 +91,16 @@ 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()
{
m_parent->CancelMapping();

IOWindow io(m_parent, m_parent->GetController(), m_reference,
m_reference->IsInput() ? IOWindow::Type::Input : IOWindow::Type::Output);
SetQWidgetWindowDecorations(&io);
Expand All @@ -113,31 +118,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_parent->QueueInputDetection(this);
}

void MappingButton::Clear()
Expand All @@ -148,19 +130,17 @@ void MappingButton::Clear()
m_parent->GetController()->UpdateSingleControlReference(g_controller_interface, m_reference);

m_parent->SaveSettings();
ConfigChanged();

m_parent->UnQueueInputDetection(this);
}

void MappingButton::UpdateIndicator()
{
if (!isActiveWindow())
return;

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)
Expand All @@ -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)
Expand All @@ -189,3 +172,8 @@ void MappingButton::mouseReleaseEvent(QMouseEvent* event)
return;
}
}

ControlReference* MappingButton::GetControlReference()
{
return m_reference;
}
Loading

0 comments on commit e553497

Please sign in to comment.