Showing posts with label QPainterPath. Show all posts
Showing posts with label QPainterPath. Show all posts

Sunday, January 22, 2012

Animating element on curve in QML

I got comment on my post "Animating object along with curve in Qt using QPainterPath" that how similar thing can be achieved in QML. 

I tried to answer that comment in this post.

Originally I intend to achieve it using only QML but did not find any easy way. So I created a custom QDeclarativeItem that uses QPainterPath and implement required functionality.

Well code it quite simple, but it required little hack so that I can use some QML element like PathQuad or PathLine into my c++ code. 

So let's now move to code, I will try to explain as much as I can.

So let's begin with QML, what I want my QML code to look like. My QML has one rect item which act as circle and my custom QML element which act as curve.

I want my curve element to be drawn on screen and I also want circle to follow the curve. In code path is list which contain different kind of path element.

I was not able to intrepreat QML Path element into C++ class , so I end up creating my own path property. My curve also has pointAtPercentage function which provide point at certain progress on curve, which I use with timer to make dot follow that curve.
import QtQuick 1.0
import MyCurve 1.0

Rectangle {
    width: 500; height: 500

    Rectangle {
        id:dot
        width:10;height:10; radius: 5
        color:"steelblue"
    }

    MyCurve {
        id:curve
        path:[
                PathQuad { x: 400; y: 400; controlX: 100; controlY: 600 },
                PathLine{x: 120; y: 120},
                PathQuad { x: 120; y: 25; controlX: 260; controlY: 75 },
                PathQuad { x: 120; y: 100; controlX: -20; controlY: 75 }
            ]
    }

    Timer{
        property variant progress:0;
        interval: 100; running: true; repeat: true
        onTriggered: {
            progress += 0.005;
            if( progress > 1 ) {
                progress  = 0;
            }
            var point = curve.pointAtPercent(progress);
            dot.x = point.x
            dot.y = point.y
        }
    }
}
Now lets look at MyCurve, custom QDeclaraiveItem class, which holds the QPainterPath. MyCurve is quite simple class if we don't include code to interpret PathQuad or PathLine QML element. We could have populated QPainterPath directly in c++ class. But I choose not to do so, to make it more reusable and also that I can try my hands on QDeclarativeListProperty and can show how we can read property of QML element.

#ifndef MYCURVE_H
#define MYCURVE_H

#include <QDeclarativeItem>
#include <QPainterPath>
#include <QGraphicspathItem>
#include <QDeclarativeListProperty>

class MyCurve : public QDeclarativeItem
{
    Q_OBJECT
    Q_PROPERTY(QDeclarativeListProperty<QObject> path READ path)
public:
    explicit MyCurve(QDeclarativeItem *parent = 0);

public slots:
    QPointF pointAtPercent(double Percent);

public:
    QDeclarativeListProperty<QObject> path();

private:
    static void appendItem(QDeclarativeListProperty<QObject> *list,
         QObject *pathItem);

    void appendPathQuad( QObject* pathQual);
    void appendPathLine( QObject* pathLine);

private:    
    QGraphicsPathItem* _path;
    QPainterPath _painterPath;
};
#endif // MYCURVE_H
Only Interesting about header is appendItem static method, which I will explain after showing its implementation. So here is implementation of header.
#include "mycurve.h"
#include <QDebug>
#include <QDeclarativeItem>
#include <QPen>

MyCurve::MyCurve(QDeclarativeItem *parent) :
    QDeclarativeItem(parent),_path(0),_startX(0),_startY(0)
{
    _path = new QGraphicsPathItem(this);
    _path->setPen(QPen(Qt::blue));
}

QPointF MyCurve::pointAtPercent(double Percent) {
    return _painterPath.pointAtPercent(Percent);
}

QDeclarativeListProperty<QObject> MyCurve::path()
{
    return QDeclarativeListProperty<QObject>(this,0,&MyCurve::appendItem);
}

void MyCurve::appendItem(QDeclarativeListProperty<QObject> *list, 
    QObject *pathItem)
{
    MyCurve *myCurve = qobject_cast<MyCurve *>(list->object);
    if (myCurve) {
         if( QString(pathItem->metaObject()->className())
                == QString("QDeclarativePathQuad") ) {
             myCurve->appendPathQuad(pathItem);
         } else if (QString(pathItem->metaObject()->className()) 
               == QString("QDeclarativePathLine")){
             myCurve->appendPathLine(pathItem);
         }
       }
}

void MyCurve::appendPathQuad( QObject* pathQuad) {
    double x = pathQuad->property("x").toDouble();
    double y = pathQuad->property("y").toDouble();
    double controlX = pathQuad->property("controlX").toDouble();
    double controlY = pathQuad->property("controlY").toDouble();

    _painterPath.quadTo(controlX,controlY,x,y);
    _path->setPath(_painterPath);
}

void MyCurve::appendPathLine( QObject* pathLine) {
    double x = pathLine->property("x").toDouble();
    double y = pathLine->property("y").toDouble();
    _painterPath.lineTo(x,y);
    _path->setPath(_painterPath);
}
There are quite a few interesting things here.

Usage of QDeclarativeListProperty(I am using it for first time), QDeclarativeListProperty is used to create list of property in QML. To operate on QDeclarativeListProperty, we need to provide static method which take pointer of list object and list item. appendItem is similar static method which add path element to our QPainterPath. To know more how other operation can be implemented on QDeclarativeListProperty, please visit this or this link.

In above code I am using metaObject to identify which QML Path element is being added by appendItem ( which is kind of dirty hack ) and after identifying element I am using property method to get its value and finally using those value to call appropriate QPainterPath method.

And finally here is how it looks,

Tuesday, January 25, 2011

Intel App Up developer program MeeGo Contest


Sometime ago intel launched App Up developer program and as part of that they launched MeeGo Contest and invited article for meego or Qt development on various topics.

I also submitted my article "Animating object along with curve in Qt using QPainterPath" for this competition for code sample topic and to my surprise it was selected as one of finalist. Check other entries here.

Now that winners are announced for each category and I am not one of them but still I will getting price for being one of finalist. Check winner list here.

Monday, November 29, 2010

Animating object along with curve in Qt using QPainterPath

Recently I was playing with Qt and found QPainterPath class. While reading documentation, I thought how can I use QPainterPath class and I created following prototype application in result.

QPainterPath can contain many different kind of shapes and we can draw all those shape using drawPath API of QPainter. But I used it to animate an object on curve path instead of just drawing, QPainterPath has some intresting API using which curve animation is quite easy.

In my sample app, I want to animate dot on curve as shown in below pic.


To achieve this, I first created a curved QPainterPath like following,
QPainterPath path;
path.moveTo(100,100);
path.quadTo(100,600,400,400);
Above code will draw curve as shown in above pic.

Now I created a timer and on timeout event, I am updating progress of dot on curve painter path. Like below.
void timeout()
{
    progress += 0.01;
    if( progress > 1 ) {
        progress  = 0;
    }
    update();
}
Now that, I have progress of dot on curve path at certain point in terms of percentage.QPainterPath has API pointAtPercent(), that can be used to get point on curve path at progress value and can draw Dot object at that point like below.
void Testwidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.drawPath( path );

    painter.setBrush(Qt::SolidPattern);
    painter.drawEllipse(path.pointAtPercent(progress),20,20);
}
Output of above code will look like following.


If you don't like creating animation using QTimer and wants same output using Qt's animation framework then following code shows how that can be done.

In following code, I have created a custom widget named DotWidget and I am animating it on curve path using QPropertyAnimation.

Following code creates a painter path and a widget then applies QPropertyAnimation on it.
createPath();
DotWidget* dotWidget = new DotWidget(this);

QPropertyAnimation* animation = new QPropertyAnimation(dotWidget,
                               "geometry",this);
animation->setDuration(20000);
animation->setEasingCurve(QEasingCurve::Linear);
animation->setLoopCount(-1); //loop forever

//setting value for animation on different position using QPainterPath
for( double i = 0 ; i < 1; i = i+0.1) {
    animation->setKeyValueAt(i,
               QRect(path.pointAtPercent(i).toPoint(),QSize(30,30)));
}
animation->start(); 
Following is code for DotWidget custom widget class. Its very simple widget that draws circle.
#include 
#include 
class DotWidget : public QWidget
{
    Q_OBJECT
public:
    explicit DotWidget(QWidget *parent = 0)
        :QWidget(parent)
    {}

    void paintEvent(QPaintEvent *)
    {
        QPainter painter(this);
        painter.setBrush(Qt::SolidPattern);
        painter.drawEllipse(0,0,25,25);
    }
};
I have created custom widget just to show how it can be done with custom widget, but you can apply same animation of any widget. I have uploaded video that shows output with QPushButton at end of post.

Thats all,following is video for custom animation with timer.



Following is video for animation using QPropertyAnimation with QPushButton