Adding custom easing curves to property animations
authorAaron Kennedy <aaron.kennedy@nokia.com>
Mon, 21 Nov 2011 17:51:31 +0000 (17:51 +0000)
committerQt by Nokia <qt-info@nokia.com>
Mon, 21 Nov 2011 18:01:16 +0000 (19:01 +0100)
QDeclarativeEasingValueType gets the property customBezierCurve.
This allows to define a custom easing curve as a cubic bezier curve.

Change-Id: I33ae128ce29bba2834eedcbb90a9769a5391f997
Reviewed-by: Aaron Kennedy <aaron.kennedy@nokia.com>

examples/declarative/animation/easing/easing.qml
src/declarative/items/qquickcanvas.h
src/declarative/qml/qdeclarativevaluetype.cpp
src/declarative/qml/qdeclarativevaluetype_p.h
tools/qmleasing/TextField.qml [new file with mode: 0644]
tools/qmleasing/easing.qml [new file with mode: 0644]
tools/qmleasing/main.cpp [new file with mode: 0644]
tools/qmleasing/qmleasing.pro [new file with mode: 0644]
tools/qmleasing/resources.qrc [new file with mode: 0644]
tools/tools.pro

index 911e6ce..233bb2d 100644 (file)
@@ -45,6 +45,9 @@ Rectangle {
     id: window
     width: 600; height: 460; color: "#232323"
 
+    property var easingCurve: [ 0.2, 0.2, 0.13, 0.65, 0.2, 0.8,
+                                0.624, 0.98, 0.93, 0.95, 1, 1 ]
+
     ListModel {
         id: easingTypes
         ListElement { name: "Easing.Linear"; type: Easing.Linear; ballColor: "DarkRed" }
@@ -88,6 +91,7 @@ Rectangle {
         ListElement { name: "Easing.InBounce"; type: Easing.InBounce; ballColor: "DimGray" }
         ListElement { name: "Easing.InOutBounce"; type: Easing.InOutBounce; ballColor: "SlateGray" }
         ListElement { name: "Easing.OutInBounce"; type: Easing.OutInBounce; ballColor: "DarkSlateGray" }
+        ListElement { name: "Easing.Bezier"; type: Easing.Bezier; ballColor: "Chartreuse"; }
     }
 
     Component {
@@ -128,8 +132,8 @@ Rectangle {
                 }
 
                 transitions: Transition {
-                    NumberAnimation { properties: "x"; easing.type: type; duration: 1000 }
-                    ColorAnimation { properties: "color"; easing.type: type; duration: 1000 }
+                    NumberAnimation { properties: "x"; easing.type: type; easing.bezierCurve: getBezierCurve(name); duration: 1000 }
+                    ColorAnimation { properties: "color"; easing.type: type; easing.bezierCurve: getBezierCurve(name); duration: 1000 }
                 }
             }
         }
@@ -156,4 +160,11 @@ Rectangle {
             Repeater { model: easingTypes; delegate: delegate }
         }
     }
+
+    function getBezierCurve(name)
+    {
+        if (name === "Easing.Bezier")
+            return easingCurve;
+        return [];
+    }
 }
index a5a9757..bf8693a 100644 (file)
@@ -52,9 +52,10 @@ QT_BEGIN_NAMESPACE
 
 QT_MODULE(Declarative)
 
-class QQuickItem;
 class QSGEngine;
+class QQuickItem;
 class QSGTexture;
+class QInputMethodEvent;
 class QQuickCanvasPrivate;
 class QOpenGLFramebufferObject;
 class QDeclarativeIncubationController;
index 9a941e6..ffd0041 100644 (file)
@@ -958,6 +958,52 @@ void QDeclarativeEasingValueType::setPeriod(qreal period)
     easing.setPeriod(period);
 }
 
+void QDeclarativeEasingValueType::setBezierCurve(const QVariantList &customCurveVariant)
+{
+    if (customCurveVariant.isEmpty())
+        return;
+
+    QVariantList variantList = customCurveVariant;
+    if ((variantList.count() % 6) == 0) {
+        bool allRealsOk = true;
+        QList<qreal> reals;
+        for (int i = 0; i < variantList.count(); i++) {
+            bool ok;
+            const qreal real = variantList.at(i).toReal(&ok);
+            reals.append(real);
+            if (!ok)
+                allRealsOk = false;
+        }
+        if (allRealsOk) {
+            QEasingCurve newEasingCurve(QEasingCurve::BezierSpline);
+            for (int i = 0; i < reals.count() / 6; i++) {
+                const qreal c1x = reals.at(i * 6);
+                const qreal c1y = reals.at(i * 6 + 1);
+                const qreal c2x = reals.at(i * 6 + 2);
+                const qreal c2y = reals.at(i * 6 + 3);
+                const qreal c3x = reals.at(i * 6 + 4);
+                const qreal c3y = reals.at(i * 6 + 5);
+
+                const QPointF c1(c1x, c1y);
+                const QPointF c2(c2x, c2y);
+                const QPointF c3(c3x, c3y);
+
+                newEasingCurve.addCubicBezierSegment(c1, c2, c3);
+                easing = newEasingCurve;
+            }
+        }
+    }
+}
+
+QVariantList QDeclarativeEasingValueType::bezierCurve() const
+{
+    QVariantList rv;
+    QList<QPointF> points = easing.cubicBezierSpline();
+    for (int ii = 0; ii < points.count(); ++ii)
+        rv << QVariant(points.at(ii).x()) << QVariant(points.at(ii).y());
+    return rv;
+}
+
 QDeclarativeFontValueType::QDeclarativeFontValueType(QObject *parent)
 : QDeclarativeValueType(parent), pixelSizeSet(false), pointSizeSet(false)
 {
index 1feab41..9f00d97 100644 (file)
@@ -456,6 +456,7 @@ class Q_DECLARATIVE_PRIVATE_EXPORT QDeclarativeEasingValueType : public QDeclara
     Q_PROPERTY(qreal amplitude READ amplitude WRITE setAmplitude)
     Q_PROPERTY(qreal overshoot READ overshoot WRITE setOvershoot)
     Q_PROPERTY(qreal period READ period WRITE setPeriod)
+    Q_PROPERTY(QVariantList bezierCurve READ bezierCurve WRITE setBezierCurve)
 public:
     enum Type {
         Linear = QEasingCurve::Linear,
@@ -480,7 +481,8 @@ public:
         InBounce = QEasingCurve::InBounce, OutBounce = QEasingCurve::OutBounce,
         InOutBounce = QEasingCurve::InOutBounce, OutInBounce = QEasingCurve::OutInBounce,
         InCurve = QEasingCurve::InCurve, OutCurve = QEasingCurve::OutCurve,
-        SineCurve = QEasingCurve::SineCurve, CosineCurve = QEasingCurve::CosineCurve
+        SineCurve = QEasingCurve::SineCurve, CosineCurve = QEasingCurve::CosineCurve,
+        Bezier = QEasingCurve::BezierSpline
     };
 
     QDeclarativeEasingValueType(QObject *parent = 0);
@@ -500,6 +502,9 @@ public:
     void setAmplitude(qreal);
     void setOvershoot(qreal);
     void setPeriod(qreal);
+    void setBezierCurve(const QVariantList &);
+    QVariantList bezierCurve() const;
+
 
 private:
     QEasingCurve easing;
diff --git a/tools/qmleasing/TextField.qml b/tools/qmleasing/TextField.qml
new file mode 100644 (file)
index 0000000..d99d469
--- /dev/null
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+FocusScope {
+    width: input.x + input.width
+    height: border.height
+
+    property alias name: name.text
+    property alias text: input.text
+
+    Text {
+        id: name
+        height: parent.height
+    }
+
+    TextInput {
+        id: input
+        anchors.left: name.right
+        anchors.leftMargin: 4
+        focus: true
+        width: 50
+        horizontalAlignment: "AlignRight"
+        Rectangle {
+            id: border
+            x: -2; y: -2
+            width: parent.width + 4
+            height: parent.height + 4
+            color: "transparent"
+            border.color: input.activeFocus?"green":"lightgreen"
+
+            border.width: 3
+            radius: 5
+        }
+    }
+}
+
diff --git a/tools/qmleasing/easing.qml b/tools/qmleasing/easing.qml
new file mode 100644 (file)
index 0000000..fe5f831
--- /dev/null
@@ -0,0 +1,219 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import EasingPlot 1.0
+
+Rectangle {
+    width: 775; height: 550
+
+    function precision(n)
+    {
+        var str = n.toPrecision(3);
+        while (str.length > 1 && (str[str.length - 1] == "0" || str[str.length - 1] == "."))
+            str = str.substr(0, str.length - 1);
+        return str;
+    }
+
+    function updateEasing() {
+        var ini = Math.min(100, Math.max(0, Number(in_inf.text)));
+        var outi = Math.min(100, Math.max(0, Number(out_inf.text)));
+
+        var ins = Number(in_slope.text);
+        var outs = Number(out_slope.text);
+
+        var p1 = [ (ini / 100), (ini / 100) * ins ];
+        var p2 = [ 1 - (outi / 100), 1 - (outi / 100) * outs ];
+
+        text.text = "[ " + precision(p1[0]) + ", " + precision(p1[1]) + ", " + precision(p2[0]) + ", " + precision(p2[1]) + ", 1, 1 ]";
+    }
+
+    Rectangle {
+        id: border
+        width: 500; height: 500
+        x: 25; y: 25
+        border.color: "lightsteelblue"
+        border.width: 3
+        radius: 5
+        color: "transparent"
+
+        EasingPlot {
+            id: plot
+
+            anchors.centerIn: parent
+            width: parent.width - 10
+            height: parent.height - 10
+
+            easing.type: "Bezier"
+            easing.bezierCurve: eval(text.text)
+        }
+
+    }
+
+    Text {
+        text: "<u>After Effects curve</u>"
+        anchors.horizontalCenter: text.horizontalCenter
+        anchors.bottom: column.top
+        anchors.bottomMargin: 14
+    }
+
+    Column {
+        id: column
+
+        y: 70
+        anchors.right: parent.right
+        anchors.rightMargin: 25
+        spacing: 5
+        TextField {
+            id: in_inf
+            focus: true
+            name: "Input influence:"
+            text: "33"
+            anchors.right: parent.right
+            KeyNavigation.tab: in_slope
+            KeyNavigation.backtab: text
+            onTextChanged: updateEasing();
+        }
+        TextField {
+            id: in_slope
+            name: "Input slope:"
+            text: "0"
+            anchors.right: parent.right
+            KeyNavigation.tab: out_inf
+            KeyNavigation.backtab: in_inf
+            onTextChanged: updateEasing();
+        }
+        TextField {
+            id: out_inf
+            name: "Output influence:"
+            text: "33"
+            anchors.right: parent.right
+            KeyNavigation.tab: out_slope
+            KeyNavigation.backtab: in_slope
+            onTextChanged: updateEasing();
+        }
+        TextField {
+            id: out_slope
+            name: "Output slope:"
+            text: "0"
+            anchors.right: parent.right
+            KeyNavigation.tab: text
+            KeyNavigation.backtab: out_info
+            onTextChanged: updateEasing();
+        }
+    }
+
+    Text {
+        text: "<u>QML Bezier curve</u>"
+        anchors.horizontalCenter: text.horizontalCenter
+        anchors.bottom: text.top
+        anchors.bottomMargin: 10
+    }
+
+    TextEdit {
+        id: text
+        x: 200
+        width: 200
+        height: 200
+
+        Rectangle {
+            x: -2; y: -2
+            width: parent.width + 4
+            height: parent.height + 4
+            color: "transparent"
+            border.color: text.activeFocus?"green":"lightgreen"
+
+            border.width: 3
+            radius: 5
+        }
+
+        wrapMode: "WordWrap"
+
+        anchors.top: column.bottom
+        anchors.topMargin: 50
+        anchors.right: column.right
+        KeyNavigation.tab: in_inf
+        KeyNavigation.backtab: out_slope
+    }
+
+
+    Item {
+        anchors.left: text.left
+        anchors.top: text.bottom
+        anchors.topMargin: 35
+        width: text.width
+        height: rect.height
+
+        Rectangle {
+            color: "gray"
+            width: 50; height: 50
+            id: rect
+
+            NumberAnimation on x {
+                id: animation
+                running: false
+                easing: plot.easing
+                duration: 1000
+            }
+
+            radius: 5
+        }
+
+        MouseArea {
+            anchors.fill: parent
+            onClicked: {
+                if (rect.x < 5) {
+                    animation.to = text.width - rect.width;
+                } else {
+                    animation.to = 0;
+                }
+                animation.start();
+            }
+        }
+
+        Text {
+            anchors.centerIn: parent
+            text: "Click to Try"
+        }
+    }
+
+    Component.onCompleted: updateEasing();
+}
diff --git a/tools/qmleasing/main.cpp b/tools/qmleasing/main.cpp
new file mode 100644 (file)
index 0000000..a9f1da4
--- /dev/null
@@ -0,0 +1,116 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QPainter>
+#include <QQuickView>
+#include <QApplication>
+#include <QEasingCurve>
+#include <QQuickPaintedItem>
+
+class EasingPlot : public QQuickPaintedItem
+{
+    Q_OBJECT
+    Q_PROPERTY(QEasingCurve easing READ easing WRITE setEasing NOTIFY easingChanged);
+
+public:
+    EasingPlot();
+
+    QEasingCurve easing() const;
+    void setEasing(const QEasingCurve &);
+
+signals:
+    void easingChanged();
+
+protected:
+    virtual void paint(QPainter *painter);
+
+private:
+    QEasingCurve m_easing;
+};
+
+EasingPlot::EasingPlot()
+{
+}
+
+QEasingCurve EasingPlot::easing() const
+{
+    return m_easing;
+}
+
+void EasingPlot::setEasing(const QEasingCurve &e)
+{
+    if (m_easing == e)
+        return;
+
+    m_easing = e;
+    emit easingChanged();
+
+    update();
+}
+
+void EasingPlot::paint(QPainter *painter)
+{
+    QPointF lastPoint(0, 0);
+
+    for (int ii = 1; ii <= 100; ++ii) {
+        qreal value = m_easing.valueForProgress(qreal(ii) / 100.);
+
+        QPointF currentPoint(width() * qreal(ii) / 100., value * (height() - 1));
+        painter->drawLine(lastPoint, currentPoint);
+
+        lastPoint = currentPoint;
+    }
+}
+
+int main(int argc, char ** argv)
+{
+    QApplication app(argc, argv);
+
+    qmlRegisterType<EasingPlot>("EasingPlot", 1, 0, "EasingPlot");
+
+    QQuickView view;
+    view.setSource(QUrl("qrc:/easing.qml"));
+    view.show();
+
+    return app.exec();
+}
+
+#include "main.moc"
diff --git a/tools/qmleasing/qmleasing.pro b/tools/qmleasing/qmleasing.pro
new file mode 100644 (file)
index 0000000..4a64fe9
--- /dev/null
@@ -0,0 +1,12 @@
+TEMPLATE = app
+TARGET =
+DEPENDPATH += .
+INCLUDEPATH += .
+
+QT += declarative widgets
+CONFIG -= app_bundle
+
+# Input
+SOURCES += main.cpp
+
+RESOURCES = $$PWD/resources.qrc
diff --git a/tools/qmleasing/resources.qrc b/tools/qmleasing/resources.qrc
new file mode 100644 (file)
index 0000000..c7a67b8
--- /dev/null
@@ -0,0 +1,6 @@
+<RCC>
+    <qresource prefix="/">
+        <file>easing.qml</file>
+        <file>TextField.qml</file>
+    </qresource>
+</RCC>
index ae2ca0c..46d381d 100644 (file)
@@ -1,5 +1,5 @@
 TEMPLATE = subdirs
-SUBDIRS +=  qmlviewer qmlscene qmlplugindump qmlmin
+SUBDIRS +=  qmlviewer qmlscene qmlplugindump qmlmin qmleasing
 
 contains(QT_CONFIG, qmltest): SUBDIRS += qmltestrunner