diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 930e95820bfe..e9d24bc4d151 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -337,10 +337,14 @@ add_executable(dolphin-emu TAS/StickWidget.h TAS/TASCheckBox.cpp TAS/TASCheckBox.h + TAS/TASControlState.cpp + TAS/TASControlState.h TAS/TASInputWindow.cpp TAS/TASInputWindow.h TAS/TASSlider.cpp TAS/TASSlider.h + TAS/TASSpinBox.cpp + TAS/TASSpinBox.h TAS/WiiTASInputWindow.cpp TAS/WiiTASInputWindow.h ToolBar.cpp diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index f776feceedb3..838e33d7a117 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -207,7 +207,9 @@ + + @@ -242,6 +244,7 @@ + @@ -387,6 +390,7 @@ + diff --git a/Source/Core/DolphinQt/TAS/GCTASInputWindow.cpp b/Source/Core/DolphinQt/TAS/GCTASInputWindow.cpp index 68075185d228..67c3d9a3d7a7 100644 --- a/Source/Core/DolphinQt/TAS/GCTASInputWindow.cpp +++ b/Source/Core/DolphinQt/TAS/GCTASInputWindow.cpp @@ -26,12 +26,10 @@ GCTASInputWindow::GCTASInputWindow(QWidget* parent, int controller_id) { setWindowTitle(tr("GameCube TAS Input %1").arg(controller_id + 1)); - m_main_stick_box = CreateStickInputs(tr("Main Stick"), GCPad::MAIN_STICK_GROUP, &m_overrider, - m_x_main_stick_value, m_y_main_stick_value, 1, 1, 255, 255, - Qt::Key_F, Qt::Key_G); - m_c_stick_box = - CreateStickInputs(tr("C Stick"), GCPad::C_STICK_GROUP, &m_overrider, m_x_c_stick_value, - m_y_c_stick_value, 1, 1, 255, 255, Qt::Key_H, Qt::Key_J); + m_main_stick_box = CreateStickInputs(tr("Main Stick"), GCPad::MAIN_STICK_GROUP, &m_overrider, 1, + 1, 255, 255, Qt::Key_F, Qt::Key_G); + m_c_stick_box = CreateStickInputs(tr("C Stick"), GCPad::C_STICK_GROUP, &m_overrider, 1, 1, 255, + 255, Qt::Key_H, Qt::Key_J); auto* top_layout = new QHBoxLayout; top_layout->addWidget(m_main_stick_box); @@ -41,11 +39,11 @@ GCTASInputWindow::GCTASInputWindow(QWidget* parent, int controller_id) auto* l_trigger_layout = CreateSliderValuePairLayout(tr("Left"), GCPad::TRIGGERS_GROUP, GCPad::L_ANALOG, &m_overrider, - m_l_trigger_value, 0, 0, 0, 255, Qt::Key_N, m_triggers_box); + 0, 0, 0, 255, Qt::Key_N, m_triggers_box); auto* r_trigger_layout = CreateSliderValuePairLayout(tr("Right"), GCPad::TRIGGERS_GROUP, GCPad::R_ANALOG, &m_overrider, - m_r_trigger_value, 0, 0, 0, 255, Qt::Key_M, m_triggers_box); + 0, 0, 0, 255, Qt::Key_M, m_triggers_box); auto* triggers_layout = new QVBoxLayout; triggers_layout->addLayout(l_trigger_layout); diff --git a/Source/Core/DolphinQt/TAS/GCTASInputWindow.h b/Source/Core/DolphinQt/TAS/GCTASInputWindow.h index 00830c0f5270..3a3fd20d49a9 100644 --- a/Source/Core/DolphinQt/TAS/GCTASInputWindow.h +++ b/Source/Core/DolphinQt/TAS/GCTASInputWindow.h @@ -37,12 +37,6 @@ class GCTASInputWindow : public TASInputWindow TASCheckBox* m_up_button; TASCheckBox* m_down_button; TASCheckBox* m_right_button; - QSpinBox* m_l_trigger_value; - QSpinBox* m_r_trigger_value; - QSpinBox* m_x_main_stick_value; - QSpinBox* m_y_main_stick_value; - QSpinBox* m_x_c_stick_value; - QSpinBox* m_y_c_stick_value; QGroupBox* m_main_stick_box; QGroupBox* m_c_stick_box; QGroupBox* m_triggers_box; diff --git a/Source/Core/DolphinQt/TAS/TASCheckBox.cpp b/Source/Core/DolphinQt/TAS/TASCheckBox.cpp index 29a7171f474b..32d1b3b0b006 100644 --- a/Source/Core/DolphinQt/TAS/TASCheckBox.cpp +++ b/Source/Core/DolphinQt/TAS/TASCheckBox.cpp @@ -6,23 +6,34 @@ #include #include "Core/Movie.h" +#include "DolphinQt/QtUtils/QueueOnObject.h" #include "DolphinQt/TAS/TASInputWindow.h" TASCheckBox::TASCheckBox(const QString& text, TASInputWindow* parent) : QCheckBox(text, parent), m_parent(parent) { setTristate(true); + + connect(this, &TASCheckBox::stateChanged, this, &TASCheckBox::OnUIValueChanged); } bool TASCheckBox::GetValue() const { - if (checkState() == Qt::PartiallyChecked) + Qt::CheckState check_state = static_cast(m_state.GetValue()); + + if (check_state == Qt::PartiallyChecked) { const u64 frames_elapsed = Movie::GetCurrentFrame() - m_frame_turbo_started; return static_cast(frames_elapsed % m_turbo_total_frames) < m_turbo_press_frames; } - return isChecked(); + return check_state != Qt::Unchecked; +} + +void TASCheckBox::OnControllerValueChanged(bool new_value) +{ + if (m_state.OnControllerValueChanged(new_value ? Qt::Checked : Qt::Unchecked)) + QueueOnObject(this, &TASCheckBox::ApplyControllerValueChange); } void TASCheckBox::mousePressEvent(QMouseEvent* event) @@ -44,3 +55,14 @@ void TASCheckBox::mousePressEvent(QMouseEvent* event) m_turbo_total_frames = m_turbo_press_frames + m_parent->GetTurboReleaseFrames(); setCheckState(Qt::PartiallyChecked); } + +void TASCheckBox::OnUIValueChanged(int new_value) +{ + m_state.OnUIValueChanged(static_cast(new_value)); +} + +void TASCheckBox::ApplyControllerValueChange() +{ + const QSignalBlocker blocker(this); + setCheckState(static_cast(m_state.ApplyControllerValueChange())); +} diff --git a/Source/Core/DolphinQt/TAS/TASCheckBox.h b/Source/Core/DolphinQt/TAS/TASCheckBox.h index afec67119433..3af68d43b207 100644 --- a/Source/Core/DolphinQt/TAS/TASCheckBox.h +++ b/Source/Core/DolphinQt/TAS/TASCheckBox.h @@ -5,6 +5,8 @@ #include +#include "DolphinQt/TAS/TASControlState.h" + class QMouseEvent; class TASInputWindow; @@ -14,13 +16,21 @@ class TASCheckBox : public QCheckBox public: explicit TASCheckBox(const QString& text, TASInputWindow* parent); + // Can be called from the CPU thread bool GetValue() const; + // Must be called from the CPU thread + void OnControllerValueChanged(bool new_value); protected: void mousePressEvent(QMouseEvent* event) override; +private slots: + void OnUIValueChanged(int new_value); + void ApplyControllerValueChange(); + private: const TASInputWindow* m_parent; + TASControlState m_state; int m_frame_turbo_started = 0; int m_turbo_press_frames = 0; int m_turbo_total_frames = 0; diff --git a/Source/Core/DolphinQt/TAS/TASControlState.cpp b/Source/Core/DolphinQt/TAS/TASControlState.cpp new file mode 100644 index 000000000000..fd0c209aa6fd --- /dev/null +++ b/Source/Core/DolphinQt/TAS/TASControlState.cpp @@ -0,0 +1,58 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/TAS/TASControlState.h" + +#include + +#include "Common/CommonTypes.h" + +u16 TASControlState::GetValue() const +{ + const State ui_thread_state = m_ui_thread_state.load(std::memory_order_relaxed); + const State cpu_thread_state = m_cpu_thread_state.load(std::memory_order_relaxed); + + return (ui_thread_state.version != cpu_thread_state.version ? cpu_thread_state : ui_thread_state) + .value; +} + +bool TASControlState::OnControllerValueChanged(u16 new_value) +{ + const State cpu_thread_state = m_cpu_thread_state.load(std::memory_order_relaxed); + + if (cpu_thread_state.value == new_value) + { + // The CPU thread state is already up to date with the controller. No need to do anything + return false; + } + + const State new_state{static_cast(cpu_thread_state.version + 1), new_value}; + m_cpu_thread_state.store(new_state, std::memory_order_relaxed); + + return true; +} + +void TASControlState::OnUIValueChanged(u16 new_value) +{ + const State ui_thread_state = m_ui_thread_state.load(std::memory_order_relaxed); + + const State new_state{ui_thread_state.version, new_value}; + m_ui_thread_state.store(new_state, std::memory_order_relaxed); +} + +u16 TASControlState::ApplyControllerValueChange() +{ + const State ui_thread_state = m_ui_thread_state.load(std::memory_order_relaxed); + const State cpu_thread_state = m_cpu_thread_state.load(std::memory_order_relaxed); + + if (ui_thread_state.version == cpu_thread_state.version) + { + // The UI thread state is already up to date with the CPU thread. No need to do anything + return ui_thread_state.value; + } + else + { + m_ui_thread_state.store(cpu_thread_state, std::memory_order_relaxed); + return cpu_thread_state.value; + } +} diff --git a/Source/Core/DolphinQt/TAS/TASControlState.h b/Source/Core/DolphinQt/TAS/TASControlState.h new file mode 100644 index 000000000000..b53c7f95f99d --- /dev/null +++ b/Source/Core/DolphinQt/TAS/TASControlState.h @@ -0,0 +1,43 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "Common/CommonTypes.h" + +class TASControlState +{ +public: + // Call this from the CPU thread to get the current value. (This function can also safely be + // called from the UI thread, but you're effectively just getting the value the UI control has.) + u16 GetValue() const; + // Call this from the CPU thread when the controller state changes. + // If the return value is true, queue up a call to ApplyControllerChangeValue on the UI thread. + bool OnControllerValueChanged(u16 new_value); + // Call this from the UI thread when the user changes the value using the UI. + void OnUIValueChanged(u16 new_value); + // Call this from the UI thread after OnControllerValueChanged returns true, + // and set the state of the UI control to the return value. + u16 ApplyControllerValueChange(); + +private: + // A description of how threading is handled: The UI thread can update its copy of the state + // whenever it wants to, and must *not* increment the version when doing so. The CPU thread can + // update its copy of the state whenever it wants to, and *must* increment the version when doing + // so. When the CPU thread updates its copy of the state, the UI thread should then (possibly + // after a delay) mirror the change by copying the CPU thread's state to the UI thread's state. + // This mirroring is the only way for the version number stored in the UI thread's state to + // change. The version numbers of the two copies can be compared to check if the UI thread's view + // of what has happened on the CPU thread is up to date. + + struct State + { + u16 version = 0; + u16 value = 0; + }; + + std::atomic m_ui_thread_state; + std::atomic m_cpu_thread_state; +}; diff --git a/Source/Core/DolphinQt/TAS/TASInputWindow.cpp b/Source/Core/DolphinQt/TAS/TASInputWindow.cpp index 0054043a8ea3..d74a84997f38 100644 --- a/Source/Core/DolphinQt/TAS/TASInputWindow.cpp +++ b/Source/Core/DolphinQt/TAS/TASInputWindow.cpp @@ -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" @@ -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); @@ -110,11 +110,11 @@ QGroupBox* TASInputWindow::CreateStickInputs(const QString& text, std::string_vi const int y_default = static_cast(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); @@ -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 scale) + InputOverrider* overrider, u16 zero, int default_, u16 min, u16 max, Qt::Key shortcut_key, + QWidget* shortcut_widget, std::optional scale) { const QKeySequence shortcut_key_sequence = QKeySequence(Qt::ALT | shortcut_key); @@ -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 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) @@ -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(&QSpinBox::valueChanged), [value, max](int i) { @@ -239,67 +239,32 @@ std::optional 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 TASInputWindow::GetSpinBox(QSpinBox* spin, u16 zero, u16 min, u16 max, +std::optional TASInputWindow::GetSpinBox(TASSpinBox* spin, u16 zero, u16 min, u16 max, ControlState controller_state) { const u16 controller_value = ControllerEmu::EmulatedController::MapFloat(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(spin->value(), zero, min, - max); + return ControllerEmu::EmulatedController::MapToFloat(spin->GetValue(), zero, + min, max); } -std::optional TASInputWindow::GetSpinBox(QSpinBox* spin, u16 zero, +std::optional TASInputWindow::GetSpinBox(TASSpinBox* spin, u16 zero, ControlState controller_state, ControlState scale) { const u16 controller_value = static_cast(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; } diff --git a/Source/Core/DolphinQt/TAS/TASInputWindow.h b/Source/Core/DolphinQt/TAS/TASInputWindow.h index e1f56e023596..45d84ba014c0 100644 --- a/Source/Core/DolphinQt/TAS/TASInputWindow.h +++ b/Source/Core/DolphinQt/TAS/TASInputWindow.h @@ -22,6 +22,7 @@ class QGroupBox; class QSpinBox; class QString; class TASCheckBox; +class TASSpinBox; class InputOverrider final { @@ -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 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 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 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; @@ -74,11 +75,8 @@ class TASInputWindow : public QDialog private: std::optional GetButton(TASCheckBox* checkbox, ControlState controller_state); - std::optional GetSpinBox(QSpinBox* spin, u16 zero, u16 min, u16 max, + std::optional GetSpinBox(TASSpinBox* spin, u16 zero, u16 min, u16 max, ControlState controller_state); - std::optional GetSpinBox(QSpinBox* spin, u16 zero, ControlState controller_state, + std::optional GetSpinBox(TASSpinBox* spin, u16 zero, ControlState controller_state, ControlState scale); - - std::map m_checkbox_set_by_controller; - std::map m_spinbox_most_recent_values; }; diff --git a/Source/Core/DolphinQt/TAS/TASSpinBox.cpp b/Source/Core/DolphinQt/TAS/TASSpinBox.cpp new file mode 100644 index 000000000000..30c3618b26f3 --- /dev/null +++ b/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::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(new_value))) + QueueOnObject(this, &TASSpinBox::ApplyControllerValueChange); +} + +void TASSpinBox::OnUIValueChanged(int new_value) +{ + m_state.OnUIValueChanged(static_cast(new_value)); +} + +void TASSpinBox::ApplyControllerValueChange() +{ + const QSignalBlocker blocker(this); + setValue(m_state.ApplyControllerValueChange()); +} diff --git a/Source/Core/DolphinQt/TAS/TASSpinBox.h b/Source/Core/DolphinQt/TAS/TASSpinBox.h new file mode 100644 index 000000000000..55908911d7e0 --- /dev/null +++ b/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 + +#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; +}; diff --git a/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp b/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp index e207ee0bb852..b6e54fb6da89 100644 --- a/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp +++ b/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp @@ -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" @@ -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. @@ -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); @@ -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); @@ -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); diff --git a/Source/Core/DolphinQt/TAS/WiiTASInputWindow.h b/Source/Core/DolphinQt/TAS/WiiTASInputWindow.h index a521301a602c..63ce0fb4dfa7 100644 --- a/Source/Core/DolphinQt/TAS/WiiTASInputWindow.h +++ b/Source/Core/DolphinQt/TAS/WiiTASInputWindow.h @@ -12,6 +12,7 @@ class QHideEvent; class QShowEvent; class QSpinBox; class TASCheckBox; +class TASSpinBox; namespace WiimoteEmu { @@ -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;