| 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
|
|
|