Make generic ScrollAnimator
[WebKit-https.git] / Source / WebCore / platform / generic / ScrollAnimatorGeneric.cpp
1 /*
2  * Copyright (c) 2016 Igalia S.L.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "ScrollAnimatorGeneric.h"
33
34 #include "ScrollAnimationKinetic.h"
35 #include "ScrollAnimationSmooth.h"
36 #include "ScrollableArea.h"
37 #include "ScrollbarTheme.h"
38
39 namespace WebCore {
40
41 static const Seconds overflowScrollbarsAnimationDuration { 1_s };
42 static const Seconds overflowScrollbarsAnimationHideDelay { 2_s };
43 static const Seconds scrollCaptureThreshold { 150_ms };
44
45 std::unique_ptr<ScrollAnimator> ScrollAnimator::create(ScrollableArea& scrollableArea)
46 {
47     return std::make_unique<ScrollAnimatorGeneric>(scrollableArea);
48 }
49
50 ScrollAnimatorGeneric::ScrollAnimatorGeneric(ScrollableArea& scrollableArea)
51     : ScrollAnimator(scrollableArea)
52     , m_overlayScrollbarAnimationTimer(*this, &ScrollAnimatorGeneric::overlayScrollbarAnimationTimerFired)
53 {
54     m_kineticAnimation = std::make_unique<ScrollAnimationKinetic>(m_scrollableArea, [this](FloatPoint&& position) {
55 #if ENABLE(SMOOTH_SCROLLING)
56         if (m_smoothAnimation)
57             m_smoothAnimation->setCurrentPosition(position);
58 #endif
59         updatePosition(WTFMove(position));
60     });
61
62 #if ENABLE(SMOOTH_SCROLLING)
63     if (scrollableArea.scrollAnimatorEnabled())
64         ensureSmoothScrollingAnimation();
65 #endif
66 }
67
68 ScrollAnimatorGeneric::~ScrollAnimatorGeneric() = default;
69
70 #if ENABLE(SMOOTH_SCROLLING)
71 void ScrollAnimatorGeneric::ensureSmoothScrollingAnimation()
72 {
73     if (m_smoothAnimation)
74         return;
75
76     m_smoothAnimation = std::make_unique<ScrollAnimationSmooth>(m_scrollableArea, m_currentPosition, [this](FloatPoint&& position) {
77         updatePosition(WTFMove(position));
78     });
79 }
80 #endif
81
82 #if ENABLE(SMOOTH_SCROLLING)
83 bool ScrollAnimatorGeneric::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float multiplier)
84 {
85     if (!m_scrollableArea.scrollAnimatorEnabled())
86         return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
87
88     ensureSmoothScrollingAnimation();
89     return m_smoothAnimation->scroll(orientation, granularity, step, multiplier);
90 }
91 #endif
92
93 void ScrollAnimatorGeneric::scrollToOffsetWithoutAnimation(const FloatPoint& offset, ScrollClamping)
94 {
95     FloatPoint position = ScrollableArea::scrollPositionFromOffset(offset, toFloatSize(m_scrollableArea.scrollOrigin()));
96     m_kineticAnimation->stop();
97     m_scrollHistory.clear();
98
99 #if ENABLE(SMOOTH_SCROLLING)
100     if (m_smoothAnimation)
101         m_smoothAnimation->setCurrentPosition(position);
102 #endif
103
104     updatePosition(WTFMove(position));
105 }
106
107 FloatPoint ScrollAnimatorGeneric::computeVelocity()
108 {
109     if (m_scrollHistory.isEmpty())
110         return { };
111
112     auto first = m_scrollHistory[0].timestamp();
113     auto last = m_scrollHistory.rbegin()->timestamp();
114
115     if (last == first)
116         return { };
117
118     FloatPoint accumDelta;
119     for (const auto& scrollEvent : m_scrollHistory)
120         accumDelta += FloatPoint(scrollEvent.deltaX(), scrollEvent.deltaY());
121
122     m_scrollHistory.clear();
123
124     return FloatPoint(accumDelta.x() * -1 / (last - first).value(), accumDelta.y() * -1 / (last - first).value());
125 }
126
127 bool ScrollAnimatorGeneric::handleWheelEvent(const PlatformWheelEvent& event)
128 {
129     m_kineticAnimation->stop();
130
131     m_scrollHistory.removeAllMatching([&event] (PlatformWheelEvent& otherEvent) -> bool {
132         return (event.timestamp() - otherEvent.timestamp()) > scrollCaptureThreshold;
133     });
134
135 #if ENABLE(ASYNC_SCROLLING)
136     if (event.isEndOfNonMomentumScroll()) {
137         // We don't need to add the event to the history as its delta will be (0, 0).
138         static_cast<ScrollAnimationKinetic*>(m_kineticAnimation.get())->start(m_currentPosition, computeVelocity(), m_scrollableArea.horizontalScrollbar(), m_scrollableArea.verticalScrollbar());
139         return true;
140     }
141     if (event.isTransitioningToMomentumScroll()) {
142         m_scrollHistory.clear();
143         static_cast<ScrollAnimationKinetic*>(m_kineticAnimation.get())->start(m_currentPosition, event.swipeVelocity(), m_scrollableArea.horizontalScrollbar(), m_scrollableArea.verticalScrollbar());
144         return true;
145     }
146 #endif
147
148     m_scrollHistory.append(event);
149
150     return ScrollAnimator::handleWheelEvent(event);
151 }
152
153 void ScrollAnimatorGeneric::willEndLiveResize()
154 {
155     m_kineticAnimation->updateVisibleLengths();
156
157 #if ENABLE(SMOOTH_SCROLLING)
158     if (m_smoothAnimation)
159         m_smoothAnimation->updateVisibleLengths();
160 #endif
161 }
162
163 void ScrollAnimatorGeneric::updatePosition(FloatPoint&& position)
164 {
165     FloatSize delta = position - m_currentPosition;
166     m_currentPosition = WTFMove(position);
167     notifyPositionChanged(delta);
168 }
169
170 void ScrollAnimatorGeneric::didAddVerticalScrollbar(Scrollbar* scrollbar)
171 {
172     m_kineticAnimation->updateVisibleLengths();
173
174 #if ENABLE(SMOOTH_SCROLLING)
175     if (m_smoothAnimation)
176         m_smoothAnimation->updateVisibleLengths();
177 #endif
178     if (!scrollbar->isOverlayScrollbar())
179         return;
180     m_verticalOverlayScrollbar = scrollbar;
181     if (!m_horizontalOverlayScrollbar)
182         m_overlayScrollbarAnimationCurrent = 1;
183     m_verticalOverlayScrollbar->setOpacity(m_overlayScrollbarAnimationCurrent);
184     hideOverlayScrollbars();
185 }
186
187 void ScrollAnimatorGeneric::didAddHorizontalScrollbar(Scrollbar* scrollbar)
188 {
189     m_kineticAnimation->updateVisibleLengths();
190
191 #if ENABLE(SMOOTH_SCROLLING)
192     if (m_smoothAnimation)
193         m_smoothAnimation->updateVisibleLengths();
194 #endif
195     if (!scrollbar->isOverlayScrollbar())
196         return;
197     m_horizontalOverlayScrollbar = scrollbar;
198     if (!m_verticalOverlayScrollbar)
199         m_overlayScrollbarAnimationCurrent = 1;
200     m_horizontalOverlayScrollbar->setOpacity(m_overlayScrollbarAnimationCurrent);
201     hideOverlayScrollbars();
202 }
203
204 void ScrollAnimatorGeneric::willRemoveVerticalScrollbar(Scrollbar* scrollbar)
205 {
206     if (m_verticalOverlayScrollbar != scrollbar)
207         return;
208     m_verticalOverlayScrollbar = nullptr;
209     if (!m_horizontalOverlayScrollbar)
210         m_overlayScrollbarAnimationCurrent = 0;
211 }
212
213 void ScrollAnimatorGeneric::willRemoveHorizontalScrollbar(Scrollbar* scrollbar)
214 {
215     if (m_horizontalOverlayScrollbar != scrollbar)
216         return;
217     m_horizontalOverlayScrollbar = nullptr;
218     if (!m_verticalOverlayScrollbar)
219         m_overlayScrollbarAnimationCurrent = 0;
220 }
221
222 void ScrollAnimatorGeneric::updateOverlayScrollbarsOpacity()
223 {
224     if (m_verticalOverlayScrollbar && m_overlayScrollbarAnimationCurrent != m_verticalOverlayScrollbar->opacity()) {
225         m_verticalOverlayScrollbar->setOpacity(m_overlayScrollbarAnimationCurrent);
226         if (m_verticalOverlayScrollbar->hoveredPart() == NoPart)
227             m_verticalOverlayScrollbar->invalidate();
228     }
229
230     if (m_horizontalOverlayScrollbar && m_overlayScrollbarAnimationCurrent != m_horizontalOverlayScrollbar->opacity()) {
231         m_horizontalOverlayScrollbar->setOpacity(m_overlayScrollbarAnimationCurrent);
232         if (m_horizontalOverlayScrollbar->hoveredPart() == NoPart)
233             m_horizontalOverlayScrollbar->invalidate();
234     }
235 }
236
237 static inline double easeOutCubic(double t)
238 {
239     double p = t - 1;
240     return p * p * p + 1;
241 }
242
243 void ScrollAnimatorGeneric::overlayScrollbarAnimationTimerFired()
244 {
245     if (!m_horizontalOverlayScrollbar && !m_verticalOverlayScrollbar)
246         return;
247     if (m_overlayScrollbarsLocked)
248         return;
249
250     MonotonicTime currentTime = MonotonicTime::now();
251     double progress = 1;
252     if (currentTime < m_overlayScrollbarAnimationEndTime)
253         progress = (currentTime - m_overlayScrollbarAnimationStartTime).value() / (m_overlayScrollbarAnimationEndTime - m_overlayScrollbarAnimationStartTime).value();
254     progress = m_overlayScrollbarAnimationSource + (easeOutCubic(progress) * (m_overlayScrollbarAnimationTarget - m_overlayScrollbarAnimationSource));
255     if (progress != m_overlayScrollbarAnimationCurrent) {
256         m_overlayScrollbarAnimationCurrent = progress;
257         updateOverlayScrollbarsOpacity();
258     }
259
260     if (m_overlayScrollbarAnimationCurrent != m_overlayScrollbarAnimationTarget) {
261         static const double frameRate = 60;
262         static const Seconds tickTime = 1_s / frameRate;
263         static const Seconds minimumTimerInterval = 1_ms;
264         Seconds deltaToNextFrame = std::max(tickTime - (MonotonicTime::now() - currentTime), minimumTimerInterval);
265         m_overlayScrollbarAnimationTimer.startOneShot(deltaToNextFrame);
266     } else
267         hideOverlayScrollbars();
268 }
269
270 void ScrollAnimatorGeneric::showOverlayScrollbars()
271 {
272     if (m_overlayScrollbarsLocked)
273         return;
274
275     if (m_overlayScrollbarAnimationTimer.isActive() && m_overlayScrollbarAnimationTarget == 1)
276         return;
277     m_overlayScrollbarAnimationTimer.stop();
278
279     if (!m_horizontalOverlayScrollbar && !m_verticalOverlayScrollbar)
280         return;
281
282     m_overlayScrollbarAnimationSource = m_overlayScrollbarAnimationCurrent;
283     m_overlayScrollbarAnimationTarget = 1;
284     if (m_overlayScrollbarAnimationTarget != m_overlayScrollbarAnimationCurrent) {
285         m_overlayScrollbarAnimationStartTime = MonotonicTime::now();
286         m_overlayScrollbarAnimationEndTime = m_overlayScrollbarAnimationStartTime + overflowScrollbarsAnimationDuration;
287         m_overlayScrollbarAnimationTimer.startOneShot(0_s);
288     } else
289         hideOverlayScrollbars();
290 }
291
292 void ScrollAnimatorGeneric::hideOverlayScrollbars()
293 {
294     if (m_overlayScrollbarAnimationTimer.isActive() && !m_overlayScrollbarAnimationTarget)
295         return;
296     m_overlayScrollbarAnimationTimer.stop();
297
298     if (!m_horizontalOverlayScrollbar && !m_verticalOverlayScrollbar)
299         return;
300
301     m_overlayScrollbarAnimationSource = m_overlayScrollbarAnimationCurrent;
302     m_overlayScrollbarAnimationTarget = 0;
303     if (m_overlayScrollbarAnimationTarget == m_overlayScrollbarAnimationCurrent)
304         return;
305     m_overlayScrollbarAnimationStartTime = MonotonicTime::now() + overflowScrollbarsAnimationHideDelay;
306     m_overlayScrollbarAnimationEndTime = m_overlayScrollbarAnimationStartTime + overflowScrollbarsAnimationDuration + overflowScrollbarsAnimationHideDelay;
307     m_overlayScrollbarAnimationTimer.startOneShot(overflowScrollbarsAnimationHideDelay);
308 }
309
310 void ScrollAnimatorGeneric::mouseEnteredContentArea()
311 {
312     showOverlayScrollbars();
313 }
314
315 void ScrollAnimatorGeneric::mouseExitedContentArea()
316 {
317     hideOverlayScrollbars();
318 }
319
320 void ScrollAnimatorGeneric::mouseMovedInContentArea()
321 {
322     showOverlayScrollbars();
323 }
324
325 void ScrollAnimatorGeneric::contentAreaDidShow()
326 {
327     showOverlayScrollbars();
328 }
329
330 void ScrollAnimatorGeneric::contentAreaDidHide()
331 {
332     if (m_overlayScrollbarsLocked)
333         return;
334     m_overlayScrollbarAnimationTimer.stop();
335     if (m_overlayScrollbarAnimationCurrent) {
336         m_overlayScrollbarAnimationCurrent = 0;
337         updateOverlayScrollbarsOpacity();
338     }
339 }
340
341 void ScrollAnimatorGeneric::notifyContentAreaScrolled(const FloatSize&)
342 {
343     showOverlayScrollbars();
344 }
345
346 void ScrollAnimatorGeneric::lockOverlayScrollbarStateToHidden(bool shouldLockState)
347 {
348     if (m_overlayScrollbarsLocked == shouldLockState)
349         return;
350     m_overlayScrollbarsLocked = shouldLockState;
351
352     if (!m_horizontalOverlayScrollbar && !m_verticalOverlayScrollbar)
353         return;
354
355     if (m_overlayScrollbarsLocked) {
356         m_overlayScrollbarAnimationTimer.stop();
357         if (m_horizontalOverlayScrollbar)
358             m_horizontalOverlayScrollbar->setOpacity(0);
359         if (m_verticalOverlayScrollbar)
360             m_verticalOverlayScrollbar->setOpacity(0);
361     } else {
362         if (m_overlayScrollbarAnimationCurrent == 1)
363             updateOverlayScrollbarsOpacity();
364         else
365             showOverlayScrollbars();
366     }
367 }
368
369 } // namespace WebCore
370