Need Help Debugging Custom Qt/C++ Switch Control (Video/GIF Reference Provided)
-
Hi Qt/C++ community,
I've implemented a custom Switch control in Qt/C++, but I'm encountering some unexpected behavior. I've attached:
The relevant source files
A screen recording demonstrating the issue
A GIF showing the desired behavior
Current Issues:[Briefly describe main bug, e.g., "Animation stutters during state transition"]
[Any other observable problems]
Expected Behavior:Smooth toggle animation matching the reference GIF
Proper visual state changes
Would appreciate if experts could:Review the implementation
Suggest fixes to match the reference behavior
Point out any architectural improvements
[Attachments]#include <QWidget>
#include <QPropertyAnimation>
#include <QPainter>
#include <QMouseEvent>
#include <QEnterEvent>
#include <QHBoxLayout>
#include <QLabel>
#include <QApplication>
#include <QVBoxLayout>
#include <QGroupBox>
#include <QDebug>class FluentSwitch : public QWidget {
Q_OBJECT
Q_PROPERTY(bool checked READ isChecked WRITE setChecked NOTIFY toggled)
Q_PROPERTY(QString text READ text WRITE setText)
Q_PROPERTY(bool dark READ isDark WRITE setDark)
Q_PROPERTY(QColor accentColor READ accentColor WRITE setAccentColor)
Q_PROPERTY(qreal handleScale READ handleScale WRITE setHandleScale)
Q_PROPERTY(int handleX READ handleX WRITE setHandleX)
Q_PROPERTY(int handleWidth READ handleWidth WRITE setHandleWidth)public:
explicit FluentSwitch(QWidget *parent = nullptr) : QWidget(parent) {
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
setMouseTracking(true);
setAttribute(Qt::WA_Hover);// 初始化动画 m_handleXAnimation = new QPropertyAnimation(this, "handleX", this); m_handleXAnimation->setDuration(167); m_handleXAnimation->setEasingCurve(QEasingCurve::OutQuad); m_handleWidthAnimation = new QPropertyAnimation(this, "handleWidth", this); m_handleWidthAnimation->setDuration(167); m_handleWidthAnimation->setEasingCurve(QEasingCurve::OutQuad); m_handleScaleAnimation = new QPropertyAnimation(this, "handleScale", this); m_handleScaleAnimation->setDuration(167); m_handleScaleAnimation->setEasingCurve(QEasingCurve::OutQuad); // 设置默认值 setHandleScale(0.857); updateHandlePosition(); } bool isChecked() const { return m_checked; } QString text() const { return m_text; } bool isDark() const { return m_dark; } QColor accentColor() const { return m_accentColor; } qreal handleScale() const { return m_handleScale; } int handleX() const { return m_handleX; } int handleWidth() const { return m_handleWidth; } QSize sizeHint() const override { int width = 40 + (m_text.isEmpty() ? 0 : (8 + fontMetrics().horizontalAdvance(m_text))); return QSize(width, 20); }
public slots:
void setChecked(bool checked) {
if (m_checked == checked) return;
m_checked = checked;
updateHandlePosition();
update();
emit toggled(m_checked);
}void setText(const QString &text) { if (m_text != text) { m_text = text; updateGeometry(); update(); } } void setDark(bool dark) { if (m_dark != dark) { m_dark = dark; update(); } } void setAccentColor(const QColor &color) { if (m_accentColor != color) { m_accentColor = color; update(); } } void setHandleScale(qreal scale) { scale = qBound(0.7, scale, 1.2); if (!qFuzzyCompare(m_handleScale, scale)) { m_handleScale = scale; update(); } } void setHandleX(int x) { int newX = qBound(4, x, 40 - 4 - m_handleWidth); if (m_handleX != newX) { m_handleX = newX; update(); } } void setHandleWidth(int width) { width = qBound(10, width, 20); if (m_handleWidth != width) { m_handleWidth = width; update(); } }
signals:
void toggled(bool checked);protected:
void paintEvent(QPaintEvent *event) override {
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);// 1. 绘制轨道背景 QRect trackRect(0, (height() - 20) / 2, 40, 20); QColor trackColor = m_checked ? getCheckedTrackColor() : getUncheckedTrackColor(); if (m_dragging && m_checked) { trackColor = m_accentColor.lighter(150); } painter.setPen(Qt::NoPen); painter.setBrush(trackColor); painter.drawRoundedRect(trackRect, 10, 10); // 2. 绘制轨道边框 QColor borderColor = m_checked ? getCheckedBorderColor() : getUncheckedBorderColor(); painter.setPen(QPen(borderColor, 1)); painter.setBrush(Qt::NoBrush); painter.drawRoundedRect(trackRect, 10, 10); // 3. 绘制手柄 QRectF handleRect( m_handleX, (height() - m_handleHeight * m_handleScale) / 2, m_handleWidth * m_handleScale, m_handleHeight * m_handleScale ); painter.setPen(Qt::NoPen); painter.setBrush(getHandleColor()); painter.drawEllipse(handleRect.center(), handleRect.width()/2, handleRect.height()/2); // 4. 绘制文本 if (!m_text.isEmpty()) { painter.setPen(m_dark ? Qt::white : Qt::black); painter.setFont(font()); painter.drawText(48, (height() + painter.fontMetrics().height()) / 2 - 2, m_text); } } void mousePressEvent(QMouseEvent *event) override { if (event->button() == Qt::LeftButton && isEnabled()) { m_pressed = true; m_dragging = true; m_dragStartX = event->pos().x(); m_dragStartHandleX = m_handleX; updateHandleWidth(16); updateHandleScale(1.0); update(); event->accept(); } else { event->ignore(); } } void mouseMoveEvent(QMouseEvent *event) override { if (!isEnabled()) return; if (m_dragging) { int deltaX = event->pos().x() - m_dragStartX; int newX = qBound(4, m_dragStartHandleX + deltaX, 40 - 4 - m_handleWidth); setHandleX(newX); bool shouldBeChecked = (newX > 20); if (shouldBeChecked != m_checked) { setChecked(shouldBeChecked); } event->accept(); } } void mouseReleaseEvent(QMouseEvent *event) override { if (event->button() == Qt::LeftButton && m_pressed) { m_pressed = false; m_dragging = false; if (qAbs(event->pos().x() - m_dragStartX) < 3) { toggle(); } else { setChecked(m_handleX > 20); } updateHandleWidth(14); updateHandlePosition(); event->accept(); } } void enterEvent(QEnterEvent *event) override { Q_UNUSED(event); if (!isEnabled()) return; m_hovered = true; if (!m_pressed && !m_dragging) { updateHandleScale(1.0); } update(); } void leaveEvent(QEvent *event) override { Q_UNUSED(event); if (!isEnabled()) return; m_hovered = false; if (!m_pressed && !m_dragging) { updateHandleScale(0.857); } update(); }
private:
void toggle() {
setChecked(!m_checked);
}QColor getCheckedTrackColor() const { return m_accentColor.isValid() ? m_accentColor : (m_dark ? QColor(0x4C, 0xC2, 0xFF) : QColor(0x00, 0x67, 0xC0)); } QColor getUncheckedTrackColor() const { if (!isEnabled()) { return m_dark ? QColor(0x2A, 0x2A, 0x2A) : QColor(0xF5, 0xF5, 0xF5); } else if (m_pressed || m_dragging) { return m_dark ? QColor(0x40, 0x40, 0x40) : QColor(0xE6, 0xE6, 0xE6); } else if (m_hovered) { return m_dark ? QColor(0x38, 0x38, 0x38) : QColor(0xF3, 0xF3, 0xF3); } else { return m_dark ? QColor(0x34, 0x34, 0x34) : QColor(0xFD, 0xFD, 0xFD); } } QColor getCheckedBorderColor() const { return getCheckedTrackColor(); } QColor getUncheckedBorderColor() const { if (!isEnabled()) { return m_dark ? QColor(0xA7, 0xA7, 0xA7) : QColor(0xB6, 0xB6, 0xB6); } else { return m_dark ? QColor(0x9A, 0x9A, 0x9A) : QColor(0x86, 0x86, 0x86); } } QColor getHandleColor() const { if (m_checked) { return !isEnabled() ? (m_dark ? QColor(0xA7, 0xA7, 0xA7) : QColor(0xB6, 0xB6, 0xB6)) : (m_dark ? QColor(0x20, 0x20, 0x20) : Qt::white); } else { return !isEnabled() ? (m_dark ? QColor(0xA7, 0xA7, 0xA7) : QColor(0xB6, 0xB6, 0xB6)) : (m_dark ? QColor(0x9A, 0x9A, 0x9A) : QColor(0x86, 0x86, 0x86)); } } void updateHandlePosition() { if (m_dragging) return; const int targetX = m_checked ? (40 - 4 - m_handleWidth * m_handleScale) : 4; if (m_handleXAnimation->state() != QPropertyAnimation::Running) { m_handleXAnimation->stop(); m_handleXAnimation->setStartValue(m_handleX); m_handleXAnimation->setEndValue(targetX); m_handleXAnimation->start(); } } void updateHandleWidth(int width) { width = qBound(10, width, 20); if (m_handleWidthAnimation->state() != QPropertyAnimation::Running) { m_handleWidthAnimation->stop(); m_handleWidthAnimation->setStartValue(m_handleWidth); m_handleWidthAnimation->setEndValue(width); m_handleWidthAnimation->start(); } } void updateHandleScale(qreal scale) { scale = qBound(0.7, scale, 1.2); if (m_handleScaleAnimation->state() != QPropertyAnimation::Running) { m_handleScaleAnimation->stop(); m_handleScaleAnimation->setStartValue(m_handleScale); m_handleScaleAnimation->setEndValue(scale); m_handleScaleAnimation->start(); } } // 成员变量 bool m_checked = false; QString m_text; bool m_dark = false; QColor m_accentColor; bool m_hovered = false; bool m_pressed = false; bool m_dragging = false; int m_dragStartX = 0; int m_dragStartHandleX = 0; int m_handleX = 4; int m_handleWidth = 14; const int m_handleHeight = 14; qreal m_handleScale = 0.857; QPropertyAnimation *m_handleXAnimation; QPropertyAnimation *m_handleWidthAnimation; QPropertyAnimation *m_handleScaleAnimation;
};
int main(int argc, char *argv[]) {
QApplication a(argc, argv);QWidget window; QVBoxLayout *layout = new QVBoxLayout(&window); // 亮色主题示例 - 完全匹配图片中的状态 QGroupBox *lightGroup = new QGroupBox("Light Theme"); QVBoxLayout *lightLayout = new QVBoxLayout(lightGroup); FluentSwitch *switch1 = new FluentSwitch; switch1->setText("Wi-Fi"); // 关闭状态 lightLayout->addWidget(switch1); FluentSwitch *switch2 = new FluentSwitch; switch2->setText("Bluetooth"); switch2->setChecked(true); // 开启状态(蓝色) lightLayout->addWidget(switch2); FluentSwitch *switch3 = new FluentSwitch; switch3->setText("Airplane Mode"); switch3->setEnabled(false); // 禁用状态 lightLayout->addWidget(switch3); layout->addWidget(lightGroup); // 暗色主题示例 - 完全匹配图片中的状态 QGroupBox *darkGroup = new QGroupBox("Dark Theme"); QVBoxLayout *darkLayout = new QVBoxLayout(darkGroup); darkGroup->setStyleSheet("QGroupBox { background-color: #202020; color: white; }"); FluentSwitch *switch4 = new FluentSwitch; switch4->setText("Dark Mode"); switch4->setDark(true); // 关闭状态(灰色) darkLayout->addWidget(switch4); FluentSwitch *switch5 = new FluentSwitch; switch5->setText("Notifications"); switch5->setDark(true); // 关闭状态(灰色) darkLayout->addWidget(switch5); FluentSwitch *switch6 = new FluentSwitch; switch6->setText("Location Services"); switch6->setDark(true); switch6->setEnabled(false); // 禁用状态 darkLayout->addWidget(switch6); FluentSwitch *switch7 = new FluentSwitch; switch7->setText("Custom Color"); switch7->setDark(true); switch7->setAccentColor(QColor(0xFF, 0x00, 0x88)); // 粉色 switch7->setChecked(true); // 开启状态(粉色) darkLayout->addWidget(switch7); layout->addWidget(darkGroup); window.setLayout(layout); window.resize(300, 300); window.show(); return a.exec();
}
#include "main.moc"
BugDemo.mp4
Thanks in advance for your expertise! -
Hi Qt/C++ community,
I've implemented a custom Switch control in Qt/C++, but I'm encountering some unexpected behavior. I've attached:
The relevant source files
A screen recording demonstrating the issue
A GIF showing the desired behavior
Current Issues:[Briefly describe main bug, e.g., "Animation stutters during state transition"]
[Any other observable problems]
Expected Behavior:Smooth toggle animation matching the reference GIF
Proper visual state changes
Would appreciate if experts could:Review the implementation
Suggest fixes to match the reference behavior
Point out any architectural improvements
[Attachments]#include <QWidget>
#include <QPropertyAnimation>
#include <QPainter>
#include <QMouseEvent>
#include <QEnterEvent>
#include <QHBoxLayout>
#include <QLabel>
#include <QApplication>
#include <QVBoxLayout>
#include <QGroupBox>
#include <QDebug>class FluentSwitch : public QWidget {
Q_OBJECT
Q_PROPERTY(bool checked READ isChecked WRITE setChecked NOTIFY toggled)
Q_PROPERTY(QString text READ text WRITE setText)
Q_PROPERTY(bool dark READ isDark WRITE setDark)
Q_PROPERTY(QColor accentColor READ accentColor WRITE setAccentColor)
Q_PROPERTY(qreal handleScale READ handleScale WRITE setHandleScale)
Q_PROPERTY(int handleX READ handleX WRITE setHandleX)
Q_PROPERTY(int handleWidth READ handleWidth WRITE setHandleWidth)public:
explicit FluentSwitch(QWidget *parent = nullptr) : QWidget(parent) {
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
setMouseTracking(true);
setAttribute(Qt::WA_Hover);// 初始化动画 m_handleXAnimation = new QPropertyAnimation(this, "handleX", this); m_handleXAnimation->setDuration(167); m_handleXAnimation->setEasingCurve(QEasingCurve::OutQuad); m_handleWidthAnimation = new QPropertyAnimation(this, "handleWidth", this); m_handleWidthAnimation->setDuration(167); m_handleWidthAnimation->setEasingCurve(QEasingCurve::OutQuad); m_handleScaleAnimation = new QPropertyAnimation(this, "handleScale", this); m_handleScaleAnimation->setDuration(167); m_handleScaleAnimation->setEasingCurve(QEasingCurve::OutQuad); // 设置默认值 setHandleScale(0.857); updateHandlePosition(); } bool isChecked() const { return m_checked; } QString text() const { return m_text; } bool isDark() const { return m_dark; } QColor accentColor() const { return m_accentColor; } qreal handleScale() const { return m_handleScale; } int handleX() const { return m_handleX; } int handleWidth() const { return m_handleWidth; } QSize sizeHint() const override { int width = 40 + (m_text.isEmpty() ? 0 : (8 + fontMetrics().horizontalAdvance(m_text))); return QSize(width, 20); }
public slots:
void setChecked(bool checked) {
if (m_checked == checked) return;
m_checked = checked;
updateHandlePosition();
update();
emit toggled(m_checked);
}void setText(const QString &text) { if (m_text != text) { m_text = text; updateGeometry(); update(); } } void setDark(bool dark) { if (m_dark != dark) { m_dark = dark; update(); } } void setAccentColor(const QColor &color) { if (m_accentColor != color) { m_accentColor = color; update(); } } void setHandleScale(qreal scale) { scale = qBound(0.7, scale, 1.2); if (!qFuzzyCompare(m_handleScale, scale)) { m_handleScale = scale; update(); } } void setHandleX(int x) { int newX = qBound(4, x, 40 - 4 - m_handleWidth); if (m_handleX != newX) { m_handleX = newX; update(); } } void setHandleWidth(int width) { width = qBound(10, width, 20); if (m_handleWidth != width) { m_handleWidth = width; update(); } }
signals:
void toggled(bool checked);protected:
void paintEvent(QPaintEvent *event) override {
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);// 1. 绘制轨道背景 QRect trackRect(0, (height() - 20) / 2, 40, 20); QColor trackColor = m_checked ? getCheckedTrackColor() : getUncheckedTrackColor(); if (m_dragging && m_checked) { trackColor = m_accentColor.lighter(150); } painter.setPen(Qt::NoPen); painter.setBrush(trackColor); painter.drawRoundedRect(trackRect, 10, 10); // 2. 绘制轨道边框 QColor borderColor = m_checked ? getCheckedBorderColor() : getUncheckedBorderColor(); painter.setPen(QPen(borderColor, 1)); painter.setBrush(Qt::NoBrush); painter.drawRoundedRect(trackRect, 10, 10); // 3. 绘制手柄 QRectF handleRect( m_handleX, (height() - m_handleHeight * m_handleScale) / 2, m_handleWidth * m_handleScale, m_handleHeight * m_handleScale ); painter.setPen(Qt::NoPen); painter.setBrush(getHandleColor()); painter.drawEllipse(handleRect.center(), handleRect.width()/2, handleRect.height()/2); // 4. 绘制文本 if (!m_text.isEmpty()) { painter.setPen(m_dark ? Qt::white : Qt::black); painter.setFont(font()); painter.drawText(48, (height() + painter.fontMetrics().height()) / 2 - 2, m_text); } } void mousePressEvent(QMouseEvent *event) override { if (event->button() == Qt::LeftButton && isEnabled()) { m_pressed = true; m_dragging = true; m_dragStartX = event->pos().x(); m_dragStartHandleX = m_handleX; updateHandleWidth(16); updateHandleScale(1.0); update(); event->accept(); } else { event->ignore(); } } void mouseMoveEvent(QMouseEvent *event) override { if (!isEnabled()) return; if (m_dragging) { int deltaX = event->pos().x() - m_dragStartX; int newX = qBound(4, m_dragStartHandleX + deltaX, 40 - 4 - m_handleWidth); setHandleX(newX); bool shouldBeChecked = (newX > 20); if (shouldBeChecked != m_checked) { setChecked(shouldBeChecked); } event->accept(); } } void mouseReleaseEvent(QMouseEvent *event) override { if (event->button() == Qt::LeftButton && m_pressed) { m_pressed = false; m_dragging = false; if (qAbs(event->pos().x() - m_dragStartX) < 3) { toggle(); } else { setChecked(m_handleX > 20); } updateHandleWidth(14); updateHandlePosition(); event->accept(); } } void enterEvent(QEnterEvent *event) override { Q_UNUSED(event); if (!isEnabled()) return; m_hovered = true; if (!m_pressed && !m_dragging) { updateHandleScale(1.0); } update(); } void leaveEvent(QEvent *event) override { Q_UNUSED(event); if (!isEnabled()) return; m_hovered = false; if (!m_pressed && !m_dragging) { updateHandleScale(0.857); } update(); }
private:
void toggle() {
setChecked(!m_checked);
}QColor getCheckedTrackColor() const { return m_accentColor.isValid() ? m_accentColor : (m_dark ? QColor(0x4C, 0xC2, 0xFF) : QColor(0x00, 0x67, 0xC0)); } QColor getUncheckedTrackColor() const { if (!isEnabled()) { return m_dark ? QColor(0x2A, 0x2A, 0x2A) : QColor(0xF5, 0xF5, 0xF5); } else if (m_pressed || m_dragging) { return m_dark ? QColor(0x40, 0x40, 0x40) : QColor(0xE6, 0xE6, 0xE6); } else if (m_hovered) { return m_dark ? QColor(0x38, 0x38, 0x38) : QColor(0xF3, 0xF3, 0xF3); } else { return m_dark ? QColor(0x34, 0x34, 0x34) : QColor(0xFD, 0xFD, 0xFD); } } QColor getCheckedBorderColor() const { return getCheckedTrackColor(); } QColor getUncheckedBorderColor() const { if (!isEnabled()) { return m_dark ? QColor(0xA7, 0xA7, 0xA7) : QColor(0xB6, 0xB6, 0xB6); } else { return m_dark ? QColor(0x9A, 0x9A, 0x9A) : QColor(0x86, 0x86, 0x86); } } QColor getHandleColor() const { if (m_checked) { return !isEnabled() ? (m_dark ? QColor(0xA7, 0xA7, 0xA7) : QColor(0xB6, 0xB6, 0xB6)) : (m_dark ? QColor(0x20, 0x20, 0x20) : Qt::white); } else { return !isEnabled() ? (m_dark ? QColor(0xA7, 0xA7, 0xA7) : QColor(0xB6, 0xB6, 0xB6)) : (m_dark ? QColor(0x9A, 0x9A, 0x9A) : QColor(0x86, 0x86, 0x86)); } } void updateHandlePosition() { if (m_dragging) return; const int targetX = m_checked ? (40 - 4 - m_handleWidth * m_handleScale) : 4; if (m_handleXAnimation->state() != QPropertyAnimation::Running) { m_handleXAnimation->stop(); m_handleXAnimation->setStartValue(m_handleX); m_handleXAnimation->setEndValue(targetX); m_handleXAnimation->start(); } } void updateHandleWidth(int width) { width = qBound(10, width, 20); if (m_handleWidthAnimation->state() != QPropertyAnimation::Running) { m_handleWidthAnimation->stop(); m_handleWidthAnimation->setStartValue(m_handleWidth); m_handleWidthAnimation->setEndValue(width); m_handleWidthAnimation->start(); } } void updateHandleScale(qreal scale) { scale = qBound(0.7, scale, 1.2); if (m_handleScaleAnimation->state() != QPropertyAnimation::Running) { m_handleScaleAnimation->stop(); m_handleScaleAnimation->setStartValue(m_handleScale); m_handleScaleAnimation->setEndValue(scale); m_handleScaleAnimation->start(); } } // 成员变量 bool m_checked = false; QString m_text; bool m_dark = false; QColor m_accentColor; bool m_hovered = false; bool m_pressed = false; bool m_dragging = false; int m_dragStartX = 0; int m_dragStartHandleX = 0; int m_handleX = 4; int m_handleWidth = 14; const int m_handleHeight = 14; qreal m_handleScale = 0.857; QPropertyAnimation *m_handleXAnimation; QPropertyAnimation *m_handleWidthAnimation; QPropertyAnimation *m_handleScaleAnimation;
};
int main(int argc, char *argv[]) {
QApplication a(argc, argv);QWidget window; QVBoxLayout *layout = new QVBoxLayout(&window); // 亮色主题示例 - 完全匹配图片中的状态 QGroupBox *lightGroup = new QGroupBox("Light Theme"); QVBoxLayout *lightLayout = new QVBoxLayout(lightGroup); FluentSwitch *switch1 = new FluentSwitch; switch1->setText("Wi-Fi"); // 关闭状态 lightLayout->addWidget(switch1); FluentSwitch *switch2 = new FluentSwitch; switch2->setText("Bluetooth"); switch2->setChecked(true); // 开启状态(蓝色) lightLayout->addWidget(switch2); FluentSwitch *switch3 = new FluentSwitch; switch3->setText("Airplane Mode"); switch3->setEnabled(false); // 禁用状态 lightLayout->addWidget(switch3); layout->addWidget(lightGroup); // 暗色主题示例 - 完全匹配图片中的状态 QGroupBox *darkGroup = new QGroupBox("Dark Theme"); QVBoxLayout *darkLayout = new QVBoxLayout(darkGroup); darkGroup->setStyleSheet("QGroupBox { background-color: #202020; color: white; }"); FluentSwitch *switch4 = new FluentSwitch; switch4->setText("Dark Mode"); switch4->setDark(true); // 关闭状态(灰色) darkLayout->addWidget(switch4); FluentSwitch *switch5 = new FluentSwitch; switch5->setText("Notifications"); switch5->setDark(true); // 关闭状态(灰色) darkLayout->addWidget(switch5); FluentSwitch *switch6 = new FluentSwitch; switch6->setText("Location Services"); switch6->setDark(true); switch6->setEnabled(false); // 禁用状态 darkLayout->addWidget(switch6); FluentSwitch *switch7 = new FluentSwitch; switch7->setText("Custom Color"); switch7->setDark(true); switch7->setAccentColor(QColor(0xFF, 0x00, 0x88)); // 粉色 switch7->setChecked(true); // 开启状态(粉色) darkLayout->addWidget(switch7); layout->addWidget(darkGroup); window.setLayout(layout); window.resize(300, 300); window.show(); return a.exec();
}
#include "main.moc"
BugDemo.mp4
Thanks in advance for your expertise!@YiShengPiAn Please format your code properly, it is hard to read
-
Hi Qt/C++ community,
I've implemented a custom Switch control in Qt/C++, but I'm encountering some unexpected behavior. I've attached:
The relevant source files
A screen recording demonstrating the issue
A GIF showing the desired behavior
Current Issues:[Briefly describe main bug, e.g., "Animation stutters during state transition"]
[Any other observable problems]
Expected Behavior:Smooth toggle animation matching the reference GIF
Proper visual state changes
Would appreciate if experts could:Review the implementation
Suggest fixes to match the reference behavior
Point out any architectural improvements
[Attachments]c++ #include <QWidget> #include <QPropertyAnimation> #include <QPainter> #include <QMouseEvent> #include <QEnterEvent> #include <QHBoxLayout> #include <QLabel> #include <QApplication> #include <QVBoxLayout> #include <QGroupBox> #include <QDebug> class FluentSwitch : public QWidget { Q_OBJECT Q_PROPERTY(bool checked READ isChecked WRITE setChecked NOTIFY toggled) Q_PROPERTY(QString text READ text WRITE setText) Q_PROPERTY(bool dark READ isDark WRITE setDark) Q_PROPERTY(QColor accentColor READ accentColor WRITE setAccentColor) Q_PROPERTY(qreal handleScale READ handleScale WRITE setHandleScale) Q_PROPERTY(int handleX READ handleX WRITE setHandleX) Q_PROPERTY(int handleWidth READ handleWidth WRITE setHandleWidth) public: explicit FluentSwitch(QWidget *parent = nullptr) : QWidget(parent) { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); setMouseTracking(true); setAttribute(Qt::WA_Hover); // 初始化动画 m_handleXAnimation = new QPropertyAnimation(this, "handleX", this); m_handleXAnimation->setDuration(167); m_handleXAnimation->setEasingCurve(QEasingCurve::OutQuad); m_handleWidthAnimation = new QPropertyAnimation(this, "handleWidth", this); m_handleWidthAnimation->setDuration(167); m_handleWidthAnimation->setEasingCurve(QEasingCurve::OutQuad); m_handleScaleAnimation = new QPropertyAnimation(this, "handleScale", this); m_handleScaleAnimation->setDuration(167); m_handleScaleAnimation->setEasingCurve(QEasingCurve::OutQuad); // 设置默认值 setHandleScale(0.857); updateHandlePosition(); } bool isChecked() const { return m_checked; } QString text() const { return m_text; } bool isDark() const { return m_dark; } QColor accentColor() const { return m_accentColor; } qreal handleScale() const { return m_handleScale; } int handleX() const { return m_handleX; } int handleWidth() const { return m_handleWidth; } QSize sizeHint() const override { int width = 40 + (m_text.isEmpty() ? 0 : (8 + fontMetrics().horizontalAdvance(m_text))); return QSize(width, 20); } public slots: void setChecked(bool checked) { if (m_checked == checked) return; m_checked = checked; updateHandlePosition(); update(); emit toggled(m_checked); } void setText(const QString &text) { if (m_text != text) { m_text = text; updateGeometry(); update(); } } void setDark(bool dark) { if (m_dark != dark) { m_dark = dark; update(); } } void setAccentColor(const QColor &color) { if (m_accentColor != color) { m_accentColor = color; update(); } } void setHandleScale(qreal scale) { scale = qBound(0.7, scale, 1.2); if (!qFuzzyCompare(m_handleScale, scale)) { m_handleScale = scale; update(); } } void setHandleX(int x) { int newX = qBound(4, x, 40 - 4 - m_handleWidth); if (m_handleX != newX) { m_handleX = newX; update(); } } void setHandleWidth(int width) { width = qBound(10, width, 20); if (m_handleWidth != width) { m_handleWidth = width; update(); } } signals: void toggled(bool checked); protected: void paintEvent(QPaintEvent *event) override { Q_UNUSED(event); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); // 1. 绘制轨道背景 QRect trackRect(0, (height() - 20) / 2, 40, 20); QColor trackColor = m_checked ? getCheckedTrackColor() : getUncheckedTrackColor(); if (m_dragging && m_checked) { trackColor = m_accentColor.lighter(150); } painter.setPen(Qt::NoPen); painter.setBrush(trackColor); painter.drawRoundedRect(trackRect, 10, 10); // 2. 绘制轨道边框 QColor borderColor = m_checked ? getCheckedBorderColor() : getUncheckedBorderColor(); painter.setPen(QPen(borderColor, 1)); painter.setBrush(Qt::NoBrush); painter.drawRoundedRect(trackRect, 10, 10); // 3. 绘制手柄 QRectF handleRect( m_handleX, (height() - m_handleHeight * m_handleScale) / 2, m_handleWidth * m_handleScale, m_handleHeight * m_handleScale ); painter.setPen(Qt::NoPen); painter.setBrush(getHandleColor()); painter.drawEllipse(handleRect.center(), handleRect.width()/2, handleRect.height()/2); // 4. 绘制文本 if (!m_text.isEmpty()) { painter.setPen(m_dark ? Qt::white : Qt::black); painter.setFont(font()); painter.drawText(48, (height() + painter.fontMetrics().height()) / 2 - 2, m_text); } } void mousePressEvent(QMouseEvent *event) override { if (event->button() == Qt::LeftButton && isEnabled()) { m_pressed = true; m_dragging = true; m_dragStartX = event->pos().x(); m_dragStartHandleX = m_handleX; updateHandleWidth(16); updateHandleScale(1.0); update(); event->accept(); } else { event->ignore(); } } void mouseMoveEvent(QMouseEvent *event) override { if (!isEnabled()) return; if (m_dragging) { int deltaX = event->pos().x() - m_dragStartX; int newX = qBound(4, m_dragStartHandleX + deltaX, 40 - 4 - m_handleWidth); setHandleX(newX); bool shouldBeChecked = (newX > 20); if (shouldBeChecked != m_checked) { setChecked(shouldBeChecked); } event->accept(); } } void mouseReleaseEvent(QMouseEvent *event) override { if (event->button() == Qt::LeftButton && m_pressed) { m_pressed = false; m_dragging = false; if (qAbs(event->pos().x() - m_dragStartX) < 3) { toggle(); } else { setChecked(m_handleX > 20); } updateHandleWidth(14); updateHandlePosition(); event->accept(); } } void enterEvent(QEnterEvent *event) override { Q_UNUSED(event); if (!isEnabled()) return; m_hovered = true; if (!m_pressed && !m_dragging) { updateHandleScale(1.0); } update(); } void leaveEvent(QEvent *event) override { Q_UNUSED(event); if (!isEnabled()) return; m_hovered = false; if (!m_pressed && !m_dragging) { updateHandleScale(0.857); } update(); } private: void toggle() { setChecked(!m_checked); } QColor getCheckedTrackColor() const { return m_accentColor.isValid() ? m_accentColor : (m_dark ? QColor(0x4C, 0xC2, 0xFF) : QColor(0x00, 0x67, 0xC0)); } QColor getUncheckedTrackColor() const { if (!isEnabled()) { return m_dark ? QColor(0x2A, 0x2A, 0x2A) : QColor(0xF5, 0xF5, 0xF5); } else if (m_pressed || m_dragging) { return m_dark ? QColor(0x40, 0x40, 0x40) : QColor(0xE6, 0xE6, 0xE6); } else if (m_hovered) { return m_dark ? QColor(0x38, 0x38, 0x38) : QColor(0xF3, 0xF3, 0xF3); } else { return m_dark ? QColor(0x34, 0x34, 0x34) : QColor(0xFD, 0xFD, 0xFD); } } QColor getCheckedBorderColor() const { return getCheckedTrackColor(); } QColor getUncheckedBorderColor() const { if (!isEnabled()) { return m_dark ? QColor(0xA7, 0xA7, 0xA7) : QColor(0xB6, 0xB6, 0xB6); } else { return m_dark ? QColor(0x9A, 0x9A, 0x9A) : QColor(0x86, 0x86, 0x86); } } QColor getHandleColor() const { if (m_checked) { return !isEnabled() ? (m_dark ? QColor(0xA7, 0xA7, 0xA7) : QColor(0xB6, 0xB6, 0xB6)) : (m_dark ? QColor(0x20, 0x20, 0x20) : Qt::white); } else { return !isEnabled() ? (m_dark ? QColor(0xA7, 0xA7, 0xA7) : QColor(0xB6, 0xB6, 0xB6)) : (m_dark ? QColor(0x9A, 0x9A, 0x9A) : QColor(0x86, 0x86, 0x86)); } } void updateHandlePosition() { if (m_dragging) return; const int targetX = m_checked ? (40 - 4 - m_handleWidth * m_handleScale) : 4; if (m_handleXAnimation->state() != QPropertyAnimation::Running) { m_handleXAnimation->stop(); m_handleXAnimation->setStartValue(m_handleX); m_handleXAnimation->setEndValue(targetX); m_handleXAnimation->start(); } } void updateHandleWidth(int width) { width = qBound(10, width, 20); if (m_handleWidthAnimation->state() != QPropertyAnimation::Running) { m_handleWidthAnimation->stop(); m_handleWidthAnimation->setStartValue(m_handleWidth); m_handleWidthAnimation->setEndValue(width); m_handleWidthAnimation->start(); } } void updateHandleScale(qreal scale) { scale = qBound(0.7, scale, 1.2); if (m_handleScaleAnimation->state() != QPropertyAnimation::Running) { m_handleScaleAnimation->stop(); m_handleScaleAnimation->setStartValue(m_handleScale); m_handleScaleAnimation->setEndValue(scale); m_handleScaleAnimation->start(); } } // 成员变量 bool m_checked = false; QString m_text; bool m_dark = false; QColor m_accentColor; bool m_hovered = false; bool m_pressed = false; bool m_dragging = false; int m_dragStartX = 0; int m_dragStartHandleX = 0; int m_handleX = 4; int m_handleWidth = 14; const int m_handleHeight = 14; qreal m_handleScale = 0.857; QPropertyAnimation *m_handleXAnimation; QPropertyAnimation *m_handleWidthAnimation; QPropertyAnimation *m_handleScaleAnimation; }; int main(int argc, char *argv[]) { QApplication a(argc, argv); QWidget window; QVBoxLayout *layout = new QVBoxLayout(&window); // 亮色主题示例 - 完全匹配图片中的状态 QGroupBox *lightGroup = new QGroupBox("Light Theme"); QVBoxLayout *lightLayout = new QVBoxLayout(lightGroup); FluentSwitch *switch1 = new FluentSwitch; switch1->setText("Wi-Fi"); // 关闭状态 lightLayout->addWidget(switch1); FluentSwitch *switch2 = new FluentSwitch; switch2->setText("Bluetooth"); switch2->setChecked(true); // 开启状态(蓝色) lightLayout->addWidget(switch2); FluentSwitch *switch3 = new FluentSwitch; switch3->setText("Airplane Mode"); switch3->setEnabled(false); // 禁用状态 lightLayout->addWidget(switch3); layout->addWidget(lightGroup); // 暗色主题示例 - 完全匹配图片中的状态 QGroupBox *darkGroup = new QGroupBox("Dark Theme"); QVBoxLayout *darkLayout = new QVBoxLayout(darkGroup); darkGroup->setStyleSheet("QGroupBox { background-color: #202020; color: white; }"); FluentSwitch *switch4 = new FluentSwitch; switch4->setText("Dark Mode"); switch4->setDark(true); // 关闭状态(灰色) darkLayout->addWidget(switch4); FluentSwitch *switch5 = new FluentSwitch; switch5->setText("Notifications"); switch5->setDark(true); // 关闭状态(灰色) darkLayout->addWidget(switch5); FluentSwitch *switch6 = new FluentSwitch; switch6->setText("Location Services"); switch6->setDark(true); switch6->setEnabled(false); // 禁用状态 darkLayout->addWidget(switch6); FluentSwitch *switch7 = new FluentSwitch; switch7->setText("Custom Color"); switch7->setDark(true); switch7->setAccentColor(QColor(0xFF, 0x00, 0x88)); // 粉色 switch7->setChecked(true); // 开启状态(粉色) darkLayout->addWidget(switch7); layout->addWidget(darkGroup); window.setLayout(layout); window.resize(300, 300); window.show(); return a.exec(); } #include "main.moc"
BugDemo.mp4
Thanks in advance for your expertise! -
here is video is link https://streamable.com/pvu9d8
-
@YiShengPiAn Please format your code properly, it is hard to read
@jsulm ok, you look see
-
@JoeCFD Yes, Qt 6.9.0. I've referenced WinUI's approach, but I need help resolving this issue.
-
@JoeCFD Yes, Qt 6.9.0. I've referenced WinUI's approach, but I need help resolving this issue.
@YiShengPiAn Your widget position changes. Add print to paintEvent to check its coordinate x and you may be be able to find out what causes the horizontal movement of your widget. x should be fixed.