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..ebd3c2aaea016e08cc61bf7d8e9a823285a92fd2 |
--- /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. |
+ |
+#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" |
+#import <QuartzCore/QuartzCore.h> |
Avi (use Gerrit)
2014/06/06 23:09:45
This import gets its own section.
erikchen
2014/06/07 00:34:04
Done.
|
+ |
+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 to progress = 2.0; |
Avi (use Gerrit)
2014/06/06 23:09:45
either progress_ or progress.
erikchen
2014/06/07 00:34:04
I switched to "progress" for both.
|
+CGFloat kMaxAnimationDuration = 0.2; |
+} |
+ |
+// 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) |
Avi (use Gerrit)
2014/06/06 23:09:45
unbalanced parenthesis.
erikchen
2014/06/07 00:34:04
removed.
|
+@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 content { |
Avi (use Gerrit)
2014/06/06 23:09:45
put this in an anon namespace
erikchen
2014/06/07 00:34:04
that's doesn't work, as this class has a forward d
|
+class WebContentsPaintObserver : public WebContentsObserver { |
+ public: |
+ WebContentsPaintObserver(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. |
+}; |
+} |
+ |
+// 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 (slidingLeft_) |
+ return NSMakePoint(progress / kMaxProgress * self.bounds.size.width, 0); |
+ return NSMakePoint((1 - progress / kMaxProgress) * self.bounds.size.width, 0); |
+} |
+ |
+- (NSView*)movingView { |
+ if (slidingLeft_) |
+ 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 -------------------------------- |
+ |
+- (void)beginOverscrollLeft:(BOOL)left { |
+ if (animating_ || inOverscroll_) |
+ return; |
+ |
+ inOverscroll_ = YES; |
+ slidingLeft_ = left; |
+ if (left) { |
+ // The middleView_ will slide to the right, revealing bottomView_. |
+ bottomView_.get().hidden = NO; |
+ } else { |
+ // The topView_ will slide in from the right, concealing middleView_. |
+ topView_.get().hidden = NO; |
+ [topView_ setFrameOrigin:NSMakePoint(self.bounds.size.width, 0)]; |
+ } |
+ |
+ [self updateOverscrollProgress:kMinProgress]; |
+} |
+ |
+- (BOOL)needsNavigationSnapshot { |
+ return YES; |
+} |
+ |
+- (void)supplyNavigationSnapshot:(NSImage*)image { |
+ if (slidingLeft_) |
+ [bottomView_ setImage:image]; |
+ else |
+ [topView_ setImage:image]; |
+} |
+ |
+- (void)addRenderWidgetHostNativeView:(NSView*)view { |
+ [middleView_ addSubview:view]; |
+} |
+ |
+- (void)updateOverscrollProgress:(CGFloat)progress { |
+ if (animating_) |
+ return; |
+ DCHECK_LT(progress, kMaxProgress + 0.00001); |
+ DCHECK_GT(progress, kMinProgress - 0.00001); |
Avi (use Gerrit)
2014/06/06 23:09:45
Why not DCHECK_LE and DCHECK_GE?
erikchen
2014/06/07 00:34:04
Equality comparisons aren't particularly useful fo
Avi (use Gerrit)
2014/06/07 02:57:32
They are.
Your values, kMax|MinProgress are 0 and
|
+ 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:^{ |
erikchen
2014/06/06 22:44:56
This API is only available in 10.7+, but the relev
Avi (use Gerrit)
2014/06/06 23:09:45
If you're guaranteed that you never will come thro
erikchen
2014/06/07 00:34:04
Done.
|
+ animating_ = NO; |
+ |
+ // Animation is complete. Now perform page load. |
+ if (slidingLeft_) |
+ 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 content::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]; |
erikchen
2014/06/06 22:44:56
Everything but the block can be shared with -compl
Avi (use Gerrit)
2014/06/06 23:09:45
Yes, there's duplication, but I don't see a ton of
|
+ [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 |