aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/handlers/qquickpinchhandler.cpp
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2022-11-16 18:20:19 +0100
committerShawn Rutledge <shawn.rutledge@qt.io>2022-12-10 03:13:57 +0100
commit7867a683fcb938939fb2837a26ac8e1941e3fe08 (patch)
tree271faa101a68abdd55c5c88146ee8bc223a413a8 /src/quick/handlers/qquickpinchhandler.cpp
parentf064497bd5021e5d28266cabbb703d548f6961b0 (diff)
Add PinchHandler.scaleAxis, rotationAxis; hold values in axes
Pointer Handlers that manipulate target item properties should now use QQuickDragAxis consistently to: - enforce minimum and maximum values - hold the persistent and active values - make those available via properties - emit a new activeValueChanged(delta) signal when the value changes, so that it's possible to incrementally update a target item property in JS (onValueDelta: target.property += delta) In the pinchHandler.qml example, you can use the PinchHandler to adjust 4 properties of one Rectangle independently (it requires coordination). m_boundedActiveValue controls whether m_activeValue will be kept between minimum and maximum. For rotation, tst_QQuickPinchHandler::scaleNativeGesture() expects it to be, although that seems questionable now, and may be addressed later. [ChangeLog][QtQuick][Event Handlers] PinchHandler now has scaleAxis and rotationAxis grouped properties, alongside the existing xAxis and yAxis; and all of these now have activeValue and persistentValue properties. The activeValueChanged signal includes a delta value, giving the incremental change since the previous activeValue. The persistentValue is settable, in case some target item property can be adjusted in multiple ways: the handler's stored value can then be synced up with the item property value after each external change. These features are also added to DragHandler's xAxis and yAxis properties. Task-number: QTBUG-68108 Task-number: QTBUG-76380 Task-number: QTBUG-76379 Task-number: QTBUG-94168 Change-Id: I78a5b43e9ba580448ef05054b6c4bc71b1834dd6 Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
Diffstat (limited to 'src/quick/handlers/qquickpinchhandler.cpp')
-rw-r--r--src/quick/handlers/qquickpinchhandler.cpp229
1 files changed, 167 insertions, 62 deletions
diff --git a/src/quick/handlers/qquickpinchhandler.cpp b/src/quick/handlers/qquickpinchhandler.cpp
index 6fec0feaa1..f11d8357eb 100644
--- a/src/quick/handlers/qquickpinchhandler.cpp
+++ b/src/quick/handlers/qquickpinchhandler.cpp
@@ -74,10 +74,10 @@ QQuickPinchHandler::QQuickPinchHandler(QQuickItem *parent)
*/
void QQuickPinchHandler::setMinimumScale(qreal minimumScale)
{
- if (qFuzzyCompare(m_minimumScale, minimumScale))
+ if (qFuzzyCompare(m_scaleAxis.minimum(), minimumScale))
return;
- m_minimumScale = minimumScale;
+ m_scaleAxis.setMinimum(minimumScale);
emit minimumScaleChanged();
}
@@ -89,10 +89,10 @@ void QQuickPinchHandler::setMinimumScale(qreal minimumScale)
*/
void QQuickPinchHandler::setMaximumScale(qreal maximumScale)
{
- if (qFuzzyCompare(m_maximumScale, maximumScale))
+ if (qFuzzyCompare(m_scaleAxis.maximum(), maximumScale))
return;
- m_maximumScale = maximumScale;
+ m_scaleAxis.setMaximum(maximumScale);
emit maximumScaleChanged();
}
@@ -104,10 +104,10 @@ void QQuickPinchHandler::setMaximumScale(qreal maximumScale)
*/
void QQuickPinchHandler::setMinimumRotation(qreal minimumRotation)
{
- if (qFuzzyCompare(m_minimumRotation, minimumRotation))
+ if (qFuzzyCompare(m_rotationAxis.minimum(), minimumRotation))
return;
- m_minimumRotation = minimumRotation;
+ m_rotationAxis.setMinimum(minimumRotation);
emit minimumRotationChanged();
}
@@ -119,10 +119,10 @@ void QQuickPinchHandler::setMinimumRotation(qreal minimumRotation)
*/
void QQuickPinchHandler::setMaximumRotation(qreal maximumRotation)
{
- if (qFuzzyCompare(m_maximumRotation, maximumRotation))
+ if (qFuzzyCompare(m_rotationAxis.maximum(), maximumRotation))
return;
- m_maximumRotation = maximumRotation;
+ m_rotationAxis.setMaximum(maximumRotation);
emit maximumRotationChanged();
}
@@ -159,26 +159,110 @@ bool QQuickPinchHandler::wantsPointerEvent(QPointerEvent *event)
\qmlproperty real QtQuick::PinchHandler::xAxis.minimum
\qmlproperty real QtQuick::PinchHandler::xAxis.maximum
\qmlproperty bool QtQuick::PinchHandler::xAxis.enabled
+ \qmlproperty real QtQuick::PinchHandler::xAxis.activeValue
+ \qmlproperty real QtQuick::PinchHandler::xAxis.persistentValue
\c xAxis controls the constraints for horizontal translation of the \l target item.
\c minimum is the minimum acceptable x coordinate of the translation.
\c maximum is the maximum acceptable x coordinate of the translation.
If \c enabled is true, horizontal dragging is allowed.
- */
+
+ The \c activeValueChanged signal is emitted when \c activeValue (and therefore
+ \c persistentValue) changes, to provide the increment by which it changed.
+ This is intended for incrementally adjusting one property via multiple handlers.
+
+ \snippet pointerHandlers/pinchHandlerAxisValueDeltas.qml 0
+
+ \note The snippet is contrived: PinchHandler already knows how to move,
+ scale and rotate its parent item, but this code achieves different behavior
+ in a less-declarative way, to illustrate how to use \c activeValueChanged
+ in special cases.
+*/
/*!
\qmlpropertygroup QtQuick::PinchHandler::yAxis
\qmlproperty real QtQuick::PinchHandler::yAxis.minimum
\qmlproperty real QtQuick::PinchHandler::yAxis.maximum
\qmlproperty bool QtQuick::PinchHandler::yAxis.enabled
+ \qmlproperty real QtQuick::PinchHandler::yAxis.activeValue
+ \qmlproperty real QtQuick::PinchHandler::yAxis.persistentValue
\c yAxis controls the constraints for vertical translation of the \l target item.
\c minimum is the minimum acceptable y coordinate of the translation.
\c maximum is the maximum acceptable y coordinate of the translation.
If \c enabled is true, vertical dragging is allowed.
- */
+
+ The \c activeValueChanged signal is emitted when \c activeValue (and therefore
+ \c persistentValue) changes, to provide the increment by which it changed.
+ This is intended for incrementally adjusting one property via multiple handlers.
+
+ \snippet pointerHandlers/pinchHandlerAxisValueDeltas.qml 0
+
+ \note The snippet is contrived: PinchHandler already knows how to move,
+ scale and rotate its parent item, but this code achieves different behavior
+ in a less-declarative way, to illustrate how to use \c activeValueChanged
+ in special cases.
+*/
+
+/*!
+ \qmlpropertygroup QtQuick::PinchHandler::scaleAxis
+ \qmlproperty real QtQuick::PinchHandler::scaleAxis.minimum
+ \qmlproperty real QtQuick::PinchHandler::scaleAxis.maximum
+ \qmlproperty bool QtQuick::PinchHandler::scaleAxis.enabled
+ \qmlproperty real QtQuick::PinchHandler::scaleAxis.activeValue
+ \qmlproperty real QtQuick::PinchHandler::scaleAxis.persistentValue
+
+ \c scaleAxis controls the constraints for setting the \l {QtQuick::Item::scale}{scale}
+ of the \l target item according to the distance between the touchpoints.
+
+ \c minimum is the minimum acceptable scale.
+ \c maximum is the maximum acceptable scale.
+ If \c enabled is true, scaling is allowed.
+ \c activeValue is the same as \l {QtQuick::PinchHandler::activeScale}.
+ \c persistentValue is the same as \l {QtQuick::PinchHandler::scale}.
+
+ The \c activeValueChanged signal is emitted when \c activeValue (and therefore
+ \c persistentValue) changes, to provide the multiplier for the incremental change.
+ This is intended for incrementally adjusting one property via multiple handlers.
+
+ \snippet pointerHandlers/pinchHandlerAxisValueDeltas.qml 0
+
+ \note The snippet is contrived: PinchHandler already knows how to move,
+ scale and rotate its parent item, but this code achieves different behavior
+ in a less-declarative way, to illustrate how to use \c activeValueChanged
+ in special cases.
+*/
+
+/*!
+ \qmlpropertygroup QtQuick::PinchHandler::rotationAxis
+ \qmlproperty real QtQuick::PinchHandler::rotationAxis.minimum
+ \qmlproperty real QtQuick::PinchHandler::rotationAxis.maximum
+ \qmlproperty bool QtQuick::PinchHandler::rotationAxis.enabled
+ \qmlproperty real QtQuick::PinchHandler::rotationAxis.activeValue
+ \qmlproperty real QtQuick::PinchHandler::rotationAxis.persistentValue
+
+ \c rotationAxis controls the constraints for setting the \l {QtQuick::Item::rotation}{rotation}
+ of the \l target item according to the rotation of the group of touchpoints.
+
+ \c minimum is the minimum acceptable rotation.
+ \c maximum is the maximum acceptable rotation.
+ If \c enabled is true, rotation is allowed.
+ \c activeValue is the same as \l {QtQuick::PinchHandler::rotation}.
+ \c persistentValue holds the accumulated value across multiple gestures.
+
+ The \c activeValueChanged signal is emitted when \c activeValue (and therefore
+ \c persistentValue) changes, to provide the increment by which it changed.
+ This is intended for incrementally adjusting one property via multiple handlers.
+
+ \snippet pointerHandlers/pinchHandlerAxisValueDeltas.qml 0
+
+ \note The snippet is contrived: PinchHandler already knows how to move,
+ scale and rotate its parent item, but this code achieves different behavior
+ in a less-declarative way, to illustrate how to use \c activeValueChanged
+ in special cases.
+*/
/*!
\readonly
@@ -193,22 +277,25 @@ bool QQuickPinchHandler::wantsPointerEvent(QPointerEvent *event)
void QQuickPinchHandler::onActiveChanged()
{
QQuickMultiPointHandler::onActiveChanged();
- if (active()) {
+ const bool curActive = active();
+ if (const QQuickItem *t = target(); curActive && t) {
+ m_xAxis.m_accumulatedValue = t->position().x();
+ m_yAxis.m_accumulatedValue = t->position().y();
+ m_scaleAxis.m_accumulatedValue = t->scale();
+ m_rotationAxis.m_accumulatedValue = t->rotation();
+ }
+ m_xAxis.onActiveChanged(curActive, 0);
+ m_yAxis.onActiveChanged(curActive, 0);
+ m_scaleAxis.onActiveChanged(curActive, 1);
+ m_rotationAxis.onActiveChanged(curActive, 0);
+
+ if (curActive) {
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();
- m_startPos = t->position();
- } else {
- m_startScale = m_accumulatedScale;
- m_startRotation = 0; // TODO m_accumulatedRotation (QTBUG-94168)
- }
- qCDebug(lcPinchHandler) << "activated with starting scale" << m_startScale << "rotation" << m_startRotation;
+ qCDebug(lcPinchHandler) << "activated with starting scale" << m_scaleAxis.m_startValue
+ << "target scale" << m_scaleAxis.m_startValue << "rotation" << m_rotationAxis.m_startValue;
} else {
- qCDebug(lcPinchHandler) << "deactivated with scale" << m_activeScale << "rotation" << m_activeRotation;
+ qCDebug(lcPinchHandler) << "deactivated with scale" << m_scaleAxis.m_activeValue << "rotation" << m_rotationAxis.m_activeValue;
}
}
@@ -226,31 +313,27 @@ void QQuickPinchHandler::handlePointerEventImpl(QPointerEvent *event)
const auto gesture = static_cast<const QNativeGestureEvent *>(event);
mutableCentroid().reset(event, event->point(0));
switch (gesture->gestureType()) {
+ case Qt::BeginNativeGesture:
+ setActive(true);
+ // Native gestures for 2-finger pinch do not allow dragging, so
+ // the centroid won't move during the gesture, and translation stays at zero
+ return;
case Qt::EndNativeGesture:
- m_activeScale = 1;
- m_activeRotation = 0;
- m_activeTranslation = QVector2D();
mutableCentroid().reset();
setActive(false);
emit updated();
return;
case Qt::ZoomNativeGesture:
- m_activeScale *= 1 + gesture->value();
- m_activeScale = qBound(m_minimumScale, m_activeScale, m_maximumScale);
+ m_scaleAxis.updateValue(1 + gesture->value(), m_scaleAxis.m_startValue * (1 + gesture->value()));
break;
case Qt::RotateNativeGesture:
- m_activeRotation += gesture->value();
+ m_rotationAxis.updateValue(m_rotationAxis.m_activeValue + gesture->value(),
+ m_rotationAxis.m_startValue + m_rotationAxis.m_activeValue + gesture->value());
break;
default:
// Nothing of interest (which is unexpected, because wantsPointerEvent() should have returned false)
return;
}
- if (!active()) {
- setActive(true);
- // 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_activeTranslation = QVector2D();
- }
} else
#endif // QT_CONFIG(gestures)
{
@@ -333,7 +416,9 @@ void QQuickPinchHandler::handlePointerEventImpl(QPointerEvent *event)
++numberOfPointsDraggedOverThreshold;
}
- const bool requiredNumberOfPointsDraggedOverThreshold = numberOfPointsDraggedOverThreshold >= minimumPointCount() && numberOfPointsDraggedOverThreshold <= maximumPointCount();
+ const bool requiredNumberOfPointsDraggedOverThreshold =
+ numberOfPointsDraggedOverThreshold >= minimumPointCount() &&
+ numberOfPointsDraggedOverThreshold <= maximumPointCount();
accumulatedMovementMagnitude /= currentPoints().size();
QVector2D avgDrag = accumulatedDrag / currentPoints().size();
@@ -346,7 +431,8 @@ void QQuickPinchHandler::handlePointerEventImpl(QPointerEvent *event)
qreal distanceToCentroidDelta = qAbs(accumulatedCentroidDistance - m_accumulatedStartCentroidDistance); // Used to detect scale
if (numberOfPointsDraggedOverThreshold >= 1) {
- if (requiredNumberOfPointsDraggedOverThreshold && avgDrag.lengthSquared() >= dragThresholdSquared && accumulatedMovementMagnitude < dragThreshold) {
+ if (requiredNumberOfPointsDraggedOverThreshold &&
+ avgDrag.lengthSquared() >= dragThresholdSquared && accumulatedMovementMagnitude < dragThreshold) {
// Drag
if (grabPoints(event, chosenPoints))
setActive(true);
@@ -369,62 +455,81 @@ void QQuickPinchHandler::handlePointerEventImpl(QPointerEvent *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(centroid().scenePosition());
- m_activeScale = dist / m_startDistance;
- m_activeScale = qBound(m_minimumScale/m_startScale, m_activeScale, m_maximumScale/m_startScale);
+ qreal activeScale = 1;
+ if (m_scaleAxis.enabled()) {
+ dist = averageTouchPointDistance(centroid().scenePosition());
+ activeScale = dist / m_startDistance;
+ activeScale = qBound(m_scaleAxis.minimum() / m_scaleAxis.m_startValue, activeScale,
+ m_scaleAxis.maximum() / m_scaleAxis.m_startValue);
+ m_scaleAxis.updateValue(activeScale, m_scaleAxis.m_startValue * activeScale,
+ activeScale / m_scaleAxis.activeValue());
+ }
// 2. rotate
- QVector<PointData> newAngles = angles(centroid().scenePosition());
- const qreal angleDelta = averageAngleDelta(m_startAngles, newAngles);
- m_activeRotation += angleDelta;
- m_startAngles = std::move(newAngles);
+ if (m_rotationAxis.enabled()) {
+ QVector<PointData> newAngles = angles(centroid().scenePosition());
+ const qreal angleDelta = averageAngleDelta(m_startAngles, newAngles);
+ m_rotationAxis.updateValue(m_rotationAxis.m_activeValue + angleDelta,
+ m_rotationAxis.m_startValue + m_rotationAxis.m_activeValue + angleDelta,
+ angleDelta);
+ m_startAngles = std::move(newAngles);
+ }
if (!containsReleasedPoints)
acceptPoints(chosenPoints);
}
- 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_accumulatedScale = m_startScale * m_activeScale;
if (target() && target()->parentItem()) {
const QPointF centroidParentPos = target()->parentItem()->mapFromScene(centroid().scenePosition());
// 3. Drag/translate
const QPointF centroidStartParentPos = target()->parentItem()->mapFromScene(centroid().sceneGrabPosition());
- m_activeTranslation = QVector2D(centroidParentPos - centroidStartParentPos);
+ auto activeTranslation = QVector2D(centroidParentPos - centroidStartParentPos);
// apply rotation + scaling around the centroid - then apply translation.
QPointF pos = QQuickItemPrivate::get(target())->adjustedPosForTransform(centroidParentPos,
- m_startPos, m_activeTranslation,
- m_startScale, m_activeScale,
- m_startRotation, m_activeRotation);
+ startPos(), activeTranslation,
+ m_scaleAxis.m_startValue,
+ m_scaleAxis.persistentValue() / m_scaleAxis.m_startValue,
+ m_rotationAxis.m_startValue,
+ m_rotationAxis.persistentValue() - m_rotationAxis.m_startValue);
if (xAxis()->enabled())
pos.setX(qBound(xAxis()->minimum(), pos.x(), xAxis()->maximum()));
else
- pos.rx() -= qreal(m_activeTranslation.x());
+ pos.rx() -= qreal(activeTranslation.x());
if (yAxis()->enabled())
pos.setY(qBound(yAxis()->minimum(), pos.y(), yAxis()->maximum()));
else
- pos.ry() -= qreal(m_activeTranslation.y());
+ pos.ry() -= qreal(activeTranslation.y());
- target()->setPosition(pos);
- target()->setRotation(rotation);
- target()->setScale(m_accumulatedScale);
+ m_xAxis.updateValue(activeTranslation.x(), pos.x(), activeTranslation.x() - m_xAxis.activeValue());
+ m_yAxis.updateValue(activeTranslation.y(), pos.y(), activeTranslation.y() - m_yAxis.activeValue());
+ target()->setPosition(QPointF(m_xAxis.persistentValue(), m_yAxis.persistentValue()));
+ target()->setRotation(m_rotationAxis.persistentValue());
+ target()->setScale(m_scaleAxis.persistentValue());
} else {
- m_activeTranslation = QVector2D(centroid().scenePosition() - centroid().scenePressPosition());
+ auto activeTranslation = centroid().scenePosition() - centroid().scenePressPosition();
+ auto accumulated = QPointF(m_xAxis.m_startValue, m_yAxis.m_startValue) + activeTranslation;
+ m_xAxis.updateValue(activeTranslation.x(), accumulated.x(),
+ activeTranslation.x() - m_xAxis.activeValue());
+ m_yAxis.updateValue(activeTranslation.y(), accumulated.y(),
+ activeTranslation.y() - m_yAxis.activeValue());
}
qCDebug(lcPinchHandler) << "centroid" << centroid().scenePressPosition() << "->" << centroid().scenePosition()
<< ", distance" << m_startDistance << "->" << dist
- << ", scale" << m_startScale << "->" << m_accumulatedScale
- << ", activeRotation" << m_activeRotation
- << ", rotation" << rotation
- << " from " << event->device()->type();
+ << ", scale" << m_scaleAxis.m_startValue << "->" << m_scaleAxis.m_accumulatedValue
+ << ", rotation" << m_rotationAxis.m_startValue << "->" << m_rotationAxis.m_accumulatedValue
+ << ", translation" << translation() << " from " << event->device()->type();
emit updated();
}
+QPointF QQuickPinchHandler::startPos()
+{
+ return {m_xAxis.m_startValue, m_yAxis.m_startValue};
+}
+
/*!
\readonly
\qmlproperty QtQuick::HandlerPoint QtQuick::PinchHandler::centroid