┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
path: root/src/kitemviews/private/kitemlistsmoothscroller.cpp
blob: 0e75460441abe4ba6c29bd5343d3384e0a2ab82e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
/*
 * SPDX-FileCopyrightText: 2011 Peter Penz <[email protected]>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "kitemlistsmoothscroller.h"

#include <KConfigGroup>
#include <KSharedConfig>

#include <QApplication>
#include <QDBusConnection>
#include <QPropertyAnimation>
#include <QScrollBar>
#include <QStyle>
#include <QWheelEvent>

KItemListSmoothScroller::KItemListSmoothScroller(QScrollBar *scrollBar, QObject *parent)
    : QObject(parent)
    , m_scrollBarPressed(false)
    , m_smoothScrolling(true)
    , m_scrollBar(scrollBar)
    , m_animation(nullptr)
{
    m_animation = new QPropertyAnimation(this);

    KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::IncludeGlobals);
    KConfigGroup configGroup(globalConfig, QStringLiteral("KDE"));
    updateAnimationDuration(configGroup.readEntry("SmoothScroll", true));

    QDBusConnection::sessionBus().connect(QStringLiteral(""),
                                          QStringLiteral("/SmoothScroll"),
                                          QStringLiteral("org.kde.SmoothScroll"),
                                          QStringLiteral("notifyChange"),
                                          this,
                                          SLOT(updateAnimationDuration(bool)));
    connect(m_animation, &QPropertyAnimation::stateChanged, this, &KItemListSmoothScroller::slotAnimationStateChanged);

    m_scrollBar->installEventFilter(this);
}

KItemListSmoothScroller::~KItemListSmoothScroller()
{
}

void KItemListSmoothScroller::setScrollBar(QScrollBar *scrollBar)
{
    m_scrollBar = scrollBar;
}

QScrollBar *KItemListSmoothScroller::scrollBar() const
{
    return m_scrollBar;
}

void KItemListSmoothScroller::setTargetObject(QObject *target)
{
    if (m_animation->state() == QAbstractAnimation::Running) {
        m_animation->stop();
    }
    m_animation->setTargetObject(target);
}

QObject *KItemListSmoothScroller::targetObject() const
{
    return m_animation->targetObject();
}

void KItemListSmoothScroller::setPropertyName(const QByteArray &propertyName)
{
    if (m_animation->state() == QAbstractAnimation::Running) {
        m_animation->stop();
    }
    m_animation->setPropertyName(propertyName);
}

QByteArray KItemListSmoothScroller::propertyName() const
{
    return m_animation->propertyName();
}

void KItemListSmoothScroller::scrollContentsBy(qreal distance)
{
    QObject *target = targetObject();
    if (!target) {
        return;
    }

    const QByteArray name = propertyName();
    const qreal currentOffset = target->property(name).toReal();
    if (static_cast<int>(currentOffset) == m_scrollBar->value()) {
        // The current offset is already synchronous to the scrollbar
        return;
    }

    const bool animRunning = (m_animation->state() == QAbstractAnimation::Running);
    if (animRunning) {
        // Stopping a running animation means skipping the range from the current offset
        // until the target offset. To prevent skipping of the range the difference
        // is added to the new target offset.
        const qreal oldEndOffset = m_animation->endValue().toReal();
        distance += (currentOffset - oldEndOffset);
    }

    const qreal endOffset = currentOffset - distance;
    if (m_smoothScrolling || animRunning) {
        qreal startOffset = currentOffset;
        if (animRunning) {
            // If the animation was running and has been interrupted by assigning a new end-offset
            // one frame must be added to the start-offset to keep the animation smooth. This also
            // assures that animation proceeds even in cases where new end-offset are triggered
            // within a very short timeslots.
            startOffset += (endOffset - currentOffset) * 1000 / (m_animation->duration() * 60);
            if (currentOffset < endOffset) {
                startOffset = qMin(startOffset, endOffset);
            } else {
                startOffset = qMax(startOffset, endOffset);
            }
        }

        m_animation->stop();
        m_animation->setStartValue(startOffset);
        m_animation->setEndValue(endOffset);
        m_animation->setEasingCurve(animRunning ? QEasingCurve::OutQuad : QEasingCurve::InOutQuad);
        m_animation->start();
        target->setProperty(name, startOffset);
    } else {
        target->setProperty(name, endOffset);
    }
}

void KItemListSmoothScroller::scrollTo(qreal position)
{
    int newValue = position;
    newValue = qBound(0, newValue, m_scrollBar->maximum());

    if (newValue != m_scrollBar->value()) {
        m_smoothScrolling = true;
        m_scrollBar->setValue(newValue);
    }
}

bool KItemListSmoothScroller::requestScrollBarUpdate(int newMaximum)
{
    if (m_animation->state() == QAbstractAnimation::Running) {
        if (newMaximum == m_scrollBar->maximum()) {
            // The value has been changed by the animation, no update
            // of the scrollbars is required as their target state will be
            // reached with the end of the animation.
            return false;
        }

        // The maximum has been changed which indicates that the content
        // of the view has been changed. Stop the animation in any case and
        // update the scrollbars immediately.
        m_animation->stop();
    }
    return true;
}

bool KItemListSmoothScroller::eventFilter(QObject *obj, QEvent *event)
{
    Q_ASSERT(obj == m_scrollBar);

    switch (event->type()) {
    case QEvent::MouseButtonPress:
        m_scrollBarPressed = true;
        m_smoothScrolling = true;
        break;

    case QEvent::MouseButtonRelease:
        m_scrollBarPressed = false;
        m_smoothScrolling = false;
        break;

    case QEvent::Wheel:
        return false; // we're the ones sending them

    default:
        break;
    }

    return QObject::eventFilter(obj, event);
}

void KItemListSmoothScroller::slotAnimationStateChanged(QAbstractAnimation::State newState, QAbstractAnimation::State oldState)
{
    Q_UNUSED(oldState)
    if (newState == QAbstractAnimation::Stopped && m_smoothScrolling && !m_scrollBarPressed) {
        m_smoothScrolling = false;
    }
    if (newState == QAbstractAnimation::Stopped) {
        Q_EMIT scrollingStopped();
    }
}

void KItemListSmoothScroller::updateAnimationDuration(bool isSmoothScrollingEnabled)
{
    if (isSmoothScrollingEnabled) {
        // Breeze sets SH_Widget_Animation_Duration from the KDE global animation speed setting
        const int animationDuration = m_scrollBar->style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, m_scrollBar);
        const bool animationEnabled = (animationDuration > 0);
        m_animation->setDuration(animationEnabled ? animationDuration : 1);
    } else {
        m_animation->setDuration(1);
    }
}

void KItemListSmoothScroller::handleWheelEvent(QWheelEvent *event)
{
    const bool previous = m_smoothScrolling;

    m_smoothScrolling = true;

    QWheelEvent *copy = event->clone();
    QApplication::sendEvent(m_scrollBar, copy);
    event->setAccepted(copy->isAccepted());

    m_smoothScrolling = previous;
}

bool KItemListSmoothScroller::isAnimating()
{
    if (m_animation) {
        return (m_animation->state() == QAbstractAnimation::Running);
    }
    return false;
}

#include "moc_kitemlistsmoothscroller.cpp"