Commit 97abfd34 authored by Roberto Moura's avatar Roberto Moura Committed by Commit Bot

Add Layout Switcher and Layout Switcher Provider classes

As described in the subsection "Thumb Strip to Tab grid transition" of
the Thumb Strip Design Doc (go/bling-thumb-strip-design), in order to
make a transition of layout in the tab switcher when the user goes from
thumb strip to tab grid, we create the layout switcher and layout
switcher provider, which allow the ViewRevealingVerticalPanHandler to
trigger a layout transition in the grid view controller.

The grid view controller implements the layout switcher protocol, and
the tab grid view controller implements the layout switcher provider
protocol. The tab grid coordinator connects the
viewRevealingVerticalPanHandler to the layout switcher provider.

Bug: 1127604, 1130037
Change-Id: Ibb170f6f1e69b70c0b20a6dd4dbff3daa507187c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2408012Reviewed-by: default avatarGauthier Ambard <gambard@chromium.org>
Commit-Queue: Roberto Moura <mouraroberto@google.com>
Cr-Commit-Position: refs/heads/master@{#809278}
parent fe6e27d6
...@@ -6,6 +6,8 @@ import("//build/config/chrome_build.gni") ...@@ -6,6 +6,8 @@ import("//build/config/chrome_build.gni")
source_set("gestures") { source_set("gestures") {
sources = [ sources = [
"layout_switcher.h",
"layout_switcher_provider.h",
"view_revealing_animatee.h", "view_revealing_animatee.h",
"view_revealing_vertical_pan_handler.h", "view_revealing_vertical_pan_handler.h",
"view_revealing_vertical_pan_handler.mm", "view_revealing_vertical_pan_handler.mm",
......
// Copyright 2020 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.
#ifndef IOS_CHROME_BROWSER_UI_GESTURES_LAYOUT_SWITCHER_H_
#define IOS_CHROME_BROWSER_UI_GESTURES_LAYOUT_SWITCHER_H_
// The 2 layout states of a view that is revealed by the view revealing vertical
// pan handler class.
enum class LayoutSwitcherState {
Horizontal, // The view layout when the view is a horizontal strip.
Full, // The view layout when the view is at its full size.
};
// Interface to manage interactive animated transitions of layout.
@protocol LayoutSwitcher
// Notifies of a transition of layout to the specified state. Called when the
// view revealing vertical pan handler starts a transition of layout. The
// conformer should prepare its layout for a transition to |nextState|.
- (void)willTransitionToLayout:(LayoutSwitcherState)nextState;
// Notifies of a change in the progress of the transition of layout currently in
// progress.
- (void)didUpdateTransitionLayoutProgress:(CGFloat)progress;
// Notifies of a transition animation that happened in the correct direction if
// |success| and in the reverse direction otherwise.
- (void)didTransitionToLayoutSuccessfully:(BOOL)success;
@end
#endif // IOS_CHROME_BROWSER_UI_GESTURES_LAYOUT_SWITCHER_H_
// Copyright 2020 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.
#ifndef IOS_CHROME_BROWSER_UI_GESTURES_LAYOUT_SWITCHER_PROVIDER_H_
#define IOS_CHROME_BROWSER_UI_GESTURES_LAYOUT_SWITCHER_PROVIDER_H_
#import "ios/chrome/browser/ui/gestures/layout_switcher.h"
// Protocol that provides access to a layout switcher.
@protocol LayoutSwitcherProvider
// The layout switcher.
@property(nonatomic, readonly, weak) id<LayoutSwitcher> layoutSwitcher;
@end
#endif // IOS_CHROME_BROWSER_UI_GESTURES_LAYOUT_SWITCHER_PROVIDER_H_
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/gestures/layout_switcher_provider.h"
#import "ios/chrome/browser/ui/gestures/view_revealing_animatee.h" #import "ios/chrome/browser/ui/gestures/view_revealing_animatee.h"
// Responsible for handling vertical pan gestures to reveal/hide a view behind // Responsible for handling vertical pan gestures to reveal/hide a view behind
...@@ -14,6 +15,12 @@ ...@@ -14,6 +15,12 @@
// TODO(crbug.com/1123512): Add support for going straight from a Hidden state // TODO(crbug.com/1123512): Add support for going straight from a Hidden state
// to a revealed state (and vice-versa) if the gesture's translation and // to a revealed state (and vice-versa) if the gesture's translation and
// velocity are enough to trigger such transition. // velocity are enough to trigger such transition.
// TODO(crbug.com/1130037): When the currentState is Peeked and a transition
// starts, if the user switches direction (from Revealed to Hidden and from
// Hidden to Revealed again), the layout switcher's collection view starts a new
// transition when one already exists: `'NSInternalInconsistencyException',
// reason: 'the collection is already in the middle of an interactive
// transition'`
@interface ViewRevealingVerticalPanHandler : NSObject @interface ViewRevealingVerticalPanHandler : NSObject
// |peekedHeight| is the height of the view when peeked (partially revealed). // |peekedHeight| is the height of the view when peeked (partially revealed).
...@@ -41,6 +48,9 @@ ...@@ -41,6 +48,9 @@
@property(nonatomic, assign, readonly) CGFloat revealedHeight; @property(nonatomic, assign, readonly) CGFloat revealedHeight;
// Height of the base view. It changes when the user rotates the screen. // Height of the base view. It changes when the user rotates the screen.
@property(nonatomic, assign) CGFloat baseViewHeight; @property(nonatomic, assign) CGFloat baseViewHeight;
// The provider for the object that switches the layout of the revealed view
// from horizontal (Peeked state) to full (Revealed state).
@property(nonatomic, weak) id<LayoutSwitcherProvider> layoutSwitcherProvider;
@end @end
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#import "ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.h" #import "ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.h"
#include "base/numerics/ranges.h" #include "base/numerics/ranges.h"
#import "ios/chrome/browser/ui/gestures/layout_switcher.h"
#if !defined(__has_feature) || !__has_feature(objc_arc) #if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support." #error "This file requires ARC support."
...@@ -43,6 +44,8 @@ const CGFloat kAnimationDuration = 0.25f; ...@@ -43,6 +44,8 @@ const CGFloat kAnimationDuration = 0.25f;
@property(nonatomic, assign) CGFloat progressWhenInterrupted; @property(nonatomic, assign) CGFloat progressWhenInterrupted;
// Set of UI elements which are animated during view reveal transitions. // Set of UI elements which are animated during view reveal transitions.
@property(nonatomic, strong) NSHashTable<id<ViewRevealingAnimatee>>* animatees; @property(nonatomic, strong) NSHashTable<id<ViewRevealingAnimatee>>* animatees;
// Whether the revealed view is undergoing a transition of layout.
@property(nonatomic, assign) BOOL layoutInTransition;
@end @end
@implementation ViewRevealingVerticalPanHandler @implementation ViewRevealingVerticalPanHandler
...@@ -58,6 +61,7 @@ const CGFloat kAnimationDuration = 0.25f; ...@@ -58,6 +61,7 @@ const CGFloat kAnimationDuration = 0.25f;
_remainingHeight = _revealedHeight - peekedHeight; _remainingHeight = _revealedHeight - peekedHeight;
_currentState = ViewRevealState::Hidden; _currentState = ViewRevealState::Hidden;
_animatees = [NSHashTable weakObjectsHashTable]; _animatees = [NSHashTable weakObjectsHashTable];
_layoutInTransition = NO;
} }
return self; return self;
} }
...@@ -133,6 +137,23 @@ const CGFloat kAnimationDuration = 0.25f; ...@@ -133,6 +137,23 @@ const CGFloat kAnimationDuration = 0.25f;
[weakSelf didAnimateViewReveal:weakSelf.currentState]; [weakSelf didAnimateViewReveal:weakSelf.currentState];
}]; }];
[self.animator pauseAnimation]; [self.animator pauseAnimation];
[self createLayoutTransitionIfNeeded];
}
// Creates a transition layout in the revealed view if going from Peeked to
// Revealed state or vice-versa.
- (void)createLayoutTransitionIfNeeded {
if (self.currentState == ViewRevealState::Peeked &&
self.nextState == ViewRevealState::Revealed) {
[self.layoutSwitcherProvider.layoutSwitcher
willTransitionToLayout:LayoutSwitcherState::Full];
self.layoutInTransition = YES;
} else if (self.currentState == ViewRevealState::Revealed &&
self.nextState == ViewRevealState::Peeked) {
[self.layoutSwitcherProvider.layoutSwitcher
willTransitionToLayout:LayoutSwitcherState::Horizontal];
self.layoutInTransition = YES;
}
} }
// Initiates a transition if there isn't already one running // Initiates a transition if there isn't already one running
...@@ -229,6 +250,10 @@ const CGFloat kAnimationDuration = 0.25f; ...@@ -229,6 +250,10 @@ const CGFloat kAnimationDuration = 0.25f;
progress += self.progressWhenInterrupted; progress += self.progressWhenInterrupted;
progress = base::ClampToRange<CGFloat>(progress, 0, 1); progress = base::ClampToRange<CGFloat>(progress, 0, 1);
self.animator.fractionComplete = progress; self.animator.fractionComplete = progress;
if (self.layoutInTransition) {
[self.layoutSwitcherProvider.layoutSwitcher
didUpdateTransitionLayoutProgress:progress];
}
} }
// Handles the start of the pan gesture. // Handles the start of the pan gesture.
...@@ -267,6 +292,11 @@ const CGFloat kAnimationDuration = 0.25f; ...@@ -267,6 +292,11 @@ const CGFloat kAnimationDuration = 0.25f;
Velocity:velocity]); Velocity:velocity]);
[self.animator continueAnimationWithTimingParameters:nil durationFactor:1]; [self.animator continueAnimationWithTimingParameters:nil durationFactor:1];
if (self.layoutInTransition) {
[self.layoutSwitcherProvider.layoutSwitcher
didTransitionToLayoutSuccessfully:!self.animator.reversed];
self.layoutInTransition = NO;
}
} }
@end @end
...@@ -37,6 +37,7 @@ source_set("tab_grid") { ...@@ -37,6 +37,7 @@ source_set("tab_grid") {
"//ios/chrome/browser/ui/alert_coordinator", "//ios/chrome/browser/ui/alert_coordinator",
"//ios/chrome/browser/ui/commands", "//ios/chrome/browser/ui/commands",
"//ios/chrome/browser/ui/coordinators:chrome_coordinators", "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
"//ios/chrome/browser/ui/gestures",
"//ios/chrome/browser/ui/history", "//ios/chrome/browser/ui/history",
"//ios/chrome/browser/ui/history/public", "//ios/chrome/browser/ui/history/public",
"//ios/chrome/browser/ui/main", "//ios/chrome/browser/ui/main",
...@@ -107,6 +108,7 @@ source_set("tab_grid_ui") { ...@@ -107,6 +108,7 @@ source_set("tab_grid_ui") {
"//ios/chrome/browser/crash_report", "//ios/chrome/browser/crash_report",
"//ios/chrome/browser/ui:feature_flags", "//ios/chrome/browser/ui:feature_flags",
"//ios/chrome/browser/ui/commands", "//ios/chrome/browser/ui/commands",
"//ios/chrome/browser/ui/gestures",
"//ios/chrome/browser/ui/recent_tabs:recent_tabs_ui", "//ios/chrome/browser/ui/recent_tabs:recent_tabs_ui",
"//ios/chrome/browser/ui/tab_grid/transitions", "//ios/chrome/browser/ui/tab_grid/transitions",
"//ios/chrome/browser/ui/table_view:styler", "//ios/chrome/browser/ui/table_view:styler",
......
...@@ -47,7 +47,9 @@ source_set("grid_ui") { ...@@ -47,7 +47,9 @@ source_set("grid_ui") {
"//ios/chrome/browser/drag_and_drop", "//ios/chrome/browser/drag_and_drop",
"//ios/chrome/browser/ui:feature_flags", "//ios/chrome/browser/ui:feature_flags",
"//ios/chrome/browser/ui/elements", "//ios/chrome/browser/ui/elements",
"//ios/chrome/browser/ui/gestures",
"//ios/chrome/browser/ui/tab_grid/transitions", "//ios/chrome/browser/ui/tab_grid/transitions",
"//ios/chrome/browser/ui/thumb_strip:feature_flags",
"//ios/chrome/browser/ui/util", "//ios/chrome/browser/ui/util",
"//ios/chrome/common/ui/colors", "//ios/chrome/common/ui/colors",
"//ios/chrome/common/ui/util", "//ios/chrome/common/ui/util",
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/gestures/layout_switcher.h"
#import "ios/chrome/browser/ui/tab_grid/grid/grid_consumer.h" #import "ios/chrome/browser/ui/tab_grid/grid/grid_consumer.h"
#import "ios/chrome/browser/ui/tab_grid/grid/grid_theme.h" #import "ios/chrome/browser/ui/tab_grid/grid/grid_theme.h"
...@@ -38,7 +39,7 @@ ...@@ -38,7 +39,7 @@
@end @end
// A view controller that contains a grid of items. // A view controller that contains a grid of items.
@interface GridViewController : UIViewController<GridConsumer> @interface GridViewController : UIViewController <GridConsumer, LayoutSwitcher>
// The gridView is accessible to manage the content inset behavior. // The gridView is accessible to manage the content inset behavior.
@property(nonatomic, readonly) UIScrollView* gridView; @property(nonatomic, readonly) UIScrollView* gridView;
// The view that is shown when there are no items. // The view that is shown when there are no items.
......
...@@ -21,8 +21,10 @@ ...@@ -21,8 +21,10 @@
#import "ios/chrome/browser/ui/tab_grid/grid/grid_image_data_source.h" #import "ios/chrome/browser/ui/tab_grid/grid/grid_image_data_source.h"
#import "ios/chrome/browser/ui/tab_grid/grid/grid_item.h" #import "ios/chrome/browser/ui/tab_grid/grid/grid_item.h"
#import "ios/chrome/browser/ui/tab_grid/grid/grid_layout.h" #import "ios/chrome/browser/ui/tab_grid/grid/grid_layout.h"
#import "ios/chrome/browser/ui/tab_grid/grid/horizontal_layout.h"
#import "ios/chrome/browser/ui/tab_grid/grid/tab_switcher_layout.h" #import "ios/chrome/browser/ui/tab_grid/grid/tab_switcher_layout.h"
#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_layout.h" #import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_layout.h"
#import "ios/chrome/browser/ui/thumb_strip/thumb_strip_feature.h"
#include "ios/chrome/browser/ui/ui_feature_flags.h" #include "ios/chrome/browser/ui/ui_feature_flags.h"
#import "ios/chrome/browser/ui/util/uikit_ui_util.h" #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h" #import "ios/chrome/common/ui/util/constraints_ui_util.h"
...@@ -74,6 +76,10 @@ NSIndexPath* CreateIndexPath(NSInteger index) { ...@@ -74,6 +76,10 @@ NSIndexPath* CreateIndexPath(NSInteger index) {
@property(nonatomic, strong) UIViewPropertyAnimator* emptyStateAnimator; @property(nonatomic, strong) UIViewPropertyAnimator* emptyStateAnimator;
// The default layout for the tab switcher. // The default layout for the tab switcher.
@property(nonatomic, strong) TabSwitcherLayout* defaultLayout; @property(nonatomic, strong) TabSwitcherLayout* defaultLayout;
// The layout for the tab grid.
@property(nonatomic, strong) GridLayout* gridLayout;
// The layout for the thumb strip.
@property(nonatomic, strong) HorizontalLayout* horizontalLayout;
// The layout used while the grid is being reordered. // The layout used while the grid is being reordered.
@property(nonatomic, strong) UICollectionViewLayout* reorderingLayout; @property(nonatomic, strong) UICollectionViewLayout* reorderingLayout;
// YES if, when reordering is enabled, the order of the cells has changed. // YES if, when reordering is enabled, the order of the cells has changed.
...@@ -86,6 +92,10 @@ NSIndexPath* CreateIndexPath(NSInteger index) { ...@@ -86,6 +92,10 @@ NSIndexPath* CreateIndexPath(NSInteger index) {
NSHashTable<UICollectionViewCell*>* pointerInteractionCells API_AVAILABLE( NSHashTable<UICollectionViewCell*>* pointerInteractionCells API_AVAILABLE(
ios(13.4)); ios(13.4));
#endif // defined(__IPHONE_13_4) #endif // defined(__IPHONE_13_4)
// The transition layout either from grid to horizontal layout or from
// horizontal to grid layout.
@property(nonatomic, strong)
UICollectionViewTransitionLayout* gridHorizontalTransitionLayout;
@end @end
@implementation GridViewController @implementation GridViewController
...@@ -101,7 +111,14 @@ NSIndexPath* CreateIndexPath(NSInteger index) { ...@@ -101,7 +111,14 @@ NSIndexPath* CreateIndexPath(NSInteger index) {
#pragma mark - UIViewController #pragma mark - UIViewController
- (void)loadView { - (void)loadView {
self.defaultLayout = [[GridLayout alloc] init]; self.horizontalLayout = [[HorizontalLayout alloc] init];
self.gridLayout = [[GridLayout alloc] init];
if (IsThumbStripEnabled()) {
self.defaultLayout = self.horizontalLayout;
} else {
self.defaultLayout = self.gridLayout;
}
UICollectionView* collectionView = UICollectionView* collectionView =
[[UICollectionView alloc] initWithFrame:CGRectZero [[UICollectionView alloc] initWithFrame:CGRectZero
collectionViewLayout:self.defaultLayout]; collectionViewLayout:self.defaultLayout];
...@@ -644,6 +661,42 @@ NSIndexPath* CreateIndexPath(NSInteger index) { ...@@ -644,6 +661,42 @@ NSIndexPath* CreateIndexPath(NSInteger index) {
collectionViewUpdatesCompletion:completion]; collectionViewUpdatesCompletion:completion];
} }
#pragma mark - LayoutSwitcher
- (void)willTransitionToLayout:(LayoutSwitcherState)nextState {
auto completionBlock = ^(BOOL completed, BOOL finished) {
self.collectionView.scrollEnabled = YES;
};
switch (nextState) {
case LayoutSwitcherState::Horizontal:
self.gridHorizontalTransitionLayout = [self.collectionView
startInteractiveTransitionToCollectionViewLayout:self.horizontalLayout
completion:completionBlock];
break;
case LayoutSwitcherState::Full:
self.gridHorizontalTransitionLayout = [self.collectionView
startInteractiveTransitionToCollectionViewLayout:self.gridLayout
completion:completionBlock];
break;
}
// Stops collectionView scrolling when the animation starts.
[self.collectionView setContentOffset:self.collectionView.contentOffset
animated:NO];
}
- (void)didUpdateTransitionLayoutProgress:(CGFloat)progress {
self.gridHorizontalTransitionLayout.transitionProgress = progress;
}
- (void)didTransitionToLayoutSuccessfully:(BOOL)success {
if (success) {
[self.collectionView finishInteractiveTransition];
} else {
[self.collectionView cancelInteractiveTransition];
}
}
#pragma mark - Private properties #pragma mark - Private properties
- (NSUInteger)selectedIndex { - (NSUInteger)selectedIndex {
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#import "ios/chrome/browser/ui/commands/browsing_data_commands.h" #import "ios/chrome/browser/ui/commands/browsing_data_commands.h"
#import "ios/chrome/browser/ui/commands/command_dispatcher.h" #import "ios/chrome/browser/ui/commands/command_dispatcher.h"
#import "ios/chrome/browser/ui/commands/open_new_tab_command.h" #import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
#import "ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.h"
#import "ios/chrome/browser/ui/history/history_coordinator.h" #import "ios/chrome/browser/ui/history/history_coordinator.h"
#import "ios/chrome/browser/ui/history/public/history_presentation_delegate.h" #import "ios/chrome/browser/ui/history/public/history_presentation_delegate.h"
#import "ios/chrome/browser/ui/main/bvc_container_view_controller.h" #import "ios/chrome/browser/ui/main/bvc_container_view_controller.h"
...@@ -348,6 +349,8 @@ ...@@ -348,6 +349,8 @@
initWithBaseViewController:baseViewController initWithBaseViewController:baseViewController
browser:self.browser]; browser:self.browser];
[self.thumbStripCoordinator start]; [self.thumbStripCoordinator start];
self.thumbStripCoordinator.panHandler.layoutSwitcherProvider =
baseViewController;
[self setUpThumbStripAttachers]; [self setUpThumbStripAttachers];
} }
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/gestures/layout_switcher_provider.h"
#import "ios/chrome/browser/ui/tab_grid/tab_grid_paging.h" #import "ios/chrome/browser/ui/tab_grid/tab_grid_paging.h"
#import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_animation_layout_providing.h" #import "ios/chrome/browser/ui/tab_grid/transitions/grid_transition_animation_layout_providing.h"
...@@ -29,7 +30,9 @@ ...@@ -29,7 +30,9 @@
// View controller representing a tab switcher. The tab switcher has an // View controller representing a tab switcher. The tab switcher has an
// incognito tab grid, regular tab grid, and remote tabs. // incognito tab grid, regular tab grid, and remote tabs.
@interface TabGridViewController @interface TabGridViewController
: UIViewController <TabGridPaging, GridTransitionAnimationLayoutProviding> : UIViewController <GridTransitionAnimationLayoutProviding,
LayoutSwitcherProvider,
TabGridPaging>
@property(nonatomic, weak) id<ApplicationCommands> handler; @property(nonatomic, weak) id<ApplicationCommands> handler;
......
...@@ -428,6 +428,12 @@ NSUInteger GetPageIndexFromPage(TabGridPage page) { ...@@ -428,6 +428,12 @@ NSUInteger GetPageIndexFromPage(TabGridPage page) {
_activePage = activePage; _activePage = activePage;
} }
#pragma mark - LayoutSwitcherProvider
- (id<LayoutSwitcher>)layoutSwitcher {
return [self gridViewControllerForPage:self.activePage];
}
#pragma mark - Private #pragma mark - Private
// Sets the proper insets for the Remote Tabs ViewController to accomodate for // Sets the proper insets for the Remote Tabs ViewController to accomodate for
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment