1 | // Copyright (C) 2019 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 | |
4 | #include "qquicktaphandler_p.h" |
5 | #include "qquicksinglepointhandler_p_p.h" |
6 | #include <QtQuick/private/qquickdeliveryagent_p_p.h> |
7 | #include <QtQuick/qquickwindow.h> |
8 | #include <qpa/qplatformtheme.h> |
9 | #include <private/qguiapplication_p.h> |
10 | #include <QtGui/qstylehints.h> |
11 | |
12 | QT_BEGIN_NAMESPACE |
13 | |
14 | Q_LOGGING_CATEGORY(lcTapHandler, "qt.quick.handler.tap") |
15 | |
16 | quint64 QQuickTapHandler::m_multiTapInterval(0); |
17 | // single tap distance is the same as the drag threshold |
18 | int QQuickTapHandler::m_mouseMultiClickDistanceSquared(-1); |
19 | int QQuickTapHandler::m_touchMultiTapDistanceSquared(-1); |
20 | |
21 | /*! |
22 | \qmltype TapHandler |
23 | \nativetype QQuickTapHandler |
24 | \inherits SinglePointHandler |
25 | \inqmlmodule QtQuick |
26 | \ingroup qtquick-input-handlers |
27 | \brief Handler for taps and clicks. |
28 | |
29 | TapHandler is a handler for taps on a touchscreen or clicks on a mouse. |
30 | |
31 | Detection of a valid tap gesture depends on \l gesturePolicy. The default |
32 | value is DragThreshold, which requires the press and release to be close |
33 | together in both space and time. In this case, DragHandler is able to |
34 | function using only a passive grab, and therefore does not interfere with |
35 | event delivery to any other Items or Input Handlers. So the default |
36 | gesturePolicy is useful when you want to modify behavior of an existing |
37 | control or Item by adding a TapHandler with bindings and/or JavaScript |
38 | callbacks. |
39 | |
40 | Note that buttons (such as QPushButton) are often implemented not to care |
41 | whether the press and release occur close together: if you press the button |
42 | and then change your mind, you need to drag all the way off the edge of the |
43 | button in order to cancel the click. For this use case, set the |
44 | \l gesturePolicy to \c TapHandler.ReleaseWithinBounds. |
45 | |
46 | \snippet pointerHandlers/tapHandlerButton.qml 0 |
47 | |
48 | For multi-tap gestures (double-tap, triple-tap etc.), the distance moved |
49 | must not exceed QStyleHints::mouseDoubleClickDistance() with mouse and |
50 | QStyleHints::touchDoubleTapDistance() with touch, and the time between |
51 | taps must not exceed QStyleHints::mouseDoubleClickInterval(). |
52 | |
53 | \sa MouseArea, {Qt Quick Examples - Pointer Handlers} |
54 | */ |
55 | |
56 | QQuickTapHandler::QQuickTapHandler(QQuickItem *parent) |
57 | : QQuickSinglePointHandler(parent) |
58 | , m_longPressThreshold(QGuiApplication::styleHints()->mousePressAndHoldInterval()) |
59 | { |
60 | if (m_mouseMultiClickDistanceSquared < 0) { |
61 | m_multiTapInterval = qApp->styleHints()->mouseDoubleClickInterval(); |
62 | m_mouseMultiClickDistanceSquared = qApp->styleHints()->mouseDoubleClickDistance(); |
63 | m_mouseMultiClickDistanceSquared *= m_mouseMultiClickDistanceSquared; |
64 | m_touchMultiTapDistanceSquared = qApp->styleHints()->touchDoubleTapDistance(); |
65 | m_touchMultiTapDistanceSquared *= m_touchMultiTapDistanceSquared; |
66 | } |
67 | } |
68 | |
69 | bool QQuickTapHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) |
70 | { |
71 | if (!QQuickDeliveryAgentPrivate::isMouseEvent(ev: event) && |
72 | !QQuickDeliveryAgentPrivate::isTouchEvent(ev: event) && |
73 | !QQuickDeliveryAgentPrivate::isTabletEvent(ev: event)) |
74 | return false; |
75 | // If the user has not violated any constraint, it could be a tap. |
76 | // Otherwise we want to give up the grab so that a competing handler |
77 | // (e.g. DragHandler) gets a chance to take over. |
78 | // Don't forget to emit released in case of a cancel. |
79 | bool ret = false; |
80 | bool overThreshold = d_func()->dragOverThreshold(point); |
81 | if (overThreshold && m_gesturePolicy != DragWithinBounds) { |
82 | if (m_longPressTimer.isActive()) |
83 | qCDebug(lcTapHandler) << objectName() << "drag threshold exceeded"; |
84 | m_longPressTimer.stop(); |
85 | m_holdTimer.invalidate(); |
86 | } |
87 | switch (point.state()) { |
88 | case QEventPoint::Pressed: |
89 | case QEventPoint::Released: |
90 | ret = parentContains(point); |
91 | break; |
92 | case QEventPoint::Updated: |
93 | ret = point.id() == this->point().id(); |
94 | switch (m_gesturePolicy) { |
95 | case DragThreshold: |
96 | ret = ret && !overThreshold && parentContains(point); |
97 | break; |
98 | case WithinBounds: |
99 | case DragWithinBounds: |
100 | ret = ret && parentContains(point); |
101 | break; |
102 | case ReleaseWithinBounds: |
103 | // no change to ret: depends only whether it's the already-tracking point ID |
104 | break; |
105 | } |
106 | break; |
107 | case QEventPoint::Stationary: |
108 | // If the point hasn't moved since last time, the return value should be the same as last time. |
109 | // If we return false here, QQuickPointerHandler::handlePointerEvent() will call setActive(false). |
110 | ret = point.id() == this->point().id(); |
111 | break; |
112 | case QEventPoint::Unknown: |
113 | break; |
114 | } |
115 | // If this is the grabber, returning false from this function will cancel the grab, |
116 | // so onGrabChanged(this, CancelGrabExclusive, point) and setPressed(false) will be called. |
117 | // But when m_gesturePolicy is DragThreshold, we don't get an exclusive grab, but |
118 | // we still don't want to be pressed anymore. |
119 | if (!ret && point.id() == this->point().id()) |
120 | setPressed(press: false, cancel: true, event: const_cast<QPointerEvent *>(event), point&: const_cast<QEventPoint &>(point)); |
121 | return ret; |
122 | } |
123 | |
124 | void QQuickTapHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point) |
125 | { |
126 | const bool isTouch = QQuickDeliveryAgentPrivate::isTouchEvent(ev: event); |
127 | switch (point.state()) { |
128 | case QEventPoint::Pressed: |
129 | setPressed(press: true, cancel: false, event, point); |
130 | break; |
131 | case QEventPoint::Released: { |
132 | if (isTouch || (static_cast<const QSinglePointEvent *>(event)->buttons() & acceptedButtons()) == Qt::NoButton) |
133 | setPressed(press: false, cancel: false, event, point); |
134 | break; |
135 | } |
136 | default: |
137 | break; |
138 | } |
139 | |
140 | QQuickSinglePointHandler::handleEventPoint(event, point); |
141 | |
142 | // If TapHandler only needs a passive grab, it should not block other items and handlers from reacting. |
143 | // If the point is accepted, QQuickItemPrivate::localizedTouchEvent() would skip it. |
144 | if (isTouch && m_gesturePolicy == DragThreshold) |
145 | point.setAccepted(false); |
146 | } |
147 | |
148 | /*! |
149 | \qmlproperty real QtQuick::TapHandler::longPressThreshold |
150 | |
151 | The time in seconds that an \l eventPoint must be pressed in order to |
152 | trigger a long press gesture and emit the \l longPressed() signal, if the |
153 | value is greater than \c 0. If the point is released before this time |
154 | limit, a tap can be detected if the \l gesturePolicy constraint is |
155 | satisfied. If \c longPressThreshold is \c 0, the timer is disabled and the |
156 | signal will not be emitted. If \c longPressThreshold is set to \c undefined, |
157 | the default value is used instead, and can be read back from this property. |
158 | |
159 | The default value is QStyleHints::mousePressAndHoldInterval() converted to |
160 | seconds. |
161 | */ |
162 | qreal QQuickTapHandler::longPressThreshold() const |
163 | { |
164 | return m_longPressThreshold / qreal(1000); |
165 | } |
166 | |
167 | void QQuickTapHandler::setLongPressThreshold(qreal longPressThreshold) |
168 | { |
169 | if (longPressThreshold < 0) { |
170 | resetLongPressThreshold(); |
171 | return; |
172 | } |
173 | int ms = qRound(d: longPressThreshold * 1000); |
174 | if (m_longPressThreshold == ms) |
175 | return; |
176 | |
177 | m_longPressThreshold = ms; |
178 | emit longPressThresholdChanged(); |
179 | } |
180 | |
181 | void QQuickTapHandler::resetLongPressThreshold() |
182 | { |
183 | int ms = QGuiApplication::styleHints()->mousePressAndHoldInterval(); |
184 | if (m_longPressThreshold == ms) |
185 | return; |
186 | |
187 | m_longPressThreshold = ms; |
188 | emit longPressThresholdChanged(); |
189 | } |
190 | |
191 | void QQuickTapHandler::timerEvent(QTimerEvent *event) |
192 | { |
193 | if (event->timerId() == m_longPressTimer.timerId()) { |
194 | m_longPressTimer.stop(); |
195 | qCDebug(lcTapHandler) << objectName() << "longPressed"; |
196 | m_longPressed = true; |
197 | emit longPressed(); |
198 | } else if (event->timerId() == m_doubleTapTimer.timerId()) { |
199 | m_doubleTapTimer.stop(); |
200 | qCDebug(lcTapHandler) << objectName() << "double-tap timer expired; taps:"<< m_tapCount; |
201 | Q_ASSERT(m_exclusiveSignals == (SingleTap | DoubleTap)); |
202 | if (m_tapCount == 1) |
203 | emit singleTapped(eventPoint: m_singleTapReleasedPoint, m_singleTapReleasedButton); |
204 | else if (m_tapCount == 2) |
205 | emit doubleTapped(eventPoint: m_singleTapReleasedPoint, m_singleTapReleasedButton); |
206 | } |
207 | } |
208 | |
209 | /*! |
210 | \qmlproperty enumeration QtQuick::TapHandler::gesturePolicy |
211 | |
212 | The spatial constraint for a tap or long press gesture to be recognized, |
213 | in addition to the constraint that the release must occur before |
214 | \l longPressThreshold has elapsed. If these constraints are not satisfied, |
215 | the \l tapped signal is not emitted, and \l tapCount is not incremented. |
216 | If the spatial constraint is violated, \l pressed transitions immediately |
217 | from true to false, regardless of the time held. |
218 | |
219 | The \c gesturePolicy also affects grab behavior as described below. |
220 | |
221 | \table |
222 | \header |
223 | \li Constant |
224 | \li Description |
225 | \row |
226 | \li \c TapHandler.DragThreshold |
227 | \image pointerHandlers/tapHandlerOverlappingButtons.webp |
228 | Grab on press: \e passive |
229 | \li (the default value) The \l eventPoint must not move significantly. |
230 | If the mouse, finger or stylus moves past the system-wide drag |
231 | threshold (QStyleHints::startDragDistance), the tap gesture is |
232 | canceled, even if the device or finger is still pressed. This policy |
233 | can be useful whenever TapHandler needs to cooperate with other |
234 | input handlers (for example \l DragHandler) or event-handling Items |
235 | (for example \l {Qt Quick Controls}), because in this case TapHandler |
236 | will not take the exclusive grab, but merely a |
237 | \l {QPointerEvent::addPassiveGrabber()}{passive grab}. |
238 | That is, \c DragThreshold is especially useful to \e augment |
239 | existing behavior: it reacts to tap/click/long-press even when |
240 | another item or handler is already reacting, perhaps even in a |
241 | different layer of the UI. The following snippet shows one |
242 | TapHandler as used in one component; but if we stack up two |
243 | instances of the component, you will see the handlers in both of them |
244 | react simultaneously when a press occurs over both of them, because |
245 | the passive grab does not stop event propagation: |
246 | \quotefromfile pointerHandlers/tapHandlerOverlappingButtons.qml |
247 | \skipto Item |
248 | \printuntil component Button |
249 | \skipto TapHandler |
250 | \printuntil } |
251 | \skipuntil Text { |
252 | \skipuntil } |
253 | \printuntil Button |
254 | \printuntil Button |
255 | \printuntil } |
256 | |
257 | \row |
258 | \li \c TapHandler.WithinBounds |
259 | \image pointerHandlers/tapHandlerButtonWithinBounds.webp |
260 | Grab on press: \e exclusive |
261 | \li If the \l eventPoint leaves the bounds of the \c parent Item, the tap |
262 | gesture is canceled. The TapHandler will take the |
263 | \l {QPointerEvent::setExclusiveGrabber}{exclusive grab} on |
264 | press, but will release the grab as soon as the boundary constraint |
265 | is no longer satisfied. |
266 | \snippet pointerHandlers/tapHandlerButtonWithinBounds.qml 1 |
267 | |
268 | \row |
269 | \li \c TapHandler.ReleaseWithinBounds |
270 | \image pointerHandlers/tapHandlerButtonReleaseWithinBounds.webp |
271 | Grab on press: \e exclusive |
272 | \li At the time of release (the mouse button is released or the finger |
273 | is lifted), if the \l eventPoint is outside the bounds of the |
274 | \c parent Item, a tap gesture is not recognized. This corresponds to |
275 | typical behavior for button widgets: you can cancel a click by |
276 | dragging outside the button, and you can also change your mind by |
277 | dragging back inside the button before release. Note that it's |
278 | necessary for TapHandler to take the |
279 | \l {QPointerEvent::setExclusiveGrabber}{exclusive grab} on press |
280 | and retain it until release in order to detect this gesture. |
281 | \snippet pointerHandlers/tapHandlerButtonReleaseWithinBounds.qml 1 |
282 | |
283 | \row |
284 | \li \c TapHandler.DragWithinBounds |
285 | \image pointerHandlers/dragReleaseMenu.webp |
286 | Grab on press: \e exclusive |
287 | \li On press, TapHandler takes the |
288 | \l {QPointerEvent::setExclusiveGrabber}{exclusive grab}; after that, |
289 | the \l eventPoint can be dragged within the bounds of the \c parent |
290 | item, while the \l timeHeld property keeps counting, and the |
291 | \l longPressed() signal will be emitted regardless of drag distance. |
292 | However, like \c WithinBounds, if the point leaves the bounds, |
293 | the tap gesture is \l {PointerHandler::}{canceled()}, \l active() |
294 | becomes \c false, and \l timeHeld stops counting. This is suitable |
295 | for implementing press-drag-release components, such as menus, in |
296 | which a single TapHandler detects press, \c timeHeld drives an |
297 | "opening" animation, and then the user can drag to a menu item and |
298 | release, while never leaving the bounds of the parent scene containing |
299 | the menu. This value was added in Qt 6.3. |
300 | \snippet pointerHandlers/dragReleaseMenu.qml 1 |
301 | \endtable |
302 | |
303 | The \l {Qt Quick Examples - Pointer Handlers} demonstrates some use cases for these. |
304 | |
305 | \note If you find that TapHandler is reacting in cases that conflict with |
306 | some other behavior, the first thing you should try is to think about which |
307 | \c gesturePolicy is appropriate. If you cannot fix it by changing \c gesturePolicy, |
308 | some cases are better served by adjusting \l {PointerHandler::}{grabPermissions}, |
309 | either in this handler, or in another handler that should \e prevent TapHandler |
310 | from reacting. |
311 | */ |
312 | void QQuickTapHandler::setGesturePolicy(QQuickTapHandler::GesturePolicy gesturePolicy) |
313 | { |
314 | if (m_gesturePolicy == gesturePolicy) |
315 | return; |
316 | |
317 | m_gesturePolicy = gesturePolicy; |
318 | emit gesturePolicyChanged(); |
319 | } |
320 | |
321 | /*! |
322 | \qmlproperty enumeration QtQuick::TapHandler::exclusiveSignals |
323 | \since 6.5 |
324 | |
325 | Determines the exclusivity of the singleTapped() and doubleTapped() signals. |
326 | |
327 | \value NotExclusive (the default) singleTapped() and doubleTapped() are |
328 | emitted immediately when the user taps once or twice, respectively. |
329 | |
330 | \value SingleTap singleTapped() is emitted immediately when the user taps |
331 | once, and doubleTapped() is never emitted. |
332 | |
333 | \value DoubleTap doubleTapped() is emitted immediately when the user taps |
334 | twice, and singleTapped() is never emitted. |
335 | |
336 | \value (SingleTap | DoubleTap) Both signals are delayed until |
337 | QStyleHints::mouseDoubleClickInterval(), such that either singleTapped() |
338 | or doubleTapped() can be emitted, but not both. But if 3 or more taps |
339 | occur within \c mouseDoubleClickInterval, neither signal is emitted. |
340 | |
341 | \note The remaining signals such as tapped() and tapCountChanged() are |
342 | always emitted immediately, regardless of this property. |
343 | */ |
344 | void QQuickTapHandler::setExclusiveSignals(QQuickTapHandler::ExclusiveSignals exc) |
345 | { |
346 | if (m_exclusiveSignals == exc) |
347 | return; |
348 | |
349 | m_exclusiveSignals = exc; |
350 | emit exclusiveSignalsChanged(); |
351 | } |
352 | |
353 | /*! |
354 | \qmlproperty bool QtQuick::TapHandler::pressed |
355 | \readonly |
356 | |
357 | Holds true whenever the mouse or touch point is pressed, |
358 | and any movement since the press is compliant with the current |
359 | \l gesturePolicy. When the \l eventPoint is released or the policy is |
360 | violated, \e pressed will change to false. |
361 | */ |
362 | void QQuickTapHandler::setPressed(bool press, bool cancel, QPointerEvent *event, QEventPoint &point) |
363 | { |
364 | if (m_pressed != press) { |
365 | qCDebug(lcTapHandler) << objectName() << "pressed"<< m_pressed << "->"<< press |
366 | << (cancel ? "CANCEL": "") << point << "gp"<< m_gesturePolicy; |
367 | m_pressed = press; |
368 | connectPreRenderSignal(conn: press); |
369 | updateTimeHeld(); |
370 | if (press) { |
371 | if (m_longPressThreshold > 0) |
372 | m_longPressTimer.start(msec: m_longPressThreshold, obj: this); |
373 | m_holdTimer.start(); |
374 | } else { |
375 | m_longPressTimer.stop(); |
376 | m_holdTimer.invalidate(); |
377 | } |
378 | if (press) { |
379 | // on press, grab before emitting changed signals |
380 | if (m_gesturePolicy == DragThreshold) |
381 | setPassiveGrab(event, point, grab: press); |
382 | else |
383 | setExclusiveGrab(ev: event, point, grab: press); |
384 | } |
385 | if (!cancel && !press && parentContains(point)) { |
386 | if (m_longPressed) { |
387 | qCDebug(lcTapHandler) << objectName() << "long press threshold"<< longPressThreshold() << "exceeded:"<< point.timeHeld(); |
388 | } else { |
389 | // Assuming here that pointerEvent()->timestamp() is in ms. |
390 | const quint64 ts = event->timestamp(); |
391 | const quint64 interval = ts - m_lastTapTimestamp; |
392 | const auto distanceSquared = QVector2D(point.scenePosition() - m_lastTapPos).lengthSquared(); |
393 | const auto singleTapReleasedButton = event->isSinglePointEvent() ? static_cast<QSinglePointEvent *>(event)->button() : Qt::NoButton; |
394 | if ((interval < m_multiTapInterval && distanceSquared < |
395 | (event->device()->type() == QInputDevice::DeviceType::Mouse ? |
396 | m_mouseMultiClickDistanceSquared : m_touchMultiTapDistanceSquared)) |
397 | && m_singleTapReleasedButton == singleTapReleasedButton) { |
398 | ++m_tapCount; |
399 | } else { |
400 | m_singleTapReleasedButton = singleTapReleasedButton; |
401 | m_singleTapReleasedPoint = point; |
402 | m_tapCount = 1; |
403 | } |
404 | qCDebug(lcTapHandler) << objectName() << "tapped"<< m_tapCount << "times; interval since last:"<< interval |
405 | << "sec; distance since last:"<< qSqrt(v: distanceSquared); |
406 | auto button = event->isSinglePointEvent() ? static_cast<QSinglePointEvent *>(event)->button() : Qt::NoButton; |
407 | emit tapped(eventPoint: point, button); |
408 | emit tapCountChanged(); |
409 | switch (m_exclusiveSignals) { |
410 | case NotExclusive: |
411 | if (m_tapCount == 1) |
412 | emit singleTapped(eventPoint: point, button); |
413 | else if (m_tapCount == 2) |
414 | emit doubleTapped(eventPoint: point, button); |
415 | break; |
416 | case SingleTap: |
417 | if (m_tapCount == 1) |
418 | emit singleTapped(eventPoint: point, button); |
419 | break; |
420 | case DoubleTap: |
421 | if (m_tapCount == 2) |
422 | emit doubleTapped(eventPoint: point, button); |
423 | break; |
424 | case (SingleTap | DoubleTap): |
425 | if (m_tapCount == 1) { |
426 | qCDebug(lcTapHandler) << objectName() << "waiting to emit singleTapped:"<< m_multiTapInterval << "ms"; |
427 | m_doubleTapTimer.start(msec: m_multiTapInterval, obj: this); |
428 | } |
429 | } |
430 | qCDebug(lcTapHandler) << objectName() << "tap"<< m_tapCount << "after"<< event->timestamp() - m_lastTapTimestamp << "ms"; |
431 | |
432 | m_lastTapTimestamp = ts; |
433 | m_lastTapPos = point.scenePosition(); |
434 | } |
435 | } |
436 | m_longPressed = false; |
437 | emit pressedChanged(); |
438 | if (!press && m_gesturePolicy != DragThreshold) { |
439 | // on release, ungrab after emitting changed signals |
440 | setExclusiveGrab(ev: event, point, grab: press); |
441 | } |
442 | if (cancel) { |
443 | emit canceled(point); |
444 | if (event) |
445 | setExclusiveGrab(ev: event, point, grab: false); |
446 | // In case there is a filtering parent (Flickable), we should not give up the passive grab, |
447 | // so that it can continue to filter future events. |
448 | d_func()->reset(); |
449 | emit pointChanged(); |
450 | } |
451 | } |
452 | } |
453 | |
454 | void QQuickTapHandler::onGrabChanged(QQuickPointerHandler *grabber, QPointingDevice::GrabTransition transition, |
455 | QPointerEvent *ev, QEventPoint &point) |
456 | { |
457 | QQuickSinglePointHandler::onGrabChanged(grabber, transition, event: ev, point); |
458 | bool isCanceled = transition == QPointingDevice::CancelGrabExclusive || transition == QPointingDevice::CancelGrabPassive; |
459 | if (grabber == this && (isCanceled || point.state() == QEventPoint::Released)) |
460 | setPressed(press: false, cancel: isCanceled, event: ev, point); |
461 | } |
462 | |
463 | void QQuickTapHandler::connectPreRenderSignal(bool conn) |
464 | { |
465 | // disconnect pre-existing connection, if any |
466 | disconnect(m_preRenderSignalConnection); |
467 | |
468 | auto par = parentItem(); |
469 | if (!par || !par->window()) |
470 | return; |
471 | |
472 | /* |
473 | Note: beforeSynchronizing is emitted from the SG thread, and the |
474 | timeHeldChanged signal can be used to do arbitrary things in user QML. |
475 | |
476 | But the docs say the GUI thread is blockd, and "Therefore, it is safe |
477 | to access GUI thread thread data in a slot or lambda that is connected |
478 | with Qt::DirectConnection." We use the default AutoConnection just in case. |
479 | */ |
480 | if (conn) { |
481 | m_preRenderSignalConnection = connect(sender: par->window(), signal: &QQuickWindow::beforeSynchronizing, |
482 | context: this, slot: &QQuickTapHandler::updateTimeHeld); |
483 | } |
484 | } |
485 | |
486 | void QQuickTapHandler::updateTimeHeld() |
487 | { |
488 | emit timeHeldChanged(); |
489 | } |
490 | |
491 | /*! |
492 | \qmlproperty int QtQuick::TapHandler::tapCount |
493 | \readonly |
494 | |
495 | The number of taps which have occurred within the time and space |
496 | constraints to be considered a single gesture. The counter is reset to 1 |
497 | if the button changed. For example, to detect a triple-tap, you can write: |
498 | |
499 | \qml |
500 | Rectangle { |
501 | width: 100; height: 30 |
502 | signal tripleTap |
503 | TapHandler { |
504 | acceptedButtons: Qt.AllButtons |
505 | onTapped: if (tapCount == 3) tripleTap() |
506 | } |
507 | } |
508 | \endqml |
509 | */ |
510 | |
511 | /*! |
512 | \qmlproperty real QtQuick::TapHandler::timeHeld |
513 | \readonly |
514 | |
515 | The amount of time in seconds that a pressed point has been held, without |
516 | moving beyond the drag threshold. It will be updated at least once per |
517 | frame rendered, which enables rendering an animation showing the progress |
518 | towards an action which will be triggered by a long-press. It is also |
519 | possible to trigger one of a series of actions depending on how long the |
520 | press is held. |
521 | |
522 | A value of less than zero means no point is being held within this |
523 | handler's \l [QML] Item. |
524 | |
525 | \note If \l gesturePolicy is set to \c TapHandler.DragWithinBounds, |
526 | \c timeHeld does not stop counting even when the pressed point is moved |
527 | beyond the drag threshold, but only when the point leaves the \l {Item::} |
528 | {parent} item's \l {QtQuick::Item::contains()}{bounds}. |
529 | */ |
530 | |
531 | /*! |
532 | \qmlsignal QtQuick::TapHandler::tapped(eventPoint eventPoint, Qt::MouseButton button) |
533 | |
534 | This signal is emitted each time the \c parent Item is tapped. |
535 | |
536 | That is, if you press and release a touchpoint or button within a time |
537 | period less than \l longPressThreshold, while any movement does not exceed |
538 | the drag threshold, then the \c tapped signal will be emitted at the time |
539 | of release. The \a eventPoint signal parameter contains information |
540 | from the release event about the point that was tapped, and \a button |
541 | is the \l {Qt::MouseButton}{mouse button} that was clicked, or \c NoButton |
542 | on a touchscreen. |
543 | |
544 | \snippet pointerHandlers/tapHandlerOnTapped.qml 0 |
545 | */ |
546 | |
547 | /*! |
548 | \qmlsignal QtQuick::TapHandler::singleTapped(eventPoint eventPoint, Qt::MouseButton button) |
549 | \since 5.11 |
550 | |
551 | This signal is emitted when the \c parent Item is tapped once. |
552 | After an amount of time greater than QStyleHints::mouseDoubleClickInterval, |
553 | it can be tapped again; but if the time until the next tap is less, |
554 | \l tapCount will increase. The \a eventPoint signal parameter contains |
555 | information from the release event about the point that was tapped, and |
556 | \a button is the \l {Qt::MouseButton}{mouse button} that was clicked, or |
557 | \c NoButton on a touchscreen. |
558 | */ |
559 | |
560 | /*! |
561 | \qmlsignal QtQuick::TapHandler::doubleTapped(eventPoint eventPoint, Qt::MouseButton button) |
562 | \since 5.11 |
563 | |
564 | This signal is emitted when the \c parent Item is tapped twice within a |
565 | short span of time (QStyleHints::mouseDoubleClickInterval()) and distance |
566 | (QStyleHints::mouseDoubleClickDistance() or |
567 | QStyleHints::touchDoubleTapDistance()). This signal always occurs after |
568 | \l singleTapped, \l tapped, and \l tapCountChanged. The \a eventPoint |
569 | signal parameter contains information from the release event about the |
570 | point that was tapped, and \a button is the |
571 | \l {Qt::MouseButton}{mouse button} that was clicked, or \c NoButton |
572 | on a touchscreen. |
573 | */ |
574 | |
575 | /*! |
576 | \qmlsignal QtQuick::TapHandler::longPressed() |
577 | |
578 | This signal is emitted when the \c parent Item is pressed and held for a |
579 | time period greater than \l longPressThreshold. That is, if you press and |
580 | hold a touchpoint or button, while any movement does not exceed the drag |
581 | threshold, then the \c longPressed signal will be emitted at the time that |
582 | \l timeHeld exceeds \l longPressThreshold. |
583 | */ |
584 | |
585 | /*! |
586 | \qmlsignal QtQuick::TapHandler::tapCountChanged() |
587 | |
588 | This signal is emitted when the \c parent Item is tapped once or more (within |
589 | a specified time and distance span) and when the present \c tapCount differs |
590 | from the previous \c tapCount. |
591 | */ |
592 | QT_END_NAMESPACE |
593 | |
594 | #include "moc_qquicktaphandler_p.cpp" |
595 |
Definitions
Learn Advanced QML with KDAB
Find out more