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

source code of kirigami/src/wheelhandler.cpp