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
39namespace WebCore {
40
41static const Seconds overflowScrollbarsAnimationDuration { 1_s };
42static const Seconds overflowScrollbarsAnimationHideDelay { 2_s };
43static const Seconds scrollCaptureThreshold { 150_ms };
44
45std::unique_ptr<ScrollAnimator> ScrollAnimator::create(ScrollableArea& scrollableArea)
46{
47 return std::make_unique<ScrollAnimatorGeneric>(scrollableArea);
48}
49
50ScrollAnimatorGeneric::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
68ScrollAnimatorGeneric::~ScrollAnimatorGeneric() = default;
69
70#if ENABLE(SMOOTH_SCROLLING)
71void 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)
83bool 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
93void 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
107FloatPoint 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
127bool 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
153void 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
163void ScrollAnimatorGeneric::updatePosition(FloatPoint&& position)
164{
165 FloatSize delta = position - m_currentPosition;
166 m_currentPosition = WTFMove(position);
167 notifyPositionChanged(delta);
168}
169
170void 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
187void 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
204void 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
213void 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
222void 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
237static inline double easeOutCubic(double t)
238{
239 double p = t - 1;
240 return p * p * p + 1;
241}
242
243void 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
270void 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
292void 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
310void ScrollAnimatorGeneric::mouseEnteredContentArea()
311{
312 showOverlayScrollbars();
313}
314
315void ScrollAnimatorGeneric::mouseExitedContentArea()
316{
317 hideOverlayScrollbars();
318}
319
320void ScrollAnimatorGeneric::mouseMovedInContentArea()
321{
322 showOverlayScrollbars();
323}
324
325void ScrollAnimatorGeneric::contentAreaDidShow()
326{
327 showOverlayScrollbars();
328}
329
330void 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
341void ScrollAnimatorGeneric::notifyContentAreaScrolled(const FloatSize&)
342{
343 showOverlayScrollbars();
344}
345
346void 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
371