Chromium Code Reviews| 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 |