Index: content/browser/web_contents/web_contents_view_overscroll_animator_slider_mac.mm |
diff --git a/content/browser/web_contents/web_contents_view_overscroll_animator_slider_mac.mm b/content/browser/web_contents/web_contents_view_overscroll_animator_slider_mac.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..29c84c1cdf57a1e629dd403a9daad0e47cf8ed12 |
--- /dev/null |
+++ b/content/browser/web_contents/web_contents_view_overscroll_animator_slider_mac.mm |
@@ -0,0 +1,259 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import <QuartzCore/QuartzCore.h> |
+ |
+#include "content/browser/web_contents/web_contents_view_overscroll_animator_slider_mac.h" |
+ |
+#include "content/browser/web_contents/web_contents_impl.h" |
+#include "content/public/browser/web_contents_observer.h" |
+ |
+namespace { |
+// The minimum possible progress of an overscroll animation. |
+CGFloat kMinProgress = 0; |
+// The maximum possible progress of an overscroll animation. |
+CGFloat kMaxProgress = 2.0; |
+// The maximum duration of the completion or cancellation animations. The |
+// effective maximum is half of this value, since the longest animation is from |
+// progress = 1.0 to progress = 2.0; |
+CGFloat kMaxAnimationDuration = 0.2; |
+} // namespace |
+ |
+// OverscrollAnimatorSliderView Private Category ------------------------------- |
+ |
+@interface OverscrollAnimatorSliderView () |
+// Callback from WebContentsPaintObserver. |
+- (void)webContentsFinishedNonEmptyPaint; |
+ |
+// Resets overscroll animation state. |
+- (void)reset; |
+ |
+// Given a |progress| from 0 to 2, the expected frame origin of the -movingView. |
+- (NSPoint)frameOriginWithProgress:(CGFloat)progress; |
+ |
+// The NSView that is moving during the overscroll animation. |
+- (NSView*)movingView; |
+ |
+// The expected duration of an animation from progress_ to |progress| |
+- (CGFloat)animationDurationForProgress:(CGFloat)progress; |
+ |
+// NSView override. During an overscroll animation, the cursor may no longer |
+// rest on the RenderWidgetHost's NativeView, which prevents wheel events from |
+// reaching the NativeView. The overscroll animation is driven by wheel events |
+// so they must be explicitly forwarded to the NativeView. |
+- (void)scrollWheel:(NSEvent*)event; |
+@end |
+ |
+// Helper Class (ResizingView) ------------------------------------------------- |
+ |
+// This NSView subclass is intended to be the RenderWidgetHost's NativeView's |
+// parent NSView. It is possible for the RenderWidgetHost's NativeView's size to |
+// become out of sync with its parent NSView. The override of |
+// -resizeSubviewsWithOldSize: ensures that the sizes will eventually become |
+// consistent. |
+// http://crbug.com/264207 |
+@interface ResizingView : NSView |
+@end |
+ |
+@implementation ResizingView |
+- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize { |
+ for (NSView* subview in self.subviews) |
+ [subview setFrame:self.bounds]; |
+} |
+@end |
+ |
+// Helper Class (WebContentsPaintObserver) ------------------------------------- |
+ |
+namespace overscroll_animator { |
+class WebContentsPaintObserver : public content::WebContentsObserver { |
+ public: |
+ WebContentsPaintObserver(content::WebContents* web_contents, |
+ OverscrollAnimatorSliderView* slider_view) |
+ : WebContentsObserver(web_contents), slider_view_(slider_view) {} |
+ |
+ virtual void DidFirstVisuallyNonEmptyPaint() OVERRIDE { |
+ [slider_view_ webContentsFinishedNonEmptyPaint]; |
+ } |
+ |
+ private: |
+ OverscrollAnimatorSliderView* slider_view_; // Weak reference. |
+}; |
+} // namespace overscroll_animator |
+ |
+// OverscrollAnimatorSliderView Implementation --------------------------------- |
+ |
+@implementation OverscrollAnimatorSliderView |
+ |
+- (instancetype)initWithFrame:(NSRect)frame { |
+ self = [super initWithFrame:frame]; |
+ if (self) { |
+ bottomView_.reset([[NSImageView alloc] initWithFrame:self.bounds]); |
+ bottomView_.get().imageScaling = NSImageScaleNone; |
+ bottomView_.get().autoresizingMask = |
+ NSViewWidthSizable | NSViewHeightSizable; |
+ bottomView_.get().imageAlignment = NSImageAlignTop; |
+ [self addSubview:bottomView_]; |
+ middleView_.reset([[ResizingView alloc] initWithFrame:self.bounds]); |
+ middleView_.get().autoresizingMask = |
+ NSViewWidthSizable | NSViewHeightSizable; |
+ [self addSubview:middleView_]; |
+ topView_.reset([[NSImageView alloc] initWithFrame:self.bounds]); |
+ topView_.get().autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; |
+ topView_.get().imageScaling = NSImageScaleNone; |
+ topView_.get().imageAlignment = NSImageAlignTop; |
+ [self addSubview:topView_]; |
+ |
+ [self reset]; |
+ } |
+ return self; |
+} |
+ |
+- (void)webContentsFinishedNonEmptyPaint { |
+ observer_.reset(); |
+ [self reset]; |
+} |
+ |
+- (void)reset { |
+ DCHECK(!animating_); |
+ inOverscroll_ = NO; |
+ progress_ = kMinProgress; |
+ |
+ [CATransaction begin]; |
+ [CATransaction setDisableActions:YES]; |
+ bottomView_.get().hidden = YES; |
+ middleView_.get().hidden = NO; |
+ topView_.get().hidden = YES; |
+ |
+ [bottomView_ setFrameOrigin:NSMakePoint(0, 0)]; |
+ [middleView_ setFrameOrigin:NSMakePoint(0, 0)]; |
+ [topView_ setFrameOrigin:NSMakePoint(0, 0)]; |
+ [CATransaction commit]; |
+} |
+ |
+- (NSPoint)frameOriginWithProgress:(CGFloat)progress { |
+ if (direction_ == content::OVERSCROLL_ANIMATOR_DIRECTION_BACKWARDS) |
+ return NSMakePoint(progress / kMaxProgress * self.bounds.size.width, 0); |
+ return NSMakePoint((1 - progress / kMaxProgress) * self.bounds.size.width, 0); |
+} |
+ |
+- (NSView*)movingView { |
+ if (direction_ == content::OVERSCROLL_ANIMATOR_DIRECTION_BACKWARDS) |
+ return middleView_; |
+ return topView_; |
+} |
+ |
+- (CGFloat)animationDurationForProgress:(CGFloat)progress { |
+ CGFloat progressPercentage = |
+ fabs(progress_ - progress) / (kMaxProgress - kMinProgress); |
+ return progressPercentage * kMaxAnimationDuration; |
+} |
+ |
+- (void)scrollWheel:(NSEvent*)event { |
+ NSView* latestRenderWidgetHostView = [[middleView_ subviews] lastObject]; |
+ [latestRenderWidgetHostView scrollWheel:event]; |
+} |
+ |
+// WebContentsOverscrollAnimator Implementation -------------------------------- |
+ |
+- (BOOL)needsNavigationSnapshot { |
+ return YES; |
+} |
+ |
+- (void)beginOverscrollInDirection: |
+ (content::OverscrollAnimatorDirection)direction |
+ navigationSnapshot:(NSImage*)snapshot { |
+ // TODO(erikchen): If snapshot is nil, need a placeholder. |
+ if (animating_ || inOverscroll_) |
+ return; |
+ |
+ inOverscroll_ = YES; |
+ direction_ = direction; |
+ if (direction_ == content::OVERSCROLL_ANIMATOR_DIRECTION_BACKWARDS) { |
+ // The middleView_ will slide to the right, revealing bottomView_. |
+ bottomView_.get().hidden = NO; |
+ [bottomView_ setImage:snapshot]; |
+ } else { |
+ // The topView_ will slide in from the right, concealing middleView_. |
+ topView_.get().hidden = NO; |
+ [topView_ setFrameOrigin:NSMakePoint(self.bounds.size.width, 0)]; |
+ [topView_ setImage:snapshot]; |
+ } |
+ |
+ [self updateOverscrollProgress:kMinProgress]; |
+} |
+ |
+- (void)addRenderWidgetHostNativeView:(NSView*)view { |
+ [middleView_ addSubview:view]; |
+} |
+ |
+- (void)updateOverscrollProgress:(CGFloat)progress { |
+ if (animating_) |
+ return; |
+ DCHECK_LE(progress, kMaxProgress); |
+ DCHECK_GE(progress, kMinProgress); |
+ progress_ = progress; |
+ [[self movingView] setFrameOrigin:[self frameOriginWithProgress:progress]]; |
+} |
+ |
+- (void)completeOverscroll:(content::WebContentsImpl*)webContents { |
+ if (animating_ || !inOverscroll_) |
+ return; |
+ |
+ animating_ = YES; |
+ |
+ NSView* view = [self movingView]; |
+ [NSAnimationContext beginGrouping]; |
+ [NSAnimationContext currentContext].duration = |
+ [self animationDurationForProgress:kMaxProgress]; |
+ [[NSAnimationContext currentContext] setCompletionHandler:^{ |
+ animating_ = NO; |
+ |
+ // Animation is complete. Now perform page load. |
+ if (direction_ == content::OVERSCROLL_ANIMATOR_DIRECTION_BACKWARDS) |
+ webContents->GetController().GoBack(); |
+ else |
+ webContents->GetController().GoForward(); |
+ |
+ // Reset the position of the middleView_, but wait for the page to paint |
+ // before showing it. |
+ middleView_.get().hidden = YES; |
+ [middleView_ setFrameOrigin:NSMakePoint(0, 0)]; |
+ observer_.reset( |
+ new overscroll_animator::WebContentsPaintObserver(webContents, self)); |
+ }]; |
+ |
+ // Animate the moving view to its final position. |
+ [[view animator] setFrameOrigin:[self frameOriginWithProgress:kMaxProgress]]; |
+ |
+ [NSAnimationContext endGrouping]; |
+} |
+ |
+- (void)cancelOverscroll { |
+ if (animating_) |
+ return; |
+ |
+ if (!inOverscroll_) { |
+ [self reset]; |
+ return; |
+ } |
+ |
+ animating_ = YES; |
+ |
+ NSView* view = [self movingView]; |
+ [NSAnimationContext beginGrouping]; |
+ [NSAnimationContext currentContext].duration = |
+ [self animationDurationForProgress:kMinProgress]; |
+ [[NSAnimationContext currentContext] setCompletionHandler:^{ |
+ // Animation is complete. Reset the state. |
+ animating_ = NO; |
+ [self reset]; |
+ }]; |
+ |
+ // Animate the moving view to its initial position. |
+ [[view animator] setFrameOrigin:[self frameOriginWithProgress:kMinProgress]]; |
+ |
+ [NSAnimationContext endGrouping]; |
+} |
+ |
+@end |