OLD | NEW |
(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 |
OLD | NEW |