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 "BlockExceptions.h"
33 #include "FloatPoint.h"
34 #include "NSScrollerImpDetails.h"
35 #include "PlatformGestureEvent.h"
36 #include "PlatformWheelEvent.h"
37 #include "ScrollView.h"
38 #include "ScrollableArea.h"
39 #include "ScrollbarTheme.h"
40 #include "ScrollbarThemeMac.h"
41 #include "WebCoreSystemInterface.h"
42 #include <wtf/PassOwnPtr.h>
43 #include <wtf/UnusedParam.h>
45 using namespace WebCore;
48 #if USE(SCROLLBAR_PAINTER)
49 static bool supportsUIStateTransitionProgress()
51 // FIXME: This is temporary until all platforms that support ScrollbarPainter support this part of the API.
52 static bool globalSupportsUIStateTransitionProgress = [NSClassFromString(@"NSScrollerImp") instancesRespondToSelector:@selector(mouseEnteredScroller)];
53 return globalSupportsUIStateTransitionProgress;
57 @interface NSObject (ScrollAnimationHelperDetails)
58 - (id)initWithDelegate:(id)delegate;
61 - (NSPoint)targetOrigin;
65 @interface WebScrollAnimationHelperDelegate : NSObject
67 WebCore::ScrollAnimatorMac* _animator;
69 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator;
72 static NSSize abs(NSSize size)
74 NSSize finalSize = size;
75 if (finalSize.width < 0)
76 finalSize.width = -finalSize.width;
77 if (finalSize.height < 0)
78 finalSize.height = -finalSize.height;
82 @implementation WebScrollAnimationHelperDelegate
84 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator
90 _animator = scrollAnimator;
94 - (void)scrollAnimatorDestroyed
104 WebCore::FloatPoint currentPosition = _animator->currentPosition();
105 return NSMakeRect(currentPosition.x(), currentPosition.y(), 0, 0);
108 - (void)_immediateScrollToPoint:(NSPoint)newPosition
112 _animator->immediateScrollToPointForScrollAnimation(newPosition);
115 - (NSPoint)_pixelAlignProposedScrollPosition:(NSPoint)newOrigin
120 - (NSSize)convertSizeToBase:(NSSize)size
125 - (NSSize)convertSizeFromBase:(NSSize)size
130 - (NSSize)convertSizeToBacking:(NSSize)size
135 - (NSSize)convertSizeFromBacking:(NSSize)size
155 - (void)_recursiveRecomputeToolTips
161 #if USE(SCROLLBAR_PAINTER)
163 @interface WebScrollbarPainterControllerDelegate : NSObject
165 WebCore::ScrollAnimatorMac* _animator;
167 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator;
170 @implementation WebScrollbarPainterControllerDelegate
172 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator
178 _animator = scrollAnimator;
182 - (void)scrollAnimatorDestroyed
187 - (NSRect)contentAreaRectForScrollerImpPair:(id)scrollerImpPair
189 UNUSED_PARAM(scrollerImpPair);
193 WebCore::IntSize contentsSize = _animator->scrollableArea()->contentsSize();
194 return NSMakeRect(0, 0, contentsSize.width(), contentsSize.height());
197 - (BOOL)inLiveResizeForScrollerImpPair:(id)scrollerImpPair
199 UNUSED_PARAM(scrollerImpPair);
203 return _animator->scrollableArea()->inLiveResize();
206 - (NSPoint)mouseLocationInContentAreaForScrollerImpPair:(id)scrollerImpPair
208 UNUSED_PARAM(scrollerImpPair);
212 return _animator->scrollableArea()->currentMousePosition();
215 - (NSPoint)scrollerImpPair:(id)scrollerImpPair convertContentPoint:(NSPoint)pointInContentArea toScrollerImp:(id)scrollerImp
217 UNUSED_PARAM(scrollerImpPair);
221 WebCore::Scrollbar* scrollbar = 0;
222 if ([scrollerImp isHorizontal])
223 scrollbar = _animator->scrollableArea()->horizontalScrollbar();
225 scrollbar = _animator->scrollableArea()->verticalScrollbar();
227 // It is possible to have a null scrollbar here since it is possible for this delegate
228 // method to be called between the moment when a scrollbar has been set to 0 and the
229 // moment when its destructor has been called. We should probably de-couple some
230 // of the clean-up work in ScrollbarThemeMac::unregisterScrollbar() to avoid this
233 return WebCore::IntPoint();
235 return scrollbar->convertFromContainingView(WebCore::IntPoint(pointInContentArea));
238 - (void)scrollerImpPair:(id)scrollerImpPair setContentAreaNeedsDisplayInRect:(NSRect)rect
240 UNUSED_PARAM(scrollerImpPair);
244 - (void)scrollerImpPair:(id)scrollerImpPair updateScrollerStyleForNewRecommendedScrollerStyle:(NSScrollerStyle)newRecommendedScrollerStyle
249 [scrollerImpPair setScrollerStyle:newRecommendedScrollerStyle];
250 _animator->updateScrollerStyle();
255 enum FeatureToAnimate {
261 @interface WebScrollbarPartAnimation : NSAnimation
263 RetainPtr<ScrollbarPainter> _scrollerPainter;
264 FeatureToAnimate _featureToAnimate;
265 ScrollAnimatorMac* _animator;
269 - (id)initWithScrollbarPainter:(ScrollbarPainter)scrollerPainter animate:(FeatureToAnimate)featureToAnimate scrollAnimator:(ScrollAnimatorMac*)scrollAnimator animateFrom:(CGFloat)startValue animateTo:(CGFloat)endValue duration:(NSTimeInterval)duration;
272 @implementation WebScrollbarPartAnimation
274 - (id)initWithScrollbarPainter:(ScrollbarPainter)scrollerPainter animate:(FeatureToAnimate)featureToAnimate scrollAnimator:(ScrollAnimatorMac*)scrollAnimator animateFrom:(CGFloat)startValue animateTo:(CGFloat)endValue duration:(NSTimeInterval)duration
276 self = [super initWithDuration:duration animationCurve:NSAnimationEaseInOut];
280 _scrollerPainter = scrollerPainter;
281 _featureToAnimate = featureToAnimate;
282 _animator = scrollAnimator;
283 _startValue = startValue;
284 _endValue = endValue;
289 - (void)setScrollbarPainter:(ScrollbarPainter)scrollerPainter
291 _scrollerPainter = scrollerPainter;
294 - (void)setStartValue:(CGFloat)startValue
296 _startValue = startValue;
299 - (void)setEndValue:(CGFloat)endValue
301 _endValue = endValue;
304 - (void)setCurrentProgress:(NSAnimationProgress)progress
306 [super setCurrentProgress:progress];
311 CGFloat currentValue;
312 if (_startValue > _endValue)
313 currentValue = 1 - progress;
315 currentValue = progress;
317 switch (_featureToAnimate) {
319 [_scrollerPainter.get() setKnobAlpha:currentValue];
322 [_scrollerPainter.get() setTrackAlpha:currentValue];
324 case UIStateTransition:
325 [_scrollerPainter.get() setUiStateTransitionProgress:currentValue];
329 // Invalidate the scrollbars so that they paint the animation
330 if (Scrollbar* verticalScrollbar = _animator->scrollableArea()->verticalScrollbar())
331 verticalScrollbar->invalidate();
332 if (Scrollbar* horizontalScrollbar = _animator->scrollableArea()->horizontalScrollbar())
333 horizontalScrollbar->invalidate();
336 - (void)scrollAnimatorDestroyed
338 BEGIN_BLOCK_OBJC_EXCEPTIONS;
339 [self stopAnimation];
340 END_BLOCK_OBJC_EXCEPTIONS;
346 @interface WebScrollbarPainterDelegate : NSObject<NSAnimationDelegate>
348 WebCore::ScrollAnimatorMac* _animator;
350 RetainPtr<WebScrollbarPartAnimation> _verticalKnobAnimation;
351 RetainPtr<WebScrollbarPartAnimation> _horizontalKnobAnimation;
353 RetainPtr<WebScrollbarPartAnimation> _verticalTrackAnimation;
354 RetainPtr<WebScrollbarPartAnimation> _horizontalTrackAnimation;
356 RetainPtr<WebScrollbarPartAnimation> _verticalUIStateTransitionAnimation;
357 RetainPtr<WebScrollbarPartAnimation> _horizontalUIStateTransitionAnimation;
359 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator;
360 - (void)cancelAnimations;
363 @implementation WebScrollbarPainterDelegate
365 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator
371 _animator = scrollAnimator;
375 - (void)cancelAnimations
377 BEGIN_BLOCK_OBJC_EXCEPTIONS;
378 [_verticalKnobAnimation.get() stopAnimation];
379 [_horizontalKnobAnimation.get() stopAnimation];
380 [_verticalTrackAnimation.get() stopAnimation];
381 [_horizontalTrackAnimation.get() stopAnimation];
382 [_verticalUIStateTransitionAnimation.get() stopAnimation];
383 [_horizontalUIStateTransitionAnimation.get() stopAnimation];
384 END_BLOCK_OBJC_EXCEPTIONS;
387 - (NSRect)convertRectToBacking:(NSRect)aRect
392 - (NSRect)convertRectFromBacking:(NSRect)aRect
401 if (!_animator->isDrawingIntoLayer())
404 // FIXME: This should attempt to return an actual layer.
405 static CALayer *dummyLayer = [[CALayer alloc] init];
409 - (NSPoint)mouseLocationInScrollerForScrollerImp:(id)scrollerImp
414 ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
415 Scrollbar* scrollbar;
416 if ([scrollerPainter isHorizontal])
417 scrollbar = _animator->scrollableArea()->horizontalScrollbar();
419 scrollbar = _animator->scrollableArea()->verticalScrollbar();
424 return scrollbar->convertFromContainingView(_animator->scrollableArea()->currentMousePosition());
427 - (void)setUpAlphaAnimation:(RetainPtr<WebScrollbarPartAnimation>&)scrollbarPartAnimation scrollerPainter:(ScrollbarPainter)scrollerPainter part:(WebCore::ScrollbarPart)part animateAlphaTo:(CGFloat)newAlpha duration:(NSTimeInterval)duration
429 // If the user has scrolled the page, then the scrollbars must be animated here.
430 // This overrides the early returns.
431 bool mustAnimate = _animator->haveScrolledSincePageLoad();
433 if (_animator->scrollbarPaintTimerIsActive() && !mustAnimate)
436 if (_animator->scrollableArea()->shouldSuspendScrollAnimations() && !mustAnimate) {
437 _animator->startScrollbarPaintTimer();
441 // At this point, we are definitely going to animate now, so stop the timer.
442 _animator->stopScrollbarPaintTimer();
444 // If we are currently animating, stop
445 if (scrollbarPartAnimation) {
446 [scrollbarPartAnimation.get() stopAnimation];
447 scrollbarPartAnimation = nil;
450 if (part == WebCore::ThumbPart && ![scrollerPainter isHorizontal]) {
452 IntRect thumbRect = IntRect([scrollerPainter rectForPart:NSScrollerKnob]);
453 _animator->setVisibleScrollerThumbRect(thumbRect);
455 _animator->setVisibleScrollerThumbRect(IntRect());
458 [NSAnimationContext beginGrouping];
459 [[NSAnimationContext currentContext] setDuration:duration];
460 scrollbarPartAnimation.adoptNS([[WebScrollbarPartAnimation alloc] initWithScrollbarPainter:scrollerPainter
461 animate:part == ThumbPart ? ThumbAlpha : TrackAlpha
462 scrollAnimator:_animator
463 animateFrom:part == ThumbPart ? [scrollerPainter knobAlpha] : [scrollerPainter trackAlpha]
466 [scrollbarPartAnimation.get() setAnimationBlockingMode:NSAnimationNonblocking];
467 [scrollbarPartAnimation.get() startAnimation];
468 [NSAnimationContext endGrouping];
471 - (void)scrollerImp:(id)scrollerImp animateKnobAlphaTo:(CGFloat)newKnobAlpha duration:(NSTimeInterval)duration
476 ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
477 if ([scrollerImp isHorizontal])
478 [self setUpAlphaAnimation:_horizontalKnobAnimation scrollerPainter:scrollerPainter part:WebCore::ThumbPart animateAlphaTo:newKnobAlpha duration:duration];
480 [self setUpAlphaAnimation:_verticalKnobAnimation scrollerPainter:scrollerPainter part:WebCore::ThumbPart animateAlphaTo:newKnobAlpha duration:duration];
483 - (void)scrollerImp:(id)scrollerImp animateTrackAlphaTo:(CGFloat)newTrackAlpha duration:(NSTimeInterval)duration
488 ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
489 if ([scrollerImp isHorizontal])
490 [self setUpAlphaAnimation:_horizontalTrackAnimation scrollerPainter:scrollerPainter part:WebCore::BackTrackPart animateAlphaTo:newTrackAlpha duration:duration];
492 [self setUpAlphaAnimation:_verticalTrackAnimation scrollerPainter:scrollerPainter part:WebCore::BackTrackPart animateAlphaTo:newTrackAlpha duration:duration];
495 - (void)scrollerImp:(id)scrollerImp animateUIStateTransitionWithDuration:(NSTimeInterval)duration
500 if (!supportsUIStateTransitionProgress())
503 ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
504 RetainPtr<WebScrollbarPartAnimation> scrollbarPartAnimation = [scrollerPainter isHorizontal] ? _horizontalUIStateTransitionAnimation : _verticalUIStateTransitionAnimation;
506 // UIStateTransition always animates to 1. In case an animation is in progress this avoids a hard transition.
507 [scrollerPainter setUiStateTransitionProgress:1 - [scrollerPainter uiStateTransitionProgress]];
509 [NSAnimationContext beginGrouping];
510 [[NSAnimationContext currentContext] setDuration:duration];
511 if (!scrollbarPartAnimation) {
512 scrollbarPartAnimation.adoptNS([[WebScrollbarPartAnimation alloc] initWithScrollbarPainter:scrollerPainter
513 animate:UIStateTransition
514 scrollAnimator:_animator
515 animateFrom:[scrollerPainter uiStateTransitionProgress]
518 [scrollbarPartAnimation.get() setAnimationBlockingMode:NSAnimationNonblocking];
520 // If we don't need to initialize the animation, just reset the values in case they have changed.
521 [scrollbarPartAnimation.get() setScrollbarPainter:scrollerPainter];
522 [scrollbarPartAnimation.get() setStartValue:[scrollerPainter uiStateTransitionProgress]];
523 [scrollbarPartAnimation.get() setEndValue:1.0];
524 [scrollbarPartAnimation.get() setDuration:duration];
526 [scrollbarPartAnimation.get() startAnimation];
527 [NSAnimationContext endGrouping];
530 - (void)scrollerImp:(id)scrollerImp overlayScrollerStateChangedTo:(NSUInteger)newOverlayScrollerState
532 UNUSED_PARAM(scrollerImp);
533 UNUSED_PARAM(newOverlayScrollerState);
536 - (void)scrollAnimatorDestroyed
539 BEGIN_BLOCK_OBJC_EXCEPTIONS;
540 [_verticalKnobAnimation.get() scrollAnimatorDestroyed];
541 [_horizontalKnobAnimation.get() scrollAnimatorDestroyed];
542 [_verticalTrackAnimation.get() scrollAnimatorDestroyed];
543 [_horizontalTrackAnimation.get() scrollAnimatorDestroyed];
544 [_verticalUIStateTransitionAnimation.get() scrollAnimatorDestroyed];
545 [_horizontalUIStateTransitionAnimation.get() scrollAnimatorDestroyed];
546 END_BLOCK_OBJC_EXCEPTIONS;
551 #endif // USE(SCROLLBAR_PAINTER)
555 PassOwnPtr<ScrollAnimator> ScrollAnimator::create(ScrollableArea* scrollableArea)
557 return adoptPtr(new ScrollAnimatorMac(scrollableArea));
560 #if USE(SCROLLBAR_PAINTER)
561 static ScrollbarThemeMac* macScrollbarTheme()
563 ScrollbarTheme* scrollbarTheme = ScrollbarTheme::theme();
564 return !scrollbarTheme->isMockTheme() ? static_cast<ScrollbarThemeMac*>(scrollbarTheme) : 0;
568 ScrollAnimatorMac::ScrollAnimatorMac(ScrollableArea* scrollableArea)
569 : ScrollAnimator(scrollableArea)
570 #if USE(SCROLLBAR_PAINTER)
571 , m_initialScrollbarPaintTimer(this, &ScrollAnimatorMac::initialScrollbarPaintTimerFired)
573 #if ENABLE(RUBBER_BANDING)
574 , m_scrollElasticityController(this)
575 , m_snapRubberBandTimer(this, &ScrollAnimatorMac::snapRubberBandTimerFired)
577 , m_drawingIntoLayer(false)
578 , m_haveScrolledSincePageLoad(false)
579 , m_needsScrollerStyleUpdate(false)
581 m_scrollAnimationHelperDelegate.adoptNS([[WebScrollAnimationHelperDelegate alloc] initWithScrollAnimator:this]);
582 m_scrollAnimationHelper.adoptNS([[NSClassFromString(@"NSScrollAnimationHelper") alloc] initWithDelegate:m_scrollAnimationHelperDelegate.get()]);
584 #if USE(SCROLLBAR_PAINTER)
585 m_scrollbarPainterControllerDelegate.adoptNS([[WebScrollbarPainterControllerDelegate alloc] initWithScrollAnimator:this]);
586 m_scrollbarPainterController = [[[NSClassFromString(@"NSScrollerImpPair") alloc] init] autorelease];
587 [m_scrollbarPainterController.get() setDelegate:m_scrollbarPainterControllerDelegate.get()];
588 [m_scrollbarPainterController.get() setScrollerStyle:wkRecommendedScrollerStyle()];
590 m_scrollbarPainterDelegate.adoptNS([[WebScrollbarPainterDelegate alloc] initWithScrollAnimator:this]);
594 ScrollAnimatorMac::~ScrollAnimatorMac()
596 #if USE(SCROLLBAR_PAINTER)
597 BEGIN_BLOCK_OBJC_EXCEPTIONS;
598 [m_scrollbarPainterControllerDelegate.get() scrollAnimatorDestroyed];
599 [m_scrollbarPainterController.get() setDelegate:nil];
600 [m_scrollbarPainterDelegate.get() scrollAnimatorDestroyed];
601 [m_scrollAnimationHelperDelegate.get() scrollAnimatorDestroyed];
602 END_BLOCK_OBJC_EXCEPTIONS;
606 bool ScrollAnimatorMac::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float multiplier)
608 m_haveScrolledSincePageLoad = true;
610 if (![[NSUserDefaults standardUserDefaults] boolForKey:@"AppleScrollAnimationEnabled"] || !m_scrollableArea->scrollAnimatorEnabled())
611 return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
613 if (granularity == ScrollByPixel)
614 return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
616 float currentPos = orientation == HorizontalScrollbar ? m_currentPosX : m_currentPosY;
617 float newPos = std::max<float>(std::min<float>(currentPos + (step * multiplier), static_cast<float>(m_scrollableArea->scrollSize(orientation))), 0);
618 if (currentPos == newPos)
622 if ([m_scrollAnimationHelper.get() _isAnimating]) {
623 NSPoint targetOrigin = [m_scrollAnimationHelper.get() targetOrigin];
624 newPoint = orientation == HorizontalScrollbar ? NSMakePoint(newPos, targetOrigin.y) : NSMakePoint(targetOrigin.x, newPos);
626 newPoint = orientation == HorizontalScrollbar ? NSMakePoint(newPos, m_currentPosY) : NSMakePoint(m_currentPosX, newPos);
627 m_scrollableArea->didStartAnimatedScroll();
630 [m_scrollAnimationHelper.get() scrollToPoint:newPoint];
634 void ScrollAnimatorMac::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
636 [m_scrollAnimationHelper.get() _stopRun];
637 immediateScrollToPoint(offset);
640 float ScrollAnimatorMac::adjustScrollXPositionIfNecessary(float position) const
642 if (!m_scrollableArea->constrainsScrollingToContentEdge())
645 return max<float>(min<float>(position, m_scrollableArea->contentsSize().width() - m_scrollableArea->visibleWidth()), 0);
648 float ScrollAnimatorMac::adjustScrollYPositionIfNecessary(float position) const
650 if (!m_scrollableArea->constrainsScrollingToContentEdge())
653 return max<float>(min<float>(position, m_scrollableArea->contentsSize().height() - m_scrollableArea->visibleHeight()), 0);
656 FloatPoint ScrollAnimatorMac::adjustScrollPositionIfNecessary(const FloatPoint& position) const
658 if (!m_scrollableArea->constrainsScrollingToContentEdge())
661 float newX = max<float>(min<float>(position.x(), m_scrollableArea->contentsSize().width() - m_scrollableArea->visibleWidth()), 0);
662 float newY = max<float>(min<float>(position.y(), m_scrollableArea->contentsSize().height() - m_scrollableArea->visibleHeight()), 0);
664 return FloatPoint(newX, newY);
667 void ScrollAnimatorMac::immediateScrollToPoint(const FloatPoint& newPosition)
669 FloatPoint adjustedPosition = adjustScrollPositionIfNecessary(newPosition);
671 m_currentPosX = adjustedPosition.x();
672 m_currentPosY = adjustedPosition.y();
673 notifyPositionChanged();
676 void ScrollAnimatorMac::immediateScrollByDeltaX(float deltaX)
678 float newPosX = adjustScrollXPositionIfNecessary(m_currentPosX + deltaX);
680 if (newPosX == m_currentPosX)
683 m_currentPosX = newPosX;
684 notifyPositionChanged();
687 void ScrollAnimatorMac::immediateScrollByDeltaY(float deltaY)
689 float newPosY = adjustScrollYPositionIfNecessary(m_currentPosY + deltaY);
691 if (newPosY == m_currentPosY)
694 m_currentPosY = newPosY;
695 notifyPositionChanged();
698 void ScrollAnimatorMac::immediateScrollToPointForScrollAnimation(const FloatPoint& newPosition)
700 ASSERT(m_scrollAnimationHelper);
701 CGFloat progress = [m_scrollAnimationHelper.get() _progress];
703 immediateScrollToPoint(newPosition);
706 m_scrollableArea->didCompleteAnimatedScroll();
709 void ScrollAnimatorMac::notifyPositionChanged()
711 #if USE(SCROLLBAR_PAINTER)
712 // This function is called when a page is going into the page cache, but the page
713 // isn't really scrolling in that case. We should only pass the message on to the
714 // ScrollbarPainterController when we're really scrolling on an active page.
715 if (scrollableArea()->isOnActivePage())
716 [m_scrollbarPainterController.get() contentAreaScrolled];
718 ScrollAnimator::notifyPositionChanged();
721 void ScrollAnimatorMac::contentAreaWillPaint() const
723 if (!scrollableArea()->isOnActivePage())
725 #if USE(SCROLLBAR_PAINTER)
726 [m_scrollbarPainterController.get() contentAreaWillDraw];
730 void ScrollAnimatorMac::mouseEnteredContentArea() const
732 if (!scrollableArea()->isOnActivePage())
734 #if USE(SCROLLBAR_PAINTER)
735 [m_scrollbarPainterController.get() mouseEnteredContentArea];
739 void ScrollAnimatorMac::mouseExitedContentArea() const
741 if (!scrollableArea()->isOnActivePage())
743 #if USE(SCROLLBAR_PAINTER)
744 [m_scrollbarPainterController.get() mouseExitedContentArea];
748 void ScrollAnimatorMac::mouseMovedInContentArea() const
750 if (!scrollableArea()->isOnActivePage())
752 #if USE(SCROLLBAR_PAINTER)
753 [m_scrollbarPainterController.get() mouseMovedInContentArea];
757 void ScrollAnimatorMac::mouseEnteredScrollbar(Scrollbar* scrollbar) const
759 if (!scrollableArea()->isOnActivePage())
761 #if USE(SCROLLBAR_PAINTER)
762 if (!supportsUIStateTransitionProgress())
764 if (ScrollbarThemeMac* theme = macScrollbarTheme()) {
765 ScrollbarPainter painter = theme->painterForScrollbar(scrollbar);
766 [painter mouseEnteredScroller];
769 UNUSED_PARAM(scrollbar);
773 void ScrollAnimatorMac::mouseExitedScrollbar(Scrollbar* scrollbar) const
775 if (!scrollableArea()->isOnActivePage())
777 #if USE(SCROLLBAR_PAINTER)
778 if (!supportsUIStateTransitionProgress())
780 if (ScrollbarThemeMac* theme = macScrollbarTheme()) {
781 ScrollbarPainter painter = theme->painterForScrollbar(scrollbar);
782 [painter mouseExitedScroller];
785 UNUSED_PARAM(scrollbar);
789 void ScrollAnimatorMac::willStartLiveResize()
791 if (!scrollableArea()->isOnActivePage())
793 #if USE(SCROLLBAR_PAINTER)
794 [m_scrollbarPainterController.get() startLiveResize];
798 void ScrollAnimatorMac::contentsResized() const
800 if (!scrollableArea()->isOnActivePage())
802 #if USE(SCROLLBAR_PAINTER)
803 [m_scrollbarPainterController.get() contentAreaDidResize];
807 void ScrollAnimatorMac::willEndLiveResize()
809 if (!scrollableArea()->isOnActivePage())
811 #if USE(SCROLLBAR_PAINTER)
812 [m_scrollbarPainterController.get() endLiveResize];
816 void ScrollAnimatorMac::contentAreaDidShow() const
818 if (!scrollableArea()->isOnActivePage())
820 #if USE(SCROLLBAR_PAINTER)
821 [m_scrollbarPainterController.get() windowOrderedIn];
825 void ScrollAnimatorMac::contentAreaDidHide() const
827 if (!scrollableArea()->isOnActivePage())
829 #if USE(SCROLLBAR_PAINTER)
830 [m_scrollbarPainterController.get() windowOrderedOut];
834 void ScrollAnimatorMac::didBeginScrollGesture() const
836 if (!scrollableArea()->isOnActivePage())
838 #if USE(SCROLLBAR_PAINTER)
839 [m_scrollbarPainterController.get() beginScrollGesture];
843 void ScrollAnimatorMac::didEndScrollGesture() const
845 if (!scrollableArea()->isOnActivePage())
847 #if USE(SCROLLBAR_PAINTER)
848 [m_scrollbarPainterController.get() endScrollGesture];
852 void ScrollAnimatorMac::didAddVerticalScrollbar(Scrollbar* scrollbar)
854 #if USE(SCROLLBAR_PAINTER)
855 if (ScrollbarThemeMac* theme = macScrollbarTheme()) {
856 ScrollbarPainter painter = theme->painterForScrollbar(scrollbar);
857 [painter setDelegate:m_scrollbarPainterDelegate.get()];
858 [m_scrollbarPainterController.get() setVerticalScrollerImp:painter];
859 if (scrollableArea()->inLiveResize())
860 [painter setKnobAlpha:1];
863 UNUSED_PARAM(scrollbar);
867 void ScrollAnimatorMac::willRemoveVerticalScrollbar(Scrollbar* scrollbar)
869 #if USE(SCROLLBAR_PAINTER)
870 if (ScrollbarThemeMac* theme = macScrollbarTheme()) {
871 ScrollbarPainter painter = theme->painterForScrollbar(scrollbar);
872 [painter setDelegate:nil];
873 [m_scrollbarPainterController.get() setVerticalScrollerImp:nil];
876 UNUSED_PARAM(scrollbar);
880 void ScrollAnimatorMac::didAddHorizontalScrollbar(Scrollbar* scrollbar)
882 #if USE(SCROLLBAR_PAINTER)
883 if (ScrollbarThemeMac* theme = macScrollbarTheme()) {
884 ScrollbarPainter painter = theme->painterForScrollbar(scrollbar);
885 [painter setDelegate:m_scrollbarPainterDelegate.get()];
886 [m_scrollbarPainterController.get() setHorizontalScrollerImp:painter];
887 if (scrollableArea()->inLiveResize())
888 [painter setKnobAlpha:1];
891 UNUSED_PARAM(scrollbar);
895 void ScrollAnimatorMac::willRemoveHorizontalScrollbar(Scrollbar* scrollbar)
897 #if USE(SCROLLBAR_PAINTER)
898 if (ScrollbarThemeMac* theme = macScrollbarTheme()) {
899 ScrollbarPainter painter = theme->painterForScrollbar(scrollbar);
900 [painter setDelegate:nil];
901 [m_scrollbarPainterController.get() setHorizontalScrollerImp:nil];
904 UNUSED_PARAM(scrollbar);
908 void ScrollAnimatorMac::cancelAnimations()
910 m_haveScrolledSincePageLoad = false;
912 #if USE(SCROLLBAR_PAINTER)
913 if (scrollbarPaintTimerIsActive())
914 stopScrollbarPaintTimer();
915 [m_scrollbarPainterDelegate.get() cancelAnimations];
919 #if ENABLE(RUBBER_BANDING)
921 static const float scrollVelocityZeroingTimeout = 0.10f;
922 static const float rubberbandStiffness = 20;
923 static const float rubberbandDirectionLockStretchRatio = 1;
924 static const float rubberbandMinimumRequiredDeltaBeforeStretch = 10;
925 static const float rubberbandAmplitude = 0.31f;
926 static const float rubberbandPeriod = 1.6f;
928 static float elasticDeltaForTimeDelta(float initialPosition, float initialVelocity, float elapsedTime)
930 float amplitude = rubberbandAmplitude;
931 float period = rubberbandPeriod;
932 float criticalDampeningFactor = expf((-elapsedTime * rubberbandStiffness) / period);
934 return (initialPosition + (-initialVelocity * elapsedTime * amplitude)) * criticalDampeningFactor;
937 static float elasticDeltaForReboundDelta(float delta)
939 float stiffness = std::max(rubberbandStiffness, 1.0f);
940 return delta / stiffness;
943 static float reboundDeltaForElasticDelta(float delta)
945 return delta * rubberbandStiffness;
948 static float scrollWheelMultiplier()
950 static float multiplier = -1;
951 if (multiplier < 0) {
952 multiplier = [[NSUserDefaults standardUserDefaults] floatForKey:@"NSScrollWheelMultiplier"];
959 static inline bool isScrollingLeftAndShouldNotRubberBand(const PlatformWheelEvent& wheelEvent, ScrollableArea* scrollableArea)
961 return wheelEvent.deltaX() > 0 && !scrollableArea->shouldRubberBandInDirection(ScrollLeft);
964 static inline bool isScrollingRightAndShouldNotRubberBand(const PlatformWheelEvent& wheelEvent, ScrollableArea* scrollableArea)
966 return wheelEvent.deltaX() < 0 && !scrollableArea->shouldRubberBandInDirection(ScrollRight);
969 bool ScrollAnimatorMac::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
971 m_haveScrolledSincePageLoad = true;
973 if (!wheelEvent.hasPreciseScrollingDeltas())
974 return ScrollAnimator::handleWheelEvent(wheelEvent);
976 // FIXME: This is somewhat roundabout hack to allow forwarding wheel events
977 // up to the parent scrollable area. It takes advantage of the fact that
978 // the base class implementation of handleWheelEvent will not accept the
979 // wheel event if there is nowhere to scroll.
980 if (fabsf(wheelEvent.deltaY()) >= fabsf(wheelEvent.deltaX())) {
981 if (!allowsVerticalStretching())
982 return ScrollAnimator::handleWheelEvent(wheelEvent);
984 if (!allowsHorizontalStretching())
985 return ScrollAnimator::handleWheelEvent(wheelEvent);
987 if (m_scrollableArea->horizontalScrollbar()) {
988 // If there is a scrollbar, we aggregate the wheel events to get an
989 // overall trend of the scroll. If the direction of the scroll is ever
990 // in the opposite direction of the pin location, then we switch the
991 // boolean, and rubber band. That is, if we were pinned to the left,
992 // and we ended up scrolling to the right, we rubber band.
993 m_scrollElasticityController.m_cumulativeHorizontalScroll += wheelEvent.deltaX();
994 if (m_scrollElasticityController.m_scrollerInitiallyPinnedOnLeft && m_scrollElasticityController.m_cumulativeHorizontalScroll < 0)
995 m_scrollElasticityController.m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin = true;
996 if (m_scrollElasticityController.m_scrollerInitiallyPinnedOnRight && m_scrollElasticityController.m_cumulativeHorizontalScroll > 0)
997 m_scrollElasticityController.m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin = true;
1000 // After a gesture begins, we go through:
1001 // 1+ PlatformWheelEventPhaseNone
1002 // 0+ PlatformWheelEventPhaseChanged
1003 // 1 PlatformWheelEventPhaseEnded if there was at least one changed event
1004 if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseNone && !m_scrollElasticityController.m_didCumulativeHorizontalScrollEverSwitchToOppositeDirectionOfPin) {
1005 if ((isScrollingLeftAndShouldNotRubberBand(wheelEvent, m_scrollableArea) &&
1006 m_scrollElasticityController.m_scrollerInitiallyPinnedOnLeft &&
1007 m_scrollableArea->isHorizontalScrollerPinnedToMinimumPosition()) ||
1008 (isScrollingRightAndShouldNotRubberBand(wheelEvent, m_scrollableArea) &&
1009 m_scrollElasticityController.m_scrollerInitiallyPinnedOnRight &&
1010 m_scrollableArea->isHorizontalScrollerPinnedToMaximumPosition())) {
1011 return ScrollAnimator::handleWheelEvent(wheelEvent);
1016 bool isMomentumScrollEvent = (wheelEvent.momentumPhase() != PlatformWheelEventPhaseNone);
1017 if (m_scrollElasticityController.m_ignoreMomentumScrolls && (isMomentumScrollEvent || m_scrollElasticityController.m_snapRubberbandTimerIsActive)) {
1018 if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseEnded) {
1019 m_scrollElasticityController.m_ignoreMomentumScrolls = false;
1025 smoothScrollWithEvent(wheelEvent);
1029 void ScrollAnimatorMac::handleGestureEvent(const PlatformGestureEvent& gestureEvent)
1031 if (gestureEvent.type() == PlatformGestureEvent::ScrollBeginType)
1032 beginScrollGesture();
1033 else if (gestureEvent.type() == PlatformGestureEvent::ScrollEndType)
1037 bool ScrollAnimatorMac::pinnedInDirection(float deltaX, float deltaY)
1039 FloatSize limitDelta;
1040 if (fabsf(deltaY) >= fabsf(deltaX)) {
1042 // We are trying to scroll up. Make sure we are not pinned to the top
1043 limitDelta.setHeight(m_scrollableArea->visibleContentRect().y() + + m_scrollableArea->scrollOrigin().y());
1045 // We are trying to scroll down. Make sure we are not pinned to the bottom
1046 limitDelta.setHeight(m_scrollableArea->contentsSize().height() - (m_scrollableArea->visibleContentRect().maxY() + m_scrollableArea->scrollOrigin().y()));
1048 } else if (deltaX != 0) {
1050 // We are trying to scroll left. Make sure we are not pinned to the left
1051 limitDelta.setWidth(m_scrollableArea->visibleContentRect().x() + m_scrollableArea->scrollOrigin().x());
1053 // We are trying to scroll right. Make sure we are not pinned to the right
1054 limitDelta.setWidth(m_scrollableArea->contentsSize().width() - (m_scrollableArea->visibleContentRect().maxX() + m_scrollableArea->scrollOrigin().x()));
1058 if ((deltaX != 0 || deltaY != 0) && (limitDelta.width() < 1 && limitDelta.height() < 1))
1063 bool ScrollAnimatorMac::isHorizontalScrollerPinnedToMinimumPosition()
1065 return m_scrollableArea->isHorizontalScrollerPinnedToMinimumPosition();
1068 bool ScrollAnimatorMac::isHorizontalScrollerPinnedToMaximumPosition()
1070 return m_scrollableArea->isHorizontalScrollerPinnedToMaximumPosition();
1073 IntSize ScrollAnimatorMac::stretchAmount()
1075 return m_scrollableArea->overhangAmount();
1078 void ScrollAnimatorMac::startSnapRubberbandTimer()
1080 m_snapRubberBandTimer.startRepeating(1.0 / 60.0);
1083 void ScrollAnimatorMac::stopSnapRubberbandTimer()
1085 m_snapRubberBandTimer.stop();
1088 bool ScrollAnimatorMac::allowsVerticalStretching() const
1090 switch (m_scrollableArea->verticalScrollElasticity()) {
1091 case ScrollElasticityAutomatic: {
1092 Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
1093 Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
1094 return (((vScroller && vScroller->enabled()) || (!hScroller || !hScroller->enabled())));
1096 case ScrollElasticityNone:
1098 case ScrollElasticityAllowed:
1102 ASSERT_NOT_REACHED();
1106 bool ScrollAnimatorMac::allowsHorizontalStretching() const
1108 switch (m_scrollableArea->horizontalScrollElasticity()) {
1109 case ScrollElasticityAutomatic: {
1110 Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
1111 Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
1112 return (((hScroller && hScroller->enabled()) || (!vScroller || !vScroller->enabled())));
1114 case ScrollElasticityNone:
1116 case ScrollElasticityAllowed:
1120 ASSERT_NOT_REACHED();
1124 void ScrollAnimatorMac::smoothScrollWithEvent(const PlatformWheelEvent& wheelEvent)
1126 m_haveScrolledSincePageLoad = true;
1128 float deltaX = m_scrollElasticityController.m_overflowScrollDelta.width();
1129 float deltaY = m_scrollElasticityController.m_overflowScrollDelta.height();
1131 // Reset overflow values because we may decide to remove delta at various points and put it into overflow.
1132 m_scrollElasticityController.m_overflowScrollDelta = FloatSize();
1134 float eventCoalescedDeltaX = -wheelEvent.deltaX();
1135 float eventCoalescedDeltaY = -wheelEvent.deltaY();
1137 deltaX += eventCoalescedDeltaX;
1138 deltaY += eventCoalescedDeltaY;
1140 // Slightly prefer scrolling vertically by applying the = case to deltaY
1141 if (fabsf(deltaY) >= fabsf(deltaX))
1146 bool isVerticallyStretched = false;
1147 bool isHorizontallyStretched = false;
1148 bool shouldStretch = false;
1150 IntSize stretchAmount = m_scrollableArea->overhangAmount();
1152 isHorizontallyStretched = stretchAmount.width();
1153 isVerticallyStretched = stretchAmount.height();
1155 PlatformWheelEventPhase phase = wheelEvent.momentumPhase();
1157 // If we are starting momentum scrolling then do some setup.
1158 if (!m_scrollElasticityController.m_momentumScrollInProgress && (phase == PlatformWheelEventPhaseBegan || phase == PlatformWheelEventPhaseChanged))
1159 m_scrollElasticityController.m_momentumScrollInProgress = true;
1161 CFTimeInterval timeDelta = wheelEvent.timestamp() - m_scrollElasticityController.m_lastMomentumScrollTimestamp;
1162 if (m_scrollElasticityController.m_inScrollGesture || m_scrollElasticityController.m_momentumScrollInProgress) {
1163 if (m_scrollElasticityController.m_lastMomentumScrollTimestamp && timeDelta > 0 && timeDelta < scrollVelocityZeroingTimeout) {
1164 m_scrollElasticityController.m_momentumVelocity.setWidth(eventCoalescedDeltaX / (float)timeDelta);
1165 m_scrollElasticityController.m_momentumVelocity.setHeight(eventCoalescedDeltaY / (float)timeDelta);
1166 m_scrollElasticityController.m_lastMomentumScrollTimestamp = wheelEvent.timestamp();
1168 m_scrollElasticityController.m_lastMomentumScrollTimestamp = wheelEvent.timestamp();
1169 m_scrollElasticityController.m_momentumVelocity = FloatSize();
1172 if (isVerticallyStretched) {
1173 if (!isHorizontallyStretched && pinnedInDirection(deltaX, 0)) {
1174 // Stretching only in the vertical.
1175 if (deltaY != 0 && (fabsf(deltaX / deltaY) < rubberbandDirectionLockStretchRatio))
1177 else if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
1178 m_scrollElasticityController.m_overflowScrollDelta.setWidth(m_scrollElasticityController.m_overflowScrollDelta.width() + deltaX);
1181 m_scrollElasticityController.m_overflowScrollDelta.setWidth(m_scrollElasticityController.m_overflowScrollDelta.width() + deltaX);
1183 } else if (isHorizontallyStretched) {
1184 // Stretching only in the horizontal.
1185 if (pinnedInDirection(0, deltaY)) {
1186 if (deltaX != 0 && (fabsf(deltaY / deltaX) < rubberbandDirectionLockStretchRatio))
1188 else if (fabsf(deltaY) < rubberbandMinimumRequiredDeltaBeforeStretch) {
1189 m_scrollElasticityController.m_overflowScrollDelta.setHeight(m_scrollElasticityController.m_overflowScrollDelta.height() + deltaY);
1192 m_scrollElasticityController.m_overflowScrollDelta.setHeight(m_scrollElasticityController.m_overflowScrollDelta.height() + deltaY);
1195 // Not stretching at all yet.
1196 if (pinnedInDirection(deltaX, deltaY)) {
1197 if (fabsf(deltaY) >= fabsf(deltaX)) {
1198 if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
1199 m_scrollElasticityController.m_overflowScrollDelta.setWidth(m_scrollElasticityController.m_overflowScrollDelta.width() + deltaX);
1202 m_scrollElasticityController.m_overflowScrollDelta.setWidth(m_scrollElasticityController.m_overflowScrollDelta.width() + deltaX);
1204 shouldStretch = true;
1209 if (deltaX != 0 || deltaY != 0) {
1210 if (!(shouldStretch || isVerticallyStretched || isHorizontallyStretched)) {
1212 deltaY *= scrollWheelMultiplier();
1213 immediateScrollByDeltaY(deltaY);
1216 deltaX *= scrollWheelMultiplier();
1217 immediateScrollByDeltaX(deltaX);
1220 if (!allowsHorizontalStretching()) {
1222 eventCoalescedDeltaX = 0;
1223 } else if ((deltaX != 0) && !isHorizontallyStretched && !pinnedInDirection(deltaX, 0)) {
1224 deltaX *= scrollWheelMultiplier();
1226 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1227 immediateScrollByDeltaX(deltaX);
1228 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1233 if (!allowsVerticalStretching()) {
1235 eventCoalescedDeltaY = 0;
1236 } else if ((deltaY != 0) && !isVerticallyStretched && !pinnedInDirection(0, deltaY)) {
1237 deltaY *= scrollWheelMultiplier();
1239 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1240 immediateScrollByDeltaY(deltaY);
1241 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1246 IntSize stretchAmount = m_scrollableArea->overhangAmount();
1248 if (m_scrollElasticityController.m_momentumScrollInProgress) {
1249 if ((pinnedInDirection(eventCoalescedDeltaX, eventCoalescedDeltaY) || (fabsf(eventCoalescedDeltaX) + fabsf(eventCoalescedDeltaY) <= 0)) && m_scrollElasticityController.m_lastMomentumScrollTimestamp) {
1250 m_scrollElasticityController.m_ignoreMomentumScrolls = true;
1251 m_scrollElasticityController.m_momentumScrollInProgress = false;
1256 m_scrollElasticityController.m_stretchScrollForce.setWidth(m_scrollElasticityController.m_stretchScrollForce.width() + deltaX);
1257 m_scrollElasticityController.m_stretchScrollForce.setHeight(m_scrollElasticityController.m_stretchScrollForce.height() + deltaY);
1259 FloatSize dampedDelta(ceilf(elasticDeltaForReboundDelta(m_scrollElasticityController.m_stretchScrollForce.width())), ceilf(elasticDeltaForReboundDelta(m_scrollElasticityController.m_stretchScrollForce.height())));
1260 FloatPoint origOrigin = (m_scrollableArea->visibleContentRect().location() + m_scrollableArea->scrollOrigin()) - stretchAmount;
1261 FloatPoint newOrigin = origOrigin + dampedDelta;
1263 if (origOrigin != newOrigin) {
1264 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1265 immediateScrollToPoint(newOrigin);
1266 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1271 if (m_scrollElasticityController.m_momentumScrollInProgress && phase == PlatformWheelEventPhaseEnded) {
1272 m_scrollElasticityController.m_momentumScrollInProgress = false;
1273 m_scrollElasticityController.m_ignoreMomentumScrolls = false;
1274 m_scrollElasticityController.m_lastMomentumScrollTimestamp = 0;
1278 void ScrollAnimatorMac::beginScrollGesture()
1280 didBeginScrollGesture();
1281 m_haveScrolledSincePageLoad = true;
1283 m_scrollElasticityController.beginScrollGesture();
1286 void ScrollAnimatorMac::endScrollGesture()
1288 didEndScrollGesture();
1293 void ScrollAnimatorMac::snapRubberBand()
1295 CFTimeInterval timeDelta = [[NSProcessInfo processInfo] systemUptime] - m_scrollElasticityController.m_lastMomentumScrollTimestamp;
1296 if (m_scrollElasticityController.m_lastMomentumScrollTimestamp && timeDelta >= scrollVelocityZeroingTimeout)
1297 m_scrollElasticityController.m_momentumVelocity = FloatSize();
1299 m_scrollElasticityController.m_inScrollGesture = false;
1301 if (m_scrollElasticityController.m_snapRubberbandTimerIsActive)
1304 m_scrollElasticityController.m_startTime = [NSDate timeIntervalSinceReferenceDate];
1305 m_scrollElasticityController.m_startStretch = FloatSize();
1306 m_scrollElasticityController.m_origOrigin = FloatPoint();
1307 m_scrollElasticityController.m_origVelocity = FloatSize();
1309 m_snapRubberBandTimer.startRepeating(1.0/60.0);
1310 m_scrollElasticityController.m_snapRubberbandTimerIsActive = true;
1313 static inline float roundTowardZero(float num)
1315 return num > 0 ? ceilf(num - 0.5f) : floorf(num + 0.5f);
1318 static inline float roundToDevicePixelTowardZero(float num)
1320 float roundedNum = roundf(num);
1321 if (fabs(num - roundedNum) < 0.125)
1324 return roundTowardZero(num);
1327 void ScrollAnimatorMac::snapRubberBandTimerFired(Timer<ScrollAnimatorMac>*)
1329 if (!m_scrollElasticityController.m_momentumScrollInProgress || m_scrollElasticityController.m_ignoreMomentumScrolls) {
1330 CFTimeInterval timeDelta = [NSDate timeIntervalSinceReferenceDate] - m_scrollElasticityController.m_startTime;
1332 if (m_scrollElasticityController.m_startStretch == FloatSize()) {
1333 m_scrollElasticityController.m_startStretch = m_scrollableArea->overhangAmount();
1334 if (m_scrollElasticityController.m_startStretch == FloatSize()) {
1335 m_snapRubberBandTimer.stop();
1337 m_scrollElasticityController.m_stretchScrollForce = FloatSize();
1338 m_scrollElasticityController.m_startTime = 0;
1339 m_scrollElasticityController.m_startStretch = FloatSize();
1340 m_scrollElasticityController.m_origOrigin = FloatPoint();
1341 m_scrollElasticityController.m_origVelocity = FloatSize();
1342 m_scrollElasticityController.m_snapRubberbandTimerIsActive = false;
1347 m_scrollableArea->didStartRubberBand(roundedIntSize(m_scrollElasticityController.m_startStretch));
1349 m_scrollElasticityController.m_origOrigin = (m_scrollableArea->visibleContentRect().location() + m_scrollableArea->scrollOrigin()) - m_scrollElasticityController.m_startStretch;
1350 m_scrollElasticityController.m_origVelocity = m_scrollElasticityController.m_momentumVelocity;
1352 // Just like normal scrolling, prefer vertical rubberbanding
1353 if (fabsf(m_scrollElasticityController.m_origVelocity.height()) >= fabsf(m_scrollElasticityController.m_origVelocity.width()))
1354 m_scrollElasticityController.m_origVelocity.setWidth(0);
1356 // Don't rubber-band horizontally if it's not possible to scroll horizontally
1357 Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
1358 if (!hScroller || !hScroller->enabled())
1359 m_scrollElasticityController.m_origVelocity.setWidth(0);
1361 // Don't rubber-band vertically if it's not possible to scroll horizontally
1362 Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
1363 if (!vScroller || !vScroller->enabled())
1364 m_scrollElasticityController.m_origVelocity.setHeight(0);
1367 FloatPoint delta(roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_scrollElasticityController.m_startStretch.width(), -m_scrollElasticityController.m_origVelocity.width(), (float)timeDelta)),
1368 roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_scrollElasticityController.m_startStretch.height(), -m_scrollElasticityController.m_origVelocity.height(), (float)timeDelta)));
1370 if (fabs(delta.x()) >= 1 || fabs(delta.y()) >= 1) {
1371 FloatPoint newOrigin = m_scrollElasticityController.m_origOrigin + delta;
1373 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1374 immediateScrollToPoint(newOrigin);
1375 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1377 FloatSize newStretch = m_scrollableArea->overhangAmount();
1379 m_scrollElasticityController.m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(newStretch.width()));
1380 m_scrollElasticityController.m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(newStretch.height()));
1382 immediateScrollToPoint(m_scrollElasticityController.m_origOrigin);
1384 m_scrollableArea->didCompleteRubberBand(roundedIntSize(m_scrollElasticityController.m_startStretch));
1386 m_snapRubberBandTimer.stop();
1388 m_scrollElasticityController.m_stretchScrollForce = FloatSize();
1389 m_scrollElasticityController.m_startTime = 0;
1390 m_scrollElasticityController.m_startStretch = FloatSize();
1391 m_scrollElasticityController.m_origOrigin = FloatPoint();
1392 m_scrollElasticityController.m_origVelocity = FloatSize();
1393 m_scrollElasticityController.m_snapRubberbandTimerIsActive = false;
1396 m_scrollElasticityController.m_startTime = [NSDate timeIntervalSinceReferenceDate];
1397 m_scrollElasticityController.m_startStretch = FloatSize();
1402 void ScrollAnimatorMac::setIsActive()
1404 #if USE(SCROLLBAR_PAINTER)
1405 if (needsScrollerStyleUpdate())
1406 updateScrollerStyle();
1410 #if USE(SCROLLBAR_PAINTER)
1411 void ScrollAnimatorMac::updateScrollerStyle()
1413 if (!scrollableArea()->isOnActivePage()) {
1414 setNeedsScrollerStyleUpdate(true);
1418 ScrollbarThemeMac* macTheme = macScrollbarTheme();
1420 setNeedsScrollerStyleUpdate(false);
1424 NSScrollerStyle newStyle = [m_scrollbarPainterController.get() scrollerStyle];
1426 if (Scrollbar* verticalScrollbar = scrollableArea()->verticalScrollbar()) {
1427 verticalScrollbar->invalidate();
1429 ScrollbarPainter oldVerticalPainter = [m_scrollbarPainterController.get() verticalScrollerImp];
1430 ScrollbarPainter newVerticalPainter = [NSClassFromString(@"NSScrollerImp") scrollerImpWithStyle:newStyle
1431 controlSize:(NSControlSize)verticalScrollbar->controlSize()
1433 replacingScrollerImp:oldVerticalPainter];
1434 macTheme->setNewPainterForScrollbar(verticalScrollbar, newVerticalPainter);
1435 [m_scrollbarPainterController.get() setVerticalScrollerImp:newVerticalPainter];
1437 // The different scrollbar styles have different thicknesses, so we must re-set the
1438 // frameRect to the new thickness, and the re-layout below will ensure the position
1439 // and length are properly updated.
1440 int thickness = macTheme->scrollbarThickness(verticalScrollbar->controlSize());
1441 verticalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
1444 if (Scrollbar* horizontalScrollbar = scrollableArea()->horizontalScrollbar()) {
1445 horizontalScrollbar->invalidate();
1447 ScrollbarPainter oldHorizontalPainter = [m_scrollbarPainterController.get() horizontalScrollerImp];
1448 ScrollbarPainter newHorizontalPainter = [NSClassFromString(@"NSScrollerImp") scrollerImpWithStyle:newStyle
1449 controlSize:(NSControlSize)horizontalScrollbar->controlSize()
1451 replacingScrollerImp:oldHorizontalPainter];
1452 macTheme->setNewPainterForScrollbar(horizontalScrollbar, newHorizontalPainter);
1453 [m_scrollbarPainterController.get() setVerticalScrollerImp:newHorizontalPainter];
1455 // The different scrollbar styles have different thicknesses, so we must re-set the
1456 // frameRect to the new thickness, and the re-layout below will ensure the position
1457 // and length are properly updated.
1458 int thickness = macTheme->scrollbarThickness(horizontalScrollbar->controlSize());
1459 horizontalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
1462 // If needsScrollerStyleUpdate() is true, then the page is restoring from the page cache, and
1463 // a relayout will happen on its own. Otherwise, we must initiate a re-layout ourselves.
1464 scrollableArea()->scrollbarStyleChanged(newStyle, !needsScrollerStyleUpdate());
1466 setNeedsScrollerStyleUpdate(false);
1469 void ScrollAnimatorMac::startScrollbarPaintTimer()
1471 m_initialScrollbarPaintTimer.startOneShot(0.1);
1474 bool ScrollAnimatorMac::scrollbarPaintTimerIsActive() const
1476 return m_initialScrollbarPaintTimer.isActive();
1479 void ScrollAnimatorMac::stopScrollbarPaintTimer()
1481 m_initialScrollbarPaintTimer.stop();
1484 void ScrollAnimatorMac::initialScrollbarPaintTimerFired(Timer<ScrollAnimatorMac>*)
1486 // To force the scrollbars to flash, we have to call hide first. Otherwise, the ScrollbarPainterController
1487 // might think that the scrollbars are already showing and bail early.
1488 [m_scrollbarPainterController.get() hideOverlayScrollers];
1489 [m_scrollbarPainterController.get() flashScrollers];
1493 void ScrollAnimatorMac::setVisibleScrollerThumbRect(const IntRect& scrollerThumb)
1495 IntRect rectInViewCoordinates = scrollerThumb;
1496 if (Scrollbar* verticalScrollbar = m_scrollableArea->verticalScrollbar())
1497 rectInViewCoordinates = verticalScrollbar->convertToContainingView(scrollerThumb);
1499 if (rectInViewCoordinates == m_visibleScrollerThumbRect)
1502 m_scrollableArea->setVisibleScrollerThumbRect(rectInViewCoordinates);
1503 m_visibleScrollerThumbRect = rectInViewCoordinates;
1506 } // namespace WebCore
1508 #endif // ENABLE(SMOOTH_SCROLLING)