Wiki

Fading Effects with Qt 4.1

by Trenton Schulz
The backing store in Qt 4.1 makes it much easier to implement transparency effects than previous versions of Qt. In this article, we will combine an alpha channel with a timer and get some nice results that can make an interface attractive without distracting the user.

[Download source code]

We use semi-transparency to implement FaderWidget, a widget that we'll put on top of another widget before fading it away. As it fades away, the widget underneath it will become visible.

class FaderWidget : public QWidget
{
    Q_OBJECT
    Q_PROPERTY(QColor fadeColor READ fadeColor \
               WRITE setFadeColor)
    Q_PROPERTY(int fadeDuration READ fadeDuration \
               WRITE setFadeDuration)
public:
    FaderWidget(QWidget *parent);
 
    QColor fadeColor() const { return color; }
    void setFadeColor(const QColor &newColor);
    int fadeDuration() const { return duration; }
    void setFadeDuration(int milliseconds);
    void start();
 
protected:
    void paintEvent(QPaintEvent *event);
 
private:
    QTimer *timer;
    int currentAlpha;
    QColor color;
    int duration;
};

The fadeColor property controls the solid color that we start fading from; fadeDuration is the time in milliseconds it takes for the fade to complete.

FaderWidget::FaderWidget(QWidget *parent)
    : QWidget(parent)
{
    if (parent)
        startColor = parent->palette().window().color();
    else
        startColor = Qt::white;
 
    currentAlpha = 0;
    duration = 333;
 
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()),
            this, SLOT(update()));
 
    setAttribute(Qt::WA_DeleteOnClose);
    resize(parent->size());
}

The constructor initializes the default fade color to the parent's "window" palette entry (the "background" entry in previous Qt versions), typically some shade of gray. The default fade duration is 333 milliseconds. This is usually short enough for the animation to finish without being too distracting.

We enable the Qt::WA_DeleteOnClose attribute because the fader is a short-lived widget that automatically deletes itself once the fading effect is finished. We also create a QTimer to call update() repeatedly. At the end, we resize the widget so that it is the same size as the parent, effectively covering it completely.

void FaderWidget::start()
{
    currentAlpha = 255;
    timer->start(33);
    show();
}

The start() function initiates the fade. Here we simply set the currentAlpha value to 255 (opaque). To obtain a rate of 30 frames per second, we use 33 milliseconds as the timer interval.

Now, every time the timer emits the timeout() signal, we will get a paint event. The paintEvent() handler looks like this:

void FaderWidget::paintEvent(QPaintEvent * /* event */)
{
    QPainter painter(this);
    QColor semiTransparentColor = startColor;
    semiTransparentColor.setAlpha(currentAlpha);
    painter.fillRect(rect(), semiTransparentColor);
 
    currentAlpha -= 255 * timer->interval() / duration;
    if (currentAlpha <= 0) {
        timer->stop();
        close();
    }
}

We create a new color based on the original color and our current alpha value. We then fill the widget with this color and decrement the alpha value. The next time we paint, the FaderWidget will be slightly more transparent. When the currentAlpha reaches zero, we stop the timer and close the widget since, at this point, the widget is completely transparent. The widget will be implicitly deleted when it is closed because we set the Qt::WA_DestroyOnClose attribute in the constructor.

Now that we have implemented the FaderWidget, let's quickly modify the Config Dialog example that is included with Qt (in "examples/dialogs/configdialog") to fade between its pages. We simply need to add a new private slot and a QPointer to the ConfigDialog class:

private slots:
    void fadeInWidget(int index);
private:
    QPointer<FaderWidget> faderWidget;

In the constructor, we connect QStackedWidget's currentChanged() signal to our new fadeInWidget() slot:

  connect(pagesWidget, SIGNAL(currentChanged(int)),
            this, SLOT(fadeInWidget(int)));

Here's the slot:

void ConfigDialog::fadeInWidget(int index)
{
    if (faderWidget)
        faderWidget->close();
    faderWidget = new FaderWidget(
                              pagesWidget->widget(index));
    faderWidget->start();
}

First, we check if we have a FaderWidget. This is where a QPointer comes in handy since, unlike a standard pointer, it is automatically reset to zero when the object it points to is deleted. If there is already a FaderWidget, we close it since we don't need to have multiple fades happening at the same time.

In any case, we create a new FaderWidget with the widget to be revealed as its parent, and call start on the FaderWidget. The rest just fades away, as you will see when you run the example.


Copyright © 2006 Trolltech Trademarks