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"
QML_ELEMENTqreal x
This class contains global kirigami settings about the current device setup It is exposed to QML as t...
Definition settings.h:24
bool smoothScroll
This property holds whether to display animated transitions when scrolling with a mouse wheel or the ...
Definition settings.h:84
A set of values to define semantically sizes and durations.
Definition units.h:80
int longDuration
units.longDuration should be used for longer, screen-covering animations, for opening and closing of ...
Definition units.h:136
bool filterMouseEvents
qreal horizontalStepSize
Qt::KeyboardModifiers pageScrollModifiers
bool keyNavigationEnabled
Q_INVOKABLE bool scrollDown(qreal stepSize=-1)
QML_ELEMENTQQuickItem * target
Q_INVOKABLE bool scrollRight(qreal stepSize=-1)
void wheel(KirigamiWheelEvent *wheel)
qreal verticalStepSize
Q_INVOKABLE bool scrollUp(qreal stepSize=-1)
Q_INVOKABLE bool scrollLeft(qreal stepSize=-1)
QWidget * window(QObject *job)
void start(QAbstractAnimation::DeletionPolicy policy)
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
T singletonInstance(QAnyStringView uri, QAnyStringView typeName)
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)
void start()
void setDuration(int msecs)
Qt::MouseEventSource source() const const
QPoint angleDelta() const const
bool inverted() const const
Qt::ScrollPhase phase() const const
QPoint pixelDelta() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 6 2024 12:09:31 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.