flippablepad.cpp Example File¶
flippablepad.h Example File¶
form.ui Example File¶
roundrectitem.cpp Example File¶
roundrectitem.h Example File¶
splashitem.cpp Example File¶
splashitem.h Example File¶
main.cpp Example File¶
artsfftscope.png Image File¶
blue_angle_swirl.jpg Image File¶
kontact_contacts.png Image File¶
kontact_journal.png Image File¶
kontact_mail.png Image File¶
kontact_notes.png Image File¶
kopeteavailable.png Image File¶
metacontact_online.png Image File¶
minitools.png Image File¶
Demonstrates how to create animated user interface.
The Pad Navigator Example shows how you can use Graphics View together with embedded widgets and Qt’s state machine framework to create a simple but useful, dynamic, animated user interface.
The interface consists of a flippable, rotating pad with icons that can be selected using the arrow keys on your keyboard or keypad. Pressing enter will flip the pad around and reveal its back side, which has a form embedded into a QGraphicsProxyWidget. You can interact with the form, and press the enter key to flip back to the front side of the pad at any time.
Graphics View provides the QGraphicsScene class for managing and interacting with a large number of custom-made 2D graphical items derived from the QGraphicsItem class, and a QGraphicsView widget for visualizing the items, with support for zooming and rotation.
This example consists of a
RoundRectItem
class, aFlippablePad
class, aPadNavigator
class, aSplashItem
class, and amain()
function.
RoundRectItem Class Definition¶
The
RoundRectItem
class is used by itself to display the icons on the pad, and as a base class forFlippablePad
, the class for the pad itself. The role of the class is to paint a round rectangle of a specified size and gradient color, and optionally to paint a pixmap icon on top. To supportFlippablePad
it also allows filling its contents with a plain window background color.Let’s start by reviewing the
RoundRectItem
class declaration.class RoundRectItem : public QGraphicsObject { Q_OBJECT Q_PROPERTY(bool fill READ fill WRITE setFill) public: RoundRectItem(const QRectF &bounds, const QColor &color, QGraphicsItem *parent = 0); QPixmap pixmap() const; void setPixmap(const QPixmap &pixmap); QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) override; bool fill() const; void setFill(bool fill);
RoundRectItem
inherits QGraphicsObject, which makes it easy to control its properties using QPropertyAnimation. Its constructor takes a rectangle to determine its bounds, and a color.Besides implementing the mandatory paint() and boundingRect() pure virtual functions, it also provides the
pixmap
andfill
properties.The
pixmap
property sets an optional pixmap that is drawn on top of the round rectangle. Thefill
property will, when true, fill the round rectangle contents with a fixed QPalette::Window background color. Otherwise the contents are filled using a gradient based on the color passed toRoundRectItem
‘s constructor.private: QPixmap pix; bool fillRect; QRectF bounds; QLinearGradient gradient; };The private data members are:
pix
: The optional pixmap that is drawn on top of the rectangle.
fillRect
: Corresponds to thefill
property.
color
: The configurable gradient color fill of the rectangle.
bounds
: The bounds of the rectangle.
gradient
: A precalculated gradient used to fill the rectangle.We will now review the
RoundRectItem
implementation. Let’s start by looking at its constructor:RoundRectItem::RoundRectItem(const QRectF &bounds, const QColor &color, QGraphicsItem *parent) : QGraphicsObject(parent), fillRect(false), bounds(bounds) { gradient.setStart(bounds.topLeft()); gradient.setFinalStop(bounds.bottomRight()); gradient.setColorAt(0, color); gradient.setColorAt(1, color.darker(200)); setCacheMode(ItemCoordinateCache); }The constructor initializes its member variables and forwards the
parent
argument to QGraphicsObject’s constructor. It then constructs the linear gradient that is used in paint() to draw the round rectangle’s gradient background. The linear gradient’s starting point is at the top-left corner of the bounds, and the end is at the bottom-left corner. The start color is identical to the color passed as an argument, and a slightly darker color is chosen for the final stop.We store this gradient as a member variable to avoid having to recreate the gradient every time the item is repainted.
Finally we set the cache mode ItemCoordinateCache. This mode causes the item’s rendering to be cached into an off-screen pixmap that remains persistent as we move and transform the item. This mode is ideal for this example, and works particularly well with OpenGL and OpenGL ES.
QPixmap RoundRectItem::pixmap() const { return pix; } void RoundRectItem::setPixmap(const QPixmap &pixmap) { pix = pixmap; update(); }The
pixmap
property implementation simple returns the member pixmap, or sets it and then calls update().QRectF RoundRectItem::boundingRect() const { return bounds.adjusted(0, 0, 2, 2); }As the paint() implementation below draws a simple drop shadow down and to the right of the item, we return a slightly adjusted rectangle from boundingRect().
void RoundRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setPen(Qt::NoPen); painter->setBrush(QColor(0, 0, 0, 64)); painter->drawRoundedRect(bounds.translated(2, 2), 25, 25, Qt::RelativeSize);The paint() implementation starts by rendering a semi transparent black round rectangle drop shadow, two units down and to the right of the main item.
if (fillRect) painter->setBrush(QApplication::palette().brush(QPalette::Window)); else painter->setBrush(gradient); painter->setPen(QPen(Qt::black, 1)); painter->drawRoundedRect(bounds, 25,25, Qt::RelativeSize);We then draw the “foreground” round rectangle itself. The fill depends on the
fill
property; if true, we will with a plain QPalette::Window color. We get the current brush from QApplication::palette(). We assign a single unit wide pen for the stroke, assign the brush, and then draw the rectangle.if (!pix.isNull()) { painter->scale(1.95, 1.95); painter->drawPixmap(-pix.width() / 2, -pix.height() / 2, pix); } }If a pixmap has been assigned to the pixmap property, we draw this pixmap in the center of the rectangle item. The pixmaps are scaled to match the size of the icons; in arguably a better approach would have been to store the icons with the right size in the first places.
bool RoundRectItem::fill() const { return fillRect; } void RoundRectItem::setFill(bool fill) { fillRect = fill; update(); }Finally, for completeness we include the
fill
property implementation. It returns thefill
member variable’s value, and when assigned to, it calls update().As mentioned already,
RoundRectItem
is the base class forFlippablePad
, which is the class representing the tilting pad itself. We will proceed to reviewingFlippablePad
.
FlippablePad Class Definition¶
FlippablePad
is, in addition to its inheritedRoundRectItem
responsibilities, responsible for creating and managing a grid of icons.class FlippablePad : public RoundRectItem { public: explicit FlippablePad(const QSize &size, QGraphicsItem *parent = 0); RoundRectItem *iconAt(int column, int row) const; private: QVector<QVector<RoundRectItem *> > iconGrid; };Its declaration is very simple: It inherits
RoundRectItem
and does not need any special polymorphic behavior. It’s suitable to declare its own constructor, and a getter-function that allowsPadNavigator
to access the icons in the grid by (row, column).The example has no “real” behavior or logic of any kind, and because of that, the icons do not need to provide any behavior or special interactions management. In a real application, however, it would be natural for the
FlippablePad
and its icons to handle more of the navigation logic. In this example, we have chosen to leave this to thePadNavigator
class, which we will get back to below.We will now review the
FlippablePad
implementation. This implementation starts with two helper functions:boundsFromSize()
andposForLocation()
:static QRectF boundsFromSize(const QSize &size) { return QRectF((-size.width() / 2.0) * 150, (-size.height() / 2.0) * 150, size.width() * 150, size.height() * 150); }
boundsForSize()
takes a QSize argument, and returns the bounding rectangle of the flippable pad item. The QSize determines how many rows and columns the icon grid should have. Each icon is given 150x150 units of space, and this determines the bounds.static QPointF posForLocation(int column, int row, const QSize &size) { return QPointF(column * 150, row * 150) - QPointF((size.width() - 1) * 75, (size.height() - 1) * 75); }
posForLocation()
returns the position of an icon given its row and column position. LikeboundsForSize()
, the function assumes each icon is given 150x150 units of space, and that all icons are centered around the flippable pad item’s origin (0, 0).FlippablePad::FlippablePad(const QSize &size, QGraphicsItem *parent) : RoundRectItem(boundsFromSize(size), QColor(226, 255, 92, 64), parent) {The
FlippablePad
constructor passes suitable bounds (usingboundsForSize()
) and specific color toRoundRectItem
‘s constructor.int numIcons = size.width() * size.height(); QList<QPixmap> pixmaps; QDirIterator it(":/images", QStringList() << "*.png"); while (it.hasNext() && pixmaps.size() < numIcons) pixmaps << it.next();It then loads pixmaps from compiled-in resources to use for its icons. QDirIterator is very useful in this context, as it allows us to fetch all resource “*.png” files inside the
:/images
directory without explicitly naming the files.We also make sure not to load more pixmaps than we need.
const QRectF iconRect(-54, -54, 108, 108); const QColor iconColor(214, 240, 110, 128); iconGrid.resize(size.height()); int n = 0; for (int y = 0; y < size.height(); ++y) { iconGrid[y].resize(size.width()); for (int x = 0; x < size.width(); ++x) { RoundRectItem *rect = new RoundRectItem(iconRect, iconColor, this); rect->setZValue(1); rect->setPos(posForLocation(x, y, size)); rect->setPixmap(pixmaps.at(n++ % pixmaps.size())); iconGrid[y][x] = rect; } } }Now that we have the pixmaps, we can create icons, position then and assign pixmaps. We start by finding a suitable size and color for the icons, and initializing a convenient grid structure for storing the icons. This
iconGrid
is also used later to find the icon for a specific (column, row) location.For each row and column in our grid, we proceed to constructing each icon as an instance of
RoundRectItem
. The item is placed by using theposForLocation()
helper function. To make room for the slip-behind selection item, we give each icon a Z-value of 1. The pixmaps are distributed to the icons in round-robin fasion.Again, this approach is only suitable for example purposes. In a real-life application where each icon represents a specific action, it would be more natural to assign the pixmaps directly, or that the icons themselves provide suitable pixmaps.
RoundRectItem *FlippablePad::iconAt(int column, int row) const { return iconGrid[row][column]; }Finally, the
iconAt()
function returns a pointer to the icon at a specific row and column. It makes a somewhat bold assumption that the input is valid, which is fair because thePadNavigator
class only calls this function with correct input.We will now review the
SplashItem
class.
SplashItem Class Definition¶
The
SplashItem
class represents the “splash window”, a semitransparent white overlay with text that appears immediately after the application has started, and disappears after pressing any key. The animation is controlled byPadNavigator
; this class is very simple by itself.class SplashItem : public QGraphicsObject { Q_OBJECT public: explicit SplashItem(QGraphicsItem *parent = 0); QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) override; private: QString text; };The class declaration shows that
SplashItem
inherits QGraphicsObject to allow it to be controlled by QPropertyAnimation. It reimplements the mandatory paint() and boundingRect() pure virtual functions, and keeps atext
member variable which will contain the information text displayed on this splash item.Let’s look at its implementation.
SplashItem::SplashItem(QGraphicsItem *parent) : QGraphicsObject(parent) { text = tr("Welcome to the Pad Navigator Example. You can use the" " keyboard arrows to navigate the icons, and press enter" " to activate an item. Press any key to begin."); setCacheMode(DeviceCoordinateCache); }The constructor forwards to QGraphicsObject as expected, assigns a text message to the
text
member variable, and enables DeviceCoordinateCache. This cache mode is suitable because the splash item only moves and is never transformed, and because it contains text, it’s important that it has a pixel perfect visual appearance (in constrast to ItemCoordinateCache, where the visual appearance is not as good).We use caching to avoid having to relayout and rerender the text for each frame. An alterative approach would be to use the new QStaticText class.
QRectF SplashItem::boundingRect() const { return QRectF(0, 0, 400, 175); }
SplashItem
‘s bounding rectangle is fixed at (400x175).void SplashItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setPen(QPen(Qt::black, 2)); painter->setBrush(QColor(245, 245, 255, 220)); painter->setClipRect(boundingRect()); painter->drawRoundedRect(3, -100 + 3, 400 - 6, 250 - 6, 25, 25, Qt::RelativeSize); QRectF textRect = boundingRect().adjusted(10, 10, -10, -10); int flags = Qt::AlignTop | Qt::AlignLeft | Qt::TextWordWrap; QFont font; font.setPixelSize(18); painter->setPen(Qt::black); painter->setFont(font); painter->drawText(textRect, flags, text); }The paint() implementation draws a clipped round rectangle with a thick 2-unit border and a semi-transparent white background. It proceeds to finding a suitable text area by adjusting the splash item’s bounding rectangle with 10 units in each side. The text is rendered inside this rectangle, with top-left alignment, and with word wrapping enabled.
The main class now remains. We will proceed to reviewing
PadNavigator
.
The main() Function¶
int main(int argc, char *argv[]) { QApplication app(argc, argv); Q_INIT_RESOURCE(padnavigator); PadNavigator navigator(QSize(3, 3)); navigator.show(); return app.exec(); }The
main
function creates the QApplication instance, usesQ_INIT_RESOURCE()
to ensure our compiled-in resources aren’t removed by the linker, and then creates a 3x3PadNavigator
instance and shows it.Our flippable pad shows up with a suitable splash item once control returns to the event loop.
Performance Notes¶
The example uses OpenGL if this is available, to achieve optimal performance; otherwise perspective tranformations can be quite costly.
Although this example does use QGraphicsProxyWidget to demonstrate integration of Qt widget components integrated into Graphics View, using QGraphicsProxyWidget comes with a performance penalty, and is therefore not recommended for embedded development.
This example uses extensive item caching to avoid rerendering of static elements, at the expense of graphics memory.