87 changes: 26 additions & 61 deletions Source/Core/DolphinQt/TAS/TASInputWindow.cpp
Expand Up @@ -23,6 +23,7 @@
#include "DolphinQt/TAS/StickWidget.h"
#include "DolphinQt/TAS/TASCheckBox.h"
#include "DolphinQt/TAS/TASSlider.h"
#include "DolphinQt/TAS/TASSpinBox.h"

#include "InputCommon/ControllerEmu/ControllerEmu.h"
#include "InputCommon/ControllerEmu/StickGate.h"
Expand Down Expand Up @@ -93,9 +94,8 @@ TASCheckBox* TASInputWindow::CreateButton(const QString& text, std::string_view
}

QGroupBox* TASInputWindow::CreateStickInputs(const QString& text, std::string_view group_name,
InputOverrider* overrider, QSpinBox*& x_value,
QSpinBox*& y_value, u16 min_x, u16 min_y, u16 max_x,
u16 max_y, Qt::Key x_shortcut_key,
InputOverrider* overrider, u16 min_x, u16 min_y,
u16 max_x, u16 max_y, Qt::Key x_shortcut_key,
Qt::Key y_shortcut_key)
{
const QKeySequence x_shortcut_key_sequence = QKeySequence(Qt::ALT | x_shortcut_key);
Expand All @@ -110,11 +110,11 @@ QGroupBox* TASInputWindow::CreateStickInputs(const QString& text, std::string_vi
const int y_default = static_cast<int>(std::round(max_y / 2.));

auto* x_layout = new QHBoxLayout;
x_value = CreateSliderValuePair(x_layout, x_default, max_x, x_shortcut_key_sequence,
Qt::Horizontal, box);
TASSpinBox* x_value = CreateSliderValuePair(x_layout, x_default, max_x, x_shortcut_key_sequence,
Qt::Horizontal, box);

auto* y_layout = new QVBoxLayout;
y_value =
TASSpinBox* y_value =
CreateSliderValuePair(y_layout, y_default, max_y, y_shortcut_key_sequence, Qt::Vertical, box);
y_value->setMaximumWidth(60);

Expand Down Expand Up @@ -153,8 +153,8 @@ QGroupBox* TASInputWindow::CreateStickInputs(const QString& text, std::string_vi

QBoxLayout* TASInputWindow::CreateSliderValuePairLayout(
const QString& text, std::string_view group_name, std::string_view control_name,
InputOverrider* overrider, QSpinBox*& value, u16 zero, int default_, u16 min, u16 max,
Qt::Key shortcut_key, QWidget* shortcut_widget, std::optional<ControlState> scale)
InputOverrider* overrider, u16 zero, int default_, u16 min, u16 max, Qt::Key shortcut_key,
QWidget* shortcut_widget, std::optional<ControlState> scale)
{
const QKeySequence shortcut_key_sequence = QKeySequence(Qt::ALT | shortcut_key);

Expand All @@ -164,20 +164,20 @@ QBoxLayout* TASInputWindow::CreateSliderValuePairLayout(
QBoxLayout* layout = new QHBoxLayout;
layout->addWidget(label);

value = CreateSliderValuePair(group_name, control_name, overrider, layout, zero, default_, min,
max, shortcut_key_sequence, Qt::Horizontal, shortcut_widget, scale);
CreateSliderValuePair(group_name, control_name, overrider, layout, zero, default_, min, max,
shortcut_key_sequence, Qt::Horizontal, shortcut_widget, scale);

return layout;
}

QSpinBox* TASInputWindow::CreateSliderValuePair(
TASSpinBox* TASInputWindow::CreateSliderValuePair(
std::string_view group_name, std::string_view control_name, InputOverrider* overrider,
QBoxLayout* layout, u16 zero, int default_, u16 min, u16 max,
QKeySequence shortcut_key_sequence, Qt::Orientation orientation, QWidget* shortcut_widget,
std::optional<ControlState> scale)
{
QSpinBox* value = CreateSliderValuePair(layout, default_, max, shortcut_key_sequence, orientation,
shortcut_widget);
TASSpinBox* value = CreateSliderValuePair(layout, default_, max, shortcut_key_sequence,
orientation, shortcut_widget);

InputOverrider::OverrideFunction func;
if (scale)
Expand All @@ -200,12 +200,12 @@ QSpinBox* TASInputWindow::CreateSliderValuePair(

// The shortcut_widget argument needs to specify the container widget that will be hidden/shown.
// This is done to avoid ambigous shortcuts
QSpinBox* TASInputWindow::CreateSliderValuePair(QBoxLayout* layout, int default_, u16 max,
QKeySequence shortcut_key_sequence,
Qt::Orientation orientation,
QWidget* shortcut_widget)
TASSpinBox* TASInputWindow::CreateSliderValuePair(QBoxLayout* layout, int default_, u16 max,
QKeySequence shortcut_key_sequence,
Qt::Orientation orientation,
QWidget* shortcut_widget)
{
auto* value = new QSpinBox();
auto* value = new TASSpinBox();
value->setRange(0, 99999);
value->setValue(default_);
connect(value, qOverload<int>(&QSpinBox::valueChanged), [value, max](int i) {
Expand Down Expand Up @@ -239,67 +239,32 @@ std::optional<ControlState> TASInputWindow::GetButton(TASCheckBox* checkbox,
{
const bool pressed = std::llround(controller_state) > 0;
if (m_use_controller->isChecked())
{
if (pressed)
{
m_checkbox_set_by_controller[checkbox] = true;
QueueOnObjectBlocking(checkbox, [checkbox] { checkbox->setChecked(true); });
}
else if (m_checkbox_set_by_controller.count(checkbox) && m_checkbox_set_by_controller[checkbox])
{
m_checkbox_set_by_controller[checkbox] = false;
QueueOnObjectBlocking(checkbox, [checkbox] { checkbox->setChecked(false); });
}
}
checkbox->OnControllerValueChanged(pressed);

return checkbox->GetValue() ? 1.0 : 0.0;
}

std::optional<ControlState> TASInputWindow::GetSpinBox(QSpinBox* spin, u16 zero, u16 min, u16 max,
std::optional<ControlState> TASInputWindow::GetSpinBox(TASSpinBox* spin, u16 zero, u16 min, u16 max,
ControlState controller_state)
{
const u16 controller_value =
ControllerEmu::EmulatedController::MapFloat<u16>(controller_state, zero, 0, max);

if (m_use_controller->isChecked())
{
if (!m_spinbox_most_recent_values.count(spin) ||
m_spinbox_most_recent_values[spin] != controller_value)
{
QueueOnObjectBlocking(spin, [spin, controller_value] { spin->setValue(controller_value); });
}

m_spinbox_most_recent_values[spin] = controller_value;
}
else
{
m_spinbox_most_recent_values.clear();
}
spin->OnControllerValueChanged(controller_value);

return ControllerEmu::EmulatedController::MapToFloat<ControlState, u16>(spin->value(), zero, min,
max);
return ControllerEmu::EmulatedController::MapToFloat<ControlState, u16>(spin->GetValue(), zero,
min, max);
}

std::optional<ControlState> TASInputWindow::GetSpinBox(QSpinBox* spin, u16 zero,
std::optional<ControlState> TASInputWindow::GetSpinBox(TASSpinBox* spin, u16 zero,
ControlState controller_state,
ControlState scale)
{
const u16 controller_value = static_cast<u16>(std::llround(controller_state * scale + zero));

if (m_use_controller->isChecked())
{
if (!m_spinbox_most_recent_values.count(spin) ||
m_spinbox_most_recent_values[spin] != controller_value)
{
QueueOnObjectBlocking(spin, [spin, controller_value] { spin->setValue(controller_value); });
}

m_spinbox_most_recent_values[spin] = controller_value;
}
else
{
m_spinbox_most_recent_values.clear();
}
spin->OnControllerValueChanged(controller_value);

return (spin->value() - zero) / scale;
return (spin->GetValue() - zero) / scale;
}
34 changes: 16 additions & 18 deletions Source/Core/DolphinQt/TAS/TASInputWindow.h
Expand Up @@ -22,6 +22,7 @@ class QGroupBox;
class QSpinBox;
class QString;
class TASCheckBox;
class TASSpinBox;

class InputOverrider final
{
Expand Down Expand Up @@ -50,22 +51,22 @@ class TASInputWindow : public QDialog
TASCheckBox* CreateButton(const QString& text, std::string_view group_name,
std::string_view control_name, InputOverrider* overrider);
QGroupBox* CreateStickInputs(const QString& text, std::string_view group_name,
InputOverrider* overrider, QSpinBox*& x_value, QSpinBox*& y_value,
u16 min_x, u16 min_y, u16 max_x, u16 max_y, Qt::Key x_shortcut_key,
Qt::Key y_shortcut_key);
InputOverrider* overrider, u16 min_x, u16 min_y, u16 max_x,
u16 max_y, Qt::Key x_shortcut_key, Qt::Key y_shortcut_key);
QBoxLayout* CreateSliderValuePairLayout(const QString& text, std::string_view group_name,
std::string_view control_name, InputOverrider* overrider,
QSpinBox*& value, u16 zero, int default_, u16 min,
u16 max, Qt::Key shortcut_key, QWidget* shortcut_widget,
u16 zero, int default_, u16 min, u16 max,
Qt::Key shortcut_key, QWidget* shortcut_widget,
std::optional<ControlState> scale = {});
QSpinBox* CreateSliderValuePair(std::string_view group_name, std::string_view control_name,
InputOverrider* overrider, QBoxLayout* layout, u16 zero,
int default_, u16 min, u16 max,
QKeySequence shortcut_key_sequence, Qt::Orientation orientation,
QWidget* shortcut_widget, std::optional<ControlState> scale = {});
QSpinBox* CreateSliderValuePair(QBoxLayout* layout, int default_, u16 max,
QKeySequence shortcut_key_sequence, Qt::Orientation orientation,
QWidget* shortcut_widget);
TASSpinBox* CreateSliderValuePair(std::string_view group_name, std::string_view control_name,
InputOverrider* overrider, QBoxLayout* layout, u16 zero,
int default_, u16 min, u16 max,
QKeySequence shortcut_key_sequence, Qt::Orientation orientation,
QWidget* shortcut_widget,
std::optional<ControlState> scale = {});
TASSpinBox* CreateSliderValuePair(QBoxLayout* layout, int default_, u16 max,
QKeySequence shortcut_key_sequence, Qt::Orientation orientation,
QWidget* shortcut_widget);

QGroupBox* m_settings_box;
QCheckBox* m_use_controller;
Expand All @@ -74,11 +75,8 @@ class TASInputWindow : public QDialog

private:
std::optional<ControlState> GetButton(TASCheckBox* checkbox, ControlState controller_state);
std::optional<ControlState> GetSpinBox(QSpinBox* spin, u16 zero, u16 min, u16 max,
std::optional<ControlState> GetSpinBox(TASSpinBox* spin, u16 zero, u16 min, u16 max,
ControlState controller_state);
std::optional<ControlState> GetSpinBox(QSpinBox* spin, u16 zero, ControlState controller_state,
std::optional<ControlState> GetSpinBox(TASSpinBox* spin, u16 zero, ControlState controller_state,
ControlState scale);

std::map<TASCheckBox*, bool> m_checkbox_set_by_controller;
std::map<QSpinBox*, u16> m_spinbox_most_recent_values;
};
33 changes: 33 additions & 0 deletions Source/Core/DolphinQt/TAS/TASSpinBox.cpp
@@ -0,0 +1,33 @@
// Copyright 2019 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "DolphinQt/TAS/TASSpinBox.h"

#include "DolphinQt/QtUtils/QueueOnObject.h"

TASSpinBox::TASSpinBox(QWidget* parent) : QSpinBox(parent)
{
connect(this, QOverload<int>::of(&TASSpinBox::valueChanged), this, &TASSpinBox::OnUIValueChanged);
}

int TASSpinBox::GetValue() const
{
return m_state.GetValue();
}

void TASSpinBox::OnControllerValueChanged(int new_value)
{
if (m_state.OnControllerValueChanged(static_cast<u16>(new_value)))
QueueOnObject(this, &TASSpinBox::ApplyControllerValueChange);
}

void TASSpinBox::OnUIValueChanged(int new_value)
{
m_state.OnUIValueChanged(static_cast<u16>(new_value));
}

void TASSpinBox::ApplyControllerValueChange()
{
const QSignalBlocker blocker(this);
setValue(m_state.ApplyControllerValueChange());
}
29 changes: 29 additions & 0 deletions Source/Core/DolphinQt/TAS/TASSpinBox.h
@@ -0,0 +1,29 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <QSpinBox>

#include "DolphinQt/TAS/TASControlState.h"

class TASInputWindow;

class TASSpinBox : public QSpinBox
{
Q_OBJECT
public:
explicit TASSpinBox(QWidget* parent = nullptr);

// Can be called from the CPU thread
int GetValue() const;
// Must be called from the CPU thread
void OnControllerValueChanged(int new_value);

private slots:
void OnUIValueChanged(int new_value);
void ApplyControllerValueChange();

private:
TASControlState m_state;
};
47 changes: 20 additions & 27 deletions Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp
Expand Up @@ -29,6 +29,7 @@
#include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/TAS/IRWidget.h"
#include "DolphinQt/TAS/TASCheckBox.h"
#include "DolphinQt/TAS/TASSpinBox.h"

#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
Expand Down Expand Up @@ -83,19 +84,17 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow(
ir_layout->addLayout(visual_layout);
m_ir_box->setLayout(ir_layout);

m_nunchuk_stick_box = CreateStickInputs(
tr("Nunchuk Stick"), WiimoteEmu::Nunchuk::STICK_GROUP, &m_nunchuk_overrider,
m_nunchuk_stick_x_value, m_nunchuk_stick_y_value, 0, 0, 255, 255, Qt::Key_X, Qt::Key_Y);
m_nunchuk_stick_box =
CreateStickInputs(tr("Nunchuk Stick"), WiimoteEmu::Nunchuk::STICK_GROUP, &m_nunchuk_overrider,
0, 0, 255, 255, Qt::Key_X, Qt::Key_Y);

m_classic_left_stick_box =
CreateStickInputs(tr("Left Stick"), WiimoteEmu::Classic::LEFT_STICK_GROUP,
&m_classic_overrider, m_classic_left_stick_x_value,
m_classic_left_stick_y_value, 0, 0, 63, 63, Qt::Key_F, Qt::Key_G);
&m_classic_overrider, 0, 0, 63, 63, Qt::Key_F, Qt::Key_G);

m_classic_right_stick_box =
CreateStickInputs(tr("Right Stick"), WiimoteEmu::Classic::RIGHT_STICK_GROUP,
&m_classic_overrider, m_classic_right_stick_x_value,
m_classic_right_stick_y_value, 0, 0, 31, 31, Qt::Key_Q, Qt::Key_W);
&m_classic_overrider, 0, 0, 31, 31, Qt::Key_Q, Qt::Key_W);

// Need to enforce the same minimum width because otherwise the different lengths in the labels
// used on the QGroupBox will cause the StickWidgets to have different sizes.
Expand All @@ -120,23 +119,20 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow(
// i18n: Refers to a 3D axis (used when mapping motion controls)
CreateSliderValuePairLayout(tr("X"), WiimoteEmu::Wiimote::ACCELEROMETER_GROUP,
ControllerEmu::ReshapableInput::X_INPUT_OVERRIDE,
&m_wiimote_overrider, m_remote_orientation_x_value, ACCEL_ZERO_G,
ACCEL_ZERO_G, ACCEL_MIN, ACCEL_MAX, Qt::Key_Q,
m_remote_orientation_box, ACCEL_SCALE);
&m_wiimote_overrider, ACCEL_ZERO_G, ACCEL_ZERO_G, ACCEL_MIN,
ACCEL_MAX, Qt::Key_Q, m_remote_orientation_box, ACCEL_SCALE);
auto* remote_orientation_y_layout =
// i18n: Refers to a 3D axis (used when mapping motion controls)
CreateSliderValuePairLayout(tr("Y"), WiimoteEmu::Wiimote::ACCELEROMETER_GROUP,
ControllerEmu::ReshapableInput::Y_INPUT_OVERRIDE,
&m_wiimote_overrider, m_remote_orientation_y_value, ACCEL_ZERO_G,
ACCEL_ZERO_G, ACCEL_MIN, ACCEL_MAX, Qt::Key_W,
m_remote_orientation_box, ACCEL_SCALE);
&m_wiimote_overrider, ACCEL_ZERO_G, ACCEL_ZERO_G, ACCEL_MIN,
ACCEL_MAX, Qt::Key_W, m_remote_orientation_box, ACCEL_SCALE);
auto* remote_orientation_z_layout =
// i18n: Refers to a 3D axis (used when mapping motion controls)
CreateSliderValuePairLayout(tr("Z"), WiimoteEmu::Wiimote::ACCELEROMETER_GROUP,
ControllerEmu::ReshapableInput::Z_INPUT_OVERRIDE,
&m_wiimote_overrider, m_remote_orientation_z_value, ACCEL_ZERO_G,
ACCEL_ONE_G, ACCEL_MIN, ACCEL_MAX, Qt::Key_E,
m_remote_orientation_box, ACCEL_SCALE);
&m_wiimote_overrider, ACCEL_ZERO_G, ACCEL_ONE_G, ACCEL_MIN,
ACCEL_MAX, Qt::Key_E, m_remote_orientation_box, ACCEL_SCALE);

auto* remote_orientation_layout = new QVBoxLayout;
remote_orientation_layout->addLayout(remote_orientation_x_layout);
Expand All @@ -150,23 +146,20 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow(
// i18n: Refers to a 3D axis (used when mapping motion controls)
CreateSliderValuePairLayout(tr("X"), WiimoteEmu::Nunchuk::ACCELEROMETER_GROUP,
ControllerEmu::ReshapableInput::X_INPUT_OVERRIDE,
&m_nunchuk_overrider, m_nunchuk_orientation_x_value, ACCEL_ZERO_G,
ACCEL_ZERO_G, ACCEL_MIN, ACCEL_MAX, Qt::Key_I,
m_nunchuk_orientation_box);
&m_nunchuk_overrider, ACCEL_ZERO_G, ACCEL_ZERO_G, ACCEL_MIN,
ACCEL_MAX, Qt::Key_I, m_nunchuk_orientation_box);
auto* nunchuk_orientation_y_layout =
// i18n: Refers to a 3D axis (used when mapping motion controls)
CreateSliderValuePairLayout(tr("Y"), WiimoteEmu::Nunchuk::ACCELEROMETER_GROUP,
ControllerEmu::ReshapableInput::Y_INPUT_OVERRIDE,
&m_nunchuk_overrider, m_nunchuk_orientation_y_value, ACCEL_ZERO_G,
ACCEL_ZERO_G, ACCEL_MIN, ACCEL_MAX, Qt::Key_O,
m_nunchuk_orientation_box);
&m_nunchuk_overrider, ACCEL_ZERO_G, ACCEL_ZERO_G, ACCEL_MIN,
ACCEL_MAX, Qt::Key_O, m_nunchuk_orientation_box);
auto* nunchuk_orientation_z_layout =
// i18n: Refers to a 3D axis (used when mapping motion controls)
CreateSliderValuePairLayout(tr("Z"), WiimoteEmu::Nunchuk::ACCELEROMETER_GROUP,
ControllerEmu::ReshapableInput::Z_INPUT_OVERRIDE,
&m_nunchuk_overrider, m_nunchuk_orientation_z_value, ACCEL_ZERO_G,
ACCEL_ONE_G, ACCEL_MIN, ACCEL_MAX, Qt::Key_P,
m_nunchuk_orientation_box);
&m_nunchuk_overrider, ACCEL_ZERO_G, ACCEL_ONE_G, ACCEL_MIN,
ACCEL_MAX, Qt::Key_P, m_nunchuk_orientation_box);

auto* nunchuk_orientation_layout = new QVBoxLayout;
nunchuk_orientation_layout->addLayout(nunchuk_orientation_x_layout);
Expand All @@ -177,10 +170,10 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow(
m_triggers_box = new QGroupBox(tr("Triggers"));
auto* l_trigger_layout = CreateSliderValuePairLayout(
tr("Left"), WiimoteEmu::Classic::TRIGGERS_GROUP, WiimoteEmu::Classic::L_ANALOG,
&m_classic_overrider, m_left_trigger_value, 0, 0, 0, 31, Qt::Key_N, m_triggers_box);
&m_classic_overrider, 0, 0, 0, 31, Qt::Key_N, m_triggers_box);
auto* r_trigger_layout = CreateSliderValuePairLayout(
tr("Right"), WiimoteEmu::Classic::TRIGGERS_GROUP, WiimoteEmu::Classic::R_ANALOG,
&m_classic_overrider, m_right_trigger_value, 0, 0, 0, 31, Qt::Key_M, m_triggers_box);
&m_classic_overrider, 0, 0, 0, 31, Qt::Key_M, m_triggers_box);

auto* triggers_layout = new QVBoxLayout;
triggers_layout->addLayout(l_trigger_layout);
Expand Down
19 changes: 3 additions & 16 deletions Source/Core/DolphinQt/TAS/WiiTASInputWindow.h
Expand Up @@ -12,6 +12,7 @@ class QHideEvent;
class QShowEvent;
class QSpinBox;
class TASCheckBox;
class TASSpinBox;

namespace WiimoteEmu
{
Expand Down Expand Up @@ -75,22 +76,8 @@ class WiiTASInputWindow : public TASInputWindow
TASCheckBox* m_classic_up_button;
TASCheckBox* m_classic_down_button;
TASCheckBox* m_classic_right_button;
QSpinBox* m_remote_orientation_x_value;
QSpinBox* m_remote_orientation_y_value;
QSpinBox* m_remote_orientation_z_value;
QSpinBox* m_nunchuk_orientation_x_value;
QSpinBox* m_nunchuk_orientation_y_value;
QSpinBox* m_nunchuk_orientation_z_value;
QSpinBox* m_ir_x_value;
QSpinBox* m_ir_y_value;
QSpinBox* m_nunchuk_stick_x_value;
QSpinBox* m_nunchuk_stick_y_value;
QSpinBox* m_classic_left_stick_x_value;
QSpinBox* m_classic_left_stick_y_value;
QSpinBox* m_classic_right_stick_x_value;
QSpinBox* m_classic_right_stick_y_value;
QSpinBox* m_left_trigger_value;
QSpinBox* m_right_trigger_value;
TASSpinBox* m_ir_x_value;
TASSpinBox* m_ir_y_value;
QGroupBox* m_remote_orientation_box;
QGroupBox* m_nunchuk_orientation_box;
QGroupBox* m_ir_box;
Expand Down