Zoom-and-rotate on mouseover effect in Graphics View

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:


class ImageZoomItem : public QObject, public QGraphicsPixmapItem
ImageZoomItem(const QPixmap &pixmap, QGraphicsScene *scene = 0);
void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
private slots:
void setFrame(int frame);
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)

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 *)
if (timeLine.state() == QTimeLine::NotRunning)

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 *)
if (timeLine.state() == QTimeLine::NotRunning)

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();

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);

QGraphicsView view(&scene);;

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.

Blog Topics: