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 |