OLD | NEW |
(Empty) | |
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/ui/stack_view/card_set.h" |
| 6 |
| 7 #import <QuartzCore/QuartzCore.h> |
| 8 |
| 9 #include "base/logging.h" |
| 10 #import "base/mac/scoped_nsobject.h" |
| 11 #import "ios/chrome/browser/tabs/tab.h" |
| 12 #import "ios/chrome/browser/tabs/tab_model.h" |
| 13 #include "ios/chrome/browser/ui/rtl_geometry.h" |
| 14 #import "ios/chrome/browser/ui/stack_view/card_stack_layout_manager.h" |
| 15 #import "ios/chrome/browser/ui/stack_view/page_animation_util.h" |
| 16 #import "ios/chrome/browser/ui/stack_view/stack_card.h" |
| 17 #include "ios/chrome/browser/ui/ui_util.h" |
| 18 #import "ios/web/web_state/ui/crw_web_controller.h" |
| 19 |
| 20 namespace { |
| 21 const CGFloat kMaxCardStaggerPercentage = 0.35; |
| 22 } |
| 23 |
| 24 @interface CardSet ()<StackCardViewProvider, TabModelObserver> { |
| 25 base::scoped_nsobject<TabModel> tabModel_; |
| 26 base::scoped_nsobject<UIView> view_; |
| 27 base::scoped_nsobject<CardStackLayoutManager> stackModel_; |
| 28 base::scoped_nsobject<UIImageView> stackShadow_; |
| 29 } |
| 30 |
| 31 // Set to |YES| when the card set should draw a shadow around the entire stack. |
| 32 @property(nonatomic, assign) BOOL shouldShowShadow; |
| 33 |
| 34 // Creates and returns an autoreleased StackCard from the given |tab| (which |
| 35 // must not be nil). |
| 36 - (StackCard*)buildCardFromTab:(Tab*)tab; |
| 37 |
| 38 // Rebuilds the set of cards from the current state of the tab model. |
| 39 - (void)rebuildCards; |
| 40 |
| 41 // Makes |card| visible (or in the view hierarchy but hidden if it's covered |
| 42 // by other cards) in the current display view at the right z-order relative |
| 43 // to any other cards from the set that are already displayed. |
| 44 - (void)displayCard:(StackCard*)card; |
| 45 |
| 46 // Updates the tab display side of the cards in the set based on the current |
| 47 // layout orientation. |
| 48 - (void)updateCardTabs; |
| 49 |
| 50 @end |
| 51 |
| 52 #pragma mark - |
| 53 |
| 54 @implementation CardSet |
| 55 |
| 56 @synthesize observer = observer_; |
| 57 @synthesize ignoresTabModelChanges = ignoresTabModelChanges_; |
| 58 @synthesize defersCardHiding = defersCardHiding_; |
| 59 @synthesize keepOnlyVisibleCardViewsAlive = keepOnlyVisibleCardViewsAlive_; |
| 60 @synthesize shouldShowShadow = shouldShowShadow_; |
| 61 @synthesize closingCard = closingCard_; |
| 62 |
| 63 - (CardStackLayoutManager*)stackModel { |
| 64 return stackModel_.get(); |
| 65 } |
| 66 |
| 67 - (id)initWithModel:(TabModel*)model { |
| 68 if ((self = [super init])) { |
| 69 tabModel_.reset([model retain]); |
| 70 [tabModel_ addObserver:self]; |
| 71 stackModel_.reset([[CardStackLayoutManager alloc] init]); |
| 72 [self rebuildCards]; |
| 73 self.shouldShowShadow = YES; |
| 74 } |
| 75 return self; |
| 76 } |
| 77 |
| 78 - (void)dealloc { |
| 79 [tabModel_ removeObserver:self]; |
| 80 [super dealloc]; |
| 81 } |
| 82 |
| 83 #pragma mark Properties |
| 84 |
| 85 - (TabModel*)tabModel { |
| 86 return tabModel_; |
| 87 } |
| 88 |
| 89 - (void)setTabModel:(TabModel*)tabModel { |
| 90 DCHECK([tabModel count] == 0); |
| 91 DCHECK([tabModel_ count] == 0); |
| 92 [tabModel_ removeObserver:self]; |
| 93 tabModel_.reset([tabModel retain]); |
| 94 if (!ignoresTabModelChanges_) |
| 95 [tabModel_ addObserver:self]; |
| 96 } |
| 97 |
| 98 - (NSArray*)cards { |
| 99 return [stackModel_ cards]; |
| 100 } |
| 101 |
| 102 - (StackCard*)currentCard { |
| 103 DCHECK(!ignoresTabModelChanges_); |
| 104 Tab* currentTab = [tabModel_ currentTab]; |
| 105 if (!currentTab) |
| 106 return nil; |
| 107 NSUInteger currentTabIndex = [tabModel_ indexOfTab:currentTab]; |
| 108 // There is a period of time during closing the current tab where currentTab |
| 109 // is still the closed tab, but that tab is no longer *in* the model. |
| 110 // TODO(stuartmorgan): Fix this in TabModel; this is dumb. |
| 111 if (currentTabIndex == NSNotFound) |
| 112 return nil; |
| 113 DCHECK(currentTabIndex < [self.cards count]); |
| 114 return [self.cards objectAtIndex:currentTabIndex]; |
| 115 } |
| 116 |
| 117 - (void)setCurrentCard:(StackCard*)card { |
| 118 DCHECK(!ignoresTabModelChanges_); |
| 119 NSInteger cardIndex = [self.cards indexOfObject:card]; |
| 120 DCHECK(cardIndex != NSNotFound); |
| 121 [tabModel_ setCurrentTab:[tabModel_ tabAtIndex:cardIndex]]; |
| 122 } |
| 123 |
| 124 - (UIView*)displayView { |
| 125 return view_.get(); |
| 126 } |
| 127 |
| 128 - (void)setDisplayView:(UIView*)view { |
| 129 if (view == view_.get()) |
| 130 return; |
| 131 for (StackCard* card in self.cards) { |
| 132 if (card.viewIsLive) { |
| 133 [card.view removeFromSuperview]; |
| 134 [card releaseView]; |
| 135 } |
| 136 } |
| 137 [stackShadow_ removeFromSuperview]; |
| 138 view_.reset([view retain]); |
| 139 // Add the stack shadow view to the new display view. |
| 140 if (!stackShadow_) { |
| 141 UIImage* shadowImage = [UIImage imageNamed:kCardShadowImageName]; |
| 142 shadowImage = [shadowImage |
| 143 resizableImageWithCapInsets:UIEdgeInsetsMake( |
| 144 shadowImage.size.height / 2.0, |
| 145 shadowImage.size.width / 2.0, |
| 146 shadowImage.size.height / 2.0, |
| 147 shadowImage.size.width / 2.0)]; |
| 148 stackShadow_.reset([[UIImageView alloc] initWithImage:shadowImage]); |
| 149 [stackShadow_ setHidden:!self.cards.count]; |
| 150 } |
| 151 [view_ addSubview:stackShadow_]; |
| 152 // Don't set the stack's end limit when the view is set to nil in order to |
| 153 // avoid losing existing card positions; these positions will be needed |
| 154 // if/when the view is restored (e.g., if the view was purged due to a memory |
| 155 // warning while in a modal view and then restored when exiting the modal |
| 156 // view). |
| 157 if (view_.get()) |
| 158 [self displayViewSizeWasChanged]; |
| 159 } |
| 160 |
| 161 - (CardCloseButtonSide)closeButtonSide { |
| 162 return [stackModel_ layoutIsVertical] ? CardCloseButtonSide::TRAILING |
| 163 : CardCloseButtonSide::LEADING; |
| 164 } |
| 165 |
| 166 - (void)setIgnoresTabModelChanges:(BOOL)ignoresTabModelChanges { |
| 167 if (ignoresTabModelChanges_ == ignoresTabModelChanges) |
| 168 return; |
| 169 ignoresTabModelChanges_ = ignoresTabModelChanges; |
| 170 if (ignoresTabModelChanges) { |
| 171 [tabModel_ removeObserver:self]; |
| 172 } else { |
| 173 [self rebuildCards]; |
| 174 [tabModel_ addObserver:self]; |
| 175 } |
| 176 } |
| 177 |
| 178 - (void)setDefersCardHiding:(BOOL)defersCardHiding { |
| 179 if (defersCardHiding_ == defersCardHiding) |
| 180 return; |
| 181 defersCardHiding_ = defersCardHiding; |
| 182 if (!defersCardHiding_) |
| 183 [self updateCardVisibilities]; |
| 184 } |
| 185 |
| 186 - (CGFloat)maximumOverextensionAmount { |
| 187 return [stackModel_ maximumOverextensionAmount]; |
| 188 } |
| 189 |
| 190 - (void)setMaximumOverextensionAmount:(CGFloat)amount { |
| 191 [stackModel_ setMaximumOverextensionAmount:amount]; |
| 192 } |
| 193 |
| 194 - (void)setKeepOnlyVisibleCardViewsAlive:(BOOL)keepAlive { |
| 195 if (keepOnlyVisibleCardViewsAlive_ == keepAlive) |
| 196 return; |
| 197 keepOnlyVisibleCardViewsAlive_ = keepAlive; |
| 198 if (!keepOnlyVisibleCardViewsAlive_) |
| 199 [self updateCardVisibilities]; |
| 200 } |
| 201 |
| 202 - (void)setShouldShowShadow:(BOOL)shouldShowShadow { |
| 203 if (shouldShowShadow_ != shouldShowShadow) { |
| 204 shouldShowShadow_ = shouldShowShadow; |
| 205 [stackShadow_ setHidden:!shouldShowShadow_]; |
| 206 } |
| 207 } |
| 208 |
| 209 - (void)setClosingCard:(StackCard*)closingCard { |
| 210 if (closingCard_ != closingCard) { |
| 211 closingCard_ = closingCard; |
| 212 if (closingCard) { |
| 213 self.shouldShowShadow = self.cards.count > 1; |
| 214 [self updateShadowLayout]; |
| 215 closingCard.view.shouldShowShadow = YES; |
| 216 closingCard.view.shouldMaskShadow = NO; |
| 217 StackCard* nextVisibleCard = nil; |
| 218 NSUInteger nextVisibleCardIdx = [self.cards indexOfObject:closingCard]; |
| 219 while (++nextVisibleCardIdx < self.cards.count) { |
| 220 nextVisibleCard = self.cards[nextVisibleCardIdx]; |
| 221 if ([nextVisibleCard viewIsLive] && !nextVisibleCard.view.hidden) |
| 222 break; |
| 223 } |
| 224 nextVisibleCard.view.shouldShowShadow = YES; |
| 225 nextVisibleCard.view.shouldMaskShadow = NO; |
| 226 } else { |
| 227 self.shouldShowShadow = YES; |
| 228 [self updateCardVisibilities]; |
| 229 } |
| 230 } |
| 231 } |
| 232 |
| 233 #pragma mark Public Methods |
| 234 |
| 235 - (void)configureLayoutParametersWithMargin:(CGFloat)margin { |
| 236 DCHECK(view_); |
| 237 |
| 238 [stackModel_ setStartLimit:margin]; |
| 239 |
| 240 BOOL isVertical = [stackModel_ layoutIsVertical]; |
| 241 CGSize cardSize = [stackModel_ cardSize]; |
| 242 CGFloat cardLength = isVertical ? cardSize.height : cardSize.width; |
| 243 [stackModel_ setMaxStagger:(kMaxCardStaggerPercentage * cardLength)]; |
| 244 } |
| 245 |
| 246 - (void)displayViewSizeWasChanged { |
| 247 for (StackCard* card in self.cards) { |
| 248 LayoutRect layout = card.layout; |
| 249 layout.boundingWidth = CGRectGetWidth(self.displayView.bounds); |
| 250 card.layout = layout; |
| 251 } |
| 252 CGFloat endLimit = [stackModel_ layoutIsVertical] ? [view_ bounds].size.height |
| 253 : [view_ bounds].size.width; |
| 254 [stackModel_ setEndLimit:endLimit]; |
| 255 } |
| 256 |
| 257 - (void)setCardSize:(CGSize)cardSize { |
| 258 [stackModel_ setCardSize:cardSize]; |
| 259 } |
| 260 |
| 261 - (void)setLayoutAxisPosition:(CGFloat)position |
| 262 isVertical:(BOOL)layoutIsVertical { |
| 263 [stackModel_ setLayoutIsVertical:layoutIsVertical]; |
| 264 [stackModel_ setLayoutAxisPosition:position]; |
| 265 [self updateCardTabs]; |
| 266 [self updateShadowLayout]; |
| 267 } |
| 268 |
| 269 - (void)layOutStartStack { |
| 270 [stackModel_ layOutStartStack]; |
| 271 [self updateCardVisibilities]; |
| 272 } |
| 273 |
| 274 - (void)fanOutCards { |
| 275 if ([self.cards count] == 0) |
| 276 return; |
| 277 [self fanOutCardsWithStartIndex:0]; |
| 278 } |
| 279 |
| 280 - (void)fanOutCardsWithStartIndex:(NSUInteger)startIndex { |
| 281 if (![self.cards count]) |
| 282 return; |
| 283 DCHECK(startIndex < [self.cards count]); |
| 284 [stackModel_ fanOutCardsWithStartIndex:startIndex]; |
| 285 [self updateCardVisibilities]; |
| 286 } |
| 287 |
| 288 - (std::vector<LayoutRect>)cardLayouts { |
| 289 std::vector<LayoutRect> cardLayouts; |
| 290 for (StackCard* card in self.cards) |
| 291 cardLayouts.push_back(card.layout); |
| 292 return cardLayouts; |
| 293 } |
| 294 |
| 295 - (void)scrollCardAtIndex:(NSUInteger)index |
| 296 byDelta:(CGFloat)delta |
| 297 allowEarlyOverscroll:(BOOL)allowEarlyOverscroll |
| 298 decayOnOverscroll:(BOOL)decayOnOverscroll |
| 299 scrollLeadingCards:(BOOL)scrollLeadingCards { |
| 300 DCHECK(index < [self.cards count]); |
| 301 [stackModel_ scrollCardAtIndex:index |
| 302 byDelta:delta |
| 303 allowEarlyOverscroll:allowEarlyOverscroll |
| 304 decayOnOverscroll:decayOnOverscroll |
| 305 scrollLeadingCards:scrollLeadingCards]; |
| 306 [self updateCardVisibilities]; |
| 307 } |
| 308 |
| 309 - (BOOL)stackIsOverextended { |
| 310 if ([self.cards count] == 0) |
| 311 return NO; |
| 312 return ([self overextensionOnCardAtIndex:0]); |
| 313 } |
| 314 |
| 315 - (BOOL)overextensionOnCardAtIndex:(NSUInteger)index { |
| 316 DCHECK(index < [self.cards count]); |
| 317 if ([self overextensionTowardStartOnCardAtIndex:index]) |
| 318 return YES; |
| 319 if ((index == 0) && [stackModel_ overextensionTowardEndOnFirstCard]) |
| 320 return YES; |
| 321 return NO; |
| 322 } |
| 323 |
| 324 - (BOOL)overextensionTowardStartOnCardAtIndex:(NSUInteger)index { |
| 325 DCHECK(index < [self.cards count]); |
| 326 return [stackModel_ overextensionTowardStartOnCardAtIndex:index]; |
| 327 } |
| 328 |
| 329 - (void)eliminateOverextension { |
| 330 [stackModel_ eliminateOverextension]; |
| 331 [self updateCardVisibilities]; |
| 332 } |
| 333 |
| 334 - (void)scrollCardAtIndex:(NSUInteger)index awayFromNeighbor:(BOOL)preceding { |
| 335 DCHECK(index < [self.cards count]); |
| 336 [stackModel_ scrollCardAtIndex:index awayFromNeighbor:preceding]; |
| 337 [self updateCardVisibilities]; |
| 338 } |
| 339 |
| 340 - (void)updateCardVisibilities { |
| 341 BOOL shouldHideNextVisibleCardShadow = YES; |
| 342 for (StackCard* card in self.cards) { |
| 343 if ([stackModel_ cardIsCovered:card]) { |
| 344 if (card.viewIsLive && !defersCardHiding_) { |
| 345 if (keepOnlyVisibleCardViewsAlive_) { |
| 346 [card.view removeFromSuperview]; |
| 347 [card releaseView]; |
| 348 } else { |
| 349 card.view.hidden = YES; |
| 350 } |
| 351 } |
| 352 } else { |
| 353 [self displayCard:card]; |
| 354 // Hide the first visible card's shadow. |
| 355 card.view.shouldShowShadow = !shouldHideNextVisibleCardShadow; |
| 356 if (shouldHideNextVisibleCardShadow) |
| 357 shouldHideNextVisibleCardShadow = NO; |
| 358 card.view.shouldMaskShadow = card.view.shouldShowShadow; |
| 359 } |
| 360 } |
| 361 if (self.shouldShowShadow) |
| 362 [self updateShadowLayout]; |
| 363 // Updates VoiceOver with currently accessible elements. |
| 364 UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, |
| 365 nil); |
| 366 } |
| 367 |
| 368 - (BOOL)preloadNextCard { |
| 369 if (keepOnlyVisibleCardViewsAlive_) |
| 370 return NO; |
| 371 // Find the next card to preload. |
| 372 StackCard* nextCard = nil; |
| 373 for (nextCard in self.cards) { |
| 374 // TODO(stuartmorgan): Change the selection here to favor the cards that |
| 375 // are closest to becoming visible. |
| 376 if (!nextCard.viewIsLive) |
| 377 break; |
| 378 } |
| 379 // If there was one, preload it. |
| 380 if (nextCard) { |
| 381 // Visible card views should have already been synchronously loaded. |
| 382 DCHECK([stackModel_ cardIsCovered:nextCard]); |
| 383 [self displayCard:nextCard]; |
| 384 } |
| 385 return nextCard != nil; |
| 386 } |
| 387 |
| 388 - (void)clearGestureRecognizerTargetAndDelegateFromCards:(id)object { |
| 389 for (StackCard* card in self.cards) { |
| 390 // Ensure that views aren't created just to remove their recognizers. |
| 391 if (!card.viewIsLive) |
| 392 continue; |
| 393 for (UIGestureRecognizer* recognizer in card.view.gestureRecognizers) { |
| 394 if (recognizer.delegate == object) |
| 395 recognizer.delegate = nil; |
| 396 // Passing NULL as the value of the |action| parameter causes all actions |
| 397 // associated with this target to be removed. Note that if |object| is |
| 398 // not a target of |recognizer| this method is a no-op. |
| 399 [recognizer removeTarget:object action:NULL]; |
| 400 } |
| 401 } |
| 402 } |
| 403 |
| 404 - (void)removeCardAtIndex:(NSUInteger)index { |
| 405 DCHECK(index < [self.cards count]); |
| 406 StackCard* card = [self.cards objectAtIndex:index]; |
| 407 [[card retain] autorelease]; |
| 408 [self.observer cardSet:self willRemoveCard:card atIndex:index]; |
| 409 [stackModel_ removeCard:card]; |
| 410 |
| 411 [self.observer cardSet:self didRemoveCard:card atIndex:index]; |
| 412 } |
| 413 |
| 414 #pragma mark Card Construction/Display |
| 415 |
| 416 - (StackCard*)buildCardFromTab:(Tab*)tab { |
| 417 DCHECK(tab); |
| 418 StackCard* card = [[[StackCard alloc] initWithViewProvider:self] autorelease]; |
| 419 card.size = [stackModel_ cardSize]; |
| 420 card.tabID = reinterpret_cast<NSUInteger>(tab); |
| 421 |
| 422 return card; |
| 423 } |
| 424 |
| 425 - (void)rebuildCards { |
| 426 [stackModel_ removeAllCards]; |
| 427 |
| 428 for (Tab* tab in tabModel_.get()) { |
| 429 StackCard* card = [self buildCardFromTab:tab]; |
| 430 [stackModel_ addCard:card]; |
| 431 } |
| 432 |
| 433 [self.observer cardSetRecreatedCards:self]; |
| 434 } |
| 435 |
| 436 - (void)displayCard:(StackCard*)card { |
| 437 DCHECK(view_); |
| 438 card.view.hidden = [stackModel_ cardIsCovered:card]; |
| 439 |
| 440 if (card.view.superview) |
| 441 return; |
| 442 // Find the card view (if any) that's above the card to add and already in the |
| 443 // view. |
| 444 StackCard* cardAboveNewCard = nil; |
| 445 NSUInteger indexOfCard = [self.cards indexOfObject:card]; |
| 446 DCHECK(indexOfCard != NSNotFound); |
| 447 for (NSUInteger i = indexOfCard + 1; i < [self.cards count]; ++i) { |
| 448 StackCard* nextCard = [self.cards objectAtIndex:i]; |
| 449 if (nextCard.viewIsLive && nextCard.view.superview) { |
| 450 cardAboveNewCard = nextCard; |
| 451 break; |
| 452 } |
| 453 } |
| 454 if (cardAboveNewCard) |
| 455 [view_ insertSubview:card.view belowSubview:cardAboveNewCard.view]; |
| 456 else |
| 457 [view_ addSubview:card.view]; |
| 458 |
| 459 LayoutRect layout = card.layout; |
| 460 layout.boundingWidth = CGRectGetWidth([view_ bounds]); |
| 461 card.layout = layout; |
| 462 |
| 463 [self.observer cardSet:self displayedCard:card]; |
| 464 } |
| 465 |
| 466 #pragma mark Deck Management |
| 467 |
| 468 - (void)updateCardTabs { |
| 469 CardCloseButtonSide closeButtonSide = self.closeButtonSide; |
| 470 for (StackCard* card in self.cards) { |
| 471 if (card.viewIsLive) |
| 472 card.view.closeButtonSide = closeButtonSide; |
| 473 } |
| 474 } |
| 475 |
| 476 #pragma mark - |
| 477 #pragma mark StackCardViewProvider Methods |
| 478 |
| 479 - (CardView*)cardViewWithFrame:(CGRect)frame forStackCard:(StackCard*)card { |
| 480 DCHECK(!ignoresTabModelChanges_); |
| 481 NSUInteger cardIndex = [self.cards indexOfObject:card]; |
| 482 DCHECK(cardIndex != NSNotFound); |
| 483 Tab* tab = [tabModel_ tabAtIndex:cardIndex]; |
| 484 DCHECK(tab); |
| 485 NSString* title = tab.title; |
| 486 if (![title length]) |
| 487 title = tab.urlDisplayString; |
| 488 CardView* view = |
| 489 [[[CardView alloc] initWithFrame:frame |
| 490 isIncognito:[tabModel_ isOffTheRecord]] autorelease]; |
| 491 [view setTitle:title]; |
| 492 [view setFavicon:[tab favicon]]; |
| 493 [tab retrieveSnapshot:^(UIImage* image) { |
| 494 [view setImage:image]; |
| 495 }]; |
| 496 if (!view.image) |
| 497 [view setImage:[CRWWebController defaultSnapshotImage]]; |
| 498 view.closeButtonSide = self.closeButtonSide; |
| 499 |
| 500 return view; |
| 501 } |
| 502 |
| 503 #pragma mark TabModelObserver Methods |
| 504 |
| 505 - (void)tabModel:(TabModel*)model |
| 506 didInsertTab:(Tab*)tab |
| 507 atIndex:(NSUInteger)index |
| 508 inForeground:(BOOL)fg { |
| 509 DCHECK(model == tabModel_); |
| 510 StackCard* newCard = [self buildCardFromTab:tab]; |
| 511 [stackModel_ insertCard:newCard atIndex:index]; |
| 512 [self.observer cardSet:self didAddCard:newCard]; |
| 513 } |
| 514 |
| 515 // A tab was removed at the given index. |
| 516 - (void)tabModel:(TabModel*)model |
| 517 didRemoveTab:(Tab*)tab |
| 518 atIndex:(NSUInteger)index { |
| 519 [self removeCardAtIndex:index]; |
| 520 } |
| 521 |
| 522 - (CGFloat)maximumStackLength { |
| 523 return [stackModel_ maximumStackLength]; |
| 524 } |
| 525 |
| 526 - (BOOL)cardIsCollapsed:(StackCard*)card { |
| 527 return [stackModel_ cardIsCollapsed:card]; |
| 528 } |
| 529 |
| 530 - (BOOL)stackIsFullyCollapsed { |
| 531 return [stackModel_ stackIsFullyCollapsed]; |
| 532 } |
| 533 |
| 534 - (BOOL)stackIsFullyFannedOut { |
| 535 return [stackModel_ stackIsFullyFannedOut]; |
| 536 } |
| 537 |
| 538 - (BOOL)stackIsFullyOverextended { |
| 539 return [stackModel_ stackIsFullyOverextended]; |
| 540 } |
| 541 |
| 542 - (CGFloat)overextensionAmount { |
| 543 return [stackModel_ overextensionAmount]; |
| 544 } |
| 545 |
| 546 - (BOOL)isCardInStartStaggerRegion:(StackCard*)card { |
| 547 NSInteger cardIndex = [self.cards indexOfObject:card]; |
| 548 DCHECK(cardIndex != NSNotFound); |
| 549 // The last card in the start stack is not actually collapsed, thus the -1. |
| 550 NSInteger indexOfLastCardInStartStaggerRegion = |
| 551 [stackModel_ lastStartStackCardIndex] - 1; |
| 552 return (cardIndex <= indexOfLastCardInStartStaggerRegion); |
| 553 } |
| 554 |
| 555 - (BOOL)isCardInEndStaggerRegion:(StackCard*)card { |
| 556 NSInteger cardIndex = [self.cards indexOfObject:card]; |
| 557 DCHECK(cardIndex != NSNotFound); |
| 558 NSInteger indexOfFirstCardInEndStaggerRegion = |
| 559 [stackModel_ firstEndStackCardIndex]; |
| 560 return (indexOfFirstCardInEndStaggerRegion != -1 && |
| 561 cardIndex >= indexOfFirstCardInEndStaggerRegion); |
| 562 } |
| 563 |
| 564 - (void)updateShadowLayout { |
| 565 CGRect stackFrame = CGRectNull; |
| 566 for (StackCard* card in self.cards) { |
| 567 if (![card isEqual:self.closingCard]) { |
| 568 CGRect cardFrame = |
| 569 AlignRectOriginAndSizeToPixels(LayoutRectGetRect(card.layout)); |
| 570 stackFrame = CGRectIsNull(stackFrame) |
| 571 ? cardFrame |
| 572 : CGRectUnion(stackFrame, cardFrame); |
| 573 } |
| 574 } |
| 575 [stackShadow_ |
| 576 setHidden:CGRectIsNull(stackFrame) || CGRectIsEmpty(stackFrame)]; |
| 577 if (![stackShadow_ isHidden]) { |
| 578 [stackShadow_ |
| 579 setFrame:UIEdgeInsetsInsetRect(stackFrame, kCardShadowLayoutOutsets)]; |
| 580 } |
| 581 } |
| 582 |
| 583 @end |
| 584 |
| 585 @implementation CardSet (Testing) |
| 586 |
| 587 - (StackCard*)cardForTab:(Tab*)tab { |
| 588 NSUInteger tabIndex = [tabModel_ indexOfTab:tab]; |
| 589 if (tabIndex == NSNotFound) |
| 590 return nil; |
| 591 return [self.cards objectAtIndex:tabIndex]; |
| 592 } |
| 593 |
| 594 - (void)setStackModelForTesting:(CardStackLayoutManager*)stackModel { |
| 595 stackModel_.reset([stackModel retain]); |
| 596 } |
| 597 |
| 598 @end |
OLD | NEW |