Index: ios/chrome/browser/ui/tabs/tab_view.mm |
diff --git a/ios/chrome/browser/ui/tabs/tab_view.mm b/ios/chrome/browser/ui/tabs/tab_view.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2b27e3b2a20f3bcc2d80fe85a1e6aff05e108d7d |
--- /dev/null |
+++ b/ios/chrome/browser/ui/tabs/tab_view.mm |
@@ -0,0 +1,380 @@ |
+ |
+// Copyright 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import "ios/chrome/browser/ui/tabs/tab_view.h" |
+ |
+#include "base/i18n/rtl.h" |
+#include "base/ios/ios_util.h" |
+#include "base/logging.h" |
+#include "base/mac/objc_property_releaser.h" |
+#include "base/strings/sys_string_conversions.h" |
+#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" |
+#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" |
+#import "ios/chrome/browser/ui/commands/generic_chrome_command.h" |
+#import "ios/chrome/browser/ui/image_util.h" |
+#include "ios/chrome/browser/ui/rtl_geometry.h" |
+#include "ios/chrome/browser/ui/tabs/tab_util.h" |
+#include "ios/chrome/browser/ui/ui_util.h" |
+#import "ios/chrome/browser/ui/uikit_ui_util.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#import "ios/third_party/material_components_ios/src/components/ActivityIndicator/src/MaterialActivityIndicator.h" |
+#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h" |
+#include "third_party/google_toolbox_for_mac/src/iPhone/GTMFadeTruncatingLabel.h" |
+#include "ui/base/l10n/l10n_util.h" |
+#include "ui/base/l10n/l10n_util_mac.h" |
+#include "ui/base/resource/resource_bundle.h" |
+#include "ui/gfx/image/image.h" |
+#import "ui/gfx/ios/uikit_util.h" |
+ |
+namespace { |
+ |
+// Tab close button insets. |
+const CGFloat kTabCloseTopInset = -1.0; |
+const CGFloat kTabCloseLeftInset = 0.0; |
+const CGFloat kTabCloseBottomInset = 0.0; |
+const CGFloat kTabCloseRightInset = 0.0; |
+const CGFloat kTabBackgroundLeftCapInset = 24.0; |
+const CGFloat kFaviconLeftInset = 23.5; |
+const CGFloat kFaviconVerticalOffset = 2.0; |
+const CGFloat kTabStripLineMargin = 2.5; |
+const CGFloat kTabStripLineHeight = 0.5; |
+const CGFloat kCloseButtonHorizontalShift = 15; |
+const CGFloat kCloseButtonVerticalShift = 4.0; |
+const CGFloat kTitleLeftMargin = 8.0; |
+const CGFloat kTitleRightMargin = 0.0; |
+ |
+const CGFloat kCloseButtonSize = 24.0; |
+const CGFloat kFaviconSize = 16.0; |
+} |
+ |
+@interface TabView () { |
+ // Close button for this tab. |
+ UIButton* _closeButton; |
+ |
+ // View that draws the tab title. |
+ GTMFadeTruncatingLabel* _titleLabel; |
+ |
+ // Background image for this tab. |
+ base::scoped_nsobject<UIImageView> _backgroundImageView; |
+ // This view is used to draw a separator line at the bottom of the tab view. |
+ // This view is hidden when the tab view is in a selected state. |
+ base::scoped_nsobject<UIView> _lineSeparator; |
+ BOOL _incognitoStyle; |
+ |
+ // Set to YES when the layout constraints have been initialized. |
+ BOOL _layoutConstraintsInitialized; |
+ |
+ // Image view used to draw the favicon and spinner. |
+ base::scoped_nsobject<UIImageView> _faviconView; |
+ |
+ // If |YES|, this view will adjust its appearance and draw as a collapsed tab. |
+ BOOL _collapsed; |
+ |
+ base::scoped_nsobject<MDCActivityIndicator> _activityIndicator; |
+ |
+ base::mac::ObjCPropertyReleaser _propertyReleaser_TabView; |
+} |
+@end |
+ |
+@interface TabView (Private) |
+ |
+// Creates the close button, favicon button, and title. |
+- (void)createButtonsAndLabel; |
+ |
+// Updates this tab's line separator color based on the current incognito style. |
+- (void)updateLineSeparator; |
+ |
+// Updates this tab's background image based on the value of |selected|. |
+- (void)updateBackgroundImage:(BOOL)selected; |
+ |
+// Updates this tab's close button image based on the current incognito style. |
+- (void)updateCloseButtonImages; |
+ |
+// Return the default favicon image based on the current incognito style. |
+- (UIImage*)defaultFaviconImage; |
+ |
+// Returns the rect in which to draw the favicon. |
+- (CGRect)faviconRectForBounds:(CGRect)bounds; |
+ |
+// Returns the rect in which to draw the tab title. |
+- (CGRect)titleRectForBounds:(CGRect)bounds; |
+ |
+// Returns the frame rect for the close button. |
+- (CGRect)closeRectForBounds:(CGRect)bounds; |
+ |
+@end |
+ |
+@implementation TabView |
+ |
+@synthesize closeButton = _closeButton; |
+@synthesize titleLabel = _titleLabel; |
+@synthesize collapsed = _collapsed; |
+@synthesize background = background_; |
+@synthesize incognitoStyle = _incognitoStyle; |
+ |
+- (id)initWithEmptyView:(BOOL)emptyView selected:(BOOL)selected { |
+ if ((self = [super initWithFrame:CGRectZero])) { |
+ _propertyReleaser_TabView.Init(self, [TabView class]); |
+ [self setOpaque:NO]; |
+ [self createCommonViews]; |
+ // -setSelected only calls -updateBackgroundImage if the selected state |
+ // changes. |isSelected| defaults to NO, so if |selected| is also NO, |
+ // -updateBackgroundImage needs to be called explicitly. |
+ [self setSelected:selected]; |
+ [self updateLineSeparator]; |
+ [self updateBackgroundImage:selected]; |
+ if (!emptyView) |
+ [self createButtonsAndLabel]; |
+ } |
+ return self; |
+} |
+ |
+- (void)setSelected:(BOOL)selected { |
+ BOOL wasSelected = [self isSelected]; |
+ [super setSelected:selected]; |
+ |
+ [_lineSeparator setHidden:selected]; |
+ |
+ if (selected != wasSelected) |
+ [self updateBackgroundImage:selected]; |
+ |
+ // It would make more sense to set active/inactive on tab_view itself, but |
+ // tab_view is not an an accessible element, and making it one would add |
+ // several complicated layers to UIA. Instead, simply set active/inactive |
+ // here to be used by UIA. |
+ [_closeButton setAccessibilityValue:(selected ? @"active" : @"inactive")]; |
+} |
+ |
+- (void)setCollapsed:(BOOL)collapsed { |
+ if (_collapsed != collapsed) |
+ [_closeButton setHidden:collapsed]; |
+ |
+ _collapsed = collapsed; |
+} |
+ |
+- (void)setTitle:(NSString*)title { |
+ if ([_titleLabel.text isEqualToString:title]) |
+ return; |
+ if (base::i18n::GetStringDirection(base::SysNSStringToUTF16(title)) == |
+ base::i18n::RIGHT_TO_LEFT) { |
+ [_titleLabel setTruncateMode:GTMFadeTruncatingHead]; |
+ } else { |
+ [_titleLabel setTruncateMode:GTMFadeTruncatingTail]; |
+ } |
+ _titleLabel.text = title; |
+} |
+ |
+- (UIImage*)favicon { |
+ return [_faviconView image]; |
+} |
+ |
+- (void)setFavicon:(UIImage*)favicon { |
+ if (!favicon) |
+ favicon = [self defaultFaviconImage]; |
+ [_faviconView setImage:favicon]; |
+} |
+ |
+- (void)setIncognitoStyle:(BOOL)incognitoStyle { |
+ _incognitoStyle = incognitoStyle; |
+ _titleLabel.textColor = |
+ incognitoStyle ? [UIColor whiteColor] : [UIColor blackColor]; |
+ [_faviconView setImage:[self defaultFaviconImage]]; |
+ [self updateLineSeparator]; |
+ [self updateCloseButtonImages]; |
+ [self updateBackgroundImage:[self isSelected]]; |
+} |
+ |
+- (void)startProgressSpinner { |
+ [_activityIndicator startAnimating]; |
+ [_activityIndicator setHidden:NO]; |
+ [_faviconView setHidden:YES]; |
+} |
+ |
+- (void)stopProgressSpinner { |
+ [_activityIndicator stopAnimating]; |
+ [_activityIndicator setHidden:YES]; |
+ [_faviconView setHidden:NO]; |
+} |
+ |
+#pragma mark - UIView overrides |
+ |
+- (void)setFrame:(CGRect)frame { |
+ const CGRect previousFrame = [self frame]; |
+ [super setFrame:frame]; |
+ // We are checking for a zero frame before triggering constraints updates in |
+ // order to prevent computation of constraints that will never be used for the |
+ // final layout. We could also initialize with a dummy frame but first this is |
+ // inefficient and second it's non trivial to compute the minimum valid frame |
+ // in regard to tweakable constants. |
+ if (CGRectEqualToRect(CGRectZero, previousFrame) && |
+ !_layoutConstraintsInitialized) { |
+ [self setNeedsUpdateConstraints]; |
+ } |
+} |
+ |
+- (void)updateConstraints { |
+ [super updateConstraints]; |
+ if (!_layoutConstraintsInitialized && |
+ !CGRectEqualToRect(CGRectZero, self.frame)) { |
+ _layoutConstraintsInitialized = YES; |
+ [self addCommonConstraints]; |
+ // Add buttons and labels constraints if needed. |
+ if (_closeButton) |
+ [self addButtonsAndLabelConstraints]; |
+ } |
+} |
+ |
+- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event { |
+ // Account for the trapezoidal shape of the tab. Inset the tab bounds by |
+ // (y = -2.2x + 56), determined empirically from looking at the tab background |
+ // images. |
+ CGFloat inset = MAX(0.0, (point.y - 56) / -2.2); |
+ return CGRectContainsPoint(CGRectInset([self bounds], inset, 0), point); |
+} |
+ |
+#pragma mark - Private |
+ |
+- (void)createCommonViews { |
+ _backgroundImageView.reset([[UIImageView alloc] init]); |
+ [_backgroundImageView setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [self addSubview:_backgroundImageView]; |
+ |
+ _lineSeparator.reset([[UIView alloc] initWithFrame:CGRectZero]); |
+ [_lineSeparator setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [self addSubview:_lineSeparator]; |
+} |
+ |
+- (void)addCommonConstraints { |
+ NSDictionary* commonViewsDictionary = @{ |
+ @"backgroundImageView" : _backgroundImageView.get(), |
+ @"lineSeparator" : _lineSeparator.get() |
+ }; |
+ NSArray* commonConstraints = @[ |
+ @"H:|-0-[backgroundImageView]-0-|", |
+ @"V:|-0-[backgroundImageView]-0-|", |
+ @"H:|-tabStripLineMargin-[lineSeparator]-tabStripLineMargin-|", |
+ @"V:[lineSeparator(==tabStripLineHeight)]-0-|", |
+ ]; |
+ NSDictionary* commonMetrics = @{ |
+ @"tabStripLineMargin" : @(kTabStripLineMargin), |
+ @"tabStripLineHeight" : @(kTabStripLineHeight) |
+ }; |
+ ApplyVisualConstraintsWithMetrics(commonConstraints, commonViewsDictionary, |
+ commonMetrics, self); |
+} |
+ |
+- (void)createButtonsAndLabel { |
+ _closeButton = [[UIButton buttonWithType:UIButtonTypeCustom] retain]; |
+ [_closeButton setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [_closeButton setImage:[UIImage imageNamed:@"tabstrip_tab_close"] |
+ forState:UIControlStateNormal]; |
+ [_closeButton setImage:[UIImage imageNamed:@"tabstrip_tab_close_pressed"] |
+ forState:UIControlStateHighlighted]; |
+ [_closeButton setContentEdgeInsets:UIEdgeInsetsMake(kTabCloseTopInset, |
+ kTabCloseLeftInset, |
+ kTabCloseBottomInset, |
+ kTabCloseRightInset)]; |
+ [_closeButton setAccessibilityLabel:l10n_util::GetNSString( |
+ IDS_IOS_TOOLS_MENU_CLOSE_TAB)]; |
+ [self addSubview:_closeButton]; |
+ |
+ // Add fade truncating label. |
+ _titleLabel = [[GTMFadeTruncatingLabel alloc] initWithFrame:CGRectZero]; |
+ [_titleLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [_titleLabel setFont:[MDCTypography body1Font]]; |
+ // Setting NSLineBreakByCharWrapping fixes an issue where the beginning of the |
+ // text is truncated for RTL text writing direction. Anyway since the label is |
+ // only one line and the end of the text is faded behind a gradient mask, it |
+ // is visually almost equivalent to NSLineBreakByClipping. |
+ [_titleLabel setLineBreakMode:NSLineBreakByCharWrapping]; |
+ |
+ [_titleLabel setTextAlignment:NSTextAlignmentNatural]; |
+ [self addSubview:_titleLabel]; |
+ |
+ CGRect faviconFrame = CGRectMake(0, 0, kFaviconSize, kFaviconSize); |
+ _faviconView.reset([[UIImageView alloc] initWithFrame:faviconFrame]); |
+ [_faviconView setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [_faviconView setContentMode:UIViewContentModeScaleAspectFit]; |
+ [_faviconView setImage:[self defaultFaviconImage]]; |
+ [_faviconView setAccessibilityIdentifier:@"Favicon"]; |
+ [self addSubview:_faviconView]; |
+ |
+ _activityIndicator.reset( |
+ [[MDCActivityIndicator alloc] initWithFrame:faviconFrame]); |
+ [_activityIndicator setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [_activityIndicator |
+ setCycleColors:@[ [[MDCPalette cr_bluePalette] tint500] ]]; |
+ [_activityIndicator setRadius:ui::AlignValueToUpperPixel(kFaviconSize / 2)]; |
+ [self addSubview:_activityIndicator]; |
+} |
+ |
+- (void)addButtonsAndLabelConstraints { |
+ // Constraints on the Top bar, snapshot view, and shadow view. |
+ NSDictionary* viewsDictionary = @{ |
+ @"close" : _closeButton, |
+ @"title" : _titleLabel, |
+ @"favicon" : _faviconView, |
+ }; |
+ NSArray* constraints = @[ |
+ @"H:|-faviconLeftInset-[favicon(faviconSize)]", |
+ @"V:|-faviconVerticalOffset-[favicon]-0-|", |
+ @"H:[close(==closeButtonSize)]-closeButtonHorizontalShift-|", |
+ @"V:|-closeButtonVerticalShift-[close]-0-|", |
+ @"H:[favicon]-titleLeftMargin-[title]-titleRightMargin-[close]", |
+ @"V:[title(==titleHeight)]", |
+ ]; |
+ NSDictionary* metrics = @{ |
+ @"closeButtonSize" : @(kCloseButtonSize), |
+ @"closeButtonHorizontalShift" : @(kCloseButtonHorizontalShift), |
+ @"closeButtonVerticalShift" : @(kCloseButtonVerticalShift), |
+ @"titleLeftMargin" : @(kTitleLeftMargin), |
+ @"titleRightMargin" : @(kTitleRightMargin), |
+ @"titleHeight" : @(kFaviconSize), |
+ @"faviconLeftInset" : @(AlignValueToPixel(kFaviconLeftInset)), |
+ @"faviconVerticalOffset" : @(kFaviconVerticalOffset), |
+ @"faviconSize" : @(kFaviconSize), |
+ }; |
+ ApplyVisualConstraintsWithMetrics(constraints, viewsDictionary, metrics, |
+ self); |
+ AddSameCenterXConstraint(self, _faviconView, _activityIndicator); |
+ AddSameCenterYConstraint(self, _faviconView, _activityIndicator); |
+ AddSameCenterYConstraint(self, _faviconView, _titleLabel); |
+} |
+ |
+- (void)updateLineSeparator { |
+ UIColor* separatorColor = |
+ _incognitoStyle ? [UIColor colorWithWhite:36 / 255.0 alpha:1.0] |
+ : [UIColor colorWithWhite:185 / 255.0 alpha:1.0]; |
+ [_lineSeparator setBackgroundColor:separatorColor]; |
+} |
+ |
+- (void)updateBackgroundImage:(BOOL)selected { |
+ NSString* state = (selected ? @"foreground" : @"background"); |
+ NSString* incognito = _incognitoStyle ? @"incognito_" : @""; |
+ NSString* imageName = |
+ [NSString stringWithFormat:@"tabstrip_%@%@_tab", incognito, state]; |
+ UIImage* backgroundImage = StretchableImageFromUIImage( |
+ [UIImage imageNamed:imageName], kTabBackgroundLeftCapInset, 0); |
+ [_backgroundImageView setImage:backgroundImage]; |
+} |
+ |
+- (void)updateCloseButtonImages { |
+ UIImage* normalImage = |
+ self.incognitoStyle ? [UIImage imageNamed:@"tabstrip_tab_close_incognito"] |
+ : [UIImage imageNamed:@"tabstrip_tab_close"]; |
+ UIImage* pressedImage = |
+ self.incognitoStyle |
+ ? [UIImage imageNamed:@"tabstrip_tab_close_incognito_pressed"] |
+ : [UIImage imageNamed:@"tabstrip_tab_close_pressed"]; |
+ [_closeButton setImage:normalImage forState:UIControlStateNormal]; |
+ [_closeButton setImage:pressedImage forState:UIControlStateHighlighted]; |
+} |
+ |
+- (UIImage*)defaultFaviconImage { |
+ return self.incognitoStyle ? [UIImage imageNamed:@"default_favicon_incognito"] |
+ : [UIImage imageNamed:@"default_favicon"]; |
+} |
+ |
+@end |