| Index: chrome/browser/cocoa/tabpose_window.mm
|
| diff --git a/chrome/browser/cocoa/tabpose_window.mm b/chrome/browser/cocoa/tabpose_window.mm
|
| index f96d0402bb5a05d53fc23272536713a9ed94b1f2..798b43a7df8f5d22cf56e0cf14ba480ca860a96b 100644
|
| --- a/chrome/browser/cocoa/tabpose_window.mm
|
| +++ b/chrome/browser/cocoa/tabpose_window.mm
|
| @@ -8,12 +8,18 @@
|
|
|
| #include "app/resource_bundle.h"
|
| #include "base/mac_util.h"
|
| +#include "base/scoped_callback_factory.h"
|
| #include "base/sys_string_conversions.h"
|
| +#include "chrome/browser/browser_process.h"
|
| #import "chrome/browser/cocoa/browser_window_controller.h"
|
| #import "chrome/browser/cocoa/tab_strip_controller.h"
|
| +#include "chrome/browser/renderer_host/backing_store_mac.h"
|
| +#include "chrome/browser/renderer_host/render_view_host.h"
|
| #include "chrome/browser/tab_contents/tab_contents.h"
|
| +#include "chrome/browser/tab_contents/thumbnail_generator.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;
|
|
|
| @@ -42,6 +48,155 @@ const CGFloat kSlomoFactor = 4;
|
| }
|
| @end
|
|
|
| +// FIXME: this needs to be cleaned up a lot
|
| +
|
| +// FIXME: look at URLFetcher's inner class for thread switching
|
| +
|
| +// FIMXE comment is wrong
|
| +// A helper class used to interface with chrome's c++ code. Used to dispatch
|
| +// stuff to threads; to listen for things; etc.
|
| +class TabposeHelper : public base::RefCountedThreadSafe<TabposeHelper> {
|
| + public:
|
| + TabposeHelper(/*TabposeWindow* window*/NSSize fullS)
|
| + : nsFullSize(fullS) /*window_(window)*//*, factory_(this)*/ , layer_(nil), factory_(this) {}
|
| +
|
| + void LoadThumbnail(RenderWidgetHost* rwh, CALayer* layer2, const gfx::Size& s);
|
| + private:
|
| + friend class base::RefCountedThreadSafe<TabposeHelper>;
|
| + ~TabposeHelper() {}
|
| +
|
| + void DidReceiveBitmap(const SkBitmap& bitmap) {
|
| + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
|
| +NSLog(@"receiving bitmap %d %d", bitmap.width(), bitmap.height());
|
| + // FIXME: too many conversions (SkCreateCGImageRef() actually shares memory,
|
| + // so it's not too bad)
|
| + cgimage.reset(SkCreateCGImageRef(bitmap));
|
| + layer_.contents = (id)cgimage.get();
|
| + }
|
| +
|
| +scoped_cftyperef<CGImageRef> cgimage;
|
| +NSSize nsFullSize;
|
| + CALayer* layer_;
|
| + base::ScopedCallbackFactory<TabposeHelper> factory_;
|
| +};
|
| +
|
| +void TabposeHelper::LoadThumbnail(RenderWidgetHost* rwh, CALayer* layer2, const
|
| +gfx::Size& s) {
|
| + gfx::Size fullSize(NSSizeToCGSize(nsFullSize));
|
| +
|
| + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
|
| +
|
| + // Don't need to go on IO thread, as this just sends an IPC anyway
|
| + // (and ScopedCallbackFactory isn't threadsafe :-P)
|
| + ThumbnailGenerator* generator = g_browser_process->GetThumbnailGenerator();
|
| +
|
| + // FIXME: Internally, this allocs memory for both transport dib and then
|
| + // an SkBitmap (which _then_ needs to be converted to a CGImageRef).
|
| + // Reduce the amount of copying; possibly by sidestepping
|
| + // ThumbnailGenerator.
|
| + CHECK(!layer_);
|
| + layer_ = layer2;
|
| +
|
| + // |fullSize| is slightly larger (view bounds doesn't include scroll bars?)
|
| + gfx::Size page_size(fullSize); // Logical size the renderer renders at
|
| +
|
| + // This is a bit tricky: We really only need a bitmap at size |s|, but when
|
| + // the user clicks a thumbnail and it zooms large, this single thumbnail
|
| + // should have a pixel size close to the view bounds, so that the image doesnt
|
| + // get blurry. One possible idea would be to get all thumbs at size |s| and
|
| + // request the one currently below the mouse at full size, but then it might
|
| + // not be ready when the click happens. For now, KISS, and request everything
|
| + // at the full resolution.
|
| + // FIXME: even better plan: call model->selectTab before starting the bg animation.
|
| + // that makes sure the target tab has a backing store. then call setNeedsDisplay on the
|
| + // layer, and it'll be redrawn from the backing store -- and all bg thumbs can stay small.
|
| + // yay! (is this true?) => no :-/, it takes too long for the backing store to load.
|
| + //gfx::Size desired_size(s); // physical pixel size the image is rendered at
|
| + gfx::Size desired_size(fullSize); // physical pixel size the image is rendered at
|
| +NSLog(@"scheduling snapshot request");
|
| +
|
| + // FIXME: check that observer is currently NULL
|
| + // FIXME: set observer back to NULL at some point
|
| + rwh->set_painting_observer(generator);
|
| + generator->AskForSnapshot(rwh, /*prefer_backing_store=*/false,
|
| + factory_.NewCallback(&TabposeHelper::DidReceiveBitmap),
|
| + page_size, desired_size);
|
| +}
|
| +
|
| +
|
| +
|
| +// A CALayer that draws a CGLayerRef (or a CGBitmap).
|
| +@interface BackingStoreLayer : CALayer {
|
| + TabContents* contents_; // should really have a tabpose::Tile*?
|
| + NSSize smallSize;
|
| + NSSize fullSize;
|
| + scoped_refptr<TabposeHelper> helper_;
|
| + BOOL didSendLoad_;
|
| +}
|
| +- (id)initWithTabContents:(TabContents*)contents smallSize:(NSSize)s fullSize:(NSSize)s2;
|
| +- (void)drawInContext:(CGContextRef)context;
|
| +@end
|
| +
|
| +@implementation BackingStoreLayer
|
| +- (id)initWithTabContents:(TabContents*)contents smallSize:(NSSize)s fullSize:(NSSize)s2 {
|
| + CHECK(contents);
|
| + if ((self = [super init])) {
|
| + contents_ = contents;
|
| + smallSize = s;
|
| + fullSize = s2;
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (void)drawInContext:(CGContextRef)context {
|
| + RenderWidgetHost* rwh = contents_->render_view_host();
|
| + BackingStoreMac* backing_store_ = (BackingStoreMac*)rwh->GetBackingStore(false);
|
| + if (!backing_store_) {
|
| + // Either the tab was never visible, or its backing store got evicted.
|
| +
|
| +NSLog(@"drawing layer without backingstore %d %@", didSendLoad_, self.contents);
|
| + // do this only if we haven't already sent a thumbnail request before
|
| + // else this is done on hide and way too much in general
|
| + if (!didSendLoad_) {
|
| + helper_ = new TabposeHelper(NSSizeFromCGSize(NSSizeToCGSize(fullSize)));
|
| + helper_->LoadThumbnail(rwh, self, gfx::Size(NSSizeToCGSize(smallSize)));
|
| + didSendLoad_ = YES;
|
| +
|
| + // Overwrites |contents|, so do this only once, to fill with bg color.
|
| + [super drawInContext:context];
|
| + }
|
| +
|
| + if ([self contents]) {
|
| + CGContextDrawImage(context, [self bounds], (CGImageRef)[self contents]);
|
| + }
|
| +
|
| + return;
|
| + }
|
| +
|
| + // TODO(thakis): re-send PaintAt ipc when tabstripmodelobserver informs us
|
| + // that a tab has changed (reload favicon & title, too)
|
| +
|
| + // TODO(thakis): backing store might have wrong size (?)
|
| +
|
| + // TODO(thakis): composited tabs might not be rendered correctly by this
|
| + // (once this is in BWC, let it add a sublayer for all IOSurfaces)
|
| +
|
| + // TODO(thakis): Draw only into rect not occupied by info bars, detached
|
| + // bookmarks bar, devtool
|
| + if (backing_store_->cg_layer()) {
|
| + CGContextDrawLayerInRect(context, [self bounds],
|
| + backing_store_->cg_layer());
|
| + } else {
|
| +NSLog(@"no layer!");
|
| + scoped_cftyperef<CGImageRef> image(
|
| + CGBitmapContextCreateImage(backing_store_->cg_bitmap()));
|
| + CGContextDrawImage(context, [self bounds], image);
|
| + }
|
| +}
|
| +@end
|
| +
|
| +
|
| +
|
| namespace {
|
|
|
| class ScopedCAActionDisabler {
|
| @@ -133,6 +288,8 @@ class Tile {
|
|
|
| // Returns an unelided title. The view logic is responsible for eliding.
|
| const string16& title() const { return contents_->GetTitle(); }
|
| +
|
| + TabContents* tab_contents() const { return contents_; }
|
| private:
|
| friend class TileSet;
|
|
|
| @@ -183,8 +340,9 @@ void Tile::set_font_metrics(CGFloat ascender, CGFloat descender) {
|
|
|
| // A tileset is responsible for owning and laying out all |Tile|s shown in a
|
| // tabpose window.
|
| -class TileSet {
|
| +class TileSet : public TabStripModelObserver {
|
| public:
|
| + virtual ~TileSet() { source_model_->RemoveObserver(this); } // FIXME: crashes if Build() wasn't called.
|
| // Fills in |tiles_|.
|
| void Build(TabStripModel* source_model);
|
|
|
| @@ -199,11 +357,29 @@ class TileSet {
|
| Tile& tile_at(int index) { return *tiles_[index]; }
|
| const Tile& tile_at(int index) const { return *tiles_[index]; }
|
|
|
| + // TabStripModelObserver
|
| + virtual void TabInsertedAt(TabContents* contents,
|
| + int index,
|
| + bool foreground) { CHECK(false); }
|
| + virtual void TabClosingAt(TabContents* contents, int index) { CHECK(false); }
|
| + virtual void TabDetachedAt(TabContents* contents, int index) { CHECK(false); }
|
| + virtual void TabMoved(TabContents* contents,
|
| + int from_index,
|
| + int to_index) { CHECK(false); }
|
| + virtual void TabStripModelDeleted() { CHECK(false); }
|
| + virtual void TabChangedAt(TabContents* contents, int index,
|
| + TabChangeType change_type) {
|
| + // FIXME: update tile with |contents|, reload title, favicon, thumb
|
| + // depending on |change_type|.
|
| + }
|
| +
|
| private:
|
| ScopedVector<Tile> tiles_;
|
|
|
| int selected_index_;
|
| int initial_index_;
|
| +
|
| + TabStripModel* source_model_;
|
| };
|
|
|
| void TileSet::Build(TabStripModel* source_model) {
|
| @@ -213,6 +389,9 @@ void TileSet::Build(TabStripModel* source_model) {
|
| tiles_[i] = new Tile;
|
| tiles_[i]->contents_ = source_model->GetTabContentsAt(i);
|
| }
|
| +
|
| + source_model->AddObserver(this);
|
| + source_model_ = source_model;
|
| }
|
|
|
| void TileSet::Layout(NSRect containing_rect) {
|
| @@ -479,36 +658,37 @@ void AnimateCALayerFrameFromTo(
|
| - (void)setUpLayers:(NSRect)bgLayerRect slomo:(BOOL)slomo {
|
| // Root layer -- covers whole window.
|
| rootLayer_ = [CALayer layer];
|
| - [[self contentView] setLayer:rootLayer_];
|
| - [[self contentView] setWantsLayer:YES];
|
| -
|
| - // Background layer -- the visible part of the window.
|
| - gray_.reset(CGColorCreateGenericGray(0.39, 1.0));
|
| - bgLayer_ = [CALayer layer];
|
| - bgLayer_.backgroundColor = gray_;
|
| - bgLayer_.frame = NSRectToCGRect(bgLayerRect);
|
| - 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(bgLayerRect) - kTopGradientHeight,
|
| - NSWidth(bgLayerRect),
|
| - kTopGradientHeight);
|
| - [gradientLayer setNeedsDisplay]; // Draw once.
|
| - [bgLayer_ addSublayer:gradientLayer];
|
|
|
| + // 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(bgLayerRect);
|
| + 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(bgLayerRect) - kTopGradientHeight,
|
| + NSWidth(bgLayerRect),
|
| + kTopGradientHeight);
|
| + [gradientLayer setNeedsDisplay]; // Draw once.
|
| + [bgLayer_ addSublayer:gradientLayer];
|
| + }
|
| // Layers for the tab thumbnails.
|
| tileSet_->Build(tabStripModel_);
|
| tileSet_->Layout(bgLayerRect);
|
| @@ -524,7 +704,11 @@ void AnimateCALayerFrameFromTo(
|
| [[NSMutableArray alloc] initWithCapacity:tabStripModel_->count()]);
|
| for (int i = 0; i < tabStripModel_->count(); ++i) {
|
| const tabpose::Tile& tile = tileSet_->tile_at(i);
|
| - CALayer* layer = [CALayer layer];
|
| +
|
| +
|
| +// FIXME: BrowserWindowController should create this layer
|
| + CALayer* layer = [[[BackingStoreLayer alloc] initWithTabContents:tile.tab_contents() smallSize:tile.thumb_rect().size fullSize:tile.GetStartRectRelativeTo(tileSet_->selected_tile()).size] autorelease];
|
| + [layer setNeedsDisplay];
|
|
|
| // Background color as placeholder for now.
|
| layer.backgroundColor = CGColorGetConstantColor(kCGColorWhite);
|
| @@ -582,6 +766,12 @@ void AnimateCALayerFrameFromTo(
|
| [allTitleLayers_ addObject:titleLayer];
|
| }
|
| [self selectTileAtIndex: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 {
|
| @@ -683,7 +873,7 @@ void AnimateCALayerFrameFromTo(
|
| }
|
|
|
| // Animate layers out, all in one transaction.
|
| - CGFloat duration = kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1);
|
| + CGFloat duration = 2 * kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1);
|
| ScopedCAActionSetDuration durationSetter(duration);
|
| for (NSUInteger i = 0; i < [allThumbnailLayers_ count]; ++i) {
|
| CALayer* layer = [allThumbnailLayers_ objectAtIndex:i];
|
| @@ -703,6 +893,11 @@ void AnimateCALayerFrameFromTo(
|
| }
|
|
|
| layer.frame = newFrame;
|
| +
|
| + if (static_cast<int>(i) == tileSet_->selected_index()) {
|
| + // FIXME: only do this if the layer has a backing store (?)
|
| + [layer setNeedsDisplay]; // Redraw layer at big resolution, so that zoom-in isn't blocky.
|
| + }
|
| }
|
| }
|
|
|
|
|