Sunday, 16 May 2010

Using platform rendering for widgets in a graphics scene

We've all written our own small toolkits, and the like in the past, and today I revisited my glory days: I made a button.

Actually, it was a bit more involved.

Some of you might have heard of a nifty thing called a QGraphicsScene (and friends), a fairly useful tool to allow for complex rendering/manipulating of 2D objects, animations, all the rest of the bling. It's been used for a lot of things over time, and recently I've been getting to know it in my copious "free time" over this weekend.

I decided to see how difficult it would be to create my own button inside a QGraphicsScene (yes, I know QGraphicsProxyWidget exists, but it is fairly slow, so using it isn't the best for many situations), so while I was at it, I decided to make it blend in with the rest of the widgets on my desktop too.

This is more a proof of concept than anything, but in case anyone else ever wants to write their own widgets and render them in a QGraphicsScene, here's a bit of a pointer in the right direction. ;)

#include <QApplication>
#include <QGraphicsWidget>
#include <QPainter>
#include <QStyleOptionGraphicsItem>

class AButton : public QGraphicsWidget
{
public:
    AButton()
        : QGraphicsWidget(),
        m_isPressed(false)
    {
    }
    QRectF boundingRect() const
    {
        // TODO: fetch QRectF from text() + icon() and cache
        return QRectF(0, 0, 40, 40);
    }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0)
    {
        QStyleOptionButton opt;
        // TODO: QStyleOption::initFrom overload for QStyleOptionGraphicsItem would be nice
        //opt.initFrom(this);

        opt.state = (m_isPressed ? QStyle::State_Sunken : QStyle::State_Raised) | QStyle::State_Enabled;
        opt.text = text();
        opt.icon = icon();
        opt.rect = option->rect;
        opt.palette = option->palette;

        QApplication::style()->drawControl(QStyle::CE_PushButton, &opt, painter);
    }

    QString text() const
    {
        return "hi";
    }

    QPixmap icon() const
    {
        return QPixmap();
    }

    void mousePressEvent(QGraphicsSceneMouseEvent *event)
    {
        m_isPressed = true;
        update();
    }

    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
    {
        m_isPressed = false;
        update();
    }

private:
    bool m_isPressed;
};

#include <QApplication>
#include <QGraphicsView>

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

    // set up scene
    QGraphicsScene scene;
    AButton button;
    scene.addItem(&button);

    // add a view
    QGraphicsView view(&scene);
    view.setRenderHint(QPainter::Antialiasing);
    view.resize(200, 100);
    view.setBackgroundBrush(QApplication::palette().background());
    view.show();

    return a.exec();
}

4 comments:

  1. I forgot to add my usual blog licence header to the code, so here it is.

    No licence is specified, use it however you want and I'll be happy. If you must have a licence for commercial reasons, consider it BSD without an advertising clause.

    ReplyDelete
  2. IMHO "fake widgets", i.e. "widgets" painted using the QStyle API directly and with their own implementation of behavior and feel, are a bad idea, they tend to have subtly different behavior from true native QWidgets and they're also more likely to cause issues with some styles.

    ReplyDelete
  3. I'm honestly not convinced by that argument.

    I'd like to have completely consistent behaviour of widgets, sure, but just as important, or even more so, is that a button looks like a button.

    What I mean is: different behaviour is a problem - but just as much so is different appearance. We should strive to prevent either.

    I don't like each and every application in the world reinventing the UI of widgets as they see fit just because it's "cool" to do so - where possible, they should fit in with how *I* want UI to look on my device.

    This becomes all the more important when you take an application from one platform (say, desktop) and move it to a mobile device, where the QStyle (and entire environment and palette) is different. If you fit in with the platform defaults, you'll blend in without any extra work.

    Perhaps what should be done is to provide a library with a series of these classes, providing reusable re-implementations of widgets on top of QGraphicsView.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete