Kirigami2

wheelhandler.cpp
1/*
2 * SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7#include "wheelhandler.h"
8#include "settings.h"
9
10#include <QQmlEngine>
11#include <QQuickItem>
12#include <QQuickWindow>
13#include <QWheelEvent>
14
15KirigamiWheelEvent::KirigamiWheelEvent(QObject *parent)
16 : QObject(parent)
17{
18}
19
20KirigamiWheelEvent::~KirigamiWheelEvent()
21{
22}
23
24void KirigamiWheelEvent::initializeFromEvent(QWheelEvent *event)
25{
26 m_x = event->position().x();
27 m_y = event->position().y();
28 m_angleDelta = event->angleDelta();
29 m_pixelDelta = event->pixelDelta();
30 m_buttons = event->buttons();
31 m_modifiers = event->modifiers();
32 m_accepted = false;
33 m_inverted = event->inverted();
34}
35
36qreal KirigamiWheelEvent::x() const
37{
38 return m_x;
39}
40
41qreal KirigamiWheelEvent::y() const
42{
43 return m_y;
44}
45
47{
48 return m_angleDelta;
49}
50
52{
53 return m_pixelDelta;
54}
55
57{
58 return m_buttons;
59}
60
62{
63 return m_modifiers;
64}
65
67{
68 return m_inverted;
69}
70
71bool KirigamiWheelEvent::isAccepted()
72{
73 return m_accepted;
74}
75
76void KirigamiWheelEvent::setAccepted(bool accepted)
77{
78 m_accepted = accepted;
79}
80
81///////////////////////////////
82
83WheelFilterItem::WheelFilterItem(QQuickItem *parent)
84 : QQuickItem(parent)
85{
86 setEnabled(false);
87}
88
89///////////////////////////////
90
91WheelHandler::WheelHandler(QObject *parent)
92 : QObject(parent)
93 , m_filterItem(new WheelFilterItem(nullptr))
94{
95 m_filterItem->installEventFilter(this);
96
97 m_wheelScrollingTimer.setSingleShot(true);
98 m_wheelScrollingTimer.setInterval(m_wheelScrollingDuration);
99 m_wheelScrollingTimer.callOnTimeout([this]() {
100 setScrolling(false);
101 });
102
103 m_xScrollAnimation.setEasingCurve(QEasingCurve::OutCubic);
104 m_yScrollAnimation.setEasingCurve(QEasingCurve::OutCubic);
105
107 m_defaultPixelStepSize = 20 * scrollLines;
108 if (!m_explicitVStepSize && m_verticalStepSize != m_defaultPixelStepSize) {
109 m_verticalStepSize = m_defaultPixelStepSize;
110 Q_EMIT verticalStepSizeChanged();
111 }
112 if (!m_explicitHStepSize && m_horizontalStepSize != m_defaultPixelStepSize) {
113 m_horizontalStepSize = m_defaultPixelStepSize;
114 Q_EMIT horizontalStepSizeChanged();
115 }
116 });
117}
118
119WheelHandler::~WheelHandler()
120{
121 delete m_filterItem;
122}
123
125{
126 return m_flickable;
127}
128
129void WheelHandler::setTarget(QQuickItem *target)
130{
131 if (m_flickable == target) {
132 return;
133 }
134
135 if (target && !target->inherits("QQuickFlickable")) {
136 qmlWarning(this) << "target must be a QQuickFlickable";
137 return;
138 }
139
140 if (m_flickable) {
141 m_flickable->removeEventFilter(this);
142 disconnect(m_flickable, nullptr, m_filterItem, nullptr);
143 disconnect(m_flickable, &QQuickItem::parentChanged, this, &WheelHandler::_k_rebindScrollBars);
144 }
145
146 m_flickable = target;
147 m_filterItem->setParentItem(target);
148 if (m_xScrollAnimation.targetObject()) {
149 m_xScrollAnimation.stop();
150 }
151 m_xScrollAnimation.setTargetObject(target);
152 if (m_yScrollAnimation.targetObject()) {
153 m_yScrollAnimation.stop();
154 }
155 m_yScrollAnimation.setTargetObject(target);
156
157 if (target) {
158 target->installEventFilter(this);
159
160 // Stack WheelFilterItem over the Flickable's scrollable content
161 m_filterItem->stackAfter(target->property("contentItem").value<QQuickItem *>());
162 // Make it fill the Flickable
163 m_filterItem->setWidth(target->width());
164 m_filterItem->setHeight(target->height());
165 connect(target, &QQuickItem::widthChanged, m_filterItem, [this, target]() {
166 m_filterItem->setWidth(target->width());
167 });
168 connect(target, &QQuickItem::heightChanged, m_filterItem, [this, target]() {
169 m_filterItem->setHeight(target->height());
170 });
171 }
172
173 _k_rebindScrollBars();
174
175 Q_EMIT targetChanged();
176}
177
178void WheelHandler::_k_rebindScrollBars()
179{
180 struct ScrollBarAttached {
181 QObject *attached = nullptr;
182 QQuickItem *vertical = nullptr;
183 QQuickItem *horizontal = nullptr;
184 };
185
186 ScrollBarAttached attachedToFlickable;
187 ScrollBarAttached attachedToScrollView;
188
189 if (m_flickable) {
190 // Get ScrollBars so that we can filter them too, even if they're not
191 // in the bounds of the Flickable
192 const auto flickableChildren = m_flickable->children();
193 for (const auto child : flickableChildren) {
194 if (child->inherits("QQuickScrollBarAttached")) {
195 attachedToFlickable.attached = child;
196 attachedToFlickable.vertical = child->property("vertical").value<QQuickItem *>();
197 attachedToFlickable.horizontal = child->property("horizontal").value<QQuickItem *>();
198 break;
199 }
200 }
201
202 // Check ScrollView if there are no scrollbars attached to the Flickable.
203 // We need to check if the parent inherits QQuickScrollView in case the
204 // parent is another Flickable that already has a Kirigami WheelHandler.
205 auto flickableParent = m_flickable->parentItem();
206 if (flickableParent && flickableParent->inherits("QQuickScrollView")) {
207 const auto siblings = flickableParent->children();
208 for (const auto child : siblings) {
209 if (child->inherits("QQuickScrollBarAttached")) {
210 attachedToScrollView.attached = child;
211 attachedToScrollView.vertical = child->property("vertical").value<QQuickItem *>();
212 attachedToScrollView.horizontal = child->property("horizontal").value<QQuickItem *>();
213 break;
214 }
215 }
216 }
217 }
218
219 // Dilemma: ScrollBars can be attached to both ScrollView and Flickable,
220 // but only one of them should be shown anyway. Let's prefer Flickable.
221
222 struct ChosenScrollBar {
223 QObject *attached = nullptr;
224 QQuickItem *scrollBar = nullptr;
225 };
226
227 ChosenScrollBar vertical;
228 if (attachedToFlickable.vertical) {
229 vertical.attached = attachedToFlickable.attached;
230 vertical.scrollBar = attachedToFlickable.vertical;
231 } else if (attachedToScrollView.vertical) {
232 vertical.attached = attachedToScrollView.attached;
233 vertical.scrollBar = attachedToScrollView.vertical;
234 }
235
236 ChosenScrollBar horizontal;
237 if (attachedToFlickable.horizontal) {
238 horizontal.attached = attachedToFlickable.attached;
239 horizontal.scrollBar = attachedToFlickable.horizontal;
240 } else if (attachedToScrollView.horizontal) {
241 horizontal.attached = attachedToScrollView.attached;
242 horizontal.scrollBar = attachedToScrollView.horizontal;
243 }
244
245 // Flickable may get re-parented to or out of a ScrollView, so we need to
246 // redo the discovery process. This is especially important for
247 // Kirigami.ScrollablePage component.
248 if (m_flickable) {
249 if (attachedToFlickable.horizontal && attachedToFlickable.vertical) {
250 // But if both scrollbars are already those from the preferred
251 // Flickable, there's no need for rediscovery.
252 disconnect(m_flickable, &QQuickItem::parentChanged, this, &WheelHandler::_k_rebindScrollBars);
253 } else {
254 connect(m_flickable, &QQuickItem::parentChanged, this, &WheelHandler::_k_rebindScrollBars, Qt::UniqueConnection);
255 }
256 }
257
258 if (m_verticalScrollBar != vertical.scrollBar) {
259 if (m_verticalScrollBar) {
260 m_verticalScrollBar->removeEventFilter(this);
261 disconnect(m_verticalChangedConnection);
262 }
263 m_verticalScrollBar = vertical.scrollBar;
264 if (vertical.scrollBar) {
265 vertical.scrollBar->installEventFilter(this);
266 m_verticalChangedConnection = connect(vertical.attached, SIGNAL(verticalChanged()), this, SLOT(_k_rebindScrollBars()));
267 }
268 }
269
270 if (m_horizontalScrollBar != horizontal.scrollBar) {
271 if (m_horizontalScrollBar) {
272 m_horizontalScrollBar->removeEventFilter(this);
273 disconnect(m_horizontalChangedConnection);
274 }
275 m_horizontalScrollBar = horizontal.scrollBar;
276 if (horizontal.scrollBar) {
277 horizontal.scrollBar->installEventFilter(this);
278 m_horizontalChangedConnection = connect(horizontal.attached, SIGNAL(horizontalChanged()), this, SLOT(_k_rebindScrollBars()));
279 }
280 }
281}
282
284{
285 return m_verticalStepSize;
286}
287
288void WheelHandler::setVerticalStepSize(qreal stepSize)
289{
290 m_explicitVStepSize = true;
291 if (qFuzzyCompare(m_verticalStepSize, stepSize)) {
292 return;
293 }
294 // Mimic the behavior of QQuickScrollBar when stepSize is 0
295 if (qFuzzyIsNull(stepSize)) {
296 resetVerticalStepSize();
297 return;
298 }
299 m_verticalStepSize = stepSize;
300 Q_EMIT verticalStepSizeChanged();
301}
302
303void WheelHandler::resetVerticalStepSize()
304{
305 m_explicitVStepSize = false;
306 if (qFuzzyCompare(m_verticalStepSize, m_defaultPixelStepSize)) {
307 return;
308 }
309 m_verticalStepSize = m_defaultPixelStepSize;
310 Q_EMIT verticalStepSizeChanged();
311}
312
314{
315 return m_horizontalStepSize;
316}
317
318void WheelHandler::setHorizontalStepSize(qreal stepSize)
319{
320 m_explicitHStepSize = true;
321 if (qFuzzyCompare(m_horizontalStepSize, stepSize)) {
322 return;
323 }
324 // Mimic the behavior of QQuickScrollBar when stepSize is 0
325 if (qFuzzyIsNull(stepSize)) {
326 resetHorizontalStepSize();
327 return;
328 }
329 m_horizontalStepSize = stepSize;
330 Q_EMIT horizontalStepSizeChanged();
331}
332
333void WheelHandler::resetHorizontalStepSize()
334{
335 m_explicitHStepSize = false;
336 if (qFuzzyCompare(m_horizontalStepSize, m_defaultPixelStepSize)) {
337 return;
338 }
339 m_horizontalStepSize = m_defaultPixelStepSize;
340 Q_EMIT horizontalStepSizeChanged();
341}
342
344{
345 return m_pageScrollModifiers;
346}
347
348void WheelHandler::setPageScrollModifiers(Qt::KeyboardModifiers modifiers)
349{
350 if (m_pageScrollModifiers == modifiers) {
351 return;
352 }
353 m_pageScrollModifiers = modifiers;
354 Q_EMIT pageScrollModifiersChanged();
355}
356
357void WheelHandler::resetPageScrollModifiers()
358{
359 setPageScrollModifiers(m_defaultPageScrollModifiers);
360}
361
363{
364 return m_filterMouseEvents;
365}
366
367void WheelHandler::setFilterMouseEvents(bool enabled)
368{
369 if (m_filterMouseEvents == enabled) {
370 return;
371 }
372 m_filterMouseEvents = enabled;
373 Q_EMIT filterMouseEventsChanged();
374}
375
377{
378 return m_keyNavigationEnabled;
379}
380
381void WheelHandler::setKeyNavigationEnabled(bool enabled)
382{
383 if (m_keyNavigationEnabled == enabled) {
384 return;
385 }
386 m_keyNavigationEnabled = enabled;
387 Q_EMIT keyNavigationEnabledChanged();
388}
389
390void WheelHandler::classBegin()
391{
392 // Initializes smooth scrolling
393 m_engine = qmlEngine(this);
394 m_units = m_engine->singletonInstance<Kirigami::Platform::Units *>("org.kde.kirigami.platform", "Units");
395 m_settings = m_engine->singletonInstance<Kirigami::Platform::Settings *>("org.kde.kirigami.platform", "Settings");
396}
397
398void WheelHandler::componentComplete()
399{
400}
401
402void WheelHandler::setScrolling(bool scrolling)
403{
404 if (m_wheelScrolling == scrolling) {
405 if (m_wheelScrolling) {
406 m_wheelScrollingTimer.start();
407 }
408 return;
409 }
410 m_wheelScrolling = scrolling;
411 m_filterItem->setEnabled(m_wheelScrolling);
412}
413
414bool WheelHandler::scrollFlickable(QPointF pixelDelta, QPointF angleDelta, Qt::KeyboardModifiers modifiers)
415{
416 if (!m_flickable || (pixelDelta.isNull() && angleDelta.isNull())) {
417 return false;
418 }
419
420 const qreal width = m_flickable->width();
421 const qreal height = m_flickable->height();
422 const qreal contentWidth = m_flickable->property("contentWidth").toReal();
423 const qreal contentHeight = m_flickable->property("contentHeight").toReal();
424 const qreal contentX = m_flickable->property("contentX").toReal();
425 const qreal contentY = m_flickable->property("contentY").toReal();
426 const qreal topMargin = m_flickable->property("topMargin").toReal();
427 const qreal bottomMargin = m_flickable->property("bottomMargin").toReal();
428 const qreal leftMargin = m_flickable->property("leftMargin").toReal();
429 const qreal rightMargin = m_flickable->property("rightMargin").toReal();
430 const qreal originX = m_flickable->property("originX").toReal();
431 const qreal originY = m_flickable->property("originY").toReal();
432 const qreal pageWidth = width - leftMargin - rightMargin;
433 const qreal pageHeight = height - topMargin - bottomMargin;
434 const auto window = m_flickable->window();
435 const auto screen = window ? window->screen() : nullptr;
436 const qreal devicePixelRatio = window != nullptr ? window->devicePixelRatio() : qGuiApp->devicePixelRatio();
437 const qreal refreshRate = screen ? screen->refreshRate() : 0;
438
439 // HACK: Only transpose deltas when not using xcb in order to not conflict with xcb's own delta transposing
440 if (modifiers & m_defaultHorizontalScrollModifiers && qGuiApp->platformName() != QLatin1String("xcb")) {
441 angleDelta = angleDelta.transposed();
442 pixelDelta = pixelDelta.transposed();
443 }
444
445 const qreal xTicks = angleDelta.x() / 120;
446 const qreal yTicks = angleDelta.y() / 120;
447 bool scrolled = false;
448
449 auto getChange = [pageScrollModifiers = modifiers & m_pageScrollModifiers](qreal ticks, qreal pixelDelta, qreal stepSize, qreal pageSize) {
450 // Use page size with pageScrollModifiers. Matches QScrollBar, which uses QAbstractSlider behavior.
452 return qBound(-pageSize, ticks * pageSize, pageSize);
453 } else if (pixelDelta != 0) {
454 return pixelDelta;
455 } else {
456 return ticks * stepSize;
457 }
458 };
459
460 auto getPosition = [devicePixelRatio](qreal size,
461 qreal contentSize,
462 qreal contentPos,
463 qreal originPos,
464 qreal pageSize,
465 qreal leadingMargin,
466 qreal trailingMargin,
467 qreal change,
468 const QPropertyAnimation &animation) {
469 if (contentSize <= pageSize) {
470 return contentPos;
471 }
472
473 // contentX and contentY use reversed signs from what x and y would normally use, so flip the signs
474
475 qreal minExtent = leadingMargin - originPos;
476 qreal maxExtent = size - (contentSize + trailingMargin + originPos);
477
478 qreal newContentPos =
479 std::clamp((animation.state() == QPropertyAnimation::Running ? animation.endValue().toReal() : contentPos) - change, -minExtent, -maxExtent);
480
481 // Flickable::pixelAligned rounds the position, so round to mimic that behavior.
482 // Rounding prevents fractional positioning from causing text to be
483 // clipped off on the top and bottom.
484 // Multiply by devicePixelRatio before rounding and divide by devicePixelRatio
485 // after to make position match pixels on the screen more closely.
486 return std::round(newContentPos * devicePixelRatio) / devicePixelRatio;
487 };
488
489 auto setPosition = [this, devicePixelRatio, refreshRate](qreal oldPos, qreal newPos, qreal stepSize, const char *property, QPropertyAnimation &animation) {
490 animation.stop();
491 if (oldPos == newPos) {
492 return false;
493 }
494 if (!m_settings->smoothScroll() || !m_engine || refreshRate <= 0) {
495 animation.setDuration(0);
496 m_flickable->setProperty(property, newPos);
497 return true;
498 }
499
500 // Can't use wheelEvent->deviceType() to determine device type since
501 // on Wayland mouse is always regarded as touchpad:
502 // https://invent.kde.org/qt/qt/qtwayland/-/blob/e695a39519a7629c1549275a148cfb9ab99a07a9/src/client/qwaylandinputdevice.cpp#L445
503 // Mouse wheel can generate angle delta like 240, 360 and so on when
504 // scrolling very fast on some mice such as the Logitech M150.
505 // Mice with hi-res mouse wheels such as the Logitech MX Master 3 can
506 // generate angle deltas as small as 16.
507 // On X11, trackpads can also generate very fine angle deltas.
508
509 // Duration is based on the duration and movement for 120 angle delta.
510 // Shorten duration for smaller movements, limit duration for big movements.
511 // We don't want fine deltas to feel extra slow and fast scrolling should still feel fast.
512 // Minimum 3 frames for a 60hz display if delta > 2 physical pixels
513 // (start already rendered -> 1/3 rendered -> 2/3 rendered -> end rendered).
514 // Skip animation if <= 2 real frames for low refresh rate screens.
515 // Otherwise, we don't scale the duration based on refresh rate or
516 // device pixel ratio to avoid making the animation unexpectedly
517 // longer or shorter on different screens.
518
519 qreal absPixelDelta = std::abs(newPos - oldPos);
520 int duration = absPixelDelta * devicePixelRatio > 2 //
521 ? std::clamp(qRound(absPixelDelta * m_units->longDuration() / stepSize), qCeil(1000.0 / 60.0 * 3), m_units->longDuration())
522 : 0;
523 animation.setDuration(duration <= qCeil(1000.0 / refreshRate * 2) ? 0 : duration);
524 if (animation.duration() > 0) {
525 animation.setEndValue(newPos);
527 } else {
528 m_flickable->setProperty(property, newPos);
529 }
530 return true;
531 };
532
533 qreal xChange = getChange(xTicks, pixelDelta.x(), m_horizontalStepSize, pageWidth);
534 qreal newContentX = getPosition(width, contentWidth, contentX, originX, pageWidth, leftMargin, rightMargin, xChange, m_xScrollAnimation);
535
536 qreal yChange = getChange(yTicks, pixelDelta.y(), m_verticalStepSize, pageHeight);
537 qreal newContentY = getPosition(height, contentHeight, contentY, originY, pageHeight, topMargin, bottomMargin, yChange, m_yScrollAnimation);
538
539 // Don't use `||` because we need the position to be set for contentX and contentY.
540 scrolled |= setPosition(contentX, newContentX, m_horizontalStepSize, "contentX", m_xScrollAnimation);
541 scrolled |= setPosition(contentY, newContentY, m_verticalStepSize, "contentY", m_yScrollAnimation);
542
543 return scrolled;
544}
545
546bool WheelHandler::scrollUp(qreal stepSize)
547{
548 if (qFuzzyIsNull(stepSize)) {
549 return false;
550 } else if (stepSize < 0) {
551 stepSize = m_verticalStepSize;
552 }
553 // contentY uses reversed sign
554 return scrollFlickable(QPointF(0, stepSize));
555}
556
557bool WheelHandler::scrollDown(qreal stepSize)
558{
559 if (qFuzzyIsNull(stepSize)) {
560 return false;
561 } else if (stepSize < 0) {
562 stepSize = m_verticalStepSize;
563 }
564 // contentY uses reversed sign
565 return scrollFlickable(QPointF(0, -stepSize));
566}
567
568bool WheelHandler::scrollLeft(qreal stepSize)
569{
570 if (qFuzzyIsNull(stepSize)) {
571 return false;
572 } else if (stepSize < 0) {
573 stepSize = m_horizontalStepSize;
574 }
575 // contentX uses reversed sign
576 return scrollFlickable(QPoint(stepSize, 0));
577}
578
579bool WheelHandler::scrollRight(qreal stepSize)
580{
581 if (qFuzzyIsNull(stepSize)) {
582 return false;
583 } else if (stepSize < 0) {
584 stepSize = m_horizontalStepSize;
585 }
586 // contentX uses reversed sign
587 return scrollFlickable(QPoint(-stepSize, 0));
588}
589
590bool WheelHandler::eventFilter(QObject *watched, QEvent *event)
591{
592 auto item = qobject_cast<QQuickItem *>(watched);
593 if (!item || !item->isEnabled()) {
594 return false;
595 }
596
597 qreal contentWidth = 0;
598 qreal contentHeight = 0;
599 qreal pageWidth = 0;
600 qreal pageHeight = 0;
601 if (m_flickable) {
602 contentWidth = m_flickable->property("contentWidth").toReal();
603 contentHeight = m_flickable->property("contentHeight").toReal();
604 pageWidth = m_flickable->width() - m_flickable->property("leftMargin").toReal() - m_flickable->property("rightMargin").toReal();
605 pageHeight = m_flickable->height() - m_flickable->property("topMargin").toReal() - m_flickable->property("bottomMargin").toReal();
606 }
607
608 // The code handling touch, mouse and hover events is mostly copied/adapted from QQuickScrollView::childMouseEventFilter()
609 switch (event->type()) {
610 case QEvent::Wheel: {
611 // QQuickScrollBar::interactive handling Matches behavior in QQuickScrollView::eventFilter()
612 if (m_filterMouseEvents) {
613 if (m_verticalScrollBar) {
614 m_verticalScrollBar->setProperty("interactive", true);
615 }
616 if (m_horizontalScrollBar) {
617 m_horizontalScrollBar->setProperty("interactive", true);
618 }
619 }
620 QWheelEvent *wheelEvent = static_cast<QWheelEvent *>(event);
621
622 // NOTE: On X11 with libinput, pixelDelta is identical to angleDelta when using a mouse that shouldn't use pixelDelta.
623 // If faulty pixelDelta, reset pixelDelta to (0,0).
624 if (wheelEvent->pixelDelta() == wheelEvent->angleDelta()) {
625 // In order to change any of the data, we have to create a whole new QWheelEvent from its constructor.
626 QWheelEvent newWheelEvent(wheelEvent->position(),
627 wheelEvent->globalPosition(),
628 QPoint(0, 0), // pixelDelta
629 wheelEvent->angleDelta(),
630 wheelEvent->buttons(),
631 wheelEvent->modifiers(),
632 wheelEvent->phase(),
633 wheelEvent->inverted(),
634 wheelEvent->source());
635 m_kirigamiWheelEvent.initializeFromEvent(&newWheelEvent);
636 } else {
637 m_kirigamiWheelEvent.initializeFromEvent(wheelEvent);
638 }
639
640 Q_EMIT wheel(&m_kirigamiWheelEvent);
641
642 if (m_kirigamiWheelEvent.isAccepted()) {
643 return true;
644 }
645
646 bool scrolled = false;
647 if (m_scrollFlickableTarget || (contentHeight <= pageHeight && contentWidth <= pageWidth)) {
648 // Don't use pixelDelta from the event unless angleDelta is not available
649 // because scrolling by pixelDelta is too slow on Wayland with libinput.
650 QPointF pixelDelta = m_kirigamiWheelEvent.angleDelta().isNull() ? m_kirigamiWheelEvent.pixelDelta() : QPoint(0, 0);
651 scrolled = scrollFlickable(pixelDelta, m_kirigamiWheelEvent.angleDelta(), Qt::KeyboardModifiers(m_kirigamiWheelEvent.modifiers()));
652 }
653 setScrolling(scrolled);
654
655 // NOTE: Wheel events created by touchpad gestures with pixel deltas will cause scrolling to jump back
656 // to where scrolling started unless the event is always accepted before it reaches the Flickable.
657 bool flickableWillUseGestureScrolling = !(wheelEvent->source() == Qt::MouseEventNotSynthesized || wheelEvent->pixelDelta().isNull());
658 return scrolled || m_blockTargetWheel || flickableWillUseGestureScrolling;
659 }
660
661 case QEvent::TouchBegin: {
662 m_wasTouched = true;
663 if (!m_filterMouseEvents) {
664 break;
665 }
666 if (m_verticalScrollBar) {
667 m_verticalScrollBar->setProperty("interactive", false);
668 }
669 if (m_horizontalScrollBar) {
670 m_horizontalScrollBar->setProperty("interactive", false);
671 }
672 break;
673 }
674
675 case QEvent::TouchEnd: {
676 m_wasTouched = false;
677 break;
678 }
679
681 // NOTE: Flickable does not handle touch events, only synthesized mouse events
682 m_wasTouched = static_cast<QMouseEvent *>(event)->source() != Qt::MouseEventNotSynthesized;
683 if (!m_filterMouseEvents) {
684 break;
685 }
686 if (!m_wasTouched) {
687 if (m_verticalScrollBar) {
688 m_verticalScrollBar->setProperty("interactive", true);
689 }
690 if (m_horizontalScrollBar) {
691 m_horizontalScrollBar->setProperty("interactive", true);
692 }
693 break;
694 }
695 return !m_wasTouched && item == m_flickable;
696 }
697
700 setScrolling(false);
701 if (!m_filterMouseEvents) {
702 break;
703 }
704 if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventNotSynthesized && item == m_flickable) {
705 return true;
706 }
707 break;
708 }
709
711 case QEvent::HoverMove: {
712 if (!m_filterMouseEvents) {
713 break;
714 }
715 if (m_wasTouched && (item == m_verticalScrollBar || item == m_horizontalScrollBar)) {
716 if (m_verticalScrollBar) {
717 m_verticalScrollBar->setProperty("interactive", true);
718 }
719 if (m_horizontalScrollBar) {
720 m_horizontalScrollBar->setProperty("interactive", true);
721 }
722 }
723 break;
724 }
725
726 case QEvent::KeyPress: {
727 if (!m_keyNavigationEnabled) {
728 break;
729 }
730 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
731 bool horizontalScroll = keyEvent->modifiers() & m_defaultHorizontalScrollModifiers;
732 switch (keyEvent->key()) {
733 case Qt::Key_Up:
734 return scrollUp();
735 case Qt::Key_Down:
736 return scrollDown();
737 case Qt::Key_Left:
738 return scrollLeft();
739 case Qt::Key_Right:
740 return scrollRight();
741 case Qt::Key_PageUp:
742 return horizontalScroll ? scrollLeft(pageWidth) : scrollUp(pageHeight);
743 case Qt::Key_PageDown:
744 return horizontalScroll ? scrollRight(pageWidth) : scrollDown(pageHeight);
745 case Qt::Key_Home:
746 return horizontalScroll ? scrollLeft(contentWidth) : scrollUp(contentHeight);
747 case Qt::Key_End:
748 return horizontalScroll ? scrollRight(contentWidth) : scrollDown(contentHeight);
749 default:
750 break;
751 }
752 break;
753 }
754
755 default:
756 break;
757 }
758
759 return false;
760}
761
762#include "moc_wheelhandler.cpp"
QPointF pixelDelta
pixelDelta: point
QPointF angleDelta
angleDelta: point
int buttons
buttons: int
bool inverted
inverted: bool
int modifiers
modifiers: int
bool accepted
accepted: bool
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.
KGUIADDONS_EXPORT QWindow * window(QObject *job)
KREPORT_EXPORT QPageSize::PageSizeId pageSize(const QString &key)
QStyleHints * styleHints()
Qt::KeyboardModifiers modifiers() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
virtual bool event(QEvent *e)
void installEventFilter(QObject *filterObj)
QVariant property(const char *name) const const
T qobject_cast(QObject *object)
bool isNull() const const
bool isNull() const const
QPointF transposed() const const
qreal x() const const
qreal y() const const
void heightChanged()
void parentChanged(QQuickItem *)
void widthChanged()
Qt::MouseButtons buttons() const const
QPointF globalPosition() const const
QPointF position() const const
void wheelScrollLinesChanged(int scrollLines)
UniqueConnection
typedef KeyboardModifiers
MouseEventNotSynthesized
void keyEvent(KeyAction action, QWidget *widget, Qt::Key key, Qt::KeyboardModifiers modifier, int delay)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
Qt::MouseEventSource source() const const
QPoint angleDelta() const const
bool inverted() const const
Qt::ScrollPhase phase() const const
QPoint pixelDelta() const const
qreal devicePixelRatio() const const
QScreen * screen() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 18 2025 12:03:26 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.