Skip to content

Commit 5e253d0

Browse files
committed
Qt: Add toggle button class
And use it for patches
1 parent f1fe00c commit 5e253d0

File tree

7 files changed

+221
-2
lines changed

7 files changed

+221
-2
lines changed

src/duckstation-qt/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ set(SRCS
149149
setupwizarddialog.h
150150
setupwizarddialog.ui
151151
texturereplacementsettingsdialog.ui
152+
togglebutton.cpp
153+
togglebutton.h
152154
)
153155

154156
set(TS_FILES

src/duckstation-qt/duckstation-qt.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@
5050
<ClCompile Include="selectdiscdialog.cpp" />
5151
<ClCompile Include="settingswindow.cpp" />
5252
<ClCompile Include="setupwizarddialog.cpp" />
53+
<ClCompile Include="togglebutton.cpp" />
5354
<ClCompile Include="vcruntimecheck.cpp" />
5455
</ItemGroup>
5556
<ItemGroup>
57+
<QtMoc Include="togglebutton.h" />
5658
<QtMoc Include="setupwizarddialog.h" />
5759
<QtMoc Include="aboutdialog.h" />
5860
<QtMoc Include="audiosettingswidget.h" />

src/duckstation-qt/duckstation-qt.vcxproj.filters

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
<ClCompile Include="gamepatchsettingswidget.cpp" />
4949
<ClCompile Include="isobrowserwindow.cpp" />
5050
<ClCompile Include="debuggercodeview.cpp" />
51+
<ClCompile Include="togglebutton.cpp" />
5152
</ItemGroup>
5253
<ItemGroup>
5354
<ClInclude Include="qtutils.h" />
@@ -108,6 +109,7 @@
108109
<QtMoc Include="gamepatchsettingswidget.h" />
109110
<QtMoc Include="isobrowserwindow.h" />
110111
<QtMoc Include="debuggercodeview.h" />
112+
<QtMoc Include="togglebutton.h" />
111113
</ItemGroup>
112114
<ItemGroup>
113115
<QtUi Include="consolesettingswidget.ui" />

src/duckstation-qt/gamepatchdetailswidget.ui

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
</property>
2525
<layout class="QGridLayout" name="gridLayout">
2626
<item row="0" column="1">
27-
<widget class="QCheckBox" name="enabled">
27+
<widget class="ToggleButton" name="enabled">
2828
<property name="text">
2929
<string>Enabled</string>
3030
</property>
@@ -73,6 +73,13 @@
7373
</item>
7474
</layout>
7575
</widget>
76+
<customwidgets>
77+
<customwidget>
78+
<class>ToggleButton</class>
79+
<extends>QAbstractButton</extends>
80+
<header>togglebutton.h</header>
81+
</customwidget>
82+
</customwidgets>
7683
<resources/>
7784
<connections/>
7885
</ui>

src/duckstation-qt/gamepatchsettingswidget.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ GamePatchDetailsWidget::GamePatchDetailsWidget(std::string name, const std::stri
3636

3737
DebugAssert(dialog->getSettingsInterface());
3838
m_ui.enabled->setChecked(enabled);
39-
connect(m_ui.enabled, &QCheckBox::checkStateChanged, this, &GamePatchDetailsWidget::onEnabledStateChanged);
39+
connect(m_ui.enabled, &ToggleButton::checkStateChanged, this, &GamePatchDetailsWidget::onEnabledStateChanged);
4040
}
4141

4242
GamePatchDetailsWidget::~GamePatchDetailsWidget() = default;
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
2+
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3+
4+
#include "togglebutton.h"
5+
6+
#include <QtGui/QKeyEvent>
7+
#include <QtGui/QPainter>
8+
#include <QtGui/QPainterPath>
9+
#include <QtWidgets/QStyleOption>
10+
11+
#include "moc_togglebutton.cpp"
12+
13+
ToggleButton::ToggleButton(QWidget* parent) : QAbstractButton(parent), m_offset_animation(this, "offset")
14+
{
15+
setCheckable(true);
16+
setCursor(Qt::PointingHandCursor);
17+
setFocusPolicy(Qt::StrongFocus);
18+
19+
m_offset_animation.setDuration(150);
20+
m_offset_animation.setEasingCurve(QEasingCurve::OutCubic);
21+
}
22+
23+
ToggleButton::~ToggleButton() = default;
24+
25+
QSize ToggleButton::sizeHint() const
26+
{
27+
return QSize(50, 25);
28+
}
29+
30+
void ToggleButton::showEvent(QShowEvent* event)
31+
{
32+
QAbstractButton::showEvent(event);
33+
34+
// Make sure the toggle position matches the current state when first shown
35+
updateTogglePosition();
36+
}
37+
38+
void ToggleButton::resizeEvent(QResizeEvent* event)
39+
{
40+
QAbstractButton::resizeEvent(event);
41+
42+
// Update position when resized since it depends on widget dimensions
43+
updateTogglePosition();
44+
}
45+
46+
void ToggleButton::updateTogglePosition()
47+
{
48+
// Immediately set the toggle to the correct position without animation
49+
if (width() > 0)
50+
{
51+
m_offset_animation.stop();
52+
m_offset = isChecked() ? width() - height() : 0;
53+
update();
54+
}
55+
}
56+
57+
void ToggleButton::paintEvent(QPaintEvent* event)
58+
{
59+
Q_UNUSED(event);
60+
61+
QPainter painter(this);
62+
painter.setRenderHint(QPainter::Antialiasing);
63+
64+
QStyleOption opt;
65+
opt.initFrom(this);
66+
67+
// Get colors from the current style
68+
QColor background_color = isChecked() ? opt.palette.highlight().color() : opt.palette.dark().color();
69+
QColor thumb_color = opt.palette.light().color();
70+
71+
if (m_hovered || hasFocus())
72+
{
73+
background_color = background_color.lighter(120);
74+
}
75+
76+
if (!isEnabled())
77+
{
78+
background_color = opt.palette.mid().color();
79+
thumb_color = opt.palette.midlight().color();
80+
}
81+
82+
// Draw background
83+
const int track_width = width() - 2;
84+
const int track_height = height() - 2;
85+
const int corner_radius = track_height / 2;
86+
87+
QPainterPath path;
88+
path.addRoundedRect(1, 1, track_width, track_height, corner_radius, corner_radius);
89+
90+
painter.fillPath(path, background_color);
91+
92+
// Draw thumb
93+
const int thumb_size = track_height - 4;
94+
const int thumb_x = m_offset + 2;
95+
const int thumb_y = 2;
96+
97+
QPainterPath thumbPath;
98+
thumbPath.addEllipse(thumb_x, thumb_y, thumb_size, thumb_size);
99+
100+
painter.fillPath(thumbPath, thumb_color);
101+
}
102+
103+
void ToggleButton::enterEvent(QEnterEvent* event)
104+
{
105+
Q_UNUSED(event);
106+
m_hovered = true;
107+
update();
108+
}
109+
110+
void ToggleButton::leaveEvent(QEvent* event)
111+
{
112+
Q_UNUSED(event);
113+
m_hovered = false;
114+
update();
115+
}
116+
117+
void ToggleButton::checkStateSet()
118+
{
119+
QAbstractButton::checkStateSet();
120+
animateToggle(isChecked());
121+
}
122+
123+
void ToggleButton::animateToggle(bool checked)
124+
{
125+
m_offset_animation.stop();
126+
m_offset_animation.setStartValue(m_offset);
127+
m_offset_animation.setEndValue(checked ? width() - height() : 0);
128+
m_offset_animation.start();
129+
}
130+
131+
void ToggleButton::nextCheckState()
132+
{
133+
QAbstractButton::nextCheckState();
134+
animateToggle(isChecked());
135+
update();
136+
}
137+
138+
void ToggleButton::keyPressEvent(QKeyEvent* event)
139+
{
140+
if (event->key() == Qt::Key_Space || event->key() == Qt::Key_Return)
141+
{
142+
setChecked(!isChecked());
143+
event->accept();
144+
}
145+
else
146+
{
147+
QAbstractButton::keyPressEvent(event);
148+
}
149+
}
150+
151+
int ToggleButton::offset() const
152+
{
153+
return m_offset;
154+
}
155+
156+
void ToggleButton::setOffset(int value)
157+
{
158+
m_offset = value;
159+
update();
160+
}

src/duckstation-qt/togglebutton.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
2+
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3+
4+
#pragma once
5+
6+
#include <QtCore/QPropertyAnimation>
7+
#include <QtWidgets/QAbstractButton>
8+
9+
class ToggleButton : public QAbstractButton
10+
{
11+
Q_OBJECT
12+
Q_PROPERTY(int offset READ offset WRITE setOffset)
13+
14+
public:
15+
explicit ToggleButton(QWidget* parent = nullptr);
16+
~ToggleButton() override;
17+
18+
QSize sizeHint() const override;
19+
20+
Q_SIGNALS:
21+
void checkStateChanged(Qt::CheckState state);
22+
23+
protected:
24+
void checkStateSet() override;
25+
void nextCheckState() override;
26+
27+
void paintEvent(QPaintEvent* event) override;
28+
void enterEvent(QEnterEvent* event) override;
29+
void leaveEvent(QEvent* event) override;
30+
void keyPressEvent(QKeyEvent* event) override;
31+
void showEvent(QShowEvent* event) override;
32+
void resizeEvent(QResizeEvent* event) override;
33+
34+
private:
35+
void animateToggle(bool checked);
36+
void updateTogglePosition();
37+
38+
int offset() const;
39+
void setOffset(int value);
40+
41+
int m_offset = 0;
42+
bool m_hovered = false;
43+
44+
QPropertyAnimation m_offset_animation;
45+
QPropertyAnimation m_background_animation;
46+
};

0 commit comments

Comments
 (0)