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

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

Issue 3163003: Mac tabpose: Add thumbnails (Closed)
Patch Set: '' Created 10 years, 3 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 #include "app/resource_bundle.h" 9 #include "app/resource_bundle.h"
10 #include "base/mac_util.h" 10 #include "base/mac_util.h"
11 #include "base/scoped_callback_factory.h"
11 #include "base/sys_string_conversions.h" 12 #include "base/sys_string_conversions.h"
13 #include "chrome/browser/browser_process.h"
14 #import "chrome/browser/cocoa/bookmark_bar_constants.h"
12 #import "chrome/browser/cocoa/browser_window_controller.h" 15 #import "chrome/browser/cocoa/browser_window_controller.h"
13 #import "chrome/browser/cocoa/tab_strip_controller.h" 16 #import "chrome/browser/cocoa/tab_strip_controller.h"
17 #import "chrome/browser/cocoa/tab_strip_model_observer_bridge.h"
18 #import "chrome/browser/debugger/devtools_window.h"
19 #include "chrome/browser/prefs/pref_service.h"
20 #include "chrome/browser/renderer_host/backing_store_mac.h"
21 #include "chrome/browser/renderer_host/render_view_host.h"
22 #include "chrome/browser/renderer_host/render_widget_host_view_mac.h"
14 #include "chrome/browser/tab_contents/tab_contents.h" 23 #include "chrome/browser/tab_contents/tab_contents.h"
24 #include "chrome/browser/tab_contents/thumbnail_generator.h"
25 #include "chrome/common/pref_names.h"
15 #include "grit/app_resources.h" 26 #include "grit/app_resources.h"
16 #include "skia/ext/skia_utils_mac.h" 27 #include "skia/ext/skia_utils_mac.h"
28 #include "third_party/skia/include/utils/mac/SkCGUtils.h"
17 29
18 const int kTopGradientHeight = 15; 30 const int kTopGradientHeight = 15;
19 31
20 NSString* const kAnimationIdKey = @"AnimationId"; 32 NSString* const kAnimationIdKey = @"AnimationId";
21 NSString* const kAnimationIdFadeIn = @"FadeIn"; 33 NSString* const kAnimationIdFadeIn = @"FadeIn";
22 NSString* const kAnimationIdFadeOut = @"FadeOut"; 34 NSString* const kAnimationIdFadeOut = @"FadeOut";
23 35
24 const CGFloat kDefaultAnimationDuration = 0.25; // In seconds. 36 const CGFloat kDefaultAnimationDuration = 0.25; // In seconds.
25 const CGFloat kSlomoFactor = 4; 37 const CGFloat kSlomoFactor = 4;
38 const CGFloat kObserverChangeAnimationDuration = 0.75; // In seconds.
26 39
27 // CAGradientLayer is 10.6-only -- roll our own. 40 // CAGradientLayer is 10.6-only -- roll our own.
28 @interface DarkGradientLayer : CALayer 41 @interface DarkGradientLayer : CALayer
29 - (void)drawInContext:(CGContextRef)context; 42 - (void)drawInContext:(CGContextRef)context;
30 @end 43 @end
31 44
32 @implementation DarkGradientLayer 45 @implementation DarkGradientLayer
33 - (void)drawInContext:(CGContextRef)context { 46 - (void)drawInContext:(CGContextRef)context {
34 scoped_cftyperef<CGColorSpaceRef> grayColorSpace( 47 scoped_cftyperef<CGColorSpaceRef> grayColorSpace(
35 CGColorSpaceCreateWithName(kCGColorSpaceGenericGray)); 48 CGColorSpaceCreateWithName(kCGColorSpaceGenericGray));
36 CGFloat grays[] = { 0.277, 1.0, 0.39, 1.0 }; 49 CGFloat grays[] = { 0.277, 1.0, 0.39, 1.0 };
37 CGFloat locations[] = { 0, 1 }; 50 CGFloat locations[] = { 0, 1 };
38 scoped_cftyperef<CGGradientRef> gradient(CGGradientCreateWithColorComponents( 51 scoped_cftyperef<CGGradientRef> gradient(CGGradientCreateWithColorComponents(
39 grayColorSpace.get(), grays, locations, arraysize(locations))); 52 grayColorSpace.get(), grays, locations, arraysize(locations)));
40 CGPoint topLeft = CGPointMake(0.0, kTopGradientHeight); 53 CGPoint topLeft = CGPointMake(0.0, kTopGradientHeight);
41 CGContextDrawLinearGradient(context, gradient.get(), topLeft, CGPointZero, 0); 54 CGContextDrawLinearGradient(context, gradient.get(), topLeft, CGPointZero, 0);
42 } 55 }
43 @end 56 @end
44 57
58 namespace tabpose {
59 class ThumbnailLoader;
60 }
61
62 // A CALayer that draws a thumbnail for a TabContents object. The layer tries
63 // to draw the TabContents's backing store directly if possible, and requests
64 // a thumbnail bitmap from the TabContents's renderer process if not.
65 @interface ThumbnailLayer : CALayer {
66 // The TabContents the thumbnail is for.
67 TabContents* contents_; // weak
68
69 // The size the thumbnail is drawn at when zoomed in.
70 NSSize fullSize_;
71
72 // Used to load a thumbnail, if required.
73 scoped_refptr<tabpose::ThumbnailLoader> loader_;
74
75 // If the backing store couldn't be used and a thumbnail was returned from a
76 // renderer process, it's stored in |thumbnail_|.
77 scoped_cftyperef<CGImageRef> thumbnail_;
78
79 // True if the layer already sent a thumbnail request to a renderer.
80 BOOL didSendLoad_;
81 }
82 - (id)initWithTabContents:(TabContents*)contents fullSize:(NSSize)fullSize;
83 - (void)drawInContext:(CGContextRef)context;
84 - (void)setThumbnail:(const SkBitmap&)bitmap;
85 @end
86
87 namespace tabpose {
88
89 // ThumbnailLoader talks to the renderer process to load a thumbnail of a given
90 // RenderWidgetHost, and sends the thumbnail back to a ThumbnailLayer once it
91 // comes back from the renderer.
92 class ThumbnailLoader : public base::RefCountedThreadSafe<ThumbnailLoader> {
93 public:
94 ThumbnailLoader(gfx::Size size, RenderWidgetHost* rwh, ThumbnailLayer* layer)
95 : size_(size), rwh_(rwh), layer_(layer), factory_(this) {}
96
97 // Starts the fetch.
98 void LoadThumbnail();
99
100 private:
101 friend class base::RefCountedThreadSafe<ThumbnailLoader>;
102 ~ThumbnailLoader() {
103 ResetPaintingObserver();
104 }
105
106 void DidReceiveBitmap(const SkBitmap& bitmap) {
107 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
108 ResetPaintingObserver();
109 [layer_ setThumbnail:bitmap];
110 }
111
112 void ResetPaintingObserver() {
113 if (rwh_->painting_observer() != NULL) {
114 DCHECK(rwh_->painting_observer() ==
115 g_browser_process->GetThumbnailGenerator());
116 rwh_->set_painting_observer(NULL);
117 }
118 }
119
120 gfx::Size size_;
121 RenderWidgetHost* rwh_; // weak
122 ThumbnailLayer* layer_; // weak, owns us
123 base::ScopedCallbackFactory<ThumbnailLoader> factory_;
124
125 DISALLOW_COPY_AND_ASSIGN(ThumbnailLoader);
126 };
127
128 void ThumbnailLoader::LoadThumbnail() {
129 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
130 ThumbnailGenerator* generator = g_browser_process->GetThumbnailGenerator();
131 if (!generator) // In unit tests.
132 return;
133
134 // As mentioned in ThumbnailLayer's -drawInContext:, it's sufficient to have
135 // thumbnails at the zoomed-out pixel size for all but the thumbnail the user
136 // clicks on in the end. But we don't don't which thumbnail that will be, so
137 // keep it simple and request full thumbnails for everything.
138 // TODO(thakis): Request smaller thumbnails for users with many tabs.
139 gfx::Size page_size(size_); // Logical size the renderer renders at.
140 gfx::Size pixel_size(size_); // Physical pixel size the image is rendered at.
141
142 DCHECK(rwh_->painting_observer() == NULL ||
143 rwh_->painting_observer() == generator);
144 rwh_->set_painting_observer(generator);
145
146 // Will send an IPC to the renderer on the IO thread.
147 generator->AskForSnapshot(
148 rwh_,
149 /*prefer_backing_store=*/false,
150 factory_.NewCallback(&ThumbnailLoader::DidReceiveBitmap),
151 page_size,
152 pixel_size);
153 }
154
155 } // namespace tabpose
156
157 @implementation ThumbnailLayer
158
159 - (id)initWithTabContents:(TabContents*)contents fullSize:(NSSize)fullSize {
160 CHECK(contents);
161 if ((self = [super init])) {
162 contents_ = contents;
163 fullSize_ = fullSize;
164 }
165 return self;
166 }
167
168 - (void)setTabContents:(TabContents*)contents {
169 contents_ = contents;
170 }
171
172 - (void)setThumbnail:(const SkBitmap&)bitmap {
173 // SkCreateCGImageRef() holds on to |bitmaps|'s memory, so this doesn't
174 // create a copy.
175 thumbnail_.reset(SkCreateCGImageRef(bitmap));
176 loader_ = NULL;
177 [self setNeedsDisplay];
178 }
179
180 - (int)topOffset {
181 int topOffset = 0;
182
183 // Medium term, we want to show thumbs of the actual info bar views, which
184 // means I need to create InfoBarControllers here. At that point, we can get
185 // the height from that controller. Until then, hardcode. :-/
186 const int kInfoBarHeight = 31;
187 topOffset += contents_->infobar_delegate_count() * kInfoBarHeight;
188
189 bool always_show_bookmark_bar =
190 contents_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar);
191 bool has_detached_bookmark_bar =
192 contents_->ShouldShowBookmarkBar() && !always_show_bookmark_bar;
193 if (has_detached_bookmark_bar)
194 topOffset += bookmarks::kNTPBookmarkBarHeight;
195
196 return topOffset;
197 }
198
199 - (int)bottomOffset {
200 int bottomOffset = 0;
201 TabContents* devToolsContents =
202 DevToolsWindow::GetDevToolsContents(contents_);
203 if (devToolsContents && devToolsContents->render_view_host() &&
204 devToolsContents->render_view_host()->view()) {
205 // The devtool's size might not be up-to-date, but since its height doesn't
206 // change on window resize, and since most users don't use devtools, this is
207 // good enough.
208 bottomOffset +=
209 devToolsContents->render_view_host()->view()->GetViewBounds().height();
210 bottomOffset += 1; // :-( Divider line between web contents and devtools.
211 }
212 return bottomOffset;
213 }
214
215 - (void)drawBackingStore:(BackingStoreMac*)backing_store
216 inRect:(CGRect)destRect
217 context:(CGContextRef)context {
218 // TODO(thakis): Add a sublayer for each accelerated surface in the rwhv.
219 // Until then, accelerated layers (CoreAnimation NPAPI plugins, compositor)
220 // won't show up in tabpose.
221 if (backing_store->cg_layer()) {
222 CGContextDrawLayerInRect(context, destRect, backing_store->cg_layer());
223 } else {
224 scoped_cftyperef<CGImageRef> image(
225 CGBitmapContextCreateImage(backing_store->cg_bitmap()));
226 CGContextDrawImage(context, destRect, image);
227 }
228 }
229
230 - (void)drawInContext:(CGContextRef)context {
231 RenderWidgetHost* rwh = contents_->render_view_host();
232 RenderWidgetHostView* rwhv = rwh->view(); // NULL if renderer crashed.
233 if (!rwhv) {
234 // TODO(thakis): Maybe draw a sad tab layer?
235 [super drawInContext:context];
236 return;
237 }
238
239 // The size of the TabContent's RenderWidgetHost might not fit to the
240 // current browser window at all, for example if the window was resized while
241 // this TabContents object was not an active tab.
242 // Compute the required size ourselves. Leave room for eventual infobars and
243 // a detached bookmarks bar on the top, and for the devtools on the bottom.
244 // Download shelf is not included in the |fullSize| rect, so no need to
245 // correct for it here.
246 // TODO(thakis): This is not resolution-independent.
247 int topOffset = [self topOffset];
248 int bottomOffset = [self bottomOffset];
249 gfx::Size desiredThumbSize(fullSize_.width,
250 fullSize_.height - topOffset - bottomOffset);
251
252 // We need to ask the renderer for a thumbnail if
253 // a) there's no backing store or
254 // b) the backing store's size doesn't match our required size and
255 // c) we didn't already send a thumbnail request to the renderer.
256 BackingStoreMac* backing_store =
257 (BackingStoreMac*)rwh->GetBackingStore(/*force_create=*/false);
258 bool draw_backing_store =
259 backing_store && backing_store->size() == desiredThumbSize;
260
261 // Next weirdness: The destination rect. If the layer is |fullSize_| big, the
262 // destination rect is (0, bottomOffset), (fullSize_.width, topOffset). But we
263 // might be amidst an animation, so interpolate that rect.
264 CGRect destRect = [self bounds];
265 CGFloat scale = destRect.size.width / fullSize_.width;
266 destRect.origin.y += bottomOffset * scale;
267 destRect.size.height -= (bottomOffset + topOffset) * scale;
268
269 // TODO(thakis): Draw infobars, detached bookmark bar as well.
270
271 // If we haven't already, sent a thumbnail request to the renderer.
272 if (!draw_backing_store && !didSendLoad_) {
273 // Either the tab was never visible, or its backing store got evicted, or
274 // the size of the backing store is wrong.
275
276 // We only need a thumbnail the size of the zoomed-out layer for all
277 // layers except the one the user clicks on. But since we can't know which
278 // layer that is, request full-resolution layers for all tabs. This is
279 // simple and seems to work in practice.
280 loader_ = new tabpose::ThumbnailLoader(desiredThumbSize, rwh, self);
281 loader_->LoadThumbnail();
282 didSendLoad_ = YES;
283
284 // Fill with bg color.
285 [super drawInContext:context];
286 }
287
288 if (draw_backing_store) {
289 // Backing store 'cache' hit!
290 [self drawBackingStore:backing_store inRect:destRect context:context];
291 } else if (thumbnail_) {
292 // No cache hit, but the renderer returned a thumbnail to us.
293 CGContextDrawImage(context, destRect, thumbnail_.get());
294 }
295 }
296
297 @end
298
45 namespace { 299 namespace {
46 300
47 class ScopedCAActionDisabler { 301 class ScopedCAActionDisabler {
48 public: 302 public:
49 ScopedCAActionDisabler() { 303 ScopedCAActionDisabler() {
50 [CATransaction begin]; 304 [CATransaction begin];
51 [CATransaction setValue:[NSNumber numberWithBool:YES] 305 [CATransaction setValue:[NSNumber numberWithBool:YES]
52 forKey:kCATransactionDisableActions]; 306 forKey:kCATransactionDisableActions];
53 } 307 }
54 308
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
99 4 * n * a * (dx + w_c) * (dy + h_c)) - 353 4 * n * a * (dx + w_c) * (dy + h_c)) -
100 n * (a * dy - dx)) 354 n * (a * dy - dx))
101 / 355 /
102 (2 * (dx + w_c)); 356 (2 * (dx + w_c));
103 } 357 }
104 358
105 namespace tabpose { 359 namespace tabpose {
106 360
107 // A tile is what is shown for a single tab in tabpose mode. It consists of a 361 // A tile is what is shown for a single tab in tabpose mode. It consists of a
108 // title, favicon, thumbnail image, and pre- and postanimation rects. 362 // title, favicon, thumbnail image, and pre- and postanimation rects.
109 // TODO(thakis): Right now, it only consists of a thumb rect.
110 class Tile { 363 class Tile {
111 public: 364 public:
365 Tile() {}
366
112 // Returns the rectangle this thumbnail is at at the beginning of the zoom-in 367 // Returns the rectangle this thumbnail is at at the beginning of the zoom-in
113 // animation. |tile| is the rectangle that's covering the whole tab area when 368 // animation. |tile| is the rectangle that's covering the whole tab area when
114 // the animation starts. 369 // the animation starts.
115 NSRect GetStartRectRelativeTo(const Tile& tile) const; 370 NSRect GetStartRectRelativeTo(const Tile& tile) const;
116 NSRect thumb_rect() const { return thumb_rect_; } 371 NSRect thumb_rect() const { return thumb_rect_; }
117 372
118 NSRect favicon_rect() const { return favicon_rect_; } 373 NSRect favicon_rect() const { return favicon_rect_; }
119 SkBitmap favicon() const; 374 SkBitmap favicon() const;
120 375
121 // This changes |title_rect| and |favicon_rect| such that the favicon is on 376 // This changes |title_rect| and |favicon_rect| such that the favicon is on
122 // the font's baseline and that the minimum distance between thumb rect and 377 // the font's baseline and that the minimum distance between thumb rect and
123 // favicon and title rects doesn't change. 378 // favicon and title rects doesn't change.
124 // The view code 379 // The view code
125 // 1. queries desired font size by calling |title_font_size()| 380 // 1. queries desired font size by calling |title_font_size()|
126 // 2. loads that font 381 // 2. loads that font
127 // 3. calls |set_font_metrics()| which updates the title rect 382 // 3. calls |set_font_metrics()| which updates the title rect
128 // 4. receives the title rect and puts the title on it with the font from 2. 383 // 4. receives the title rect and puts the title on it with the font from 2.
129 void set_font_metrics(CGFloat ascender, CGFloat descender); 384 void set_font_metrics(CGFloat ascender, CGFloat descender);
130 CGFloat title_font_size() const { return title_font_size_; } 385 CGFloat title_font_size() const { return title_font_size_; }
131 386
132 NSRect title_rect() const { return title_rect_; } 387 NSRect title_rect() const { return title_rect_; }
133 388
134 // Returns an unelided title. The view logic is responsible for eliding. 389 // Returns an unelided title. The view logic is responsible for eliding.
135 const string16& title() const { return contents_->GetTitle(); } 390 const string16& title() const { return contents_->GetTitle(); }
391
392 TabContents* tab_contents() const { return contents_; }
393 void set_tab_contents(TabContents* new_contents) { contents_ = new_contents; }
394
136 private: 395 private:
137 friend class TileSet; 396 friend class TileSet;
138 397
139 // The thumb rect includes infobars, detached thumbnail bar, web contents, 398 // The thumb rect includes infobars, detached thumbnail bar, web contents,
140 // and devtools. 399 // and devtools.
141 NSRect thumb_rect_; 400 NSRect thumb_rect_;
142 NSRect start_thumb_rect_; 401 NSRect start_thumb_rect_;
143 402
144 NSRect favicon_rect_; 403 NSRect favicon_rect_;
145 404
146 CGFloat title_font_size_; 405 CGFloat title_font_size_;
147 NSRect title_rect_; 406 NSRect title_rect_;
148 407
149 TabContents* contents_; // weak 408 TabContents* contents_; // weak
409
410 DISALLOW_COPY_AND_ASSIGN(Tile);
150 }; 411 };
151 412
152 NSRect Tile::GetStartRectRelativeTo(const Tile& tile) const { 413 NSRect Tile::GetStartRectRelativeTo(const Tile& tile) const {
153 NSRect rect = start_thumb_rect_; 414 NSRect rect = start_thumb_rect_;
154 rect.origin.x -= tile.start_thumb_rect_.origin.x; 415 rect.origin.x -= tile.start_thumb_rect_.origin.x;
155 rect.origin.y -= tile.start_thumb_rect_.origin.y; 416 rect.origin.y -= tile.start_thumb_rect_.origin.y;
156 return rect; 417 return rect;
157 } 418 }
158 419
159 SkBitmap Tile::favicon() const { 420 SkBitmap Tile::favicon() const {
(...skipping 18 matching lines...) Expand all
178 } else { 439 } else {
179 // Move title down. 440 // Move title down.
180 title_rect_.origin.y = favicon_rect_.origin.y - descender; 441 title_rect_.origin.y = favicon_rect_.origin.y - descender;
181 } 442 }
182 } 443 }
183 444
184 // A tileset is responsible for owning and laying out all |Tile|s shown in a 445 // A tileset is responsible for owning and laying out all |Tile|s shown in a
185 // tabpose window. 446 // tabpose window.
186 class TileSet { 447 class TileSet {
187 public: 448 public:
449 TileSet() {}
450
188 // Fills in |tiles_|. 451 // Fills in |tiles_|.
189 void Build(TabStripModel* source_model); 452 void Build(TabStripModel* source_model);
190 453
191 // Computes coordinates for |tiles_|. 454 // Computes coordinates for |tiles_|.
192 void Layout(NSRect containing_rect); 455 void Layout(NSRect containing_rect);
193 456
194 int selected_index() const { return selected_index_; } 457 int selected_index() const { return selected_index_; }
195 void set_selected_index(int index); 458 void set_selected_index(int index);
196 void ResetSelectedIndex() { selected_index_ = initial_index_; }
197 459
198 const Tile& selected_tile() const { return *tiles_[selected_index()]; } 460 const Tile& selected_tile() const { return *tiles_[selected_index()]; }
199 Tile& tile_at(int index) { return *tiles_[index]; } 461 Tile& tile_at(int index) { return *tiles_[index]; }
200 const Tile& tile_at(int index) const { return *tiles_[index]; } 462 const Tile& tile_at(int index) const { return *tiles_[index]; }
201 463
464 // Inserts a new Tile object containing |contents| at |index|. Does no
465 // relayout.
466 void InsertTileAt(int index, TabContents* contents);
467
468 // Removes the Tile object at |index|. Does no relayout.
469 void RemoveTileAt(int index);
470
471 // Moves the Tile object at |from_index| to |to_index|. Since this doesn't
472 // change the number of tiles, relayout can be done just by swapping the
473 // tile rectangles in the index interval [from_index, to_index], so this does
474 // layout.
475 void MoveTileFromTo(int from_index, int to_index);
476
202 private: 477 private:
203 ScopedVector<Tile> tiles_; 478 ScopedVector<Tile> tiles_;
479 int selected_index_;
204 480
205 int selected_index_; 481 DISALLOW_COPY_AND_ASSIGN(TileSet);
206 int initial_index_;
207 }; 482 };
208 483
209 void TileSet::Build(TabStripModel* source_model) { 484 void TileSet::Build(TabStripModel* source_model) {
210 selected_index_ = initial_index_ = source_model->selected_index(); 485 selected_index_ = source_model->selected_index();
211 tiles_.resize(source_model->count()); 486 tiles_.resize(source_model->count());
212 for (size_t i = 0; i < tiles_.size(); ++i) { 487 for (size_t i = 0; i < tiles_.size(); ++i) {
213 tiles_[i] = new Tile; 488 tiles_[i] = new Tile;
214 tiles_[i]->contents_ = source_model->GetTabContentsAt(i); 489 tiles_[i]->contents_ = source_model->GetTabContentsAt(i);
215 } 490 }
216 } 491 }
217 492
218 void TileSet::Layout(NSRect containing_rect) { 493 void TileSet::Layout(NSRect containing_rect) {
219 int tile_count = tiles_.size(); 494 int tile_count = tiles_.size();
495 if (tile_count == 0) // Happens e.g. during test shutdown.
496 return;
220 497
221 // Room around the tiles insde of |containing_rect|. 498 // Room around the tiles insde of |containing_rect|.
222 const int kSmallPaddingTop = 30; 499 const int kSmallPaddingTop = 30;
223 const int kSmallPaddingLeft = 30; 500 const int kSmallPaddingLeft = 30;
224 const int kSmallPaddingRight = 30; 501 const int kSmallPaddingRight = 30;
225 const int kSmallPaddingBottom = 30; 502 const int kSmallPaddingBottom = 30;
226 503
227 // Favicon / title area. 504 // Favicon / title area.
228 const int kThumbTitlePaddingY = 6; 505 const int kThumbTitlePaddingY = 6;
229 const int kFaviconSize = 16; 506 const int kFaviconSize = 16;
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after
363 tiles_[i]->title_rect_.origin.x += small_last_row_shift_x; 640 tiles_[i]->title_rect_.origin.x += small_last_row_shift_x;
364 } 641 }
365 } 642 }
366 643
367 void TileSet::set_selected_index(int index) { 644 void TileSet::set_selected_index(int index) {
368 CHECK_GE(index, 0); 645 CHECK_GE(index, 0);
369 CHECK_LT(index, static_cast<int>(tiles_.size())); 646 CHECK_LT(index, static_cast<int>(tiles_.size()));
370 selected_index_ = index; 647 selected_index_ = index;
371 } 648 }
372 649
650 void TileSet::InsertTileAt(int index, TabContents* contents) {
651 tiles_.insert(tiles_.begin() + index, new Tile);
652 tiles_[index]->contents_ = contents;
653 }
654
655 void TileSet::RemoveTileAt(int index) {
656 tiles_.erase(tiles_.begin() + index);
657 }
658
659 // Moves the Tile object at |from_index| to |to_index|. Also updates rectangles
660 // so that the tiles stay in a left-to-right, top-to-bottom layout when walked
661 // in sequential order.
662 void TileSet::MoveTileFromTo(int from_index, int to_index) {
663 NSRect thumb = tiles_[from_index]->thumb_rect_;
664 NSRect start_thumb = tiles_[from_index]->start_thumb_rect_;
665 NSRect favicon = tiles_[from_index]->favicon_rect_;
666 NSRect title = tiles_[from_index]->title_rect_;
667
668 scoped_ptr<Tile> tile(tiles_[from_index]);
669 tiles_.weak_erase(tiles_.begin() + from_index);
670 tiles_.insert(tiles_.begin() + to_index, tile.release());
671
672 int step = from_index < to_index ? -1 : 1;
673 for (int i = to_index; (i - from_index) * step < 0; i += step) {
674 tiles_[i]->thumb_rect_ = tiles_[i + step]->thumb_rect_;
675 tiles_[i]->start_thumb_rect_ = tiles_[i + step]->start_thumb_rect_;
676 tiles_[i]->favicon_rect_ = tiles_[i + step]->favicon_rect_;
677 tiles_[i]->title_rect_ = tiles_[i + step]->title_rect_;
678 }
679 tiles_[from_index]->thumb_rect_ = thumb;
680 tiles_[from_index]->start_thumb_rect_ = start_thumb;
681 tiles_[from_index]->favicon_rect_ = favicon;
682 tiles_[from_index]->title_rect_ = title;
683 }
684
373 } // namespace tabpose 685 } // namespace tabpose
374 686
375 void AnimateCALayerFrameFromTo( 687 void AnimateCALayerFrameFromTo(
376 CALayer* layer, const NSRect& from, const NSRect& to, 688 CALayer* layer, const NSRect& from, const NSRect& to,
377 NSTimeInterval duration, id boundsAnimationDelegate) { 689 NSTimeInterval duration, id boundsAnimationDelegate) {
378 // http://developer.apple.com/mac/library/qa/qa2008/qa1620.html 690 // http://developer.apple.com/mac/library/qa/qa2008/qa1620.html
379 CABasicAnimation* animation; 691 CABasicAnimation* animation;
380 692
381 animation = [CABasicAnimation animationWithKeyPath:@"bounds"]; 693 animation = [CABasicAnimation animationWithKeyPath:@"bounds"];
382 animation.fromValue = [NSValue valueWithRect:from]; 694 animation.fromValue = [NSValue valueWithRect:from];
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
416 728
417 // Add the animation, overriding the implicit animation. 729 // Add the animation, overriding the implicit animation.
418 [layer addAnimation:animation forKey:@"position"]; 730 [layer addAnimation:animation forKey:@"position"];
419 } 731 }
420 732
421 @interface TabposeWindow (Private) 733 @interface TabposeWindow (Private)
422 - (id)initForWindow:(NSWindow*)parent 734 - (id)initForWindow:(NSWindow*)parent
423 rect:(NSRect)rect 735 rect:(NSRect)rect
424 slomo:(BOOL)slomo 736 slomo:(BOOL)slomo
425 tabStripModel:(TabStripModel*)tabStripModel; 737 tabStripModel:(TabStripModel*)tabStripModel;
426 - (void)setUpLayers:(NSRect)bgLayerRect slomo:(BOOL)slomo; 738 - (void)setUpLayersInSlomo:(BOOL)slomo;
427 - (void)fadeAway:(BOOL)slomo; 739 - (void)fadeAway:(BOOL)slomo;
428 - (void)selectTileAtIndex:(int)newIndex; 740 - (void)selectTileAtIndex:(int)newIndex;
429 @end 741 @end
430 742
431 @implementation TabposeWindow 743 @implementation TabposeWindow
432 744
433 + (id)openTabposeFor:(NSWindow*)parent 745 + (id)openTabposeFor:(NSWindow*)parent
434 rect:(NSRect)rect 746 rect:(NSRect)rect
435 slomo:(BOOL)slomo 747 slomo:(BOOL)slomo
436 tabStripModel:(TabStripModel*)tabStripModel { 748 tabStripModel:(TabStripModel*)tabStripModel {
437 // Releases itself when closed. 749 // Releases itself when closed.
438 return [[TabposeWindow alloc] 750 return [[TabposeWindow alloc]
439 initForWindow:parent rect:rect slomo:slomo tabStripModel:tabStripModel]; 751 initForWindow:parent rect:rect slomo:slomo tabStripModel:tabStripModel];
440 } 752 }
441 753
442 - (id)initForWindow:(NSWindow*)parent 754 - (id)initForWindow:(NSWindow*)parent
443 rect:(NSRect)rect 755 rect:(NSRect)rect
444 slomo:(BOOL)slomo 756 slomo:(BOOL)slomo
445 tabStripModel:(TabStripModel*)tabStripModel { 757 tabStripModel:(TabStripModel*)tabStripModel {
446 NSRect frame = [parent frame]; 758 NSRect frame = [parent frame];
447 if ((self = [super initWithContentRect:frame 759 if ((self = [super initWithContentRect:frame
448 styleMask:NSBorderlessWindowMask 760 styleMask:NSBorderlessWindowMask
449 backing:NSBackingStoreBuffered 761 backing:NSBackingStoreBuffered
450 defer:NO])) { 762 defer:NO])) {
451 // TODO(thakis): Add a TabStripModelObserver to |tabStripModel_|. 763 containingRect_ = rect;
452 tabStripModel_ = tabStripModel; 764 tabStripModel_ = tabStripModel;
453 state_ = tabpose::kFadingIn; 765 state_ = tabpose::kFadingIn;
454 tileSet_.reset(new tabpose::TileSet); 766 tileSet_.reset(new tabpose::TileSet);
767 tabStripModelObserverBridge_.reset(
768 new TabStripModelObserverBridge(tabStripModel_, self));
455 [self setReleasedWhenClosed:YES]; 769 [self setReleasedWhenClosed:YES];
456 [self setOpaque:NO]; 770 [self setOpaque:NO];
457 [self setBackgroundColor:[NSColor clearColor]]; 771 [self setBackgroundColor:[NSColor clearColor]];
458 [self setUpLayers:rect slomo:slomo]; 772 [self setUpLayersInSlomo:slomo];
459 [self setAcceptsMouseMovedEvents:YES]; 773 [self setAcceptsMouseMovedEvents:YES];
460 [parent addChildWindow:self ordered:NSWindowAbove]; 774 [parent addChildWindow:self ordered:NSWindowAbove];
461 [self makeKeyAndOrderFront:self]; 775 [self makeKeyAndOrderFront:self];
462 } 776 }
463 return self; 777 return self;
464 } 778 }
465 779
466 - (CALayer*)selectedLayer { 780 - (CALayer*)selectedLayer {
467 return [allThumbnailLayers_ objectAtIndex:tileSet_->selected_index()]; 781 return [allThumbnailLayers_ objectAtIndex:tileSet_->selected_index()];
468 } 782 }
469 783
470 - (void)selectTileAtIndex:(int)newIndex { 784 - (void)selectTileAtIndex:(int)newIndex {
471 ScopedCAActionDisabler disabler;
472 const tabpose::Tile& tile = tileSet_->tile_at(newIndex); 785 const tabpose::Tile& tile = tileSet_->tile_at(newIndex);
473 selectionHighlight_.frame = 786 selectionHighlight_.frame =
474 NSRectToCGRect(NSInsetRect(tile.thumb_rect(), -5, -5)); 787 NSRectToCGRect(NSInsetRect(tile.thumb_rect(), -5, -5));
475
476 tileSet_->set_selected_index(newIndex); 788 tileSet_->set_selected_index(newIndex);
477 } 789 }
478 790
479 - (void)setUpLayers:(NSRect)bgLayerRect slomo:(BOOL)slomo { 791 - (void)selectTileAtIndexWithoutAnimation:(int)newIndex {
792 ScopedCAActionDisabler disabler;
793 [self selectTileAtIndex:newIndex];
794 }
795
796 - (void)addLayersForTile:(tabpose::Tile&)tile
797 showZoom:(BOOL)showZoom
798 slomo:(BOOL)slomo
799 animationDelegate:(id)animationDelegate {
800 scoped_nsobject<CALayer> layer([[ThumbnailLayer alloc]
801 initWithTabContents:tile.tab_contents()
802 fullSize:tile.GetStartRectRelativeTo(
803 tileSet_->selected_tile()).size]);
804 [layer setNeedsDisplay];
805
806 // Background color as placeholder for now.
807 layer.get().backgroundColor = CGColorGetConstantColor(kCGColorWhite);
808 if (showZoom) {
809 AnimateCALayerFrameFromTo(
810 layer,
811 tile.GetStartRectRelativeTo(tileSet_->selected_tile()),
812 tile.thumb_rect(),
813 kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1),
814 animationDelegate);
815 } else {
816 layer.get().frame = NSRectToCGRect(tile.thumb_rect());
817 }
818
819 layer.get().shadowRadius = 10;
820 layer.get().shadowOffset = CGSizeMake(0, -10);
821 if (state_ == tabpose::kFadedIn)
822 layer.get().shadowOpacity = 0.5;
823
824 [bgLayer_ addSublayer:layer];
825 [allThumbnailLayers_ addObject:layer];
826
827 // Favicon and title.
828 NSFont* font = [NSFont systemFontOfSize:tile.title_font_size()];
829 tile.set_font_metrics([font ascender], -[font descender]);
830
831 NSImage* nsFavicon = gfx::SkBitmapToNSImage(tile.favicon());
832 // Either we don't have a valid favicon or there was some issue converting
833 // it from an SkBitmap. Either way, just show the default.
834 if (!nsFavicon) {
835 NSImage* defaultFavIcon =
836 ResourceBundle::GetSharedInstance().GetNSImageNamed(
837 IDR_DEFAULT_FAVICON);
838 nsFavicon = defaultFavIcon;
839 }
840 scoped_cftyperef<CGImageRef> favicon(
841 mac_util::CopyNSImageToCGImage(nsFavicon));
842
843 CALayer* faviconLayer = [CALayer layer];
844 faviconLayer.frame = NSRectToCGRect(tile.favicon_rect());
845 faviconLayer.contents = (id)favicon.get();
846 faviconLayer.zPosition = 1; // On top of the thumb shadow.
847 if (state_ == tabpose::kFadingIn)
848 faviconLayer.hidden = YES;
849 [bgLayer_ addSublayer:faviconLayer];
850 [allFaviconLayers_ addObject:faviconLayer];
851
852 CATextLayer* titleLayer = [CATextLayer layer];
853 titleLayer.frame = NSRectToCGRect(tile.title_rect());
854 titleLayer.string = base::SysUTF16ToNSString(tile.title());
855 titleLayer.fontSize = [font pointSize];
856 titleLayer.truncationMode = kCATruncationEnd;
857 titleLayer.font = font;
858 titleLayer.zPosition = 1; // On top of the thumb shadow.
859 if (state_ == tabpose::kFadingIn)
860 titleLayer.hidden = YES;
861 [bgLayer_ addSublayer:titleLayer];
862 [allTitleLayers_ addObject:titleLayer];
863 }
864
865 - (void)setUpLayersInSlomo:(BOOL)slomo {
480 // Root layer -- covers whole window. 866 // Root layer -- covers whole window.
481 rootLayer_ = [CALayer layer]; 867 rootLayer_ = [CALayer layer];
482 [[self contentView] setLayer:rootLayer_];
483 [[self contentView] setWantsLayer:YES];
484 868
485 // Background layer -- the visible part of the window. 869 // In a block so that the layers don't fade in.
486 gray_.reset(CGColorCreateGenericGray(0.39, 1.0)); 870 {
487 bgLayer_ = [CALayer layer]; 871 ScopedCAActionDisabler disabler;
488 bgLayer_.backgroundColor = gray_; 872 // Background layer -- the visible part of the window.
489 bgLayer_.frame = NSRectToCGRect(bgLayerRect); 873 gray_.reset(CGColorCreateGenericGray(0.39, 1.0));
490 bgLayer_.masksToBounds = YES; 874 bgLayer_ = [CALayer layer];
491 [rootLayer_ addSublayer:bgLayer_]; 875 bgLayer_.backgroundColor = gray_;
876 bgLayer_.frame = NSRectToCGRect(containingRect_);
877 bgLayer_.masksToBounds = YES;
878 [rootLayer_ addSublayer:bgLayer_];
492 879
493 // Selection highlight layer. 880 // Selection highlight layer.
494 darkBlue_.reset(CGColorCreateGenericRGB(0.25, 0.34, 0.86, 1.0)); 881 darkBlue_.reset(CGColorCreateGenericRGB(0.25, 0.34, 0.86, 1.0));
495 selectionHighlight_ = [CALayer layer]; 882 selectionHighlight_ = [CALayer layer];
496 selectionHighlight_.backgroundColor = darkBlue_; 883 selectionHighlight_.backgroundColor = darkBlue_;
497 selectionHighlight_.cornerRadius = 5.0; 884 selectionHighlight_.cornerRadius = 5.0;
498 selectionHighlight_.zPosition = -1; // Behind other layers. 885 selectionHighlight_.zPosition = -1; // Behind other layers.
499 selectionHighlight_.hidden = YES; 886 selectionHighlight_.hidden = YES;
500 [bgLayer_ addSublayer:selectionHighlight_]; 887 [bgLayer_ addSublayer:selectionHighlight_];
501 888
502 // Top gradient. 889 // Top gradient.
503 CALayer* gradientLayer = [DarkGradientLayer layer]; 890 CALayer* gradientLayer = [DarkGradientLayer layer];
504 gradientLayer.frame = CGRectMake( 891 gradientLayer.frame = CGRectMake(
505 0, 892 0,
506 NSHeight(bgLayerRect) - kTopGradientHeight, 893 NSHeight(containingRect_) - kTopGradientHeight,
507 NSWidth(bgLayerRect), 894 NSWidth(containingRect_),
508 kTopGradientHeight); 895 kTopGradientHeight);
509 [gradientLayer setNeedsDisplay]; // Draw once. 896 [gradientLayer setNeedsDisplay]; // Draw once.
510 [bgLayer_ addSublayer:gradientLayer]; 897 [bgLayer_ addSublayer:gradientLayer];
898 }
511 899
512 // Layers for the tab thumbnails. 900 // Layers for the tab thumbnails.
513 tileSet_->Build(tabStripModel_); 901 tileSet_->Build(tabStripModel_);
514 tileSet_->Layout(bgLayerRect); 902 tileSet_->Layout(containingRect_);
515
516 NSImage* defaultFavIcon = ResourceBundle::GetSharedInstance().GetNSImageNamed(
517 IDR_DEFAULT_FAVICON);
518
519 allThumbnailLayers_.reset( 903 allThumbnailLayers_.reset(
520 [[NSMutableArray alloc] initWithCapacity:tabStripModel_->count()]); 904 [[NSMutableArray alloc] initWithCapacity:tabStripModel_->count()]);
521 allFaviconLayers_.reset( 905 allFaviconLayers_.reset(
522 [[NSMutableArray alloc] initWithCapacity:tabStripModel_->count()]); 906 [[NSMutableArray alloc] initWithCapacity:tabStripModel_->count()]);
523 allTitleLayers_.reset( 907 allTitleLayers_.reset(
524 [[NSMutableArray alloc] initWithCapacity:tabStripModel_->count()]); 908 [[NSMutableArray alloc] initWithCapacity:tabStripModel_->count()]);
909
525 for (int i = 0; i < tabStripModel_->count(); ++i) { 910 for (int i = 0; i < tabStripModel_->count(); ++i) {
526 const tabpose::Tile& tile = tileSet_->tile_at(i);
527 CALayer* layer = [CALayer layer];
528
529 // Background color as placeholder for now.
530 layer.backgroundColor = CGColorGetConstantColor(kCGColorWhite);
531
532 AnimateCALayerFrameFromTo(
533 layer,
534 tile.GetStartRectRelativeTo(tileSet_->selected_tile()),
535 tile.thumb_rect(),
536 kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1),
537 i == tileSet_->selected_index() ? self : nil);
538
539 // Add a delegate to one of the animations to get a notification once the 911 // Add a delegate to one of the animations to get a notification once the
540 // animations are done. 912 // animations are done.
913 [self addLayersForTile:tileSet_->tile_at(i)
914 showZoom:YES
915 slomo:slomo
916 animationDelegate:i == tileSet_->selected_index() ? self : nil];
541 if (i == tileSet_->selected_index()) { 917 if (i == tileSet_->selected_index()) {
918 CALayer* layer = [allThumbnailLayers_ objectAtIndex:i];
542 CAAnimation* animation = [layer animationForKey:@"bounds"]; 919 CAAnimation* animation = [layer animationForKey:@"bounds"];
543 DCHECK(animation); 920 DCHECK(animation);
544 [animation setValue:kAnimationIdFadeIn forKey:kAnimationIdKey]; 921 [animation setValue:kAnimationIdFadeIn forKey:kAnimationIdKey];
545 } 922 }
923 }
924 [self selectTileAtIndexWithoutAnimation:tileSet_->selected_index()];
546 925
547 layer.shadowRadius = 10; 926 // Needs to happen after all layers have been added to |rootLayer_|, else
548 layer.shadowOffset = CGSizeMake(0, -10); 927 // there's a one frame flash of grey at the beginning of the animation
549 928 // (|bgLayer_| showing through with none of its children visible yet).
550 [bgLayer_ addSublayer:layer]; 929 [[self contentView] setLayer:rootLayer_];
551 [allThumbnailLayers_ addObject:layer]; 930 [[self contentView] setWantsLayer:YES];
552
553 // Favicon and title.
554 NSFont* font = [NSFont systemFontOfSize:tile.title_font_size()];
555 tileSet_->tile_at(i).set_font_metrics([font ascender], -[font descender]);
556
557 NSImage* ns_favicon = gfx::SkBitmapToNSImage(tile.favicon());
558 // Either we don't have a valid favicon or there was some issue converting
559 // it from an SkBitmap. Either way, just show the default.
560 if (!ns_favicon)
561 ns_favicon = defaultFavIcon;
562 scoped_cftyperef<CGImageRef> favicon(
563 mac_util::CopyNSImageToCGImage(ns_favicon));
564
565 CALayer* faviconLayer = [CALayer layer];
566 faviconLayer.frame = NSRectToCGRect(tile.favicon_rect());
567 faviconLayer.contents = (id)favicon.get();
568 faviconLayer.zPosition = 1; // On top of the thumb shadow.
569 faviconLayer.hidden = YES;
570 [bgLayer_ addSublayer:faviconLayer];
571 [allFaviconLayers_ addObject:faviconLayer];
572
573 CATextLayer* titleLayer = [CATextLayer layer];
574 titleLayer.frame = NSRectToCGRect(tile.title_rect());
575 titleLayer.string = base::SysUTF16ToNSString(tile.title());
576 titleLayer.fontSize = [font pointSize];
577 titleLayer.truncationMode = kCATruncationEnd;
578 titleLayer.font = font;
579 titleLayer.zPosition = 1; // On top of the thumb shadow.
580 titleLayer.hidden = YES;
581 [bgLayer_ addSublayer:titleLayer];
582 [allTitleLayers_ addObject:titleLayer];
583 }
584 [self selectTileAtIndex:tileSet_->selected_index()];
585 } 931 }
586 932
587 - (BOOL)canBecomeKeyWindow { 933 - (BOOL)canBecomeKeyWindow {
588 return YES; 934 return YES;
589 } 935 }
590 936
591 - (void)keyDown:(NSEvent*)event { 937 - (void)keyDown:(NSEvent*)event {
592 // Overridden to prevent beeps. 938 // Overridden to prevent beeps.
593 } 939 }
594 940
595 - (void)keyUp:(NSEvent*)event { 941 - (void)keyUp:(NSEvent*)event {
596 if (state_ == tabpose::kFadingOut) 942 if (state_ == tabpose::kFadingOut)
597 return; 943 return;
598 944
599 NSString* characters = [event characters]; 945 NSString* characters = [event characters];
600 if ([characters length] < 1) 946 if ([characters length] < 1)
601 return; 947 return;
602 948
603 unichar character = [characters characterAtIndex:0]; 949 unichar character = [characters characterAtIndex:0];
604 switch (character) { 950 switch (character) {
605 case NSEnterCharacter: 951 case NSEnterCharacter:
606 case NSNewlineCharacter: 952 case NSNewlineCharacter:
607 case NSCarriageReturnCharacter: 953 case NSCarriageReturnCharacter:
608 case ' ': 954 case ' ':
609 [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0]; 955 [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0];
610 break; 956 break;
611 case '\e': // Escape 957 case '\e': // Escape
612 tileSet_->ResetSelectedIndex(); 958 tileSet_->set_selected_index(tabStripModel_->selected_index());
613 [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0]; 959 [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0];
614 break; 960 break;
615 // TODO(thakis): Support moving the selection via arrow keys. 961 // TODO(thakis): Support moving the selection via arrow keys.
616 } 962 }
617 } 963 }
618 964
619 - (void)mouseMoved:(NSEvent*)event { 965 - (void)mouseMoved:(NSEvent*)event {
620 int newIndex = -1; 966 int newIndex = -1;
621 CGPoint p = NSPointToCGPoint([event locationInWindow]); 967 CGPoint p = NSPointToCGPoint([event locationInWindow]);
622 for (NSUInteger i = 0; i < [allThumbnailLayers_ count]; ++i) { 968 for (NSUInteger i = 0; i < [allThumbnailLayers_ count]; ++i) {
623 CALayer* layer = [allThumbnailLayers_ objectAtIndex:i]; 969 CALayer* layer = [allThumbnailLayers_ objectAtIndex:i];
624 CGPoint lp = [layer convertPoint:p fromLayer:rootLayer_]; 970 CGPoint lp = [layer convertPoint:p fromLayer:rootLayer_];
625 if ([static_cast<CALayer*>([layer presentationLayer]) containsPoint:lp]) 971 if ([static_cast<CALayer*>([layer presentationLayer]) containsPoint:lp])
626 newIndex = i; 972 newIndex = i;
627 } 973 }
628 if (newIndex >= 0) 974 if (newIndex >= 0)
629 [self selectTileAtIndex:newIndex]; 975 [self selectTileAtIndexWithoutAnimation:newIndex];
630 } 976 }
631 977
632 - (void)mouseDown:(NSEvent*)event { 978 - (void)mouseDown:(NSEvent*)event {
633 [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0]; 979 [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0];
634 } 980 }
635 981
636 - (void)swipeWithEvent:(NSEvent*)event { 982 - (void)swipeWithEvent:(NSEvent*)event {
637 if ([event deltaY] > 0.5) // Swipe up 983 if ([event deltaY] > 0.5) // Swipe up
638 [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0]; 984 [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0];
639 } 985 }
(...skipping 14 matching lines...) Expand all
654 } 1000 }
655 1001
656 - (void)fadeAway:(BOOL)slomo { 1002 - (void)fadeAway:(BOOL)slomo {
657 if (state_ == tabpose::kFadingOut) 1003 if (state_ == tabpose::kFadingOut)
658 return; 1004 return;
659 1005
660 state_ = tabpose::kFadingOut; 1006 state_ = tabpose::kFadingOut;
661 [self setAcceptsMouseMovedEvents:NO]; 1007 [self setAcceptsMouseMovedEvents:NO];
662 1008
663 // Select chosen tab. 1009 // Select chosen tab.
664 tabStripModel_->SelectTabContentsAt(tileSet_->selected_index(), 1010 if (tileSet_->selected_index() < tabStripModel_->count()) {
665 /*user_gesture=*/true); 1011 tabStripModel_->SelectTabContentsAt(tileSet_->selected_index(),
1012 /*user_gesture=*/true);
1013 } else {
1014 DCHECK_EQ(tileSet_->selected_index(), 0);
1015 }
666 1016
667 { 1017 {
668 ScopedCAActionDisabler disableCAActions; 1018 ScopedCAActionDisabler disableCAActions;
669 1019
670 // Move the selected layer on top of all other layers. 1020 // Move the selected layer on top of all other layers.
671 [self selectedLayer].zPosition = 1; 1021 [self selectedLayer].zPosition = 1;
672 1022
673 selectionHighlight_.hidden = YES; 1023 selectionHighlight_.hidden = YES;
674 for (CALayer* layer in allFaviconLayers_.get()) 1024 for (CALayer* layer in allFaviconLayers_.get())
675 layer.hidden = YES; 1025 layer.hidden = YES;
676 for (CALayer* layer in allTitleLayers_.get()) 1026 for (CALayer* layer in allTitleLayers_.get())
677 layer.hidden = YES; 1027 layer.hidden = YES;
678 1028
679 // Running animations with shadows is slow, so turn shadows off before 1029 // Running animations with shadows is slow, so turn shadows off before
680 // running the exit animation. 1030 // running the exit animation.
681 for (CALayer* layer in allThumbnailLayers_.get()) 1031 for (CALayer* layer in allThumbnailLayers_.get())
682 layer.shadowOpacity = 0.0; 1032 layer.shadowOpacity = 0.0;
683 } 1033 }
684 1034
685 // Animate layers out, all in one transaction. 1035 // Animate layers out, all in one transaction.
686 CGFloat duration = kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1); 1036 CGFloat duration = 2 * kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1);
687 ScopedCAActionSetDuration durationSetter(duration); 1037 ScopedCAActionSetDuration durationSetter(duration);
688 for (NSUInteger i = 0; i < [allThumbnailLayers_ count]; ++i) { 1038 for (NSUInteger i = 0; i < [allThumbnailLayers_ count]; ++i) {
689 CALayer* layer = [allThumbnailLayers_ objectAtIndex:i]; 1039 CALayer* layer = [allThumbnailLayers_ objectAtIndex:i];
690 // |start_thumb_rect_| was relative to |initial_index_|, now this needs to 1040 // |start_thumb_rect_| was relative to the initial index, now this needs to
691 // be relative to |selectedIndex_| (whose start rect was relative to 1041 // be relative to |selectedIndex_| (whose start rect was relative to
692 // |initial_index_| too) 1042 // the initial index, too).
693 CGRect newFrame = NSRectToCGRect( 1043 CGRect newFrame = NSRectToCGRect(
694 tileSet_->tile_at(i).GetStartRectRelativeTo(tileSet_->selected_tile())); 1044 tileSet_->tile_at(i).GetStartRectRelativeTo(tileSet_->selected_tile()));
695 1045
696 // Add a delegate to one of the implicit animations to get a notification 1046 // Add a delegate to one of the implicit animations to get a notification
697 // once the animations are done. 1047 // once the animations are done.
698 if (static_cast<int>(i) == tileSet_->selected_index()) { 1048 if (static_cast<int>(i) == tileSet_->selected_index()) {
699 CAAnimation* animation = [CAAnimation animation]; 1049 CAAnimation* animation = [CAAnimation animation];
700 animation.delegate = self; 1050 animation.delegate = self;
701 [animation setValue:kAnimationIdFadeOut forKey:kAnimationIdKey]; 1051 [animation setValue:kAnimationIdFadeOut forKey:kAnimationIdKey];
702 [layer addAnimation:animation forKey:@"frame"]; 1052 [layer addAnimation:animation forKey:@"frame"];
703 } 1053 }
704 1054
705 layer.frame = newFrame; 1055 layer.frame = newFrame;
1056
1057 if (static_cast<int>(i) == tileSet_->selected_index()) {
1058 // Redraw layer at big resolution, so that zoom-in isn't blocky.
1059 [layer setNeedsDisplay];
1060 }
706 } 1061 }
707 } 1062 }
708 1063
709 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished { 1064 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished {
710 NSString* animationId = [animation valueForKey:kAnimationIdKey]; 1065 NSString* animationId = [animation valueForKey:kAnimationIdKey];
711 if ([animationId isEqualToString:kAnimationIdFadeIn]) { 1066 if ([animationId isEqualToString:kAnimationIdFadeIn]) {
712 if (finished) { 1067 if (finished) {
713 // If the user clicks while the fade in animation is still running, 1068 // If the user clicks while the fade in animation is still running,
714 // |state_| is already kFadingOut. In that case, don't do anything. 1069 // |state_| is already kFadingOut. In that case, don't do anything.
715 DCHECK_EQ(tabpose::kFadingIn, state_); 1070 DCHECK_EQ(tabpose::kFadingIn, state_);
(...skipping 10 matching lines...) Expand all
726 ScopedCAActionDisabler disableCAActions; 1081 ScopedCAActionDisabler disableCAActions;
727 for (CALayer* layer in allThumbnailLayers_.get()) 1082 for (CALayer* layer in allThumbnailLayers_.get())
728 layer.shadowOpacity = 0.5; 1083 layer.shadowOpacity = 0.5;
729 } 1084 }
730 } else if ([animationId isEqualToString:kAnimationIdFadeOut]) { 1085 } else if ([animationId isEqualToString:kAnimationIdFadeOut]) {
731 DCHECK_EQ(tabpose::kFadingOut, state_); 1086 DCHECK_EQ(tabpose::kFadingOut, state_);
732 [self close]; 1087 [self close];
733 } 1088 }
734 } 1089 }
735 1090
1091 - (NSUInteger)thumbnailLayerCount {
1092 return [allThumbnailLayers_ count];
1093 }
1094
1095 - (int)selectedIndex {
1096 return tileSet_->selected_index();
1097 }
1098
1099 #pragma mark TabStripModelBridge
1100
1101 - (void)refreshLayerFramesAtIndex:(int)i {
1102 const tabpose::Tile& tile = tileSet_->tile_at(i);
1103
1104 CALayer* faviconLayer = [allFaviconLayers_ objectAtIndex:i];
1105 faviconLayer.frame = NSRectToCGRect(tile.favicon_rect());
1106 CALayer* titleLayer = [allTitleLayers_ objectAtIndex:i];
1107 titleLayer.frame = NSRectToCGRect(tile.title_rect());
1108 CALayer* thumbLayer = [allThumbnailLayers_ objectAtIndex:i];
1109 thumbLayer.frame = NSRectToCGRect(tile.thumb_rect());
1110 }
1111
1112 - (void)insertTabWithContents:(TabContents*)contents
1113 atIndex:(NSInteger)index
1114 inForeground:(bool)inForeground {
1115 // This happens if you cmd-click a link and then immediately open tabpose
1116 // on a slowish machine.
1117 ScopedCAActionSetDuration durationSetter(kObserverChangeAnimationDuration);
1118
1119 // Insert new layer and relayout.
1120 tileSet_->InsertTileAt(index, contents);
1121 tileSet_->Layout(containingRect_);
1122 [self addLayersForTile:tileSet_->tile_at(index)
1123 showZoom:NO
1124 slomo:NO
1125 animationDelegate:nil];
1126
1127 // Update old layers.
1128 DCHECK_EQ(tabStripModel_->count(),
1129 static_cast<int>([allThumbnailLayers_ count]));
1130 DCHECK_EQ(tabStripModel_->count(),
1131 static_cast<int>([allTitleLayers_ count]));
1132 DCHECK_EQ(tabStripModel_->count(),
1133 static_cast<int>([allFaviconLayers_ count]));
1134
1135 for (int i = 0; i < tabStripModel_->count(); ++i) {
1136 if (i == index) // The new layer.
1137 continue;
1138 [self refreshLayerFramesAtIndex:i];
1139 }
1140
1141 // Update selection.
1142 int selectedIndex = tileSet_->selected_index();
1143 if (selectedIndex >= index)
1144 selectedIndex++;
1145 [self selectTileAtIndex:selectedIndex];
1146 }
1147
1148 - (void)tabClosingWithContents:(TabContents*)contents
1149 atIndex:(NSInteger)index {
1150 // We will also get a -tabDetachedWithContents:atIndex: notification for
1151 // closing tabs, so do nothing here.
1152 }
1153
1154 - (void)tabDetachedWithContents:(TabContents*)contents
1155 atIndex:(NSInteger)index {
1156 ScopedCAActionSetDuration durationSetter(kObserverChangeAnimationDuration);
1157
1158 // Remove layer and relayout.
1159 tileSet_->RemoveTileAt(index);
1160 tileSet_->Layout(containingRect_);
1161
1162 [[allThumbnailLayers_ objectAtIndex:index] removeFromSuperlayer];
1163 [allThumbnailLayers_ removeObjectAtIndex:index];
1164 [[allTitleLayers_ objectAtIndex:index] removeFromSuperlayer];
1165 [allTitleLayers_ removeObjectAtIndex:index];
1166 [[allFaviconLayers_ objectAtIndex:index] removeFromSuperlayer];
1167 [allFaviconLayers_ removeObjectAtIndex:index];
1168
1169 // Update old layers.
1170 DCHECK_EQ(tabStripModel_->count(),
1171 static_cast<int>([allThumbnailLayers_ count]));
1172 DCHECK_EQ(tabStripModel_->count(),
1173 static_cast<int>([allTitleLayers_ count]));
1174 DCHECK_EQ(tabStripModel_->count(),
1175 static_cast<int>([allFaviconLayers_ count]));
1176
1177 if (tabStripModel_->count() == 0)
1178 [self close];
1179
1180 for (int i = 0; i < tabStripModel_->count(); ++i)
1181 [self refreshLayerFramesAtIndex:i];
1182
1183 // Update selection.
1184 int selectedIndex = tileSet_->selected_index();
1185 if (selectedIndex >= index)
1186 selectedIndex--;
1187 if (selectedIndex >= 0)
1188 [self selectTileAtIndex:selectedIndex];
1189 }
1190
1191 - (void)tabMovedWithContents:(TabContents*)contents
1192 fromIndex:(NSInteger)from
1193 toIndex:(NSInteger)to {
1194 ScopedCAActionSetDuration durationSetter(kObserverChangeAnimationDuration);
1195
1196 // Move tile from |from| to |to|.
1197 tileSet_->MoveTileFromTo(from, to);
1198
1199 // Move corresponding layers from |from| to |to|.
1200 scoped_nsobject<CALayer> thumbLayer(
1201 [[allThumbnailLayers_ objectAtIndex:from] retain]);
1202 [allThumbnailLayers_ removeObjectAtIndex:from];
1203 [allThumbnailLayers_ insertObject:thumbLayer.get() atIndex:to];
1204 scoped_nsobject<CALayer> faviconLayer(
1205 [[allFaviconLayers_ objectAtIndex:from] retain]);
1206 [allFaviconLayers_ removeObjectAtIndex:from];
1207 [allFaviconLayers_ insertObject:faviconLayer.get() atIndex:to];
1208 scoped_nsobject<CALayer> titleLayer(
1209 [[allTitleLayers_ objectAtIndex:from] retain]);
1210 [allTitleLayers_ removeObjectAtIndex:from];
1211 [allTitleLayers_ insertObject:titleLayer.get() atIndex:to];
1212
1213 // Update frames of the layers.
1214 for (int i = std::min(from, to); i <= std::max(from, to); ++i)
1215 [self refreshLayerFramesAtIndex:i];
1216
1217 // Update selection.
1218 int selectedIndex = tileSet_->selected_index();
1219 if (from == selectedIndex)
1220 selectedIndex = to;
1221 else if (from < selectedIndex && selectedIndex <= to)
1222 selectedIndex--;
1223 else if (to <= selectedIndex && selectedIndex < from)
1224 selectedIndex++;
1225 [self selectTileAtIndex:selectedIndex];
1226 }
1227
1228 - (void)tabChangedWithContents:(TabContents*)contents
1229 atIndex:(NSInteger)index
1230 changeType:(TabStripModelObserver::TabChangeType)change {
1231 // Tell the window to update text, title, and thumb layers at |index| to get
1232 // their data from |contents|. |contents| can be different from the old
1233 // contents at that index!
1234 // While a tab is loading, this is unfortunately called quite often for
1235 // both the "loading" and the "all" change types, so we don't really want to
1236 // send thumb requests to the corresponding renderer when this is called.
1237 // For now, just make sure that we don't hold on to an invalid TabContents
1238 // object.
1239 tabpose::Tile& tile = tileSet_->tile_at(index);
1240 if (contents == tile.tab_contents()) {
1241 // TODO(thakis): Install a timer to send a thumb request/update title/update
1242 // favicon after 20ms or so, and reset the timer every time this is called
1243 // to make sure we get an updated thumb, without requesting them all over.
1244 return;
1245 }
1246
1247 tile.set_tab_contents(contents);
1248 ThumbnailLayer* thumbLayer = [allThumbnailLayers_ objectAtIndex:index];
1249 [thumbLayer setTabContents:contents];
1250 }
1251
736 @end 1252 @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