| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "chrome/browser/cocoa/tabpose_window.h" | 5 #import "chrome/browser/cocoa/tabpose_window.h" |
| 6 | 6 |
| 7 #import <QuartzCore/QuartzCore.h> | 7 #import <QuartzCore/QuartzCore.h> |
| 8 | 8 |
| 9 #import "chrome/browser/cocoa/browser_window_controller.h" |
| 10 #import "chrome/browser/cocoa/tab_strip_controller.h" |
| 11 |
| 9 const int kTopGradientHeight = 15; | 12 const int kTopGradientHeight = 15; |
| 10 | 13 |
| 14 NSString* const kAnimationIdKey = @"AnimationId"; |
| 15 NSString* const kAnimationIdFadeIn = @"FadeIn"; |
| 16 NSString* const kAnimationIdFadeOut = @"FadeOut"; |
| 17 |
| 18 const CGFloat kDefaultAnimationDuration = 0.25; // In seconds. |
| 19 const CGFloat kSlomoFactor = 4; |
| 20 |
| 11 // CAGradientLayer is 10.6-only -- roll our own. | 21 // CAGradientLayer is 10.6-only -- roll our own. |
| 12 @interface DarkGradientLayer : CALayer | 22 @interface DarkGradientLayer : CALayer |
| 13 - (void)drawInContext:(CGContextRef)context; | 23 - (void)drawInContext:(CGContextRef)context; |
| 14 @end | 24 @end |
| 15 | 25 |
| 16 @implementation DarkGradientLayer | 26 @implementation DarkGradientLayer |
| 17 - (void)drawInContext:(CGContextRef)context { | 27 - (void)drawInContext:(CGContextRef)context { |
| 18 scoped_cftyperef<CGColorSpaceRef> grayColorSpace( | 28 scoped_cftyperef<CGColorSpaceRef> grayColorSpace( |
| 19 CGColorSpaceCreateWithName(kCGColorSpaceGenericGray)); | 29 CGColorSpaceCreateWithName(kCGColorSpaceGenericGray)); |
| 20 CGFloat grays[] = { 0.277, 1.0, 0.39, 1.0 }; | 30 CGFloat grays[] = { 0.277, 1.0, 0.39, 1.0 }; |
| 21 CGFloat locations[] = { 0, 1 }; | 31 CGFloat locations[] = { 0, 1 }; |
| 22 scoped_cftyperef<CGGradientRef> gradient(CGGradientCreateWithColorComponents( | 32 scoped_cftyperef<CGGradientRef> gradient(CGGradientCreateWithColorComponents( |
| 23 grayColorSpace.get(), grays, locations, arraysize(locations))); | 33 grayColorSpace.get(), grays, locations, arraysize(locations))); |
| 24 CGPoint topLeft = CGPointMake(0.0, kTopGradientHeight); | 34 CGPoint topLeft = CGPointMake(0.0, kTopGradientHeight); |
| 25 CGContextDrawLinearGradient(context, gradient.get(), topLeft, CGPointZero, 0); | 35 CGContextDrawLinearGradient(context, gradient.get(), topLeft, CGPointZero, 0); |
| 26 } | 36 } |
| 27 @end | 37 @end |
| 28 | 38 |
| 39 namespace { |
| 40 |
| 41 class ScopedCAActionDisabler { |
| 42 public: |
| 43 ScopedCAActionDisabler() { |
| 44 [CATransaction begin]; |
| 45 [CATransaction setValue:[NSNumber numberWithBool:YES] |
| 46 forKey:kCATransactionDisableActions]; |
| 47 } |
| 48 |
| 49 ~ScopedCAActionDisabler() { |
| 50 [CATransaction commit]; |
| 51 } |
| 52 }; |
| 53 |
| 54 class ScopedCAActionSetDuration { |
| 55 public: |
| 56 explicit ScopedCAActionSetDuration(CGFloat duration) { |
| 57 [CATransaction begin]; |
| 58 [CATransaction setValue:[NSNumber numberWithFloat:duration] |
| 59 forKey:kCATransactionAnimationDuration]; |
| 60 } |
| 61 |
| 62 ~ScopedCAActionSetDuration() { |
| 63 [CATransaction commit]; |
| 64 } |
| 65 }; |
| 66 |
| 67 } // namespace |
| 68 |
| 69 // Given the number |n| of tiles with a desired aspect ratio of |a| and a |
| 70 // desired distance |dx|, |dy| between tiles, returns how many tiles fit |
| 71 // vertically into a rectangle with the dimensions |w_c|, |h_c|. This returns |
| 72 // an exact solution, which is usually a fractional number. |
| 73 static float FitNRectsWithAspectIntoBoundingSizeWithConstantPadding( |
| 74 int n, double a, int w_c, int h_c, int dx, int dy) { |
| 75 // We want to have the small rects have the same aspect ratio a as a full |
| 76 // tab. Let w, h be the size of a small rect, and w_c, h_c the size of the |
| 77 // container. dx, dy are the distances between small rects in x, y direction. |
| 78 |
| 79 // Geometry yields: |
| 80 // w_c = nx * (w + dx) - dx <=> w = (w_c + d_x) / nx - d_x |
| 81 // h_c = ny * (h + dy) - dy <=> h = (h_c + d_y) / ny - d_t |
| 82 // Plugging this into |
| 83 // a := tab_width / tab_height = w / h |
| 84 // yields |
| 85 // a = ((w_c - (nx - 1)*d_x)*ny) / (nx*(h_c - (ny - 1)*d_y)) |
| 86 // Plugging in nx = n/ny and pen and paper (or wolfram alpha: |
| 87 // http://www.wolframalpha.com/input/?i=(-sqrt((d+n-a+f+n)^2-4+(a+f%2Ba+h)+(-d
+n-n+w))%2Ba+f+n-d+n)/(2+a+(f%2Bh)) , (solution for nx) |
| 88 // http://www.wolframalpha.com/input/?i=+(-sqrt((a+f+n-d+n)^2-4+(d%2Bw)+(-a+f+
n-a+h+n))-a+f+n%2Bd+n)/(2+(d%2Bw)) , (solution for ny) |
| 89 // ) gives us nx and ny (but the wrong root -- s/-sqrt(FOO)/sqrt(FOO)/. |
| 90 |
| 91 // This function returns ny. |
| 92 return (sqrt(pow(n * (a * dy - dx), 2) + |
| 93 4 * n * a * (dx + w_c) * (dy + h_c)) - |
| 94 n * (a * dy - dx)) |
| 95 / |
| 96 (2 * (dx + w_c)); |
| 97 } |
| 98 |
| 99 namespace tabpose { |
| 100 |
| 101 // A tile is what is shown for a single tab in tabpose mode. It consists of a |
| 102 // title, favicon, thumbnail image, and pre- and postanimation rects. |
| 103 // TODO(thakis): Right now, it only consists of a thumb rect. |
| 104 class Tile { |
| 105 public: |
| 106 // Returns the rectangle this thumbnail is at at the beginning of the zoom-in |
| 107 // animation. |tile| is the rectangle that's covering the whole tab area when |
| 108 // the animation starts. |
| 109 NSRect GetStartRectRelativeTo(const Tile& tile) const; |
| 110 NSRect thumb_rect() const { return thumb_rect_; } |
| 111 |
| 112 private: |
| 113 friend class TileSet; |
| 114 |
| 115 // The thumb rect includes infobars, detached thumbnail bar, web contents, |
| 116 // and devtools. |
| 117 NSRect thumb_rect_; |
| 118 NSRect start_thumb_rect_; |
| 119 }; |
| 120 |
| 121 NSRect Tile::GetStartRectRelativeTo(const Tile& tile) const { |
| 122 NSRect rect = start_thumb_rect_; |
| 123 rect.origin.x -= tile.start_thumb_rect_.origin.x; |
| 124 rect.origin.y -= tile.start_thumb_rect_.origin.y; |
| 125 return rect; |
| 126 } |
| 127 |
| 128 // A tileset is responsible for owning and laying out all |Tile|s shown in a |
| 129 // tabpose window. |
| 130 class TileSet { |
| 131 public: |
| 132 // Fills in |tiles_|. |
| 133 void Build(TabStripModel* source_model); |
| 134 |
| 135 // Computes coordinates for |tiles_|. |
| 136 void Layout(NSRect containing_rect); |
| 137 |
| 138 int selected_index() const { return selected_index_; } |
| 139 void set_selected_index(int index); |
| 140 void ResetSelectedIndex() { selected_index_ = initial_index_; } |
| 141 |
| 142 const Tile& selected_tile() const { return tiles_[selected_index()]; } |
| 143 const Tile& tile_at(int index) const { return tiles_[index]; } |
| 144 |
| 145 private: |
| 146 std::vector<Tile> tiles_; // Doesn't change often, hence values are fine. |
| 147 |
| 148 int selected_index_; |
| 149 int initial_index_; |
| 150 }; |
| 151 |
| 152 void TileSet::Build(TabStripModel* source_model) { |
| 153 selected_index_ = initial_index_ = source_model->selected_index(); |
| 154 tiles_.resize(source_model->count()); |
| 155 } |
| 156 |
| 157 void TileSet::Layout(NSRect containing_rect) { |
| 158 int tile_count = tiles_.size(); |
| 159 |
| 160 // Room around the tiles insde of |containing_rect|. |
| 161 const int kSmallPaddingTop = 30; |
| 162 const int kSmallPaddingLeft = 30; |
| 163 const int kSmallPaddingRight = 30; |
| 164 const int kSmallPaddingBottom = 30; |
| 165 |
| 166 // Room between the tiles. |
| 167 const int kSmallPaddingX = 15; |
| 168 const int kSmallPaddingY = 13; |
| 169 |
| 170 // Aspect ratio of the containing rect. |
| 171 CGFloat aspect = NSWidth(containing_rect) / NSHeight(containing_rect); |
| 172 |
| 173 // Room left in container after the outer padding is removed. |
| 174 double container_width = |
| 175 NSWidth(containing_rect) - kSmallPaddingLeft - kSmallPaddingRight; |
| 176 double container_height = |
| 177 NSHeight(containing_rect) - kSmallPaddingTop - kSmallPaddingBottom; |
| 178 |
| 179 // The tricky part is figuring out the size of a tab thumbnail, or since the |
| 180 // size of the containing rect is known, the number of tiles in x and y |
| 181 // direction. |
| 182 // Given are the size of the containing rect, and the number of thumbnails |
| 183 // that need to fit into that rect. The aspect ratio of the thumbnails needs |
| 184 // to be the same as that of |containing_rect|, else they will look distorted. |
| 185 // The thumbnails need to be distributed such that |
| 186 // |count_x * count_y >= tile_count|, and such that wasted space is minimized. |
| 187 // See the comments in |
| 188 // |FitNRectsWithAspectIntoBoundingSizeWithConstantPadding()| for a more |
| 189 // detailed discussion. |
| 190 // TODO(thakis): It might be good enough to choose |count_x| and |count_y| |
| 191 // such that count_x / count_y is roughly equal to |aspect|? |
| 192 double fny = FitNRectsWithAspectIntoBoundingSizeWithConstantPadding( |
| 193 tile_count, aspect, container_width, container_height, |
| 194 kSmallPaddingX, kSmallPaddingY); |
| 195 int count_y(roundf(fny)); |
| 196 int count_x(ceilf(tile_count / float(count_y))); |
| 197 int last_row_count_x = tile_count - count_x * (count_y - 1); |
| 198 |
| 199 // Now that |count_x| and |count_y| are known, it's straightforward to compute |
| 200 // thumbnail width/height. See comment in |
| 201 // |FitNRectsWithAspectIntoBoundingSizeWithConstantPadding| for the derivation |
| 202 // of these two formulas. |
| 203 int small_width = |
| 204 floor((container_width + kSmallPaddingX) / float(count_x) - |
| 205 kSmallPaddingX); |
| 206 int small_height = |
| 207 floor((container_height + kSmallPaddingY) / float(count_y) - |
| 208 kSmallPaddingY); |
| 209 |
| 210 // |small_width / small_height| has only roughly an aspect ratio of |aspect|. |
| 211 // Shrink the thumbnail rect to make the aspect ratio fit exactly, and add |
| 212 // the extra space won by shrinking to the outer padding. |
| 213 int smallExtraPaddingLeft = 0; |
| 214 int smallExtraPaddingTop = 0; |
| 215 if (aspect > small_width/float(small_height)) { |
| 216 small_height = small_width / aspect; |
| 217 CGFloat all_tiles_height = |
| 218 (small_height + kSmallPaddingY) * count_y - kSmallPaddingY; |
| 219 smallExtraPaddingTop = (container_height - all_tiles_height)/2; |
| 220 } else { |
| 221 small_width = small_height * aspect; |
| 222 CGFloat all_tiles_width = |
| 223 (small_width + kSmallPaddingX) * count_x - kSmallPaddingX; |
| 224 smallExtraPaddingLeft = (container_width - all_tiles_width)/2; |
| 225 } |
| 226 |
| 227 // Compute inter-tile padding in the zoomed-out view. |
| 228 CGFloat scale_small_to_big = NSWidth(containing_rect) / float(small_width); |
| 229 CGFloat big_padding_x = kSmallPaddingX * scale_small_to_big; |
| 230 CGFloat big_padding_y = kSmallPaddingY * scale_small_to_big; |
| 231 |
| 232 // Now all dimensions are known. Lay out all tiles on a regular grid: |
| 233 // X X X X |
| 234 // X X X X |
| 235 // X X |
| 236 for (int row = 0, i = 0; i < tile_count; ++row) { |
| 237 for (int col = 0; col < count_x && i < tile_count; ++col, ++i) { |
| 238 // Compute the smalled, zoomed-out thumbnail rect. |
| 239 tiles_[i].thumb_rect_.size = NSMakeSize(small_width, small_height); |
| 240 |
| 241 int small_x = col * (small_width + kSmallPaddingX) + |
| 242 kSmallPaddingLeft + smallExtraPaddingLeft; |
| 243 int small_y = row * (small_height + kSmallPaddingY) + |
| 244 kSmallPaddingTop + smallExtraPaddingTop; |
| 245 |
| 246 tiles_[i].thumb_rect_.origin = NSMakePoint( |
| 247 small_x, NSHeight(containing_rect) - small_y - small_height); |
| 248 |
| 249 // Compute the big, pre-zoom thumbnail rect. |
| 250 tiles_[i].start_thumb_rect_.size = containing_rect.size; |
| 251 |
| 252 int big_x = col * (NSWidth(containing_rect) + big_padding_x); |
| 253 int big_y = row * (NSHeight(containing_rect) + big_padding_y); |
| 254 tiles_[i].start_thumb_rect_.origin = NSMakePoint(big_x, -big_y); |
| 255 } |
| 256 } |
| 257 |
| 258 // Go through last row and center it: |
| 259 // X X X X |
| 260 // X X X X |
| 261 // X X |
| 262 int last_row_empty_tiles_x = count_x - last_row_count_x; |
| 263 CGFloat small_last_row_shift_x = |
| 264 last_row_empty_tiles_x * (small_width + kSmallPaddingX) / 2; |
| 265 CGFloat big_last_row_shift_x = |
| 266 last_row_empty_tiles_x * (NSWidth(containing_rect) + big_padding_x) / 2; |
| 267 for (int i = tile_count - last_row_count_x; i < tile_count; ++i) { |
| 268 tiles_[i].thumb_rect_.origin.x += small_last_row_shift_x; |
| 269 tiles_[i].start_thumb_rect_.origin.x += big_last_row_shift_x; |
| 270 } |
| 271 } |
| 272 |
| 273 void TileSet::set_selected_index(int index) { |
| 274 CHECK_GE(index, 0); |
| 275 CHECK_LT(index, static_cast<int>(tiles_.size())); |
| 276 selected_index_ = index; |
| 277 } |
| 278 |
| 279 } // namespace tabpose |
| 280 |
| 281 void AnimateCALayerFrameFromTo( |
| 282 CALayer* layer, const NSRect& from, const NSRect& to, |
| 283 NSTimeInterval duration, id boundsAnimationDelegate) { |
| 284 // http://developer.apple.com/mac/library/qa/qa2008/qa1620.html |
| 285 CABasicAnimation* animation; |
| 286 |
| 287 animation = [CABasicAnimation animationWithKeyPath:@"bounds"]; |
| 288 animation.fromValue = [NSValue valueWithRect:from]; |
| 289 animation.toValue = [NSValue valueWithRect:to]; |
| 290 animation.duration = duration; |
| 291 animation.timingFunction = |
| 292 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; |
| 293 animation.delegate = boundsAnimationDelegate; |
| 294 |
| 295 // Update the layer's bounds so the layer doesn't snap back when the animation |
| 296 // completes. |
| 297 layer.bounds = NSRectToCGRect(to); |
| 298 |
| 299 // Add the animation, overriding the implicit animation. |
| 300 [layer addAnimation:animation forKey:@"bounds"]; |
| 301 |
| 302 // Prepare the animation from the current position to the new position. |
| 303 NSPoint opoint = from.origin; |
| 304 NSPoint point = to.origin; |
| 305 |
| 306 // Adapt to anchorPoint. |
| 307 opoint.x += NSWidth(from) * layer.anchorPoint.x; |
| 308 opoint.y += NSHeight(from) * layer.anchorPoint.y; |
| 309 point.x += NSWidth(to) * layer.anchorPoint.x; |
| 310 point.y += NSHeight(to) * layer.anchorPoint.y; |
| 311 |
| 312 animation = [CABasicAnimation animationWithKeyPath:@"position"]; |
| 313 animation.fromValue = [NSValue valueWithPoint:opoint]; |
| 314 animation.toValue = [NSValue valueWithPoint:point]; |
| 315 animation.duration = duration; |
| 316 animation.timingFunction = |
| 317 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; |
| 318 |
| 319 // Update the layer's position so that the layer doesn't snap back when the |
| 320 // animation completes. |
| 321 layer.position = NSPointToCGPoint(point); |
| 322 |
| 323 // Add the animation, overriding the implicit animation. |
| 324 [layer addAnimation:animation forKey:@"position"]; |
| 325 } |
| 326 |
| 29 @interface TabposeWindow (Private) | 327 @interface TabposeWindow (Private) |
| 30 - (id)initForWindow:(NSWindow*)parent rect:(NSRect)rect slomo:(BOOL)slomo; | 328 - (id)initForWindow:(NSWindow*)parent |
| 31 - (void)setUpLayers:(NSRect)bgLayerRect; | 329 rect:(NSRect)rect |
| 330 slomo:(BOOL)slomo |
| 331 tabStripModel:(TabStripModel*)tabStripModel; |
| 332 - (void)setUpLayers:(NSRect)bgLayerRect slomo:(BOOL)slomo; |
| 333 - (void)fadeAway:(BOOL)slomo; |
| 334 - (void)selectTileAtIndex:(int)newIndex; |
| 32 @end | 335 @end |
| 33 | 336 |
| 34 @implementation TabposeWindow | 337 @implementation TabposeWindow |
| 35 | 338 |
| 36 + (id)openTabposeFor:(NSWindow*)parent rect:(NSRect)rect slomo:(BOOL)slomo { | 339 + (id)openTabposeFor:(NSWindow*)parent |
| 340 rect:(NSRect)rect |
| 341 slomo:(BOOL)slomo |
| 342 tabStripModel:(TabStripModel*)tabStripModel { |
| 37 // Releases itself when closed. | 343 // Releases itself when closed. |
| 38 return [[TabposeWindow alloc] initForWindow:parent rect:rect slomo:slomo]; | 344 return [[TabposeWindow alloc] |
| 39 } | 345 initForWindow:parent rect:rect slomo:slomo tabStripModel:tabStripModel]; |
| 40 | 346 } |
| 41 - (id)initForWindow:(NSWindow*)parent rect:(NSRect)rect slomo:(BOOL)slomo { | 347 |
| 348 - (id)initForWindow:(NSWindow*)parent |
| 349 rect:(NSRect)rect |
| 350 slomo:(BOOL)slomo |
| 351 tabStripModel:(TabStripModel*)tabStripModel { |
| 42 NSRect frame = [parent frame]; | 352 NSRect frame = [parent frame]; |
| 43 if ((self = [super initWithContentRect:frame | 353 if ((self = [super initWithContentRect:frame |
| 44 styleMask:NSBorderlessWindowMask | 354 styleMask:NSBorderlessWindowMask |
| 45 backing:NSBackingStoreBuffered | 355 backing:NSBackingStoreBuffered |
| 46 defer:NO])) { | 356 defer:NO])) { |
| 357 // TODO(thakis): Add a TabStripModelObserver to |tabStripModel_|. |
| 358 tabStripModel_ = tabStripModel; |
| 359 state_ = tabpose::kFadingIn; |
| 360 tileSet_.reset(new tabpose::TileSet); |
| 47 [self setReleasedWhenClosed:YES]; | 361 [self setReleasedWhenClosed:YES]; |
| 48 [self setOpaque:NO]; | 362 [self setOpaque:NO]; |
| 49 [self setBackgroundColor:[NSColor clearColor]]; | 363 [self setBackgroundColor:[NSColor clearColor]]; |
| 50 [self setUpLayers:rect]; | 364 [self setUpLayers:rect slomo:slomo]; |
| 365 [self setAcceptsMouseMovedEvents:YES]; |
| 51 [parent addChildWindow:self ordered:NSWindowAbove]; | 366 [parent addChildWindow:self ordered:NSWindowAbove]; |
| 52 [self makeKeyAndOrderFront:self]; | 367 [self makeKeyAndOrderFront:self]; |
| 53 } | 368 } |
| 54 return self; | 369 return self; |
| 55 } | 370 } |
| 56 | 371 |
| 57 - (void)setUpLayers:(NSRect)bgLayerRect { | 372 - (CALayer*)selectedLayer { |
| 373 return [allLayers_ objectAtIndex:tileSet_->selected_index()]; |
| 374 } |
| 375 |
| 376 - (void)selectTileAtIndex:(int)newIndex { |
| 377 // TODO(thakis): Have a nicer indicator for the current selection |
| 378 // (a blue outline, probably). |
| 379 int oldIndex = tileSet_->selected_index(); |
| 380 CALayer* oldSelectedLayer = [allLayers_ objectAtIndex:oldIndex]; |
| 381 oldSelectedLayer.backgroundColor = CGColorGetConstantColor(kCGColorBlack); |
| 382 CALayer* newSelectedLayer = [allLayers_ objectAtIndex:newIndex]; |
| 383 newSelectedLayer.backgroundColor = CGColorGetConstantColor(kCGColorWhite); |
| 384 |
| 385 tileSet_->set_selected_index(newIndex); |
| 386 } |
| 387 |
| 388 - (void)setUpLayers:(NSRect)bgLayerRect slomo:(BOOL)slomo { |
| 58 // Root layer -- covers whole window. | 389 // Root layer -- covers whole window. |
| 59 rootLayer_ = [CALayer layer]; | 390 rootLayer_ = [CALayer layer]; |
| 60 [[self contentView] setLayer:rootLayer_]; | 391 [[self contentView] setLayer:rootLayer_]; |
| 61 [[self contentView] setWantsLayer:YES]; | 392 [[self contentView] setWantsLayer:YES]; |
| 62 | 393 |
| 63 // Background layer -- the visible part of the window. | 394 // Background layer -- the visible part of the window. |
| 64 gray_.reset(CGColorCreateGenericGray(0.39, 1.0)); | 395 gray_.reset(CGColorCreateGenericGray(0.39, 1.0)); |
| 65 bgLayer_ = [CALayer layer]; | 396 bgLayer_ = [CALayer layer]; |
| 66 bgLayer_.backgroundColor = gray_; | 397 bgLayer_.backgroundColor = gray_; |
| 67 bgLayer_.frame = NSRectToCGRect(bgLayerRect); | 398 bgLayer_.frame = NSRectToCGRect(bgLayerRect); |
| 68 bgLayer_.masksToBounds = YES; | 399 bgLayer_.masksToBounds = YES; |
| 69 [rootLayer_ addSublayer:bgLayer_]; | 400 [rootLayer_ addSublayer:bgLayer_]; |
| 70 | 401 |
| 71 // Top gradient. | 402 // Top gradient. |
| 72 CALayer* gradientLayer = [DarkGradientLayer layer]; | 403 CALayer* gradientLayer = [DarkGradientLayer layer]; |
| 73 gradientLayer.frame = CGRectMake( | 404 gradientLayer.frame = CGRectMake( |
| 74 0, | 405 0, |
| 75 NSHeight(bgLayerRect) - kTopGradientHeight, | 406 NSHeight(bgLayerRect) - kTopGradientHeight, |
| 76 NSWidth(bgLayerRect), | 407 NSWidth(bgLayerRect), |
| 77 kTopGradientHeight); | 408 kTopGradientHeight); |
| 78 [gradientLayer setNeedsDisplay]; // Draw once. | 409 [gradientLayer setNeedsDisplay]; // Draw once. |
| 79 [bgLayer_ addSublayer:gradientLayer]; | 410 [bgLayer_ addSublayer:gradientLayer]; |
| 411 |
| 412 // Layers for the tab thumbnails. |
| 413 tileSet_->Build(tabStripModel_); |
| 414 tileSet_->Layout(bgLayerRect); |
| 415 |
| 416 allLayers_.reset( |
| 417 [[NSMutableArray alloc] initWithCapacity:tabStripModel_->count()]); |
| 418 for (int i = 0; i < tabStripModel_->count(); ++i) { |
| 419 CALayer* layer = [CALayer layer]; |
| 420 |
| 421 // Background color as placeholder for now. |
| 422 layer.backgroundColor = CGColorGetConstantColor(kCGColorBlack); |
| 423 |
| 424 AnimateCALayerFrameFromTo( |
| 425 layer, |
| 426 tileSet_->tile_at(i).GetStartRectRelativeTo(tileSet_->selected_tile()), |
| 427 tileSet_->tile_at(i).thumb_rect(), |
| 428 kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1), |
| 429 i == tileSet_->selected_index() ? self : nil); |
| 430 |
| 431 // Add a delegate to one of the animations to get a notification once the |
| 432 // animations are done. |
| 433 if (i == tileSet_->selected_index()) { |
| 434 CAAnimation* animation = [layer animationForKey:@"bounds"]; |
| 435 DCHECK(animation); |
| 436 [animation setValue:kAnimationIdFadeIn forKey:kAnimationIdKey]; |
| 437 } |
| 438 |
| 439 layer.shadowRadius = 10; |
| 440 layer.shadowOffset = CGSizeMake(0, -10); |
| 441 |
| 442 [bgLayer_ addSublayer:layer]; |
| 443 [allLayers_ addObject:layer]; |
| 444 } |
| 445 [self selectTileAtIndex:tileSet_->selected_index()]; |
| 80 } | 446 } |
| 81 | 447 |
| 82 - (BOOL)canBecomeKeyWindow { | 448 - (BOOL)canBecomeKeyWindow { |
| 83 return YES; | 449 return YES; |
| 84 } | 450 } |
| 85 | 451 |
| 86 - (void)keyDown:(NSEvent*)event { | 452 - (void)keyDown:(NSEvent*)event { |
| 87 // Overridden to prevent beeps. | 453 // Overridden to prevent beeps. |
| 88 } | 454 } |
| 89 | 455 |
| 90 - (void)keyUp:(NSEvent*)event { | 456 - (void)keyUp:(NSEvent*)event { |
| 457 if (state_ == tabpose::kFadingOut) |
| 458 return; |
| 459 |
| 91 NSString* characters = [event characters]; | 460 NSString* characters = [event characters]; |
| 92 if ([characters length] < 1) | 461 if ([characters length] < 1) |
| 93 return; | 462 return; |
| 94 | 463 |
| 95 unichar character = [characters characterAtIndex:0]; | 464 unichar character = [characters characterAtIndex:0]; |
| 96 switch (character) { | 465 switch (character) { |
| 97 case NSEnterCharacter: | 466 case NSEnterCharacter: |
| 98 case NSNewlineCharacter: | 467 case NSNewlineCharacter: |
| 99 case NSCarriageReturnCharacter: | 468 case NSCarriageReturnCharacter: |
| 100 case ' ': | 469 case ' ': |
| 470 [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0]; |
| 471 break; |
| 101 case '\e': // Escape | 472 case '\e': // Escape |
| 102 [self close]; | 473 tileSet_->ResetSelectedIndex(); |
| 474 [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0]; |
| 103 break; | 475 break; |
| 476 // TODO(thakis): Support moving the selection via arrow keys. |
| 104 } | 477 } |
| 105 } | 478 } |
| 106 | 479 |
| 480 - (void)mouseMoved:(NSEvent*)event { |
| 481 int newIndex = -1; |
| 482 CGPoint p = NSPointToCGPoint([event locationInWindow]); |
| 483 for (NSUInteger i = 0; i < [allLayers_ count]; ++i) { |
| 484 CALayer* layer = [allLayers_ objectAtIndex:i]; |
| 485 CGPoint lp = [layer convertPoint:p fromLayer:rootLayer_]; |
| 486 if ([static_cast<CALayer*>([layer presentationLayer]) containsPoint:lp]) |
| 487 newIndex = i; |
| 488 } |
| 489 if (newIndex >= 0) |
| 490 [self selectTileAtIndex:newIndex]; |
| 491 } |
| 492 |
| 107 - (void)mouseDown:(NSEvent*)event { | 493 - (void)mouseDown:(NSEvent*)event { |
| 108 [self close]; | 494 [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0]; |
| 109 } | 495 } |
| 110 | 496 |
| 111 - (void)swipeWithEvent:(NSEvent*)event { | 497 - (void)swipeWithEvent:(NSEvent*)event { |
| 112 if ([event deltaY] > 0.5) // Swipe up | 498 if ([event deltaY] > 0.5) // Swipe up |
| 113 [self close]; | 499 [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0]; |
| 114 } | 500 } |
| 115 | 501 |
| 116 - (void)close { | 502 - (void)close { |
| 117 // Prevent parent window from disappearing. | 503 // Prevent parent window from disappearing. |
| 118 [[self parentWindow] removeChildWindow:self]; | 504 [[self parentWindow] removeChildWindow:self]; |
| 119 [super close]; | 505 [super close]; |
| 120 } | 506 } |
| 121 | 507 |
| 122 - (void)commandDispatch:(id)sender { | 508 - (void)commandDispatch:(id)sender { |
| 123 // Without this, -validateUserInterfaceItem: is not called. | 509 // Without this, -validateUserInterfaceItem: is not called. |
| 124 } | 510 } |
| 125 | 511 |
| 126 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { | 512 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { |
| 127 // Disable all browser-related menu items. | 513 // Disable all browser-related menu items. |
| 128 return NO; | 514 return NO; |
| 129 } | 515 } |
| 130 | 516 |
| 517 - (void)fadeAway:(BOOL)slomo { |
| 518 if (state_ == tabpose::kFadingOut) |
| 519 return; |
| 520 |
| 521 state_ = tabpose::kFadingOut; |
| 522 [self setAcceptsMouseMovedEvents:NO]; |
| 523 |
| 524 // Select chosen tab. |
| 525 tabStripModel_->SelectTabContentsAt(tileSet_->selected_index(), |
| 526 /*user_gesture=*/true); |
| 527 |
| 528 { |
| 529 ScopedCAActionDisabler disableCAActions; |
| 530 |
| 531 // Move the selected layer on top of all other layers. |
| 532 [self selectedLayer].zPosition = 1; |
| 533 |
| 534 // Running animations with shadows is slow, so turn shadows off before |
| 535 // running the exit animation. |
| 536 for (CALayer* layer in allLayers_.get()) |
| 537 layer.shadowOpacity = 0.0; |
| 538 } |
| 539 |
| 540 // Animate layers out, all in one transaction. |
| 541 CGFloat duration = kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1); |
| 542 ScopedCAActionSetDuration durationSetter(duration); |
| 543 for (NSUInteger i = 0; i < [allLayers_ count]; ++i) { |
| 544 CALayer* layer = [allLayers_ objectAtIndex:i]; |
| 545 // |start_thumb_rect_| was relative to |initial_index_|, now this needs to |
| 546 // be relative to |selectedIndex_| (whose start rect was relative to |
| 547 // |initial_index_| too) |
| 548 CGRect newFrame = NSRectToCGRect( |
| 549 tileSet_->tile_at(i).GetStartRectRelativeTo(tileSet_->selected_tile())); |
| 550 |
| 551 // Add a delegate to one of the implicit animations to get a notification |
| 552 // once the animations are done. |
| 553 if (static_cast<int>(i) == tileSet_->selected_index()) { |
| 554 CAAnimation* animation = [CAAnimation animation]; |
| 555 animation.delegate = self; |
| 556 [animation setValue:kAnimationIdFadeOut forKey:kAnimationIdKey]; |
| 557 [layer addAnimation:animation forKey:@"frame"]; |
| 558 } |
| 559 |
| 560 layer.frame = newFrame; |
| 561 } |
| 562 } |
| 563 |
| 564 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished { |
| 565 NSString* animationId = [animation valueForKey:kAnimationIdKey]; |
| 566 if ([animationId isEqualToString:kAnimationIdFadeIn]) { |
| 567 if (finished) { |
| 568 // If the user clicks while the fade in animation is still running, |
| 569 // |state_| is already kFadingOut. In that case, don't do anything. |
| 570 DCHECK_EQ(tabpose::kFadingIn, state_); |
| 571 state_ = tabpose::kFadedIn; |
| 572 |
| 573 // Running animations with shadows is slow, so turn shadows on only after |
| 574 // the animation is done. |
| 575 ScopedCAActionDisabler disableCAActions; |
| 576 for (CALayer* layer in allLayers_.get()) |
| 577 layer.shadowOpacity = 0.5; |
| 578 } |
| 579 } else if ([animationId isEqualToString:kAnimationIdFadeOut]) { |
| 580 DCHECK_EQ(tabpose::kFadingOut, state_); |
| 581 [self close]; |
| 582 } |
| 583 } |
| 584 |
| 131 @end | 585 @end |
| OLD | NEW |