Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(518)

Side by Side Diff: ios/chrome/browser/ui/side_swipe/card_side_swipe_view.mm

Issue 2587023002: Upstream Chrome on iOS source code [8/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2012 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 "ios/chrome/browser/ui/side_swipe/card_side_swipe_view.h"
6
7 #include <cmath>
8
9 #include "base/ios/device_util.h"
10 #include "base/metrics/user_metrics.h"
11 #include "base/metrics/user_metrics_action.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "ios/chrome/browser/chrome_url_constants.h"
14 #import "ios/chrome/browser/tabs/tab.h"
15 #import "ios/chrome/browser/tabs/tab_model.h"
16 #import "ios/chrome/browser/ui/background_generator.h"
17 #import "ios/chrome/browser/ui/ntp/new_tab_page_panel_protocol.h"
18 #include "ios/chrome/browser/ui/rtl_geometry.h"
19 #import "ios/chrome/browser/ui/side_swipe/side_swipe_util.h"
20 #import "ios/chrome/browser/ui/side_swipe_gesture_recognizer.h"
21 #import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
22 #include "ios/chrome/browser/ui/ui_util.h"
23 #import "ios/chrome/browser/ui/uikit_ui_util.h"
24 #include "ios/chrome/grit/ios_theme_resources.h"
25 #import "ios/web/web_state/ui/crw_web_controller.h"
26 #include "ui/base/resource/resource_bundle.h"
27 #include "url/gurl.h"
28
29 using base::UserMetricsAction;
30
31 namespace {
32 // Spacing between cards.
33 const CGFloat kCardHorizontalSpacing = 30;
34
35 // Portion of the screen an edge card can be dragged.
36 const CGFloat kEdgeCardDragPercentage = 0.35;
37
38 // Card animation times.
39 const NSTimeInterval kAnimationDuration = 0.15;
40
41 // Reduce size in -smallGreyImage by this factor.
42 const CGFloat kResizeFactor = 4;
43 } // anonymous namespace
44
45 @implementation SwipeView
46
47 - (id)initWithFrame:(CGRect)frame topMargin:(CGFloat)topMargin {
48 self = [super initWithFrame:frame];
49 if (self) {
50 image_.reset([[UIImageView alloc] initWithFrame:CGRectZero]);
51 [image_ setClipsToBounds:YES];
52 [image_ setContentMode:UIViewContentModeScaleAspectFill];
53 [self addSubview:image_];
54
55 toolbarHolder_.reset([[UIImageView alloc] initWithFrame:CGRectZero]);
56 [self addSubview:toolbarHolder_];
57
58 shadowView_.reset([[UIImageView alloc] initWithFrame:self.bounds]);
59 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
60 gfx::Image shadow = rb.GetNativeImageNamed(IDR_IOS_TOOLBAR_SHADOW);
61 [shadowView_ setImage:shadow.ToUIImage()];
62 [self addSubview:shadowView_];
63
64 // All subviews are as wide as the parent
65 NSMutableArray* constraints = [NSMutableArray array];
66 for (UIView* view in self.subviews) {
67 [view setTranslatesAutoresizingMaskIntoConstraints:NO];
68 [constraints addObject:[view.leadingAnchor
69 constraintEqualToAnchor:self.leadingAnchor]];
70 [constraints addObject:[view.trailingAnchor
71 constraintEqualToAnchor:self.trailingAnchor]];
72 }
73
74 [constraints addObjectsFromArray:@[
75 [[image_ topAnchor] constraintEqualToAnchor:self.topAnchor
76 constant:topMargin],
77 [[image_ bottomAnchor] constraintEqualToAnchor:self.bottomAnchor],
78 [[toolbarHolder_ topAnchor] constraintEqualToAnchor:self.topAnchor
79 constant:-StatusBarHeight()],
80 [[toolbarHolder_ heightAnchor]
81 constraintEqualToConstant:topMargin + StatusBarHeight()],
82 [[shadowView_ topAnchor] constraintEqualToAnchor:self.topAnchor
83 constant:topMargin],
84 [[shadowView_ heightAnchor]
85 constraintEqualToConstant:kNewTabPageShadowHeight]
86 ]];
87
88 [NSLayoutConstraint activateConstraints:constraints];
89 }
90 return self;
91 }
92
93 - (void)layoutSubviews {
94 [super layoutSubviews];
95 [self updateImageBoundsAndZoom];
96 }
97
98 - (void)updateImageBoundsAndZoom {
99 UIImage* image = [image_ image];
100 if (image) {
101 CGSize imageSize = image.size;
102 CGSize viewSize = [image_ frame].size;
103 CGFloat zoomRatio = std::max(viewSize.height / imageSize.height,
104 viewSize.width / imageSize.width);
105 [image_ layer].contentsRect =
106 CGRectMake(0.0, 0.0, viewSize.width / (zoomRatio * imageSize.width),
107 viewSize.height / (zoomRatio * imageSize.height));
108 }
109 }
110
111 - (void)setImage:(UIImage*)image {
112 [image_ setImage:image];
113 [self updateImageBoundsAndZoom];
114 }
115
116 - (void)setToolbarImage:(UIImage*)image isNewTabPage:(BOOL)isNewTabPage {
117 [toolbarHolder_ setImage:image];
118 [shadowView_ setHidden:isNewTabPage];
119 }
120
121 @end
122
123 @interface CardSideSwipeView ()
124 // Pan touches ended or were cancelled.
125 - (void)finishPan;
126 // Is the current card is an edge card based on swipe direction.
127 - (BOOL)isEdgeSwipe;
128 // Initialize card based on model_ index.
129 - (void)setupCard:(SwipeView*)card
130 withIndex:(NSInteger)index
131 withToolbar:(WebToolbarController*)toolbarController;
132 // Build a |kResizeFactor| sized greyscaled version of |image|.
133 - (UIImage*)smallGreyImage:(UIImage*)image;
134 @end
135
136 @implementation CardSideSwipeView
137
138 @synthesize delegate = delegate_;
139 @synthesize topMargin = topMargin_;
140
141 - (id)initWithFrame:(CGRect)frame
142 topMargin:(CGFloat)topMargin
143 model:(TabModel*)model {
144 self = [super initWithFrame:frame];
145 if (self) {
146 model_ = model;
147 currentPoint_ = CGPointZero;
148 topMargin_ = topMargin;
149
150 base::scoped_nsobject<UIView> background(
151 [[UIView alloc] initWithFrame:CGRectZero]);
152 [self addSubview:background];
153
154 [background setTranslatesAutoresizingMaskIntoConstraints:NO];
155 [NSLayoutConstraint activateConstraints:@[
156 [[background rightAnchor] constraintEqualToAnchor:self.rightAnchor],
157 [[background leftAnchor] constraintEqualToAnchor:self.leftAnchor],
158 [[background topAnchor] constraintEqualToAnchor:self.topAnchor
159 constant:-StatusBarHeight()],
160 [[background bottomAnchor] constraintEqualToAnchor:self.bottomAnchor]
161 ]];
162
163 InstallBackgroundInView(background);
164
165 rightCard_.reset(
166 [[SwipeView alloc] initWithFrame:CGRectZero topMargin:topMargin]);
167 leftCard_.reset(
168 [[SwipeView alloc] initWithFrame:CGRectZero topMargin:topMargin]);
169 [rightCard_ setTranslatesAutoresizingMaskIntoConstraints:NO];
170 [leftCard_ setTranslatesAutoresizingMaskIntoConstraints:NO];
171 [self addSubview:rightCard_];
172 [self addSubview:leftCard_];
173 AddSameSizeConstraint(rightCard_, self);
174 AddSameSizeConstraint(leftCard_, self);
175 }
176 return self;
177 }
178
179 - (CGRect)cardFrame {
180 return self.bounds;
181 }
182
183 // Set up left and right card views depending on current tab and swipe
184 // direction.
185 - (void)updateViewsForDirection:(UISwipeGestureRecognizerDirection)direction
186 withToolbar:(WebToolbarController*)tbc {
187 direction_ = direction;
188 CGRect cardFrame = [self cardFrame];
189 NSUInteger currentIndex = [model_ indexOfTab:model_.currentTab];
190 CGFloat offset = UseRTLLayout() ? -1 : 1;
191 if (direction_ == UISwipeGestureRecognizerDirectionRight) {
192 [self setupCard:rightCard_ withIndex:currentIndex withToolbar:tbc];
193 [rightCard_ setFrame:cardFrame];
194 [self setupCard:leftCard_ withIndex:currentIndex - offset withToolbar:tbc];
195 cardFrame.origin.x -= cardFrame.size.width + kCardHorizontalSpacing;
196 [leftCard_ setFrame:cardFrame];
197 } else {
198 [self setupCard:leftCard_ withIndex:currentIndex withToolbar:tbc];
199 [leftCard_ setFrame:cardFrame];
200 [self setupCard:rightCard_ withIndex:currentIndex + offset withToolbar:tbc];
201 cardFrame.origin.x += cardFrame.size.width + kCardHorizontalSpacing;
202 [rightCard_ setFrame:cardFrame];
203 }
204 [tbc resetToolbarAfterSideSwipeSnapshot];
205 }
206
207 - (UIImage*)smallGreyImage:(UIImage*)image {
208 CGRect smallSize = CGRectMake(0, 0, image.size.width / kResizeFactor,
209 image.size.height / kResizeFactor);
210 // Using CIFilter here on iOS 5+ might be faster, but it doesn't easily
211 // allow for resizing. At the max size, it's still too slow for side swipe.
212 UIGraphicsBeginImageContextWithOptions(smallSize.size, YES, 0);
213 [image drawInRect:smallSize blendMode:kCGBlendModeLuminosity alpha:1.0];
214 UIImage* greyImage = UIGraphicsGetImageFromCurrentImageContext();
215 UIGraphicsEndImageContext();
216 return greyImage;
217 }
218
219 // Create card view based on TabModel index.
220 - (void)setupCard:(SwipeView*)card
221 withIndex:(NSInteger)index
222 withToolbar:(WebToolbarController*)toolbarController {
223 if (index < 0 || index >= (NSInteger)[model_ count]) {
224 [card setHidden:YES];
225 return;
226 }
227 [card setHidden:NO];
228
229 Tab* tab = [model_ tabAtIndex:index];
230 BOOL isNTP = tab.url.host() == kChromeUINewTabHost;
231 [toolbarController updateToolbarForSideSwipeSnapshot:tab];
232 UIImage* toolbarView = CaptureViewWithOption([toolbarController view],
233 [[UIScreen mainScreen] scale],
234 kClientSideRendering);
235 [card setToolbarImage:toolbarView isNewTabPage:isNTP];
236
237 // Converting snapshotted images to grey takes too much time for single core
238 // devices. Instead, show the colored image for single core devices and the
239 // grey image for multi core devices.
240 dispatch_queue_t priorityQueue =
241 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
242 [tab retrieveSnapshot:^(UIImage* image) {
243 if (tab.webController.usePlaceholderOverlay &&
244 !ios::device_util::IsSingleCoreDevice()) {
245 [card setImage:[CRWWebController defaultSnapshotImage]];
246 dispatch_async(priorityQueue, ^{
247 UIImage* greyImage = [self smallGreyImage:image];
248 dispatch_async(dispatch_get_main_queue(), ^{
249 [card setImage:greyImage];
250 });
251 });
252 } else {
253 [card setImage:image];
254 }
255 }];
256 }
257
258 // Place cards around |currentPoint_.x|, and lean towards each card near the
259 // X edges of |bounds|. Shrink cards as they are dragged towards the middle of
260 // the |bounds|, and edge cards only drag |kEdgeCardDragPercentage| of |bounds|.
261 - (void)updateCardPositions {
262 CGRect bounds = [self cardFrame];
263 [rightCard_ setFrame:bounds];
264 [leftCard_ setFrame:bounds];
265
266 CGFloat width = CGRectGetWidth([self cardFrame]);
267 CGPoint center = CGPointMake(bounds.origin.x + bounds.size.width / 2,
268 bounds.origin.y + bounds.size.height / 2);
269 if ([self isEdgeSwipe]) {
270 // If an edge card, don't allow the card to be dragged across the screen.
271 // Instead, drag across |kEdgeCardDragPercentage| of the screen.
272 center.x = currentPoint_.x - width / 2 -
273 (currentPoint_.x - width) / width *
274 (width * (1 - kEdgeCardDragPercentage));
275 [leftCard_ setCenter:center];
276 center.x = currentPoint_.x / width * (width * kEdgeCardDragPercentage) +
277 bounds.size.width / 2;
278 [rightCard_ setCenter:center];
279 } else {
280 // Place cards around the finger as it is dragged across the screen.
281 // Place the finger between the cards in the middle of the screen, on the
282 // right card border when on the left side of the screen, and on the left
283 // card border when on the right side of the screen.
284 CGFloat rightXBuffer = kCardHorizontalSpacing * currentPoint_.x / width;
285 CGFloat leftXBuffer = kCardHorizontalSpacing - rightXBuffer;
286
287 center.x = currentPoint_.x - leftXBuffer - width / 2;
288 [leftCard_ setCenter:center];
289
290 center.x = currentPoint_.x + rightXBuffer + width / 2;
291 [rightCard_ setCenter:center];
292 }
293 }
294
295 // Update layout with new touch event.
296 - (void)handleHorizontalPan:(SideSwipeGestureRecognizer*)gesture {
297 currentPoint_ = [gesture locationInView:self];
298 currentPoint_.x -= gesture.swipeOffset;
299
300 // Since it's difficult to touch the very edge of the screen (touches tend to
301 // sit near x ~ 4), push the touch towards the edge.
302 CGFloat width = CGRectGetWidth([self cardFrame]);
303 CGFloat half = floor(width / 2);
304 CGFloat padding = floor(std::abs(currentPoint_.x - half) / half);
305
306 // Push towards the edges.
307 if (currentPoint_.x > half)
308 currentPoint_.x += padding;
309 else
310 currentPoint_.x -= padding;
311
312 // But don't go past the edges.
313 if (currentPoint_.x < 0)
314 currentPoint_.x = 0;
315 else if (currentPoint_.x > width)
316 currentPoint_.x = width;
317
318 [self updateCardPositions];
319
320 if (gesture.state == UIGestureRecognizerStateEnded ||
321 gesture.state == UIGestureRecognizerStateCancelled ||
322 gesture.state == UIGestureRecognizerStateFailed) {
323 [self finishPan];
324 }
325 }
326
327 - (BOOL)isEdgeSwipe {
328 NSUInteger currentIndex = [model_ indexOfTab:model_.currentTab];
329 return (IsSwipingBack(direction_) && currentIndex == 0) ||
330 (IsSwipingForward(direction_) && currentIndex == [model_ count] - 1);
331 }
332
333 // Update the current tab and animate the proper card view if the
334 // |currentPoint_| is past the center of |bounds|.
335 - (void)finishPan {
336 NSUInteger currentIndex = [model_ indexOfTab:model_.currentTab];
337 // Something happened and now currentTab is gone. End card side swipe and let
338 // BVC show no tabs UI.
339 if (currentIndex == NSNotFound)
340 return [delegate_ sideSwipeViewDismissAnimationDidEnd:self];
341
342 CGRect finalSize = [self cardFrame];
343 CGFloat width = CGRectGetWidth([self cardFrame]);
344 CGRect leftFrame, rightFrame;
345 SwipeView* dominantCard;
346 Tab* destinationTab = model_.currentTab;
347 CGFloat offset = UseRTLLayout() ? -1 : 1;
348 if (direction_ == UISwipeGestureRecognizerDirectionRight) {
349 // If swipe is right and |currentPoint_.x| is over the first 1/3, move left.
350 if (currentPoint_.x > width / 3.0 && ![self isEdgeSwipe]) {
351 destinationTab = [model_ tabAtIndex:currentIndex - offset];
352 dominantCard = leftCard_;
353 rightFrame = leftFrame = finalSize;
354 rightFrame.origin.x += rightFrame.size.width + kCardHorizontalSpacing;
355 base::RecordAction(UserMetricsAction("MobileStackSwipeCompleted"));
356 } else {
357 dominantCard = rightCard_;
358 leftFrame = rightFrame = finalSize;
359 leftFrame.origin.x -= rightFrame.size.width + kCardHorizontalSpacing;
360 base::RecordAction(UserMetricsAction("MobileStackSwipeCancelled"));
361 }
362 } else {
363 // If swipe is left and |currentPoint_.x| is over the first 1/3, move right.
364 if (currentPoint_.x < (width / 3.0) * 2.0 && ![self isEdgeSwipe]) {
365 destinationTab = [model_ tabAtIndex:currentIndex + offset];
366 dominantCard = rightCard_;
367 leftFrame = rightFrame = finalSize;
368 leftFrame.origin.x -= rightFrame.size.width + kCardHorizontalSpacing;
369 base::RecordAction(UserMetricsAction("MobileStackSwipeCompleted"));
370 } else {
371 dominantCard = leftCard_;
372 rightFrame = leftFrame = finalSize;
373 rightFrame.origin.x += rightFrame.size.width + kCardHorizontalSpacing;
374 base::RecordAction(UserMetricsAction("MobileStackSwipeCancelled"));
375 }
376 }
377
378 // Changing the model even when the tab is the same at the end of the
379 // animation allows the UI to recover.
380 [model_ setCurrentTab:destinationTab];
381
382 // Make sure the dominant card animates on top.
383 [dominantCard.superview bringSubviewToFront:dominantCard];
384
385 [UIView animateWithDuration:kAnimationDuration
386 animations:^{
387 [leftCard_ setTransform:CGAffineTransformIdentity];
388 [rightCard_ setTransform:CGAffineTransformIdentity];
389 [leftCard_ setFrame:leftFrame];
390 [rightCard_ setFrame:rightFrame];
391 }
392 completion:^(BOOL finished) {
393 [leftCard_ setImage:nil];
394 [rightCard_ setImage:nil];
395 [leftCard_ setToolbarImage:nil isNewTabPage:NO];
396 [rightCard_ setToolbarImage:nil isNewTabPage:NO];
397 [delegate_ sideSwipeViewDismissAnimationDidEnd:self];
398 }];
399 }
400
401 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698