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 #include "base/mac/scoped_nsobject.h" |
| 6 #include "ios/chrome/browser/ui/rtl_geometry.h" |
| 7 #import "ios/chrome/browser/ui/stack_view/card_stack_layout_manager.h" |
| 8 #import "ios/chrome/browser/ui/stack_view/stack_card.h" |
| 9 #include "testing/gtest/include/gtest/gtest.h" |
| 10 #include "testing/platform_test.h" |
| 11 |
| 12 @interface CardStackLayoutManager (Private) |
| 13 - (CGFloat)minStackStaggerAmount; |
| 14 - (CGFloat)scrollCardAwayFromNeighborAmount; |
| 15 - (CGFloat)staggerOffsetForIndexFromEdge:(NSInteger)countFromEdge; |
| 16 - (CGFloat)maximumCardSeparation; |
| 17 @end |
| 18 |
| 19 // A mock version of StackCard. |
| 20 @interface MockStackCard : NSObject |
| 21 |
| 22 @property(nonatomic, readwrite, assign) BOOL synchronizeView; |
| 23 @property(nonatomic, readwrite, assign) LayoutRect layout; |
| 24 @property(nonatomic, readwrite, assign) CGSize size; |
| 25 |
| 26 @end |
| 27 |
| 28 @implementation MockStackCard |
| 29 |
| 30 @synthesize synchronizeView = _synchronizeView; |
| 31 @synthesize layout = _layout; |
| 32 @synthesize size = _size; |
| 33 |
| 34 - (void)setSize:(CGSize)size { |
| 35 _layout.position.leading += (_layout.size.width - size.width) / 2.0; |
| 36 _layout.position.originY += (_layout.size.height - size.height) / 2.0; |
| 37 _layout.size = size; |
| 38 _size = size; |
| 39 } |
| 40 |
| 41 @end |
| 42 |
| 43 namespace { |
| 44 |
| 45 // Create a fixture to get an autorelease pool. |
| 46 class CardStackLayoutManagerTest : public PlatformTest {}; |
| 47 |
| 48 const float kMargin = 5; |
| 49 const float kMaxStagger = 40; |
| 50 const float kAxisPosition = 55; |
| 51 const float kCardWidth = 300; |
| 52 const float kCardHeight = 400; |
| 53 const float kDefaultEndLimitFraction = 0.4; |
| 54 |
| 55 // Returns the offset of |point| in the current layout direction. |
| 56 CGFloat LayoutOffset(CardStackLayoutManager* stack, |
| 57 LayoutRectPosition position) { |
| 58 return [stack layoutIsVertical] ? position.originY : position.leading; |
| 59 } |
| 60 |
| 61 // Returns the distance along the layout axis between the cards at |firstIndex| |
| 62 // and |secondIndex|. |
| 63 CGFloat SeparationOnLayoutAxis(CardStackLayoutManager* stack, |
| 64 NSUInteger firstIndex, |
| 65 NSUInteger secondIndex) { |
| 66 StackCard* firstCard = [[stack cards] objectAtIndex:firstIndex]; |
| 67 StackCard* secondCard = [[stack cards] objectAtIndex:secondIndex]; |
| 68 CGFloat firstCardOffset = LayoutOffset(stack, firstCard.layout.position); |
| 69 CGFloat secondCardOffset = LayoutOffset(stack, secondCard.layout.position); |
| 70 return secondCardOffset - firstCardOffset; |
| 71 } |
| 72 |
| 73 // Validates basic constraints: |
| 74 // - All cards should be centered at kAxisPosition along the non-layout axis. |
| 75 // - If not overscrolled toward start, all start edges should be at or past |
| 76 // kMargin. |
| 77 // - All start edges should be visibly before endLimit. |
| 78 // - No card should start before a previous card. |
| 79 // - No card should start after the end of a previous card. |
| 80 // If |shouldBeWithinMaxStagger|: |
| 81 // - Consecutive cards should be no more than kMaxStagger apart. |
| 82 void ValidateCardPositioningConstraints(CardStackLayoutManager* stack, |
| 83 CGFloat endLimit, |
| 84 bool shouldBeWithinMaxStagger) { |
| 85 BOOL isVertical = [stack layoutIsVertical]; |
| 86 StackCard* previousCard = nil; |
| 87 for (StackCard* card in [stack cards]) { |
| 88 CGRect cardFrame = LayoutRectGetRect(card.layout); |
| 89 CGFloat nonLayoutAxisCenter = |
| 90 isVertical ? CGRectGetMidX(cardFrame) : CGRectGetMidY(cardFrame); |
| 91 EXPECT_FLOAT_EQ(kAxisPosition, nonLayoutAxisCenter); |
| 92 CGFloat startEdge = LayoutOffset(stack, card.layout.position); |
| 93 if (![stack overextensionTowardStartOnCardAtIndex:0]) |
| 94 EXPECT_LE(kMargin, startEdge); |
| 95 EXPECT_GT(endLimit, startEdge); |
| 96 if (previousCard != nil) { |
| 97 CGFloat previousTopEdge = |
| 98 LayoutOffset(stack, previousCard.layout.position); |
| 99 EXPECT_LE(previousTopEdge, startEdge); |
| 100 CGFloat cardSize = isVertical ? kCardHeight : kCardWidth; |
| 101 EXPECT_GE(cardSize, startEdge - previousTopEdge); |
| 102 if (shouldBeWithinMaxStagger) |
| 103 EXPECT_GE(kMaxStagger, startEdge - previousTopEdge); |
| 104 } |
| 105 previousCard = card; |
| 106 } |
| 107 } |
| 108 |
| 109 // Creates a new |CardStackLayoutManager|, adds |n| cards to it, and sets |
| 110 // dimensional/positioning parameters to the constants defined above. |
| 111 CardStackLayoutManager* newStackOfNCards(unsigned int n, BOOL layoutIsVertical) |
| 112 NS_RETURNS_RETAINED { |
| 113 CardStackLayoutManager* stack = [[CardStackLayoutManager alloc] init]; |
| 114 stack.layoutIsVertical = layoutIsVertical; |
| 115 for (unsigned int i = 0; i < n; ++i) { |
| 116 base::scoped_nsobject<StackCard> card( |
| 117 (StackCard*)[[MockStackCard alloc] init]); |
| 118 [stack addCard:card]; |
| 119 } |
| 120 |
| 121 CGSize cardSize = CGSizeMake(kCardWidth, kCardHeight); |
| 122 [stack setCardSize:cardSize]; |
| 123 [stack setStartLimit:kMargin]; |
| 124 [stack setMaxStagger:kMaxStagger]; |
| 125 [stack setLayoutAxisPosition:kAxisPosition]; |
| 126 CGFloat cardLength = layoutIsVertical ? cardSize.height : cardSize.width; |
| 127 [stack setMaximumOverextensionAmount:cardLength / 2.0]; |
| 128 |
| 129 return stack; |
| 130 } |
| 131 |
| 132 TEST_F(CardStackLayoutManagerTest, CardSizing) { |
| 133 BOOL boolValues[2] = {NO, YES}; |
| 134 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 135 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 136 [[CardStackLayoutManager alloc] init]); |
| 137 stack.get().layoutIsVertical = boolValues[i]; |
| 138 |
| 139 base::scoped_nsobject<StackCard> view1( |
| 140 (StackCard*)[[MockStackCard alloc] init]); |
| 141 base::scoped_nsobject<StackCard> view2( |
| 142 (StackCard*)[[MockStackCard alloc] init]); |
| 143 base::scoped_nsobject<StackCard> view3( |
| 144 (StackCard*)[[MockStackCard alloc] init]); |
| 145 [stack addCard:view1.get()]; |
| 146 [stack addCard:view2.get()]; |
| 147 [stack addCard:view3.get()]; |
| 148 // Ensure that removed cards are not altered. |
| 149 [stack removeCard:view2]; |
| 150 |
| 151 CGSize cardSize = CGSizeMake(111, 222); |
| 152 [stack setCardSize:cardSize]; |
| 153 |
| 154 EXPECT_FLOAT_EQ(cardSize.width, [view1 size].width); |
| 155 EXPECT_FLOAT_EQ(cardSize.height, [view1 size].height); |
| 156 EXPECT_FLOAT_EQ(0.0, [view2 size].width); |
| 157 EXPECT_FLOAT_EQ(0.0, [view2 size].height); |
| 158 EXPECT_FLOAT_EQ(cardSize.width, [view3 size].width); |
| 159 EXPECT_FLOAT_EQ(cardSize.height, [view3 size].height); |
| 160 |
| 161 // But it should be automatically updated when it's added again. |
| 162 [stack addCard:view2]; |
| 163 EXPECT_FLOAT_EQ(cardSize.width, [view2 size].width); |
| 164 EXPECT_FLOAT_EQ(cardSize.height, [view2 size].height); |
| 165 } |
| 166 } |
| 167 |
| 168 TEST_F(CardStackLayoutManagerTest, StackSizes) { |
| 169 BOOL boolValues[2] = {NO, YES}; |
| 170 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 171 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 172 [[CardStackLayoutManager alloc] init]); |
| 173 stack.get().layoutIsVertical = boolValues[i]; |
| 174 CGRect cardFrame = CGRectMake(0, 0, 100, 200); |
| 175 [stack setCardSize:cardFrame.size]; |
| 176 [stack setMaxStagger:30]; |
| 177 |
| 178 // Asking the size for a collapsed stack should give the same result. |
| 179 CGFloat emptyCollapsedSize = [stack fullyCollapsedStackLength]; |
| 180 for (int i = 0; i < 10; ++i) { |
| 181 base::scoped_nsobject<StackCard> card( |
| 182 (StackCard*)[[UIView alloc] initWithFrame:cardFrame]); |
| 183 [stack addCard:card]; |
| 184 } |
| 185 CGFloat largeCollapsedSize = [stack fullyCollapsedStackLength]; |
| 186 EXPECT_FLOAT_EQ(emptyCollapsedSize, largeCollapsedSize); |
| 187 |
| 188 // But a fanned-out stack should get bigger, and the maximum stack size |
| 189 // should be larger still. |
| 190 CGFloat largeExpandedSize = [stack fannedStackLength]; |
| 191 EXPECT_GT(largeExpandedSize, largeCollapsedSize); |
| 192 CGFloat largeMaximumSize = [stack maximumStackLength]; |
| 193 EXPECT_GT(largeMaximumSize, largeExpandedSize); |
| 194 base::scoped_nsobject<StackCard> card( |
| 195 (StackCard*)[[MockStackCard alloc] init]); |
| 196 [stack addCard:card]; |
| 197 CGFloat evenLargerExpandedSize = [stack fannedStackLength]; |
| 198 EXPECT_LT(largeExpandedSize, evenLargerExpandedSize); |
| 199 CGFloat evenLargerMaximumSize = [stack maximumStackLength]; |
| 200 EXPECT_LT(largeMaximumSize, evenLargerMaximumSize); |
| 201 |
| 202 // And start limit shouldn't matter. |
| 203 [stack setStartLimit:10]; |
| 204 EXPECT_FLOAT_EQ(emptyCollapsedSize, [stack fullyCollapsedStackLength]); |
| 205 EXPECT_FLOAT_EQ(evenLargerExpandedSize, [stack fannedStackLength]); |
| 206 EXPECT_FLOAT_EQ(evenLargerMaximumSize, [stack maximumStackLength]); |
| 207 } |
| 208 } |
| 209 |
| 210 TEST_F(CardStackLayoutManagerTest, StackLayout) { |
| 211 BOOL boolValues[2] = {NO, YES}; |
| 212 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 213 const unsigned int kCardCount = 30; |
| 214 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 215 newStackOfNCards(kCardCount, boolValues[i])); |
| 216 |
| 217 const float kEndLimit = |
| 218 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 219 [stack setEndLimit:kEndLimit]; |
| 220 [stack fanOutCardsWithStartIndex:0]; |
| 221 |
| 222 EXPECT_EQ(kCardCount, [[stack cards] count]); |
| 223 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 224 EXPECT_EQ(0, [stack lastStartStackCardIndex]); |
| 225 EXPECT_LT(0, [stack firstEndStackCardIndex]); |
| 226 for (NSInteger i = 0; i < [stack firstEndStackCardIndex]; i++) { |
| 227 StackCard* card = [[stack cards] objectAtIndex:i]; |
| 228 EXPECT_FLOAT_EQ(kMargin + i * kMaxStagger, |
| 229 LayoutOffset(stack, card.layout.position)); |
| 230 } |
| 231 } |
| 232 } |
| 233 |
| 234 TEST_F(CardStackLayoutManagerTest, PreservingPositionsOnCardSizeChange) { |
| 235 BOOL boolValues[2] = {NO, YES}; |
| 236 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 237 const unsigned int kCardCount = 3; |
| 238 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 239 newStackOfNCards(kCardCount, boolValues[i])); |
| 240 |
| 241 const float kEndLimit = |
| 242 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 243 [stack setEndLimit:kEndLimit]; |
| 244 [stack fanOutCardsWithStartIndex:0]; |
| 245 |
| 246 EXPECT_EQ(kCardCount, [[stack cards] count]); |
| 247 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 248 EXPECT_EQ(0, [stack lastStartStackCardIndex]); |
| 249 EXPECT_LT(0, [stack firstEndStackCardIndex]); |
| 250 for (NSInteger i = 0; i < [stack firstEndStackCardIndex]; i++) { |
| 251 StackCard* card = [[stack cards] objectAtIndex:i]; |
| 252 EXPECT_FLOAT_EQ(kMargin + i * kMaxStagger, |
| 253 LayoutOffset(stack, card.layout.position)); |
| 254 } |
| 255 |
| 256 // Cards should retain their positions after changing the card size. |
| 257 CGSize cardSize = CGSizeMake(kCardWidth + 10, kCardHeight + 10); |
| 258 [stack setCardSize:cardSize]; |
| 259 EXPECT_EQ(kCardCount, [[stack cards] count]); |
| 260 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 261 EXPECT_EQ(0, [stack lastStartStackCardIndex]); |
| 262 EXPECT_LT(0, [stack firstEndStackCardIndex]); |
| 263 for (NSInteger i = 0; i < [stack firstEndStackCardIndex]; i++) { |
| 264 StackCard* card = [[stack cards] objectAtIndex:i]; |
| 265 EXPECT_FLOAT_EQ(kMargin + i * kMaxStagger, |
| 266 LayoutOffset(stack, card.layout.position)); |
| 267 } |
| 268 } |
| 269 } |
| 270 |
| 271 TEST_F(CardStackLayoutManagerTest, SwappingPositionsOnOrientationChange) { |
| 272 BOOL boolValues[2] = {NO, YES}; |
| 273 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 274 const unsigned int kCardCount = 3; |
| 275 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 276 newStackOfNCards(kCardCount, boolValues[i])); |
| 277 |
| 278 const float kEndLimit = |
| 279 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 280 [stack setEndLimit:kEndLimit]; |
| 281 [stack fanOutCardsWithStartIndex:0]; |
| 282 |
| 283 EXPECT_EQ(kCardCount, [[stack cards] count]); |
| 284 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 285 EXPECT_EQ(0, [stack lastStartStackCardIndex]); |
| 286 EXPECT_LT(0, [stack firstEndStackCardIndex]); |
| 287 for (NSInteger i = 0; i < [stack firstEndStackCardIndex]; i++) { |
| 288 StackCard* card = [[stack cards] objectAtIndex:i]; |
| 289 EXPECT_FLOAT_EQ(kMargin + i * kMaxStagger, |
| 290 LayoutOffset(stack, card.layout.position)); |
| 291 } |
| 292 |
| 293 // After changing orientation, cards' layout offsets should be preserved on |
| 294 // the new layout axis. |
| 295 [stack setLayoutIsVertical:![stack layoutIsVertical]]; |
| 296 EXPECT_EQ(kCardCount, [[stack cards] count]); |
| 297 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 298 EXPECT_EQ(0, [stack lastStartStackCardIndex]); |
| 299 EXPECT_LT(0, [stack firstEndStackCardIndex]); |
| 300 for (NSInteger i = 0; i < [stack firstEndStackCardIndex]; i++) { |
| 301 StackCard* card = [[stack cards] objectAtIndex:i]; |
| 302 EXPECT_FLOAT_EQ(kMargin + i * kMaxStagger, |
| 303 LayoutOffset(stack, card.layout.position)); |
| 304 } |
| 305 } |
| 306 } |
| 307 TEST_F(CardStackLayoutManagerTest, EndStackRecomputationOnEndLimitChange) { |
| 308 BOOL boolValues[2] = {NO, YES}; |
| 309 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 310 const unsigned int kCardCount = 3; |
| 311 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 312 newStackOfNCards(kCardCount, boolValues[i])); |
| 313 |
| 314 CGFloat endLimit = [stack maximumStackLength]; |
| 315 [stack setEndLimit:endLimit]; |
| 316 [stack fanOutCardsWithStartIndex:0]; |
| 317 |
| 318 EXPECT_EQ(kCardCount, [[stack cards] count]); |
| 319 ValidateCardPositioningConstraints(stack, endLimit, true); |
| 320 EXPECT_EQ(0, [stack lastStartStackCardIndex]); |
| 321 EXPECT_EQ((int)kCardCount, [stack firstEndStackCardIndex]); |
| 322 for (NSInteger i = 0; i < [stack firstEndStackCardIndex]; i++) { |
| 323 StackCard* card = [[stack cards] objectAtIndex:i]; |
| 324 EXPECT_FLOAT_EQ(kMargin + i * kMaxStagger, |
| 325 LayoutOffset(stack, card.layout.position)); |
| 326 } |
| 327 |
| 328 // Setting smaller end limit should push third card into end stack. |
| 329 endLimit = 2 * kMaxStagger; |
| 330 [stack setEndLimit:endLimit]; |
| 331 ValidateCardPositioningConstraints(stack, endLimit, true); |
| 332 EXPECT_EQ(2, [stack firstEndStackCardIndex]); |
| 333 |
| 334 // Making it smaller still should push second card into end stack. |
| 335 endLimit = kMaxStagger; |
| 336 [stack setEndLimit:endLimit]; |
| 337 ValidateCardPositioningConstraints(stack, endLimit, true); |
| 338 EXPECT_EQ(1, [stack firstEndStackCardIndex]); |
| 339 |
| 340 // Making it larger again should re-fanout the end stack cards. |
| 341 endLimit = [stack maximumStackLength]; |
| 342 [stack setEndLimit:endLimit]; |
| 343 ValidateCardPositioningConstraints(stack, endLimit, true); |
| 344 EXPECT_EQ(0, [stack lastStartStackCardIndex]); |
| 345 EXPECT_EQ((int)kCardCount, [stack firstEndStackCardIndex]); |
| 346 for (NSInteger i = 0; i < [stack firstEndStackCardIndex]; i++) { |
| 347 StackCard* card = [[stack cards] objectAtIndex:i]; |
| 348 EXPECT_FLOAT_EQ(kMargin + i * kMaxStagger, |
| 349 LayoutOffset(stack, card.layout.position)); |
| 350 } |
| 351 } |
| 352 } |
| 353 |
| 354 TEST_F(CardStackLayoutManagerTest, StackLayoutAtSpecificIndex) { |
| 355 BOOL boolValues[2] = {NO, YES}; |
| 356 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 357 const unsigned int kCardCount = 30; |
| 358 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 359 newStackOfNCards(kCardCount, boolValues[i])); |
| 360 |
| 361 NSInteger startIndex = 10; |
| 362 |
| 363 const float kEndLimit = |
| 364 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 365 [stack setEndLimit:kEndLimit]; |
| 366 [stack fanOutCardsWithStartIndex:startIndex]; |
| 367 |
| 368 EXPECT_EQ(kCardCount, [[stack cards] count]); |
| 369 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 370 EXPECT_EQ(startIndex, [stack lastStartStackCardIndex]); |
| 371 // Take into account start stack when verifying position of card at |
| 372 // |startIndex|. |
| 373 StackCard* startCard = [[stack cards] objectAtIndex:startIndex]; |
| 374 EXPECT_FLOAT_EQ(kMargin + [stack staggerOffsetForIndexFromEdge:startIndex], |
| 375 LayoutOffset(stack, startCard.layout.position)); |
| 376 NSInteger firstEndStackCardIndex = [stack firstEndStackCardIndex]; |
| 377 for (NSInteger i = startIndex + 1; i < firstEndStackCardIndex; i++) { |
| 378 StackCard* card = [[stack cards] objectAtIndex:i]; |
| 379 EXPECT_FLOAT_EQ(kMargin + (i - startIndex) * kMaxStagger, |
| 380 LayoutOffset(stack, card.layout.position)); |
| 381 } |
| 382 } |
| 383 } |
| 384 |
| 385 TEST_F(CardStackLayoutManagerTest, CardIsCovered) { |
| 386 BOOL boolValues[2] = {NO, YES}; |
| 387 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 388 const unsigned int kCardCount = 3; |
| 389 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 390 newStackOfNCards(kCardCount, boolValues[i])); |
| 391 |
| 392 const float kEndLimit = |
| 393 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 394 [stack setEndLimit:kEndLimit]; |
| 395 [stack fanOutCardsWithStartIndex:0]; |
| 396 |
| 397 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 398 EXPECT_EQ(0, [stack lastStartStackCardIndex]); |
| 399 EXPECT_EQ((int)kCardCount, [stack firstEndStackCardIndex]); |
| 400 // Since no cards are hidden in the start or end stack, all cards should |
| 401 // be visible (i.e., not covered). |
| 402 for (NSUInteger i = 0; i < kCardCount; i++) { |
| 403 StackCard* card = [[stack cards] objectAtIndex:i]; |
| 404 EXPECT_FALSE([stack cardIsCovered:card]); |
| 405 } |
| 406 // Moving the second card to the same location as the third card should |
| 407 // result in the third card covering the second card. |
| 408 StackCard* secondCard = [[stack cards] objectAtIndex:1]; |
| 409 StackCard* thirdCard = [[stack cards] objectAtIndex:2]; |
| 410 secondCard.layout = thirdCard.layout; |
| 411 EXPECT_TRUE([stack cardIsCovered:secondCard]); |
| 412 EXPECT_FALSE([stack cardIsCovered:thirdCard]); |
| 413 } |
| 414 } |
| 415 |
| 416 TEST_F(CardStackLayoutManagerTest, CardIsCollapsed) { |
| 417 BOOL boolValues[2] = {NO, YES}; |
| 418 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 419 const unsigned int kCardCount = 3; |
| 420 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 421 newStackOfNCards(kCardCount, boolValues[i])); |
| 422 |
| 423 const float kEndLimit = |
| 424 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 425 [stack setEndLimit:kEndLimit]; |
| 426 [stack fanOutCardsWithStartIndex:0]; |
| 427 |
| 428 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 429 EXPECT_EQ(0, [stack lastStartStackCardIndex]); |
| 430 EXPECT_EQ((int)kCardCount, [stack firstEndStackCardIndex]); |
| 431 // Since the cards are fully fanned out, no cards should be collapsed. |
| 432 for (NSUInteger i = 0; i < kCardCount; i++) { |
| 433 StackCard* card = [[stack cards] objectAtIndex:i]; |
| 434 EXPECT_FALSE([stack cardIsCollapsed:card]); |
| 435 } |
| 436 // Moving the second card to be |minStackStaggerAmount| away from the |
| 437 // third card should result in the second card being collapsed. |
| 438 StackCard* secondCard = [[stack cards] objectAtIndex:1]; |
| 439 StackCard* thirdCard = [[stack cards] objectAtIndex:2]; |
| 440 LayoutRect collapsedLayout = thirdCard.layout; |
| 441 if ([stack layoutIsVertical]) |
| 442 collapsedLayout.position.originY -= [stack minStackStaggerAmount]; |
| 443 else |
| 444 collapsedLayout.position.leading -= [stack minStackStaggerAmount]; |
| 445 secondCard.layout = collapsedLayout; |
| 446 EXPECT_TRUE([stack cardIsCollapsed:secondCard]); |
| 447 } |
| 448 } |
| 449 |
| 450 TEST_F(CardStackLayoutManagerTest, BasicScroll) { |
| 451 BOOL boolValues[2] = {NO, YES}; |
| 452 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 453 const unsigned int kCardCount = 3; |
| 454 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 455 newStackOfNCards(kCardCount, boolValues[i])); |
| 456 StackCard* firstCard = [[stack cards] objectAtIndex:0]; |
| 457 |
| 458 const float kEndLimit = |
| 459 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 460 [stack setEndLimit:kEndLimit]; |
| 461 [stack fanOutCardsWithStartIndex:0]; |
| 462 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 463 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 464 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 0, 1)); |
| 465 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 466 // Scrolling towards start stack should keep first card anchored, and move |
| 467 // the other two. |
| 468 [stack scrollCardAtIndex:kCardCount - 1 |
| 469 byDelta:-10 |
| 470 allowEarlyOverscroll:YES |
| 471 decayOnOverscroll:YES |
| 472 scrollLeadingCards:YES]; |
| 473 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 474 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 475 EXPECT_FLOAT_EQ(kMaxStagger - 10, SeparationOnLayoutAxis(stack, 0, 1)); |
| 476 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 477 // Scrolling back towards end stack should reverse the reverse scroll. |
| 478 [stack scrollCardAtIndex:kCardCount - 1 |
| 479 byDelta:10 |
| 480 allowEarlyOverscroll:YES |
| 481 decayOnOverscroll:YES |
| 482 scrollLeadingCards:YES]; |
| 483 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 484 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 485 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 0, 1)); |
| 486 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 487 } |
| 488 } |
| 489 |
| 490 TEST_F(CardStackLayoutManagerTest, ScrollCardAwayFromNeighbor) { |
| 491 BOOL boolValues[2] = {NO, YES}; |
| 492 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 493 const unsigned int kCardCount = 3; |
| 494 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 495 newStackOfNCards(kCardCount, boolValues[i])); |
| 496 StackCard* firstCard = [[stack cards] objectAtIndex:0]; |
| 497 |
| 498 // Configure the stack so that the first card is > the scroll-away distance |
| 499 // from the end stack, but the second card is not. |
| 500 const float kEndLimit = |
| 501 [stack scrollCardAwayFromNeighborAmount] + 2 * [stack maxStagger]; |
| 502 [stack setEndLimit:kEndLimit]; |
| 503 [stack fanOutCardsWithStartIndex:0]; |
| 504 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 505 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 506 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 0, 1)); |
| 507 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 508 |
| 509 // Scrolling the third card away from the second card should result in it |
| 510 // being placed in the end stack. |
| 511 [stack scrollCardAtIndex:2 awayFromNeighbor:YES]; |
| 512 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 513 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 514 EXPECT_EQ(2, [stack firstEndStackCardIndex]); |
| 515 |
| 516 // Scrolling the second card away from the first card should result in it |
| 517 // being the min of |maxStagger + scrollAwayAmount, maximumCardSeparation| |
| 518 // away from the first card. |
| 519 [stack scrollCardAtIndex:1 awayFromNeighbor:YES]; |
| 520 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 521 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 522 CGFloat separation = |
| 523 std::min([stack maxStagger] + [stack scrollCardAwayFromNeighborAmount], |
| 524 [stack maximumCardSeparation]); |
| 525 EXPECT_FLOAT_EQ(separation, SeparationOnLayoutAxis(stack, 0, 1)); |
| 526 EXPECT_EQ(2, [stack firstEndStackCardIndex]); |
| 527 |
| 528 // Scrolling the second card away from the third card should result in it |
| 529 // being the min of |maxStagger + scrollAwayAmount, maximumCardSeparation| |
| 530 // away from the third card. |
| 531 separation = std::min([stack maximumCardSeparation], |
| 532 SeparationOnLayoutAxis(stack, 1, 2) + |
| 533 [stack scrollCardAwayFromNeighborAmount]); |
| 534 [stack scrollCardAtIndex:1 awayFromNeighbor:NO]; |
| 535 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 536 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 537 EXPECT_FLOAT_EQ(separation, SeparationOnLayoutAxis(stack, 1, 2)); |
| 538 EXPECT_EQ(2, [stack firstEndStackCardIndex]); |
| 539 |
| 540 // Scrolling the third card away from the end stack should result in it |
| 541 // being |scrollAwayAmount| away from the endLimit and not being in the |
| 542 // end stack. |
| 543 StackCard* thirdCard = [[stack cards] objectAtIndex:2]; |
| 544 separation = |
| 545 std::min([stack maximumCardSeparation], |
| 546 kEndLimit - LayoutOffset(stack, thirdCard.layout.position) + |
| 547 [stack scrollCardAwayFromNeighborAmount]); |
| 548 [stack scrollCardAtIndex:2 awayFromNeighbor:NO]; |
| 549 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 550 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 551 EXPECT_FLOAT_EQ(separation, |
| 552 kEndLimit - LayoutOffset(stack, thirdCard.layout.position)); |
| 553 EXPECT_EQ(3, [stack firstEndStackCardIndex]); |
| 554 } |
| 555 } |
| 556 |
| 557 TEST_F(CardStackLayoutManagerTest, ScrollNotScrollingLeadingCards) { |
| 558 BOOL boolValues[2] = {NO, YES}; |
| 559 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 560 const unsigned int kCardCount = 4; |
| 561 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 562 newStackOfNCards(kCardCount, boolValues[i])); |
| 563 StackCard* firstCard = [[stack cards] objectAtIndex:0]; |
| 564 |
| 565 // Make the stack large enough to fan out all its cards to avoid having to |
| 566 // worry about the end stack below. |
| 567 const float kEndLimit = [stack fannedStackLength]; |
| 568 [stack setEndLimit:kEndLimit]; |
| 569 [stack fanOutCardsWithStartIndex:0]; |
| 570 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 571 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 572 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 0, 1)); |
| 573 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 574 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 2, 3)); |
| 575 |
| 576 // Scrolling third card toward start stack without scrolling leading cards |
| 577 // should result in third and fourth cards scrolling, but second card not |
| 578 // scrolling. |
| 579 [stack scrollCardAtIndex:2 |
| 580 byDelta:-10 |
| 581 allowEarlyOverscroll:YES |
| 582 decayOnOverscroll:YES |
| 583 scrollLeadingCards:NO]; |
| 584 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 585 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 0, 1)); |
| 586 EXPECT_FLOAT_EQ(kMaxStagger - 10, SeparationOnLayoutAxis(stack, 1, 2)); |
| 587 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 2, 3)); |
| 588 |
| 589 // Doing the same toward the end stack should have the opposite effect. |
| 590 [stack fanOutCardsWithStartIndex:0]; |
| 591 // First give the cards some room to scroll away from the start stack. |
| 592 [stack scrollCardAtIndex:3 |
| 593 byDelta:-10 |
| 594 allowEarlyOverscroll:YES |
| 595 decayOnOverscroll:YES |
| 596 scrollLeadingCards:YES]; |
| 597 [stack scrollCardAtIndex:2 |
| 598 byDelta:10 |
| 599 allowEarlyOverscroll:YES |
| 600 decayOnOverscroll:YES |
| 601 scrollLeadingCards:NO]; |
| 602 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 603 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 0, 1)); |
| 604 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 605 EXPECT_FLOAT_EQ(kMaxStagger - 10, SeparationOnLayoutAxis(stack, 2, 3)); |
| 606 } |
| 607 } |
| 608 |
| 609 TEST_F(CardStackLayoutManagerTest, ScrollCollapseExpansionOfLargeStack) { |
| 610 BOOL boolValues[2] = {NO, YES}; |
| 611 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 612 const unsigned int kCardCount = 10; |
| 613 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 614 newStackOfNCards(kCardCount, boolValues[i])); |
| 615 |
| 616 const float kEndLimit = |
| 617 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 618 [stack setEndLimit:kEndLimit]; |
| 619 |
| 620 [stack fanOutCardsWithStartIndex:0]; |
| 621 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 622 EXPECT_TRUE([stack stackIsFullyFannedOut]); |
| 623 EXPECT_FALSE([stack stackIsFullyCollapsed]); |
| 624 EXPECT_FALSE([stack stackIsFullyOverextended]); |
| 625 |
| 626 // Test fanning out/overextension toward end stack. |
| 627 [stack scrollCardAtIndex:0 |
| 628 byDelta:-10 |
| 629 allowEarlyOverscroll:NO |
| 630 decayOnOverscroll:YES |
| 631 scrollLeadingCards:YES]; |
| 632 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 633 EXPECT_FALSE([stack stackIsFullyFannedOut]); |
| 634 EXPECT_FALSE([stack stackIsFullyCollapsed]); |
| 635 EXPECT_FALSE([stack stackIsFullyOverextended]); |
| 636 |
| 637 [stack scrollCardAtIndex:0 |
| 638 byDelta:[stack maximumStackLength] |
| 639 allowEarlyOverscroll:NO |
| 640 decayOnOverscroll:YES |
| 641 scrollLeadingCards:YES]; |
| 642 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 643 EXPECT_TRUE([stack stackIsFullyFannedOut]); |
| 644 EXPECT_FALSE([stack stackIsFullyCollapsed]); |
| 645 EXPECT_TRUE([stack stackIsFullyOverextended]); |
| 646 |
| 647 // Test collapsing/overextension toward start stack. |
| 648 [stack fanOutCardsWithStartIndex:0]; |
| 649 [stack scrollCardAtIndex:0 |
| 650 byDelta:-2.0 * [stack maximumStackLength] |
| 651 allowEarlyOverscroll:NO |
| 652 decayOnOverscroll:YES |
| 653 scrollLeadingCards:YES]; |
| 654 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 655 EXPECT_FALSE([stack stackIsFullyFannedOut]); |
| 656 EXPECT_TRUE([stack stackIsFullyCollapsed]); |
| 657 EXPECT_TRUE([stack stackIsFullyOverextended]); |
| 658 } |
| 659 } |
| 660 |
| 661 TEST_F(CardStackLayoutManagerTest, ScrollCollapseExpansionOfStackCornerCases) { |
| 662 BOOL boolValues[2] = {NO, YES}; |
| 663 const unsigned int kCardCount = 1; |
| 664 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 665 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 666 newStackOfNCards(kCardCount, boolValues[i])); |
| 667 |
| 668 // A laid-out stack with one card is fully collapsed and fully fanned out, |
| 669 // but not fully overextended. |
| 670 [stack fanOutCardsWithStartIndex:0]; |
| 671 EXPECT_TRUE([stack stackIsFullyFannedOut]); |
| 672 EXPECT_TRUE([stack stackIsFullyCollapsed]); |
| 673 EXPECT_FALSE([stack stackIsFullyOverextended]); |
| 674 |
| 675 // A stack with no cards is fully collapsed, fully fanned out, and fully |
| 676 // overextended. |
| 677 [stack removeCard:[[stack cards] objectAtIndex:0]]; |
| 678 EXPECT_TRUE([stack stackIsFullyFannedOut]); |
| 679 EXPECT_TRUE([stack stackIsFullyCollapsed]); |
| 680 EXPECT_TRUE([stack stackIsFullyOverextended]); |
| 681 } |
| 682 } |
| 683 |
| 684 TEST_F(CardStackLayoutManagerTest, OneCardOverscroll) { |
| 685 BOOL boolValues[2] = {NO, YES}; |
| 686 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 687 const unsigned int kCardCount = 1; |
| 688 const float kScrollAwayAmount = 20.0; |
| 689 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 690 newStackOfNCards(kCardCount, boolValues[i])); |
| 691 StackCard* firstCard = [[stack cards] objectAtIndex:0]; |
| 692 |
| 693 const float kEndLimit = |
| 694 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 695 [stack setEndLimit:kEndLimit]; |
| 696 [stack fanOutCardsWithStartIndex:0]; |
| 697 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 698 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 699 |
| 700 // Scrolling toward end should result in overscroll. |
| 701 [stack scrollCardAtIndex:0 |
| 702 byDelta:kScrollAwayAmount |
| 703 allowEarlyOverscroll:YES |
| 704 decayOnOverscroll:YES |
| 705 scrollLeadingCards:YES]; |
| 706 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 707 EXPECT_TRUE([stack overextensionTowardEndOnFirstCard]); |
| 708 EXPECT_FALSE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 709 EXPECT_FLOAT_EQ(kMargin + kScrollAwayAmount, |
| 710 LayoutOffset(stack, firstCard.layout.position)); |
| 711 |
| 712 // Eliminate the overscroll to test scrolling toward start. |
| 713 [stack eliminateOverextension]; |
| 714 EXPECT_FALSE([stack overextensionTowardEndOnFirstCard]); |
| 715 EXPECT_FALSE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 716 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 717 |
| 718 // Scrolling toward start should result in overscroll. |
| 719 [stack scrollCardAtIndex:0 |
| 720 byDelta:-kScrollAwayAmount |
| 721 allowEarlyOverscroll:YES |
| 722 decayOnOverscroll:YES |
| 723 scrollLeadingCards:YES]; |
| 724 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 725 EXPECT_FALSE([stack overextensionTowardEndOnFirstCard]); |
| 726 EXPECT_TRUE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 727 EXPECT_FLOAT_EQ(kMargin - kScrollAwayAmount, |
| 728 LayoutOffset(stack, firstCard.layout.position)); |
| 729 } |
| 730 } |
| 731 |
| 732 TEST_F(CardStackLayoutManagerTest, MaximumOverextensionAmount) { |
| 733 BOOL boolValues[2] = {NO, YES}; |
| 734 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 735 const unsigned int kCardCount = 1; |
| 736 const float kScrollAwayAmount = 20.0; |
| 737 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 738 newStackOfNCards(kCardCount, boolValues[i])); |
| 739 StackCard* firstCard = [[stack cards] objectAtIndex:0]; |
| 740 |
| 741 const float kEndLimit = |
| 742 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 743 [stack setEndLimit:kEndLimit]; |
| 744 [stack fanOutCardsWithStartIndex:0]; |
| 745 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 746 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 747 |
| 748 // Scrolling toward start/end should have no impact. |
| 749 [stack setMaximumOverextensionAmount:0]; |
| 750 [stack scrollCardAtIndex:0 |
| 751 byDelta:kScrollAwayAmount |
| 752 allowEarlyOverscroll:YES |
| 753 decayOnOverscroll:YES |
| 754 scrollLeadingCards:YES]; |
| 755 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 756 EXPECT_FALSE([stack overextensionTowardEndOnFirstCard]); |
| 757 EXPECT_FALSE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 758 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 759 EXPECT_FLOAT_EQ(0, [stack overextensionAmount]); |
| 760 |
| 761 [stack scrollCardAtIndex:0 |
| 762 byDelta:-kScrollAwayAmount |
| 763 allowEarlyOverscroll:YES |
| 764 decayOnOverscroll:YES |
| 765 scrollLeadingCards:YES]; |
| 766 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 767 EXPECT_FALSE([stack overextensionTowardEndOnFirstCard]); |
| 768 EXPECT_FALSE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 769 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 770 EXPECT_FLOAT_EQ(0, [stack overextensionAmount]); |
| 771 |
| 772 // Setting a maximum overextension amount > 0 should allow overscrolling to |
| 773 // that limit. |
| 774 CGFloat maxOverextensionAmount = kScrollAwayAmount / 2.0; |
| 775 [stack setMaximumOverextensionAmount:maxOverextensionAmount]; |
| 776 [stack scrollCardAtIndex:0 |
| 777 byDelta:kScrollAwayAmount |
| 778 allowEarlyOverscroll:YES |
| 779 decayOnOverscroll:NO |
| 780 scrollLeadingCards:YES]; |
| 781 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 782 EXPECT_TRUE([stack overextensionTowardEndOnFirstCard]); |
| 783 EXPECT_FALSE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 784 EXPECT_FLOAT_EQ(kMargin + maxOverextensionAmount, |
| 785 LayoutOffset(stack, firstCard.layout.position)); |
| 786 EXPECT_FLOAT_EQ(maxOverextensionAmount, [stack overextensionAmount]); |
| 787 |
| 788 // Eliminate the overscroll to test scrolling toward start. |
| 789 [stack eliminateOverextension]; |
| 790 |
| 791 [stack scrollCardAtIndex:0 |
| 792 byDelta:-kScrollAwayAmount |
| 793 allowEarlyOverscroll:YES |
| 794 decayOnOverscroll:NO |
| 795 scrollLeadingCards:YES]; |
| 796 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 797 EXPECT_FALSE([stack overextensionTowardEndOnFirstCard]); |
| 798 EXPECT_TRUE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 799 EXPECT_FLOAT_EQ(kMargin - maxOverextensionAmount, |
| 800 LayoutOffset(stack, firstCard.layout.position)); |
| 801 EXPECT_FLOAT_EQ(maxOverextensionAmount, [stack overextensionAmount]); |
| 802 } |
| 803 } |
| 804 |
| 805 TEST_F(CardStackLayoutManagerTest, DecayOnOverscroll) { |
| 806 BOOL boolValues[2] = {NO, YES}; |
| 807 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 808 const unsigned int kCardCount = 1; |
| 809 const float kScrollAwayAmount = 10.0; |
| 810 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 811 newStackOfNCards(kCardCount, boolValues[i])); |
| 812 StackCard* firstCard = [[stack cards] objectAtIndex:0]; |
| 813 |
| 814 const float kEndLimit = |
| 815 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 816 [stack setEndLimit:kEndLimit]; |
| 817 [stack fanOutCardsWithStartIndex:0]; |
| 818 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 819 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 820 |
| 821 // Scrolling toward end by |kScrollAwayAmount| should result in overscroll. |
| 822 [stack scrollCardAtIndex:0 |
| 823 byDelta:kScrollAwayAmount |
| 824 allowEarlyOverscroll:YES |
| 825 decayOnOverscroll:YES |
| 826 scrollLeadingCards:YES]; |
| 827 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 828 EXPECT_TRUE([stack overextensionTowardEndOnFirstCard]); |
| 829 EXPECT_FALSE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 830 EXPECT_FLOAT_EQ(kMargin + kScrollAwayAmount, |
| 831 LayoutOffset(stack, firstCard.layout.position)); |
| 832 |
| 833 // Scrolling again by |kScrollAwayAmount| with no decay on overscroll |
| 834 // should result in another move of |kScrollAwayAmount|. |
| 835 [stack scrollCardAtIndex:0 |
| 836 byDelta:kScrollAwayAmount |
| 837 allowEarlyOverscroll:YES |
| 838 decayOnOverscroll:NO |
| 839 scrollLeadingCards:YES]; |
| 840 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 841 EXPECT_TRUE([stack overextensionTowardEndOnFirstCard]); |
| 842 EXPECT_FALSE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 843 EXPECT_FLOAT_EQ(kMargin + 2 * kScrollAwayAmount, |
| 844 LayoutOffset(stack, firstCard.layout.position)); |
| 845 |
| 846 // Scrolling by |kScrollAwayAmount| a third time *with* decay on overscroll |
| 847 // should result in a move of less than |kScrollAwayAmount|. |
| 848 [stack scrollCardAtIndex:0 |
| 849 byDelta:kScrollAwayAmount |
| 850 allowEarlyOverscroll:YES |
| 851 decayOnOverscroll:YES |
| 852 scrollLeadingCards:YES]; |
| 853 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 854 EXPECT_TRUE([stack overextensionTowardEndOnFirstCard]); |
| 855 EXPECT_FALSE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 856 EXPECT_GT(kMargin + 3 * kScrollAwayAmount, |
| 857 LayoutOffset(stack, firstCard.layout.position)); |
| 858 } |
| 859 } |
| 860 |
| 861 TEST_F(CardStackLayoutManagerTest, EliminateOverextension) { |
| 862 BOOL boolValues[2] = {NO, YES}; |
| 863 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 864 const unsigned int kCardCount = 2; |
| 865 const float kScrollAwayAmount = 20.0; |
| 866 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 867 newStackOfNCards(kCardCount, boolValues[i])); |
| 868 StackCard* firstCard = [[stack cards] objectAtIndex:0]; |
| 869 StackCard* secondCard = [[stack cards] objectAtIndex:1]; |
| 870 |
| 871 const float kEndLimit = |
| 872 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 873 [stack setEndLimit:kEndLimit]; |
| 874 [stack fanOutCardsWithStartIndex:0]; |
| 875 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 876 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 877 |
| 878 CGFloat firstCardInitialOrigin = |
| 879 LayoutOffset(stack, firstCard.layout.position); |
| 880 CGFloat secondCardInitialOrigin = |
| 881 LayoutOffset(stack, secondCard.layout.position); |
| 882 |
| 883 // Scrolling toward end should result in overscroll. |
| 884 [stack scrollCardAtIndex:0 |
| 885 byDelta:kScrollAwayAmount |
| 886 allowEarlyOverscroll:YES |
| 887 decayOnOverscroll:YES |
| 888 scrollLeadingCards:YES]; |
| 889 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 890 EXPECT_TRUE([stack overextensionTowardEndOnFirstCard]); |
| 891 EXPECT_FALSE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 892 EXPECT_FLOAT_EQ(firstCardInitialOrigin + kScrollAwayAmount, |
| 893 LayoutOffset(stack, firstCard.layout.position)); |
| 894 EXPECT_FLOAT_EQ(secondCardInitialOrigin + kScrollAwayAmount, |
| 895 LayoutOffset(stack, secondCard.layout.position)); |
| 896 |
| 897 // Calling |eliminateOverextension| should undo the overscroll on the first |
| 898 // and second card. |
| 899 [stack eliminateOverextension]; |
| 900 EXPECT_FLOAT_EQ(firstCardInitialOrigin, |
| 901 LayoutOffset(stack, firstCard.layout.position)); |
| 902 EXPECT_FLOAT_EQ(secondCardInitialOrigin, |
| 903 LayoutOffset(stack, secondCard.layout.position)); |
| 904 |
| 905 // Scrolling toward start should result in overscroll. |
| 906 [stack scrollCardAtIndex:0 |
| 907 byDelta:-kScrollAwayAmount |
| 908 allowEarlyOverscroll:YES |
| 909 decayOnOverscroll:YES |
| 910 scrollLeadingCards:YES]; |
| 911 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 912 EXPECT_FALSE([stack overextensionTowardEndOnFirstCard]); |
| 913 EXPECT_TRUE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 914 EXPECT_FLOAT_EQ(firstCardInitialOrigin - kScrollAwayAmount, |
| 915 LayoutOffset(stack, firstCard.layout.position)); |
| 916 EXPECT_FLOAT_EQ(secondCardInitialOrigin - kScrollAwayAmount, |
| 917 LayoutOffset(stack, secondCard.layout.position)); |
| 918 |
| 919 // Calling |eliminateOverextension| should undo the overscroll on the first |
| 920 // card but leave the second card as-is, since it's not overscrolled. |
| 921 [stack eliminateOverextension]; |
| 922 EXPECT_FLOAT_EQ(firstCardInitialOrigin, |
| 923 LayoutOffset(stack, firstCard.layout.position)); |
| 924 EXPECT_FLOAT_EQ(secondCardInitialOrigin - kScrollAwayAmount, |
| 925 LayoutOffset(stack, secondCard.layout.position)); |
| 926 |
| 927 // Reset state to test pinch. |
| 928 [stack fanOutCardsWithStartIndex:0]; |
| 929 |
| 930 // Pinching first card toward end should result in it being overextended. |
| 931 [stack handleMultitouchWithFirstDelta:kScrollAwayAmount |
| 932 secondDelta:kScrollAwayAmount |
| 933 firstCardIndex:0 |
| 934 secondCardIndex:1 |
| 935 decayOnOverpinch:YES]; |
| 936 EXPECT_FLOAT_EQ(firstCardInitialOrigin + kScrollAwayAmount, |
| 937 LayoutOffset(stack, firstCard.layout.position)); |
| 938 EXPECT_FLOAT_EQ(secondCardInitialOrigin + kScrollAwayAmount, |
| 939 LayoutOffset(stack, secondCard.layout.position)); |
| 940 EXPECT_TRUE([stack overextensionTowardEndOnFirstCard]); |
| 941 EXPECT_FALSE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 942 |
| 943 // ELiminating the overextension, which is now an overpinch, should restore |
| 944 // the first card to its initial position but not alter the offset of the |
| 945 // second card. |
| 946 [stack eliminateOverextension]; |
| 947 EXPECT_FLOAT_EQ(firstCardInitialOrigin, |
| 948 LayoutOffset(stack, firstCard.layout.position)); |
| 949 EXPECT_FLOAT_EQ(secondCardInitialOrigin + kScrollAwayAmount, |
| 950 LayoutOffset(stack, secondCard.layout.position)); |
| 951 } |
| 952 } |
| 953 |
| 954 TEST_F(CardStackLayoutManagerTest, MultiCardOverscroll) { |
| 955 BOOL boolValues[2] = {NO, YES}; |
| 956 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 957 const unsigned int kCardCount = 3; |
| 958 const float kScrollAwayAmount = 100.0; |
| 959 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 960 newStackOfNCards(kCardCount, boolValues[i])); |
| 961 StackCard* firstCard = [[stack cards] objectAtIndex:0]; |
| 962 |
| 963 const float kEndLimit = |
| 964 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 965 [stack setEndLimit:kEndLimit]; |
| 966 [stack fanOutCardsWithStartIndex:0]; |
| 967 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 968 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 969 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 0, 1)); |
| 970 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 971 // Scrolling away from the start stack should result in overscroll. |
| 972 [stack scrollCardAtIndex:0 |
| 973 byDelta:kScrollAwayAmount |
| 974 allowEarlyOverscroll:YES |
| 975 decayOnOverscroll:YES |
| 976 scrollLeadingCards:YES]; |
| 977 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 978 EXPECT_TRUE([stack overextensionTowardEndOnFirstCard]); |
| 979 EXPECT_FALSE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 980 EXPECT_FLOAT_EQ(kMargin + kScrollAwayAmount, |
| 981 LayoutOffset(stack, firstCard.layout.position)); |
| 982 |
| 983 // Calling |eliminateOverextension| should restore the stack to its previous |
| 984 // fanned-out state. |
| 985 [stack eliminateOverextension]; |
| 986 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 987 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 988 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 0, 1)); |
| 989 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 990 |
| 991 // Scrolling toward the start more than is necessary to fully collapse the |
| 992 // stack should result in overscroll toward the start. |
| 993 CGFloat lastCardCollapsedPosition = |
| 994 kMargin + [stack staggerOffsetForIndexFromEdge:kCardCount - 1]; |
| 995 StackCard* lastCard = [[stack cards] objectAtIndex:kCardCount - 1]; |
| 996 CGFloat distanceToCollapsedStack = |
| 997 lastCardCollapsedPosition - |
| 998 LayoutOffset(stack, lastCard.layout.position); |
| 999 [stack scrollCardAtIndex:kCardCount - 1 |
| 1000 byDelta:distanceToCollapsedStack - 10 |
| 1001 allowEarlyOverscroll:YES |
| 1002 decayOnOverscroll:YES |
| 1003 scrollLeadingCards:YES]; |
| 1004 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1005 EXPECT_FALSE([stack overextensionTowardEndOnFirstCard]); |
| 1006 EXPECT_TRUE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 1007 EXPECT_FLOAT_EQ(kMargin - 10, |
| 1008 LayoutOffset(stack, firstCard.layout.position)); |
| 1009 } |
| 1010 } |
| 1011 |
| 1012 TEST_F(CardStackLayoutManagerTest, Fling) { |
| 1013 BOOL boolValues[2] = {NO, YES}; |
| 1014 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 1015 const unsigned int kCardCount = 3; |
| 1016 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 1017 newStackOfNCards(kCardCount, boolValues[i])); |
| 1018 StackCard* firstCard = [[stack cards] objectAtIndex:0]; |
| 1019 |
| 1020 const float kEndLimit = |
| 1021 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 1022 [stack setEndLimit:kEndLimit]; |
| 1023 [stack fanOutCardsWithStartIndex:0]; |
| 1024 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1025 EXPECT_FLOAT_EQ(kMargin, LayoutOffset(stack, firstCard.layout.position)); |
| 1026 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 0, 1)); |
| 1027 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 1028 |
| 1029 // Flinging on the first card should not result in overscroll... |
| 1030 [stack scrollCardAtIndex:0 |
| 1031 byDelta:-20.0 |
| 1032 allowEarlyOverscroll:NO |
| 1033 decayOnOverscroll:YES |
| 1034 scrollLeadingCards:YES]; |
| 1035 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1036 EXPECT_FALSE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 1037 EXPECT_FALSE([stack overextensionTowardEndOnFirstCard]); |
| 1038 // ... until the last card becomes overscrolled. |
| 1039 [stack scrollCardAtIndex:0 |
| 1040 byDelta:-[stack maximumStackLength] |
| 1041 allowEarlyOverscroll:NO |
| 1042 decayOnOverscroll:YES |
| 1043 scrollLeadingCards:YES]; |
| 1044 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1045 EXPECT_TRUE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 1046 EXPECT_FALSE([stack overextensionTowardEndOnFirstCard]); |
| 1047 } |
| 1048 } |
| 1049 |
| 1050 TEST_F(CardStackLayoutManagerTest, ScrollAroundStartStack) { |
| 1051 BOOL boolValues[2] = {NO, YES}; |
| 1052 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 1053 const unsigned int kCardCount = 3; |
| 1054 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 1055 newStackOfNCards(kCardCount, boolValues[i])); |
| 1056 |
| 1057 const float kEndLimit = |
| 1058 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 1059 [stack setEndLimit:kEndLimit]; |
| 1060 [stack fanOutCardsWithStartIndex:0]; |
| 1061 |
| 1062 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 0, 1)); |
| 1063 EXPECT_EQ(0, [stack lastStartStackCardIndex]); |
| 1064 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1065 |
| 1066 // Scroll second card into start stack. |
| 1067 CGFloat cardTwoStartStackOffset = |
| 1068 kMargin + [stack staggerOffsetForIndexFromEdge:1]; |
| 1069 LayoutRectPosition cardTwoPosition = |
| 1070 ((StackCard*)[[stack cards] objectAtIndex:1]).layout.position; |
| 1071 [stack scrollCardAtIndex:1 |
| 1072 byDelta:cardTwoStartStackOffset - |
| 1073 LayoutOffset(stack, cardTwoPosition) |
| 1074 allowEarlyOverscroll:YES |
| 1075 decayOnOverscroll:YES |
| 1076 scrollLeadingCards:YES]; |
| 1077 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1078 EXPECT_EQ(1, [stack lastStartStackCardIndex]); |
| 1079 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 1080 |
| 1081 // Scroll third card toward start stack, and check that everything is as |
| 1082 // expected. |
| 1083 [stack scrollCardAtIndex:2 |
| 1084 byDelta:kMaxStagger / -2.0 |
| 1085 allowEarlyOverscroll:YES |
| 1086 decayOnOverscroll:YES |
| 1087 scrollLeadingCards:YES]; |
| 1088 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1089 EXPECT_EQ(1, [stack lastStartStackCardIndex]); |
| 1090 EXPECT_FLOAT_EQ(kMaxStagger / 2.0, SeparationOnLayoutAxis(stack, 1, 2)); |
| 1091 |
| 1092 // Scroll third card away from stack stack, and check that second card |
| 1093 // doesn't come out before it should. |
| 1094 [stack scrollCardAtIndex:2 |
| 1095 byDelta:kMaxStagger / 4.0 |
| 1096 allowEarlyOverscroll:YES |
| 1097 decayOnOverscroll:YES |
| 1098 scrollLeadingCards:YES]; |
| 1099 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1100 EXPECT_EQ(1, [stack lastStartStackCardIndex]); |
| 1101 EXPECT_FLOAT_EQ(kMaxStagger * .75, SeparationOnLayoutAxis(stack, 1, 2)); |
| 1102 [stack scrollCardAtIndex:2 |
| 1103 byDelta:kMaxStagger / 4.0 + 1 |
| 1104 allowEarlyOverscroll:YES |
| 1105 decayOnOverscroll:YES |
| 1106 scrollLeadingCards:YES]; |
| 1107 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1108 EXPECT_EQ(0, [stack lastStartStackCardIndex]); |
| 1109 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 1110 } |
| 1111 } |
| 1112 |
| 1113 TEST_F(CardStackLayoutManagerTest, ScrollAroundEndStack) { |
| 1114 BOOL boolValues[2] = {NO, YES}; |
| 1115 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 1116 const unsigned int kCardCount = 7; |
| 1117 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 1118 newStackOfNCards(kCardCount, boolValues[i])); |
| 1119 |
| 1120 const float kEndLimit = 0.2 * [stack fannedStackLength]; |
| 1121 [stack setEndLimit:kEndLimit]; |
| 1122 [stack fanOutCardsWithStartIndex:4]; |
| 1123 |
| 1124 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 5, 6)); |
| 1125 EXPECT_EQ(4, [stack lastStartStackCardIndex]); |
| 1126 EXPECT_EQ(7, [stack firstEndStackCardIndex]); |
| 1127 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1128 |
| 1129 // Scroll seventh card into end stack. |
| 1130 CGFloat cardSevenEndStackOffset = |
| 1131 kEndLimit - ([stack staggerOffsetForIndexFromEdge:0] + |
| 1132 [stack minStackStaggerAmount]); |
| 1133 LayoutRectPosition cardSevenPosition = |
| 1134 ((StackCard*)[[stack cards] objectAtIndex:6]).layout.position; |
| 1135 [stack scrollCardAtIndex:6 |
| 1136 byDelta:cardSevenEndStackOffset - |
| 1137 LayoutOffset(stack, cardSevenPosition) |
| 1138 allowEarlyOverscroll:YES |
| 1139 decayOnOverscroll:YES |
| 1140 scrollLeadingCards:YES]; |
| 1141 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1142 EXPECT_EQ([stack firstEndStackCardIndex], 6); |
| 1143 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 5, 6)); |
| 1144 |
| 1145 // Scroll sixth card toward end stack, and check that everything is as |
| 1146 // expected. |
| 1147 [stack scrollCardAtIndex:5 |
| 1148 byDelta:kMaxStagger / 2.0 |
| 1149 allowEarlyOverscroll:YES |
| 1150 decayOnOverscroll:YES |
| 1151 scrollLeadingCards:YES]; |
| 1152 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1153 EXPECT_EQ([stack firstEndStackCardIndex], 6); |
| 1154 EXPECT_FLOAT_EQ(kMaxStagger / 2.0, SeparationOnLayoutAxis(stack, 5, 6)); |
| 1155 |
| 1156 // Scroll sixth card away from end stack, and check that seventh card |
| 1157 // doesn't come out before it should. |
| 1158 [stack scrollCardAtIndex:5 |
| 1159 byDelta:kMaxStagger / -4.0 |
| 1160 allowEarlyOverscroll:YES |
| 1161 decayOnOverscroll:YES |
| 1162 scrollLeadingCards:YES]; |
| 1163 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1164 EXPECT_EQ([stack firstEndStackCardIndex], 6); |
| 1165 EXPECT_FLOAT_EQ(SeparationOnLayoutAxis(stack, 5, 6), kMaxStagger * .75); |
| 1166 [stack scrollCardAtIndex:5 |
| 1167 byDelta:kMaxStagger / -4.0 - 1 |
| 1168 allowEarlyOverscroll:YES |
| 1169 decayOnOverscroll:YES |
| 1170 scrollLeadingCards:YES]; |
| 1171 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1172 EXPECT_EQ([stack firstEndStackCardIndex], 7); |
| 1173 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 5, 6)); |
| 1174 } |
| 1175 } |
| 1176 |
| 1177 TEST_F(CardStackLayoutManagerTest, BasicMultitouch) { |
| 1178 BOOL boolValues[2] = {NO, YES}; |
| 1179 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 1180 const unsigned int kCardCount = 3; |
| 1181 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 1182 newStackOfNCards(kCardCount, boolValues[i])); |
| 1183 |
| 1184 const float kEndLimit = |
| 1185 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 1186 [stack setEndLimit:kEndLimit]; |
| 1187 [stack fanOutCardsWithStartIndex:0]; |
| 1188 |
| 1189 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1190 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 0, 1)); |
| 1191 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 1192 |
| 1193 [stack handleMultitouchWithFirstDelta:-10 |
| 1194 secondDelta:50 |
| 1195 firstCardIndex:1 |
| 1196 secondCardIndex:2 |
| 1197 decayOnOverpinch:YES]; |
| 1198 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1199 EXPECT_FLOAT_EQ(kMaxStagger - 10, SeparationOnLayoutAxis(stack, 0, 1)); |
| 1200 EXPECT_FLOAT_EQ(kMaxStagger + 60, SeparationOnLayoutAxis(stack, 1, 2)); |
| 1201 } |
| 1202 } |
| 1203 |
| 1204 TEST_F(CardStackLayoutManagerTest, MultitouchBoundedByNeighbor) { |
| 1205 BOOL boolValues[2] = {NO, YES}; |
| 1206 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 1207 const unsigned int kCardCount = 3; |
| 1208 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 1209 newStackOfNCards(kCardCount, boolValues[i])); |
| 1210 |
| 1211 // Make sure that the stack end limit isn't hit in this test. |
| 1212 const float kEndLimit = 2.0 * [stack maximumStackLength]; |
| 1213 [stack setEndLimit:kEndLimit]; |
| 1214 [stack fanOutCardsWithStartIndex:0]; |
| 1215 |
| 1216 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1217 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 0, 1)); |
| 1218 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 1219 |
| 1220 CGFloat cardSize = (i > 0) ? kCardHeight : kCardWidth; |
| 1221 |
| 1222 // Verify that it's not possible to pinch the third card entirely off the |
| 1223 // second card. |
| 1224 [stack handleMultitouchWithFirstDelta:0 |
| 1225 secondDelta:2.0 * cardSize |
| 1226 firstCardIndex:1 |
| 1227 secondCardIndex:2 |
| 1228 decayOnOverpinch:YES]; |
| 1229 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1230 // Verify that it's not possible to pinch the second card entirely off the |
| 1231 // third card. |
| 1232 [stack handleMultitouchWithFirstDelta:-2.0 * cardSize |
| 1233 secondDelta:0 |
| 1234 firstCardIndex:1 |
| 1235 secondCardIndex:2 |
| 1236 decayOnOverpinch:YES]; |
| 1237 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1238 // Verify that it's not possible to pinch the two cards off each other. |
| 1239 [stack handleMultitouchWithFirstDelta:-cardSize |
| 1240 secondDelta:cardSize |
| 1241 firstCardIndex:1 |
| 1242 secondCardIndex:2 |
| 1243 decayOnOverpinch:YES]; |
| 1244 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1245 } |
| 1246 } |
| 1247 |
| 1248 TEST_F(CardStackLayoutManagerTest, OverpinchTowardStart) { |
| 1249 BOOL boolValues[2] = {NO, YES}; |
| 1250 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 1251 const unsigned int kCardCount = 2; |
| 1252 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 1253 newStackOfNCards(kCardCount, boolValues[i])); |
| 1254 |
| 1255 const float kEndLimit = |
| 1256 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 1257 [stack setEndLimit:kEndLimit]; |
| 1258 [stack fanOutCardsWithStartIndex:0]; |
| 1259 |
| 1260 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1261 StackCard* firstCard = [[stack cards] objectAtIndex:0]; |
| 1262 LayoutRectPosition firstCardPosition = firstCard.layout.position; |
| 1263 StackCard* secondCard = [[stack cards] objectAtIndex:1]; |
| 1264 LayoutRectPosition secondCardPosition = secondCard.layout.position; |
| 1265 |
| 1266 [stack handleMultitouchWithFirstDelta:-20 |
| 1267 secondDelta:10 |
| 1268 firstCardIndex:0 |
| 1269 secondCardIndex:1 |
| 1270 decayOnOverpinch:YES]; |
| 1271 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1272 // First card should have been overpinched. |
| 1273 EXPECT_TRUE([stack overextensionTowardStartOnCardAtIndex:0]); |
| 1274 EXPECT_FALSE([stack overextensionTowardEndOnFirstCard]); |
| 1275 EXPECT_FLOAT_EQ(LayoutOffset(stack, firstCardPosition) - 20, |
| 1276 LayoutOffset(stack, firstCard.layout.position)); |
| 1277 EXPECT_FLOAT_EQ(LayoutOffset(stack, secondCardPosition) + 10, |
| 1278 LayoutOffset(stack, secondCard.layout.position)); |
| 1279 } |
| 1280 } |
| 1281 |
| 1282 TEST_F(CardStackLayoutManagerTest, OverpinchTowardEnd) { |
| 1283 BOOL boolValues[2] = {NO, YES}; |
| 1284 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 1285 const unsigned int kCardCount = 2; |
| 1286 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 1287 newStackOfNCards(kCardCount, boolValues[i])); |
| 1288 |
| 1289 const float kEndLimit = |
| 1290 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 1291 [stack setEndLimit:kEndLimit]; |
| 1292 [stack fanOutCardsWithStartIndex:0]; |
| 1293 |
| 1294 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1295 StackCard* firstCard = [[stack cards] objectAtIndex:0]; |
| 1296 LayoutRectPosition firstCardPosition = firstCard.layout.position; |
| 1297 StackCard* secondCard = [[stack cards] objectAtIndex:1]; |
| 1298 LayoutRectPosition secondCardPosition = secondCard.layout.position; |
| 1299 |
| 1300 [stack handleMultitouchWithFirstDelta:20 |
| 1301 secondDelta:10 |
| 1302 firstCardIndex:0 |
| 1303 secondCardIndex:1 |
| 1304 decayOnOverpinch:YES]; |
| 1305 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1306 // Both first and second card should have moved. |
| 1307 EXPECT_FLOAT_EQ(LayoutOffset(stack, firstCardPosition) + 20, |
| 1308 LayoutOffset(stack, firstCard.layout.position)); |
| 1309 EXPECT_FLOAT_EQ(LayoutOffset(stack, secondCardPosition) + 10, |
| 1310 LayoutOffset(stack, secondCard.layout.position)); |
| 1311 } |
| 1312 } |
| 1313 |
| 1314 TEST_F(CardStackLayoutManagerTest, StressMultitouch) { |
| 1315 BOOL boolValues[2] = {NO, YES}; |
| 1316 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 1317 const unsigned int kCardCount = 30; |
| 1318 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 1319 newStackOfNCards(kCardCount, boolValues[i])); |
| 1320 |
| 1321 const float kEndLimit = |
| 1322 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 1323 [stack setEndLimit:kEndLimit]; |
| 1324 [stack fanOutCardsWithStartIndex:0]; |
| 1325 |
| 1326 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1327 |
| 1328 [stack handleMultitouchWithFirstDelta:-10 |
| 1329 secondDelta:50 |
| 1330 firstCardIndex:5 |
| 1331 secondCardIndex:10 |
| 1332 decayOnOverpinch:YES]; |
| 1333 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1334 |
| 1335 [stack handleMultitouchWithFirstDelta:20 |
| 1336 secondDelta:-10 |
| 1337 firstCardIndex:3 |
| 1338 secondCardIndex:15 |
| 1339 decayOnOverpinch:YES]; |
| 1340 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1341 |
| 1342 [stack handleMultitouchWithFirstDelta:-20 |
| 1343 secondDelta:-10 |
| 1344 firstCardIndex:0 |
| 1345 secondCardIndex:4 |
| 1346 decayOnOverpinch:YES]; |
| 1347 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1348 } |
| 1349 } |
| 1350 |
| 1351 TEST_F(CardStackLayoutManagerTest, ScrollAfterMultitouch) { |
| 1352 BOOL boolValues[2] = {NO, YES}; |
| 1353 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 1354 const unsigned int kCardCount = 3; |
| 1355 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 1356 newStackOfNCards(kCardCount, boolValues[i])); |
| 1357 |
| 1358 const float kEndLimit = |
| 1359 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 1360 const float kPinchDistance = 50; |
| 1361 [stack setEndLimit:kEndLimit]; |
| 1362 [stack fanOutCardsWithStartIndex:0]; |
| 1363 |
| 1364 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1365 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 1366 |
| 1367 [stack handleMultitouchWithFirstDelta:0 |
| 1368 secondDelta:kPinchDistance |
| 1369 firstCardIndex:1 |
| 1370 secondCardIndex:2 |
| 1371 decayOnOverpinch:YES]; |
| 1372 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1373 EXPECT_FLOAT_EQ(kMaxStagger + kPinchDistance, |
| 1374 SeparationOnLayoutAxis(stack, 1, 2)); |
| 1375 |
| 1376 [stack scrollCardAtIndex:2 |
| 1377 byDelta:10 |
| 1378 allowEarlyOverscroll:YES |
| 1379 decayOnOverscroll:YES |
| 1380 scrollLeadingCards:YES]; |
| 1381 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1382 // Separation between cards should be maintained. |
| 1383 EXPECT_FLOAT_EQ(kMaxStagger + kPinchDistance, |
| 1384 SeparationOnLayoutAxis(stack, 1, 2)); |
| 1385 [stack scrollCardAtIndex:2 |
| 1386 byDelta:-20 |
| 1387 allowEarlyOverscroll:YES |
| 1388 decayOnOverscroll:YES |
| 1389 scrollLeadingCards:YES]; |
| 1390 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1391 // Separation between cards should be maintained. |
| 1392 EXPECT_FLOAT_EQ(kMaxStagger + kPinchDistance, |
| 1393 SeparationOnLayoutAxis(stack, 1, 2)); |
| 1394 } |
| 1395 } |
| 1396 |
| 1397 TEST_F(CardStackLayoutManagerTest, ScrollEveningOutAfterMultitouch) { |
| 1398 BOOL boolValues[2] = {NO, YES}; |
| 1399 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 1400 const unsigned int kCardCount = 3; |
| 1401 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 1402 newStackOfNCards(kCardCount, boolValues[i])); |
| 1403 |
| 1404 const float kEndLimit = |
| 1405 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 1406 const float kPinchDistance = kMaxStagger / 2.0; |
| 1407 [stack setEndLimit:kEndLimit]; |
| 1408 [stack fanOutCardsWithStartIndex:0]; |
| 1409 |
| 1410 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1411 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 1412 |
| 1413 // Scroll cards toward start stack to give some room to scroll second card |
| 1414 // toward end stack (see below). |
| 1415 [stack scrollCardAtIndex:1 |
| 1416 byDelta:-10 |
| 1417 allowEarlyOverscroll:YES |
| 1418 decayOnOverscroll:YES |
| 1419 scrollLeadingCards:YES]; |
| 1420 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1421 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 1422 |
| 1423 // Pinch the third card closer to the second card. |
| 1424 [stack handleMultitouchWithFirstDelta:0 |
| 1425 secondDelta:-kPinchDistance |
| 1426 firstCardIndex:1 |
| 1427 secondCardIndex:2 |
| 1428 decayOnOverpinch:YES]; |
| 1429 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1430 EXPECT_FLOAT_EQ(kMaxStagger - kPinchDistance, |
| 1431 SeparationOnLayoutAxis(stack, 1, 2)); |
| 1432 |
| 1433 // Separation between cards should be maintained when second card is |
| 1434 // scrolled towards third card. |
| 1435 [stack scrollCardAtIndex:1 |
| 1436 byDelta:10 |
| 1437 allowEarlyOverscroll:YES |
| 1438 decayOnOverscroll:YES |
| 1439 scrollLeadingCards:YES]; |
| 1440 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1441 EXPECT_FLOAT_EQ(kMaxStagger - kPinchDistance, |
| 1442 SeparationOnLayoutAxis(stack, 1, 2)); |
| 1443 |
| 1444 // Scrolling second card away from third card by the distance that the |
| 1445 // third card was pinched should restore separation of |kMaxStagger| |
| 1446 // between the second and third card. |
| 1447 [stack scrollCardAtIndex:1 |
| 1448 byDelta:-kPinchDistance |
| 1449 allowEarlyOverscroll:YES |
| 1450 decayOnOverscroll:YES |
| 1451 scrollLeadingCards:YES]; |
| 1452 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1453 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 1454 } |
| 1455 } |
| 1456 |
| 1457 TEST_F(CardStackLayoutManagerTest, ScrollAroundStartStackAfterMultitouch) { |
| 1458 BOOL boolValues[2] = {NO, YES}; |
| 1459 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 1460 const unsigned int kCardCount = 3; |
| 1461 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 1462 newStackOfNCards(kCardCount, boolValues[i])); |
| 1463 |
| 1464 const float kEndLimit = |
| 1465 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 1466 const float kPinchDistance = 50; |
| 1467 [stack setEndLimit:kEndLimit]; |
| 1468 [stack fanOutCardsWithStartIndex:0]; |
| 1469 |
| 1470 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 0, 1)); |
| 1471 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1472 |
| 1473 [stack handleMultitouchWithFirstDelta:0 |
| 1474 secondDelta:kPinchDistance |
| 1475 firstCardIndex:1 |
| 1476 secondCardIndex:2 |
| 1477 decayOnOverpinch:YES]; |
| 1478 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1479 EXPECT_FLOAT_EQ(kMaxStagger + kPinchDistance, |
| 1480 SeparationOnLayoutAxis(stack, 1, 2)); |
| 1481 |
| 1482 [stack scrollCardAtIndex:2 |
| 1483 byDelta:-20 |
| 1484 allowEarlyOverscroll:YES |
| 1485 decayOnOverscroll:YES |
| 1486 scrollLeadingCards:YES]; |
| 1487 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1488 // Separation between cards should be maintained. |
| 1489 EXPECT_FLOAT_EQ(kMaxStagger + kPinchDistance, |
| 1490 SeparationOnLayoutAxis(stack, 1, 2)); |
| 1491 |
| 1492 // Scroll the cards completely into the start stack. |
| 1493 CGFloat lastCardCollapsedPosition = |
| 1494 kMargin + [stack staggerOffsetForIndexFromEdge:kCardCount - 1]; |
| 1495 StackCard* lastCard = [[stack cards] objectAtIndex:kCardCount - 1]; |
| 1496 CGFloat distanceToCollapsedStack = |
| 1497 lastCardCollapsedPosition - |
| 1498 LayoutOffset(stack, lastCard.layout.position); |
| 1499 [stack scrollCardAtIndex:2 |
| 1500 byDelta:distanceToCollapsedStack |
| 1501 allowEarlyOverscroll:YES |
| 1502 decayOnOverscroll:YES |
| 1503 scrollLeadingCards:YES]; |
| 1504 EXPECT_EQ((NSInteger)(kCardCount - 1), [stack lastStartStackCardIndex]); |
| 1505 // Scroll the cards out of the start stack: they should now be separated by |
| 1506 // |kMaxStagger|. |
| 1507 [stack scrollCardAtIndex:2 |
| 1508 byDelta:2 * kMaxStagger |
| 1509 allowEarlyOverscroll:YES |
| 1510 decayOnOverscroll:YES |
| 1511 scrollLeadingCards:YES]; |
| 1512 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 1, 2)); |
| 1513 } |
| 1514 } |
| 1515 |
| 1516 TEST_F(CardStackLayoutManagerTest, ScrollAroundEndStackAfterMultitouch) { |
| 1517 BOOL boolValues[2] = {NO, YES}; |
| 1518 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 1519 const unsigned int kCardCount = 7; |
| 1520 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 1521 newStackOfNCards(kCardCount, boolValues[i])); |
| 1522 |
| 1523 const float kEndLimit = 0.3 * [stack fannedStackLength]; |
| 1524 const float kPinchDistance = 20; |
| 1525 [stack setEndLimit:kEndLimit]; |
| 1526 // Start in the middle of the stack to be able to scroll cards into the end |
| 1527 // stack. |
| 1528 [stack fanOutCardsWithStartIndex:4]; |
| 1529 |
| 1530 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 5, 6)); |
| 1531 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1532 |
| 1533 [stack handleMultitouchWithFirstDelta:0 |
| 1534 secondDelta:kPinchDistance |
| 1535 firstCardIndex:5 |
| 1536 secondCardIndex:6 |
| 1537 decayOnOverpinch:YES]; |
| 1538 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1539 EXPECT_FLOAT_EQ(kMaxStagger + kPinchDistance, |
| 1540 SeparationOnLayoutAxis(stack, 5, 6)); |
| 1541 |
| 1542 [stack scrollCardAtIndex:5 |
| 1543 byDelta:-10 |
| 1544 allowEarlyOverscroll:YES |
| 1545 decayOnOverscroll:YES |
| 1546 scrollLeadingCards:YES]; |
| 1547 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1548 // Separation between cards should be maintained. |
| 1549 EXPECT_FLOAT_EQ(kMaxStagger + kPinchDistance, |
| 1550 SeparationOnLayoutAxis(stack, 5, 6)); |
| 1551 // Scroll the two cards in question into the end stack. |
| 1552 CGFloat cardSixEndStackPosition = |
| 1553 kEndLimit - [stack staggerOffsetForIndexFromEdge:1]; |
| 1554 LayoutRectPosition cardSixPosition = |
| 1555 ((StackCard*)[[stack cards] objectAtIndex:5]).layout.position; |
| 1556 [stack scrollCardAtIndex:5 |
| 1557 byDelta:cardSixEndStackPosition - |
| 1558 LayoutOffset(stack, cardSixPosition) |
| 1559 allowEarlyOverscroll:YES |
| 1560 decayOnOverscroll:YES |
| 1561 scrollLeadingCards:YES]; |
| 1562 cardSixPosition = |
| 1563 ((StackCard*)[[stack cards] objectAtIndex:5]).layout.position; |
| 1564 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1565 EXPECT_EQ(5, [stack firstEndStackCardIndex]); |
| 1566 // Scroll the cards out of the end stack: cards should now be |
| 1567 // separated by |kMaxStagger|. |
| 1568 [stack scrollCardAtIndex:5 |
| 1569 byDelta:-2.0 * kMaxStagger |
| 1570 allowEarlyOverscroll:YES |
| 1571 decayOnOverscroll:YES |
| 1572 scrollLeadingCards:YES]; |
| 1573 EXPECT_FLOAT_EQ(SeparationOnLayoutAxis(stack, 5, 6), kMaxStagger); |
| 1574 } |
| 1575 } |
| 1576 |
| 1577 TEST_F(CardStackLayoutManagerTest, ScrollAfterPinchOutOfStartStack) { |
| 1578 BOOL boolValues[2] = {NO, YES}; |
| 1579 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 1580 const unsigned int kCardCount = 3; |
| 1581 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 1582 newStackOfNCards(kCardCount, boolValues[i])); |
| 1583 |
| 1584 const float kEndLimit = |
| 1585 kDefaultEndLimitFraction * [stack fannedStackLength]; |
| 1586 const float kPinchDistance = 50; |
| 1587 [stack setEndLimit:kEndLimit]; |
| 1588 [stack fanOutCardsWithStartIndex:0]; |
| 1589 |
| 1590 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 0, 1)); |
| 1591 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1592 |
| 1593 [stack handleMultitouchWithFirstDelta:0 |
| 1594 secondDelta:kPinchDistance |
| 1595 firstCardIndex:1 |
| 1596 secondCardIndex:2 |
| 1597 decayOnOverpinch:YES]; |
| 1598 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1599 EXPECT_FLOAT_EQ(kMaxStagger + kPinchDistance, |
| 1600 SeparationOnLayoutAxis(stack, 1, 2)); |
| 1601 |
| 1602 [stack scrollCardAtIndex:1 |
| 1603 byDelta:-20 |
| 1604 allowEarlyOverscroll:YES |
| 1605 decayOnOverscroll:YES |
| 1606 scrollLeadingCards:YES]; |
| 1607 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1608 // Separation between cards should be maintained. |
| 1609 EXPECT_FLOAT_EQ(kMaxStagger + kPinchDistance, |
| 1610 SeparationOnLayoutAxis(stack, 1, 2)); |
| 1611 // Scroll the cards completely into the start stack. |
| 1612 CGFloat lastCardCollapsedPosition = |
| 1613 kMargin + [stack staggerOffsetForIndexFromEdge:kCardCount - 1]; |
| 1614 StackCard* lastCard = [[stack cards] objectAtIndex:kCardCount - 1]; |
| 1615 CGFloat distanceToCollapsedStack = |
| 1616 lastCardCollapsedPosition - |
| 1617 LayoutOffset(stack, lastCard.layout.position); |
| 1618 [stack scrollCardAtIndex:kCardCount - 1 |
| 1619 byDelta:distanceToCollapsedStack |
| 1620 allowEarlyOverscroll:YES |
| 1621 decayOnOverscroll:YES |
| 1622 scrollLeadingCards:YES]; |
| 1623 EXPECT_EQ((NSInteger)(kCardCount - 1), [stack lastStartStackCardIndex]); |
| 1624 CGFloat inStackSeparation = SeparationOnLayoutAxis(stack, 1, 2); |
| 1625 // Pinch the third card far out of the start stack. |
| 1626 [stack handleMultitouchWithFirstDelta:0 |
| 1627 secondDelta:kMaxStagger |
| 1628 firstCardIndex:1 |
| 1629 secondCardIndex:2 |
| 1630 decayOnOverpinch:YES]; |
| 1631 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1632 EXPECT_FLOAT_EQ(inStackSeparation + kMaxStagger, |
| 1633 SeparationOnLayoutAxis(stack, 1, 2)); |
| 1634 // A scroll should immediately bring the second card out of the start |
| 1635 // stack, without affecting the distance between the second and third cards. |
| 1636 [stack scrollCardAtIndex:2 |
| 1637 byDelta:kMaxStagger / 2.0 |
| 1638 allowEarlyOverscroll:YES |
| 1639 decayOnOverscroll:YES |
| 1640 scrollLeadingCards:YES]; |
| 1641 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1642 EXPECT_FLOAT_EQ(inStackSeparation + kMaxStagger, |
| 1643 SeparationOnLayoutAxis(stack, 1, 2)); |
| 1644 } |
| 1645 } |
| 1646 |
| 1647 TEST_F(CardStackLayoutManagerTest, ScrollAfterPinchOutOfEndStack) { |
| 1648 BOOL boolValues[2] = {NO, YES}; |
| 1649 for (unsigned long i = 0; i < arraysize(boolValues); i++) { |
| 1650 const unsigned int kCardCount = 7; |
| 1651 base::scoped_nsobject<CardStackLayoutManager> stack( |
| 1652 newStackOfNCards(kCardCount, boolValues[i])); |
| 1653 |
| 1654 const float kEndLimit = 0.3 * [stack fannedStackLength]; |
| 1655 const float kPinchDistance = 20; |
| 1656 [stack setEndLimit:kEndLimit]; |
| 1657 // Start in the middle of the stack to be able to scroll cards into the end |
| 1658 // stack. |
| 1659 [stack fanOutCardsWithStartIndex:4]; |
| 1660 |
| 1661 EXPECT_FLOAT_EQ(kMaxStagger, SeparationOnLayoutAxis(stack, 5, 6)); |
| 1662 ValidateCardPositioningConstraints(stack, kEndLimit, true); |
| 1663 |
| 1664 [stack handleMultitouchWithFirstDelta:0 |
| 1665 secondDelta:kPinchDistance |
| 1666 firstCardIndex:5 |
| 1667 secondCardIndex:6 |
| 1668 decayOnOverpinch:YES]; |
| 1669 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1670 EXPECT_FLOAT_EQ(kMaxStagger + kPinchDistance, |
| 1671 SeparationOnLayoutAxis(stack, 5, 6)); |
| 1672 |
| 1673 [stack scrollCardAtIndex:5 |
| 1674 byDelta:-10 |
| 1675 allowEarlyOverscroll:YES |
| 1676 decayOnOverscroll:YES |
| 1677 scrollLeadingCards:YES]; |
| 1678 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1679 // Separation between cards should be maintained. |
| 1680 EXPECT_FLOAT_EQ(kMaxStagger + kPinchDistance, |
| 1681 SeparationOnLayoutAxis(stack, 5, 6)); |
| 1682 |
| 1683 // Scroll the two cards in question into the end stack. |
| 1684 CGFloat cardSixEndStackOffset = |
| 1685 kEndLimit - ([stack staggerOffsetForIndexFromEdge:1] + |
| 1686 [stack minStackStaggerAmount]); |
| 1687 LayoutRectPosition cardSixPosition = |
| 1688 ((StackCard*)[[stack cards] objectAtIndex:5]).layout.position; |
| 1689 [stack scrollCardAtIndex:5 |
| 1690 byDelta:cardSixEndStackOffset - |
| 1691 LayoutOffset(stack, cardSixPosition) |
| 1692 allowEarlyOverscroll:YES |
| 1693 decayOnOverscroll:YES |
| 1694 scrollLeadingCards:YES]; |
| 1695 cardSixPosition = |
| 1696 ((StackCard*)[[stack cards] objectAtIndex:5]).layout.position; |
| 1697 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1698 EXPECT_EQ(5, [stack firstEndStackCardIndex]); |
| 1699 CGFloat inStackSeparation = SeparationOnLayoutAxis(stack, 5, 6); |
| 1700 // Pinch the sixth card far out of the start stack. |
| 1701 [stack handleMultitouchWithFirstDelta:-2.0 * kMaxStagger |
| 1702 secondDelta:0 |
| 1703 firstCardIndex:5 |
| 1704 secondCardIndex:6 |
| 1705 decayOnOverpinch:YES]; |
| 1706 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1707 EXPECT_EQ(6, [stack firstEndStackCardIndex]); |
| 1708 EXPECT_FLOAT_EQ(inStackSeparation + 2 * kMaxStagger, |
| 1709 SeparationOnLayoutAxis(stack, 5, 6)); |
| 1710 // A scroll should immediately bring the seventh card out of the start |
| 1711 // stack, without affecting the distance between the sixth and seventh |
| 1712 // cards. |
| 1713 [stack scrollCardAtIndex:5 |
| 1714 byDelta:kMaxStagger / -2.0 |
| 1715 allowEarlyOverscroll:YES |
| 1716 decayOnOverscroll:YES |
| 1717 scrollLeadingCards:YES]; |
| 1718 ValidateCardPositioningConstraints(stack, kEndLimit, false); |
| 1719 EXPECT_EQ(7, [stack firstEndStackCardIndex]); |
| 1720 EXPECT_FLOAT_EQ(inStackSeparation + 2 * kMaxStagger, |
| 1721 SeparationOnLayoutAxis(stack, 5, 6)); |
| 1722 } |
| 1723 } |
| 1724 |
| 1725 } // namespace |
OLD | NEW |