diff options
Diffstat (limited to 'src/quick/handlers')
-rw-r--r-- | src/quick/handlers/handlers.pri | 7 | ||||
-rw-r--r-- | src/quick/handlers/qquickdraghandler.cpp | 45 | ||||
-rw-r--r-- | src/quick/handlers/qquickdraghandler_p.h | 13 | ||||
-rw-r--r-- | src/quick/handlers/qquickhoverhandler.cpp | 11 | ||||
-rw-r--r-- | src/quick/handlers/qquickmultipointhandler.cpp | 170 | ||||
-rw-r--r-- | src/quick/handlers/qquickmultipointhandler_p.h | 18 | ||||
-rw-r--r-- | src/quick/handlers/qquickmultipointhandler_p_p.h | 83 | ||||
-rw-r--r-- | src/quick/handlers/qquickpinchhandler.cpp | 58 | ||||
-rw-r--r-- | src/quick/handlers/qquickpinchhandler_p.h | 1 | ||||
-rw-r--r-- | src/quick/handlers/qquickpointerdevicehandler.cpp | 3 | ||||
-rw-r--r-- | src/quick/handlers/qquicktaphandler.cpp | 16 | ||||
-rw-r--r-- | src/quick/handlers/qquickwheelhandler.cpp | 520 | ||||
-rw-r--r-- | src/quick/handlers/qquickwheelhandler_p.h | 128 | ||||
-rw-r--r-- | src/quick/handlers/qquickwheelhandler_p_p.h | 87 |
14 files changed, 1045 insertions, 115 deletions
diff --git a/src/quick/handlers/handlers.pri b/src/quick/handlers/handlers.pri index 226cca22cb..49975bd1ca 100644 --- a/src/quick/handlers/handlers.pri +++ b/src/quick/handlers/handlers.pri @@ -3,6 +3,7 @@ HEADERS += \ $$PWD/qquickhandlerpoint_p.h \ $$PWD/qquickhoverhandler_p.h \ $$PWD/qquickmultipointhandler_p.h \ + $$PWD/qquickmultipointhandler_p_p.h \ $$PWD/qquickpinchhandler_p.h \ $$PWD/qquickpointerdevicehandler_p.h \ $$PWD/qquickpointerdevicehandler_p_p.h \ @@ -26,3 +27,9 @@ SOURCES += \ $$PWD/qquicksinglepointhandler.cpp \ $$PWD/qquicktaphandler.cpp \ $$PWD/qquickdragaxis.cpp + +qtConfig(wheelevent) { + HEADERS += $$PWD/qquickwheelhandler_p.h $$PWD/qquickwheelhandler_p_p.h + SOURCES += $$PWD/qquickwheelhandler.cpp +} + diff --git a/src/quick/handlers/qquickdraghandler.cpp b/src/quick/handlers/qquickdraghandler.cpp index e5531369cf..61b66beff4 100644 --- a/src/quick/handlers/qquickdraghandler.cpp +++ b/src/quick/handlers/qquickdraghandler.cpp @@ -103,7 +103,7 @@ bool QQuickDragHandler::targetContainsCentroid() QPointF QQuickDragHandler::targetCentroidPosition() { - QPointF pos = m_centroid.position(); + QPointF pos = centroid().position(); if (target() != parentItem()) pos = parentItem()->mapToItem(target(), pos); return pos; @@ -114,8 +114,14 @@ void QQuickDragHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEvent QQuickMultiPointHandler::onGrabChanged(grabber, transition, point); if (grabber == this && transition == QQuickEventPoint::GrabExclusive && target()) { // In case the grab got handed over from another grabber, we might not get the Press. - if (!m_pressedInsideTarget) { - if (target() != parentItem()) + + auto isDescendant = [](QQuickItem *parent, QQuickItem *target) { + return (target != parent) && !target->isAncestorOf(parent); + }; + if (m_snapMode == SnapAlways + || (m_snapMode == SnapIfPressedOutsideTarget && !m_pressedInsideTarget) + || (m_snapMode == SnapAuto && !m_pressedInsideTarget && isDescendant(parentItem(), target())) + ) { m_pressTargetPos = QPointF(target()->width(), target()->height()) / 2; } else if (m_pressTargetPos.isNull()) { m_pressTargetPos = targetCentroidPosition(); @@ -123,6 +129,33 @@ void QQuickDragHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEvent } } +/*! + \qmlproperty enumeration QtQuick.DragHandler::snapMode + + This property holds the snap mode. + + The snap mode configures snapping of the \l target item's center to the event point. + + Possible values: + \value DragHandler.SnapNever Never snap + \value DragHandler.SnapAuto The \l target snaps if the event point was pressed outside of the \l target + item \e and the \l target is a descendant of \l parentItem (default) + \value DragHandler.SnapWhenPressedOutsideTarget The \l target snaps if the event point was pressed outside of the \l target + \value DragHandler.SnapAlways Always snap +*/ +QQuickDragHandler::SnapMode QQuickDragHandler::snapMode() const +{ + return m_snapMode; +} + +void QQuickDragHandler::setSnapMode(QQuickDragHandler::SnapMode mode) +{ + if (mode == m_snapMode) + return; + m_snapMode = mode; + emit snapModeChanged(); +} + void QQuickDragHandler::onActiveChanged() { QQuickMultiPointHandler::onActiveChanged(); @@ -153,7 +186,7 @@ void QQuickDragHandler::handlePointerEventImpl(QQuickPointerEvent *event) if (active()) { // Calculate drag delta, taking into account the axis enabled constraint // i.e. if xAxis is not enabled, then ignore the horizontal component of the actual movement - QVector2D accumulatedDragDelta = QVector2D(m_centroid.scenePosition() - m_centroid.scenePressPosition()); + QVector2D accumulatedDragDelta = QVector2D(centroid().scenePosition() - centroid().scenePressPosition()); if (!m_xAxis.enabled()) accumulatedDragDelta.setX(0); if (!m_yAxis.enabled()) @@ -169,9 +202,9 @@ void QQuickDragHandler::handlePointerEventImpl(QQuickPointerEvent *event) QVector <QQuickEventPoint *> chosenPoints; if (event->isPressEvent()) - m_pressedInsideTarget = target() && m_currentPoints.count() > 0; + m_pressedInsideTarget = target() && currentPoints().count() > 0; - for (const QQuickHandlerPoint &p : m_currentPoints) { + for (const QQuickHandlerPoint &p : currentPoints()) { if (!allOverThreshold) break; QQuickEventPoint *point = event->pointById(p.id()); diff --git a/src/quick/handlers/qquickdraghandler_p.h b/src/quick/handlers/qquickdraghandler_p.h index 748026488a..e8f47163ed 100644 --- a/src/quick/handlers/qquickdraghandler_p.h +++ b/src/quick/handlers/qquickdraghandler_p.h @@ -62,8 +62,17 @@ class Q_QUICK_PRIVATE_EXPORT QQuickDragHandler : public QQuickMultiPointHandler Q_PROPERTY(QQuickDragAxis * xAxis READ xAxis CONSTANT) Q_PROPERTY(QQuickDragAxis * yAxis READ yAxis CONSTANT) Q_PROPERTY(QVector2D translation READ translation NOTIFY translationChanged) + Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode NOTIFY snapModeChanged REVISION 14) public: + enum SnapMode { + NoSnap = 0, + SnapAuto, + SnapIfPressedOutsideTarget, + SnapAlways + }; + Q_ENUM(SnapMode) + explicit QQuickDragHandler(QQuickItem *parent = nullptr); void handlePointerEventImpl(QQuickPointerEvent *event) override; @@ -73,11 +82,14 @@ public: QVector2D translation() const { return m_translation; } void setTranslation(const QVector2D &trans); + QQuickDragHandler::SnapMode snapMode() const; + void setSnapMode(QQuickDragHandler::SnapMode mode); void enforceConstraints(); Q_SIGNALS: void translationChanged(); + Q_REVISION(14) void snapModeChanged(); protected: void onActiveChanged() override; @@ -97,6 +109,7 @@ private: QQuickDragAxis m_xAxis; QQuickDragAxis m_yAxis; + QQuickDragHandler::SnapMode m_snapMode = SnapAuto; bool m_pressedInsideTarget = false; friend class QQuickDragAxis; diff --git a/src/quick/handlers/qquickhoverhandler.cpp b/src/quick/handlers/qquickhoverhandler.cpp index a6325e084b..79cb288af8 100644 --- a/src/quick/handlers/qquickhoverhandler.cpp +++ b/src/quick/handlers/qquickhoverhandler.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. @@ -72,9 +72,6 @@ QQuickHoverHandler::QQuickHoverHandler(QQuickItem *parent) { // Tell QQuickPointerDeviceHandler::wantsPointerEvent() to ignore button state d_func()->acceptedButtons = Qt::NoButton; - // Rule out the touchscreen for now (can be overridden in QML in case a hover-detecting touchscreen exists) - setAcceptedDevices(static_cast<QQuickPointerDevice::DeviceType>( - static_cast<int>(QQuickPointerDevice::AllDevices) ^ static_cast<int>(QQuickPointerDevice::TouchScreen))); } QQuickHoverHandler::~QQuickHoverHandler() @@ -103,7 +100,11 @@ bool QQuickHoverHandler::wantsPointerEvent(QQuickPointerEvent *event) void QQuickHoverHandler::handleEventPoint(QQuickEventPoint *point) { - setHovered(true); + bool hovered = true; + if (point->state() == QQuickEventPoint::Released && + point->pointerEvent()->device()->pointerType() == QQuickPointerDevice::Finger) + hovered = false; + setHovered(hovered); setPassiveGrab(point); } diff --git a/src/quick/handlers/qquickmultipointhandler.cpp b/src/quick/handlers/qquickmultipointhandler.cpp index 6fbab29077..f404788de4 100644 --- a/src/quick/handlers/qquickmultipointhandler.cpp +++ b/src/quick/handlers/qquickmultipointhandler.cpp @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qquickmultipointhandler_p.h" +#include "qquickmultipointhandler_p_p.h" #include <private/qquickitem_p.h> #include <QLineF> #include <QMouseEvent> @@ -59,14 +60,13 @@ QT_BEGIN_NAMESPACE of multiple touchpoints. */ QQuickMultiPointHandler::QQuickMultiPointHandler(QQuickItem *parent, int minimumPointCount, int maximumPointCount) - : QQuickPointerDeviceHandler(parent) - , m_minimumPointCount(minimumPointCount) - , m_maximumPointCount(maximumPointCount) + : QQuickPointerDeviceHandler(*(new QQuickMultiPointHandlerPrivate(minimumPointCount, maximumPointCount)), parent) { } bool QQuickMultiPointHandler::wantsPointerEvent(QQuickPointerEvent *event) { + Q_D(QQuickMultiPointHandler); if (!QQuickPointerDeviceHandler::wantsPointerEvent(event)) return false; @@ -84,14 +84,14 @@ bool QQuickMultiPointHandler::wantsPointerEvent(QQuickPointerEvent *event) // handle a specific number of points, so a differing number of points will // usually result in different behavior. But otherwise if the currentPoints // are all still there in the event, we're good to go (do not reset - // m_currentPoints, because we don't want to lose the pressPosition, and do + // currentPoints, because we don't want to lose the pressPosition, and do // not want to reshuffle the order either). const QVector<QQuickEventPoint *> candidatePoints = eligiblePoints(event); - if (candidatePoints.count() != m_currentPoints.count()) { - m_currentPoints.clear(); + if (candidatePoints.count() != d->currentPoints.count()) { + d->currentPoints.clear(); if (active()) { setActive(false); - m_centroid.reset(); + d->centroid.reset(); emit centroidChanged(); } } else if (hasCurrentPoints(event)) { @@ -101,57 +101,60 @@ bool QQuickMultiPointHandler::wantsPointerEvent(QQuickPointerEvent *event) ret = ret || (candidatePoints.size() >= minimumPointCount() && candidatePoints.size() <= maximumPointCount()); if (ret) { const int c = candidatePoints.count(); - m_currentPoints.resize(c); + d->currentPoints.resize(c); for (int i = 0; i < c; ++i) { - m_currentPoints[i].reset(candidatePoints[i]); - m_currentPoints[i].localize(parentItem()); + d->currentPoints[i].reset(candidatePoints[i]); + d->currentPoints[i].localize(parentItem()); } } else { - m_currentPoints.clear(); + d->currentPoints.clear(); } return ret; } void QQuickMultiPointHandler::handlePointerEventImpl(QQuickPointerEvent *event) { + Q_D(QQuickMultiPointHandler); QQuickPointerHandler::handlePointerEventImpl(event); - // event's points can be reordered since the previous event, which is why m_currentPoints + // event's points can be reordered since the previous event, which is why currentPoints // is _not_ a shallow copy of the QQuickPointerTouchEvent::m_touchPoints vector. - // So we have to update our m_currentPoints instances based on the given event. - for (QQuickHandlerPoint &p : m_currentPoints) { + // So we have to update our currentPoints instances based on the given event. + for (QQuickHandlerPoint &p : d->currentPoints) { const QQuickEventPoint *ep = event->pointById(p.id()); if (ep) p.reset(ep); } - QPointF sceneGrabPos = m_centroid.sceneGrabPosition(); - m_centroid.reset(m_currentPoints); - m_centroid.m_sceneGrabPosition = sceneGrabPos; // preserve as it was + QPointF sceneGrabPos = d->centroid.sceneGrabPosition(); + d->centroid.reset(d->currentPoints); + d->centroid.m_sceneGrabPosition = sceneGrabPos; // preserve as it was emit centroidChanged(); } void QQuickMultiPointHandler::onActiveChanged() { + Q_D(QQuickMultiPointHandler); if (active()) { - m_centroid.m_sceneGrabPosition = m_centroid.m_scenePosition; + d->centroid.m_sceneGrabPosition = d->centroid.m_scenePosition; } else { - // Don't call m_centroid.reset() here, because in a QML onActiveChanged + // Don't call centroid.reset() here, because in a QML onActiveChanged // callback, we'd like to see what the position _was_, what the velocity _was_, etc. // (having them undefined is not useful) // But pressedButtons and pressedModifiers are meant to be more real-time than those // (which seems a bit inconsistent, from one side). - m_centroid.m_pressedButtons = Qt::NoButton; - m_centroid.m_pressedModifiers = Qt::NoModifier; + d->centroid.m_pressedButtons = Qt::NoButton; + d->centroid.m_pressedModifiers = Qt::NoModifier; } } void QQuickMultiPointHandler::onGrabChanged(QQuickPointerHandler *, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *) { + Q_D(QQuickMultiPointHandler); // If another handler or item takes over this set of points, assume it has // decided that it's the better fit for them. Don't immediately re-grab // at the next opportunity. This should help to avoid grab cycles // (e.g. between DragHandler and PinchHandler). if (transition == QQuickEventPoint::UngrabExclusive || transition == QQuickEventPoint::CancelGrabExclusive) - m_currentPoints.clear(); + d->currentPoints.clear(); } QVector<QQuickEventPoint *> QQuickMultiPointHandler::eligiblePoints(QQuickPointerEvent *event) @@ -191,14 +194,21 @@ QVector<QQuickEventPoint *> QQuickMultiPointHandler::eligiblePoints(QQuickPointe The default value is 2. */ +int QQuickMultiPointHandler::minimumPointCount() const +{ + Q_D(const QQuickMultiPointHandler); + return d->minimumPointCount; +} + void QQuickMultiPointHandler::setMinimumPointCount(int c) { - if (m_minimumPointCount == c) + Q_D(QQuickMultiPointHandler); + if (d->minimumPointCount == c) return; - m_minimumPointCount = c; + d->minimumPointCount = c; emit minimumPointCountChanged(); - if (m_maximumPointCount < 0) + if (d->maximumPointCount < 0) emit maximumPointCountChanged(); } @@ -217,22 +227,61 @@ void QQuickMultiPointHandler::setMinimumPointCount(int c) The default value is the same as \l minimumPointCount. */ +int QQuickMultiPointHandler::maximumPointCount() const +{ + Q_D(const QQuickMultiPointHandler); + return d->maximumPointCount >= 0 ? d->maximumPointCount : d->minimumPointCount; +} + void QQuickMultiPointHandler::setMaximumPointCount(int maximumPointCount) { - if (m_maximumPointCount == maximumPointCount) + Q_D(QQuickMultiPointHandler); + if (d->maximumPointCount == maximumPointCount) return; - m_maximumPointCount = maximumPointCount; + d->maximumPointCount = maximumPointCount; emit maximumPointCountChanged(); } +/*! + \readonly + \qmlproperty QtQuick::HandlerPoint QtQuick::MultiPointHandler::centroid + + A point exactly in the middle of the currently-pressed touch points. + If only one point is pressed, it's the same as that point. + A handler that has a \l target will normally transform it relative to this point. +*/ +const QQuickHandlerPoint &QQuickMultiPointHandler::centroid() const +{ + Q_D(const QQuickMultiPointHandler); + return d->centroid; +} + +/*! + Returns a modifiable reference to the point that will be returned by the + \l centroid property. If you modify it, you are responsible to emit + \l centroidChanged. +*/ +QQuickHandlerPoint &QQuickMultiPointHandler::mutableCentroid() +{ + Q_D(QQuickMultiPointHandler); + return d->centroid; +} + +QVector<QQuickHandlerPoint> &QQuickMultiPointHandler::currentPoints() +{ + Q_D(QQuickMultiPointHandler); + return d->currentPoints; +} + bool QQuickMultiPointHandler::hasCurrentPoints(QQuickPointerEvent *event) { - if (event->pointCount() < m_currentPoints.size() || m_currentPoints.size() == 0) + Q_D(const QQuickMultiPointHandler); + if (event->pointCount() < d->currentPoints.size() || d->currentPoints.size() == 0) return false; // TODO optimize: either ensure the points are sorted, // or use std::equal with a predicate - for (const QQuickHandlerPoint &p : qAsConst(m_currentPoints)) { + for (const QQuickHandlerPoint &p : qAsConst(d->currentPoints)) { const QQuickEventPoint *ep = event->pointById(p.id()); if (!ep) return false; @@ -244,30 +293,33 @@ bool QQuickMultiPointHandler::hasCurrentPoints(QQuickPointerEvent *event) qreal QQuickMultiPointHandler::averageTouchPointDistance(const QPointF &ref) { + Q_D(const QQuickMultiPointHandler); qreal ret = 0; - if (Q_UNLIKELY(m_currentPoints.size() == 0)) + if (Q_UNLIKELY(d->currentPoints.size() == 0)) return ret; - for (const QQuickHandlerPoint &p : m_currentPoints) + for (const QQuickHandlerPoint &p : d->currentPoints) ret += QVector2D(p.scenePosition() - ref).length(); - return ret / m_currentPoints.size(); + return ret / d->currentPoints.size(); } qreal QQuickMultiPointHandler::averageStartingDistance(const QPointF &ref) { + Q_D(const QQuickMultiPointHandler); // TODO cache it in setActive()? qreal ret = 0; - if (Q_UNLIKELY(m_currentPoints.size() == 0)) + if (Q_UNLIKELY(d->currentPoints.size() == 0)) return ret; - for (const QQuickHandlerPoint &p : m_currentPoints) + for (const QQuickHandlerPoint &p : d->currentPoints) ret += QVector2D(p.sceneGrabPosition() - ref).length(); - return ret / m_currentPoints.size(); + return ret / d->currentPoints.size(); } QVector<QQuickMultiPointHandler::PointData> QQuickMultiPointHandler::angles(const QPointF &ref) const { + Q_D(const QQuickMultiPointHandler); QVector<PointData> angles; - angles.reserve(m_currentPoints.count()); - for (const QQuickHandlerPoint &p : m_currentPoints) { + angles.reserve(d->currentPoints.count()); + for (const QQuickHandlerPoint &p : d->currentPoints) { qreal angle = QLineF(ref, p.scenePosition()).angle(); angles.append(PointData(p.id(), -angle)); // convert to clockwise, to be consistent with QQuickItem::rotation } @@ -312,7 +364,7 @@ void QQuickMultiPointHandler::acceptPoints(const QVector<QQuickEventPoint *> &po point->setAccepted(); } -bool QQuickMultiPointHandler::grabPoints(QVector<QQuickEventPoint *> points) +bool QQuickMultiPointHandler::grabPoints(const QVector<QQuickEventPoint *> &points) { if (points.isEmpty()) return false; @@ -332,17 +384,41 @@ bool QQuickMultiPointHandler::grabPoints(QVector<QQuickEventPoint *> points) void QQuickMultiPointHandler::moveTarget(QPointF pos) { - target()->setPosition(pos); - m_centroid.m_position = target()->mapFromScene(m_centroid.m_scenePosition); + Q_D(QQuickMultiPointHandler); + if (QQuickItem *t = target()) { + d->xMetaProperty().write(t, pos.x()); + d->yMetaProperty().write(t, pos.y()); + d->centroid.m_position = t->mapFromScene(d->centroid.m_scenePosition); + } else { + qWarning() << "moveTarget: target is null"; + } } -/*! - \readonly - \qmlproperty QtQuick::HandlerPoint QtQuick::MultiPointHandler::centroid +QQuickMultiPointHandlerPrivate::QQuickMultiPointHandlerPrivate(int minPointCount, int maxPointCount) + : QQuickPointerDeviceHandlerPrivate() + , minimumPointCount(minPointCount) + , maximumPointCount(maxPointCount) +{ +} - A point exactly in the middle of the currently-pressed touch points. - If only one point is pressed, it's the same as that point. - A handler that has a \l target will normally transform it relative to this point. -*/ +QMetaProperty &QQuickMultiPointHandlerPrivate::xMetaProperty() const +{ + Q_Q(const QQuickMultiPointHandler); + if (!xProperty.isValid() && q->target()) { + const QMetaObject *targetMeta = q->target()->metaObject(); + xProperty = targetMeta->property(targetMeta->indexOfProperty("x")); + } + return xProperty; +} + +QMetaProperty &QQuickMultiPointHandlerPrivate::yMetaProperty() const +{ + Q_Q(const QQuickMultiPointHandler); + if (!yProperty.isValid() && q->target()) { + const QMetaObject *targetMeta = q->target()->metaObject(); + yProperty = targetMeta->property(targetMeta->indexOfProperty("y")); + } + return yProperty; +} QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickmultipointhandler_p.h b/src/quick/handlers/qquickmultipointhandler_p.h index 06f170154b..480f69035b 100644 --- a/src/quick/handlers/qquickmultipointhandler_p.h +++ b/src/quick/handlers/qquickmultipointhandler_p.h @@ -58,6 +58,8 @@ QT_BEGIN_NAMESPACE +class QQuickMultiPointHandlerPrivate; + class Q_QUICK_PRIVATE_EXPORT QQuickMultiPointHandler : public QQuickPointerDeviceHandler { Q_OBJECT @@ -68,13 +70,13 @@ class Q_QUICK_PRIVATE_EXPORT QQuickMultiPointHandler : public QQuickPointerDevic public: explicit QQuickMultiPointHandler(QQuickItem *parent = nullptr, int minimumPointCount = 2, int maximumPointCount = -1); - int minimumPointCount() const { return m_minimumPointCount; } + int minimumPointCount() const; void setMinimumPointCount(int c); - int maximumPointCount() const { return m_maximumPointCount >= 0 ? m_maximumPointCount : m_minimumPointCount; } + int maximumPointCount() const; void setMaximumPointCount(int maximumPointCount); - QQuickHandlerPoint centroid() const { return m_centroid; } + const QQuickHandlerPoint ¢roid() const; signals: void minimumPointCountChanged(); @@ -94,6 +96,8 @@ protected: void handlePointerEventImpl(QQuickPointerEvent *event) override; void onActiveChanged() override; void onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabTransition transition, QQuickEventPoint *point) override; + QVector<QQuickHandlerPoint> ¤tPoints(); + QQuickHandlerPoint &mutableCentroid(); bool hasCurrentPoints(QQuickPointerEvent *event); QVector<QQuickEventPoint *> eligiblePoints(QQuickPointerEvent *event); qreal averageTouchPointDistance(const QPointF &ref); @@ -103,14 +107,10 @@ protected: QVector<PointData> angles(const QPointF &ref) const; static qreal averageAngleDelta(const QVector<PointData> &old, const QVector<PointData> &newAngles); void acceptPoints(const QVector<QQuickEventPoint *> &points); - bool grabPoints(QVector<QQuickEventPoint *> points); + bool grabPoints(const QVector<QQuickEventPoint *> &points); void moveTarget(QPointF pos); -protected: - QVector<QQuickHandlerPoint> m_currentPoints; - QQuickHandlerPoint m_centroid; - int m_minimumPointCount; - int m_maximumPointCount; + Q_DECLARE_PRIVATE(QQuickMultiPointHandler) }; QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickmultipointhandler_p_p.h b/src/quick/handlers/qquickmultipointhandler_p_p.h new file mode 100644 index 0000000000..406e4a1fdf --- /dev/null +++ b/src/quick/handlers/qquickmultipointhandler_p_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** 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$ +** +****************************************************************************/ + +#ifndef QQUICKPOINTERMULTIHANDLER_P_H +#define QQUICKPOINTERMULTIHANDLER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickhandlerpoint_p.h" +#include "qquickpointerdevicehandler_p_p.h" +#include "qquickmultipointhandler_p.h" + +QT_BEGIN_NAMESPACE + +class Q_QUICK_PRIVATE_EXPORT QQuickMultiPointHandlerPrivate : public QQuickPointerDeviceHandlerPrivate +{ + Q_DECLARE_PUBLIC(QQuickMultiPointHandler) + +public: + static QQuickMultiPointHandlerPrivate* get(QQuickMultiPointHandler *q) { return q->d_func(); } + static const QQuickMultiPointHandlerPrivate* get(const QQuickMultiPointHandler *q) { return q->d_func(); } + + QQuickMultiPointHandlerPrivate(int minPointCount, int maxPointCount); + + QMetaProperty &xMetaProperty() const; + QMetaProperty &yMetaProperty() const; + + QVector<QQuickHandlerPoint> currentPoints; + QQuickHandlerPoint centroid; + int minimumPointCount; + int maximumPointCount; + mutable QMetaProperty xProperty; + mutable QMetaProperty yProperty; +}; + +QT_END_NAMESPACE + +#endif // QQUICKPOINTERMULTIHANDLER_P_H diff --git a/src/quick/handlers/qquickpinchhandler.cpp b/src/quick/handlers/qquickpinchhandler.cpp index dc1a9a92f9..a5a867015c 100644 --- a/src/quick/handlers/qquickpinchhandler.cpp +++ b/src/quick/handlers/qquickpinchhandler.cpp @@ -267,20 +267,14 @@ void QQuickPinchHandler::onActiveChanged() { QQuickMultiPointHandler::onActiveChanged(); if (active()) { - m_startMatrix = QMatrix4x4(); - m_startAngles = angles(m_centroid.sceneGrabPosition()); - m_startDistance = averageTouchPointDistance(m_centroid.sceneGrabPosition()); + m_startAngles = angles(centroid().sceneGrabPosition()); + m_startDistance = averageTouchPointDistance(centroid().sceneGrabPosition()); m_activeRotation = 0; m_activeTranslation = QVector2D(); if (const QQuickItem *t = target()) { m_startScale = t->scale(); // TODO incompatible with independent x/y scaling m_startRotation = t->rotation(); - QVector3D xformOrigin(t->transformOriginPoint()); - m_startMatrix.translate(float(t->x()), float(t->y())); - m_startMatrix.translate(xformOrigin); - m_startMatrix.scale(float(m_startScale)); - m_startMatrix.rotate(float(m_startRotation), 0, 0, -1); - m_startMatrix.translate(-xformOrigin); + m_startPos = t->position(); } else { m_startScale = m_accumulatedScale; m_startRotation = 0; @@ -294,7 +288,7 @@ void QQuickPinchHandler::onActiveChanged() void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) { if (Q_UNLIKELY(lcPinchHandler().isDebugEnabled())) { - for (const QQuickHandlerPoint &p : m_currentPoints) + for (const QQuickHandlerPoint &p : currentPoints()) qCDebug(lcPinchHandler) << hex << p.id() << p.sceneGrabPosition() << "->" << p.scenePosition(); } QQuickMultiPointHandler::handlePointerEventImpl(event); @@ -302,13 +296,13 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) qreal dist = 0; #if QT_CONFIG(gestures) if (const auto gesture = event->asPointerNativeGestureEvent()) { - m_centroid.reset(event->point(0)); + mutableCentroid().reset(event->point(0)); switch (gesture->type()) { case Qt::EndNativeGesture: m_activeScale = 1; m_activeRotation = 0; m_activeTranslation = QVector2D(); - m_centroid.reset(); + mutableCentroid().reset(); setActive(false); emit updated(); return; @@ -333,7 +327,7 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) { const bool containsReleasedPoints = event->isReleaseEvent(); QVector<QQuickEventPoint *> chosenPoints; - for (const QQuickHandlerPoint &p : m_currentPoints) { + for (const QQuickHandlerPoint &p : currentPoints()) { QQuickEventPoint *ep = event->pointById(p.id()); chosenPoints << ep; } @@ -341,8 +335,8 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) // Verify that at least one of the points has moved beyond threshold needed to activate the handler int numberOfPointsDraggedOverThreshold = 0; QVector2D accumulatedDrag; - const QVector2D currentCentroid(m_centroid.scenePosition()); - const QVector2D pressCentroid(m_centroid.scenePressPosition()); + const QVector2D currentCentroid(centroid().scenePosition()); + const QVector2D pressCentroid(centroid().scenePressPosition()); QStyleHints *styleHints = QGuiApplication::styleHints(); const int dragThreshold = styleHints->startDragDistance(); @@ -410,9 +404,9 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) } const bool requiredNumberOfPointsDraggedOverThreshold = numberOfPointsDraggedOverThreshold >= minimumPointCount() && numberOfPointsDraggedOverThreshold <= maximumPointCount(); - accumulatedMovementMagnitude /= m_currentPoints.count(); + accumulatedMovementMagnitude /= currentPoints().count(); - QVector2D avgDrag = accumulatedDrag / m_currentPoints.count(); + QVector2D avgDrag = accumulatedDrag / currentPoints().count(); if (!xAxis()->enabled()) avgDrag.setX(0); if (!yAxis()->enabled()) @@ -445,12 +439,12 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) // avoid mapping the minima and maxima, as they might have unmappable values // such as -inf/+inf. Because of this we perform the bounding to min/max in local coords. // 1. scale - dist = averageTouchPointDistance(m_centroid.scenePosition()); + dist = averageTouchPointDistance(centroid().scenePosition()); m_activeScale = dist / m_startDistance; m_activeScale = qBound(m_minimumScale/m_startScale, m_activeScale, m_maximumScale/m_startScale); // 2. rotate - QVector<PointData> newAngles = angles(m_centroid.scenePosition()); + QVector<PointData> newAngles = angles(centroid().scenePosition()); const qreal angleDelta = averageAngleDelta(m_startAngles, newAngles); m_activeRotation += angleDelta; m_startAngles = std::move(newAngles); @@ -465,25 +459,15 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) m_accumulatedScale = m_startScale * m_activeScale; if (target() && target()->parentItem()) { - const QPointF centroidParentPos = target()->parentItem()->mapFromScene(m_centroid.scenePosition()); + const QPointF centroidParentPos = target()->parentItem()->mapFromScene(centroid().scenePosition()); // 3. Drag/translate - const QPointF centroidStartParentPos = target()->parentItem()->mapFromScene(m_centroid.sceneGrabPosition()); + const QPointF centroidStartParentPos = target()->parentItem()->mapFromScene(centroid().sceneGrabPosition()); m_activeTranslation = QVector2D(centroidParentPos - centroidStartParentPos); // apply rotation + scaling around the centroid - then apply translation. - QMatrix4x4 mat; - - const QVector3D centroidParentVector(centroidParentPos); - mat.translate(centroidParentVector); - mat.rotate(float(m_activeRotation), 0, 0, 1); - mat.scale(float(m_activeScale)); - mat.translate(-centroidParentVector); - mat.translate(QVector3D(m_activeTranslation)); - - mat = mat * m_startMatrix; - - QPointF xformOriginPoint = target()->transformOriginPoint(); - QPointF pos = mat * xformOriginPoint; - pos -= xformOriginPoint; + QPointF pos = QQuickItemPrivate::get(target())->adjustedPosForTransform(centroidParentPos, + m_startPos, m_activeTranslation, + m_startScale, m_activeScale, + m_startRotation, m_activeRotation); if (xAxis()->enabled()) pos.setX(qBound(xAxis()->minimum(), pos.x(), xAxis()->maximum())); @@ -498,10 +482,10 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) target()->setRotation(rotation); target()->setScale(m_accumulatedScale); } else { - m_activeTranslation = QVector2D(m_centroid.scenePosition() - m_centroid.scenePressPosition()); + m_activeTranslation = QVector2D(centroid().scenePosition() - centroid().scenePressPosition()); } - qCDebug(lcPinchHandler) << "centroid" << m_centroid.scenePressPosition() << "->" << m_centroid.scenePosition() + qCDebug(lcPinchHandler) << "centroid" << centroid().scenePressPosition() << "->" << centroid().scenePosition() << ", distance" << m_startDistance << "->" << dist << ", startScale" << m_startScale << "->" << m_accumulatedScale << ", activeRotation" << m_activeRotation diff --git a/src/quick/handlers/qquickpinchhandler_p.h b/src/quick/handlers/qquickpinchhandler_p.h index 766d57f892..cea794f0c8 100644 --- a/src/quick/handlers/qquickpinchhandler_p.h +++ b/src/quick/handlers/qquickpinchhandler_p.h @@ -158,7 +158,6 @@ private: qreal m_accumulatedStartCentroidDistance = 0; QVector<PointData> m_startAngles; QQuickMatrix4x4 m_transform; - QMatrix4x4 m_startMatrix; }; QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickpointerdevicehandler.cpp b/src/quick/handlers/qquickpointerdevicehandler.cpp index 2964042f39..90f31bf9fd 100644 --- a/src/quick/handlers/qquickpointerdevicehandler.cpp +++ b/src/quick/handlers/qquickpointerdevicehandler.cpp @@ -307,7 +307,8 @@ bool QQuickPointerDeviceHandler::wantsPointerEvent(QQuickPointerEvent *event) return false; // HoverHandler sets acceptedButtons to Qt::NoButton to indicate that button state is irrelevant. if (event->device()->pointerType() != QQuickPointerDevice::Finger && acceptedButtons() != Qt::NoButton && - (event->buttons() & acceptedButtons()) == 0 && (event->button() & acceptedButtons()) == 0) + (event->buttons() & acceptedButtons()) == 0 && (event->button() & acceptedButtons()) == 0 + && !event->asPointerScrollEvent()) return false; return true; } diff --git a/src/quick/handlers/qquicktaphandler.cpp b/src/quick/handlers/qquicktaphandler.cpp index 3b42c5e536..7b26adbe6f 100644 --- a/src/quick/handlers/qquicktaphandler.cpp +++ b/src/quick/handlers/qquicktaphandler.cpp @@ -78,8 +78,8 @@ int QQuickTapHandler::m_touchMultiTapDistanceSquared(-1); \l gesturePolicy to \c TapHandler.ReleaseWithinBounds. For multi-tap gestures (double-tap, triple-tap etc.), the distance moved - must not exceed QPlatformTheme::MouseDoubleClickDistance with mouse and - QPlatformTheme::TouchDoubleTapDistance with touch, and the time between + must not exceed QStyleHints::mouseDoubleClickDistance() with mouse and + QStyleHints::touchDoubleTapDistance() with touch, and the time between taps must not exceed QStyleHints::mouseDoubleClickInterval(). \sa MouseArea @@ -90,11 +90,9 @@ QQuickTapHandler::QQuickTapHandler(QQuickItem *parent) { if (m_mouseMultiClickDistanceSquared < 0) { m_multiTapInterval = qApp->styleHints()->mouseDoubleClickInterval() / 1000.0; - m_mouseMultiClickDistanceSquared = QGuiApplicationPrivate::platformTheme()-> - themeHint(QPlatformTheme::MouseDoubleClickDistance).toInt(); + m_mouseMultiClickDistanceSquared = qApp->styleHints()->mouseDoubleClickDistance(); m_mouseMultiClickDistanceSquared *= m_mouseMultiClickDistanceSquared; - m_touchMultiTapDistanceSquared = QGuiApplicationPrivate::platformTheme()-> - themeHint(QPlatformTheme::TouchDoubleTapDistance).toInt(); + m_touchMultiTapDistanceSquared = qApp->styleHints()->touchDoubleTapDistance(); m_touchMultiTapDistanceSquared *= m_touchMultiTapDistanceSquared; } } @@ -410,9 +408,9 @@ void QQuickTapHandler::updateTimeHeld() \since 5.11 This signal is emitted when the \c parent Item is tapped twice within a - short span of time (QStyleHints::mouseDoubleClickInterval) and distance - (QPlatformTheme::MouseDoubleClickDistance or - QPlatformTheme::TouchDoubleTapDistance). This signal always occurs after + short span of time (QStyleHints::mouseDoubleClickInterval()) and distance + (QStyleHints::mouseDoubleClickDistance() or + QStyleHints::touchDoubleTapDistance()). This signal always occurs after \l singleTapped, \l tapped, and \l tapCountChanged. The \a eventPoint signal parameter contains information from the release event about the point that was tapped. diff --git a/src/quick/handlers/qquickwheelhandler.cpp b/src/quick/handlers/qquickwheelhandler.cpp new file mode 100644 index 0000000000..90e4fef97e --- /dev/null +++ b/src/quick/handlers/qquickwheelhandler.cpp @@ -0,0 +1,520 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** 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$ +** +****************************************************************************/ + +#include "qquickwheelhandler_p.h" +#include "qquickwheelhandler_p_p.h" +#include <QLoggingCategory> +#include <QtMath> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcWheelHandler, "qt.quick.handler.wheel") + +/*! + \qmltype WheelHandler + \instantiates QQuickWheelHandler + \inqmlmodule QtQuick + \ingroup qtquick-input-handlers + \brief Handler for the mouse wheel. + + WheelHandler is a handler that is used to interactively manipulate some + numeric property of an Item as the user rotates the mouse wheel. Like other + Input Handlers, by default it manipulates its \l {PointerHandler::target} + {target}. Declare \l property to control which target property will be + manipulated: + + \snippet pointerHandlers/wheelHandler.qml 0 + + \l BoundaryRule is quite useful in combination with WheelHandler (as well + as with other Input Handlers) to declare the allowed range of values that + the target property can have. For example it is possible to implement + scrolling using a combination of WheelHandler and \l DragHandler to + manipulate the scrollable Item's \l{QQuickItem::y}{y} property when the + user rotates the wheel or drags the item on a touchscreen, and + \l BoundaryRule to limit the range of motion from the top to the bottom: + + \snippet pointerHandlers/handlerFlick.qml 0 + + Alternatively if \l targetProperty is not set or \l target is null, + WheelHandler will not automatically manipulate anything; but the + \l rotation property can be used in a binding to manipulate another + property, or you can implement \c onWheel and handle the wheel event + directly. + + WheelHandler handles only a rotating mouse wheel by default. + Optionally it can handle smooth-scrolling events from touchpad gestures, + by setting \l acceptedDevices to \c{PointerDevice.Mouse | PointerDevice.TouchPad}. + + \note Some non-mouse hardware (such as a touch-sensitive Wacom tablet, or + a Linux laptop touchpad) generates real wheel events from gestures. + WheelHandler will respond to those events as wheel events regardless of the + setting of the \l acceptedDevices property. + + \sa MouseArea + \sa Flickable +*/ + +QQuickWheelHandler::QQuickWheelHandler(QQuickItem *parent) + : QQuickSinglePointHandler(*(new QQuickWheelHandlerPrivate), parent) +{ + setAcceptedDevices(QQuickPointerDevice::Mouse); +} + +/*! + \qmlproperty enum QtQuick::WheelHandler::orientation + + Which wheel to react to. The default is \c Qt.Vertical. + + Not every mouse has a \c Horizontal wheel; sometimes it is emulated by + tilting the wheel sideways. A touchpad can usually generate both vertical + and horizontal wheel events. +*/ +Qt::Orientation QQuickWheelHandler::orientation() const +{ + Q_D(const QQuickWheelHandler); + return d->orientation; +} + +void QQuickWheelHandler::setOrientation(Qt::Orientation orientation) +{ + Q_D(QQuickWheelHandler); + if (d->orientation == orientation) + return; + + d->orientation = orientation; + emit orientationChanged(); +} + +/*! + \qmlproperty bool QtQuick::WheelHandler::invertible + + Whether or not to reverse the direction of property change if + \l QQuickPointerScrollEvent::inverted is true. The default is \c true. + + If the operating system has a "natural scrolling" setting that causes + scrolling to be in the same direction as the finger movement, then if this + property is set to \c true, and WheelHandler is directly setting a property + on \l target, the direction of movement will correspond to the system setting. + If this property is set to \l false, it will invert the \l rotation so that + the direction of motion is always the same as the direction of finger movement. +*/ +bool QQuickWheelHandler::isInvertible() const +{ + Q_D(const QQuickWheelHandler); + return d->invertible; +} + +void QQuickWheelHandler::setInvertible(bool invertible) +{ + Q_D(QQuickWheelHandler); + if (d->invertible == invertible) + return; + + d->invertible = invertible; + emit invertibleChanged(); +} + +/*! + \qmlproperty real QtQuick::WheelHandler::activeTimeout + + The amount of time in seconds after which the \l active property will + revert to \c false if no more wheel events are received. The default is + \c 0.1 (100 ms). + + When WheelHandler handles events that contain + \l {Qt::ScrollPhase}{scroll phase} information, such as events from some + touchpads, the \l active property will become \c false as soon as an event + with phase \l Qt::ScrollEnd is received; in that case the timeout is not + necessary. But a conventional mouse with a wheel does not provide the + \l {QQuickPointerScrollEvent::phase}{scroll phase}: the mouse cannot detect + when the user has decided to stop scrolling, so the \l active property + transitions to \c false after this much time has elapsed. +*/ +qreal QQuickWheelHandler::activeTimeout() const +{ + Q_D(const QQuickWheelHandler); + return d->activeTimeout; +} + +void QQuickWheelHandler::setActiveTimeout(qreal timeout) +{ + Q_D(QQuickWheelHandler); + if (qFuzzyCompare(d->activeTimeout, timeout)) + return; + + if (timeout < 0) { + qWarning("activeTimeout must be positive"); + return; + } + + d->activeTimeout = timeout; + emit activeTimeoutChanged(); +} + +/*! + \qmlproperty real QtQuick::WheelHandler::rotation + + The angle through which the mouse wheel has been rotated since the last + time this property was set, in wheel degrees. + + A positive value indicates that the wheel was rotated up/right; + a negative value indicates that the wheel was rotated down/left. + + A basic mouse click-wheel works in steps of 15 degrees. + + The default is \c 0 at startup. It can be programmatically set to any value + at any time. The value will be adjusted from there as the user rotates the + mouse wheel. + + \sa orientation +*/ +qreal QQuickWheelHandler::rotation() const +{ + Q_D(const QQuickWheelHandler); + return d->rotation * d->rotationScale; +} + +void QQuickWheelHandler::setRotation(qreal rotation) +{ + Q_D(QQuickWheelHandler); + if (qFuzzyCompare(d->rotation, rotation / d->rotationScale)) + return; + + d->rotation = rotation / d->rotationScale; + emit rotationChanged(); +} + +/*! + \qmlproperty real QtQuick::WheelHandler::rotationScale + + The scaling to be applied to the \l rotation property, and to the + \l property on the \l target item, if any. The default is 1, such that + \l rotation will be in units of degrees of rotation. It can be set to a + negative number to invert the effect of the direction of mouse wheel + rotation. +*/ +qreal QQuickWheelHandler::rotationScale() const +{ + Q_D(const QQuickWheelHandler); + return d->rotationScale; +} + +void QQuickWheelHandler::setRotationScale(qreal rotationScale) +{ + Q_D(QQuickWheelHandler); + if (qFuzzyCompare(d->rotationScale, rotationScale)) + return; + if (qFuzzyIsNull(rotationScale)) { + qWarning("rotationScale cannot be set to zero"); + return; + } + + d->rotationScale = rotationScale; + emit rotationScaleChanged(); +} + +/*! + \qmlproperty string QtQuick::WheelHandler::property + + The property to be modified on the \l target when the mouse wheel is rotated. + + The default is no property (empty string). When no target property is being + automatically modified, you can use bindings to react to mouse wheel + rotation in arbitrary ways. + + You can use the mouse wheel to adjust any numeric property. For example if + \c property is set to \c x, the \l target will move horizontally as the + wheel is rotated. The following properties have special behavior: + + \value scale + \l{QQuickItem::scale}{scale} will be modified in a non-linear fashion + as described under \l targetScaleMultiplier. If + \l targetTransformAroundCursor is \c true, the \l{QQuickItem::x}{x} and + \l{QQuickItem::y}{y} properties will be simultaneously adjusted so that + the user will effectively zoom into or out of the point under the mouse + cursor. + \value rotation + \l{QQuickItem::rotation}{rotation} will be set to \l rotation. If + \l targetTransformAroundCursor is \c true, the l{QQuickItem::x}{x} and + \l{QQuickItem::y}{y} properties will be simultaneously adjusted so + that the user will effectively rotate the item around the point under + the mouse cursor. + + The adjustment of the given target property is always scaled by \l rotationScale. +*/ +QString QQuickWheelHandler::property() const +{ + Q_D(const QQuickWheelHandler); + return d->propertyName; +} + +void QQuickWheelHandler::setProperty(const QString &propertyName) +{ + Q_D(QQuickWheelHandler); + if (d->propertyName == propertyName) + return; + + d->propertyName = propertyName; + d->metaPropertyDirty = true; + emit propertyChanged(); +} + +/*! + \qmlproperty real QtQuick::WheelHandler::targetScaleMultiplier + + The amount by which the \l target \l{QQuickItem::scale}{scale} is to be + multiplied whenever the \l rotation changes by 15 degrees. This + is relevant only when \l property is \c "scale". + + The \c scale will be multiplied by + \c targetScaleMultiplier \sup {angleDelta * rotationScale / 15}. + The default is \c 2 \sup {1/3}, which means that if \l rotationScale is left + at its default value, and the mouse wheel is rotated by one "click" + (15 degrees), the \l target will be scaled by approximately 1.25; after + three "clicks" its size will be doubled or halved, depending on the + direction that the wheel is rotated. If you want to make it double or halve + with every 2 clicks of the wheel, set this to \c 2 \sup {1/2} (1.4142). + If you want to make it scale the opposite way as the wheel is rotated, + set \c rotationScale to a negative value. +*/ +qreal QQuickWheelHandler::targetScaleMultiplier() const +{ + Q_D(const QQuickWheelHandler); + return d->targetScaleMultiplier; +} + +void QQuickWheelHandler::setTargetScaleMultiplier(qreal targetScaleMultiplier) +{ + Q_D(QQuickWheelHandler); + if (qFuzzyCompare(d->targetScaleMultiplier, targetScaleMultiplier)) + return; + + d->targetScaleMultiplier = targetScaleMultiplier; + emit targetScaleMultiplierChanged(); +} + +/*! + \qmlproperty bool QtQuick::WheelHandler::targetTransformAroundCursor + + Whether the \l target should automatically be repositioned in such a way + that it is transformed around the mouse cursor position while the + \l property is adjusted. The default is \c true. + + If \l property is set to \c "rotation" and \l targetTransformAroundCursor + is \c true, then as the wheel is rotated, the \l target item will rotate in + place around the mouse cursor position. If \c targetTransformAroundCursor + is \c false, it will rotate around its + \l{QQuickItem::transformOrigin}{transformOrigin} instead. +*/ +bool QQuickWheelHandler::isTargetTransformAroundCursor() const +{ + Q_D(const QQuickWheelHandler); + return d->targetTransformAroundCursor; +} + +void QQuickWheelHandler::setTargetTransformAroundCursor(bool ttac) +{ + Q_D(QQuickWheelHandler); + if (d->targetTransformAroundCursor == ttac) + return; + + d->targetTransformAroundCursor = ttac; + emit targetTransformAroundCursorChanged(); +} + +bool QQuickWheelHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + if (!event) + return false; + QQuickPointerScrollEvent *scroll = event->asPointerScrollEvent(); + if (!scroll) + return false; + if (!acceptedDevices().testFlag(QQuickPointerDevice::DeviceType::TouchPad) + && scroll->synthSource() != Qt::MouseEventNotSynthesized) + return false; + if (!active()) { + switch (orientation()) { + case Qt::Horizontal: + if (qFuzzyIsNull(scroll->angleDelta().x()) && qFuzzyIsNull(scroll->pixelDelta().x())) + return false; + break; + case Qt::Vertical: + if (qFuzzyIsNull(scroll->angleDelta().y()) && qFuzzyIsNull(scroll->pixelDelta().y())) + return false; + break; + } + } + QQuickEventPoint *point = event->point(0); + if (QQuickPointerDeviceHandler::wantsPointerEvent(event) && wantsEventPoint(point) && parentContains(point)) { + setPointId(point->pointId()); + return true; + } + return false; +} + +void QQuickWheelHandler::handleEventPoint(QQuickEventPoint *point) +{ + Q_D(QQuickWheelHandler); + QQuickPointerScrollEvent *event = point->pointerEvent()->asPointerScrollEvent(); + setActive(true); // ScrollEnd will not happen unless it was already active (see setActive(false) below) + point->setAccepted(); + qreal inversion = !d->invertible && event->isInverted() ? -1 : 1; + qreal angleDelta = inversion * qreal(orientation() == Qt::Horizontal ? event->angleDelta().x() : + event->angleDelta().y()) / 8; + d->rotation += angleDelta; + emit rotationChanged(); + emit wheel(event); + if (!d->propertyName.isEmpty() && target()) { + QQuickItem *t = target(); + // writing target()'s property is done via QMetaProperty::write() so that any registered interceptors can react. + if (d->propertyName == QLatin1String("scale")) { + qreal multiplier = qPow(d->targetScaleMultiplier, angleDelta * d->rotationScale / 15); // wheel "clicks" + const QPointF centroidParentPos = t->parentItem()->mapFromScene(point->scenePosition()); + const QPointF positionWas = t->position(); + const qreal scaleWas = t->scale(); + const qreal activePropertyValue = scaleWas * multiplier; + qCDebug(lcWheelHandler) << objectName() << "angle delta" << event->angleDelta() << "pixel delta" << event->pixelDelta() + << "@" << point->position() << "in parent" << centroidParentPos + << "in scene" << point->scenePosition() + << "multiplier" << multiplier << "scale" << scaleWas + << "->" << activePropertyValue; + d->targetMetaProperty().write(t, activePropertyValue); + if (d->targetTransformAroundCursor) { + // If an interceptor intervened, scale may now be different than we asked for. Adjust accordingly. + multiplier = t->scale() / scaleWas; + const QPointF adjPos = QQuickItemPrivate::get(t)->adjustedPosForTransform( + centroidParentPos, positionWas, QVector2D(), scaleWas, multiplier, t->rotation(), 0); + qCDebug(lcWheelHandler) << "adjusting item pos" << adjPos << "in scene" << t->parentItem()->mapToScene(adjPos); + t->setPosition(adjPos); + } + } else if (d->propertyName == QLatin1String("rotation")) { + const QPointF positionWas = t->position(); + const qreal rotationWas = t->rotation(); + const qreal activePropertyValue = rotationWas + angleDelta * d->rotationScale; + const QPointF centroidParentPos = t->parentItem()->mapFromScene(point->scenePosition()); + qCDebug(lcWheelHandler) << objectName() << "angle delta" << event->angleDelta() << "pixel delta" << event->pixelDelta() + << "@" << point->position() << "in parent" << centroidParentPos + << "in scene" << point->scenePosition() << "rotation" << t->rotation() + << "->" << activePropertyValue; + d->targetMetaProperty().write(t, activePropertyValue); + if (d->targetTransformAroundCursor) { + // If an interceptor intervened, rotation may now be different than we asked for. Adjust accordingly. + const QPointF adjPos = QQuickItemPrivate::get(t)->adjustedPosForTransform( + centroidParentPos, positionWas, QVector2D(), + t->scale(), 1, rotationWas, t->rotation() - rotationWas); + qCDebug(lcWheelHandler) << "adjusting item pos" << adjPos << "in scene" << t->parentItem()->mapToScene(adjPos); + t->setPosition(adjPos); + } + } else { + qCDebug(lcWheelHandler) << objectName() << "angle delta" << event->angleDelta() << "scaled" << angleDelta << "total" << d->rotation << "pixel delta" << event->pixelDelta() + << "@" << point->position() << "in scene" << point->scenePosition() << "rotation" << t->rotation(); + qreal delta = 0; + if (event->hasPixelDelta()) { + delta = inversion * d->rotationScale * qreal(orientation() == Qt::Horizontal ? event->pixelDelta().x() : event->pixelDelta().y()); + qCDebug(lcWheelHandler) << "changing target" << d->propertyName << "by pixel delta" << delta << "from" << event; + } else { + delta = angleDelta * d->rotationScale; + qCDebug(lcWheelHandler) << "changing target" << d->propertyName << "by scaled angle delta" << delta << "from" << event; + } + bool ok = false; + qreal value = d->targetMetaProperty().read(t).toReal(&ok); + if (ok) + d->targetMetaProperty().write(t, value + qreal(delta)); + else + qWarning() << "failed to read property" << d->propertyName << "of" << t; + } + } + switch (event->phase()) { + case Qt::ScrollEnd: + qCDebug(lcWheelHandler) << objectName() << "deactivating due to ScrollEnd phase"; + setActive(false); + break; + case Qt::NoScrollPhase: + d->deactivationTimer.start(qRound(d->activeTimeout * 1000), this); + break; + case Qt::ScrollBegin: + case Qt::ScrollUpdate: + case Qt::ScrollMomentum: + break; + } +} + +void QQuickWheelHandler::onTargetChanged(QQuickItem *oldTarget) +{ + Q_UNUSED(oldTarget) + Q_D(QQuickWheelHandler); + d->metaPropertyDirty = true; +} + +void QQuickWheelHandler::onActiveChanged() +{ + Q_D(QQuickWheelHandler); + if (!active()) + d->deactivationTimer.stop(); +} + +void QQuickWheelHandler::timerEvent(QTimerEvent *event) +{ + Q_D(const QQuickWheelHandler); + if (event->timerId() == d->deactivationTimer.timerId()) { + qCDebug(lcWheelHandler) << objectName() << "deactivating due to timeout"; + setActive(false); + } +} + +QQuickWheelHandlerPrivate::QQuickWheelHandlerPrivate() + : QQuickSinglePointHandlerPrivate() +{ +} + +QMetaProperty &QQuickWheelHandlerPrivate::targetMetaProperty() const +{ + Q_Q(const QQuickWheelHandler); + if (metaPropertyDirty && q->target()) { + if (!propertyName.isEmpty()) { + const QMetaObject *targetMeta = q->target()->metaObject(); + metaProperty = targetMeta->property( + targetMeta->indexOfProperty(propertyName.toLocal8Bit().constData())); + } + metaPropertyDirty = false; + } + return metaProperty; +} + +QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickwheelhandler_p.h b/src/quick/handlers/qquickwheelhandler_p.h new file mode 100644 index 0000000000..f8d1c00726 --- /dev/null +++ b/src/quick/handlers/qquickwheelhandler_p.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** 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$ +** +****************************************************************************/ + +#ifndef QQUICKWHEELHANDLER_H +#define QQUICKWHEELHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquickitem.h" +#include "qevent.h" +#include "qquicksinglepointhandler_p.h" + +QT_BEGIN_NAMESPACE + +class QQuickWheelEvent; +class QQuickWheelHandlerPrivate; + +class Q_QUICK_PRIVATE_EXPORT QQuickWheelHandler : public QQuickSinglePointHandler +{ + Q_OBJECT + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged) + Q_PROPERTY(bool invertible READ isInvertible WRITE setInvertible NOTIFY invertibleChanged) + Q_PROPERTY(qreal activeTimeout READ activeTimeout WRITE setActiveTimeout NOTIFY activeTimeoutChanged) + Q_PROPERTY(qreal rotation READ rotation WRITE setRotation NOTIFY rotationChanged) + Q_PROPERTY(qreal rotationScale READ rotationScale WRITE setRotationScale NOTIFY rotationScaleChanged) + Q_PROPERTY(QString property READ property WRITE setProperty NOTIFY propertyChanged) + Q_PROPERTY(qreal targetScaleMultiplier READ targetScaleMultiplier WRITE setTargetScaleMultiplier NOTIFY targetScaleMultiplierChanged) + Q_PROPERTY(bool targetTransformAroundCursor READ isTargetTransformAroundCursor WRITE setTargetTransformAroundCursor NOTIFY targetTransformAroundCursorChanged) + +public: + explicit QQuickWheelHandler(QQuickItem *parent = nullptr); + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + + bool isInvertible() const; + void setInvertible(bool invertible); + + qreal activeTimeout() const; + void setActiveTimeout(qreal timeout); + + qreal rotation() const; + void setRotation(qreal rotation); + + qreal rotationScale() const; + void setRotationScale(qreal rotationScale); + + QString property() const; + void setProperty(const QString &name); + + qreal targetScaleMultiplier() const; + void setTargetScaleMultiplier(qreal targetScaleMultiplier); + + bool isTargetTransformAroundCursor() const; + void setTargetTransformAroundCursor(bool ttac); + +Q_SIGNALS: + void wheel(QQuickPointerScrollEvent *event); + + void orientationChanged(); + void invertibleChanged(); + void activeTimeoutChanged(); + void rotationChanged(); + void rotationScaleChanged(); + void propertyChanged(); + void targetScaleMultiplierChanged(); + void targetTransformAroundCursorChanged(); + +protected: + bool wantsPointerEvent(QQuickPointerEvent *event) override; + void handleEventPoint(QQuickEventPoint *point) override; + void onTargetChanged(QQuickItem *oldTarget) override; + void onActiveChanged() override; + void timerEvent(QTimerEvent *event) override; + + Q_DECLARE_PRIVATE(QQuickWheelHandler) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickWheelHandler) + +#endif // QQUICKWHEELHANDLER_H diff --git a/src/quick/handlers/qquickwheelhandler_p_p.h b/src/quick/handlers/qquickwheelhandler_p_p.h new file mode 100644 index 0000000000..d35e04c51b --- /dev/null +++ b/src/quick/handlers/qquickwheelhandler_p_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** 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$ +** +****************************************************************************/ + +#ifndef QQUICKWHEELHANDLER_P_P_H +#define QQUICKWHEELHANDLER_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qquicksinglepointhandler_p_p.h" +#include "qquickwheelhandler_p.h" +#include <QtCore/qbasictimer.h> + +QT_BEGIN_NAMESPACE + +class Q_QUICK_PRIVATE_EXPORT QQuickWheelHandlerPrivate : public QQuickSinglePointHandlerPrivate +{ + Q_DECLARE_PUBLIC(QQuickWheelHandler) + +public: + static QQuickWheelHandlerPrivate* get(QQuickWheelHandler *q) { return q->d_func(); } + static const QQuickWheelHandlerPrivate* get(const QQuickWheelHandler *q) { return q->d_func(); } + + QQuickWheelHandlerPrivate(); + + QMetaProperty &targetMetaProperty() const; + + QBasicTimer deactivationTimer; + qreal activeTimeout = 0.1; + qreal rotationScale = 1; + qreal rotation = 0; // in units of degrees + qreal targetScaleMultiplier = 1.25992104989487; // qPow(2, 1/3) + QString propertyName; + mutable QMetaProperty metaProperty; + Qt::Orientation orientation = Qt::Vertical; + mutable bool metaPropertyDirty = true; + bool invertible = true; + bool targetTransformAroundCursor = true; +}; + +QT_END_NAMESPACE + +#endif // QQUICKWHEELHANDLER_P_P_H |