ScrollAnimatorMac.mm [plain text]
/*
* Copyright (C) 2010, 2011 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.
*/
#include "config.h"
#if ENABLE(SMOOTH_SCROLLING)
#include "ScrollAnimatorMac.h"
#include "BlockExceptions.h"
#include "FloatPoint.h"
#include "NSScrollerImpDetails.h"
#include "PlatformGestureEvent.h"
#include "PlatformWheelEvent.h"
#include "ScrollView.h"
#include "ScrollableArea.h"
#include "ScrollbarTheme.h"
#include "ScrollbarThemeMac.h"
#include "WebCoreSystemInterface.h"
#include <wtf/PassOwnPtr.h>
#include <wtf/UnusedParam.h>
using namespace WebCore;
using namespace std;
#if USE(SCROLLBAR_PAINTER)
static bool supportsUIStateTransitionProgress()
{
// FIXME: This is temporary until all platforms that support ScrollbarPainter support this part of the API.
static bool globalSupportsUIStateTransitionProgress = [NSClassFromString(@"NSScrollerImp") instancesRespondToSelector:@selector(mouseEnteredScroller)];
return globalSupportsUIStateTransitionProgress;
}
#endif
@interface NSObject (ScrollAnimationHelperDetails)
- (id)initWithDelegate:(id)delegate;
- (void)_stopRun;
- (BOOL)_isAnimating;
- (NSPoint)targetOrigin;
- (CGFloat)_progress;
@end
@interface ScrollAnimationHelperDelegate : NSObject
{
WebCore::ScrollAnimatorMac* _animator;
}
- (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator;
@end
static NSSize abs(NSSize size)
{
NSSize finalSize = size;
if (finalSize.width < 0)
finalSize.width = -finalSize.width;
if (finalSize.height < 0)
finalSize.height = -finalSize.height;
return finalSize;
}
@implementation ScrollAnimationHelperDelegate
- (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator
{
self = [super init];
if (!self)
return nil;
_animator = scrollAnimator;
return self;
}
- (void)scrollAnimatorDestroyed
{
_animator = 0;
}
- (NSRect)bounds
{
if (!_animator)
return NSZeroRect;
WebCore::FloatPoint currentPosition = _animator->currentPosition();
return NSMakeRect(currentPosition.x(), currentPosition.y(), 0, 0);
}
- (void)_immediateScrollToPoint:(NSPoint)newPosition
{
if (!_animator)
return;
_animator->immediateScrollToPointForScrollAnimation(newPosition);
}
- (NSPoint)_pixelAlignProposedScrollPosition:(NSPoint)newOrigin
{
return newOrigin;
}
- (NSSize)convertSizeToBase:(NSSize)size
{
return abs(size);
}
- (NSSize)convertSizeFromBase:(NSSize)size
{
return abs(size);
}
- (NSSize)convertSizeToBacking:(NSSize)size
{
return abs(size);
}
- (NSSize)convertSizeFromBacking:(NSSize)size
{
return abs(size);
}
- (id)superview
{
return nil;
}
- (id)documentView
{
return nil;
}
- (id)window
{
return nil;
}
- (void)_recursiveRecomputeToolTips
{
}
@end
#if USE(SCROLLBAR_PAINTER)
@interface ScrollbarPainterControllerDelegate : NSObject
{
WebCore::ScrollAnimatorMac* _animator;
}
- (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator;
@end
@implementation ScrollbarPainterControllerDelegate
- (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator
{
self = [super init];
if (!self)
return nil;
_animator = scrollAnimator;
return self;
}
- (void)scrollAnimatorDestroyed
{
_animator = 0;
}
- (NSRect)contentAreaRectForScrollerImpPair:(id)scrollerImpPair
{
UNUSED_PARAM(scrollerImpPair);
if (!_animator)
return NSZeroRect;
WebCore::IntSize contentsSize = _animator->scrollableArea()->contentsSize();
return NSMakeRect(0, 0, contentsSize.width(), contentsSize.height());
}
- (BOOL)inLiveResizeForScrollerImpPair:(id)scrollerImpPair
{
UNUSED_PARAM(scrollerImpPair);
if (!_animator)
return NO;
return _animator->scrollableArea()->inLiveResize();
}
- (NSPoint)mouseLocationInContentAreaForScrollerImpPair:(id)scrollerImpPair
{
UNUSED_PARAM(scrollerImpPair);
if (!_animator)
return NSZeroPoint;
return _animator->scrollableArea()->currentMousePosition();
}
- (NSPoint)scrollerImpPair:(id)scrollerImpPair convertContentPoint:(NSPoint)pointInContentArea toScrollerImp:(id)scrollerImp
{
UNUSED_PARAM(scrollerImpPair);
if (!_animator)
return NSZeroPoint;
WebCore::Scrollbar* scrollbar = 0;
if ([scrollerImp isHorizontal])
scrollbar = _animator->scrollableArea()->horizontalScrollbar();
else
scrollbar = _animator->scrollableArea()->verticalScrollbar();
// It is possible to have a null scrollbar here since it is possible for this delegate
// method to be called between the moment when a scrollbar has been set to 0 and the
// moment when its destructor has been called. We should probably de-couple some
// of the clean-up work in ScrollbarThemeMac::unregisterScrollbar() to avoid this
// issue.
if (!scrollbar)
return WebCore::IntPoint();
return scrollbar->convertFromContainingView(WebCore::IntPoint(pointInContentArea));
}
- (void)scrollerImpPair:(id)scrollerImpPair setContentAreaNeedsDisplayInRect:(NSRect)rect
{
UNUSED_PARAM(scrollerImpPair);
UNUSED_PARAM(rect);
}
- (void)scrollerImpPair:(id)scrollerImpPair updateScrollerStyleForNewRecommendedScrollerStyle:(NSScrollerStyle)newRecommendedScrollerStyle
{
if (!_animator)
return;
[scrollerImpPair setScrollerStyle:newRecommendedScrollerStyle];
_animator->updateScrollerStyle();
}
@end
enum FeatureToAnimate {
ThumbAlpha,
TrackAlpha,
UIStateTransition
};
@interface ScrollbarPartAnimation : NSAnimation
{
RetainPtr<ScrollbarPainter> _scrollerPainter;
FeatureToAnimate _featureToAnimate;
ScrollAnimatorMac* _animator;
CGFloat _startValue;
CGFloat _endValue;
}
- (id)initWithScrollbarPainter:(ScrollbarPainter)scrollerPainter animate:(FeatureToAnimate)featureToAnimate scrollAnimator:(ScrollAnimatorMac*)scrollAnimator animateFrom:(CGFloat)startValue animateTo:(CGFloat)endValue duration:(NSTimeInterval)duration;
@end
@implementation ScrollbarPartAnimation
- (id)initWithScrollbarPainter:(ScrollbarPainter)scrollerPainter animate:(FeatureToAnimate)featureToAnimate scrollAnimator:(ScrollAnimatorMac*)scrollAnimator animateFrom:(CGFloat)startValue animateTo:(CGFloat)endValue duration:(NSTimeInterval)duration
{
self = [super initWithDuration:duration animationCurve:NSAnimationEaseInOut];
if (!self)
return nil;
_scrollerPainter = scrollerPainter;
_featureToAnimate = featureToAnimate;
_animator = scrollAnimator;
_startValue = startValue;
_endValue = endValue;
return self;
}
- (void)setScrollbarPainter:(ScrollbarPainter)scrollerPainter
{
_scrollerPainter = scrollerPainter;
}
- (void)setStartValue:(CGFloat)startValue
{
_startValue = startValue;
}
- (void)setEndValue:(CGFloat)endValue
{
_endValue = endValue;
}
- (void)setCurrentProgress:(NSAnimationProgress)progress
{
[super setCurrentProgress:progress];
if (!_animator)
return;
CGFloat currentValue;
if (_startValue > _endValue)
currentValue = 1 - progress;
else
currentValue = progress;
switch (_featureToAnimate) {
case ThumbAlpha:
[_scrollerPainter.get() setKnobAlpha:currentValue];
break;
case TrackAlpha:
[_scrollerPainter.get() setTrackAlpha:currentValue];
break;
case UIStateTransition:
[_scrollerPainter.get() setUiStateTransitionProgress:currentValue];
break;
}
// Invalidate the scrollbars so that they paint the animation
if (Scrollbar* verticalScrollbar = _animator->scrollableArea()->verticalScrollbar())
verticalScrollbar->invalidate();
if (Scrollbar* horizontalScrollbar = _animator->scrollableArea()->horizontalScrollbar())
horizontalScrollbar->invalidate();
}
- (void)scrollAnimatorDestroyed
{
BEGIN_BLOCK_OBJC_EXCEPTIONS;
[self stopAnimation];
END_BLOCK_OBJC_EXCEPTIONS;
_animator = 0;
}
@end
@interface ScrollbarPainterDelegate : NSObject<NSAnimationDelegate>
{
WebCore::ScrollAnimatorMac* _animator;
RetainPtr<ScrollbarPartAnimation> _verticalKnobAnimation;
RetainPtr<ScrollbarPartAnimation> _horizontalKnobAnimation;
RetainPtr<ScrollbarPartAnimation> _verticalTrackAnimation;
RetainPtr<ScrollbarPartAnimation> _horizontalTrackAnimation;
RetainPtr<ScrollbarPartAnimation> _verticalUIStateTransitionAnimation;
RetainPtr<ScrollbarPartAnimation> _horizontalUIStateTransitionAnimation;
}
- (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator;
- (void)cancelAnimations;
@end
@implementation ScrollbarPainterDelegate
- (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator
{
self = [super init];
if (!self)
return nil;
_animator = scrollAnimator;
return self;
}
- (void)cancelAnimations
{
BEGIN_BLOCK_OBJC_EXCEPTIONS;
[_verticalKnobAnimation.get() stopAnimation];
[_horizontalKnobAnimation.get() stopAnimation];
[_verticalTrackAnimation.get() stopAnimation];
[_horizontalTrackAnimation.get() stopAnimation];
[_verticalUIStateTransitionAnimation.get() stopAnimation];
[_horizontalUIStateTransitionAnimation.get() stopAnimation];
END_BLOCK_OBJC_EXCEPTIONS;
}
- (NSRect)convertRectToBacking:(NSRect)aRect
{
return aRect;
}
- (NSRect)convertRectFromBacking:(NSRect)aRect
{
return aRect;
}
- (CALayer *)layer
{
if (!_animator)
return nil;
if (!_animator->isDrawingIntoLayer())
return nil;
// FIXME: This should attempt to return an actual layer.
static CALayer *dummyLayer = [[CALayer alloc] init];
return dummyLayer;
}
- (NSPoint)mouseLocationInScrollerForScrollerImp:(id)scrollerImp
{
if (!_animator)
return NSZeroPoint;
ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
Scrollbar* scrollbar;
if ([scrollerPainter isHorizontal])
scrollbar = _animator->scrollableArea()->horizontalScrollbar();
else
scrollbar = _animator->scrollableArea()->verticalScrollbar();
if (!scrollbar)
return NSZeroPoint;
return scrollbar->convertFromContainingView(_animator->scrollableArea()->currentMousePosition());
}
- (void)setUpAlphaAnimation:(RetainPtr<ScrollbarPartAnimation>&)scrollbarPartAnimation scrollerPainter:(ScrollbarPainter)scrollerPainter part:(WebCore::ScrollbarPart)part animateAlphaTo:(CGFloat)newAlpha duration:(NSTimeInterval)duration
{
// If the user has scrolled the page, then the scrollbars must be animated here.
// This overrides the early returns.
bool mustAnimate = _animator->haveScrolledSincePageLoad();
if (_animator->scrollbarPaintTimerIsActive() && !mustAnimate)
return;
if (_animator->scrollableArea()->shouldSuspendScrollAnimations() && !mustAnimate) {
_animator->startScrollbarPaintTimer();
return;
}
// At this point, we are definitely going to animate now, so stop the timer.
_animator->stopScrollbarPaintTimer();
// If we are currently animating, stop
if (scrollbarPartAnimation) {
[scrollbarPartAnimation.get() stopAnimation];
scrollbarPartAnimation = nil;
}
if (part == WebCore::ThumbPart && ![scrollerPainter isHorizontal]) {
if (newAlpha == 1) {
IntRect thumbRect = IntRect([scrollerPainter rectForPart:NSScrollerKnob]);
_animator->setVisibleScrollerThumbRect(thumbRect);
} else
_animator->setVisibleScrollerThumbRect(IntRect());
}
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:duration];
scrollbarPartAnimation.adoptNS([[ScrollbarPartAnimation alloc] initWithScrollbarPainter:scrollerPainter
animate:part == ThumbPart ? ThumbAlpha : TrackAlpha
scrollAnimator:_animator
animateFrom:part == ThumbPart ? [scrollerPainter knobAlpha] : [scrollerPainter trackAlpha]
animateTo:newAlpha
duration:duration]);
[scrollbarPartAnimation.get() setAnimationBlockingMode:NSAnimationNonblocking];
[scrollbarPartAnimation.get() startAnimation];
[NSAnimationContext endGrouping];
}
- (void)scrollerImp:(id)scrollerImp animateKnobAlphaTo:(CGFloat)newKnobAlpha duration:(NSTimeInterval)duration
{
if (!_animator)
return;
ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
if ([scrollerImp isHorizontal])
[self setUpAlphaAnimation:_horizontalKnobAnimation scrollerPainter:scrollerPainter part:WebCore::ThumbPart animateAlphaTo:newKnobAlpha duration:duration];
else
[self setUpAlphaAnimation:_verticalKnobAnimation scrollerPainter:scrollerPainter part:WebCore::ThumbPart animateAlphaTo:newKnobAlpha duration:duration];
}
- (void)scrollerImp:(id)scrollerImp animateTrackAlphaTo:(CGFloat)newTrackAlpha duration:(NSTimeInterval)duration
{
if (!_animator)
return;
ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
if ([scrollerImp isHorizontal])
[self setUpAlphaAnimation:_horizontalTrackAnimation scrollerPainter:scrollerPainter part:WebCore::BackTrackPart animateAlphaTo:newTrackAlpha duration:duration];
else
[self setUpAlphaAnimation:_verticalTrackAnimation scrollerPainter:scrollerPainter part:WebCore::BackTrackPart animateAlphaTo:newTrackAlpha duration:duration];
}
- (void)scrollerImp:(id)scrollerImp animateUIStateTransitionWithDuration:(NSTimeInterval)duration
{
if (!_animator)
return;
if (!supportsUIStateTransitionProgress())
return;
ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
RetainPtr<ScrollbarPartAnimation> scrollbarPartAnimation = [scrollerPainter isHorizontal] ? _horizontalUIStateTransitionAnimation : _verticalUIStateTransitionAnimation;
// UIStateTransition always animates to 1. In case an animation is in progress this avoids a hard transition.
[scrollerPainter setUiStateTransitionProgress:1 - [scrollerPainter uiStateTransitionProgress]];
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:duration];
if (!scrollbarPartAnimation) {
scrollbarPartAnimation.adoptNS([[ScrollbarPartAnimation alloc] initWithScrollbarPainter:scrollerPainter
animate:UIStateTransition
scrollAnimator:_animator
animateFrom:[scrollerPainter uiStateTransitionProgress]
animateTo:1.0f
duration:duration]);
[scrollbarPartAnimation.get() setAnimationBlockingMode:NSAnimationNonblocking];
} else {
// If we don't need to initialize the animation, just reset the values in case they have changed.
[scrollbarPartAnimation.get() setScrollbarPainter:scrollerPainter];
[scrollbarPartAnimation.get() setStartValue:[scrollerPainter uiStateTransitionProgress]];
[scrollbarPartAnimation.get() setEndValue:1.0f];
[scrollbarPartAnimation.get() setDuration:duration];
}
[scrollbarPartAnimation.get() startAnimation];
[NSAnimationContext endGrouping];
}
- (void)scrollerImp:(id)scrollerImp overlayScrollerStateChangedTo:(NSUInteger)newOverlayScrollerState
{
UNUSED_PARAM(scrollerImp);
UNUSED_PARAM(newOverlayScrollerState);
}
- (void)scrollAnimatorDestroyed
{
_animator = 0;
BEGIN_BLOCK_OBJC_EXCEPTIONS;
[_verticalKnobAnimation.get() scrollAnimatorDestroyed];
[_horizontalKnobAnimation.get() scrollAnimatorDestroyed];
[_verticalTrackAnimation.get() scrollAnimatorDestroyed];
[_horizontalTrackAnimation.get() scrollAnimatorDestroyed];
[_verticalUIStateTransitionAnimation.get() scrollAnimatorDestroyed];
[_horizontalUIStateTransitionAnimation.get() scrollAnimatorDestroyed];
END_BLOCK_OBJC_EXCEPTIONS;
}
@end
#endif // USE(SCROLLBAR_PAINTER)
namespace WebCore {
PassOwnPtr<ScrollAnimator> ScrollAnimator::create(ScrollableArea* scrollableArea)
{
return adoptPtr(new ScrollAnimatorMac(scrollableArea));
}
ScrollAnimatorMac::ScrollAnimatorMac(ScrollableArea* scrollableArea)
: ScrollAnimator(scrollableArea)
#if USE(SCROLLBAR_PAINTER)
, m_initialScrollbarPaintTimer(this, &ScrollAnimatorMac::initialScrollbarPaintTimerFired)
#endif
#if ENABLE(RUBBER_BANDING)
, m_inScrollGesture(false)
, m_momentumScrollInProgress(false)
, m_ignoreMomentumScrolls(false)
, m_lastMomentumScrollTimestamp(0)
, m_startTime(0)
, m_snapRubberBandTimer(this, &ScrollAnimatorMac::snapRubberBandTimerFired)
#endif
, m_drawingIntoLayer(false)
, m_haveScrolledSincePageLoad(false)
, m_needsScrollerStyleUpdate(false)
{
m_scrollAnimationHelperDelegate.adoptNS([[ScrollAnimationHelperDelegate alloc] initWithScrollAnimator:this]);
m_scrollAnimationHelper.adoptNS([[NSClassFromString(@"NSScrollAnimationHelper") alloc] initWithDelegate:m_scrollAnimationHelperDelegate.get()]);
#if USE(SCROLLBAR_PAINTER)
m_scrollbarPainterControllerDelegate.adoptNS([[ScrollbarPainterControllerDelegate alloc] initWithScrollAnimator:this]);
m_scrollbarPainterController = [[[NSClassFromString(@"NSScrollerImpPair") alloc] init] autorelease];
[m_scrollbarPainterController.get() setDelegate:m_scrollbarPainterControllerDelegate.get()];
[m_scrollbarPainterController.get() setScrollerStyle:wkRecommendedScrollerStyle()];
m_scrollbarPainterDelegate.adoptNS([[ScrollbarPainterDelegate alloc] initWithScrollAnimator:this]);
#endif
}
ScrollAnimatorMac::~ScrollAnimatorMac()
{
#if USE(SCROLLBAR_PAINTER)
BEGIN_BLOCK_OBJC_EXCEPTIONS;
[m_scrollbarPainterControllerDelegate.get() scrollAnimatorDestroyed];
[m_scrollbarPainterController.get() setDelegate:nil];
[m_scrollbarPainterDelegate.get() scrollAnimatorDestroyed];
[m_scrollAnimationHelperDelegate.get() scrollAnimatorDestroyed];
END_BLOCK_OBJC_EXCEPTIONS;
#endif
}
bool ScrollAnimatorMac::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float multiplier)
{
m_haveScrolledSincePageLoad = true;
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"AppleScrollAnimationEnabled"])
return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
if (granularity == ScrollByPixel)
return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
float currentPos = orientation == HorizontalScrollbar ? m_currentPosX : m_currentPosY;
float newPos = std::max<float>(std::min<float>(currentPos + (step * multiplier), static_cast<float>(m_scrollableArea->scrollSize(orientation))), 0);
if (currentPos == newPos)
return false;
NSPoint newPoint;
if ([m_scrollAnimationHelper.get() _isAnimating]) {
NSPoint targetOrigin = [m_scrollAnimationHelper.get() targetOrigin];
newPoint = orientation == HorizontalScrollbar ? NSMakePoint(newPos, targetOrigin.y) : NSMakePoint(targetOrigin.x, newPos);
} else {
newPoint = orientation == HorizontalScrollbar ? NSMakePoint(newPos, m_currentPosY) : NSMakePoint(m_currentPosX, newPos);
m_scrollableArea->didStartAnimatedScroll();
}
[m_scrollAnimationHelper.get() scrollToPoint:newPoint];
return true;
}
void ScrollAnimatorMac::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
{
[m_scrollAnimationHelper.get() _stopRun];
immediateScrollToPoint(offset);
}
float ScrollAnimatorMac::adjustScrollXPositionIfNecessary(float position) const
{
if (!m_scrollableArea->constrainsScrollingToContentEdge())
return position;
return max<float>(min<float>(position, m_scrollableArea->contentsSize().width() - m_scrollableArea->visibleWidth()), 0);
}
float ScrollAnimatorMac::adjustScrollYPositionIfNecessary(float position) const
{
if (!m_scrollableArea->constrainsScrollingToContentEdge())
return position;
return max<float>(min<float>(position, m_scrollableArea->contentsSize().height() - m_scrollableArea->visibleHeight()), 0);
}
FloatPoint ScrollAnimatorMac::adjustScrollPositionIfNecessary(const FloatPoint& position) const
{
if (!m_scrollableArea->constrainsScrollingToContentEdge())
return position;
float newX = max<float>(min<float>(position.x(), m_scrollableArea->contentsSize().width() - m_scrollableArea->visibleWidth()), 0);
float newY = max<float>(min<float>(position.y(), m_scrollableArea->contentsSize().height() - m_scrollableArea->visibleHeight()), 0);
return FloatPoint(newX, newY);
}
void ScrollAnimatorMac::immediateScrollToPoint(const FloatPoint& newPosition)
{
FloatPoint adjustedPosition = adjustScrollPositionIfNecessary(newPosition);
if (adjustedPosition.x() == m_currentPosX && adjustedPosition.y() == m_currentPosY)
return;
m_currentPosX = adjustedPosition.x();
m_currentPosY = adjustedPosition.y();
notityPositionChanged();
}
void ScrollAnimatorMac::immediateScrollByDeltaX(float deltaX)
{
float newPosX = adjustScrollXPositionIfNecessary(m_currentPosX + deltaX);
if (newPosX == m_currentPosX)
return;
m_currentPosX = newPosX;
notityPositionChanged();
}
void ScrollAnimatorMac::immediateScrollByDeltaY(float deltaY)
{
float newPosY = adjustScrollYPositionIfNecessary(m_currentPosY + deltaY);
if (newPosY == m_currentPosY)
return;
m_currentPosY = newPosY;
notityPositionChanged();
}
void ScrollAnimatorMac::immediateScrollToPointForScrollAnimation(const FloatPoint& newPosition)
{
ASSERT(m_scrollAnimationHelper);
CGFloat progress = [m_scrollAnimationHelper.get() _progress];
immediateScrollToPoint(newPosition);
if (progress >= 1.0)
m_scrollableArea->didCompleteAnimatedScroll();
}
void ScrollAnimatorMac::notityPositionChanged()
{
#if USE(SCROLLBAR_PAINTER)
// This function is called when a page is going into the page cache, but the page
// isn't really scrolling in that case. We should only pass the message on to the
// ScrollbarPainterController when we're really scrolling on an active page.
if (scrollableArea()->isOnActivePage())
[m_scrollbarPainterController.get() contentAreaScrolled];
#endif
ScrollAnimator::notityPositionChanged();
}
void ScrollAnimatorMac::contentAreaWillPaint() const
{
if (!scrollableArea()->isOnActivePage())
return;
#if USE(SCROLLBAR_PAINTER)
[m_scrollbarPainterController.get() contentAreaWillDraw];
#endif
}
void ScrollAnimatorMac::mouseEnteredContentArea() const
{
if (!scrollableArea()->isOnActivePage())
return;
#if USE(SCROLLBAR_PAINTER)
[m_scrollbarPainterController.get() mouseEnteredContentArea];
#endif
}
void ScrollAnimatorMac::mouseExitedContentArea() const
{
if (!scrollableArea()->isOnActivePage())
return;
#if USE(SCROLLBAR_PAINTER)
[m_scrollbarPainterController.get() mouseExitedContentArea];
#endif
}
void ScrollAnimatorMac::mouseMovedInContentArea() const
{
if (!scrollableArea()->isOnActivePage())
return;
#if USE(SCROLLBAR_PAINTER)
[m_scrollbarPainterController.get() mouseMovedInContentArea];
#endif
}
void ScrollAnimatorMac::mouseEnteredScrollbar(Scrollbar* scrollbar) const
{
if (!scrollableArea()->isOnActivePage())
return;
#if USE(SCROLLBAR_PAINTER)
if (!supportsUIStateTransitionProgress())
return;
if (ScrollbarThemeMac* theme = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())) {
ScrollbarPainter painter = theme->painterForScrollbar(scrollbar);
[painter mouseEnteredScroller];
}
#else
UNUSED_PARAM(scrollbar);
#endif
}
void ScrollAnimatorMac::mouseExitedScrollbar(Scrollbar* scrollbar) const
{
if (!scrollableArea()->isOnActivePage())
return;
#if USE(SCROLLBAR_PAINTER)
if (!supportsUIStateTransitionProgress())
return;
if (ScrollbarThemeMac* theme = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())) {
ScrollbarPainter painter = theme->painterForScrollbar(scrollbar);
[painter mouseExitedScroller];
}
#else
UNUSED_PARAM(scrollbar);
#endif
}
void ScrollAnimatorMac::willStartLiveResize()
{
if (!scrollableArea()->isOnActivePage())
return;
#if USE(SCROLLBAR_PAINTER)
[m_scrollbarPainterController.get() startLiveResize];
#endif
}
void ScrollAnimatorMac::contentsResized() const
{
if (!scrollableArea()->isOnActivePage())
return;
#if USE(SCROLLBAR_PAINTER)
[m_scrollbarPainterController.get() contentAreaDidResize];
#endif
}
void ScrollAnimatorMac::willEndLiveResize()
{
if (!scrollableArea()->isOnActivePage())
return;
#if USE(SCROLLBAR_PAINTER)
[m_scrollbarPainterController.get() endLiveResize];
#endif
}
void ScrollAnimatorMac::contentAreaDidShow() const
{
if (!scrollableArea()->isOnActivePage())
return;
#if USE(SCROLLBAR_PAINTER)
[m_scrollbarPainterController.get() windowOrderedIn];
#endif
}
void ScrollAnimatorMac::contentAreaDidHide() const
{
if (!scrollableArea()->isOnActivePage())
return;
#if USE(SCROLLBAR_PAINTER)
[m_scrollbarPainterController.get() windowOrderedOut];
#endif
}
void ScrollAnimatorMac::didBeginScrollGesture() const
{
if (!scrollableArea()->isOnActivePage())
return;
#if USE(SCROLLBAR_PAINTER)
[m_scrollbarPainterController.get() beginScrollGesture];
#endif
}
void ScrollAnimatorMac::didEndScrollGesture() const
{
if (!scrollableArea()->isOnActivePage())
return;
#if USE(SCROLLBAR_PAINTER)
[m_scrollbarPainterController.get() endScrollGesture];
#endif
}
void ScrollAnimatorMac::didAddVerticalScrollbar(Scrollbar* scrollbar)
{
#if USE(SCROLLBAR_PAINTER)
ScrollbarPainter painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
[painter setDelegate:m_scrollbarPainterDelegate.get()];
[m_scrollbarPainterController.get() setVerticalScrollerImp:painter];
if (scrollableArea()->inLiveResize())
[painter setKnobAlpha:1];
#else
UNUSED_PARAM(scrollbar);
#endif
}
void ScrollAnimatorMac::willRemoveVerticalScrollbar(Scrollbar* scrollbar)
{
#if USE(SCROLLBAR_PAINTER)
ScrollbarPainter painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
[painter setDelegate:nil];
[m_scrollbarPainterController.get() setVerticalScrollerImp:nil];
#else
UNUSED_PARAM(scrollbar);
#endif
}
void ScrollAnimatorMac::didAddHorizontalScrollbar(Scrollbar* scrollbar)
{
#if USE(SCROLLBAR_PAINTER)
ScrollbarPainter painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
[painter setDelegate:m_scrollbarPainterDelegate.get()];
[m_scrollbarPainterController.get() setHorizontalScrollerImp:painter];
if (scrollableArea()->inLiveResize())
[painter setKnobAlpha:1];
#else
UNUSED_PARAM(scrollbar);
#endif
}
void ScrollAnimatorMac::willRemoveHorizontalScrollbar(Scrollbar* scrollbar)
{
#if USE(SCROLLBAR_PAINTER)
ScrollbarPainter painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
[painter setDelegate:nil];
[m_scrollbarPainterController.get() setHorizontalScrollerImp:nil];
#else
UNUSED_PARAM(scrollbar);
#endif
}
void ScrollAnimatorMac::cancelAnimations()
{
m_haveScrolledSincePageLoad = false;
#if USE(SCROLLBAR_PAINTER)
if (scrollbarPaintTimerIsActive())
stopScrollbarPaintTimer();
[m_scrollbarPainterDelegate.get() cancelAnimations];
#endif
}
#if ENABLE(RUBBER_BANDING)
static const float scrollVelocityZeroingTimeout = 0.10f;
static const float rubberbandStiffness = 20;
static const float rubberbandDirectionLockStretchRatio = 1;
static const float rubberbandMinimumRequiredDeltaBeforeStretch = 10;
static const float rubberbandAmplitude = 0.31f;
static const float rubberbandPeriod = 1.6f;
static float elasticDeltaForTimeDelta(float initialPosition, float initialVelocity, float elapsedTime)
{
float amplitude = rubberbandAmplitude;
float period = rubberbandPeriod;
float criticalDampeningFactor = expf((-elapsedTime * rubberbandStiffness) / period);
return (initialPosition + (-initialVelocity * elapsedTime * amplitude)) * criticalDampeningFactor;
}
static float elasticDeltaForReboundDelta(float delta)
{
float stiffness = std::max(rubberbandStiffness, 1.0f);
return delta / stiffness;
}
static float reboundDeltaForElasticDelta(float delta)
{
return delta * rubberbandStiffness;
}
static float scrollWheelMultiplier()
{
static float multiplier = -1;
if (multiplier < 0) {
multiplier = [[NSUserDefaults standardUserDefaults] floatForKey:@"NSScrollWheelMultiplier"];
if (multiplier <= 0)
multiplier = 1;
}
return multiplier;
}
static inline bool isScrollingLeftAndShouldNotRubberBand(PlatformWheelEvent& wheelEvent, ScrollableArea* scrollableArea)
{
return wheelEvent.deltaX() > 0 && !scrollableArea->shouldRubberBandInDirection(ScrollLeft);
}
static inline bool isScrollingRightAndShouldNotRubberBand(PlatformWheelEvent& wheelEvent, ScrollableArea* scrollableArea)
{
return wheelEvent.deltaX() < 0 && !scrollableArea->shouldRubberBandInDirection(ScrollRight);
}
void ScrollAnimatorMac::handleWheelEvent(PlatformWheelEvent& wheelEvent)
{
m_haveScrolledSincePageLoad = true;
if (!wheelEvent.hasPreciseScrollingDeltas()) {
ScrollAnimator::handleWheelEvent(wheelEvent);
return;
}
// FIXME: This is somewhat roundabout hack to allow forwarding wheel events
// up to the parent scrollable area. It takes advantage of the fact that
// the base class implemenatation of handleWheelEvent will not accept the
// wheel event if there is nowhere to scroll.
if (fabsf(wheelEvent.deltaY()) >= fabsf(wheelEvent.deltaX())) {
if (!allowsVerticalStretching()) {
ScrollAnimator::handleWheelEvent(wheelEvent);
return;
}
} else {
if (!allowsHorizontalStretching()) {
ScrollAnimator::handleWheelEvent(wheelEvent);
return;
}
if (m_scrollableArea->horizontalScrollbar()) {
// If there is a scrollbar, we aggregate the wheel events to get an
// overall trend of the scroll. If the direction of the scroll is ever
// in the opposite direction of the pin location, then we switch the
// boolean, and rubber band. That is, if we were pinned to the left,
// and we ended up scrolling to the right, we rubber band.
m_cumulativeHorizontalScroll += wheelEvent.deltaX();
if (m_scrollerInitiallyPinnedOnLeft && m_cumulativeHorizontalScroll < 0)
m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin = true;
if (m_scrollerInitiallyPinnedOnRight && m_cumulativeHorizontalScroll > 0)
m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin = true;
}
// After a gesture begins, we go through:
// 1+ PlatformWheelEventPhaseNone
// 0+ PlatformWheelEventPhaseChanged
// 1 PlatformWheelEventPhaseEnded if there was at least one changed event
if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseNone && !m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin) {
if ((isScrollingLeftAndShouldNotRubberBand(wheelEvent, m_scrollableArea) &&
m_scrollerInitiallyPinnedOnLeft &&
m_scrollableArea->isHorizontalScrollerPinnedToMinimumPosition()) ||
(isScrollingRightAndShouldNotRubberBand(wheelEvent, m_scrollableArea) &&
m_scrollerInitiallyPinnedOnRight &&
m_scrollableArea->isHorizontalScrollerPinnedToMaximumPosition())) {
ScrollAnimator::handleWheelEvent(wheelEvent);
return;
}
}
}
bool isMomentumScrollEvent = (wheelEvent.momentumPhase() != PlatformWheelEventPhaseNone);
if (m_ignoreMomentumScrolls && (isMomentumScrollEvent || m_snapRubberBandTimer.isActive())) {
if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseEnded) {
m_ignoreMomentumScrolls = false;
wheelEvent.accept();
}
return;
}
wheelEvent.accept();
smoothScrollWithEvent(wheelEvent);
}
void ScrollAnimatorMac::handleGestureEvent(const PlatformGestureEvent& gestureEvent)
{
if (gestureEvent.type() == PlatformGestureEvent::ScrollBeginType)
beginScrollGesture();
else
endScrollGesture();
}
bool ScrollAnimatorMac::pinnedInDirection(float deltaX, float deltaY)
{
FloatSize limitDelta;
if (fabsf(deltaY) >= fabsf(deltaX)) {
if (deltaY < 0) {
// We are trying to scroll up. Make sure we are not pinned to the top
limitDelta.setHeight(m_scrollableArea->visibleContentRect().y() + + m_scrollableArea->scrollOrigin().y());
} else {
// We are trying to scroll down. Make sure we are not pinned to the bottom
limitDelta.setHeight(m_scrollableArea->contentsSize().height() - (m_scrollableArea->visibleContentRect().maxY() + m_scrollableArea->scrollOrigin().y()));
}
} else if (deltaX != 0) {
if (deltaX < 0) {
// We are trying to scroll left. Make sure we are not pinned to the left
limitDelta.setWidth(m_scrollableArea->visibleContentRect().x() + m_scrollableArea->scrollOrigin().x());
} else {
// We are trying to scroll right. Make sure we are not pinned to the right
limitDelta.setWidth(m_scrollableArea->contentsSize().width() - (m_scrollableArea->visibleContentRect().maxX() + m_scrollableArea->scrollOrigin().x()));
}
}
if ((deltaX != 0 || deltaY != 0) && (limitDelta.width() < 1 && limitDelta.height() < 1))
return true;
return false;
}
bool ScrollAnimatorMac::allowsVerticalStretching() const
{
switch (m_scrollableArea->verticalScrollElasticity()) {
case ScrollElasticityAutomatic: {
Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
return (((vScroller && vScroller->enabled()) || (!hScroller || !hScroller->enabled())));
}
case ScrollElasticityNone:
return false;
case ScrollElasticityAllowed:
return true;
}
ASSERT_NOT_REACHED();
return false;
}
bool ScrollAnimatorMac::allowsHorizontalStretching() const
{
switch (m_scrollableArea->horizontalScrollElasticity()) {
case ScrollElasticityAutomatic: {
Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
return (((hScroller && hScroller->enabled()) || (!vScroller || !vScroller->enabled())));
}
case ScrollElasticityNone:
return false;
case ScrollElasticityAllowed:
return true;
}
ASSERT_NOT_REACHED();
return false;
}
void ScrollAnimatorMac::smoothScrollWithEvent(PlatformWheelEvent& wheelEvent)
{
m_haveScrolledSincePageLoad = true;
float deltaX = m_overflowScrollDelta.width();
float deltaY = m_overflowScrollDelta.height();
// Reset overflow values because we may decide to remove delta at various points and put it into overflow.
m_overflowScrollDelta = FloatSize();
float eventCoalescedDeltaX = -wheelEvent.deltaX();
float eventCoalescedDeltaY = -wheelEvent.deltaY();
deltaX += eventCoalescedDeltaX;
deltaY += eventCoalescedDeltaY;
// Slightly prefer scrolling vertically by applying the = case to deltaY
if (fabsf(deltaY) >= fabsf(deltaX))
deltaX = 0;
else
deltaY = 0;
bool isVerticallyStretched = false;
bool isHorizontallyStretched = false;
bool shouldStretch = false;
IntSize stretchAmount = m_scrollableArea->overhangAmount();
isHorizontallyStretched = stretchAmount.width();
isVerticallyStretched = stretchAmount.height();
PlatformWheelEventPhase phase = wheelEvent.momentumPhase();
// If we are starting momentum scrolling then do some setup.
if (!m_momentumScrollInProgress && (phase == PlatformWheelEventPhaseBegan || phase == PlatformWheelEventPhaseChanged))
m_momentumScrollInProgress = true;
CFTimeInterval timeDelta = wheelEvent.timestamp() - m_lastMomentumScrollTimestamp;
if (m_inScrollGesture || m_momentumScrollInProgress) {
if (m_lastMomentumScrollTimestamp && timeDelta > 0 && timeDelta < scrollVelocityZeroingTimeout) {
m_momentumVelocity.setWidth(eventCoalescedDeltaX / (float)timeDelta);
m_momentumVelocity.setHeight(eventCoalescedDeltaY / (float)timeDelta);
m_lastMomentumScrollTimestamp = wheelEvent.timestamp();
} else {
m_lastMomentumScrollTimestamp = wheelEvent.timestamp();
m_momentumVelocity = FloatSize();
}
if (isVerticallyStretched) {
if (!isHorizontallyStretched && pinnedInDirection(deltaX, 0)) {
// Stretching only in the vertical.
if (deltaY != 0 && (fabsf(deltaX / deltaY) < rubberbandDirectionLockStretchRatio))
deltaX = 0;
else if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
deltaX = 0;
} else
m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
}
} else if (isHorizontallyStretched) {
// Stretching only in the horizontal.
if (pinnedInDirection(0, deltaY)) {
if (deltaX != 0 && (fabsf(deltaY / deltaX) < rubberbandDirectionLockStretchRatio))
deltaY = 0;
else if (fabsf(deltaY) < rubberbandMinimumRequiredDeltaBeforeStretch) {
m_overflowScrollDelta.setHeight(m_overflowScrollDelta.height() + deltaY);
deltaY = 0;
} else
m_overflowScrollDelta.setHeight(m_overflowScrollDelta.height() + deltaY);
}
} else {
// Not stretching at all yet.
if (pinnedInDirection(deltaX, deltaY)) {
if (fabsf(deltaY) >= fabsf(deltaX)) {
if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
deltaX = 0;
} else
m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
}
shouldStretch = true;
}
}
}
if (deltaX != 0 || deltaY != 0) {
if (!(shouldStretch || isVerticallyStretched || isHorizontallyStretched)) {
if (deltaY != 0) {
deltaY *= scrollWheelMultiplier();
immediateScrollByDeltaY(deltaY);
}
if (deltaX != 0) {
deltaX *= scrollWheelMultiplier();
immediateScrollByDeltaX(deltaX);
}
} else {
if (!allowsHorizontalStretching()) {
deltaX = 0;
eventCoalescedDeltaX = 0;
} else if ((deltaX != 0) && !isHorizontallyStretched && !pinnedInDirection(deltaX, 0)) {
deltaX *= scrollWheelMultiplier();
m_scrollableArea->setConstrainsScrollingToContentEdge(false);
immediateScrollByDeltaX(deltaX);
m_scrollableArea->setConstrainsScrollingToContentEdge(true);
deltaX = 0;
}
if (!allowsVerticalStretching()) {
deltaY = 0;
eventCoalescedDeltaY = 0;
} else if ((deltaY != 0) && !isVerticallyStretched && !pinnedInDirection(0, deltaY)) {
deltaY *= scrollWheelMultiplier();
m_scrollableArea->setConstrainsScrollingToContentEdge(false);
immediateScrollByDeltaY(deltaY);
m_scrollableArea->setConstrainsScrollingToContentEdge(true);
deltaY = 0;
}
IntSize stretchAmount = m_scrollableArea->overhangAmount();
if (m_momentumScrollInProgress) {
if ((pinnedInDirection(eventCoalescedDeltaX, eventCoalescedDeltaY) || (fabsf(eventCoalescedDeltaX) + fabsf(eventCoalescedDeltaY) <= 0)) && m_lastMomentumScrollTimestamp) {
m_ignoreMomentumScrolls = true;
m_momentumScrollInProgress = false;
snapRubberBand();
}
}
m_stretchScrollForce.setWidth(m_stretchScrollForce.width() + deltaX);
m_stretchScrollForce.setHeight(m_stretchScrollForce.height() + deltaY);
FloatSize dampedDelta(ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.width())), ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.height())));
FloatPoint origOrigin = (m_scrollableArea->visibleContentRect().location() + m_scrollableArea->scrollOrigin()) - stretchAmount;
FloatPoint newOrigin = origOrigin + dampedDelta;
if (origOrigin != newOrigin) {
m_scrollableArea->setConstrainsScrollingToContentEdge(false);
immediateScrollToPoint(newOrigin);
m_scrollableArea->setConstrainsScrollingToContentEdge(true);
}
}
}
if (m_momentumScrollInProgress && phase == PlatformWheelEventPhaseEnded) {
m_momentumScrollInProgress = false;
m_ignoreMomentumScrolls = false;
m_lastMomentumScrollTimestamp = 0;
}
}
void ScrollAnimatorMac::beginScrollGesture()
{
didBeginScrollGesture();
m_haveScrolledSincePageLoad = true;
m_inScrollGesture = true;
m_momentumScrollInProgress = false;
m_ignoreMomentumScrolls = false;
m_lastMomentumScrollTimestamp = 0;
m_momentumVelocity = FloatSize();
m_scrollerInitiallyPinnedOnLeft = m_scrollableArea->isHorizontalScrollerPinnedToMinimumPosition();
m_scrollerInitiallyPinnedOnRight = m_scrollableArea->isHorizontalScrollerPinnedToMaximumPosition();
m_cumulativeHorizontalScroll = 0;
m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin = false;
IntSize stretchAmount = m_scrollableArea->overhangAmount();
m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(stretchAmount.width()));
m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(stretchAmount.height()));
m_overflowScrollDelta = FloatSize();
if (m_snapRubberBandTimer.isActive())
m_snapRubberBandTimer.stop();
}
void ScrollAnimatorMac::endScrollGesture()
{
didEndScrollGesture();
snapRubberBand();
}
void ScrollAnimatorMac::snapRubberBand()
{
CFTimeInterval timeDelta = [[NSProcessInfo processInfo] systemUptime] - m_lastMomentumScrollTimestamp;
if (m_lastMomentumScrollTimestamp && timeDelta >= scrollVelocityZeroingTimeout)
m_momentumVelocity = FloatSize();
m_inScrollGesture = false;
if (m_snapRubberBandTimer.isActive())
return;
m_startTime = [NSDate timeIntervalSinceReferenceDate];
m_startStretch = FloatSize();
m_origOrigin = FloatPoint();
m_origVelocity = FloatSize();
m_snapRubberBandTimer.startRepeating(1.0/60.0);
}
static inline float roundTowardZero(float num)
{
return num > 0 ? ceilf(num - 0.5f) : floorf(num + 0.5f);
}
static inline float roundToDevicePixelTowardZero(float num)
{
float roundedNum = roundf(num);
if (fabs(num - roundedNum) < 0.125)
num = roundedNum;
return roundTowardZero(num);
}
void ScrollAnimatorMac::snapRubberBandTimerFired(Timer<ScrollAnimatorMac>*)
{
if (!m_momentumScrollInProgress || m_ignoreMomentumScrolls) {
CFTimeInterval timeDelta = [NSDate timeIntervalSinceReferenceDate] - m_startTime;
if (m_startStretch == FloatSize()) {
m_startStretch = m_scrollableArea->overhangAmount();
if (m_startStretch == FloatSize()) {
m_snapRubberBandTimer.stop();
m_stretchScrollForce = FloatSize();
m_startTime = 0;
m_startStretch = FloatSize();
m_origOrigin = FloatPoint();
m_origVelocity = FloatSize();
return;
}
m_scrollableArea->didStartRubberBand(roundedIntSize(m_startStretch));
m_origOrigin = (m_scrollableArea->visibleContentRect().location() + m_scrollableArea->scrollOrigin()) - m_startStretch;
m_origVelocity = m_momentumVelocity;
// Just like normal scrolling, prefer vertical rubberbanding
if (fabsf(m_origVelocity.height()) >= fabsf(m_origVelocity.width()))
m_origVelocity.setWidth(0);
// Don't rubber-band horizontally if it's not possible to scroll horizontally
Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
if (!hScroller || !hScroller->enabled())
m_origVelocity.setWidth(0);
// Don't rubber-band vertically if it's not possible to scroll horizontally
Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
if (!vScroller || !vScroller->enabled())
m_origVelocity.setHeight(0);
}
FloatPoint delta(roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_startStretch.width(), -m_origVelocity.width(), (float)timeDelta)),
roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_startStretch.height(), -m_origVelocity.height(), (float)timeDelta)));
if (fabs(delta.x()) >= 1 || fabs(delta.y()) >= 1) {
FloatPoint newOrigin = m_origOrigin + delta;
m_scrollableArea->setConstrainsScrollingToContentEdge(false);
immediateScrollToPoint(newOrigin);
m_scrollableArea->setConstrainsScrollingToContentEdge(true);
FloatSize newStretch = m_scrollableArea->overhangAmount();
m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(newStretch.width()));
m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(newStretch.height()));
} else {
immediateScrollToPoint(m_origOrigin);
m_scrollableArea->didCompleteRubberBand(roundedIntSize(m_startStretch));
m_snapRubberBandTimer.stop();
m_stretchScrollForce = FloatSize();
m_startTime = 0;
m_startStretch = FloatSize();
m_origOrigin = FloatPoint();
m_origVelocity = FloatSize();
}
} else {
m_startTime = [NSDate timeIntervalSinceReferenceDate];
m_startStretch = FloatSize();
}
}
#endif
void ScrollAnimatorMac::setIsActive()
{
#if USE(SCROLLBAR_PAINTER)
if (needsScrollerStyleUpdate())
updateScrollerStyle();
#endif
}
#if USE(SCROLLBAR_PAINTER)
void ScrollAnimatorMac::updateScrollerStyle()
{
if (!scrollableArea()->isOnActivePage()) {
setNeedsScrollerStyleUpdate(true);
return;
}
ScrollbarThemeMac* macTheme = (ScrollbarThemeMac*)ScrollbarTheme::nativeTheme();
NSScrollerStyle newStyle = [m_scrollbarPainterController.get() scrollerStyle];
if (Scrollbar* verticalScrollbar = scrollableArea()->verticalScrollbar()) {
ScrollbarPainter oldVerticalPainter = [m_scrollbarPainterController.get() verticalScrollerImp];
ScrollbarPainter newVerticalPainter = [NSClassFromString(@"NSScrollerImp") scrollerImpWithStyle:newStyle
controlSize:(NSControlSize)verticalScrollbar->controlSize()
horizontal:NO
replacingScrollerImp:oldVerticalPainter];
macTheme->setNewPainterForScrollbar(verticalScrollbar, newVerticalPainter);
[m_scrollbarPainterController.get() setVerticalScrollerImp:newVerticalPainter];
// The different scrollbar styles have different thicknesses, so we must re-set the
// frameRect to the new thickness, and the re-layout below will ensure the position
// and length are properly updated.
int thickness = macTheme->scrollbarThickness(verticalScrollbar->controlSize());
verticalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
}
if (Scrollbar* horizontalScrollbar = scrollableArea()->horizontalScrollbar()) {
ScrollbarPainter oldHorizontalPainter = [m_scrollbarPainterController.get() horizontalScrollerImp];
ScrollbarPainter newHorizontalPainter = [NSClassFromString(@"NSScrollerImp") scrollerImpWithStyle:newStyle
controlSize:(NSControlSize)horizontalScrollbar->controlSize()
horizontal:YES
replacingScrollerImp:oldHorizontalPainter];
macTheme->setNewPainterForScrollbar(horizontalScrollbar, newHorizontalPainter);
[m_scrollbarPainterController.get() setVerticalScrollerImp:newHorizontalPainter];
// The different scrollbar styles have different thicknesses, so we must re-set the
// frameRect to the new thickness, and the re-layout below will ensure the position
// and length are properly updated.
int thickness = macTheme->scrollbarThickness(horizontalScrollbar->controlSize());
horizontalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
}
// If needsScrollerStyleUpdate() is true, then the page is restoring from the page cache, and
// a relayout will happen on its own. Otherwise, we must initiate a re-layout ourselves.
scrollableArea()->scrollbarStyleChanged(newStyle, !needsScrollerStyleUpdate());
setNeedsScrollerStyleUpdate(false);
}
void ScrollAnimatorMac::startScrollbarPaintTimer()
{
m_initialScrollbarPaintTimer.startOneShot(0.1);
}
bool ScrollAnimatorMac::scrollbarPaintTimerIsActive() const
{
return m_initialScrollbarPaintTimer.isActive();
}
void ScrollAnimatorMac::stopScrollbarPaintTimer()
{
m_initialScrollbarPaintTimer.stop();
}
void ScrollAnimatorMac::initialScrollbarPaintTimerFired(Timer<ScrollAnimatorMac>*)
{
// To force the scrollbars to flash, we have to call hide first. Otherwise, the ScrollbarPainterController
// might think that the scrollbars are already showing and bail early.
[m_scrollbarPainterController.get() hideOverlayScrollers];
[m_scrollbarPainterController.get() flashScrollers];
}
#endif
void ScrollAnimatorMac::setVisibleScrollerThumbRect(const IntRect& scrollerThumb)
{
IntRect rectInViewCoordinates = scrollerThumb;
if (Scrollbar* verticalScrollbar = m_scrollableArea->verticalScrollbar())
rectInViewCoordinates = verticalScrollbar->convertToContainingView(scrollerThumb);
if (rectInViewCoordinates == m_visibleScrollerThumbRect)
return;
m_scrollableArea->setVisibleScrollerThumbRect(rectInViewCoordinates);
m_visibleScrollerThumbRect = rectInViewCoordinates;
}
} // namespace WebCore
#endif // ENABLE(SMOOTH_SCROLLING)