Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / content / browser / web_contents / web_contents_view_overscroll_animator_slider_mac.mm
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import <QuartzCore/QuartzCore.h>
6
7 #include "content/browser/web_contents/web_contents_view_overscroll_animator_slider_mac.h"
8
9 #include "content/browser/web_contents/web_contents_impl.h"
10 #include "content/public/browser/web_contents_observer.h"
11
12 namespace {
13 // The minimum possible progress of an overscroll animation.
14 CGFloat kMinProgress = 0;
15 // The maximum possible progress of an overscroll animation.
16 CGFloat kMaxProgress = 2.0;
17 // The maximum duration of the completion or cancellation animations. The
18 // effective maximum is half of this value, since the longest animation is from
19 // progress = 1.0 to progress = 2.0;
20 CGFloat kMaxAnimationDuration = 0.2;
21 }  // namespace
22
23 // OverscrollAnimatorSliderView Private Category -------------------------------
24
25 @interface OverscrollAnimatorSliderView ()
26 // Callback from WebContentsPaintObserver.
27 - (void)webContentsFinishedNonEmptyPaint;
28
29 // Resets overscroll animation state.
30 - (void)reset;
31
32 // Given a |progress| from 0 to 2, the expected frame origin of the -movingView.
33 - (NSPoint)frameOriginWithProgress:(CGFloat)progress;
34
35 // The NSView that is moving during the overscroll animation.
36 - (NSView*)movingView;
37
38 // The expected duration of an animation from progress_ to |progress|
39 - (CGFloat)animationDurationForProgress:(CGFloat)progress;
40
41 // NSView override. During an overscroll animation, the cursor may no longer
42 // rest on the RenderWidgetHost's NativeView, which prevents wheel events from
43 // reaching the NativeView. The overscroll animation is driven by wheel events
44 // so they must be explicitly forwarded to the NativeView.
45 - (void)scrollWheel:(NSEvent*)event;
46 @end
47
48 // Helper Class (ResizingView) -------------------------------------------------
49
50 // This NSView subclass is intended to be the RenderWidgetHost's NativeView's
51 // parent NSView. It is possible for the RenderWidgetHost's NativeView's size to
52 // become out of sync with its parent NSView. The override of
53 // -resizeSubviewsWithOldSize: ensures that the sizes will eventually become
54 // consistent.
55 // http://crbug.com/264207
56 @interface ResizingView : NSView
57 @end
58
59 @implementation ResizingView
60 - (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
61   for (NSView* subview in self.subviews)
62     [subview setFrame:self.bounds];
63 }
64 @end
65
66 // Helper Class (WebContentsPaintObserver) -------------------------------------
67
68 namespace overscroll_animator {
69 class WebContentsPaintObserver : public content::WebContentsObserver {
70  public:
71   WebContentsPaintObserver(content::WebContents* web_contents,
72                            OverscrollAnimatorSliderView* slider_view)
73       : WebContentsObserver(web_contents), slider_view_(slider_view) {}
74
75   virtual void DidFirstVisuallyNonEmptyPaint() override {
76     [slider_view_ webContentsFinishedNonEmptyPaint];
77   }
78
79  private:
80   OverscrollAnimatorSliderView* slider_view_;  // Weak reference.
81 };
82 }  // namespace overscroll_animator
83
84 // OverscrollAnimatorSliderView Implementation ---------------------------------
85
86 @implementation OverscrollAnimatorSliderView
87
88 - (instancetype)initWithFrame:(NSRect)frame {
89   self = [super initWithFrame:frame];
90   if (self) {
91     bottomView_.reset([[NSImageView alloc] initWithFrame:self.bounds]);
92     bottomView_.get().imageScaling = NSImageScaleNone;
93     bottomView_.get().autoresizingMask =
94         NSViewWidthSizable | NSViewHeightSizable;
95     bottomView_.get().imageAlignment = NSImageAlignTop;
96     [self addSubview:bottomView_];
97     middleView_.reset([[ResizingView alloc] initWithFrame:self.bounds]);
98     middleView_.get().autoresizingMask =
99         NSViewWidthSizable | NSViewHeightSizable;
100     [self addSubview:middleView_];
101     topView_.reset([[NSImageView alloc] initWithFrame:self.bounds]);
102     topView_.get().autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
103     topView_.get().imageScaling = NSImageScaleNone;
104     topView_.get().imageAlignment = NSImageAlignTop;
105     [self addSubview:topView_];
106
107     [self reset];
108   }
109   return self;
110 }
111
112 - (void)webContentsFinishedNonEmptyPaint {
113   observer_.reset();
114   [self reset];
115 }
116
117 - (void)reset {
118   DCHECK(!animating_);
119   inOverscroll_ = NO;
120   progress_ = kMinProgress;
121
122   [CATransaction begin];
123   [CATransaction setDisableActions:YES];
124   bottomView_.get().hidden = YES;
125   middleView_.get().hidden = NO;
126   topView_.get().hidden = YES;
127
128   [bottomView_ setFrameOrigin:NSMakePoint(0, 0)];
129   [middleView_ setFrameOrigin:NSMakePoint(0, 0)];
130   [topView_ setFrameOrigin:NSMakePoint(0, 0)];
131   [CATransaction commit];
132 }
133
134 - (NSPoint)frameOriginWithProgress:(CGFloat)progress {
135   if (direction_ == content::OVERSCROLL_ANIMATOR_DIRECTION_BACKWARDS)
136     return NSMakePoint(progress / kMaxProgress * self.bounds.size.width, 0);
137   return NSMakePoint((1 - progress / kMaxProgress) * self.bounds.size.width, 0);
138 }
139
140 - (NSView*)movingView {
141   if (direction_ == content::OVERSCROLL_ANIMATOR_DIRECTION_BACKWARDS)
142     return middleView_;
143   return topView_;
144 }
145
146 - (CGFloat)animationDurationForProgress:(CGFloat)progress {
147   CGFloat progressPercentage =
148       fabs(progress_ - progress) / (kMaxProgress - kMinProgress);
149   return progressPercentage * kMaxAnimationDuration;
150 }
151
152 - (void)scrollWheel:(NSEvent*)event {
153   NSView* latestRenderWidgetHostView = [[middleView_ subviews] lastObject];
154   [latestRenderWidgetHostView scrollWheel:event];
155 }
156
157 // WebContentsOverscrollAnimator Implementation --------------------------------
158
159 - (BOOL)needsNavigationSnapshot {
160   return YES;
161 }
162
163 - (void)beginOverscrollInDirection:
164             (content::OverscrollAnimatorDirection)direction
165                 navigationSnapshot:(NSImage*)snapshot {
166   // TODO(erikchen): If snapshot is nil, need a placeholder.
167   if (animating_ || inOverscroll_)
168     return;
169
170   inOverscroll_ = YES;
171   direction_ = direction;
172   if (direction_ == content::OVERSCROLL_ANIMATOR_DIRECTION_BACKWARDS) {
173     // The middleView_ will slide to the right, revealing bottomView_.
174     bottomView_.get().hidden = NO;
175     [bottomView_ setImage:snapshot];
176   } else {
177     // The topView_ will slide in from the right, concealing middleView_.
178     topView_.get().hidden = NO;
179     [topView_ setFrameOrigin:NSMakePoint(self.bounds.size.width, 0)];
180     [topView_ setImage:snapshot];
181   }
182
183   [self updateOverscrollProgress:kMinProgress];
184 }
185
186 - (void)addRenderWidgetHostNativeView:(NSView*)view {
187   [middleView_ addSubview:view];
188 }
189
190 - (void)updateOverscrollProgress:(CGFloat)progress {
191   if (animating_)
192     return;
193   DCHECK_LE(progress, kMaxProgress);
194   DCHECK_GE(progress, kMinProgress);
195   progress_ = progress;
196   [[self movingView] setFrameOrigin:[self frameOriginWithProgress:progress]];
197 }
198
199 - (void)completeOverscroll:(content::WebContentsImpl*)webContents {
200   if (animating_ || !inOverscroll_)
201     return;
202
203   animating_ = YES;
204
205   NSView* view = [self movingView];
206   [NSAnimationContext beginGrouping];
207   [NSAnimationContext currentContext].duration =
208       [self animationDurationForProgress:kMaxProgress];
209   [[NSAnimationContext currentContext] setCompletionHandler:^{
210       animating_ = NO;
211
212       // Animation is complete. Now perform page load.
213       if (direction_ == content::OVERSCROLL_ANIMATOR_DIRECTION_BACKWARDS)
214         webContents->GetController().GoBack();
215       else
216         webContents->GetController().GoForward();
217
218       // Reset the position of the middleView_, but wait for the page to paint
219       // before showing it.
220       middleView_.get().hidden = YES;
221       [middleView_ setFrameOrigin:NSMakePoint(0, 0)];
222       observer_.reset(
223           new overscroll_animator::WebContentsPaintObserver(webContents, self));
224   }];
225
226   // Animate the moving view to its final position.
227   [[view animator] setFrameOrigin:[self frameOriginWithProgress:kMaxProgress]];
228
229   [NSAnimationContext endGrouping];
230 }
231
232 - (void)cancelOverscroll {
233   if (animating_)
234     return;
235
236   if (!inOverscroll_) {
237     [self reset];
238     return;
239   }
240
241   animating_ = YES;
242
243   NSView* view = [self movingView];
244   [NSAnimationContext beginGrouping];
245   [NSAnimationContext currentContext].duration =
246       [self animationDurationForProgress:kMinProgress];
247   [[NSAnimationContext currentContext] setCompletionHandler:^{
248       // Animation is complete. Reset the state.
249       animating_ = NO;
250       [self reset];
251   }];
252
253   // Animate the moving view to its initial position.
254   [[view animator] setFrameOrigin:[self frameOriginWithProgress:kMinProgress]];
255
256   [NSAnimationContext endGrouping];
257 }
258
259 @end