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 "EmptyProtocolDefinitions.h"
34 #include "FloatPoint.h"
35 #include "NSScrollerImpDetails.h"
36 #include "PlatformGestureEvent.h"
37 #include "PlatformWheelEvent.h"
38 #include "ScrollView.h"
39 #include "ScrollableArea.h"
40 #include "ScrollbarTheme.h"
41 #include "ScrollbarThemeMac.h"
42 #include "WebCoreSystemInterface.h"
43 #include <wtf/PassOwnPtr.h>
44 #include <wtf/UnusedParam.h>
46 using namespace WebCore;
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;
56 static bool supportsExpansionTransitionProgress()
58 static bool globalSupportsExpansionTransitionProgress = [NSClassFromString(@"NSScrollerImp") instancesRespondToSelector:@selector(expansionTransitionProgress)];
59 return globalSupportsExpansionTransitionProgress;
62 static ScrollbarThemeMac* macScrollbarTheme()
64 ScrollbarTheme* scrollbarTheme = ScrollbarTheme::theme();
65 return !scrollbarTheme->isMockTheme() ? static_cast<ScrollbarThemeMac*>(scrollbarTheme) : 0;
68 static ScrollbarPainter scrollbarPainterForScrollbar(Scrollbar* scrollbar)
70 if (ScrollbarThemeMac* scrollbarTheme = macScrollbarTheme())
71 return scrollbarTheme->painterForScrollbar(scrollbar);
76 @interface NSObject (ScrollAnimationHelperDetails)
77 - (id)initWithDelegate:(id)delegate;
80 - (NSPoint)targetOrigin;
84 @interface WebScrollAnimationHelperDelegate : NSObject
86 WebCore::ScrollAnimatorMac* _animator;
88 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator;
91 static NSSize abs(NSSize size)
93 NSSize finalSize = size;
94 if (finalSize.width < 0)
95 finalSize.width = -finalSize.width;
96 if (finalSize.height < 0)
97 finalSize.height = -finalSize.height;
101 @implementation WebScrollAnimationHelperDelegate
103 - (id)initWithScrollAnimator:(WebCore::ScrollAnimatorMac*)scrollAnimator
109 _animator = scrollAnimator;
123 WebCore::FloatPoint currentPosition = _animator->currentPosition();
124 return NSMakeRect(currentPosition.x(), currentPosition.y(), 0, 0);
127 - (void)_immediateScrollToPoint:(NSPoint)newPosition
131 _animator->immediateScrollToPointForScrollAnimation(newPosition);
134 - (NSPoint)_pixelAlignProposedScrollPosition:(NSPoint)newOrigin
139 - (NSSize)convertSizeToBase:(NSSize)size
144 - (NSSize)convertSizeFromBase:(NSSize)size
149 - (NSSize)convertSizeToBacking:(NSSize)size
154 - (NSSize)convertSizeFromBacking:(NSSize)size
174 - (void)_recursiveRecomputeToolTips
180 @interface WebScrollbarPainterControllerDelegate : NSObject
182 ScrollableArea* _scrollableArea;
184 - (id)initWithScrollableArea:(ScrollableArea*)scrollableArea;
187 @implementation WebScrollbarPainterControllerDelegate
189 - (id)initWithScrollableArea:(ScrollableArea*)scrollableArea
195 _scrollableArea = scrollableArea;
204 - (NSRect)contentAreaRectForScrollerImpPair:(id)scrollerImpPair
206 UNUSED_PARAM(scrollerImpPair);
207 if (!_scrollableArea)
210 WebCore::IntSize contentsSize = _scrollableArea->contentsSize();
211 return NSMakeRect(0, 0, contentsSize.width(), contentsSize.height());
214 - (BOOL)inLiveResizeForScrollerImpPair:(id)scrollerImpPair
216 UNUSED_PARAM(scrollerImpPair);
217 if (!_scrollableArea)
220 return _scrollableArea->inLiveResize();
223 - (NSPoint)mouseLocationInContentAreaForScrollerImpPair:(id)scrollerImpPair
225 UNUSED_PARAM(scrollerImpPair);
226 if (!_scrollableArea)
229 return _scrollableArea->currentMousePosition();
232 - (NSPoint)scrollerImpPair:(id)scrollerImpPair convertContentPoint:(NSPoint)pointInContentArea toScrollerImp:(id)scrollerImp
234 UNUSED_PARAM(scrollerImpPair);
236 if (!_scrollableArea || !scrollerImp)
239 WebCore::Scrollbar* scrollbar = 0;
240 if ([scrollerImp isHorizontal])
241 scrollbar = _scrollableArea->horizontalScrollbar();
243 scrollbar = _scrollableArea->verticalScrollbar();
245 // It is possible to have a null scrollbar here since it is possible for this delegate
246 // method to be called between the moment when a scrollbar has been set to 0 and the
247 // moment when its destructor has been called. We should probably de-couple some
248 // of the clean-up work in ScrollbarThemeMac::unregisterScrollbar() to avoid this
253 ASSERT(scrollerImp == scrollbarPainterForScrollbar(scrollbar));
255 return scrollbar->convertFromContainingView(WebCore::IntPoint(pointInContentArea));
258 - (void)scrollerImpPair:(id)scrollerImpPair setContentAreaNeedsDisplayInRect:(NSRect)rect
260 UNUSED_PARAM(scrollerImpPair);
263 if (!_scrollableArea)
266 if (!_scrollableArea->isOnActivePage())
269 _scrollableArea->scrollAnimator()->contentAreaWillPaint();
272 - (void)scrollerImpPair:(id)scrollerImpPair updateScrollerStyleForNewRecommendedScrollerStyle:(NSScrollerStyle)newRecommendedScrollerStyle
274 if (!_scrollableArea)
277 [scrollerImpPair setScrollerStyle:newRecommendedScrollerStyle];
279 static_cast<ScrollAnimatorMac*>(_scrollableArea->scrollAnimator())->updateScrollerStyle();
284 enum FeatureToAnimate {
291 @interface WebScrollbarPartAnimation : NSAnimation
293 Scrollbar* _scrollbar;
294 RetainPtr<ScrollbarPainter> _scrollbarPainter;
295 FeatureToAnimate _featureToAnimate;
299 - (id)initWithScrollbar:(Scrollbar*)scrollbar featureToAnimate:(FeatureToAnimate)featureToAnimate animateFrom:(CGFloat)startValue animateTo:(CGFloat)endValue duration:(NSTimeInterval)duration;
302 @implementation WebScrollbarPartAnimation
304 - (id)initWithScrollbar:(Scrollbar*)scrollbar featureToAnimate:(FeatureToAnimate)featureToAnimate animateFrom:(CGFloat)startValue animateTo:(CGFloat)endValue duration:(NSTimeInterval)duration
306 self = [super initWithDuration:duration animationCurve:NSAnimationEaseInOut];
310 _scrollbar = scrollbar;
311 _featureToAnimate = featureToAnimate;
312 _startValue = startValue;
313 _endValue = endValue;
315 [self setAnimationBlockingMode:NSAnimationNonblocking];
320 - (void)startAnimation
324 _scrollbarPainter = scrollbarPainterForScrollbar(_scrollbar);
326 [super startAnimation];
329 - (void)setStartValue:(CGFloat)startValue
331 _startValue = startValue;
334 - (void)setEndValue:(CGFloat)endValue
336 _endValue = endValue;
339 - (void)setCurrentProgress:(NSAnimationProgress)progress
341 [super setCurrentProgress:progress];
345 CGFloat currentValue;
346 if (_startValue > _endValue)
347 currentValue = 1 - progress;
349 currentValue = progress;
351 switch (_featureToAnimate) {
353 [_scrollbarPainter.get() setKnobAlpha:currentValue];
356 [_scrollbarPainter.get() setTrackAlpha:currentValue];
358 case UIStateTransition:
359 [_scrollbarPainter.get() setUiStateTransitionProgress:currentValue];
361 case ExpansionTransition:
362 [_scrollbarPainter.get() setExpansionTransitionProgress:currentValue];
366 _scrollbar->invalidate();
371 BEGIN_BLOCK_OBJC_EXCEPTIONS;
372 [self stopAnimation];
373 END_BLOCK_OBJC_EXCEPTIONS;
379 @interface WebScrollbarPainterDelegate : NSObject<NSAnimationDelegate>
381 WebCore::Scrollbar* _scrollbar;
383 RetainPtr<WebScrollbarPartAnimation> _knobAlphaAnimation;
384 RetainPtr<WebScrollbarPartAnimation> _trackAlphaAnimation;
385 RetainPtr<WebScrollbarPartAnimation> _uiStateTransitionAnimation;
386 RetainPtr<WebScrollbarPartAnimation> _expansionTransitionAnimation;
388 - (id)initWithScrollbar:(WebCore::Scrollbar*)scrollbar;
389 - (void)cancelAnimations;
392 @implementation WebScrollbarPainterDelegate
394 - (id)initWithScrollbar:(WebCore::Scrollbar*)scrollbar
400 _scrollbar = scrollbar;
404 - (void)cancelAnimations
406 BEGIN_BLOCK_OBJC_EXCEPTIONS;
407 [_knobAlphaAnimation.get() stopAnimation];
408 [_trackAlphaAnimation.get() stopAnimation];
409 [_uiStateTransitionAnimation.get() stopAnimation];
410 [_expansionTransitionAnimation.get() stopAnimation];
411 END_BLOCK_OBJC_EXCEPTIONS;
414 - (ScrollAnimatorMac*)scrollAnimator
416 return static_cast<ScrollAnimatorMac*>(_scrollbar->scrollableArea()->scrollAnimator());
419 - (NSRect)convertRectToBacking:(NSRect)aRect
424 - (NSRect)convertRectFromBacking:(NSRect)aRect
429 #if !PLATFORM(CHROMIUM)
435 if (!ScrollbarThemeMac::isCurrentlyDrawingIntoLayer())
438 // FIXME: This should attempt to return an actual layer.
439 static CALayer *dummyLayer = [[CALayer alloc] init];
444 - (NSPoint)mouseLocationInScrollerForScrollerImp:(id)scrollerImp
449 ASSERT_UNUSED(scrollerImp, scrollerImp == scrollbarPainterForScrollbar(_scrollbar));
451 return _scrollbar->convertFromContainingView(_scrollbar->scrollableArea()->currentMousePosition());
454 - (void)setUpAlphaAnimation:(RetainPtr<WebScrollbarPartAnimation>&)scrollbarPartAnimation scrollerPainter:(ScrollbarPainter)scrollerPainter part:(WebCore::ScrollbarPart)part animateAlphaTo:(CGFloat)newAlpha duration:(NSTimeInterval)duration
456 // If the user has scrolled the page, then the scrollbars must be animated here.
457 // This overrides the early returns.
458 bool mustAnimate = [self scrollAnimator]->haveScrolledSincePageLoad();
460 if ([self scrollAnimator]->scrollbarPaintTimerIsActive() && !mustAnimate)
463 if (_scrollbar->scrollableArea()->shouldSuspendScrollAnimations() && !mustAnimate) {
464 [self scrollAnimator]->startScrollbarPaintTimer();
468 // At this point, we are definitely going to animate now, so stop the timer.
469 [self scrollAnimator]->stopScrollbarPaintTimer();
471 // If we are currently animating, stop
472 if (scrollbarPartAnimation) {
473 [scrollbarPartAnimation.get() stopAnimation];
474 scrollbarPartAnimation = nil;
477 if (part == WebCore::ThumbPart && _scrollbar->orientation() == VerticalScrollbar) {
479 IntRect thumbRect = IntRect([scrollerPainter rectForPart:NSScrollerKnob]);
480 [self scrollAnimator]->setVisibleScrollerThumbRect(thumbRect);
482 [self scrollAnimator]->setVisibleScrollerThumbRect(IntRect());
485 scrollbarPartAnimation.adoptNS([[WebScrollbarPartAnimation alloc] initWithScrollbar:_scrollbar
486 featureToAnimate:part == ThumbPart ? ThumbAlpha : TrackAlpha
487 animateFrom:part == ThumbPart ? [scrollerPainter knobAlpha] : [scrollerPainter trackAlpha]
490 [scrollbarPartAnimation.get() startAnimation];
493 - (void)scrollerImp:(id)scrollerImp animateKnobAlphaTo:(CGFloat)newKnobAlpha duration:(NSTimeInterval)duration
498 ASSERT(scrollerImp == scrollbarPainterForScrollbar(_scrollbar));
500 ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
501 [self setUpAlphaAnimation:_knobAlphaAnimation scrollerPainter:scrollerPainter part:WebCore::ThumbPart animateAlphaTo:newKnobAlpha duration:duration];
504 - (void)scrollerImp:(id)scrollerImp animateTrackAlphaTo:(CGFloat)newTrackAlpha duration:(NSTimeInterval)duration
509 ASSERT(scrollerImp == scrollbarPainterForScrollbar(_scrollbar));
511 ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
512 [self setUpAlphaAnimation:_trackAlphaAnimation scrollerPainter:scrollerPainter part:WebCore::BackTrackPart animateAlphaTo:newTrackAlpha duration:duration];
515 - (void)scrollerImp:(id)scrollerImp animateUIStateTransitionWithDuration:(NSTimeInterval)duration
520 if (!supportsUIStateTransitionProgress())
523 ASSERT(scrollerImp == scrollbarPainterForScrollbar(_scrollbar));
525 ScrollbarPainter scrollbarPainter = (ScrollbarPainter)scrollerImp;
527 // UIStateTransition always animates to 1. In case an animation is in progress this avoids a hard transition.
528 [scrollbarPainter setUiStateTransitionProgress:1 - [scrollerImp uiStateTransitionProgress]];
530 if (!_uiStateTransitionAnimation)
531 _uiStateTransitionAnimation.adoptNS([[WebScrollbarPartAnimation alloc] initWithScrollbar:_scrollbar
532 featureToAnimate:UIStateTransition
533 animateFrom:[scrollbarPainter uiStateTransitionProgress]
537 // If we don't need to initialize the animation, just reset the values in case they have changed.
538 [_uiStateTransitionAnimation.get() setStartValue:[scrollbarPainter uiStateTransitionProgress]];
539 [_uiStateTransitionAnimation.get() setEndValue:1.0];
540 [_uiStateTransitionAnimation.get() setDuration:duration];
542 [_uiStateTransitionAnimation.get() startAnimation];
545 - (void)scrollerImp:(id)scrollerImp animateExpansionTransitionWithDuration:(NSTimeInterval)duration
550 if (!supportsExpansionTransitionProgress())
553 ASSERT(scrollerImp == scrollbarPainterForScrollbar(_scrollbar));
555 ScrollbarPainter scrollbarPainter = (ScrollbarPainter)scrollerImp;
557 // ExpansionTransition always animates to 1. In case an animation is in progress this avoids a hard transition.
558 [scrollbarPainter setExpansionTransitionProgress:1 - [scrollerImp expansionTransitionProgress]];
560 if (!_expansionTransitionAnimation) {
561 _expansionTransitionAnimation.adoptNS([[WebScrollbarPartAnimation alloc] initWithScrollbar:_scrollbar
562 featureToAnimate:ExpansionTransition
563 animateFrom:[scrollbarPainter expansionTransitionProgress]
567 // If we don't need to initialize the animation, just reset the values in case they have changed.
568 [_expansionTransitionAnimation.get() setStartValue:[scrollbarPainter uiStateTransitionProgress]];
569 [_expansionTransitionAnimation.get() setEndValue:1.0];
570 [_expansionTransitionAnimation.get() setDuration:duration];
572 [_expansionTransitionAnimation.get() startAnimation];
575 - (void)scrollerImp:(id)scrollerImp overlayScrollerStateChangedTo:(NSUInteger)newOverlayScrollerState
577 UNUSED_PARAM(scrollerImp);
578 UNUSED_PARAM(newOverlayScrollerState);
584 BEGIN_BLOCK_OBJC_EXCEPTIONS;
585 [_knobAlphaAnimation.get() invalidate];
586 [_trackAlphaAnimation.get() invalidate];
587 [_uiStateTransitionAnimation.get() invalidate];
588 [_expansionTransitionAnimation.get() invalidate];
589 END_BLOCK_OBJC_EXCEPTIONS;
596 PassOwnPtr<ScrollAnimator> ScrollAnimator::create(ScrollableArea* scrollableArea)
598 return adoptPtr(new ScrollAnimatorMac(scrollableArea));
601 ScrollAnimatorMac::ScrollAnimatorMac(ScrollableArea* scrollableArea)
602 : ScrollAnimator(scrollableArea)
603 , m_initialScrollbarPaintTimer(this, &ScrollAnimatorMac::initialScrollbarPaintTimerFired)
604 , m_sendContentAreaScrolledTimer(this, &ScrollAnimatorMac::sendContentAreaScrolledTimerFired)
605 #if ENABLE(RUBBER_BANDING)
606 , m_scrollElasticityController(this)
607 , m_snapRubberBandTimer(this, &ScrollAnimatorMac::snapRubberBandTimerFired)
609 , m_haveScrolledSincePageLoad(false)
610 , m_needsScrollerStyleUpdate(false)
612 m_scrollAnimationHelperDelegate.adoptNS([[WebScrollAnimationHelperDelegate alloc] initWithScrollAnimator:this]);
613 m_scrollAnimationHelper.adoptNS([[NSClassFromString(@"NSScrollAnimationHelper") alloc] initWithDelegate:m_scrollAnimationHelperDelegate.get()]);
615 if (isScrollbarOverlayAPIAvailable()) {
616 m_scrollbarPainterControllerDelegate.adoptNS([[WebScrollbarPainterControllerDelegate alloc] initWithScrollableArea:scrollableArea]);
617 m_scrollbarPainterController = [[[NSClassFromString(@"NSScrollerImpPair") alloc] init] autorelease];
618 [m_scrollbarPainterController.get() setDelegate:m_scrollbarPainterControllerDelegate.get()];
619 [m_scrollbarPainterController.get() setScrollerStyle:recommendedScrollerStyle()];
623 ScrollAnimatorMac::~ScrollAnimatorMac()
625 if (isScrollbarOverlayAPIAvailable()) {
626 BEGIN_BLOCK_OBJC_EXCEPTIONS;
627 [m_scrollbarPainterControllerDelegate.get() invalidate];
628 [m_scrollbarPainterController.get() setDelegate:nil];
629 [m_horizontalScrollbarPainterDelegate.get() invalidate];
630 [m_verticalScrollbarPainterDelegate.get() invalidate];
631 [m_scrollAnimationHelperDelegate.get() invalidate];
632 END_BLOCK_OBJC_EXCEPTIONS;
636 static bool scrollAnimationEnabledForSystem()
638 #if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1070 || PLATFORM(CHROMIUM)
639 return [[NSUserDefaults standardUserDefaults] boolForKey:@"AppleScrollAnimationEnabled"];
641 return [[NSUserDefaults standardUserDefaults] boolForKey:@"NSScrollAnimationEnabled"];
645 bool ScrollAnimatorMac::scroll(ScrollbarOrientation orientation, ScrollGranularity granularity, float step, float multiplier)
647 m_haveScrolledSincePageLoad = true;
649 if (!scrollAnimationEnabledForSystem() || !m_scrollableArea->scrollAnimatorEnabled())
650 return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
652 if (granularity == ScrollByPixel)
653 return ScrollAnimator::scroll(orientation, granularity, step, multiplier);
655 float currentPos = orientation == HorizontalScrollbar ? m_currentPosX : m_currentPosY;
656 float newPos = std::max<float>(std::min<float>(currentPos + (step * multiplier), static_cast<float>(m_scrollableArea->scrollSize(orientation))), 0);
657 if (currentPos == newPos)
661 if ([m_scrollAnimationHelper.get() _isAnimating]) {
662 NSPoint targetOrigin = [m_scrollAnimationHelper.get() targetOrigin];
663 newPoint = orientation == HorizontalScrollbar ? NSMakePoint(newPos, targetOrigin.y) : NSMakePoint(targetOrigin.x, newPos);
665 newPoint = orientation == HorizontalScrollbar ? NSMakePoint(newPos, m_currentPosY) : NSMakePoint(m_currentPosX, newPos);
667 [m_scrollAnimationHelper.get() scrollToPoint:newPoint];
671 void ScrollAnimatorMac::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
673 [m_scrollAnimationHelper.get() _stopRun];
674 immediateScrollTo(offset);
677 FloatPoint ScrollAnimatorMac::adjustScrollPositionIfNecessary(const FloatPoint& position) const
679 if (!m_scrollableArea->constrainsScrollingToContentEdge())
682 float newX = max<float>(min<float>(position.x(), m_scrollableArea->contentsSize().width() - m_scrollableArea->visibleWidth()), 0);
683 float newY = max<float>(min<float>(position.y(), m_scrollableArea->contentsSize().height() - m_scrollableArea->visibleHeight()), 0);
685 return FloatPoint(newX, newY);
688 void ScrollAnimatorMac::immediateScrollTo(const FloatPoint& newPosition)
690 FloatPoint adjustedPosition = adjustScrollPositionIfNecessary(newPosition);
692 bool positionChanged = adjustedPosition.x() != m_currentPosX || adjustedPosition.y() != m_currentPosY;
693 if (!positionChanged && !scrollableArea()->scrollOriginChanged())
696 m_currentPosX = adjustedPosition.x();
697 m_currentPosY = adjustedPosition.y();
698 notifyPositionChanged();
701 bool ScrollAnimatorMac::isRubberBandInProgress() const
703 #if !ENABLE(RUBBER_BANDING)
706 return m_scrollElasticityController.isRubberBandInProgress();
710 void ScrollAnimatorMac::immediateScrollToPointForScrollAnimation(const FloatPoint& newPosition)
712 ASSERT(m_scrollAnimationHelper);
713 immediateScrollTo(newPosition);
716 void ScrollAnimatorMac::notifyPositionChanged()
718 notifyContentAreaScrolled();
719 ScrollAnimator::notifyPositionChanged();
722 void ScrollAnimatorMac::contentAreaWillPaint() const
724 if (!scrollableArea()->isOnActivePage())
726 if (isScrollbarOverlayAPIAvailable())
727 [m_scrollbarPainterController.get() contentAreaWillDraw];
730 void ScrollAnimatorMac::mouseEnteredContentArea() const
732 if (!scrollableArea()->isOnActivePage())
734 if (isScrollbarOverlayAPIAvailable())
735 [m_scrollbarPainterController.get() mouseEnteredContentArea];
738 void ScrollAnimatorMac::mouseExitedContentArea() const
740 if (!scrollableArea()->isOnActivePage())
742 if (isScrollbarOverlayAPIAvailable())
743 [m_scrollbarPainterController.get() mouseExitedContentArea];
746 void ScrollAnimatorMac::mouseMovedInContentArea() const
748 if (!scrollableArea()->isOnActivePage())
750 if (isScrollbarOverlayAPIAvailable())
751 [m_scrollbarPainterController.get() mouseMovedInContentArea];
754 void ScrollAnimatorMac::mouseEnteredScrollbar(Scrollbar* scrollbar) const
756 // At this time, only legacy scrollbars needs to send notifications here.
757 if (recommendedScrollerStyle() != NSScrollerStyleLegacy)
760 if (!scrollableArea()->isOnActivePage())
763 if (isScrollbarOverlayAPIAvailable()) {
764 if (!supportsUIStateTransitionProgress())
766 if (ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar))
767 [painter mouseEnteredScroller];
771 void ScrollAnimatorMac::mouseExitedScrollbar(Scrollbar* scrollbar) const
773 // At this time, only legacy scrollbars needs to send notifications here.
774 if (recommendedScrollerStyle() != NSScrollerStyleLegacy)
777 if (!scrollableArea()->isOnActivePage())
780 if (isScrollbarOverlayAPIAvailable()) {
781 if (!supportsUIStateTransitionProgress())
783 if (ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar))
784 [painter mouseExitedScroller];
788 void ScrollAnimatorMac::willStartLiveResize()
790 if (!scrollableArea()->isOnActivePage())
792 if (isScrollbarOverlayAPIAvailable())
793 [m_scrollbarPainterController.get() startLiveResize];
796 void ScrollAnimatorMac::contentsResized() const
798 if (!scrollableArea()->isOnActivePage())
800 if (isScrollbarOverlayAPIAvailable())
801 [m_scrollbarPainterController.get() contentAreaDidResize];
804 void ScrollAnimatorMac::willEndLiveResize()
806 if (!scrollableArea()->isOnActivePage())
808 if (isScrollbarOverlayAPIAvailable())
809 [m_scrollbarPainterController.get() endLiveResize];
812 void ScrollAnimatorMac::contentAreaDidShow() const
814 if (!scrollableArea()->isOnActivePage())
816 if (isScrollbarOverlayAPIAvailable())
817 [m_scrollbarPainterController.get() windowOrderedIn];
820 void ScrollAnimatorMac::contentAreaDidHide() const
822 if (!scrollableArea()->isOnActivePage())
824 if (isScrollbarOverlayAPIAvailable())
825 [m_scrollbarPainterController.get() windowOrderedOut];
828 void ScrollAnimatorMac::didBeginScrollGesture() const
830 if (!scrollableArea()->isOnActivePage())
832 if (isScrollbarOverlayAPIAvailable())
833 [m_scrollbarPainterController.get() beginScrollGesture];
836 void ScrollAnimatorMac::didEndScrollGesture() const
838 if (!scrollableArea()->isOnActivePage())
840 if (isScrollbarOverlayAPIAvailable())
841 [m_scrollbarPainterController.get() endScrollGesture];
844 void ScrollAnimatorMac::mayBeginScrollGesture() const
846 if (!scrollableArea()->isOnActivePage())
848 if (!isScrollbarOverlayAPIAvailable())
851 [m_scrollbarPainterController.get() beginScrollGesture];
852 [m_scrollbarPainterController.get() contentAreaScrolled];
855 void ScrollAnimatorMac::didAddVerticalScrollbar(Scrollbar* scrollbar)
857 if (!isScrollbarOverlayAPIAvailable())
860 ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
864 ASSERT(!m_verticalScrollbarPainterDelegate);
865 m_verticalScrollbarPainterDelegate.adoptNS([[WebScrollbarPainterDelegate alloc] initWithScrollbar:scrollbar]);
867 [painter setDelegate:m_verticalScrollbarPainterDelegate.get()];
868 [m_scrollbarPainterController.get() setVerticalScrollerImp:painter];
869 if (scrollableArea()->inLiveResize())
870 [painter setKnobAlpha:1];
873 void ScrollAnimatorMac::willRemoveVerticalScrollbar(Scrollbar* scrollbar)
875 if (!isScrollbarOverlayAPIAvailable())
878 ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
882 ASSERT(m_verticalScrollbarPainterDelegate);
883 [m_verticalScrollbarPainterDelegate.get() invalidate];
884 m_verticalScrollbarPainterDelegate = nullptr;
886 [painter setDelegate:nil];
887 [m_scrollbarPainterController.get() setVerticalScrollerImp:nil];
890 void ScrollAnimatorMac::didAddHorizontalScrollbar(Scrollbar* scrollbar)
892 if (!isScrollbarOverlayAPIAvailable())
895 ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
899 ASSERT(!m_horizontalScrollbarPainterDelegate);
900 m_horizontalScrollbarPainterDelegate.adoptNS([[WebScrollbarPainterDelegate alloc] initWithScrollbar:scrollbar]);
902 [painter setDelegate:m_horizontalScrollbarPainterDelegate.get()];
903 [m_scrollbarPainterController.get() setHorizontalScrollerImp:painter];
904 if (scrollableArea()->inLiveResize())
905 [painter setKnobAlpha:1];
908 void ScrollAnimatorMac::willRemoveHorizontalScrollbar(Scrollbar* scrollbar)
910 if (!isScrollbarOverlayAPIAvailable())
913 ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
917 ASSERT(m_horizontalScrollbarPainterDelegate);
918 [m_horizontalScrollbarPainterDelegate.get() invalidate];
919 m_horizontalScrollbarPainterDelegate = nullptr;
921 [painter setDelegate:nil];
922 [m_scrollbarPainterController.get() setHorizontalScrollerImp:nil];
925 bool ScrollAnimatorMac::shouldScrollbarParticipateInHitTesting(Scrollbar* scrollbar)
927 // Non-overlay scrollbars should always participate in hit testing.
928 if (recommendedScrollerStyle() != NSScrollerStyleOverlay)
931 if (!isScrollbarOverlayAPIAvailable())
934 // Overlay scrollbars should participate in hit testing whenever they are at all visible.
935 ScrollbarPainter painter = scrollbarPainterForScrollbar(scrollbar);
938 return [painter knobAlpha] > 0;
941 void ScrollAnimatorMac::notifyContentAreaScrolled()
943 if (!isScrollbarOverlayAPIAvailable())
946 // This function is called when a page is going into the page cache, but the page
947 // isn't really scrolling in that case. We should only pass the message on to the
948 // ScrollbarPainterController when we're really scrolling on an active page.
949 if (scrollableArea()->isOnActivePage())
950 sendContentAreaScrolledSoon();
953 void ScrollAnimatorMac::cancelAnimations()
955 m_haveScrolledSincePageLoad = false;
957 if (isScrollbarOverlayAPIAvailable()) {
958 if (scrollbarPaintTimerIsActive())
959 stopScrollbarPaintTimer();
960 [m_horizontalScrollbarPainterDelegate.get() cancelAnimations];
961 [m_verticalScrollbarPainterDelegate.get() cancelAnimations];
965 void ScrollAnimatorMac::handleWheelEventPhase(PlatformWheelEventPhase phase)
967 // This may not have been set to true yet if the wheel event was handled by the ScrollingTree,
968 // So set it to true here.
969 m_haveScrolledSincePageLoad = true;
971 if (phase == PlatformWheelEventPhaseBegan)
972 didBeginScrollGesture();
973 else if (phase == PlatformWheelEventPhaseEnded || phase == PlatformWheelEventPhaseCancelled)
974 didEndScrollGesture();
975 else if (phase == PlatformWheelEventPhaseMayBegin)
976 mayBeginScrollGesture();
979 #if ENABLE(RUBBER_BANDING)
980 bool ScrollAnimatorMac::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
982 m_haveScrolledSincePageLoad = true;
984 if (!wheelEvent.hasPreciseScrollingDeltas())
985 return ScrollAnimator::handleWheelEvent(wheelEvent);
987 // FIXME: This is somewhat roundabout hack to allow forwarding wheel events
988 // up to the parent scrollable area. It takes advantage of the fact that
989 // the base class implementation of handleWheelEvent will not accept the
990 // wheel event if there is nowhere to scroll.
991 if (fabsf(wheelEvent.deltaY()) >= fabsf(wheelEvent.deltaX())) {
992 if (!allowsVerticalStretching())
993 return ScrollAnimator::handleWheelEvent(wheelEvent);
995 if (!allowsHorizontalStretching())
996 return ScrollAnimator::handleWheelEvent(wheelEvent);
999 bool didHandleEvent = m_scrollElasticityController.handleWheelEvent(wheelEvent);
1002 handleWheelEventPhase(wheelEvent.phase());
1004 return didHandleEvent;
1007 bool ScrollAnimatorMac::pinnedInDirection(float deltaX, float deltaY)
1009 FloatSize limitDelta;
1010 if (fabsf(deltaY) >= fabsf(deltaX)) {
1012 // We are trying to scroll up. Make sure we are not pinned to the top
1013 limitDelta.setHeight(m_scrollableArea->visibleContentRect().y() + + m_scrollableArea->scrollOrigin().y());
1015 // We are trying to scroll down. Make sure we are not pinned to the bottom
1016 limitDelta.setHeight(m_scrollableArea->contentsSize().height() - (m_scrollableArea->visibleContentRect().maxY() + m_scrollableArea->scrollOrigin().y()));
1018 } else if (deltaX != 0) {
1020 // We are trying to scroll left. Make sure we are not pinned to the left
1021 limitDelta.setWidth(m_scrollableArea->visibleContentRect().x() + m_scrollableArea->scrollOrigin().x());
1023 // We are trying to scroll right. Make sure we are not pinned to the right
1024 limitDelta.setWidth(m_scrollableArea->contentsSize().width() - (m_scrollableArea->visibleContentRect().maxX() + m_scrollableArea->scrollOrigin().x()));
1028 if ((deltaX != 0 || deltaY != 0) && (limitDelta.width() < 1 && limitDelta.height() < 1))
1033 bool ScrollAnimatorMac::allowsVerticalStretching()
1035 switch (m_scrollableArea->verticalScrollElasticity()) {
1036 case ScrollElasticityAutomatic: {
1037 Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
1038 Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
1039 return (((vScroller && vScroller->enabled()) || (!hScroller || !hScroller->enabled())));
1041 case ScrollElasticityNone:
1043 case ScrollElasticityAllowed:
1047 ASSERT_NOT_REACHED();
1051 bool ScrollAnimatorMac::allowsHorizontalStretching()
1053 switch (m_scrollableArea->horizontalScrollElasticity()) {
1054 case ScrollElasticityAutomatic: {
1055 Scrollbar* hScroller = m_scrollableArea->horizontalScrollbar();
1056 Scrollbar* vScroller = m_scrollableArea->verticalScrollbar();
1057 return (((hScroller && hScroller->enabled()) || (!vScroller || !vScroller->enabled())));
1059 case ScrollElasticityNone:
1061 case ScrollElasticityAllowed:
1065 ASSERT_NOT_REACHED();
1069 IntSize ScrollAnimatorMac::stretchAmount()
1071 return m_scrollableArea->overhangAmount();
1074 bool ScrollAnimatorMac::pinnedInDirection(const FloatSize& direction)
1076 return pinnedInDirection(direction.width(), direction.height());
1079 bool ScrollAnimatorMac::canScrollHorizontally()
1081 Scrollbar* scrollbar = m_scrollableArea->horizontalScrollbar();
1084 return scrollbar->enabled();
1087 bool ScrollAnimatorMac::canScrollVertically()
1089 Scrollbar* scrollbar = m_scrollableArea->verticalScrollbar();
1092 return scrollbar->enabled();
1095 bool ScrollAnimatorMac::shouldRubberBandInDirection(ScrollDirection direction)
1097 return m_scrollableArea->shouldRubberBandInDirection(direction);
1100 IntPoint ScrollAnimatorMac::absoluteScrollPosition()
1102 return m_scrollableArea->visibleContentRect().location() + m_scrollableArea->scrollOrigin();
1105 void ScrollAnimatorMac::immediateScrollByWithoutContentEdgeConstraints(const FloatSize& delta)
1107 m_scrollableArea->setConstrainsScrollingToContentEdge(false);
1108 immediateScrollBy(delta);
1109 m_scrollableArea->setConstrainsScrollingToContentEdge(true);
1112 void ScrollAnimatorMac::immediateScrollBy(const FloatSize& delta)
1114 FloatPoint newPos = adjustScrollPositionIfNecessary(FloatPoint(m_currentPosX, m_currentPosY) + delta);
1115 if (newPos.x() == m_currentPosX && newPos.y() == m_currentPosY)
1118 m_currentPosX = newPos.x();
1119 m_currentPosY = newPos.y();
1120 notifyPositionChanged();
1123 void ScrollAnimatorMac::startSnapRubberbandTimer()
1125 m_snapRubberBandTimer.startRepeating(1.0 / 60.0);
1128 void ScrollAnimatorMac::stopSnapRubberbandTimer()
1130 m_snapRubberBandTimer.stop();
1133 void ScrollAnimatorMac::snapRubberBandTimerFired(Timer<ScrollAnimatorMac>*)
1135 m_scrollElasticityController.snapRubberBandTimerFired();
1139 void ScrollAnimatorMac::setIsActive()
1141 if (!isScrollbarOverlayAPIAvailable())
1144 if (!m_needsScrollerStyleUpdate)
1147 updateScrollerStyle();
1150 void ScrollAnimatorMac::updateScrollerStyle()
1152 if (!isScrollbarOverlayAPIAvailable())
1155 if (!scrollableArea()->isOnActivePage()) {
1156 m_needsScrollerStyleUpdate = true;
1160 ScrollbarThemeMac* macTheme = macScrollbarTheme();
1162 m_needsScrollerStyleUpdate = false;
1166 NSScrollerStyle newStyle = [m_scrollbarPainterController.get() scrollerStyle];
1168 if (Scrollbar* verticalScrollbar = scrollableArea()->verticalScrollbar()) {
1169 verticalScrollbar->invalidate();
1171 ScrollbarPainter oldVerticalPainter = [m_scrollbarPainterController.get() verticalScrollerImp];
1172 ScrollbarPainter newVerticalPainter = [NSClassFromString(@"NSScrollerImp") scrollerImpWithStyle:newStyle
1173 controlSize:(NSControlSize)verticalScrollbar->controlSize()
1175 replacingScrollerImp:oldVerticalPainter];
1176 [m_scrollbarPainterController.get() setVerticalScrollerImp:newVerticalPainter];
1177 macTheme->setNewPainterForScrollbar(verticalScrollbar, newVerticalPainter);
1179 // The different scrollbar styles have different thicknesses, so we must re-set the
1180 // frameRect to the new thickness, and the re-layout below will ensure the position
1181 // and length are properly updated.
1182 int thickness = macTheme->scrollbarThickness(verticalScrollbar->controlSize());
1183 verticalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
1186 if (Scrollbar* horizontalScrollbar = scrollableArea()->horizontalScrollbar()) {
1187 horizontalScrollbar->invalidate();
1189 ScrollbarPainter oldHorizontalPainter = [m_scrollbarPainterController.get() horizontalScrollerImp];
1190 ScrollbarPainter newHorizontalPainter = [NSClassFromString(@"NSScrollerImp") scrollerImpWithStyle:newStyle
1191 controlSize:(NSControlSize)horizontalScrollbar->controlSize()
1193 replacingScrollerImp:oldHorizontalPainter];
1194 [m_scrollbarPainterController.get() setHorizontalScrollerImp:newHorizontalPainter];
1195 macTheme->setNewPainterForScrollbar(horizontalScrollbar, newHorizontalPainter);
1197 // The different scrollbar styles have different thicknesses, so we must re-set the
1198 // frameRect to the new thickness, and the re-layout below will ensure the position
1199 // and length are properly updated.
1200 int thickness = macTheme->scrollbarThickness(horizontalScrollbar->controlSize());
1201 horizontalScrollbar->setFrameRect(IntRect(0, 0, thickness, thickness));
1204 // If m_needsScrollerStyleUpdate is true, then the page is restoring from the page cache, and
1205 // a relayout will happen on its own. Otherwise, we must initiate a re-layout ourselves.
1206 scrollableArea()->scrollbarStyleChanged(newStyle, !m_needsScrollerStyleUpdate);
1208 m_needsScrollerStyleUpdate = false;
1211 void ScrollAnimatorMac::startScrollbarPaintTimer()
1213 m_initialScrollbarPaintTimer.startOneShot(0.1);
1216 bool ScrollAnimatorMac::scrollbarPaintTimerIsActive() const
1218 return m_initialScrollbarPaintTimer.isActive();
1221 void ScrollAnimatorMac::stopScrollbarPaintTimer()
1223 m_initialScrollbarPaintTimer.stop();
1226 void ScrollAnimatorMac::initialScrollbarPaintTimerFired(Timer<ScrollAnimatorMac>*)
1228 if (isScrollbarOverlayAPIAvailable()) {
1229 // To force the scrollbars to flash, we have to call hide first. Otherwise, the ScrollbarPainterController
1230 // might think that the scrollbars are already showing and bail early.
1231 [m_scrollbarPainterController.get() hideOverlayScrollers];
1232 [m_scrollbarPainterController.get() flashScrollers];
1236 void ScrollAnimatorMac::sendContentAreaScrolledSoon()
1238 if (!m_sendContentAreaScrolledTimer.isActive())
1239 m_sendContentAreaScrolledTimer.startOneShot(0);
1242 void ScrollAnimatorMac::sendContentAreaScrolledTimerFired(Timer<ScrollAnimatorMac>*)
1244 [m_scrollbarPainterController.get() contentAreaScrolled];
1247 void ScrollAnimatorMac::setVisibleScrollerThumbRect(const IntRect& scrollerThumb)
1249 IntRect rectInViewCoordinates = scrollerThumb;
1250 if (Scrollbar* verticalScrollbar = m_scrollableArea->verticalScrollbar())
1251 rectInViewCoordinates = verticalScrollbar->convertToContainingView(scrollerThumb);
1253 if (rectInViewCoordinates == m_visibleScrollerThumbRect)
1256 m_scrollableArea->setVisibleScrollerThumbRect(rectInViewCoordinates);
1257 m_visibleScrollerThumbRect = rectInViewCoordinates;
1260 } // namespace WebCore
1262 #endif // ENABLE(SMOOTH_SCROLLING)