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, &copy);
0193     event->setAccepted(copy.isAccepted());
0194 #endif
0195 
0196     m_smoothScrolling = previous;
0197 }
0198 
0199 #include "moc_kitemlistsmoothscroller.cpp"