diff options
author | Paolo Angelelli <paolo.angelelli@qt.io> | 2017-09-22 11:46:36 -0500 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@qt.io> | 2018-01-25 16:22:51 +0000 |
commit | bf74a908cb0591c2adc024a6f93d566c7348c125 (patch) | |
tree | 71e749e71e8dcd4b159303526e7343dcd968e920 | |
parent | f6222f825831202c084835412a3c217a9420cad7 (diff) |
Support masking of QQuickItems
Adding a new property, containsMask, to QQuickItem, that can be set
to any QObject defining a
Q_INVOKABLE bool contains(const QPointF &point).
When this property is set, the mask object contains method is used in
place of the item own contains method.
[ChangeLog][QtQuick][QQuickItem] Added containsMask property.
Task-number: QTBUG-20524
Change-Id: I5b0696e2cddc6ae3e217ce149c5f44980fdb69aa
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
-rw-r--r-- | src/quick/items/qquickitem.cpp | 72 | ||||
-rw-r--r-- | src/quick/items/qquickitem.h | 4 | ||||
-rw-r--r-- | src/quick/items/qquickitem_p.h | 7 | ||||
-rw-r--r-- | src/quick/items/qquickitemsmodule.cpp | 1 | ||||
-rw-r--r-- | src/quick/items/qquickmousearea_p_p.h | 2 | ||||
-rw-r--r-- | tests/auto/quick/qquickmousearea/data/mask.qml | 63 | ||||
-rw-r--r-- | tests/auto/quick/qquickmousearea/qquickmousearea.pro | 2 | ||||
-rw-r--r-- | tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp | 63 |
8 files changed, 210 insertions, 4 deletions
diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index f1f6f97356..f4d11e32bd 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -7568,9 +7568,75 @@ void QQuickItem::setKeepTouchGrab(bool keep) bool QQuickItem::contains(const QPointF &point) const { Q_D(const QQuickItem); - qreal x = point.x(); - qreal y = point.y(); - return x >= 0 && y >= 0 && x <= d->width && y <= d->height; + if (d->mask) { + bool res = false; + d->extra->maskContains.invoke(d->mask, + Qt::DirectConnection, + Q_RETURN_ARG(bool, res), + Q_ARG(QPointF, point)); + return res; + } else { + qreal x = point.x(); + qreal y = point.y(); + return x >= 0 && y >= 0 && x <= d->width && y <= d->height; + } +} + +/*! + \qmlproperty QObject * QtQuick::Item::containsMask + \since 5.11 + This property holds an optional mask for the Item to be used in the + QtQuick::Item::contains method. + QtQuick::Item::contains main use is currently to determine whether + an input event has landed into the item or not. + + By default the \l contains method will return true for any point + within the Item's bounding box. \c containsMask allows for a + more fine-grained control. For example, the developer could + define and use an AnotherItem element as containsMask, + which has a specialized contains method, like: + + \code + Item { id: item; containsMask: AnotherItem { id: anotherItem } } + \endcode + + \e{item}'s contains method would then return true only if + \e{anotherItem}'s contains implementation returns true. +*/ +QObject *QQuickItem::containsMask() const +{ + Q_D(const QQuickItem); + return d->mask.data(); +} + +void QQuickItem::setContainsMask(QObject *mask) +{ + Q_D(QQuickItem); + // an Item can't mask itself (to prevent infinite loop in contains()) + if (d->mask.data() == mask || mask == static_cast<QObject *>(this)) + return; + + QQuickItem *quickMask = qobject_cast<QQuickItem *>(d->mask); + if (quickMask) { + QQuickItemPrivate *maskPrivate = QQuickItemPrivate::get(quickMask); + maskPrivate->registerAsContainsMask(this, false); // removed from use as my mask + } + + if (mask) { + int methodIndex = mask->metaObject()->indexOfMethod(QByteArrayLiteral("contains(QPointF)")); + if (methodIndex < 0) { + qmlWarning(this) << QStringLiteral("QQuickItem: Object set as mask does not have an invokable contains method, ignoring it."); + return; + } + d->extra.value().maskContains = mask->metaObject()->method(methodIndex); + } + d->mask = mask; + quickMask = qobject_cast<QQuickItem *>(mask); + if (quickMask) { + QQuickItemPrivate *maskPrivate = QQuickItemPrivate::get(quickMask); + maskPrivate->registerAsContainsMask(this, true); // telling maskPrivate that "this" is using it as mask + } + emit containsMaskChanged(); } /*! diff --git a/src/quick/items/qquickitem.h b/src/quick/items/qquickitem.h index f6ee54e94d..e38c276b9f 100644 --- a/src/quick/items/qquickitem.h +++ b/src/quick/items/qquickitem.h @@ -144,6 +144,7 @@ class Q_QUICK_EXPORT QQuickItem : public QObject, public QQmlParserStatus Q_PROPERTY(bool antialiasing READ antialiasing WRITE setAntialiasing NOTIFY antialiasingChanged RESET resetAntialiasing) Q_PROPERTY(qreal implicitWidth READ implicitWidth WRITE setImplicitWidth NOTIFY implicitWidthChanged) Q_PROPERTY(qreal implicitHeight READ implicitHeight WRITE setImplicitHeight NOTIFY implicitHeightChanged) + Q_PROPERTY(QObject *containsMask READ containsMask WRITE setContainsMask NOTIFY containsMaskChanged REVISION 11) Q_PRIVATE_PROPERTY(QQuickItem::d_func(), QQuickItemLayer *layer READ layer DESIGNABLE false CONSTANT FINAL) @@ -320,6 +321,8 @@ public: QSharedPointer<QQuickItemGrabResult> grabToImage(const QSize &targetSize = QSize()); Q_INVOKABLE virtual bool contains(const QPointF &point) const; + QObject *containsMask() const; + void setContainsMask(QObject *mask); QTransform itemTransform(QQuickItem *, bool *) const; QPointF mapToItem(const QQuickItem *item, const QPointF &point) const; @@ -390,6 +393,7 @@ Q_SIGNALS: void zChanged(); void implicitWidthChanged(); void implicitHeightChanged(); + Q_REVISION(11) void containsMaskChanged(); protected: bool event(QEvent *) override; diff --git a/src/quick/items/qquickitem_p.h b/src/quick/items/qquickitem_p.h index 9ed5286f22..0c02c66ef2 100644 --- a/src/quick/items/qquickitem_p.h +++ b/src/quick/items/qquickitem_p.h @@ -368,6 +368,9 @@ public: QQuickDefaultClipNode *clipNode; QSGRootNode *rootNode; + // Mask contains() method + QMetaMethod maskContains; + QObjectList resourcesList; // Although acceptedMouseButtons is inside ExtraData, we actually store @@ -382,6 +385,10 @@ public: // 26 bits padding }; QLazilyAllocated<ExtraData> extra; + // Contains mask + QPointer<QObject> mask; + // If the mask is an Item, inform it that it's being used as a mask (true) or is no longer being used (false) + virtual void registerAsContainsMask(QQuickItem * /* maskedItem */, bool /* set */) { } QQuickAnchors *anchors() const; mutable QQuickAnchors *_anchors; diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp index 4839b4dd4f..f03942ecee 100644 --- a/src/quick/items/qquickitemsmodule.cpp +++ b/src/quick/items/qquickitemsmodule.cpp @@ -415,6 +415,7 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor) #if QT_CONFIG(quick_animatedimage) qmlRegisterType<QQuickAnimatedImage, 11>(uri, 2, 11,"AnimatedImage"); #endif + qmlRegisterType<QQuickItem, 11>(uri, 2, 11,"Item"); } static void initResources() diff --git a/src/quick/items/qquickmousearea_p_p.h b/src/quick/items/qquickmousearea_p_p.h index 34cda9e193..0dd2690d43 100644 --- a/src/quick/items/qquickmousearea_p_p.h +++ b/src/quick/items/qquickmousearea_p_p.h @@ -61,6 +61,7 @@ QT_BEGIN_NAMESPACE class QQuickMouseEvent; class QQuickMouseArea; +class QQuickPointerMask; class QQuickMouseAreaPrivate : public QQuickItemPrivate { Q_DECLARE_PUBLIC(QQuickMouseArea) @@ -99,6 +100,7 @@ public: #if QT_CONFIG(draganddrop) QQuickDrag *drag; #endif + QPointer<QQuickPointerMask> mask; QPointF startScene; QPointF targetStartPos; QPointF lastPos; diff --git a/tests/auto/quick/qquickmousearea/data/mask.qml b/tests/auto/quick/qquickmousearea/data/mask.qml new file mode 100644 index 0000000000..23e93f0310 --- /dev/null +++ b/tests/auto/quick/qquickmousearea/data/mask.qml @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Ford Motor Company +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.11 +import Test 1.0 + +Item { + id: root + property int clicked: 0 + property int pressed: 0 + property int released: 0 + + width: 200; height: 200 + + MouseArea { + id: mouseArea + width: 200; height: 200 + onPressed: { root.pressed++ } + onClicked: { root.clicked++ } + onReleased: { root.released++ } + + containsMask: CircleMask { + radius: mouseArea.width/2 + } + } +} + diff --git a/tests/auto/quick/qquickmousearea/qquickmousearea.pro b/tests/auto/quick/qquickmousearea/qquickmousearea.pro index 3a4dfa946f..ee9d6dce2b 100644 --- a/tests/auto/quick/qquickmousearea/qquickmousearea.pro +++ b/tests/auto/quick/qquickmousearea/qquickmousearea.pro @@ -6,6 +6,8 @@ HEADERS += ../../shared/testhttpserver.h SOURCES += tst_qquickmousearea.cpp \ ../../shared/testhttpserver.cpp +OTHER_FILES += $$files(data/*.qml) + include (../../shared/util.pri) include (../shared/util.pri) diff --git a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp index b9a9dabf00..4fe01787fa 100644 --- a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp +++ b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp @@ -68,13 +68,45 @@ static bool initView(QQuickView &v, const QUrl &url, bool moveMouseOut, QByteArr return true; } +class CircleMask : public QObject +{ + Q_OBJECT + Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged) + +public: + virtual ~CircleMask() {} + qreal radius() const { return m_radius; } + void setRadius(qreal radius) + { + if (m_radius == radius) + return; + m_radius = radius; + emit radiusChanged(); + } + + Q_INVOKABLE bool contains(const QPointF &point) const + { + QPointF center(m_radius, m_radius); + QLineF line(center, point); + return line.length() <= m_radius; + } + +signals: + void radiusChanged(); + +private: + qreal m_radius; +}; + class tst_QQuickMouseArea: public QQmlDataTest { Q_OBJECT public: tst_QQuickMouseArea() : device(nullptr) - {} + { + qmlRegisterType<CircleMask>("Test", 1, 0, "CircleMask"); + } private slots: void initTestCase() override; @@ -132,6 +164,7 @@ private slots: void pressAndHold(); void pressOneAndTapAnother_data(); void pressOneAndTapAnother(); + void mask(); private: int startDragDistance() const { @@ -2247,6 +2280,34 @@ void tst_QQuickMouseArea::pressOneAndTapAnother() } } +void tst_QQuickMouseArea::mask() +{ + QQuickView window; + QByteArray errorMessage; + QVERIFY2(initView(window, testFileUrl("mask.qml"), true, &errorMessage), errorMessage.constData()); + window.show(); + window.requestActivate(); + QVERIFY(QTest::qWaitForWindowExposed(&window)); + QQuickItem *root = window.rootObject(); + QVERIFY(root != 0); + + // click inside the mask, and verify it registers + QTest::mousePress(&window, Qt::LeftButton, 0, QPoint(100,100)); + QTest::mouseRelease(&window, Qt::LeftButton, 0, QPoint(100,100)); + + QCOMPARE(window.rootObject()->property("pressed").toInt(), 1); + QCOMPARE(window.rootObject()->property("released").toInt(), 1); + QCOMPARE(window.rootObject()->property("clicked").toInt(), 1); + + // click outside the mask (but inside the MouseArea), and verify it doesn't register + QTest::mousePress(&window, Qt::LeftButton, 0, QPoint(10,10)); + QTest::mouseRelease(&window, Qt::LeftButton, 0, QPoint(10,10)); + + QCOMPARE(window.rootObject()->property("pressed").toInt(), 1); + QCOMPARE(window.rootObject()->property("released").toInt(), 1); + QCOMPARE(window.rootObject()->property("clicked").toInt(), 1); +} + QTEST_MAIN(tst_QQuickMouseArea) #include "tst_qquickmousearea.moc" |