OLD | NEW |
| (Empty) |
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_sli
der_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 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 | |
OLD | NEW |