Kirigami2

wheelhandler.h
1/* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
2 * SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
3 * SPDX-License-Identifier: LGPL-2.0-or-later
4 */
5
6#pragma once
7
8#include <QGuiApplication>
9#include <QObject>
10#include <QPoint>
11#include <QPropertyAnimation>
12#include <QQmlParserStatus>
13#include <QQuickItem>
14#include <QStyleHints>
15#include <QTimer>
16
17#include "platform/settings.h"
18#include "platform/units.h"
19
20class QWheelEvent;
21class QQmlEngine;
22class WheelHandler;
23
24/**
25 * Describes the mouse wheel event
26 */
27class KirigamiWheelEvent : public QObject
28{
30 QML_NAMED_ELEMENT(WheelEvent)
31 QML_UNCREATABLE("")
32
33 /**
34 * x: real
35 *
36 * X coordinate of the mouse pointer
37 */
38 Q_PROPERTY(qreal x READ x CONSTANT FINAL)
39
40 /**
41 * y: real
42 *
43 * Y coordinate of the mouse pointer
44 */
45 Q_PROPERTY(qreal y READ y CONSTANT FINAL)
46
47 /**
48 * angleDelta: point
49 *
50 * The distance the wheel is rotated in degrees.
51 * The x and y coordinates indicate the horizontal and vertical wheels respectively.
52 * A positive value indicates it was rotated up/right, negative, bottom/left
53 * This value is more likely to be set in traditional mice.
54 */
56
57 /**
58 * pixelDelta: point
59 *
60 * provides the delta in screen pixels available on high resolution trackpads
61 */
63
64 /**
65 * buttons: int
66 *
67 * it contains an OR combination of the buttons that were pressed during the wheel, they can be:
68 * Qt.LeftButton, Qt.MiddleButton, Qt.RightButton
69 */
70 Q_PROPERTY(int buttons READ buttons CONSTANT FINAL)
71
72 /**
73 * modifiers: int
74 *
75 * Keyboard mobifiers that were pressed during the wheel event, such as:
76 * Qt.NoModifier (default, no modifiers)
77 * Qt.ControlModifier
78 * Qt.ShiftModifier
79 * ...
80 */
81 Q_PROPERTY(int modifiers READ modifiers CONSTANT FINAL)
82
83 /**
84 * inverted: bool
85 *
86 * Whether the delta values are inverted
87 * On some platformsthe returned delta are inverted, so positive values would mean bottom/left
88 */
89 Q_PROPERTY(bool inverted READ inverted CONSTANT FINAL)
90
91 /**
92 * accepted: bool
93 *
94 * If set, the event shouldn't be managed anymore,
95 * for instance it can be used to block the handler to manage the scroll of a view on some scenarios
96 * @code
97 * // This handler handles automatically the scroll of
98 * // flickableItem, unless Ctrl is pressed, in this case the
99 * // app has custom code to handle Ctrl+wheel zooming
100 * Kirigami.WheelHandler {
101 * target: flickableItem
102 * blockTargetWheel: true
103 * scrollFlickableTarget: true
104 * onWheel: {
105 * if (wheel.modifiers & Qt.ControlModifier) {
106 * wheel.accepted = true;
107 * // Handle scaling of the view
108 * }
109 * }
110 * }
111 * @endcode
112 *
113 */
114 Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted FINAL)
115
116public:
117 KirigamiWheelEvent(QObject *parent = nullptr);
118 ~KirigamiWheelEvent() override;
119
120 void initializeFromEvent(QWheelEvent *event);
121
122 qreal x() const;
123 qreal y() const;
124 QPointF angleDelta() const;
125 QPointF pixelDelta() const;
126 int buttons() const;
127 int modifiers() const;
128 bool inverted() const;
129 bool isAccepted();
130 void setAccepted(bool accepted);
131
132private:
133 qreal m_x = 0;
134 qreal m_y = 0;
135 QPointF m_angleDelta;
136 QPointF m_pixelDelta;
137 Qt::MouseButtons m_buttons = Qt::NoButton;
138 Qt::KeyboardModifiers m_modifiers = Qt::NoModifier;
139 bool m_inverted = false;
140 bool m_accepted = false;
141};
142
143class WheelFilterItem : public QQuickItem
144{
146public:
147 WheelFilterItem(QQuickItem *parent = nullptr);
148};
149
150/**
151 * @brief Handles scrolling for a Flickable and 2 attached ScrollBars.
152 *
153 * WheelHandler filters events from a Flickable, a vertical ScrollBar and a horizontal ScrollBar.
154 * Wheel and KeyPress events (when `keyNavigationEnabled` is true) are used to scroll the Flickable.
155 * When `filterMouseEvents` is true, WheelHandler blocks mouse button input from reaching the Flickable
156 * and sets the `interactive` property of the scrollbars to false when touch input is used.
157 *
158 * Wheel event handling behavior:
159 *
160 * - Pixel delta is ignored unless angle delta is not available because pixel delta scrolling is too slow. Qt Widgets doesn't use pixel delta either, so the
161 * default scroll speed should be consistent with Qt Widgets.
162 * - When using angle delta, scroll using the step increments defined by `verticalStepSize` and `horizontalStepSize`.
163 * - When one of the keyboard modifiers in `pageScrollModifiers` is used, scroll by pages.
164 * - When using a device that doesn't use 120 angle delta unit increments such as a touchpad, the `verticalStepSize`, `horizontalStepSize` and page increments
165 * (if using page scrolling) will be multiplied by `angle delta / 120` to keep scrolling smooth.
166 * - If scrolling has happened in the last 400ms, use an internal QQuickItem stacked over the Flickable's contentItem to catch wheel events and use those wheel
167 * events to scroll, if possible. This prevents controls inside the Flickable's contentItem that allow scrolling to change the value (e.g., Sliders, SpinBoxes)
168 * from conflicting with scrolling the page.
169 *
170 * Common usage with a Flickable:
171 *
172 * @include wheelhandler/FlickableUsage.qml
173 *
174 * Common usage inside of a ScrollView template:
175 *
176 * @include wheelhandler/ScrollViewUsage.qml
177 *
178 */
179class WheelHandler : public QObject, public QQmlParserStatus
180{
183 QML_ELEMENT
184
185 /**
186 * @brief This property holds the Qt Quick Flickable that the WheelHandler will control.
187 */
188 Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged FINAL)
189
190 /**
191 * @brief This property holds the vertical step size.
192 *
193 * The default value is equivalent to `20 * Qt.styleHints.wheelScrollLines`. This is consistent with the default increment for QScrollArea.
194 *
195 * @sa horizontalStepSize
196 *
197 * @since KDE Frameworks 5.89
198 */
199 Q_PROPERTY(qreal verticalStepSize READ verticalStepSize WRITE setVerticalStepSize RESET resetVerticalStepSize NOTIFY verticalStepSizeChanged FINAL)
200
201 /**
202 * @brief This property holds the horizontal step size.
203 *
204 * The default value is equivalent to `20 * Qt.styleHints.wheelScrollLines`. This is consistent with the default increment for QScrollArea.
205 *
206 * @sa verticalStepSize
207 *
208 * @since KDE Frameworks 5.89
209 */
211 qreal horizontalStepSize READ horizontalStepSize WRITE setHorizontalStepSize RESET resetHorizontalStepSize NOTIFY horizontalStepSizeChanged FINAL)
212
213 /**
214 * @brief This property holds the keyboard modifiers that will be used to start page scrolling.
215 *
216 * The default value is equivalent to `Qt.ControlModifier | Qt.ShiftModifier`. This matches QScrollBar, which uses QAbstractSlider behavior.
217 *
218 * @since KDE Frameworks 5.89
219 */
220 Q_PROPERTY(Qt::KeyboardModifiers pageScrollModifiers READ pageScrollModifiers WRITE setPageScrollModifiers RESET resetPageScrollModifiers NOTIFY
221 pageScrollModifiersChanged FINAL)
222
223 /**
224 * @brief This property holds whether the WheelHandler filters mouse events like a Qt Quick Controls ScrollView would.
225 *
226 * Touch events are allowed to flick the view and they make the scrollbars not interactive.
227 *
228 * Mouse events are not allowed to flick the view and they make the scrollbars interactive.
229 *
230 * Hover events on the scrollbars and wheel events on anything also make the scrollbars interactive when this property is set to true.
231 *
232 * The default value is `false`.
233 *
234 * @since KDE Frameworks 5.89
235 */
236 Q_PROPERTY(bool filterMouseEvents READ filterMouseEvents WRITE setFilterMouseEvents NOTIFY filterMouseEventsChanged FINAL)
237
238 /**
239 * @brief This property holds whether the WheelHandler handles keyboard scrolling.
240 *
241 * - Left arrow scrolls a step to the left.
242 * - Right arrow scrolls a step to the right.
243 * - Up arrow scrolls a step upwards.
244 * - Down arrow scrolls a step downwards.
245 * - PageUp scrolls to the previous page.
246 * - PageDown scrolls to the next page.
247 * - Home scrolls to the beginning.
248 * - End scrolls to the end.
249 * - When Alt is held, scroll horizontally when using PageUp, PageDown, Home or End.
250 *
251 * The default value is `false`.
252 *
253 * @since KDE Frameworks 5.89
254 */
255 Q_PROPERTY(bool keyNavigationEnabled READ keyNavigationEnabled WRITE setKeyNavigationEnabled NOTIFY keyNavigationEnabledChanged FINAL)
256
257 /**
258 * @brief This property holds whether the WheelHandler blocks all wheel events from reaching the Flickable.
259 *
260 * When this property is false, scrolling the Flickable with WheelHandler will only block an event from reaching the Flickable if the Flickable is actually
261 * scrolled by WheelHandler.
262 *
263 * NOTE: Wheel events created by touchpad gestures with pixel deltas will always be accepted no matter what. This is because they will cause the Flickable
264 * to jump back to where scrolling started unless the events are always accepted before they reach the Flickable.
265 *
266 * The default value is true.
267 */
268 Q_PROPERTY(bool blockTargetWheel MEMBER m_blockTargetWheel NOTIFY blockTargetWheelChanged FINAL)
269
270 /**
271 * @brief This property holds whether the WheelHandler can use wheel events to scroll the Flickable.
272 *
273 * The default value is true.
274 */
275 Q_PROPERTY(bool scrollFlickableTarget MEMBER m_scrollFlickableTarget NOTIFY scrollFlickableTargetChanged FINAL)
276
277public:
278 explicit WheelHandler(QObject *parent = nullptr);
279 ~WheelHandler() override;
280
281 QQuickItem *target() const;
282 void setTarget(QQuickItem *target);
283
284 qreal verticalStepSize() const;
285 void setVerticalStepSize(qreal stepSize);
286 void resetVerticalStepSize();
287
288 qreal horizontalStepSize() const;
289 void setHorizontalStepSize(qreal stepSize);
290 void resetHorizontalStepSize();
291
292 Qt::KeyboardModifiers pageScrollModifiers() const;
293 void setPageScrollModifiers(Qt::KeyboardModifiers modifiers);
294 void resetPageScrollModifiers();
295
296 bool filterMouseEvents() const;
297 void setFilterMouseEvents(bool enabled);
298
299 bool keyNavigationEnabled() const;
300 void setKeyNavigationEnabled(bool enabled);
301
302 /**
303 * Scroll up one step. If the stepSize parameter is less than 0, the verticalStepSize will be used.
304 *
305 * returns true if the contentItem was moved.
306 *
307 * @since KDE Frameworks 5.89
308 */
309 Q_INVOKABLE bool scrollUp(qreal stepSize = -1);
310
311 /**
312 * Scroll down one step. If the stepSize parameter is less than 0, the verticalStepSize will be used.
313 *
314 * returns true if the contentItem was moved.
315 *
316 * @since KDE Frameworks 5.89
317 */
318 Q_INVOKABLE bool scrollDown(qreal stepSize = -1);
319
320 /**
321 * Scroll left one step. If the stepSize parameter is less than 0, the horizontalStepSize will be used.
322 *
323 * returns true if the contentItem was moved.
324 *
325 * @since KDE Frameworks 5.89
326 */
327 Q_INVOKABLE bool scrollLeft(qreal stepSize = -1);
328
329 /**
330 * Scroll right one step. If the stepSize parameter is less than 0, the horizontalStepSize will be used.
331 *
332 * returns true if the contentItem was moved.
333 *
334 * @since KDE Frameworks 5.89
335 */
336 Q_INVOKABLE bool scrollRight(qreal stepSize = -1);
337
339 void targetChanged();
340 void verticalStepSizeChanged();
341 void horizontalStepSizeChanged();
342 void pageScrollModifiersChanged();
343 void filterMouseEventsChanged();
344 void keyNavigationEnabledChanged();
345 void blockTargetWheelChanged();
346 void scrollFlickableTargetChanged();
347
348 /**
349 * @brief This signal is emitted when a wheel event reaches the event filter, just before scrolling is handled.
350 *
351 * Accepting the wheel event in the `onWheel` signal handler prevents scrolling from happening.
352 */
354
355protected:
356 bool eventFilter(QObject *watched, QEvent *event) override;
357
358private Q_SLOTS:
359 void _k_rebindScrollBars();
360
361private:
362 void classBegin() override;
363 void componentComplete() override;
364 void initSmoothScrollDuration();
365
366 void setScrolling(bool scrolling);
367 bool scrollFlickable(QPointF pixelDelta, QPointF angleDelta = {}, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
368
369 Kirigami::Platform::Units *m_units = nullptr;
370 Kirigami::Platform::Settings *m_settings = nullptr;
371 QPointer<QQuickItem> m_flickable;
372 QPointer<QQuickItem> m_verticalScrollBar;
373 QPointer<QQuickItem> m_horizontalScrollBar;
374 QMetaObject::Connection m_verticalChangedConnection;
375 QMetaObject::Connection m_horizontalChangedConnection;
376 QPointer<QQuickItem> m_filterItem;
377 // Matches QScrollArea and QTextEdit
378 qreal m_defaultPixelStepSize = 20 * QGuiApplication::styleHints()->wheelScrollLines();
379 qreal m_verticalStepSize = m_defaultPixelStepSize;
380 qreal m_horizontalStepSize = m_defaultPixelStepSize;
381 bool m_explicitVStepSize = false;
382 bool m_explicitHStepSize = false;
383 bool m_wheelScrolling = false;
384 constexpr static qreal m_wheelScrollingDuration = 400;
385 bool m_filterMouseEvents = false;
386 bool m_keyNavigationEnabled = false;
387 bool m_blockTargetWheel = true;
388 bool m_scrollFlickableTarget = true;
389 // Same as QXcbWindow.
390 constexpr static Qt::KeyboardModifiers m_defaultHorizontalScrollModifiers = Qt::AltModifier;
391 // Same as QScrollBar/QAbstractSlider.
392 constexpr static Qt::KeyboardModifiers m_defaultPageScrollModifiers = Qt::ControlModifier | Qt::ShiftModifier;
393 Qt::KeyboardModifiers m_pageScrollModifiers = m_defaultPageScrollModifiers;
394 QTimer m_wheelScrollingTimer;
395 KirigamiWheelEvent m_kirigamiWheelEvent;
396
397 // Smooth scrolling
398 QQmlEngine *m_engine = nullptr;
399 QPropertyAnimation m_yScrollAnimation{nullptr, "contentY"};
400 bool m_wasTouched = false;
401};
Describes the mouse wheel event.
QPointF pixelDelta
pixelDelta: point
QPointF angleDelta
angleDelta: point
int buttons
buttons: int
bool inverted
inverted: bool
int modifiers
modifiers: int
bool accepted
accepted: bool
This class contains global kirigami settings about the current device setup It is exposed to QML as t...
Definition settings.h:24
A set of values to define semantically sizes and durations.
Definition units.h:80
Handles scrolling for a Flickable and 2 attached ScrollBars.
bool filterMouseEvents
This property holds whether the WheelHandler filters mouse events like a Qt Quick Controls ScrollView...
qreal horizontalStepSize
This property holds the horizontal step size.
Qt::KeyboardModifiers pageScrollModifiers
This property holds the keyboard modifiers that will be used to start page scrolling.
bool keyNavigationEnabled
This property holds whether the WheelHandler handles keyboard scrolling.
Q_INVOKABLE bool scrollDown(qreal stepSize=-1)
Scroll down one step.
QML_ELEMENTQQuickItem * target
This property holds the Qt Quick Flickable that the WheelHandler will control.
Q_INVOKABLE bool scrollRight(qreal stepSize=-1)
Scroll right one step.
void wheel(KirigamiWheelEvent *wheel)
This signal is emitted when a wheel event reaches the event filter, just before scrolling is handled.
qreal verticalStepSize
This property holds the vertical step size.
Q_INVOKABLE bool scrollUp(qreal stepSize=-1)
Scroll up one step.
Q_INVOKABLE bool scrollLeft(qreal stepSize=-1)
Scroll left one step.
bool scrollFlickableTarget
This property holds whether the WheelHandler can use wheel events to scroll the Flickable.
bool blockTargetWheel
This property holds whether the WheelHandler blocks all wheel events from reaching the Flickable.
QStyleHints * styleHints()
QObject(QObject *parent)
Q_INTERFACES(...)
Q_INVOKABLEQ_INVOKABLE
Q_OBJECTQ_OBJECT
Q_PROPERTY(...)
Q_SIGNALSQ_SIGNALS
Q_SLOTSQ_SLOTS
virtual bool event(QEvent *e)
QObject * parent() const const
typedef KeyboardModifiers
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:51:21 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.