summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa/tabpose_window.mm
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/cocoa/tabpose_window.mm')
-rw-r--r--chrome/browser/cocoa/tabpose_window.mm1437
1 files changed, 0 insertions, 1437 deletions
diff --git a/chrome/browser/cocoa/tabpose_window.mm b/chrome/browser/cocoa/tabpose_window.mm
deleted file mode 100644
index 07385ca..0000000
--- a/chrome/browser/cocoa/tabpose_window.mm
+++ /dev/null
@@ -1,1437 +0,0 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "chrome/browser/cocoa/tabpose_window.h"
-
-#import <QuartzCore/QuartzCore.h>
-
-#include "app/resource_bundle.h"
-#include "base/mac_util.h"
-#include "base/mac/scoped_cftyperef.h"
-#include "base/scoped_callback_factory.h"
-#include "base/sys_string_conversions.h"
-#include "chrome/app/chrome_command_ids.h"
-#include "chrome/browser/browser_process.h"
-#import "chrome/browser/cocoa/bookmarks/bookmark_bar_constants.h"
-#import "chrome/browser/cocoa/browser_window_controller.h"
-#import "chrome/browser/cocoa/tab_strip_controller.h"
-#import "chrome/browser/cocoa/tab_strip_model_observer_bridge.h"
-#import "chrome/browser/debugger/devtools_window.h"
-#include "chrome/browser/prefs/pref_service.h"
-#include "chrome/browser/renderer_host/backing_store_mac.h"
-#include "chrome/browser/renderer_host/render_view_host.h"
-#include "chrome/browser/renderer_host/render_widget_host_view_mac.h"
-#include "chrome/browser/tab_contents/tab_contents.h"
-#include "chrome/browser/tab_contents_wrapper.h"
-#include "chrome/browser/tab_contents/thumbnail_generator.h"
-#include "chrome/browser/tab_contents_wrapper.h"
-#include "chrome/common/pref_names.h"
-#include "grit/app_resources.h"
-#include "skia/ext/skia_utils_mac.h"
-#include "third_party/skia/include/utils/mac/SkCGUtils.h"
-
-const int kTopGradientHeight = 15;
-
-NSString* const kAnimationIdKey = @"AnimationId";
-NSString* const kAnimationIdFadeIn = @"FadeIn";
-NSString* const kAnimationIdFadeOut = @"FadeOut";
-
-const CGFloat kDefaultAnimationDuration = 0.25; // In seconds.
-const CGFloat kSlomoFactor = 4;
-const CGFloat kObserverChangeAnimationDuration = 0.75; // In seconds.
-
-// CAGradientLayer is 10.6-only -- roll our own.
-@interface DarkGradientLayer : CALayer
-- (void)drawInContext:(CGContextRef)context;
-@end
-
-@implementation DarkGradientLayer
-- (void)drawInContext:(CGContextRef)context {
- base::mac::ScopedCFTypeRef<CGColorSpaceRef> grayColorSpace(
- CGColorSpaceCreateWithName(kCGColorSpaceGenericGray));
- CGFloat grays[] = { 0.277, 1.0, 0.39, 1.0 };
- CGFloat locations[] = { 0, 1 };
- base::mac::ScopedCFTypeRef<CGGradientRef> gradient(
- CGGradientCreateWithColorComponents(
- grayColorSpace.get(), grays, locations, arraysize(locations)));
- CGPoint topLeft = CGPointMake(0.0, kTopGradientHeight);
- CGContextDrawLinearGradient(context, gradient.get(), topLeft, CGPointZero, 0);
-}
-@end
-
-namespace tabpose {
-class ThumbnailLoader;
-}
-
-// A CALayer that draws a thumbnail for a TabContents object. The layer tries
-// to draw the TabContents's backing store directly if possible, and requests
-// a thumbnail bitmap from the TabContents's renderer process if not.
-@interface ThumbnailLayer : CALayer {
- // The TabContents the thumbnail is for.
- TabContents* contents_; // weak
-
- // The size the thumbnail is drawn at when zoomed in.
- NSSize fullSize_;
-
- // Used to load a thumbnail, if required.
- scoped_refptr<tabpose::ThumbnailLoader> loader_;
-
- // If the backing store couldn't be used and a thumbnail was returned from a
- // renderer process, it's stored in |thumbnail_|.
- base::mac::ScopedCFTypeRef<CGImageRef> thumbnail_;
-
- // True if the layer already sent a thumbnail request to a renderer.
- BOOL didSendLoad_;
-}
-- (id)initWithTabContents:(TabContents*)contents fullSize:(NSSize)fullSize;
-- (void)drawInContext:(CGContextRef)context;
-- (void)setThumbnail:(const SkBitmap&)bitmap;
-@end
-
-namespace tabpose {
-
-// ThumbnailLoader talks to the renderer process to load a thumbnail of a given
-// RenderWidgetHost, and sends the thumbnail back to a ThumbnailLayer once it
-// comes back from the renderer.
-class ThumbnailLoader : public base::RefCountedThreadSafe<ThumbnailLoader> {
- public:
- ThumbnailLoader(gfx::Size size, RenderWidgetHost* rwh, ThumbnailLayer* layer)
- : size_(size), rwh_(rwh), layer_(layer), factory_(this) {}
-
- // Starts the fetch.
- void LoadThumbnail();
-
- private:
- friend class base::RefCountedThreadSafe<ThumbnailLoader>;
- ~ThumbnailLoader() {
- ResetPaintingObserver();
- }
-
- void DidReceiveBitmap(const SkBitmap& bitmap) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- ResetPaintingObserver();
- [layer_ setThumbnail:bitmap];
- }
-
- void ResetPaintingObserver() {
- if (rwh_->painting_observer() != NULL) {
- DCHECK(rwh_->painting_observer() ==
- g_browser_process->GetThumbnailGenerator());
- rwh_->set_painting_observer(NULL);
- }
- }
-
- gfx::Size size_;
- RenderWidgetHost* rwh_; // weak
- ThumbnailLayer* layer_; // weak, owns us
- base::ScopedCallbackFactory<ThumbnailLoader> factory_;
-
- DISALLOW_COPY_AND_ASSIGN(ThumbnailLoader);
-};
-
-void ThumbnailLoader::LoadThumbnail() {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- ThumbnailGenerator* generator = g_browser_process->GetThumbnailGenerator();
- if (!generator) // In unit tests.
- return;
-
- // As mentioned in ThumbnailLayer's -drawInContext:, it's sufficient to have
- // thumbnails at the zoomed-out pixel size for all but the thumbnail the user
- // clicks on in the end. But we don't don't which thumbnail that will be, so
- // keep it simple and request full thumbnails for everything.
- // TODO(thakis): Request smaller thumbnails for users with many tabs.
- gfx::Size page_size(size_); // Logical size the renderer renders at.
- gfx::Size pixel_size(size_); // Physical pixel size the image is rendered at.
-
- DCHECK(rwh_->painting_observer() == NULL ||
- rwh_->painting_observer() == generator);
- rwh_->set_painting_observer(generator);
-
- // Will send an IPC to the renderer on the IO thread.
- generator->AskForSnapshot(
- rwh_,
- /*prefer_backing_store=*/false,
- factory_.NewCallback(&ThumbnailLoader::DidReceiveBitmap),
- page_size,
- pixel_size);
-}
-
-} // namespace tabpose
-
-@implementation ThumbnailLayer
-
-- (id)initWithTabContents:(TabContents*)contents fullSize:(NSSize)fullSize {
- CHECK(contents);
- if ((self = [super init])) {
- contents_ = contents;
- fullSize_ = fullSize;
- }
- return self;
-}
-
-- (void)setTabContents:(TabContents*)contents {
- contents_ = contents;
-}
-
-- (void)setThumbnail:(const SkBitmap&)bitmap {
- // SkCreateCGImageRef() holds on to |bitmaps|'s memory, so this doesn't
- // create a copy.
- thumbnail_.reset(SkCreateCGImageRef(bitmap));
- loader_ = NULL;
- [self setNeedsDisplay];
-}
-
-- (int)topOffset {
- int topOffset = 0;
-
- // Medium term, we want to show thumbs of the actual info bar views, which
- // means I need to create InfoBarControllers here. At that point, we can get
- // the height from that controller. Until then, hardcode. :-/
- const int kInfoBarHeight = 31;
- topOffset += contents_->infobar_delegate_count() * kInfoBarHeight;
-
- bool always_show_bookmark_bar =
- contents_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar);
- bool has_detached_bookmark_bar =
- contents_->ShouldShowBookmarkBar() && !always_show_bookmark_bar;
- if (has_detached_bookmark_bar)
- topOffset += bookmarks::kNTPBookmarkBarHeight;
-
- return topOffset;
-}
-
-- (int)bottomOffset {
- int bottomOffset = 0;
- TabContents* devToolsContents =
- DevToolsWindow::GetDevToolsContents(contents_);
- if (devToolsContents && devToolsContents->render_view_host() &&
- devToolsContents->render_view_host()->view()) {
- // The devtool's size might not be up-to-date, but since its height doesn't
- // change on window resize, and since most users don't use devtools, this is
- // good enough.
- bottomOffset +=
- devToolsContents->render_view_host()->view()->GetViewBounds().height();
- bottomOffset += 1; // :-( Divider line between web contents and devtools.
- }
- return bottomOffset;
-}
-
-- (void)drawBackingStore:(BackingStoreMac*)backing_store
- inRect:(CGRect)destRect
- context:(CGContextRef)context {
- // TODO(thakis): Add a sublayer for each accelerated surface in the rwhv.
- // Until then, accelerated layers (CoreAnimation NPAPI plugins, compositor)
- // won't show up in tabpose.
- if (backing_store->cg_layer()) {
- CGContextDrawLayerInRect(context, destRect, backing_store->cg_layer());
- } else {
- base::mac::ScopedCFTypeRef<CGImageRef> image(
- CGBitmapContextCreateImage(backing_store->cg_bitmap()));
- CGContextDrawImage(context, destRect, image);
- }
-}
-
-- (void)drawInContext:(CGContextRef)context {
- RenderWidgetHost* rwh = contents_->render_view_host();
- // NULL if renderer crashed.
- RenderWidgetHostView* rwhv = rwh ? rwh->view() : NULL;
- if (!rwhv) {
- // TODO(thakis): Maybe draw a sad tab layer?
- [super drawInContext:context];
- return;
- }
-
- // The size of the TabContent's RenderWidgetHost might not fit to the
- // current browser window at all, for example if the window was resized while
- // this TabContents object was not an active tab.
- // Compute the required size ourselves. Leave room for eventual infobars and
- // a detached bookmarks bar on the top, and for the devtools on the bottom.
- // Download shelf is not included in the |fullSize| rect, so no need to
- // correct for it here.
- // TODO(thakis): This is not resolution-independent.
- int topOffset = [self topOffset];
- int bottomOffset = [self bottomOffset];
- gfx::Size desiredThumbSize(fullSize_.width,
- fullSize_.height - topOffset - bottomOffset);
-
- // We need to ask the renderer for a thumbnail if
- // a) there's no backing store or
- // b) the backing store's size doesn't match our required size and
- // c) we didn't already send a thumbnail request to the renderer.
- BackingStoreMac* backing_store =
- (BackingStoreMac*)rwh->GetBackingStore(/*force_create=*/false);
- bool draw_backing_store =
- backing_store && backing_store->size() == desiredThumbSize;
-
- // Next weirdness: The destination rect. If the layer is |fullSize_| big, the
- // destination rect is (0, bottomOffset), (fullSize_.width, topOffset). But we
- // might be amidst an animation, so interpolate that rect.
- CGRect destRect = [self bounds];
- CGFloat scale = destRect.size.width / fullSize_.width;
- destRect.origin.y += bottomOffset * scale;
- destRect.size.height -= (bottomOffset + topOffset) * scale;
-
- // TODO(thakis): Draw infobars, detached bookmark bar as well.
-
- // If we haven't already, sent a thumbnail request to the renderer.
- if (!draw_backing_store && !didSendLoad_) {
- // Either the tab was never visible, or its backing store got evicted, or
- // the size of the backing store is wrong.
-
- // We only need a thumbnail the size of the zoomed-out layer for all
- // layers except the one the user clicks on. But since we can't know which
- // layer that is, request full-resolution layers for all tabs. This is
- // simple and seems to work in practice.
- loader_ = new tabpose::ThumbnailLoader(desiredThumbSize, rwh, self);
- loader_->LoadThumbnail();
- didSendLoad_ = YES;
-
- // Fill with bg color.
- [super drawInContext:context];
- }
-
- if (draw_backing_store) {
- // Backing store 'cache' hit!
- [self drawBackingStore:backing_store inRect:destRect context:context];
- } else if (thumbnail_) {
- // No cache hit, but the renderer returned a thumbnail to us.
- CGContextDrawImage(context, destRect, thumbnail_.get());
- }
-}
-
-@end
-
-namespace {
-
-class ScopedCAActionDisabler {
- public:
- ScopedCAActionDisabler() {
- [CATransaction begin];
- [CATransaction setValue:[NSNumber numberWithBool:YES]
- forKey:kCATransactionDisableActions];
- }
-
- ~ScopedCAActionDisabler() {
- [CATransaction commit];
- }
-};
-
-class ScopedCAActionSetDuration {
- public:
- explicit ScopedCAActionSetDuration(CGFloat duration) {
- [CATransaction begin];
- [CATransaction setValue:[NSNumber numberWithFloat:duration]
- forKey:kCATransactionAnimationDuration];
- }
-
- ~ScopedCAActionSetDuration() {
- [CATransaction commit];
- }
-};
-
-} // namespace
-
-// Given the number |n| of tiles with a desired aspect ratio of |a| and a
-// desired distance |dx|, |dy| between tiles, returns how many tiles fit
-// vertically into a rectangle with the dimensions |w_c|, |h_c|. This returns
-// an exact solution, which is usually a fractional number.
-static float FitNRectsWithAspectIntoBoundingSizeWithConstantPadding(
- int n, double a, int w_c, int h_c, int dx, int dy) {
- // We want to have the small rects have the same aspect ratio a as a full
- // tab. Let w, h be the size of a small rect, and w_c, h_c the size of the
- // container. dx, dy are the distances between small rects in x, y direction.
-
- // Geometry yields:
- // w_c = nx * (w + dx) - dx <=> w = (w_c + d_x) / nx - d_x
- // h_c = ny * (h + dy) - dy <=> h = (h_c + d_y) / ny - d_t
- // Plugging this into
- // a := tab_width / tab_height = w / h
- // yields
- // a = ((w_c - (nx - 1)*d_x)*ny) / (nx*(h_c - (ny - 1)*d_y))
- // Plugging in nx = n/ny and pen and paper (or wolfram alpha:
- // http://www.wolframalpha.com/input/?i=(-sqrt((d+n-a+f+n)^2-4+(a+f%2Ba+h)+(-d+n-n+w))%2Ba+f+n-d+n)/(2+a+(f%2Bh)) , (solution for nx)
- // http://www.wolframalpha.com/input/?i=+(-sqrt((a+f+n-d+n)^2-4+(d%2Bw)+(-a+f+n-a+h+n))-a+f+n%2Bd+n)/(2+(d%2Bw)) , (solution for ny)
- // ) gives us nx and ny (but the wrong root -- s/-sqrt(FOO)/sqrt(FOO)/.
-
- // This function returns ny.
- return (sqrt(pow(n * (a * dy - dx), 2) +
- 4 * n * a * (dx + w_c) * (dy + h_c)) -
- n * (a * dy - dx))
- /
- (2 * (dx + w_c));
-}
-
-namespace tabpose {
-
-// A tile is what is shown for a single tab in tabpose mode. It consists of a
-// title, favicon, thumbnail image, and pre- and postanimation rects.
-class Tile {
- public:
- Tile() {}
-
- // Returns the rectangle this thumbnail is at at the beginning of the zoom-in
- // animation. |tile| is the rectangle that's covering the whole tab area when
- // the animation starts.
- NSRect GetStartRectRelativeTo(const Tile& tile) const;
- NSRect thumb_rect() const { return thumb_rect_; }
-
- NSRect favicon_rect() const { return favicon_rect_; }
- SkBitmap favicon() const;
-
- // This changes |title_rect| and |favicon_rect| such that the favicon is on
- // the font's baseline and that the minimum distance between thumb rect and
- // favicon and title rects doesn't change.
- // The view code
- // 1. queries desired font size by calling |title_font_size()|
- // 2. loads that font
- // 3. calls |set_font_metrics()| which updates the title rect
- // 4. receives the title rect and puts the title on it with the font from 2.
- void set_font_metrics(CGFloat ascender, CGFloat descender);
- CGFloat title_font_size() const { return title_font_size_; }
-
- NSRect title_rect() const { return title_rect_; }
-
- // Returns an unelided title. The view logic is responsible for eliding.
- const string16& title() const { return contents_->GetTitle(); }
-
- TabContents* tab_contents() const { return contents_; }
- void set_tab_contents(TabContents* new_contents) { contents_ = new_contents; }
-
- private:
- friend class TileSet;
-
- // The thumb rect includes infobars, detached thumbnail bar, web contents,
- // and devtools.
- NSRect thumb_rect_;
- NSRect start_thumb_rect_;
-
- NSRect favicon_rect_;
-
- CGFloat title_font_size_;
- NSRect title_rect_;
-
- TabContents* contents_; // weak
-
- DISALLOW_COPY_AND_ASSIGN(Tile);
-};
-
-NSRect Tile::GetStartRectRelativeTo(const Tile& tile) const {
- NSRect rect = start_thumb_rect_;
- rect.origin.x -= tile.start_thumb_rect_.origin.x;
- rect.origin.y -= tile.start_thumb_rect_.origin.y;
- return rect;
-}
-
-SkBitmap Tile::favicon() const {
- if (contents_->is_app()) {
- SkBitmap* icon = contents_->GetExtensionAppIcon();
- if (icon)
- return *icon;
- }
- return contents_->GetFavIcon();
-}
-
-// Changes |title_rect| and |favicon_rect| such that the favicon is on the
-// font's baseline and that the minimum distance between thumb rect and
-// favicon and title rects doesn't change.
-void Tile::set_font_metrics(CGFloat ascender, CGFloat descender) {
- title_rect_.origin.y -= ascender + descender - NSHeight(title_rect_);
- title_rect_.size.height = ascender + descender;
-
- if (NSHeight(favicon_rect_) < ascender) {
- // Move favicon down.
- favicon_rect_.origin.y = title_rect_.origin.y + descender;
- } else {
- // Move title down.
- title_rect_.origin.y = favicon_rect_.origin.y - descender;
- }
-}
-
-// A tileset is responsible for owning and laying out all |Tile|s shown in a
-// tabpose window.
-class TileSet {
- public:
- TileSet() {}
-
- // Fills in |tiles_|.
- void Build(TabStripModel* source_model);
-
- // Computes coordinates for |tiles_|.
- void Layout(NSRect containing_rect);
-
- int selected_index() const { return selected_index_; }
- void set_selected_index(int index);
-
- const Tile& selected_tile() const { return *tiles_[selected_index()]; }
- Tile& tile_at(int index) { return *tiles_[index]; }
- const Tile& tile_at(int index) const { return *tiles_[index]; }
-
- // These return which index needs to be selected when the user presses
- // up, down, left, or right respectively.
- int up_index() const;
- int down_index() const;
- int left_index() const;
- int right_index() const;
-
- // These return which index needs to be selected on tab / shift-tab.
- int next_index() const;
- int previous_index() const;
-
- // Inserts a new Tile object containing |contents| at |index|. Does no
- // relayout.
- void InsertTileAt(int index, TabContents* contents);
-
- // Removes the Tile object at |index|. Does no relayout.
- void RemoveTileAt(int index);
-
- // Moves the Tile object at |from_index| to |to_index|. Since this doesn't
- // change the number of tiles, relayout can be done just by swapping the
- // tile rectangles in the index interval [from_index, to_index], so this does
- // layout.
- void MoveTileFromTo(int from_index, int to_index);
-
- private:
- int count_x() const {
- return ceilf(tiles_.size() / static_cast<float>(count_y_));
- }
- int count_y() const {
- return count_y_;
- }
- int last_row_count_x() const {
- return tiles_.size() - count_x() * (count_y() - 1);
- }
- int tiles_in_row(int row) const {
- return row != count_y() - 1 ? count_x() : last_row_count_x();
- }
- void index_to_tile_xy(int index, int* tile_x, int* tile_y) const {
- *tile_x = index % count_x();
- *tile_y = index / count_x();
- }
- int tile_xy_to_index(int tile_x, int tile_y) const {
- return tile_y * count_x() + tile_x;
- }
-
- ScopedVector<Tile> tiles_;
- int selected_index_;
- int count_y_;
-
- DISALLOW_COPY_AND_ASSIGN(TileSet);
-};
-
-void TileSet::Build(TabStripModel* source_model) {
- selected_index_ = source_model->selected_index();
- tiles_.resize(source_model->count());
- for (size_t i = 0; i < tiles_.size(); ++i) {
- tiles_[i] = new Tile;
- tiles_[i]->contents_ = source_model->GetTabContentsAt(i)->tab_contents();
- }
-}
-
-void TileSet::Layout(NSRect containing_rect) {
- int tile_count = tiles_.size();
- if (tile_count == 0) // Happens e.g. during test shutdown.
- return;
-
- // Room around the tiles insde of |containing_rect|.
- const int kSmallPaddingTop = 30;
- const int kSmallPaddingLeft = 30;
- const int kSmallPaddingRight = 30;
- const int kSmallPaddingBottom = 30;
-
- // Favicon / title area.
- const int kThumbTitlePaddingY = 6;
- const int kFaviconSize = 16;
- const int kTitleHeight = 14; // Font size.
- const int kTitleExtraHeight = kThumbTitlePaddingY + kTitleHeight;
- const int kFaviconExtraHeight = kThumbTitlePaddingY + kFaviconSize;
- const int kFaviconTitleDistanceX = 6;
- const int kFooterExtraHeight =
- std::max(kFaviconExtraHeight, kTitleExtraHeight);
-
- // Room between the tiles.
- const int kSmallPaddingX = 15;
- const int kSmallPaddingY = kFooterExtraHeight;
-
- // Aspect ratio of the containing rect.
- CGFloat aspect = NSWidth(containing_rect) / NSHeight(containing_rect);
-
- // Room left in container after the outer padding is removed.
- double container_width =
- NSWidth(containing_rect) - kSmallPaddingLeft - kSmallPaddingRight;
- double container_height =
- NSHeight(containing_rect) - kSmallPaddingTop - kSmallPaddingBottom;
-
- // The tricky part is figuring out the size of a tab thumbnail, or since the
- // size of the containing rect is known, the number of tiles in x and y
- // direction.
- // Given are the size of the containing rect, and the number of thumbnails
- // that need to fit into that rect. The aspect ratio of the thumbnails needs
- // to be the same as that of |containing_rect|, else they will look distorted.
- // The thumbnails need to be distributed such that
- // |count_x * count_y >= tile_count|, and such that wasted space is minimized.
- // See the comments in
- // |FitNRectsWithAspectIntoBoundingSizeWithConstantPadding()| for a more
- // detailed discussion.
- // TODO(thakis): It might be good enough to choose |count_x| and |count_y|
- // such that count_x / count_y is roughly equal to |aspect|?
- double fny = FitNRectsWithAspectIntoBoundingSizeWithConstantPadding(
- tile_count, aspect,
- container_width, container_height - kFooterExtraHeight,
- kSmallPaddingX, kSmallPaddingY + kFooterExtraHeight);
- count_y_ = roundf(fny);
-
- // Now that |count_x()| and |count_y_| are known, it's straightforward to
- // compute thumbnail width/height. See comment in
- // |FitNRectsWithAspectIntoBoundingSizeWithConstantPadding| for the derivation
- // of these two formulas.
- int small_width =
- floor((container_width + kSmallPaddingX) / static_cast<float>(count_x()) -
- kSmallPaddingX);
- int small_height =
- floor((container_height + kSmallPaddingY) / static_cast<float>(count_y_) -
- (kSmallPaddingY + kFooterExtraHeight));
-
- // |small_width / small_height| has only roughly an aspect ratio of |aspect|.
- // Shrink the thumbnail rect to make the aspect ratio fit exactly, and add
- // the extra space won by shrinking to the outer padding.
- int smallExtraPaddingLeft = 0;
- int smallExtraPaddingTop = 0;
- if (aspect > small_width/static_cast<float>(small_height)) {
- small_height = small_width / aspect;
- CGFloat all_tiles_height =
- (small_height + kSmallPaddingY + kFooterExtraHeight) * count_y() -
- (kSmallPaddingY + kFooterExtraHeight);
- smallExtraPaddingTop = (container_height - all_tiles_height)/2;
- } else {
- small_width = small_height * aspect;
- CGFloat all_tiles_width =
- (small_width + kSmallPaddingX) * count_x() - kSmallPaddingX;
- smallExtraPaddingLeft = (container_width - all_tiles_width)/2;
- }
-
- // Compute inter-tile padding in the zoomed-out view.
- CGFloat scale_small_to_big =
- NSWidth(containing_rect) / static_cast<float>(small_width);
- CGFloat big_padding_x = kSmallPaddingX * scale_small_to_big;
- CGFloat big_padding_y =
- (kSmallPaddingY + kFooterExtraHeight) * scale_small_to_big;
-
- // Now all dimensions are known. Lay out all tiles on a regular grid:
- // X X X X
- // X X X X
- // X X
- for (int row = 0, i = 0; i < tile_count; ++row) {
- for (int col = 0; col < count_x() && i < tile_count; ++col, ++i) {
- // Compute the smalled, zoomed-out thumbnail rect.
- tiles_[i]->thumb_rect_.size = NSMakeSize(small_width, small_height);
-
- int small_x = col * (small_width + kSmallPaddingX) +
- kSmallPaddingLeft + smallExtraPaddingLeft;
- int small_y = row * (small_height + kSmallPaddingY + kFooterExtraHeight) +
- kSmallPaddingTop + smallExtraPaddingTop;
-
- tiles_[i]->thumb_rect_.origin = NSMakePoint(
- small_x, NSHeight(containing_rect) - small_y - small_height);
-
- tiles_[i]->favicon_rect_.size = NSMakeSize(kFaviconSize, kFaviconSize);
- tiles_[i]->favicon_rect_.origin = NSMakePoint(
- small_x,
- NSHeight(containing_rect) -
- (small_y + small_height + kFaviconExtraHeight));
-
- // Align lower left corner of title rect with lower left corner of favicon
- // for now. The final position is computed later by
- // |Tile::set_font_metrics()|.
- tiles_[i]->title_font_size_ = kTitleHeight;
- tiles_[i]->title_rect_.origin = NSMakePoint(
- NSMaxX(tiles_[i]->favicon_rect()) + kFaviconTitleDistanceX,
- NSMinY(tiles_[i]->favicon_rect()));
- tiles_[i]->title_rect_.size = NSMakeSize(
- small_width -
- NSWidth(tiles_[i]->favicon_rect()) - kFaviconTitleDistanceX,
- kTitleHeight);
-
- // Compute the big, pre-zoom thumbnail rect.
- tiles_[i]->start_thumb_rect_.size = containing_rect.size;
-
- int big_x = col * (NSWidth(containing_rect) + big_padding_x);
- int big_y = row * (NSHeight(containing_rect) + big_padding_y);
- tiles_[i]->start_thumb_rect_.origin = NSMakePoint(big_x, -big_y);
- }
- }
-
- // Go through last row and center it:
- // X X X X
- // X X X X
- // X X
- int last_row_empty_tiles_x = count_x() - last_row_count_x();
- CGFloat small_last_row_shift_x =
- last_row_empty_tiles_x * (small_width + kSmallPaddingX) / 2;
- CGFloat big_last_row_shift_x =
- last_row_empty_tiles_x * (NSWidth(containing_rect) + big_padding_x) / 2;
- for (int i = tile_count - last_row_count_x(); i < tile_count; ++i) {
- tiles_[i]->thumb_rect_.origin.x += small_last_row_shift_x;
- tiles_[i]->start_thumb_rect_.origin.x += big_last_row_shift_x;
- tiles_[i]->favicon_rect_.origin.x += small_last_row_shift_x;
- tiles_[i]->title_rect_.origin.x += small_last_row_shift_x;
- }
-}
-
-void TileSet::set_selected_index(int index) {
- CHECK_GE(index, 0);
- CHECK_LT(index, static_cast<int>(tiles_.size()));
- selected_index_ = index;
-}
-
-// Given a |value| in [0, from_scale), map it into [0, to_scale) such that:
-// * [0, from_scale) ends up in the middle of [0, to_scale) if the latter is
-// a bigger range
-// * The middle of [0, from_scale) is mapped to [0, to_scale), and the parts
-// of the former that don't fit are mapped to 0 and to_scale - respectively
-// if the former is a bigger range.
-static int rescale(int value, int from_scale, int to_scale) {
- int left = (to_scale - from_scale) / 2;
- int result = value + left;
- if (result < 0)
- return 0;
- if (result >= to_scale)
- return to_scale - 1;
- return result;
-}
-
-int TileSet::up_index() const {
- int tile_x, tile_y;
- index_to_tile_xy(selected_index(), &tile_x, &tile_y);
- tile_y -= 1;
- if (tile_y == count_y() - 2) {
- // Transition from last row to second-to-last row.
- tile_x = rescale(tile_x, last_row_count_x(), count_x());
- } else if (tile_y < 0) {
- // Transition from first row to last row.
- tile_x = rescale(tile_x, count_x(), last_row_count_x());
- tile_y = count_y() - 1;
- }
- return tile_xy_to_index(tile_x, tile_y);
-}
-
-int TileSet::down_index() const {
- int tile_x, tile_y;
- index_to_tile_xy(selected_index(), &tile_x, &tile_y);
- tile_y += 1;
- if (tile_y == count_y() - 1) {
- // Transition from second-to-last row to last row.
- tile_x = rescale(tile_x, count_x(), last_row_count_x());
- } else if (tile_y >= count_y()) {
- // Transition from last row to first row.
- tile_x = rescale(tile_x, last_row_count_x(), count_x());
- tile_y = 0;
- }
- return tile_xy_to_index(tile_x, tile_y);
-}
-
-int TileSet::left_index() const {
- int tile_x, tile_y;
- index_to_tile_xy(selected_index(), &tile_x, &tile_y);
- tile_x -= 1;
- if (tile_x < 0)
- tile_x = tiles_in_row(tile_y) - 1;
- return tile_xy_to_index(tile_x, tile_y);
-}
-
-int TileSet::right_index() const {
- int tile_x, tile_y;
- index_to_tile_xy(selected_index(), &tile_x, &tile_y);
- tile_x += 1;
- if (tile_x >= tiles_in_row(tile_y))
- tile_x = 0;
- return tile_xy_to_index(tile_x, tile_y);
-}
-
-int TileSet::next_index() const {
- int new_index = selected_index() + 1;
- if (new_index >= static_cast<int>(tiles_.size()))
- new_index = 0;
- return new_index;
-}
-
-int TileSet::previous_index() const {
- int new_index = selected_index() - 1;
- if (new_index < 0)
- new_index = tiles_.size() - 1;
- return new_index;
-}
-
-void TileSet::InsertTileAt(int index, TabContents* contents) {
- tiles_.insert(tiles_.begin() + index, new Tile);
- tiles_[index]->contents_ = contents;
-}
-
-void TileSet::RemoveTileAt(int index) {
- tiles_.erase(tiles_.begin() + index);
-}
-
-// Moves the Tile object at |from_index| to |to_index|. Also updates rectangles
-// so that the tiles stay in a left-to-right, top-to-bottom layout when walked
-// in sequential order.
-void TileSet::MoveTileFromTo(int from_index, int to_index) {
- NSRect thumb = tiles_[from_index]->thumb_rect_;
- NSRect start_thumb = tiles_[from_index]->start_thumb_rect_;
- NSRect favicon = tiles_[from_index]->favicon_rect_;
- NSRect title = tiles_[from_index]->title_rect_;
-
- scoped_ptr<Tile> tile(tiles_[from_index]);
- tiles_.weak_erase(tiles_.begin() + from_index);
- tiles_.insert(tiles_.begin() + to_index, tile.release());
-
- int step = from_index < to_index ? -1 : 1;
- for (int i = to_index; (i - from_index) * step < 0; i += step) {
- tiles_[i]->thumb_rect_ = tiles_[i + step]->thumb_rect_;
- tiles_[i]->start_thumb_rect_ = tiles_[i + step]->start_thumb_rect_;
- tiles_[i]->favicon_rect_ = tiles_[i + step]->favicon_rect_;
- tiles_[i]->title_rect_ = tiles_[i + step]->title_rect_;
- }
- tiles_[from_index]->thumb_rect_ = thumb;
- tiles_[from_index]->start_thumb_rect_ = start_thumb;
- tiles_[from_index]->favicon_rect_ = favicon;
- tiles_[from_index]->title_rect_ = title;
-}
-
-} // namespace tabpose
-
-void AnimateCALayerFrameFromTo(
- CALayer* layer, const NSRect& from, const NSRect& to,
- NSTimeInterval duration, id boundsAnimationDelegate) {
- // http://developer.apple.com/mac/library/qa/qa2008/qa1620.html
- CABasicAnimation* animation;
-
- animation = [CABasicAnimation animationWithKeyPath:@"bounds"];
- animation.fromValue = [NSValue valueWithRect:from];
- animation.toValue = [NSValue valueWithRect:to];
- animation.duration = duration;
- animation.timingFunction =
- [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
- animation.delegate = boundsAnimationDelegate;
-
- // Update the layer's bounds so the layer doesn't snap back when the animation
- // completes.
- layer.bounds = NSRectToCGRect(to);
-
- // Add the animation, overriding the implicit animation.
- [layer addAnimation:animation forKey:@"bounds"];
-
- // Prepare the animation from the current position to the new position.
- NSPoint opoint = from.origin;
- NSPoint point = to.origin;
-
- // Adapt to anchorPoint.
- opoint.x += NSWidth(from) * layer.anchorPoint.x;
- opoint.y += NSHeight(from) * layer.anchorPoint.y;
- point.x += NSWidth(to) * layer.anchorPoint.x;
- point.y += NSHeight(to) * layer.anchorPoint.y;
-
- animation = [CABasicAnimation animationWithKeyPath:@"position"];
- animation.fromValue = [NSValue valueWithPoint:opoint];
- animation.toValue = [NSValue valueWithPoint:point];
- animation.duration = duration;
- animation.timingFunction =
- [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
-
- // Update the layer's position so that the layer doesn't snap back when the
- // animation completes.
- layer.position = NSPointToCGPoint(point);
-
- // Add the animation, overriding the implicit animation.
- [layer addAnimation:animation forKey:@"position"];
-}
-
-@interface TabposeWindow (Private)
-- (id)initForWindow:(NSWindow*)parent
- rect:(NSRect)rect
- slomo:(BOOL)slomo
- tabStripModel:(TabStripModel*)tabStripModel;
-- (void)setUpLayersInSlomo:(BOOL)slomo;
-- (void)fadeAway:(BOOL)slomo;
-- (void)selectTileAtIndex:(int)newIndex;
-@end
-
-@implementation TabposeWindow
-
-+ (id)openTabposeFor:(NSWindow*)parent
- rect:(NSRect)rect
- slomo:(BOOL)slomo
- tabStripModel:(TabStripModel*)tabStripModel {
- // Releases itself when closed.
- return [[TabposeWindow alloc]
- initForWindow:parent rect:rect slomo:slomo tabStripModel:tabStripModel];
-}
-
-- (id)initForWindow:(NSWindow*)parent
- rect:(NSRect)rect
- slomo:(BOOL)slomo
- tabStripModel:(TabStripModel*)tabStripModel {
- NSRect frame = [parent frame];
- if ((self = [super initWithContentRect:frame
- styleMask:NSBorderlessWindowMask
- backing:NSBackingStoreBuffered
- defer:NO])) {
- containingRect_ = rect;
- tabStripModel_ = tabStripModel;
- state_ = tabpose::kFadingIn;
- tileSet_.reset(new tabpose::TileSet);
- tabStripModelObserverBridge_.reset(
- new TabStripModelObserverBridge(tabStripModel_, self));
- [self setReleasedWhenClosed:YES];
- [self setOpaque:NO];
- [self setBackgroundColor:[NSColor clearColor]];
- [self setUpLayersInSlomo:slomo];
- [self setAcceptsMouseMovedEvents:YES];
- [parent addChildWindow:self ordered:NSWindowAbove];
- [self makeKeyAndOrderFront:self];
- }
- return self;
-}
-
-- (CALayer*)selectedLayer {
- return [allThumbnailLayers_ objectAtIndex:tileSet_->selected_index()];
-}
-
-- (void)selectTileAtIndex:(int)newIndex {
- const tabpose::Tile& tile = tileSet_->tile_at(newIndex);
- selectionHighlight_.frame =
- NSRectToCGRect(NSInsetRect(tile.thumb_rect(), -5, -5));
- tileSet_->set_selected_index(newIndex);
-}
-
-- (void)selectTileAtIndexWithoutAnimation:(int)newIndex {
- ScopedCAActionDisabler disabler;
- [self selectTileAtIndex:newIndex];
-}
-
-- (void)addLayersForTile:(tabpose::Tile&)tile
- showZoom:(BOOL)showZoom
- slomo:(BOOL)slomo
- animationDelegate:(id)animationDelegate {
- scoped_nsobject<CALayer> layer([[ThumbnailLayer alloc]
- initWithTabContents:tile.tab_contents()
- fullSize:tile.GetStartRectRelativeTo(
- tileSet_->selected_tile()).size]);
- [layer setNeedsDisplay];
-
- // Background color as placeholder for now.
- layer.get().backgroundColor = CGColorGetConstantColor(kCGColorWhite);
- if (showZoom) {
- AnimateCALayerFrameFromTo(
- layer,
- tile.GetStartRectRelativeTo(tileSet_->selected_tile()),
- tile.thumb_rect(),
- kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1),
- animationDelegate);
- } else {
- layer.get().frame = NSRectToCGRect(tile.thumb_rect());
- }
-
- layer.get().shadowRadius = 10;
- layer.get().shadowOffset = CGSizeMake(0, -10);
- if (state_ == tabpose::kFadedIn)
- layer.get().shadowOpacity = 0.5;
-
- [bgLayer_ addSublayer:layer];
- [allThumbnailLayers_ addObject:layer];
-
- // Favicon and title.
- NSFont* font = [NSFont systemFontOfSize:tile.title_font_size()];
- tile.set_font_metrics([font ascender], -[font descender]);
-
- NSImage* nsFavicon = gfx::SkBitmapToNSImage(tile.favicon());
- // Either we don't have a valid favicon or there was some issue converting
- // it from an SkBitmap. Either way, just show the default.
- if (!nsFavicon) {
- NSImage* defaultFavIcon =
- ResourceBundle::GetSharedInstance().GetNativeImageNamed(
- IDR_DEFAULT_FAVICON);
- nsFavicon = defaultFavIcon;
- }
- base::mac::ScopedCFTypeRef<CGImageRef> favicon(
- mac_util::CopyNSImageToCGImage(nsFavicon));
-
- CALayer* faviconLayer = [CALayer layer];
- faviconLayer.frame = NSRectToCGRect(tile.favicon_rect());
- faviconLayer.contents = (id)favicon.get();
- faviconLayer.zPosition = 1; // On top of the thumb shadow.
- if (state_ == tabpose::kFadingIn)
- faviconLayer.hidden = YES;
- [bgLayer_ addSublayer:faviconLayer];
- [allFaviconLayers_ addObject:faviconLayer];
-
- CATextLayer* titleLayer = [CATextLayer layer];
- titleLayer.frame = NSRectToCGRect(tile.title_rect());
- titleLayer.string = base::SysUTF16ToNSString(tile.title());
- titleLayer.fontSize = [font pointSize];
- titleLayer.truncationMode = kCATruncationEnd;
- titleLayer.font = font;
- titleLayer.zPosition = 1; // On top of the thumb shadow.
- if (state_ == tabpose::kFadingIn)
- titleLayer.hidden = YES;
- [bgLayer_ addSublayer:titleLayer];
- [allTitleLayers_ addObject:titleLayer];
-}
-
-- (void)setUpLayersInSlomo:(BOOL)slomo {
- // Root layer -- covers whole window.
- rootLayer_ = [CALayer layer];
-
- // In a block so that the layers don't fade in.
- {
- ScopedCAActionDisabler disabler;
- // Background layer -- the visible part of the window.
- gray_.reset(CGColorCreateGenericGray(0.39, 1.0));
- bgLayer_ = [CALayer layer];
- bgLayer_.backgroundColor = gray_;
- bgLayer_.frame = NSRectToCGRect(containingRect_);
- bgLayer_.masksToBounds = YES;
- [rootLayer_ addSublayer:bgLayer_];
-
- // Selection highlight layer.
- darkBlue_.reset(CGColorCreateGenericRGB(0.25, 0.34, 0.86, 1.0));
- selectionHighlight_ = [CALayer layer];
- selectionHighlight_.backgroundColor = darkBlue_;
- selectionHighlight_.cornerRadius = 5.0;
- selectionHighlight_.zPosition = -1; // Behind other layers.
- selectionHighlight_.hidden = YES;
- [bgLayer_ addSublayer:selectionHighlight_];
-
- // Top gradient.
- CALayer* gradientLayer = [DarkGradientLayer layer];
- gradientLayer.frame = CGRectMake(
- 0,
- NSHeight(containingRect_) - kTopGradientHeight,
- NSWidth(containingRect_),
- kTopGradientHeight);
- [gradientLayer setNeedsDisplay]; // Draw once.
- [bgLayer_ addSublayer:gradientLayer];
- }
-
- // Layers for the tab thumbnails.
- tileSet_->Build(tabStripModel_);
- tileSet_->Layout(containingRect_);
- allThumbnailLayers_.reset(
- [[NSMutableArray alloc] initWithCapacity:tabStripModel_->count()]);
- allFaviconLayers_.reset(
- [[NSMutableArray alloc] initWithCapacity:tabStripModel_->count()]);
- allTitleLayers_.reset(
- [[NSMutableArray alloc] initWithCapacity:tabStripModel_->count()]);
-
- for (int i = 0; i < tabStripModel_->count(); ++i) {
- // Add a delegate to one of the animations to get a notification once the
- // animations are done.
- [self addLayersForTile:tileSet_->tile_at(i)
- showZoom:YES
- slomo:slomo
- animationDelegate:i == tileSet_->selected_index() ? self : nil];
- if (i == tileSet_->selected_index()) {
- CALayer* layer = [allThumbnailLayers_ objectAtIndex:i];
- CAAnimation* animation = [layer animationForKey:@"bounds"];
- DCHECK(animation);
- [animation setValue:kAnimationIdFadeIn forKey:kAnimationIdKey];
- }
- }
- [self selectTileAtIndexWithoutAnimation:tileSet_->selected_index()];
-
- // Needs to happen after all layers have been added to |rootLayer_|, else
- // there's a one frame flash of grey at the beginning of the animation
- // (|bgLayer_| showing through with none of its children visible yet).
- [[self contentView] setLayer:rootLayer_];
- [[self contentView] setWantsLayer:YES];
-}
-
-- (BOOL)canBecomeKeyWindow {
- return YES;
-}
-
-// Handle key events that should be executed repeatedly while the key is down.
-- (void)keyDown:(NSEvent*)event {
- if (state_ == tabpose::kFadingOut)
- return;
- NSString* characters = [event characters];
- if ([characters length] < 1)
- return;
-
- unichar character = [characters characterAtIndex:0];
- int newIndex = -1;
- switch (character) {
- case NSUpArrowFunctionKey:
- newIndex = tileSet_->up_index();
- break;
- case NSDownArrowFunctionKey:
- newIndex = tileSet_->down_index();
- break;
- case NSLeftArrowFunctionKey:
- newIndex = tileSet_->left_index();
- break;
- case NSRightArrowFunctionKey:
- newIndex = tileSet_->right_index();
- break;
- case NSTabCharacter:
- newIndex = tileSet_->next_index();
- break;
- case NSBackTabCharacter:
- newIndex = tileSet_->previous_index();
- break;
- }
- if (newIndex != -1)
- [self selectTileAtIndexWithoutAnimation:newIndex];
-}
-
-// Handle keyboard events that should be executed once when the key is released.
-- (void)keyUp:(NSEvent*)event {
- if (state_ == tabpose::kFadingOut)
- return;
- NSString* characters = [event characters];
- if ([characters length] < 1)
- return;
-
- unichar character = [characters characterAtIndex:0];
- switch (character) {
- case NSEnterCharacter:
- case NSNewlineCharacter:
- case NSCarriageReturnCharacter:
- case ' ':
- [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0];
- break;
- case '\e': // Escape
- tileSet_->set_selected_index(tabStripModel_->selected_index());
- [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0];
- break;
- }
-}
-
-// Handle keyboard events that contain cmd or ctrl.
-- (BOOL)performKeyEquivalent:(NSEvent*)event {
- if (state_ == tabpose::kFadingOut)
- return NO;
- NSString* characters = [event characters];
- if ([characters length] < 1)
- return NO;
- unichar character = [characters characterAtIndex:0];
- if ([event modifierFlags] & NSCommandKeyMask) {
- if (character >= '1' && character <= '9') {
- int index =
- character == '9' ? tabStripModel_->count() - 1 : character - '1';
- if (index < tabStripModel_->count()) {
- tileSet_->set_selected_index(index);
- [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0];
- return YES;
- }
- }
- }
- return NO;
-}
-
--(void)selectTileFromMouseEvent:(NSEvent*)event {
- int newIndex = -1;
- CGPoint p = NSPointToCGPoint([event locationInWindow]);
- for (NSUInteger i = 0; i < [allThumbnailLayers_ count]; ++i) {
- CALayer* layer = [allThumbnailLayers_ objectAtIndex:i];
- CGPoint lp = [layer convertPoint:p fromLayer:rootLayer_];
- if ([static_cast<CALayer*>([layer presentationLayer]) containsPoint:lp])
- newIndex = i;
- }
- if (newIndex >= 0)
- [self selectTileAtIndexWithoutAnimation:newIndex];
-}
-
-- (void)mouseMoved:(NSEvent*)event {
- [self selectTileFromMouseEvent:event];
-}
-
-- (void)mouseDown:(NSEvent*)event {
- // Just in case the user clicked without ever moving the mouse.
- [self selectTileFromMouseEvent:event];
-
- [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0];
-}
-
-- (void)swipeWithEvent:(NSEvent*)event {
- if (abs([event deltaY]) > 0.5) // Swipe up or down.
- [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0];
-}
-
-- (void)close {
- // Prevent parent window from disappearing.
- [[self parentWindow] removeChildWindow:self];
-
- // We're dealloc'd in an autorelease pool – by then the observer registry
- // might be dead, so explicitly reset the observer now.
- tabStripModelObserverBridge_.reset();
-
- [super close];
-}
-
-- (void)commandDispatch:(id)sender {
- if ([sender tag] == IDC_TABPOSE)
- [self fadeAway:NO];
-}
-
-- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
- // Disable all browser-related menu items except the tab overview toggle.
- SEL action = [item action];
- NSInteger tag = [item tag];
- return action == @selector(commandDispatch:) && tag == IDC_TABPOSE;
-}
-
-- (void)fadeAway:(BOOL)slomo {
- if (state_ == tabpose::kFadingOut)
- return;
-
- state_ = tabpose::kFadingOut;
- [self setAcceptsMouseMovedEvents:NO];
-
- // Select chosen tab.
- if (tileSet_->selected_index() < tabStripModel_->count()) {
- tabStripModel_->SelectTabContentsAt(tileSet_->selected_index(),
- /*user_gesture=*/true);
- } else {
- DCHECK_EQ(tileSet_->selected_index(), 0);
- }
-
- {
- ScopedCAActionDisabler disableCAActions;
-
- // Move the selected layer on top of all other layers.
- [self selectedLayer].zPosition = 1;
-
- selectionHighlight_.hidden = YES;
- for (CALayer* layer in allFaviconLayers_.get())
- layer.hidden = YES;
- for (CALayer* layer in allTitleLayers_.get())
- layer.hidden = YES;
-
- // Running animations with shadows is slow, so turn shadows off before
- // running the exit animation.
- for (CALayer* layer in allThumbnailLayers_.get())
- layer.shadowOpacity = 0.0;
- }
-
- // Animate layers out, all in one transaction.
- CGFloat duration =
- 1.3 * kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1);
- ScopedCAActionSetDuration durationSetter(duration);
- for (NSUInteger i = 0; i < [allThumbnailLayers_ count]; ++i) {
- CALayer* layer = [allThumbnailLayers_ objectAtIndex:i];
- // |start_thumb_rect_| was relative to the initial index, now this needs to
- // be relative to |selectedIndex_| (whose start rect was relative to
- // the initial index, too).
- CGRect newFrame = NSRectToCGRect(
- tileSet_->tile_at(i).GetStartRectRelativeTo(tileSet_->selected_tile()));
-
- // Add a delegate to one of the implicit animations to get a notification
- // once the animations are done.
- if (static_cast<int>(i) == tileSet_->selected_index()) {
- CAAnimation* animation = [CAAnimation animation];
- animation.delegate = self;
- [animation setValue:kAnimationIdFadeOut forKey:kAnimationIdKey];
- [layer addAnimation:animation forKey:@"frame"];
- }
-
- layer.frame = newFrame;
-
- if (static_cast<int>(i) == tileSet_->selected_index()) {
- // Redraw layer at big resolution, so that zoom-in isn't blocky.
- [layer setNeedsDisplay];
- }
- }
-}
-
-- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished {
- NSString* animationId = [animation valueForKey:kAnimationIdKey];
- if ([animationId isEqualToString:kAnimationIdFadeIn]) {
- if (finished && state_ == tabpose::kFadingIn) {
- // If the user clicks while the fade in animation is still running,
- // |state_| is already kFadingOut. In that case, don't do anything.
- state_ = tabpose::kFadedIn;
-
- selectionHighlight_.hidden = NO;
- for (CALayer* layer in allFaviconLayers_.get())
- layer.hidden = NO;
- for (CALayer* layer in allTitleLayers_.get())
- layer.hidden = NO;
-
- // Running animations with shadows is slow, so turn shadows on only after
- // the animation is done.
- ScopedCAActionDisabler disableCAActions;
- for (CALayer* layer in allThumbnailLayers_.get())
- layer.shadowOpacity = 0.5;
- }
- } else if ([animationId isEqualToString:kAnimationIdFadeOut]) {
- DCHECK_EQ(tabpose::kFadingOut, state_);
- [self close];
- }
-}
-
-- (NSUInteger)thumbnailLayerCount {
- return [allThumbnailLayers_ count];
-}
-
-- (int)selectedIndex {
- return tileSet_->selected_index();
-}
-
-#pragma mark TabStripModelBridge
-
-- (void)refreshLayerFramesAtIndex:(int)i {
- const tabpose::Tile& tile = tileSet_->tile_at(i);
-
- CALayer* faviconLayer = [allFaviconLayers_ objectAtIndex:i];
- faviconLayer.frame = NSRectToCGRect(tile.favicon_rect());
- CALayer* titleLayer = [allTitleLayers_ objectAtIndex:i];
- titleLayer.frame = NSRectToCGRect(tile.title_rect());
- CALayer* thumbLayer = [allThumbnailLayers_ objectAtIndex:i];
- thumbLayer.frame = NSRectToCGRect(tile.thumb_rect());
-}
-
-- (void)insertTabWithContents:(TabContentsWrapper*)contents
- atIndex:(NSInteger)index
- inForeground:(bool)inForeground {
- // This happens if you cmd-click a link and then immediately open tabpose
- // on a slowish machine.
- ScopedCAActionSetDuration durationSetter(kObserverChangeAnimationDuration);
-
- // Insert new layer and relayout.
- tileSet_->InsertTileAt(index, contents->tab_contents());
- tileSet_->Layout(containingRect_);
- [self addLayersForTile:tileSet_->tile_at(index)
- showZoom:NO
- slomo:NO
- animationDelegate:nil];
-
- // Update old layers.
- DCHECK_EQ(tabStripModel_->count(),
- static_cast<int>([allThumbnailLayers_ count]));
- DCHECK_EQ(tabStripModel_->count(),
- static_cast<int>([allTitleLayers_ count]));
- DCHECK_EQ(tabStripModel_->count(),
- static_cast<int>([allFaviconLayers_ count]));
-
- for (int i = 0; i < tabStripModel_->count(); ++i) {
- if (i == index) // The new layer.
- continue;
- [self refreshLayerFramesAtIndex:i];
- }
-
- // Update selection.
- int selectedIndex = tileSet_->selected_index();
- if (selectedIndex >= index)
- selectedIndex++;
- [self selectTileAtIndex:selectedIndex];
-}
-
-- (void)tabClosingWithContents:(TabContentsWrapper*)contents
- atIndex:(NSInteger)index {
- // We will also get a -tabDetachedWithContents:atIndex: notification for
- // closing tabs, so do nothing here.
-}
-
-- (void)tabDetachedWithContents:(TabContentsWrapper*)contents
- atIndex:(NSInteger)index {
- ScopedCAActionSetDuration durationSetter(kObserverChangeAnimationDuration);
-
- // Remove layer and relayout.
- tileSet_->RemoveTileAt(index);
- tileSet_->Layout(containingRect_);
-
- [[allThumbnailLayers_ objectAtIndex:index] removeFromSuperlayer];
- [allThumbnailLayers_ removeObjectAtIndex:index];
- [[allTitleLayers_ objectAtIndex:index] removeFromSuperlayer];
- [allTitleLayers_ removeObjectAtIndex:index];
- [[allFaviconLayers_ objectAtIndex:index] removeFromSuperlayer];
- [allFaviconLayers_ removeObjectAtIndex:index];
-
- // Update old layers.
- DCHECK_EQ(tabStripModel_->count(),
- static_cast<int>([allThumbnailLayers_ count]));
- DCHECK_EQ(tabStripModel_->count(),
- static_cast<int>([allTitleLayers_ count]));
- DCHECK_EQ(tabStripModel_->count(),
- static_cast<int>([allFaviconLayers_ count]));
-
- if (tabStripModel_->count() == 0)
- [self close];
-
- for (int i = 0; i < tabStripModel_->count(); ++i)
- [self refreshLayerFramesAtIndex:i];
-
- // Update selection.
- int selectedIndex = tileSet_->selected_index();
- if (selectedIndex >= index)
- selectedIndex--;
- if (selectedIndex >= 0)
- [self selectTileAtIndex:selectedIndex];
-}
-
-- (void)tabMovedWithContents:(TabContentsWrapper*)contents
- fromIndex:(NSInteger)from
- toIndex:(NSInteger)to {
- ScopedCAActionSetDuration durationSetter(kObserverChangeAnimationDuration);
-
- // Move tile from |from| to |to|.
- tileSet_->MoveTileFromTo(from, to);
-
- // Move corresponding layers from |from| to |to|.
- scoped_nsobject<CALayer> thumbLayer(
- [[allThumbnailLayers_ objectAtIndex:from] retain]);
- [allThumbnailLayers_ removeObjectAtIndex:from];
- [allThumbnailLayers_ insertObject:thumbLayer.get() atIndex:to];
- scoped_nsobject<CALayer> faviconLayer(
- [[allFaviconLayers_ objectAtIndex:from] retain]);
- [allFaviconLayers_ removeObjectAtIndex:from];
- [allFaviconLayers_ insertObject:faviconLayer.get() atIndex:to];
- scoped_nsobject<CALayer> titleLayer(
- [[allTitleLayers_ objectAtIndex:from] retain]);
- [allTitleLayers_ removeObjectAtIndex:from];
- [allTitleLayers_ insertObject:titleLayer.get() atIndex:to];
-
- // Update frames of the layers.
- for (int i = std::min(from, to); i <= std::max(from, to); ++i)
- [self refreshLayerFramesAtIndex:i];
-
- // Update selection.
- int selectedIndex = tileSet_->selected_index();
- if (from == selectedIndex)
- selectedIndex = to;
- else if (from < selectedIndex && selectedIndex <= to)
- selectedIndex--;
- else if (to <= selectedIndex && selectedIndex < from)
- selectedIndex++;
- [self selectTileAtIndex:selectedIndex];
-}
-
-- (void)tabChangedWithContents:(TabContentsWrapper*)contents
- atIndex:(NSInteger)index
- changeType:(TabStripModelObserver::TabChangeType)change {
- // Tell the window to update text, title, and thumb layers at |index| to get
- // their data from |contents|. |contents| can be different from the old
- // contents at that index!
- // While a tab is loading, this is unfortunately called quite often for
- // both the "loading" and the "all" change types, so we don't really want to
- // send thumb requests to the corresponding renderer when this is called.
- // For now, just make sure that we don't hold on to an invalid TabContents
- // object.
- tabpose::Tile& tile = tileSet_->tile_at(index);
- if (contents->tab_contents() == tile.tab_contents()) {
- // TODO(thakis): Install a timer to send a thumb request/update title/update
- // favicon after 20ms or so, and reset the timer every time this is called
- // to make sure we get an updated thumb, without requesting them all over.
- return;
- }
-
- tile.set_tab_contents(contents->tab_contents());
- ThumbnailLayer* thumbLayer = [allThumbnailLayers_ objectAtIndex:index];
- [thumbLayer setTabContents:contents->tab_contents()];
-}
-
-- (void)tabStripModelDeleted {
- [self close];
-}
-
-@end