7#include "wheelhandler.h"
12#include <QQuickWindow>
15#include "platform/units.h"
17KirigamiWheelEvent::KirigamiWheelEvent(
QObject *parent)
22KirigamiWheelEvent::~KirigamiWheelEvent()
26void KirigamiWheelEvent::initializeFromEvent(
QWheelEvent *event)
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();
35 m_inverted =
event->inverted();
73bool KirigamiWheelEvent::isAccepted()
78void KirigamiWheelEvent::setAccepted(
bool accepted)
85WheelFilterItem::WheelFilterItem(
QQuickItem *parent)
93WheelHandler::WheelHandler(
QObject *parent)
95 , m_filterItem(new WheelFilterItem(nullptr))
99 m_wheelScrollingTimer.setSingleShot(
true);
100 m_wheelScrollingTimer.setInterval(m_wheelScrollingDuration);
101 m_wheelScrollingTimer.callOnTimeout([
this]() {
108 m_defaultPixelStepSize = 20 * scrollLines;
109 if (!m_explicitVStepSize && m_verticalStepSize != m_defaultPixelStepSize) {
110 m_verticalStepSize = m_defaultPixelStepSize;
111 Q_EMIT verticalStepSizeChanged();
113 if (!m_explicitHStepSize && m_horizontalStepSize != m_defaultPixelStepSize) {
114 m_horizontalStepSize = m_defaultPixelStepSize;
115 Q_EMIT horizontalStepSizeChanged();
120WheelHandler::~WheelHandler()
130void WheelHandler::setTarget(
QQuickItem *target)
132 if (m_flickable ==
target) {
137 qmlWarning(
this) <<
"target must be a QQuickFlickable";
142 m_flickable->removeEventFilter(
this);
143 disconnect(m_flickable,
nullptr, m_filterItem,
nullptr);
148 m_filterItem->setParentItem(
target);
150 m_yScrollAnimation.
stop();
155 target->installEventFilter(
this);
158 m_filterItem->stackAfter(
target->property(
"contentItem").value<
QQuickItem *>());
160 m_filterItem->setWidth(
target->width());
161 m_filterItem->setHeight(
target->height());
163 m_filterItem->setWidth(
target->width());
166 m_filterItem->setHeight(
target->height());
170 _k_rebindScrollBars();
175void WheelHandler::_k_rebindScrollBars()
177 struct ScrollBarAttached {
183 ScrollBarAttached attachedToFlickable;
184 ScrollBarAttached attachedToScrollView;
189 const auto flickableChildren = m_flickable->children();
190 for (
const auto child : flickableChildren) {
191 if (child->inherits(
"QQuickScrollBarAttached")) {
192 attachedToFlickable.attached = child;
193 attachedToFlickable.vertical = child->property(
"vertical").value<
QQuickItem *>();
194 attachedToFlickable.horizontal = child->property(
"horizontal").value<
QQuickItem *>();
202 auto flickableParent = m_flickable->parentItem();
203 if (flickableParent && flickableParent->inherits(
"QQuickScrollView")) {
204 const auto siblings = flickableParent->children();
205 for (
const auto child : siblings) {
206 if (child->inherits(
"QQuickScrollBarAttached")) {
207 attachedToScrollView.attached = child;
208 attachedToScrollView.vertical = child->property(
"vertical").value<
QQuickItem *>();
209 attachedToScrollView.horizontal = child->property(
"horizontal").value<
QQuickItem *>();
219 struct ChosenScrollBar {
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;
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;
246 if (attachedToFlickable.horizontal && attachedToFlickable.vertical) {
255 if (m_verticalScrollBar != vertical.scrollBar) {
256 if (m_verticalScrollBar) {
257 m_verticalScrollBar->removeEventFilter(
this);
260 m_verticalScrollBar = vertical.scrollBar;
261 if (vertical.scrollBar) {
262 vertical.scrollBar->installEventFilter(
this);
263 m_verticalChangedConnection =
connect(vertical.attached, SIGNAL(verticalChanged()),
this, SLOT(_k_rebindScrollBars()));
267 if (m_horizontalScrollBar != horizontal.scrollBar) {
268 if (m_horizontalScrollBar) {
269 m_horizontalScrollBar->removeEventFilter(
this);
272 m_horizontalScrollBar = horizontal.scrollBar;
273 if (horizontal.scrollBar) {
274 horizontal.scrollBar->installEventFilter(
this);
275 m_horizontalChangedConnection =
connect(horizontal.attached, SIGNAL(horizontalChanged()),
this, SLOT(_k_rebindScrollBars()));
282 return m_verticalStepSize;
285void WheelHandler::setVerticalStepSize(qreal stepSize)
287 m_explicitVStepSize =
true;
288 if (qFuzzyCompare(m_verticalStepSize, stepSize)) {
292 if (qFuzzyIsNull(stepSize)) {
293 resetVerticalStepSize();
296 m_verticalStepSize = stepSize;
297 Q_EMIT verticalStepSizeChanged();
300void WheelHandler::resetVerticalStepSize()
302 m_explicitVStepSize =
false;
303 if (qFuzzyCompare(m_verticalStepSize, m_defaultPixelStepSize)) {
306 m_verticalStepSize = m_defaultPixelStepSize;
307 Q_EMIT verticalStepSizeChanged();
312 return m_horizontalStepSize;
315void WheelHandler::setHorizontalStepSize(qreal stepSize)
317 m_explicitHStepSize =
true;
318 if (qFuzzyCompare(m_horizontalStepSize, stepSize)) {
322 if (qFuzzyIsNull(stepSize)) {
323 resetHorizontalStepSize();
326 m_horizontalStepSize = stepSize;
327 Q_EMIT horizontalStepSizeChanged();
330void WheelHandler::resetHorizontalStepSize()
332 m_explicitHStepSize =
false;
333 if (qFuzzyCompare(m_horizontalStepSize, m_defaultPixelStepSize)) {
336 m_horizontalStepSize = m_defaultPixelStepSize;
337 Q_EMIT horizontalStepSizeChanged();
342 return m_pageScrollModifiers;
347 if (m_pageScrollModifiers == modifiers) {
350 m_pageScrollModifiers = modifiers;
351 Q_EMIT pageScrollModifiersChanged();
354void WheelHandler::resetPageScrollModifiers()
356 setPageScrollModifiers(m_defaultPageScrollModifiers);
361 return m_filterMouseEvents;
364void WheelHandler::setFilterMouseEvents(
bool enabled)
366 if (m_filterMouseEvents == enabled) {
369 m_filterMouseEvents = enabled;
370 Q_EMIT filterMouseEventsChanged();
375 return m_keyNavigationEnabled;
378void WheelHandler::setKeyNavigationEnabled(
bool enabled)
380 if (m_keyNavigationEnabled == enabled) {
383 m_keyNavigationEnabled = enabled;
384 Q_EMIT keyNavigationEnabledChanged();
387void WheelHandler::classBegin()
390 m_engine = qmlEngine(
this);
392 m_yScrollAnimation.
setDuration(units->longDuration());
393 connect(units, &Kirigami::Platform::Units::longDurationChanged,
this, [
this] {
398void WheelHandler::componentComplete()
402void WheelHandler::setScrolling(
bool scrolling)
404 if (m_wheelScrolling == scrolling) {
405 if (m_wheelScrolling) {
406 m_wheelScrollingTimer.
start();
410 m_wheelScrolling = scrolling;
411 m_filterItem->setEnabled(m_wheelScrolling);
416 if (!m_flickable || (pixelDelta.
isNull() && angleDelta.
isNull())) {
420 const qreal width = m_flickable->width();
421 const qreal height = m_flickable->height();
422 const qreal contentWidth = m_flickable->property(
"contentWidth").toReal();
423 const qreal contentHeight = m_flickable->property(
"contentHeight").toReal();
424 const qreal contentX = m_flickable->property(
"contentX").toReal();
425 const qreal contentY = m_flickable->property(
"contentY").toReal();
426 const qreal topMargin = m_flickable->property(
"topMargin").toReal();
427 const qreal bottomMargin = m_flickable->property(
"bottomMargin").toReal();
428 const qreal leftMargin = m_flickable->property(
"leftMargin").toReal();
429 const qreal rightMargin = m_flickable->property(
"rightMargin").toReal();
430 const qreal originX = m_flickable->property(
"originX").toReal();
431 const qreal originY = m_flickable->property(
"originY").toReal();
432 const qreal pageWidth = width - leftMargin - rightMargin;
433 const qreal pageHeight = height - topMargin - bottomMargin;
434 const auto window = m_flickable->window();
438 if (modifiers & m_defaultHorizontalScrollModifiers && qGuiApp->platformName() !=
QLatin1String(
"xcb")) {
443 const qreal xTicks = angleDelta.
x() / 120;
444 const qreal yTicks = angleDelta.
y() / 120;
447 bool scrolled =
false;
450 if (contentWidth > pageWidth) {
452 if (modifiers & m_pageScrollModifiers) {
453 xChange = qBound(-pageWidth, xTicks * pageWidth, pageWidth);
454 }
else if (pixelDelta.
x() != 0) {
455 xChange = pixelDelta.
x();
457 xChange = xTicks * m_horizontalStepSize;
462 qreal minXExtent = leftMargin - originX;
463 qreal maxXExtent = width - (contentWidth + rightMargin + originX);
465 qreal newContentX = qBound(-minXExtent, contentX - xChange, -maxXExtent);
471 newContentX = std::round(newContentX * devicePixelRatio) / devicePixelRatio;
472 if (contentX != newContentX) {
474 m_flickable->setProperty(
"contentX", newContentX);
479 if (contentHeight > pageHeight) {
480 if (modifiers & m_pageScrollModifiers) {
481 yChange = qBound(-pageHeight, yTicks * pageHeight, pageHeight);
482 }
else if (pixelDelta.
y() != 0) {
483 yChange = pixelDelta.
y();
485 yChange = yTicks * m_verticalStepSize;
490 qreal minYExtent = topMargin - originY;
491 qreal maxYExtent = height - (contentHeight + bottomMargin + originY);
495 m_yScrollAnimation.
stop();
496 newContentY = std::clamp(m_yScrollAnimation.
endValue().toReal() + -yChange, -minYExtent, -maxYExtent);
498 newContentY = std::clamp(contentY - yChange, -minYExtent, -maxYExtent);
506 newContentY = std::round(newContentY * devicePixelRatio) / devicePixelRatio;
507 if (contentY != newContentY) {
509 if (m_wasTouched || !m_engine) {
510 m_flickable->setProperty(
"contentY", newContentY);
523 if (qFuzzyIsNull(stepSize)) {
525 }
else if (stepSize < 0) {
526 stepSize = m_verticalStepSize;
529 return scrollFlickable(
QPointF(0, stepSize));
534 if (qFuzzyIsNull(stepSize)) {
536 }
else if (stepSize < 0) {
537 stepSize = m_verticalStepSize;
540 return scrollFlickable(
QPointF(0, -stepSize));
545 if (qFuzzyIsNull(stepSize)) {
547 }
else if (stepSize < 0) {
548 stepSize = m_horizontalStepSize;
551 return scrollFlickable(
QPoint(stepSize, 0));
556 if (qFuzzyIsNull(stepSize)) {
558 }
else if (stepSize < 0) {
559 stepSize = m_horizontalStepSize;
562 return scrollFlickable(
QPoint(-stepSize, 0));
565bool WheelHandler::eventFilter(
QObject *watched,
QEvent *event)
567 auto item = qobject_cast<QQuickItem *>(watched);
568 if (!item || !item->isEnabled()) {
572 qreal contentWidth = 0;
573 qreal contentHeight = 0;
575 qreal pageHeight = 0;
577 contentWidth = m_flickable->property(
"contentWidth").toReal();
578 contentHeight = m_flickable->property(
"contentHeight").toReal();
579 pageWidth = m_flickable->width() - m_flickable->property(
"leftMargin").toReal() - m_flickable->property(
"rightMargin").toReal();
580 pageHeight = m_flickable->height() - m_flickable->property(
"topMargin").toReal() - m_flickable->property(
"bottomMargin").toReal();
584 switch (
event->type()) {
587 if (m_filterMouseEvents) {
588 if (m_verticalScrollBar) {
589 m_verticalScrollBar->setProperty(
"interactive",
true);
591 if (m_horizontalScrollBar) {
592 m_horizontalScrollBar->setProperty(
"interactive",
true);
603 m_wasTouched = (std::abs(wheelEvent->
angleDelta().
y()) != 0 && std::abs(wheelEvent->
angleDelta().
y()) % 120 != 0);
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;
744#include "moc_wheelhandler.cpp"
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)
Q_INVOKABLE bool scrollUp(qreal stepSize=-1)
Q_INVOKABLE bool scrollLeft(qreal stepSize=-1)
void start(QAbstractAnimation::DeletionPolicy policy)
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)
QObject * sender() const const
qreal devicePixelRatio() const const
bool isNull() const const
bool isNull() const const
QPointF transposed() const const
T singletonInstance(QAnyStringView uri, QAnyStringView typeName)
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