2 * Copyright (C) 2010, 2011 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
28 #if ENABLE(SMOOTH_SCROLLING)
30 #include "ScrollAnimatorMac.h"
32 #include "FloatPoint.h"
34 #include "PlatformGestureEvent.h"
35 #include "PlatformWheelEvent.h"
36 #include "ScrollView.h"
37 #include "ScrollableArea.h"
38 #include "ScrollbarTheme.h"
39 #include "ScrollbarThemeMac.h"
40 #include <wtf/PassOwnPtr.h>
41 #include <wtf/UnusedParam.h>
43 using namespace WebCore;
46 @interface NSObject (ScrollAnimationHelperDetails)
47 - (id)initWithDelegate:(id)delegate;
50 - (NSPoint)targetOrigin;
53 @interface ScrollAnimationHelperDelegate : NSObject
55 WebCore::ScrollAnimatorMac* _animator;
57 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator;
60 static NSSize abs(NSSize size)
62 NSSize finalSize = size;
63 if (finalSize.width < 0)
64 finalSize.width = -finalSize.width;
65 if (finalSize.height < 0)
66 finalSize.height = -finalSize.height;
70 @implementation ScrollAnimationHelperDelegate
72 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator
78 _animator = scrollAnimator;
82 - (void)scrollAnimatorDestroyed
92 WebCore::FloatPoint currentPosition = _animator->currentPosition();
93 return NSMakeRect(currentPosition.x(), currentPosition.y(), 0, 0);
96 - (void)_immediateScrollToPoint:(NSPoint)newPosition
100 _animator->immediateScrollToPoint(newPosition);
103 - (NSPoint)_pixelAlignProposedScrollPosition:(NSPoint)newOrigin
108 - (NSSize)convertSizeToBase:(NSSize)size
113 - (NSSize)convertSizeFromBase:(NSSize)size
118 - (NSSize)convertSizeToBacking:(NSSize)size
123 - (NSSize)convertSizeFromBacking:(NSSize)size
143 - (void)_recursiveRecomputeToolTips
149 #if USE(WK_SCROLLBAR_PAINTER)
151 @interface ScrollbarPainterControllerDelegate : NSObject
153 WebCore::ScrollAnimatorMac* _animator;
155 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator;
158 @implementation ScrollbarPainterControllerDelegate
160 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator
166 _animator = scrollAnimator;
170 - (void)scrollAnimatorDestroyed
175 - (NSRect)contentAreaRectForScrollerImpPair:(id)scrollerImpPair
177 UNUSED_PARAM(scrollerImpPair);
181 WebCore::IntSize contentsSize = _animator->scrollableArea()->contentsSize();
182 return NSMakeRect(0, 0, contentsSize.width(), contentsSize.height());
185 - (BOOL)inLiveResizeForScrollerImpPair:(id)scrollerImpPair
187 UNUSED_PARAM(scrollerImpPair);
191 return _animator->scrollableArea()->inLiveResize();
194 - (NSPoint)mouseLocationInContentAreaForScrollerImpPair:(id)scrollerImpPair
196 UNUSED_PARAM(scrollerImpPair);
200 return _animator->scrollableArea()->currentMousePosition();
203 - (NSPoint)scrollerImpPair:(id)scrollerImpPair convertContentPoint:(NSPoint)pointInContentArea toScrollerImp:(id)scrollerImp
205 UNUSED_PARAM(scrollerImpPair);
209 WebCore::Scrollbar* scrollbar = 0;
210 if (wkScrollbarPainterIsHorizontal((WKScrollbarPainterRef)scrollerImp))
211 scrollbar = _animator->scrollableArea()->horizontalScrollbar();
213 scrollbar = _animator->scrollableArea()->verticalScrollbar();
215 // It is possible to have a null scrollbar here since it is possible for this delegate
216 // method to be called between the moment when a scrollbar has been set to 0 and the
217 // moment when its destructor has been called. We should probably de-couple some
218 // of the clean-up work in ScrollbarThemeMac::unregisterScrollbar() to avoid this
221 return WebCore::IntPoint();
223 return scrollbar->convertFromContainingView(WebCore::IntPoint(pointInContentArea));
226 - (void)scrollerImpPair:(id)scrollerImpPair setContentAreaNeedsDisplayInRect:(NSRect)rect
228 UNUSED_PARAM(scrollerImpPair);
232 - (void)scrollerImpPair:(id)scrollerImpPair updateScrollerStyleForNewRecommendedScrollerStyle:(NSScrollerStyle)newRecommendedScrollerStyle
237 WKScrollbarPainterControllerRef painterController = (WKScrollbarPainterControllerRef)scrollerImpPair;
238 WebCore::ScrollbarThemeMac* macTheme = (WebCore::ScrollbarThemeMac*)WebCore::ScrollbarTheme::nativeTheme();
240 WKScrollbarPainterRef oldVerticalPainter = wkVerticalScrollbarPainterForController(painterController);
241 if (oldVerticalPainter) {
242 WebCore::Scrollbar* verticalScrollbar = _animator->scrollableArea()->verticalScrollbar();
243 WKScrollbarPainterRef newVerticalPainter = wkMakeScrollbarReplacementPainter(oldVerticalPainter,
244 newRecommendedScrollerStyle,
245 verticalScrollbar->controlSize(),
247 macTheme->setNewPainterForScrollbar(verticalScrollbar, newVerticalPainter);
248 wkSetPainterForPainterController(painterController, newVerticalPainter, false);
250 // The different scrollbar styles have different thicknesses, so we must re-set the
251 // frameRect to the new thickness, and the re-layout below will ensure the position
252 // and length are properly updated.
253 int thickness = macTheme->scrollbarThickness(verticalScrollbar->controlSize());
254 verticalScrollbar->setFrameRect(WebCore::IntRect(0, 0, thickness, thickness));
257 WKScrollbarPainterRef oldHorizontalPainter = wkHorizontalScrollbarPainterForController(painterController);
258 if (oldHorizontalPainter) {
259 WebCore::Scrollbar* horizontalScrollbar = _animator->scrollableArea()->horizontalScrollbar();
260 WKScrollbarPainterRef newHorizontalPainter = wkMakeScrollbarReplacementPainter(oldHorizontalPainter,
261 newRecommendedScrollerStyle,
262 horizontalScrollbar->controlSize(),
264 macTheme->setNewPainterForScrollbar(horizontalScrollbar, newHorizontalPainter);
265 wkSetPainterForPainterController(painterController, newHorizontalPainter, true);
267 // The different scrollbar styles have different thicknesses, so we must re-set the
268 // frameRect to the new thickness, and the re-layout below will ensure the position
269 // and length are properly updated.
270 int thickness = macTheme->scrollbarThickness(horizontalScrollbar->controlSize());
271 horizontalScrollbar->setFrameRect(WebCore::IntRect(0, 0, thickness, thickness));
274 wkSetScrollbarPainterControllerStyle(painterController, newRecommendedScrollerStyle);
276 // The different scrollbar styles affect layout, so we must re-layout everything.
277 _animator->scrollableArea()->scrollbarStyleChanged();
282 @interface ScrollbarPartAnimation : NSAnimation
284 RetainPtr<WKScrollbarPainterRef> _scrollerPainter;
285 WebCore::ScrollbarPart _part;
286 WebCore::ScrollAnimatorMac* _animator;
287 CGFloat _initialAlpha;
290 - (id)initWithScrollbarPainter:(WKScrollbarPainterRef)scrollerPainter part:(WebCore::ScrollbarPart)part scrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator animateAlphaTo:(CGFloat)newAlpha duration:(NSTimeInterval)duration;
293 @implementation ScrollbarPartAnimation
295 - (id)initWithScrollbarPainter:(WKScrollbarPainterRef)scrollerPainter part:(WebCore::ScrollbarPart)part scrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator animateAlphaTo:(CGFloat)newAlpha duration:(NSTimeInterval)duration
297 self = [super initWithDuration:duration animationCurve:NSAnimationEaseInOut];
301 _scrollerPainter = scrollerPainter;
303 _animator = scrollAnimator;
304 _initialAlpha = _part == WebCore::ThumbPart ? wkScrollbarPainterKnobAlpha(_scrollerPainter.get()) : wkScrollbarPainterTrackAlpha(_scrollerPainter.get());
305 _newAlpha = newAlpha;
310 - (void)setCurrentProgress:(NSAnimationProgress)progress
312 [super setCurrentProgress:progress];
317 CGFloat currentAlpha;
318 if (_initialAlpha > _newAlpha)
319 currentAlpha = 1 - progress;
321 currentAlpha = progress;
323 if (_part == WebCore::ThumbPart)
324 wkSetScrollbarPainterKnobAlpha(_scrollerPainter.get(), currentAlpha);
326 wkSetScrollbarPainterTrackAlpha(_scrollerPainter.get(), currentAlpha);
328 // Invalidate the scrollbars so that they paint the animation
329 if (WebCore::Scrollbar* verticalScrollbar = _animator->scrollableArea()->verticalScrollbar())
330 verticalScrollbar->invalidateRect(WebCore::IntRect(0, 0, verticalScrollbar->width(), verticalScrollbar->height()));
331 if (WebCore::Scrollbar* horizontalScrollbar = _animator->scrollableArea()->horizontalScrollbar())
332 horizontalScrollbar->invalidateRect(WebCore::IntRect(0, 0, horizontalScrollbar->width(), horizontalScrollbar->height()));
335 - (void)scrollAnimatorDestroyed
337 [self stopAnimation];
343 @interface ScrollbarPainterDelegate : NSObject<NSAnimationDelegate>
345 WebCore::ScrollAnimatorMac* _animator;
347 RetainPtr<ScrollbarPartAnimation> _verticalKnobAnimation;
348 RetainPtr<ScrollbarPartAnimation> _horizontalKnobAnimation;
350 RetainPtr<ScrollbarPartAnimation> _verticalTrackAnimation;
351 RetainPtr<ScrollbarPartAnimation> _horizontalTrackAnimation;
353 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator;
354 - (void)cancelAnimations;
357 @implementation ScrollbarPainterDelegate
359 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator
365 _animator = scrollAnimator;
369 - (void)cancelAnimations
371 [_verticalKnobAnimation.get() stopAnimation];
372 [_horizontalKnobAnimation.get() stopAnimation];
373 [_verticalTrackAnimation.get() stopAnimation];
374 [_horizontalTrackAnimation.get() stopAnimation];
377 - (NSRect)convertRectToBacking:(NSRect)aRect
382 - (NSRect)convertRectFromBacking:(NSRect)aRect
391 if (!_animator->isDrawingIntoLayer())
394 // FIXME: This should attempt to return an actual layer.
395 static CALayer *dummyLayer = [[CALayer alloc] init];
399 - (void)setUpAnimation:(RetainPtr<ScrollbarPartAnimation>&)scrollbarPartAnimation scrollerPainter:(WKScrollbarPainterRef)scrollerPainter part:(WebCore::ScrollbarPart)part animateAlphaTo:(CGFloat)newAlpha duration:(NSTimeInterval)duration
401 // If the user has scrolled the page, then the scrollbars must be animated here.
402 // This overrides the early returns.
403 bool mustAnimate = _animator->haveScrolledSincePageLoad();
405 if (_animator->scrollbarPaintTimerIsActive() && !mustAnimate)
408 if (_animator->scrollableArea()->shouldSuspendScrollAnimations() && !mustAnimate) {
409 _animator->startScrollbarPaintTimer();
413 // At this point, we are definitely going to animate now, so stop the timer.
414 _animator->stopScrollbarPaintTimer();
416 // If we are currently animating, stop
417 if (scrollbarPartAnimation) {
418 [scrollbarPartAnimation.get() stopAnimation];
419 scrollbarPartAnimation = nil;
422 [NSAnimationContext beginGrouping];
423 [[NSAnimationContext currentContext] setDuration:duration];
424 scrollbarPartAnimation.adoptNS([[ScrollbarPartAnimation alloc] initWithScrollbarPainter:scrollerPainter
426 scrollAnimator:_animator
427 animateAlphaTo:newAlpha
429 [scrollbarPartAnimation.get() setAnimationBlockingMode:NSAnimationNonblocking];
430 [scrollbarPartAnimation.get() startAnimation];
431 [NSAnimationContext endGrouping];
434 - (void)scrollerImp:(id)scrollerImp animateKnobAlphaTo:(CGFloat)newKnobAlpha duration:(NSTimeInterval)duration
439 WKScrollbarPainterRef scrollerPainter = (WKScrollbarPainterRef)scrollerImp;
440 if (wkScrollbarPainterIsHorizontal(scrollerPainter))
441 [self setUpAnimation:_horizontalKnobAnimation scrollerPainter:scrollerPainter part:WebCore::ThumbPart animateAlphaTo:newKnobAlpha duration:duration];
443 [self setUpAnimation:_verticalKnobAnimation scrollerPainter:scrollerPainter part:WebCore::ThumbPart animateAlphaTo:newKnobAlpha duration:duration];
446 - (void)scrollerImp:(id)scrollerImp animateTrackAlphaTo:(CGFloat)newTrackAlpha duration:(NSTimeInterval)duration
451 WKScrollbarPainterRef scrollerPainter = (WKScrollbarPainterRef)scrollerImp;
452 if (wkScrollbarPainterIsHorizontal(scrollerPainter))
453 [self setUpAnimation:_horizontalTrackAnimation scrollerPainter:scrollerPainter part:WebCore::BackTrackPart animateAlphaTo:newTrackAlpha duration:duration];
455 [self setUpAnimation:_verticalTrackAnimation scrollerPainter:scrollerPainter part:WebCore::BackTrackPart animateAlphaTo:newTrackAlpha duration:duration];
458 - (void)scrollerImp:(id)scrollerImp overlayScrollerStateChangedTo:(NSUInteger)newOverlayScrollerState
460 UNUSED_PARAM(scrollerImp);
461 UNUSED_PARAM(newOverlayScrollerState);
464 - (void)scrollAnimatorDestroyed
467 [_verticalKnobAnimation.get() scrollAnimatorDestroyed];
468 [_horizontalKnobAnimation.get() scrollAnimatorDestroyed];
469 [_verticalTrackAnimation.get() scrollAnimatorDestroyed];
470 [_horizontalTrackAnimation.get() scrollAnimatorDestroyed];
475 #endif // USE(WK_SCROLLBAR_PAINTER)
479 PassOwnPtr<ScrollAnimator> ScrollAnimator::create(ScrollableArea* scrollableArea)
481 return adoptPtr(new ScrollAnimatorMac(scrollableArea));
484 ScrollAnimatorMac::ScrollAnimatorMac(ScrollableArea* scrollableArea)
485 : ScrollAnimator(scrollableArea)
486 #if USE(WK_SCROLLBAR_PAINTER)
487 , m_initialScrollbarPaintTimer(this, &ScrollAnimatorMac::initialScrollbarPaintTimerFired)
489 #if ENABLE(RUBBER_BANDING)
490 , m_inScrollGesture(false)
491 , m_momentumScrollInProgress(false)
492 , m_ignoreMomentumScrolls(false)
493 , m_lastMomemtumScrollTimestamp(0)
495 , m_snapRubberBandTimer(this, &ScrollAnimatorMac::snapRubberBandTimerFired)
497 , m_drawingIntoLayer(false)
498 , m_haveScrolledSincePageLoad(false)
500 m_scrollAnimationHelperDelegate.adoptNS([[ScrollAnimationHelperDelegate alloc] initWithScrollAnimator:this]);
501 m_scrollAnimationHelper.adoptNS([[NSClassFromString(@"NSScrollAnimationHelper") alloc] initWithDelegate:m_scrollAnimationHelperDelegate.get()]);
503 #if USE(WK_SCROLLBAR_PAINTER)
504 m_scrollbarPainterControllerDelegate.adoptNS([[ScrollbarPainterControllerDelegate alloc] initWithScrollAnimator:this]);
505 m_scrollbarPainterController = wkMakeScrollbarPainterController(m_scrollbarPainterControllerDelegate.get());
506 m_scrollbarPainterDelegate.adoptNS([[ScrollbarPainterDelegate alloc] initWithScrollAnimator:this]);
510 ScrollAnimatorMac::~ScrollAnimatorMac()
512 #if USE(WK_SCROLLBAR_PAINTER)
513 [m_scrollbarPainterControllerDelegate.get() scrollAnimatorDestroyed];
514 [(id)m_scrollbarPainterController.get() setDelegate:nil];
515 [m_scrollbarPainterDelegate.get() scrollAnimatorDestroyed];
516 [m_scrollAnimationHelperDelegate.get() scrollAnimatorDestroyed];
520 bool ScrollAnimatorMac::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float multiplier)
522 m_haveScrolledSincePageLoad = true;
524 if (![[NSUserDefaults standardUserDefaults] boolForKey:@"AppleScrollAnimationEnabled"])
525 return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
527 if (granularity == ScrollByPixel)
528 return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
530 float currentPos = orientation == HorizontalScrollbar ? m_currentPosX : m_currentPosY;
531 float newPos = std::max<float>(std::min<float>(currentPos + (step * multiplier), static_cast<float>(m_scrollableArea->scrollSize(orientation))), 0);
532 if (currentPos == newPos)
536 if ([m_scrollAnimationHelper.get() _isAnimating]) {
537 NSPoint targetOrigin = [m_scrollAnimationHelper.get() targetOrigin];
538 newPoint = orientation == HorizontalScrollbar ? NSMakePoint(newPos, targetOrigin.y) : NSMakePoint(targetOrigin.x, newPos);
540 newPoint = orientation == HorizontalScrollbar ? NSMakePoint(newPos, m_currentPosY) : NSMakePoint(m_currentPosX, newPos);
542 [m_scrollAnimationHelper.get() scrollToPoint:newPoint];
546 void ScrollAnimatorMac::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
548 [m_scrollAnimationHelper.get() _stopRun];
549 immediateScrollToPoint(offset);
552 float ScrollAnimatorMac::adjustScrollXPositionIfNecessary(float position) const
554 if (!m_scrollableArea->constrainsScrollingToContentEdge())
557 return max<float>(min<float>(position, m_scrollableArea->contentsSize().width() - m_scrollableArea->visibleWidth()), 0);
560 float ScrollAnimatorMac::adjustScrollYPositionIfNecessary(float position) const
562 if (!m_scrollableArea->constrainsScrollingToContentEdge())
565 return max<float>(min<float>(position, m_scrollableArea->contentsSize().height() - m_scrollableArea->visibleHeight()), 0);
568 FloatPoint ScrollAnimatorMac::adjustScrollPositionIfNecessary(const FloatPoint& position) const
570 if (!m_scrollableArea->constrainsScrollingToContentEdge())
573 float newX = max<float>(min<float>(position.x(), m_scrollableArea->contentsSize().width() - m_scrollableArea->visibleWidth()), 0);
574 float newY = max<float>(min<float>(position.y(), m_scrollableArea->contentsSize().height() - m_scrollableArea->visibleHeight()), 0);
576 return FloatPoint(newX, newY);
579 void ScrollAnimatorMac::immediateScrollToPoint(const FloatPoint& newPosition)
581 FloatPoint adjustedPosition = adjustScrollPositionIfNecessary(newPosition);
583 if (adjustedPosition.x() == m_currentPosX && adjustedPosition.y() == m_currentPosY)
586 m_currentPosX = adjustedPosition.x();
587 m_currentPosY = adjustedPosition.y();
588 notityPositionChanged();
591 void ScrollAnimatorMac::immediateScrollByDeltaX(float deltaX)
593 float newPosX = adjustScrollXPositionIfNecessary(m_currentPosX + deltaX);
595 if (newPosX == m_currentPosX)
598 m_currentPosX = newPosX;
599 notityPositionChanged();
602 void ScrollAnimatorMac::immediateScrollByDeltaY(float deltaY)
604 float newPosY = adjustScrollYPositionIfNecessary(m_currentPosY + deltaY);
606 if (newPosY == m_currentPosY)
609 m_currentPosY = newPosY;
610 notityPositionChanged();
613 void ScrollAnimatorMac::notityPositionChanged()
615 #if USE(WK_SCROLLBAR_PAINTER)
616 wkContentAreaScrolled(m_scrollbarPainterController.get());
618 ScrollAnimator::notityPositionChanged();
621 void ScrollAnimatorMac::contentAreaWillPaint() const
623 #if USE(WK_SCROLLBAR_PAINTER)
624 wkContentAreaWillPaint(m_scrollbarPainterController.get());
628 void ScrollAnimatorMac::mouseEnteredContentArea() const
630 #if USE(WK_SCROLLBAR_PAINTER)
631 wkMouseEnteredContentArea(m_scrollbarPainterController.get());
635 void ScrollAnimatorMac::mouseExitedContentArea() const
637 #if USE(WK_SCROLLBAR_PAINTER)
638 wkMouseExitedContentArea(m_scrollbarPainterController.get());
642 void ScrollAnimatorMac::mouseMovedInContentArea() const
644 #if USE(WK_SCROLLBAR_PAINTER)
645 wkMouseMovedInContentArea(m_scrollbarPainterController.get());
649 void ScrollAnimatorMac::willStartLiveResize()
651 #if USE(WK_SCROLLBAR_PAINTER)
652 wkWillStartLiveResize(m_scrollbarPainterController.get());
656 void ScrollAnimatorMac::contentsResized() const
658 #if USE(WK_SCROLLBAR_PAINTER)
659 wkContentAreaResized(m_scrollbarPainterController.get());
663 void ScrollAnimatorMac::willEndLiveResize()
665 #if USE(WK_SCROLLBAR_PAINTER)
666 wkWillEndLiveResize(m_scrollbarPainterController.get());
670 void ScrollAnimatorMac::contentAreaDidShow() const
672 #if USE(WK_SCROLLBAR_PAINTER)
673 wkContentAreaDidShow(m_scrollbarPainterController.get());
677 void ScrollAnimatorMac::contentAreaDidHide() const
679 #if USE(WK_SCROLLBAR_PAINTER)
680 wkContentAreaDidHide(m_scrollbarPainterController.get());
684 void ScrollAnimatorMac::didBeginScrollGesture() const
686 #if USE(WK_SCROLLBAR_PAINTER)
687 wkDidBeginScrollGesture(m_scrollbarPainterController.get());
691 void ScrollAnimatorMac::didEndScrollGesture() const
693 #if USE(WK_SCROLLBAR_PAINTER)
694 wkDidEndScrollGesture(m_scrollbarPainterController.get());
698 void ScrollAnimatorMac::didAddVerticalScrollbar(Scrollbar* scrollbar)
700 #if USE(WK_SCROLLBAR_PAINTER)
701 WKScrollbarPainterRef painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
702 wkScrollbarPainterSetDelegate(painter, m_scrollbarPainterDelegate.get());
703 wkSetPainterForPainterController(m_scrollbarPainterController.get(), painter, false);
704 if (scrollableArea()->inLiveResize())
705 wkSetScrollbarPainterKnobAlpha(painter, 1);
707 UNUSED_PARAM(scrollbar);
711 void ScrollAnimatorMac::willRemoveVerticalScrollbar(Scrollbar* scrollbar)
713 #if USE(WK_SCROLLBAR_PAINTER)
714 WKScrollbarPainterRef painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
715 wkScrollbarPainterSetDelegate(painter, nil);
716 wkSetPainterForPainterController(m_scrollbarPainterController.get(), nil, false);
718 UNUSED_PARAM(scrollbar);
722 void ScrollAnimatorMac::didAddHorizontalScrollbar(Scrollbar* scrollbar)
724 #if USE(WK_SCROLLBAR_PAINTER)
725 WKScrollbarPainterRef painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
726 wkScrollbarPainterSetDelegate(painter, m_scrollbarPainterDelegate.get());
727 wkSetPainterForPainterController(m_scrollbarPainterController.get(), painter, true);
728 if (scrollableArea()->inLiveResize())
729 wkSetScrollbarPainterKnobAlpha(painter, 1);
731 UNUSED_PARAM(scrollbar);
735 void ScrollAnimatorMac::willRemoveHorizontalScrollbar(Scrollbar* scrollbar)
737 #if USE(WK_SCROLLBAR_PAINTER)
738 WKScrollbarPainterRef painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
739 wkScrollbarPainterSetDelegate(painter, nil);
740 wkSetPainterForPainterController(m_scrollbarPainterController.get(), nil, true);
742 UNUSED_PARAM(scrollbar);
746 void ScrollAnimatorMac::cancelAnimations()
748 m_haveScrolledSincePageLoad = false;
750 #if USE(WK_SCROLLBAR_PAINTER)
751 if (scrollbarPaintTimerIsActive())
752 stopScrollbarPaintTimer();
753 [m_scrollbarPainterDelegate.get() cancelAnimations];
757 #if ENABLE(RUBBER_BANDING)
759 static const float scrollVelocityZeroingTimeout = 0.10f;
760 static const float rubberbandStiffness = 20;
761 static const float rubberbandDirectionLockStretchRatio = 1;
762 static const float rubberbandMinimumRequiredDeltaBeforeStretch = 10;
763 static const float rubberbandAmplitude = 0.31f;
764 static const float rubberbandPeriod = 1.6f;
766 static float elasticDeltaForTimeDelta(float initialPosition, float initialVelocity, float elapsedTime)
768 float amplitude = rubberbandAmplitude;
769 float period = rubberbandPeriod;
770 float criticalDampeningFactor = expf((-elapsedTime * rubberbandStiffness) / period);
772 return (initialPosition + (-initialVelocity * elapsedTime * amplitude)) * criticalDampeningFactor;
775 static float elasticDeltaForReboundDelta(float delta)
777 float stiffness = std::max(rubberbandStiffness, 1.0f);
778 return delta / stiffness;
781 static float reboundDeltaForElasticDelta(float delta)
783 return delta * rubberbandStiffness;
786 static float scrollWheelMultiplier()
788 static float multiplier = -1;
789 if (multiplier < 0) {
790 multiplier = [[NSUserDefaults standardUserDefaults] floatForKey:@"NSScrollWheelMultiplier"];
797 void ScrollAnimatorMac::handleWheelEvent(PlatformWheelEvent& wheelEvent)
799 m_haveScrolledSincePageLoad = true;
801 if (!wheelEvent.hasPreciseScrollingDeltas()) {
802 ScrollAnimator::handleWheelEvent(wheelEvent);
806 // FIXME: This is somewhat roundabout hack to allow forwarding wheel events
807 // up to the parent scrollable area. It takes advantage of the fact that
808 // the base class implemenatation of handleWheelEvent will not accept the
809 // wheel event if there is nowhere to scroll.
810 if (fabsf(wheelEvent.deltaY()) >= fabsf(wheelEvent.deltaX())) {
811 if (!allowsVerticalStretching()) {
812 ScrollAnimator::handleWheelEvent(wheelEvent);
816 if (!allowsHorizontalStretching()) {
817 ScrollAnimator::handleWheelEvent(wheelEvent);
824 bool isMometumScrollEvent = (wheelEvent.momentumPhase() != PlatformWheelEventPhaseNone);
825 if (m_ignoreMomentumScrolls && (isMometumScrollEvent || m_snapRubberBandTimer.isActive())) {
826 if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseEnded)
827 m_ignoreMomentumScrolls = false;
831 smoothScrollWithEvent(wheelEvent);
834 void ScrollAnimatorMac::handleGestureEvent(const PlatformGestureEvent& gestureEvent)
836 if (gestureEvent.type() == PlatformGestureEvent::ScrollBeginType)
837 beginScrollGesture();
842 bool ScrollAnimatorMac::pinnedInDirection(float deltaX, float deltaY)
844 FloatSize limitDelta;
845 if (fabsf(deltaY) >= fabsf(deltaX)) {
847 // We are trying to scroll up. Make sure we are not pinned to the top
848 limitDelta.setHeight(m_scrollableArea->visibleContentRect().y() + + m_scrollableArea->scrollOrigin().y());
850 // We are trying to scroll down. Make sure we are not pinned to the bottom
851 limitDelta.setHeight(m_scrollableArea->contentsSize().height() - (m_scrollableArea->visibleContentRect().maxY() + m_scrollableArea->scrollOrigin().y()));
853 } else if (deltaX != 0) {
855 // We are trying to scroll left. Make sure we are not pinned to the left
856 limitDelta.setWidth(m_scrollableArea->visibleContentRect().x() + m_scrollableArea->scrollOrigin().x());
858 // We are trying to scroll right. Make sure we are not pinned to the right
859 limitDelta.setWidth(m_scrollableArea->contentsSize().width() - (m_scrollableArea->visibleContentRect().maxX() + m_scrollableArea->scrollOrigin().x()));
863 if ((deltaX != 0 || deltaY != 0) && (limitDelta.width() < 1 && limitDelta.height() < 1))
868 bool ScrollAnimatorMac::allowsVerticalStretching() const
870 switch (m_scrollableArea->verticalScrollElasticity()) {
871 case ScrollElasticityAutomatic: {
872 Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
873 Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
874 return (((vScroller && vScroller->enabled()) || (!hScroller || !hScroller->enabled())));
876 case ScrollElasticityNone:
878 case ScrollElasticityAllowed:
882 ASSERT_NOT_REACHED();
886 bool ScrollAnimatorMac::allowsHorizontalStretching() const
888 switch (m_scrollableArea->horizontalScrollElasticity()) {
889 case ScrollElasticityAutomatic: {
890 Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
891 Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
892 return (((hScroller && hScroller->enabled()) || (!vScroller || !vScroller->enabled())));
894 case ScrollElasticityNone:
896 case ScrollElasticityAllowed:
900 ASSERT_NOT_REACHED();
904 void ScrollAnimatorMac::smoothScrollWithEvent(PlatformWheelEvent& wheelEvent)
906 m_haveScrolledSincePageLoad = true;
908 float deltaX = m_overflowScrollDelta.width();
909 float deltaY = m_overflowScrollDelta.height();
911 // Reset overflow values because we may decide to remove delta at various points and put it into overflow.
912 m_overflowScrollDelta = FloatSize();
914 float eventCoallescedDeltaX = -wheelEvent.deltaX();
915 float eventCoallescedDeltaY = -wheelEvent.deltaY();
917 deltaX += eventCoallescedDeltaX;
918 deltaY += eventCoallescedDeltaY;
920 // Slightly prefer scrolling vertically by applying the = case to deltaY
921 if (fabsf(deltaY) >= fabsf(deltaX))
926 bool isVerticallyStretched = false;
927 bool isHorizontallyStretched = false;
928 bool shouldStretch = false;
930 IntSize stretchAmount = m_scrollableArea->overhangAmount();
932 isHorizontallyStretched = stretchAmount.width();
933 isVerticallyStretched = stretchAmount.height();
935 PlatformWheelEventPhase phase = wheelEvent.momentumPhase();
937 // If we are starting momentum scrolling then do some setup.
938 if (!m_momentumScrollInProgress && (phase == PlatformWheelEventPhaseBegan || phase == PlatformWheelEventPhaseChanged))
939 m_momentumScrollInProgress = true;
941 CFTimeInterval timeDelta = wheelEvent.timestamp() - m_lastMomemtumScrollTimestamp;
942 if (m_inScrollGesture || m_momentumScrollInProgress) {
943 if (m_lastMomemtumScrollTimestamp && timeDelta > 0 && timeDelta < scrollVelocityZeroingTimeout) {
944 m_momentumVelocity.setWidth(eventCoallescedDeltaX / (float)timeDelta);
945 m_momentumVelocity.setHeight(eventCoallescedDeltaY / (float)timeDelta);
946 m_lastMomemtumScrollTimestamp = wheelEvent.timestamp();
948 m_lastMomemtumScrollTimestamp = wheelEvent.timestamp();
949 m_momentumVelocity = FloatSize();
952 if (isVerticallyStretched) {
953 if (!isHorizontallyStretched && pinnedInDirection(deltaX, 0)) {
954 // Stretching only in the vertical.
955 if (deltaY != 0 && (fabsf(deltaX / deltaY) < rubberbandDirectionLockStretchRatio))
957 else if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
958 m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
961 m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
963 } else if (isHorizontallyStretched) {
964 // Stretching only in the horizontal.
965 if (pinnedInDirection(0, deltaY)) {
966 if (deltaX != 0 && (fabsf(deltaY / deltaX) < rubberbandDirectionLockStretchRatio))
968 else if (fabsf(deltaY) < rubberbandMinimumRequiredDeltaBeforeStretch) {
969 m_overflowScrollDelta.setHeight(m_overflowScrollDelta.height() + deltaY);
972 m_overflowScrollDelta.setHeight(m_overflowScrollDelta.height() + deltaY);
975 // Not stretching at all yet.
976 if (pinnedInDirection(deltaX, deltaY)) {
977 if (fabsf(deltaY) >= fabsf(deltaX)) {
978 if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
979 m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
982 m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
984 shouldStretch = true;
989 if (deltaX != 0 || deltaY != 0) {
990 if (!(shouldStretch || isVerticallyStretched || isHorizontallyStretched)) {
992 deltaY *= scrollWheelMultiplier();
993 immediateScrollByDeltaY(deltaY);
996 deltaX *= scrollWheelMultiplier();
997 immediateScrollByDeltaX(deltaX);
1000 if (!allowsHorizontalStretching()) {
1002 eventCoallescedDeltaX = 0;
1003 } else if ((deltaX != 0) && !isHorizontallyStretched && !pinnedInDirection(deltaX, 0)) {
1004 deltaX *= scrollWheelMultiplier();
1006 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1007 immediateScrollByDeltaX(deltaX);
1008 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1013 if (!allowsVerticalStretching()) {
1015 eventCoallescedDeltaY = 0;
1016 } else if ((deltaY != 0) && !isVerticallyStretched && !pinnedInDirection(0, deltaY)) {
1017 deltaY *= scrollWheelMultiplier();
1019 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1020 immediateScrollByDeltaY(deltaY);
1021 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1026 IntSize stretchAmount = m_scrollableArea->overhangAmount();
1028 if (m_momentumScrollInProgress) {
1029 if ((pinnedInDirection(eventCoallescedDeltaX, eventCoallescedDeltaY) || (fabsf(eventCoallescedDeltaX) + fabsf(eventCoallescedDeltaY) <= 0)) && m_lastMomemtumScrollTimestamp) {
1030 m_ignoreMomentumScrolls = true;
1031 m_momentumScrollInProgress = false;
1036 m_stretchScrollForce.setWidth(m_stretchScrollForce.width() + deltaX);
1037 m_stretchScrollForce.setHeight(m_stretchScrollForce.height() + deltaY);
1039 FloatSize dampedDelta(ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.width())), ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.height())));
1040 FloatPoint origOrigin = (m_scrollableArea->visibleContentRect().location() + m_scrollableArea->scrollOrigin()) - stretchAmount;
1041 FloatPoint newOrigin = origOrigin + dampedDelta;
1043 if (origOrigin != newOrigin) {
1044 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1045 immediateScrollToPoint(newOrigin);
1046 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1051 if (m_momentumScrollInProgress && phase == PlatformWheelEventPhaseEnded) {
1052 m_momentumScrollInProgress = false;
1053 m_ignoreMomentumScrolls = false;
1054 m_lastMomemtumScrollTimestamp = 0;
1058 void ScrollAnimatorMac::beginScrollGesture()
1060 didBeginScrollGesture();
1062 m_haveScrolledSincePageLoad = true;
1063 m_inScrollGesture = true;
1064 m_momentumScrollInProgress = false;
1065 m_ignoreMomentumScrolls = false;
1066 m_lastMomemtumScrollTimestamp = 0;
1067 m_momentumVelocity = FloatSize();
1069 IntSize stretchAmount = m_scrollableArea->overhangAmount();
1070 m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(stretchAmount.width()));
1071 m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(stretchAmount.height()));
1073 m_overflowScrollDelta = FloatSize();
1075 if (m_snapRubberBandTimer.isActive())
1076 m_snapRubberBandTimer.stop();
1079 void ScrollAnimatorMac::endScrollGesture()
1081 didEndScrollGesture();
1086 void ScrollAnimatorMac::snapRubberBand()
1088 CFTimeInterval timeDelta = [[NSProcessInfo processInfo] systemUptime] - m_lastMomemtumScrollTimestamp;
1089 if (m_lastMomemtumScrollTimestamp && timeDelta >= scrollVelocityZeroingTimeout)
1090 m_momentumVelocity = FloatSize();
1092 m_inScrollGesture = false;
1094 if (m_snapRubberBandTimer.isActive())
1097 m_startTime = [NSDate timeIntervalSinceReferenceDate];
1098 m_startStretch = FloatSize();
1099 m_origOrigin = FloatPoint();
1100 m_origVelocity = FloatSize();
1102 m_snapRubberBandTimer.startRepeating(1.0/60.0);
1105 static inline float roundTowardZero(float num)
1107 return num > 0 ? ceilf(num - 0.5f) : floorf(num + 0.5f);
1110 static inline float roundToDevicePixelTowardZero(float num)
1112 float roundedNum = roundf(num);
1113 if (fabs(num - roundedNum) < 0.125)
1116 return roundTowardZero(num);
1119 void ScrollAnimatorMac::snapRubberBandTimerFired(Timer<ScrollAnimatorMac>*)
1121 if (!m_momentumScrollInProgress || m_ignoreMomentumScrolls) {
1122 CFTimeInterval timeDelta = [NSDate timeIntervalSinceReferenceDate] - m_startTime;
1124 if (m_startStretch == FloatSize()) {
1125 m_startStretch = m_scrollableArea->overhangAmount();
1126 if (m_startStretch == FloatSize()) {
1127 m_snapRubberBandTimer.stop();
1128 m_stretchScrollForce = FloatSize();
1130 m_startStretch = FloatSize();
1131 m_origOrigin = FloatPoint();
1132 m_origVelocity = FloatSize();
1137 m_origOrigin = (m_scrollableArea->visibleContentRect().location() + m_scrollableArea->scrollOrigin()) - m_startStretch;
1138 m_origVelocity = m_momentumVelocity;
1140 // Just like normal scrolling, prefer vertical rubberbanding
1141 if (fabsf(m_origVelocity.height()) >= fabsf(m_origVelocity.width()))
1142 m_origVelocity.setWidth(0);
1144 // Don't rubber-band horizontally if it's not possible to scroll horizontally
1145 Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
1146 if (!hScroller || !hScroller->enabled())
1147 m_origVelocity.setWidth(0);
1149 // Don't rubber-band vertically if it's not possible to scroll horizontally
1150 Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
1151 if (!vScroller || !vScroller->enabled())
1152 m_origVelocity.setHeight(0);
1155 FloatPoint delta(roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_startStretch.width(), -m_origVelocity.width(), (float)timeDelta)),
1156 roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_startStretch.height(), -m_origVelocity.height(), (float)timeDelta)));
1158 if (fabs(delta.x()) >= 1 || fabs(delta.y()) >= 1) {
1159 FloatPoint newOrigin = m_origOrigin + delta;
1161 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1162 immediateScrollToPoint(newOrigin);
1163 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1165 FloatSize newStretch = m_scrollableArea->overhangAmount();
1167 m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(newStretch.width()));
1168 m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(newStretch.height()));
1170 immediateScrollToPoint(m_origOrigin);
1172 m_scrollableArea->didCompleteRubberBand(roundedIntSize(m_startStretch));
1174 m_snapRubberBandTimer.stop();
1175 m_stretchScrollForce = FloatSize();
1178 m_startStretch = FloatSize();
1179 m_origOrigin = FloatPoint();
1180 m_origVelocity = FloatSize();
1183 m_startTime = [NSDate timeIntervalSinceReferenceDate];
1184 m_startStretch = FloatSize();
1189 #if USE(WK_SCROLLBAR_PAINTER)
1190 void ScrollAnimatorMac::startScrollbarPaintTimer()
1192 m_initialScrollbarPaintTimer.startOneShot(0.1);
1195 bool ScrollAnimatorMac::scrollbarPaintTimerIsActive() const
1197 return m_initialScrollbarPaintTimer.isActive();
1200 void ScrollAnimatorMac::stopScrollbarPaintTimer()
1202 m_initialScrollbarPaintTimer.stop();
1205 void ScrollAnimatorMac::initialScrollbarPaintTimerFired(Timer<ScrollAnimatorMac>*)
1207 wkScrollbarPainterForceFlashScrollers(m_scrollbarPainterController.get());
1211 } // namespace WebCore
1213 #endif // ENABLE(SMOOTH_SCROLLING)