@@ -23,6 +23,7 @@
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
#include "InputCommon/ControllerInterface/Device.h"

#include "DolphinQt/Config/Mapping/MappingWidget.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/Settings.h"

@@ -50,15 +51,9 @@ const QColor SWING_GATE_COLOR = 0xcea2d9;

constexpr int INPUT_DOT_RADIUS = 2;

constexpr int INDICATOR_UPDATE_FREQ = 30;

MappingIndicator::MappingIndicator(ControllerEmu::ControlGroup* group) : m_group(group)
{
setMinimumHeight(128);

const auto timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, [this] { repaint(); });
timer->start(1000 / INDICATOR_UPDATE_FREQ);
}

namespace
@@ -133,12 +128,8 @@ void MappingIndicator::DrawCursor(ControllerEmu::Cursor& cursor)
const QColor tv_brush_color = CURSOR_TV_COLOR;
const QColor tv_pen_color = tv_brush_color.darker(125);

// TODO: This SetControllerStateNeeded interface leaks input into the game
// We should probably hold the mutex for UI updates.
Settings::Instance().SetControllerStateNeeded(true);
const auto raw_coord = cursor.GetState(false);
const auto adj_coord = cursor.GetState(true);
Settings::Instance().SetControllerStateNeeded(false);

UpdateCalibrationWidget({raw_coord.x, raw_coord.y});

@@ -250,9 +241,6 @@ void MappingIndicator::DrawReshapableInput(ControllerEmu::ReshapableInput& stick

const QColor gate_pen_color = gate_brush_color.darker(125);

// TODO: This SetControllerStateNeeded interface leaks input into the game
// We should probably hold the mutex for UI updates.
Settings::Instance().SetControllerStateNeeded(true);
const auto raw_coord = stick.GetReshapableState(false);

Common::DVec2 adj_coord;
@@ -267,8 +255,6 @@ void MappingIndicator::DrawReshapableInput(ControllerEmu::ReshapableInput& stick
adj_coord = stick.GetReshapableState(true);
}

Settings::Instance().SetControllerStateNeeded(false);

UpdateCalibrationWidget(raw_coord);

// Bounding box size:
@@ -343,10 +329,8 @@ void MappingIndicator::DrawMixedTriggers()
const std::array<u16, TRIGGER_COUNT> button_masks = {0x1, 0x2};
u16 button_state = 0;

Settings::Instance().SetControllerStateNeeded(true);
triggers.GetState(&button_state, button_masks.data(), raw_analog_state.data(), false);
triggers.GetState(&button_state, button_masks.data(), adj_analog_state.data(), true);
Settings::Instance().SetControllerStateNeeded(false);

// Rectangle sizes:
const int trigger_height = 32;
@@ -429,13 +413,9 @@ void MappingIndicator::DrawForce(ControllerEmu::Force& force)
const QColor gate_brush_color = SWING_GATE_COLOR;
const QColor gate_pen_color = gate_brush_color.darker(125);

// TODO: This SetControllerStateNeeded interface leaks input into the game
// We should probably hold the mutex for UI updates.
Settings::Instance().SetControllerStateNeeded(true);
const auto raw_coord = force.GetState(false);
WiimoteEmu::EmulateSwing(&m_motion_state, &force, 1.f / INDICATOR_UPDATE_FREQ);
const auto& adj_coord = m_motion_state.position;
Settings::Instance().SetControllerStateNeeded(false);

UpdateCalibrationWidget({raw_coord.x, raw_coord.y});

@@ -520,6 +500,9 @@ void MappingIndicator::DrawForce(ControllerEmu::Force& force)

void MappingIndicator::paintEvent(QPaintEvent*)
{
// TODO: The SetControllerStateNeeded interface leaks input into the game.
Settings::Instance().SetControllerStateNeeded(true);

switch (m_group->type)
{
case ControllerEmu::GroupType::Cursor:
@@ -538,6 +521,8 @@ void MappingIndicator::paintEvent(QPaintEvent*)
default:
break;
}

Settings::Instance().SetControllerStateNeeded(false);
}

void MappingIndicator::DrawCalibration(QPainter& p, Common::DVec2 point)
@@ -10,25 +10,23 @@
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"

MappingNumeric::MappingNumeric(MappingWidget* widget, ControllerEmu::NumericSetting* setting)
: m_parent(widget), m_setting(setting)
MappingNumeric::MappingNumeric(MappingWidget* parent, ControllerEmu::NumericSetting* setting)
: m_setting(*setting)
{
setRange(setting->m_low, setting->m_high);
Update();
Connect();
}

void MappingNumeric::Connect()
{
connect(this, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
[this](int value) {
m_setting->SetValue(static_cast<double>(value) / 100);
m_parent->SaveSettings();
m_parent->GetController()->UpdateReferences(g_controller_interface);
[this, parent](int value) {
m_setting.SetValue(static_cast<double>(value) / 100);
parent->SaveSettings();
});

connect(parent, &MappingWidget::ConfigChanged, this, &MappingNumeric::ConfigChanged);
}

void MappingNumeric::Update()
void MappingNumeric::ConfigChanged()
{
setValue(m_setting->GetValue() * 100);
const bool old_state = blockSignals(true);
setValue(m_setting.GetValue() * 100);
blockSignals(old_state);
}
@@ -19,11 +19,8 @@ class MappingNumeric : public QSpinBox
public:
MappingNumeric(MappingWidget* widget, ControllerEmu::NumericSetting* ref);

void Update();

private:
void Connect();
void ConfigChanged();

MappingWidget* m_parent;
ControllerEmu::NumericSetting* m_setting;
ControllerEmu::NumericSetting& m_setting;
};
@@ -10,23 +10,20 @@
#include "InputCommon/ControllerEmu/Setting/BooleanSetting.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"

MappingRadio::MappingRadio(MappingWidget* widget, ControllerEmu::BooleanSetting* setting)
: QRadioButton(tr(setting->m_ui_name.c_str())), m_parent(widget), m_setting(setting)
MappingRadio::MappingRadio(MappingWidget* parent, ControllerEmu::BooleanSetting* setting)
: QRadioButton(tr(setting->m_ui_name.c_str())), m_setting(*setting)
{
Update();
Connect();
}

void MappingRadio::Connect()
{
connect(this, &QRadioButton::toggled, this, [this](int value) {
m_setting->SetValue(value);
m_parent->SaveSettings();
m_parent->GetController()->UpdateReferences(g_controller_interface);
connect(this, &QRadioButton::toggled, this, [this, parent](int value) {
m_setting.SetValue(value);
parent->SaveSettings();
});

connect(parent, &MappingWidget::ConfigChanged, this, &MappingRadio::ConfigChanged);
}

void MappingRadio::Update()
void MappingRadio::ConfigChanged()
{
setChecked(m_setting->GetValue());
const bool old_state = blockSignals(true);
setChecked(m_setting.GetValue());
blockSignals(old_state);
}
@@ -18,11 +18,8 @@ class MappingRadio : public QRadioButton
public:
MappingRadio(MappingWidget* widget, ControllerEmu::BooleanSetting* setting);

void Update();

private:
void Connect();
void ConfigChanged();

MappingWidget* m_parent;
ControllerEmu::BooleanSetting* m_setting;
ControllerEmu::BooleanSetting& m_setting;
};
@@ -7,6 +7,7 @@
#include <QFormLayout>
#include <QGroupBox>
#include <QPushButton>
#include <QTimer>

#include "DolphinQt/Config/Mapping/IOWindow.h"
#include "DolphinQt/Config/Mapping/MappingBool.h"
@@ -15,18 +16,32 @@
#include "DolphinQt/Config/Mapping/MappingNumeric.h"
#include "DolphinQt/Config/Mapping/MappingRadio.h"
#include "DolphinQt/Config/Mapping/MappingWindow.h"
#include "DolphinQt/Settings.h"

#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
#include "InputCommon/ControllerEmu/Setting/BooleanSetting.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
#include "InputCommon/ControllerEmu/StickGate.h"

MappingWidget::MappingWidget(MappingWindow* window) : m_parent(window)
MappingWidget::MappingWidget(MappingWindow* parent) : m_parent(parent)
{
connect(window, &MappingWindow::Update, this, &MappingWidget::Update);
connect(window, &MappingWindow::Save, this, &MappingWidget::SaveSettings);
connect(parent, &MappingWindow::Update, this, &MappingWidget::Update);
connect(parent, &MappingWindow::Save, this, &MappingWidget::SaveSettings);
connect(parent, &MappingWindow::ConfigChanged, this, &MappingWidget::ConfigChanged);

const auto timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, [this] {
// TODO: The SetControllerStateNeeded interface leaks input into the game.
const auto lock = m_parent->GetController()->GetStateLock();
Settings::Instance().SetControllerStateNeeded(true);
emit Update();
Settings::Instance().SetControllerStateNeeded(false);
});

timer->start(1000 / INDICATOR_UPDATE_FREQ);
}

MappingWindow* MappingWidget::GetParent() const
@@ -57,11 +72,6 @@ void MappingWidget::NextButton(MappingButton* button)
NextButton(next);
}

std::shared_ptr<ciface::Core::Device> MappingWidget::GetDevice() const
{
return m_parent->GetDevice();
}

int MappingWidget::GetPort() const
{
return m_parent->GetPort();
@@ -96,27 +106,13 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con
translate ? tr(control->ui_name.c_str()) : QString::fromStdString(control->ui_name);
form_layout->addRow(translated_name, button);

auto* control_ref = control->control_ref.get();

connect(button, &MappingButton::AdvancedPressed, [this, button, control_ref] {
if (m_parent->GetDevice() == nullptr)
return;

IOWindow io(this, m_parent->GetController(), control_ref,
control_ref->IsInput() ? IOWindow::Type::Input : IOWindow::Type::Output);
io.exec();
SaveSettings();
button->Update();
});

m_buttons.push_back(button);
}

for (auto& numeric : group->numeric_settings)
{
auto* spinbox = new MappingNumeric(this, numeric.get());
form_layout->addRow(tr(numeric->m_name.c_str()), spinbox);
m_numerics.push_back(spinbox);
}

for (auto& boolean : group->boolean_settings)
@@ -127,7 +123,6 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con
auto* checkbox = new MappingRadio(this, boolean.get());

form_layout->addRow(checkbox);
m_radio.push_back(checkbox);
}

for (auto& boolean : group->boolean_settings)
@@ -138,12 +133,12 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con
auto* checkbox = new MappingBool(this, boolean.get());

form_layout->addRow(checkbox);
m_bools.push_back(checkbox);
}

if (need_indicator)
{
auto const indicator = new MappingIndicator(group);
connect(this, &MappingWidget::Update, indicator, QOverload<>::of(&MappingIndicator::update));

if (need_calibration)
{
@@ -159,23 +154,6 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con
return group_box;
}

void MappingWidget::Update()
{
for (auto* button : m_buttons)
button->Update();

for (auto* spinbox : m_numerics)
spinbox->Update();

for (auto* checkbox : m_bools)
checkbox->Update();

for (auto* radio : m_radio)
radio->Update();

SaveSettings();
}

ControllerEmu::EmulatedController* MappingWidget::GetController() const
{
return m_parent->GetController();
@@ -35,14 +35,15 @@ class Device;
}
} // namespace ciface

constexpr int INDICATOR_UPDATE_FREQ = 30;

class MappingWidget : public QWidget
{
Q_OBJECT
public:
explicit MappingWidget(MappingWindow* window);

ControllerEmu::EmulatedController* GetController() const;
std::shared_ptr<ciface::Core::Device> GetDevice() const;

MappingWindow* GetParent() const;

@@ -53,7 +54,9 @@ class MappingWidget : public QWidget
virtual void SaveSettings() = 0;
virtual InputConfig* GetConfig() = 0;

signals:
void Update();
void ConfigChanged();

protected:
int GetPort() const;
@@ -62,8 +65,5 @@ class MappingWidget : public QWidget
private:
MappingWindow* m_parent;
bool m_first = true;
std::vector<MappingBool*> m_bools;
std::vector<MappingRadio*> m_radio;
std::vector<MappingButton*> m_buttons;
std::vector<MappingNumeric*> m_numerics;
};
@@ -13,13 +13,13 @@
#include <QTabWidget>
#include <QVBoxLayout>

#include "Core/Core.h"

#include "Common/FileSearch.h"
#include "Common/FileUtil.h"
#include "Common/IniFile.h"
#include "Common/StringUtil.h"

#include "Core/Core.h"

#include "DolphinQt/Config/Mapping/GCKeyboardEmu.h"
#include "DolphinQt/Config/Mapping/GCMicrophone.h"
#include "DolphinQt/Config/Mapping/GCPadEmu.h"
@@ -58,6 +58,8 @@ MappingWindow::MappingWindow(QWidget* parent, Type type, int port_num)
CreateMainLayout();
ConnectWidgets();
SetMappingType(type);

emit ConfigChanged();
}

void MappingWindow::CreateDevicesLayout()
@@ -141,17 +143,21 @@ void MappingWindow::ConnectWidgets()
{
connect(&Settings::Instance(), &Settings::DevicesChanged, this,
&MappingWindow::OnGlobalDevicesChanged);
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(m_devices_refresh, &QPushButton::clicked, this, &MappingWindow::RefreshDevices);
connect(this, &MappingWindow::ConfigChanged, this, &MappingWindow::OnGlobalDevicesChanged);
connect(m_devices_combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, &MappingWindow::OnDeviceChanged);
this, &MappingWindow::OnSelectDevice);

connect(m_devices_refresh, &QPushButton::clicked, this, &MappingWindow::RefreshDevices);

connect(m_reset_clear, &QPushButton::clicked, this, &MappingWindow::OnClearFieldsPressed);
connect(m_reset_default, &QPushButton::clicked, this, &MappingWindow::OnDefaultFieldsPressed);
connect(m_profiles_save, &QPushButton::clicked, this, &MappingWindow::OnSaveProfilePressed);
connect(m_profiles_load, &QPushButton::clicked, this, &MappingWindow::OnLoadProfilePressed);
connect(m_profiles_delete, &QPushButton::clicked, this, &MappingWindow::OnDeleteProfilePressed);

// 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);
}

void MappingWindow::OnDeleteProfilePressed()
@@ -206,9 +212,7 @@ void MappingWindow::OnLoadProfilePressed()
m_controller->LoadConfig(ini.GetOrCreateSection("Profile"));
m_controller->UpdateReferences(g_controller_interface);

emit Update();

RefreshDevices();
emit ConfigChanged();
}

void MappingWindow::OnSaveProfilePressed()
@@ -235,13 +239,16 @@ void MappingWindow::OnSaveProfilePressed()
}
}

void MappingWindow::OnDeviceChanged(int index)
void MappingWindow::OnSelectDevice(int index)
{
if (IsMappingAllDevices())
return;

const auto device = m_devices_combo->currentText().toStdString();
// Original string is stored in the "user-data".
const auto device = m_devices_combo->currentData().toString().toStdString();

m_controller->SetDefaultDevice(device);
m_controller->UpdateReferences(g_controller_interface);
}

bool MappingWindow::IsMappingAllDevices() const
@@ -256,26 +263,42 @@ void MappingWindow::RefreshDevices()

void MappingWindow::OnGlobalDevicesChanged()
{
const auto old_state = m_devices_combo->blockSignals(true);

m_devices_combo->clear();

Core::RunAsCPUThread([&] {
m_controller->UpdateReferences(g_controller_interface);
for (const auto& name : g_controller_interface.GetAllDeviceStrings())
{
const auto qname = QString::fromStdString(name);
m_devices_combo->addItem(qname, qname);
}

const auto default_device = m_controller->GetDefaultDevice().ToString();
m_devices_combo->insertSeparator(m_devices_combo->count());

if (!default_device.empty())
m_devices_combo->addItem(QString::fromStdString(default_device));
const auto default_device = m_controller->GetDefaultDevice().ToString();

for (const auto& name : g_controller_interface.GetAllDeviceStrings())
if (!default_device.empty())
{
const auto default_device_index =
m_devices_combo->findText(QString::fromStdString(default_device));

if (default_device_index != -1)
{
m_devices_combo->setCurrentIndex(default_device_index);
}
else
{
if (name != default_device)
m_devices_combo->addItem(QString::fromStdString(name));
// Selected device is not currently attached.
const auto qname = QString::fromStdString(default_device);
m_devices_combo->addItem(
QStringLiteral("[") + tr("disconnected") + QStringLiteral("] ") + qname, qname);
m_devices_combo->setCurrentIndex(m_devices_combo->count() - 1);
}
}

m_devices_combo->addItem(tr("All devices"));
m_devices_combo->addItem(tr("All devices"));

m_devices_combo->setCurrentIndex(0);
});
m_devices_combo->blockSignals(old_state);
}

void MappingWindow::SetMappingType(MappingWindow::Type type)
@@ -372,26 +395,26 @@ ControllerEmu::EmulatedController* MappingWindow::GetController() const
return m_controller;
}

std::shared_ptr<ciface::Core::Device> MappingWindow::GetDevice() const
{
return g_controller_interface.FindDevice(GetController()->GetDefaultDevice());
}

void MappingWindow::OnDefaultFieldsPressed()
{
m_controller->LoadDefaults(g_controller_interface);
m_controller->UpdateReferences(g_controller_interface);
emit Update();
emit ConfigChanged();
emit Save();
}

void MappingWindow::OnClearFieldsPressed()
{
// Loading an empty inifile section clears everything.
IniFile::Section sec;

// Keep the currently selected device.
const auto default_device = m_controller->GetDefaultDevice();
m_controller->LoadConfig(&sec);
m_controller->SetDefaultDevice(default_device);

m_controller->UpdateReferences(g_controller_interface);
emit Update();
emit ConfigChanged();
emit Save();
}

@@ -49,12 +49,14 @@ class MappingWindow final : public QDialog
explicit MappingWindow(QWidget* parent, Type type, int port_num);

int GetPort() const;
std::shared_ptr<ciface::Core::Device> GetDevice() const;
ControllerEmu::EmulatedController* GetController() const;
bool IsIterativeInput() const;
bool IsMappingAllDevices() const;

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.
void Update();
void Save();

@@ -75,7 +77,7 @@ class MappingWindow final : public QDialog
void OnSaveProfilePressed();
void OnDefaultFieldsPressed();
void OnClearFieldsPressed();
void OnDeviceChanged(int index);
void OnSelectDevice(int index);
void OnGlobalDevicesChanged();

ControllerEmu::EmulatedController* m_controller = nullptr;