File indexing completed on 2024-05-12 05:47:30
0001 /* 0002 * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kitemlistsmoothscroller.h" 0008 0009 #include <QApplication> 0010 #include <QPropertyAnimation> 0011 #include <QScrollBar> 0012 #include <QStyle> 0013 #include <QWheelEvent> 0014 0015 KItemListSmoothScroller::KItemListSmoothScroller(QScrollBar *scrollBar, QObject *parent) 0016 : QObject(parent) 0017 , m_scrollBarPressed(false) 0018 , m_smoothScrolling(true) 0019 , m_scrollBar(scrollBar) 0020 , m_animation(nullptr) 0021 { 0022 m_animation = new QPropertyAnimation(this); 0023 const int animationDuration = m_scrollBar->style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, m_scrollBar); 0024 const bool animationEnabled = (animationDuration > 0); 0025 m_animation->setDuration(animationEnabled ? animationDuration : 1); 0026 connect(m_animation, &QPropertyAnimation::stateChanged, this, &KItemListSmoothScroller::slotAnimationStateChanged); 0027 0028 m_scrollBar->installEventFilter(this); 0029 } 0030 0031 KItemListSmoothScroller::~KItemListSmoothScroller() 0032 { 0033 } 0034 0035 void KItemListSmoothScroller::setScrollBar(QScrollBar *scrollBar) 0036 { 0037 m_scrollBar = scrollBar; 0038 } 0039 0040 QScrollBar *KItemListSmoothScroller::scrollBar() const 0041 { 0042 return m_scrollBar; 0043 } 0044 0045 void KItemListSmoothScroller::setTargetObject(QObject *target) 0046 { 0047 m_animation->setTargetObject(target); 0048 } 0049 0050 QObject *KItemListSmoothScroller::targetObject() const 0051 { 0052 return m_animation->targetObject(); 0053 } 0054 0055 void KItemListSmoothScroller::setPropertyName(const QByteArray &propertyName) 0056 { 0057 m_animation->setPropertyName(propertyName); 0058 } 0059 0060 QByteArray KItemListSmoothScroller::propertyName() const 0061 { 0062 return m_animation->propertyName(); 0063 } 0064 0065 void KItemListSmoothScroller::scrollContentsBy(qreal distance) 0066 { 0067 QObject *target = targetObject(); 0068 if (!target) { 0069 return; 0070 } 0071 0072 const QByteArray name = propertyName(); 0073 const qreal currentOffset = target->property(name).toReal(); 0074 if (static_cast<int>(currentOffset) == m_scrollBar->value()) { 0075 // The current offset is already synchronous to the scrollbar 0076 return; 0077 } 0078 0079 const bool animRunning = (m_animation->state() == QAbstractAnimation::Running); 0080 if (animRunning) { 0081 // Stopping a running animation means skipping the range from the current offset 0082 // until the target offset. To prevent skipping of the range the difference 0083 // is added to the new target offset. 0084 const qreal oldEndOffset = m_animation->endValue().toReal(); 0085 distance += (currentOffset - oldEndOffset); 0086 } 0087 0088 const qreal endOffset = currentOffset - distance; 0089 if (m_smoothScrolling || animRunning) { 0090 qreal startOffset = currentOffset; 0091 if (animRunning) { 0092 // If the animation was running and has been interrupted by assigning a new end-offset 0093 // one frame must be added to the start-offset to keep the animation smooth. This also 0094 // assures that animation proceeds even in cases where new end-offset are triggered 0095 // within a very short timeslots. 0096 startOffset += (endOffset - currentOffset) * 1000 / (m_animation->duration() * 60); 0097 if (currentOffset < endOffset) { 0098 startOffset = qMin(startOffset, endOffset); 0099 } else { 0100 startOffset = qMax(startOffset, endOffset); 0101 } 0102 } 0103 0104 m_animation->stop(); 0105 m_animation->setStartValue(startOffset); 0106 m_animation->setEndValue(endOffset); 0107 m_animation->setEasingCurve(animRunning ? QEasingCurve::OutQuad : QEasingCurve::InOutQuad); 0108 m_animation->start(); 0109 target->setProperty(name, startOffset); 0110 } else { 0111 target->setProperty(name, endOffset); 0112 } 0113 } 0114 0115 void KItemListSmoothScroller::scrollTo(qreal position) 0116 { 0117 int newValue = position; 0118 newValue = qBound(0, newValue, m_scrollBar->maximum()); 0119 0120 if (newValue != m_scrollBar->value()) { 0121 m_smoothScrolling = true; 0122 m_scrollBar->setValue(newValue); 0123 } 0124 } 0125 0126 bool KItemListSmoothScroller::requestScrollBarUpdate(int newMaximum) 0127 { 0128 if (m_animation->state() == QAbstractAnimation::Running) { 0129 if (newMaximum == m_scrollBar->maximum()) { 0130 // The value has been changed by the animation, no update 0131 // of the scrollbars is required as their target state will be 0132 // reached with the end of the animation. 0133 return false; 0134 } 0135 0136 // The maximum has been changed which indicates that the content 0137 // of the view has been changed. Stop the animation in any case and 0138 // update the scrollbars immediately. 0139 m_animation->stop(); 0140 } 0141 return true; 0142 } 0143 0144 bool KItemListSmoothScroller::eventFilter(QObject *obj, QEvent *event) 0145 { 0146 Q_ASSERT(obj == m_scrollBar); 0147 0148 switch (event->type()) { 0149 case QEvent::MouseButtonPress: 0150 m_scrollBarPressed = true; 0151 m_smoothScrolling = true; 0152 break; 0153 0154 case QEvent::MouseButtonRelease: 0155 m_scrollBarPressed = false; 0156 m_smoothScrolling = false; 0157 break; 0158 0159 case QEvent::Wheel: 0160 return false; // we're the ones sending them 0161 0162 default: 0163 break; 0164 } 0165 0166 return QObject::eventFilter(obj, event); 0167 } 0168 0169 void KItemListSmoothScroller::slotAnimationStateChanged(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) 0170 { 0171 Q_UNUSED(oldState) 0172 if (newState == QAbstractAnimation::Stopped && m_smoothScrolling && !m_scrollBarPressed) { 0173 m_smoothScrolling = false; 0174 } 0175 if (newState == QAbstractAnimation::Stopped) { 0176 Q_EMIT scrollingStopped(); 0177 } 0178 } 0179 0180 void KItemListSmoothScroller::handleWheelEvent(QWheelEvent *event) 0181 { 0182 const bool previous = m_smoothScrolling; 0183 0184 m_smoothScrolling = true; 0185 0186 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0187 QWheelEvent *copy = event->clone(); 0188 QApplication::sendEvent(m_scrollBar, copy); 0189 event->setAccepted(copy->isAccepted()); 0190 #else 0191 QWheelEvent copy = *event; 0192 QApplication::sendEvent(m_scrollBar, ©); 0193 event->setAccepted(copy.isAccepted()); 0194 #endif 0195 0196 m_smoothScrolling = previous; 0197 } 0198 0199 #include "moc_kitemlistsmoothscroller.cpp"