diff options
author | Eirik Aavitsland <[email protected]> | 2024-05-16 16:59:54 +0200 |
---|---|---|
committer | Eirik Aavitsland <[email protected]> | 2024-05-26 18:04:42 +0200 |
commit | 22272aefeb4755cbcf78e91e450c44caab5fbfa6 (patch) | |
tree | 89201c81b1527420f34caa55f921e696158b5941 | |
parent | 2818506ba357e7912af20b5a8d24607e38f2dfb4 (diff) |
Add PathRectangle, a PathElement for optionally rounded rectangles
This new path element type is particularly useful for QuickShapes,
where it can be used to mimic a Rectangle item. It provides the same
API for specifying common and/or individual corner radii.
[ChangeLog][QtQuick] Add PathRectangle, a PathElement for optionally rounded rectangles
Fixes: QTBUG-123913
Change-Id: Ia0de252f70c665bd70f63aa31e3010584cc9cd8c
Reviewed-by: Eskil Abrahamsen Blomfeldt <[email protected]>
-rw-r--r-- | examples/quick/quickshapes/shapes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | examples/quick/quickshapes/shapes/fillTransform.qml | 7 | ||||
-rw-r--r-- | examples/quick/quickshapes/shapes/rectangle.qml | 57 | ||||
-rw-r--r-- | examples/quick/quickshapes/shapes/shapegallery.qml | 4 | ||||
-rw-r--r-- | examples/quick/quickshapes/shapes/shapes.qrc | 1 | ||||
-rw-r--r-- | src/quick/util/qquickpath.cpp | 280 | ||||
-rw-r--r-- | src/quick/util/qquickpath_p.h | 78 | ||||
-rw-r--r-- | tests/auto/quick/qquickpath/tst_qquickpath.cpp | 103 | ||||
-rw-r--r-- | tests/baseline/scenegraph/data/shape/shape_rectangle.qml | 151 |
9 files changed, 670 insertions, 12 deletions
diff --git a/examples/quick/quickshapes/shapes/CMakeLists.txt b/examples/quick/quickshapes/shapes/CMakeLists.txt index 4618309ca0..32a0723926 100644 --- a/examples/quick/quickshapes/shapes/CMakeLists.txt +++ b/examples/quick/quickshapes/shapes/CMakeLists.txt @@ -53,6 +53,7 @@ qt_add_qml_module(shapesexample "tiger.qml" "zoomtiger.qml" "fillTransform.qml" + "rectangle.qml" ) install(TARGETS shapesexample diff --git a/examples/quick/quickshapes/shapes/fillTransform.qml b/examples/quick/quickshapes/shapes/fillTransform.qml index f2f39d2ea6..f09a5d63e1 100644 --- a/examples/quick/quickshapes/shapes/fillTransform.qml +++ b/examples/quick/quickshapes/shapes/fillTransform.qml @@ -40,12 +40,7 @@ Rectangle { fillTransform: PlanarTransform.fromScale(fillScale, 1, grad.centerX, grad.centerY) - startX: 0 - startY: 0 - PathLine { relativeX: 200; relativeY: 0 } - PathLine { relativeX: 0; relativeY: 100 } - PathLine { relativeX: -200; relativeY: 0 } - PathLine { relativeX: 0; relativeY: -100 } + PathRectangle { width: 200; height: 100 } } } } diff --git a/examples/quick/quickshapes/shapes/rectangle.qml b/examples/quick/quickshapes/shapes/rectangle.qml new file mode 100644 index 0000000000..afac8850ff --- /dev/null +++ b/examples/quick/quickshapes/shapes/rectangle.qml @@ -0,0 +1,57 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Shapes + +Rectangle { + color: "lightGray" + width: 256 + height: 256 + + Shape { + id: myShape + anchors.fill: parent + + ShapePath { + id: myPath + strokeColor: "green" + strokeWidth: myShape.width / 25 + joinStyle: ShapePath.MiterJoin + fillGradient: LinearGradient { + x2: myShape.width + y2: x2 + GradientStop { position: 0.1; color: "yellow" } + GradientStop { position: 0.45; color: "lightyellow" } + GradientStop { position: 0.7; color: "yellow" } + } + + property real animRadius: 0 + SequentialAnimation on animRadius { + loops: Animation.Infinite + NumberAnimation { + from: 0 + to: 100 + duration: 3000 + } + NumberAnimation { + from: 100 + to: 0 + duration: 3000 + } + PauseAnimation { + duration: 1000 + } + } + + PathRectangle { + x: myShape.width / 5 + y: x + width: myShape.width - 2 * x + height: width + topLeftRadius: myPath.animRadius + bottomRightRadius: myPath.animRadius + } + } + } +} diff --git a/examples/quick/quickshapes/shapes/shapegallery.qml b/examples/quick/quickshapes/shapes/shapegallery.qml index eb7c204c16..ece85c38f5 100644 --- a/examples/quick/quickshapes/shapes/shapegallery.qml +++ b/examples/quick/quickshapes/shapes/shapegallery.qml @@ -150,6 +150,10 @@ Rectangle { name: qsTr("Fill transform") shapeUrl: "fillTransform.qml" } + ListElement { + name: qsTr("Shape Rectangle") + shapeUrl: "rectangle.qml" + } } } } diff --git a/examples/quick/quickshapes/shapes/shapes.qrc b/examples/quick/quickshapes/shapes/shapes.qrc index ea25f9d43d..afbc7fcf5e 100644 --- a/examples/quick/quickshapes/shapes/shapes.qrc +++ b/examples/quick/quickshapes/shapes/shapes.qrc @@ -25,5 +25,6 @@ <file>text.qml</file> <file>zoomtiger.qml</file> <file>fillTransform.qml</file> + <file>rectangle.qml</file> </qresource> </RCC> diff --git a/src/quick/util/qquickpath.cpp b/src/quick/util/qquickpath.cpp index 8995c64d0b..5cd158b191 100644 --- a/src/quick/util/qquickpath.cpp +++ b/src/quick/util/qquickpath.cpp @@ -25,7 +25,7 @@ QT_BEGIN_NAMESPACE be instantiated. \sa Path, PathAttribute, PathPercent, PathLine, PathPolyline, PathQuad, PathCubic, PathArc, - PathAngleArc, PathCurve, PathSvg + PathAngleArc, PathCurve, PathSvg, PathRectangle */ /*! @@ -70,7 +70,7 @@ QT_BEGIN_NAMESPACE \li Yes \li Yes \row - \li PathMultiLine + \li PathMultiline \li Yes \li Yes \li Yes @@ -100,6 +100,11 @@ QT_BEGIN_NAMESPACE \li Yes \li Yes \row + \li PathRectangle + \li Yes + \li Yes + \li Yes + \row \li PathAttribute \li Yes \li N/A @@ -119,7 +124,7 @@ QT_BEGIN_NAMESPACE \note Path is a non-visual type; it does not display anything on its own. To draw a path, use \l Shape. - \sa PathView, Shape, PathAttribute, PathPercent, PathLine, PathPolyline, PathMove, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg + \sa PathView, Shape, PathAttribute, PathPercent, PathLine, PathPolyline, PathMove, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathRectangle */ QQuickPath::QQuickPath(QObject *parent) : QObject(*(new QQuickPathPrivate), parent) @@ -210,6 +215,7 @@ bool QQuickPath::isClosed() const \li \l PathArc - an arc to a given position with a radius. \li \l PathAngleArc - an arc specified by center point, radii, and angles. \li \l PathSvg - a path specified as an SVG path data string. + \li \l PathRectangle - a rectangle with a given position and size \li \l PathCurve - a point on a Catmull-Rom curve. \li \l PathAttribute - an attribute at a given position in the path. \li \l PathPercent - a way to spread out items along various segments of the path. @@ -1191,7 +1197,7 @@ void QQuickPathAttribute::setValue(qreal value) } \endqml - \sa Path, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathMove, PathPolyline + \sa Path, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg, PathMove, PathPolyline, PathRectangle */ /*! @@ -1467,7 +1473,7 @@ void QQuickPathQuad::addToPath(QPainterPath &path, const QQuickPathData &data) \endqml \endtable - \sa Path, PathQuad, PathLine, PathArc, PathAngleArc, PathCurve, PathSvg + \sa Path, PathQuad, PathLine, PathArc, PathAngleArc, PathCurve, PathSvg, PathRectangle */ /*! @@ -2035,7 +2041,7 @@ void QQuickPathArc::addToPath(QPainterPath &path, const QQuickPathData &data) to work as part of a larger path (specifying start and end), PathAngleArc is designed to make a path where the arc is primary (such as a circular progress indicator) more intuitive. - \sa Path, PathLine, PathQuad, PathCubic, PathCurve, PathSvg, PathArc + \sa Path, PathLine, PathQuad, PathCubic, PathCurve, PathSvg, PathArc, PathRectangle */ /*! @@ -2252,6 +2258,268 @@ void QQuickPathSvg::addToPath(QPainterPath &path, const QQuickPathData &) /****************************************************************************/ /*! + \qmltype PathRectangle + \instantiates QQuickPathRectangle + \inqmlmodule QtQuick + \ingroup qtquick-animation-paths + \brief Defines a rectangle with optionally rounded corners. + \since QtQuick 6.8 + + PathRectangle provides an easy way to specify a rectangle, optionally with rounded corners. The + API corresponds to that of the \l Rectangle item. + + \sa Path, PathLine, PathQuad, PathCubic, PathArc, PathAngleArc, PathCurve, PathSvg +*/ + +/*! + \qmlproperty real QtQuick::PathRectangle::x + \qmlproperty real QtQuick::PathRectangle::y + + Defines the top left corner of the rectangle. + + Unless that corner is rounded, this will also be the start and end point of the path. + + \sa relativeX, relativeY +*/ + +/*! + \qmlproperty real QtQuick::PathRectangle::relativeX + \qmlproperty real QtQuick::PathRectangle::relativeY + + Defines the top left corner of the rectangle relative to the path's start point. + + If both a relative and absolute end position are specified for a single axis, the relative + position will be used. + + Relative and absolute positions can be mixed, for example it is valid to set a relative x + and an absolute y. + + \sa x, y +*/ + +/*! + \qmlproperty real QtQuick::PathRectangle::width + \qmlproperty real QtQuick::PathRectangle::height + + Defines the width and height of the rectangle. + + \sa x, y +*/ + +qreal QQuickPathRectangle::width() const +{ + return _width; +} + +void QQuickPathRectangle::setWidth(qreal width) +{ + if (_width == width) + return; + + _width = width; + emit widthChanged(); + emit changed(); +} + +qreal QQuickPathRectangle::height() const +{ + return _height; +} + +void QQuickPathRectangle::setHeight(qreal height) +{ + if (_height == height) + return; + + _height = height; + emit heightChanged(); + emit changed(); +} + +/*! + \qmlproperty real QtQuick::PathRectangle::strokeAdjustment + + This property defines the stroke width adjustment to the rectangle coordinates. + + When used in a \l ShapePath with stroking enabled, the actual stroked rectangle will by default + extend beyond the defined rectangle by half the stroke width on all sides. This is the expected + behavior since the path defines the midpoint line of the stroking, and corresponds to QPainter + and SVG rendering. + + If one instead wants the defined rectangle to be the outer edge of the stroked rectangle, like + a \l Rectangle item with a border, one can set strokeAdjustment to the stroke width. This will + effectively shift all edges inwards by half the stroke width. Like in the following example: + + \qml + ShapePath { + id: myRec + fillColor: "white" + strokeColor: "black" + strokeWidth: 16 + joinStyle: ShapePath.MiterJoin + + PathRectangle { x: 10; y: 10; width: 200; height: 100; strokeAdjustment: myRec.strokeWidth } + } + \endqml +*/ + +qreal QQuickPathRectangle::strokeAdjustment() const +{ + return _strokeAdjustment; +} + +void QQuickPathRectangle::setStrokeAdjustment(qreal newStrokeAdjustment) +{ + if (_strokeAdjustment == newStrokeAdjustment) + return; + _strokeAdjustment = newStrokeAdjustment; + emit strokeAdjustmentChanged(); + emit changed(); +} + +/*! + \qmlproperty real QtQuick::PathRectangle::radius + + This property defines the corner radius used to define a rounded rectangle. + + If radius is a positive value, the rectangle path will be defined as a rounded rectangle, + otherwise it will be defined as a normal rectangle. + + This property may be overridden by the individual corner radius properties. + + \sa topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius +*/ + +qreal QQuickPathRectangle::radius() const +{ + return _extra.isAllocated() ? _extra->radius : 0; +} + +void QQuickPathRectangle::setRadius(qreal newRadius) +{ + if (_extra.value().radius == newRadius) + return; + _extra->radius = newRadius; + emit radiusChanged(); + if (_extra->cornerRadii[Qt::TopLeftCorner] < 0) + emit topLeftRadiusChanged(); + if (_extra->cornerRadii[Qt::TopRightCorner] < 0) + emit topRightRadiusChanged(); + if (_extra->cornerRadii[Qt::BottomLeftCorner] < 0) + emit bottomLeftRadiusChanged(); + if (_extra->cornerRadii[Qt::BottomRightCorner] < 0) + emit bottomRightRadiusChanged(); + emit changed(); +} + +/*! + \qmlproperty real QtQuick::PathRectangle::topLeftRadius + \qmlproperty real QtQuick::PathRectangle::topRightRadius + \qmlproperty real QtQuick::PathRectangle::bottomLeftRadius + \qmlproperty real QtQuick::PathRectangle::bottomRightRadius + + If set, these properties define the individual corner radii. A zero value defines that corner + to be sharp, while a positive value defines it to be rounded. When unset, the value of \l + radius is used instead. + + These properties are unset by default. Assign \c undefined to them to return them to the unset + state. + + \sa radius +*/ + +qreal QQuickPathRectangle::cornerRadius(Qt::Corner corner) const +{ + if (_extra.isAllocated()) + return _extra->cornerRadii[corner] < 0 ? _extra->radius : _extra->cornerRadii[corner]; + else + return 0; +} + +void QQuickPathRectangle::setCornerRadius(Qt::Corner corner, qreal newCornerRadius) +{ + if (newCornerRadius < 0 || _extra.value().cornerRadii[corner] == newCornerRadius) + return; + _extra->cornerRadii[corner] = newCornerRadius; + emitCornerRadiusChanged(corner); +} + +void QQuickPathRectangle::resetCornerRadius(Qt::Corner corner) +{ + if (!_extra.isAllocated() || _extra->cornerRadii[corner] < 0) + return; + _extra->cornerRadii[corner] = -1; + emitCornerRadiusChanged(corner); +} + +void QQuickPathRectangle::emitCornerRadiusChanged(Qt::Corner corner) +{ + switch (corner) { + case Qt::TopLeftCorner: + emit topLeftRadiusChanged(); + break; + case Qt::TopRightCorner: + emit topRightRadiusChanged(); + break; + case Qt::BottomLeftCorner: + emit bottomLeftRadiusChanged(); + break; + case Qt::BottomRightCorner: + emit bottomRightRadiusChanged(); + break; + } + emit changed(); +} + +void QQuickPathRectangle::addToPath(QPainterPath &path, const QQuickPathData &data) +{ + QRectF rect(positionForCurve(data, path.currentPosition()), QSizeF(_width, _height)); + + qreal halfStroke = _strokeAdjustment * 0.5; + rect.adjust(halfStroke, halfStroke, -halfStroke, -halfStroke); + if (rect.isEmpty()) + return; + + if (!_extra.isAllocated()) { + // No rounded corners + path.addRect(rect); + } else { + // Radii must not exceed half of the width or half of the height + const qreal maxDiameter = qMin(rect.width(), rect.height()); + const qreal generalDiameter = qMax(qreal(0), qMin(maxDiameter, 2 * _extra->radius)); + auto effectiveDiameter = [&](Qt::Corner corner) { + qreal radius = _extra->cornerRadii[corner]; + return radius < 0 ? generalDiameter : qMin(maxDiameter, 2 * radius); + }; + const qreal diamTL = effectiveDiameter(Qt::TopLeftCorner); + const qreal diamTR = effectiveDiameter(Qt::TopRightCorner); + const qreal diamBL = effectiveDiameter(Qt::BottomLeftCorner); + const qreal diamBR = effectiveDiameter(Qt::BottomRightCorner); + + path.moveTo(rect.left() + diamTL * 0.5, rect.top()); + if (diamTR) + path.arcTo(QRectF(QPointF(rect.right() - diamTR, rect.top()), QSizeF(diamTR, diamTR)), 90, -90); + else + path.lineTo(rect.topRight()); + if (diamBR) + path.arcTo(QRectF(QPointF(rect.right() - diamBR, rect.bottom() - diamBR), QSizeF(diamBR, diamBR)), 0, -90); + else + path.lineTo(rect.bottomRight()); + if (diamBL) + path.arcTo(QRectF(QPointF(rect.left(), rect.bottom() - diamBL), QSizeF(diamBL, diamBL)), 270, -90); + else + path.lineTo(rect.bottomLeft()); + if (diamTL) + path.arcTo(QRectF(rect.topLeft(), QSizeF(diamTL, diamTL)), 180, -90); + else + path.lineTo(rect.topLeft()); + path.closeSubpath(); + } +} + +/****************************************************************************/ + +/*! \qmltype PathPercent \instantiates QQuickPathPercent \inqmlmodule QtQuick diff --git a/src/quick/util/qquickpath_p.h b/src/quick/util/qquickpath_p.h index 55400d2ddd..173bdd2fea 100644 --- a/src/quick/util/qquickpath_p.h +++ b/src/quick/util/qquickpath_p.h @@ -22,6 +22,7 @@ QT_REQUIRE_CONFIG(quick_path); #include <qqml.h> #include <private/qqmlnullablevalue_p.h> +#include <private/qlazilyallocated_p.h> #include <private/qbezier_p.h> #include <private/qtquickglobal_p.h> @@ -397,6 +398,83 @@ private: QString _path; }; +class Q_QUICK_EXPORT QQuickPathRectangle : public QQuickCurve +{ + Q_OBJECT + + Q_PROPERTY(qreal width READ width WRITE setWidth NOTIFY widthChanged FINAL) + Q_PROPERTY(qreal height READ height WRITE setHeight NOTIFY heightChanged FINAL) + Q_PROPERTY(qreal strokeAdjustment READ strokeAdjustment WRITE setStrokeAdjustment NOTIFY strokeAdjustmentChanged FINAL) + Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL) + Q_PROPERTY(qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius RESET resetTopLeftRadius NOTIFY topLeftRadiusChanged FINAL) + Q_PROPERTY(qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY topRightRadiusChanged RESET resetTopRightRadius FINAL) + Q_PROPERTY(qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius NOTIFY bottomLeftRadiusChanged RESET resetBottomLeftRadius FINAL) + Q_PROPERTY(qreal bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius NOTIFY bottomRightRadiusChanged RESET resetBottomRightRadius FINAL) + + QML_NAMED_ELEMENT(PathRectangle) + QML_ADDED_IN_VERSION(6, 8) +public: + QQuickPathRectangle(QObject *parent = nullptr) : QQuickCurve(parent) {} + + qreal width() const; + void setWidth(qreal width); + + qreal height() const; + void setHeight(qreal height); + + qreal strokeAdjustment() const; + void setStrokeAdjustment(qreal newStrokeAdjustment); + + qreal radius() const; + void setRadius(qreal newRadius); + + qreal topLeftRadius() const { return cornerRadius(Qt::TopLeftCorner); } + void setTopLeftRadius(qreal radius) { setCornerRadius(Qt::TopLeftCorner, radius); } + void resetTopLeftRadius() { resetCornerRadius(Qt::TopLeftCorner); } + + qreal topRightRadius() const { return cornerRadius(Qt::TopRightCorner); } + void setTopRightRadius(qreal radius) { setCornerRadius(Qt::TopRightCorner, radius); } + void resetTopRightRadius() { resetCornerRadius(Qt::TopRightCorner); } + + qreal bottomLeftRadius() const { return cornerRadius(Qt::BottomLeftCorner); } + void setBottomLeftRadius(qreal radius) { setCornerRadius(Qt::BottomLeftCorner, radius); } + void resetBottomLeftRadius() { resetCornerRadius(Qt::BottomLeftCorner); } + + qreal bottomRightRadius() const { return cornerRadius(Qt::BottomRightCorner); } + void setBottomRightRadius(qreal radius) { setCornerRadius(Qt::BottomRightCorner, radius); } + void resetBottomRightRadius() { resetCornerRadius(Qt::BottomRightCorner); } + + qreal cornerRadius(Qt::Corner corner) const; + void setCornerRadius(Qt::Corner corner, qreal newCornerRadius); + void resetCornerRadius(Qt::Corner corner); + + void addToPath(QPainterPath &path, const QQuickPathData &) override; + +Q_SIGNALS: + void widthChanged(); + void heightChanged(); + void strokeAdjustmentChanged(); + void radiusChanged(); + void topLeftRadiusChanged(); + void topRightRadiusChanged(); + void bottomLeftRadiusChanged(); + void bottomRightRadiusChanged(); + +private: + void emitCornerRadiusChanged(Qt::Corner corner); + + qreal _width = 0; + qreal _height = 0; + qreal _strokeAdjustment = 0; + struct ExtraData + { + ExtraData() { std::fill_n(cornerRadii, 4, -1); } + qreal radius = 0; + qreal cornerRadii[4]; + }; + QLazilyAllocated<ExtraData> _extra; +}; + class Q_QUICK_EXPORT QQuickPathPercent : public QQuickPathElement { Q_OBJECT diff --git a/tests/auto/quick/qquickpath/tst_qquickpath.cpp b/tests/auto/quick/qquickpath/tst_qquickpath.cpp index 9fd4d9ec8a..3e02f63ea5 100644 --- a/tests/auto/quick/qquickpath/tst_qquickpath.cpp +++ b/tests/auto/quick/qquickpath/tst_qquickpath.cpp @@ -5,6 +5,7 @@ #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> #include <QtQuick/private/qquickpath_p.h> +#include <QtQuick/private/qquickrectangle_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> @@ -21,6 +22,9 @@ private slots: void closedCatmullRomCurve(); void svg(); void line(); + void rectangle_data(); + void rectangle(); + void rectangleRadii(); private: void arc(QSizeF scale); @@ -29,6 +33,7 @@ private: void closedCatmullRomCurve(QSizeF scale, const QVector<QPointF> &points); void svg(QSizeF scale); void line(QSizeF scale); + void rectangle(const QQuickPath *path, const QRectF &rect); }; static void compare(const QPointF &point, const QSizeF &scale, int line, double x, double y) @@ -318,6 +323,104 @@ void tst_QuickPath::line() line(QSizeF(7.23,7.23)); } +void tst_QuickPath::rectangle_data() +{ + QTest::addColumn<QByteArray>("pathqml"); + QTest::addColumn<QRectF>("rect"); + + QTest::newRow("basic") << QByteArray("PathRectangle { width: 100; height: 100 }\n") + << QRectF(0, 0, 100, 100); + + QTest::newRow("relative") << QByteArray("startX: -50; startY: -100\nPathRectangle {" + "relativeX: 100.2; relativeY: 200.3;" + "width: 10.5; height: 10.5 }\n") + << QRectF(50.2, 100.3, 10.5, 10.5); + + QTest::newRow("stroke") << QByteArray("PathRectangle { x: 5; y: 10; width: 100; height: 100;" + "strokeAdjustment: 20 }\n") + << QRectF(5, 10, 100, 100).adjusted(10, 10, -10, -10); +} + +void tst_QuickPath::rectangle(const QQuickPath *path, const QRectF &rect) +{ + QCOMPARE(path->pointAtPercent(0), rect.topLeft()); + QCOMPARE(path->pointAtPercent(1), rect.topLeft()); + QCOMPARE(path->pointAtPercent(1.0 / 8), QPointF(rect.center().x(), rect.top())); + QCOMPARE(path->pointAtPercent(3.0 / 8), QPointF(rect.right(), rect.center().y())); + QCOMPARE(path->pointAtPercent(5.0 / 8), QPointF(rect.center().x(), rect.bottom())); + QCOMPARE(path->pointAtPercent(7.0 / 8), QPointF(rect.left(), rect.center().y())); +} + +void tst_QuickPath::rectangle() +{ + QFETCH(QByteArray, pathqml); + QFETCH(QRectF, rect); + + QQmlEngine engine; + QQmlComponent c1(&engine); + c1.setData("import QtQuick\nPath {\n" + pathqml + "}", QUrl()); + QScopedPointer<QObject> o1(c1.create()); + QQuickPath *path = qobject_cast<QQuickPath *>(o1.data()); + QVERIFY(path); + QCOMPARE(path->pointAtPercent(0), rect.topLeft()); + QCOMPARE(path->pointAtPercent(1), rect.topLeft()); + QCOMPARE(path->pointAtPercent(1.0 / 8), QPointF(rect.center().x(), rect.top())); + QCOMPARE(path->pointAtPercent(3.0 / 8), QPointF(rect.right(), rect.center().y())); + QCOMPARE(path->pointAtPercent(5.0 / 8), QPointF(rect.center().x(), rect.bottom())); + QCOMPARE(path->pointAtPercent(7.0 / 8), QPointF(rect.left(), rect.center().y())); +} + +#define COMPARE_RADII(P, Q) \ + QCOMPARE(P.radius(), Q->radius()); \ + QCOMPARE(P.topLeftRadius(), Q->topLeftRadius()); \ + QCOMPARE(P.topRightRadius(), Q->topRightRadius()); \ + QCOMPARE(P.bottomLeftRadius(), Q->bottomLeftRadius()); \ + QCOMPARE(P.bottomRightRadius(), Q->bottomRightRadius()); + +void tst_QuickPath::rectangleRadii() +{ + // Test that the radius logic of PathRectangle is the same as Rectangle's + QQmlEngine engine; + QQmlComponent c1(&engine); + c1.setData("import QtQuick\n" + "Rectangle { x: 10; y: 20; width: 30; height: 40\n" + "}", + QUrl()); + QScopedPointer<QObject> o1(c1.create()); + QQuickRectangle *quickRectangle = qobject_cast<QQuickRectangle *>(o1.data()); + QVERIFY(quickRectangle); + QQuickPathRectangle pathRectangle; + pathRectangle.setX(quickRectangle->x()); + pathRectangle.setY(quickRectangle->y()); + pathRectangle.setWidth(quickRectangle->width()); + pathRectangle.setHeight(quickRectangle->height()); + COMPARE_RADII(pathRectangle, quickRectangle); + pathRectangle.setRadius(5); + quickRectangle->setRadius(5); + COMPARE_RADII(pathRectangle, quickRectangle); + pathRectangle.setBottomLeftRadius(15); + quickRectangle->setBottomLeftRadius(15); + COMPARE_RADII(pathRectangle, quickRectangle); + pathRectangle.setRadius(-5); + quickRectangle->setRadius(-5); + COMPARE_RADII(pathRectangle, quickRectangle); + pathRectangle.setRadius(0); + quickRectangle->setRadius(0); + COMPARE_RADII(pathRectangle, quickRectangle); + pathRectangle.setTopLeftRadius(-7); + quickRectangle->setTopLeftRadius(-7); + COMPARE_RADII(pathRectangle, quickRectangle); + pathRectangle.setRadius(4); + quickRectangle->setRadius(4); + pathRectangle.resetBottomLeftRadius(); + quickRectangle->resetBottomLeftRadius(); + pathRectangle.setTopRightRadius(0); + quickRectangle->setTopRightRadius(0); + pathRectangle.setTopLeftRadius(200); + quickRectangle->setTopLeftRadius(200); + COMPARE_RADII(pathRectangle, quickRectangle); +} + QTEST_MAIN(tst_QuickPath) #include "tst_qquickpath.moc" diff --git a/tests/baseline/scenegraph/data/shape/shape_rectangle.qml b/tests/baseline/scenegraph/data/shape/shape_rectangle.qml new file mode 100644 index 0000000000..50e2895f85 --- /dev/null +++ b/tests/baseline/scenegraph/data/shape/shape_rectangle.qml @@ -0,0 +1,151 @@ +import QtQuick +import QtQuick.Shapes + +Rectangle { + width: 320 + height: 480 + color: "lightgray" + + ListModel { + id: renderers + ListElement { renderer: Shape.GeometryRenderer } + ListElement { renderer: Shape.CurveRenderer } + } + + Row { + padding: 10 + Repeater { + model: renderers + Column { + spacing: 10 + Shape { + width: 160 + preferredRendererType: renderer + + ShapePath { + fillColor: "transparent" + strokeColor: "blue" + strokeWidth: 1 + + PathRectangle { + x: 20; y: 0 + width: 100; height: 20 + } + + PathRectangle { + x: 20.5; y: 30.5 + width: 100; height: 20 + } + } + } + + Shape { + width: 160 + preferredRendererType: renderer + + ShapePath { + fillColor: "yellow" + strokeColor: "transparent" + + PathRectangle { + x: 20; y: 0 + width: 100; height: 20 + } + + PathRectangle { + x: 20.5; y: 30.5 + width: 100; height: 20 + } + } + } + + Shape { + width: 160 + preferredRendererType: renderer + + ShapePath { + fillColor: "yellow" + strokeColor: "green" + strokeWidth: 5 + joinStyle: ShapePath.RoundJoin + + PathRectangle { + x: 20; y: 00 + width: 100; height: 20 + } + + PathRectangle { + x: 20; y: 30 + width: 100; height: 20 + radius: 5 + } + } + + ShapePath { + fillColor: "yellow" + strokeColor: "green" + strokeWidth: 5 + joinStyle: ShapePath.MiterJoin + + PathRectangle { + x: 20; y: 60 + width: 100; height: 20 + } + + PathRectangle { + x: 20; y: 90 + width: 100; height: 20 + radius: 5 + } + + PathRectangle { + x: 20; y: 120 + width: 100; height: 20 + radius: 50 + } + + PathRectangle { + x: 20; y: 150 + width: 100; height: 30 + radius: 10 + topLeftRadius: 50 + bottomRightRadius: 5 + bottomLeftRadius: 0 + } + } + } + + Rectangle { + id: rect + width: 120 + height: 60 + color: "white" + border.width: 20 + border.color: "blue" + topRightRadius: 30 + } + + Shape { + width: 160 + preferredRendererType: renderer + + ShapePath { + id: myPath + fillColor: rect.color + strokeColor: rect.border.color + strokeWidth: rect.border.width + joinStyle: ShapePath.MiterJoin + + PathRectangle { + width: rect.width + height: rect.height + topRightRadius: rect.topRightRadius + strokeAdjustment: myPath.strokeWidth + } + } + } + } + } + } +} + |