Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickpincharea.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6#include "qquickwindow.h"
7
8#include <QtCore/qmath.h>
9#include <QtGui/qevent.h>
10#include <QtGui/qguiapplication.h>
11#include <QtGui/qstylehints.h>
12#include <qpa/qplatformintegration.h>
13#include <qpa/qplatformnativeinterface.h>
14#include <private/qguiapplication_p.h>
15#include <QVariant>
16
17#include <float.h>
18
19QT_BEGIN_NAMESPACE
20
21Q_STATIC_LOGGING_CATEGORY(lcPA, "qt.quick.pincharea")
22
23/*!
24 \qmltype PinchEvent
25 \nativetype QQuickPinchEvent
26 \inqmlmodule QtQuick
27 \ingroup qtquick-input-events
28 \brief For specifying information about a pinch event.
29
30 The \c center, \c startCenter, \c previousCenter properties provide the center position between the two touch points.
31
32 The \c scale and \c previousScale properties provide the scale factor.
33
34 The \c angle, \c previousAngle and \c rotation properties provide the angle between the two points and the amount of rotation.
35
36 The \c point1, \c point2, \c startPoint1, \c startPoint2 properties provide the positions of the touch points.
37
38 The \c accepted property may be set to false in the \c onPinchStarted handler if the gesture should not
39 be handled.
40
41 \sa PinchArea
42*/
43
44/*!
45 \qmlproperty point QtQuick::PinchEvent::center
46 \qmlproperty point QtQuick::PinchEvent::startCenter
47 \qmlproperty point QtQuick::PinchEvent::previousCenter
48
49 These properties hold the position of the center point between the two touch points.
50
51 \list
52 \li \c center is the current center point
53 \li \c previousCenter is the center point of the previous event.
54 \li \c startCenter is the center point when the gesture began
55 \endlist
56*/
57
58/*!
59 \qmlproperty real QtQuick::PinchEvent::scale
60 \qmlproperty real QtQuick::PinchEvent::previousScale
61
62 These properties hold the scale factor determined by the change in distance between the two touch points.
63
64 \list
65 \li \c scale is the current scale factor.
66 \li \c previousScale is the scale factor of the previous event.
67 \endlist
68
69 When a pinch gesture is started, the scale is \c 1.0.
70*/
71
72/*!
73 \qmlproperty real QtQuick::PinchEvent::angle
74 \qmlproperty real QtQuick::PinchEvent::previousAngle
75 \qmlproperty real QtQuick::PinchEvent::rotation
76
77 These properties hold the angle between the two touch points.
78
79 \list
80 \li \c angle is the current angle between the two points in the range -180 to 180.
81 \li \c previousAngle is the angle of the previous event.
82 \li \c rotation is the total rotation since the pinch gesture started.
83 \endlist
84
85 When a pinch gesture is started, the rotation is \c 0.0.
86*/
87
88/*!
89 \qmlproperty point QtQuick::PinchEvent::point1
90 \qmlproperty point QtQuick::PinchEvent::startPoint1
91 \qmlproperty point QtQuick::PinchEvent::point2
92 \qmlproperty point QtQuick::PinchEvent::startPoint2
93
94 These properties provide the actual touch points generating the pinch.
95
96 \list
97 \li \c point1 and \c point2 hold the current positions of the points.
98 \li \c startPoint1 and \c startPoint2 hold the positions of the points when the second point was touched.
99 \endlist
100*/
101
102/*!
103 \qmlproperty bool QtQuick::PinchEvent::accepted
104
105 Setting this property to false in the \c PinchArea::onPinchStarted handler
106 will result in no further pinch events being generated, and the gesture
107 ignored.
108*/
109
110/*!
111 \qmlproperty int QtQuick::PinchEvent::pointCount
112
113 Holds the number of points currently touched. The PinchArea will not react
114 until two touch points have initited a gesture, but will remain active until
115 all touch points have been released.
116*/
117
118QQuickPinch::QQuickPinch()
119 : m_target(nullptr), m_minScale(1.0), m_maxScale(1.0)
120 , m_minRotation(0.0), m_maxRotation(0.0)
121 , m_axis(NoDrag), m_xmin(-FLT_MAX), m_xmax(FLT_MAX)
122 , m_ymin(-FLT_MAX), m_ymax(FLT_MAX), m_active(false)
123{
124}
125
126QQuickPinchAreaPrivate::~QQuickPinchAreaPrivate()
127{
128 delete pinch;
129}
130
131/*!
132 \qmltype PinchArea
133 \nativetype QQuickPinchArea
134 \inqmlmodule QtQuick
135 \ingroup qtquick-input
136 \inherits Item
137 \brief Enables simple pinch gesture handling.
138
139 A PinchArea is an invisible item that is typically used in conjunction with
140 a visible item in order to provide pinch gesture handling for that item.
141
142 The \l enabled property is used to enable and disable pinch handling for
143 the proxied item. When disabled, the pinch area becomes transparent to
144 mouse/touch events.
145
146 PinchArea can be used in two ways:
147
148 \list
149 \li setting a \c pinch.target to provide automatic interaction with an item
150 \li using the onPinchStarted, onPinchUpdated and onPinchFinished handlers
151 \endlist
152
153 Since Qt 5.5, PinchArea can react to native pinch gesture events from the
154 operating system if available; otherwise it reacts only to touch events.
155
156 \sa PinchEvent, QNativeGestureEvent, QTouchEvent
157*/
158
159/*!
160 \qmlsignal QtQuick::PinchArea::pinchStarted(PinchEvent pinch)
161
162 This signal is emitted when the pinch area detects that a pinch gesture has
163 started: two touch points (fingers) have been detected, and they have moved
164 beyond the \l {QStyleHints}{startDragDistance} threshold for the gesture to begin.
165
166 The \a pinch parameter (not the same as the \l {PinchArea}{pinch}
167 property) provides information about the pinch gesture, including the scale,
168 center and angle of the pinch. At the time of the \c pinchStarted signal,
169 these values are reset to the default values, regardless of the results
170 from previous gestures: pinch.scale will be \c 1.0 and pinch.rotation will be \c 0.0.
171 As the gesture progresses, \l pinchUpdated will report the deviation from those
172 defaults.
173
174 To ignore this gesture set the \c pinch.accepted property to false. The gesture
175 will be canceled and no further events will be sent.
176*/
177
178/*!
179 \qmlsignal QtQuick::PinchArea::pinchUpdated(PinchEvent pinch)
180
181 This signal is emitted when the pinch area detects that a pinch gesture has changed.
182
183 The \a pinch parameter provides information about the pinch
184 gesture, including the scale, center and angle of the pinch. These values
185 reflect changes only since the beginning of the current gesture, and
186 therefore are not limited by the minimum and maximum limits in the
187 \l {PinchArea}{pinch} property.
188*/
189
190/*!
191 \qmlsignal QtQuick::PinchArea::pinchFinished(PinchEvent pinch)
192
193 This signal is emitted when the pinch area detects that a pinch gesture has finished.
194
195 The \a pinch parameter (not the same as the \l {PinchArea}{pinch}
196 property) provides information about the pinch gesture, including the
197 scale, center and angle of the pinch.
198*/
199
200/*!
201 \qmlsignal QtQuick::PinchArea::smartZoom(PinchEvent pinch)
202 \since 5.5
203
204 This signal is emitted when the pinch area detects a smart zoom gesture.
205 This gesture occurs only on certain operating systems such as \macos.
206
207 The \a pinch parameter provides information about the pinch
208 gesture, including the location where the gesture occurred. \c pinch.scale
209 will be greater than zero when the gesture indicates that the user wishes to
210 enter smart zoom, and zero when exiting (even though typically the same gesture
211 is used to toggle between the two states).
212*/
213
214
215/*!
216 \qmlpropertygroup QtQuick::PinchArea::pinch
217 \qmlproperty Item QtQuick::PinchArea::pinch.target
218 \qmlproperty bool QtQuick::PinchArea::pinch.active
219 \qmlproperty real QtQuick::PinchArea::pinch.minimumScale
220 \qmlproperty real QtQuick::PinchArea::pinch.maximumScale
221 \qmlproperty real QtQuick::PinchArea::pinch.minimumRotation
222 \qmlproperty real QtQuick::PinchArea::pinch.maximumRotation
223 \qmlproperty enumeration QtQuick::PinchArea::pinch.dragAxis
224 \qmlproperty real QtQuick::PinchArea::pinch.minimumX
225 \qmlproperty real QtQuick::PinchArea::pinch.maximumX
226 \qmlproperty real QtQuick::PinchArea::pinch.minimumY
227 \qmlproperty real QtQuick::PinchArea::pinch.maximumY
228
229 \c pinch provides a convenient way to make an item react to pinch gestures.
230
231 \list
232 \li \c pinch.target specifies the id of the item to drag.
233 \li \c pinch.active specifies if the target item is currently being dragged.
234 \li \c pinch.minimumScale and \c pinch.maximumScale limit the range of the Item.scale property, but not the \c PinchEvent \l {PinchEvent}{scale} property.
235 \li \c pinch.minimumRotation and \c pinch.maximumRotation limit the range of the Item.rotation property, but not the \c PinchEvent \l {PinchEvent}{rotation} property.
236 \li \c pinch.dragAxis specifies whether dragging in not allowed (\c Pinch.NoDrag), can be done horizontally (\c Pinch.XAxis), vertically (\c Pinch.YAxis), or both (\c Pinch.XAndYAxis)
237 \li \c pinch.minimum and \c pinch.maximum limit how far the target can be dragged along the corresponding axes.
238 \endlist
239*/
240
241QQuickPinchArea::QQuickPinchArea(QQuickItem *parent)
242 : QQuickItem(*(new QQuickPinchAreaPrivate), parent)
243{
244 Q_D(QQuickPinchArea);
245 d->init();
246 setAcceptTouchEvents(true);
247#ifdef Q_OS_MACOS
248 setAcceptHoverEvents(true); // needed to enable touch events on mouse hover.
249#endif
250}
251
252QQuickPinchArea::~QQuickPinchArea()
253{
254}
255/*!
256 \qmlproperty bool QtQuick::PinchArea::enabled
257 This property holds whether the item accepts pinch gestures.
258
259 This property defaults to true.
260*/
261bool QQuickPinchArea::isEnabled() const
262{
263 Q_D(const QQuickPinchArea);
264 return d->enabled;
265}
266
267void QQuickPinchArea::setEnabled(bool a)
268{
269 Q_D(QQuickPinchArea);
270 if (a != d->enabled) {
271 d->enabled = a;
272 emit enabledChanged();
273 }
274}
275
276void QQuickPinchArea::touchEvent(QTouchEvent *event)
277{
278 Q_D(QQuickPinchArea);
279 if (!d->enabled || !isVisible()) {
280 QQuickItem::touchEvent(event);
281 return;
282 }
283
284 // A common non-trivial starting scenario is the user puts down one finger,
285 // then that finger remains stationary while putting down a second one.
286 // However QQuickWindow will not send TouchUpdates for TouchPoints which
287 // were not initially accepted; that would be inefficient and noisy.
288 // So even if there is only one touchpoint so far, it's important to accept it
289 // in order to get updates later on (and it's accepted by default anyway).
290 // If the user puts down one finger, we're waiting for the other finger to drop.
291 // Therefore updatePinch() must do the right thing for any combination of
292 // points and states that may occur, and there is no reason to ignore any event.
293 // One consequence though is that if PinchArea is on top of something else,
294 // it's always going to accept the touches, and that means the item underneath
295 // will not get them (unless the PA's parent is doing parent filtering,
296 // as the Flickable does, for example).
297 switch (event->type()) {
298 case QEvent::TouchBegin:
299 case QEvent::TouchUpdate:
300 d->touchPoints.clear();
301 for (int i = 0; i < event->pointCount(); ++i) {
302 auto &tp = event->point(i);
303 if (tp.state() != QEventPoint::State::Released) {
304 d->touchPoints << tp;
305 tp.setAccepted();
306 }
307 }
308 updatePinch(event, false);
309 break;
310 case QEvent::TouchEnd:
311 clearPinch(event);
312 break;
313 case QEvent::TouchCancel:
314 cancelPinch(event);
315 break;
316 default:
317 QQuickItem::touchEvent(event);
318 }
319}
320
321void QQuickPinchArea::clearPinch(QTouchEvent *event)
322{
323 Q_D(QQuickPinchArea);
324 qCDebug(lcPA, "clear: %" PRIdQSIZETYPE " touchpoints", d->touchPoints.size());
325 d->touchPoints.clear();
326 if (d->inPinch) {
327 d->inPinch = false;
328 QPointF pinchCenter = mapFromScene(d->sceneLastCenter);
329 QQuickPinchEvent pe(pinchCenter, d->pinchLastScale, d->pinchLastAngle, d->pinchRotation);
330 pe.setStartCenter(d->pinchStartCenter);
331 pe.setPreviousCenter(pinchCenter);
332 pe.setPreviousAngle(d->pinchLastAngle);
333 pe.setPreviousScale(d->pinchLastScale);
334 pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
335 pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
336 pe.setPoint1(mapFromScene(d->lastPoint1));
337 pe.setPoint2(mapFromScene(d->lastPoint2));
338 emit pinchFinished(&pe);
339 if (d->pinch && d->pinch->target())
340 d->pinch->setActive(false);
341 }
342 d->pinchStartDist = 0;
343 d->pinchActivated = false;
344 d->initPinch = false;
345 d->pinchRejected = false;
346 d->id1 = -1;
347 if (event) {
348 for (const auto &point : event->points()) {
349 if (event->exclusiveGrabber(point) == this)
350 event->setExclusiveGrabber(point, nullptr);
351 }
352 }
353 setKeepTouchGrab(false);
354 setKeepMouseGrab(false);
355}
356
357void QQuickPinchArea::cancelPinch(QTouchEvent *event)
358{
359 Q_D(QQuickPinchArea);
360 qCDebug(lcPA, "cancel: %" PRIdQSIZETYPE " touchpoints", d->touchPoints.size());
361 d->touchPoints.clear();
362 if (d->inPinch) {
363 d->inPinch = false;
364 QPointF pinchCenter = mapFromScene(d->sceneLastCenter);
365 QQuickPinchEvent pe(d->pinchStartCenter, d->pinchStartScale, d->pinchStartAngle, d->pinchStartRotation);
366 pe.setStartCenter(d->pinchStartCenter);
367 pe.setPreviousCenter(pinchCenter);
368 pe.setPreviousAngle(d->pinchLastAngle);
369 pe.setPreviousScale(d->pinchLastScale);
370 pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
371 pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
372 pe.setPoint1(pe.startPoint1());
373 pe.setPoint2(pe.startPoint2());
374 emit pinchFinished(&pe);
375
376 d->pinchLastScale = d->pinchStartScale;
377 d->sceneLastCenter = d->sceneStartCenter;
378 d->pinchLastAngle = d->pinchStartAngle;
379 d->lastPoint1 = pe.startPoint1();
380 d->lastPoint2 = pe.startPoint2();
381 updatePinchTarget();
382
383 if (d->pinch && d->pinch->target())
384 d->pinch->setActive(false);
385 }
386 d->pinchStartDist = 0;
387 d->pinchActivated = false;
388 d->initPinch = false;
389 d->pinchRejected = false;
390 d->id1 = -1;
391 for (const auto &point : event->points()) {
392 if (event->exclusiveGrabber(point) == this)
393 event->setExclusiveGrabber(point, nullptr);
394 }
395 setKeepTouchGrab(false);
396 setKeepMouseGrab(false);
397}
398
399void QQuickPinchArea::updatePinch(QTouchEvent *event, bool filtering)
400{
401 Q_D(QQuickPinchArea);
402
403 if (d->touchPoints.size() < 2) {
404 // A pinch gesture is not occurring, so stealing the grab is permitted.
405 setKeepTouchGrab(false);
406 setKeepMouseGrab(false);
407 // During filtering, there's no need to hold a grab for one point,
408 // because filtering happens for every event anyway.
409 // But if we receive the event via direct delivery, and give up the grab,
410 // not only will we not see any more updates, but any filtering parent
411 // (such as Flickable) will also not get a chance to filter them.
412 // Continuing to hold the grab in this case keeps tst_TouchMouse::pinchOnFlickable() working.
413 if (filtering && !d->touchPoints.isEmpty() && event->exclusiveGrabber(d->touchPoints.first()) == this)
414 event->setExclusiveGrabber(d->touchPoints.first(), nullptr);
415 }
416
417 if (d->touchPoints.size() == 0) {
418 if (d->inPinch) {
419 d->inPinch = false;
420 QPointF pinchCenter = mapFromScene(d->sceneLastCenter);
421 QQuickPinchEvent pe(pinchCenter, d->pinchLastScale, d->pinchLastAngle, d->pinchRotation);
422 pe.setStartCenter(d->pinchStartCenter);
423 pe.setPreviousCenter(pinchCenter);
424 pe.setPreviousAngle(d->pinchLastAngle);
425 pe.setPreviousScale(d->pinchLastScale);
426 pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
427 pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
428 pe.setPoint1(mapFromScene(d->lastPoint1));
429 pe.setPoint2(mapFromScene(d->lastPoint2));
430 setKeepTouchGrab(false);
431 setKeepMouseGrab(false);
432 emit pinchFinished(&pe);
433 d->pinchStartDist = 0;
434 d->pinchActivated = false;
435 if (d->pinch && d->pinch->target())
436 d->pinch->setActive(false);
437 }
438 d->initPinch = false;
439 d->pinchRejected = false;
440 return;
441 }
442
443 QEventPoint touchPoint1 = d->touchPoints.at(0);
444 QEventPoint touchPoint2 = d->touchPoints.at(d->touchPoints.size() >= 2 ? 1 : 0);
445
446 if (touchPoint1.state() == QEventPoint::State::Pressed)
447 d->sceneStartPoint1 = touchPoint1.scenePosition();
448
449 if (touchPoint2.state() == QEventPoint::State::Pressed)
450 d->sceneStartPoint2 = touchPoint2.scenePosition();
451
452 qCDebug(lcPA) << "updating based on" << touchPoint1 << touchPoint2;
453
454 QRectF bounds = clipRect();
455 // Pinch is not started unless there are exactly two touch points
456 // AND one or more of the points has just now been pressed (wasn't pressed already)
457 // AND both points are inside the bounds.
458 if (d->touchPoints.size() == 2
459 && (touchPoint1.state() == QEventPoint::State::Pressed || touchPoint2.state() == QEventPoint::State::Pressed) &&
460 bounds.contains(touchPoint1.position()) && bounds.contains(touchPoint2.position())) {
461 d->id1 = touchPoint1.id();
462 if (!d->pinchActivated)
463 qCDebug(lcPA, "pinch activating");
464 d->pinchActivated = true;
465 d->initPinch = true;
466 event->setExclusiveGrabber(touchPoint1, this);
467 event->setExclusiveGrabber(touchPoint2, this);
468 setKeepTouchGrab(true);
469 setKeepMouseGrab(true);
470 }
471 if (d->pinchActivated && !d->pinchRejected) {
472 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
473 QPointF p1 = touchPoint1.scenePosition();
474 QPointF p2 = touchPoint2.scenePosition();
475 qreal dx = p1.x() - p2.x();
476 qreal dy = p1.y() - p2.y();
477 qreal dist = qSqrt(dx*dx + dy*dy);
478 QPointF sceneCenter = (p1 + p2)/2;
479 qreal angle = QLineF(p1, p2).angle();
480 if (d->touchPoints.size() == 1) {
481 // If we only have one point then just move the center
482 if (d->id1 == touchPoint1.id())
483 sceneCenter = d->sceneLastCenter + touchPoint1.scenePosition() - d->lastPoint1;
484 else
485 sceneCenter = d->sceneLastCenter + touchPoint2.scenePosition() - d->lastPoint2;
486 angle = d->pinchLastAngle;
487 }
488 d->id1 = touchPoint1.id();
489 if (angle > 180)
490 angle -= 360;
491 qCDebug(lcPA, "pinch \u2316 %.1lf,%.1lf \u21e4%.1lf\u21e5 \u2220 %.1lf",
492 sceneCenter.x(), sceneCenter.y(), dist, angle);
493 if (!d->inPinch || d->initPinch) {
494 if (d->touchPoints.size() >= 2) {
495 if (d->initPinch) {
496 if (!d->inPinch)
497 d->pinchStartDist = dist;
498 d->initPinch = false;
499 }
500 d->sceneStartCenter = sceneCenter;
501 d->sceneLastCenter = sceneCenter;
502 d->pinchStartCenter = mapFromScene(sceneCenter);
503 d->pinchStartAngle = angle;
504 d->pinchLastScale = 1.0;
505 d->pinchLastAngle = angle;
506 d->pinchRotation = 0.0;
507 d->lastPoint1 = p1;
508 d->lastPoint2 = p2;
509 if (qAbs(dist - d->pinchStartDist) >= dragThreshold ||
510 (pinch()->axis() != QQuickPinch::NoDrag &&
511 (qAbs(p1.x()-d->sceneStartPoint1.x()) >= dragThreshold
512 || qAbs(p1.y()-d->sceneStartPoint1.y()) >= dragThreshold
513 || qAbs(p2.x()-d->sceneStartPoint2.x()) >= dragThreshold
514 || qAbs(p2.y()-d->sceneStartPoint2.y()) >= dragThreshold))) {
515 QQuickPinchEvent pe(d->pinchStartCenter, 1.0, angle, 0.0);
516 d->pinchStartDist = dist;
517 pe.setStartCenter(d->pinchStartCenter);
518 pe.setPreviousCenter(d->pinchStartCenter);
519 pe.setPreviousAngle(d->pinchLastAngle);
520 pe.setPreviousScale(d->pinchLastScale);
521 pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
522 pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
523 pe.setPoint1(mapFromScene(d->lastPoint1));
524 pe.setPoint2(mapFromScene(d->lastPoint2));
525 pe.setPointCount(d->touchPoints.size());
526 emit pinchStarted(&pe);
527 if (pe.accepted()) {
528 d->inPinch = true;
529 event->setExclusiveGrabber(touchPoint1, this);
530 event->setExclusiveGrabber(touchPoint2, this);
531 setKeepTouchGrab(true);
532 // So that PinchArea works in PathView, grab mouse events too.
533 // We should be able to remove these setKeepMouseGrab calls when QTBUG-105567 is fixed.
534 setKeepMouseGrab(true);
535 d->inPinch = true;
536 if (d->pinch && d->pinch->target()) {
537 auto targetParent = pinch()->target()->parentItem();
538 d->pinchStartPos = targetParent ?
539 targetParent->mapToScene(pinch()->target()->position()) :
540 pinch()->target()->position();
541 d->pinchStartScale = d->pinch->target()->scale();
542 d->pinchStartRotation = d->pinch->target()->rotation();
543 d->pinch->setActive(true);
544 }
545 } else {
546 d->pinchRejected = true;
547 }
548 }
549 }
550 } else if (d->pinchStartDist > 0) {
551 qreal scale = dist ? dist / d->pinchStartDist : d->pinchLastScale;
552 qreal da = d->pinchLastAngle - angle;
553 if (da > 180)
554 da -= 360;
555 else if (da < -180)
556 da += 360;
557 d->pinchRotation += da;
558 QPointF pinchCenter = mapFromScene(sceneCenter);
559 QQuickPinchEvent pe(pinchCenter, scale, angle, d->pinchRotation);
560 pe.setStartCenter(d->pinchStartCenter);
561 pe.setPreviousCenter(mapFromScene(d->sceneLastCenter));
562 pe.setPreviousAngle(d->pinchLastAngle);
563 pe.setPreviousScale(d->pinchLastScale);
564 pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
565 pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
566 pe.setPoint1(touchPoint1.position());
567 pe.setPoint2(touchPoint2.position());
568 pe.setPointCount(d->touchPoints.size());
569 d->pinchLastScale = scale;
570 d->sceneLastCenter = sceneCenter;
571 d->pinchLastAngle = angle;
572 d->lastPoint1 = touchPoint1.scenePosition();
573 d->lastPoint2 = touchPoint2.scenePosition();
574 emit pinchUpdated(&pe);
575 updatePinchTarget();
576 }
577 }
578}
579
580void QQuickPinchArea::updatePinchTarget()
581{
582 Q_D(QQuickPinchArea);
583 if (d->pinch && d->pinch->target()) {
584 qreal s = d->pinchStartScale * d->pinchLastScale;
585 s = qMin(qMax(pinch()->minimumScale(),s), pinch()->maximumScale());
586 pinch()->target()->setScale(s);
587 QPointF pos = d->sceneLastCenter - d->sceneStartCenter + d->pinchStartPos;
588 if (auto targetParent = pinch()->target()->parentItem())
589 pos = targetParent->mapFromScene(pos);
590
591 if (pinch()->axis() & QQuickPinch::XAxis) {
592 qreal x = pos.x();
593 if (x < pinch()->xmin())
594 x = pinch()->xmin();
595 else if (x > pinch()->xmax())
596 x = pinch()->xmax();
597 pinch()->target()->setX(x);
598 }
599 if (pinch()->axis() & QQuickPinch::YAxis) {
600 qreal y = pos.y();
601 if (y < pinch()->ymin())
602 y = pinch()->ymin();
603 else if (y > pinch()->ymax())
604 y = pinch()->ymax();
605 pinch()->target()->setY(y);
606 }
607 if (d->pinchStartRotation >= pinch()->minimumRotation()
608 && d->pinchStartRotation <= pinch()->maximumRotation()) {
609 qreal r = d->pinchRotation + d->pinchStartRotation;
610 r = qMin(qMax(pinch()->minimumRotation(),r), pinch()->maximumRotation());
611 pinch()->target()->setRotation(r);
612 }
613 }
614}
615
616/*! \internal
617 PinchArea needs to filter touch events going to its children: in case
618 one of them stops event propagation by accepting the touch event, filtering
619 is the only way PinchArea can see the touch event.
620
621 This method is called childMouseEventFilter instead of childPointerEventFilter
622 for historical reasons, but actually filters all pointer events (and the
623 occasional QEvent::UngrabMouse).
624*/
625bool QQuickPinchArea::childMouseEventFilter(QQuickItem *i, QEvent *e)
626{
627 Q_D(QQuickPinchArea);
628 if (!d->enabled || !isVisible())
629 return QQuickItem::childMouseEventFilter(i, e);
630 auto *te = static_cast<QTouchEvent*>(e);
631 switch (e->type()) {
632 case QEvent::TouchBegin:
633 clearPinch(te);
634 Q_FALLTHROUGH();
635 case QEvent::TouchUpdate: {
636 const auto &points = te->points();
637 d->touchPoints.clear();
638 for (auto &tp : points) {
639 if (tp.state() != QEventPoint::State::Released)
640 d->touchPoints << tp;
641 }
642 updatePinch(te, true);
643 }
644 e->setAccepted(d->inPinch);
645 return d->inPinch;
646 case QEvent::TouchEnd:
647 clearPinch(te);
648 break;
649 default:
650 break;
651 }
652
653 return QQuickItem::childMouseEventFilter(i, e);
654}
655
656void QQuickPinchArea::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
657{
658 QQuickItem::geometryChange(newGeometry, oldGeometry);
659}
660
661void QQuickPinchArea::itemChange(ItemChange change, const ItemChangeData &value)
662{
663 QQuickItem::itemChange(change, value);
664}
665
666bool QQuickPinchArea::event(QEvent *event)
667{
668 Q_D(QQuickPinchArea);
669 if (!d->enabled || !isVisible())
670 return QQuickItem::event(event);
671
672 switch (event->type()) {
673#if QT_CONFIG(gestures)
674 case QEvent::NativeGesture: {
675 QNativeGestureEvent *gesture = static_cast<QNativeGestureEvent *>(event);
676 switch (gesture->gestureType()) {
677 case Qt::BeginNativeGesture:
678 clearPinch(nullptr); // probably not necessary; JIC
679 d->pinchStartCenter = gesture->position();
680 d->pinchStartAngle = 0.0;
681 d->pinchStartRotation = 0.0;
682 d->pinchRotation = 0.0;
683 d->pinchStartScale = 1.0;
684 d->pinchLastAngle = 0.0;
685 d->pinchLastScale = 1.0;
686 d->sceneStartPoint1 = gesture->scenePosition();
687 d->sceneStartPoint2 = gesture->scenePosition(); // TODO we never really know
688 d->lastPoint1 = gesture->scenePosition();
689 d->lastPoint2 = gesture->scenePosition(); // TODO we never really know
690 if (d->pinch && d->pinch->target()) {
691 d->pinchStartPos = d->pinch->target()->position();
692 d->pinchStartScale = d->pinch->target()->scale();
693 d->pinchStartRotation = d->pinch->target()->rotation();
694 d->pinch->setActive(true);
695 }
696 break;
697 case Qt::EndNativeGesture:
698 clearPinch(nullptr);
699 break;
700 case Qt::ZoomNativeGesture: {
701 if (d->pinchRejected)
702 break;
703 qreal scale = d->pinchLastScale * (1.0 + gesture->value());
704 QQuickPinchEvent pe(d->pinchStartCenter, scale, d->pinchLastAngle, 0.0);
705 pe.setStartCenter(d->pinchStartCenter);
706 pe.setPreviousCenter(d->pinchStartCenter);
707 pe.setPreviousAngle(d->pinchLastAngle);
708 pe.setPreviousScale(d->pinchLastScale);
709 pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
710 pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
711 pe.setPoint1(mapFromScene(d->lastPoint1));
712 pe.setPoint2(mapFromScene(d->lastPoint2));
713 pe.setPointCount(2);
714 d->pinchLastScale = scale;
715 if (d->inPinch)
716 emit pinchUpdated(&pe);
717 else
718 emit pinchStarted(&pe);
719 d->inPinch = true;
720 if (pe.accepted())
721 updatePinchTarget();
722 else
723 d->pinchRejected = true;
724 } break;
725 case Qt::SmartZoomNativeGesture: {
726 if (gesture->value() > 0.0 && d->pinch && d->pinch->target()) {
727 d->pinchStartPos = pinch()->target()->position();
728 d->pinchStartCenter = mapToItem(pinch()->target()->parentItem(), pinch()->target()->boundingRect().center());
729 d->pinchStartScale = d->pinch->target()->scale();
730 d->pinchStartRotation = d->pinch->target()->rotation();
731 d->pinchLastScale = d->pinchStartScale = d->pinch->target()->scale();
732 d->pinchLastAngle = d->pinchStartRotation = d->pinch->target()->rotation();
733 }
734 QQuickPinchEvent pe(gesture->position(), gesture->value(), d->pinchLastAngle, 0.0);
735 pe.setStartCenter(gesture->position());
736 pe.setPreviousCenter(d->pinchStartCenter);
737 pe.setPreviousAngle(d->pinchLastAngle);
738 pe.setPreviousScale(d->pinchLastScale);
739 pe.setStartPoint1(gesture->position());
740 pe.setStartPoint2(gesture->position());
741 pe.setPoint1(mapFromScene(gesture->scenePosition()));
742 pe.setPoint2(mapFromScene(gesture->scenePosition()));
743 pe.setPointCount(2);
744 emit smartZoom(&pe);
745 } break;
746 case Qt::RotateNativeGesture: {
747 if (d->pinchRejected)
748 break;
749 qreal angle = d->pinchLastAngle + gesture->value();
750 QQuickPinchEvent pe(d->pinchStartCenter, d->pinchLastScale, angle, 0.0);
751 pe.setStartCenter(d->pinchStartCenter);
752 pe.setPreviousCenter(d->pinchStartCenter);
753 pe.setPreviousAngle(d->pinchLastAngle);
754 pe.setPreviousScale(d->pinchLastScale);
755 pe.setStartPoint1(mapFromScene(d->sceneStartPoint1));
756 pe.setStartPoint2(mapFromScene(d->sceneStartPoint2));
757 pe.setPoint1(mapFromScene(d->lastPoint1));
758 pe.setPoint2(mapFromScene(d->lastPoint2));
759 pe.setPointCount(2);
760 d->pinchLastAngle = angle;
761 if (d->inPinch)
762 emit pinchUpdated(&pe);
763 else
764 emit pinchStarted(&pe);
765 d->inPinch = true;
766 d->pinchRotation = angle;
767 if (pe.accepted())
768 updatePinchTarget();
769 else
770 d->pinchRejected = true;
771 } break;
772 default:
773 return QQuickItem::event(event);
774 }
775 } break;
776#endif // gestures
777 case QEvent::Wheel:
778 event->ignore();
779 return false;
780 default:
781 return QQuickItem::event(event);
782 }
783
784 return true;
785}
786
787QQuickPinch *QQuickPinchArea::pinch()
788{
789 Q_D(QQuickPinchArea);
790 if (!d->pinch)
791 d->pinch = new QQuickPinch;
792 return d->pinch;
793}
794
795QT_END_NAMESPACE
796
797#include "moc_qquickpincharea_p.cpp"