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]() {
107 m_defaultPixelStepSize = 20 * scrollLines;
108 if (!m_explicitVStepSize && m_verticalStepSize != m_defaultPixelStepSize) {
109 m_verticalStepSize = m_defaultPixelStepSize;
110 Q_EMIT verticalStepSizeChanged();
112 if (!m_explicitHStepSize && m_horizontalStepSize != m_defaultPixelStepSize) {
113 m_horizontalStepSize = m_defaultPixelStepSize;
114 Q_EMIT horizontalStepSizeChanged();
119WheelHandler::~WheelHandler()
129void WheelHandler::setTarget(
QQuickItem *target)
131 if (m_flickable ==
target) {
136 qmlWarning(
this) <<
"target must be a QQuickFlickable";
141 m_flickable->removeEventFilter(
this);
142 disconnect(m_flickable,
nullptr, m_filterItem,
nullptr);
147 m_filterItem->setParentItem(
target);
148 if (m_xScrollAnimation.targetObject()) {
149 m_xScrollAnimation.stop();
151 m_xScrollAnimation.setTargetObject(
target);
152 if (m_yScrollAnimation.targetObject()) {
153 m_yScrollAnimation.stop();
155 m_yScrollAnimation.setTargetObject(
target);
158 target->installEventFilter(
this);
161 m_filterItem->stackAfter(
target->property(
"contentItem").value<QQuickItem *>());
163 m_filterItem->setWidth(
target->width());
164 m_filterItem->setHeight(
target->height());
166 m_filterItem->setWidth(
target->width());
169 m_filterItem->setHeight(
target->height());
173 _k_rebindScrollBars();
178void WheelHandler::_k_rebindScrollBars()
180 struct ScrollBarAttached {
181 QObject *attached =
nullptr;
182 QQuickItem *vertical =
nullptr;
183 QQuickItem *horizontal =
nullptr;
186 ScrollBarAttached attachedToFlickable;
187 ScrollBarAttached attachedToScrollView;
192 const auto flickableChildren = m_flickable->children();
193 for (
const auto child : flickableChildren) {
194 if (child->inherits(
"QQuickScrollBarAttached")) {
195 attachedToFlickable.attached = child;
196 attachedToFlickable.vertical = child->property(
"vertical").value<QQuickItem *>();
197 attachedToFlickable.horizontal = child->property(
"horizontal").value<QQuickItem *>();
205 auto flickableParent = m_flickable->parentItem();
206 if (flickableParent && flickableParent->inherits(
"QQuickScrollView")) {
207 const auto siblings = flickableParent->children();
208 for (
const auto child : siblings) {
209 if (child->inherits(
"QQuickScrollBarAttached")) {
210 attachedToScrollView.attached = child;
211 attachedToScrollView.vertical = child->property(
"vertical").value<QQuickItem *>();
212 attachedToScrollView.horizontal = child->property(
"horizontal").value<QQuickItem *>();
222 struct ChosenScrollBar {
223 QObject *attached =
nullptr;
224 QQuickItem *scrollBar =
nullptr;
227 ChosenScrollBar vertical;
228 if (attachedToFlickable.vertical) {
229 vertical.attached = attachedToFlickable.attached;
230 vertical.scrollBar = attachedToFlickable.vertical;
231 }
else if (attachedToScrollView.vertical) {
232 vertical.attached = attachedToScrollView.attached;
233 vertical.scrollBar = attachedToScrollView.vertical;
236 ChosenScrollBar horizontal;
237 if (attachedToFlickable.horizontal) {
238 horizontal.attached = attachedToFlickable.attached;
239 horizontal.scrollBar = attachedToFlickable.horizontal;
240 }
else if (attachedToScrollView.horizontal) {
241 horizontal.attached = attachedToScrollView.attached;
242 horizontal.scrollBar = attachedToScrollView.horizontal;
249 if (attachedToFlickable.horizontal && attachedToFlickable.vertical) {
258 if (m_verticalScrollBar != vertical.scrollBar) {
259 if (m_verticalScrollBar) {
260 m_verticalScrollBar->removeEventFilter(
this);
263 m_verticalScrollBar = vertical.scrollBar;
264 if (vertical.scrollBar) {
265 vertical.scrollBar->installEventFilter(
this);
266 m_verticalChangedConnection =
connect(vertical.attached, SIGNAL(verticalChanged()),
this, SLOT(_k_rebindScrollBars()));
270 if (m_horizontalScrollBar != horizontal.scrollBar) {
271 if (m_horizontalScrollBar) {
272 m_horizontalScrollBar->removeEventFilter(
this);
275 m_horizontalScrollBar = horizontal.scrollBar;
276 if (horizontal.scrollBar) {
277 horizontal.scrollBar->installEventFilter(
this);
278 m_horizontalChangedConnection =
connect(horizontal.attached, SIGNAL(horizontalChanged()),
this, SLOT(_k_rebindScrollBars()));
285 return m_verticalStepSize;
288void WheelHandler::setVerticalStepSize(qreal stepSize)
290 m_explicitVStepSize =
true;
291 if (qFuzzyCompare(m_verticalStepSize, stepSize)) {
295 if (qFuzzyIsNull(stepSize)) {
296 resetVerticalStepSize();
299 m_verticalStepSize = stepSize;
300 Q_EMIT verticalStepSizeChanged();
303void WheelHandler::resetVerticalStepSize()
305 m_explicitVStepSize =
false;
306 if (qFuzzyCompare(m_verticalStepSize, m_defaultPixelStepSize)) {
309 m_verticalStepSize = m_defaultPixelStepSize;
310 Q_EMIT verticalStepSizeChanged();
315 return m_horizontalStepSize;
318void WheelHandler::setHorizontalStepSize(qreal stepSize)
320 m_explicitHStepSize =
true;
321 if (qFuzzyCompare(m_horizontalStepSize, stepSize)) {
325 if (qFuzzyIsNull(stepSize)) {
326 resetHorizontalStepSize();
329 m_horizontalStepSize = stepSize;
330 Q_EMIT horizontalStepSizeChanged();
333void WheelHandler::resetHorizontalStepSize()
335 m_explicitHStepSize =
false;
336 if (qFuzzyCompare(m_horizontalStepSize, m_defaultPixelStepSize)) {
339 m_horizontalStepSize = m_defaultPixelStepSize;
340 Q_EMIT horizontalStepSizeChanged();
345 return m_pageScrollModifiers;
350 if (m_pageScrollModifiers == modifiers) {
353 m_pageScrollModifiers = modifiers;
354 Q_EMIT pageScrollModifiersChanged();
357void WheelHandler::resetPageScrollModifiers()
359 setPageScrollModifiers(m_defaultPageScrollModifiers);
364 return m_filterMouseEvents;
367void WheelHandler::setFilterMouseEvents(
bool enabled)
369 if (m_filterMouseEvents == enabled) {
372 m_filterMouseEvents = enabled;
373 Q_EMIT filterMouseEventsChanged();
378 return m_keyNavigationEnabled;
381void WheelHandler::setKeyNavigationEnabled(
bool enabled)
383 if (m_keyNavigationEnabled == enabled) {
386 m_keyNavigationEnabled = enabled;
387 Q_EMIT keyNavigationEnabledChanged();
390void WheelHandler::classBegin()
393 m_engine = qmlEngine(
this);
394 m_units = m_engine->singletonInstance<Kirigami::Platform::Units *>(
"org.kde.kirigami.platform",
"Units");
395 m_settings = m_engine->singletonInstance<Kirigami::Platform::Settings *>(
"org.kde.kirigami.platform",
"Settings");
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();
437 const qreal refreshRate = screen ? screen->refreshRate() : 0;
440 if (modifiers & m_defaultHorizontalScrollModifiers && qGuiApp->platformName() != QLatin1String(
"xcb")) {
445 const qreal xTicks = angleDelta.
x() / 120;
446 const qreal yTicks = angleDelta.
y() / 120;
447 bool scrolled =
false;
449 auto getChange = [
pageScrollModifiers = modifiers & m_pageScrollModifiers](qreal ticks, qreal pixelDelta, qreal stepSize, qreal
pageSize) {
452 return qBound(-pageSize, ticks * pageSize, pageSize);
453 }
else if (pixelDelta != 0) {
456 return ticks * stepSize;
460 auto getPosition = [devicePixelRatio](qreal size,
466 qreal trailingMargin,
468 const QPropertyAnimation &animation) {
469 if (contentSize <= pageSize) {
475 qreal minExtent = leadingMargin - originPos;
476 qreal maxExtent = size - (contentSize + trailingMargin + originPos);
478 qreal newContentPos =
479 std::clamp((animation.state() ==
QPropertyAnimation::Running ? animation.endValue().toReal() : contentPos) - change, -minExtent, -maxExtent);
486 return std::round(newContentPos * devicePixelRatio) / devicePixelRatio;
489 auto setPosition = [
this, devicePixelRatio, refreshRate](qreal oldPos, qreal newPos, qreal stepSize,
const char *
property, QPropertyAnimation &animation) {
491 if (oldPos == newPos) {
494 if (!m_settings->smoothScroll() || !m_engine || refreshRate <= 0) {
495 animation.setDuration(0);
496 m_flickable->setProperty(
property, newPos);
519 qreal absPixelDelta = std::abs(newPos - oldPos);
520 int duration = absPixelDelta * devicePixelRatio > 2
521 ? std::clamp(qRound(absPixelDelta * m_units->longDuration() / stepSize), qCeil(1000.0 / 60.0 * 3), m_units->longDuration())
523 animation.setDuration(duration <= qCeil(1000.0 / refreshRate * 2) ? 0 : duration);
524 if (animation.duration() > 0) {
525 animation.setEndValue(newPos);
528 m_flickable->setProperty(
property, newPos);
533 qreal xChange = getChange(xTicks, pixelDelta.x(), m_horizontalStepSize, pageWidth);
534 qreal newContentX = getPosition(width, contentWidth, contentX, originX, pageWidth, leftMargin, rightMargin, xChange, m_xScrollAnimation);
536 qreal yChange = getChange(yTicks, pixelDelta.y(), m_verticalStepSize, pageHeight);
537 qreal newContentY = getPosition(height, contentHeight, contentY, originY, pageHeight, topMargin, bottomMargin, yChange, m_yScrollAnimation);
540 scrolled |= setPosition(contentX, newContentX, m_horizontalStepSize,
"contentX", m_xScrollAnimation);
541 scrolled |= setPosition(contentY, newContentY, m_verticalStepSize,
"contentY", m_yScrollAnimation);
548 if (qFuzzyIsNull(stepSize)) {
550 }
else if (stepSize < 0) {
551 stepSize = m_verticalStepSize;
554 return scrollFlickable(
QPointF(0, stepSize));
559 if (qFuzzyIsNull(stepSize)) {
561 }
else if (stepSize < 0) {
562 stepSize = m_verticalStepSize;
565 return scrollFlickable(
QPointF(0, -stepSize));
570 if (qFuzzyIsNull(stepSize)) {
572 }
else if (stepSize < 0) {
573 stepSize = m_horizontalStepSize;
576 return scrollFlickable(
QPoint(stepSize, 0));
581 if (qFuzzyIsNull(stepSize)) {
583 }
else if (stepSize < 0) {
584 stepSize = m_horizontalStepSize;
587 return scrollFlickable(
QPoint(-stepSize, 0));
590bool WheelHandler::eventFilter(
QObject *watched,
QEvent *event)
593 if (!item || !item->isEnabled()) {
597 qreal contentWidth = 0;
598 qreal contentHeight = 0;
600 qreal pageHeight = 0;
602 contentWidth = m_flickable->property(
"contentWidth").toReal();
603 contentHeight = m_flickable->property(
"contentHeight").toReal();
604 pageWidth = m_flickable->width() - m_flickable->property(
"leftMargin").toReal() - m_flickable->property(
"rightMargin").toReal();
605 pageHeight = m_flickable->height() - m_flickable->property(
"topMargin").toReal() - m_flickable->property(
"bottomMargin").toReal();
609 switch (
event->type()) {
612 if (m_filterMouseEvents) {
613 if (m_verticalScrollBar) {
614 m_verticalScrollBar->setProperty(
"interactive",
true);
616 if (m_horizontalScrollBar) {
617 m_horizontalScrollBar->setProperty(
"interactive",
true);
620 QWheelEvent *wheelEvent =
static_cast<QWheelEvent *
>(
event);
626 QWheelEvent newWheelEvent(wheelEvent->
position(),
635 m_kirigamiWheelEvent.initializeFromEvent(&newWheelEvent);
637 m_kirigamiWheelEvent.initializeFromEvent(wheelEvent);
642 if (m_kirigamiWheelEvent.isAccepted()) {
646 bool scrolled =
false;
647 if (m_scrollFlickableTarget || (contentHeight <= pageHeight && contentWidth <= pageWidth)) {
650 QPointF pixelDelta = m_kirigamiWheelEvent.angleDelta().isNull() ? m_kirigamiWheelEvent.pixelDelta() : QPoint(0, 0);
651 scrolled = scrollFlickable(pixelDelta, m_kirigamiWheelEvent.angleDelta(),
Qt::KeyboardModifiers(m_kirigamiWheelEvent.modifiers()));
653 setScrolling(scrolled);
658 return scrolled || m_blockTargetWheel || flickableWillUseGestureScrolling;
663 if (!m_filterMouseEvents) {
666 if (m_verticalScrollBar) {
667 m_verticalScrollBar->setProperty(
"interactive",
false);
669 if (m_horizontalScrollBar) {
670 m_horizontalScrollBar->setProperty(
"interactive",
false);
676 m_wasTouched =
false;
683 if (!m_filterMouseEvents) {
687 if (m_verticalScrollBar) {
688 m_verticalScrollBar->setProperty(
"interactive",
true);
690 if (m_horizontalScrollBar) {
691 m_horizontalScrollBar->setProperty(
"interactive",
true);
695 return !m_wasTouched && item == m_flickable;
701 if (!m_filterMouseEvents) {
712 if (!m_filterMouseEvents) {
715 if (m_wasTouched && (item == m_verticalScrollBar || item == m_horizontalScrollBar)) {
716 if (m_verticalScrollBar) {
717 m_verticalScrollBar->setProperty(
"interactive",
true);
719 if (m_horizontalScrollBar) {
720 m_horizontalScrollBar->setProperty(
"interactive",
true);
727 if (!m_keyNavigationEnabled) {
731 bool horizontalScroll =
keyEvent->modifiers() & m_defaultHorizontalScrollModifiers;
762#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.
KGUIADDONS_EXPORT QWindow * window(QObject *job)
KREPORT_EXPORT QPageSize::PageSizeId pageSize(const QString &key)
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)
QVariant property(const char *name) const const
T qobject_cast(QObject *object)
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
qreal devicePixelRatio() const const
QScreen * screen() const const