diff options
author | Adriano Rezende <atdrez@gmail.com> | 2012-02-26 17:26:53 +0100 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2012-04-19 06:16:28 +0200 |
commit | adb0848513198db72c6066b9a97077c7083df9fe (patch) | |
tree | be96156388b1e348cad79ec98bd30a87ac364f7a | |
parent | 7bd68f6447c79aa58a9854bb6c44db00ea36a06a (diff) |
Add contains method to QQuickItem public API
This method can be overwritten in order to provide fine grained control
over the mouse events handled by the item.
Change-Id: I23cb61958d3ac0b2f5091c47fa9e0ed07dc5e5d0
Reviewed-by: Martin Jones <martin.jones@nokia.com>
19 files changed, 730 insertions, 46 deletions
diff --git a/examples/quick/maskedmousearea/images/cloud_1.png b/examples/quick/maskedmousearea/images/cloud_1.png Binary files differnew file mode 100644 index 0000000000..87c54af253 --- /dev/null +++ b/examples/quick/maskedmousearea/images/cloud_1.png diff --git a/examples/quick/maskedmousearea/images/cloud_2.png b/examples/quick/maskedmousearea/images/cloud_2.png Binary files differnew file mode 100644 index 0000000000..981bbd2630 --- /dev/null +++ b/examples/quick/maskedmousearea/images/cloud_2.png diff --git a/examples/quick/maskedmousearea/images/moon.png b/examples/quick/maskedmousearea/images/moon.png Binary files differnew file mode 100644 index 0000000000..0a8037dd85 --- /dev/null +++ b/examples/quick/maskedmousearea/images/moon.png diff --git a/examples/quick/maskedmousearea/main.cpp b/examples/quick/maskedmousearea/main.cpp new file mode 100644 index 0000000000..407497e640 --- /dev/null +++ b/examples/quick/maskedmousearea/main.cpp @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QGuiApplication> +#include <QQuickView> + +#include "maskedmousearea.h" + + +int main(int argc, char* argv[]) +{ + QGuiApplication app(argc,argv); + QQuickView view; + + qmlRegisterType<MaskedMouseArea>("Example", 1, 0, "MaskedMouseArea"); + + view.setSource(QUrl::fromLocalFile("maskedmousearea.qml")); + view.show(); + return app.exec(); +} diff --git a/examples/quick/maskedmousearea/maskedmousearea.cpp b/examples/quick/maskedmousearea/maskedmousearea.cpp new file mode 100644 index 0000000000..3f872a66f6 --- /dev/null +++ b/examples/quick/maskedmousearea/maskedmousearea.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "maskedmousearea.h" + +#include <QStyleHints> +#include <QGuiApplication> + + +MaskedMouseArea::MaskedMouseArea(QQuickItem *parent) + : QQuickItem(parent), + m_pressed(false), + m_alphaThreshold(0.0), + m_containsMouse(false) +{ + setAcceptHoverEvents(true); + setAcceptedMouseButtons(Qt::LeftButton); +} + +void MaskedMouseArea::setPressed(bool pressed) +{ + if (m_pressed != pressed) { + m_pressed = pressed; + emit pressedChanged(); + } +} + +void MaskedMouseArea::setContainsMouse(bool containsMouse) +{ + if (m_containsMouse != containsMouse) { + m_containsMouse = containsMouse; + emit containsMouseChanged(); + } +} + +void MaskedMouseArea::setMaskSource(const QUrl &source) +{ + if (m_maskSource != source) { + m_maskSource = source; + m_maskImage = QImage(source.toLocalFile()); + emit maskSourceChanged(); + } +} + +void MaskedMouseArea::setAlphaThreshold(qreal threshold) +{ + if (m_alphaThreshold != threshold) { + m_alphaThreshold = threshold; + emit alphaThresholdChanged(); + } +} + +bool MaskedMouseArea::contains(const QPointF &point) const +{ + if (!QQuickItem::contains(point) || m_maskImage.isNull()) + return false; + + QPoint p = point.toPoint(); + + if (p.x() < 0 || p.x() >= m_maskImage.width() || + p.y() < 0 || p.y() >= m_maskImage.height()) + return false; + + qreal r = qBound<int>(0, m_alphaThreshold * 255, 255); + return qAlpha(m_maskImage.pixel(p)) > r; +} + +void MaskedMouseArea::mousePressEvent(QMouseEvent *event) +{ + setPressed(true); + m_pressPoint = event->pos(); + emit pressed(); +} + +void MaskedMouseArea::mouseReleaseEvent(QMouseEvent *event) +{ + setPressed(false); + emit released(); + + const int threshold = qApp->styleHints()->startDragDistance(); + const bool isClick = (threshold >= qAbs(event->x() - m_pressPoint.x()) && + threshold >= qAbs(event->y() - m_pressPoint.y())); + + if (isClick) + emit clicked(); +} + +void MaskedMouseArea::mouseUngrabEvent() +{ + setPressed(false); + emit canceled(); +} + +void MaskedMouseArea::hoverEnterEvent(QHoverEvent *event) +{ + Q_UNUSED(event); + setContainsMouse(true); +} + +void MaskedMouseArea::hoverLeaveEvent(QHoverEvent *event) +{ + Q_UNUSED(event); + setContainsMouse(false); +} diff --git a/examples/quick/maskedmousearea/maskedmousearea.h b/examples/quick/maskedmousearea/maskedmousearea.h new file mode 100644 index 0000000000..045d02d6f0 --- /dev/null +++ b/examples/quick/maskedmousearea/maskedmousearea.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MASKEDMOUSEAREA_H +#define MASKEDMOUSEAREA_H + +#include <QImage> +#include <QQuickItem> + + +class MaskedMouseArea : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged) + Q_PROPERTY(bool containsMouse READ containsMouse NOTIFY containsMouseChanged) + Q_PROPERTY(QUrl maskSource READ maskSource WRITE setMaskSource NOTIFY maskSourceChanged) + Q_PROPERTY(qreal alphaThreshold READ alphaThreshold WRITE setAlphaThreshold NOTIFY alphaThresholdChanged) + +public: + MaskedMouseArea(QQuickItem *parent = 0); + + bool contains(const QPointF &point) const; + + bool isPressed() const { return m_pressed; } + bool containsMouse() const { return m_containsMouse; } + + QUrl maskSource() const { return m_maskSource; } + void setMaskSource(const QUrl &source); + + qreal alphaThreshold() const { return m_alphaThreshold; } + void setAlphaThreshold(qreal threshold); + +signals: + void pressed(); + void released(); + void clicked(); + void canceled(); + void pressedChanged(); + void maskSourceChanged(); + void containsMouseChanged(); + void alphaThresholdChanged(); + +protected: + void setPressed(bool pressed); + void setContainsMouse(bool containsMouse); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void hoverEnterEvent(QHoverEvent *event); + void hoverLeaveEvent(QHoverEvent *event); + void mouseUngrabEvent(); + +private: + bool m_pressed; + QUrl m_maskSource; + QImage m_maskImage; + QPointF m_pressPoint; + qreal m_alphaThreshold; + bool m_containsMouse; +}; + +#endif diff --git a/examples/quick/maskedmousearea/maskedmousearea.pro b/examples/quick/maskedmousearea/maskedmousearea.pro new file mode 100644 index 0000000000..53e14aaa4b --- /dev/null +++ b/examples/quick/maskedmousearea/maskedmousearea.pro @@ -0,0 +1,14 @@ +TEMPLATE = app + +QT += quick qml + +HEADERS += maskedmousearea.h + +SOURCES += main.cpp \ + maskedmousearea.cpp + +target.path = $$[QT_INSTALL_EXAMPLES]/qtdeclarative/qtquick/maskedmousearea +qml.files = maskedmousearea.qml images +qml.path = $$[QT_INSTALL_EXAMPLES]/qtdeclarative/qtquick/maskedmousearea +INSTALLS += target qml + diff --git a/examples/quick/maskedmousearea/maskedmousearea.qml b/examples/quick/maskedmousearea/maskedmousearea.qml new file mode 100644 index 0000000000..82d351b04f --- /dev/null +++ b/examples/quick/maskedmousearea/maskedmousearea.qml @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import Example 1.0 + +Rectangle { + height: 480 + width: 320 + color: "black" + + Text { + text: qsTr("CLICK AND HOVER") + opacity: 0.6 + color: "white" + font.pixelSize: 20 + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 50 + } + + Image { + id: moon + smooth: true + anchors.centerIn: parent + scale: moonArea.pressed ? 1.1 : 1.0 + opacity: moonArea.containsMouse ? 1.0 : 0.7 + source: Qt.resolvedUrl("images/moon.png") + + MaskedMouseArea { + id: moonArea + anchors.fill: parent + alphaThreshold: 0.4 + maskSource: moon.source + } + + Behavior on opacity { + NumberAnimation { duration: 200 } + } + Behavior on scale { + NumberAnimation { duration: 100 } + } + } + + Image { + id: rightCloud + anchors { + centerIn: moon + verticalCenterOffset: 30 + horizontalCenterOffset: 80 + } + smooth: true + scale: rightCloudArea.pressed ? 1.1 : 1.0 + opacity: rightCloudArea.containsMouse ? 1.0 : 0.7 + source: Qt.resolvedUrl("images/cloud_2.png") + + MaskedMouseArea { + id: rightCloudArea + anchors.fill: parent + alphaThreshold: 0.4 + maskSource: rightCloud.source + } + + Behavior on opacity { + NumberAnimation { duration: 200 } + } + Behavior on scale { + NumberAnimation { duration: 100 } + } + } + + Image { + id: leftCloud + anchors { + centerIn: moon + verticalCenterOffset: 40 + horizontalCenterOffset: -80 + } + smooth: true + scale: leftCloudArea.pressed ? 1.1 : 1.0 + opacity: leftCloudArea.containsMouse ? 1.0 : 0.7 + source: Qt.resolvedUrl("images/cloud_1.png") + + MaskedMouseArea { + id: leftCloudArea + anchors.fill: parent + alphaThreshold: 0.4 + maskSource: leftCloud.source + } + + Behavior on opacity { + NumberAnimation { duration: 200 } + } + Behavior on scale { + NumberAnimation { duration: 100 } + } + } +} diff --git a/examples/quick/maskedmousearea/maskedmousearea.qmlproject b/examples/quick/maskedmousearea/maskedmousearea.qmlproject new file mode 100644 index 0000000000..709c19866f --- /dev/null +++ b/examples/quick/maskedmousearea/maskedmousearea.qmlproject @@ -0,0 +1,16 @@ +import QmlProject 1.1 + +Project { + mainFile: "maskedmousearea.qml" + + /* Include .qml, .js, and image files from current directory and subdirectories */ + QmlFiles { + directory: "." + } + JavaScriptFiles { + directory: "." + } + ImageFiles { + directory: "." + } +} diff --git a/src/quick/items/qquickcanvas.cpp b/src/quick/items/qquickcanvas.cpp index a288531beb..91102331a6 100644 --- a/src/quick/items/qquickcanvas.cpp +++ b/src/quick/items/qquickcanvas.cpp @@ -1039,7 +1039,7 @@ bool QQuickCanvasPrivate::deliverInitialMousePressEvent(QQuickItem *item, QMouse if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { QPointF p = item->mapFromScene(event->windowPos()); - if (!QRectF(0, 0, item->width(), item->height()).contains(p)) + if (!item->contains(p)) return false; } @@ -1054,7 +1054,7 @@ bool QQuickCanvasPrivate::deliverInitialMousePressEvent(QQuickItem *item, QMouse if (itemPrivate->acceptedMouseButtons() & event->button()) { QPointF p = item->mapFromScene(event->windowPos()); - if (QRectF(0, 0, item->width(), item->height()).contains(p)) { + if (item->contains(p)) { QMouseEvent me(event->type(), p, event->windowPos(), event->screenPos(), event->button(), event->buttons(), event->modifiers()); me.accept(); @@ -1218,7 +1218,7 @@ bool QQuickCanvasPrivate::deliverHoverEvent(QQuickItem *item, const QPointF &sce if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { QPointF p = item->mapFromScene(scenePos); - if (!QRectF(0, 0, item->width(), item->height()).contains(p)) + if (!item->contains(p)) return false; } @@ -1233,7 +1233,7 @@ bool QQuickCanvasPrivate::deliverHoverEvent(QQuickItem *item, const QPointF &sce if (itemPrivate->hoverEnabled) { QPointF p = item->mapFromScene(scenePos); - if (QRectF(0, 0, item->width(), item->height()).contains(p)) { + if (item->contains(p)) { if (!hoverItems.isEmpty() && hoverItems[0] == item) { //move accepted = sendHoverEvent(QEvent::HoverMove, item, scenePos, lastScenePos, modifiers, accepted); @@ -1286,7 +1286,7 @@ bool QQuickCanvasPrivate::deliverWheelEvent(QQuickItem *item, QWheelEvent *event if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { QPointF p = item->mapFromScene(event->posF()); - if (!QRectF(0, 0, item->width(), item->height()).contains(p)) + if (!item->contains(p)) return false; } @@ -1300,7 +1300,8 @@ bool QQuickCanvasPrivate::deliverWheelEvent(QQuickItem *item, QWheelEvent *event } QPointF p = item->mapFromScene(event->posF()); - if (QRectF(0, 0, item->width(), item->height()).contains(p)) { + + if (item->contains(p)) { QWheelEvent wheel(p, p, event->pixelDelta(), event->angleDelta(), event->delta(), event->orientation(), event->buttons(), event->modifiers()); wheel.accept(); @@ -1422,10 +1423,9 @@ bool QQuickCanvasPrivate::deliverTouchPoints(QQuickItem *item, QTouchEvent *even return false; if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { - QRectF bounds(0, 0, item->width(), item->height()); for (int i=0; i<newPoints.count(); i++) { QPointF p = item->mapFromScene(newPoints[i].scenePos()); - if (!bounds.contains(p)) + if (!item->contains(p)) return false; } } @@ -1441,12 +1441,11 @@ bool QQuickCanvasPrivate::deliverTouchPoints(QQuickItem *item, QTouchEvent *even QList<QTouchEvent::TouchPoint> matchingPoints; if (newPoints.count() > 0 && acceptedNewPoints->count() < newPoints.count()) { - QRectF bounds(0, 0, item->width(), item->height()); for (int i=0; i<newPoints.count(); i++) { if (acceptedNewPoints->contains(newPoints[i].id())) continue; QPointF p = item->mapFromScene(newPoints[i].scenePos()); - if (bounds.contains(p)) + if (item->contains(p)) matchingPoints << newPoints[i]; } } @@ -1537,7 +1536,7 @@ void QQuickCanvasPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QEvent *e moveEvent->setAccepted(true); for (++grabItem; grabItem != grabber->end();) { QPointF p = (**grabItem)->mapFromScene(moveEvent->pos()); - if (QRectF(0, 0, (**grabItem)->width(), (**grabItem)->height()).contains(p)) { + if ((**grabItem)->contains(p)) { QDragMoveEvent translatedEvent( p.toPoint(), moveEvent->possibleActions(), @@ -1582,7 +1581,7 @@ bool QQuickCanvasPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QQuickIte return false; QPointF p = item->mapFromScene(event->pos()); - if (QRectF(0, 0, item->width(), item->height()).contains(p)) { + if (item->contains(p)) { if (event->type() == QEvent::DragMove || itemPrivate->flags & QQuickItem::ItemAcceptsDrops) { QDragMoveEvent translatedEvent( p.toPoint(), diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index 75c9919e34..76d36987dd 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -1772,14 +1772,14 @@ void QQuickFlickable::mouseUngrabEvent() bool QQuickFlickable::sendMouseEvent(QMouseEvent *event) { Q_D(QQuickFlickable); - QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height())); + QPointF localPos = mapFromScene(event->windowPos()); QQuickCanvas *c = canvas(); QQuickItem *grabber = c ? c->mouseGrabberItem() : 0; bool disabledItem = grabber && !grabber->isEnabled(); bool stealThisEvent = d->stealMouse; - if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) { - QQuickMouseEventEx mouseEvent(event->type(), mapFromScene(event->windowPos()), + if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) { + QQuickMouseEventEx mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(), event->button(), event->buttons(), event->modifiers()); QQuickMouseEventEx *eventEx = QQuickMouseEventEx::extended(event); diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index ffaec540a2..628e19dbec 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -4886,9 +4886,7 @@ bool QQuickItem::isUnderMouse() const return false; QPointF cursorPos = QGuiApplicationPrivate::lastCursorPosition; - if (QRectF(0, 0, width(), height()).contains(mapFromScene(cursorPos))) // ### refactor: d->canvas->mapFromGlobal(cursorPos)))) - return true; - return false; + return contains(mapFromScene(cursorPos)); // ### refactor: d->canvas->mapFromGlobal(cursorPos)))) } bool QQuickItem::acceptHoverEvents() const @@ -5056,6 +5054,24 @@ void QQuickItem::setKeepTouchGrab(bool keep) } /*! + Returns true if this item contains \a point, which is in local coordinates; + returns false otherwise. + + This function can be overwritten in order to handle point collisions in items + with custom shapes. The default implementation checks if the point is inside + the item's bounding rect. + + Note that it's normally used to check if the item is under the mouse cursor, + and for that reason, the implementation of this function should be as light-weight + as possible. +*/ +bool QQuickItem::contains(const QPointF &point) const +{ + Q_D(const QQuickItem); + return QRectF(0, 0, d->width, d->height).contains(point); +} + +/*! \qmlmethod object QtQuick2::Item::mapFromItem(Item item, real x, real y) Maps the point (\a x, \a y), which is in \a item's coordinate system, to diff --git a/src/quick/items/qquickitem.h b/src/quick/items/qquickitem.h index 16dee80380..0d74cc0c4d 100644 --- a/src/quick/items/qquickitem.h +++ b/src/quick/items/qquickitem.h @@ -292,6 +292,8 @@ public: bool keepTouchGrab() const; void setKeepTouchGrab(bool); + Q_INVOKABLE virtual bool contains(const QPointF &point) const; + QTransform itemTransform(QQuickItem *, bool *) const; QPointF mapToItem(const QQuickItem *item, const QPointF &point) const; QPointF mapToScene(const QPointF &point) const; diff --git a/src/quick/items/qquickmousearea.cpp b/src/quick/items/qquickmousearea.cpp index b8555124eb..956ca09aac 100644 --- a/src/quick/items/qquickmousearea.cpp +++ b/src/quick/items/qquickmousearea.cpp @@ -263,7 +263,7 @@ bool QQuickMouseAreaPrivate::propagateHelper(QQuickMouseEvent *ev, QQuickItem *i if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { QPointF p = item->mapFromScene(sp); - if (!QRectF(0, 0, item->width(), item->height()).contains(p)) + if (!item->contains(p)) return false; } @@ -293,7 +293,7 @@ bool QQuickMouseAreaPrivate::propagateHelper(QQuickMouseEvent *ev, QQuickItem *i break; } QPointF p = item->mapFromScene(sp); - if (QRectF(0, 0, item->width(), item->height()).contains(p)) { + if (item->contains(p)) { ev->setX(p.x()); ev->setY(p.y()); ev->setAccepted(true);//It is connected, they have to explicitly ignore to let it slide @@ -725,10 +725,10 @@ void QQuickMouseArea::mouseMoveEvent(QMouseEvent *event) // ### we should skip this if these signals aren't used // ### can GV handle this for us? - bool contains = boundingRect().contains(d->lastPos); - if (d->hovered && !contains) + const bool isInside = contains(d->lastPos); + if (d->hovered && !isInside) setHovered(false); - else if (!d->hovered && contains) + else if (!d->hovered && isInside) setHovered(true); if (d->drag && d->drag->target()) { @@ -921,13 +921,13 @@ void QQuickMouseArea::mouseUngrabEvent() bool QQuickMouseArea::sendMouseEvent(QMouseEvent *event) { Q_D(QQuickMouseArea); - QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height())); + QPointF localPos = mapFromScene(event->windowPos()); QQuickCanvas *c = canvas(); QQuickItem *grabber = c ? c->mouseGrabberItem() : 0; bool stealThisEvent = d->stealMouse; - if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) { - QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(), + if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) { + QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(), event->button(), event->buttons(), event->modifiers()); mouseEvent.setAccepted(false); diff --git a/src/quick/items/qquickmultipointtoucharea.cpp b/src/quick/items/qquickmultipointtoucharea.cpp index 2ba7b80748..df5edfb00d 100644 --- a/src/quick/items/qquickmultipointtoucharea.cpp +++ b/src/quick/items/qquickmultipointtoucharea.cpp @@ -644,13 +644,13 @@ void QQuickMultiPointTouchArea::touchUngrabEvent() bool QQuickMultiPointTouchArea::sendMouseEvent(QMouseEvent *event) { - QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height())); + QPointF localPos = mapFromScene(event->windowPos()); QQuickCanvas *c = canvas(); QQuickItem *grabber = c ? c->mouseGrabberItem() : 0; bool stealThisEvent = _stealMouse; - if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) { - QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(), + if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) { + QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(), event->button(), event->buttons(), event->modifiers()); mouseEvent.setAccepted(false); @@ -724,25 +724,23 @@ bool QQuickMultiPointTouchArea::shouldFilter(QEvent *event) QQuickItem *grabber = c ? c->mouseGrabberItem() : 0; bool disabledItem = grabber && !grabber->isEnabled(); bool stealThisEvent = _stealMouse; - bool contains = false; + bool containsPoint = false; if (!stealThisEvent) { switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseMove: case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast<QMouseEvent*>(event); - QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height())); - contains = myRect.contains(me->windowPos()); + containsPoint = contains(mapFromScene(me->windowPos())); } break; case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: { QTouchEvent *te = static_cast<QTouchEvent*>(event); - QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height())); foreach (const QTouchEvent::TouchPoint &point, te->touchPoints()) { - if (myRect.contains(point.scenePos())) { - contains = true; + if (contains(mapFromScene(point.scenePos()))) { + containsPoint = true; break; } } @@ -752,7 +750,7 @@ bool QQuickMultiPointTouchArea::shouldFilter(QEvent *event) break; } } - if ((stealThisEvent || contains) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) { + if ((stealThisEvent || containsPoint) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) { return true; } ungrab(); diff --git a/src/quick/items/qquickpathview.cpp b/src/quick/items/qquickpathview.cpp index 836943c478..ba5b237596 100644 --- a/src/quick/items/qquickpathview.cpp +++ b/src/quick/items/qquickpathview.cpp @@ -1300,12 +1300,10 @@ void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event) if (!interactive || !items.count() || !model || !modelCount) return; velocityBuffer.clear(); - QPointF scenePoint = q->mapToScene(event->localPos()); int idx = 0; for (; idx < items.count(); ++idx) { - QRectF rect = items.at(idx)->boundingRect(); - rect = items.at(idx)->mapRectToScene(rect); - if (rect.contains(scenePoint)) + QQuickItem *item = items.at(idx); + if (item->contains(item->mapFromScene(event->windowPos()))) break; } if (idx == items.count() && dragMargin == 0.) // didn't click on an item @@ -1470,12 +1468,13 @@ void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *) bool QQuickPathView::sendMouseEvent(QMouseEvent *event) { Q_D(QQuickPathView); - QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height())); + QPointF localPos = mapFromScene(event->windowPos()); + QQuickCanvas *c = canvas(); QQuickItem *grabber = c ? c->mouseGrabberItem() : 0; bool stealThisEvent = d->stealMouse; - if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) { - QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(), + if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) { + QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(), event->button(), event->buttons(), event->modifiers()); mouseEvent.setAccepted(false); diff --git a/src/quick/items/qquickpincharea.cpp b/src/quick/items/qquickpincharea.cpp index 6f79385d96..eba9d098ff 100644 --- a/src/quick/items/qquickpincharea.cpp +++ b/src/quick/items/qquickpincharea.cpp @@ -503,13 +503,13 @@ void QQuickPinchArea::mouseUngrabEvent() bool QQuickPinchArea::sendMouseEvent(QMouseEvent *event) { Q_D(QQuickPinchArea); - QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height())); + QPointF localPos = mapFromScene(event->windowPos()); QQuickCanvas *c = canvas(); QQuickItem *grabber = c ? c->mouseGrabberItem() : 0; bool stealThisEvent = d->stealMouse; - if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) { - QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(), + if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) { + QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(), event->button(), event->buttons(), event->modifiers()); mouseEvent.setAccepted(false); diff --git a/tests/auto/quick/qquickitem2/data/hollowTestItem.qml b/tests/auto/quick/qquickitem2/data/hollowTestItem.qml new file mode 100644 index 0000000000..d07d89c4f4 --- /dev/null +++ b/tests/auto/quick/qquickitem2/data/hollowTestItem.qml @@ -0,0 +1,38 @@ +import QtQuick 2.0 +import Test 1.0 + +Rectangle { + width: 400 + height: 400 + + Rectangle { + x: 100 + y: 100 + width: 200 + height: 200 + rotation: 45 + + Rectangle { + scale: 0.5 + color: "black" + anchors.fill: parent + radius: hollowItem.circle ? 100 : 0 + + Rectangle { + color: "white" + anchors.centerIn: parent + width: hollowItem.holeRadius * 2 + height: hollowItem.holeRadius * 2 + radius: hollowItem.circle ? 100 : 0 + } + + HollowTestItem { + id: hollowItem + anchors.fill: parent + objectName: "hollowItem" + holeRadius: 50 + circle: circleShapeTest + } + } + } +} diff --git a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp index c2390c4525..34f51875c9 100644 --- a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp @@ -93,6 +93,9 @@ private slots: void qtbug_16871(); void visibleChildren(); void parentLoop(); + void contains_data(); + void contains(); + private: QQmlEngine engine; }; @@ -178,6 +181,69 @@ public: QML_DECLARE_TYPE(KeyTestItem); +class HollowTestItem : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(bool circle READ isCircle WRITE setCircle) + Q_PROPERTY(qreal holeRadius READ holeRadius WRITE setHoleRadius) + +public: + HollowTestItem(QQuickItem *parent = 0) + : QQuickItem(parent), + m_isPressed(false), + m_isHovered(false), + m_isCircle(false), + m_holeRadius(50) + { + setAcceptHoverEvents(true); + setAcceptedMouseButtons(Qt::LeftButton); + } + + bool isPressed() const { return m_isPressed; } + bool isHovered() const { return m_isHovered; } + + bool isCircle() const { return m_isCircle; } + void setCircle(bool circle) { m_isCircle = circle; } + + qreal holeRadius() const { return m_holeRadius; } + void setHoleRadius(qreal radius) { m_holeRadius = radius; } + + bool contains(const QPointF &point) const { + const qreal w = width(); + const qreal h = height(); + const qreal r = m_holeRadius; + + // check boundaries + if (!QRectF(0, 0, w, h).contains(point)) + return false; + + // square shape + if (!m_isCircle) + return !QRectF(w / 2 - r, h / 2 - r, r * 2, r * 2).contains(point); + + // circle shape + const qreal dx = point.x() - (w / 2); + const qreal dy = point.y() - (h / 2); + const qreal dd = (dx * dx) + (dy * dy); + const qreal outerRadius = qMin<qreal>(w / 2, h / 2); + return dd > (r * r) && dd <= outerRadius * outerRadius; + } + +protected: + void hoverEnterEvent(QHoverEvent *) { m_isHovered = true; } + void hoverLeaveEvent(QHoverEvent *) { m_isHovered = false; } + void mousePressEvent(QMouseEvent *) { m_isPressed = true; } + void mouseReleaseEvent(QMouseEvent *) { m_isPressed = false; } + +private: + bool m_isPressed; + bool m_isHovered; + bool m_isCircle; + qreal m_holeRadius; +}; + +QML_DECLARE_TYPE(HollowTestItem); + tst_QQuickItem::tst_QQuickItem() { @@ -187,6 +253,7 @@ void tst_QQuickItem::initTestCase() { QQmlDataTest::initTestCase(); qmlRegisterType<KeyTestItem>("Test",1,0,"KeyTestItem"); + qmlRegisterType<HollowTestItem>("Test", 1, 0, "HollowTestItem"); } void tst_QQuickItem::cleanup() @@ -1375,6 +1442,110 @@ void tst_QQuickItem::parentLoop() delete canvas; } +void tst_QQuickItem::contains_data() +{ + QTest::addColumn<bool>("circleTest"); + QTest::addColumn<bool>("insideTarget"); + QTest::addColumn<QList<QPoint> >("points"); + + QList<QPoint> points; + + points << QPoint(176, 176) + << QPoint(176, 226) + << QPoint(226, 176) + << QPoint(226, 226) + << QPoint(150, 200) + << QPoint(200, 150) + << QPoint(200, 250) + << QPoint(250, 200); + QTest::newRow("hollow square: testing points inside") << false << true << points; + + points.clear(); + points << QPoint(162, 162) + << QPoint(162, 242) + << QPoint(242, 162) + << QPoint(242, 242) + << QPoint(200, 200) + << QPoint(175, 200) + << QPoint(200, 175) + << QPoint(200, 228) + << QPoint(228, 200) + << QPoint(200, 122) + << QPoint(122, 200) + << QPoint(200, 280) + << QPoint(280, 200); + QTest::newRow("hollow square: testing points outside") << false << false << points; + + points.clear(); + points << QPoint(174, 174) + << QPoint(174, 225) + << QPoint(225, 174) + << QPoint(225, 225) + << QPoint(165, 200) + << QPoint(200, 165) + << QPoint(200, 235) + << QPoint(235, 200); + QTest::newRow("hollow circle: testing points inside") << true << true << points; + + points.clear(); + points << QPoint(160, 160) + << QPoint(160, 240) + << QPoint(240, 160) + << QPoint(240, 240) + << QPoint(200, 200) + << QPoint(185, 185) + << QPoint(185, 216) + << QPoint(216, 185) + << QPoint(216, 216) + << QPoint(145, 200) + << QPoint(200, 145) + << QPoint(255, 200) + << QPoint(200, 255); + QTest::newRow("hollow circle: testing points outside") << true << false << points; +} + +void tst_QQuickItem::contains() +{ + QFETCH(bool, circleTest); + QFETCH(bool, insideTarget); + QFETCH(QList<QPoint>, points); + + QQuickView *canvas = new QQuickView(0); + canvas->rootContext()->setContextProperty("circleShapeTest", circleTest); + canvas->setBaseSize(QSize(400, 400)); + canvas->setSource(testFileUrl("hollowTestItem.qml")); + canvas->show(); + canvas->requestActivateWindow(); + QTest::qWaitForWindowShown(canvas); + QTRY_VERIFY(QGuiApplication::focusWindow() == canvas); + + QQuickItem *root = qobject_cast<QQuickItem *>(canvas->rootObject()); + QVERIFY(root); + + HollowTestItem *hollowItem = root->findChild<HollowTestItem *>("hollowItem"); + QVERIFY(hollowItem); + + foreach (const QPoint &point, points) { + // check mouse hover + QTest::mouseMove(canvas, point); + QTest::qWait(10); + QCOMPARE(hollowItem->isHovered(), insideTarget); + + // check mouse press + QTest::mousePress(canvas, Qt::LeftButton, 0, point); + QTest::qWait(10); + QCOMPARE(hollowItem->isPressed(), insideTarget); + + // check mouse release + QTest::mouseRelease(canvas, Qt::LeftButton, 0, point); + QTest::qWait(10); + QCOMPARE(hollowItem->isPressed(), false); + } + + delete canvas; +} + + QTEST_MAIN(tst_QQuickItem) #include "tst_qquickitem.moc" |