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 | |
371 | |