diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index 0b42c4558..7b246ca3c 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -149,6 +149,8 @@ set(SRCS setupwizarddialog.h setupwizarddialog.ui texturereplacementsettingsdialog.ui + togglebutton.cpp + togglebutton.h ) set(TS_FILES diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index 824567dd5..b1383c3ad 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -50,9 +50,11 @@ + + diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters index dd09949ab..ebfdf8f11 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj.filters +++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters @@ -48,6 +48,7 @@ + @@ -108,6 +109,7 @@ + diff --git a/src/duckstation-qt/gamepatchdetailswidget.ui b/src/duckstation-qt/gamepatchdetailswidget.ui index 06fe901b3..4ea19de85 100644 --- a/src/duckstation-qt/gamepatchdetailswidget.ui +++ b/src/duckstation-qt/gamepatchdetailswidget.ui @@ -24,7 +24,7 @@ - + Enabled @@ -73,6 +73,13 @@ + + + ToggleButton + QAbstractButton +
togglebutton.h
+
+
diff --git a/src/duckstation-qt/gamepatchsettingswidget.cpp b/src/duckstation-qt/gamepatchsettingswidget.cpp index a9e72ab94..8e204cb47 100644 --- a/src/duckstation-qt/gamepatchsettingswidget.cpp +++ b/src/duckstation-qt/gamepatchsettingswidget.cpp @@ -36,7 +36,7 @@ GamePatchDetailsWidget::GamePatchDetailsWidget(std::string name, const std::stri DebugAssert(dialog->getSettingsInterface()); m_ui.enabled->setChecked(enabled); - connect(m_ui.enabled, &QCheckBox::checkStateChanged, this, &GamePatchDetailsWidget::onEnabledStateChanged); + connect(m_ui.enabled, &ToggleButton::checkStateChanged, this, &GamePatchDetailsWidget::onEnabledStateChanged); } GamePatchDetailsWidget::~GamePatchDetailsWidget() = default; diff --git a/src/duckstation-qt/togglebutton.cpp b/src/duckstation-qt/togglebutton.cpp new file mode 100644 index 000000000..2bbe0f861 --- /dev/null +++ b/src/duckstation-qt/togglebutton.cpp @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#include "togglebutton.h" + +#include +#include +#include +#include + +#include "moc_togglebutton.cpp" + +ToggleButton::ToggleButton(QWidget* parent) : QAbstractButton(parent), m_offset_animation(this, "offset") +{ + setCheckable(true); + setCursor(Qt::PointingHandCursor); + setFocusPolicy(Qt::StrongFocus); + + m_offset_animation.setDuration(150); + m_offset_animation.setEasingCurve(QEasingCurve::OutCubic); +} + +ToggleButton::~ToggleButton() = default; + +QSize ToggleButton::sizeHint() const +{ + return QSize(50, 25); +} + +void ToggleButton::showEvent(QShowEvent* event) +{ + QAbstractButton::showEvent(event); + + // Make sure the toggle position matches the current state when first shown + updateTogglePosition(); +} + +void ToggleButton::resizeEvent(QResizeEvent* event) +{ + QAbstractButton::resizeEvent(event); + + // Update position when resized since it depends on widget dimensions + updateTogglePosition(); +} + +void ToggleButton::updateTogglePosition() +{ + // Immediately set the toggle to the correct position without animation + if (width() > 0) + { + m_offset_animation.stop(); + m_offset = isChecked() ? width() - height() : 0; + update(); + } +} + +void ToggleButton::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QStyleOption opt; + opt.initFrom(this); + + // Get colors from the current style + QColor background_color = isChecked() ? opt.palette.highlight().color() : opt.palette.dark().color(); + QColor thumb_color = opt.palette.light().color(); + + if (m_hovered || hasFocus()) + { + background_color = background_color.lighter(120); + } + + if (!isEnabled()) + { + background_color = opt.palette.mid().color(); + thumb_color = opt.palette.midlight().color(); + } + + // Draw background + const int track_width = width() - 2; + const int track_height = height() - 2; + const int corner_radius = track_height / 2; + + QPainterPath path; + path.addRoundedRect(1, 1, track_width, track_height, corner_radius, corner_radius); + + painter.fillPath(path, background_color); + + // Draw thumb + const int thumb_size = track_height - 4; + const int thumb_x = m_offset + 2; + const int thumb_y = 2; + + QPainterPath thumbPath; + thumbPath.addEllipse(thumb_x, thumb_y, thumb_size, thumb_size); + + painter.fillPath(thumbPath, thumb_color); +} + +void ToggleButton::enterEvent(QEnterEvent* event) +{ + Q_UNUSED(event); + m_hovered = true; + update(); +} + +void ToggleButton::leaveEvent(QEvent* event) +{ + Q_UNUSED(event); + m_hovered = false; + update(); +} + +void ToggleButton::checkStateSet() +{ + QAbstractButton::checkStateSet(); + animateToggle(isChecked()); +} + +void ToggleButton::animateToggle(bool checked) +{ + m_offset_animation.stop(); + m_offset_animation.setStartValue(m_offset); + m_offset_animation.setEndValue(checked ? width() - height() : 0); + m_offset_animation.start(); +} + +void ToggleButton::nextCheckState() +{ + QAbstractButton::nextCheckState(); + animateToggle(isChecked()); + update(); +} + +void ToggleButton::keyPressEvent(QKeyEvent* event) +{ + if (event->key() == Qt::Key_Space || event->key() == Qt::Key_Return) + { + setChecked(!isChecked()); + event->accept(); + } + else + { + QAbstractButton::keyPressEvent(event); + } +} + +int ToggleButton::offset() const +{ + return m_offset; +} + +void ToggleButton::setOffset(int value) +{ + m_offset = value; + update(); +} \ No newline at end of file diff --git a/src/duckstation-qt/togglebutton.h b/src/duckstation-qt/togglebutton.h new file mode 100644 index 000000000..4de840d7f --- /dev/null +++ b/src/duckstation-qt/togglebutton.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +#include +#include + +class ToggleButton : public QAbstractButton +{ + Q_OBJECT + Q_PROPERTY(int offset READ offset WRITE setOffset) + +public: + explicit ToggleButton(QWidget* parent = nullptr); + ~ToggleButton() override; + + QSize sizeHint() const override; + +Q_SIGNALS: + void checkStateChanged(Qt::CheckState state); + +protected: + void checkStateSet() override; + void nextCheckState() override; + + void paintEvent(QPaintEvent* event) override; + void enterEvent(QEnterEvent* event) override; + void leaveEvent(QEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + void showEvent(QShowEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + +private: + void animateToggle(bool checked); + void updateTogglePosition(); + + int offset() const; + void setOffset(int value); + + int m_offset = 0; + bool m_hovered = false; + + QPropertyAnimation m_offset_animation; + QPropertyAnimation m_background_animation; +};