Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(429)

Unified Diff: ios/chrome/browser/ui/stack_view/card_set.mm

Issue 2587023002: Upstream Chrome on iOS source code [8/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ios/chrome/browser/ui/stack_view/card_set.h ('k') | ios/chrome/browser/ui/stack_view/card_set_unittest.mm » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ios/chrome/browser/ui/stack_view/card_set.mm
diff --git a/ios/chrome/browser/ui/stack_view/card_set.mm b/ios/chrome/browser/ui/stack_view/card_set.mm
new file mode 100644
index 0000000000000000000000000000000000000000..67617e44aa16e96ffe327399377cb7f2c742ac3e
--- /dev/null
+++ b/ios/chrome/browser/ui/stack_view/card_set.mm
@@ -0,0 +1,598 @@
+// 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/stack_view/card_set.h"
+
+#import <QuartzCore/QuartzCore.h>
+
+#include "base/logging.h"
+#import "base/mac/scoped_nsobject.h"
+#import "ios/chrome/browser/tabs/tab.h"
+#import "ios/chrome/browser/tabs/tab_model.h"
+#include "ios/chrome/browser/ui/rtl_geometry.h"
+#import "ios/chrome/browser/ui/stack_view/card_stack_layout_manager.h"
+#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
+#import "ios/chrome/browser/ui/stack_view/stack_card.h"
+#include "ios/chrome/browser/ui/ui_util.h"
+#import "ios/web/web_state/ui/crw_web_controller.h"
+
+namespace {
+const CGFloat kMaxCardStaggerPercentage = 0.35;
+}
+
+@interface CardSet ()<StackCardViewProvider, TabModelObserver> {
+ base::scoped_nsobject<TabModel> tabModel_;
+ base::scoped_nsobject<UIView> view_;
+ base::scoped_nsobject<CardStackLayoutManager> stackModel_;
+ base::scoped_nsobject<UIImageView> stackShadow_;
+}
+
+// Set to |YES| when the card set should draw a shadow around the entire stack.
+@property(nonatomic, assign) BOOL shouldShowShadow;
+
+// Creates and returns an autoreleased StackCard from the given |tab| (which
+// must not be nil).
+- (StackCard*)buildCardFromTab:(Tab*)tab;
+
+// Rebuilds the set of cards from the current state of the tab model.
+- (void)rebuildCards;
+
+// Makes |card| visible (or in the view hierarchy but hidden if it's covered
+// by other cards) in the current display view at the right z-order relative
+// to any other cards from the set that are already displayed.
+- (void)displayCard:(StackCard*)card;
+
+// Updates the tab display side of the cards in the set based on the current
+// layout orientation.
+- (void)updateCardTabs;
+
+@end
+
+#pragma mark -
+
+@implementation CardSet
+
+@synthesize observer = observer_;
+@synthesize ignoresTabModelChanges = ignoresTabModelChanges_;
+@synthesize defersCardHiding = defersCardHiding_;
+@synthesize keepOnlyVisibleCardViewsAlive = keepOnlyVisibleCardViewsAlive_;
+@synthesize shouldShowShadow = shouldShowShadow_;
+@synthesize closingCard = closingCard_;
+
+- (CardStackLayoutManager*)stackModel {
+ return stackModel_.get();
+}
+
+- (id)initWithModel:(TabModel*)model {
+ if ((self = [super init])) {
+ tabModel_.reset([model retain]);
+ [tabModel_ addObserver:self];
+ stackModel_.reset([[CardStackLayoutManager alloc] init]);
+ [self rebuildCards];
+ self.shouldShowShadow = YES;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [tabModel_ removeObserver:self];
+ [super dealloc];
+}
+
+#pragma mark Properties
+
+- (TabModel*)tabModel {
+ return tabModel_;
+}
+
+- (void)setTabModel:(TabModel*)tabModel {
+ DCHECK([tabModel count] == 0);
+ DCHECK([tabModel_ count] == 0);
+ [tabModel_ removeObserver:self];
+ tabModel_.reset([tabModel retain]);
+ if (!ignoresTabModelChanges_)
+ [tabModel_ addObserver:self];
+}
+
+- (NSArray*)cards {
+ return [stackModel_ cards];
+}
+
+- (StackCard*)currentCard {
+ DCHECK(!ignoresTabModelChanges_);
+ Tab* currentTab = [tabModel_ currentTab];
+ if (!currentTab)
+ return nil;
+ NSUInteger currentTabIndex = [tabModel_ indexOfTab:currentTab];
+ // There is a period of time during closing the current tab where currentTab
+ // is still the closed tab, but that tab is no longer *in* the model.
+ // TODO(stuartmorgan): Fix this in TabModel; this is dumb.
+ if (currentTabIndex == NSNotFound)
+ return nil;
+ DCHECK(currentTabIndex < [self.cards count]);
+ return [self.cards objectAtIndex:currentTabIndex];
+}
+
+- (void)setCurrentCard:(StackCard*)card {
+ DCHECK(!ignoresTabModelChanges_);
+ NSInteger cardIndex = [self.cards indexOfObject:card];
+ DCHECK(cardIndex != NSNotFound);
+ [tabModel_ setCurrentTab:[tabModel_ tabAtIndex:cardIndex]];
+}
+
+- (UIView*)displayView {
+ return view_.get();
+}
+
+- (void)setDisplayView:(UIView*)view {
+ if (view == view_.get())
+ return;
+ for (StackCard* card in self.cards) {
+ if (card.viewIsLive) {
+ [card.view removeFromSuperview];
+ [card releaseView];
+ }
+ }
+ [stackShadow_ removeFromSuperview];
+ view_.reset([view retain]);
+ // Add the stack shadow view to the new display view.
+ if (!stackShadow_) {
+ UIImage* shadowImage = [UIImage imageNamed:kCardShadowImageName];
+ shadowImage = [shadowImage
+ resizableImageWithCapInsets:UIEdgeInsetsMake(
+ shadowImage.size.height / 2.0,
+ shadowImage.size.width / 2.0,
+ shadowImage.size.height / 2.0,
+ shadowImage.size.width / 2.0)];
+ stackShadow_.reset([[UIImageView alloc] initWithImage:shadowImage]);
+ [stackShadow_ setHidden:!self.cards.count];
+ }
+ [view_ addSubview:stackShadow_];
+ // Don't set the stack's end limit when the view is set to nil in order to
+ // avoid losing existing card positions; these positions will be needed
+ // if/when the view is restored (e.g., if the view was purged due to a memory
+ // warning while in a modal view and then restored when exiting the modal
+ // view).
+ if (view_.get())
+ [self displayViewSizeWasChanged];
+}
+
+- (CardCloseButtonSide)closeButtonSide {
+ return [stackModel_ layoutIsVertical] ? CardCloseButtonSide::TRAILING
+ : CardCloseButtonSide::LEADING;
+}
+
+- (void)setIgnoresTabModelChanges:(BOOL)ignoresTabModelChanges {
+ if (ignoresTabModelChanges_ == ignoresTabModelChanges)
+ return;
+ ignoresTabModelChanges_ = ignoresTabModelChanges;
+ if (ignoresTabModelChanges) {
+ [tabModel_ removeObserver:self];
+ } else {
+ [self rebuildCards];
+ [tabModel_ addObserver:self];
+ }
+}
+
+- (void)setDefersCardHiding:(BOOL)defersCardHiding {
+ if (defersCardHiding_ == defersCardHiding)
+ return;
+ defersCardHiding_ = defersCardHiding;
+ if (!defersCardHiding_)
+ [self updateCardVisibilities];
+}
+
+- (CGFloat)maximumOverextensionAmount {
+ return [stackModel_ maximumOverextensionAmount];
+}
+
+- (void)setMaximumOverextensionAmount:(CGFloat)amount {
+ [stackModel_ setMaximumOverextensionAmount:amount];
+}
+
+- (void)setKeepOnlyVisibleCardViewsAlive:(BOOL)keepAlive {
+ if (keepOnlyVisibleCardViewsAlive_ == keepAlive)
+ return;
+ keepOnlyVisibleCardViewsAlive_ = keepAlive;
+ if (!keepOnlyVisibleCardViewsAlive_)
+ [self updateCardVisibilities];
+}
+
+- (void)setShouldShowShadow:(BOOL)shouldShowShadow {
+ if (shouldShowShadow_ != shouldShowShadow) {
+ shouldShowShadow_ = shouldShowShadow;
+ [stackShadow_ setHidden:!shouldShowShadow_];
+ }
+}
+
+- (void)setClosingCard:(StackCard*)closingCard {
+ if (closingCard_ != closingCard) {
+ closingCard_ = closingCard;
+ if (closingCard) {
+ self.shouldShowShadow = self.cards.count > 1;
+ [self updateShadowLayout];
+ closingCard.view.shouldShowShadow = YES;
+ closingCard.view.shouldMaskShadow = NO;
+ StackCard* nextVisibleCard = nil;
+ NSUInteger nextVisibleCardIdx = [self.cards indexOfObject:closingCard];
+ while (++nextVisibleCardIdx < self.cards.count) {
+ nextVisibleCard = self.cards[nextVisibleCardIdx];
+ if ([nextVisibleCard viewIsLive] && !nextVisibleCard.view.hidden)
+ break;
+ }
+ nextVisibleCard.view.shouldShowShadow = YES;
+ nextVisibleCard.view.shouldMaskShadow = NO;
+ } else {
+ self.shouldShowShadow = YES;
+ [self updateCardVisibilities];
+ }
+ }
+}
+
+#pragma mark Public Methods
+
+- (void)configureLayoutParametersWithMargin:(CGFloat)margin {
+ DCHECK(view_);
+
+ [stackModel_ setStartLimit:margin];
+
+ BOOL isVertical = [stackModel_ layoutIsVertical];
+ CGSize cardSize = [stackModel_ cardSize];
+ CGFloat cardLength = isVertical ? cardSize.height : cardSize.width;
+ [stackModel_ setMaxStagger:(kMaxCardStaggerPercentage * cardLength)];
+}
+
+- (void)displayViewSizeWasChanged {
+ for (StackCard* card in self.cards) {
+ LayoutRect layout = card.layout;
+ layout.boundingWidth = CGRectGetWidth(self.displayView.bounds);
+ card.layout = layout;
+ }
+ CGFloat endLimit = [stackModel_ layoutIsVertical] ? [view_ bounds].size.height
+ : [view_ bounds].size.width;
+ [stackModel_ setEndLimit:endLimit];
+}
+
+- (void)setCardSize:(CGSize)cardSize {
+ [stackModel_ setCardSize:cardSize];
+}
+
+- (void)setLayoutAxisPosition:(CGFloat)position
+ isVertical:(BOOL)layoutIsVertical {
+ [stackModel_ setLayoutIsVertical:layoutIsVertical];
+ [stackModel_ setLayoutAxisPosition:position];
+ [self updateCardTabs];
+ [self updateShadowLayout];
+}
+
+- (void)layOutStartStack {
+ [stackModel_ layOutStartStack];
+ [self updateCardVisibilities];
+}
+
+- (void)fanOutCards {
+ if ([self.cards count] == 0)
+ return;
+ [self fanOutCardsWithStartIndex:0];
+}
+
+- (void)fanOutCardsWithStartIndex:(NSUInteger)startIndex {
+ if (![self.cards count])
+ return;
+ DCHECK(startIndex < [self.cards count]);
+ [stackModel_ fanOutCardsWithStartIndex:startIndex];
+ [self updateCardVisibilities];
+}
+
+- (std::vector<LayoutRect>)cardLayouts {
+ std::vector<LayoutRect> cardLayouts;
+ for (StackCard* card in self.cards)
+ cardLayouts.push_back(card.layout);
+ return cardLayouts;
+}
+
+- (void)scrollCardAtIndex:(NSUInteger)index
+ byDelta:(CGFloat)delta
+ allowEarlyOverscroll:(BOOL)allowEarlyOverscroll
+ decayOnOverscroll:(BOOL)decayOnOverscroll
+ scrollLeadingCards:(BOOL)scrollLeadingCards {
+ DCHECK(index < [self.cards count]);
+ [stackModel_ scrollCardAtIndex:index
+ byDelta:delta
+ allowEarlyOverscroll:allowEarlyOverscroll
+ decayOnOverscroll:decayOnOverscroll
+ scrollLeadingCards:scrollLeadingCards];
+ [self updateCardVisibilities];
+}
+
+- (BOOL)stackIsOverextended {
+ if ([self.cards count] == 0)
+ return NO;
+ return ([self overextensionOnCardAtIndex:0]);
+}
+
+- (BOOL)overextensionOnCardAtIndex:(NSUInteger)index {
+ DCHECK(index < [self.cards count]);
+ if ([self overextensionTowardStartOnCardAtIndex:index])
+ return YES;
+ if ((index == 0) && [stackModel_ overextensionTowardEndOnFirstCard])
+ return YES;
+ return NO;
+}
+
+- (BOOL)overextensionTowardStartOnCardAtIndex:(NSUInteger)index {
+ DCHECK(index < [self.cards count]);
+ return [stackModel_ overextensionTowardStartOnCardAtIndex:index];
+}
+
+- (void)eliminateOverextension {
+ [stackModel_ eliminateOverextension];
+ [self updateCardVisibilities];
+}
+
+- (void)scrollCardAtIndex:(NSUInteger)index awayFromNeighbor:(BOOL)preceding {
+ DCHECK(index < [self.cards count]);
+ [stackModel_ scrollCardAtIndex:index awayFromNeighbor:preceding];
+ [self updateCardVisibilities];
+}
+
+- (void)updateCardVisibilities {
+ BOOL shouldHideNextVisibleCardShadow = YES;
+ for (StackCard* card in self.cards) {
+ if ([stackModel_ cardIsCovered:card]) {
+ if (card.viewIsLive && !defersCardHiding_) {
+ if (keepOnlyVisibleCardViewsAlive_) {
+ [card.view removeFromSuperview];
+ [card releaseView];
+ } else {
+ card.view.hidden = YES;
+ }
+ }
+ } else {
+ [self displayCard:card];
+ // Hide the first visible card's shadow.
+ card.view.shouldShowShadow = !shouldHideNextVisibleCardShadow;
+ if (shouldHideNextVisibleCardShadow)
+ shouldHideNextVisibleCardShadow = NO;
+ card.view.shouldMaskShadow = card.view.shouldShowShadow;
+ }
+ }
+ if (self.shouldShowShadow)
+ [self updateShadowLayout];
+ // Updates VoiceOver with currently accessible elements.
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification,
+ nil);
+}
+
+- (BOOL)preloadNextCard {
+ if (keepOnlyVisibleCardViewsAlive_)
+ return NO;
+ // Find the next card to preload.
+ StackCard* nextCard = nil;
+ for (nextCard in self.cards) {
+ // TODO(stuartmorgan): Change the selection here to favor the cards that
+ // are closest to becoming visible.
+ if (!nextCard.viewIsLive)
+ break;
+ }
+ // If there was one, preload it.
+ if (nextCard) {
+ // Visible card views should have already been synchronously loaded.
+ DCHECK([stackModel_ cardIsCovered:nextCard]);
+ [self displayCard:nextCard];
+ }
+ return nextCard != nil;
+}
+
+- (void)clearGestureRecognizerTargetAndDelegateFromCards:(id)object {
+ for (StackCard* card in self.cards) {
+ // Ensure that views aren't created just to remove their recognizers.
+ if (!card.viewIsLive)
+ continue;
+ for (UIGestureRecognizer* recognizer in card.view.gestureRecognizers) {
+ if (recognizer.delegate == object)
+ recognizer.delegate = nil;
+ // Passing NULL as the value of the |action| parameter causes all actions
+ // associated with this target to be removed. Note that if |object| is
+ // not a target of |recognizer| this method is a no-op.
+ [recognizer removeTarget:object action:NULL];
+ }
+ }
+}
+
+- (void)removeCardAtIndex:(NSUInteger)index {
+ DCHECK(index < [self.cards count]);
+ StackCard* card = [self.cards objectAtIndex:index];
+ [[card retain] autorelease];
+ [self.observer cardSet:self willRemoveCard:card atIndex:index];
+ [stackModel_ removeCard:card];
+
+ [self.observer cardSet:self didRemoveCard:card atIndex:index];
+}
+
+#pragma mark Card Construction/Display
+
+- (StackCard*)buildCardFromTab:(Tab*)tab {
+ DCHECK(tab);
+ StackCard* card = [[[StackCard alloc] initWithViewProvider:self] autorelease];
+ card.size = [stackModel_ cardSize];
+ card.tabID = reinterpret_cast<NSUInteger>(tab);
+
+ return card;
+}
+
+- (void)rebuildCards {
+ [stackModel_ removeAllCards];
+
+ for (Tab* tab in tabModel_.get()) {
+ StackCard* card = [self buildCardFromTab:tab];
+ [stackModel_ addCard:card];
+ }
+
+ [self.observer cardSetRecreatedCards:self];
+}
+
+- (void)displayCard:(StackCard*)card {
+ DCHECK(view_);
+ card.view.hidden = [stackModel_ cardIsCovered:card];
+
+ if (card.view.superview)
+ return;
+ // Find the card view (if any) that's above the card to add and already in the
+ // view.
+ StackCard* cardAboveNewCard = nil;
+ NSUInteger indexOfCard = [self.cards indexOfObject:card];
+ DCHECK(indexOfCard != NSNotFound);
+ for (NSUInteger i = indexOfCard + 1; i < [self.cards count]; ++i) {
+ StackCard* nextCard = [self.cards objectAtIndex:i];
+ if (nextCard.viewIsLive && nextCard.view.superview) {
+ cardAboveNewCard = nextCard;
+ break;
+ }
+ }
+ if (cardAboveNewCard)
+ [view_ insertSubview:card.view belowSubview:cardAboveNewCard.view];
+ else
+ [view_ addSubview:card.view];
+
+ LayoutRect layout = card.layout;
+ layout.boundingWidth = CGRectGetWidth([view_ bounds]);
+ card.layout = layout;
+
+ [self.observer cardSet:self displayedCard:card];
+}
+
+#pragma mark Deck Management
+
+- (void)updateCardTabs {
+ CardCloseButtonSide closeButtonSide = self.closeButtonSide;
+ for (StackCard* card in self.cards) {
+ if (card.viewIsLive)
+ card.view.closeButtonSide = closeButtonSide;
+ }
+}
+
+#pragma mark -
+#pragma mark StackCardViewProvider Methods
+
+- (CardView*)cardViewWithFrame:(CGRect)frame forStackCard:(StackCard*)card {
+ DCHECK(!ignoresTabModelChanges_);
+ NSUInteger cardIndex = [self.cards indexOfObject:card];
+ DCHECK(cardIndex != NSNotFound);
+ Tab* tab = [tabModel_ tabAtIndex:cardIndex];
+ DCHECK(tab);
+ NSString* title = tab.title;
+ if (![title length])
+ title = tab.urlDisplayString;
+ CardView* view =
+ [[[CardView alloc] initWithFrame:frame
+ isIncognito:[tabModel_ isOffTheRecord]] autorelease];
+ [view setTitle:title];
+ [view setFavicon:[tab favicon]];
+ [tab retrieveSnapshot:^(UIImage* image) {
+ [view setImage:image];
+ }];
+ if (!view.image)
+ [view setImage:[CRWWebController defaultSnapshotImage]];
+ view.closeButtonSide = self.closeButtonSide;
+
+ return view;
+}
+
+#pragma mark TabModelObserver Methods
+
+- (void)tabModel:(TabModel*)model
+ didInsertTab:(Tab*)tab
+ atIndex:(NSUInteger)index
+ inForeground:(BOOL)fg {
+ DCHECK(model == tabModel_);
+ StackCard* newCard = [self buildCardFromTab:tab];
+ [stackModel_ insertCard:newCard atIndex:index];
+ [self.observer cardSet:self didAddCard:newCard];
+}
+
+// A tab was removed at the given index.
+- (void)tabModel:(TabModel*)model
+ didRemoveTab:(Tab*)tab
+ atIndex:(NSUInteger)index {
+ [self removeCardAtIndex:index];
+}
+
+- (CGFloat)maximumStackLength {
+ return [stackModel_ maximumStackLength];
+}
+
+- (BOOL)cardIsCollapsed:(StackCard*)card {
+ return [stackModel_ cardIsCollapsed:card];
+}
+
+- (BOOL)stackIsFullyCollapsed {
+ return [stackModel_ stackIsFullyCollapsed];
+}
+
+- (BOOL)stackIsFullyFannedOut {
+ return [stackModel_ stackIsFullyFannedOut];
+}
+
+- (BOOL)stackIsFullyOverextended {
+ return [stackModel_ stackIsFullyOverextended];
+}
+
+- (CGFloat)overextensionAmount {
+ return [stackModel_ overextensionAmount];
+}
+
+- (BOOL)isCardInStartStaggerRegion:(StackCard*)card {
+ NSInteger cardIndex = [self.cards indexOfObject:card];
+ DCHECK(cardIndex != NSNotFound);
+ // The last card in the start stack is not actually collapsed, thus the -1.
+ NSInteger indexOfLastCardInStartStaggerRegion =
+ [stackModel_ lastStartStackCardIndex] - 1;
+ return (cardIndex <= indexOfLastCardInStartStaggerRegion);
+}
+
+- (BOOL)isCardInEndStaggerRegion:(StackCard*)card {
+ NSInteger cardIndex = [self.cards indexOfObject:card];
+ DCHECK(cardIndex != NSNotFound);
+ NSInteger indexOfFirstCardInEndStaggerRegion =
+ [stackModel_ firstEndStackCardIndex];
+ return (indexOfFirstCardInEndStaggerRegion != -1 &&
+ cardIndex >= indexOfFirstCardInEndStaggerRegion);
+}
+
+- (void)updateShadowLayout {
+ CGRect stackFrame = CGRectNull;
+ for (StackCard* card in self.cards) {
+ if (![card isEqual:self.closingCard]) {
+ CGRect cardFrame =
+ AlignRectOriginAndSizeToPixels(LayoutRectGetRect(card.layout));
+ stackFrame = CGRectIsNull(stackFrame)
+ ? cardFrame
+ : CGRectUnion(stackFrame, cardFrame);
+ }
+ }
+ [stackShadow_
+ setHidden:CGRectIsNull(stackFrame) || CGRectIsEmpty(stackFrame)];
+ if (![stackShadow_ isHidden]) {
+ [stackShadow_
+ setFrame:UIEdgeInsetsInsetRect(stackFrame, kCardShadowLayoutOutsets)];
+ }
+}
+
+@end
+
+@implementation CardSet (Testing)
+
+- (StackCard*)cardForTab:(Tab*)tab {
+ NSUInteger tabIndex = [tabModel_ indexOfTab:tab];
+ if (tabIndex == NSNotFound)
+ return nil;
+ return [self.cards objectAtIndex:tabIndex];
+}
+
+- (void)setStackModelForTesting:(CardStackLayoutManager*)stackModel {
+ stackModel_.reset([stackModel retain]);
+}
+
+@end
« no previous file with comments | « ios/chrome/browser/ui/stack_view/card_set.h ('k') | ios/chrome/browser/ui/stack_view/card_set_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698