Index: ios/chrome/browser/ui/tab_switcher/tab_switcher_view.mm |
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_switcher_view.mm b/ios/chrome/browser/ui/tab_switcher/tab_switcher_view.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..493133c4b8d69603a723ae83d9bfb39c0db90325 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/tab_switcher/tab_switcher_view.mm |
@@ -0,0 +1,368 @@ |
+// Copyright 2015 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/tab_switcher/tab_switcher_view.h" |
+ |
+#include "base/logging.h" |
+#include "base/mac/foundation_util.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/metrics/user_metrics.h" |
+#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" |
+#include "ios/chrome/browser/ui/rtl_geometry.h" |
+#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_header_view.h" |
+#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_controller.h" |
+#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_overlay_view.h" |
+#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_view.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h" |
+#import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h" |
+#include "ui/base/l10n/l10n_util.h" |
+ |
+namespace { |
+const CGFloat kHeaderHeight = 95; |
+const CGFloat kNewTabButtonMarginFromEdges = 48; |
+const CGFloat kNewTabButtonWidth = 48; |
+} |
+ |
+@interface TabSwitcherView ()<UIScrollViewDelegate> { |
+ base::scoped_nsobject<TabSwitcherHeaderView> _headerView; |
+ base::scoped_nsobject<UIScrollView> _scrollView; |
+ base::scoped_nsobject<MDCButton> _openNewTabButton; |
+ base::scoped_nsobject<NSMutableArray> _panels; |
+ ios_internal::NewTabButtonStyle _openNewTabButtonStyle; |
+ NSInteger _previousPanelIndex; |
+} |
+ |
+// Returns the header view frame. |
+- (CGRect)headerViewFrame; |
+// Returns the scrollview frame. |
+- (CGRect)scrollViewFrame; |
+// Returns the new tab button frame. |
+- (CGRect)openNewTabButtonFrame; |
+// Returns the new tab button frame when hidden. |
+- (CGRect)openNewTabButtonFrameOffscreen; |
+// Called when the new tab button is pressed. |
+- (void)openNewTabButtonPressed; |
+// Returns the index of the panel presented in |_scrollview|. When two panels |
+// are visible, it returns the most visible one. |
+- (NSInteger)currentPageIndex; |
+// Select the panel at the given index, updating both the header and content. |
+// The scrollview scroll will be |animated| if requested. |
+- (void)selectPanelAtIndex:(NSInteger)index animated:(BOOL)animated; |
+@end |
+ |
+@implementation TabSwitcherView |
+ |
+@synthesize delegate = delegate_; |
+ |
+- (instancetype)initWithFrame:(CGRect)frame { |
+ self = [super initWithFrame:frame]; |
+ if (self) { |
+ _openNewTabButtonStyle = ios_internal::NewTabButtonStyle::UNINITIALIZED; |
+ [self loadSubviews]; |
+ _panels.reset([[NSMutableArray alloc] init]); |
+ _previousPanelIndex = -1; |
+ } |
+ return self; |
+} |
+ |
+- (TabSwitcherHeaderView*)headerView { |
+ return _headerView; |
+} |
+ |
+- (UIScrollView*)scrollView { |
+ return _scrollView; |
+} |
+ |
+- (void)layoutSubviews { |
+ [super layoutSubviews]; |
+ NSInteger pageIndexBeforeLayout = [self currentPageIndex]; |
+ [CATransaction begin]; |
+ [CATransaction setDisableActions:YES]; |
+ [_headerView setFrame:[self headerViewFrame]]; |
+ [_scrollView setFrame:[self scrollViewFrame]]; |
+ [CATransaction commit]; |
+ [self updateScrollViewContent]; |
+ [self selectPanelAtIndex:pageIndexBeforeLayout animated:NO]; |
+} |
+ |
+- (void)selectPanelAtIndex:(NSInteger)index { |
+ [self selectPanelAtIndex:index animated:!UIAccessibilityIsVoiceOverRunning()]; |
+} |
+ |
+- (void)addPanelView:(UIView*)view atIndex:(NSUInteger)index { |
+ if (UseRTLLayout()) |
+ [view setTransform:CGAffineTransformMakeScale(-1, 1)]; |
+ [_scrollView addSubview:view]; |
+ [_panels insertObject:view atIndex:index]; |
+ [self updateScrollViewContent]; |
+} |
+ |
+- (void)removePanelViewAtIndex:(NSUInteger)index { |
+ [self removePanelViewAtIndex:index updateScrollView:YES]; |
+} |
+ |
+- (void)removePanelViewAtIndex:(NSUInteger)index updateScrollView:(BOOL)update { |
+ DCHECK_EQ([[_panels objectAtIndex:index] superview], _scrollView.get()); |
+ [[_panels objectAtIndex:index] removeFromSuperview]; |
+ [_panels removeObjectAtIndex:index]; |
+ if (update) |
+ [self updateScrollViewContent]; |
+} |
+ |
+- (NSInteger)currentPanelIndex { |
+ return [self currentPageIndex]; |
+} |
+ |
+- (void)updateOverlayButtonState { |
+ NSInteger panelIndex = [self currentPageIndex]; |
+ ios_internal::NewTabButtonStyle newButtonStyle = |
+ [delegate_ buttonStyleForPanelAtIndex:panelIndex]; |
+ [self setNewTabButtonStyle:newButtonStyle]; |
+ BOOL dismissButtonVisible = |
+ [self.delegate shouldShowDismissButtonForPanelAtIndex:panelIndex]; |
+ [UIView beginAnimations:nil context:NULL]; |
+ [[_headerView dismissButton] setAlpha:dismissButtonVisible ? 1.0 : 0.0]; |
+ [UIView commitAnimations]; |
+} |
+ |
+#pragma mark - Private |
+ |
+- (void)selectPanelAtIndex:(NSInteger)index animated:(BOOL)animated { |
+ CGPoint pageOffset = CGPointZero; |
+ pageOffset.x = self.bounds.size.width * index; |
+ [_scrollView setContentOffset:pageOffset animated:animated]; |
+ [_headerView selectItemAtIndex:index]; |
+ UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, |
+ nil); |
+} |
+ |
+- (NSInteger)currentPageIndex { |
+ if ([_scrollView frame].size.width == 0) |
+ return 0; |
+ NSInteger index = floor( |
+ [_scrollView contentOffset].x / [_scrollView frame].size.width + 0.5); |
+ return std::max<NSInteger>(std::min<NSInteger>(index, [_panels count] - 1), |
+ 0); |
+} |
+ |
+- (void)updateHeaderSelection { |
+ [_headerView selectItemAtIndex:[self currentPageIndex]]; |
+} |
+ |
+- (CGRect)frameForPanelAtIndex:(NSInteger)index { |
+ CGSize panelSize = [_scrollView frame].size; |
+ CGRect panelFrame = |
+ CGRectMake(panelSize.width * index, 0, panelSize.width, panelSize.height); |
+ return panelFrame; |
+} |
+ |
+- (void)updateScrollViewContent { |
+ CGSize panelSize = [_scrollView frame].size; |
+ CGSize currentContentSize = [_scrollView contentSize]; |
+ currentContentSize.width = [_panels count] * panelSize.width; |
+ [_scrollView setContentSize:currentContentSize]; |
+ for (NSUInteger i = 0; i < [_panels count]; i++) { |
+ id panelView = [_panels objectAtIndex:i]; |
+ [panelView setFrame:[self frameForPanelAtIndex:i]]; |
+ } |
+} |
+ |
+- (void)loadSubviews { |
+ // Creates and add the header view showing the list of panels. |
+ base::scoped_nsobject<TabSwitcherHeaderView> headerView( |
+ [[TabSwitcherHeaderView alloc] initWithFrame:[self headerViewFrame]]); |
+ [self addSubview:headerView]; |
+ _headerView = headerView; |
+ |
+ // Creates and add the scrollview containing the panels. |
+ base::scoped_nsobject<UIScrollView> scrollView( |
+ [[UIScrollView alloc] initWithFrame:[self scrollViewFrame]]); |
+ [scrollView setBackgroundColor:[[MDCPalette greyPalette] tint900]]; |
+ [scrollView setAlwaysBounceHorizontal:YES]; |
+ [scrollView setDelegate:self]; |
+ [scrollView setPagingEnabled:YES]; |
+ [scrollView setDelegate:self]; |
+ if (UseRTLLayout()) |
+ [scrollView setTransform:CGAffineTransformMakeScale(-1, 1)]; |
+ [self addSubview:scrollView]; |
+ _scrollView = scrollView; |
+ |
+ // Creates and add the floating new tab button. |
+ _openNewTabButton.reset([[MDCFloatingButton alloc] init]); |
+ UIImage* openNewTabButtonImage = |
+ [[UIImage imageNamed:@"tabswitcher_new_tab_fab"] |
+ imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
+ [_openNewTabButton setImage:openNewTabButtonImage |
+ forState:UIControlStateNormal]; |
+ [[_openNewTabButton imageView] setTintColor:[UIColor whiteColor]]; |
+ [_openNewTabButton setFrame:[self openNewTabButtonFrame]]; |
+ // When the button is positioned with autolayout, the animation of the |
+ // button's position conflicts with other animations. |
+ [_openNewTabButton |
+ setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin | |
+ UIViewAutoresizingFlexibleLeadingMargin()]; |
+ [_openNewTabButton addTarget:self |
+ action:@selector(openNewTabButtonPressed) |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ [self addSubview:_openNewTabButton]; |
+ [self setNewTabButtonStyle:ios_internal::NewTabButtonStyle::GRAY]; |
+} |
+ |
+- (CGRect)headerViewFrame { |
+ const CGFloat kStatusBarHeight = |
+ [[UIApplication sharedApplication] statusBarFrame].size.height; |
+ CGRect headerViewFrame = self.bounds; |
+ headerViewFrame.origin.y += kStatusBarHeight; |
+ headerViewFrame.size.height = kHeaderHeight; |
+ return headerViewFrame; |
+} |
+ |
+- (CGRect)scrollViewFrame { |
+ CGRect scrollViewFrame = self.bounds; |
+ scrollViewFrame.origin.y = CGRectGetMaxY([self headerViewFrame]); |
+ scrollViewFrame.size.height -= scrollViewFrame.origin.y; |
+ return scrollViewFrame; |
+} |
+ |
+- (CGRect)openNewTabButtonFrame { |
+ CGRect buttonFrame = self.bounds; |
+ buttonFrame.origin.y = self.bounds.size.height - |
+ kNewTabButtonMarginFromEdges - kNewTabButtonWidth; |
+ if (UseRTLLayout()) { |
+ buttonFrame.origin.x = kNewTabButtonMarginFromEdges; |
+ } else { |
+ buttonFrame.origin.x = self.bounds.size.width - |
+ kNewTabButtonMarginFromEdges - kNewTabButtonWidth; |
+ } |
+ buttonFrame.size.width = kNewTabButtonWidth; |
+ buttonFrame.size.height = kNewTabButtonWidth; |
+ return buttonFrame; |
+} |
+ |
+- (CGRect)openNewTabButtonFrameOffscreen { |
+ CGRect buttonFrame = [self openNewTabButtonFrame]; |
+ buttonFrame.origin.y = self.bounds.size.height + kNewTabButtonWidth; |
+ return buttonFrame; |
+} |
+ |
+- (void)openNewTabButtonPressed { |
+ [self.delegate openNewTabInPanelAtIndex:[self currentPageIndex]]; |
+} |
+ |
+- (void)setNewTabButtonStyle:(ios_internal::NewTabButtonStyle)newStyle { |
+ if (newStyle == _openNewTabButtonStyle) |
+ return; |
+ _openNewTabButtonStyle = newStyle; |
+ [UIView animateWithDuration:0.25 |
+ animations:^{ |
+ switch (_openNewTabButtonStyle) { |
+ case ios_internal::NewTabButtonStyle::HIDDEN: |
+ [_openNewTabButton setFrame:[self openNewTabButtonFrameOffscreen]]; |
+ break; |
+ case ios_internal::NewTabButtonStyle::BLUE: { |
+ [_openNewTabButton setFrame:[self openNewTabButtonFrame]]; |
+ MDCPalette* palette = [MDCPalette cr_bluePalette]; |
+ [_openNewTabButton |
+ setInkColor:[[palette tint300] colorWithAlphaComponent:0.5f]]; |
+ [_openNewTabButton setBackgroundColor:[palette tint500] |
+ forState:UIControlStateNormal]; |
+ [_openNewTabButton |
+ setBackgroundColor:[UIColor colorWithWhite:0.8f alpha:1.0f] |
+ forState:UIControlStateDisabled]; |
+ [_openNewTabButton |
+ setAccessibilityLabel:l10n_util::GetNSString( |
+ IDS_IOS_TAB_SWITCHER_CREATE_NEW_TAB)]; |
+ break; |
+ } |
+ case ios_internal::NewTabButtonStyle::GRAY: { |
+ [_openNewTabButton setFrame:[self openNewTabButtonFrame]]; |
+ MDCPalette* palette = [MDCPalette greyPalette]; |
+ [_openNewTabButton |
+ setInkColor:[[palette tint300] colorWithAlphaComponent:0.25f]]; |
+ [_openNewTabButton setBackgroundColor:[palette tint500] |
+ forState:UIControlStateNormal]; |
+ [_openNewTabButton |
+ setBackgroundColor:[UIColor colorWithWhite:0.8f alpha:1.0f] |
+ forState:UIControlStateDisabled]; |
+ [_openNewTabButton |
+ setAccessibilityLabel: |
+ l10n_util::GetNSString( |
+ IDS_IOS_TAB_SWITCHER_CREATE_NEW_INCOGNITO_TAB)]; |
+ break; |
+ } |
+ case ios_internal::NewTabButtonStyle::UNINITIALIZED: |
+ NOTREACHED(); |
+ } |
+ } |
+ completion:^(BOOL finished) { |
+ // Informs VoiceOver that the |_openNewTabButton| visibility may have |
+ // changed. |
+ UIAccessibilityPostNotification( |
+ UIAccessibilityLayoutChangedNotification, nil); |
+ }]; |
+} |
+ |
+#pragma mark - UIScrollViewDelegate |
+ |
+- (void)scrollViewDidEndScrollingAnimation:(UIScrollView*)scrollView { |
+ [self updateHeaderSelection]; |
+} |
+ |
+- (void)scrollViewDidScroll:(UIScrollView*)scrollView { |
+ if ([scrollView isDragging]) { |
+ [self updateHeaderSelection]; |
+ } |
+ [self updateOverlayButtonState]; |
+ |
+ NSInteger panelIndex = [self currentPanelIndex]; |
+ TabSwitcherPanelOverlayView* overlayView = |
+ base::mac::ObjCCast<TabSwitcherPanelOverlayView>( |
+ [_panels objectAtIndex:panelIndex]); |
+ if (panelIndex != _previousPanelIndex && overlayView && |
+ [overlayView overlayType] == |
+ TabSwitcherPanelOverlayType::OVERLAY_PANEL_USER_SIGNED_OUT) { |
+ base::RecordAction( |
+ base::UserMetricsAction("Signin_Impression_FromTabSwitcher")); |
+ } |
+ _previousPanelIndex = panelIndex; |
+} |
+ |
+#pragma mark - UIScrollViewAccessibilityDelegate |
+ |
+- (NSString*)accessibilityScrollStatusForScrollView:(UIScrollView*)scrollView { |
+ NSInteger panelIndex = [self currentPageIndex]; |
+ return [_headerView panelTitleAtIndex:panelIndex]; |
+} |
+ |
+#pragma mark - UIAccessibilityAction |
+ |
+- (BOOL)accessibilityPerformEscape { |
+ // If the Dismiss Button is visible, then the escape gesture triggers the |
+ // dismissal of the tab switcher. |
+ NSInteger panelIndex = [self currentPageIndex]; |
+ if ([self.delegate shouldShowDismissButtonForPanelAtIndex:panelIndex]) { |
+ [delegate_ tabSwitcherViewDelegateDismissTabSwitcher:self]; |
+ return YES; |
+ } |
+ return NO; |
+} |
+ |
+- (BOOL)accessibilityPerformMagicTap { |
+ // If the New Tab Button is visible, then the magic tap opens a new tab. |
+ NSInteger panelIndex = [self currentPageIndex]; |
+ ios_internal::NewTabButtonStyle buttonStyle = |
+ [delegate_ buttonStyleForPanelAtIndex:panelIndex]; |
+ switch (buttonStyle) { |
+ case ios_internal::BLUE: |
+ case ios_internal::GRAY: |
+ [self.delegate openNewTabInPanelAtIndex:panelIndex]; |
+ return YES; |
+ case ios_internal::UNINITIALIZED: |
+ case ios_internal::HIDDEN: |
+ return NO; |
+ } |
+} |
+ |
+@end |