[WTF] Add makeUnique<T>, which ensures T is fast-allocated, makeUnique / makeUniqueWi...
[WebKit-https.git] / Source / WebCore / platform / ios / ScrollAnimatorIOS.mm
1 /*
2  * Copyright (C) 2011, 2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "ScrollAnimatorIOS.h"
28
29 #if PLATFORM(IOS_FAMILY)
30
31 #import "Frame.h"
32 #import "RenderLayer.h"
33 #import "ScrollableArea.h"
34
35 #if ENABLE(TOUCH_EVENTS)
36 #import "PlatformTouchEventIOS.h"
37 #endif
38
39 namespace WebCore {
40
41 std::unique_ptr<ScrollAnimator> ScrollAnimator::create(ScrollableArea& scrollableArea)
42 {
43     return makeUnique<ScrollAnimatorIOS>(scrollableArea);
44 }
45
46 ScrollAnimatorIOS::ScrollAnimatorIOS(ScrollableArea& scrollableArea)
47     : ScrollAnimator(scrollableArea)
48 #if ENABLE(TOUCH_EVENTS)
49     , m_touchScrollAxisLatch(AxisLatchNotComputed)
50     , m_inTouchSequence(false)
51     , m_committedToScrollAxis(false)
52     , m_startedScroll(false)
53     , m_scrollableAreaForTouchSequence(0)
54 #endif
55 {
56 }
57
58 ScrollAnimatorIOS::~ScrollAnimatorIOS()
59 {
60 }
61
62 #if ENABLE(TOUCH_EVENTS)
63 bool ScrollAnimatorIOS::handleTouchEvent(const PlatformTouchEvent& touchEvent)
64 {
65     if (touchEvent.type() == PlatformEvent::TouchStart && touchEvent.touchCount() == 1) {
66         m_firstTouchPoint = touchEvent.touchLocationAtIndex(0);
67         m_lastTouchPoint = m_firstTouchPoint;
68         m_inTouchSequence = true;
69         m_committedToScrollAxis = false;
70         m_startedScroll = false;
71         m_touchScrollAxisLatch = AxisLatchNotComputed;
72         // Never claim to have handled the TouchStart, because that will kill default scrolling behavior.
73         return false;
74     }
75
76     if (!m_inTouchSequence)
77         return false;
78
79     if (touchEvent.type() == PlatformEvent::TouchEnd || touchEvent.type() == PlatformEvent::TouchCancel) {
80         m_inTouchSequence = false;
81         m_scrollableAreaForTouchSequence = 0;
82         if (m_startedScroll)
83             scrollableArea().didEndScroll();
84         return false;
85     }
86
87     // If a second touch appears, assume that the user is trying to zoom, and bail on the scrolling sequence.
88     // FIXME: if that second touch is inside the scrollable area, should we keep scrolling?
89     if (touchEvent.touchCount() != 1) {
90         m_inTouchSequence = false;
91         m_scrollableAreaForTouchSequence = 0;
92         if (m_startedScroll)
93             scrollableArea().didEndScroll();
94         return false;
95     }
96     
97     IntPoint currentPoint = touchEvent.touchLocationAtIndex(0);
98
99     IntSize touchDelta = m_lastTouchPoint - currentPoint;
100     m_lastTouchPoint = currentPoint;
101
102     if (!m_scrollableAreaForTouchSequence)
103         determineScrollableAreaForTouchSequence(touchDelta);
104
105     if (!m_committedToScrollAxis) {
106         auto scrollSize = m_scrollableArea.maximumScrollPosition() - m_scrollableArea.minimumScrollPosition();
107         bool horizontallyScrollable = scrollSize.width();
108         bool verticallyScrollable = scrollSize.height();
109
110         if (!horizontallyScrollable && !verticallyScrollable)
111             return false;
112
113         IntSize deltaFromStart = m_firstTouchPoint - currentPoint;
114     
115         const int latchAxisMovementThreshold = 10;
116         if (!horizontallyScrollable && verticallyScrollable) {
117             m_touchScrollAxisLatch = AxisLatchVertical;
118             if (abs(deltaFromStart.height()) >= latchAxisMovementThreshold)
119                 m_committedToScrollAxis = true;
120         } else if (horizontallyScrollable && !verticallyScrollable) {
121             m_touchScrollAxisLatch = AxisLatchHorizontal;
122             if (abs(deltaFromStart.width()) >= latchAxisMovementThreshold)
123                 m_committedToScrollAxis = true;
124         } else {
125             m_committedToScrollAxis = true;
126
127             if (m_touchScrollAxisLatch == AxisLatchNotComputed) {
128                 const float lockAngleDegrees = 20;
129                 if (deltaFromStart.width() && deltaFromStart.height()) {
130                     float dragAngle = atanf(static_cast<float>(abs(deltaFromStart.height())) / abs(deltaFromStart.width()));
131                     if (dragAngle <= deg2rad(lockAngleDegrees))
132                         m_touchScrollAxisLatch = AxisLatchHorizontal;
133                     else if (dragAngle >= deg2rad(90 - lockAngleDegrees))
134                         m_touchScrollAxisLatch = AxisLatchVertical;
135                 }
136             }
137         }
138         
139         if (!m_committedToScrollAxis)
140             return false;
141     }
142
143     bool handled = false;
144     
145     // Horizontal
146     if (m_touchScrollAxisLatch != AxisLatchVertical) {
147         int delta = touchDelta.width();
148         handled |= m_scrollableAreaForTouchSequence->scroll(delta < 0 ? ScrollLeft : ScrollRight, ScrollByPixel, abs(delta));
149     }
150     
151     // Vertical
152     if (m_touchScrollAxisLatch != AxisLatchHorizontal) {
153         int delta = touchDelta.height();
154         handled |= m_scrollableAreaForTouchSequence->scroll(delta < 0 ? ScrollUp : ScrollDown, ScrollByPixel, abs(delta));
155     }
156     
157     // Return false until we manage to scroll at all, and then keep returning true until the gesture ends.
158     if (!m_startedScroll) {
159         if (!handled)
160             return false;
161         m_startedScroll = true;
162         scrollableArea().didStartScroll();
163     } else if (handled)
164         scrollableArea().didUpdateScroll();
165     
166     return true;
167 }
168
169 void ScrollAnimatorIOS::determineScrollableAreaForTouchSequence(const IntSize& scrollDelta)
170 {
171     ASSERT(!m_scrollableAreaForTouchSequence);
172
173     ScrollableArea* scrollableArea = &m_scrollableArea;
174     while (true) {
175         if (!scrollableArea->isPinnedInBothDirections(scrollDelta))
176             break;
177
178         ScrollableArea* enclosingArea = scrollableArea->enclosingScrollableArea();
179         if (!enclosingArea)
180             break;
181
182         scrollableArea = enclosingArea;
183     }
184
185     ASSERT(scrollableArea);
186     m_scrollableAreaForTouchSequence = scrollableArea;
187 }
188 #endif
189
190 } // namespace WebCore
191
192 #endif // PLATFORM(IOS_FAMILY)