| Index: chrome/browser/cocoa/tabpose_window.mm | 
| diff --git a/chrome/browser/cocoa/tabpose_window.mm b/chrome/browser/cocoa/tabpose_window.mm | 
| index c69c5c16b91cb7594513afa03cf49713b00bb04a..6a12a3ced701d9fa2a3beafa49dc42ebfc95d975 100644 | 
| --- a/chrome/browser/cocoa/tabpose_window.mm | 
| +++ b/chrome/browser/cocoa/tabpose_window.mm | 
| @@ -6,6 +6,27 @@ | 
|  | 
| #import <QuartzCore/QuartzCore.h> | 
|  | 
| +#include "base/mac_util.h" | 
| +#include "base/scoped_cftyperef.h" | 
| +#include "base/task.h" | 
| +#include "chrome/browser/browser_process.h" | 
| +#import "chrome/browser/cocoa/browser_window_controller.h" | 
| +#import "chrome/browser/cocoa/fast_resize_view.h" | 
| +#import "chrome/browser/cocoa/tab_strip_controller.h" | 
| +#include "chrome/browser/renderer_host/backing_store_mac.h" | 
| +#include "chrome/browser/renderer_host/render_widget_host_view.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 "third_party/skia/include/utils/mac/SkCGUtils.h" | 
| + | 
| +// FIXME: Need to listen for tab creation / destruction and react to that | 
| +// FIXME: would be nice if at least the selected tab would be "live" (i.e. | 
| +// videos play etc) | 
| +// FIXME (related?): crashes if invoked while stuff is still loading | 
| + | 
| +const float S = 1.03; | 
| + | 
| const int kTopGradientHeight  = 15; | 
|  | 
| // CAGradientLayer is 10.6-only -- roll our own. | 
| @@ -13,6 +34,34 @@ const int kTopGradientHeight  = 15; | 
| - (void)drawInContext:(CGContextRef)context; | 
| @end | 
|  | 
| +// CAGradientLayer is 10.6-only -- roll our own | 
| +@interface GradientLayer : CALayer | 
| +- (void)drawInContext:(CGContextRef)context; | 
| +@end | 
| + | 
| +@implementation GradientLayer | 
| +- (void)drawInContext:(CGContextRef)context { | 
| +  scoped_cftyperef<CGColorSpaceRef> grayColorspace( | 
| +      CGColorSpaceCreateWithName(kCGColorSpaceGenericGray)); | 
| +NSLog(@"count: %d", CGColorSpaceGetNumberOfComponents(grayColorspace)); | 
| +  CGFloat grays[] = { 0.1, 0.39 }; | 
| +  CGFloat locations[] = { 0, 1 }; | 
| +  scoped_cftyperef<CGGradientRef> gradient(CGGradientCreateWithColorComponents( | 
| +      grayColorspace, grays, locations, arraysize(grays))); | 
| + | 
| +  CGPoint midY = CGPointMake(0.0, 15); | 
| +  CGContextDrawLinearGradient(context, gradient, midY, CGPointZero, 0); | 
| +} | 
| +@end | 
| + | 
| +// A CALayer that draws a CGLayerRef (or a CGBitmap). | 
| +@interface BackingStoreLayer : CALayer { | 
| +  BackingStoreMac* backing_store_; | 
| +} | 
| +- (id)initWithBackingStore:(BackingStoreMac*)store; | 
| +- (void)drawInContext:(CGContextRef)context; | 
| +@end | 
| + | 
| @implementation DarkGradientLayer | 
| - (void)drawInContext:(CGContextRef)context { | 
| scoped_cftyperef<CGColorSpaceRef> grayColorSpace( | 
| @@ -26,19 +75,137 @@ const int kTopGradientHeight  = 15; | 
| } | 
| @end | 
|  | 
| +@implementation BackingStoreLayer | 
| +- (id)initWithBackingStore:(BackingStoreMac*)store { | 
| +  CHECK(store); | 
| +  if ((self = [super init])) { | 
| +    backing_store_ = store; | 
| +  } | 
| +  return self; | 
| +} | 
| + | 
| +- (void)drawInContext:(CGContextRef)context { | 
| +  // Hrm, looks like I need to use the drawing delegate functionality of | 
| +  // a layer here | 
| +  if (backing_store_->cg_layer()) { | 
| +    // TODO: add clipping to dirtyRect if it improves drawing performance. | 
| +    CGContextDrawLayerInRect(context, [self bounds], | 
| +                             backing_store_->cg_layer()); | 
| +  } else { | 
| +NSLog(@"no layer!"); | 
| +    // if we haven't created a layer yet, draw the cached bitmap into | 
| +    // the window.  The CGLayer will be created the next time the renderer | 
| +    // paints. | 
| +    scoped_cftyperef<CGImageRef> image( | 
| +        CGBitmapContextCreateImage(backing_store_->cg_bitmap())); | 
| +    CGContextDrawImage(context, [self bounds], image); | 
| +  } | 
| +} | 
| +@end | 
| + | 
| +// FIXME comment | 
| +class TabposeCallback { | 
| + public: | 
| +  TabposeCallback(CALayer* layer) | 
| +      : layer_(layer) {} | 
| + | 
| +  void DidReceiveBitmap(const SkBitmap& bitmap) { | 
| +    DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); | 
| +NSLog(@"receiving bitmap %d %d", bitmap.width(), bitmap.height()); | 
| +    // FIXME: too many conversions | 
| +    scoped_cftyperef<CGImageRef> cgimage(SkCreateCGImageRef(bitmap)); | 
| +    layer_.contents = (id)cgimage.get(); | 
| +    layer_.backgroundColor = nil;  // clear out bg color to default value | 
| +  } | 
| + private: | 
| +  CALayer* layer_; | 
| +}; | 
| + | 
| +// FIXME: look at DISABLE_RUNNABLE_METHOD_REFCOUNT | 
| +// 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) | 
| +      : window_(window)/*, factory_(this)*/ {} | 
| + | 
| +  void LoadThumbnail(RenderWidgetHost* rwh, CALayer* layer2, const gfx::Size& s); | 
| + private: | 
| +  friend class base::RefCountedThreadSafe<TabposeHelper>; | 
| +  ~TabposeHelper() {} | 
| +  void LoadThumbnailIO(RenderWidgetHost* rwh, CALayer* layer2, const gfx::Size& s, const gfx::Size& fullSize); | 
| + | 
| +  TabposeWindow* window_; | 
| +//  ScopedRunnableMethodFactory<TabposeHelper> factory_; | 
| +}; | 
| + | 
| +void TabposeHelper::LoadThumbnail(RenderWidgetHost* rwh, CALayer* layer2, const gfx::Size& s) { | 
| +  // rwh->set_painting_observer(NULL);  // FIXME | 
| + | 
| +  NSSize nsFullSize = [[window_->browser_ tabContentArea] frame].size; | 
| +  gfx::Size fullSize(NSSizeToCGSize(nsFullSize)); | 
| + | 
| +  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); | 
| +  // FIXME: is the hop to the IO thread really necessary? | 
| +  ChromeThread::PostTask(ChromeThread::IO, FROM_HERE, | 
| +      NewRunnableMethod(this, &TabposeHelper::LoadThumbnailIO, rwh, layer2, s, fullSize)); | 
| +} | 
| + | 
| +void TabposeHelper::LoadThumbnailIO(RenderWidgetHost* rwh, CALayer* layer2, const gfx::Size& s, const gfx::Size& fullSize) { | 
| +  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); | 
| +  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. | 
| +  TabposeCallback* callback = new TabposeCallback(layer2); | 
| +  window_->callbacks_.push_back(callback);  // takes ownership | 
| + | 
| +  // |fullSize| is slightly larger (view bounds doesn't include scroll bars?) | 
| +//  gfx::Size page_size(rwh->view()->GetViewBounds().size());  // Logical size the renderer renders at | 
| +  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. | 
| +//  gfx::Size desired_size(s);  // physical pixel size the image is rendered at | 
| +  gfx::Size desired_size(page_size);  // physical pixel size the image is rendered at | 
| +NSLog(@"scheduling snapshot request"); | 
| + | 
| +  // FIXME: check that observer is currently NULL | 
| +  // FIXME: this lets us receive straggling requests from an old tabpose window. leads to crashes. | 
| +  rwh->set_painting_observer(generator); | 
| +  generator->AskForSnapshot(rwh, /*prefer_backing_store=*/false, | 
| +                            NewCallback(callback, &TabposeCallback::DidReceiveBitmap), | 
| +                            page_size, desired_size); | 
| +  // FIXME: needs to be cancelled somehow if we die before renderer returns | 
| +} | 
| + | 
| @interface TabposeWindow (Private) | 
| -- (id)initForWindow:(NSWindow*)parent rect:(NSRect)rect slomo:(BOOL)slomo; | 
| +- (id)initForWindow:(NSWindow*)parent rect:(NSRect)rect slomo:(BOOL)slomo | 
| +            tempBWC:(BrowserWindowController*)bwc; | 
| - (void)setUpLayers:(NSRect)bgLayerRect; | 
| +- (void)fadeAway:(BOOL)slomo; | 
| @end | 
|  | 
| @implementation TabposeWindow | 
|  | 
| -+ (id)openTabposeFor:(NSWindow*)parent rect:(NSRect)rect slomo:(BOOL)slomo { | 
| ++ (id)openTabposeFor:(NSWindow*)parent rect:(NSRect)rect slomo:(BOOL)slomo | 
| +             tempBWC:(BrowserWindowController*)bwc { | 
| // Releases itself when closed. | 
| -  return [[TabposeWindow alloc] initForWindow:parent rect:rect slomo:slomo]; | 
| +  return [[TabposeWindow alloc] initForWindow:parent rect:rect slomo:slomo tempBWC:bwc]; | 
| } | 
|  | 
| -- (id)initForWindow:(NSWindow*)parent rect:(NSRect)rect slomo:(BOOL)slomo { | 
| +- (id)initForWindow:(NSWindow*)parent rect:(NSRect)rect slomo:(BOOL)slomo | 
| +             tempBWC:(BrowserWindowController*)bwc { | 
| NSRect frame = [parent frame]; | 
| if ((self = [super initWithContentRect:frame | 
| styleMask:NSBorderlessWindowMask | 
| @@ -47,9 +214,343 @@ const int kTopGradientHeight  = 15; | 
| [self setReleasedWhenClosed:YES]; | 
| [self setOpaque:NO]; | 
| [self setBackgroundColor:[NSColor clearColor]]; | 
| -    [self setUpLayers:rect]; | 
| [parent addChildWindow:self ordered:NSWindowAbove]; | 
| [self makeKeyAndOrderFront:self]; | 
| + | 
| +      //NSImage* img = [NSImage imageNamed:@"contents1"]; | 
| + | 
| +//  [self makeFirstResponder:self]; | 
| + | 
| +    helper_ = new TabposeHelper(self); | 
| + | 
| +    state_ = kFadingIn; | 
| + | 
| +    browser_ = bwc; | 
| + | 
| +    NSView* contentArea = [browser_ tabContentArea] ;  // FIXME | 
| +    NSRect areaFrame = [contentArea frame]; | 
| + | 
| +    [self setUpLayers:rect]; | 
| + | 
| +    // Configure hand cursor | 
| +    NSTrackingArea* trackingArea = [[NSTrackingArea alloc] | 
| +        initWithRect:[[self contentView] frame] | 
| +             options:NSTrackingCursorUpdate|NSTrackingActiveInKeyWindow | 
| +               owner:self | 
| +            userInfo:nil]; | 
| +    [[self contentView] addTrackingArea:[trackingArea autorelease]]; | 
| + | 
| +    [self disableCursorRects]; | 
| +    if (NSPointInRect([NSEvent mouseLocation], [self frame])) | 
| +      [[NSCursor pointingHandCursor] set]; | 
| + | 
| +    // Needs to be called after -disableCursorRects. | 
| +    [self setAcceptsMouseMovedEvents:YES]; | 
| + | 
| + | 
| +    // set up perspective | 
| +    CATransform3D t = CATransform3DIdentity; | 
| +    t.m34 = 1.0 / 1850; | 
| +    bgLayer_.sublayerTransform = t; | 
| + | 
| +#if 0 | 
| +    // Animate in. | 
| +    // FIXME: probably want to use a CABasicAnimation instead | 
| +    CATransition* animation = [CATransition animation]; | 
| +    //animation.type = kCATransitionPush; | 
| +    //animation.type = kCATransitionMoveIn; | 
| +    //animation.type = kCATransitionFade; | 
| +    animation.type = kCATransitionReveal; | 
| +    //animation.subtype = kCATransitionFromTop; | 
| +    //animation.delegate = self; | 
| +    animation.duration = 1.f * (slomo ? 4 : 1);  // half for in, half for out | 
| +    //[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; | 
| +    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]]; | 
| +    [bgLayer_ addAnimation:animation forKey:nil]; | 
| +#endif | 
| + | 
| +    // Count tabs. | 
| +//    Browser* browser = browserController->browser_.get(); | 
| +    TabStripController* tabStripController = browser_->tabStripController_.get(); | 
| +    TabStripModel* tabStripModel = tabStripController->tabStripModel_; | 
| +    NSArray* arrays = tabStripController->tabArray_.get(); | 
| +    NSSet* closing = tabStripController->closingControllers_.get(); | 
| + | 
| +    selectedLayer_ = nil; | 
| +    initiallySelectedIndex_ = selectedIndex_ = tabStripModel->selected_index(); | 
| + | 
| + | 
| +    int n = [arrays count] - [closing count]; | 
| + | 
| +    // 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)/. | 
| + | 
| +    int row = 0, col = 0, i = 0; | 
| + | 
| +    CGFloat a = NSWidth([contentArea frame]) / NSHeight([contentArea frame]); | 
| + | 
| +    // FIXME: need to audit everything to check where i want rects with and without bookmarks bar | 
| +    // (right now, detached bookmark bar screws this up a bit) | 
| +    int kOffsetTop = 30 + NSHeight([[browser_ window] frame]) - NSHeight([[browser_ tabContentArea] frame]); | 
| +//    if (browser_->bookmarkBarController_.get()->visualState_ == bookmarks::kDetachedState)  // FIXME: check for NULL? | 
| +//      kOffsetTop -= NSHeight([[browser_->bookmarkBarController_ view] frame]); | 
| +    int kOffsetLeft = 30; | 
| +    int kOffsetRight = 30; | 
| +    int kOffsetBottom = 30; | 
| +    int kPaddingX = 15; | 
| +    int kPaddingY = 10; | 
| + | 
| +    int extraOffsetX = 0; | 
| +    int extraOffsetY = 0; | 
| +    double w_c = NSWidth(frame) - kOffsetLeft - kOffsetRight; | 
| +    double h_c = NSHeight(frame) - kOffsetTop - kOffsetBottom; | 
| +    dx = kPaddingX; | 
| +    dy = kPaddingY; | 
| +    double fny = (sqrt(pow(n*(a*dy - dx), 2) + 4*n*a*(dx + w_c)*(dy + h_c)) - n*(a*dy - dx)) / (2*(dx + w_c)); | 
| +    ny = int(roundf(fny)); | 
| +    nx = int(ceilf(n / float(ny))); | 
| +    last_nx = n - nx * (ny - 1); | 
| +    w = floor((w_c + dx)/float(nx) - dx); | 
| +    h = floor((h_c + dy)/float(ny) - dy); | 
| +    if (a > w/float(h)) { | 
| +      h = w / a; | 
| +      extraOffsetY = (h_c - ((h + dy)*ny - dy))/2; | 
| +    } else { | 
| +      w = h * a; | 
| +      extraOffsetX = (w_c - ((w + dx)*nx - dx))/2; | 
| +    } | 
| + | 
| +    NSMutableArray* allLayers = [[NSMutableArray alloc] initWithCapacity:n]; | 
| +    allLayers_.reset(allLayers); | 
| +//    for (TabController* tab in arrays) { | 
| +    for (int ci = 0; ci < n; ++ci) { | 
| +#if 0 | 
| +      if ([closing containsObject:tab]) | 
| +        continue; | 
| + | 
| +      NSView* tabView = [tab view]; | 
| + | 
| +      CALayer* layer = [CALayer layer]; | 
| +      layer.backgroundColor = blue; | 
| +      layer.frame = NSRectToCGRect([[self contentView] convertRect:[tabView bounds] fromView:tabView]); | 
| +#endif | 
| +#if 0 | 
| +      // Get tab image. FIXME: on bg thread? | 
| +#if 0 | 
| +      // XXX: Doesn't work?! | 
| +      NSBitmapImageRep* imageRep = | 
| +          [tabView bitmapImageRepForCachingDisplayInRect:[tabView bounds]]; | 
| +      if ([imageRep bitmapData]) | 
| +        bzero([imageRep bitmapData], | 
| +              [imageRep bytesPerRow] * [imageRep pixelsHigh]); | 
| +      [tabView cacheDisplayInRect:[tabView bounds] toBitmapImageRep:imageRep]; | 
| +      CGImageRef image = CGImageCreateCopy([imageRep CGImage]); | 
| +      layer.contents = (id)image; | 
| +#elif 0 | 
| +      [tabView lockFocus]; | 
| +      NSBitmapImageRep* imageRep = | 
| +          [[[NSBitmapImageRep alloc] initWithFocusedViewRect:[tabView bounds]] autorelease]; | 
| +      [tabView unlockFocus]; | 
| +      CGImageRef image = CGImageCreateCopy([imageRep CGImage]); | 
| +      layer.contents = (id)image; | 
| +#else | 
| +      NSImage* nsi = [NSImage imageNamed:@"NSUserGroup"]; | 
| +      layer.contents = nsi; | 
| +#endif | 
| +//      CFRelease(image); | 
| +//      layer.contents = imageRep; | 
| + | 
| +//      NSImage *image = [[[NSImage alloc] initWithSize:[imageRep size]] autorelease]; | 
| +//      [image addRepresentation:imageRep]; | 
| +//      layer.contents = image; | 
| +#endif | 
| + | 
| +//      [rootLayer addSublayer:layer]; | 
| + | 
| +      CALayer* layer2; | 
| +      // FIXME: on bg thread; also call the other method; ; also do | 
| +      // CGLayer->CGImage conversion directly without going through skia, etc. | 
| +      TabContents* contents = tabStripModel->GetTabContentsAt(i); | 
| +      DCHECK(contents); | 
| +      RenderViewHost* rvh = contents->render_view_host(); | 
| +      RenderWidgetHost* rwh = rvh; | 
| +      DCHECK(rwh); | 
| + | 
| +      BackingStoreMac* backing_store = (BackingStoreMac*)rwh->GetBackingStore(false); | 
| +      if (backing_store) { | 
| +        // FIXME: looks like the backing store can go away while we're running :-/ | 
| +        layer2 = [[[BackingStoreLayer alloc] initWithBackingStore:backing_store] autorelease]; | 
| +        [layer2 setNeedsDisplay]; | 
| +      } else { | 
| +        layer2 = [CALayer layer]; | 
| +        // Set placeholder | 
| +#if 0 | 
| +        // Icon as placeholder (FIXME: load only once) | 
| +        int m = MIN(w, h); | 
| +        // Can't use nsimage_cache::ImageNamed(), because that uses | 
| +        // initWithContentsOfFile, which creates a cached representation at | 
| +        // 16x16 and then throws away the vector data. | 
| +        NSString* path = [mac_util::MainAppBundle() pathForImageResource:@"nav.pdf"]; | 
| +        NSPDFImageRep* pdfRep = [NSPDFImageRep imageRepWithContentsOfFile:path]; | 
| +        NSImage* pdfImage = [[[NSImage alloc] initWithSize:NSMakeSize(m, m)] autorelease]; | 
| +        [pdfImage addRepresentation:pdfRep]; | 
| +        // FIXME: if we don't leak this, the image rep frees its cgimage soon | 
| +        // and the CALayer crashes when it wants to draw. | 
| +        NSBitmapImageRep* bitmap = [[NSBitmapImageRep imageRepWithData:[pdfImage TIFFRepresentation]] retain];  // XXX | 
| +        layer2.contents = (id)[bitmap CGImage]; | 
| +#else | 
| +        // background color as placeholder | 
| +        layer2.backgroundColor = CGColorGetConstantColor(kCGColorWhite); | 
| +#endif | 
| +        helper_->LoadThumbnail(rwh, layer2, gfx::Size(w, h)); | 
| +      } | 
| +      layer2.contentsGravity = kCAGravityResizeAspect;  // for placeholder | 
| + | 
| +//      layer2.backgroundColor = blue; | 
| +      int x = kOffsetLeft + col*(w + kPaddingX) + extraOffsetX; | 
| +      if (row == ny - 1) { | 
| +        // last row | 
| +        x += (nx - last_nx) * (w + kPaddingX) / 2; | 
| +      } | 
| +      int y = kOffsetTop + row*(h + kPaddingY) + extraOffsetY; | 
| +      layer2.anchorPoint = CGPointMake(0.5, 0.5); | 
| + | 
| +//      NSBitmapImageRep* rep = (NSBitmapImageRep*)[img bestRepresentationForDevice:nil]; | 
| +//      layer2.contents = (id)[rep CGImage]; | 
| +//      layer2.contentsGravity = kCAGravityResizeAspect; | 
| + | 
| +//      layer2.shadowOpacity = 0.5;  // FIXME: Slows things down, even if disabled before animation runs | 
| +      layer2.shadowRadius = 10; | 
| +      layer2.shadowOffset = CGSizeMake(0, -10); | 
| +//      layer2.borderColor = blue; | 
| + | 
| + | 
| +      if (i == selectedIndex_) { | 
| +        // http://developer.apple.com/mac/library/qa/qa2008/qa1620.html | 
| +        // Prepare the animation from the old size to the new size | 
| +        CGRect oldBounds = NSRectToCGRect([contentArea frame]); | 
| +        CGRect newBounds = oldBounds; | 
| +        newBounds.size = CGSizeMake(w, h); | 
| +        CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"bounds"]; | 
| +        animation.fromValue = [NSValue valueWithRect:NSRectFromCGRect(oldBounds)]; | 
| +        animation.toValue = [NSValue valueWithRect:NSRectFromCGRect(newBounds)]; | 
| +        animation.duration = .25f * (slomo ? 4 : 1); | 
| +        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; | 
| + | 
| +        // Update the layer's bounds so the layer doesn't snap back when the animation completes. | 
| +        layer2.bounds = newBounds; | 
| + | 
| +        // Add the animation, overriding the implicit animation. | 
| +        [layer2 addAnimation:animation forKey:@"bounds"]; | 
| + | 
| +        // Prepare the animation from the current position to the new position | 
| +        NSPoint t = areaFrame.origin; | 
| + | 
| +        CGPoint opoint = CGPointMake(t.x + NSWidth(areaFrame)/2, t.y + NSHeight(areaFrame)/2); | 
| +        CGPoint point = CGPointMake(x + w/2, NSHeight(frame) - y - h + h/2); | 
| +        animation = [CABasicAnimation animationWithKeyPath:@"position"]; | 
| +        animation.fromValue = [NSValue valueWithPoint:NSPointFromCGPoint(opoint)]; | 
| +        animation.toValue = [NSValue valueWithPoint:NSPointFromCGPoint(point)]; | 
| +        animation.duration = .25f * (slomo ? 4 : 1); | 
| +        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; | 
| + | 
| +        // Update the layer's position so that the layer doesn't snap back when the animation completes. | 
| +        layer2.position = point; | 
| + | 
| +        // Add the animation, overriding the implicit animation. | 
| +        [layer2 addAnimation:animation forKey:@"position"]; | 
| + | 
| +        [rootLayer_ insertSublayer:layer2 above:bgLayer_]; | 
| +        selectedLayer_ = layer2; | 
| + | 
| +//        selectedLayer_.borderWidth = 5; | 
| +//        [selectedLayer_ setValue:[NSNumber numberWithFloat:S] forKey:@"transform.scale"]; | 
| +    [CATransaction begin]; | 
| +    [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; | 
| +        selectedLayer_.transform = CATransform3DMakeScale(S, S, 1); | 
| +    [CATransaction commit]; | 
| +      } else { | 
| +#if 0 | 
| +       CABasicAnimation* zoom = [CABasicAnimation animationWithKeyPath:@"transform.translation.z"]; | 
| +       [layer2 setValue:[NSNumber numberWithFloat:0] forKey:@"transform.translation.z"]; | 
| +       zoom.fromValue = [NSNumber numberWithFloat:1000]; | 
| +       zoom.toValue = [NSNumber numberWithFloat:0]; | 
| +       zoom.duration = .25f * (slomo ? 4 : 1); | 
| +       zoom.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; | 
| +       [layer2 addAnimation:zoom forKey:@"transform"]; | 
| +#else | 
| +       int sel_col = selectedIndex_ % nx; | 
| +       int sel_row = selectedIndex_ / nx; | 
| +       CGFloat r = NSWidth([contentArea frame]) / float(w); | 
| +       int ox = (NSWidth([contentArea frame]) + dx*r) * (col - sel_col); | 
| +       if (row == ny - 1) { | 
| +         // last row | 
| +         ox += (nx - last_nx) * (NSWidth([contentArea frame]) + dx*r) / 2; | 
| +       } | 
| +       if (sel_row == ny - 1) { | 
| +         // last row | 
| +         ox -= (nx - last_nx) * (NSWidth([contentArea frame]) + dx*r) / 2; | 
| +       } | 
| +       int oy = (NSHeight([contentArea frame]) + dy*r) * (row - sel_row); | 
| + | 
| +       CGRect oldBounds = NSRectToCGRect([contentArea frame]); | 
| +       CGRect newBounds = oldBounds; | 
| +       newBounds.size = CGSizeMake(w, h); | 
| +       CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"bounds"]; | 
| +       animation.fromValue = [NSValue valueWithRect:NSRectFromCGRect(oldBounds)]; | 
| +       animation.toValue = [NSValue valueWithRect:NSRectFromCGRect(newBounds)]; | 
| +       animation.duration = .25f * (slomo ? 4 : 1); | 
| +       animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; | 
| + | 
| +       // Update the layer's bounds so the layer doesn't snap back when the animation completes. | 
| +       layer2.bounds = newBounds; | 
| + | 
| +       // Add the animation, overriding the implicit animation. | 
| +       [layer2 addAnimation:animation forKey:@"bounds"]; | 
| + | 
| +       // Prepare the animation from the current position to the new position | 
| +       NSPoint t = [contentArea frame].origin; | 
| +       CGPoint opoint = CGPointMake(ox + t.x + NSWidth(areaFrame)/2, /*NSHeight(frame) - h*/- oy + t.y + NSHeight(areaFrame)/2); | 
| + | 
| +       CGPoint point = CGPointMake(x + w/2, NSHeight(frame) - y - h + h/2); | 
| +       animation = [CABasicAnimation animationWithKeyPath:@"position"]; | 
| +       animation.fromValue = [NSValue valueWithPoint:NSPointFromCGPoint(opoint)]; | 
| +       animation.toValue = [NSValue valueWithPoint:NSPointFromCGPoint(point)]; | 
| +       animation.duration = .25f * (slomo ? 4 : 1); | 
| +       animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; | 
| + | 
| +       // Update the layer's position so that the layer doesn't snap back when the animation completes. | 
| +       layer2.position = point; | 
| + | 
| +       // Add the animation, overriding the implicit animation. | 
| +       [layer2 addAnimation:animation forKey:@"position"]; | 
| +#endif | 
| + | 
| +        [bgLayer_ addSublayer:layer2]; | 
| +      } | 
| +      [allLayers addObject:layer2]; | 
| + | 
| + | 
| +      ++col; | 
| +      if (col == nx) { | 
| +        col = 0; | 
| +        ++row; | 
| +      } | 
| +      ++i; | 
| +    } | 
| } | 
| return self; | 
| } | 
| @@ -88,6 +589,9 @@ const int kTopGradientHeight  = 15; | 
| } | 
|  | 
| - (void)keyUp:(NSEvent*)event { | 
| +  if (state_ == kFadingOut) | 
| +    return; | 
| + | 
| NSString* characters = [event characters]; | 
| if ([characters length] < 1) | 
| return; | 
| @@ -98,19 +602,58 @@ const int kTopGradientHeight  = 15; | 
| case NSNewlineCharacter: | 
| case NSCarriageReturnCharacter: | 
| case ' ': | 
| +      [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0]; | 
| +      break; | 
| case '\e':  // Escape | 
| -      [self close]; | 
| +      selectedIndex_ = initiallySelectedIndex_; | 
| +      selectedLayer_ = [allLayers_ objectAtIndex:selectedIndex_]; | 
| +      [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0]; | 
| break; | 
| } | 
| } | 
|  | 
| +- (void)mouseMoved:(NSEvent*)event { | 
| +  int i = 0; | 
| +  CALayer* newSelectedLayer = nil; | 
| +  int newIndex = -1; | 
| +  CGPoint p = NSPointToCGPoint([event locationInWindow]); | 
| +  for (CALayer* layer in allLayers_.get()) { | 
| +    CGPoint lp = [layer convertPoint:p fromLayer:rootLayer_]; | 
| +    if ([layer containsPoint:lp]) { | 
| +      newSelectedLayer = layer; | 
| +      newIndex = i; | 
| +    } | 
| +    ++i; | 
| +  } | 
| + | 
| +  if (newSelectedLayer && newSelectedLayer != selectedLayer_) { | 
| +    CALayer* oldSelectedLayer = selectedLayer_; | 
| + | 
| +    [CATransaction begin]; | 
| +    [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; | 
| +    if (selectedLayer_) | 
| +      [bgLayer_ addSublayer:selectedLayer_]; | 
| +    selectedLayer_ = newSelectedLayer; | 
| +    [selectedLayer_ removeFromSuperlayer]; | 
| +    [rootLayer_ insertSublayer:selectedLayer_ above:bgLayer_]; | 
| +    selectedIndex_ = newIndex; | 
| +    [CATransaction commit]; | 
| + | 
| +    if (oldSelectedLayer) | 
| +      oldSelectedLayer.transform = CATransform3DIdentity; | 
| +    if (newSelectedLayer) | 
| +      newSelectedLayer.transform = CATransform3DMakeScale(S, S, 1); | 
| +  } | 
| +} | 
| + | 
| +// FIXME: do this on long hover, too | 
| - (void)mouseDown:(NSEvent*)event { | 
| - [self close]; | 
| +  [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0]; | 
| } | 
|  | 
| - (void)swipeWithEvent:(NSEvent*)event { | 
| if ([event deltaY] > 0.5)  // Swipe up | 
| -    [self close]; | 
| +    [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0]; | 
| } | 
|  | 
| - (void)close { | 
| @@ -128,4 +671,155 @@ const int kTopGradientHeight  = 15; | 
| return NO; | 
| } | 
|  | 
| + | 
| +- (void)cursorUpdate:(NSEvent*)event { | 
| +  [[NSCursor pointingHandCursor] set]; | 
| +} | 
| + | 
| +- (void)fadeAway:(BOOL)slomo { | 
| +  if (state_ == kFadingOut) | 
| +    return; | 
| + | 
| +  [[NSCursor arrowCursor] set]; | 
| + | 
| +  state_ = kFadingOut; | 
| +  [self setAcceptsMouseMovedEvents:NO]; | 
| + | 
| +  // Clean out observers, so that eventual pending thumbnail requests don't | 
| +  // resolve against dead objects. | 
| +  TabStripController* tabStripController = browser_->tabStripController_.get(); | 
| +  TabStripModel* tabStripModel = tabStripController->tabStripModel_; | 
| +  for (int i = 0; i < tabStripModel->count(); ++i) { | 
| +    TabContents* contents = tabStripModel->GetTabContentsAt(i); | 
| +    contents->render_view_host()->set_painting_observer(NULL); | 
| +  } | 
| + | 
| +  // Select chosen tab (FIXME: don't on ESC) | 
| +  if (selectedIndex_ >= 0) | 
| +    tabStripModel->SelectTabContentsAt(selectedIndex_, /*user_gesture=*/true); | 
| + | 
| +#if 1 | 
| +  // FIXME: this is only here so that we get a notification after 4 / 1 seconds. | 
| +  // Do that in a simpler way. | 
| +  CABasicAnimation* fade = [CABasicAnimation animationWithKeyPath:@"opacity"]; | 
| +  bgLayer_.opacity = 0.99;  // make it stick, change model value | 
| +  fade.fromValue = [NSNumber numberWithFloat:1]; | 
| +  fade.toValue = [NSNumber numberWithFloat:0.99]; | 
| +  fade.duration = .5f * (slomo ? 4 : 1); | 
| +  fade.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; | 
| +fade.delegate = self; | 
| +#endif | 
| + | 
| +  if (selectedIndex_ >= 0) { | 
| +    CALayer* layer = selectedLayer_; //[[bgLayer_ sublayers] objectAtIndex:selectedIndex_]; | 
| + | 
| +    [CATransaction begin]; | 
| +    [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; | 
| +//    layer.contentsGravity = kCAGravityResize; | 
| +//    layer.opaque = YES; | 
| +//    layer.compositingFilter = [CIFilter filterWithName:@"CISourceInCompositing"]; | 
| +    selectedLayer_.borderWidth = 0; | 
| + | 
| +    // Animating large shadows is slow -- don't. | 
| +    layer.shadowOpacity = 0; | 
| +    layer.transform = CATransform3DIdentity; | 
| + | 
| +    [layer removeFromSuperlayer]; | 
| +//    [rootLayer_ insertSublayer:layer above:bgLayer_]; | 
| +    [bgLayer_ addSublayer:layer];  // move on top of all other layers | 
| + | 
| +    [CATransaction commit]; | 
| + | 
| + | 
| + | 
| +    [CATransaction begin]; | 
| +    if (slomo) { | 
| +      CGFloat f = [[CATransaction valueForKey:kCATransactionAnimationDuration] floatValue]; | 
| +      f = 0.5;  // the above returns 0 :-P | 
| +      [CATransaction setValue:[NSNumber numberWithFloat:4*f] forKey:kCATransactionAnimationDuration]; | 
| +    } | 
| +    layer.frame = NSRectToCGRect([[browser_ tabContentArea] frame]); | 
| +    if ([layer isKindOfClass:[BackingStoreLayer class]]) | 
| +      [layer setNeedsDisplay];  // Redraw layer at big resolution, so that zoom-in isn't blocky. | 
| +//    layer.opacity = 0; | 
| +    [CATransaction commit]; | 
| +  } | 
| + | 
| +  int i = 0, col = 0, row = 0; | 
| +  for (CALayer* layer in allLayers_.get()) { | 
| +//    DCHECK_NE(layer, selectedLayer_); | 
| +//    if (layer == selectedLayer_) continue; | 
| +    if (i == selectedIndex_) { | 
| +      ++i; | 
| +      ++col; | 
| +      if (col == nx) { | 
| +        col = 0; | 
| +        ++row; | 
| +      } | 
| +      continue; | 
| +    } | 
| + | 
| +    // Animating large shadows is slow -- don't. | 
| +    [CATransaction begin]; | 
| +    [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; | 
| +    layer.shadowOpacity = 0; | 
| +    [CATransaction commit]; | 
| + | 
| +#if 0 | 
| +    CABasicAnimation* zoom = [CABasicAnimation animationWithKeyPath:@"transform.translation.z"]; | 
| +//    [layer setValue:[NSNumber numberWithFloat:1000] forKey:@"transform.translation.z"]; | 
| +    zoom.fromValue = [NSNumber numberWithFloat:0]; | 
| +    zoom.toValue = [NSNumber numberWithFloat:-1000];  // negative Z to keep them behind the selected layer | 
| +    zoom.duration = .5f * (slomo ? 4 : 1); | 
| +    zoom.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; | 
| +    [layer addAnimation:zoom forKey:@"transform"]; | 
| +#else | 
| +     int sel_col = selectedIndex_ % nx; | 
| +     int sel_row = selectedIndex_ / nx; | 
| +  NSLog(@"%d %d %d %d %d", col, row, sel_col, sel_row, selectedIndex_); | 
| +     CGFloat r = NSWidth([[browser_ tabContentArea] frame]) / float(w); | 
| +     int ox = (NSWidth([[browser_ tabContentArea] frame]) + dx*r) * (col - sel_col); | 
| +     if (row == ny - 1) { | 
| +       // last row | 
| +       ox += (nx - last_nx) * (NSWidth([[browser_ tabContentArea] frame]) + dx*r) / 2; | 
| +     } | 
| +     if (sel_row == ny - 1) { | 
| +       // last row | 
| +       ox -= (nx - last_nx) * (NSWidth([[browser_ tabContentArea] frame]) + dx*r) / 2; | 
| +     } | 
| +     int oy = (NSHeight([[browser_ tabContentArea] frame]) + dy*r) * (row - sel_row); | 
| + | 
| +     CGRect newFrame = NSRectToCGRect([[browser_ tabContentArea] frame]); | 
| +     newFrame.origin.x += ox; | 
| +     newFrame.origin.y -= oy; | 
| + | 
| +     [CATransaction begin]; | 
| +     if (slomo) { | 
| +       CGFloat f = 0.5; | 
| +       [CATransaction setValue:[NSNumber numberWithFloat:4*f] forKey:kCATransactionAnimationDuration]; | 
| +     } | 
| +     layer.frame = newFrame; | 
| +     [CATransaction commit]; | 
| + | 
| +     ++col; | 
| +     if (col == nx) { | 
| +       col = 0; | 
| +       ++row; | 
| +     } | 
| +#endif | 
| +    ++i; | 
| +  } | 
| + | 
| +  [bgLayer_ addAnimation:fade forKey:@"opacity"];  // override any implicit animation | 
| +} | 
| + | 
| +- (BOOL)windowShouldClose:(id)window { | 
| +  [self fadeAway:NO]; | 
| +  return NO; | 
| +} | 
| + | 
| +- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flag { | 
| +  [self close]; | 
| +} | 
| + | 
| @end | 
|  |