7#include "wheelhandler.h"
12#include <QQuickWindow>
15KirigamiWheelEvent::KirigamiWheelEvent(
QObject *parent)
20KirigamiWheelEvent::~KirigamiWheelEvent()
24void KirigamiWheelEvent::initializeFromEvent(
QWheelEvent *event)
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();
33 m_inverted =
event->inverted();
71bool KirigamiWheelEvent::isAccepted()
76void KirigamiWheelEvent::setAccepted(
bool accepted)
83WheelFilterItem::WheelFilterItem(
QQuickItem *parent)
91WheelHandler::WheelHandler(
QObject *parent)
93 , m_filterItem(new WheelFilterItem(nullptr))
97 m_wheelScrollingTimer.setSingleShot(
true);
98 m_wheelScrollingTimer.setInterval(m_wheelScrollingDuration);
99 m_wheelScrollingTimer.callOnTimeout([
this]() {
106 m_defaultPixelStepSize = 20 * scrollLines;
107 if (!m_explicitVStepSize && m_verticalStepSize != m_defaultPixelStepSize) {
108 m_verticalStepSize = m_defaultPixelStepSize;
109 Q_EMIT verticalStepSizeChanged();
111 if (!m_explicitHStepSize && m_horizontalStepSize != m_defaultPixelStepSize) {
112 m_horizontalStepSize = m_defaultPixelStepSize;
113 Q_EMIT horizontalStepSizeChanged();
118WheelHandler::~WheelHandler()
128void WheelHandler::setTarget(
QQuickItem *target)
130 if (m_flickable ==
target) {
135 qmlWarning(
this) <<
"target must be a QQuickFlickable";
140 m_flickable->removeEventFilter(
this);
141 disconnect(m_flickable,
nullptr, m_filterItem,
nullptr);
146 m_filterItem->setParentItem(
target);
147 if (m_yScrollAnimation.targetObject()) {
148 m_yScrollAnimation.stop();
150 m_yScrollAnimation.setTargetObject(
target);
153 target->installEventFilter(
this);
156 m_filterItem->stackAfter(
target->property(
"contentItem").value<QQuickItem *>());
158 m_filterItem->setWidth(
target->width());
159 m_filterItem->setHeight(
target->height());
161 m_filterItem->setWidth(
target->width());
164 m_filterItem->setHeight(
target->height());
168 _k_rebindScrollBars();
173void WheelHandler::_k_rebindScrollBars()
175 struct ScrollBarAttached {
176 QObject *attached =
nullptr;
177 QQuickItem *vertical =
nullptr;
178 QQuickItem *horizontal =
nullptr;
181 ScrollBarAttached attachedToFlickable;
182 ScrollBarAttached attachedToScrollView;
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 *>();
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 *>();
217 struct ChosenScrollBar {
218 QObject *attached =
nullptr;
219 QQuickItem *scrollBar =
nullptr;
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;
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;
244 if (attachedToFlickable.horizontal && attachedToFlickable.vertical) {
253 if (m_verticalScrollBar != vertical.scrollBar) {
254 if (m_verticalScrollBar) {
255 m_verticalScrollBar->removeEventFilter(
this);
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()));
265 if (m_horizontalScrollBar != horizontal.scrollBar) {
266 if (m_horizontalScrollBar) {
267 m_horizontalScrollBar->removeEventFilter(
this);
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()));
280 return m_verticalStepSize;
283void WheelHandler::setVerticalStepSize(qreal stepSize)
285 m_explicitVStepSize =
true;
286 if (qFuzzyCompare(m_verticalStepSize, stepSize)) {
290 if (qFuzzyIsNull(stepSize)) {
291 resetVerticalStepSize();
294 m_verticalStepSize = stepSize;
295 Q_EMIT verticalStepSizeChanged();
298void WheelHandler::resetVerticalStepSize()
300 m_explicitVStepSize =
false;
301 if (qFuzzyCompare(m_verticalStepSize, m_defaultPixelStepSize)) {
304 m_verticalStepSize = m_defaultPixelStepSize;
305 Q_EMIT verticalStepSizeChanged();
310 return m_horizontalStepSize;
313void WheelHandler::setHorizontalStepSize(qreal stepSize)
315 m_explicitHStepSize =
true;
316 if (qFuzzyCompare(m_horizontalStepSize, stepSize)) {
320 if (qFuzzyIsNull(stepSize)) {
321 resetHorizontalStepSize();
324 m_horizontalStepSize = stepSize;
325 Q_EMIT horizontalStepSizeChanged();
328void WheelHandler::resetHorizontalStepSize()
330 m_explicitHStepSize =
false;
331 if (qFuzzyCompare(m_horizontalStepSize, m_defaultPixelStepSize)) {
334 m_horizontalStepSize = m_defaultPixelStepSize;
335 Q_EMIT horizontalStepSizeChanged();
340 return m_pageScrollModifiers;
345 if (m_pageScrollModifiers == modifiers) {
348 m_pageScrollModifiers = modifiers;
349 Q_EMIT pageScrollModifiersChanged();
352void WheelHandler::resetPageScrollModifiers()
354 setPageScrollModifiers(m_defaultPageScrollModifiers);
359 return m_filterMouseEvents;
362void WheelHandler::setFilterMouseEvents(
bool enabled)
364 if (m_filterMouseEvents == enabled) {
367 m_filterMouseEvents = enabled;
368 Q_EMIT filterMouseEventsChanged();
373 return m_keyNavigationEnabled;
376void WheelHandler::setKeyNavigationEnabled(
bool enabled)
378 if (m_keyNavigationEnabled == enabled) {
381 m_keyNavigationEnabled = enabled;
382 Q_EMIT keyNavigationEnabledChanged();
385void WheelHandler::classBegin()
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();
393 connect(m_units, &Kirigami::Platform::Units::longDurationChanged,
this, &WheelHandler::initSmoothScrollDuration);
394 connect(m_settings, &Kirigami::Platform::Settings::smoothScrollChanged,
this, &WheelHandler::initSmoothScrollDuration);
397void WheelHandler::componentComplete()
401void WheelHandler::initSmoothScrollDuration()
403 if (m_settings->smoothScroll()) {
404 m_yScrollAnimation.setDuration(m_units->longDuration());
406 m_yScrollAnimation.setDuration(0);
410void WheelHandler::setScrolling(
bool scrolling)
412 if (m_wheelScrolling == scrolling) {
413 if (m_wheelScrolling) {
414 m_wheelScrollingTimer.start();
418 m_wheelScrolling = scrolling;
419 m_filterItem->setEnabled(m_wheelScrolling);
424 if (!m_flickable || (pixelDelta.
isNull() && angleDelta.
isNull())) {
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;
446 if (modifiers & m_defaultHorizontalScrollModifiers && qGuiApp->platformName() != QLatin1String(
"xcb")) {
451 const qreal xTicks = angleDelta.
x() / 120;
452 const qreal yTicks = angleDelta.
y() / 120;
455 bool scrolled =
false;
458 if (contentWidth > pageWidth) {
460 if (modifiers & m_pageScrollModifiers) {
461 xChange = qBound(-pageWidth, xTicks * pageWidth, pageWidth);
462 }
else if (pixelDelta.
x() != 0) {
463 xChange = pixelDelta.
x();
465 xChange = xTicks * m_horizontalStepSize;
470 qreal minXExtent = leftMargin - originX;
471 qreal maxXExtent = width - (contentWidth + rightMargin + originX);
473 qreal newContentX = qBound(-minXExtent, contentX - xChange, -maxXExtent);
479 newContentX = std::round(newContentX * devicePixelRatio) / devicePixelRatio;
480 if (contentX != newContentX) {
482 m_flickable->setProperty(
"contentX", newContentX);
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();
493 yChange = yTicks * m_verticalStepSize;
498 qreal minYExtent = topMargin - originY;
499 qreal maxYExtent = height - (contentHeight + bottomMargin + originY);
503 m_yScrollAnimation.stop();
504 newContentY = std::clamp(m_yScrollAnimation.endValue().toReal() + -yChange, -minYExtent, -maxYExtent);
506 newContentY = std::clamp(contentY - yChange, -minYExtent, -maxYExtent);
514 newContentY = std::round(newContentY * devicePixelRatio) / devicePixelRatio;
515 if (contentY != newContentY) {
517 if (m_wasTouched || !m_engine) {
518 m_flickable->setProperty(
"contentY", newContentY);
520 m_yScrollAnimation.setEndValue(newContentY);
531 if (qFuzzyIsNull(stepSize)) {
533 }
else if (stepSize < 0) {
534 stepSize = m_verticalStepSize;
537 return scrollFlickable(
QPointF(0, stepSize));
542 if (qFuzzyIsNull(stepSize)) {
544 }
else if (stepSize < 0) {
545 stepSize = m_verticalStepSize;
548 return scrollFlickable(
QPointF(0, -stepSize));
553 if (qFuzzyIsNull(stepSize)) {
555 }
else if (stepSize < 0) {
556 stepSize = m_horizontalStepSize;
559 return scrollFlickable(
QPoint(stepSize, 0));
564 if (qFuzzyIsNull(stepSize)) {
566 }
else if (stepSize < 0) {
567 stepSize = m_horizontalStepSize;
570 return scrollFlickable(
QPoint(-stepSize, 0));
573bool WheelHandler::eventFilter(
QObject *watched,
QEvent *event)
576 if (!item || !item->isEnabled()) {
580 qreal contentWidth = 0;
581 qreal contentHeight = 0;
583 qreal pageHeight = 0;
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();
592 switch (
event->type()) {
595 if (m_filterMouseEvents) {
596 if (m_verticalScrollBar) {
597 m_verticalScrollBar->setProperty(
"interactive",
true);
599 if (m_horizontalScrollBar) {
600 m_horizontalScrollBar->setProperty(
"interactive",
true);
603 QWheelEvent *wheelEvent =
static_cast<QWheelEvent *
>(
event);
611 m_wasTouched = (std::abs(wheelEvent->
angleDelta().
y()) != 0 && std::abs(wheelEvent->
angleDelta().
y()) % 120 != 0);
616 QWheelEvent newWheelEvent(wheelEvent->
position(),
625 m_kirigamiWheelEvent.initializeFromEvent(&newWheelEvent);
627 m_kirigamiWheelEvent.initializeFromEvent(wheelEvent);
632 if (m_kirigamiWheelEvent.isAccepted()) {
636 bool scrolled =
false;
637 if (m_scrollFlickableTarget || (contentHeight <= pageHeight && contentWidth <= pageWidth)) {
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()));
643 setScrolling(scrolled);
648 return scrolled || m_blockTargetWheel || flickableWillUseGestureScrolling;
653 if (!m_filterMouseEvents) {
656 if (m_verticalScrollBar) {
657 m_verticalScrollBar->setProperty(
"interactive",
false);
659 if (m_horizontalScrollBar) {
660 m_horizontalScrollBar->setProperty(
"interactive",
false);
666 m_wasTouched =
false;
673 if (!m_filterMouseEvents) {
677 if (m_verticalScrollBar) {
678 m_verticalScrollBar->setProperty(
"interactive",
true);
680 if (m_horizontalScrollBar) {
681 m_horizontalScrollBar->setProperty(
"interactive",
true);
685 return !m_wasTouched && item == m_flickable;
691 if (!m_filterMouseEvents) {
702 if (!m_filterMouseEvents) {
705 if (m_wasTouched && (item == m_verticalScrollBar || item == m_horizontalScrollBar)) {
706 if (m_verticalScrollBar) {
707 m_verticalScrollBar->setProperty(
"interactive",
true);
709 if (m_horizontalScrollBar) {
710 m_horizontalScrollBar->setProperty(
"interactive",
true);
717 if (!m_keyNavigationEnabled) {
721 bool horizontalScroll =
keyEvent->modifiers() & m_defaultHorizontalScrollModifiers;
752#include "moc_wheelhandler.cpp"
QPointF pixelDelta
pixelDelta: point
QPointF angleDelta
angleDelta: point
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.
QStyleHints * styleHints()
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
bool isNull() const const
QPointF transposed() const const
void parentChanged(QQuickItem *)
QPointF globalPosition() const const
QPointF position() const const
typedef KeyboardModifiers
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