| /* |
| * Copyright (C) 2019 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 "config.h" |
| #include "ScrollerMac.h" |
| |
| #if PLATFORM(MAC) |
| |
| #include "ScrollerPairMac.h" |
| #include <QuartzCore/CALayer.h> |
| #include <WebCore/FloatPoint.h> |
| #include <WebCore/IntRect.h> |
| #include <WebCore/NSScrollerImpDetails.h> |
| #include <WebCore/PlatformWheelEvent.h> |
| #include <pal/spi/mac/NSScrollerImpSPI.h> |
| #include <wtf/BlockObjCExceptions.h> |
| |
| enum class FeatureToAnimate { |
| KnobAlpha, |
| TrackAlpha, |
| UIStateTransition, |
| ExpansionTransition |
| }; |
| |
| @interface WKScrollbarPartAnimation : NSAnimation { |
| WebKit::ScrollerMac* _scroller; |
| FeatureToAnimate _featureToAnimate; |
| CGFloat _startValue; |
| CGFloat _endValue; |
| } |
| - (id)initWithScroller:(WebKit::ScrollerMac*)scroller featureToAnimate:(FeatureToAnimate)featureToAnimate animateFrom:(CGFloat)startValue animateTo:(CGFloat)endValue duration:(NSTimeInterval)duration; |
| @end |
| |
| @implementation WKScrollbarPartAnimation |
| |
| - (id)initWithScroller:(WebKit::ScrollerMac*)scroller featureToAnimate:(FeatureToAnimate)featureToAnimate animateFrom:(CGFloat)startValue animateTo:(CGFloat)endValue duration:(NSTimeInterval)duration |
| { |
| self = [super initWithDuration:duration animationCurve:NSAnimationEaseInOut]; |
| if (!self) |
| return nil; |
| |
| _scroller = scroller; |
| _featureToAnimate = featureToAnimate; |
| _startValue = startValue; |
| _endValue = endValue; |
| |
| [self setAnimationBlockingMode:NSAnimationNonblocking]; |
| |
| return self; |
| } |
| |
| - (void)startAnimation |
| { |
| ASSERT(_scroller); |
| |
| [super startAnimation]; |
| } |
| |
| - (void)setStartValue:(CGFloat)startValue |
| { |
| _startValue = startValue; |
| } |
| |
| - (void)setEndValue:(CGFloat)endValue |
| { |
| _endValue = endValue; |
| } |
| |
| - (void)setCurrentProgress:(NSAnimationProgress)progress |
| { |
| [super setCurrentProgress:progress]; |
| |
| CGFloat currentValue; |
| if (_startValue > _endValue) |
| currentValue = 1 - progress; |
| else |
| currentValue = progress; |
| |
| switch (_featureToAnimate) { |
| case FeatureToAnimate::KnobAlpha: |
| [_scroller->scrollerImp() setKnobAlpha:currentValue]; |
| break; |
| case FeatureToAnimate::TrackAlpha: |
| [_scroller->scrollerImp() setTrackAlpha:currentValue]; |
| break; |
| case FeatureToAnimate::UIStateTransition: |
| [_scroller->scrollerImp() setUiStateTransitionProgress:currentValue]; |
| break; |
| case FeatureToAnimate::ExpansionTransition: |
| [_scroller->scrollerImp() setExpansionTransitionProgress:currentValue]; |
| break; |
| } |
| } |
| |
| - (void)invalidate |
| { |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| [self stopAnimation]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| _scroller = nullptr; |
| } |
| |
| @end |
| |
| @interface WKScrollerImpDelegate : NSObject<NSAnimationDelegate, NSScrollerImpDelegate> { |
| WebKit::ScrollerMac* _scroller; |
| |
| RetainPtr<WKScrollbarPartAnimation> _knobAlphaAnimation; |
| RetainPtr<WKScrollbarPartAnimation> _trackAlphaAnimation; |
| RetainPtr<WKScrollbarPartAnimation> _uiStateTransitionAnimation; |
| RetainPtr<WKScrollbarPartAnimation> _expansionTransitionAnimation; |
| } |
| - (id)initWithScroller:(WebKit::ScrollerMac*)scroller; |
| - (void)cancelAnimations; |
| @end |
| |
| @implementation WKScrollerImpDelegate |
| |
| - (id)initWithScroller:(WebKit::ScrollerMac*)scroller |
| { |
| self = [super init]; |
| if (!self) |
| return nil; |
| |
| _scroller = scroller; |
| return self; |
| } |
| |
| - (void)cancelAnimations |
| { |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| [_knobAlphaAnimation stopAnimation]; |
| [_trackAlphaAnimation stopAnimation]; |
| [_uiStateTransitionAnimation stopAnimation]; |
| [_expansionTransitionAnimation stopAnimation]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| - (NSRect)convertRectToBacking:(NSRect)aRect |
| { |
| return aRect; |
| } |
| |
| - (NSRect)convertRectFromBacking:(NSRect)aRect |
| { |
| return aRect; |
| } |
| |
| - (CALayer *)layer |
| { |
| return nil; |
| } |
| |
| - (NSPoint)mouseLocationInScrollerForScrollerImp:(NSScrollerImp *)scrollerImp |
| { |
| if (!_scroller) |
| return NSZeroPoint; |
| |
| ASSERT_UNUSED(scrollerImp, scrollerImp == _scroller->scrollerImp()); |
| |
| return _scroller->convertFromContent(_scroller->pair().lastKnownMousePosition()); |
| } |
| |
| - (NSRect)convertRectToLayer:(NSRect)rect |
| { |
| return rect; |
| } |
| |
| - (BOOL)shouldUseLayerPerPartForScrollerImp:(NSScrollerImp *)scrollerImp |
| { |
| UNUSED_PARAM(scrollerImp); |
| |
| return true; |
| } |
| |
| #if HAVE(OS_DARK_MODE_SUPPORT) |
| - (NSAppearance *)effectiveAppearanceForScrollerImp:(NSScrollerImp *)scrollerImp |
| { |
| UNUSED_PARAM(scrollerImp); |
| |
| if (!_scroller) |
| return [NSAppearance currentAppearance]; |
| |
| // The base system does not support dark Aqua, so we might get a null result. |
| if (auto *appearance = [NSAppearance appearanceNamed:_scroller->pair().useDarkAppearance() ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua]) |
| return appearance; |
| return [NSAppearance currentAppearance]; |
| } |
| #endif |
| |
| - (void)setUpAlphaAnimation:(RetainPtr<WKScrollbarPartAnimation>&)scrollbarPartAnimation featureToAnimate:(FeatureToAnimate)featureToAnimate animateAlphaTo:(CGFloat)newAlpha duration:(NSTimeInterval)duration |
| { |
| // If we are currently animating, stop |
| if (scrollbarPartAnimation) { |
| [scrollbarPartAnimation stopAnimation]; |
| scrollbarPartAnimation = nil; |
| } |
| |
| scrollbarPartAnimation = adoptNS([[WKScrollbarPartAnimation alloc] initWithScroller:_scroller |
| featureToAnimate:featureToAnimate |
| animateFrom:featureToAnimate == FeatureToAnimate::KnobAlpha ? [_scroller->scrollerImp() knobAlpha] : [_scroller->scrollerImp() trackAlpha] |
| animateTo:newAlpha |
| duration:duration]); |
| [scrollbarPartAnimation startAnimation]; |
| } |
| |
| - (void)scrollerImp:(NSScrollerImp *)scrollerImp animateKnobAlphaTo:(CGFloat)newKnobAlpha duration:(NSTimeInterval)duration |
| { |
| if (!_scroller) |
| return; |
| |
| ASSERT_UNUSED(scrollerImp, scrollerImp == _scroller->scrollerImp()); |
| [self setUpAlphaAnimation:_knobAlphaAnimation featureToAnimate:FeatureToAnimate::KnobAlpha animateAlphaTo:newKnobAlpha duration:duration]; |
| } |
| |
| - (void)scrollerImp:(NSScrollerImp *)scrollerImp animateTrackAlphaTo:(CGFloat)newTrackAlpha duration:(NSTimeInterval)duration |
| { |
| if (!_scroller) |
| return; |
| |
| ASSERT_UNUSED(scrollerImp, scrollerImp == _scroller->scrollerImp()); |
| [self setUpAlphaAnimation:_trackAlphaAnimation featureToAnimate:FeatureToAnimate::TrackAlpha animateAlphaTo:newTrackAlpha duration:duration]; |
| } |
| |
| - (void)scrollerImp:(NSScrollerImp *)scrollerImp animateUIStateTransitionWithDuration:(NSTimeInterval)duration |
| { |
| if (!_scroller) |
| return; |
| |
| ASSERT(scrollerImp == _scroller->scrollerImp()); |
| |
| // UIStateTransition always animates to 1. In case an animation is in progress this avoids a hard transition. |
| [scrollerImp setUiStateTransitionProgress:1 - [scrollerImp uiStateTransitionProgress]]; |
| |
| if (!_uiStateTransitionAnimation) { |
| _uiStateTransitionAnimation = adoptNS([[WKScrollbarPartAnimation alloc] initWithScroller:_scroller |
| featureToAnimate:FeatureToAnimate::UIStateTransition |
| animateFrom:[scrollerImp 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 setStartValue:[scrollerImp uiStateTransitionProgress]]; |
| [_uiStateTransitionAnimation setEndValue:1.0]; |
| [_uiStateTransitionAnimation setDuration:duration]; |
| } |
| [_uiStateTransitionAnimation startAnimation]; |
| } |
| |
| - (void)scrollerImp:(NSScrollerImp *)scrollerImp animateExpansionTransitionWithDuration:(NSTimeInterval)duration |
| { |
| if (!_scroller) |
| return; |
| |
| ASSERT(scrollerImp == _scroller->scrollerImp()); |
| |
| // ExpansionTransition always animates to 1. In case an animation is in progress this avoids a hard transition. |
| [scrollerImp setExpansionTransitionProgress:1 - [scrollerImp expansionTransitionProgress]]; |
| |
| if (!_expansionTransitionAnimation) { |
| _expansionTransitionAnimation = adoptNS([[WKScrollbarPartAnimation alloc] initWithScroller:_scroller |
| featureToAnimate:FeatureToAnimate::ExpansionTransition |
| animateFrom:[scrollerImp 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 setStartValue:[scrollerImp uiStateTransitionProgress]]; |
| [_expansionTransitionAnimation setEndValue:1.0]; |
| [_expansionTransitionAnimation setDuration:duration]; |
| } |
| [_expansionTransitionAnimation startAnimation]; |
| } |
| |
| - (void)scrollerImp:(NSScrollerImp *)scrollerImp overlayScrollerStateChangedTo:(NSOverlayScrollerState)newOverlayScrollerState |
| { |
| UNUSED_PARAM(scrollerImp); |
| UNUSED_PARAM(newOverlayScrollerState); |
| } |
| |
| - (void)invalidate |
| { |
| _scroller = nil; |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| [_knobAlphaAnimation invalidate]; |
| [_trackAlphaAnimation invalidate]; |
| [_uiStateTransitionAnimation invalidate]; |
| [_expansionTransitionAnimation invalidate]; |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| @end |
| |
| namespace WebKit { |
| |
| ScrollerMac::ScrollerMac(ScrollerPairMac& pair, Orientation orientation) |
| : m_pair(pair) |
| , m_orientation(orientation) |
| { |
| } |
| |
| ScrollerMac::~ScrollerMac() |
| { |
| [m_scrollerImpDelegate invalidate]; |
| [m_scrollerImp setDelegate:nil]; |
| } |
| |
| void ScrollerMac::attach() |
| { |
| m_scrollerImpDelegate = adoptNS([[WKScrollerImpDelegate alloc] initWithScroller:this]); |
| |
| NSScrollerStyle newStyle = [m_pair.scrollerImpPair() scrollerStyle]; |
| m_scrollerImp = [NSScrollerImp scrollerImpWithStyle:newStyle controlSize:NSControlSizeRegular horizontal:m_orientation == Orientation::Horizontal replacingScrollerImp:nil]; |
| [m_scrollerImp setDelegate:m_scrollerImpDelegate.get()]; |
| } |
| |
| void ScrollerMac::setHostLayer(CALayer *layer) |
| { |
| if (m_hostLayer == layer) |
| return; |
| |
| m_hostLayer = layer; |
| |
| [m_scrollerImp setLayer:layer]; |
| |
| if (m_orientation == Orientation::Vertical) |
| [m_pair.scrollerImpPair() setVerticalScrollerImp:layer ? m_scrollerImp.get() : nil]; |
| else |
| [m_pair.scrollerImpPair() setHorizontalScrollerImp:layer ? m_scrollerImp.get() : nil]; |
| } |
| |
| void ScrollerMac::updateValues() |
| { |
| auto values = m_pair.valuesForOrientation(m_orientation); |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS; |
| |
| [m_scrollerImp setEnabled:!!m_hostLayer]; |
| [m_scrollerImp setBoundsSize:NSSizeFromCGSize([m_hostLayer bounds].size)]; |
| [m_scrollerImp setDoubleValue:values.value]; |
| [m_scrollerImp setPresentationValue:values.value]; |
| [m_scrollerImp setKnobProportion:values.proportion]; |
| |
| END_BLOCK_OBJC_EXCEPTIONS; |
| } |
| |
| WebCore::FloatPoint ScrollerMac::convertFromContent(const WebCore::FloatPoint& point) const |
| { |
| return WebCore::FloatPoint { [m_hostLayer convertPoint:point fromLayer:[m_hostLayer superlayer]] }; |
| } |
| |
| } |
| |
| #endif |