From 6ba732d5f68830c14e313009f67bca58b015e0d4 Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Thu, 10 Aug 2017 12:05:41 +0200 Subject: PinchHandler: add native pinch gesture support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit macOS generates QNativeGestureEvents for 2-finger trackpad zoom and rotation gestures. Now PinchHandler will react to them in the same way that PinchArea does. Change-Id: I4c7dab1d3561d20897e3671f4eb68d01ea06b9bd Reviewed-by: Jan Arve Sæther --- src/quick/handlers/qquickmultipointhandler.cpp | 3 + src/quick/handlers/qquickpinchhandler.cpp | 120 ++++++++++++++++++------- src/quick/handlers/qquickpinchhandler_p.h | 1 + src/quick/items/qquickevents.cpp | 118 +++++++++++++++++++++++- src/quick/items/qquickevents_p_p.h | 41 ++++++++- src/quick/items/qquickwindow.cpp | 45 +++++++--- src/quick/items/qquickwindow_p.h | 2 +- 7 files changed, 284 insertions(+), 46 deletions(-) diff --git a/src/quick/handlers/qquickmultipointhandler.cpp b/src/quick/handlers/qquickmultipointhandler.cpp index d0b4edf0ac..ff4914394c 100644 --- a/src/quick/handlers/qquickmultipointhandler.cpp +++ b/src/quick/handlers/qquickmultipointhandler.cpp @@ -76,6 +76,9 @@ bool QQuickMultiPointHandler::wantsPointerEvent(QQuickPointerEvent *event) if (!QQuickPointerDeviceHandler::wantsPointerEvent(event)) return false; + if (event->asPointerNativeGestureEvent()) + return true; + if (sameAsCurrentPoints(event)) return true; diff --git a/src/quick/handlers/qquickpinchhandler.cpp b/src/quick/handlers/qquickpinchhandler.cpp index eb544ff169..84c4e912d1 100644 --- a/src/quick/handlers/qquickpinchhandler.cpp +++ b/src/quick/handlers/qquickpinchhandler.cpp @@ -243,6 +243,28 @@ void QQuickPinchHandler::setMaximumY(qreal maxY) emit maximumYChanged(); } +bool QQuickPinchHandler::wantsPointerEvent(QQuickPointerEvent *event) +{ + if (!QQuickMultiPointHandler::wantsPointerEvent(event)) + return false; + + if (minimumPointCount() == 2) { + if (const auto gesture = event->asPointerNativeGestureEvent()) { + switch (gesture->type()) { + case Qt::BeginNativeGesture: + case Qt::EndNativeGesture: + case Qt::ZoomNativeGesture: + case Qt::RotateNativeGesture: + return parentContains(event->point(0)); + default: + return false; + } + } + } + + return true; +} + /*! \qmlproperty int QtQuick::PinchHandler::minimumTouchPoints @@ -289,51 +311,87 @@ void QQuickPinchHandler::onActiveChanged() void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) { - Q_UNUSED(event) if (Q_UNLIKELY(lcPinchHandler().isDebugEnabled())) { for (QQuickEventPoint *point : qAsConst(m_currentPoints)) qCDebug(lcPinchHandler) << point->state() << point->sceneGrabPosition() << "->" << point->scenePosition(); } - bool containsReleasedPoints = event->isReleaseEvent(); - if (!active() && !containsReleasedPoints) { - // Verify that least one of the points have moved beyond threshold needed to activate the handler - for (QQuickEventPoint *point : qAsConst(m_currentPoints)) { - if (QQuickWindowPrivate::dragOverThreshold(point)) { - if (grabPoints(m_currentPoints)) - setActive(true); - break; + qreal dist = 0; + if (const auto gesture = event->asPointerNativeGestureEvent()) { + switch (gesture->type()) { + case Qt::EndNativeGesture: + m_activeScale = 1; + m_activeRotation = 0; + m_activeTranslation = QVector2D(); + m_centroid = QPointF(); + m_centroidVelocity = QVector2D(); + setActive(false); + emit updated(); + return; + case Qt::ZoomNativeGesture: + m_activeScale *= 1 + gesture->value(); + break; + case Qt::RotateNativeGesture: + m_activeRotation += gesture->value(); + break; + default: + // Nothing of interest (which is unexpected, because wantsPointerEvent() should have returned false) + return; + } + if (!active()) { + m_centroid = gesture->point(0)->scenePosition(); + setActive(true); + m_startCentroid = m_centroid; + // Native gestures for 2-finger pinch do not allow dragging, so + // the centroid won't move during the gesture, and translation stays at zero + m_centroidVelocity = QVector2D(); + m_activeTranslation = QVector2D(); + } + } else { + bool containsReleasedPoints = event->isReleaseEvent(); + if (!active() && !containsReleasedPoints) { + // Verify that at least one of the points has moved beyond threshold needed to activate the handler + for (QQuickEventPoint *point : qAsConst(m_currentPoints)) { + if (QQuickWindowPrivate::dragOverThreshold(point)) { + if (grabPoints(m_currentPoints)) + setActive(true); + break; + } } + if (!active()) + return; } - if (!active()) - return; + // TODO check m_pinchOrigin: right now it acts like it's set to PinchCenter + m_centroid = touchPointCentroid(); + m_centroidVelocity = touchPointCentroidVelocity(); + // 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); + m_activeScale = dist / m_startDistance; + m_activeScale = qBound(m_minimumScale/m_startScale, m_activeScale, m_maximumScale/m_startScale); + + // 2. rotate + QVector newAngles = angles(m_centroid); + const qreal angleDelta = averageAngleDelta(m_startAngles, newAngles); + m_activeRotation += angleDelta; + m_startAngles = std::move(newAngles); + + if (!containsReleasedPoints) + acceptPoints(m_currentPoints); } - // TODO check m_pinchOrigin: right now it acts like it's set to PinchCenter - m_centroid = touchPointCentroid(); - m_centroidVelocity = touchPointCentroidVelocity(); - QRectF bounds(m_minimumX, m_minimumY, m_maximumX - m_minimumX, m_maximumY - m_minimumY); - // 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. + QPointF centroidParentPos; + QRectF bounds(m_minimumX, m_minimumY, m_maximumX - m_minimumX, m_maximumY - m_minimumY); if (target() && target()->parentItem()) { centroidParentPos = target()->parentItem()->mapFromScene(m_centroid); centroidParentPos = QPointF(qBound(bounds.left(), centroidParentPos.x(), bounds.right()), qBound(bounds.top(), centroidParentPos.y(), bounds.bottom())); } - // 1. scale - const qreal dist = averageTouchPointDistance(m_centroid); - m_activeScale = dist / m_startDistance; - m_activeScale = qBound(m_minimumScale/m_startScale, m_activeScale, m_maximumScale/m_startScale); - const qreal scale = m_startScale * m_activeScale; - - // 2. rotate - QVector newAngles = angles(m_centroid); - const qreal angleDelta = averageAngleDelta(m_startAngles, newAngles); - m_activeRotation += angleDelta; const qreal totalRotation = m_startRotation + m_activeRotation; const qreal rotation = qBound(m_minimumRotation, totalRotation, m_maximumRotation); m_activeRotation += (rotation - totalRotation); //adjust for the potential bounding above - m_startAngles = std::move(newAngles); + const qreal scale = m_startScale * m_activeScale; if (target() && target()->parentItem()) { // 3. Drag/translate @@ -364,14 +422,14 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event) } else { m_activeTranslation = QVector2D(m_centroid - m_startCentroid); } + qCDebug(lcPinchHandler) << "centroid" << m_startCentroid << "->" << m_centroid << ", distance" << m_startDistance << "->" << dist << ", startScale" << m_startScale << "->" << scale << ", activeRotation" << m_activeRotation - << ", rotation" << rotation; + << ", rotation" << rotation + << " from " << event->device()->type(); - if (!containsReleasedPoints) - acceptPoints(m_currentPoints); emit updated(); } diff --git a/src/quick/handlers/qquickpinchhandler_p.h b/src/quick/handlers/qquickpinchhandler_p.h index 28da0db93c..7d6b7d9509 100644 --- a/src/quick/handlers/qquickpinchhandler_p.h +++ b/src/quick/handlers/qquickpinchhandler_p.h @@ -128,6 +128,7 @@ signals: void updated(); protected: + bool wantsPointerEvent(QQuickPointerEvent *event) override; void onActiveChanged() override; void handlePointerEventImpl(QQuickPointerEvent *event) override; diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp index 3011d75aa0..0f64890825 100644 --- a/src/quick/items/qquickevents.cpp +++ b/src/quick/items/qquickevents.cpp @@ -40,6 +40,7 @@ #include "qquickevents_p_p.h" #include #include +#include #include #include #include @@ -525,7 +526,7 @@ Item { \sa QTouchDevice::capabilities */ -typedef QHash PointerDeviceForTouchDeviceHash; +typedef QHash PointerDeviceForTouchDeviceHash; Q_GLOBAL_STATIC(PointerDeviceForTouchDeviceHash, g_touchDevices) struct ConstructableQQuickPointerDevice : public QQuickPointerDevice @@ -561,7 +562,7 @@ static const QString pointDeviceName(const QQuickEventPoint *point) } -QQuickPointerDevice *QQuickPointerDevice::touchDevice(QTouchDevice *d) +QQuickPointerDevice *QQuickPointerDevice::touchDevice(const QTouchDevice *d) { if (g_touchDevices->contains(d)) return g_touchDevices->value(d); @@ -1330,6 +1331,36 @@ void QQuickPointerTouchEvent::localize(QQuickItem *target) point->localizePosition(target); } +QQuickPointerEvent *QQuickPointerNativeGestureEvent::reset(QEvent *event) +{ + auto ev = static_cast(event); + m_event = ev; + if (!event) + return this; + + m_device = QQuickPointerDevice::touchDevice(ev->device()); + m_device->eventDeliveryTargets().clear(); + Qt::TouchPointState state = Qt::TouchPointMoved; + switch (type()) { + case Qt::BeginNativeGesture: + state = Qt::TouchPointPressed; + break; + case Qt::EndNativeGesture: + state = Qt::TouchPointReleased; + break; + default: + break; + } + quint64 deviceId = QTouchDevicePrivate::get(const_cast(ev->device()))->id; // a bit roundabout since QTouchDevice::mTouchDeviceId is protected + m_gesturePoint->reset(state, ev->windowPos(), deviceId << 24, ev->timestamp()); + return this; +} + +void QQuickPointerNativeGestureEvent::localize(QQuickItem *target) +{ + m_gesturePoint->localizePosition(target); +} + QQuickEventPoint *QQuickPointerMouseEvent::point(int i) const { if (i == 0) return m_mousePoint; @@ -1342,6 +1373,12 @@ QQuickEventPoint *QQuickPointerTouchEvent::point(int i) const { return nullptr; } +QQuickEventPoint *QQuickPointerNativeGestureEvent::point(int i) const { + if (i == 0) + return m_gesturePoint; + return nullptr; +} + QQuickEventPoint::QQuickEventPoint(QQuickPointerEvent *parent) : QObject(parent), m_pointId(0), m_exclusiveGrabber(nullptr), m_timestamp(0), m_pressTimestamp(0), m_state(QQuickEventPoint::Released), m_accept(false), m_grabberIsHandler(false) @@ -1561,6 +1598,64 @@ QMouseEvent *QQuickPointerTouchEvent::syntheticMouseEvent(int pointID, QQuickIte return &m_synthMouseEvent; } +/*! + Returns the exclusive grabber of this event, if any, in a vector. +*/ +QVector QQuickPointerNativeGestureEvent::exclusiveGrabbers() const +{ + QVector result; + if (QObject *grabber = m_gesturePoint->exclusiveGrabber()) + result << grabber; + return result; +} + +/*! + Remove all passive and exclusive grabbers of this event, without notifying. +*/ +void QQuickPointerNativeGestureEvent::clearGrabbers() const { + m_gesturePoint->setGrabberItem(nullptr); + m_gesturePoint->clearPassiveGrabbers(); +} + +/*! + Returns whether the given \a handler is the exclusive grabber of this event. +*/ +bool QQuickPointerNativeGestureEvent::hasExclusiveGrabber(const QQuickPointerHandler *handler) const +{ + return m_gesturePoint->exclusiveGrabber() == handler; +} + +bool QQuickPointerNativeGestureEvent::isPressEvent() const +{ + return type() == Qt::BeginNativeGesture; +} + +bool QQuickPointerNativeGestureEvent::isUpdateEvent() const +{ + switch (type()) { + case Qt::BeginNativeGesture: + case Qt::EndNativeGesture: + return false; + default: + return true; + } +} + +bool QQuickPointerNativeGestureEvent::isReleaseEvent() const +{ + return type() == Qt::EndNativeGesture; +} + +Qt::NativeGestureType QQuickPointerNativeGestureEvent::type() const +{ + return static_cast(m_event)->gestureType(); +} + +qreal QQuickPointerNativeGestureEvent::value() const +{ + return static_cast(m_event)->value(); +} + /*! \internal Returns a pointer to the QQuickEventPoint which has the \a pointId as @@ -1583,6 +1678,12 @@ QQuickEventPoint *QQuickPointerTouchEvent::pointById(int pointId) const { return nullptr; } +QQuickEventPoint *QQuickPointerNativeGestureEvent::pointById(int pointId) const { + if (m_gesturePoint && pointId == m_gesturePoint->pointId()) + return m_gesturePoint; + return nullptr; +} + /*! \internal @@ -1701,6 +1802,19 @@ QTouchEvent *QQuickPointerTouchEvent::asTouchEvent() const return static_cast(m_event); } +bool QQuickPointerNativeGestureEvent::allPointsAccepted() const { + return m_gesturePoint->isAccepted(); +} + +bool QQuickPointerNativeGestureEvent::allUpdatedPointsAccepted() const { + return m_gesturePoint->state() == QQuickEventPoint::Pressed || m_gesturePoint->isAccepted(); +} + +bool QQuickPointerNativeGestureEvent::allPointsGrabbed() const +{ + return m_gesturePoint->exclusiveGrabber() != nullptr; +} + #ifndef QT_NO_DEBUG_STREAM Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickPointerDevice *dev) { diff --git a/src/quick/items/qquickevents_p_p.h b/src/quick/items/qquickevents_p_p.h index 50c9a86b9f..09a63febdc 100644 --- a/src/quick/items/qquickevents_p_p.h +++ b/src/quick/items/qquickevents_p_p.h @@ -66,6 +66,7 @@ QT_BEGIN_NAMESPACE class QQuickPointerDevice; class QQuickPointerEvent; class QQuickPointerMouseEvent; +class QQuickPointerNativeGestureEvent; class QQuickPointerTabletEvent; class QQuickPointerTouchEvent; class QQuickPointerHandler; @@ -413,9 +414,11 @@ public: // helpers for C++ only (during event delivery) virtual QQuickPointerMouseEvent *asPointerMouseEvent() { return nullptr; } virtual QQuickPointerTouchEvent *asPointerTouchEvent() { return nullptr; } virtual QQuickPointerTabletEvent *asPointerTabletEvent() { return nullptr; } + virtual QQuickPointerNativeGestureEvent *asPointerNativeGestureEvent() { return nullptr; } virtual const QQuickPointerMouseEvent *asPointerMouseEvent() const { return nullptr; } virtual const QQuickPointerTouchEvent *asPointerTouchEvent() const { return nullptr; } virtual const QQuickPointerTabletEvent *asPointerTabletEvent() const { return nullptr; } + virtual const QQuickPointerNativeGestureEvent *asPointerNativeGestureEvent() const { return nullptr; } virtual bool allPointsAccepted() const = 0; virtual bool allUpdatedPointsAccepted() const = 0; virtual bool allPointsGrabbed() const = 0; @@ -515,6 +518,42 @@ private: Q_DISABLE_COPY(QQuickPointerTouchEvent) }; +class Q_QUICK_PRIVATE_EXPORT QQuickPointerNativeGestureEvent : public QQuickPointerEvent +{ + Q_OBJECT + Q_PROPERTY(Qt::NativeGestureType type READ type CONSTANT) + Q_PROPERTY(qreal value READ value CONSTANT) + +public: + QQuickPointerNativeGestureEvent(QObject *parent = nullptr, QQuickPointerDevice *device = nullptr) + : QQuickPointerEvent(parent, device), m_gesturePoint(new QQuickEventPoint(this)) { } + + QQuickPointerEvent *reset(QEvent *) override; + void localize(QQuickItem *target) override; + bool isPressEvent() const override; + bool isUpdateEvent() const override; + bool isReleaseEvent() const override; + QQuickPointerNativeGestureEvent *asPointerNativeGestureEvent() override { return this; } + const QQuickPointerNativeGestureEvent *asPointerNativeGestureEvent() const override { return this; } + int pointCount() const override { return 1; } + QQuickEventPoint *point(int i) const override; + QQuickEventPoint *pointById(int pointId) const override; + bool allPointsAccepted() const override; + bool allUpdatedPointsAccepted() const override; + bool allPointsGrabbed() const override; + QVector exclusiveGrabbers() const override; + void clearGrabbers() const override; + bool hasExclusiveGrabber(const QQuickPointerHandler *handler) const override; + Qt::NativeGestureType type() const; + qreal value() const; + +private: + QQuickEventPoint *m_gesturePoint; + + Q_DISABLE_COPY(QQuickPointerNativeGestureEvent) +}; + + // ### Qt 6: move this to qtbase, replace QTouchDevice and the enums in QTabletEvent class Q_QUICK_PRIVATE_EXPORT QQuickPointerDevice : public QObject { @@ -579,7 +618,7 @@ public: QString name() const { return m_name; } QPointingDeviceUniqueId uniqueId() const { return m_uniqueId; } - static QQuickPointerDevice *touchDevice(QTouchDevice *d); + static QQuickPointerDevice *touchDevice(const QTouchDevice *d); static QList touchDevices(); static QQuickPointerDevice *genericMouseDevice(); static QQuickPointerDevice *tabletDevice(qint64); diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index bd1a5076fd..8c7cbe5117 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -1915,10 +1915,20 @@ bool QQuickWindowPrivate::deliverNativeGestureEvent(QQuickItem *item, QNativeGes return true; } - QPointF p = item->mapFromScene(event->localPos()); + // Try the Item's pointer handlers first + QQuickPointerEvent *pointerEvent = pointerEventInstance(event); + pointerEvent->localize(item); + if (itemPrivate->handlePointerEvent(pointerEvent, false)) { + if (pointerEvent->allPointsAccepted()) { + event->accept(); + return true; + } + } + // If still not accepted, try direct delivery to the item + QPointF p = item->mapFromScene(event->localPos()); if (item->contains(p)) { - QNativeGestureEvent copy(event->gestureType(), p, event->windowPos(), event->screenPos(), + QNativeGestureEvent copy(event->gestureType(), event->device(), p, event->windowPos(), event->screenPos(), event->value(), 0L, 0L); // TODO can't copy things I can't access event->accept(); item->event(©); @@ -2164,23 +2174,36 @@ void QQuickWindowPrivate::flushFrameSynchronousEvents() } } -QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QQuickPointerDevice *device) const +QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QQuickPointerDevice *device, QEvent::Type eventType) const { - // the list of devices should be very small so a linear search should be ok - for (QQuickPointerEvent *e: pointerEventInstances) { + // Search for a matching reusable event object. + for (QQuickPointerEvent *e : pointerEventInstances) { + // If device can generate native gestures (e.g. a trackpad), there might be two QQuickPointerEvents: + // QQuickPointerNativeGestureEvent and QQuickPointerTouchEvent. Use eventType to disambiguate. + if (eventType == QEvent::NativeGesture && !qobject_cast(e)) + continue; + // Otherwise we assume there's only one event type per device. + // More disambiguation tests might need to be added above if that changes later. if (e->device() == device) return e; } + // Not found: we have to create a suitable event instance. QQuickPointerEvent *ev = nullptr; QQuickWindow *q = const_cast(q_func()); switch (device->type()) { case QQuickPointerDevice::Mouse: + // QWindowSystemInterface::handleMouseEvent() does not take a device parameter: + // we assume all mouse events come from one mouse (the "core pointer"). + // So when the event is a mouse event, device == QQuickPointerDevice::genericMouseDevice() ev = new QQuickPointerMouseEvent(q, device); break; case QQuickPointerDevice::TouchPad: case QQuickPointerDevice::TouchScreen: - ev = new QQuickPointerTouchEvent(q, device); + if (eventType == QEvent::NativeGesture) + ev = new QQuickPointerNativeGestureEvent(q, device); + else // assume QEvent::Type is one of TouchBegin/Update/End + ev = new QQuickPointerTouchEvent(q, device); break; default: // TODO tablet event types @@ -2200,29 +2223,29 @@ QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QQuickPointerDevic QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QEvent *event) const { QQuickPointerDevice *dev = nullptr; - QQuickPointerEvent *ev = nullptr; switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::MouseMove: dev = QQuickPointerDevice::genericMouseDevice(); - ev = pointerEventInstance(dev); break; case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: case QEvent::TouchCancel: dev = QQuickPointerDevice::touchDevice(static_cast(event)->device()); - ev = pointerEventInstance(dev); break; // TODO tablet event types + case QEvent::NativeGesture: + dev = QQuickPointerDevice::touchDevice(static_cast(event)->device()); + break; default: break; } - Q_ASSERT(ev); - return ev->reset(event); + Q_ASSERT(dev); + return pointerEventInstance(dev, event->type())->reset(event); } void QQuickWindowPrivate::deliverPointerEvent(QQuickPointerEvent *event) diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h index 0399b26f62..ae49d5e304 100644 --- a/src/quick/items/qquickwindow_p.h +++ b/src/quick/items/qquickwindow_p.h @@ -166,7 +166,7 @@ public: // the device-specific event instances which are reused during event delivery mutable QVector pointerEventInstances; - QQuickPointerEvent *pointerEventInstance(QQuickPointerDevice *device) const; + QQuickPointerEvent *pointerEventInstance(QQuickPointerDevice *device, QEvent::Type eventType = QEvent::None) const; // delivery of pointer events: QQuickPointerEvent *pointerEventInstance(QEvent *ev) const; -- cgit v1.2.3