2 * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
4 * SPDX-License-Identifier: GPL-2.0-or-later
7 #include "kitemlistsmoothscroller.h"
9 #include <QApplication>
10 #include <QPropertyAnimation>
13 #include <QWheelEvent>
15 KItemListSmoothScroller::KItemListSmoothScroller(QScrollBar
* scrollBar
,
18 m_scrollBarPressed(false),
19 m_smoothScrolling(true),
20 m_scrollBar(scrollBar
),
23 m_animation
= new QPropertyAnimation(this);
24 const int animationDuration
= m_scrollBar
->style()->styleHint(QStyle::SH_Widget_Animation_Duration
, nullptr, m_scrollBar
);
25 const bool animationEnabled
= (animationDuration
> 0);
26 m_animation
->setDuration(animationEnabled
? animationDuration
: 1);
27 connect(m_animation
, &QPropertyAnimation::stateChanged
,
28 this, &KItemListSmoothScroller::slotAnimationStateChanged
);
30 m_scrollBar
->installEventFilter(this);
33 KItemListSmoothScroller::~KItemListSmoothScroller()
37 void KItemListSmoothScroller::setScrollBar(QScrollBar
*scrollBar
)
39 m_scrollBar
= scrollBar
;
42 QScrollBar
* KItemListSmoothScroller::scrollBar() const
47 void KItemListSmoothScroller::setTargetObject(QObject
* target
)
49 m_animation
->setTargetObject(target
);
52 QObject
* KItemListSmoothScroller::targetObject() const
54 return m_animation
->targetObject();
57 void KItemListSmoothScroller::setPropertyName(const QByteArray
& propertyName
)
59 m_animation
->setPropertyName(propertyName
);
62 QByteArray
KItemListSmoothScroller::propertyName() const
64 return m_animation
->propertyName();
67 void KItemListSmoothScroller::scrollContentsBy(qreal distance
)
69 QObject
* target
= targetObject();
74 const QByteArray name
= propertyName();
75 const qreal currentOffset
= target
->property(name
).toReal();
76 if (static_cast<int>(currentOffset
) == m_scrollBar
->value()) {
77 // The current offset is already synchronous to the scrollbar
81 const bool animRunning
= (m_animation
->state() == QAbstractAnimation::Running
);
83 // Stopping a running animation means skipping the range from the current offset
84 // until the target offset. To prevent skipping of the range the difference
85 // is added to the new target offset.
86 const qreal oldEndOffset
= m_animation
->endValue().toReal();
87 distance
+= (currentOffset
- oldEndOffset
);
90 const qreal endOffset
= currentOffset
- distance
;
91 if (m_smoothScrolling
|| animRunning
) {
92 qreal startOffset
= currentOffset
;
94 // If the animation was running and has been interrupted by assigning a new end-offset
95 // one frame must be added to the start-offset to keep the animation smooth. This also
96 // assures that animation proceeds even in cases where new end-offset are triggered
97 // within a very short timeslots.
98 startOffset
+= (endOffset
- currentOffset
) * 1000 / (m_animation
->duration() * 60);
99 if (currentOffset
< endOffset
) {
100 startOffset
= qMin(startOffset
, endOffset
);
102 startOffset
= qMax(startOffset
, endOffset
);
107 m_animation
->setStartValue(startOffset
);
108 m_animation
->setEndValue(endOffset
);
109 m_animation
->setEasingCurve(animRunning
? QEasingCurve::OutQuad
: QEasingCurve::InOutQuad
);
110 m_animation
->start();
111 target
->setProperty(name
, startOffset
);
113 target
->setProperty(name
, endOffset
);
117 void KItemListSmoothScroller::scrollTo(qreal position
)
119 int newValue
= position
;
120 newValue
= qBound(0, newValue
, m_scrollBar
->maximum());
122 if (newValue
!= m_scrollBar
->value()) {
123 m_smoothScrolling
= true;
124 m_scrollBar
->setValue(newValue
);
128 bool KItemListSmoothScroller::requestScrollBarUpdate(int newMaximum
)
130 if (m_animation
->state() == QAbstractAnimation::Running
) {
131 if (newMaximum
== m_scrollBar
->maximum()) {
132 // The value has been changed by the animation, no update
133 // of the scrollbars is required as their target state will be
134 // reached with the end of the animation.
138 // The maximum has been changed which indicates that the content
139 // of the view has been changed. Stop the animation in any case and
140 // update the scrollbars immediately.
146 bool KItemListSmoothScroller::eventFilter(QObject
* obj
, QEvent
* event
)
148 Q_ASSERT(obj
== m_scrollBar
);
150 switch (event
->type()) {
151 case QEvent::MouseButtonPress
:
152 m_scrollBarPressed
= true;
153 m_smoothScrolling
= true;
156 case QEvent::MouseButtonRelease
:
157 m_scrollBarPressed
= false;
158 m_smoothScrolling
= false;
162 return false; // we're the ones sending them
168 return QObject::eventFilter(obj
, event
);
171 void KItemListSmoothScroller::slotAnimationStateChanged(QAbstractAnimation::State newState
,
172 QAbstractAnimation::State oldState
)
175 if (newState
== QAbstractAnimation::Stopped
&& m_smoothScrolling
&& !m_scrollBarPressed
) {
176 m_smoothScrolling
= false;
178 if (newState
== QAbstractAnimation::Stopped
) {
179 Q_EMIT
scrollingStopped();
183 void KItemListSmoothScroller::handleWheelEvent(QWheelEvent
* event
)
185 const bool previous
= m_smoothScrolling
;
187 m_smoothScrolling
= true;
189 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
190 QWheelEvent
*copy
= event
->clone();
191 QApplication::sendEvent(m_scrollBar
, copy
);
192 event
->setAccepted(copy
->isAccepted());
194 QWheelEvent copy
= *event
;
195 QApplication::sendEvent(m_scrollBar
, ©
);
196 event
->setAccepted(copy
.isAccepted());
199 m_smoothScrolling
= previous
;