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