Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Need Help Debugging Custom Qt/C++ Switch Control (Video/GIF Reference Provided)
Forum Updated to NodeBB v4.3 + New Features

Need Help Debugging Custom Qt/C++ Switch Control (Video/GIF Reference Provided)

Scheduled Pinned Locked Moved Unsolved General and Desktop
8 Posts 3 Posters 205 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • Y Offline
    Y Offline
    YiShengPiAn
    wrote last edited by
    #1

    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
    2.gif
    Thanks in advance for your expertise!

    jsulmJ 1 Reply Last reply
    0
    • Y YiShengPiAn

      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
      2.gif
      Thanks in advance for your expertise!

      jsulmJ Offline
      jsulmJ Offline
      jsulm
      Lifetime Qt Champion
      wrote last edited by
      #2

      @YiShengPiAn Please format your code properly, it is hard to read

      https://forum.qt.io/topic/113070/qt-code-of-conduct

      Y 1 Reply Last reply
      1
      • Y Offline
        Y Offline
        YiShengPiAn
        wrote last edited by
        #3

        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
        4db5986b-3e9e-4acd-b9d8-4168b959a8c6-2.gif
        Thanks in advance for your expertise!

        1 Reply Last reply
        0
        • Y Offline
          Y Offline
          YiShengPiAn
          wrote last edited by
          #4

          here is video is link https://streamable.com/pvu9d8

          1 Reply Last reply
          0
          • jsulmJ jsulm

            @YiShengPiAn Please format your code properly, it is hard to read

            Y Offline
            Y Offline
            YiShengPiAn
            wrote last edited by
            #5

            @jsulm ok, you look see

            1 Reply Last reply
            0
            • JoeCFDJ Offline
              JoeCFDJ Offline
              JoeCFD
              wrote last edited by
              #6

              your Qt version and OS?

              Y 1 Reply Last reply
              0
              • JoeCFDJ JoeCFD

                your Qt version and OS?

                Y Offline
                Y Offline
                YiShengPiAn
                wrote last edited by
                #7

                @JoeCFD Yes, Qt 6.9.0. I've referenced WinUI's approach, but I need help resolving this issue.

                JoeCFDJ 1 Reply Last reply
                0
                • Y YiShengPiAn

                  @JoeCFD Yes, Qt 6.9.0. I've referenced WinUI's approach, but I need help resolving this issue.

                  JoeCFDJ Offline
                  JoeCFDJ Offline
                  JoeCFD
                  wrote last edited by
                  #8

                  @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.

                  1 Reply Last reply
                  0

                  • Login

                  • Login or register to search.
                  • First post
                    Last post
                  0
                  • Categories
                  • Recent
                  • Tags
                  • Popular
                  • Users
                  • Groups
                  • Search
                  • Get Qt Extensions
                  • Unsolved