From fee3fe7d76f1a4556130a097dceb818cc6f33c47 Mon Sep 17 00:00:00 2001 From: Michael Brasser Date: Thu, 1 Sep 2011 08:47:56 +1000 Subject: [PATCH] Add new path functionality. Introduces new elements PathCurve, PathArc, PathSvg, PathAnimation, and PathInterpolator. Change-Id: I0f5b6284511f332e826ed373018418d2e2a8c07e Reviewed-on: http://codereview.qt-project.org/4220 Reviewed-by: Martin Jones --- src/declarative/items/context2d/qsgcontext2d.cpp | 587 +----------------- src/declarative/items/qsganimation.cpp | 228 +++++++ src/declarative/items/qsganimation_p.h | 62 ++ src/declarative/items/qsganimation_p_p.h | 41 ++ src/declarative/items/qsgcanvas.cpp | 2 +- src/declarative/items/qsgitem.cpp | 12 + src/declarative/items/qsgitem.h | 1 + src/declarative/items/qsgitem_p.h | 2 + src/declarative/items/qsgitemsmodule.cpp | 6 + src/declarative/util/qdeclarativepath.cpp | 661 ++++++++++++++++++-- src/declarative/util/qdeclarativepath_p.h | 181 +++++- src/declarative/util/qdeclarativepath_p_p.h | 13 +- .../util/qdeclarativepathinterpolator.cpp | 122 ++++ .../util/qdeclarativepathinterpolator_p.h | 100 +++ src/declarative/util/qdeclarativesvgparser.cpp | 633 +++++++++++++++++++ src/declarative/util/qdeclarativesvgparser_p.h | 60 ++ src/declarative/util/util.pri | 8 +- .../qdeclarativeanimations/data/pathAnimation.qml | 27 + .../data/pathInterpolator.qml | 13 + .../qdeclarativeanimations/data/pathTransition.qml | 41 ++ .../tst_qdeclarativeanimations.cpp | 81 +++ .../auto/declarative/qdeclarativepath/data/arc.qml | 11 + .../declarative/qdeclarativepath/data/curve.qml | 9 + .../auto/declarative/qdeclarativepath/data/svg.qml | 5 + .../qdeclarativepath/qdeclarativepath.pro | 17 + .../qdeclarativepath/tst_qdeclarativepath.cpp | 169 +++++ 26 files changed, 2437 insertions(+), 655 deletions(-) create mode 100644 src/declarative/util/qdeclarativepathinterpolator.cpp create mode 100644 src/declarative/util/qdeclarativepathinterpolator_p.h create mode 100644 src/declarative/util/qdeclarativesvgparser.cpp create mode 100644 src/declarative/util/qdeclarativesvgparser_p.h create mode 100644 tests/auto/declarative/qdeclarativeanimations/data/pathAnimation.qml create mode 100644 tests/auto/declarative/qdeclarativeanimations/data/pathInterpolator.qml create mode 100644 tests/auto/declarative/qdeclarativeanimations/data/pathTransition.qml create mode 100644 tests/auto/declarative/qdeclarativepath/data/arc.qml create mode 100644 tests/auto/declarative/qdeclarativepath/data/curve.qml create mode 100644 tests/auto/declarative/qdeclarativepath/data/svg.qml create mode 100644 tests/auto/declarative/qdeclarativepath/qdeclarativepath.pro create mode 100644 tests/auto/declarative/qdeclarativepath/tst_qdeclarativepath.cpp diff --git a/src/declarative/items/context2d/qsgcontext2d.cpp b/src/declarative/items/context2d/qsgcontext2d.cpp index 4cfe1ba..a2a57cb 100644 --- a/src/declarative/items/context2d/qsgcontext2d.cpp +++ b/src/declarative/items/context2d/qsgcontext2d.cpp @@ -46,6 +46,7 @@ #include #include #include "private/qsgcontext_p.h" +#include "private/qdeclarativesvgparser_p.h" #include #include @@ -82,7 +83,6 @@ void copy_vector(QVector* dst, const QVector& src) // But it really should be considered private API void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0); void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed = 0); -static bool parsePathDataFast(const QString &dataStr, QPainterPath &path); #define DEGREES(t) ((t) * 180.0 / Q_PI) #define qClamp(val, min, max) qMin(qMax(val, min), max) @@ -2312,7 +2312,7 @@ void QSGContext2D::setPath(QSGCanvasPath* path) QSGCanvasPath* QSGContext2D::createPath(const QString& pathString) { QPainterPath path; - if (parsePathDataFast(pathString, path)) { + if (QDeclarativeSvgParser::parsePathDataFast(pathString, path)) { return new QSGCanvasPath(path, this); } return 0; @@ -2573,592 +2573,11 @@ bool QSGContext2D::isPointInPath(qreal x, qreal y) const return d->path.contains(QPointF(x, y)); } -//copied from QtSvg (qsvghandler.cpp). -Q_CORE_EXPORT double qstrtod(const char *s00, char const **se, bool *ok); -// '0' is 0x30 and '9' is 0x39 -static inline bool isDigit(ushort ch) -{ - static quint16 magic = 0x3ff; - return ((ch >> 4) == 3) && (magic >> (ch & 15)); -} - -static qreal toDouble(const QChar *&str) -{ - const int maxLen = 255;//technically doubles can go til 308+ but whatever - char temp[maxLen+1]; - int pos = 0; - - if (*str == QLatin1Char('-')) { - temp[pos++] = '-'; - ++str; - } else if (*str == QLatin1Char('+')) { - ++str; - } - while (isDigit(str->unicode()) && pos < maxLen) { - temp[pos++] = str->toLatin1(); - ++str; - } - if (*str == QLatin1Char('.') && pos < maxLen) { - temp[pos++] = '.'; - ++str; - } - while (isDigit(str->unicode()) && pos < maxLen) { - temp[pos++] = str->toLatin1(); - ++str; - } - bool exponent = false; - if ((*str == QLatin1Char('e') || *str == QLatin1Char('E')) && pos < maxLen) { - exponent = true; - temp[pos++] = 'e'; - ++str; - if ((*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) { - temp[pos++] = str->toLatin1(); - ++str; - } - while (isDigit(str->unicode()) && pos < maxLen) { - temp[pos++] = str->toLatin1(); - ++str; - } - } - - temp[pos] = '\0'; - - qreal val; - if (!exponent && pos < 10) { - int ival = 0; - const char *t = temp; - bool neg = false; - if(*t == '-') { - neg = true; - ++t; - } - while(*t && *t != '.') { - ival *= 10; - ival += (*t) - '0'; - ++t; - } - if(*t == '.') { - ++t; - int div = 1; - while(*t) { - ival *= 10; - ival += (*t) - '0'; - div *= 10; - ++t; - } - val = ((qreal)ival)/((qreal)div); - } else { - val = ival; - } - if (neg) - val = -val; - } else { -#if defined(Q_WS_QWS) && !defined(Q_OS_VXWORKS) - if(sizeof(qreal) == sizeof(float)) - val = strtof(temp, 0); - else -#endif - { - bool ok = false; - val = qstrtod(temp, 0, &ok); - } - } - return val; - -} -static qreal toDouble(const QString &str, bool *ok = NULL) -{ - const QChar *c = str.constData(); - qreal res = toDouble(c); - if (ok) { - *ok = ((*c) == QLatin1Char('\0')); - } - return res; -} - -static qreal toDouble(const QStringRef &str, bool *ok = NULL) -{ - const QChar *c = str.constData(); - qreal res = toDouble(c); - if (ok) { - *ok = (c == (str.constData() + str.length())); - } - return res; -} -static inline void parseNumbersArray(const QChar *&str, QVarLengthArray &points) -{ - while (str->isSpace()) - ++str; - while (isDigit(str->unicode()) || - *str == QLatin1Char('-') || *str == QLatin1Char('+') || - *str == QLatin1Char('.')) { - - points.append(toDouble(str)); - - while (str->isSpace()) - ++str; - if (*str == QLatin1Char(',')) - ++str; - - //eat the rest of space - while (str->isSpace()) - ++str; - } -} - -static void pathArcSegment(QPainterPath &path, - qreal xc, qreal yc, - qreal th0, qreal th1, - qreal rx, qreal ry, qreal xAxisRotation) -{ - qreal sinTh, cosTh; - qreal a00, a01, a10, a11; - qreal x1, y1, x2, y2, x3, y3; - qreal t; - qreal thHalf; - - sinTh = qSin(xAxisRotation * (Q_PI / 180.0)); - cosTh = qCos(xAxisRotation * (Q_PI / 180.0)); - - a00 = cosTh * rx; - a01 = -sinTh * ry; - a10 = sinTh * rx; - a11 = cosTh * ry; - - thHalf = 0.5 * (th1 - th0); - t = (8.0 / 3.0) * qSin(thHalf * 0.5) * qSin(thHalf * 0.5) / qSin(thHalf); - x1 = xc + qCos(th0) - t * qSin(th0); - y1 = yc + qSin(th0) + t * qCos(th0); - x3 = xc + qCos(th1); - y3 = yc + qSin(th1); - x2 = x3 + t * qSin(th1); - y2 = y3 - t * qCos(th1); - - path.cubicTo(a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, - a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, - a00 * x3 + a01 * y3, a10 * x3 + a11 * y3); -} - -static void pathArc(QPainterPath &path, - qreal rx, - qreal ry, - qreal x_axis_rotation, - int large_arc_flag, - int sweep_flag, - qreal x, - qreal y, - qreal curx, qreal cury) -{ - qreal sin_th, cos_th; - qreal a00, a01, a10, a11; - qreal x0, y0, x1, y1, xc, yc; - qreal d, sfactor, sfactor_sq; - qreal th0, th1, th_arc; - int i, n_segs; - qreal dx, dy, dx1, dy1, Pr1, Pr2, Px, Py, check; - - rx = qAbs(rx); - ry = qAbs(ry); - - sin_th = qSin(x_axis_rotation * (Q_PI / 180.0)); - cos_th = qCos(x_axis_rotation * (Q_PI / 180.0)); - - dx = (curx - x) / 2.0; - dy = (cury - y) / 2.0; - dx1 = cos_th * dx + sin_th * dy; - dy1 = -sin_th * dx + cos_th * dy; - Pr1 = rx * rx; - Pr2 = ry * ry; - Px = dx1 * dx1; - Py = dy1 * dy1; - /* Spec : check if radii are large enough */ - check = Px / Pr1 + Py / Pr2; - if (check > 1) { - rx = rx * qSqrt(check); - ry = ry * qSqrt(check); - } - - a00 = cos_th / rx; - a01 = sin_th / rx; - a10 = -sin_th / ry; - a11 = cos_th / ry; - x0 = a00 * curx + a01 * cury; - y0 = a10 * curx + a11 * cury; - x1 = a00 * x + a01 * y; - y1 = a10 * x + a11 * y; - /* (x0, y0) is current point in transformed coordinate space. - (x1, y1) is new point in transformed coordinate space. - - The arc fits a unit-radius circle in this space. - */ - d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0); - sfactor_sq = 1.0 / d - 0.25; - if (sfactor_sq < 0) sfactor_sq = 0; - sfactor = qSqrt(sfactor_sq); - if (sweep_flag == large_arc_flag) sfactor = -sfactor; - xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0); - yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0); - /* (xc, yc) is center of the circle. */ - - th0 = qAtan2(y0 - yc, x0 - xc); - th1 = qAtan2(y1 - yc, x1 - xc); - - th_arc = th1 - th0; - if (th_arc < 0 && sweep_flag) - th_arc += 2 * Q_PI; - else if (th_arc > 0 && !sweep_flag) - th_arc -= 2 * Q_PI; - - n_segs = qCeil(qAbs(th_arc / (Q_PI * 0.5 + 0.001))); - - for (i = 0; i < n_segs; i++) { - pathArcSegment(path, xc, yc, - th0 + i * th_arc / n_segs, - th0 + (i + 1) * th_arc / n_segs, - rx, ry, x_axis_rotation); - } -} - - -static bool parsePathDataFast(const QString &dataStr, QPainterPath &path) -{ - qreal x0 = 0, y0 = 0; // starting point - qreal x = 0, y = 0; // current point - char lastMode = 0; - QPointF ctrlPt; - const QChar *str = dataStr.constData(); - const QChar *end = str + dataStr.size(); - - while (str != end) { - while (str->isSpace()) - ++str; - QChar pathElem = *str; - ++str; - QChar endc = *end; - *const_cast(end) = 0; // parseNumbersArray requires 0-termination that QStringRef cannot guarantee - QVarLengthArray arg; - parseNumbersArray(str, arg); - *const_cast(end) = endc; - if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z')) - arg.append(0);//dummy - const qreal *num = arg.constData(); - int count = arg.count(); - while (count > 0) { - qreal offsetX = x; // correction offsets - qreal offsetY = y; // for relative commands - switch (pathElem.unicode()) { - case 'm': { - if (count < 2) { - num++; - count--; - break; - } - x = x0 = num[0] + offsetX; - y = y0 = num[1] + offsetY; - num += 2; - count -= 2; - path.moveTo(x0, y0); - - // As per 1.2 spec 8.3.2 The "moveto" commands - // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands, - // the subsequent pairs shall be treated as implicit 'lineto' commands. - pathElem = QLatin1Char('l'); - } - break; - case 'M': { - if (count < 2) { - num++; - count--; - break; - } - x = x0 = num[0]; - y = y0 = num[1]; - num += 2; - count -= 2; - path.moveTo(x0, y0); - - // As per 1.2 spec 8.3.2 The "moveto" commands - // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands, - // the subsequent pairs shall be treated as implicit 'lineto' commands. - pathElem = QLatin1Char('L'); - } - break; - case 'z': - case 'Z': { - x = x0; - y = y0; - count--; // skip dummy - num++; - path.closeSubpath(); - } - break; - case 'l': { - if (count < 2) { - num++; - count--; - break; - } - x = num[0] + offsetX; - y = num[1] + offsetY; - num += 2; - count -= 2; - path.lineTo(x, y); - - } - break; - case 'L': { - if (count < 2) { - num++; - count--; - break; - } - x = num[0]; - y = num[1]; - num += 2; - count -= 2; - path.lineTo(x, y); - } - break; - case 'h': { - x = num[0] + offsetX; - num++; - count--; - path.lineTo(x, y); - } - break; - case 'H': { - x = num[0]; - num++; - count--; - path.lineTo(x, y); - } - break; - case 'v': { - y = num[0] + offsetY; - num++; - count--; - path.lineTo(x, y); - } - break; - case 'V': { - y = num[0]; - num++; - count--; - path.lineTo(x, y); - } - break; - case 'c': { - if (count < 6) { - num += count; - count = 0; - break; - } - QPointF c1(num[0] + offsetX, num[1] + offsetY); - QPointF c2(num[2] + offsetX, num[3] + offsetY); - QPointF e(num[4] + offsetX, num[5] + offsetY); - num += 6; - count -= 6; - path.cubicTo(c1, c2, e); - ctrlPt = c2; - x = e.x(); - y = e.y(); - break; - } - case 'C': { - if (count < 6) { - num += count; - count = 0; - break; - } - QPointF c1(num[0], num[1]); - QPointF c2(num[2], num[3]); - QPointF e(num[4], num[5]); - num += 6; - count -= 6; - path.cubicTo(c1, c2, e); - ctrlPt = c2; - x = e.x(); - y = e.y(); - break; - } - case 's': { - if (count < 4) { - num += count; - count = 0; - break; - } - QPointF c1; - if (lastMode == 'c' || lastMode == 'C' || - lastMode == 's' || lastMode == 'S') - c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); - else - c1 = QPointF(x, y); - QPointF c2(num[0] + offsetX, num[1] + offsetY); - QPointF e(num[2] + offsetX, num[3] + offsetY); - num += 4; - count -= 4; - path.cubicTo(c1, c2, e); - ctrlPt = c2; - x = e.x(); - y = e.y(); - break; - } - case 'S': { - if (count < 4) { - num += count; - count = 0; - break; - } - QPointF c1; - if (lastMode == 'c' || lastMode == 'C' || - lastMode == 's' || lastMode == 'S') - c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); - else - c1 = QPointF(x, y); - QPointF c2(num[0], num[1]); - QPointF e(num[2], num[3]); - num += 4; - count -= 4; - path.cubicTo(c1, c2, e); - ctrlPt = c2; - x = e.x(); - y = e.y(); - break; - } - case 'q': { - if (count < 4) { - num += count; - count = 0; - break; - } - QPointF c(num[0] + offsetX, num[1] + offsetY); - QPointF e(num[2] + offsetX, num[3] + offsetY); - num += 4; - count -= 4; - path.quadTo(c, e); - ctrlPt = c; - x = e.x(); - y = e.y(); - break; - } - case 'Q': { - if (count < 4) { - num += count; - count = 0; - break; - } - QPointF c(num[0], num[1]); - QPointF e(num[2], num[3]); - num += 4; - count -= 4; - path.quadTo(c, e); - ctrlPt = c; - x = e.x(); - y = e.y(); - break; - } - case 't': { - if (count < 2) { - num += count; - count = 0; - break; - } - QPointF e(num[0] + offsetX, num[1] + offsetY); - num += 2; - count -= 2; - QPointF c; - if (lastMode == 'q' || lastMode == 'Q' || - lastMode == 't' || lastMode == 'T') - c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); - else - c = QPointF(x, y); - path.quadTo(c, e); - ctrlPt = c; - x = e.x(); - y = e.y(); - break; - } - case 'T': { - if (count < 2) { - num += count; - count = 0; - break; - } - QPointF e(num[0], num[1]); - num += 2; - count -= 2; - QPointF c; - if (lastMode == 'q' || lastMode == 'Q' || - lastMode == 't' || lastMode == 'T') - c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); - else - c = QPointF(x, y); - path.quadTo(c, e); - ctrlPt = c; - x = e.x(); - y = e.y(); - break; - } - case 'a': { - if (count < 7) { - num += count; - count = 0; - break; - } - qreal rx = (*num++); - qreal ry = (*num++); - qreal xAxisRotation = (*num++); - qreal largeArcFlag = (*num++); - qreal sweepFlag = (*num++); - qreal ex = (*num++) + offsetX; - qreal ey = (*num++) + offsetY; - count -= 7; - qreal curx = x; - qreal cury = y; - pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag), - int(sweepFlag), ex, ey, curx, cury); - - x = ex; - y = ey; - } - break; - case 'A': { - if (count < 7) { - num += count; - count = 0; - break; - } - qreal rx = (*num++); - qreal ry = (*num++); - qreal xAxisRotation = (*num++); - qreal largeArcFlag = (*num++); - qreal sweepFlag = (*num++); - qreal ex = (*num++); - qreal ey = (*num++); - count -= 7; - qreal curx = x; - qreal cury = y; - pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag), - int(sweepFlag), ex, ey, curx, cury); - - x = ex; - y = ey; - } - break; - default: - return false; - } - lastMode = pathElem.toLatin1(); - } - } - return true; -} - void QSGContext2D::setPathString(const QString& path) { Q_D(QSGContext2D); d->path = QPainterPath(); - parsePathDataFast(path, d->path); + QDeclarativeSvgParser::parsePathDataFast(path, d->path); } QList QSGContext2D::getImageData(qreal sx, qreal sy, qreal sw, qreal sh) diff --git a/src/declarative/items/qsganimation.cpp b/src/declarative/items/qsganimation.cpp index 446dc5a..6fadbbf 100644 --- a/src/declarative/items/qsganimation.cpp +++ b/src/declarative/items/qsganimation.cpp @@ -43,6 +43,9 @@ #include "qsganimation_p_p.h" #include "qsgstateoperations_p.h" +#include +#include + #include #include #include @@ -438,4 +441,229 @@ void QSGAnchorAnimation::transition(QDeclarativeStateActions &actions, } } +QSGPathAnimation::QSGPathAnimation(QObject *parent) +: QDeclarativeAbstractAnimation(*(new QSGPathAnimationPrivate), parent) +{ + Q_D(QSGPathAnimation); + d->pa = new QDeclarativeBulkValueAnimator; + QDeclarative_setParent_noEvent(d->pa, this); +} + +QSGPathAnimation::~QSGPathAnimation() +{ +} + +int QSGPathAnimation::duration() const +{ + Q_D(const QSGPathAnimation); + return d->pa->duration(); +} + +void QSGPathAnimation::setDuration(int duration) +{ + if (duration < 0) { + qmlInfo(this) << tr("Cannot set a duration of < 0"); + return; + } + + Q_D(QSGPathAnimation); + if (d->pa->duration() == duration) + return; + d->pa->setDuration(duration); + emit durationChanged(duration); +} + +QEasingCurve QSGPathAnimation::easing() const +{ + Q_D(const QSGPathAnimation); + return d->pa->easingCurve(); +} + +void QSGPathAnimation::setEasing(const QEasingCurve &e) +{ + Q_D(QSGPathAnimation); + if (d->pa->easingCurve() == e) + return; + + d->pa->setEasingCurve(e); + emit easingChanged(e); +} + +QDeclarativePath *QSGPathAnimation::path() const +{ + Q_D(const QSGPathAnimation); + return d->path; +} + +void QSGPathAnimation::setPath(QDeclarativePath *path) +{ + Q_D(QSGPathAnimation); + if (d->path == path) + return; + + d->path = path; + emit pathChanged(); +} + +QSGItem *QSGPathAnimation::target() const +{ + Q_D(const QSGPathAnimation); + return d->target; +} + +void QSGPathAnimation::setTarget(QSGItem *target) +{ + Q_D(QSGPathAnimation); + if (d->target == target) + return; + + d->target = target; + emit targetChanged(); +} + +QSGPathAnimation::Orientation QSGPathAnimation::orientation() const +{ + Q_D(const QSGPathAnimation); + return d->orientation; +} + +void QSGPathAnimation::setOrientation(Orientation orientation) +{ + Q_D(QSGPathAnimation); + if (d->orientation == orientation) + return; + + d->orientation = orientation; + emit orientationChanged(d->orientation); +} + +QPointF QSGPathAnimation::anchorPoint() const +{ + Q_D(const QSGPathAnimation); + return d->anchorPoint; +} + +void QSGPathAnimation::setAnchorPoint(const QPointF &point) +{ + Q_D(QSGPathAnimation); + if (d->anchorPoint == point) + return; + + d->anchorPoint = point; + emit anchorPointChanged(point); +} + +QAbstractAnimation *QSGPathAnimation::qtAnimation() +{ + Q_D(QSGPathAnimation); + return d->pa; +} + +void QSGPathAnimation::transition(QDeclarativeStateActions &actions, + QDeclarativeProperties &modified, + TransitionDirection direction) +{ + Q_D(QSGPathAnimation); + QSGPathAnimationUpdater *data = new QSGPathAnimationUpdater; + + data->orientation = d->orientation; + data->anchorPoint = d->anchorPoint; + data->reverse = direction == Backward ? true : false; + data->fromSourced = false; + data->fromDefined = d->path ? !d->path->hasStartX() || !d->path->hasStartY() ? false : true : false; //### handle x/y separately, as well as endpoint specification? + int origModifiedSize = modified.count(); + + for (int i = 0; i < actions.count(); ++i) { + QDeclarativeAction &action = actions[i]; + if (action.event) + continue; + if (action.specifiedObject == d->target && action.property.name() == QLatin1String("x")) { + data->toX = action.toValue.toReal(); + modified << action.property; + action.fromValue = action.toValue; + } + if (action.specifiedObject == d->target && action.property.name() == QLatin1String("y")) { + data->toY = action.toValue.toReal(); + modified << action.property; + action.fromValue = action.toValue; + } + } + + if (d->target && d->path && + (modified.count() > origModifiedSize || data->fromDefined)) { + data->target = d->target; + data->path = d->path; + if (!d->rangeIsSet) { + d->pa->setStartValue(qreal(0)); + d->pa->setEndValue(qreal(1)); + d->rangeIsSet = true; + } + d->pa->setFromSourcedValue(&data->fromSourced); + d->pa->setAnimValue(data, QAbstractAnimation::DeleteWhenStopped); + } else { + d->pa->setFromSourcedValue(0); + d->pa->setAnimValue(0, QAbstractAnimation::DeleteWhenStopped); + delete data; + } +} + +void QSGPathAnimationUpdater::setValue(qreal v) +{ + if (!fromSourced && !fromDefined) { //### check if !toDefined? + qreal startX = reverse ? toX + anchorPoint.x() : target->x() + anchorPoint.x(); + qreal startY = reverse ? toY + anchorPoint.y() : target->y() + anchorPoint.y(); + qreal endX = reverse ? target->x() + anchorPoint.x() : toX + anchorPoint.x(); + qreal endY = reverse ? target->y() + anchorPoint.y() : toY + anchorPoint.y(); + + prevBez.isValid = false; + painterPath = path->createPath(QPointF(startX, startY), QPointF(endX, endY), QStringList(), pathLength, attributePoints); + fromSourced = true; + } + + qreal angle; + bool fixed = orientation == QSGPathAnimation::Fixed; + QPointF currentPos = !painterPath.isEmpty() ? path->sequentialPointAt(painterPath, pathLength, attributePoints, prevBez, v, fixed ? 0 : &angle) : path->sequentialPointAt(v, fixed ? 0 : &angle); + + //adjust position according to anchor point + if (!anchorPoint.isNull()) { + currentPos -= anchorPoint; + if ((reverse && v == 1.0) || (!reverse && v == 0.0)) { + if (!anchorPoint.isNull() && !fixed) + target->setTransformOriginPoint(anchorPoint); + } + } + + //### too expensive to reconstruct properties each time? + QDeclarativePropertyPrivate::write(QDeclarativeProperty(target, "x"), currentPos.x(), QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::DontRemoveBinding); + QDeclarativePropertyPrivate::write(QDeclarativeProperty(target, "y"), currentPos.y(), QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::DontRemoveBinding); + + //adjust angle according to orientation + if (!fixed) { + switch (orientation) { + case QSGPathAnimation::RightFirst: + angle = -angle; + break; + case QSGPathAnimation::LeftFirst: + angle = -angle + 180; + break; + case QSGPathAnimation::BottomFirst: + angle = -angle + 270; + break; + case QSGPathAnimation::TopFirst: + angle = -angle + 450; + break; + default: + angle = 0; + break; + } + QDeclarativePropertyPrivate::write(QDeclarativeProperty(target, "rotation"), angle, QDeclarativePropertyPrivate::BypassInterceptor | QDeclarativePropertyPrivate::DontRemoveBinding); + } + + //### resetting transform causes visual jump if ending on an angle +// if ((reverse && v == 0.0) || (!reverse && v == 1.0)) { +// if (!anchorPoint.isNull() && !fixed) +// target->setTransformOriginPoint(QPointF()); +// } +} + QT_END_NAMESPACE diff --git a/src/declarative/items/qsganimation_p.h b/src/declarative/items/qsganimation_p.h index ee3fe8d..6406b86 100644 --- a/src/declarative/items/qsganimation_p.h +++ b/src/declarative/items/qsganimation_p.h @@ -122,10 +122,72 @@ protected: virtual QAbstractAnimation *qtAnimation(); }; +class QSGItem; +class QDeclarativePath; +class QSGPathAnimationPrivate; +class Q_AUTOTEST_EXPORT QSGPathAnimation : public QDeclarativeAbstractAnimation +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QSGPathAnimation) + + Q_PROPERTY(int duration READ duration WRITE setDuration NOTIFY durationChanged) + Q_PROPERTY(QEasingCurve easing READ easing WRITE setEasing NOTIFY easingChanged) + Q_PROPERTY(QDeclarativePath *path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QSGItem *target READ target WRITE setTarget NOTIFY targetChanged) + Q_PROPERTY(Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged) + Q_PROPERTY(QPointF anchorPoint READ anchorPoint WRITE setAnchorPoint NOTIFY anchorPointChanged) + +public: + QSGPathAnimation(QObject *parent=0); + virtual ~QSGPathAnimation(); + + enum Orientation { + Fixed, + RightFirst, + LeftFirst, + BottomFirst, + TopFirst + }; + Q_ENUMS(Orientation) + + int duration() const; + void setDuration(int); + + QEasingCurve easing() const; + void setEasing(const QEasingCurve &); + + QDeclarativePath *path() const; + void setPath(QDeclarativePath *); + + QSGItem *target() const; + void setTarget(QSGItem *); + + Orientation orientation() const; + void setOrientation(Orientation orientation); + + QPointF anchorPoint() const; + void setAnchorPoint(const QPointF &point); + +protected: + virtual void transition(QDeclarativeStateActions &actions, + QDeclarativeProperties &modified, + TransitionDirection direction); + virtual QAbstractAnimation *qtAnimation(); + +Q_SIGNALS: + void durationChanged(int); + void easingChanged(const QEasingCurve &); + void pathChanged(); + void targetChanged(); + void orientationChanged(Orientation); + void anchorPointChanged(const QPointF &); +}; + QT_END_NAMESPACE QML_DECLARE_TYPE(QSGParentAnimation) QML_DECLARE_TYPE(QSGAnchorAnimation) +QML_DECLARE_TYPE(QSGPathAnimation) QT_END_HEADER diff --git a/src/declarative/items/qsganimation_p_p.h b/src/declarative/items/qsganimation_p_p.h index 6f26f35..e26dbbd 100644 --- a/src/declarative/items/qsganimation_p_p.h +++ b/src/declarative/items/qsganimation_p_p.h @@ -56,6 +56,7 @@ #include "qsganimation_p.h" +#include #include QT_BEGIN_NAMESPACE @@ -92,6 +93,46 @@ public: QList targets; }; +class QSGPathAnimationUpdater : public QDeclarativeBulkValueUpdater +{ +public: + QDeclarativePath *path; + + QPainterPath painterPath; + QDeclarativeCachedBezier prevBez; + qreal pathLength; + QList attributePoints; + + QSGItem *target; + bool reverse; + bool fromSourced; + bool fromDefined; + qreal toX; + qreal toY; + QSGPathAnimation::Orientation orientation; + QPointF anchorPoint; + QSGPathAnimationUpdater() : path(0), target(0), reverse(false), + fromSourced(false), fromDefined(false), toX(0), toY(0), orientation(QSGPathAnimation::Fixed) {} + ~QSGPathAnimationUpdater() {} + void setValue(qreal v); +}; + +class QSGPathAnimationPrivate : public QDeclarativeAbstractAnimationPrivate +{ + Q_DECLARE_PUBLIC(QSGPathAnimation) +public: + QSGPathAnimationPrivate() : path(0), target(0), + rangeIsSet(false), orientation(QSGPathAnimation::Fixed), pa(0) {} + + QDeclarativePath *path; + QSGItem *target; + bool rangeIsSet; + QSGPathAnimation::Orientation orientation; + QPointF anchorPoint; + QDeclarativeBulkValueAnimator *pa; +}; + + QT_END_NAMESPACE #endif // QSGANIMATION_P_H diff --git a/src/declarative/items/qsgcanvas.cpp b/src/declarative/items/qsgcanvas.cpp index 845617c..3b1974c 100644 --- a/src/declarative/items/qsgcanvas.cpp +++ b/src/declarative/items/qsgcanvas.cpp @@ -1701,7 +1701,7 @@ void QSGCanvasPrivate::updateDirtyNode(QSGItem *item) itemPriv->transforms.at(ii)->applyTo(&matrix); if (itemPriv->scale != 1. || itemPriv->rotation != 0.) { - QPointF origin = itemPriv->computeTransformOrigin(); + QPointF origin = item->transformOriginPoint(); matrix.translate(origin.x(), origin.y()); if (itemPriv->scale != 1.) matrix.scale(itemPriv->scale, itemPriv->scale); diff --git a/src/declarative/items/qsgitem.cpp b/src/declarative/items/qsgitem.cpp index c2abc46..fd1cf66 100644 --- a/src/declarative/items/qsgitem.cpp +++ b/src/declarative/items/qsgitem.cpp @@ -3458,9 +3458,21 @@ void QSGItem::setTransformOrigin(TransformOrigin origin) QPointF QSGItem::transformOriginPoint() const { Q_D(const QSGItem); + if (!d->transformOriginPoint.isNull()) + return d->transformOriginPoint; return d->computeTransformOrigin(); } +void QSGItem::setTransformOriginPoint(const QPointF &point) +{ + Q_D(QSGItem); + if (d->transformOriginPoint == point) + return; + + d->transformOriginPoint = point; + d->dirty(QSGItemPrivate::TransformOrigin); +} + qreal QSGItem::z() const { Q_D(const QSGItem); diff --git a/src/declarative/items/qsgitem.h b/src/declarative/items/qsgitem.h index 249a206..9a540d7 100644 --- a/src/declarative/items/qsgitem.h +++ b/src/declarative/items/qsgitem.h @@ -231,6 +231,7 @@ public: TransformOrigin transformOrigin() const; void setTransformOrigin(TransformOrigin); QPointF transformOriginPoint() const; + void setTransformOriginPoint(const QPointF &); qreal z() const; void setZ(qreal); diff --git a/src/declarative/items/qsgitem_p.h b/src/declarative/items/qsgitem_p.h index a3acb97..efe61ce 100644 --- a/src/declarative/items/qsgitem_p.h +++ b/src/declarative/items/qsgitem_p.h @@ -300,6 +300,8 @@ public: Qt::MouseButtons acceptedMouseButtons; Qt::InputMethodHints imHints; + QPointF transformOriginPoint; + virtual qreal getImplicitWidth() const; virtual qreal getImplicitHeight() const; virtual void implicitWidthChanged(); diff --git a/src/declarative/items/qsgitemsmodule.cpp b/src/declarative/items/qsgitemsmodule.cpp index bca0437..8eb7cb3 100644 --- a/src/declarative/items/qsgitemsmodule.cpp +++ b/src/declarative/items/qsgitemsmodule.cpp @@ -61,6 +61,7 @@ #include "qsggridview_p.h" #include "qsgpathview_p.h" #include +#include #include "qsgpositioners_p.h" #include "qsgrepeater_p.h" #include "qsgloader_p.h" @@ -125,6 +126,9 @@ static void qt_sgitems_defineModule(const char *uri, int major, int minor) qmlRegisterType(uri,major,minor,"PathLine"); qmlRegisterType(uri,major,minor,"PathPercent"); qmlRegisterType(uri,major,minor,"PathQuad"); + qmlRegisterType("QtQuick",2,0,"PathCurve"); + qmlRegisterType("QtQuick",2,0,"PathArc"); + qmlRegisterType("QtQuick",2,0,"PathSvg"); qmlRegisterType(uri,major,minor,"PathView"); qmlRegisterUncreatableType(uri,major,minor,"Positioner","Positioner is an abstract type that is only available as an attached property."); #ifndef QT_NO_VALIDATOR @@ -191,6 +195,8 @@ static void qt_sgitems_defineModule(const char *uri, int major, int minor) qmlRegisterType(); qmlRegisterType(uri, major, minor,"AnchorAnimation"); qmlRegisterType(uri, major, minor,"ParentAnimation"); + qmlRegisterType("QtQuick",2,0,"PathAnimation"); + qmlRegisterType("QtQuick",2,0,"PathInterpolator"); qmlRegisterType("QtQuick", 2, 0, "DragTarget"); qmlRegisterType(); diff --git a/src/declarative/util/qdeclarativepath.cpp b/src/declarative/util/qdeclarativepath.cpp index 34752b5..57387f5 100644 --- a/src/declarative/util/qdeclarativepath.cpp +++ b/src/declarative/util/qdeclarativepath.cpp @@ -41,6 +41,7 @@ #include "private/qdeclarativepath_p.h" #include "private/qdeclarativepath_p_p.h" +#include "private/qdeclarativesvgparser_p.h" #include #include @@ -97,35 +98,47 @@ QDeclarativePath::~QDeclarativePath() qreal QDeclarativePath::startX() const { Q_D(const QDeclarativePath); - return d->startX; + return d->startX.isNull ? 0 : d->startX.value; } void QDeclarativePath::setStartX(qreal x) { Q_D(QDeclarativePath); - if (qFuzzyCompare(x, d->startX)) + if (d->startX.isValid() && qFuzzyCompare(x, d->startX)) return; d->startX = x; emit startXChanged(); processPath(); } +bool QDeclarativePath::hasStartX() const +{ + Q_D(const QDeclarativePath); + return d->startX.isValid(); +} + qreal QDeclarativePath::startY() const { Q_D(const QDeclarativePath); - return d->startY; + return d->startY.isNull ? 0 : d->startY.value; } void QDeclarativePath::setStartY(qreal y) { Q_D(QDeclarativePath); - if (qFuzzyCompare(y, d->startY)) + if (d->startY.isValid() && qFuzzyCompare(y, d->startY)) return; d->startY = y; emit startYChanged(); processPath(); } +bool QDeclarativePath::hasStartY() const +{ + Q_D(const QDeclarativePath); + return d->startY.isValid(); +} + /*! \qmlproperty bool QtQuick2::Path::closed This property holds whether the start and end of the path are identical. @@ -163,6 +176,11 @@ QDeclarativeListProperty QDeclarativePath::pathElements void QDeclarativePath::interpolate(int idx, const QString &name, qreal value) { Q_D(QDeclarativePath); + interpolate(d->_attributePoints, idx, name, value); +} + +void QDeclarativePath::interpolate(QList &attributePoints, int idx, const QString &name, qreal value) +{ if (!idx) return; @@ -170,7 +188,7 @@ void QDeclarativePath::interpolate(int idx, const QString &name, qreal value) qreal lastPercent = 0; int search = idx - 1; while(search >= 0) { - const AttributePoint &point = d->_attributePoints.at(search); + const AttributePoint &point = attributePoints.at(search); if (point.values.contains(name)) { lastValue = point.values.value(name); lastPercent = point.origpercent; @@ -181,10 +199,10 @@ void QDeclarativePath::interpolate(int idx, const QString &name, qreal value) ++search; - const AttributePoint &curPoint = d->_attributePoints.at(idx); + const AttributePoint &curPoint = attributePoints.at(idx); for (int ii = search; ii < idx; ++ii) { - AttributePoint &point = d->_attributePoints[ii]; + AttributePoint &point = attributePoints[ii]; qreal val = lastValue + (value - lastValue) * (point.origpercent - lastPercent) / (curPoint.origpercent - lastPercent); point.values.insert(name, val); @@ -208,6 +226,22 @@ void QDeclarativePath::endpoint(const QString &name) } } +void QDeclarativePath::endpoint(QList &attributePoints, const QString &name) +{ + const AttributePoint &first = attributePoints.first(); + qreal val = first.values.value(name); + for (int ii = attributePoints.count() - 1; ii >= 0; ii--) { + const AttributePoint &point = attributePoints.at(ii); + if (point.values.contains(name)) { + for (int jj = ii + 1; jj < attributePoints.count(); ++jj) { + AttributePoint &setPoint = attributePoints[jj]; + setPoint.values.insert(name, val); + } + return; + } + } +} + static QString percentString(QStringLiteral("_qfx_percent")); void QDeclarativePath::processPath() @@ -218,42 +252,64 @@ void QDeclarativePath::processPath() return; d->_pointCache.clear(); - d->_attributePoints.clear(); - d->_path = QPainterPath(); + d->prevBez.isValid = false; + + d->_path = createPath(QPointF(), QPointF(), d->_attributes, d->pathLength, d->_attributePoints, &d->closed); + + emit changed(); +} + +QPainterPath QDeclarativePath::createPath(const QPointF &startPoint, const QPointF &endPoint, const QStringList &attributes, qreal &pathLength, QList &attributePoints, bool *closed) +{ + Q_D(QDeclarativePath); + + pathLength = 0; + attributePoints.clear(); + + if (!d->componentComplete) + return QPainterPath(); + + QPainterPath path; AttributePoint first; - for (int ii = 0; ii < d->_attributes.count(); ++ii) - first.values[d->_attributes.at(ii)] = 0; - d->_attributePoints << first; + for (int ii = 0; ii < attributes.count(); ++ii) + first.values[attributes.at(ii)] = 0; + attributePoints << first; - d->_path.moveTo(d->startX, d->startY); + qreal startX = d->startX.isValid() ? d->startX.value : startPoint.x(); + qreal startY = d->startY.isValid() ? d->startY.value : startPoint.y(); + path.moveTo(startX, startY); - QDeclarativeCurve *lastCurve = 0; bool usesPercent = false; + int index = 0; foreach (QDeclarativePathElement *pathElement, d->_pathElements) { if (QDeclarativeCurve *curve = qobject_cast(pathElement)) { - curve->addToPath(d->_path); + QDeclarativePathData data; + data.index = index; + data.endPoint = endPoint; + data.curves = d->_pathCurves; + curve->addToPath(path, data); AttributePoint p; - p.origpercent = d->_path.length(); - d->_attributePoints << p; - lastCurve = curve; + p.origpercent = path.length(); + attributePoints << p; + ++index; } else if (QDeclarativePathAttribute *attribute = qobject_cast(pathElement)) { - AttributePoint &point = d->_attributePoints.last(); + AttributePoint &point = attributePoints.last(); point.values[attribute->name()] = attribute->value(); - interpolate(d->_attributePoints.count() - 1, attribute->name(), attribute->value()); + interpolate(attributePoints, attributePoints.count() - 1, attribute->name(), attribute->value()); } else if (QDeclarativePathPercent *percent = qobject_cast(pathElement)) { - AttributePoint &point = d->_attributePoints.last(); + AttributePoint &point = attributePoints.last(); point.values[percentString] = percent->value(); - interpolate(d->_attributePoints.count() - 1, percentString, percent->value()); + interpolate(attributePoints, attributePoints.count() - 1, percentString, percent->value()); usesPercent = true; } } // Fixup end points - const AttributePoint &last = d->_attributePoints.last(); - for (int ii = 0; ii < d->_attributes.count(); ++ii) { - if (!last.values.contains(d->_attributes.at(ii))) - endpoint(d->_attributes.at(ii)); + const AttributePoint &last = attributePoints.last(); + for (int ii = 0; ii < attributes.count(); ++ii) { + if (!last.values.contains(attributes.at(ii))) + endpoint(attributePoints, attributes.at(ii)); } if (usesPercent && !last.values.contains(percentString)) { d->_attributePoints.last().values[percentString] = 1; @@ -262,30 +318,34 @@ void QDeclarativePath::processPath() // Adjust percent - qreal length = d->_path.length(); + qreal length = path.length(); qreal prevpercent = 0; qreal prevorigpercent = 0; - for (int ii = 0; ii < d->_attributePoints.count(); ++ii) { - const AttributePoint &point = d->_attributePoints.at(ii); + for (int ii = 0; ii < attributePoints.count(); ++ii) { + const AttributePoint &point = attributePoints.at(ii); if (point.values.contains(percentString)) { //special string for QDeclarativePathPercent if ( ii > 0) { - qreal scale = (d->_attributePoints[ii].origpercent/length - prevorigpercent) / + qreal scale = (attributePoints[ii].origpercent/length - prevorigpercent) / (point.values.value(percentString)-prevpercent); - d->_attributePoints[ii].scale = scale; + attributePoints[ii].scale = scale; } - d->_attributePoints[ii].origpercent /= length; - d->_attributePoints[ii].percent = point.values.value(percentString); - prevorigpercent = d->_attributePoints[ii].origpercent; - prevpercent = d->_attributePoints[ii].percent; + attributePoints[ii].origpercent /= length; + attributePoints[ii].percent = point.values.value(percentString); + prevorigpercent = attributePoints[ii].origpercent; + prevpercent = attributePoints[ii].percent; } else { - d->_attributePoints[ii].origpercent /= length; - d->_attributePoints[ii].percent = d->_attributePoints[ii].origpercent; + attributePoints[ii].origpercent /= length; + attributePoints[ii].percent = attributePoints[ii].origpercent; } } - d->closed = lastCurve && d->startX == lastCurve->x() && d->startY == lastCurve->y(); + if (closed) { + QPointF end = path.currentPosition(); + *closed = length > 0 && startX == end.x() && startY == end.y(); + } + pathLength = length; - emit changed(); + return path; } void QDeclarativePath::classBegin() @@ -302,8 +362,11 @@ void QDeclarativePath::componentComplete() // First gather up all the attributes foreach (QDeclarativePathElement *pathElement, d->_pathElements) { - if (QDeclarativePathAttribute *attribute = - qobject_cast(pathElement)) + if (QDeclarativeCurve *curve = + qobject_cast(pathElement)) + d->_pathCurves.append(curve); + else if (QDeclarativePathAttribute *attribute = + qobject_cast(pathElement)) attrs.insert(attribute->name()); } d->_attributes = attrs.toList(); @@ -337,10 +400,10 @@ QStringList QDeclarativePath::attributes() const return d->_attributes; } -static inline QBezier nextBezier(const QPainterPath &path, int *from, qreal *bezLength) +static inline QBezier nextBezier(const QPainterPath &path, int *from, qreal *bezLength, bool reverse = false) { - const int lastElement = path.elementCount() - 1; - for (int i=*from; i <= lastElement; ++i) { + const int lastElement = reverse ? 0 : path.elementCount() - 1; + for (int i=*from; reverse ? i >= lastElement : i <= lastElement; reverse ? --i : ++i) { const QPainterPath::Element &e = path.elementAt(i); switch (e.type) { @@ -352,7 +415,7 @@ static inline QBezier nextBezier(const QPainterPath &path, int *from, qreal *bez *bezLength = line.length(); QPointF a = path.elementAt(i-1); QPointF delta = e - a; - *from = i+1; + *from = reverse ? i-1 : i+1; return QBezier::fromPoints(a, a + delta / 3, a + 2 * delta / 3, e); } case QPainterPath::CurveToElement: @@ -362,7 +425,7 @@ static inline QBezier nextBezier(const QPainterPath &path, int *from, qreal *bez path.elementAt(i+1), path.elementAt(i+2)); *bezLength = b.length(); - *from = i+3; + *from = reverse ? i-1 : i+3; return b; } default: @@ -374,10 +437,16 @@ static inline QBezier nextBezier(const QPainterPath &path, int *from, qreal *bez return QBezier(); } +//derivative of the equation +static inline qreal slopeAt(qreal t, qreal a, qreal b, qreal c, qreal d) +{ + return 3*t*t*(d - 3*c + 3*b - a) + 6*t*(c - 2*b + a) + 3*(b - a); +} + void QDeclarativePath::createPointCache() const { Q_D(const QDeclarativePath); - qreal pathLength = d->_path.length(); + qreal pathLength = d->pathLength; if (pathLength <= 0 || qIsNaN(pathLength)) return; // more points means less jitter between items as they move along the @@ -426,6 +495,140 @@ void QDeclarativePath::createPointCache() const } } +QPointF QDeclarativePath::sequentialPointAt(qreal p, qreal *angle) const +{ + Q_D(const QDeclarativePath); + return sequentialPointAt(d->_path, d->pathLength, d->_attributePoints, d->prevBez, p, angle); +} + +QPointF QDeclarativePath::sequentialPointAt(const QPainterPath &path, const qreal &pathLength, const QList &attributePoints, QDeclarativeCachedBezier &prevBez, qreal p, qreal *angle) +{ + if (!prevBez.isValid) + return p > .5 ? backwardsPointAt(path, pathLength, attributePoints, prevBez, p, angle) : + forwardsPointAt(path, pathLength, attributePoints, prevBez, p, angle); + + return p < prevBez.p ? backwardsPointAt(path, pathLength, attributePoints, prevBez, p, angle) : + forwardsPointAt(path, pathLength, attributePoints, prevBez, p, angle); +} + +QPointF QDeclarativePath::forwardsPointAt(const QPainterPath &path, const qreal &pathLength, const QList &attributePoints, QDeclarativeCachedBezier &prevBez, qreal p, qreal *angle) +{ + if (pathLength <= 0 || qIsNaN(pathLength)) + return path.pointAtPercent(0); //expensive? + + const int lastElement = path.elementCount() - 1; + bool haveCachedBez = prevBez.isValid; + int currElement = haveCachedBez ? prevBez.element : 0; + qreal bezLength = haveCachedBez ? prevBez.bezLength : 0; + QBezier currBez = haveCachedBez ? prevBez.bezier : nextBezier(path, &currElement, &bezLength); + qreal currLength = haveCachedBez ? prevBez.currLength : bezLength; + qreal epc = currLength / pathLength; + + //find which set we are in + qreal prevPercent = 0; + qreal prevOrigPercent = 0; + for (int ii = 0; ii < attributePoints.count(); ++ii) { + qreal percent = p; + const AttributePoint &point = attributePoints.at(ii); + if (percent < point.percent || ii == attributePoints.count() - 1) { + qreal elementPercent = (percent - prevPercent); + + qreal spc = prevOrigPercent + elementPercent * point.scale; + + while (spc > epc) { + if (currElement > lastElement) + break; + currBez = nextBezier(path, &currElement, &bezLength); + /*if (bezLength == 0.0) { + currLength = pathLength; + epc = 1.0; + break; + }*/ + currLength += bezLength; + epc = currLength / pathLength; + } + prevBez.element = currElement; + prevBez.bezLength = bezLength; + prevBez.currLength = currLength; + prevBez.bezier = currBez; + prevBez.p = p; + prevBez.isValid = true; + + qreal realT = (pathLength * spc - (currLength - bezLength)) / bezLength; + + if (angle) { + qreal m1 = slopeAt(realT, currBez.x1, currBez.x2, currBez.x3, currBez.x4); + qreal m2 = slopeAt(realT, currBez.y1, currBez.y2, currBez.y3, currBez.y4); + *angle = QLineF(0, 0, m1, m2).angle(); + } + + return currBez.pointAt(qBound(qreal(0), realT, qreal(1))); + } + prevOrigPercent = point.origpercent; + prevPercent = point.percent; + } + + return QPointF(0,0); +} + +//ideally this should be merged with forwardsPointAt +QPointF QDeclarativePath::backwardsPointAt(const QPainterPath &path, const qreal &pathLength, const QList &attributePoints, QDeclarativeCachedBezier &prevBez, qreal p, qreal *angle) +{ + if (pathLength <= 0 || qIsNaN(pathLength)) + return path.pointAtPercent(0); + + const int firstElement = 0; + bool haveCachedBez = prevBez.isValid; + int currElement = haveCachedBez ? prevBez.element : path.elementCount() - 1; + qreal bezLength = haveCachedBez ? prevBez.bezLength : 0; + QBezier currBez = haveCachedBez ? prevBez.bezier : nextBezier(path, &currElement, &bezLength, true /*reverse*/); + qreal currLength = haveCachedBez ? prevBez.currLength : pathLength; + qreal prevLength = currLength - bezLength; + qreal epc = prevLength / pathLength; + + for (int ii = attributePoints.count() - 1; ii > 0; --ii) { + qreal percent = p; + const AttributePoint &point = attributePoints.at(ii); + const AttributePoint &prevPoint = attributePoints.at(ii-1); + if (percent > prevPoint.percent || ii == 1) { + qreal elementPercent = (percent - prevPoint.percent); + + qreal spc = prevPoint.origpercent + elementPercent * point.scale; + + while (spc < epc) { + if (currElement < firstElement) + break; + currBez = nextBezier(path, &currElement, &bezLength, true /*reverse*/); + /*if (bezLength == 0.0) { + currLength = 0; + epc = 0.0; + break; + }*/ + currLength = prevLength; + epc = (currLength - bezLength) / pathLength; + } + prevBez.element = currElement; + prevBez.bezLength = bezLength; + prevBez.currLength = currLength; + prevBez.bezier = currBez; + prevBez.p = p; + prevBez.isValid = true; + + qreal realT = (pathLength * spc - (currLength - bezLength)) / bezLength; + + if (angle) { + qreal m1 = slopeAt(realT, currBez.x1, currBez.x2, currBez.x3, currBez.x4); + qreal m2 = slopeAt(realT, currBez.y1, currBez.y2, currBez.y3, currBez.y4); + *angle = QLineF(0, 0, m1, m2).angle(); + } + + return currBez.pointAt(qBound(qreal(0), realT, qreal(1))); + } + } + + return QPointF(0,0); +} + QPointF QDeclarativePath::pointAt(qreal p) const { Q_D(const QDeclarativePath); @@ -472,32 +675,80 @@ qreal QDeclarativePath::attributeAt(const QString &name, qreal percent) const qreal QDeclarativeCurve::x() const { - return _x; + return _x.isNull ? 0 : _x.value; } void QDeclarativeCurve::setX(qreal x) { - if (_x != x) { + if (_x.isNull || _x != x) { _x = x; emit xChanged(); emit changed(); } } +bool QDeclarativeCurve::hasX() +{ + return _x.isValid(); +} + qreal QDeclarativeCurve::y() const { - return _y; + return _y.isNull ? 0 : _y.value; } void QDeclarativeCurve::setY(qreal y) { - if (_y != y) { + if (_y.isNull || _y != y) { _y = y; emit yChanged(); emit changed(); } } +bool QDeclarativeCurve::hasY() +{ + return _y.isValid(); +} + +qreal QDeclarativeCurve::relativeX() const +{ + return _relativeX; +} + +void QDeclarativeCurve::setRelativeX(qreal x) +{ + if (_relativeX.isNull || _relativeX != x) { + _relativeX = x; + emit relativeXChanged(); + emit changed(); + } +} + +bool QDeclarativeCurve::hasRelativeX() +{ + return _relativeX.isValid(); +} + +qreal QDeclarativeCurve::relativeY() const +{ + return _relativeY; +} + +void QDeclarativeCurve::setRelativeY(qreal y) +{ + if (_relativeY.isNull || _relativeY != y) { + _relativeY = y; + emit relativeYChanged(); + emit changed(); + } +} + +bool QDeclarativeCurve::hasRelativeY() +{ + return _relativeY.isValid(); +} + /****************************************************************************/ /*! @@ -642,9 +893,17 @@ void QDeclarativePathAttribute::setValue(qreal value) Defines the end point of the line. */ -void QDeclarativePathLine::addToPath(QPainterPath &path) +inline QPointF positionForCurve(const QDeclarativePathData &data, const QPointF &prevPoint) { - path.lineTo(x(), y()); + QDeclarativeCurve *curve = data.curves.at(data.index); + bool isEnd = data.index == data.curves.size() - 1; + return QPointF(curve->hasRelativeX() ? prevPoint.x() + curve->relativeX() : !isEnd || curve->hasX() ? curve->x() : data.endPoint.x(), + curve->hasRelativeY() ? prevPoint.y() + curve->relativeY() : !isEnd || curve->hasY() ? curve->y() : data.endPoint.y()); +} + +void QDeclarativePathLine::addToPath(QPainterPath &path, const QDeclarativePathData &data) +{ + path.lineTo(positionForCurve(data, path.currentPosition())); } /****************************************************************************/ @@ -720,9 +979,50 @@ void QDeclarativePathQuad::setControlY(qreal y) } } -void QDeclarativePathQuad::addToPath(QPainterPath &path) +qreal QDeclarativePathQuad::relativeControlX() const +{ + return _relativeControlX; +} + +void QDeclarativePathQuad::setRelativeControlX(qreal x) +{ + if (_relativeControlX.isNull || _relativeControlX != x) { + _relativeControlX = x; + emit relativeControlXChanged(); + emit changed(); + } +} + +bool QDeclarativePathQuad::hasRelativeControlX() +{ + return _relativeControlX.isValid(); +} + +qreal QDeclarativePathQuad::relativeControlY() const +{ + return _relativeControlY; +} + +void QDeclarativePathQuad::setRelativeControlY(qreal y) +{ + if (_relativeControlY.isNull || _relativeControlY != y) { + _relativeControlY = y; + emit relativeControlYChanged(); + emit changed(); + } +} + +bool QDeclarativePathQuad::hasRelativeControlY() { - path.quadTo(controlX(), controlY(), x(), y()); + return _relativeControlY.isValid(); +} + +void QDeclarativePathQuad::addToPath(QPainterPath &path, const QDeclarativePathData &data) +{ + const QPointF &prevPoint = path.currentPosition(); + QPointF controlPoint(hasRelativeControlX() ? prevPoint.x() + relativeControlX() : controlX(), + hasRelativeControlY() ? prevPoint.y() + relativeControlY() : controlY()); + path.quadTo(controlPoint, positionForCurve(data, path.currentPosition())); } /****************************************************************************/ @@ -828,9 +1128,256 @@ void QDeclarativePathCubic::setControl2Y(qreal y) } } -void QDeclarativePathCubic::addToPath(QPainterPath &path) +qreal QDeclarativePathCubic::relativeControl1X() const +{ + return _relativeControl1X; +} + +void QDeclarativePathCubic::setRelativeControl1X(qreal x) +{ + if (_relativeControl1X.isNull || _relativeControl1X != x) { + _relativeControl1X = x; + emit relativeControl1XChanged(); + emit changed(); + } +} + +bool QDeclarativePathCubic::hasRelativeControl1X() +{ + return _relativeControl1X.isValid(); +} + +qreal QDeclarativePathCubic::relativeControl1Y() const +{ + return _relativeControl1Y; +} + +void QDeclarativePathCubic::setRelativeControl1Y(qreal y) +{ + if (_relativeControl1Y.isNull || _relativeControl1Y != y) { + _relativeControl1Y = y; + emit relativeControl1YChanged(); + emit changed(); + } +} + +bool QDeclarativePathCubic::hasRelativeControl1Y() +{ + return _relativeControl1Y.isValid(); +} + +qreal QDeclarativePathCubic::relativeControl2X() const +{ + return _relativeControl2X; +} + +void QDeclarativePathCubic::setRelativeControl2X(qreal x) +{ + if (_relativeControl2X.isNull || _relativeControl2X != x) { + _relativeControl2X = x; + emit relativeControl2XChanged(); + emit changed(); + } +} + +bool QDeclarativePathCubic::hasRelativeControl2X() +{ + return _relativeControl2X.isValid(); +} + +qreal QDeclarativePathCubic::relativeControl2Y() const +{ + return _relativeControl2Y; +} + +void QDeclarativePathCubic::setRelativeControl2Y(qreal y) +{ + if (_relativeControl2Y.isNull || _relativeControl2Y != y) { + _relativeControl2Y = y; + emit relativeControl2YChanged(); + emit changed(); + } +} + +bool QDeclarativePathCubic::hasRelativeControl2Y() +{ + return _relativeControl2Y.isValid(); +} + +void QDeclarativePathCubic::addToPath(QPainterPath &path, const QDeclarativePathData &data) +{ + const QPointF &prevPoint = path.currentPosition(); + QPointF controlPoint1(hasRelativeControl1X() ? prevPoint.x() + relativeControl1X() : control1X(), + hasRelativeControl1Y() ? prevPoint.y() + relativeControl1Y() : control1Y()); + QPointF controlPoint2(hasRelativeControl2X() ? prevPoint.x() + relativeControl2X() : control2X(), + hasRelativeControl2Y() ? prevPoint.y() + relativeControl2Y() : control2Y()); + path.cubicTo(controlPoint1, controlPoint2, positionForCurve(data, path.currentPosition())); +} + +/****************************************************************************/ + +inline QPointF previousPathPosition(const QPainterPath &path) +{ + int count = path.elementCount(); + if (count < 1) + return QPointF(); + + int index = path.elementAt(count-1).type == QPainterPath::CurveToDataElement ? count - 4 : count - 2; + return index > -1 ? QPointF(path.elementAt(index)) : path.pointAtPercent(0); +} + +void QDeclarativePathCatmullRomCurve::addToPath(QPainterPath &path, const QDeclarativePathData &data) +{ + //here we convert catmull-rom spline to bezier for use in QPainterPath. + //basic conversion algorithm: + // catmull-rom points * inverse bezier matrix * catmull-rom matrix = bezier points + //each point in the catmull-rom spline produces a bezier endpoint + 2 control points + //calculations for each point use a moving window of 4 points + // (previous 2 points + current point + next point) + QPointF prevFar, prev, point, next; + + //get previous points + int index = data.index - 1; + QDeclarativeCurve *curve = index == -1 ? 0 : data.curves.at(index); + if (qobject_cast(curve)) { + prev = path.currentPosition(); + prevFar = previousPathPosition(path); + } else + prevFar = prev = path.currentPosition(); + + //get current point + point = positionForCurve(data, path.currentPosition()); + + //get next point + index = data.index + 1; + if (index < data.curves.count() && qobject_cast(data.curves.at(index))) { + QDeclarativePathData nextData; + nextData.index = index; + nextData.endPoint = data.endPoint; + nextData.curves = data.curves; + next = positionForCurve(nextData, point); + } else + next = point; + + /* + full conversion matrix (inverse bezier * catmull-rom): + 0.000, 1.000, 0.000, 0.000, + -0.167, 1.000, 0.167, 0.000, + 0.000, 0.167, 1.000, -0.167, + 0.000, 0.000, 1.000, 0.000 + + conversion doesn't require full matrix multiplication, + so below we simplify + */ + QPointF control1(prevFar.x() * qreal(-0.167) + + prev.x() + + point.x() * qreal(0.167), + prevFar.y() * qreal(-0.167) + + prev.y() + + point.y() * qreal(0.167)); + + QPointF control2(prev.x() * qreal(0.167) + + point.x() + + next.x() * qreal(-0.167), + prev.y() * qreal(0.167) + + point.y() + + next.y() * qreal(-0.167)); + + path.cubicTo(control1, control2, point); +} + +/****************************************************************************/ + +qreal QDeclarativePathArc::radiusX() const +{ + return _radiusX; +} + +void QDeclarativePathArc::setRadiusX(qreal radius) +{ + if (_radiusX == radius) + return; + + _radiusX = radius; + emit radiusXChanged(); +} + +qreal QDeclarativePathArc::radiusY() const +{ + return _radiusY; +} + +void QDeclarativePathArc::setRadiusY(qreal radius) +{ + if (_radiusY == radius) + return; + + _radiusY = radius; + emit radiusYChanged(); +} + +bool QDeclarativePathArc::useLargeArc() const +{ + return _useLargeArc; +} + +void QDeclarativePathArc::setUseLargeArc(bool largeArc) +{ + if (_useLargeArc == largeArc) + return; + + _useLargeArc = largeArc; + emit useLargeArcChanged(); +} + +QDeclarativePathArc::ArcDirection QDeclarativePathArc::direction() const +{ + return _direction; +} + +void QDeclarativePathArc::setDirection(ArcDirection direction) +{ + if (_direction == direction) + return; + + _direction = direction; + emit directionChanged(); +} + +void QDeclarativePathArc::addToPath(QPainterPath &path, const QDeclarativePathData &data) +{ + const QPointF &startPoint = path.currentPosition(); + const QPointF &endPoint = positionForCurve(data, startPoint); + QDeclarativeSvgParser::pathArc(path, + _radiusX, + _radiusY, + 0, //xAxisRotation + _useLargeArc, + _direction == Clockwise ? 1 : 0, + endPoint.x(), + endPoint.y(), + startPoint.x(), startPoint.y()); +} + +/****************************************************************************/ + +QString QDeclarativePathSvg::path() const +{ + return _path; +} + +void QDeclarativePathSvg::setPath(const QString &path) +{ + if (_path == path) + return; + + _path = path; + emit pathChanged(); +} + +void QDeclarativePathSvg::addToPath(QPainterPath &path, const QDeclarativePathData &) { - path.cubicTo(control1X(), control1Y(), control2X(), control2Y(), x(), y()); + QDeclarativeSvgParser::parsePathDataFast(_path, path); } /****************************************************************************/ diff --git a/src/declarative/util/qdeclarativepath_p.h b/src/declarative/util/qdeclarativepath_p.h index c8420eb..4ce1bcf 100644 --- a/src/declarative/util/qdeclarativepath_p.h +++ b/src/declarative/util/qdeclarativepath_p.h @@ -44,6 +44,9 @@ #include +#include "private/qdeclarativenullablevalue_p_p.h" +#include + #include #include @@ -52,6 +55,15 @@ QT_BEGIN_HEADER QT_BEGIN_NAMESPACE QT_MODULE(Declarative) + +class QDeclarativeCurve; +struct QDeclarativePathData +{ + int index; + QPointF endPoint; + QList curves; +}; + class Q_AUTOTEST_EXPORT QDeclarativePathElement : public QObject { Q_OBJECT @@ -92,24 +104,40 @@ class Q_AUTOTEST_EXPORT QDeclarativeCurve : public QDeclarativePathElement Q_PROPERTY(qreal x READ x WRITE setX NOTIFY xChanged) Q_PROPERTY(qreal y READ y WRITE setY NOTIFY yChanged) + Q_PROPERTY(qreal relativeX READ relativeX WRITE setRelativeX NOTIFY relativeXChanged) + Q_PROPERTY(qreal relativeY READ relativeY WRITE setRelativeY NOTIFY relativeYChanged) public: - QDeclarativeCurve(QObject *parent=0) : QDeclarativePathElement(parent), _x(0), _y(0) {} + QDeclarativeCurve(QObject *parent=0) : QDeclarativePathElement(parent) {} qreal x() const; void setX(qreal x); + bool hasX(); qreal y() const; void setY(qreal y); + bool hasY(); + + qreal relativeX() const; + void setRelativeX(qreal x); + bool hasRelativeX(); + + qreal relativeY() const; + void setRelativeY(qreal y); + bool hasRelativeY(); - virtual void addToPath(QPainterPath &) {} + virtual void addToPath(QPainterPath &, const QDeclarativePathData &) {} Q_SIGNALS: void xChanged(); void yChanged(); + void relativeXChanged(); + void relativeYChanged(); private: - qreal _x; - qreal _y; + QDeclarativeNullableValue _x; + QDeclarativeNullableValue _y; + QDeclarativeNullableValue _relativeX; + QDeclarativeNullableValue _relativeY; }; class Q_AUTOTEST_EXPORT QDeclarativePathLine : public QDeclarativeCurve @@ -118,7 +146,7 @@ class Q_AUTOTEST_EXPORT QDeclarativePathLine : public QDeclarativeCurve public: QDeclarativePathLine(QObject *parent=0) : QDeclarativeCurve(parent) {} - void addToPath(QPainterPath &path); + void addToPath(QPainterPath &path, const QDeclarativePathData &); }; class Q_AUTOTEST_EXPORT QDeclarativePathQuad : public QDeclarativeCurve @@ -127,6 +155,8 @@ class Q_AUTOTEST_EXPORT QDeclarativePathQuad : public QDeclarativeCurve Q_PROPERTY(qreal controlX READ controlX WRITE setControlX NOTIFY controlXChanged) Q_PROPERTY(qreal controlY READ controlY WRITE setControlY NOTIFY controlYChanged) + Q_PROPERTY(qreal relativeControlX READ relativeControlX WRITE setRelativeControlX NOTIFY relativeControlXChanged) + Q_PROPERTY(qreal relativeControlY READ relativeControlY WRITE setRelativeControlY NOTIFY relativeControlYChanged) public: QDeclarativePathQuad(QObject *parent=0) : QDeclarativeCurve(parent), _controlX(0), _controlY(0) {} @@ -136,15 +166,27 @@ public: qreal controlY() const; void setControlY(qreal y); - void addToPath(QPainterPath &path); + qreal relativeControlX() const; + void setRelativeControlX(qreal x); + bool hasRelativeControlX(); + + qreal relativeControlY() const; + void setRelativeControlY(qreal y); + bool hasRelativeControlY(); + + void addToPath(QPainterPath &path, const QDeclarativePathData &); Q_SIGNALS: void controlXChanged(); void controlYChanged(); + void relativeControlXChanged(); + void relativeControlYChanged(); private: qreal _controlX; qreal _controlY; + QDeclarativeNullableValue _relativeControlX; + QDeclarativeNullableValue _relativeControlY; }; class Q_AUTOTEST_EXPORT QDeclarativePathCubic : public QDeclarativeCurve @@ -155,6 +197,10 @@ class Q_AUTOTEST_EXPORT QDeclarativePathCubic : public QDeclarativeCurve Q_PROPERTY(qreal control1Y READ control1Y WRITE setControl1Y NOTIFY control1YChanged) Q_PROPERTY(qreal control2X READ control2X WRITE setControl2X NOTIFY control2XChanged) Q_PROPERTY(qreal control2Y READ control2Y WRITE setControl2Y NOTIFY control2YChanged) + Q_PROPERTY(qreal relativeControl1X READ relativeControl1X WRITE setRelativeControl1X NOTIFY relativeControl1XChanged) + Q_PROPERTY(qreal relativeControl1Y READ relativeControl1Y WRITE setRelativeControl1Y NOTIFY relativeControl1YChanged) + Q_PROPERTY(qreal relativeControl2X READ relativeControl2X WRITE setRelativeControl2X NOTIFY relativeControl2XChanged) + Q_PROPERTY(qreal relativeControl2Y READ relativeControl2Y WRITE setRelativeControl2Y NOTIFY relativeControl2YChanged) public: QDeclarativePathCubic(QObject *parent=0) : QDeclarativeCurve(parent), _control1X(0), _control1Y(0), _control2X(0), _control2Y(0) {} @@ -170,19 +216,113 @@ public: qreal control2Y() const; void setControl2Y(qreal y); - void addToPath(QPainterPath &path); + qreal relativeControl1X() const; + void setRelativeControl1X(qreal x); + bool hasRelativeControl1X(); + + qreal relativeControl1Y() const; + void setRelativeControl1Y(qreal y); + bool hasRelativeControl1Y(); + + qreal relativeControl2X() const; + void setRelativeControl2X(qreal x); + bool hasRelativeControl2X(); + + qreal relativeControl2Y() const; + void setRelativeControl2Y(qreal y); + bool hasRelativeControl2Y(); + + void addToPath(QPainterPath &path, const QDeclarativePathData &); Q_SIGNALS: void control1XChanged(); void control1YChanged(); void control2XChanged(); void control2YChanged(); + void relativeControl1XChanged(); + void relativeControl1YChanged(); + void relativeControl2XChanged(); + void relativeControl2YChanged(); private: qreal _control1X; qreal _control1Y; qreal _control2X; qreal _control2Y; + QDeclarativeNullableValue _relativeControl1X; + QDeclarativeNullableValue _relativeControl1Y; + QDeclarativeNullableValue _relativeControl2X; + QDeclarativeNullableValue _relativeControl2Y; +}; + +class Q_AUTOTEST_EXPORT QDeclarativePathCatmullRomCurve : public QDeclarativeCurve +{ + Q_OBJECT +public: + QDeclarativePathCatmullRomCurve(QObject *parent=0) : QDeclarativeCurve(parent) {} + + void addToPath(QPainterPath &path, const QDeclarativePathData &); +}; + +class Q_AUTOTEST_EXPORT QDeclarativePathArc : public QDeclarativeCurve +{ + Q_OBJECT + Q_PROPERTY(qreal radiusX READ radiusX WRITE setRadiusX NOTIFY radiusXChanged) + Q_PROPERTY(qreal radiusY READ radiusY WRITE setRadiusY NOTIFY radiusYChanged) + Q_PROPERTY(bool useLargeArc READ useLargeArc WRITE setUseLargeArc NOTIFY useLargeArcChanged) + Q_PROPERTY(ArcDirection direction READ direction WRITE setDirection NOTIFY directionChanged) + +public: + QDeclarativePathArc(QObject *parent=0) + : QDeclarativeCurve(parent), _radiusX(0), _radiusY(0), _useLargeArc(false), _direction(Clockwise) {} + + enum ArcDirection { Clockwise, Counterclockwise }; + Q_ENUMS(ArcDirection) + + qreal radiusX() const; + void setRadiusX(qreal); + + qreal radiusY() const; + void setRadiusY(qreal); + + bool useLargeArc() const; + void setUseLargeArc(bool); + + ArcDirection direction() const; + void setDirection(ArcDirection direction); + + void addToPath(QPainterPath &path, const QDeclarativePathData &); + +Q_SIGNALS: + void radiusXChanged(); + void radiusYChanged(); + void useLargeArcChanged(); + void directionChanged(); + +private: + qreal _radiusX; + qreal _radiusY; + bool _useLargeArc; + ArcDirection _direction; +}; + +class Q_AUTOTEST_EXPORT QDeclarativePathSvg : public QDeclarativeCurve +{ + Q_OBJECT + Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) +public: + QDeclarativePathSvg(QObject *parent=0) : QDeclarativeCurve(parent) {} + + QString path() const; + void setPath(const QString &path); + + void addToPath(QPainterPath &path, const QDeclarativePathData &); + +Q_SIGNALS: + void pathChanged(); + +private: + QString _path; }; class Q_AUTOTEST_EXPORT QDeclarativePathPercent : public QDeclarativePathElement @@ -202,6 +342,17 @@ private: qreal _value; }; +struct QDeclarativeCachedBezier +{ + QDeclarativeCachedBezier() : isValid(false) {} + QBezier bezier; + int element; + qreal bezLength; + qreal currLength; + qreal p; + bool isValid; +}; + class QDeclarativePathPrivate; class Q_AUTOTEST_EXPORT QDeclarativePath : public QObject, public QDeclarativeParserStatus { @@ -222,9 +373,11 @@ public: qreal startX() const; void setStartX(qreal x); + bool hasStartX() const; qreal startY() const; void setStartY(qreal y); + bool hasStartY() const; bool isClosed() const; @@ -232,6 +385,7 @@ public: QStringList attributes() const; qreal attributeAt(const QString &, qreal) const; QPointF pointAt(qreal) const; + QPointF sequentialPointAt(qreal p, qreal *angle = 0) const; Q_SIGNALS: void changed(); @@ -263,9 +417,19 @@ private: void endpoint(const QString &name); void createPointCache() const; + static void interpolate(QList &points, int idx, const QString &name, qreal value); + static void endpoint(QList &attributePoints, const QString &name); + static QPointF forwardsPointAt(const QPainterPath &path, const qreal &pathLength, const QList &attributePoints, QDeclarativeCachedBezier &prevBez, qreal p, qreal *angle = 0); + static QPointF backwardsPointAt(const QPainterPath &path, const qreal &pathLength, const QList &attributePoints, QDeclarativeCachedBezier &prevBez, qreal p, qreal *angle = 0); + private: Q_DISABLE_COPY(QDeclarativePath) Q_DECLARE_PRIVATE(QDeclarativePath) + friend class QSGPathAnimationUpdater; + +public: + QPainterPath createPath(const QPointF &startPoint, const QPointF &endPoint, const QStringList &attributes, qreal &pathLength, QList &attributePoints, bool *closed = 0); + static QPointF sequentialPointAt(const QPainterPath &path, const qreal &pathLength, const QList &attributePoints, QDeclarativeCachedBezier &prevBez, qreal p, qreal *angle = 0); }; QT_END_NAMESPACE @@ -276,6 +440,9 @@ QML_DECLARE_TYPE(QDeclarativeCurve) QML_DECLARE_TYPE(QDeclarativePathLine) QML_DECLARE_TYPE(QDeclarativePathQuad) QML_DECLARE_TYPE(QDeclarativePathCubic) +QML_DECLARE_TYPE(QDeclarativePathCatmullRomCurve) +QML_DECLARE_TYPE(QDeclarativePathArc) +QML_DECLARE_TYPE(QDeclarativePathSvg) QML_DECLARE_TYPE(QDeclarativePathPercent) QML_DECLARE_TYPE(QDeclarativePath) diff --git a/src/declarative/util/qdeclarativepath_p_p.h b/src/declarative/util/qdeclarativepath_p_p.h index 4e407ec..c7313ea 100644 --- a/src/declarative/util/qdeclarativepath_p_p.h +++ b/src/declarative/util/qdeclarativepath_p_p.h @@ -56,28 +56,33 @@ #include "private/qdeclarativepath_p.h" #include +#include -#include #include QT_BEGIN_NAMESPACE + class QDeclarativePathPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(QDeclarativePath) public: - QDeclarativePathPrivate() : startX(0), startY(0), closed(false), componentComplete(true) { } + QDeclarativePathPrivate() : pathLength(0), closed(false), componentComplete(true) { } QPainterPath _path; QList _pathElements; mutable QVector _pointCache; QList _attributePoints; QStringList _attributes; - int startX; - int startY; + QList _pathCurves; + mutable QDeclarativeCachedBezier prevBez; + QDeclarativeNullableValue startX; + QDeclarativeNullableValue startY; + qreal pathLength; bool closed; bool componentComplete; }; QT_END_NAMESPACE + #endif diff --git a/src/declarative/util/qdeclarativepathinterpolator.cpp b/src/declarative/util/qdeclarativepathinterpolator.cpp new file mode 100644 index 0000000..db6ef15 --- /dev/null +++ b/src/declarative/util/qdeclarativepathinterpolator.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qdeclarativepathinterpolator_p.h" + +#include "private/qdeclarativepath_p.h" + +QT_BEGIN_NAMESPACE + +QDeclarativePathInterpolator::QDeclarativePathInterpolator(QObject *parent) : + QObject(parent), _path(0), _x(0), _y(0), _angle(0), _progress(0) +{ +} + +QDeclarativePath *QDeclarativePathInterpolator::path() const +{ + return _path; +} + +void QDeclarativePathInterpolator::setPath(QDeclarativePath *path) +{ + if (_path == path) + return; + if (_path) + disconnect(_path, SIGNAL(changed()), this, SLOT(_q_pathUpdated())); + _path = path; + connect(_path, SIGNAL(changed()), this, SLOT(_q_pathUpdated())); + emit pathChanged(); +} + +qreal QDeclarativePathInterpolator::progress() const +{ + return _progress; +} + +void QDeclarativePathInterpolator::setProgress(qreal progress) +{ + if (progress == _progress) + return; + _progress = progress; + emit progressChanged(); + _q_pathUpdated(); +} + +qreal QDeclarativePathInterpolator::x() const +{ + return _x; +} + +qreal QDeclarativePathInterpolator::y() const +{ + return _y; +} + +qreal QDeclarativePathInterpolator::angle() const +{ + return _angle; +} + +void QDeclarativePathInterpolator::_q_pathUpdated() +{ + if (! _path) + return; + + qreal angle = 0; + const QPointF pt = _path->sequentialPointAt(_progress, &angle); + + if (_x != pt.x()) { + _x = pt.x(); + emit xChanged(); + } + + if (_y != pt.y()) { + _y = pt.y(); + emit yChanged(); + } + + if (angle != _angle) { + _angle = angle; + emit angleChanged(); + } +} + +QT_END_NAMESPACE diff --git a/src/declarative/util/qdeclarativepathinterpolator_p.h b/src/declarative/util/qdeclarativepathinterpolator_p.h new file mode 100644 index 0000000..cb8ccfa --- /dev/null +++ b/src/declarative/util/qdeclarativepathinterpolator_p.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPATHINTERPOLATOR_P_H +#define QDECLARATIVEPATHINTERPOLATOR_P_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QDeclarativePath; +class Q_AUTOTEST_EXPORT QDeclarativePathInterpolator : public QObject +{ + Q_OBJECT + Q_PROPERTY(QDeclarativePath *path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged) + Q_PROPERTY(qreal x READ x NOTIFY xChanged) + Q_PROPERTY(qreal y READ y NOTIFY yChanged) + Q_PROPERTY(qreal angle READ angle NOTIFY angleChanged) +public: + explicit QDeclarativePathInterpolator(QObject *parent = 0); + + QDeclarativePath *path() const; + void setPath(QDeclarativePath *path); + + qreal progress() const; + void setProgress(qreal progress); + + qreal x() const; + qreal y() const; + qreal angle() const; + +Q_SIGNALS: + void pathChanged(); + void progressChanged(); + void xChanged(); + void yChanged(); + void angleChanged(); + +private Q_SLOTS: + void _q_pathUpdated(); + +private: + QDeclarativePath *_path; + qreal _x; + qreal _y; + qreal _angle; + qreal _progress; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativePathInterpolator) + +QT_END_HEADER + +#endif // QDECLARATIVEPATHINTERPOLATOR_P_H diff --git a/src/declarative/util/qdeclarativesvgparser.cpp b/src/declarative/util/qdeclarativesvgparser.cpp new file mode 100644 index 0000000..36714d7 --- /dev/null +++ b/src/declarative/util/qdeclarativesvgparser.cpp @@ -0,0 +1,633 @@ +/**************************************************************************** +** +** 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 QtDeclaractive 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$ +** +****************************************************************************/ + +#include "qdeclarativesvgparser_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static const double Q_PI = 3.14159265358979323846; // pi + +//copied from QtSvg (qsvghandler.cpp). +Q_CORE_EXPORT double qstrtod(const char *s00, char const **se, bool *ok); +// '0' is 0x30 and '9' is 0x39 +static inline bool isDigit(ushort ch) +{ + static quint16 magic = 0x3ff; + return ((ch >> 4) == 3) && (magic >> (ch & 15)); +} + +static qreal toDouble(const QChar *&str) +{ + const int maxLen = 255;//technically doubles can go til 308+ but whatever + char temp[maxLen+1]; + int pos = 0; + + if (*str == QLatin1Char('-')) { + temp[pos++] = '-'; + ++str; + } else if (*str == QLatin1Char('+')) { + ++str; + } + while (isDigit(str->unicode()) && pos < maxLen) { + temp[pos++] = str->toLatin1(); + ++str; + } + if (*str == QLatin1Char('.') && pos < maxLen) { + temp[pos++] = '.'; + ++str; + } + while (isDigit(str->unicode()) && pos < maxLen) { + temp[pos++] = str->toLatin1(); + ++str; + } + bool exponent = false; + if ((*str == QLatin1Char('e') || *str == QLatin1Char('E')) && pos < maxLen) { + exponent = true; + temp[pos++] = 'e'; + ++str; + if ((*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) { + temp[pos++] = str->toLatin1(); + ++str; + } + while (isDigit(str->unicode()) && pos < maxLen) { + temp[pos++] = str->toLatin1(); + ++str; + } + } + + temp[pos] = '\0'; + + qreal val; + if (!exponent && pos < 10) { + int ival = 0; + const char *t = temp; + bool neg = false; + if(*t == '-') { + neg = true; + ++t; + } + while(*t && *t != '.') { + ival *= 10; + ival += (*t) - '0'; + ++t; + } + if(*t == '.') { + ++t; + int div = 1; + while(*t) { + ival *= 10; + ival += (*t) - '0'; + div *= 10; + ++t; + } + val = ((qreal)ival)/((qreal)div); + } else { + val = ival; + } + if (neg) + val = -val; + } else { +#if defined(Q_WS_QWS) && !defined(Q_OS_VXWORKS) + if(sizeof(qreal) == sizeof(float)) + val = strtof(temp, 0); + else +#endif + { + bool ok = false; + val = qstrtod(temp, 0, &ok); + } + } + return val; + +} +static qreal toDouble(const QString &str, bool *ok = NULL) +{ + const QChar *c = str.constData(); + qreal res = toDouble(c); + if (ok) { + *ok = ((*c) == QLatin1Char('\0')); + } + return res; +} + +static qreal toDouble(const QStringRef &str, bool *ok = NULL) +{ + const QChar *c = str.constData(); + qreal res = toDouble(c); + if (ok) { + *ok = (c == (str.constData() + str.length())); + } + return res; +} +static inline void parseNumbersArray(const QChar *&str, QVarLengthArray &points) +{ + while (str->isSpace()) + ++str; + while (isDigit(str->unicode()) || + *str == QLatin1Char('-') || *str == QLatin1Char('+') || + *str == QLatin1Char('.')) { + + points.append(toDouble(str)); + + while (str->isSpace()) + ++str; + if (*str == QLatin1Char(',')) + ++str; + + //eat the rest of space + while (str->isSpace()) + ++str; + } +} + +static void pathArcSegment(QPainterPath &path, + qreal xc, qreal yc, + qreal th0, qreal th1, + qreal rx, qreal ry, qreal xAxisRotation) +{ + qreal sinTh, cosTh; + qreal a00, a01, a10, a11; + qreal x1, y1, x2, y2, x3, y3; + qreal t; + qreal thHalf; + + sinTh = qSin(xAxisRotation * (Q_PI / 180.0)); + cosTh = qCos(xAxisRotation * (Q_PI / 180.0)); + + a00 = cosTh * rx; + a01 = -sinTh * ry; + a10 = sinTh * rx; + a11 = cosTh * ry; + + thHalf = 0.5 * (th1 - th0); + t = (8.0 / 3.0) * qSin(thHalf * 0.5) * qSin(thHalf * 0.5) / qSin(thHalf); + x1 = xc + qCos(th0) - t * qSin(th0); + y1 = yc + qSin(th0) + t * qCos(th0); + x3 = xc + qCos(th1); + y3 = yc + qSin(th1); + x2 = x3 + t * qSin(th1); + y2 = y3 - t * qCos(th1); + + path.cubicTo(a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, + a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, + a00 * x3 + a01 * y3, a10 * x3 + a11 * y3); +} + +void QDeclarativeSvgParser::pathArc(QPainterPath &path, + qreal rx, + qreal ry, + qreal x_axis_rotation, + int large_arc_flag, + int sweep_flag, + qreal x, + qreal y, + qreal curx, qreal cury) +{ + qreal sin_th, cos_th; + qreal a00, a01, a10, a11; + qreal x0, y0, x1, y1, xc, yc; + qreal d, sfactor, sfactor_sq; + qreal th0, th1, th_arc; + int i, n_segs; + qreal dx, dy, dx1, dy1, Pr1, Pr2, Px, Py, check; + + rx = qAbs(rx); + ry = qAbs(ry); + + sin_th = qSin(x_axis_rotation * (Q_PI / 180.0)); + cos_th = qCos(x_axis_rotation * (Q_PI / 180.0)); + + dx = (curx - x) / 2.0; + dy = (cury - y) / 2.0; + dx1 = cos_th * dx + sin_th * dy; + dy1 = -sin_th * dx + cos_th * dy; + Pr1 = rx * rx; + Pr2 = ry * ry; + Px = dx1 * dx1; + Py = dy1 * dy1; + /* Spec : check if radii are large enough */ + check = Px / Pr1 + Py / Pr2; + if (check > 1) { + rx = rx * qSqrt(check); + ry = ry * qSqrt(check); + } + + a00 = cos_th / rx; + a01 = sin_th / rx; + a10 = -sin_th / ry; + a11 = cos_th / ry; + x0 = a00 * curx + a01 * cury; + y0 = a10 * curx + a11 * cury; + x1 = a00 * x + a01 * y; + y1 = a10 * x + a11 * y; + /* (x0, y0) is current point in transformed coordinate space. + (x1, y1) is new point in transformed coordinate space. + + The arc fits a unit-radius circle in this space. + */ + d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0); + sfactor_sq = 1.0 / d - 0.25; + if (sfactor_sq < 0) sfactor_sq = 0; + sfactor = qSqrt(sfactor_sq); + if (sweep_flag == large_arc_flag) sfactor = -sfactor; + xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0); + yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0); + /* (xc, yc) is center of the circle. */ + + th0 = qAtan2(y0 - yc, x0 - xc); + th1 = qAtan2(y1 - yc, x1 - xc); + + th_arc = th1 - th0; + if (th_arc < 0 && sweep_flag) + th_arc += 2 * Q_PI; + else if (th_arc > 0 && !sweep_flag) + th_arc -= 2 * Q_PI; + + n_segs = qCeil(qAbs(th_arc / (Q_PI * 0.5 + 0.001))); + + for (i = 0; i < n_segs; i++) { + pathArcSegment(path, xc, yc, + th0 + i * th_arc / n_segs, + th0 + (i + 1) * th_arc / n_segs, + rx, ry, x_axis_rotation); + } +} + + +bool QDeclarativeSvgParser::parsePathDataFast(const QString &dataStr, QPainterPath &path) +{ + qreal x0 = 0, y0 = 0; // starting point + qreal x = 0, y = 0; // current point + char lastMode = 0; + QPointF ctrlPt; + const QChar *str = dataStr.constData(); + const QChar *end = str + dataStr.size(); + + while (str != end) { + while (str->isSpace()) + ++str; + QChar pathElem = *str; + ++str; + QChar endc = *end; + *const_cast(end) = 0; // parseNumbersArray requires 0-termination that QStringRef cannot guarantee + QVarLengthArray arg; + parseNumbersArray(str, arg); + *const_cast(end) = endc; + if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z')) + arg.append(0);//dummy + const qreal *num = arg.constData(); + int count = arg.count(); + while (count > 0) { + qreal offsetX = x; // correction offsets + qreal offsetY = y; // for relative commands + switch (pathElem.unicode()) { + case 'm': { + if (count < 2) { + num++; + count--; + break; + } + x = x0 = num[0] + offsetX; + y = y0 = num[1] + offsetY; + num += 2; + count -= 2; + path.moveTo(x0, y0); + + // As per 1.2 spec 8.3.2 The "moveto" commands + // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands, + // the subsequent pairs shall be treated as implicit 'lineto' commands. + pathElem = QLatin1Char('l'); + } + break; + case 'M': { + if (count < 2) { + num++; + count--; + break; + } + x = x0 = num[0]; + y = y0 = num[1]; + num += 2; + count -= 2; + path.moveTo(x0, y0); + + // As per 1.2 spec 8.3.2 The "moveto" commands + // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands, + // the subsequent pairs shall be treated as implicit 'lineto' commands. + pathElem = QLatin1Char('L'); + } + break; + case 'z': + case 'Z': { + x = x0; + y = y0; + count--; // skip dummy + num++; + path.closeSubpath(); + } + break; + case 'l': { + if (count < 2) { + num++; + count--; + break; + } + x = num[0] + offsetX; + y = num[1] + offsetY; + num += 2; + count -= 2; + path.lineTo(x, y); + + } + break; + case 'L': { + if (count < 2) { + num++; + count--; + break; + } + x = num[0]; + y = num[1]; + num += 2; + count -= 2; + path.lineTo(x, y); + } + break; + case 'h': { + x = num[0] + offsetX; + num++; + count--; + path.lineTo(x, y); + } + break; + case 'H': { + x = num[0]; + num++; + count--; + path.lineTo(x, y); + } + break; + case 'v': { + y = num[0] + offsetY; + num++; + count--; + path.lineTo(x, y); + } + break; + case 'V': { + y = num[0]; + num++; + count--; + path.lineTo(x, y); + } + break; + case 'c': { + if (count < 6) { + num += count; + count = 0; + break; + } + QPointF c1(num[0] + offsetX, num[1] + offsetY); + QPointF c2(num[2] + offsetX, num[3] + offsetY); + QPointF e(num[4] + offsetX, num[5] + offsetY); + num += 6; + count -= 6; + path.cubicTo(c1, c2, e); + ctrlPt = c2; + x = e.x(); + y = e.y(); + break; + } + case 'C': { + if (count < 6) { + num += count; + count = 0; + break; + } + QPointF c1(num[0], num[1]); + QPointF c2(num[2], num[3]); + QPointF e(num[4], num[5]); + num += 6; + count -= 6; + path.cubicTo(c1, c2, e); + ctrlPt = c2; + x = e.x(); + y = e.y(); + break; + } + case 's': { + if (count < 4) { + num += count; + count = 0; + break; + } + QPointF c1; + if (lastMode == 'c' || lastMode == 'C' || + lastMode == 's' || lastMode == 'S') + c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); + else + c1 = QPointF(x, y); + QPointF c2(num[0] + offsetX, num[1] + offsetY); + QPointF e(num[2] + offsetX, num[3] + offsetY); + num += 4; + count -= 4; + path.cubicTo(c1, c2, e); + ctrlPt = c2; + x = e.x(); + y = e.y(); + break; + } + case 'S': { + if (count < 4) { + num += count; + count = 0; + break; + } + QPointF c1; + if (lastMode == 'c' || lastMode == 'C' || + lastMode == 's' || lastMode == 'S') + c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); + else + c1 = QPointF(x, y); + QPointF c2(num[0], num[1]); + QPointF e(num[2], num[3]); + num += 4; + count -= 4; + path.cubicTo(c1, c2, e); + ctrlPt = c2; + x = e.x(); + y = e.y(); + break; + } + case 'q': { + if (count < 4) { + num += count; + count = 0; + break; + } + QPointF c(num[0] + offsetX, num[1] + offsetY); + QPointF e(num[2] + offsetX, num[3] + offsetY); + num += 4; + count -= 4; + path.quadTo(c, e); + ctrlPt = c; + x = e.x(); + y = e.y(); + break; + } + case 'Q': { + if (count < 4) { + num += count; + count = 0; + break; + } + QPointF c(num[0], num[1]); + QPointF e(num[2], num[3]); + num += 4; + count -= 4; + path.quadTo(c, e); + ctrlPt = c; + x = e.x(); + y = e.y(); + break; + } + case 't': { + if (count < 2) { + num += count; + count = 0; + break; + } + QPointF e(num[0] + offsetX, num[1] + offsetY); + num += 2; + count -= 2; + QPointF c; + if (lastMode == 'q' || lastMode == 'Q' || + lastMode == 't' || lastMode == 'T') + c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); + else + c = QPointF(x, y); + path.quadTo(c, e); + ctrlPt = c; + x = e.x(); + y = e.y(); + break; + } + case 'T': { + if (count < 2) { + num += count; + count = 0; + break; + } + QPointF e(num[0], num[1]); + num += 2; + count -= 2; + QPointF c; + if (lastMode == 'q' || lastMode == 'Q' || + lastMode == 't' || lastMode == 'T') + c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); + else + c = QPointF(x, y); + path.quadTo(c, e); + ctrlPt = c; + x = e.x(); + y = e.y(); + break; + } + case 'a': { + if (count < 7) { + num += count; + count = 0; + break; + } + qreal rx = (*num++); + qreal ry = (*num++); + qreal xAxisRotation = (*num++); + qreal largeArcFlag = (*num++); + qreal sweepFlag = (*num++); + qreal ex = (*num++) + offsetX; + qreal ey = (*num++) + offsetY; + count -= 7; + qreal curx = x; + qreal cury = y; + pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag), + int(sweepFlag), ex, ey, curx, cury); + + x = ex; + y = ey; + } + break; + case 'A': { + if (count < 7) { + num += count; + count = 0; + break; + } + qreal rx = (*num++); + qreal ry = (*num++); + qreal xAxisRotation = (*num++); + qreal largeArcFlag = (*num++); + qreal sweepFlag = (*num++); + qreal ex = (*num++); + qreal ey = (*num++); + count -= 7; + qreal curx = x; + qreal cury = y; + pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag), + int(sweepFlag), ex, ey, curx, cury); + + x = ex; + y = ey; + } + break; + default: + return false; + } + lastMode = pathElem.toLatin1(); + } + } + return true; +} + +QT_END_NAMESPACE diff --git a/src/declarative/util/qdeclarativesvgparser_p.h b/src/declarative/util/qdeclarativesvgparser_p.h new file mode 100644 index 0000000..0d7be10 --- /dev/null +++ b/src/declarative/util/qdeclarativesvgparser_p.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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 QtDeclaractive 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$ +** +****************************************************************************/ + +#ifndef QDECLARATIVESVGPARSER_P_H +#define QDECLARATIVESVGPARSER_P_H + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QDeclarativeSvgParser +{ + bool parsePathDataFast(const QString &dataStr, QPainterPath &path); + void pathArc(QPainterPath &path, qreal rx, qreal ry, qreal x_axis_rotation, + int large_arc_flag, int sweep_flag, qreal x, qreal y, qreal curx, + qreal cury); +} + +QT_END_NAMESPACE + +#endif // QDECLARATIVESVGPARSER_P_H diff --git a/src/declarative/util/util.pri b/src/declarative/util/util.pri index 965acf8..ddd7026 100644 --- a/src/declarative/util/util.pri +++ b/src/declarative/util/util.pri @@ -29,7 +29,9 @@ SOURCES += \ $$PWD/qdeclarativelistmodelworkeragent.cpp \ $$PWD/qdeclarativepath.cpp \ $$PWD/qdeclarativechangeset.cpp \ - $$PWD/qlistmodelinterface.cpp + $$PWD/qlistmodelinterface.cpp \ + $$PWD/qdeclarativepathinterpolator.cpp \ + $$PWD/qdeclarativesvgparser.cpp HEADERS += \ $$PWD/qdeclarativeapplication_p.h \ @@ -65,7 +67,9 @@ HEADERS += \ $$PWD/qdeclarativepath_p.h \ $$PWD/qdeclarativepath_p_p.h \ $$PWD/qdeclarativechangeset_p.h \ - $$PWD/qlistmodelinterface_p.h + $$PWD/qlistmodelinterface_p.h \ + $$PWD/qdeclarativepathinterpolator_p.h \ + $$PWD/qdeclarativesvgparser_p.h contains(QT_CONFIG, xmlpatterns) { QT+=xmlpatterns diff --git a/tests/auto/declarative/qdeclarativeanimations/data/pathAnimation.qml b/tests/auto/declarative/qdeclarativeanimations/data/pathAnimation.qml new file mode 100644 index 0000000..d2006a1 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeanimations/data/pathAnimation.qml @@ -0,0 +1,27 @@ +import QtQuick 2.0 + +Rectangle { + width: 400 + height: 400 + + Rectangle { + id: redRect + color: "red" + width: 100; height: 100 + x: 50; y: 50 + } + + PathAnimation { + target: redRect + duration: 100; + path: Path { + startX: 50; startY: 50 + PathCubic { + x: 300; y: 300 + + control1X: 300; control1Y: 50 + control2X: 50; control2Y: 300 + } + } + } +} diff --git a/tests/auto/declarative/qdeclarativeanimations/data/pathInterpolator.qml b/tests/auto/declarative/qdeclarativeanimations/data/pathInterpolator.qml new file mode 100644 index 0000000..0104412 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeanimations/data/pathInterpolator.qml @@ -0,0 +1,13 @@ +import QtQuick 2.0 + +PathInterpolator { + path: Path { + startX: 50; startY: 50 + PathCubic { + x: 300; y: 300 + + control1X: 300; control1Y: 50 + control2X: 50; control2Y: 300 + } + } +} diff --git a/tests/auto/declarative/qdeclarativeanimations/data/pathTransition.qml b/tests/auto/declarative/qdeclarativeanimations/data/pathTransition.qml new file mode 100644 index 0000000..55ffc33 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeanimations/data/pathTransition.qml @@ -0,0 +1,41 @@ +import QtQuick 2.0 + +Rectangle { + width: 800 + height: 800 + + Rectangle { + id: redRect; objectName: "redRect" + color: "red" + width: 50; height: 50 + x: 500; y: 50 + } + + states: State { + name: "moved" + PropertyChanges { + target: redRect + x: 100; y: 700 + } + } + + transitions: Transition { + to: "moved"; reversible: true + PathAnimation { + id: pathAnim + target: redRect + duration: 300 + path: Path { + PathCurve { x: 100; y: 100 } + PathCurve { x: 200; y: 350 } + PathCurve { x: 600; y: 500 } + PathCurve {} + } + } + } + + MouseArea { + anchors.fill: parent + onClicked: parent.state = parent.state == "moved" ? "" : "moved" + } +} diff --git a/tests/auto/declarative/qdeclarativeanimations/tst_qdeclarativeanimations.cpp b/tests/auto/declarative/qdeclarativeanimations/tst_qdeclarativeanimations.cpp index 367de01..df840e9 100644 --- a/tests/auto/declarative/qdeclarativeanimations/tst_qdeclarativeanimations.cpp +++ b/tests/auto/declarative/qdeclarativeanimations/tst_qdeclarativeanimations.cpp @@ -44,6 +44,8 @@ #include #include #include +#include +#include #include #include #include @@ -68,6 +70,8 @@ private slots: void simpleNumber(); void simpleColor(); void simpleRotation(); + void simplePath(); + void pathInterpolator(); void alwaysRunToEnd(); void complete(); void resume(); @@ -77,6 +81,7 @@ private slots: void mixedTypes(); void properties(); void propertiesTransition(); + void pathTransition(); void disabledTransition(); void invalidDuration(); void attached(); @@ -213,6 +218,62 @@ void tst_qdeclarativeanimations::simpleRotation() QCOMPARE(rect.rotation(), qreal(135)); } +void tst_qdeclarativeanimations::simplePath() +{ + QDeclarativeEngine engine; + QDeclarativeComponent c(&engine, QUrl::fromLocalFile(SRCDIR "/data/pathAnimation.qml")); + QSGRectangle *rect = qobject_cast(c.create()); + QVERIFY(rect); + + QSGRectangle *redRect = rect->findChild(); + QVERIFY(redRect); + QSGPathAnimation *pathAnim = rect->findChild(); + QVERIFY(pathAnim); + + pathAnim->start(); + pathAnim->pause(); + + pathAnim->setCurrentTime(50); + QCOMPARE(redRect->x(), qreal(175)); + QCOMPARE(redRect->y(), qreal(175)); + + pathAnim->setCurrentTime(100); + QCOMPARE(redRect->x(), qreal(300)); + QCOMPARE(redRect->y(), qreal(300)); + + //verify animation runs to end + pathAnim->start(); + QCOMPARE(redRect->x(), qreal(50)); + QCOMPARE(redRect->y(), qreal(50)); + QTRY_COMPARE(redRect->x(), qreal(300)); + QCOMPARE(redRect->y(), qreal(300)); +} + +void tst_qdeclarativeanimations::pathInterpolator() +{ + QDeclarativeEngine engine; + QDeclarativeComponent c(&engine, QUrl::fromLocalFile(SRCDIR "/data/pathInterpolator.qml")); + QDeclarativePathInterpolator *interpolator = qobject_cast(c.create()); + QVERIFY(interpolator); + + QCOMPARE(interpolator->progress(), qreal(0)); + QCOMPARE(interpolator->x(), qreal(50)); + QCOMPARE(interpolator->y(), qreal(50)); + QCOMPARE(interpolator->angle(), qreal(0)); + + interpolator->setProgress(.5); + QCOMPARE(interpolator->progress(), qreal(.5)); + QCOMPARE(interpolator->x(), qreal(175)); + QCOMPARE(interpolator->y(), qreal(175)); + QCOMPARE(interpolator->angle(), qreal(270)); + + interpolator->setProgress(1); + QCOMPARE(interpolator->progress(), qreal(1)); + QCOMPARE(interpolator->x(), qreal(300)); + QCOMPARE(interpolator->y(), qreal(300)); + QCOMPARE(interpolator->angle(), qreal(0)); +} + void tst_qdeclarativeanimations::alwaysRunToEnd() { QSGRectangle rect; @@ -577,6 +638,26 @@ void tst_qdeclarativeanimations::propertiesTransition() } +void tst_qdeclarativeanimations::pathTransition() +{ + QDeclarativeEngine engine; + QDeclarativeComponent c(&engine, QUrl::fromLocalFile(SRCDIR "/data/pathTransition.qml")); + QSGRectangle *rect = qobject_cast(c.create()); + QVERIFY(rect); + + QSGRectangle *myRect = rect->findChild("redRect"); + QVERIFY(myRect); + + QSGItemPrivate::get(rect)->setState("moved"); + QTRY_VERIFY(myRect->x() < 500 && myRect->x() > 100 && myRect->y() > 50 && myRect->y() < 700 ); //animation started + QTRY_VERIFY(qFuzzyCompare(myRect->x(), qreal(100)) && qFuzzyCompare(myRect->y(), qreal(700))); + QTest::qWait(100); + + QSGItemPrivate::get(rect)->setState(""); + QTRY_VERIFY(myRect->x() < 500 && myRect->x() > 100 && myRect->y() > 50 && myRect->y() < 700 ); //animation started + QTRY_VERIFY(qFuzzyCompare(myRect->x(), qreal(500)) && qFuzzyCompare(myRect->y(), qreal(50))); +} + void tst_qdeclarativeanimations::disabledTransition() { QDeclarativeEngine engine; diff --git a/tests/auto/declarative/qdeclarativepath/data/arc.qml b/tests/auto/declarative/qdeclarativepath/data/arc.qml new file mode 100644 index 0000000..000221c --- /dev/null +++ b/tests/auto/declarative/qdeclarativepath/data/arc.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 + +Path { + startX: 0; startY: 0 + + PathArc { + x: 100; y: 100 + radiusX: 100; radiusY: 100 + direction: PathArc.Clockwise + } +} diff --git a/tests/auto/declarative/qdeclarativepath/data/curve.qml b/tests/auto/declarative/qdeclarativepath/data/curve.qml new file mode 100644 index 0000000..c571186 --- /dev/null +++ b/tests/auto/declarative/qdeclarativepath/data/curve.qml @@ -0,0 +1,9 @@ +import QtQuick 2.0 + +Path { + startX: 0; startY: 0 + + PathCurve { x: 100; y: 50 } + PathCurve { x: 50; y: 100 } + PathCurve { x: 100; y: 150 } +} diff --git a/tests/auto/declarative/qdeclarativepath/data/svg.qml b/tests/auto/declarative/qdeclarativepath/data/svg.qml new file mode 100644 index 0000000..cec0f75 --- /dev/null +++ b/tests/auto/declarative/qdeclarativepath/data/svg.qml @@ -0,0 +1,5 @@ +import QtQuick 2.0 + +Path { + PathSvg { path: "M200,300 Q400,50 600,300 T1000,300" } +} diff --git a/tests/auto/declarative/qdeclarativepath/qdeclarativepath.pro b/tests/auto/declarative/qdeclarativepath/qdeclarativepath.pro new file mode 100644 index 0000000..d6fe1cf --- /dev/null +++ b/tests/auto/declarative/qdeclarativepath/qdeclarativepath.pro @@ -0,0 +1,17 @@ +load(qttest_p4) +contains(QT_CONFIG,declarative): QT += declarative +macx:CONFIG -= app_bundle + +SOURCES += tst_qdeclarativepath.cpp + +symbian: { + importFiles.files = data + importFiles.path = . + DEPLOYMENT += importFiles +} else { + DEFINES += SRCDIR=\\\"$$PWD\\\" +} + +CONFIG += parallel_test + +QT += core-private gui-private v8-private declarative-private diff --git a/tests/auto/declarative/qdeclarativepath/tst_qdeclarativepath.cpp b/tests/auto/declarative/qdeclarativepath/tst_qdeclarativepath.cpp new file mode 100644 index 0000000..d8a539e --- /dev/null +++ b/tests/auto/declarative/qdeclarativepath/tst_qdeclarativepath.cpp @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** 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 test suite 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 +#include +#include +#include + +#include "../../../shared/util.h" + +#ifdef Q_OS_SYMBIAN +// In Symbian OS test data is located in applications private dir +#define SRCDIR "." +#endif + +class tst_QDeclarativePath : public QObject +{ + Q_OBJECT +public: + tst_QDeclarativePath() {} + +private slots: + void arc(); + void catmullromCurve(); + void svg(); +}; + +void tst_QDeclarativePath::arc() +{ + QDeclarativeEngine engine; + QDeclarativeComponent c(&engine, QUrl::fromLocalFile(SRCDIR "/data/arc.qml")); + QDeclarativePath *obj = qobject_cast(c.create()); + QVERIFY(obj != 0); + + QCOMPARE(obj->startX(), 0.); + QCOMPARE(obj->startY(), 0.); + + QDeclarativeListReference list(obj, "pathElements"); + QCOMPARE(list.count(), 1); + + QDeclarativePathArc* arc = qobject_cast(list.at(0)); + QVERIFY(arc != 0); + QCOMPARE(arc->x(), 100.); + QCOMPARE(arc->y(), 100.); + QCOMPARE(arc->radiusX(), 100.); + QCOMPARE(arc->radiusY(), 100.); + QCOMPARE(arc->useLargeArc(), false); + QCOMPARE(arc->direction(), QDeclarativePathArc::Clockwise); + + QPainterPath path = obj->path(); + QVERIFY(path != QPainterPath()); + + QPointF pos = obj->pointAt(0); + QCOMPARE(pos, QPointF(0,0)); + pos = obj->pointAt(.25); + QCOMPARE(pos.toPoint(), QPoint(39,8)); //fuzzy compare + pos = obj->pointAt(.75); + QCOMPARE(pos.toPoint(), QPoint(92,61)); //fuzzy compare + pos = obj->pointAt(1); + QCOMPARE(pos, QPointF(100,100)); +} + +void tst_QDeclarativePath::catmullromCurve() +{ + QDeclarativeEngine engine; + QDeclarativeComponent c(&engine, QUrl::fromLocalFile(SRCDIR "/data/curve.qml")); + QDeclarativePath *obj = qobject_cast(c.create()); + QVERIFY(obj != 0); + + QCOMPARE(obj->startX(), 0.); + QCOMPARE(obj->startY(), 0.); + + QDeclarativeListReference list(obj, "pathElements"); + QCOMPARE(list.count(), 3); + + QDeclarativePathCatmullRomCurve* arc = qobject_cast(list.at(0)); +// QVERIFY(arc != 0); +// QCOMPARE(arc->x(), 100.); +// QCOMPARE(arc->y(), 100.); +// QCOMPARE(arc->radiusX(), 100.); +// QCOMPARE(arc->radiusY(), 100.); +// QCOMPARE(arc->useLargeArc(), false); +// QCOMPARE(arc->direction(), QDeclarativePathArc::Clockwise); + + QPainterPath path = obj->path(); + QVERIFY(path != QPainterPath()); + + QPointF pos = obj->pointAt(0); + QCOMPARE(pos, QPointF(0,0)); + pos = obj->pointAt(.25); + QCOMPARE(pos.toPoint(), QPoint(63,26)); //fuzzy compare + pos = obj->pointAt(.75); + QCOMPARE(pos.toPoint(), QPoint(51,105)); //fuzzy compare + pos = obj->pointAt(1); + QCOMPARE(pos, QPointF(100,150)); +} + +void tst_QDeclarativePath::svg() +{ + QDeclarativeEngine engine; + QDeclarativeComponent c(&engine, QUrl::fromLocalFile(SRCDIR "/data/svg.qml")); + QDeclarativePath *obj = qobject_cast(c.create()); + QVERIFY(obj != 0); + + QCOMPARE(obj->startX(), 0.); + QCOMPARE(obj->startY(), 0.); + + QDeclarativeListReference list(obj, "pathElements"); + QCOMPARE(list.count(), 1); + + QDeclarativePathSvg* svg = qobject_cast(list.at(0)); + QVERIFY(svg != 0); + QCOMPARE(svg->path(), QLatin1String("M200,300 Q400,50 600,300 T1000,300")); + + QPainterPath path = obj->path(); + QVERIFY(path != QPainterPath()); + + QPointF pos = obj->pointAt(0); + QCOMPARE(pos, QPointF(200,300)); + pos = obj->pointAt(.25); + QCOMPARE(pos.toPoint(), QPoint(400,175)); //fuzzy compare + pos = obj->pointAt(.75); + QCOMPARE(pos.toPoint(), QPoint(800,425)); //fuzzy compare + pos = obj->pointAt(1); + QCOMPARE(pos, QPointF(1000,300)); +} + + +QTEST_MAIN(tst_QDeclarativePath) + +#include "tst_qdeclarativepath.moc" -- 1.7.2.5