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 "qquickhoverhandler_p.h"
5#include <private/qquicksinglepointhandler_p_p.h>
6#include <private/qquickdeliveryagent_p.h>
7#include <private/qquickitem_p.h>
8
9QT_BEGIN_NAMESPACE
10
11Q_LOGGING_CATEGORY(lcHoverHandler, "qt.quick.handler.hover")
12
13/*!
14 \qmltype HoverHandler
15 \instantiates QQuickHoverHandler
16 \inherits SinglePointHandler
17 \inqmlmodule QtQuick
18 \ingroup qtquick-input-handlers
19 \brief Handler for mouse and tablet hover.
20
21 HoverHandler detects a hovering mouse or tablet stylus cursor.
22
23 A binding to the \l hovered property is the easiest way to react when the
24 cursor enters or leaves the \l {PointerHandler::parent}{parent} Item.
25 The \l {SinglePointHandler::point}{point} property provides more detail,
26 including the cursor position. The
27 \l {PointerDeviceHandler::acceptedDevices}{acceptedDevices},
28 \l {PointerDeviceHandler::acceptedPointerTypes}{acceptedPointerTypes},
29 and \l {PointerDeviceHandler::acceptedModifiers}{acceptedModifiers}
30 properties can be used to narrow the behavior to detect hovering of
31 specific kinds of devices or while holding a modifier key.
32
33 The \l cursorShape property allows changing the cursor whenever
34 \l hovered changes to \c true.
35
36 \sa MouseArea, PointHandler, {Qt Quick Examples - Pointer Handlers}
37*/
38
39class QQuickHoverHandlerPrivate : public QQuickSinglePointHandlerPrivate
40{
41 Q_DECLARE_PUBLIC(QQuickHoverHandler)
42
43public:
44 void onEnabledChanged() override;
45 void onParentChanged(QQuickItem *oldParent, QQuickItem *newParent) override;
46
47 void updateHasHoverInChild(QQuickItem *item, bool hasHover);
48};
49
50void QQuickHoverHandlerPrivate::onEnabledChanged()
51{
52 Q_Q(QQuickHoverHandler);
53
54 if (auto parent = q->parentItem())
55 updateHasHoverInChild(item: parent, hasHover: enabled);
56 if (!enabled)
57 q->setHovered(false);
58
59 QQuickSinglePointHandlerPrivate::onEnabledChanged();
60}
61
62void QQuickHoverHandlerPrivate::onParentChanged(QQuickItem *oldParent, QQuickItem *newParent)
63{
64 if (oldParent)
65 updateHasHoverInChild(item: oldParent, hasHover: false);
66 if (newParent)
67 updateHasHoverInChild(item: newParent, hasHover: true);
68
69 QQuickSinglePointHandlerPrivate::onParentChanged(oldParent, newParent);
70}
71
72void QQuickHoverHandlerPrivate::updateHasHoverInChild(QQuickItem *item, bool hasHover)
73{
74 QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(item);
75 itemPriv->setHasHoverInChild(hasHover);
76 // The DA needs to resolve which items and handlers should now be hovered or unhovered.
77 // Marking the parent item dirty ensures that flushFrameSynchronousEvents() will be called from the render loop,
78 // even if this change is not in response to a mouse event and no item has already marked itself dirty.
79 itemPriv->dirty(QQuickItemPrivate::Content);
80}
81
82QQuickHoverHandler::QQuickHoverHandler(QQuickItem *parent)
83 : QQuickSinglePointHandler(*(new QQuickHoverHandlerPrivate), parent)
84{
85 Q_D(QQuickHoverHandler);
86 // Tell QQuickPointerDeviceHandler::wantsPointerEvent() to ignore button state
87 d->acceptedButtons = Qt::NoButton;
88 if (parent)
89 d->updateHasHoverInChild(item: parent, hasHover: true);
90}
91
92QQuickHoverHandler::~QQuickHoverHandler()
93{
94 Q_D(QQuickHoverHandler);
95 if (auto parent = parentItem())
96 d->updateHasHoverInChild(item: parent, hasHover: false);
97}
98
99/*!
100 \qmlproperty bool QtQuick::HoverHandler::blocking
101 \since 6.3
102
103 Whether this handler prevents other items or handlers behind it from
104 being hovered at the same time. This property is \c false by default.
105*/
106void QQuickHoverHandler::setBlocking(bool blocking)
107{
108 if (m_blocking == blocking)
109 return;
110
111 m_blocking = blocking;
112 emit blockingChanged();
113}
114
115bool QQuickHoverHandler::event(QEvent *event)
116{
117 switch (event->type())
118 {
119 case QEvent::HoverLeave:
120 setHovered(false);
121 setActive(false);
122 break;
123 default:
124 return QQuickSinglePointHandler::event(event);
125 break;
126 }
127
128 return true;
129}
130
131void QQuickHoverHandler::componentComplete()
132{
133 Q_D(QQuickHoverHandler);
134 QQuickSinglePointHandler::componentComplete();
135
136 if (d->enabled) {
137 if (auto parent = parentItem())
138 d->updateHasHoverInChild(item: parent, hasHover: true);
139 }
140}
141
142bool QQuickHoverHandler::wantsPointerEvent(QPointerEvent *event)
143{
144 // No state change should occur if a button is being pressed or released.
145 if (event->isSinglePointEvent() && static_cast<QSinglePointEvent *>(event)->button())
146 return false;
147 auto &point = event->point(i: 0);
148 if (QQuickPointerDeviceHandler::wantsPointerEvent(event) && wantsEventPoint(event, point) && parentContains(point)) {
149 // assume this is a mouse or tablet event, so there's only one point
150 setPointId(point.id());
151 return true;
152 }
153
154 // Some hover events come from QQuickWindow::tabletEvent(). In between,
155 // some hover events come from QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(),
156 // but those look like mouse events. If a particular HoverHandler instance
157 // is filtering for tablet events only (e.g. by setting
158 // acceptedDevices:PointerDevice.Stylus), those events should not cause
159 // the hovered property to transition to false prematurely.
160 // If a QQuickPointerTabletEvent caused the hovered property to become true,
161 // then only another QQuickPointerTabletEvent can make it become false.
162 // But after kCursorOverrideTimeout ms, QQuickItemPrivate::effectiveCursorHandler()
163 // will ignore it, just in case there is no QQuickPointerTabletEvent to unset it.
164 // For example, a tablet proximity leave event could occur, but we don't deliver it to the window.
165 if (!(m_hoveredTablet && QQuickDeliveryAgentPrivate::isMouseEvent(ev: event)))
166 setHovered(false);
167
168 return false;
169}
170
171void QQuickHoverHandler::handleEventPoint(QPointerEvent *ev, QEventPoint &point)
172{
173 bool hovered = true;
174 if (point.state() == QEventPoint::Released &&
175 ev->pointingDevice()->pointerType() == QPointingDevice::PointerType::Finger)
176 hovered = false;
177 else if (QQuickDeliveryAgentPrivate::isTabletEvent(ev))
178 m_hoveredTablet = true;
179 setHovered(hovered);
180}
181
182/*!
183 \qmlproperty bool QtQuick::HoverHandler::hovered
184 \readonly
185
186 Holds true whenever any pointing device cursor (mouse or tablet) is within
187 the bounds of the \c parent Item, extended by the
188 \l {PointerHandler::margin}{margin}, if any.
189*/
190void QQuickHoverHandler::setHovered(bool hovered)
191{
192 if (m_hovered != hovered) {
193 qCDebug(lcHoverHandler) << objectName() << "hovered" << m_hovered << "->" << hovered;
194 m_hovered = hovered;
195 if (!hovered)
196 m_hoveredTablet = false;
197 emit hoveredChanged();
198 }
199}
200
201/*!
202 \internal
203 \qmlproperty flags QtQuick::HoverHandler::acceptedButtons
204
205 This property is not used in HoverHandler.
206*/
207
208/*!
209 \qmlproperty flags QtQuick::HoverHandler::acceptedDevices
210
211 The types of pointing devices that can activate the pointer handler.
212
213 By default, this property is set to
214 \l{QInputDevice::DeviceType}{PointerDevice.AllDevices}.
215 If you set it to an OR combination of device types, it will ignore pointer
216 events from the non-matching devices.
217
218 For example, an item could be made to respond to mouse hover in one way,
219 and stylus hover in another way, with two handlers:
220
221 \snippet pointerHandlers/hoverMouseOrStylus.qml 0
222
223 The available device types are as follows:
224
225 \value PointerDevice.Mouse A mouse.
226 \value PointerDevice.TouchScreen A touchscreen.
227 \value PointerDevice.TouchPad A touchpad or trackpad.
228 \value PointerDevice.Stylus A stylus on a graphics tablet.
229 \value PointerDevice.Airbrush An airbrush on a graphics tablet.
230 \value PointerDevice.Puck A digitizer with crosshairs, on a graphics tablet.
231 \value PointerDevice.AllDevices Any type of pointing device.
232
233 \sa QInputDevice::DeviceType
234*/
235
236/*!
237 \qmlproperty flags QtQuick::HoverHandler::acceptedPointerTypes
238
239 The types of pointing instruments (generic, stylus, eraser, and so on)
240 that can activate the pointer handler.
241
242 By default, this property is set to
243 \l {QPointingDevice::PointerType} {PointerDevice.AllPointerTypes}.
244 If you set it to an OR combination of device types, it will ignore events
245 from non-matching events.
246
247 For example, you could provide feedback by changing the cursor depending on
248 whether a stylus or eraser is hovering over a graphics tablet:
249
250 \snippet pointerHandlers/hoverStylusOrEraser.qml 0
251
252 The available pointer types are as follows:
253
254 \value PointerDevice.Generic A mouse or a device that emulates a mouse.
255 \value PointerDevice.Finger A finger on a touchscreen (hover detection is unlikely).
256 \value PointerDevice.Pen A stylus on a graphics tablet.
257 \value PointerDevice.Eraser An eraser on a graphics tablet.
258 \value PointerDevice.Cursor A digitizer with crosshairs, on a graphics tablet.
259 \value PointerDevice.AllPointerTypes Any type of pointing device.
260
261 \sa QPointingDevice::PointerType
262*/
263
264/*!
265 \qmlproperty flags QtQuick::HoverHandler::acceptedModifiers
266
267 If this property is set, a hover event is handled only if the given keyboard
268 modifiers are pressed. The event is ignored without the modifiers.
269
270 This property is set to \c Qt.KeyboardModifierMask by default, resulting
271 in handling hover events regardless of any modifier keys.
272
273 For example, an \l[QML]{Item} could have two handlers of the same type, one
274 of which is enabled only if the required keyboard modifiers are pressed:
275
276 \snippet pointerHandlers/hoverModifiers.qml 0
277
278 The available modifiers are as follows:
279
280 \value Qt.NoModifier No modifier key is allowed.
281 \value Qt.ShiftModifier A Shift key on the keyboard must be pressed.
282 \value Qt.ControlModifier A Ctrl key on the keyboard must be pressed.
283 \value Qt.AltModifier An Alt key on the keyboard must be pressed.
284 \value Qt.MetaModifier A Meta key on the keyboard must be pressed.
285 \value Qt.KeypadModifier A keypad button must be pressed.
286 \value Qt.GroupSwitchModifier A Mode_switch key on the keyboard must be pressed.
287 X11 only (unless activated on Windows by a command line argument).
288 \value Qt.KeyboardModifierMask The handler ignores modifier keys.
289
290 \sa Qt::KeyboardModifier
291*/
292
293/*!
294 \since 5.15
295 \qmlproperty Qt::CursorShape QtQuick::HoverHandler::cursorShape
296 This property holds the cursor shape that will appear whenever
297 \l hovered is \c true and no other handler is overriding it.
298
299 The available cursor shapes are:
300 \list
301 \li Qt.ArrowCursor
302 \li Qt.UpArrowCursor
303 \li Qt.CrossCursor
304 \li Qt.WaitCursor
305 \li Qt.IBeamCursor
306 \li Qt.SizeVerCursor
307 \li Qt.SizeHorCursor
308 \li Qt.SizeBDiagCursor
309 \li Qt.SizeFDiagCursor
310 \li Qt.SizeAllCursor
311 \li Qt.BlankCursor
312 \li Qt.SplitVCursor
313 \li Qt.SplitHCursor
314 \li Qt.PointingHandCursor
315 \li Qt.ForbiddenCursor
316 \li Qt.WhatsThisCursor
317 \li Qt.BusyCursor
318 \li Qt.OpenHandCursor
319 \li Qt.ClosedHandCursor
320 \li Qt.DragCopyCursor
321 \li Qt.DragMoveCursor
322 \li Qt.DragLinkCursor
323 \endlist
324
325 The default value of this property is not set, which allows any active
326 handler on the same \e parent item to determine the cursor shape.
327 This property can be reset to the initial condition by setting it to
328 \c undefined.
329
330 If any handler with defined \c cursorShape is
331 \l {PointerHandler::active}{active}, that cursor will appear.
332 Else if the HoverHandler has a defined \c cursorShape, that cursor will appear.
333 Otherwise, the \l {QQuickItem::cursor()}{cursor} of \e parent item will appear.
334
335 \note When this property has not been set, or has been set to \c undefined,
336 if you read the value it will return \c Qt.ArrowCursor.
337
338 \sa Qt::CursorShape, QQuickItem::cursor()
339*/
340
341/*!
342 \internal
343 \qmlproperty flags HoverHandler::dragThreshold
344
345 This property is not used in HoverHandler.
346*/
347
348QT_END_NAMESPACE
349
350#include "moc_qquickhoverhandler_p.cpp"
351

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