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

Side by Side Diff: ios/chrome/browser/ui/toolbar/toolbar_controller.mm

Issue 2588733002: Upstream Chrome on iOS source code [9/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/toolbar/toolbar_controller.h"
6
7 #include <QuartzCore/QuartzCore.h>
8
9 #include "base/format_macros.h"
10 #include "base/i18n/rtl.h"
11 #include "base/ios/ios_util.h"
12 #include "base/mac/bundle_locations.h"
13 #include "base/mac/foundation_util.h"
14 #include "base/memory/ptr_util.h"
15 #include "base/metrics/user_metrics.h"
16 #include "base/metrics/user_metrics_action.h"
17 #import "ios/chrome/browser/ui/animation_util.h"
18 #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
19 #import "ios/chrome/browser/ui/commands/generic_chrome_command.h"
20 #include "ios/chrome/browser/ui/commands/ios_command_ids.h"
21 #import "ios/chrome/browser/ui/fullscreen_controller.h"
22 #import "ios/chrome/browser/ui/image_util.h"
23 #import "ios/chrome/browser/ui/reversed_animation.h"
24 #include "ios/chrome/browser/ui/rtl_geometry.h"
25 #import "ios/chrome/browser/ui/toolbar/toolbar_controller+protected.h"
26 #import "ios/chrome/browser/ui/toolbar/toolbar_controller_private.h"
27 #include "ios/chrome/browser/ui/toolbar/toolbar_resource_macros.h"
28 #import "ios/chrome/browser/ui/toolbar/toolbar_tools_menu_button.h"
29 #import "ios/chrome/browser/ui/toolbar/tools_menu_button_observer_bridge.h"
30 #import "ios/chrome/browser/ui/tools_menu/tools_menu_context.h"
31 #import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
32 #import "ios/chrome/browser/ui/uikit_ui_util.h"
33 #import "ios/chrome/common/material_timing.h"
34 #include "ios/chrome/grit/ios_strings.h"
35 #include "ios/chrome/grit/ios_theme_resources.h"
36 #import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoF ontLoader.h"
37 #include "ui/base/resource/resource_bundle.h"
38
39 using base::UserMetricsAction;
40 using ios::material::TimingFunction;
41
42 // Animation key used for stack view transition animations
43 NSString* const kToolbarTransitionAnimationKey = @"ToolbarTransitionAnimation";
44
45 // Externed max tab count.
46 const NSInteger kStackButtonMaxTabCount = 99;
47 // Font sizes for the button containing the tab count
48 const NSInteger kFontSizeFewerThanTenTabs = 11;
49 const NSInteger kFontSizeTenTabsOrMore = 9;
50
51 // The initial capacity used to construct |self.transitionLayers|. The value
52 // is chosen because WebToolbarController animates 11 separate layers during
53 // transitions; this value should be updated if new subviews are animated in
54 // the future.
55 const NSUInteger kTransitionLayerCapacity = 11;
56
57 // Externed delay before non-initial button images are loaded.
58 const int64_t kNonInitialImageAdditionDelayNanosec = 500000LL;
59 NSString* const kMenuWillShowNotification = @"kMenuWillShowNotification";
60 NSString* const kMenuWillHideNotification = @"kMenuWillHideNotification";
61
62 NSString* const kToolbarIdentifier = @"kToolbarIdentifier";
63 NSString* const kIncognitoToolbarIdentifier = @"kIncognitoToolbarIdentifier";
64 NSString* const kToolbarToolsMenuButtonIdentifier =
65 @"kToolbarToolsMenuButtonIdentifier";
66 NSString* const kToolbarStackButtonIdentifier =
67 @"kToolbarStackButtonIdentifier";
68 NSString* const kToolbarShareButtonIdentifier =
69 @"kToolbarShareButtonIdentifier";
70
71 // Macros for creating CGRects of height H, origin (0,0), with the portrait
72 // width of phone/pad devices.
73 // clang-format off
74 #define IPHONE_FRAME(H) { { 0, 0 }, { kPortraitWidth[IPHONE_IDIOM], H } }
75 #define IPAD_FRAME(H) { { 0, 0 }, { kPortraitWidth[IPAD_IDIOM], H } }
76
77 // Makes a two-element C array of CGRects as described above, one for each
78 // device idiom.
79 #define FRAME_PAIR(H) { IPHONE_FRAME(H), IPAD_FRAME(H) }
80 // clang-format on
81
82 const CGRect kToolbarFrame[INTERFACE_IDIOM_COUNT] = FRAME_PAIR(56);
83
84 namespace {
85
86 // Color constants for the stack button text, normal and pressed states. These
87 // arrays are indexed by ToolbarControllerStyle enum values.
88 const CGFloat kStackButtonNormalColors[] = {
89 85.0 / 255.0, // ToolbarControllerStyleLightMode
90 238.0 / 255.0, // ToolbarControllerStyleDarkMode
91 238.0 / 255.0, // ToolbarControllerStyleIncognitoMode
92 };
93
94 const int kStackButtonHighlightedColors[] = {
95 0x4285F4, // ToolbarControllerStyleLightMode
96 0x888a8c, // ToolbarControllerStyleDarkMode
97 0x888a8c, // ToolbarControllerStyleIncognitoMode
98 };
99
100 // UI frames. iPhone values followed by iPad values.
101 // Full-width frames that don't change for RTL languages.
102 const CGRect kBackgroundViewFrame[INTERFACE_IDIOM_COUNT] = FRAME_PAIR(56);
103 const CGRect kShadowViewFrame[INTERFACE_IDIOM_COUNT] = FRAME_PAIR(2);
104 // Full bleed shadow frame is iPhone-only
105 const CGRect kFullBleedShadowViewFrame = IPHONE_FRAME(10);
106
107 // Frames that change for RTL.
108 // clang-format off
109 const LayoutRect kStackButtonFrame =
110 {kPortraitWidth[IPHONE_IDIOM], {230, 4}, {48, 48}};
111 const LayoutRect kShareMenuButtonFrame =
112 {kPortraitWidth[IPAD_IDIOM], {680, 4}, {46, 48}};
113 const LayoutRect kToolsMenuButtonFrame[INTERFACE_IDIOM_COUNT] = {
114 {kPortraitWidth[IPHONE_IDIOM], {276, 4}, {44, 48}},
115 {kPortraitWidth[IPAD_IDIOM], {723, 4}, {46, 48}}
116 };
117 // clang-format on
118
119 // Distance to shift buttons when fading out.
120 const LayoutOffset kButtonFadeOutXOffset = 10;
121
122 } // namespace
123
124 // Helper class to display a UIButton with the image and text centered
125 // vertically and horizontally.
126 @interface ToolbarCenteredButton : UIButton {
127 }
128 @end
129
130 @implementation ToolbarCenteredButton
131
132 - (instancetype)initWithFrame:(CGRect)frame {
133 self = [super initWithFrame:frame];
134 if (self) {
135 self.titleLabel.textAlignment = NSTextAlignmentCenter;
136 }
137 return self;
138 }
139
140 - (void)layoutSubviews {
141 [super layoutSubviews];
142 CGSize size = self.bounds.size;
143 CGPoint center = CGPointMake(size.width / 2, size.height / 2);
144 self.imageView.center = center;
145 self.imageView.frame = AlignRectToPixel(self.imageView.frame);
146 self.titleLabel.frame = self.bounds;
147 }
148
149 @end
150
151 @implementation ToolbarView
152
153 @synthesize animatingTransition = animatingTransition_;
154 @synthesize hitTestBoundsContraintRelaxed = hitTestBoundsContraintRelaxed_;
155
156 // Some views added to the toolbar have bounds larger than the toolbar bounds
157 // and still needs to receive touches. The overscroll actions view is one of
158 // those. That method is overridden in order to still perform hit testing on
159 // subviews that resides outside the toolbar bounds.
160 - (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event {
161 UIView* hitView = [super hitTest:point withEvent:event];
162 if (hitView || !self.hitTestBoundsContraintRelaxed)
163 return hitView;
164
165 for (UIView* view in [[self subviews] reverseObjectEnumerator]) {
166 if (!view.userInteractionEnabled || [view isHidden] || [view alpha] < 0.01)
167 continue;
168 const CGPoint convertedPoint = [view convertPoint:point fromView:self];
169 if ([view pointInside:convertedPoint withEvent:event]) {
170 hitView = [view hitTest:convertedPoint withEvent:event];
171 if (hitView)
172 break;
173 }
174 }
175 return hitView;
176 }
177
178 - (void)setDelegate:(id<ToolbarFrameDelegate>)delegate {
179 delegate_.reset(delegate);
180 }
181
182 - (void)setFrame:(CGRect)frame {
183 CGRect oldFrame = self.frame;
184 [super setFrame:frame];
185 [delegate_ frameDidChangeFrame:frame fromFrame:oldFrame];
186 }
187
188 - (void)didMoveToWindow {
189 [super didMoveToWindow];
190 [delegate_ windowDidChange];
191 }
192
193 - (id<CAAction>)actionForLayer:(CALayer*)layer forKey:(NSString*)event {
194 // Don't allow UIView block-based animations if we're already performing
195 // explicit transition animations.
196 if (self.animatingTransition)
197 return (id<CAAction>)[NSNull null];
198 return [super actionForLayer:layer forKey:event];
199 }
200
201 @end
202
203 @interface ToolbarController () {
204 // The top-level toolbar view.
205 base::scoped_nsobject<ToolbarView> view_;
206 // The view for the toolbar background image.
207 base::scoped_nsobject<UIImageView> backgroundView_;
208 base::scoped_nsobject<UIImageView> shadowView_;
209 base::scoped_nsobject<UIImageView> fullBleedShadowView_;
210
211 // The backing object for |self.transitionLayers|.
212 base::scoped_nsobject<NSMutableArray> transitionLayers_;
213
214 base::scoped_nsobject<ToolbarToolsMenuButton> toolsMenuButton_;
215 base::scoped_nsobject<UIButton> stackButton_;
216 base::scoped_nsobject<UIButton> shareButton_;
217 base::scoped_nsobject<NSArray> standardButtons_;
218 std::unique_ptr<ToolsMenuButtonObserverBridge> toolsMenuButtonObserverBridge_;
219 ToolbarControllerStyle style_;
220
221 // The following is nil if not visible.
222 base::scoped_nsobject<ToolsPopupController> toolsPopupController_;
223 }
224
225 // Returns the background image that should be used for |style|.
226 - (const gfx::Image&)getBackgroundImageForStyle:(ToolbarControllerStyle)style;
227
228 // Whether the share button should be visible in the toolbar.
229 - (BOOL)shareButtonShouldBeVisible;
230
231 // Update share button visibility and |standardButtons_| array.
232 - (void)updateStandardButtons;
233
234 // Returns an animation for |button| for a toolbar transition animation with
235 // |style|. |button|'s frame will be interpolated between its layout in the
236 // screen toolbar to the card's tab frame, and will be faded in for
237 // ToolbarTransitionStyleToStackView and faded out for
238 // ToolbarTransitionStyleToBVC.
239 - (CAAnimation*)transitionAnimationForButton:(UIButton*)button
240 containerBeginBounds:(CGRect)containerBeginBounds
241 containerEndBounds:(CGRect)containerEndBounds
242 withStyle:(ToolbarTransitionStyle)style;
243 @end
244
245 @implementation ToolbarController
246
247 @synthesize readingListModel = readingListModel_;
248
249 @synthesize style = style_;
250
251 - (void)setReadingListModel:(ReadingListModel*)readingListModel {
252 readingListModel_ = readingListModel;
253 if (readingListModel_)
254 toolsMenuButtonObserverBridge_ =
255 base::MakeUnique<ToolsMenuButtonObserverBridge>(readingListModel_,
256 toolsMenuButton_);
257 }
258
259 - (instancetype)initWithStyle:(ToolbarControllerStyle)style {
260 self = [super init];
261 if (self) {
262 style_ = style;
263 DCHECK_LT(style_, ToolbarControllerStyleMaxStyles);
264
265 InterfaceIdiom idiom = IsIPadIdiom() ? IPAD_IDIOM : IPHONE_IDIOM;
266 CGRect viewFrame = kToolbarFrame[idiom];
267 CGRect backgroundFrame = kBackgroundViewFrame[idiom];
268 CGRect stackButtonFrame = LayoutRectGetRect(kStackButtonFrame);
269 CGRect toolsMenuButtonFrame =
270 LayoutRectGetRect(kToolsMenuButtonFrame[idiom]);
271
272 if (idiom == IPHONE_IDIOM) {
273 CGFloat statusBarOffset = [self statusBarOffset];
274 viewFrame.size.height += statusBarOffset;
275 backgroundFrame.size.height += statusBarOffset;
276 stackButtonFrame.origin.y += statusBarOffset;
277 toolsMenuButtonFrame.origin.y += statusBarOffset;
278 }
279
280 view_.reset([[ToolbarView alloc] initWithFrame:viewFrame]);
281 backgroundView_.reset([[UIImageView alloc] initWithFrame:backgroundFrame]);
282 toolsMenuButton_.reset([[ToolbarToolsMenuButton alloc]
283 initWithFrame:toolsMenuButtonFrame
284 style:style_]);
285 [toolsMenuButton_ setTag:IDC_SHOW_TOOLS_MENU];
286 [toolsMenuButton_
287 setAutoresizingMask:UIViewAutoresizingFlexibleLeadingMargin() |
288 UIViewAutoresizingFlexibleBottomMargin];
289
290 [view_ addSubview:backgroundView_];
291 [view_ addSubview:toolsMenuButton_];
292 [view_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
293 [backgroundView_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
294 UIViewAutoresizingFlexibleHeight];
295
296 if (idiom == IPAD_IDIOM) {
297 CGRect shareButtonFrame = LayoutRectGetRect(kShareMenuButtonFrame);
298 shareButton_.reset([[UIButton alloc] initWithFrame:shareButtonFrame]);
299 [shareButton_ setTag:IDC_SHARE_PAGE];
300 [shareButton_
301 setAutoresizingMask:UIViewAutoresizingFlexibleLeadingMargin() |
302 UIViewAutoresizingFlexibleBottomMargin];
303 [self setUpButton:shareButton_
304 withImageEnum:ToolbarButtonNameShare
305 forInitialState:UIControlStateNormal
306 hasDisabledImage:YES
307 synchronously:NO];
308 SetA11yLabelAndUiAutomationName(shareButton_, IDS_IOS_TOOLS_MENU_SHARE,
309 kToolbarShareButtonIdentifier);
310 [view_ addSubview:shareButton_];
311 }
312
313 CGRect shadowFrame = kShadowViewFrame[idiom];
314 shadowFrame.origin.y = CGRectGetMaxY(backgroundFrame);
315 shadowView_.reset([[UIImageView alloc] initWithFrame:shadowFrame]);
316 [shadowView_ setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
317 [shadowView_ setUserInteractionEnabled:NO];
318 [view_ addSubview:shadowView_];
319 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
320 gfx::Image shadow = rb.GetNativeImageNamed(IDR_IOS_TOOLBAR_SHADOW);
321 [shadowView_ setImage:shadow.ToUIImage()];
322
323 if (idiom == IPHONE_IDIOM) {
324 // iPad omnibox does not expand to full bleed.
325 CGRect fullBleedShadowFrame = kFullBleedShadowViewFrame;
326 fullBleedShadowFrame.origin.y = shadowFrame.origin.y;
327 fullBleedShadowView_.reset(
328 [[UIImageView alloc] initWithFrame:fullBleedShadowFrame]);
329 [fullBleedShadowView_
330 setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
331 [fullBleedShadowView_ setUserInteractionEnabled:NO];
332 [fullBleedShadowView_ setAlpha:0];
333 [view_ addSubview:fullBleedShadowView_];
334 gfx::Image fullBleedShadow =
335 rb.GetNativeImageNamed(IDR_IOS_TOOLBAR_SHADOW_FULL_BLEED);
336 [fullBleedShadowView_ setImage:fullBleedShadow.ToUIImage()];
337 }
338
339 transitionLayers_.reset(
340 [[NSMutableArray alloc] initWithCapacity:kTransitionLayerCapacity]);
341
342 // UIImageViews do not default to userInteractionEnabled:YES.
343 [view_ setUserInteractionEnabled:YES];
344 [backgroundView_ setUserInteractionEnabled:YES];
345
346 gfx::Image tile = [self getBackgroundImageForStyle:style];
347 [[self backgroundView]
348 setImage:StretchableImageFromUIImage(tile.ToUIImage(), 0.0, 3.0)];
349
350 if (idiom == IPHONE_IDIOM) {
351 stackButton_.reset(
352 [[ToolbarCenteredButton alloc] initWithFrame:stackButtonFrame]);
353 [stackButton_ setTag:IDC_TOGGLE_TAB_SWITCHER];
354 [[stackButton_ titleLabel]
355 setFont:[self fontForSize:kFontSizeFewerThanTenTabs]];
356 [stackButton_
357 setTitleColor:[UIColor colorWithWhite:kStackButtonNormalColors[style_]
358 alpha:1.0]
359 forState:UIControlStateNormal];
360 UIColor* highlightColor =
361 UIColorFromRGB(kStackButtonHighlightedColors[style_], 1.0);
362 [stackButton_ setTitleColor:highlightColor
363 forState:UIControlStateHighlighted];
364
365 [stackButton_
366 setAutoresizingMask:UIViewAutoresizingFlexibleLeadingMargin() |
367 UIViewAutoresizingFlexibleBottomMargin];
368 [stackButton_ addTarget:self
369 action:@selector(stackButtonTouchDown:)
370 forControlEvents:UIControlEventTouchDown];
371
372 [self setUpButton:stackButton_
373 withImageEnum:ToolbarButtonNameStack
374 forInitialState:UIControlStateNormal
375 hasDisabledImage:NO
376 synchronously:NO];
377 [view_ addSubview:stackButton_];
378 }
379 [self registerEventsForButton:toolsMenuButton_];
380
381 self.view.accessibilityIdentifier =
382 style == ToolbarControllerStyleIncognitoMode
383 ? kIncognitoToolbarIdentifier
384 : kToolbarIdentifier;
385 SetA11yLabelAndUiAutomationName(stackButton_, IDS_IOS_TOOLBAR_SHOW_TABS,
386 kToolbarStackButtonIdentifier);
387 SetA11yLabelAndUiAutomationName(toolsMenuButton_, IDS_IOS_TOOLBAR_SETTINGS,
388 kToolbarToolsMenuButtonIdentifier);
389 [self updateStandardButtons];
390
391 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
392 [defaultCenter addObserver:self
393 selector:@selector(applicationDidEnterBackground:)
394 name:UIApplicationDidEnterBackgroundNotification
395 object:nil];
396 }
397 return self;
398 }
399
400 - (instancetype)init {
401 NOTREACHED();
402 return nil;
403 }
404
405 - (UIFont*)fontForSize:(NSInteger)size {
406 return [[MDFRobotoFontLoader sharedInstance] boldFontOfSize:size];
407 }
408
409 - (void)dealloc {
410 [[NSNotificationCenter defaultCenter] removeObserver:self];
411 [toolsPopupController_ setDelegate:nil];
412 [super dealloc];
413 }
414
415 - (UIImageView*)view {
416 return view_.get();
417 }
418
419 - (UIImageView*)backgroundView {
420 return backgroundView_.get();
421 }
422
423 - (CGFloat)statusBarOffset {
424 return StatusBarHeight();
425 }
426
427 - (UIImageView*)shadowView {
428 return shadowView_.get();
429 }
430
431 - (NSMutableArray*)transitionLayers {
432 return transitionLayers_.get();
433 }
434
435 - (BOOL)imageShouldFlipForRightToLeftLayoutDirection:(int)imageEnum {
436 // None of the images this class knows about should flip.
437 return NO;
438 }
439
440 - (void)updateStandardButtons {
441 BOOL shareButtonShouldBeVisible = [self shareButtonShouldBeVisible];
442 [shareButton_ setHidden:!shareButtonShouldBeVisible];
443 NSMutableArray* standardButtons = [NSMutableArray array];
444 [standardButtons addObject:toolsMenuButton_];
445 if (stackButton_)
446 [standardButtons addObject:stackButton_];
447 if (shareButtonShouldBeVisible)
448 [standardButtons addObject:shareButton_];
449 standardButtons_.reset([standardButtons retain]);
450 }
451
452 - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
453 [self updateStandardButtons];
454 }
455
456 - (ToolsPopupController*)toolsPopupController {
457 return toolsPopupController_.get();
458 }
459
460 - (void)applicationDidEnterBackground:(NSNotification*)notify {
461 if (toolsPopupController_.get()) {
462 // Dismiss the tools popup menu without animation.
463 [toolsMenuButton_ setToolsMenuIsVisible:NO];
464 toolsPopupController_.reset(nil);
465 [[NSNotificationCenter defaultCenter]
466 postNotificationName:kMenuWillHideNotification
467 object:nil];
468 }
469 }
470
471 - (BOOL)shareButtonShouldBeVisible {
472 // The share button only exists on iPad, and when some tabs are visible
473 // (i.e. when not in DarkMode), and when the width is greater than
474 // the tablet mini view.
475 if (!IsIPadIdiom() || style_ == ToolbarControllerStyleDarkMode ||
476 IsCompactTablet(self.view))
477 return NO;
478
479 return YES;
480 }
481
482 - (void)setShareButtonEnabled:(BOOL)enabled {
483 [shareButton_ setEnabled:enabled];
484 }
485
486 - (UIImage*)imageForImageEnum:(int)imageEnum
487 forState:(ToolbarButtonUIState)state {
488 int imageId =
489 [self imageIdForImageEnum:imageEnum style:[self style] forState:state];
490 DCHECK(imageId);
491 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
492 gfx::Image tile = rb.GetNativeImageNamed(imageId);
493 UIImage* image = tile.ToUIImage();
494 return (UseRTLLayout() &&
495 [self imageShouldFlipForRightToLeftLayoutDirection:imageEnum])
496 ? [image imageFlippedForRightToLeftLayoutDirection]
497 : image;
498 }
499
500 - (int)imageEnumForButton:(UIButton*)button {
501 if (button == stackButton_.get())
502 return ToolbarButtonNameStack;
503 return NumberOfToolbarButtonNames;
504 }
505
506 - (int)imageIdForImageEnum:(int)index
507 style:(ToolbarControllerStyle)style
508 forState:(ToolbarButtonUIState)state {
509 DCHECK(index < NumberOfToolbarButtonNames);
510 DCHECK(style < ToolbarControllerStyleMaxStyles);
511 DCHECK(state < NumberOfToolbarButtonUIStates);
512 // Incognito mode gets dark buttons.
513 if (style == ToolbarControllerStyleIncognitoMode)
514 style = ToolbarControllerStyleDarkMode;
515
516 // Name, style [light, dark], UIControlState [normal, pressed, disabled]
517 static int buttonImageIds[NumberOfToolbarButtonNames][2]
518 [NumberOfToolbarButtonUIStates] = {
519 TOOLBAR_IDR_THREE_STATE(OVERVIEW),
520 TOOLBAR_IDR_THREE_STATE(SHARE),
521 };
522
523 DCHECK(buttonImageIds[index][style][state]);
524 return buttonImageIds[index][style][state];
525 }
526
527 - (void)setUpButton:(UIButton*)button
528 withImageEnum:(int)imageEnum
529 forInitialState:(UIControlState)initialState
530 hasDisabledImage:(BOOL)hasDisabledImage
531 synchronously:(BOOL)synchronously {
532 [self registerEventsForButton:button];
533 // Add the non-initial images after a slight delay, to help performance
534 // and responsiveness on startup.
535 dispatch_time_t addImageDelay =
536 dispatch_time(DISPATCH_TIME_NOW, kNonInitialImageAdditionDelayNanosec);
537
538 void (^normalImageBlock)(void) = ^{
539 UIImage* image =
540 [self imageForImageEnum:imageEnum forState:ToolbarButtonUIStateNormal];
541 [button setImage:image forState:UIControlStateNormal];
542 };
543 if (synchronously || initialState == UIControlStateNormal)
544 normalImageBlock();
545 else
546 dispatch_after(addImageDelay, dispatch_get_main_queue(), normalImageBlock);
547
548 void (^pressedImageBlock)(void) = ^{
549 UIImage* image =
550 [self imageForImageEnum:imageEnum forState:ToolbarButtonUIStatePressed];
551 [button setImage:image forState:UIControlStateHighlighted];
552 };
553 if (synchronously || initialState == UIControlStateHighlighted)
554 pressedImageBlock();
555 else
556 dispatch_after(addImageDelay, dispatch_get_main_queue(), pressedImageBlock);
557
558 if (hasDisabledImage) {
559 void (^disabledImageBlock)(void) = ^{
560 UIImage* image = [self imageForImageEnum:imageEnum
561 forState:ToolbarButtonUIStateDisabled];
562 [button setImage:image forState:UIControlStateDisabled];
563 };
564 if (synchronously || initialState == UIControlStateDisabled) {
565 disabledImageBlock();
566 } else {
567 dispatch_after(addImageDelay, dispatch_get_main_queue(),
568 disabledImageBlock);
569 }
570 }
571 }
572
573 - (void)registerEventsForButton:(UIButton*)button {
574 if (button != toolsMenuButton_.get()) {
575 // |target| must be |self| (as opposed to |nil|) because |self| isn't in the
576 // responder chain.
577 [button addTarget:self
578 action:@selector(standardButtonPressed:)
579 forControlEvents:UIControlEventTouchUpInside];
580 }
581 [button addTarget:self
582 action:@selector(recordUserMetrics:)
583 forControlEvents:UIControlEventTouchUpInside];
584 [button addTarget:button
585 action:@selector(chromeExecuteCommand:)
586 forControlEvents:UIControlEventTouchUpInside];
587 }
588
589 - (CGRect)shareButtonAnchorRect {
590 // Shrink the padding around the shareButton so the popovers are anchored
591 // correctly.
592 return CGRectInset([shareButton_ bounds], 10, 0);
593 }
594
595 - (UIView*)shareButtonView {
596 return shareButton_.get();
597 }
598
599 - (void)showToolsMenuPopupWithContext:(ToolsMenuContext*)context {
600 // Because an animation hides and shows the tools popup menu it is possible to
601 // tap the tools button multiple times before the tools menu is shown. Ignore
602 // repeated taps between animations.
603 if (toolsPopupController_)
604 return;
605
606 base::RecordAction(UserMetricsAction("ShowAppMenu"));
607
608 // Keep the button pressed.
609 [toolsMenuButton_ setToolsMenuIsVisible:YES];
610
611 [context setToolsMenuButton:toolsMenuButton_];
612 toolsPopupController_.reset(
613 [[ToolsPopupController alloc] initWithContext:context]);
614
615 [toolsPopupController_ setDelegate:self];
616
617 [[NSNotificationCenter defaultCenter]
618 postNotificationName:kMenuWillShowNotification
619 object:nil];
620 }
621
622 - (void)dismissToolsMenuPopup {
623 if (!toolsPopupController_.get())
624 return;
625 ToolsPopupController* tempTPC = toolsPopupController_.get();
626 [tempTPC containerView].userInteractionEnabled = NO;
627 [tempTPC dismissAnimatedWithCompletion:^{
628 // Unpress the tools menu button by restoring the normal and
629 // highlighted images to their usual state.
630 [toolsMenuButton_ setToolsMenuIsVisible:NO];
631 // Reference tempTPC so the block retains it.
632 [tempTPC self];
633 }];
634 // reset tabHistoryPopupController_ to prevent -applicationDidEnterBackground
635 // from posting another kMenuWillHideNotification.
636 toolsPopupController_.reset();
637
638 [[NSNotificationCenter defaultCenter]
639 postNotificationName:kMenuWillHideNotification
640 object:nil];
641 }
642
643 - (const gfx::Image&)getBackgroundImageForStyle:(ToolbarControllerStyle)style {
644 int backgroundImageId;
645 if (style == ToolbarControllerStyleLightMode)
646 backgroundImageId = IDR_IOS_TOOLBAR_LIGHT_BACKGROUND;
647 else
648 backgroundImageId = IDR_IOS_TOOLBAR_DARK_BACKGROUND;
649
650 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
651 return rb.GetNativeImageNamed(backgroundImageId);
652 }
653
654 - (CGRect)specificControlsArea {
655 // Return the rect to the leading side of the leading-most trailing control.
656 UIView* trailingControl = toolsMenuButton_;
657 if (!IsIPadIdiom())
658 trailingControl = stackButton_;
659 if ([self shareButtonShouldBeVisible])
660 trailingControl = shareButton_;
661 LayoutRect trailing =
662 LayoutRectForRectInBoundingRect(trailingControl.frame, self.view.bounds);
663 LayoutRect controlsArea = LayoutRectGetLeadingLayout(trailing);
664 controlsArea.size.height = self.view.bounds.size.height;
665 controlsArea.position.originY = self.view.bounds.origin.y;
666 CGRect controlsFrame = LayoutRectGetRect(controlsArea);
667
668 if (!IsIPadIdiom()) {
669 controlsFrame.origin.y += StatusBarHeight();
670 controlsFrame.size.height -= StatusBarHeight();
671 }
672 return controlsFrame;
673 }
674
675 - (void)animateStandardControlsForOmniboxExpansion:(BOOL)growOmnibox {
676 if (growOmnibox)
677 [self fadeOutStandardControls];
678 else
679 [self fadeInStandardControls];
680 }
681
682 - (void)fadeOutStandardControls {
683 // The opacity animation has a different duration from the position animation.
684 // Thus they require separate CATransations.
685
686 // Animate the opacity of the buttons to 0.
687 [CATransaction begin];
688 [CATransaction setAnimationDuration:ios::material::kDuration2];
689 [CATransaction
690 setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseIn)];
691 CABasicAnimation* fadeButtons =
692 [CABasicAnimation animationWithKeyPath:@"opacity"];
693 fadeButtons.fromValue = @1;
694 fadeButtons.toValue = @0;
695
696 for (UIButton* button in standardButtons_.get()) {
697 if (![button isHidden]) {
698 [button layer].opacity = 0;
699 [[button layer] addAnimation:fadeButtons forKey:@"fade"];
700 }
701 }
702 [CATransaction commit];
703
704 // Animate the buttons 10 pixels in the leading-to-trailing direction
705 [CATransaction begin];
706 [CATransaction setAnimationDuration:ios::material::kDuration1];
707 [CATransaction
708 setAnimationTimingFunction:TimingFunction(ios::material::CurveEaseIn)];
709
710 for (UIButton* button in standardButtons_.get()) {
711 CABasicAnimation* shiftButton =
712 [CABasicAnimation animationWithKeyPath:@"position"];
713 CGPoint startPosition = [button layer].position;
714 CGPoint endPosition =
715 CGPointLayoutOffset(startPosition, kButtonFadeOutXOffset);
716 shiftButton.fromValue = [NSValue valueWithCGPoint:startPosition];
717 shiftButton.toValue = [NSValue valueWithCGPoint:endPosition];
718 [[button layer] addAnimation:shiftButton forKey:@"shiftButton"];
719 }
720
721 [CATransaction commit];
722
723 // Fade to the full bleed shadow.
724 [UIView animateWithDuration:ios::material::kDuration1
725 animations:^{
726 [shadowView_ setAlpha:0];
727 [fullBleedShadowView_ setAlpha:1];
728 }];
729 }
730
731 - (void)fadeInStandardControls {
732 for (UIButton* button in standardButtons_.get()) {
733 [self fadeInView:button
734 fromLeadingOffset:10
735 withDuration:ios::material::kDuration2
736 afterDelay:ios::material::kDuration1];
737 }
738
739 // Fade to the normal shadow.
740 [UIView animateWithDuration:ios::material::kDuration1
741 animations:^{
742 [shadowView_ setAlpha:self.backgroundView.alpha];
743 [fullBleedShadowView_ setAlpha:0];
744 }];
745 }
746
747 - (void)animationDidStart:(CAAnimation*)anim {
748 // Once the buttons start fading in, set their opacity to 1 so there's no
749 // flicker at the end of the animation.
750 for (UIButton* button in standardButtons_.get()) {
751 if (anim == [[button layer] animationForKey:@"fadeIn"]) {
752 [button layer].opacity = 1;
753 return;
754 }
755 }
756 }
757
758 - (void)fadeInView:(UIView*)view
759 fromLeadingOffset:(LayoutOffset)leadingOffset
760 withDuration:(NSTimeInterval)duration
761 afterDelay:(NSTimeInterval)delay {
762 [CATransaction begin];
763 [CATransaction setDisableActions:YES];
764 [CATransaction setCompletionBlock:^{
765 [view.layer removeAnimationForKey:@"fadeIn"];
766 }];
767 view.alpha = 1.0;
768
769 // Animate the position of |view| |leadingOffset| pixels after |delay|.
770 CGRect shiftedFrame = CGRectLayoutOffset(view.frame, leadingOffset);
771 CAAnimation* shiftAnimation =
772 FrameAnimationMake(view.layer, shiftedFrame, view.frame);
773 shiftAnimation.duration = duration;
774 shiftAnimation.beginTime = delay;
775 shiftAnimation.timingFunction = TimingFunction(ios::material::CurveEaseInOut);
776
777 // Animate the opacity of |view| to 1 after |delay|.
778 CAAnimation* fadeAnimation = OpacityAnimationMake(0.0, 1.0);
779 fadeAnimation.duration = duration;
780 fadeAnimation.beginTime = delay;
781 shiftAnimation.timingFunction = TimingFunction(ios::material::CurveEaseInOut);
782
783 // Add group animation to layer.
784 CAAnimation* group = AnimationGroupMake(@[ shiftAnimation, fadeAnimation ]);
785 [view.layer addAnimation:group forKey:@"fadeIn"];
786
787 [CATransaction commit];
788 }
789
790 - (CAAnimation*)transitionAnimationForButton:(UIButton*)button
791 containerBeginBounds:(CGRect)containerBeginBounds
792 containerEndBounds:(CGRect)containerEndBounds
793 withStyle:(ToolbarTransitionStyle)style {
794 BOOL toStackView = style == TOOLBAR_TRANSITION_STYLE_TO_STACK_VIEW;
795 CGRect cardBounds = toStackView ? containerEndBounds : containerBeginBounds;
796 CGRect toolbarBounds =
797 toStackView ? containerBeginBounds : containerEndBounds;
798
799 // |button|'s model layer frame is the button's frame within |toolbarBounds|.
800 CGRect toolbarButtonFrame = button.layer.frame;
801 LayoutRect toolbarButtonLayout =
802 LayoutRectForRectInBoundingRect(toolbarButtonFrame, toolbarBounds);
803
804 // |button|'s leading or trailing padding is maintained depending on its
805 // resizing mask. Its vertical positioning should be centered within the
806 // container view's card bounds.
807 LayoutRect cardButtonLayout = toolbarButtonLayout;
808 cardButtonLayout.boundingWidth = CGRectGetWidth(cardBounds);
809 BOOL flexibleLeading =
810 button.autoresizingMask & UIViewAutoresizingFlexibleLeadingMargin();
811 if (flexibleLeading) {
812 CGFloat trailingPadding =
813 LayoutRectGetTrailingLayout(toolbarButtonLayout).size.width;
814 cardButtonLayout.position.leading = cardButtonLayout.boundingWidth -
815 trailingPadding -
816 cardButtonLayout.size.width;
817 }
818 cardButtonLayout.position.originY =
819 CGRectGetMidY(cardBounds) - 0.5 * cardButtonLayout.size.height;
820 cardButtonLayout.position =
821 AlignLayoutRectPositionToPixel(cardButtonLayout.position);
822 CGRect cardButtonFrame = LayoutRectGetRect(cardButtonLayout);
823
824 CGRect beginFrame = toStackView ? toolbarButtonFrame : cardButtonFrame;
825 CGRect endFrame = toStackView ? cardButtonFrame : toolbarButtonFrame;
826
827 // Create animations.
828 CAAnimation* frameAnimation =
829 FrameAnimationMake(button.layer, beginFrame, endFrame);
830 frameAnimation.duration = ios::material::kDuration1;
831 frameAnimation.timingFunction = TimingFunction(ios::material::CurveEaseInOut);
832 CAAnimation* fadeAnimation =
833 OpacityAnimationMake(toStackView ? 1.0 : 0.0, toStackView ? 0.0 : 1.0);
834 fadeAnimation.duration = ios::material::kDuration8;
835 fadeAnimation.timingFunction = TimingFunction(ios::material::CurveEaseIn);
836 return AnimationGroupMake(@[ frameAnimation, fadeAnimation ]);
837 }
838
839 - (void)animateTransitionForButtonsInView:(UIView*)containerView
840 containerBeginBounds:(CGRect)containerBeginBounds
841 containerEndBounds:(CGRect)containerEndBounds
842 transitionStyle:(ToolbarTransitionStyle)style {
843 [containerView.subviews enumerateObjectsUsingBlock:^(
844 UIButton* button, NSUInteger idx, BOOL* stop) {
845 if ([button isKindOfClass:[UIButton class]] && button.alpha > 0.0) {
846 CAAnimation* buttonAnimation =
847 [self transitionAnimationForButton:button
848 containerBeginBounds:containerBeginBounds
849 containerEndBounds:containerEndBounds
850 withStyle:style];
851 [self.transitionLayers addObject:button.layer];
852 [button.layer addAnimation:buttonAnimation
853 forKey:kToolbarTransitionAnimationKey];
854 }
855 }];
856 }
857
858 - (void)reverseTransitionAnimations {
859 ReverseAnimationsForKeyForLayers(kToolbarTransitionAnimationKey,
860 [self transitionLayers]);
861 }
862
863 - (UIButton*)stackButton {
864 return stackButton_;
865 }
866
867 - (void)cleanUpTransitionAnimations {
868 RemoveAnimationForKeyFromLayers(kToolbarTransitionAnimationKey,
869 self.transitionLayers);
870 [self.transitionLayers removeAllObjects];
871 }
872
873 - (void)animateTransitionWithBeginFrame:(CGRect)beginFrame
874 endFrame:(CGRect)endFrame
875 transitionStyle:(ToolbarTransitionStyle)style {
876 // Animation values.
877 DCHECK(!self.transitionLayers.count);
878 BOOL transitioningToStackView =
879 (style == TOOLBAR_TRANSITION_STYLE_TO_STACK_VIEW);
880 CAAnimation* frameAnimation = nil;
881 CAMediaTimingFunction* frameTiming =
882 TimingFunction(ios::material::CurveEaseInOut);
883 CFTimeInterval frameDuration = ios::material::kDuration1;
884 CGRect beginBounds = {CGPointZero, beginFrame.size};
885 CGRect endBounds = {CGPointZero, endFrame.size};
886
887 // Update layer geometry.
888 frameAnimation = FrameAnimationMake(self.view.layer, beginFrame, endFrame);
889 frameAnimation.duration = frameDuration;
890 frameAnimation.timingFunction = frameTiming;
891 [self.transitionLayers addObject:self.view.layer];
892 [self.view.layer addAnimation:frameAnimation
893 forKey:kToolbarTransitionAnimationKey];
894
895 // Hide background view using CAAnimation so it can be unhidden when the
896 // animations are removed in |-cleanUpTransitionAnimations|.
897 CAAnimation* hideAnimation = OpacityAnimationMake(0.0, 0.0);
898 [self.transitionLayers addObject:self.backgroundView.layer];
899 [self.backgroundView.layer addAnimation:hideAnimation
900 forKey:kToolbarTransitionAnimationKey];
901
902 // Update shadow. When transitioning to the stack view, hide the shadow.
903 // When transitioning to the BVC, animate its frame while fading in.
904 CAAnimation* shadowAnimation = nil;
905 if (transitioningToStackView) {
906 shadowAnimation = hideAnimation;
907 } else {
908 InterfaceIdiom idiom = IsIPadIdiom() ? IPAD_IDIOM : IPHONE_IDIOM;
909 CGFloat shadowHeight = kShadowViewFrame[idiom].size.height;
910 CGFloat shadowVerticalOffset = [[self class] toolbarDropShadowHeight];
911 beginFrame = CGRectOffset(beginBounds, 0.0,
912 beginBounds.size.height - shadowVerticalOffset);
913 beginFrame.size.height = shadowHeight;
914 endFrame = CGRectOffset(endBounds, 0.0,
915 endBounds.size.height - shadowVerticalOffset);
916 endFrame.size.height = shadowHeight;
917 frameAnimation =
918 FrameAnimationMake([shadowView_ layer], beginFrame, endFrame);
919 frameAnimation.duration = frameDuration;
920 frameAnimation.timingFunction = frameTiming;
921 CAAnimation* fadeAnimation = OpacityAnimationMake(0.0, 1.0);
922 fadeAnimation.timingFunction = TimingFunction(ios::material::CurveEaseOut);
923 fadeAnimation.duration = ios::material::kDuration3;
924 shadowAnimation = AnimationGroupMake(@[ frameAnimation, fadeAnimation ]);
925 }
926 [self.transitionLayers addObject:[shadowView_ layer]];
927 [[shadowView_ layer] addAnimation:shadowAnimation
928 forKey:kToolbarTransitionAnimationKey];
929
930 // Animate toolbar buttons
931 [self animateTransitionForButtonsInView:self.view
932 containerBeginBounds:beginBounds
933 containerEndBounds:endBounds
934 transitionStyle:style];
935 }
936
937 - (void)hideViewsForNewTabPage:(BOOL)hide {
938 DCHECK(!IsIPadIdiom());
939 [shadowView_ setHidden:hide];
940 }
941
942 - (void)setStandardControlsVisible:(BOOL)visible {
943 if (visible) {
944 for (UIButton* button in standardButtons_.get()) {
945 [button setAlpha:1.0];
946 }
947 } else {
948 for (UIButton* button in standardButtons_.get()) {
949 [button setAlpha:0.0];
950 }
951 }
952 }
953
954 - (void)setStandardControlsAlpha:(CGFloat)alpha {
955 for (UIButton* button in standardButtons_.get()) {
956 if (![button isHidden])
957 [button setAlpha:alpha];
958 }
959 }
960
961 - (void)setBackgroundAlpha:(CGFloat)alpha {
962 [backgroundView_ setAlpha:alpha];
963 [shadowView_ setAlpha:alpha];
964 }
965
966 - (void)setStandardControlsTransform:(CGAffineTransform)transform {
967 for (UIButton* button in standardButtons_.get()) {
968 [button setTransform:transform];
969 }
970 }
971
972 - (void)standardButtonPressed:(UIButton*)sender {
973 // This check for valid button images assumes that the buttons all have a
974 // different image for the highlighted state as for the normal state.
975 // Currently, that assumption is true.
976 if ([sender imageForState:UIControlStateHighlighted] ==
977 [sender imageForState:UIControlStateNormal]) {
978 // Update the button images synchronously - somehow the button was pressed
979 // before the dispatched task completed.
980 [self setUpButton:sender
981 withImageEnum:[self imageEnumForButton:sender]
982 forInitialState:UIControlStateNormal
983 hasDisabledImage:NO
984 synchronously:YES];
985 }
986 }
987
988 - (void)setTabCount:(NSInteger)tabCount {
989 if (!stackButton_)
990 return;
991 // Enable or disable the stack view icon based on the number of tabs. This
992 // locks the user in the stack view when there are no tabs.
993 [stackButton_ setEnabled:tabCount > 0 ? YES : NO];
994
995 // Update the text shown in the |stackButton_|. Note that the button's title
996 // may be empty or contain an easter egg, but the accessibility value will
997 // always be equal to |tabCount|. Also, the text of |stackButton_| is shifted
998 // up, via |kEasterEggTitleInsets|, to avoid overlapping with the button's
999 // outline.
1000 NSString* stackButtonValue =
1001 [NSString stringWithFormat:@"%" PRIdNS, tabCount];
1002 NSString* stackButtonTitle;
1003 if (tabCount <= 0) {
1004 stackButtonTitle = @"";
1005 } else if (tabCount > kStackButtonMaxTabCount) {
1006 stackButtonTitle = @":)";
1007 [[stackButton_ titleLabel]
1008 setFont:[self fontForSize:kFontSizeFewerThanTenTabs]];
1009 } else {
1010 stackButtonTitle = stackButtonValue;
1011 if (tabCount < 10) {
1012 [[stackButton_ titleLabel]
1013 setFont:[self fontForSize:kFontSizeFewerThanTenTabs]];
1014 } else {
1015 [[stackButton_ titleLabel]
1016 setFont:[self fontForSize:kFontSizeTenTabsOrMore]];
1017 }
1018 }
1019
1020 [stackButton_ setTitle:stackButtonTitle forState:UIControlStateNormal];
1021 [stackButton_ setAccessibilityValue:stackButtonValue];
1022 }
1023
1024 - (IBAction)recordUserMetrics:(id)sender {
1025 if (sender == toolsMenuButton_.get())
1026 base::RecordAction(UserMetricsAction("MobileToolbarShowMenu"));
1027 else if (sender == stackButton_.get())
1028 base::RecordAction(UserMetricsAction("MobileToolbarShowStackView"));
1029 else if (sender == shareButton_.get())
1030 base::RecordAction(UserMetricsAction("MobileToolbarShareMenu"));
1031 else
1032 NOTREACHED();
1033 }
1034
1035 - (IBAction)stackButtonTouchDown:(id)sender {
1036 // Exists only for override by subclasses.
1037 }
1038
1039 + (CGFloat)toolbarDropShadowHeight {
1040 return 0.0;
1041 }
1042
1043 - (uint32_t)snapshotHash {
1044 // Only the 3 lowest bits are used by UIControlState.
1045 uint32_t hash = [toolsMenuButton_ state] & 0x07;
1046 // When the tools popup controller is valid, it means that the images
1047 // representing the tools menu button have been swapped. Factor that in by
1048 // adding in whether or not the tools popup menu is a valid object, rather
1049 // than trying to figure out which image is currently visible.
1050 hash |= toolsPopupController_ ? (1 << 4) : 0;
1051 // The label of the stack button changes with the number of tabs open.
1052 hash ^= [[stackButton_ titleForState:UIControlStateNormal] hash];
1053 return hash;
1054 }
1055
1056 #pragma mark -
1057 #pragma mark PopupMenuDelegate methods.
1058
1059 - (void)dismissPopupMenu:(PopupMenuController*)controller {
1060 if ([controller isKindOfClass:[ToolsPopupController class]] &&
1061 (ToolsPopupController*)controller == toolsPopupController_)
1062 [self dismissToolsMenuPopup];
1063 }
1064
1065 @end
OLDNEW
« no previous file with comments | « ios/chrome/browser/ui/toolbar/toolbar_controller.h ('k') | ios/chrome/browser/ui/toolbar/toolbar_controller+protected.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698