blob: 49574688b71042946f93b900bbd22985867abebe [file] [log] [blame]
/*
* Copyright (C) 2011, 2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "ScrollAnimatorIOS.h"
#if PLATFORM(IOS_FAMILY)
#import "Frame.h"
#import "RenderLayer.h"
#import "ScrollableArea.h"
#if ENABLE(TOUCH_EVENTS)
#import "PlatformTouchEventIOS.h"
#endif
namespace WebCore {
std::unique_ptr<ScrollAnimator> ScrollAnimator::create(ScrollableArea& scrollableArea)
{
return std::make_unique<ScrollAnimatorIOS>(scrollableArea);
}
ScrollAnimatorIOS::ScrollAnimatorIOS(ScrollableArea& scrollableArea)
: ScrollAnimator(scrollableArea)
#if ENABLE(TOUCH_EVENTS)
, m_touchScrollAxisLatch(AxisLatchNotComputed)
, m_inTouchSequence(false)
, m_committedToScrollAxis(false)
, m_startedScroll(false)
, m_scrollableAreaForTouchSequence(0)
#endif
{
}
ScrollAnimatorIOS::~ScrollAnimatorIOS()
{
}
#if ENABLE(TOUCH_EVENTS)
bool ScrollAnimatorIOS::handleTouchEvent(const PlatformTouchEvent& touchEvent)
{
if (touchEvent.type() == PlatformEvent::TouchStart && touchEvent.touchCount() == 1) {
m_firstTouchPoint = touchEvent.touchLocationAtIndex(0);
m_lastTouchPoint = m_firstTouchPoint;
m_inTouchSequence = true;
m_committedToScrollAxis = false;
m_startedScroll = false;
m_touchScrollAxisLatch = AxisLatchNotComputed;
// Never claim to have handled the TouchStart, because that will kill default scrolling behavior.
return false;
}
if (!m_inTouchSequence)
return false;
if (touchEvent.type() == PlatformEvent::TouchEnd || touchEvent.type() == PlatformEvent::TouchCancel) {
m_inTouchSequence = false;
m_scrollableAreaForTouchSequence = 0;
if (m_startedScroll)
scrollableArea().didEndScroll();
return false;
}
// If a second touch appears, assume that the user is trying to zoom, and bail on the scrolling sequence.
// FIXME: if that second touch is inside the scrollable area, should we keep scrolling?
if (touchEvent.touchCount() != 1) {
m_inTouchSequence = false;
m_scrollableAreaForTouchSequence = 0;
if (m_startedScroll)
scrollableArea().didEndScroll();
return false;
}
IntPoint currentPoint = touchEvent.touchLocationAtIndex(0);
IntSize touchDelta = m_lastTouchPoint - currentPoint;
m_lastTouchPoint = currentPoint;
if (!m_scrollableAreaForTouchSequence)
determineScrollableAreaForTouchSequence(touchDelta);
if (!m_committedToScrollAxis) {
bool horizontallyScrollable = m_scrollableArea.scrollSize(HorizontalScrollbar);
bool verticallyScrollable = m_scrollableArea.scrollSize(VerticalScrollbar);
if (!horizontallyScrollable && !verticallyScrollable)
return false;
IntSize deltaFromStart = m_firstTouchPoint - currentPoint;
const int latchAxisMovementThreshold = 10;
if (!horizontallyScrollable && verticallyScrollable) {
m_touchScrollAxisLatch = AxisLatchVertical;
if (abs(deltaFromStart.height()) >= latchAxisMovementThreshold)
m_committedToScrollAxis = true;
} else if (horizontallyScrollable && !verticallyScrollable) {
m_touchScrollAxisLatch = AxisLatchHorizontal;
if (abs(deltaFromStart.width()) >= latchAxisMovementThreshold)
m_committedToScrollAxis = true;
} else {
m_committedToScrollAxis = true;
if (m_touchScrollAxisLatch == AxisLatchNotComputed) {
const float lockAngleDegrees = 20;
if (deltaFromStart.width() && deltaFromStart.height()) {
float dragAngle = atanf(static_cast<float>(abs(deltaFromStart.height())) / abs(deltaFromStart.width()));
if (dragAngle <= deg2rad(lockAngleDegrees))
m_touchScrollAxisLatch = AxisLatchHorizontal;
else if (dragAngle >= deg2rad(90 - lockAngleDegrees))
m_touchScrollAxisLatch = AxisLatchVertical;
}
}
}
if (!m_committedToScrollAxis)
return false;
}
bool handled = false;
// Horizontal
if (m_touchScrollAxisLatch != AxisLatchVertical) {
int delta = touchDelta.width();
handled |= m_scrollableAreaForTouchSequence->scroll(delta < 0 ? ScrollLeft : ScrollRight, ScrollByPixel, abs(delta));
}
// Vertical
if (m_touchScrollAxisLatch != AxisLatchHorizontal) {
int delta = touchDelta.height();
handled |= m_scrollableAreaForTouchSequence->scroll(delta < 0 ? ScrollUp : ScrollDown, ScrollByPixel, abs(delta));
}
// Return false until we manage to scroll at all, and then keep returning true until the gesture ends.
if (!m_startedScroll) {
if (!handled)
return false;
m_startedScroll = true;
scrollableArea().didStartScroll();
} else if (handled)
scrollableArea().didUpdateScroll();
return true;
}
void ScrollAnimatorIOS::determineScrollableAreaForTouchSequence(const IntSize& scrollDelta)
{
ASSERT(!m_scrollableAreaForTouchSequence);
ScrollableArea* scrollableArea = &m_scrollableArea;
while (true) {
if (!scrollableArea->isPinnedInBothDirections(scrollDelta))
break;
ScrollableArea* enclosingArea = scrollableArea->enclosingScrollableArea();
if (!enclosingArea)
break;
scrollableArea = enclosingArea;
}
ASSERT(scrollableArea);
m_scrollableAreaForTouchSequence = scrollableArea;
}
#endif
} // namespace WebCore
#endif // PLATFORM(IOS_FAMILY)