| /* |
| * Copyright (C) 2010, 2011 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/scroll/scroll_animator_mac.h" |
| |
| #import <AppKit/AppKit.h> |
| |
| #include <memory> |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/renderer/core/scroll/ns_scroller_imp_details.h" |
| #include "third_party/blink/renderer/core/scroll/scrollable_area.h" |
| #include "third_party/blink/renderer/core/scroll/scrollbar_theme.h" |
| #include "third_party/blink/renderer/core/scroll/scrollbar_theme_mac.h" |
| #include "third_party/blink/renderer/platform/animation/timing_function.h" |
| #include "third_party/blink/renderer/platform/geometry/float_rect.h" |
| #include "third_party/blink/renderer/platform/geometry/int_rect.h" |
| #include "third_party/blink/renderer/platform/mac/block_exceptions.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h" |
| #include "third_party/blink/renderer/platform/timer.h" |
| #include "third_party/blink/renderer/platform/wtf/math_extras.h" |
| |
| namespace { |
| |
| bool SupportsUIStateTransitionProgress() { |
| // FIXME: This is temporary until all platforms that support ScrollbarPainter |
| // support this part of the API. |
| static bool global_supports_ui_state_transition_progress = |
| [NSClassFromString(@"NSScrollerImp") |
| instancesRespondToSelector:@selector(mouseEnteredScroller)]; |
| return global_supports_ui_state_transition_progress; |
| } |
| |
| bool SupportsExpansionTransitionProgress() { |
| static bool global_supports_expansion_transition_progress = |
| [NSClassFromString(@"NSScrollerImp") |
| instancesRespondToSelector:@selector(expansionTransitionProgress)]; |
| return global_supports_expansion_transition_progress; |
| } |
| |
| bool SupportsContentAreaScrolledInDirection() { |
| static bool global_supports_content_area_scrolled_in_direction = |
| [NSClassFromString(@"NSScrollerImpPair") |
| instancesRespondToSelector:@selector |
| (contentAreaScrolledInDirection:)]; |
| return global_supports_content_area_scrolled_in_direction; |
| } |
| |
| blink::ScrollbarThemeMac* MacOverlayScrollbarTheme( |
| blink::ScrollbarTheme& scrollbar_theme) { |
| return !scrollbar_theme.IsMockTheme() |
| ? static_cast<blink::ScrollbarThemeMac*>(&scrollbar_theme) |
| : nil; |
| } |
| |
| ScrollbarPainter ScrollbarPainterForScrollbar(blink::Scrollbar& scrollbar) { |
| if (blink::ScrollbarThemeMac* scrollbar_theme = |
| MacOverlayScrollbarTheme(scrollbar.GetTheme())) |
| return scrollbar_theme->PainterForScrollbar(scrollbar); |
| |
| return nil; |
| } |
| |
| } // namespace |
| |
| @interface NSObject (ScrollAnimationHelperDetails) |
| - (id)initWithDelegate:(id)delegate; |
| - (void)_stopRun; |
| - (BOOL)_isAnimating; |
| - (NSPoint)targetOrigin; |
| - (CGFloat)_progress; |
| @end |
| |
| @interface BlinkScrollAnimationHelperDelegate : NSObject { |
| blink::ScrollAnimatorMac* _animator; |
| } |
| - (id)initWithScrollAnimator:(blink::ScrollAnimatorMac*)scrollAnimator; |
| @end |
| |
| static NSSize abs(NSSize size) { |
| NSSize finalSize = size; |
| if (finalSize.width < 0) |
| finalSize.width = -finalSize.width; |
| if (finalSize.height < 0) |
| finalSize.height = -finalSize.height; |
| return finalSize; |
| } |
| |
| @implementation BlinkScrollAnimationHelperDelegate |
| |
| - (id)initWithScrollAnimator:(blink::ScrollAnimatorMac*)scrollAnimator { |
| self = [super init]; |
| if (!self) |
| return nil; |
| |
| _animator = scrollAnimator; |
| return self; |
| } |
| |
| - (void)invalidate { |
| _animator = 0; |
| } |
| |
| - (NSRect)bounds { |
| if (!_animator) |
| return NSZeroRect; |
| |
| blink::ScrollOffset currentOffset = _animator->CurrentOffset(); |
| return NSMakeRect(currentOffset.Width(), currentOffset.Height(), 0, 0); |
| } |
| |
| - (void)_immediateScrollToPoint:(NSPoint)newPosition { |
| if (!_animator) |
| return; |
| _animator->ImmediateScrollToOffsetForScrollAnimation( |
| blink::ToScrollOffset(newPosition)); |
| } |
| |
| - (NSPoint)_pixelAlignProposedScrollPosition:(NSPoint)newOrigin { |
| return newOrigin; |
| } |
| |
| - (NSSize)convertSizeToBase:(NSSize)size { |
| return abs(size); |
| } |
| |
| - (NSSize)convertSizeFromBase:(NSSize)size { |
| return abs(size); |
| } |
| |
| - (NSSize)convertSizeToBacking:(NSSize)size { |
| return abs(size); |
| } |
| |
| - (NSSize)convertSizeFromBacking:(NSSize)size { |
| return abs(size); |
| } |
| |
| - (id)superview { |
| return nil; |
| } |
| |
| - (id)documentView { |
| return nil; |
| } |
| |
| - (id)window { |
| return nil; |
| } |
| |
| - (void)_recursiveRecomputeToolTips { |
| } |
| |
| @end |
| |
| @interface BlinkScrollbarPainterControllerDelegate : NSObject { |
| blink::ScrollableArea* _scrollableArea; |
| } |
| - (id)initWithScrollableArea:(blink::ScrollableArea*)scrollableArea; |
| @end |
| |
| @implementation BlinkScrollbarPainterControllerDelegate |
| |
| - (id)initWithScrollableArea:(blink::ScrollableArea*)scrollableArea { |
| self = [super init]; |
| if (!self) |
| return nil; |
| |
| _scrollableArea = scrollableArea; |
| return self; |
| } |
| |
| - (void)invalidate { |
| _scrollableArea = 0; |
| } |
| |
| - (NSRect)contentAreaRectForScrollerImpPair:(id)scrollerImpPair { |
| if (!_scrollableArea) |
| return NSZeroRect; |
| |
| blink::IntSize contentsSize = _scrollableArea->ContentsSize(); |
| return NSMakeRect(0, 0, contentsSize.Width(), contentsSize.Height()); |
| } |
| |
| - (BOOL)inLiveResizeForScrollerImpPair:(id)scrollerImpPair { |
| return NO; |
| } |
| |
| - (NSPoint)mouseLocationInContentAreaForScrollerImpPair:(id)scrollerImpPair { |
| if (!_scrollableArea) |
| return NSZeroPoint; |
| |
| return _scrollableArea->LastKnownMousePosition(); |
| } |
| |
| - (NSPoint)scrollerImpPair:(id)scrollerImpPair |
| convertContentPoint:(NSPoint)pointInContentArea |
| toScrollerImp:(id)scrollerImp { |
| if (!_scrollableArea || !scrollerImp) |
| return NSZeroPoint; |
| |
| blink::Scrollbar* scrollbar = nil; |
| if ([scrollerImp isHorizontal]) |
| scrollbar = _scrollableArea->HorizontalScrollbar(); |
| else |
| scrollbar = _scrollableArea->VerticalScrollbar(); |
| |
| // It is possible to have a null scrollbar here since it is possible for this |
| // delegate |
| // method to be called between the moment when a scrollbar has been set to 0 |
| // and the |
| // moment when its destructor has been called. We should probably de-couple |
| // some |
| // of the clean-up work in ScrollbarThemeMac::unregisterScrollbar() to avoid |
| // this |
| // issue. |
| if (!scrollbar) |
| return NSZeroPoint; |
| |
| DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*scrollbar)); |
| |
| return scrollbar->ConvertFromContainingEmbeddedContentView( |
| blink::IntPoint(pointInContentArea)); |
| } |
| |
| - (void)scrollerImpPair:(id)scrollerImpPair |
| setContentAreaNeedsDisplayInRect:(NSRect)rect { |
| if (!_scrollableArea) |
| return; |
| |
| if (!_scrollableArea->ScrollbarsCanBeActive()) |
| return; |
| |
| _scrollableArea->GetScrollAnimator().ContentAreaWillPaint(); |
| } |
| |
| - (void)scrollerImpPair:(id)scrollerImpPair |
| updateScrollerStyleForNewRecommendedScrollerStyle: |
| (NSScrollerStyle)newRecommendedScrollerStyle { |
| // Chrome has a single process mode which is used for testing on Mac. In that |
| // mode, WebKit runs on a thread in the |
| // browser process. This notification is called by the OS on the main thread |
| // in the browser process, and not on the |
| // the WebKit thread. Better to not update the style than crash. |
| // http://crbug.com/126514 |
| if (!IsMainThread()) |
| return; |
| |
| if (!_scrollableArea) |
| return; |
| |
| [scrollerImpPair setScrollerStyle:newRecommendedScrollerStyle]; |
| |
| static_cast<blink::ScrollAnimatorMac&>(_scrollableArea->GetScrollAnimator()) |
| .UpdateScrollerStyle(); |
| } |
| |
| @end |
| |
| enum FeatureToAnimate { |
| ThumbAlpha, |
| TrackAlpha, |
| UIStateTransition, |
| ExpansionTransition |
| }; |
| |
| @class BlinkScrollbarPartAnimation; |
| |
| namespace blink { |
| |
| // This class is used to drive the animation timer for |
| // BlinkScrollbarPartAnimation |
| // objects. This is used instead of NSAnimation because CoreAnimation |
| // establishes connections to the WindowServer, which should not be done in a |
| // sandboxed renderer process. |
| class BlinkScrollbarPartAnimationTimer { |
| public: |
| BlinkScrollbarPartAnimationTimer(BlinkScrollbarPartAnimation* animation, |
| CFTimeInterval duration) |
| : timer_(ThreadScheduler::Current()->CompositorTaskRunner(), |
| this, |
| &BlinkScrollbarPartAnimationTimer::TimerFired), |
| start_time_(0.0), |
| duration_(duration), |
| animation_(animation), |
| timing_function_(CubicBezierTimingFunction::Preset( |
| CubicBezierTimingFunction::EaseType::EASE_IN_OUT)) {} |
| |
| ~BlinkScrollbarPartAnimationTimer() {} |
| |
| void Start() { |
| start_time_ = WTF::CurrentTime(); |
| // Set the framerate of the animation. NSAnimation uses a default |
| // framerate of 60 Hz, so use that here. |
| timer_.StartRepeating(TimeDelta::FromSecondsD(1.0 / 60.0), FROM_HERE); |
| } |
| |
| void Stop() { timer_.Stop(); } |
| |
| void SetDuration(CFTimeInterval duration) { duration_ = duration; } |
| |
| private: |
| void TimerFired(TimerBase*) { |
| double current_time = WTF::CurrentTime(); |
| double delta = current_time - start_time_; |
| |
| if (delta >= duration_) |
| timer_.Stop(); |
| |
| double fraction = delta / duration_; |
| fraction = clampTo(fraction, 0.0, 1.0); |
| double progress = timing_function_->Evaluate(fraction, 0.001); |
| [animation_ setCurrentProgress:progress]; |
| } |
| |
| TaskRunnerTimer<BlinkScrollbarPartAnimationTimer> timer_; |
| double start_time_; // In seconds. |
| double duration_; // In seconds. |
| BlinkScrollbarPartAnimation* animation_; // Weak, owns this. |
| scoped_refptr<CubicBezierTimingFunction> timing_function_; |
| }; |
| |
| } // namespace blink |
| |
| @interface BlinkScrollbarPartAnimation : NSObject { |
| blink::Scrollbar* _scrollbar; |
| std::unique_ptr<blink::BlinkScrollbarPartAnimationTimer> _timer; |
| RetainPtr<ScrollbarPainter> _scrollbarPainter; |
| FeatureToAnimate _featureToAnimate; |
| CGFloat _startValue; |
| CGFloat _endValue; |
| } |
| - (id)initWithScrollbar:(blink::Scrollbar*)scrollbar |
| featureToAnimate:(FeatureToAnimate)featureToAnimate |
| animateFrom:(CGFloat)startValue |
| animateTo:(CGFloat)endValue |
| duration:(NSTimeInterval)duration; |
| @end |
| |
| @implementation BlinkScrollbarPartAnimation |
| |
| - (id)initWithScrollbar:(blink::Scrollbar*)scrollbar |
| featureToAnimate:(FeatureToAnimate)featureToAnimate |
| animateFrom:(CGFloat)startValue |
| animateTo:(CGFloat)endValue |
| duration:(NSTimeInterval)duration { |
| self = [super init]; |
| if (!self) |
| return nil; |
| |
| _timer = |
| std::make_unique<blink::BlinkScrollbarPartAnimationTimer>(self, duration); |
| _scrollbar = scrollbar; |
| _featureToAnimate = featureToAnimate; |
| _startValue = startValue; |
| _endValue = endValue; |
| |
| return self; |
| } |
| |
| - (void)startAnimation { |
| DCHECK(_scrollbar); |
| |
| _scrollbarPainter = ScrollbarPainterForScrollbar(*_scrollbar); |
| _timer->Start(); |
| } |
| |
| - (void)stopAnimation { |
| _timer->Stop(); |
| } |
| |
| - (void)setDuration:(CFTimeInterval)duration { |
| _timer->SetDuration(duration); |
| } |
| |
| - (void)setStartValue:(CGFloat)startValue { |
| _startValue = startValue; |
| } |
| |
| - (void)setEndValue:(CGFloat)endValue { |
| _endValue = endValue; |
| } |
| |
| - (void)setCurrentProgress:(NSAnimationProgress)progress { |
| DCHECK(_scrollbar); |
| |
| CGFloat currentValue; |
| if (_startValue > _endValue) |
| currentValue = 1 - progress; |
| else |
| currentValue = progress; |
| |
| blink::ScrollbarPart invalidParts = blink::kNoPart; |
| switch (_featureToAnimate) { |
| case ThumbAlpha: |
| [_scrollbarPainter.Get() setKnobAlpha:currentValue]; |
| break; |
| case TrackAlpha: |
| [_scrollbarPainter.Get() setTrackAlpha:currentValue]; |
| invalidParts = static_cast<blink::ScrollbarPart>(~blink::kThumbPart); |
| break; |
| case UIStateTransition: |
| [_scrollbarPainter.Get() setUiStateTransitionProgress:currentValue]; |
| invalidParts = blink::kAllParts; |
| break; |
| case ExpansionTransition: |
| [_scrollbarPainter.Get() setExpansionTransitionProgress:currentValue]; |
| invalidParts = blink::kThumbPart; |
| break; |
| } |
| |
| _scrollbar->SetNeedsPaintInvalidation(invalidParts); |
| } |
| |
| - (void)invalidate { |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| [self stopAnimation]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| _scrollbar = 0; |
| } |
| |
| @end |
| |
| @interface BlinkScrollbarPainterDelegate : NSObject<NSAnimationDelegate> { |
| blink::Scrollbar* _scrollbar; |
| |
| RetainPtr<BlinkScrollbarPartAnimation> _knobAlphaAnimation; |
| RetainPtr<BlinkScrollbarPartAnimation> _trackAlphaAnimation; |
| RetainPtr<BlinkScrollbarPartAnimation> _uiStateTransitionAnimation; |
| RetainPtr<BlinkScrollbarPartAnimation> _expansionTransitionAnimation; |
| BOOL _hasExpandedSinceInvisible; |
| } |
| - (id)initWithScrollbar:(blink::Scrollbar*)scrollbar; |
| - (void)updateVisibilityImmediately:(bool)show; |
| - (void)cancelAnimations; |
| @end |
| |
| @implementation BlinkScrollbarPainterDelegate |
| |
| - (id)initWithScrollbar:(blink::Scrollbar*)scrollbar { |
| self = [super init]; |
| if (!self) |
| return nil; |
| |
| _scrollbar = scrollbar; |
| return self; |
| } |
| |
| - (void)updateVisibilityImmediately:(bool)show { |
| [self cancelAnimations]; |
| [ScrollbarPainterForScrollbar(*_scrollbar) setKnobAlpha:(show ? 1.0 : 0.0)]; |
| } |
| |
| - (void)cancelAnimations { |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| [_knobAlphaAnimation.Get() stopAnimation]; |
| [_trackAlphaAnimation.Get() stopAnimation]; |
| [_uiStateTransitionAnimation.Get() stopAnimation]; |
| [_expansionTransitionAnimation.Get() stopAnimation]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| - (blink::ScrollAnimatorMac&)scrollAnimator { |
| return static_cast<blink::ScrollAnimatorMac&>( |
| _scrollbar->GetScrollableArea()->GetScrollAnimator()); |
| } |
| |
| - (NSRect)convertRectToBacking:(NSRect)aRect { |
| return aRect; |
| } |
| |
| - (NSRect)convertRectFromBacking:(NSRect)aRect { |
| return aRect; |
| } |
| |
| - (NSPoint)mouseLocationInScrollerForScrollerImp:(id)scrollerImp { |
| if (!_scrollbar) |
| return NSZeroPoint; |
| |
| DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar)); |
| |
| return _scrollbar->ConvertFromContainingEmbeddedContentView( |
| _scrollbar->GetScrollableArea()->LastKnownMousePosition()); |
| } |
| |
| - (void)setUpAlphaAnimation: |
| (RetainPtr<BlinkScrollbarPartAnimation>&)scrollbarPartAnimation |
| scrollerPainter:(ScrollbarPainter)scrollerPainter |
| part:(blink::ScrollbarPart)part |
| animateAlphaTo:(CGFloat)newAlpha |
| duration:(NSTimeInterval)duration { |
| // If the user has scrolled the page, then the scrollbars must be animated |
| // here. |
| // This overrides the early returns. |
| bool mustAnimate = [self scrollAnimator].HaveScrolledSincePageLoad(); |
| |
| if ([self scrollAnimator].ScrollbarPaintTimerIsActive() && !mustAnimate) |
| return; |
| |
| if (_scrollbar->GetScrollableArea()->ShouldSuspendScrollAnimations() && |
| !mustAnimate) { |
| [self scrollAnimator].StartScrollbarPaintTimer(); |
| return; |
| } |
| |
| // At this point, we are definitely going to animate now, so stop the timer. |
| [self scrollAnimator].StopScrollbarPaintTimer(); |
| |
| // If we are currently animating, stop |
| if (scrollbarPartAnimation) { |
| [scrollbarPartAnimation.Get() stopAnimation]; |
| scrollbarPartAnimation = nullptr; |
| } |
| |
| scrollbarPartAnimation.AdoptNS([[BlinkScrollbarPartAnimation alloc] |
| initWithScrollbar:_scrollbar |
| featureToAnimate:part == blink::kThumbPart ? ThumbAlpha : TrackAlpha |
| animateFrom:part == blink::kThumbPart ? [scrollerPainter knobAlpha] |
| : [scrollerPainter trackAlpha] |
| animateTo:newAlpha |
| duration:duration]); |
| [scrollbarPartAnimation.Get() startAnimation]; |
| } |
| |
| - (void)scrollerImp:(id)scrollerImp |
| animateKnobAlphaTo:(CGFloat)newKnobAlpha |
| duration:(NSTimeInterval)duration { |
| if (!_scrollbar) |
| return; |
| |
| DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar)); |
| |
| ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp; |
| [self setUpAlphaAnimation:_knobAlphaAnimation |
| scrollerPainter:scrollerPainter |
| part:blink::kThumbPart |
| animateAlphaTo:newKnobAlpha |
| duration:duration]; |
| } |
| |
| - (void)scrollerImp:(id)scrollerImp |
| animateTrackAlphaTo:(CGFloat)newTrackAlpha |
| duration:(NSTimeInterval)duration { |
| if (!_scrollbar) |
| return; |
| |
| DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar)); |
| |
| ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp; |
| [self setUpAlphaAnimation:_trackAlphaAnimation |
| scrollerPainter:scrollerPainter |
| part:blink::kBackTrackPart |
| animateAlphaTo:newTrackAlpha |
| duration:duration]; |
| } |
| |
| - (void)scrollerImp:(id)scrollerImp |
| animateUIStateTransitionWithDuration:(NSTimeInterval)duration { |
| if (!_scrollbar) |
| return; |
| |
| if (!SupportsUIStateTransitionProgress()) |
| return; |
| |
| DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar)); |
| |
| ScrollbarPainter scrollbarPainter = (ScrollbarPainter)scrollerImp; |
| |
| // UIStateTransition always animates to 1. In case an animation is in progress |
| // this avoids a hard transition. |
| [scrollbarPainter |
| setUiStateTransitionProgress:1 - [scrollerImp uiStateTransitionProgress]]; |
| |
| if (!_uiStateTransitionAnimation) |
| _uiStateTransitionAnimation.AdoptNS([[BlinkScrollbarPartAnimation alloc] |
| initWithScrollbar:_scrollbar |
| featureToAnimate:UIStateTransition |
| animateFrom:[scrollbarPainter uiStateTransitionProgress] |
| animateTo:1.0 |
| duration:duration]); |
| else { |
| // If we don't need to initialize the animation, just reset the values in |
| // case they have changed. |
| [_uiStateTransitionAnimation.Get() |
| setStartValue:[scrollbarPainter uiStateTransitionProgress]]; |
| [_uiStateTransitionAnimation.Get() setEndValue:1.0]; |
| [_uiStateTransitionAnimation.Get() setDuration:duration]; |
| } |
| [_uiStateTransitionAnimation.Get() startAnimation]; |
| } |
| |
| - (void)scrollerImp:(id)scrollerImp |
| animateExpansionTransitionWithDuration:(NSTimeInterval)duration { |
| if (!_scrollbar) |
| return; |
| |
| if (!SupportsExpansionTransitionProgress()) |
| return; |
| |
| DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar)); |
| |
| ScrollbarPainter scrollbarPainter = (ScrollbarPainter)scrollerImp; |
| |
| // ExpansionTransition always animates to 1. In case an animation is in |
| // progress this avoids a hard transition. |
| [scrollbarPainter |
| setExpansionTransitionProgress:1 - |
| [scrollerImp expansionTransitionProgress]]; |
| |
| if (!_expansionTransitionAnimation) { |
| _expansionTransitionAnimation.AdoptNS([[BlinkScrollbarPartAnimation alloc] |
| initWithScrollbar:_scrollbar |
| featureToAnimate:ExpansionTransition |
| animateFrom:[scrollbarPainter expansionTransitionProgress] |
| animateTo:1.0 |
| duration:duration]); |
| } else { |
| // If we don't need to initialize the animation, just reset the values in |
| // case they have changed. |
| [_expansionTransitionAnimation.Get() |
| setStartValue:[scrollbarPainter uiStateTransitionProgress]]; |
| [_expansionTransitionAnimation.Get() setEndValue:1.0]; |
| [_expansionTransitionAnimation.Get() setDuration:duration]; |
| } |
| [_expansionTransitionAnimation.Get() startAnimation]; |
| } |
| |
| - (void)scrollerImp:(id)scrollerImp |
| overlayScrollerStateChangedTo:(NSUInteger)newOverlayScrollerState { |
| // The names of these states are based on their observed behavior, and are not |
| // based on documentation. |
| enum { |
| NSScrollerStateInvisible = 0, |
| NSScrollerStateKnob = 1, |
| NSScrollerStateExpanded = 2 |
| }; |
| // We do not receive notifications about the thumb un-expanding when the |
| // scrollbar fades away. Ensure |
| // that we re-paint the thumb the next time that we transition away from being |
| // invisible, so that |
| // the thumb doesn't stick in an expanded state. |
| if (newOverlayScrollerState == NSScrollerStateExpanded) { |
| _hasExpandedSinceInvisible = YES; |
| } else if (newOverlayScrollerState != NSScrollerStateInvisible && |
| _hasExpandedSinceInvisible) { |
| _scrollbar->SetNeedsPaintInvalidation(blink::kThumbPart); |
| _hasExpandedSinceInvisible = NO; |
| } |
| } |
| |
| - (void)invalidate { |
| _scrollbar = 0; |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| [_knobAlphaAnimation.Get() invalidate]; |
| [_trackAlphaAnimation.Get() invalidate]; |
| [_uiStateTransitionAnimation.Get() invalidate]; |
| [_expansionTransitionAnimation.Get() invalidate]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| @end |
| |
| namespace blink { |
| |
| ScrollAnimatorBase* ScrollAnimatorBase::Create( |
| blink::ScrollableArea* scrollable_area) { |
| return new ScrollAnimatorMac(scrollable_area); |
| } |
| |
| ScrollAnimatorMac::ScrollAnimatorMac(blink::ScrollableArea* scrollable_area) |
| : ScrollAnimatorBase(scrollable_area), |
| task_runner_(ThreadScheduler::Current()->CompositorTaskRunner()), |
| have_scrolled_since_page_load_(false), |
| needs_scroller_style_update_(false) { |
| scroll_animation_helper_delegate_.AdoptNS( |
| [[BlinkScrollAnimationHelperDelegate alloc] initWithScrollAnimator:this]); |
| scroll_animation_helper_.AdoptNS( |
| [[NSClassFromString(@"NSScrollAnimationHelper") alloc] |
| initWithDelegate:scroll_animation_helper_delegate_.Get()]); |
| |
| scrollbar_painter_controller_delegate_.AdoptNS( |
| [[BlinkScrollbarPainterControllerDelegate alloc] |
| initWithScrollableArea:scrollable_area]); |
| scrollbar_painter_controller_ = |
| [[[NSClassFromString(@"NSScrollerImpPair") alloc] init] autorelease]; |
| [scrollbar_painter_controller_.Get() |
| performSelector:@selector(setDelegate:) |
| withObject:scrollbar_painter_controller_delegate_.Get()]; |
| [scrollbar_painter_controller_.Get() |
| setScrollerStyle:ScrollbarThemeMac::RecommendedScrollerStyle()]; |
| } |
| |
| ScrollAnimatorMac::~ScrollAnimatorMac() {} |
| |
| void ScrollAnimatorMac::Dispose() { |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| ScrollbarPainter horizontal_scrollbar_painter = |
| [scrollbar_painter_controller_.Get() horizontalScrollerImp]; |
| [horizontal_scrollbar_painter setDelegate:nil]; |
| |
| ScrollbarPainter vertical_scrollbar_painter = |
| [scrollbar_painter_controller_.Get() verticalScrollerImp]; |
| [vertical_scrollbar_painter setDelegate:nil]; |
| |
| [scrollbar_painter_controller_delegate_.Get() invalidate]; |
| [scrollbar_painter_controller_.Get() setDelegate:nil]; |
| [horizontal_scrollbar_painter_delegate_.Get() invalidate]; |
| [vertical_scrollbar_painter_delegate_.Get() invalidate]; |
| [scroll_animation_helper_delegate_.Get() invalidate]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| |
| initial_scrollbar_paint_task_handle_.Cancel(); |
| send_content_area_scrolled_task_handle_.Cancel(); |
| } |
| |
| ScrollResult ScrollAnimatorMac::UserScroll(ScrollGranularity granularity, |
| const ScrollOffset& delta) { |
| have_scrolled_since_page_load_ = true; |
| |
| if (!scrollable_area_->ScrollAnimatorEnabled()) |
| return ScrollAnimatorBase::UserScroll(granularity, delta); |
| |
| if (granularity == kScrollByPixel || granularity == kScrollByPrecisePixel) |
| return ScrollAnimatorBase::UserScroll(granularity, delta); |
| |
| ScrollOffset consumed_delta = ComputeDeltaToConsume(delta); |
| ScrollOffset new_offset = current_offset_ + consumed_delta; |
| if (current_offset_ == new_offset) |
| return ScrollResult(); |
| |
| // Prevent clobbering an existing animation on an unscrolled axis. |
| if ([scroll_animation_helper_.Get() _isAnimating]) { |
| NSPoint target_origin = [scroll_animation_helper_.Get() targetOrigin]; |
| if (!delta.Width()) |
| new_offset.SetWidth(target_origin.x); |
| if (!delta.Height()) |
| new_offset.SetHeight(target_origin.y); |
| } |
| |
| NSPoint new_point = NSMakePoint(new_offset.Width(), new_offset.Height()); |
| [scroll_animation_helper_.Get() scrollToPoint:new_point]; |
| |
| // TODO(bokan): This has different semantics on ScrollResult than |
| // ScrollAnimator, |
| // which only returns unused delta if there's no animation and we don't start |
| // one. |
| return ScrollResult(consumed_delta.Width(), consumed_delta.Height(), |
| delta.Width() - consumed_delta.Width(), |
| delta.Height() - consumed_delta.Height()); |
| } |
| |
| void ScrollAnimatorMac::ScrollToOffsetWithoutAnimation( |
| const ScrollOffset& offset) { |
| [scroll_animation_helper_.Get() _stopRun]; |
| ImmediateScrollTo(offset); |
| } |
| |
| ScrollOffset ScrollAnimatorMac::AdjustScrollOffsetIfNecessary( |
| const ScrollOffset& offset) const { |
| ScrollOffset min_offset = scrollable_area_->MinimumScrollOffset(); |
| ScrollOffset max_offset = scrollable_area_->MaximumScrollOffset(); |
| |
| float new_x = clampTo<float, float>(offset.Width(), min_offset.Width(), |
| max_offset.Width()); |
| float new_y = clampTo<float, float>(offset.Height(), min_offset.Height(), |
| max_offset.Height()); |
| |
| return ScrollOffset(new_x, new_y); |
| } |
| |
| void ScrollAnimatorMac::ImmediateScrollTo(const ScrollOffset& new_offset) { |
| ScrollOffset adjusted_offset = AdjustScrollOffsetIfNecessary(new_offset); |
| |
| bool offset_changed = adjusted_offset != current_offset_; |
| if (!offset_changed) |
| return; |
| |
| ScrollOffset delta = adjusted_offset - current_offset_; |
| |
| current_offset_ = adjusted_offset; |
| NotifyContentAreaScrolled(delta, kUserScroll); |
| NotifyOffsetChanged(); |
| } |
| |
| void ScrollAnimatorMac::ImmediateScrollToOffsetForScrollAnimation( |
| const ScrollOffset& new_offset) { |
| DCHECK(scroll_animation_helper_); |
| ImmediateScrollTo(new_offset); |
| } |
| |
| void ScrollAnimatorMac::ContentAreaWillPaint() const { |
| if (!GetScrollableArea()->ScrollbarsCanBeActive()) |
| return; |
| [scrollbar_painter_controller_.Get() contentAreaWillDraw]; |
| } |
| |
| void ScrollAnimatorMac::MouseEnteredContentArea() const { |
| if (!GetScrollableArea()->ScrollbarsCanBeActive()) |
| return; |
| [scrollbar_painter_controller_.Get() mouseEnteredContentArea]; |
| } |
| |
| void ScrollAnimatorMac::MouseExitedContentArea() const { |
| if (!GetScrollableArea()->ScrollbarsCanBeActive()) |
| return; |
| [scrollbar_painter_controller_.Get() mouseExitedContentArea]; |
| } |
| |
| void ScrollAnimatorMac::MouseMovedInContentArea() const { |
| if (!GetScrollableArea()->ScrollbarsCanBeActive()) |
| return; |
| [scrollbar_painter_controller_.Get() mouseMovedInContentArea]; |
| } |
| |
| void ScrollAnimatorMac::MouseEnteredScrollbar(Scrollbar& scrollbar) const { |
| if (!GetScrollableArea()->ScrollbarsCanBeActive()) |
| return; |
| |
| if (!SupportsUIStateTransitionProgress()) |
| return; |
| if (ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar)) |
| [painter mouseEnteredScroller]; |
| } |
| |
| void ScrollAnimatorMac::MouseExitedScrollbar(Scrollbar& scrollbar) const { |
| if (!GetScrollableArea()->ScrollbarsCanBeActive()) |
| return; |
| |
| if (!SupportsUIStateTransitionProgress()) |
| return; |
| if (ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar)) |
| [painter mouseExitedScroller]; |
| } |
| |
| void ScrollAnimatorMac::ContentsResized() const { |
| if (!GetScrollableArea()->ScrollbarsCanBeActive()) |
| return; |
| [scrollbar_painter_controller_.Get() contentAreaDidResize]; |
| } |
| |
| void ScrollAnimatorMac::ContentAreaDidShow() const { |
| if (!GetScrollableArea()->ScrollbarsCanBeActive()) |
| return; |
| [scrollbar_painter_controller_.Get() windowOrderedIn]; |
| } |
| |
| void ScrollAnimatorMac::ContentAreaDidHide() const { |
| if (!GetScrollableArea()->ScrollbarsCanBeActive()) |
| return; |
| [scrollbar_painter_controller_.Get() windowOrderedOut]; |
| } |
| |
| void ScrollAnimatorMac::FinishCurrentScrollAnimations() { |
| [scrollbar_painter_controller_.Get() hideOverlayScrollers]; |
| } |
| |
| void ScrollAnimatorMac::DidAddVerticalScrollbar(Scrollbar& scrollbar) { |
| ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar); |
| if (!painter) |
| return; |
| |
| DCHECK(!vertical_scrollbar_painter_delegate_); |
| vertical_scrollbar_painter_delegate_.AdoptNS( |
| [[BlinkScrollbarPainterDelegate alloc] initWithScrollbar:&scrollbar]); |
| |
| [painter setDelegate:vertical_scrollbar_painter_delegate_.Get()]; |
| [scrollbar_painter_controller_.Get() setVerticalScrollerImp:painter]; |
| } |
| |
| void ScrollAnimatorMac::WillRemoveVerticalScrollbar(Scrollbar& scrollbar) { |
| ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar); |
| DCHECK_EQ([scrollbar_painter_controller_.Get() verticalScrollerImp], painter); |
| |
| if (!painter) |
| DCHECK(!vertical_scrollbar_painter_delegate_); |
| |
| [painter setDelegate:nil]; |
| [vertical_scrollbar_painter_delegate_.Get() invalidate]; |
| vertical_scrollbar_painter_delegate_ = nullptr; |
| [scrollbar_painter_controller_.Get() setVerticalScrollerImp:nil]; |
| } |
| |
| void ScrollAnimatorMac::DidAddHorizontalScrollbar(Scrollbar& scrollbar) { |
| ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar); |
| if (!painter) |
| return; |
| |
| DCHECK(!horizontal_scrollbar_painter_delegate_); |
| horizontal_scrollbar_painter_delegate_.AdoptNS( |
| [[BlinkScrollbarPainterDelegate alloc] initWithScrollbar:&scrollbar]); |
| |
| [painter setDelegate:horizontal_scrollbar_painter_delegate_.Get()]; |
| [scrollbar_painter_controller_.Get() setHorizontalScrollerImp:painter]; |
| } |
| |
| void ScrollAnimatorMac::WillRemoveHorizontalScrollbar(Scrollbar& scrollbar) { |
| ScrollbarPainter painter = ScrollbarPainterForScrollbar(scrollbar); |
| DCHECK_EQ([scrollbar_painter_controller_.Get() horizontalScrollerImp], |
| painter); |
| |
| if (!painter) |
| DCHECK(!horizontal_scrollbar_painter_delegate_); |
| |
| [painter setDelegate:nil]; |
| [horizontal_scrollbar_painter_delegate_.Get() invalidate]; |
| horizontal_scrollbar_painter_delegate_ = nullptr; |
| [scrollbar_painter_controller_.Get() setHorizontalScrollerImp:nil]; |
| } |
| |
| void ScrollAnimatorMac::NotifyContentAreaScrolled(const ScrollOffset& delta, |
| ScrollType scrollType) { |
| // This function is called when a page is going into the page cache, but the |
| // page |
| // isn't really scrolling in that case. We should only pass the message on to |
| // the |
| // ScrollbarPainterController when we're really scrolling on an active page. |
| if (IsExplicitScrollType(scrollType) && |
| GetScrollableArea()->ScrollbarsCanBeActive()) |
| SendContentAreaScrolledSoon(delta); |
| } |
| |
| bool ScrollAnimatorMac::SetScrollbarsVisibleForTesting(bool show) { |
| if (show) |
| [scrollbar_painter_controller_.Get() flashScrollers]; |
| else |
| [scrollbar_painter_controller_.Get() hideOverlayScrollers]; |
| |
| [vertical_scrollbar_painter_delegate_.Get() updateVisibilityImmediately:show]; |
| [horizontal_scrollbar_painter_delegate_.Get() |
| updateVisibilityImmediately:show]; |
| return true; |
| } |
| |
| void ScrollAnimatorMac::CancelAnimation() { |
| [scroll_animation_helper_.Get() _stopRun]; |
| have_scrolled_since_page_load_ = false; |
| } |
| |
| void ScrollAnimatorMac::UpdateScrollerStyle() { |
| if (!GetScrollableArea()->ScrollbarsCanBeActive()) { |
| needs_scroller_style_update_ = true; |
| return; |
| } |
| |
| blink::ScrollbarThemeMac* mac_theme = |
| MacOverlayScrollbarTheme(scrollable_area_->GetPageScrollbarTheme()); |
| if (!mac_theme) { |
| needs_scroller_style_update_ = false; |
| return; |
| } |
| |
| NSScrollerStyle new_style = |
| [scrollbar_painter_controller_.Get() scrollerStyle]; |
| |
| if (Scrollbar* vertical_scrollbar = |
| GetScrollableArea()->VerticalScrollbar()) { |
| vertical_scrollbar->SetNeedsPaintInvalidation(kAllParts); |
| |
| ScrollbarPainter old_vertical_painter = |
| [scrollbar_painter_controller_.Get() verticalScrollerImp]; |
| ScrollbarPainter new_vertical_painter = [NSClassFromString(@"NSScrollerImp") |
| scrollerImpWithStyle:new_style |
| controlSize:(NSControlSize)vertical_scrollbar->GetControlSize() |
| horizontal:NO |
| replacingScrollerImp:old_vertical_painter]; |
| [old_vertical_painter setDelegate:nil]; |
| [new_vertical_painter |
| setDelegate:vertical_scrollbar_painter_delegate_.Get()]; |
| [scrollbar_painter_controller_.Get() |
| setVerticalScrollerImp:new_vertical_painter]; |
| mac_theme->SetNewPainterForScrollbar(*vertical_scrollbar, |
| new_vertical_painter); |
| |
| // The different scrollbar styles have different thicknesses, so we must |
| // re-set the |
| // frameRect to the new thickness, and the re-layout below will ensure the |
| // offset |
| // and length are properly updated. |
| int thickness = |
| mac_theme->ScrollbarThickness(vertical_scrollbar->GetControlSize()); |
| vertical_scrollbar->SetFrameRect(IntRect(0, 0, thickness, thickness)); |
| } |
| |
| if (Scrollbar* horizontal_scrollbar = |
| GetScrollableArea()->HorizontalScrollbar()) { |
| horizontal_scrollbar->SetNeedsPaintInvalidation(kAllParts); |
| |
| ScrollbarPainter old_horizontal_painter = |
| [scrollbar_painter_controller_.Get() horizontalScrollerImp]; |
| ScrollbarPainter new_horizontal_painter = |
| [NSClassFromString(@"NSScrollerImp") |
| scrollerImpWithStyle:new_style |
| controlSize:(NSControlSize) |
| horizontal_scrollbar->GetControlSize() |
| horizontal:YES |
| replacingScrollerImp:old_horizontal_painter]; |
| [old_horizontal_painter setDelegate:nil]; |
| [new_horizontal_painter |
| setDelegate:horizontal_scrollbar_painter_delegate_.Get()]; |
| [scrollbar_painter_controller_.Get() |
| setHorizontalScrollerImp:new_horizontal_painter]; |
| mac_theme->SetNewPainterForScrollbar(*horizontal_scrollbar, |
| new_horizontal_painter); |
| |
| // The different scrollbar styles have different thicknesses, so we must |
| // re-set the |
| // frameRect to the new thickness, and the re-layout below will ensure the |
| // offset |
| // and length are properly updated. |
| int thickness = |
| mac_theme->ScrollbarThickness(horizontal_scrollbar->GetControlSize()); |
| horizontal_scrollbar->SetFrameRect(IntRect(0, 0, thickness, thickness)); |
| } |
| |
| // If m_needsScrollerStyleUpdate is true, then the page is restoring from the |
| // page cache, and |
| // a relayout will happen on its own. Otherwise, we must initiate a re-layout |
| // ourselves. |
| if (!needs_scroller_style_update_) |
| GetScrollableArea()->ScrollbarStyleChanged(); |
| |
| needs_scroller_style_update_ = false; |
| } |
| |
| void ScrollAnimatorMac::StartScrollbarPaintTimer() { |
| // Post a task with 1 ms delay to give a chance to run other immediate tasks |
| // that may cancel this. |
| initial_scrollbar_paint_task_handle_ = PostDelayedCancellableTask( |
| *task_runner_, FROM_HERE, |
| WTF::Bind(&ScrollAnimatorMac::InitialScrollbarPaintTask, |
| WrapWeakPersistent(this)), |
| TimeDelta::FromMilliseconds(1)); |
| } |
| |
| bool ScrollAnimatorMac::ScrollbarPaintTimerIsActive() const { |
| return initial_scrollbar_paint_task_handle_.IsActive(); |
| } |
| |
| void ScrollAnimatorMac::StopScrollbarPaintTimer() { |
| initial_scrollbar_paint_task_handle_.Cancel(); |
| } |
| |
| void ScrollAnimatorMac::InitialScrollbarPaintTask() { |
| // To force the scrollbars to flash, we have to call hide first. Otherwise, |
| // the ScrollbarPainterController |
| // might think that the scrollbars are already showing and bail early. |
| [scrollbar_painter_controller_.Get() hideOverlayScrollers]; |
| [scrollbar_painter_controller_.Get() flashScrollers]; |
| } |
| |
| void ScrollAnimatorMac::SendContentAreaScrolledSoon(const ScrollOffset& delta) { |
| content_area_scrolled_timer_scroll_delta_ = delta; |
| |
| if (send_content_area_scrolled_task_handle_.IsActive()) |
| return; |
| send_content_area_scrolled_task_handle_ = PostCancellableTask( |
| *task_runner_, FROM_HERE, |
| WTF::Bind(&ScrollAnimatorMac::SendContentAreaScrolledTask, |
| WrapWeakPersistent(this))); |
| } |
| |
| void ScrollAnimatorMac::SendContentAreaScrolledTask() { |
| if (SupportsContentAreaScrolledInDirection()) { |
| [scrollbar_painter_controller_.Get() |
| contentAreaScrolledInDirection: |
| NSMakePoint(content_area_scrolled_timer_scroll_delta_.Width(), |
| content_area_scrolled_timer_scroll_delta_.Height())]; |
| content_area_scrolled_timer_scroll_delta_ = ScrollOffset(); |
| } else |
| [scrollbar_painter_controller_.Get() contentAreaScrolled]; |
| } |
| |
| } // namespace blink |