Add TAS interface for balance board

This commit is contained in:
Pokechu22 2019-07-24 18:27:30 -07:00
parent 1bf2a7ff9d
commit 08476ab425
8 changed files with 426 additions and 0 deletions

View file

@ -364,6 +364,8 @@ add_executable(dolphin-emu
SkylanderPortal/SkylanderModifyDialog.h
SkylanderPortal/SkylanderPortalWindow.cpp
SkylanderPortal/SkylanderPortalWindow.h
TAS/BalanceBoardWidget.cpp
TAS/BalanceBoardWidget.h
TAS/GCTASInputWindow.cpp
TAS/GCTASInputWindow.h
TAS/GBATASInputWindow.cpp

View file

@ -219,6 +219,7 @@
<ClCompile Include="Settings\WiiPane.cpp" />
<ClCompile Include="SkylanderPortal\SkylanderModifyDialog.cpp" />
<ClCompile Include="SkylanderPortal\SkylanderPortalWindow.cpp" />
<ClCompile Include="TAS\BalanceBoardWidget.cpp" />
<ClCompile Include="TAS\GCTASInputWindow.cpp" />
<ClCompile Include="TAS\GBATASInputWindow.cpp" />
<ClCompile Include="TAS\IRWidget.cpp" />
@ -424,6 +425,7 @@
<QtMoc Include="Settings\USBDeviceAddToWhitelistDialog.h" />
<QtMoc Include="Settings\WiiPane.h" />
<QtMoc Include="SkylanderPortal\SkylanderPortalWindow.h" />
<QtMoc Include="TAS\BalanceBoardWidget.h" />
<QtMoc Include="TAS\GCTASInputWindow.h" />
<QtMoc Include="TAS\GBATASInputWindow.h" />
<QtMoc Include="TAS\IRWidget.h" />

View file

@ -0,0 +1,177 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/TAS/BalanceBoardWidget.h"
#include <algorithm>
#include <cmath>
#include <QMouseEvent>
#include <QPainter>
#include "Common/CommonTypes.h"
BalanceBoardWidget::BalanceBoardWidget(QWidget* parent) : QWidget(parent)
{
setMouseTracking(false);
setToolTip(tr("Left click to set the balance value.\n"
"Right click to return to perfect balance."));
}
void BalanceBoardWidget::SetTR(double top_right)
{
m_top_right = top_right;
if (m_reentrance_level < MAX_REENTRANCE)
{
const ReentranceBlocker blocker(this);
emit ChangedTotal(TotalWeight());
}
update();
}
void BalanceBoardWidget::SetBR(double bottom_right)
{
m_bottom_right = bottom_right;
if (m_reentrance_level < MAX_REENTRANCE)
{
const ReentranceBlocker blocker(this);
emit ChangedTotal(TotalWeight());
}
update();
}
void BalanceBoardWidget::SetTL(double top_left)
{
m_top_left = top_left;
if (m_reentrance_level < MAX_REENTRANCE)
{
const ReentranceBlocker blocker(this);
emit ChangedTotal(TotalWeight());
}
update();
}
void BalanceBoardWidget::SetBL(double bottom_left)
{
m_bottom_left = bottom_left;
if (m_reentrance_level < MAX_REENTRANCE)
{
const ReentranceBlocker blocker(this);
emit ChangedTotal(TotalWeight());
}
update();
}
void BalanceBoardWidget::SetTotal(double total)
{
const double current_total = TotalWeight();
if (current_total != 0)
{
const double ratio = total / current_total;
m_top_right *= ratio;
m_bottom_right *= ratio;
m_top_left *= ratio;
m_bottom_left *= ratio;
}
else
{
m_top_right = total / 4;
m_bottom_right = total / 4;
m_top_left = total / 4;
m_bottom_left = total / 4;
}
if (m_reentrance_level < MAX_REENTRANCE)
{
const ReentranceBlocker blocker(this);
emit ChangedTR(m_top_right);
emit ChangedBR(m_bottom_right);
emit ChangedTL(m_top_left);
emit ChangedBL(m_bottom_left);
const double new_total = TotalWeight();
if (new_total != total)
{
// This probably shouldn't happen, and I probably should round out numbers a bit closer
emit ChangedTotal(new_total);
}
}
update();
}
void BalanceBoardWidget::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
painter.setBrush(Qt::white);
painter.drawRect(0, 0, width() - 1, height() - 1);
painter.drawLine(0, height() / 2, width(), height() / 2);
painter.drawLine(width() / 2, 0, width() / 2, height());
// Compute center of balance
const double total = TotalWeight();
const double right = m_top_right + m_bottom_right;
const double left = m_top_left + m_bottom_left;
const double top = m_top_right + m_top_left;
const double bottom = m_bottom_right + m_bottom_left;
const double com_x = (total != 0) ? (right - left) / total : 0;
const double com_y = (total != 0) ? (top - bottom) / total : 0;
const int x = (int)((com_x + 1) * width() / 2);
const int y = (int)((1 - com_y) * height() / 2);
painter.drawLine(width() / 2, height() / 2, x, y);
painter.setBrush(Qt::blue);
const int wh_avg = (width() + height()) / 2;
const int radius = wh_avg / 30;
painter.drawEllipse(x - radius, y - radius, radius * 2, radius * 2);
}
void BalanceBoardWidget::mousePressEvent(QMouseEvent* event)
{
handleMouseEvent(event);
m_ignore_movement = event->button() == Qt::RightButton;
}
void BalanceBoardWidget::mouseMoveEvent(QMouseEvent* event)
{
if (!m_ignore_movement)
handleMouseEvent(event);
}
void BalanceBoardWidget::handleMouseEvent(QMouseEvent* event)
{
const double total = TotalWeight();
if (event->button() == Qt::RightButton)
{
m_top_right = total / 4;
m_bottom_right = total / 4;
m_top_left = total / 4;
m_bottom_left = total / 4;
}
else
{
// convert from widget space to value space
const double com_x = std::clamp((event->pos().x() * 2.) / width() - 1, -1., 1.);
const double com_y = std::clamp(1 - (event->pos().y() * 2.) / height(), -1., 1.);
m_top_right = total * (1 + com_x + com_y) / 4;
m_bottom_right = total * (1 + com_x - com_y) / 4;
m_top_left = total * (1 - com_x + com_y) / 4;
m_bottom_left = total * (1 - com_x - com_y) / 4;
}
if (m_reentrance_level < MAX_REENTRANCE)
{
const ReentranceBlocker blocker(this);
emit ChangedTR(m_top_right);
emit ChangedBR(m_bottom_right);
emit ChangedTL(m_top_left);
emit ChangedBL(m_bottom_left);
}
update();
}

View file

@ -0,0 +1,61 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QDialog>
#include "Common/CommonTypes.h"
class BalanceBoardWidget : public QWidget
{
Q_OBJECT
public:
explicit BalanceBoardWidget(QWidget* parent);
signals:
void ChangedTR(double top_right);
void ChangedBR(double bottom_right);
void ChangedTL(double top_left);
void ChangedBL(double bottom_left);
void ChangedTotal(double total_weight);
public slots:
void SetTR(double top_right);
void SetBR(double bottom_right);
void SetTL(double top_left);
void SetBL(double bottom_left);
void SetTotal(double total_weight);
protected:
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void handleMouseEvent(QMouseEvent* event);
private:
double TotalWeight() { return m_top_right + m_bottom_right + m_top_left + m_bottom_left; }
double m_top_right = 0;
double m_bottom_right = 0;
double m_top_left = 0;
double m_bottom_left = 0;
bool m_ignore_movement = false;
// Allow reentering when < max. Needed to keep changes to total from changing individual values
// from changing total... eventually causing a stack overflow.
u32 m_reentrance_level = 0;
constexpr static u32 MAX_REENTRANCE = 2;
class ReentranceBlocker
{
public:
// Does not check MAX_REENTRANCE; only increments/decrements via RAII.
ReentranceBlocker(BalanceBoardWidget* owner) : m_owner(owner) { m_owner->m_reentrance_level++; }
~ReentranceBlocker() { m_owner->m_reentrance_level--; }
private:
ReentranceBlocker(const ReentranceBlocker& other) = delete;
ReentranceBlocker& operator=(const ReentranceBlocker& other) = delete;
BalanceBoardWidget* const m_owner;
};
};

View file

@ -8,6 +8,7 @@
#include <QApplication>
#include <QCheckBox>
#include <QDoubleSpinBox>
#include <QEvent>
#include <QGroupBox>
#include <QHBoxLayout>
@ -237,6 +238,39 @@ TASSpinBox* TASInputWindow::CreateSliderValuePair(QBoxLayout* layout, int defaul
return value;
}
// The shortcut_widget argument needs to specify the container widget that will be hidden/shown.
// This is done to avoid ambigous shortcuts
QDoubleSpinBox* TASInputWindow::CreateWeightSliderValuePair(QBoxLayout* layout, int min, int max,
QKeySequence shortcut_key_sequence,
QWidget* shortcut_widget)
{
auto* value = new QDoubleSpinBox();
value->setRange(min, max);
value->setDecimals(2);
value->setSuffix(QStringLiteral("kg"));
auto* slider = new QSlider(Qt::Orientation::Horizontal);
slider->setRange(min * 100, max * 100);
slider->setFocusPolicy(Qt::ClickFocus);
slider->setSingleStep(100);
slider->setPageStep(1000);
slider->setTickPosition(QSlider::TickPosition::TicksBelow);
connect(slider, &QSlider::valueChanged, value, [value](int i) { value->setValue(i / 100.0); });
connect(value, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
slider, [slider](double d) { slider->setValue((int)(d * 100)); });
auto* shortcut = new QShortcut(shortcut_key_sequence, shortcut_widget);
connect(shortcut, &QShortcut::activated, [value] {
value->setFocus();
value->selectAll();
});
layout->addWidget(slider);
layout->addWidget(value);
return value;
}
std::optional<ControlState> TASInputWindow::GetButton(TASCheckBox* checkbox,
ControlState controller_state)
{

View file

@ -18,6 +18,7 @@
class QBoxLayout;
class QCheckBox;
class QDialog;
class QDoubleSpinBox;
class QEvent;
class QGroupBox;
class QSpinBox;
@ -68,6 +69,9 @@ protected:
TASSpinBox* CreateSliderValuePair(QBoxLayout* layout, int default_, int max,
QKeySequence shortcut_key_sequence, Qt::Orientation orientation,
QWidget* shortcut_widget);
QDoubleSpinBox* CreateWeightSliderValuePair(QBoxLayout* layout, int min, int max,
QKeySequence shortcut_key_sequence,
QWidget* shortcut_widget);
void changeEvent(QEvent* event) override;

View file

@ -13,12 +13,17 @@
#include <QSpinBox>
#include <QVBoxLayout>
#include "Common/BitUtils.h"
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/MathUtil.h"
#include "Common/Swap.h"
#include "Core/Core.h"
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteCommon/DataReport.h"
#include "Core/HW/WiimoteEmu/Encryption.h"
#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h"
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
#include "Core/HW/WiimoteEmu/Extension/Extension.h"
#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"
@ -30,6 +35,7 @@
#include "DolphinQt/QtUtils/AspectRatioWidget.h"
#include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
#include "DolphinQt/TAS/BalanceBoardWidget.h"
#include "DolphinQt/TAS/IRWidget.h"
#include "DolphinQt/TAS/TASCheckBox.h"
#include "DolphinQt/TAS/TASSpinBox.h"
@ -99,6 +105,82 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow(
CreateStickInputs(tr("Right Stick"), WiimoteEmu::Classic::RIGHT_STICK_GROUP,
&m_classic_overrider, 0, 0, 31, 31, Qt::Key_Q, Qt::Key_W);
const QKeySequence balance_tl_shortcut_key_sequence = QKeySequence(Qt::ALT | Qt::Key_L);
const QKeySequence balance_tr_shortcut_key_sequence = QKeySequence(Qt::ALT | Qt::Key_R);
const QKeySequence balance_bl_shortcut_key_sequence =
QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_L);
const QKeySequence balance_br_shortcut_key_sequence =
QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_R);
const QKeySequence balance_weight_shortcut_key_sequence = QKeySequence(Qt::ALT | Qt::Key_W);
m_balance_board_box = new QGroupBox(
QStringLiteral("%1 (%2/%3/%4)")
.arg(tr("Balance"), balance_tl_shortcut_key_sequence.toString(QKeySequence::NativeText),
balance_tr_shortcut_key_sequence.toString(QKeySequence::NativeText),
balance_weight_shortcut_key_sequence.toString(QKeySequence::NativeText)));
auto* bal_top_layout = new QHBoxLayout;
m_top_left_balance_value = CreateWeightSliderValuePair(
bal_top_layout, -34, 68, balance_tl_shortcut_key_sequence, m_balance_board_box);
m_top_right_balance_value = CreateWeightSliderValuePair(
bal_top_layout, -34, 68, balance_tr_shortcut_key_sequence, m_balance_board_box);
auto* bal_bottom_layout = new QHBoxLayout;
m_bottom_left_balance_value = CreateWeightSliderValuePair(
bal_bottom_layout, -34, 68, balance_bl_shortcut_key_sequence, m_balance_board_box);
m_bottom_right_balance_value = CreateWeightSliderValuePair(
bal_bottom_layout, -34, 68, balance_br_shortcut_key_sequence, m_balance_board_box);
auto* bal_weight_layout = new QHBoxLayout;
m_total_weight_value = CreateWeightSliderValuePair(
bal_weight_layout, 0, 136, balance_weight_shortcut_key_sequence, m_balance_board_box);
auto* bal_visual = new BalanceBoardWidget(this);
connect(m_top_right_balance_value,
static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), bal_visual,
&BalanceBoardWidget::SetTR);
connect(m_bottom_right_balance_value,
static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), bal_visual,
&BalanceBoardWidget::SetBR);
connect(m_top_left_balance_value,
static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), bal_visual,
&BalanceBoardWidget::SetTL);
connect(m_bottom_left_balance_value,
static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), bal_visual,
&BalanceBoardWidget::SetBL);
connect(bal_visual, &BalanceBoardWidget::ChangedTR, m_top_right_balance_value,
&QDoubleSpinBox::setValue);
connect(bal_visual, &BalanceBoardWidget::ChangedBR, m_bottom_right_balance_value,
&QDoubleSpinBox::setValue);
connect(bal_visual, &BalanceBoardWidget::ChangedTL, m_top_left_balance_value,
&QDoubleSpinBox::setValue);
connect(bal_visual, &BalanceBoardWidget::ChangedBL, m_bottom_left_balance_value,
&QDoubleSpinBox::setValue);
m_top_right_balance_value->setValue(WiimoteEmu::BalanceBoardExt::DEFAULT_WEIGHT / 4);
m_bottom_right_balance_value->setValue(WiimoteEmu::BalanceBoardExt::DEFAULT_WEIGHT / 4);
m_top_left_balance_value->setValue(WiimoteEmu::BalanceBoardExt::DEFAULT_WEIGHT / 4);
m_bottom_left_balance_value->setValue(WiimoteEmu::BalanceBoardExt::DEFAULT_WEIGHT / 4);
connect(m_total_weight_value,
static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), bal_visual,
&BalanceBoardWidget::SetTotal);
connect(bal_visual, &BalanceBoardWidget::ChangedTotal, m_total_weight_value,
&QDoubleSpinBox::setValue);
m_total_weight_value->setValue(WiimoteEmu::BalanceBoardExt::DEFAULT_WEIGHT);
auto* bal_ar = new AspectRatioWidget(bal_visual, 20, 12);
bal_ar->setMinimumHeight(120);
auto* bal_visual_layout = new QHBoxLayout;
bal_visual_layout->addWidget(bal_ar);
auto* bal_layout = new QVBoxLayout;
bal_layout->addLayout(bal_top_layout);
bal_layout->addLayout(bal_visual_layout);
bal_layout->addLayout(bal_bottom_layout);
bal_layout->addLayout(bal_weight_layout);
m_balance_board_box->setLayout(bal_layout);
// 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.
m_ir_box->setMinimumWidth(20);
@ -109,6 +191,7 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow(
top_layout->addWidget(m_nunchuk_stick_box);
top_layout->addWidget(m_classic_left_stick_box);
top_layout->addWidget(m_classic_right_stick_box);
top_layout->addWidget(m_balance_board_box);
m_remote_accelerometer_box = new QGroupBox(tr("Wii Remote Accelerometer"));
@ -397,6 +480,10 @@ void WiiTASInputWindow::LoadExtensionAndMotionPlus()
m_active_extension = wiimote->GetActiveExtensionNumber();
m_is_motion_plus_attached = wiimote->GetMotionPlusSetting().GetValue();
}
else if (m_num == WIIMOTE_BALANCE_BOARD)
{
m_active_extension = WiimoteEmu::ExtensionNumber::BALANCE_BOARD;
}
else
{
Common::IniFile ini;
@ -410,6 +497,8 @@ void WiiTASInputWindow::LoadExtensionAndMotionPlus()
m_active_extension = WiimoteEmu::ExtensionNumber::NUNCHUK;
else if (extension == "Classic")
m_active_extension = WiimoteEmu::ExtensionNumber::CLASSIC;
else if (extension == "BalanceBoard")
m_active_extension = WiimoteEmu::ExtensionNumber::BALANCE_BOARD;
else
m_active_extension = WiimoteEmu::ExtensionNumber::NONE;
@ -452,6 +541,7 @@ void WiiTASInputWindow::UpdateControlVisibility()
SetQWidgetWindowDecorations(m_remote_buttons_box);
m_remote_buttons_box->show();
m_classic_buttons_box->hide();
m_balance_board_box->hide();
}
else if (m_active_extension == WiimoteEmu::ExtensionNumber::CLASSIC)
{
@ -471,6 +561,23 @@ void WiiTASInputWindow::UpdateControlVisibility()
m_nunchuk_buttons_box->hide();
SetQWidgetWindowDecorations(m_classic_buttons_box);
m_classic_buttons_box->show();
m_balance_board_box->hide();
}
else if (m_active_extension == WiimoteEmu::ExtensionNumber::BALANCE_BOARD)
{
setWindowTitle(tr("Wii TAS Input %1 - Balance Board").arg(m_num + 1));
m_ir_box->hide();
m_nunchuk_stick_box->hide();
m_classic_right_stick_box->hide();
m_classic_left_stick_box->hide();
m_remote_accelerometer_box->hide();
m_remote_gyroscope_box->hide();
m_nunchuk_accelerometer_box->hide();
m_triggers_box->hide();
m_remote_buttons_box->show();
m_nunchuk_buttons_box->hide();
m_classic_buttons_box->hide();
m_balance_board_box->show();
}
else
{
@ -488,6 +595,7 @@ void WiiTASInputWindow::UpdateControlVisibility()
m_remote_buttons_box->show();
m_nunchuk_buttons_box->hide();
m_classic_buttons_box->hide();
m_balance_board_box->hide();
}
// Without these calls, switching between attachments can result in the Stick/IRWidgets being
@ -528,4 +636,36 @@ void WiiTASInputWindow::UpdateInputOverrideFunction()
if (m_active_extension == WiimoteEmu::ExtensionNumber::CLASSIC)
GetExtension()->SetInputOverrideFunction(m_classic_overrider.GetInputOverrideFunction());
/*
if (rpt.HasExt() && m_balance_board_box->isVisible())
{
using WiimoteEmu::BalanceBoard;
u8* const ext_data = rpt.GetExtDataPtr();
BalanceBoard::DataFormat bb_data = Common::BitCastPtr<BalanceBoard::DataFormat>(ext_data);
// TODO: Reading the existing values, but then just clobbering them instead of using them if
// controller input is enabled
double top_right = BalanceBoard::ConvertToKilograms(Common::swap16(bb_data.top_right));
double bottom_right = BalanceBoard::ConvertToKilograms(Common::swap16(bb_data.bottom_right));
double top_left = BalanceBoard::ConvertToKilograms(Common::swap16(bb_data.top_left));
double bottom_left = BalanceBoard::ConvertToKilograms(Common::swap16(bb_data.bottom_left));
top_right = m_top_right_balance_value->value();
bottom_right = m_bottom_right_balance_value->value();
top_left = m_top_left_balance_value->value();
bottom_left = m_bottom_left_balance_value->value();
bb_data.top_right = Common::swap16(BalanceBoard::ConvertToSensorWeight(top_right));
bb_data.bottom_right = Common::swap16(BalanceBoard::ConvertToSensorWeight(bottom_right));
bb_data.top_left = Common::swap16(BalanceBoard::ConvertToSensorWeight(top_left));
bb_data.bottom_left = Common::swap16(BalanceBoard::ConvertToSensorWeight(bottom_left));
bb_data.temperature = BalanceBoard::TEMPERATURE;
bb_data.battery = 0x83;
Common::BitCastPtr<BalanceBoard::DataFormat>(ext_data) = bb_data;
key.Encrypt(ext_data, 0, sizeof(BalanceBoard::DataFormat));
}
*/
}

View file

@ -86,6 +86,11 @@ private:
TASCheckBox* m_classic_right_button;
TASSpinBox* m_ir_x_value;
TASSpinBox* m_ir_y_value;
QDoubleSpinBox* m_total_weight_value;
QDoubleSpinBox* m_top_right_balance_value;
QDoubleSpinBox* m_bottom_right_balance_value;
QDoubleSpinBox* m_top_left_balance_value;
QDoubleSpinBox* m_bottom_left_balance_value;
QGroupBox* m_remote_accelerometer_box;
QGroupBox* m_remote_gyroscope_box;
QGroupBox* m_nunchuk_accelerometer_box;
@ -97,4 +102,5 @@ private:
QGroupBox* m_nunchuk_buttons_box;
QGroupBox* m_classic_buttons_box;
QGroupBox* m_triggers_box;
QGroupBox* m_balance_board_box;
};