aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEskil Abrahamsen Blomfeldt <[email protected]>2024-12-02 13:45:16 +0100
committerEskil Abrahamsen Blomfeldt <[email protected]>2024-12-05 17:07:42 +0100
commit3a050a7c65c2b5829e13dca909c81b182d7b821d (patch)
tree037c67e0ca4c1ebe0be8acddd5a6dc4a5f9e69ea
parent65a3ec2c442b596359775e8707672af88b29c2f4 (diff)
Add Shear transform type
Shearing an item either by given angle or a relative offset is a common operation in vector graphics. We already have types for Rotate, Scale, etc. so this fits into the list there. For cases where you want to animate the angles it can be especially useful to have a transform where these are exposed as properties. [ChangeLog][QtQuick] Added Shear transform types for shearing an item by a factor or angle along the x- and y-axes. Change-Id: I6be81b40ca555a0ac0d8cb14dd81598502238a3d Reviewed-by: Shawn Rutledge <[email protected]>
-rw-r--r--src/quick/doc/images/x-shear.pngbin0 -> 970 bytes
-rw-r--r--src/quick/items/qquickitem.cpp1
-rw-r--r--src/quick/items/qquicktranslate.cpp212
-rw-r--r--src/quick/items/qquicktranslate_p.h43
-rw-r--r--tests/auto/quick/qquicktransform/CMakeLists.txt16
-rw-r--r--tests/auto/quick/qquicktransform/tst_qquicktransform.cpp99
-rw-r--r--tests/baseline/scenegraph/data/shear/shear_1.qml26
-rw-r--r--tests/manual/pointer/wheelShear.qml84
8 files changed, 481 insertions, 0 deletions
diff --git a/src/quick/doc/images/x-shear.png b/src/quick/doc/images/x-shear.png
new file mode 100644
index 0000000000..21ef7eb663
--- /dev/null
+++ b/src/quick/doc/images/x-shear.png
Binary files differ
diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp
index ac9e0e9306..a3b57672bc 100644
--- a/src/quick/items/qquickitem.cpp
+++ b/src/quick/items/qquickitem.cpp
@@ -106,6 +106,7 @@ static void setActiveFocus(QQuickItem *item, Qt::FocusReason reason)
\li \l Rotation
\li \l Scale
\li \l Translate
+ \li \l Shear
\li \l Matrix4x4
\endlist
diff --git a/src/quick/items/qquicktranslate.cpp b/src/quick/items/qquicktranslate.cpp
index 09a5dcbbe4..12c60369f0 100644
--- a/src/quick/items/qquicktranslate.cpp
+++ b/src/quick/items/qquicktranslate.cpp
@@ -406,6 +406,218 @@ void QQuickRotation::applyTo(QMatrix4x4 *matrix) const
matrix->translate(-d->origin);
}
+/*!
+ \qmltype Shear
+ \nativetype QQuickShear
+ \inqmlmodule QtQuick
+ \ingroup qtquick-visual-transforms
+ \since 6.9
+ \brief Provides a way to shear an Item.
+
+ The Shear type provides a way to transform an \l Item by a two-dimensional shear-type
+ matrix, sometimes known as a \e skew transform.
+
+ \qml
+ Rectangle {
+ width: 100; height: 100
+ color: "blue"
+ transform: Shear {
+ xFactor: 1.0
+ }
+ }
+ \endqml
+
+ This shears the item by a factor of \c 1.0 along the x-axis without modifying anything along the
+ y-axis. Each point \c P is displaced by \c{xFactor(P.y - origin.y)} (the signed vertical
+ distance to the \l{origin} multiplied with the \l{xFactor}). Setting the \l{yFactor} shears the
+ item along the y-axis and proportionally to the horizontal distance.
+
+ \image x-shear.png
+
+ Since the default origin is at \c{(0, 0)}, the top of the item remains untransformed, whereas
+ the bottom is displaced 100 pixels to the right (corresponding to the height of the item.)
+
+ This code is equivalent to the following:
+
+ \qml
+ Rectangle {
+ width: 100; height: 100
+ color: "blue"
+ transform: Shear {
+ xAngle: 45.0
+ }
+ }
+ \endqml
+
+ \note If both \c{xFactor}/\c{yFactor} and \c{xAngle}/\c{yAngle} are set, then the sum of the
+ two displacements will be used.
+*/
+class QQuickShearPrivate : public QQuickTransformPrivate
+{
+public:
+ QVector3D origin;
+ qreal xFactor = 0.0;
+ qreal yFactor = 0.0;
+ qreal xAngle = 0.0;
+ qreal yAngle = 0.0;
+};
+
+QQuickShear::QQuickShear(QObject *parent)
+ : QQuickTransform(*new QQuickShearPrivate, parent)
+{
+}
+
+/*!
+ \qmlpropertygroup QtQuick::Shear::origin
+ \qmlproperty real QtQuick::Shear::origin.x
+ \qmlproperty real QtQuick::Shear::origin.y
+
+ The origin point of the transformation (i.e., the point that stays fixed relative to the parent
+ as the rest of the item is sheared).
+
+ By default the origin is \c (0, 0).
+*/
+QVector3D QQuickShear::origin() const
+{
+ Q_D(const QQuickShear);
+ return d->origin;
+}
+
+void QQuickShear::setOrigin(const QVector3D &point)
+{
+ Q_D(QQuickShear);
+ if (d->origin == point)
+ return;
+ d->origin = point;
+ update();
+ emit originChanged();
+}
+
+/*!
+ \qmlproperty real QtQuick::Shear::xFactor
+
+ The factor by which to shear the item's coordinate system along the x-axis. Each point \c P is
+ displaced by \c{xFactor(P.y - origin.y)}
+
+ This corresponds to the \c sh parameter in \l{QTransform::shear()} and the \c xShear parameter
+ in calls to \l{PlanarTransform.fromShear()}.
+
+ The default value is \c 0.0.
+
+ \sa xAngle
+*/
+qreal QQuickShear::xFactor() const
+{
+ Q_D(const QQuickShear);
+ return d->xFactor;
+}
+void QQuickShear::setXFactor(qreal xFactor)
+{
+ Q_D(QQuickShear);
+ if (d->xFactor == xFactor)
+ return;
+ d->xFactor = xFactor;
+ update();
+ emit xFactorChanged();
+}
+
+/*!
+ \qmlproperty real QtQuick::Shear::yFactor
+
+ The factor by which to shear the item's coordinate system along the y-axis. The factor by which
+ to shear the item's coordinate system along the x-axis. Each point \c P is displaced by
+ \c{xFactor(P.y - origin.y)}
+
+ This corresponds to the \c sv parameter in \l{QTransform::shear()} and the \c yShear parameter
+ in calls to \l{PlanarTransform.fromShear()}.
+
+ The default value is \c 0.0.
+
+ \sa yAngle
+*/
+qreal QQuickShear::yFactor() const
+{
+ Q_D(const QQuickShear);
+ return d->yFactor;
+}
+void QQuickShear::setYFactor(qreal yFactor)
+{
+ Q_D(QQuickShear);
+ if (d->yFactor == yFactor)
+ return;
+ d->yFactor = yFactor;
+ update();
+ emit yFactorChanged();
+}
+
+/*!
+ \qmlproperty real QtQuick::Shear::xAngle
+
+ The angle (in degrees) by which to shear the item's coordinate system along the x-axis. This
+ is equivalent to setting \l{xFactor} to \c{tan(xAngle)}.
+
+ The default value is \c 0.0.
+
+ \sa xFactor
+*/
+qreal QQuickShear::xAngle() const
+{
+ Q_D(const QQuickShear);
+ return d->xAngle;
+}
+void QQuickShear::setXAngle(qreal xAngle)
+{
+ Q_D(QQuickShear);
+ if (d->xAngle == xAngle)
+ return;
+ d->xAngle = xAngle;
+ update();
+ emit xAngleChanged();
+}
+
+/*!
+ \qmlproperty real QtQuick::Shear::yAngle
+
+ The angle (in degrees) by which to shear the item's coordinate system along the y-axis. This
+ is equivalent to setting \l{yFactor} to \c{tan(yAngle)}.
+
+ The default value is \c 0.0.
+
+ \sa yFactor
+*/
+qreal QQuickShear::yAngle() const
+{
+ Q_D(const QQuickShear);
+ return d->yAngle;
+}
+void QQuickShear::setYAngle(qreal yAngle)
+{
+ Q_D(QQuickShear);
+ if (d->yAngle == yAngle)
+ return;
+ d->yAngle = yAngle;
+ update();
+ emit yAngleChanged();
+}
+
+void QQuickShear::applyTo(QMatrix4x4 *matrix) const
+{
+ Q_D(const QQuickShear);
+ if (d->xFactor == 0.0 && d->yFactor == 0.0 && d->xAngle == 0.0 && d->yAngle == 0.0)
+ return;
+
+ const qreal xShear = qTan(qDegreesToRadians(d->xAngle)) + d->xFactor;
+ const qreal yShear = qTan(qDegreesToRadians(d->yAngle)) + d->yFactor;
+
+ matrix->translate(d->origin);
+ *matrix *= QMatrix4x4(1.0, xShear, 0.0, 0.0,
+ yShear, 1.0, 0.0, 0.0,
+ 0.0, 0.0, 1.0, 0.0,
+ 0.0, 0.0, 0.0, 1.0);
+ matrix->translate(-d->origin);
+}
+
+
class QQuickMatrix4x4Private : public QQuickTransformPrivate
{
public:
diff --git a/src/quick/items/qquicktranslate_p.h b/src/quick/items/qquicktranslate_p.h
index 496a115d3f..ed152dbe48 100644
--- a/src/quick/items/qquicktranslate_p.h
+++ b/src/quick/items/qquicktranslate_p.h
@@ -125,6 +125,49 @@ private:
Q_DECLARE_PRIVATE(QQuickRotation)
};
+class QQuickShearPrivate;
+class Q_QUICK_EXPORT QQuickShear : public QQuickTransform
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QVector3D origin READ origin WRITE setOrigin NOTIFY originChanged)
+ Q_PROPERTY(qreal xFactor READ xFactor WRITE setXFactor NOTIFY xFactorChanged)
+ Q_PROPERTY(qreal yFactor READ yFactor WRITE setYFactor NOTIFY yFactorChanged)
+ Q_PROPERTY(qreal xAngle READ xAngle WRITE setXAngle NOTIFY xAngleChanged)
+ Q_PROPERTY(qreal yAngle READ yAngle WRITE setYAngle NOTIFY yAngleChanged)
+ QML_NAMED_ELEMENT(Shear)
+ QML_ADDED_IN_VERSION(6, 9)
+public:
+ QQuickShear(QObject *parent = nullptr);
+
+ QVector3D origin() const;
+ void setOrigin(const QVector3D &point);
+
+ qreal xFactor() const;
+ void setXFactor(qreal);
+
+ qreal yFactor() const;
+ void setYFactor(qreal);
+
+ qreal xAngle() const;
+ void setXAngle(qreal);
+
+ qreal yAngle() const;
+ void setYAngle(qreal);
+
+ void applyTo(QMatrix4x4 *matrix) const override;
+
+Q_SIGNALS:
+ void originChanged();
+ void xFactorChanged();
+ void yFactorChanged();
+ void xAngleChanged();
+ void yAngleChanged();
+
+private:
+ Q_DECLARE_PRIVATE(QQuickShear)
+};
+
class QQuickMatrix4x4Private;
class Q_QUICK_EXPORT QQuickMatrix4x4 : public QQuickTransform
{
diff --git a/tests/auto/quick/qquicktransform/CMakeLists.txt b/tests/auto/quick/qquicktransform/CMakeLists.txt
new file mode 100644
index 0000000000..ab63f7cce5
--- /dev/null
+++ b/tests/auto/quick/qquicktransform/CMakeLists.txt
@@ -0,0 +1,16 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qquicktext LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_test(tst_qquicktransform
+ SOURCES
+ tst_qquicktransform.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::QuickPrivate
+)
diff --git a/tests/auto/quick/qquicktransform/tst_qquicktransform.cpp b/tests/auto/quick/qquicktransform/tst_qquicktransform.cpp
new file mode 100644
index 0000000000..1d7dbf24f1
--- /dev/null
+++ b/tests/auto/quick/qquicktransform/tst_qquicktransform.cpp
@@ -0,0 +1,99 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest>
+#include <QtQuick>
+#include <QtQuick/private/qquicktranslate_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class tst_qquicktransform : public QObject
+{
+ Q_OBJECT
+private slots:
+ void shearTransform();
+};
+
+void tst_qquicktransform::shearTransform()
+{
+ const QRect rect(10, 20, 100, 200);
+
+ {
+ QQuickShear shear;
+ shear.setXFactor(1.0);
+ shear.setOrigin(QVector3D(rect.topLeft()));
+
+ QMatrix4x4 matrix;
+ shear.applyTo(&matrix);
+
+ QRect mappedRect = matrix.mapRect(rect);
+
+ QCOMPARE(mappedRect.topLeft(), rect.topLeft());
+ QCOMPARE(mappedRect.bottomRight(), rect.bottomRight() + QPoint(rect.height(), 0.0));
+ }
+
+ {
+ QQuickShear shear;
+ shear.setOrigin(QVector3D(rect.topLeft()));
+ shear.setYFactor(0.5);
+
+ QMatrix4x4 matrix;
+ shear.applyTo(&matrix);
+
+ QRect mappedRect = matrix.mapRect(rect);
+
+ QCOMPARE(mappedRect.topLeft(), rect.topLeft());
+ QCOMPARE(mappedRect.bottomRight(), rect.bottomRight() + QPoint(0.0, rect.width() * 0.5));
+ }
+
+ {
+ QQuickShear shear;
+ shear.setOrigin(QVector3D(rect.topLeft()));
+ shear.setXFactor(1.0);
+ shear.setYFactor(0.5);
+
+ QMatrix4x4 matrix;
+ shear.applyTo(&matrix);
+
+ QRect mappedRect = matrix.mapRect(rect);
+
+ QCOMPARE(mappedRect.topLeft(), rect.topLeft());
+ QCOMPARE(mappedRect.bottomRight(), rect.bottomRight() + QPoint(rect.height(), rect.width() * 0.5));
+ }
+
+ {
+ QQuickShear shear;
+ shear.setOrigin(QVector3D(rect.topLeft()));
+ shear.setXFactor(0.5);
+ shear.setXAngle(qAtan(0.5) * 180.0 / M_PI);
+
+ QMatrix4x4 matrix;
+ shear.applyTo(&matrix);
+
+ QRect mappedRect = matrix.mapRect(rect);
+
+ QCOMPARE(mappedRect.topLeft(), rect.topLeft());
+ QCOMPARE(mappedRect.bottomRight(), rect.bottomRight() + QPoint(rect.height(), 0.0));
+ }
+
+ {
+ QQuickShear shear;
+ shear.setOrigin(QVector3D(rect.topLeft()));
+ shear.setYFactor(0.25);
+ shear.setYAngle(qAtan(0.25) * 180.0 / M_PI);
+
+ QMatrix4x4 matrix;
+ shear.applyTo(&matrix);
+
+ QRect mappedRect = matrix.mapRect(rect);
+
+ QCOMPARE(mappedRect.topLeft(), rect.topLeft());
+ QCOMPARE(mappedRect.bottomRight(), rect.bottomRight() + QPoint(0.0, rect.width() * 0.5));
+ }
+}
+
+QT_END_NAMESPACE
+
+QTEST_MAIN(tst_qquicktransform)
+
+#include "tst_qquicktransform.moc"
diff --git a/tests/baseline/scenegraph/data/shear/shear_1.qml b/tests/baseline/scenegraph/data/shear/shear_1.qml
new file mode 100644
index 0000000000..7d7ef4cb33
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shear/shear_1.qml
@@ -0,0 +1,26 @@
+import QtQuick
+
+Rectangle {
+ width: 480
+ height: 240
+ property int standardWidth: 60
+ property int standardHeight: 60
+ property int standardSpacing: 60
+ property bool smoothing: true
+ Grid{
+ id: grid_0000
+ anchors.top: parent.baseline
+ anchors.left: parent.left
+ anchors.margins: 30
+ columns: 4
+ spacing: standardSpacing
+ Rectangle{ color: "red"; width: standardWidth; height: standardHeight; transform: Shear { origin.x: standardWidth/2; origin.y: standardHeight/2; xAngle: 45 } smooth: smoothing}
+ Rectangle{ color: "orange"; width: standardWidth; height: standardHeight; transform: Shear { origin.x: standardWidth/2; origin.y: standardHeight/2; yAngle: 45 } smooth: smoothing }
+ Rectangle{ color: "yellow"; width: standardWidth; height: standardHeight; transform: Shear { origin.x: standardWidth/2; origin.y: standardHeight/2; xFactor: 1; } smooth: smoothing }
+ Rectangle{ color: "blue"; width: standardWidth; height: standardHeight; transform: Shear { origin.x: standardWidth/2; origin.y: standardHeight/2; yFactor: 1; } smooth: smoothing }
+ Rectangle{ color: "green"; width: standardWidth; height: standardHeight; transform: Shear { origin.x: standardWidth/2; origin.y: standardHeight/2; yFactor: 0.5; yAngle: 25 } smooth: smoothing }
+ Rectangle{ color: "indigo"; width: standardWidth; height: standardHeight; transform: Shear { origin.x: standardWidth/2; origin.y: standardHeight/2; xFactor: 0.5; yAngle: 25 } smooth: smoothing }
+ Rectangle{ color: "violet"; width: standardWidth; height: standardHeight; transform: Shear { origin.x: standardWidth/2; origin.y: standardHeight/2; xFactor: 0.5; yFactor: 0.5 } smooth: smoothing }
+ Rectangle{ color: "light green"; width: standardWidth; height: standardHeight; transform: Shear { origin.x: standardWidth/2; origin.y: standardHeight/2; xAngle: 25; yAngle: 25 } smooth: smoothing }
+ }
+}
diff --git a/tests/manual/pointer/wheelShear.qml b/tests/manual/pointer/wheelShear.qml
new file mode 100644
index 0000000000..862141747d
--- /dev/null
+++ b/tests/manual/pointer/wheelShear.qml
@@ -0,0 +1,84 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+
+Rectangle {
+ width: 400
+ height: 400
+ color: palette.window
+
+ Row {
+ CheckBox {
+ id: smoothCB
+ text: "smooth"
+ }
+ CheckBox {
+ id: aaCB
+ text: "antialiasing"
+ checked: true
+ }
+ CheckBox {
+ id: angleCB
+ text: "by angle"
+ }
+ }
+
+ Rectangle {
+ color: "orange"
+ width: 300; height: 200
+ anchors.centerIn: parent
+ transform: Shear {
+ id: barber
+ origin.x: 150
+ origin.y: 100
+ xAngle: angleCB.checked ? hwheel.rotation : 0
+ yAngle: angleCB.checked ? vwheel.rotation : 0
+ xFactor: !angleCB.checked ? hwheel.rotation / 120 : 0
+ yFactor: !angleCB.checked ? vwheel.rotation / 120 : 0
+ }
+ smooth: smoothCB.checked
+ antialiasing: aaCB.checked
+
+ Text {
+ text: "use mouse wheels / touchpad gestures to shear\nclick to set shear center"
+ horizontalAlignment: Text.AlignHCenter
+ anchors.centerIn: parent
+ }
+
+ Rectangle {
+ x: barber.origin.x - width / 2
+ y: barber.origin.y - width / 2
+ width: 5; height: 5; radius: 2.5
+ color: "red"
+ }
+
+ WheelHandler {
+ id: vwheel
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
+ target: null
+ }
+
+ WheelHandler {
+ id: hwheel
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
+ orientation: Qt.Horizontal
+ target: null
+ }
+
+ TapHandler {
+ onTapped: barber.origin = point.position
+ }
+ }
+
+ Text {
+ color: palette.windowText
+ text: angleCB.checked ?
+ "angles " + barber.xAngle.toFixed(3) + ", " + barber.yAngle.toFixed(3)
+ + "\ntangents " + Math.tan(barber.xAngle * Math.PI / 180).toFixed(2) + ", "
+ + Math.tan(barber.yAngle * Math.PI / 180).toFixed(2)
+ : "factors " + barber.xFactor.toFixed(3) + ", " + barber.yFactor.toFixed(3)
+ anchors.bottom: parent.bottom
+ }
+}