Merge pull request #11622 from JosJuice/tas-input-nonblocking
DolphinQt: Rework TAS input threading
This commit is contained in:
commit
234c5dd90e
|
@ -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
|
||||
|
|
|
@ -207,7 +207,9 @@
|
|||
<ClCompile Include="TAS\StickWidget.cpp" />
|
||||
<ClCompile Include="TAS\TASCheckBox.cpp" />
|
||||
<ClCompile Include="TAS\TASInputWindow.cpp" />
|
||||
<ClCompile Include="TAS\TASControlState.cpp" />
|
||||
<ClCompile Include="TAS\TASSlider.cpp" />
|
||||
<ClCompile Include="TAS\TASSpinBox.cpp" />
|
||||
<ClCompile Include="TAS\WiiTASInputWindow.cpp" />
|
||||
<ClCompile Include="ToolBar.cpp" />
|
||||
<ClCompile Include="Translation.cpp" />
|
||||
|
@ -242,6 +244,7 @@
|
|||
<ClInclude Include="QtUtils\WrapInScrollArea.h" />
|
||||
<ClInclude Include="ResourcePackManager.h" />
|
||||
<ClInclude Include="Resources.h" />
|
||||
<ClInclude Include="TAS\TASControlState.h" />
|
||||
<ClInclude Include="TAS\TASSlider.h" />
|
||||
<ClInclude Include="Translation.h" />
|
||||
<ClInclude Include="WiiUpdate.h" />
|
||||
|
@ -387,6 +390,7 @@
|
|||
<QtMoc Include="TAS\StickWidget.h" />
|
||||
<QtMoc Include="TAS\TASCheckBox.h" />
|
||||
<QtMoc Include="TAS\TASInputWindow.h" />
|
||||
<QtMoc Include="TAS\TASSpinBox.h" />
|
||||
<QtMoc Include="TAS\WiiTASInputWindow.h" />
|
||||
<QtMoc Include="ToolBar.h" />
|
||||
<QtMoc Include="Updater.h" />
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -37,12 +37,6 @@ private:
|
|||
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;
|
||||
|
|
|
@ -6,23 +6,34 @@
|
|||
#include <QMouseEvent>
|
||||
|
||||
#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<Qt::CheckState>(m_state.GetValue());
|
||||
|
||||
if (check_state == Qt::PartiallyChecked)
|
||||
{
|
||||
const u64 frames_elapsed = Movie::GetCurrentFrame() - m_frame_turbo_started;
|
||||
return static_cast<int>(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<u16>(new_value));
|
||||
}
|
||||
|
||||
void TASCheckBox::ApplyControllerValueChange()
|
||||
{
|
||||
const QSignalBlocker blocker(this);
|
||||
setCheckState(static_cast<Qt::CheckState>(m_state.ApplyControllerValueChange()));
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
#include <QCheckBox>
|
||||
|
||||
#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;
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "DolphinQt/TAS/TASControlState.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#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<u16>(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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#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<State> m_ui_thread_state;
|
||||
std::atomic<State> m_cpu_thread_state;
|
||||
};
|
|
@ -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<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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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); });
|
||||
}
|
||||
spin->OnControllerValueChanged(controller_value);
|
||||
|
||||
m_spinbox_most_recent_values[spin] = controller_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_spinbox_most_recent_values.clear();
|
||||
}
|
||||
|
||||
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); });
|
||||
}
|
||||
spin->OnControllerValueChanged(controller_value);
|
||||
|
||||
m_spinbox_most_recent_values[spin] = controller_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_spinbox_most_recent_values.clear();
|
||||
}
|
||||
|
||||
return (spin->value() - zero) / scale;
|
||||
return (spin->GetValue() - zero) / scale;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ class QGroupBox;
|
|||
class QSpinBox;
|
||||
class QString;
|
||||
class TASCheckBox;
|
||||
class TASSpinBox;
|
||||
|
||||
class InputOverrider final
|
||||
{
|
||||
|
@ -50,22 +51,22 @@ protected:
|
|||
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;
|
||||
|
@ -74,11 +75,8 @@ protected:
|
|||
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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());
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
|
|
|
@ -12,6 +12,7 @@ class QHideEvent;
|
|||
class QShowEvent;
|
||||
class QSpinBox;
|
||||
class TASCheckBox;
|
||||
class TASSpinBox;
|
||||
|
||||
namespace WiimoteEmu
|
||||
{
|
||||
|
@ -75,22 +76,8 @@ private:
|
|||
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;
|
||||
|
|
Loading…
Reference in New Issue