| Index: chrome/browser/ui/cocoa/tabpose_window.mm
|
| diff --git a/chrome/browser/ui/cocoa/tabpose_window.mm b/chrome/browser/ui/cocoa/tabpose_window.mm
|
| deleted file mode 100644
|
| index f4cc4fc25ae857e0054a90dcc4494e301a1ff6ce..0000000000000000000000000000000000000000
|
| --- a/chrome/browser/ui/cocoa/tabpose_window.mm
|
| +++ /dev/null
|
| @@ -1,1664 +0,0 @@
|
| -// Copyright (c) 2012 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/ui/cocoa/tabpose_window.h"
|
| -
|
| -#import <QuartzCore/QuartzCore.h>
|
| -
|
| -#include <algorithm>
|
| -
|
| -#include "base/mac/mac_util.h"
|
| -#include "base/mac/scoped_cftyperef.h"
|
| -#include "base/memory/weak_ptr.h"
|
| -#include "base/prefs/pref_service.h"
|
| -#include "base/strings/sys_string_conversions.h"
|
| -#include "chrome/app/chrome_command_ids.h"
|
| -#include "chrome/browser/browser_process.h"
|
| -#include "chrome/browser/devtools/devtools_window.h"
|
| -#include "chrome/browser/extensions/tab_helper.h"
|
| -#include "chrome/browser/profiles/profile.h"
|
| -#include "chrome/browser/thumbnails/render_widget_snapshot_taker.h"
|
| -#include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
|
| -#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_constants.h"
|
| -#import "chrome/browser/ui/cocoa/browser_window_controller.h"
|
| -#import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
|
| -#import "chrome/browser/ui/cocoa/tab_contents/favicon_util_mac.h"
|
| -#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
|
| -#import "chrome/browser/ui/cocoa/tabs/tab_strip_model_observer_bridge.h"
|
| -#include "chrome/common/pref_names.h"
|
| -#include "content/public/browser/browser_thread.h"
|
| -#include "content/public/browser/render_view_host.h"
|
| -#include "content/public/browser/render_widget_host_view.h"
|
| -#include "content/public/browser/web_contents.h"
|
| -#include "content/public/browser/web_contents_view.h"
|
| -#include "grit/theme_resources.h"
|
| -#include "grit/ui_resources.h"
|
| -#include "skia/ext/skia_utils_mac.h"
|
| -#include "third_party/skia/include/utils/mac/SkCGUtils.h"
|
| -#include "ui/base/cocoa/animation_utils.h"
|
| -#include "ui/base/resource/resource_bundle.h"
|
| -#include "ui/gfx/image/image.h"
|
| -#include "ui/gfx/scoped_cg_context_save_gstate_mac.h"
|
| -
|
| -using content::BrowserThread;
|
| -using content::RenderWidgetHost;
|
| -
|
| -// Height of the bottom gradient, in pixels.
|
| -const CGFloat kBottomGradientHeight = 50;
|
| -
|
| -// The shade of gray at the top of the window. There's a gradient from
|
| -// this to |kCentralGray| at the top of the window.
|
| -const CGFloat kTopGray = 0.77;
|
| -
|
| -// The shade of gray at the center of the window. Most of the window background
|
| -// has this color.
|
| -const CGFloat kCentralGray = 0.6;
|
| -
|
| -// The shade of gray at the bottom of the window. There's a gradient from
|
| -// |kCentralGray| to this at the bottom of the window, |kBottomGradientHeight|
|
| -// high.
|
| -const CGFloat kBottomGray = 0.5;
|
| -
|
| -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.25; // In seconds.
|
| -const CGFloat kSelectionInset = 5;
|
| -
|
| -// CAGradientLayer is 10.6-only -- roll our own.
|
| -@interface GrayGradientLayer : CALayer {
|
| - @private
|
| - CGFloat startGray_;
|
| - CGFloat endGray_;
|
| -}
|
| -- (id)initWithStartGray:(CGFloat)startGray endGray:(CGFloat)endGray;
|
| -- (void)drawInContext:(CGContextRef)context;
|
| -@end
|
| -
|
| -@implementation GrayGradientLayer
|
| -- (id)initWithStartGray:(CGFloat)startGray endGray:(CGFloat)endGray {
|
| - if ((self = [super init])) {
|
| - startGray_ = startGray;
|
| - endGray_ = endGray;
|
| - }
|
| - return self;
|
| -}
|
| -
|
| -- (void)drawInContext:(CGContextRef)context {
|
| - base::ScopedCFTypeRef<CGColorSpaceRef> grayColorSpace(
|
| - CGColorSpaceCreateWithName(kCGColorSpaceGenericGray));
|
| - CGFloat grays[] = { startGray_, 1.0, endGray_, 1.0 };
|
| - CGFloat locations[] = { 0, 1 };
|
| - base::ScopedCFTypeRef<CGGradientRef> gradient(
|
| - CGGradientCreateWithColorComponents(
|
| - grayColorSpace.get(), grays, locations, arraysize(locations)));
|
| - CGPoint topLeft = CGPointMake(0.0, self.bounds.size.height);
|
| - CGContextDrawLinearGradient(context, gradient.get(), topLeft, CGPointZero, 0);
|
| -}
|
| -@end
|
| -
|
| -namespace tabpose {
|
| -class ThumbnailLoader;
|
| -}
|
| -
|
| -// A CALayer that draws a thumbnail for a WebContents object. The layer
|
| -// tries to draw the WebContents's backing store directly if possible, and
|
| -// requests a thumbnail bitmap from the WebContents's renderer process if not.
|
| -@interface ThumbnailLayer : CALayer {
|
| - // The WebContents the thumbnail is for.
|
| - content::WebContents* 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::ScopedCFTypeRef<CGImageRef> thumbnail_;
|
| -
|
| - // True if the layer already sent a thumbnail request to a renderer.
|
| - BOOL didSendLoad_;
|
| -}
|
| -- (id)initWithWebContents:(content::WebContents*)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), weak_factory_(this) {}
|
| -
|
| - // Starts the fetch.
|
| - void LoadThumbnail();
|
| -
|
| - private:
|
| - friend class base::RefCountedThreadSafe<ThumbnailLoader>;
|
| - ~ThumbnailLoader() {
|
| - }
|
| -
|
| - void DidReceiveBitmap(const SkBitmap& bitmap) {
|
| - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| - [layer_ setThumbnail:bitmap];
|
| - }
|
| -
|
| - gfx::Size size_;
|
| - RenderWidgetHost* rwh_; // weak
|
| - ThumbnailLayer* layer_; // weak, owns us
|
| - base::WeakPtrFactory<ThumbnailLoader> weak_factory_;
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(ThumbnailLoader);
|
| -};
|
| -
|
| -void ThumbnailLoader::LoadThumbnail() {
|
| - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| -
|
| - // 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.
|
| -
|
| - // Will send an IPC to the renderer on the IO thread.
|
| - g_browser_process->GetRenderWidgetSnapshotTaker()->AskForSnapshot(
|
| - rwh_,
|
| - base::Bind(&ThumbnailLoader::DidReceiveBitmap,
|
| - weak_factory_.GetWeakPtr()),
|
| - page_size,
|
| - pixel_size);
|
| -}
|
| -
|
| -} // namespace tabpose
|
| -
|
| -@implementation ThumbnailLayer
|
| -
|
| -- (id)initWithWebContents:(content::WebContents*)contents
|
| - fullSize:(NSSize)fullSize {
|
| - CHECK(contents);
|
| - if ((self = [super init])) {
|
| - contents_ = contents;
|
| - fullSize_ = fullSize;
|
| - }
|
| - return self;
|
| -}
|
| -
|
| -- (void)setWebContents:(content::WebContents*)contents {
|
| - contents_ = contents;
|
| -}
|
| -
|
| -- (void)setThumbnail:(const SkBitmap&)bitmap {
|
| - // SkCreateCGImageRef() holds on to |bitmaps|'s memory, so this doesn't
|
| - // create a copy. The renderer always draws data in the system colorspace.
|
| - thumbnail_.reset(SkCreateCGImageRefWithColorspace(
|
| - bitmap, base::mac::GetSystemColorSpace()));
|
| - 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.
|
| - NSWindow* window = [contents_->GetView()->GetNativeView() window];
|
| - NSWindowController* windowController = [window windowController];
|
| - if ([windowController isKindOfClass:[BrowserWindowController class]]) {
|
| - BrowserWindowController* bwc =
|
| - static_cast<BrowserWindowController*>(windowController);
|
| - InfoBarContainerController* infoBarContainer =
|
| - [bwc infoBarContainerController];
|
| - // TODO(thakis|rsesek): This is not correct for background tabs with
|
| - // infobars as the aspect ratio will be wrong. Fix that.
|
| - topOffset += NSHeight([[infoBarContainer view] frame]) -
|
| - [infoBarContainer overlappingTipHeight];
|
| - }
|
| -
|
| - BookmarkTabHelper* bookmark_tab_helper =
|
| - BookmarkTabHelper::FromWebContents(contents_);
|
| - Profile* profile =
|
| - Profile::FromBrowserContext(contents_->GetBrowserContext());
|
| - bool always_show_bookmark_bar =
|
| - profile->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar);
|
| - bool has_detached_bookmark_bar =
|
| - bookmark_tab_helper->ShouldShowBookmarkBar() &&
|
| - !always_show_bookmark_bar;
|
| - if (has_detached_bookmark_bar)
|
| - topOffset += chrome::kNTPBookmarkBarHeight;
|
| -
|
| - return topOffset;
|
| -}
|
| -
|
| -- (int)bottomOffset {
|
| - int bottomOffset = 0;
|
| - DevToolsWindow* devToolsWindow =
|
| - DevToolsWindow::GetDockedInstanceForInspectedTab(contents_);
|
| - content::WebContents* devToolsContents =
|
| - devToolsWindow ? devToolsWindow->web_contents() : NULL;
|
| - if (devToolsContents && devToolsContents->GetRenderViewHost() &&
|
| - devToolsContents->GetRenderViewHost()->GetView()) {
|
| - // 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->GetRenderViewHost()->GetView()->
|
| - GetViewBounds().height();
|
| - bottomOffset += 1; // :-( Divider line between web contents and devtools.
|
| - }
|
| - return bottomOffset;
|
| -}
|
| -
|
| -- (void)drawInContext:(CGContextRef)context {
|
| - RenderWidgetHost* rwh = contents_->GetRenderViewHost();
|
| - // NULL if renderer crashed.
|
| - content::RenderWidgetHostView* rwhv = rwh ? rwh->GetView() : NULL;
|
| - if (!rwhv) {
|
| - // TODO(thakis): Maybe draw a sad tab layer?
|
| - [super drawInContext:context];
|
| - return;
|
| - }
|
| -
|
| - // The size of the WebContents's RenderWidgetHost might not fit to the
|
| - // current browser window at all, for example if the window was resized while
|
| - // this WebContents 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.
|
| - bool draw_backing_store = rwh->GetBackingStoreSize() == 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!
|
| - // 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.
|
| - rwh->CopyFromBackingStoreToCGContext(destRect, context);
|
| - } else if (thumbnail_) {
|
| - // No cache hit, but the renderer returned a thumbnail to us.
|
| - gfx::ScopedCGContextSaveGState save_gstate(context);
|
| - CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
|
| - CGContextDrawImage(context, destRect, thumbnail_.get());
|
| - }
|
| -}
|
| -
|
| -@end
|
| -
|
| -// 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 {
|
| -
|
| -CGFloat ScaleWithOrigin(CGFloat x, CGFloat origin, CGFloat scale) {
|
| - return (x - origin) * scale + origin;
|
| -}
|
| -
|
| -NSRect ScaleRectWithOrigin(NSRect r, NSPoint p, CGFloat scale) {
|
| - return NSMakeRect(ScaleWithOrigin(NSMinX(r), p.x, scale),
|
| - ScaleWithOrigin(NSMinY(r), p.y, scale),
|
| - NSWidth(r) * scale,
|
| - NSHeight(r) * scale);
|
| -}
|
| -
|
| -// 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 GetFaviconStartRectRelativeTo(const Tile& tile) const;
|
| - NSRect favicon_rect() const { return NSIntegralRect(favicon_rect_); }
|
| - NSImage* 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 GetTitleStartRectRelativeTo(const Tile& tile) const;
|
| - NSRect title_rect() const { return NSIntegralRect(title_rect_); }
|
| -
|
| - // Returns an unelided title. The view logic is responsible for eliding.
|
| - const base::string16& title() const {
|
| - return contents_->GetTitle();
|
| - }
|
| -
|
| - content::WebContents* web_contents() const { return contents_; }
|
| - void set_tab_contents(content::WebContents* 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_;
|
| -
|
| - content::WebContents* 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;
|
| -}
|
| -
|
| -NSRect Tile::GetFaviconStartRectRelativeTo(const Tile& tile) const {
|
| - NSRect thumb_start = GetStartRectRelativeTo(tile);
|
| - CGFloat scale_to_start = NSWidth(thumb_start) / NSWidth(thumb_rect_);
|
| - NSRect rect =
|
| - ScaleRectWithOrigin(favicon_rect_, thumb_rect_.origin, scale_to_start);
|
| - rect.origin.x += NSMinX(thumb_start) - NSMinX(thumb_rect_);
|
| - rect.origin.y += NSMinY(thumb_start) - NSMinY(thumb_rect_);
|
| - return rect;
|
| -}
|
| -
|
| -NSImage* Tile::favicon() const {
|
| - extensions::TabHelper* extensions_tab_helper =
|
| - extensions::TabHelper::FromWebContents(contents_);
|
| - if (extensions_tab_helper->is_app()) {
|
| - SkBitmap* bitmap = extensions_tab_helper->GetExtensionAppIcon();
|
| - if (bitmap)
|
| - return gfx::SkBitmapToNSImage(*bitmap);
|
| - }
|
| - return mac::FaviconForWebContents(contents_);
|
| -}
|
| -
|
| -NSRect Tile::GetTitleStartRectRelativeTo(const Tile& tile) const {
|
| - NSRect thumb_start = GetStartRectRelativeTo(tile);
|
| - CGFloat scale_to_start = NSWidth(thumb_start) / NSWidth(thumb_rect_);
|
| - NSRect rect =
|
| - ScaleRectWithOrigin(title_rect_, thumb_rect_.origin, scale_to_start);
|
| - rect.origin.x += NSMinX(thumb_start) - NSMinX(thumb_rect_);
|
| - rect.origin.y += NSMinY(thumb_start) - NSMinY(thumb_rect_);
|
| - return rect;
|
| -}
|
| -
|
| -// Changes |title_rect| and |favicon_rect| such that the favicon's and the
|
| -// title's vertical center is aligned and that the minimum distance between
|
| -// the thumb rect and favicon and title rects doesn't change.
|
| -void Tile::set_font_metrics(CGFloat ascender, CGFloat descender) {
|
| - // Make the title height big enough to fit the font, and adopt the title
|
| - // position to keep its distance from the thumb rect.
|
| - title_rect_.origin.y -= ascender + descender - NSHeight(title_rect_);
|
| - title_rect_.size.height = ascender + descender;
|
| -
|
| - // Align vertical center. Both rects are currently aligned on their top edge.
|
| - CGFloat delta_y = NSMidY(title_rect_) - NSMidY(favicon_rect_);
|
| - if (delta_y > 0) {
|
| - // Title is higher: Move favicon down to align the centers.
|
| - favicon_rect_.origin.y += delta_y;
|
| - } else {
|
| - // Favicon is higher: Move title down to align the centers.
|
| - title_rect_.origin.y -= delta_y;
|
| - }
|
| -}
|
| -
|
| -// 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, content::WebContents* 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->active_index();
|
| - tiles_.resize(source_model->count());
|
| - for (size_t i = 0; i < tiles_.size(); ++i) {
|
| - tiles_[i] = new Tile;
|
| - tiles_[i]->contents_ = source_model->GetWebContentsAt(i);
|
| - }
|
| -}
|
| -
|
| -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);
|
| - }
|
| - }
|
| -}
|
| -
|
| -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, content::WebContents* 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 AnimateScaledCALayerFrameFromTo(
|
| - CALayer* layer,
|
| - const NSRect& from, CGFloat from_scale,
|
| - const NSRect& to, CGFloat to_scale,
|
| - 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) * from_scale * layer.anchorPoint.x;
|
| - opoint.y += NSHeight(from) * from_scale * layer.anchorPoint.y;
|
| - point.x += NSWidth(to) * to_scale * layer.anchorPoint.x;
|
| - point.y += NSHeight(to) * to_scale * 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"];
|
| -}
|
| -
|
| -void AnimateCALayerFrameFromTo(
|
| - CALayer* layer, const NSRect& from, const NSRect& to,
|
| - NSTimeInterval duration, id boundsAnimationDelegate) {
|
| - AnimateScaledCALayerFrameFromTo(
|
| - layer, from, 1.0, to, 1.0, duration, boundsAnimationDelegate);
|
| -}
|
| -
|
| -void AnimateCALayerOpacityFromTo(
|
| - CALayer* layer, double from, double to, NSTimeInterval duration) {
|
| - CABasicAnimation* animation;
|
| - animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
|
| - animation.fromValue = [NSNumber numberWithFloat:from];
|
| - animation.toValue = [NSNumber numberWithFloat:to];
|
| - animation.duration = duration;
|
| -
|
| - layer.opacity = to;
|
| - // Add the animation, overriding the implicit animation.
|
| - [layer addAnimation:animation forKey:@"opacity"];
|
| -}
|
| -
|
| -@interface TabposeWindow (Private)
|
| -- (id)initForWindow:(NSWindow*)parent
|
| - rect:(NSRect)rect
|
| - slomo:(BOOL)slomo
|
| - tabStripModel:(TabStripModel*)tabStripModel;
|
| -
|
| -// Creates and initializes the CALayer in the background and all the CALayers
|
| -// for the thumbnails, favicons, and titles.
|
| -- (void)setUpLayersInSlomo:(BOOL)slomo;
|
| -
|
| -// Tells the browser to make the tab corresponding to currently selected
|
| -// thumbnail the current tab and starts the tabpose exit animmation.
|
| -- (void)fadeAwayInSlomo:(BOOL)slomo;
|
| -
|
| -// Returns the CALayer for the close button belonging to the thumbnail at
|
| -// index |index|.
|
| -- (CALayer*)closebuttonLayerAtIndex:(NSUInteger)index;
|
| -
|
| -// Updates the visibility of all closebutton layers.
|
| -- (void)updateClosebuttonLayersVisibility;
|
| -@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));
|
| - closeIcon_.reset([ResourceBundle::GetSharedInstance().GetNativeImageNamed(
|
| - IDR_TABPOSE_CLOSE).ToNSImage() retain]);
|
| - [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)selectTileAtIndexWithoutAnimation:(int)newIndex {
|
| - ScopedCAActionDisabler disabler;
|
| - const tabpose::Tile& tile = tileSet_->tile_at(newIndex);
|
| - selectionHighlight_.frame =
|
| - NSRectToCGRect(NSInsetRect(tile.thumb_rect(),
|
| - -kSelectionInset, -kSelectionInset));
|
| - tileSet_->set_selected_index(newIndex);
|
| -
|
| - [self updateClosebuttonLayersVisibility];
|
| -}
|
| -
|
| -- (void)addLayersForTile:(tabpose::Tile&)tile
|
| - showZoom:(BOOL)showZoom
|
| - slomo:(BOOL)slomo
|
| - animationDelegate:(id)animationDelegate {
|
| - base::scoped_nsobject<CALayer> layer(
|
| - [[ThumbnailLayer alloc] initWithWebContents:tile.web_contents()
|
| - fullSize:tile.GetStartRectRelativeTo(
|
| - tileSet_->selected_tile()).size]);
|
| - [layer setNeedsDisplay];
|
| -
|
| - NSTimeInterval interval =
|
| - kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1);
|
| -
|
| - // Background color as placeholder for now.
|
| - layer.get().backgroundColor = CGColorGetConstantColor(kCGColorWhite);
|
| - if (showZoom) {
|
| - AnimateCALayerFrameFromTo(
|
| - layer,
|
| - tile.GetStartRectRelativeTo(tileSet_->selected_tile()),
|
| - tile.thumb_rect(),
|
| - interval,
|
| - 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;
|
| -
|
| - // Add a close button to the thumb layer.
|
| - CALayer* closeLayer = [CALayer layer];
|
| - closeLayer.contents = closeIcon_.get();
|
| - CGRect closeBounds = {};
|
| - closeBounds.size = NSSizeToCGSize([closeIcon_ size]);
|
| - closeLayer.bounds = closeBounds;
|
| - closeLayer.hidden = YES;
|
| -
|
| - [closeLayer addConstraint:
|
| - [CAConstraint constraintWithAttribute:kCAConstraintMidX
|
| - relativeTo:@"superlayer"
|
| - attribute:kCAConstraintMinX]];
|
| - [closeLayer addConstraint:
|
| - [CAConstraint constraintWithAttribute:kCAConstraintMidY
|
| - relativeTo:@"superlayer"
|
| - attribute:kCAConstraintMaxY]];
|
| -
|
| - layer.get().layoutManager = [CAConstraintLayoutManager layoutManager];
|
| - [layer.get() addSublayer:closeLayer];
|
| -
|
| - [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]);
|
| -
|
| - CALayer* faviconLayer = [CALayer layer];
|
| - if (showZoom) {
|
| - AnimateCALayerFrameFromTo(
|
| - faviconLayer,
|
| - tile.GetFaviconStartRectRelativeTo(tileSet_->selected_tile()),
|
| - tile.favicon_rect(),
|
| - interval,
|
| - nil);
|
| - AnimateCALayerOpacityFromTo(faviconLayer, 0.0, 1.0, interval);
|
| - } else {
|
| - faviconLayer.frame = NSRectToCGRect(tile.favicon_rect());
|
| - }
|
| - faviconLayer.contents = tile.favicon();
|
| - faviconLayer.zPosition = 1; // On top of the thumb shadow.
|
| - [bgLayer_ addSublayer:faviconLayer];
|
| - [allFaviconLayers_ addObject:faviconLayer];
|
| -
|
| - // CATextLayers can't animate their fontSize property, at least on 10.5.
|
| - // Animate transform.scale instead.
|
| -
|
| - // The scaling should have its origin in the layer's upper left corner.
|
| - // This needs to be set before |AnimateCALayerFrameFromTo()| is called.
|
| - CATextLayer* titleLayer = [CATextLayer layer];
|
| - titleLayer.anchorPoint = CGPointMake(0, 1);
|
| - if (showZoom) {
|
| - NSRect fromRect =
|
| - tile.GetTitleStartRectRelativeTo(tileSet_->selected_tile());
|
| - NSRect toRect = tile.title_rect();
|
| - CGFloat scale = NSWidth(fromRect) / NSWidth(toRect);
|
| - fromRect.size = toRect.size;
|
| -
|
| - // Add scale animation.
|
| - CABasicAnimation* scaleAnimation =
|
| - [CABasicAnimation animationWithKeyPath:@"transform.scale"];
|
| - scaleAnimation.fromValue = [NSNumber numberWithDouble:scale];
|
| - scaleAnimation.toValue = [NSNumber numberWithDouble:1.0];
|
| - scaleAnimation.duration = interval;
|
| - scaleAnimation.timingFunction =
|
| - [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
|
| - [titleLayer addAnimation:scaleAnimation forKey:@"transform.scale"];
|
| -
|
| - // Add the position and opacity animations.
|
| - AnimateScaledCALayerFrameFromTo(
|
| - titleLayer, fromRect, scale, toRect, 1.0, interval, nil);
|
| - AnimateCALayerOpacityFromTo(faviconLayer, 0.0, 1.0, interval);
|
| - } else {
|
| - 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.
|
| - [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(kCentralGray, 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_];
|
| -
|
| - // Bottom gradient.
|
| - CALayer* gradientLayer = [[[GrayGradientLayer alloc]
|
| - initWithStartGray:kCentralGray endGray:kBottomGray] autorelease];
|
| - gradientLayer.frame = CGRectMake(
|
| - 0,
|
| - 0,
|
| - NSWidth(containingRect_),
|
| - kBottomGradientHeight);
|
| - [gradientLayer setNeedsDisplay]; // Draw once.
|
| - [bgLayer_ addSublayer:gradientLayer];
|
| - }
|
| - // Top gradient (fades in).
|
| - CGFloat toolbarHeight = NSHeight([self frame]) - NSHeight(containingRect_);
|
| - topGradient_ = [[[GrayGradientLayer alloc]
|
| - initWithStartGray:kTopGray endGray:kCentralGray] autorelease];
|
| - topGradient_.frame = CGRectMake(
|
| - 0,
|
| - NSHeight([self frame]) - toolbarHeight,
|
| - NSWidth(containingRect_),
|
| - toolbarHeight);
|
| - [topGradient_ setNeedsDisplay]; // Draw once.
|
| - [rootLayer_ addSublayer:topGradient_];
|
| - NSTimeInterval interval =
|
| - kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1);
|
| - AnimateCALayerOpacityFromTo(topGradient_, 0, 1, interval);
|
| -
|
| - // 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;
|
| -}
|
| -
|
| -// Lets the traffic light buttons on the browser window keep their "active"
|
| -// state while an info bubble is open. Only has an effect on 10.7.
|
| -- (BOOL)_sharesParentKeyState {
|
| - 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 fadeAwayInSlomo:([event modifierFlags] & NSShiftKeyMask) != 0];
|
| - break;
|
| - case '\e': // Escape
|
| - tileSet_->set_selected_index(tabStripModel_->active_index());
|
| - [self fadeAwayInSlomo:([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 fadeAwayInSlomo:([event modifierFlags] & NSShiftKeyMask) != 0];
|
| - return YES;
|
| - }
|
| - }
|
| - }
|
| - return NO;
|
| -}
|
| -
|
| -- (void)flagsChanged:(NSEvent*)event {
|
| - showAllCloseLayers_ = ([event modifierFlags] & NSAlternateKeyMask) != 0;
|
| - [self updateClosebuttonLayersVisibility];
|
| -}
|
| -
|
| -- (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];
|
| -}
|
| -
|
| -- (CALayer*)closebuttonLayerAtIndex:(NSUInteger)index {
|
| - CALayer* layer = [allThumbnailLayers_ objectAtIndex:index];
|
| - return [[layer sublayers] objectAtIndex:0];
|
| -}
|
| -
|
| -- (void)updateClosebuttonLayersVisibility {
|
| - for (NSUInteger i = 0; i < [allThumbnailLayers_ count]; ++i) {
|
| - CALayer* layer = [self closebuttonLayerAtIndex:i];
|
| - BOOL isSelectedTile = static_cast<int>(i) == tileSet_->selected_index();
|
| - BOOL isVisible = state_ == tabpose::kFadedIn &&
|
| - (isSelectedTile || showAllCloseLayers_);
|
| - layer.hidden = !isVisible;
|
| - }
|
| -}
|
| -
|
| -- (void)mouseDown:(NSEvent*)event {
|
| - // Just in case the user clicked without ever moving the mouse.
|
| - [self selectTileFromMouseEvent:event];
|
| -
|
| - // If the click occurred in a close box, close that tab and don't do anything
|
| - // else.
|
| - CGPoint p = NSPointToCGPoint([event locationInWindow]);
|
| - for (NSUInteger i = 0; i < [allThumbnailLayers_ count]; ++i) {
|
| - CALayer* layer = [self closebuttonLayerAtIndex:i];
|
| - CGPoint lp = [layer convertPoint:p fromLayer:rootLayer_];
|
| - if ([static_cast<CALayer*>([layer presentationLayer]) containsPoint:lp] &&
|
| - !layer.hidden) {
|
| - tabStripModel_->CloseWebContentsAt(i,
|
| - TabStripModel::CLOSE_USER_GESTURE |
|
| - TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
|
| - return;
|
| - }
|
| - }
|
| -
|
| - [self fadeAwayInSlomo:([event modifierFlags] & NSShiftKeyMask) != 0];
|
| -}
|
| -
|
| -- (void)swipeWithEvent:(NSEvent*)event {
|
| - if (abs([event deltaY]) > 0.5) // Swipe up or down.
|
| - [self fadeAwayInSlomo:([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 fadeAwayInSlomo: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)fadeAwayTileAtIndex:(int)index {
|
| - const tabpose::Tile& tile = tileSet_->tile_at(index);
|
| - CALayer* layer = [allThumbnailLayers_ objectAtIndex:index];
|
| - // Add a delegate to one of the implicit animations to get a notification
|
| - // once the animations are done.
|
| - if (static_cast<int>(index) == tileSet_->selected_index()) {
|
| - CAAnimation* animation = [CAAnimation animation];
|
| - animation.delegate = self;
|
| - [animation setValue:kAnimationIdFadeOut forKey:kAnimationIdKey];
|
| - [layer addAnimation:animation forKey:@"frame"];
|
| - }
|
| -
|
| - // Thumbnail.
|
| - layer.frame = NSRectToCGRect(
|
| - tile.GetStartRectRelativeTo(tileSet_->selected_tile()));
|
| -
|
| - if (static_cast<int>(index) == tileSet_->selected_index()) {
|
| - // Redraw layer at big resolution, so that zoom-in isn't blocky.
|
| - [layer setNeedsDisplay];
|
| - }
|
| -
|
| - // Title.
|
| - CALayer* faviconLayer = [allFaviconLayers_ objectAtIndex:index];
|
| - faviconLayer.frame = NSRectToCGRect(
|
| - tile.GetFaviconStartRectRelativeTo(tileSet_->selected_tile()));
|
| - faviconLayer.opacity = 0;
|
| -
|
| - // Favicon.
|
| - // The |fontSize| cannot be animated directly, animate the layer's scale
|
| - // instead. |transform.scale| affects the rendered width, so keep the small
|
| - // bounds.
|
| - CALayer* titleLayer = [allTitleLayers_ objectAtIndex:index];
|
| - NSRect titleRect = tile.title_rect();
|
| - NSRect titleToRect =
|
| - tile.GetTitleStartRectRelativeTo(tileSet_->selected_tile());
|
| - CGFloat scale = NSWidth(titleToRect) / NSWidth(titleRect);
|
| - titleToRect.origin.x +=
|
| - NSWidth(titleRect) * scale * titleLayer.anchorPoint.x;
|
| - titleToRect.origin.y +=
|
| - NSHeight(titleRect) * scale * titleLayer.anchorPoint.y;
|
| - titleLayer.position = NSPointToCGPoint(titleToRect.origin);
|
| - [titleLayer setValue:[NSNumber numberWithDouble:scale]
|
| - forKeyPath:@"transform.scale"];
|
| - titleLayer.opacity = 0;
|
| -}
|
| -
|
| -- (void)fadeAwayInSlomo:(BOOL)slomo {
|
| - if (state_ == tabpose::kFadingOut)
|
| - return;
|
| -
|
| - state_ = tabpose::kFadingOut;
|
| - [self setAcceptsMouseMovedEvents:NO];
|
| -
|
| - // Select chosen tab.
|
| - if (tileSet_->selected_index() < tabStripModel_->count()) {
|
| - tabStripModel_->ActivateTabAt(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;
|
| - // 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;
|
| -
|
| - [self updateClosebuttonLayersVisibility];
|
| - }
|
| -
|
| - // Animate layers out, all in one transaction.
|
| - CGFloat duration =
|
| - 1.3 * kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1);
|
| - ScopedCAActionSetDuration durationSetter(duration);
|
| - for (int i = 0; i < tabStripModel_->count(); ++i)
|
| - [self fadeAwayTileAtIndex:i];
|
| - AnimateCALayerOpacityFromTo(topGradient_, 1, 0, duration);
|
| -}
|
| -
|
| -- (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;
|
| -
|
| - // 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;
|
| -
|
| - [self updateClosebuttonLayersVisibility];
|
| - }
|
| - } 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* thumbLayer = [allThumbnailLayers_ objectAtIndex:i];
|
| -
|
| - if (i == tileSet_->selected_index()) {
|
| - AnimateCALayerFrameFromTo(
|
| - selectionHighlight_,
|
| - NSInsetRect(NSRectFromCGRect(thumbLayer.frame),
|
| - -kSelectionInset, -kSelectionInset),
|
| - NSInsetRect(tile.thumb_rect(),
|
| - -kSelectionInset, -kSelectionInset),
|
| - kObserverChangeAnimationDuration,
|
| - nil);
|
| - }
|
| -
|
| - // Repaint layer if necessary.
|
| - if (!NSEqualSizes(NSRectFromCGRect(thumbLayer.frame).size,
|
| - tile.thumb_rect().size)) {
|
| - [thumbLayer setNeedsDisplay];
|
| - }
|
| -
|
| - // Use AnimateCALayerFrameFromTo() instead of just setting |frame| to let
|
| - // the animation match the selection animation --
|
| - // |kCAMediaTimingFunctionDefault| is 10.6-only.
|
| - AnimateCALayerFrameFromTo(
|
| - thumbLayer,
|
| - NSRectFromCGRect(thumbLayer.frame),
|
| - tile.thumb_rect(),
|
| - kObserverChangeAnimationDuration,
|
| - nil);
|
| -
|
| - CALayer* faviconLayer = [allFaviconLayers_ objectAtIndex:i];
|
| - AnimateCALayerFrameFromTo(
|
| - faviconLayer,
|
| - NSRectFromCGRect(faviconLayer.frame),
|
| - tile.favicon_rect(),
|
| - kObserverChangeAnimationDuration,
|
| - nil);
|
| -
|
| - CALayer* titleLayer = [allTitleLayers_ objectAtIndex:i];
|
| - AnimateCALayerFrameFromTo(
|
| - titleLayer,
|
| - NSRectFromCGRect(titleLayer.frame),
|
| - tile.title_rect(),
|
| - kObserverChangeAnimationDuration,
|
| - nil);
|
| -}
|
| -
|
| -- (void)insertTabWithContents:(content::WebContents*)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);
|
| - 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]));
|
| -
|
| - // Update selection.
|
| - int selectedIndex = tileSet_->selected_index();
|
| - if (selectedIndex >= index)
|
| - selectedIndex++;
|
| - [self selectTileAtIndexWithoutAnimation:selectedIndex];
|
| -
|
| - // Animate everything into its new place.
|
| - for (int i = 0; i < tabStripModel_->count(); ++i) {
|
| - if (i == index) // The new layer.
|
| - continue;
|
| - [self refreshLayerFramesAtIndex:i];
|
| - }
|
| -}
|
| -
|
| -- (void)tabClosingWithContents:(content::WebContents*)contents
|
| - atIndex:(NSInteger)index {
|
| - // We will also get a -tabDetachedWithContents:atIndex: notification for
|
| - // closing tabs, so do nothing here.
|
| -}
|
| -
|
| -- (void)tabDetachedWithContents:(content::WebContents*)contents
|
| - atIndex:(NSInteger)index {
|
| - ScopedCAActionSetDuration durationSetter(kObserverChangeAnimationDuration);
|
| -
|
| - // Remove layer and relayout.
|
| - tileSet_->RemoveTileAt(index);
|
| - tileSet_->Layout(containingRect_);
|
| -
|
| - {
|
| - ScopedCAActionDisabler disabler;
|
| - [[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];
|
| -
|
| - // Update selection.
|
| - int selectedIndex = tileSet_->selected_index();
|
| - if (selectedIndex > index || selectedIndex >= tabStripModel_->count())
|
| - selectedIndex--;
|
| - if (selectedIndex >= 0)
|
| - [self selectTileAtIndexWithoutAnimation:selectedIndex];
|
| -
|
| - // Animate everything into its new place.
|
| - for (int i = 0; i < tabStripModel_->count(); ++i)
|
| - [self refreshLayerFramesAtIndex:i];
|
| -}
|
| -
|
| -- (void)tabMovedWithContents:(content::WebContents*)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|.
|
| - base::scoped_nsobject<CALayer> thumbLayer(
|
| - [[allThumbnailLayers_ objectAtIndex:from] retain]);
|
| - [allThumbnailLayers_ removeObjectAtIndex:from];
|
| - [allThumbnailLayers_ insertObject:thumbLayer.get() atIndex:to];
|
| - base::scoped_nsobject<CALayer> faviconLayer(
|
| - [[allFaviconLayers_ objectAtIndex:from] retain]);
|
| - [allFaviconLayers_ removeObjectAtIndex:from];
|
| - [allFaviconLayers_ insertObject:faviconLayer.get() atIndex:to];
|
| - base::scoped_nsobject<CALayer> titleLayer(
|
| - [[allTitleLayers_ objectAtIndex:from] retain]);
|
| - [allTitleLayers_ removeObjectAtIndex:from];
|
| - [allTitleLayers_ insertObject:titleLayer.get() atIndex:to];
|
| -
|
| - // 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 selectTileAtIndexWithoutAnimation:selectedIndex];
|
| -
|
| - // Update frames of the layers.
|
| - for (int i = std::min(from, to); i <= std::max(from, to); ++i)
|
| - [self refreshLayerFramesAtIndex:i];
|
| -}
|
| -
|
| -- (void)tabChangedWithContents:(content::WebContents*)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 WebContents
|
| - // object.
|
| - tabpose::Tile& tile = tileSet_->tile_at(index);
|
| - if (contents == tile.web_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);
|
| - ThumbnailLayer* thumbLayer = [allThumbnailLayers_ objectAtIndex:index];
|
| - [thumbLayer setWebContents:contents];
|
| -}
|
| -
|
| -- (void)tabStripModelDeleted {
|
| - [self close];
|
| -}
|
| -
|
| -@end
|
|
|