DolphinQt: Add Triforce tab to main settings window.

This commit is contained in:
Jordan Woyak
2026-02-15 20:14:15 -06:00
parent 0fc8f463f2
commit f00b1c590e
16 changed files with 156 additions and 89 deletions
+2 -2
View File
@@ -376,8 +376,8 @@ add_executable(dolphin-emu
Settings/OnScreenDisplayPane.h Settings/OnScreenDisplayPane.h
Settings/PathPane.cpp Settings/PathPane.cpp
Settings/PathPane.h Settings/PathPane.h
Settings/TriforceBaseboardSettingsDialog.cpp Settings/TriforcePane.cpp
Settings/TriforceBaseboardSettingsDialog.h Settings/TriforcePane.h
Settings/USBDevicePicker.cpp Settings/USBDevicePicker.cpp
Settings/USBDevicePicker.h Settings/USBDevicePicker.h
Settings/WiiPane.cpp Settings/WiiPane.cpp
@@ -133,7 +133,6 @@ void GamecubeControllersWidget::OnGCPadConfigure(size_t index)
case SerialInterface::SIDEVICE_NONE: case SerialInterface::SIDEVICE_NONE:
case SerialInterface::SIDEVICE_GC_GBA: case SerialInterface::SIDEVICE_GC_GBA:
return; return;
case SerialInterface::SIDEVICE_AM_BASEBOARD:
case SerialInterface::SIDEVICE_GC_CONTROLLER: case SerialInterface::SIDEVICE_GC_CONTROLLER:
type = MappingWindow::Type::MAPPING_GCPAD; type = MappingWindow::Type::MAPPING_GCPAD;
break; break;
@@ -158,6 +157,9 @@ void GamecubeControllersWidget::OnGCPadConfigure(size_t index)
case SerialInterface::SIDEVICE_GC_KEYBOARD: case SerialInterface::SIDEVICE_GC_KEYBOARD:
type = MappingWindow::Type::MAPPING_GC_KEYBOARD; type = MappingWindow::Type::MAPPING_GC_KEYBOARD;
break; break;
case SerialInterface::SIDEVICE_AM_BASEBOARD:
type = MappingWindow::Type::MAPPING_AM_BASEBOARD;
break;
default: default:
return; return;
} }
@@ -8,19 +8,15 @@
#include "Core/HW/GCPad.h" #include "Core/HW/GCPad.h"
#include "Core/HW/GCPadEmu.h" #include "Core/HW/GCPadEmu.h"
#include "Core/HW/SI/SI.h"
#include "Core/HW/SI/SI_DeviceAMBaseboard.h"
#include "Core/Config/MainSettings.h"
#include "InputCommon/InputConfig.h" #include "InputCommon/InputConfig.h"
GCPadEmu::GCPadEmu(MappingWindow* window) : MappingWidget(window) GCPadEmu::GCPadEmu(MappingWindow* window, SubType sub_type) : MappingWidget(window)
{ {
CreateMainLayout(); CreateMainLayout(sub_type);
} }
void GCPadEmu::CreateMainLayout() void GCPadEmu::CreateMainLayout(SubType sub_type)
{ {
auto* layout = new QGridLayout; auto* layout = new QGridLayout;
@@ -28,8 +24,7 @@ void GCPadEmu::CreateMainLayout()
0); 0);
layout->addWidget(CreateGroupBox(tr("D-Pad"), Pad::GetGroup(GetPort(), PadGroup::DPad)), 1, 0); layout->addWidget(CreateGroupBox(tr("D-Pad"), Pad::GetGroup(GetPort(), PadGroup::DPad)), 1, 0);
if (Config::Get(Config::GetInfoForSIDevice(0)) == if (sub_type == SubType::AMBaseboard)
SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD)
{ {
layout->addWidget(CreateGroupBox(tr("Triforce"), Pad::GetGroup(GetPort(), PadGroup::Triforce)), layout->addWidget(CreateGroupBox(tr("Triforce"), Pad::GetGroup(GetPort(), PadGroup::Triforce)),
2, 0); 2, 0);
@@ -9,12 +9,28 @@ class GCPadEmu final : public MappingWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit GCPadEmu(MappingWindow* window); enum class SubType
{
StandardController,
AMBaseboard,
};
GCPadEmu(MappingWindow* window, SubType sub_type);
InputConfig* GetConfig() override; InputConfig* GetConfig() override;
private: private:
void LoadSettings() override; void LoadSettings() override;
void SaveSettings() override; void SaveSettings() override;
void CreateMainLayout(); void CreateMainLayout(SubType sub_type);
}; };
[[nodiscard]] inline auto CreateStandardControllerMappingWidget(MappingWindow* parent)
{
return new GCPadEmu{parent, GCPadEmu::SubType::StandardController};
}
[[nodiscard]] inline auto CreateAMBaseboardMappingWidget(MappingWindow* parent)
{
return new GCPadEmu{parent, GCPadEmu::SubType::AMBaseboard};
}
@@ -460,18 +460,9 @@ void MappingWindow::SetMappingType(MappingWindow::Type type)
case Type::MAPPING_GC_STEERINGWHEEL: case Type::MAPPING_GC_STEERINGWHEEL:
case Type::MAPPING_GC_DANCEMAT: case Type::MAPPING_GC_DANCEMAT:
case Type::MAPPING_GCPAD: case Type::MAPPING_GCPAD:
widget = new GCPadEmu(this); widget = CreateStandardControllerMappingWidget(this);
if (Config::Get(Config::GetInfoForSIDevice(GetPort())) == setWindowTitle(tr("GameCube Controller at Port %1").arg(GetPort() + 1));
SerialInterface::SIDevices::SIDEVICE_AM_BASEBOARD) AddWidget(tr("GameCube Controller"), widget);
{
setWindowTitle(tr("Triforce Baseboard at Port %1").arg(GetPort() + 1));
AddWidget(tr("Triforce Baseboard"), widget);
}
else
{
setWindowTitle(tr("GameCube Controller at Port %1").arg(GetPort() + 1));
AddWidget(tr("GameCube Controller"), widget);
}
break; break;
case Type::MAPPING_GC_MICROPHONE: case Type::MAPPING_GC_MICROPHONE:
widget = new GCMicrophone(this); widget = new GCMicrophone(this);
@@ -528,6 +519,11 @@ void MappingWindow::SetMappingType(MappingWindow::Type type)
setWindowTitle(tr("Free Look Controller %1").arg(GetPort() + 1)); setWindowTitle(tr("Free Look Controller %1").arg(GetPort() + 1));
} }
break; break;
case Type::MAPPING_AM_BASEBOARD:
widget = CreateAMBaseboardMappingWidget(this);
setWindowTitle(tr("Triforce Baseboard at Port %1").arg(GetPort() + 1));
AddWidget(tr("Triforce Baseboard"), widget);
break;
default: default:
return; return;
} }
@@ -45,6 +45,8 @@ public:
MAPPING_HOTKEYS, MAPPING_HOTKEYS,
// Freelook // Freelook
MAPPING_FREELOOK, MAPPING_FREELOOK,
// Triforce
MAPPING_AM_BASEBOARD,
}; };
explicit MappingWindow(QWidget* parent, Type type, int port_num); explicit MappingWindow(QWidget* parent, Type type, int port_num);
@@ -29,6 +29,7 @@
#include "DolphinQt/Settings/InterfacePane.h" #include "DolphinQt/Settings/InterfacePane.h"
#include "DolphinQt/Settings/OnScreenDisplayPane.h" #include "DolphinQt/Settings/OnScreenDisplayPane.h"
#include "DolphinQt/Settings/PathPane.h" #include "DolphinQt/Settings/PathPane.h"
#include "DolphinQt/Settings/TriforcePane.h"
#include "DolphinQt/Settings/WiiPane.h" #include "DolphinQt/Settings/WiiPane.h"
StackedSettingsWindow::StackedSettingsWindow(QWidget* parent) : QDialog{parent} StackedSettingsWindow::StackedSettingsWindow(QWidget* parent) : QDialog{parent}
@@ -197,8 +198,9 @@ SettingsWindow::SettingsWindow(MainWindow* parent) : StackedSettingsWindow{paren
AddWrappedPane(new OnScreenDisplayPane, tr("On-Screen Display")); AddWrappedPane(new OnScreenDisplayPane, tr("On-Screen Display"));
AddWrappedPane(new AudioPane, tr("Audio")); AddWrappedPane(new AudioPane, tr("Audio"));
AddWrappedPane(new PathPane, tr("Paths")); AddWrappedPane(new PathPane, tr("Paths"));
AddWrappedPane(new GameCubePane, tr("GameCube")); AddWrappedPane(new GameCubePane{parent}, tr("GameCube"));
AddWrappedPane(new WiiPane, tr("Wii")); AddWrappedPane(new WiiPane, tr("Wii"));
AddWrappedPane(new TriforcePane, tr("Triforce"));
AddWrappedPane(new AdvancedPane, tr("Advanced")); AddWrappedPane(new AdvancedPane, tr("Advanced"));
OnDoneCreatingPanes(); OnDoneCreatingPanes();
@@ -49,6 +49,7 @@ enum class SettingsWindowPaneIndex : int
Paths, Paths,
GameCube, GameCube,
Wii, Wii,
Triforce,
Advanced, Advanced,
}; };
+2 -2
View File
@@ -223,7 +223,7 @@
<ClCompile Include="Settings\GeneralPane.cpp" /> <ClCompile Include="Settings\GeneralPane.cpp" />
<ClCompile Include="Settings\InterfacePane.cpp" /> <ClCompile Include="Settings\InterfacePane.cpp" />
<ClCompile Include="Settings\OnScreenDisplayPane.cpp" /> <ClCompile Include="Settings\OnScreenDisplayPane.cpp" />
<ClCompile Include="Settings\TriforceBaseboardSettingsDialog.cpp" /> <ClCompile Include="Settings\TriforcePane.cpp" />
<ClCompile Include="Settings\PathPane.cpp" /> <ClCompile Include="Settings\PathPane.cpp" />
<ClCompile Include="Settings\USBDevicePicker.cpp" /> <ClCompile Include="Settings\USBDevicePicker.cpp" />
<ClCompile Include="Settings\WiiPane.cpp" /> <ClCompile Include="Settings\WiiPane.cpp" />
@@ -440,7 +440,7 @@
<QtMoc Include="Settings\GeneralPane.h" /> <QtMoc Include="Settings\GeneralPane.h" />
<QtMoc Include="Settings\InterfacePane.h" /> <QtMoc Include="Settings\InterfacePane.h" />
<QtMoc Include="Settings\PathPane.h" /> <QtMoc Include="Settings\PathPane.h" />
<QtMoc Include="Settings\TriforceBaseboardSettingsDialog.h" /> <QtMoc Include="Settings\TriforcePane.h" />
<QtMoc Include="Settings\USBDevicePicker.h" /> <QtMoc Include="Settings\USBDevicePicker.h" />
<QtMoc Include="Settings\WiiPane.h" /> <QtMoc Include="Settings\WiiPane.h" />
<QtMoc Include="SkylanderPortal\SkylanderPortalWindow.h" /> <QtMoc Include="SkylanderPortal\SkylanderPortalWindow.h" />
+6
View File
@@ -1315,6 +1315,12 @@ void MainWindow::ShowControllersWindow()
m_settings_window->SelectPane(SettingsWindowPaneIndex::Controllers); m_settings_window->SelectPane(SettingsWindowPaneIndex::Controllers);
} }
void MainWindow::ShowTriforceWindow()
{
ShowSettingsWindow();
m_settings_window->SelectPane(SettingsWindowPaneIndex::Triforce);
}
void MainWindow::ShowFreeLookWindow() void MainWindow::ShowFreeLookWindow()
{ {
if (!m_freelook_window) if (!m_freelook_window)
+2
View File
@@ -92,6 +92,8 @@ public:
bool eventFilter(QObject* object, QEvent* event) override; bool eventFilter(QObject* object, QEvent* event) override;
QMenu* createPopupMenu() override; QMenu* createPopupMenu() override;
void ShowTriforceWindow();
signals: signals:
void ReadOnlyModeChanged(bool read_only); void ReadOnlyModeChanged(bool read_only);
void RecordingStatusChanged(bool recording); void RecordingStatusChanged(bool recording);
@@ -31,6 +31,7 @@
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/HW/EXI/EXI.h" #include "Core/HW/EXI/EXI.h"
#include "Core/HW/GCMemcard/GCMemcard.h" #include "Core/HW/GCMemcard/GCMemcard.h"
#include "DolphinQt/Config/SettingsWindow.h"
#ifdef HAS_LIBMGBA #ifdef HAS_LIBMGBA
#include "Core/NetPlayServer.h" #include "Core/NetPlayServer.h"
#endif #endif
@@ -50,16 +51,18 @@
#include "DolphinQt/QtUtils/SignalBlocking.h" #include "DolphinQt/QtUtils/SignalBlocking.h"
#include "DolphinQt/Settings.h" #include "DolphinQt/Settings.h"
#include "DolphinQt/Settings/BroadbandAdapterSettingsDialog.h" #include "DolphinQt/Settings/BroadbandAdapterSettingsDialog.h"
#include "DolphinQt/Settings/TriforceBaseboardSettingsDialog.h" #include "DolphinQt/Settings/TriforcePane.h"
constexpr std::initializer_list<ExpansionInterface::Slot> GUI_SLOTS = { constexpr std::initializer_list<ExpansionInterface::Slot> GUI_SLOTS = {
ExpansionInterface::Slot::A, ExpansionInterface::Slot::B, ExpansionInterface::Slot::SP1}; ExpansionInterface::Slot::A, ExpansionInterface::Slot::B, ExpansionInterface::Slot::SP1};
GameCubePane::GameCubePane() GameCubePane::GameCubePane(MainWindow* main_window)
{ {
CreateWidgets(); CreateWidgets();
LoadSettings(); LoadSettings();
ConnectWidgets(); ConnectWidgets();
connect(this, &GameCubePane::ShowTriforceWindow, main_window, &MainWindow::ShowTriforceWindow);
} }
void GameCubePane::CreateWidgets() void GameCubePane::CreateWidgets()
@@ -423,8 +426,7 @@ void GameCubePane::OnConfigPressed(ExpansionInterface::Slot slot)
} }
case ExpansionInterface::EXIDeviceType::Baseboard: case ExpansionInterface::EXIDeviceType::Baseboard:
{ {
TriforceBaseboardSettingsDialog dialog(this); ShowTriforceWindow();
dialog.exec();
return; return;
} }
default: default:
@@ -11,6 +11,7 @@
#include "Common/EnumMap.h" #include "Common/EnumMap.h"
#include "Core/HW/EXI/EXI.h" #include "Core/HW/EXI/EXI.h"
#include "DolphinQt/MainWindow.h"
class ConfigBool; class ConfigBool;
class ConfigChoice; class ConfigChoice;
@@ -28,10 +29,13 @@ class GameCubePane : public QWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit GameCubePane(); explicit GameCubePane(MainWindow* main_window);
static std::string GetOpenGBARom(std::string_view title); static std::string GetOpenGBARom(std::string_view title);
signals:
void ShowTriforceWindow();
private: private:
void CreateWidgets(); void CreateWidgets();
void ConnectWidgets(); void ConnectWidgets();
@@ -1,26 +0,0 @@
// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QDialog>
class QLineEdit;
class QTableWidget;
class TriforceBaseboardSettingsDialog final : public QDialog
{
Q_OBJECT
public:
explicit TriforceBaseboardSettingsDialog(QWidget* parent);
private:
void LoadConfig();
void SaveConfig();
void LoadDefault();
void OnClear();
QTableWidget* m_ip_overrides_table;
};
@@ -1,12 +1,11 @@
// Copyright 2026 Dolphin Emulator Project // Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/Settings/TriforceBaseboardSettingsDialog.h" #include "DolphinQt/Settings/TriforcePane.h"
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QGroupBox> #include <QGroupBox>
#include <QHeaderView> #include <QHeaderView>
#include <QLineEdit>
#include <QPushButton> #include <QPushButton>
#include <QTableWidget> #include <QTableWidget>
#include <QVBoxLayout> #include <QVBoxLayout>
@@ -19,25 +18,84 @@
#include "Core/Config/MainSettings.h" #include "Core/Config/MainSettings.h"
#include "Core/HW/DVD/AMMediaboard.h" #include "Core/HW/DVD/AMMediaboard.h"
#include "DolphinQt/Config/Mapping/MappingWindow.h"
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
#include "DolphinQt/QtUtils/QtUtils.h" #include "DolphinQt/QtUtils/QtUtils.h"
static constexpr int COLUMN_EMULATED = 0; namespace
static constexpr int COLUMN_REAL = 1;
static constexpr int COLUMN_DESCRIPTION = 2;
TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent) : QDialog{parent}
{ {
setWindowTitle(tr("Triforce Baseboard"));
auto* const dialog_layout = new QVBoxLayout{this}; constexpr int COLUMN_EMULATED = 0;
constexpr int COLUMN_REAL = 1;
constexpr int COLUMN_DESCRIPTION = 2;
class IPAddressOverridesDialog final : public QDialog
{
public:
explicit IPAddressOverridesDialog(QWidget* parent);
private:
void LoadConfig();
void SaveConfig() const;
void LoadDefault();
void OnClear();
QTableWidget* m_ip_overrides_table;
};
} // namespace
TriforcePane::TriforcePane()
{
auto* const main_layout = new QVBoxLayout{this};
auto* const controllers_group = new QGroupBox{tr("Controllers")};
main_layout->addWidget(controllers_group);
auto* const controllers_layout = new QVBoxLayout{controllers_group};
auto* const configure_controllers_button = new NonDefaultQPushButton{tr("Configure")};
controllers_layout->addWidget(configure_controllers_button);
connect(configure_controllers_button, &QPushButton::clicked, this, [this] {
auto* const window = new MappingWindow(this, MappingWindow::Type::MAPPING_AM_BASEBOARD, 0);
window->setAttribute(Qt::WA_DeleteOnClose, true);
window->setWindowModality(Qt::WindowModality::WindowModal);
window->show();
});
auto* const ip_override_group = new QGroupBox{tr("IP Address Overrides")}; auto* const ip_override_group = new QGroupBox{tr("IP Address Overrides")};
dialog_layout->addWidget(ip_override_group); main_layout->addWidget(ip_override_group);
auto* const ip_override_layout = new QVBoxLayout{ip_override_group}; auto* const ip_override_layout = new QVBoxLayout{ip_override_group};
auto* const configure_ip_overrides_button = new NonDefaultQPushButton{tr("Configure")};
ip_override_layout->addWidget(configure_ip_overrides_button);
connect(configure_ip_overrides_button, &QPushButton::clicked, this, [this] {
auto* const ip_overrides = new IPAddressOverridesDialog{this};
ip_overrides->setAttribute(Qt::WA_DeleteOnClose);
ip_overrides->open();
});
main_layout->addStretch(1);
}
IPAddressOverridesDialog::IPAddressOverridesDialog(QWidget* parent) : QDialog{parent}
{
setWindowTitle(tr("IP Address Overrides"));
auto* const main_layout = new QVBoxLayout{this};
auto* const load_default_button = new QPushButton(tr("Default"));
connect(load_default_button, &QPushButton::clicked, this, &IPAddressOverridesDialog::LoadDefault);
auto* const clear_button = new QPushButton(tr("Clear"));
connect(clear_button, &QPushButton::clicked, this, &IPAddressOverridesDialog::OnClear);
m_ip_overrides_table = new QTableWidget; m_ip_overrides_table = new QTableWidget;
ip_override_layout->addWidget(m_ip_overrides_table); main_layout->addWidget(m_ip_overrides_table);
m_ip_overrides_table->setColumnCount(3); m_ip_overrides_table->setColumnCount(3);
m_ip_overrides_table->setHorizontalHeaderLabels({tr("Emulated"), tr("Real"), tr("Description")}); m_ip_overrides_table->setHorizontalHeaderLabels({tr("Emulated"), tr("Real"), tr("Description")});
@@ -48,23 +106,15 @@ TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent
QAbstractItemView::EditKeyPressed); QAbstractItemView::EditKeyPressed);
auto* const button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto* const button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
dialog_layout->addWidget(button_box); main_layout->addWidget(button_box);
auto* const load_default_button = new QPushButton(tr("Default"));
connect(load_default_button, &QPushButton::clicked, this,
&TriforceBaseboardSettingsDialog::LoadDefault);
auto* const clear_button = new QPushButton(tr("Clear"));
connect(clear_button, &QPushButton::clicked, this, &TriforceBaseboardSettingsDialog::OnClear);
button_box->addButton(load_default_button, QDialogButtonBox::ActionRole); button_box->addButton(load_default_button, QDialogButtonBox::ActionRole);
button_box->addButton(clear_button, QDialogButtonBox::ActionRole); button_box->addButton(clear_button, QDialogButtonBox::ActionRole);
connect(button_box, &QDialogButtonBox::accepted, this, &TriforceBaseboardSettingsDialog::accept); connect(button_box, &QDialogButtonBox::accepted, this, &IPAddressOverridesDialog::accept);
connect(button_box, &QDialogButtonBox::rejected, this, &TriforceBaseboardSettingsDialog::reject); connect(button_box, &QDialogButtonBox::rejected, this, &IPAddressOverridesDialog::reject);
connect(this, &TriforceBaseboardSettingsDialog::accepted, this, connect(this, &IPAddressOverridesDialog::accepted, this, &IPAddressOverridesDialog::SaveConfig);
&TriforceBaseboardSettingsDialog::SaveConfig);
connect(m_ip_overrides_table, &QTableWidget::itemChanged, this, [this](QTableWidgetItem* item) { connect(m_ip_overrides_table, &QTableWidget::itemChanged, this, [this](QTableWidgetItem* item) {
const int row_count = m_ip_overrides_table->rowCount(); const int row_count = m_ip_overrides_table->rowCount();
@@ -87,7 +137,7 @@ TriforceBaseboardSettingsDialog::TriforceBaseboardSettingsDialog(QWidget* parent
QtUtils::AdjustSizeWithinScreen(this); QtUtils::AdjustSizeWithinScreen(this);
} }
void TriforceBaseboardSettingsDialog::LoadConfig() void IPAddressOverridesDialog::LoadConfig()
{ {
m_ip_overrides_table->setRowCount(0); m_ip_overrides_table->setRowCount(0);
@@ -120,7 +170,7 @@ void TriforceBaseboardSettingsDialog::LoadConfig()
} }
} }
void TriforceBaseboardSettingsDialog::SaveConfig() void IPAddressOverridesDialog::SaveConfig() const
{ {
// Ignore our empty row. // Ignore our empty row.
const int row_count = m_ip_overrides_table->rowCount() - 1; const int row_count = m_ip_overrides_table->rowCount() - 1;
@@ -152,7 +202,7 @@ void TriforceBaseboardSettingsDialog::SaveConfig()
fmt::format("{}", fmt::join(replacements, ","))); fmt::format("{}", fmt::join(replacements, ",")));
} }
void TriforceBaseboardSettingsDialog::LoadDefault() void IPAddressOverridesDialog::LoadDefault()
{ {
// This alters the config before "OK" is pressed. Bad UX.. // This alters the config before "OK" is pressed. Bad UX..
@@ -161,8 +211,9 @@ void TriforceBaseboardSettingsDialog::LoadDefault()
LoadConfig(); LoadConfig();
} }
void TriforceBaseboardSettingsDialog::OnClear() void IPAddressOverridesDialog::OnClear()
{ {
Config::SetBaseOrCurrent(Config::MAIN_TRIFORCE_IP_OVERRIDES, ""); m_ip_overrides_table->setRowCount(0);
LoadConfig(); // Add the final empty row.
m_ip_overrides_table->insertRow(0);
} }
@@ -0,0 +1,14 @@
// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QWidget>
class TriforcePane final : public QWidget
{
Q_OBJECT
public:
TriforcePane();
};