1/*
2 * Copyright (C) 2014-2015 Apple Inc. All rights reserved.
3 * Copyright (c) 2010, Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include "config.h"
33#include "ScrollAnimator.h"
34
35#include "FloatPoint.h"
36#include "LayoutSize.h"
37#include "PlatformWheelEvent.h"
38#include "ScrollableArea.h"
39#include <algorithm>
40
41namespace WebCore {
42
43#if !ENABLE(SMOOTH_SCROLLING) && !PLATFORM(IOS_FAMILY) && !PLATFORM(MAC)
44std::unique_ptr<ScrollAnimator> ScrollAnimator::create(ScrollableArea& scrollableArea)
45{
46 return std::make_unique<ScrollAnimator>(scrollableArea);
47}
48#endif
49
50ScrollAnimator::ScrollAnimator(ScrollableArea& scrollableArea)
51 : m_scrollableArea(scrollableArea)
52#if ENABLE(CSS_SCROLL_SNAP) || ENABLE(RUBBER_BANDING)
53 , m_scrollController(*this)
54#endif
55{
56}
57
58ScrollAnimator::~ScrollAnimator() = default;
59
60bool ScrollAnimator::scroll(ScrollbarOrientation orientation, ScrollGranularity, float step, float multiplier)
61{
62 FloatPoint currentPosition = this->currentPosition();
63 FloatSize delta;
64 if (orientation == HorizontalScrollbar)
65 delta.setWidth(step * multiplier);
66 else
67 delta.setHeight(step * multiplier);
68
69 FloatPoint newPosition = FloatPoint(currentPosition + delta).constrainedBetween(m_scrollableArea.minimumScrollPosition(), m_scrollableArea.maximumScrollPosition());
70 if (currentPosition == newPosition)
71 return false;
72
73 m_currentPosition = newPosition;
74 notifyPositionChanged(newPosition - currentPosition);
75 return true;
76}
77
78void ScrollAnimator::scrollToOffsetWithoutAnimation(const FloatPoint& offset, ScrollClamping)
79{
80 FloatPoint newPositon = ScrollableArea::scrollPositionFromOffset(offset, toFloatSize(m_scrollableArea.scrollOrigin()));
81 FloatSize delta = newPositon - currentPosition();
82 m_currentPosition = newPositon;
83 notifyPositionChanged(delta);
84 updateActiveScrollSnapIndexForOffset();
85}
86
87#if ENABLE(CSS_SCROLL_SNAP)
88#if PLATFORM(MAC)
89bool ScrollAnimator::processWheelEventForScrollSnap(const PlatformWheelEvent& wheelEvent)
90{
91 return m_scrollController.processWheelEventForScrollSnap(wheelEvent);
92}
93#endif
94
95bool ScrollAnimator::activeScrollSnapIndexDidChange() const
96{
97 return m_scrollController.activeScrollSnapIndexDidChange();
98}
99
100unsigned ScrollAnimator::activeScrollSnapIndexForAxis(ScrollEventAxis axis) const
101{
102 return m_scrollController.activeScrollSnapIndexForAxis(axis);
103}
104#endif
105
106bool ScrollAnimator::handleWheelEvent(const PlatformWheelEvent& e)
107{
108#if ENABLE(CSS_SCROLL_SNAP) && PLATFORM(MAC)
109 if (!m_scrollController.processWheelEventForScrollSnap(e))
110 return false;
111#endif
112#if PLATFORM(COCOA)
113 // Events in the PlatformWheelEventPhaseMayBegin phase have no deltas, and therefore never passes through the scroll handling logic below.
114 // This causes us to return with an 'unhandled' return state, even though this event was successfully processed.
115 //
116 // We receive at least one PlatformWheelEventPhaseMayBegin when starting main-thread scrolling (see FrameView::wheelEvent), which can
117 // fool the scrolling thread into attempting to handle the scroll, unless we treat the event as handled here.
118 if (e.phase() == PlatformWheelEventPhaseMayBegin)
119 return true;
120#endif
121
122 Scrollbar* horizontalScrollbar = m_scrollableArea.horizontalScrollbar();
123 Scrollbar* verticalScrollbar = m_scrollableArea.verticalScrollbar();
124
125 // Accept the event if we have a scrollbar in that direction and can still
126 // scroll any further.
127 float deltaX = horizontalScrollbar ? e.deltaX() : 0;
128 float deltaY = verticalScrollbar ? e.deltaY() : 0;
129
130 bool handled = false;
131
132 ScrollGranularity granularity = ScrollByPixel;
133 IntSize maxForwardScrollDelta = m_scrollableArea.maximumScrollPosition() - m_scrollableArea.scrollPosition();
134 IntSize maxBackwardScrollDelta = m_scrollableArea.scrollPosition() - m_scrollableArea.minimumScrollPosition();
135 if ((deltaX < 0 && maxForwardScrollDelta.width() > 0)
136 || (deltaX > 0 && maxBackwardScrollDelta.width() > 0)
137 || (deltaY < 0 && maxForwardScrollDelta.height() > 0)
138 || (deltaY > 0 && maxBackwardScrollDelta.height() > 0)) {
139 handled = true;
140
141 if (deltaY) {
142 if (e.granularity() == ScrollByPageWheelEvent) {
143 bool negative = deltaY < 0;
144 deltaY = Scrollbar::pageStepDelta(m_scrollableArea.visibleHeight());
145 if (negative)
146 deltaY = -deltaY;
147 }
148 scroll(VerticalScrollbar, granularity, verticalScrollbar->pixelStep(), -deltaY);
149 }
150
151 if (deltaX) {
152 if (e.granularity() == ScrollByPageWheelEvent) {
153 bool negative = deltaX < 0;
154 deltaX = Scrollbar::pageStepDelta(m_scrollableArea.visibleWidth());
155 if (negative)
156 deltaX = -deltaX;
157 }
158 scroll(HorizontalScrollbar, granularity, horizontalScrollbar->pixelStep(), -deltaX);
159 }
160 }
161 return handled;
162}
163
164#if ENABLE(TOUCH_EVENTS)
165bool ScrollAnimator::handleTouchEvent(const PlatformTouchEvent&)
166{
167 return false;
168}
169#endif
170
171void ScrollAnimator::setCurrentPosition(const FloatPoint& position)
172{
173 m_currentPosition = position;
174 updateActiveScrollSnapIndexForOffset();
175}
176
177void ScrollAnimator::updateActiveScrollSnapIndexForOffset()
178{
179#if ENABLE(CSS_SCROLL_SNAP)
180 // FIXME: Needs offset/position disambiguation.
181 m_scrollController.setActiveScrollSnapIndicesForOffset(m_currentPosition.x(), m_currentPosition.y());
182 if (m_scrollController.activeScrollSnapIndexDidChange()) {
183 m_scrollableArea.setCurrentHorizontalSnapPointIndex(m_scrollController.activeScrollSnapIndexForAxis(ScrollEventAxis::Horizontal));
184 m_scrollableArea.setCurrentVerticalSnapPointIndex(m_scrollController.activeScrollSnapIndexForAxis(ScrollEventAxis::Vertical));
185 }
186#endif
187}
188
189void ScrollAnimator::notifyPositionChanged(const FloatSize& delta)
190{
191 UNUSED_PARAM(delta);
192 // FIXME: need to not map back and forth all the time.
193 m_scrollableArea.setScrollOffsetFromAnimation(m_scrollableArea.scrollOffsetFromPosition(roundedIntPoint(currentPosition())));
194}
195
196#if ENABLE(CSS_SCROLL_SNAP)
197void ScrollAnimator::updateScrollSnapState()
198{
199 m_scrollController.updateScrollSnapState(m_scrollableArea);
200}
201
202FloatPoint ScrollAnimator::scrollOffset() const
203{
204 return m_currentPosition;
205}
206
207void ScrollAnimator::immediateScrollOnAxis(ScrollEventAxis axis, float delta)
208{
209 FloatSize deltaSize;
210 if (axis == ScrollEventAxis::Horizontal)
211 deltaSize.setWidth(delta);
212 else
213 deltaSize.setHeight(delta);
214
215 scrollToOffsetWithoutAnimation(currentPosition() + deltaSize);
216}
217
218LayoutSize ScrollAnimator::scrollExtent() const
219{
220 return m_scrollableArea.contentsSize();
221}
222
223FloatSize ScrollAnimator::viewportSize() const
224{
225 return m_scrollableArea.visibleSize();
226}
227
228#endif
229
230#if (ENABLE(CSS_SCROLL_SNAP) || ENABLE(RUBBER_BANDING)) && PLATFORM(MAC)
231void ScrollAnimator::deferTestsForReason(WheelEventTestTrigger::ScrollableAreaIdentifier identifier, WheelEventTestTrigger::DeferTestTriggerReason reason) const
232{
233 if (!m_wheelEventTestTrigger)
234 return;
235
236 m_wheelEventTestTrigger->deferTestsForReason(identifier, reason);
237}
238
239void ScrollAnimator::removeTestDeferralForReason(WheelEventTestTrigger::ScrollableAreaIdentifier identifier, WheelEventTestTrigger::DeferTestTriggerReason reason) const
240{
241 if (!m_wheelEventTestTrigger)
242 return;
243
244 m_wheelEventTestTrigger->removeTestDeferralForReason(identifier, reason);
245}
246#endif
247
248} // namespace WebCore
249