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

Side by Side 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 unified diff | Download patch
OLDNEW
(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
OLDNEW
« 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