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