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
12QT_BEGIN_NAMESPACE
13
14Q_STATIC_LOGGING_CATEGORY(lcTapHandler, "qt.quick.handler.tap")
15
16quint64 QQuickTapHandler::m_multiTapInterval(0);
17// single tap distance is the same as the drag threshold
18int QQuickTapHandler::m_mouseMultiClickDistanceSquared(-1);
19int 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
56QQuickTapHandler::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
69bool 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
124void 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*/
162qreal QQuickTapHandler::longPressThreshold() const
163{
164 return m_longPressThreshold / qreal(1000);
165}
166
167void 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
181void 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
191void 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*/
312void 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*/
344void 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*/
362void 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 if (event) {
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
454void QQuickTapHandler::onGrabChanged(QQuickPointerHandler *grabber, QPointingDevice::GrabTransition transition,
455 QPointerEvent *ev, QEventPoint &point)
456{
457 // QQuickPointerHandler::onGrabChanged() calls setActive(false) in many cases.
458 QQuickSinglePointHandler::onGrabChanged(grabber, transition, event: ev, point);
459 // We don't override onActiveChanged(): we could not call setPressed(false) from there anyway.
460 // But ensure that if the TapHandler just got deactivated, it's no longer pressed either.
461 const bool isCanceled = transition == QPointingDevice::CancelGrabExclusive || transition == QPointingDevice::CancelGrabPassive;
462 // But passive grab/ungrab does not change the active state, so that's not a reason to change pressed state either
463 // (i.e. when gesturePolicy == DragThreshold, TapHandler does not become active).
464 const bool passiveGrab = transition == QPointingDevice::GrabPassive || transition == QPointingDevice::UngrabPassive;
465 if (grabber == this && (isCanceled || point.state() == QEventPoint::Released || (!active() && !passiveGrab)))
466 setPressed(press: false, cancel: isCanceled, event: ev, point);
467}
468
469void QQuickTapHandler::connectPreRenderSignal(bool conn)
470{
471 // disconnect pre-existing connection, if any
472 disconnect(m_preRenderSignalConnection);
473
474 auto par = parentItem();
475 if (!par || !par->window())
476 return;
477
478 /*
479 Note: beforeSynchronizing is emitted from the SG thread, and the
480 timeHeldChanged signal can be used to do arbitrary things in user QML.
481
482 But the docs say the GUI thread is blockd, and "Therefore, it is safe
483 to access GUI thread thread data in a slot or lambda that is connected
484 with Qt::DirectConnection." We use the default AutoConnection just in case.
485 */
486 if (conn) {
487 m_preRenderSignalConnection = connect(sender: par->window(), signal: &QQuickWindow::beforeSynchronizing,
488 context: this, slot: &QQuickTapHandler::updateTimeHeld);
489 }
490}
491
492void QQuickTapHandler::updateTimeHeld()
493{
494 emit timeHeldChanged();
495}
496
497/*!
498 \qmlproperty int QtQuick::TapHandler::tapCount
499 \readonly
500
501 The number of taps which have occurred within the time and space
502 constraints to be considered a single gesture. The counter is reset to 1
503 if the button changed. For example, to detect a triple-tap, you can write:
504
505 \qml
506 Rectangle {
507 width: 100; height: 30
508 signal tripleTap
509 TapHandler {
510 acceptedButtons: Qt.AllButtons
511 onTapped: if (tapCount == 3) tripleTap()
512 }
513 }
514 \endqml
515*/
516
517/*!
518 \qmlproperty real QtQuick::TapHandler::timeHeld
519 \readonly
520
521 The amount of time in seconds that a pressed point has been held, without
522 moving beyond the drag threshold. It will be updated at least once per
523 frame rendered, which enables rendering an animation showing the progress
524 towards an action which will be triggered by a long-press. It is also
525 possible to trigger one of a series of actions depending on how long the
526 press is held.
527
528 A value of less than zero means no point is being held within this
529 handler's \l [QML] Item.
530
531 \note If \l gesturePolicy is set to \c TapHandler.DragWithinBounds,
532 \c timeHeld does not stop counting even when the pressed point is moved
533 beyond the drag threshold, but only when the point leaves the \l {Item::}
534 {parent} item's \l {QtQuick::Item::contains()}{bounds}.
535*/
536
537/*!
538 \qmlsignal QtQuick::TapHandler::tapped(eventPoint eventPoint, Qt::MouseButton button)
539
540 This signal is emitted each time the \c parent Item is tapped.
541
542 That is, if you press and release a touchpoint or button within a time
543 period less than \l longPressThreshold, while any movement does not exceed
544 the drag threshold, then the \c tapped signal will be emitted at the time
545 of release. The \a eventPoint signal parameter contains information
546 from the release event about the point that was tapped, and \a button
547 is the \l {Qt::MouseButton}{mouse button} that was clicked, or \c NoButton
548 on a touchscreen.
549
550 \snippet pointerHandlers/tapHandlerOnTapped.qml 0
551*/
552
553/*!
554 \qmlsignal QtQuick::TapHandler::singleTapped(eventPoint eventPoint, Qt::MouseButton button)
555 \since 5.11
556
557 This signal is emitted when the \c parent Item is tapped once.
558 After an amount of time greater than QStyleHints::mouseDoubleClickInterval,
559 it can be tapped again; but if the time until the next tap is less,
560 \l tapCount will increase. The \a eventPoint signal parameter contains
561 information from the release event about the point that was tapped, and
562 \a button is the \l {Qt::MouseButton}{mouse button} that was clicked, or
563 \c NoButton on a touchscreen.
564*/
565
566/*!
567 \qmlsignal QtQuick::TapHandler::doubleTapped(eventPoint eventPoint, Qt::MouseButton button)
568 \since 5.11
569
570 This signal is emitted when the \c parent Item is tapped twice within a
571 short span of time (QStyleHints::mouseDoubleClickInterval()) and distance
572 (QStyleHints::mouseDoubleClickDistance() or
573 QStyleHints::touchDoubleTapDistance()). This signal always occurs after
574 \l singleTapped, \l tapped, and \l tapCountChanged. The \a eventPoint
575 signal parameter contains information from the release event about the
576 point that was tapped, and \a button is the
577 \l {Qt::MouseButton}{mouse button} that was clicked, or \c NoButton
578 on a touchscreen.
579*/
580
581/*!
582 \qmlsignal QtQuick::TapHandler::longPressed()
583
584 This signal is emitted when the \c parent Item is pressed and held for a
585 time period greater than \l longPressThreshold. That is, if you press and
586 hold a touchpoint or button, while any movement does not exceed the drag
587 threshold, then the \c longPressed signal will be emitted at the time that
588 \l timeHeld exceeds \l longPressThreshold.
589*/
590
591/*!
592 \qmlsignal QtQuick::TapHandler::tapCountChanged()
593
594 This signal is emitted when the \c parent Item is tapped once or more (within
595 a specified time and distance span) and when the present \c tapCount differs
596 from the previous \c tapCount.
597*/
598QT_END_NAMESPACE
599
600#include "moc_qquicktaphandler_p.cpp"
601

source code of qtdeclarative/src/quick/handlers/qquicktaphandler.cpp