How to add a hover transition to QPushButton?

Viewed 9886

I try to make a custom QPushButton with a stylesheet. I want to custom color of button when we mouse over it. It works, but I want to put a transition duration. But in Qt this option is not available.

Here is my custom button:

#include "bouton.h"

Bouton::Bouton(QString title, QWidget *parent) : QPushButton()
{
  setGeometry(50,50,120,40);
  setText(title);
  setMinimumHeight(30);
  setParent(parent);
  setStyleSheet(" QPushButton {"
              "border-radius: 5px; "
              "border: 1.5px solid rgb(91,231,255); "
              "background-color: white; }"
              "QPushButton:pressed {"
              "border: 1.4px solid rgb(73,186,205); }"
              "QPushButton:hover {"
              "font-size: 16px;"
              "transition: 0.9s; }");
}

The argument "transition 0.9s" doesn't work.

Here is an example in CSS.

Are there other ways to do this?

2 Answers

UPDATE

For some reason the proposed solution does not work as expected on Windows 10. I have updated the answer using painter.setOpacity(0.25); and painter.fillRect(rect(), m_currentColor); as a workaround. The code in the GitHub repository is updated as well.


Cause

QSS is not CSS. There is no transition property. Here is a list of all available properties.

Solution

Instead of using stylesheets, I would suggest you to take another path, which is longer, but gives you more flexibility. Here is the solution:

  1. Create a subclass of QPushButton, e.g. AnimatedHoverButton

  2. Get notified about QEvent::HoverEnter and QEvent::HoverLeave events by reimplementing QPushButton::event

     bool AnimatedHoverButton::event(QEvent *event)
     {
         switch (event->type()) {
             case QEvent::HoverEnter:
                 animateHover(true);
                 break;
             case QEvent::HoverLeave:
                 animateHover(false);
                 break;
             default:
                 break;
         }
    
         return QPushButton::event(event);
     }
    
  3. Create the in and out transition by using QVariantAnimation

     void AnimatedHoverButton::animateHover(bool in)
     {
         if (m_transition)
             m_transition->stop();
    
         m_transition = new QVariantAnimation(this);
         m_transition->setDuration(m_duration);
         m_transition->setStartValue(m_currentColor);
         m_transition->setEndValue(in ? palette().highlight().color()
                                      : Qt::transparent);
    
         connect(m_transition, &QVariantAnimation::valueChanged,
                 this, [this](const QVariant &value){
             m_currentColor = value.value<QColor>();
             repaint();
         });
    
         connect(m_transition, &QVariantAnimation::destroyed,
                 this, [this](){
             m_transition = nullptr;
             repaint();
         });
    
         m_transition->start(QAbstractAnimation::DeleteWhenStopped);
     }
    
  4. Paint the button by reimplementing the QPushButton::paintEvent event handler and taking into account the current value of the animation

     void AnimatedHoverButton::paintEvent(QPaintEvent */*event*/)
     {
         QStylePainter painter(this);
         QStyleOptionButton option;
    
         initStyleOption(&option);
    
         option.state &= ~QStyle::State_MouseOver;
    
         painter.drawControl(QStyle::CE_PushButton, option);
         painter.setOpacity(0.25);
         painter.fillRect(rect(), m_currentColor);
     }
    

Note: This solution uses the widget's palette to set the start and end values of the animation.

Example

The solution might seem complicated, but fortunatelly I have prepared a working example for you of how to implement and use the AnimatedHoverButton class.

The following code fragment uses the AnimatedHoverButton class to produce a result, similar to the CSS example you have provided:

#include <QApplication>
#include "AnimatedHoverButton.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    AnimatedHoverButton button(QObject::tr("Hover Over Me"));

    button.setTransitionDuration(300);
    button.resize(300, 150);
    button.show();

    return a.exec();
}

The full code of the example is available on GitHub.

Result

The given example produces the following result:

Window with a highlighted custom button

You can use Animation.

MyButton.h

#include <QPushButton>
#include <QColor>
#include <QPropertyAnimation>

class MyButton : public QPushButton
{
    Q_OBJECT
    Q_PROPERTY(QColor color READ GetColor WRITE SetColor)

public:
    explicit MyButton(QWidget *parent = 0);

    void SetColor(const QColor& color);
    const QColor& GetColor() const;

protected:
    bool eventFilter(QObject *obj, QEvent *e);

private:
    QColor m_currentColor;

    QPropertyAnimation m_colorAnimation;

    void StartHoverEnterAnimation();
    void StartHoverLeaveAnimation();
};

MyButton.cpp

#include "MyButton.h"

#include <QEvent>
#include <QDebug>

MyButton::MyButton(QWidget *parent) :
    QPushButton(parent),
    m_colorAnimation(this, "color")
{
    this->installEventFilter(this);
}

void MyButton::SetColor(const QColor& color)
{
    m_currentColor = color;
    QString css = "QPushButton { border-radius: 5px; ";
    css.append("border: 1.5px solid rgb(91,231,255); ");
    QString strColor = QString("rgb(%1, %2, %3)").arg(color.red()).arg(color.green()).arg(color.blue());
    css.append("background-color: " + strColor + "; }");
    setStyleSheet(css);
}

const QColor& MyButton::GetColor() const
{
    return m_currentColor;
}

bool MyButton::eventFilter(QObject *obj, QEvent *e)
{
    if (e->type() == QEvent::HoverEnter) {
        StartHoverEnterAnimation();
    }

    if (e->type() == QEvent::HoverLeave) {
        StartHoverLeaveAnimation();
    }

    return false;
}

void MyButton::StartHoverEnterAnimation()
{
    m_colorAnimation.stop();

    m_colorAnimation.setDuration(900); //set your transition
    m_colorAnimation.setStartValue(GetColor()); //starts from current color
    m_colorAnimation.setEndValue(QColor(100, 100, 100));//set your hover color

    m_colorAnimation.setEasingCurve(QEasingCurve::Linear);//animation style

    m_colorAnimation.start();
}

void MyButton::StartHoverLeaveAnimation()
{
    m_colorAnimation.stop();

    m_colorAnimation.setDuration(900); //set your transition
    m_colorAnimation.setStartValue(GetColor()); //starts from current color
    m_colorAnimation.setEndValue(QColor(255, 0, 0));//set your regular color

    m_colorAnimation.setEasingCurve(QEasingCurve::Linear);//animation style

    m_colorAnimation.start();
}

It will conflict with external qss setting. So set all qss in SetColor.

Related