Andreas
Qt
Labs
Graphics View
Graphics Items
Posted by Andreas
 in Qt, Labs, Graphics View, Graphics Items
 on Thursday, November 30, 2006 @ 14:01

Have you seen flash-interfaces with controls that grow and twist when you move your mouse over them? Here’s how to do that in Graphics View. I was so surprised at how easy it was that I thought I’d share it. Here’s a screenshot, just so you know what I’m talking about:


I’ll do this in the good old all-in-one-file style that we all love to hate. So, main.cpp:

#include 

class ImageZoomItem : public QObject, public QGraphicsPixmapItem
{
    Q_OBJECT
public:
    ImageZoomItem(const QPixmap &pixmap, QGraphicsScene *scene = 0);
protected:
    void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
    void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
private slots:
    void setFrame(int frame);
private:
    QTimeLine timeLine;
};

An image item that cross-inherits from QObject and QGraphicsPixmapItem? Isn’t that considered inherently evil by the API purists we are? In fact, no. We designed QGraphicsItem specificly so you could do this. So you can easily have signals and slots and all that fun stuff, in addition to the events. Graphics items aren’t QObjects by default, because QObject requires memory. Event support, however, is free. So now you know. If you want slots, bring in QObject. So let’s move on.

ImageZoomItem::ImageZoomItem(const QPixmap &pixmap, QGraphicsScene *scene)
    : QGraphicsPixmapItem(pixmap, 0, scene)
{
    setAcceptsHoverEvents(true);

    timeLine.setDuration(100);
    timeLine.setFrameRange(0, 100);
    connect(&timeLine, SIGNAL(frameChanged(int)), this, SLOT(setFrame(int)));
}

The image zoom item starts by enabling hover events. You need those to track the mouse entering and leaving your item. Then, we set up a QTimeLine (beautiful class, that is ;-)), with a 100 millisecond duration, starting at frame 0 and ending at 100. We’ll use the timeline to control our animation as the item zooms in and out. Finally, we connect the timeline to a slot in this class.

void ImageZoomItem::hoverEnterEvent(QGraphicsSceneHoverEvent *)
{
    timeLine.setDirection(QTimeLine::Forward);
    if (timeLine.state() == QTimeLine::NotRunning)
        timeLine.start();
}

The hoverEnterEvent() implementation is called when the mouse enters the item’s area. For semi-transparent images like the one in the screenshot above, this means when the mouse enters the opaque part of the bulb itself. So we start by setting the timeline direction, then start the timeline if necessary. With QTimeLine::Forward, this will make QTimeLine call the slot below 25 times per second over a period of 100 milliseconds, increasing the frame step from 0 to 100 with even steps.

The leave event is almost the same:

void ImageZoomItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *)
{
    timeLine.setDirection(QTimeLine::Backward);
    if (timeLine.state() == QTimeLine::NotRunning)
        timeLine.start();
}

Just set the timeline direction to QTimeLine::Backward when the mouse leaves the item. The cool thing is that this seamlessly supports moving the mouse over a bunch of items like crazy. Once the mouse moves in, the item will start growing, and as soon as you leave the item it’ll switch back to shrinking again. OK now for the slot:

void ImageZoomItem::setFrame(int frame)
{
    QPointF center = boundingRect().center();
    resetMatrix();

    translate(center.x(), center.y());
    scale(1 + frame / 150.0, 1 + frame / 150.0);
    rotate(frame / 8.0);
    translate(-center.x(), -center.y());
}

Reset the matrix, translate the item by half its height and width to ensure the items doesn’t visually move while zooming, scale it a bit, rotate a bit, and translate back again. And make note that the scale and rotate operations are not accumulative, but rather a function of the frame number. That means we can play the animation forwards and backwards or skip around randomly.

That leaves us with the main function itself:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QPixmap pix("light.png");
    QGraphicsScene scene;

    for (int i = 0; i setPos(qrand() % 1000, qrand() % 1000);
        scene.addItem(item);
    }

    QGraphicsView view(&scene);
    view.show();

    return app.exec();
}

#include "main.moc"

Scatter a hundred bulb items around the scene. Create and show a view for the scene. Done.

Download the sources here.

3 Responses to “Zoom-and-rotate on mouseover effect in Graphics View”

» Posted by Ricard
 on Thursday, November 30, 2006 @ 15:24

Nice example.
This also illustrates a bug in Qt 4.2.0 (shipped with Kubuntu).

I don’t know about Qt 4.2.2, but in Qt 4.3.0-snapshot-20061114 it has been fixed.

The bug is that it’s not refreshing the rectangle that gets uncovered by the pixmaps when they scale back down.

» Posted by Thomas Zander
 on Friday, December 01, 2006 @ 09:29

To Ricard; that same bug is present in plain qt4.2 (from qt-copy).

I’m wondering; what speed is the anim for you? On my ati-card it takes about 2 seconds for the ‘anim’ to complete.

» Posted by Anonymous
 on Saturday, December 02, 2006 @ 20:04

Ricard: Tested with Qt 4.2.2 in Mandriva 2007 and seems it has been fixed (I tested it yesterday with 4.2.1 and it has the same problem you have described).

Thomas: With a Geforce 6100 (a very basic integrated card) the animation takes a second fraction, it’s very fast (with X11 Render extension enabled and nvidia binary drivers installed, if this means something).



© 2008 Nokia Corporation and/or its subsidiaries. Nokia, Qt and their respective logos are trademarks of Nokia Corporation in Finland and/or other countries worldwide.
All other trademarks are property of their respective owners.