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
, QObject
*parent
)
17 , m_scrollBarPressed(false)
18 , m_smoothScrolling(true)
19 , m_scrollBar(scrollBar
)
20 , m_animation(nullptr)
22 m_animation
= new QPropertyAnimation(this);
23 const int animationDuration
= m_scrollBar
->style()->styleHint(QStyle::SH_Widget_Animation_Duration
, nullptr, m_scrollBar
);
24 const bool animationEnabled
= (animationDuration
> 0);
25 m_animation
->setDuration(animationEnabled
? animationDuration
: 1);
26 connect(m_animation
, &QPropertyAnimation::stateChanged
, this, &KItemListSmoothScroller::slotAnimationStateChanged
);
28 m_scrollBar
->installEventFilter(this);
31 KItemListSmoothScroller::~KItemListSmoothScroller()
35 void KItemListSmoothScroller::setScrollBar(QScrollBar
*scrollBar
)
37 m_scrollBar
= scrollBar
;
40 QScrollBar
*KItemListSmoothScroller::scrollBar() const
45 void KItemListSmoothScroller::setTargetObject(QObject
*target
)
47 m_animation
->setTargetObject(target
);
50 QObject
*KItemListSmoothScroller::targetObject() const
52 return m_animation
->targetObject();
55 void KItemListSmoothScroller::setPropertyName(const QByteArray
&propertyName
)
57 m_animation
->setPropertyName(propertyName
);
60 QByteArray
KItemListSmoothScroller::propertyName() const
62 return m_animation
->propertyName();
65 void KItemListSmoothScroller::scrollContentsBy(qreal distance
)
67 QObject
*target
= targetObject();
72 const QByteArray name
= propertyName();
73 const qreal currentOffset
= target
->property(name
).toReal();
74 if (static_cast<int>(currentOffset
) == m_scrollBar
->value()) {
75 // The current offset is already synchronous to the scrollbar
79 const bool animRunning
= (m_animation
->state() == QAbstractAnimation::Running
);
81 // Stopping a running animation means skipping the range from the current offset
82 // until the target offset. To prevent skipping of the range the difference
83 // is added to the new target offset.
84 const qreal oldEndOffset
= m_animation
->endValue().toReal();
85 distance
+= (currentOffset
- oldEndOffset
);
88 const qreal endOffset
= currentOffset
- distance
;
89 if (m_smoothScrolling
|| animRunning
) {
90 qreal startOffset
= currentOffset
;
92 // If the animation was running and has been interrupted by assigning a new end-offset
93 // one frame must be added to the start-offset to keep the animation smooth. This also
94 // assures that animation proceeds even in cases where new end-offset are triggered
95 // within a very short timeslots.
96 startOffset
+= (endOffset
- currentOffset
) * 1000 / (m_animation
->duration() * 60);
97 if (currentOffset
< endOffset
) {
98 startOffset
= qMin(startOffset
, endOffset
);
100 startOffset
= qMax(startOffset
, endOffset
);
105 m_animation
->setStartValue(startOffset
);
106 m_animation
->setEndValue(endOffset
);
107 m_animation
->setEasingCurve(animRunning
? QEasingCurve::OutQuad
: QEasingCurve::InOutQuad
);
108 m_animation
->start();
109 target
->setProperty(name
, startOffset
);
111 target
->setProperty(name
, endOffset
);
115 void KItemListSmoothScroller::scrollTo(qreal position
)
117 int newValue
= position
;
118 newValue
= qBound(0, newValue
, m_scrollBar
->maximum());
120 if (newValue
!= m_scrollBar
->value()) {
121 m_smoothScrolling
= true;
122 m_scrollBar
->setValue(newValue
);
126 bool KItemListSmoothScroller::requestScrollBarUpdate(int newMaximum
)
128 if (m_animation
->state() == QAbstractAnimation::Running
) {
129 if (newMaximum
== m_scrollBar
->maximum()) {
130 // The value has been changed by the animation, no update
131 // of the scrollbars is required as their target state will be
132 // reached with the end of the animation.
136 // The maximum has been changed which indicates that the content
137 // of the view has been changed. Stop the animation in any case and
138 // update the scrollbars immediately.
144 bool KItemListSmoothScroller::eventFilter(QObject
*obj
, QEvent
*event
)
146 Q_ASSERT(obj
== m_scrollBar
);
148 switch (event
->type()) {
149 case QEvent::MouseButtonPress
:
150 m_scrollBarPressed
= true;
151 m_smoothScrolling
= true;
154 case QEvent::MouseButtonRelease
:
155 m_scrollBarPressed
= false;
156 m_smoothScrolling
= false;
160 return false; // we're the ones sending them
166 return QObject::eventFilter(obj
, event
);
169 void KItemListSmoothScroller::slotAnimationStateChanged(QAbstractAnimation::State newState
, QAbstractAnimation::State oldState
)
172 if (newState
== QAbstractAnimation::Stopped
&& m_smoothScrolling
&& !m_scrollBarPressed
) {
173 m_smoothScrolling
= false;
175 if (newState
== QAbstractAnimation::Stopped
) {
176 Q_EMIT
scrollingStopped();
180 void KItemListSmoothScroller::handleWheelEvent(QWheelEvent
*event
)
182 const bool previous
= m_smoothScrolling
;
184 m_smoothScrolling
= true;
186 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
187 QWheelEvent
*copy
= event
->clone();
188 QApplication::sendEvent(m_scrollBar
, copy
);
189 event
->setAccepted(copy
->isAccepted());
191 QWheelEvent copy
= *event
;
192 QApplication::sendEvent(m_scrollBar
, ©
);
193 event
->setAccepted(copy
.isAccepted());
196 m_smoothScrolling
= previous
;
199 #include "moc_kitemlistsmoothscroller.cpp"