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

Side by Side Diff: chrome/browser/cocoa/tabpose_window.mm

Issue 3063031: Mac: more tabpose (Closed)
Patch Set: comments Created 10 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/browser/cocoa/tabpose_window.h ('k') | chrome/browser/cocoa/tabpose_window_unittest.mm » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
OLDNEW
« no previous file with comments | « chrome/browser/cocoa/tabpose_window.h ('k') | chrome/browser/cocoa/tabpose_window_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698