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 _animator->scrollableArea()->invalidateScrollbarRect(verticalScrollbar, WebCore::IntRect(0, 0, verticalScrollbar->width(), verticalScrollbar->height()));
331 if (WebCore::Scrollbar* horizontalScrollbar = _animator->scrollableArea()->horizontalScrollbar())
332 _animator->scrollableArea()->invalidateScrollbarRect(horizontalScrollbar, 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::didAddVerticalScrollbar(Scrollbar* scrollbar)
686 #if USE(WK_SCROLLBAR_PAINTER)
687 WKScrollbarPainterRef painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
688 wkScrollbarPainterSetDelegate(painter, m_scrollbarPainterDelegate.get());
689 wkSetPainterForPainterController(m_scrollbarPainterController.get(), painter, false);
690 if (scrollableArea()->inLiveResize())
691 wkSetScrollbarPainterKnobAlpha(painter, 1);
693 UNUSED_PARAM(scrollbar);
697 void ScrollAnimatorMac::willRemoveVerticalScrollbar(Scrollbar* scrollbar)
699 #if USE(WK_SCROLLBAR_PAINTER)
700 WKScrollbarPainterRef painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
701 wkScrollbarPainterSetDelegate(painter, nil);
702 wkSetPainterForPainterController(m_scrollbarPainterController.get(), nil, false);
704 UNUSED_PARAM(scrollbar);
708 void ScrollAnimatorMac::didAddHorizontalScrollbar(Scrollbar* scrollbar)
710 #if USE(WK_SCROLLBAR_PAINTER)
711 WKScrollbarPainterRef painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
712 wkScrollbarPainterSetDelegate(painter, m_scrollbarPainterDelegate.get());
713 wkSetPainterForPainterController(m_scrollbarPainterController.get(), painter, true);
714 if (scrollableArea()->inLiveResize())
715 wkSetScrollbarPainterKnobAlpha(painter, 1);
717 UNUSED_PARAM(scrollbar);
721 void ScrollAnimatorMac::willRemoveHorizontalScrollbar(Scrollbar* scrollbar)
723 #if USE(WK_SCROLLBAR_PAINTER)
724 WKScrollbarPainterRef painter = static_cast<WebCore::ScrollbarThemeMac*>(WebCore::ScrollbarTheme::nativeTheme())->painterForScrollbar(scrollbar);
725 wkScrollbarPainterSetDelegate(painter, nil);
726 wkSetPainterForPainterController(m_scrollbarPainterController.get(), nil, true);
728 UNUSED_PARAM(scrollbar);
732 void ScrollAnimatorMac::cancelAnimations()
734 m_haveScrolledSincePageLoad = false;
736 #if USE(WK_SCROLLBAR_PAINTER)
737 if (scrollbarPaintTimerIsActive())
738 stopScrollbarPaintTimer();
739 [m_scrollbarPainterDelegate.get() cancelAnimations];
743 #if ENABLE(RUBBER_BANDING)
745 static const float scrollVelocityZeroingTimeout = 0.10f;
746 static const float rubberbandStiffness = 20;
747 static const float rubberbandDirectionLockStretchRatio = 1;
748 static const float rubberbandMinimumRequiredDeltaBeforeStretch = 10;
749 static const float rubberbandAmplitude = 0.31f;
750 static const float rubberbandPeriod = 1.6f;
752 static float elasticDeltaForTimeDelta(float initialPosition, float initialVelocity, float elapsedTime)
754 float amplitude = rubberbandAmplitude;
755 float period = rubberbandPeriod;
756 float criticalDampeningFactor = expf((-elapsedTime * rubberbandStiffness) / period);
758 return (initialPosition + (-initialVelocity * elapsedTime * amplitude)) * criticalDampeningFactor;
761 static float elasticDeltaForReboundDelta(float delta)
763 float stiffness = std::max(rubberbandStiffness, 1.0f);
764 return delta / stiffness;
767 static float reboundDeltaForElasticDelta(float delta)
769 return delta * rubberbandStiffness;
772 static float scrollWheelMultiplier()
774 static float multiplier = -1;
775 if (multiplier < 0) {
776 multiplier = [[NSUserDefaults standardUserDefaults] floatForKey:@"NSScrollWheelMultiplier"];
783 void ScrollAnimatorMac::handleWheelEvent(PlatformWheelEvent& wheelEvent)
785 m_haveScrolledSincePageLoad = true;
787 if (!wheelEvent.hasPreciseScrollingDeltas()) {
788 ScrollAnimator::handleWheelEvent(wheelEvent);
794 bool isMometumScrollEvent = (wheelEvent.momentumPhase() != PlatformWheelEventPhaseNone);
795 if (m_ignoreMomentumScrolls && (isMometumScrollEvent || m_snapRubberBandTimer.isActive())) {
796 if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseEnded)
797 m_ignoreMomentumScrolls = false;
801 smoothScrollWithEvent(wheelEvent);
804 void ScrollAnimatorMac::handleGestureEvent(const PlatformGestureEvent& gestureEvent)
806 if (gestureEvent.type() == PlatformGestureEvent::ScrollBeginType)
807 beginScrollGesture();
812 bool ScrollAnimatorMac::pinnedInDirection(float deltaX, float deltaY)
814 FloatSize limitDelta;
815 if (fabsf(deltaY) >= fabsf(deltaX)) {
817 // We are trying to scroll up. Make sure we are not pinned to the top
818 limitDelta.setHeight(m_scrollableArea->visibleContentRect().y() + + m_scrollableArea->scrollOrigin().y());
820 // We are trying to scroll down. Make sure we are not pinned to the bottom
821 limitDelta.setHeight(m_scrollableArea->contentsSize().height() - (m_scrollableArea->visibleContentRect().maxY() + m_scrollableArea->scrollOrigin().y()));
823 } else if (deltaX != 0) {
825 // We are trying to scroll left. Make sure we are not pinned to the left
826 limitDelta.setWidth(m_scrollableArea->visibleContentRect().x() + m_scrollableArea->scrollOrigin().x());
828 // We are trying to scroll right. Make sure we are not pinned to the right
829 limitDelta.setWidth(m_scrollableArea->contentsSize().width() - (m_scrollableArea->visibleContentRect().maxX() + m_scrollableArea->scrollOrigin().x()));
833 if ((deltaX != 0 || deltaY != 0) && (limitDelta.width() < 1 && limitDelta.height() < 1))
838 bool ScrollAnimatorMac::allowsVerticalStretching() const
840 Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
841 Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
842 if (((vScroller && vScroller->enabled()) || (!hScroller || !hScroller->enabled())))
848 bool ScrollAnimatorMac::allowsHorizontalStretching() const
850 Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
851 Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
852 if (((hScroller && hScroller->enabled()) || (!vScroller || !vScroller->enabled())))
858 void ScrollAnimatorMac::smoothScrollWithEvent(PlatformWheelEvent& wheelEvent)
860 m_haveScrolledSincePageLoad = true;
862 float deltaX = m_overflowScrollDelta.width();
863 float deltaY = m_overflowScrollDelta.height();
865 // Reset overflow values because we may decide to remove delta at various points and put it into overflow.
866 m_overflowScrollDelta = FloatSize();
868 float eventCoallescedDeltaX = -wheelEvent.deltaX();
869 float eventCoallescedDeltaY = -wheelEvent.deltaY();
871 deltaX += eventCoallescedDeltaX;
872 deltaY += eventCoallescedDeltaY;
874 // Slightly prefer scrolling vertically by applying the = case to deltaY
875 if (fabsf(deltaY) >= fabsf(deltaX))
880 bool isVerticallyStretched = false;
881 bool isHorizontallyStretched = false;
882 bool shouldStretch = false;
884 IntSize stretchAmount = m_scrollableArea->overhangAmount();
886 isHorizontallyStretched = stretchAmount.width();
887 isVerticallyStretched = stretchAmount.height();
889 PlatformWheelEventPhase phase = wheelEvent.momentumPhase();
891 // If we are starting momentum scrolling then do some setup.
892 if (!m_momentumScrollInProgress && (phase == PlatformWheelEventPhaseBegan || phase == PlatformWheelEventPhaseChanged))
893 m_momentumScrollInProgress = true;
895 CFTimeInterval timeDelta = wheelEvent.timestamp() - m_lastMomemtumScrollTimestamp;
896 if (m_inScrollGesture || m_momentumScrollInProgress) {
897 if (m_lastMomemtumScrollTimestamp && timeDelta > 0 && timeDelta < scrollVelocityZeroingTimeout) {
898 m_momentumVelocity.setWidth(eventCoallescedDeltaX / (float)timeDelta);
899 m_momentumVelocity.setHeight(eventCoallescedDeltaY / (float)timeDelta);
900 m_lastMomemtumScrollTimestamp = wheelEvent.timestamp();
902 m_lastMomemtumScrollTimestamp = wheelEvent.timestamp();
903 m_momentumVelocity = FloatSize();
906 if (isVerticallyStretched) {
907 if (!isHorizontallyStretched && pinnedInDirection(deltaX, 0)) {
908 // Stretching only in the vertical.
909 if (deltaY != 0 && (fabsf(deltaX / deltaY) < rubberbandDirectionLockStretchRatio))
911 else if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
912 m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
915 m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
917 } else if (isHorizontallyStretched) {
918 // Stretching only in the horizontal.
919 if (pinnedInDirection(0, deltaY)) {
920 if (deltaX != 0 && (fabsf(deltaY / deltaX) < rubberbandDirectionLockStretchRatio))
922 else if (fabsf(deltaY) < rubberbandMinimumRequiredDeltaBeforeStretch) {
923 m_overflowScrollDelta.setHeight(m_overflowScrollDelta.height() + deltaY);
926 m_overflowScrollDelta.setHeight(m_overflowScrollDelta.height() + deltaY);
929 // Not stretching at all yet.
930 if (pinnedInDirection(deltaX, deltaY)) {
931 if (fabsf(deltaY) >= fabsf(deltaX)) {
932 if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStretch) {
933 m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
936 m_overflowScrollDelta.setWidth(m_overflowScrollDelta.width() + deltaX);
938 shouldStretch = true;
943 if (deltaX != 0 || deltaY != 0) {
944 if (!(shouldStretch || isVerticallyStretched || isHorizontallyStretched)) {
946 deltaY *= scrollWheelMultiplier();
947 immediateScrollByDeltaY(deltaY);
950 deltaX *= scrollWheelMultiplier();
951 immediateScrollByDeltaX(deltaX);
954 if (!allowsHorizontalStretching()) {
956 eventCoallescedDeltaX = 0;
957 } else if ((deltaX != 0) && !isHorizontallyStretched && !pinnedInDirection(deltaX, 0)) {
958 deltaX *= scrollWheelMultiplier();
960 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
961 immediateScrollByDeltaX(deltaX);
962 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
967 if (!allowsVerticalStretching()) {
969 eventCoallescedDeltaY = 0;
970 } else if ((deltaY != 0) && !isVerticallyStretched && !pinnedInDirection(0, deltaY)) {
971 deltaY *= scrollWheelMultiplier();
973 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
974 immediateScrollByDeltaY(deltaY);
975 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
980 IntSize stretchAmount = m_scrollableArea->overhangAmount();
982 if (m_momentumScrollInProgress) {
983 if ((pinnedInDirection(eventCoallescedDeltaX, eventCoallescedDeltaY) || (fabsf(eventCoallescedDeltaX) + fabsf(eventCoallescedDeltaY) <= 0)) && m_lastMomemtumScrollTimestamp) {
984 m_ignoreMomentumScrolls = true;
985 m_momentumScrollInProgress = false;
990 m_stretchScrollForce.setWidth(m_stretchScrollForce.width() + deltaX);
991 m_stretchScrollForce.setHeight(m_stretchScrollForce.height() + deltaY);
993 FloatSize dampedDelta(ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.width())), ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.height())));
994 FloatPoint origOrigin = (m_scrollableArea->visibleContentRect().location() + m_scrollableArea->scrollOrigin()) - stretchAmount;
995 FloatPoint newOrigin = origOrigin + dampedDelta;
997 if (origOrigin != newOrigin) {
998 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
999 immediateScrollToPoint(newOrigin);
1000 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1005 if (m_momentumScrollInProgress && phase == PlatformWheelEventPhaseEnded) {
1006 m_momentumScrollInProgress = false;
1007 m_ignoreMomentumScrolls = false;
1008 m_lastMomemtumScrollTimestamp = 0;
1012 void ScrollAnimatorMac::beginScrollGesture()
1014 m_haveScrolledSincePageLoad = true;
1015 m_inScrollGesture = true;
1016 m_momentumScrollInProgress = false;
1017 m_ignoreMomentumScrolls = false;
1018 m_lastMomemtumScrollTimestamp = 0;
1019 m_momentumVelocity = FloatSize();
1021 IntSize stretchAmount = m_scrollableArea->overhangAmount();
1022 m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(stretchAmount.width()));
1023 m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(stretchAmount.height()));
1025 m_overflowScrollDelta = FloatSize();
1027 if (m_snapRubberBandTimer.isActive())
1028 m_snapRubberBandTimer.stop();
1031 void ScrollAnimatorMac::endScrollGesture()
1036 void ScrollAnimatorMac::snapRubberBand()
1038 CFTimeInterval timeDelta = [[NSProcessInfo processInfo] systemUptime] - m_lastMomemtumScrollTimestamp;
1039 if (m_lastMomemtumScrollTimestamp && timeDelta >= scrollVelocityZeroingTimeout)
1040 m_momentumVelocity = FloatSize();
1042 m_inScrollGesture = false;
1044 if (m_snapRubberBandTimer.isActive())
1047 m_startTime = [NSDate timeIntervalSinceReferenceDate];
1048 m_startStretch = FloatSize();
1049 m_origOrigin = FloatPoint();
1050 m_origVelocity = FloatSize();
1052 m_snapRubberBandTimer.startRepeating(1.0/60.0);
1055 static inline float roundTowardZero(float num)
1057 return num > 0 ? ceilf(num - 0.5f) : floorf(num + 0.5f);
1060 static inline float roundToDevicePixelTowardZero(float num)
1062 float roundedNum = roundf(num);
1063 if (fabs(num - roundedNum) < 0.125)
1066 return roundTowardZero(num);
1069 void ScrollAnimatorMac::snapRubberBandTimerFired(Timer<ScrollAnimatorMac>*)
1071 if (!m_momentumScrollInProgress || m_ignoreMomentumScrolls) {
1072 CFTimeInterval timeDelta = [NSDate timeIntervalSinceReferenceDate] - m_startTime;
1074 if (m_startStretch == FloatSize()) {
1075 m_startStretch = m_scrollableArea->overhangAmount();
1076 if (m_startStretch == FloatSize()) {
1077 m_snapRubberBandTimer.stop();
1078 m_stretchScrollForce = FloatSize();
1080 m_startStretch = FloatSize();
1081 m_origOrigin = FloatPoint();
1082 m_origVelocity = FloatSize();
1087 m_origOrigin = (m_scrollableArea->visibleContentRect().location() + m_scrollableArea->scrollOrigin()) - m_startStretch;
1088 m_origVelocity = m_momentumVelocity;
1090 // Just like normal scrolling, prefer vertical rubberbanding
1091 if (fabsf(m_origVelocity.height()) >= fabsf(m_origVelocity.width()))
1092 m_origVelocity.setWidth(0);
1094 // Don't rubber-band horizontally if it's not possible to scroll horizontally
1095 Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
1096 if (!hScroller || !hScroller->enabled())
1097 m_origVelocity.setWidth(0);
1099 // Don't rubber-band vertically if it's not possible to scroll horizontally
1100 Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
1101 if (!vScroller || !vScroller->enabled())
1102 m_origVelocity.setHeight(0);
1105 FloatPoint delta(roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_startStretch.width(), -m_origVelocity.width(), (float)timeDelta)),
1106 roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m_startStretch.height(), -m_origVelocity.height(), (float)timeDelta)));
1108 if (fabs(delta.x()) >= 1 || fabs(delta.y()) >= 1) {
1109 FloatPoint newOrigin = m_origOrigin + delta;
1111 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1112 immediateScrollToPoint(newOrigin);
1113 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1115 FloatSize newStretch = m_scrollableArea->overhangAmount();
1117 m_stretchScrollForce.setWidth(reboundDeltaForElasticDelta(newStretch.width()));
1118 m_stretchScrollForce.setHeight(reboundDeltaForElasticDelta(newStretch.height()));
1120 immediateScrollToPoint(m_origOrigin);
1122 m_scrollableArea->didCompleteRubberBand(roundedIntSize(m_startStretch));
1124 m_snapRubberBandTimer.stop();
1125 m_stretchScrollForce = FloatSize();
1128 m_startStretch = FloatSize();
1129 m_origOrigin = FloatPoint();
1130 m_origVelocity = FloatSize();
1133 m_startTime = [NSDate timeIntervalSinceReferenceDate];
1134 m_startStretch = FloatSize();
1139 #if USE(WK_SCROLLBAR_PAINTER)
1140 void ScrollAnimatorMac::startScrollbarPaintTimer()
1142 m_initialScrollbarPaintTimer.startOneShot(0.1);
1145 bool ScrollAnimatorMac::scrollbarPaintTimerIsActive() const
1147 return m_initialScrollbarPaintTimer.isActive();
1150 void ScrollAnimatorMac::stopScrollbarPaintTimer()
1152 m_initialScrollbarPaintTimer.stop();
1155 void ScrollAnimatorMac::initialScrollbarPaintTimerFired(Timer<ScrollAnimatorMac>*)
1157 wkScrollbarPainterForceFlashScrollers(m_scrollbarPainterController.get());
1161 } // namespace WebCore
1163 #endif // ENABLE(SMOOTH_SCROLLING)