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 | |
9 | QT_BEGIN_NAMESPACE |
10 | |
11 | Q_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 | |
39 | class QQuickHoverHandlerPrivate : public QQuickSinglePointHandlerPrivate |
40 | { |
41 | Q_DECLARE_PUBLIC(QQuickHoverHandler) |
42 | |
43 | public: |
44 | void onEnabledChanged() override; |
45 | void onParentChanged(QQuickItem *oldParent, QQuickItem *newParent) override; |
46 | |
47 | void updateHasHoverInChild(QQuickItem *item, bool hasHover); |
48 | }; |
49 | |
50 | void 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 | |
62 | void 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 | |
72 | void 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 | |
82 | QQuickHoverHandler::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 | |
92 | QQuickHoverHandler::~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 | */ |
106 | void QQuickHoverHandler::setBlocking(bool blocking) |
107 | { |
108 | if (m_blocking == blocking) |
109 | return; |
110 | |
111 | m_blocking = blocking; |
112 | emit blockingChanged(); |
113 | } |
114 | |
115 | bool 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 | |
131 | void 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 | |
142 | bool 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 | |
171 | void 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 | */ |
190 | void 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 | |
348 | QT_END_NAMESPACE |
349 | |
350 | #include "moc_qquickhoverhandler_p.cpp" |
351 | |