Flippin’ Widgets (Medium Rare, Please)

Published Tuesday June 9th, 2009
9 Comments on Flippin’ Widgets (Medium Rare, Please)
Posted in Kinetic, Qt

In Qt 4.6, QGraphicsWidget is gaining some new properties for transforming items: {x,y,z}Rotation, {x,y}Scale, {horizontal,vertical}Shear and transformOrigin, to be specific. How does this fit with the 4.6 master plan, the almighty Qt Kinetic project? Did someone just say “Like a glove! (Ka-bling!)“?! Yes, that sounds about right. Naturally I set out to use and abuse that shiny new stuff by means of states and animations. Seeing as I’m born flippy, a basic use case that came to mind was to create a widget that can be flipped between a “front” side and “back” side. Such a “two-faced” widget can (for example) be implemented as a QStackedWidget that contains the “front” widget on page 0 and the “back” widget on page 1. Flipping to the other side is then a matter of toggling the QStackedWidget’s currentIndex property. But obviously we want to smoothly animate this change, as failure to do so would kinda take the “flip” out of “flippin'” (ya know?). One way of doing it is to first animate the item’s yRotation from 0 to 90 (at which point the item will effectively be invisible), then toggle the currentIndex property, and finally animate yRotation from -90 to 0. The visual effect is that the “two-faced” item appears to rotate 180 degrees, exposing its Good (Evil) side.

Today’s post is brough to you courtesy of programming languages of yore (either you’re with them, or you’re against them). Alas, here’s the C++ code that sets up the stacked widget and such:

FlipWidget::FlipWidget(QGraphicsItem *parent)
    : QGraphicsWidget(parent)
{
    // The front.
    QWidget *front = new QWidget();
    QPushButton *toBackButton = new QPushButton("To Back");
    QObject::connect(toBackButton, SIGNAL(clicked()), this, SIGNAL(flipRequest()));
    toBackButton->setStyleSheet("background: pink");
    {
        QVBoxLayout *vbox = new QVBoxLayout(front);
        vbox->addWidget(toBackButton);
    }

    // The back.
    QWidget *back = new QWidget();
    QPushButton *toFrontButton = new QPushButton("To Front");
    QObject::connect(toFrontButton, SIGNAL(clicked()), this, SIGNAL(flipRequest()));
    toFrontButton->setStyleSheet("background: yellow");
    {
        QVBoxLayout *vbox = new QVBoxLayout(back);
        vbox->addWidget(toFrontButton);
    }

    // The stacked widget.
    m_stack = new QStackedWidget();
    m_stack->addWidget(front);
    m_stack->addWidget(back);

    QGraphicsProxyWidget *stackProxy = new QGraphicsProxyWidget();
    stackProxy->setWidget(m_stack);
    QGraphicsLinearLayout *vbox = new QGraphicsLinearLayout(this);
    vbox->addItem(stackProxy);

The “front” and “back” widgets don’t contain anything interesting in this example, just buttons that flip the item. Clicking both the “front” and “back” button will cause the item’s own flipRequest() signal to be emitted; this is so that the same state logic can be used to handle flips from both “sides”. Which brings us to the interesting bit, which is the code that sets up the states and animations for doing the flip:

    QStateMachine *machine = new QStateMachine(this);
    QState *s0 = new QState(machine->rootState());
    s0->assignProperty(this, "yRotation", 0);

    QState *s1 = new QState(machine->rootState());
    s1->assignProperty(this, "yRotation", 90);

    QAbstractTransition *t1 = s0->addTransition(this, SIGNAL(flipRequest()), s1);
    QPropertyAnimation *yRotationAnim = new QPropertyAnimation(this, "yRotation");
    yRotationAnim->setDuration(250);
    t1->addAnimation(yRotationAnim);

    QState *s2 = new QState(machine->rootState());
    QObject::connect(s2, SIGNAL(entered()), this, SLOT(togglePage()));
    s2->assignProperty(this, "yRotation", -90);
    s1->addTransition(s1, SIGNAL(polished()), s2);

    QAbstractTransition *t2 = s2->addTransition(s0);
    t2->addAnimation(yRotationAnim);

    machine->setInitialState(s0);
    machine->start();

Note the use of the QState::polished() signal (name still subject to change); this signal is emitted when all properties associated with a state change have reached their target values (effectively waiting for property animations, if any, to finish). When that happens, the machine transitions to a state that toggles the currentIndex (togglePage()) and inverts the rotation, and then unconditionally transitions back to the initial state (yRotation = 0), again animating the change. So, just three states to handle the flip both ways. Maybe it’s the bias talking, but isn’t it nice?

Finally, let’s create a number of independently flippable widgets and put them in a grid:

class Window : public QWidget
{
public:
    Window(QWidget *parent = 0)
        : QWidget(parent) {
        QGraphicsScene *scene = new QGraphicsScene(this);
        QGraphicsWidget *widget = new QGraphicsWidget();
        QGraphicsGridLayout *grid = new QGraphicsGridLayout(widget);
        for (int i = 0; i < 6; ++i) {
            for (int j = 0; j < 6; ++j)
                grid->addItem(new FlipWidget(), i, j);
        }
        scene->addItem(widget);
        QGraphicsView *view = new QGraphicsView(scene);
        QVBoxLayout *vbox = new QVBoxLayout(this);
        vbox->addWidget(view);
    }
};

EDIT: Added video:

Man, those widgets they be flippin’ all over! Best not show the Widget Pimp, lest he be pimpin’ out!

The source code for this example can be grabbed here. You’ll need to build it against qt/master. Something that might be fun to do is try to make the animated transition more “dramatic”, for example by animating the scale of the item as well, experimenting with the duration (“How slow can it go?”), and setting some whacky easing curves (did I declare my love for InOutElastic already? Why yes, yes I did). Oh yeah, if you really want to differentiate yourself you can create a different animation for the back-to-front flip.

The FlipWidget class could be made more general-purpose by allowing you to pass in any widgets to use as the two sides, and adding a flip() slot (so an item can be flipped programmatically) and a flipped() signal. I’ll leave that as a small exercise for the reader (hint 1: the flip() slot is a one-liner due to the presence of the flipRequest() signal; hint 2: the flipped() signal can be provided based on the QState::entered() signal, but you’ll need to insert an extra state). Once you have flip() and flipped(), you should be able to add some logic (Qt State Machine-based, of course) to the basic example and turn it into a “Find-two-of-a-kind” memory game. (As I’ve forgotten to mention on numerous occasions, the criteria by which a Qt API will ultimately be judged is its ability to sustain the healthy development of silly games. Lots and lots of silly little games.)

You know, I’m starting to get the same “It’s all coming together”-feeling as I did back with 4.4. It’s a good feeling. Anyway, happy hacking, and don’t be afraid to share your ideas and code!

Do you like this? Share it
Share on LinkedInGoogle+Share on FacebookTweet about this on Twitter

Posted in Kinetic, Qt

9 comments

Arnonym says:

But isn’t that only working for 2 tabs on the widget? What if there are more?

What about not flipping around but resizing the old on to get compacted to the left and the new one to expand from the right (depending on the items to be switched between). And maybe add some sheering to make them hide/show from smaller sizes to full-size…

sohail says:

I just wiped my machine, can we see a video πŸ˜€

Anonymous says:

+1 for a video

Scorp1us says:

Kent, I love all your work. But I am disappointed that the bindings generator won’t be a part of 4.6. Perhaps you could express that and get it in there?

Scorp1us says:

I don’t understand why you would not just make a serial animation of two 90deg flips, connect finished of the first to increment the stacked widget index modulo number of widgets int he stack?

Kent says:

@Arnonym: Yeah, the whole premise of the example is that there are two widgets. Animated multi-page stacked widget would be nice too of course, but that’s an altogether different beast to tackle.

@sohail, @Anonymous: Your wish has been granted: http://www.youtube.com/watch?v=SBgqb2l2tLk

Scorp1us says:

LOL. You should have used pink and brown circles… πŸ™‚

WidgetPimp says:

Kent, What are you doing hiding this bling from me! Seriously, though, I may have to watch out for a state machine pimp!

@Arnonym There was an example of what you talked about using old school widgets (which always have a place in this Pimp’s heart) and working in Qt 4.2 and up in Tips and Tricks talk from DevDays last year. You can grab the source code for that here:

http://dl.getdropbox.com/u/242583/tips-and-tricks-examples.zip

Look for the dancing and fading example.

ssk says:

The flipping widget (button) was interesting. I had a similar interest about the line (polyline) widgets. Two scenarios I would like mention
a) Given a parent widget. given two points (x,y). Can the drawLine be shown as animated instead of just appearing as an object.
b) Given a parent widget, which already has many child widgets given two points, the polyline is draws (animated way) from the source to destination such that it does not intersect the other widgets on the path. The polyline is allowed to take any 90degree turns.

thanks,
sendil

( may tweet back @iamssk )

Commenting closed.

Get started today with Qt Download now