Search code examples
c++qthovertransitionqwidget

How to add a hover transition to QPushButton?


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?


Solution

  • 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