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 #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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 |
OLD | NEW |