7#include "wheelhandler.h"
11KirigamiWheelEvent::KirigamiWheelEvent(
QObject *parent)
16KirigamiWheelEvent::~KirigamiWheelEvent()
20void KirigamiWheelEvent::initializeFromEvent(
QWheelEvent *event)
22 m_x =
event->position().x();
23 m_y =
event->position().y();
24 m_angleDelta =
event->angleDelta();
25 m_pixelDelta =
event->pixelDelta();
26 m_buttons =
event->buttons();
27 m_modifiers =
event->modifiers();
29 m_inverted =
event->inverted();
67bool KirigamiWheelEvent::isAccepted()
72void KirigamiWheelEvent::setAccepted(
bool accepted)
79WheelFilterItem::WheelFilterItem(
QQuickItem *parent)
87WheelHandler::WheelHandler(
QObject *parent)
89 , m_filterItem(new WheelFilterItem(nullptr))
93 m_wheelScrollingTimer.setSingleShot(
true);
94 m_wheelScrollingTimer.setInterval(m_wheelScrollingDuration);
95 m_wheelScrollingTimer.callOnTimeout([
this]() {
102 m_defaultPixelStepSize = 20 * scrollLines;
103 if (!m_explicitVStepSize && m_verticalStepSize != m_defaultPixelStepSize) {
104 m_verticalStepSize = m_defaultPixelStepSize;
105 Q_EMIT verticalStepSizeChanged();
107 if (!m_explicitHStepSize && m_horizontalStepSize != m_defaultPixelStepSize) {
108 m_horizontalStepSize = m_defaultPixelStepSize;
109 Q_EMIT horizontalStepSizeChanged();
114WheelHandler::~WheelHandler()
124void WheelHandler::setTarget(
QQuickItem *target)
126 if (m_flickable ==
target) {
131 qmlWarning(
this) <<
"target must be a QQuickFlickable";
136 m_flickable->removeEventFilter(
this);
137 disconnect(m_flickable,
nullptr, m_filterItem,
nullptr);
142 m_filterItem->setParentItem(
target);
144 m_yScrollAnimation.
stop();
151 target->installEventFilter(
this);
154 m_filterItem->stackAfter(
target->property(
"contentItem").value<
QQuickItem*>());
156 m_filterItem->setWidth(
target->width());
157 m_filterItem->setHeight(
target->height());
159 m_filterItem->setWidth(
target->width());
162 m_filterItem->setHeight(
target->height());
166 _k_rebindScrollBars();
172void WheelHandler::_k_rebindScrollBars()
174 struct ScrollBarAttached {
180 ScrollBarAttached attachedToFlickable;
181 ScrollBarAttached attachedToScrollView;
186 const auto flickableChildren = m_flickable->children();
187 for (
const auto child : flickableChildren) {
188 if (child->inherits(
"QQuickScrollBarAttached")) {
189 attachedToFlickable.attached = child;
190 attachedToFlickable.vertical = child->property(
"vertical").value<
QQuickItem *>();
191 attachedToFlickable.horizontal = child->property(
"horizontal").value<
QQuickItem *>();
199 auto flickableParent = m_flickable->parentItem();
200 if (flickableParent && flickableParent->inherits(
"QQuickScrollView")) {
201 const auto siblings = flickableParent->children();
202 for (
const auto child : siblings) {
203 if (child->inherits(
"QQuickScrollBarAttached")) {
204 attachedToScrollView.attached = child;
205 attachedToScrollView.vertical = child->property(
"vertical").value<
QQuickItem *>();
206 attachedToScrollView.horizontal = child->property(
"horizontal").value<
QQuickItem *>();
216 struct ChosenScrollBar {
221 ChosenScrollBar vertical;
222 if (attachedToFlickable.vertical) {
223 vertical.attached = attachedToFlickable.attached;
224 vertical.scrollBar = attachedToFlickable.vertical;
225 }
else if (attachedToScrollView.vertical) {
226 vertical.attached = attachedToScrollView.attached;
227 vertical.scrollBar = attachedToScrollView.vertical;
230 ChosenScrollBar horizontal;
231 if (attachedToFlickable.horizontal) {
232 horizontal.attached = attachedToFlickable.attached;
233 horizontal.scrollBar = attachedToFlickable.horizontal;
234 }
else if (attachedToScrollView.horizontal) {
235 horizontal.attached = attachedToScrollView.attached;
236 horizontal.scrollBar = attachedToScrollView.horizontal;
243 if (attachedToFlickable.horizontal && attachedToFlickable.vertical) {
252 if (m_verticalScrollBar != vertical.scrollBar) {
253 if (m_verticalScrollBar) {
254 m_verticalScrollBar->removeEventFilter(
this);
257 m_verticalScrollBar = vertical.scrollBar;
258 if (vertical.scrollBar) {
259 vertical.scrollBar->installEventFilter(
this);
260 m_verticalChangedConnection =
connect(vertical.attached, SIGNAL(verticalChanged()),
this, SLOT(_k_rebindScrollBars()));
264 if (m_horizontalScrollBar != horizontal.scrollBar) {
265 if (m_horizontalScrollBar) {
266 m_horizontalScrollBar->removeEventFilter(
this);
269 m_horizontalScrollBar = horizontal.scrollBar;
270 if (horizontal.scrollBar) {
271 horizontal.scrollBar->installEventFilter(
this);
272 m_horizontalChangedConnection =
connect(horizontal.attached, SIGNAL(horizontalChanged()),
this, SLOT(_k_rebindScrollBars()));
279 return m_verticalStepSize;
282void WheelHandler::setVerticalStepSize(qreal stepSize)
284 m_explicitVStepSize =
true;
285 if (qFuzzyCompare(m_verticalStepSize, stepSize)) {
289 if (qFuzzyIsNull(stepSize)) {
290 resetVerticalStepSize();
293 m_verticalStepSize = stepSize;
294 Q_EMIT verticalStepSizeChanged();
297void WheelHandler::resetVerticalStepSize()
299 m_explicitVStepSize =
false;
300 if (qFuzzyCompare(m_verticalStepSize, m_defaultPixelStepSize)) {
303 m_verticalStepSize = m_defaultPixelStepSize;
304 Q_EMIT verticalStepSizeChanged();
309 return m_horizontalStepSize;
312void WheelHandler::setHorizontalStepSize(qreal stepSize)
314 m_explicitHStepSize =
true;
315 if (qFuzzyCompare(m_horizontalStepSize, stepSize)) {
319 if (qFuzzyIsNull(stepSize)) {
320 resetHorizontalStepSize();
323 m_horizontalStepSize = stepSize;
324 Q_EMIT horizontalStepSizeChanged();
327void WheelHandler::resetHorizontalStepSize()
329 m_explicitHStepSize =
false;
330 if (qFuzzyCompare(m_horizontalStepSize, m_defaultPixelStepSize)) {
333 m_horizontalStepSize = m_defaultPixelStepSize;
334 Q_EMIT horizontalStepSizeChanged();
339 return m_pageScrollModifiers;
344 if (m_pageScrollModifiers == modifiers) {
347 m_pageScrollModifiers = modifiers;
348 Q_EMIT pageScrollModifiersChanged();
351void WheelHandler::resetPageScrollModifiers()
353 setPageScrollModifiers(m_defaultPageScrollModifiers);
358 return m_filterMouseEvents;
361void WheelHandler::setFilterMouseEvents(
bool enabled)
363 if (m_filterMouseEvents == enabled) {
366 m_filterMouseEvents = enabled;
367 Q_EMIT filterMouseEventsChanged();
372 return m_keyNavigationEnabled;
375void WheelHandler::setKeyNavigationEnabled(
bool enabled)
377 if (m_keyNavigationEnabled == enabled) {
380 m_keyNavigationEnabled = enabled;
381 Q_EMIT keyNavigationEnabledChanged();
384void WheelHandler::classBegin()
387 m_engine = qmlEngine(
this);
395void WheelHandler::componentComplete()
399void WheelHandler::setScrolling(
bool scrolling)
401 if (m_wheelScrolling == scrolling) {
402 if (m_wheelScrolling) {
403 m_wheelScrollingTimer.
start();
407 m_wheelScrolling = scrolling;
408 m_filterItem->setEnabled(m_wheelScrolling);
413 return m_primaryOrientation;
418 if(m_primaryOrientation == orientation)
423 m_primaryOrientation = orientation;
424 Q_EMIT primaryOrientationChanged();
430 if (!m_flickable || (pixelDelta.
isNull() && angleDelta.
isNull())) {
434 const qreal width = m_flickable->width();
435 const qreal height = m_flickable->height();
436 const qreal contentWidth = m_flickable->property(
"contentWidth").toReal();
437 const qreal contentHeight = m_flickable->property(
"contentHeight").toReal();
438 const qreal contentX = m_flickable->property(
"contentX").toReal();
439 const qreal contentY = m_flickable->property(
"contentY").toReal();
440 const qreal topMargin = m_flickable->property(
"topMargin").toReal();
441 const qreal bottomMargin = m_flickable->property(
"bottomMargin").toReal();
442 const qreal leftMargin = m_flickable->property(
"leftMargin").toReal();
443 const qreal rightMargin = m_flickable->property(
"rightMargin").toReal();
444 const qreal originX = m_flickable->property(
"originX").toReal();
445 const qreal originY = m_flickable->property(
"originY").toReal();
446 const qreal pageWidth = width - leftMargin - rightMargin;
447 const qreal pageHeight = height - topMargin - bottomMargin;
448 const auto window = m_flickable->window();
452 if (m_primaryOrientation == Qt::Orientation::Horizontal) {
457 const qreal xTicks = angleDelta.
x() / 120;
458 const qreal yTicks = angleDelta.
y() / 120;
461 bool scrolled =
false;
464 if (contentWidth > pageWidth) {
466 if (modifiers & m_pageScrollModifiers) {
467 xChange = qBound(-pageWidth, xTicks * pageWidth, pageWidth);
468 }
else if (pixelDelta.
x() != 0) {
469 xChange = pixelDelta.
x();
471 xChange = xTicks * m_horizontalStepSize;
476 qreal minXExtent = leftMargin - originX;
477 qreal maxXExtent = width - (contentWidth + rightMargin + originX);
479 qreal newContentX = qBound(-minXExtent, contentX - xChange, -maxXExtent);
485 newContentX = std::round(newContentX * devicePixelRatio) / devicePixelRatio;
486 if (contentX != newContentX) {
488 m_flickable->setProperty(
"contentX", newContentX);
493 if (contentHeight > pageHeight) {
494 if (modifiers & m_pageScrollModifiers) {
495 yChange = qBound(-pageHeight, yTicks * pageHeight, pageHeight);
496 }
else if (pixelDelta.
y() != 0) {
497 yChange = pixelDelta.
y();
499 yChange = yTicks * m_verticalStepSize;
504 qreal minYExtent = topMargin - originY;
505 qreal maxYExtent = height - (contentHeight + bottomMargin + originY);
507 qreal newContentY = qBound(-minYExtent, contentY - yChange, -maxYExtent);
513 newContentY = std::round(newContentY * devicePixelRatio) / devicePixelRatio;
514 if (contentY != newContentY) {
516 m_flickable->setProperty(
"contentY", newContentY);
525 if (qFuzzyIsNull(stepSize)) {
527 }
else if (stepSize < 0) {
528 stepSize = m_verticalStepSize;
531 return scrollFlickable(
QPointF(0, stepSize));
536 if (qFuzzyIsNull(stepSize)) {
538 }
else if (stepSize < 0) {
539 stepSize = m_verticalStepSize;
542 return scrollFlickable(
QPointF(0, -stepSize));
547 if (qFuzzyIsNull(stepSize)) {
549 }
else if (stepSize < 0) {
550 stepSize = m_horizontalStepSize;
553 return scrollFlickable(
QPoint(stepSize, 0));
558 if (qFuzzyIsNull(stepSize)) {
560 }
else if (stepSize < 0) {
561 stepSize = m_horizontalStepSize;
564 return scrollFlickable(
QPoint(-stepSize, 0));
567bool WheelHandler::eventFilter(
QObject *watched,
QEvent *event)
570 if (!item || !item->isEnabled()) {
574 qreal contentWidth = 0;
575 qreal contentHeight = 0;
577 qreal pageHeight = 0;
579 contentWidth = m_flickable->property(
"contentWidth").toReal();
580 contentHeight = m_flickable->property(
"contentHeight").toReal();
581 pageWidth = m_flickable->width() - m_flickable->property(
"leftMargin").toReal() - m_flickable->property(
"rightMargin").toReal();
582 pageHeight = m_flickable->height() - m_flickable->property(
"topMargin").toReal() - m_flickable->property(
"bottomMargin").toReal();
586 switch (
event->type()) {
589 if (m_filterMouseEvents) {
590 if (m_verticalScrollBar) {
591 m_verticalScrollBar->setProperty(
"interactive",
true);
593 if (m_horizontalScrollBar) {
594 m_horizontalScrollBar->setProperty(
"interactive",
true);
602 m_wasTouched = std::abs(wheelEvent->
angleDelta().
y()) != 120 && std::abs(wheelEvent->
angleDelta().
x()) != 120;
617 m_kirigamiWheelEvent.initializeFromEvent(&newWheelEvent);
619 m_kirigamiWheelEvent.initializeFromEvent(wheelEvent);
624 if (m_kirigamiWheelEvent.isAccepted()) {
628 bool scrolled =
false;
629 if (m_scrollFlickableTarget || (contentHeight <= pageHeight && contentWidth <= pageWidth)) {
635 setScrolling(scrolled);
640 return scrolled || m_blockTargetWheel || flickableWillUseGestureScrolling;
645 if (!m_filterMouseEvents) {
648 if (m_verticalScrollBar) {
649 m_verticalScrollBar->setProperty(
"interactive",
false);
651 if (m_horizontalScrollBar) {
652 m_horizontalScrollBar->setProperty(
"interactive",
false);
658 m_wasTouched =
false;
665 if (!m_filterMouseEvents) {
669 if (m_verticalScrollBar) {
670 m_verticalScrollBar->setProperty(
"interactive",
true);
672 if (m_horizontalScrollBar) {
673 m_horizontalScrollBar->setProperty(
"interactive",
true);
677 return !m_wasTouched && item == m_flickable;
683 if (!m_filterMouseEvents) {
694 if (!m_filterMouseEvents) {
697 if (m_wasTouched && (item == m_verticalScrollBar || item == m_horizontalScrollBar)) {
698 if (m_verticalScrollBar) {
699 m_verticalScrollBar->setProperty(
"interactive",
true);
701 if (m_horizontalScrollBar) {
702 m_horizontalScrollBar->setProperty(
"interactive",
true);
709 if (!m_keyNavigationEnabled) {
713 bool horizontalScroll =
keyEvent->modifiers() & m_defaultHorizontalScrollModifiers;
QML_ELEMENTqreal x
x: real
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)
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