diff options
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/cocoa/browser_window_controller.mm | 5 | ||||
-rw-r--r-- | chrome/browser/cocoa/tabpose_window.h | 41 | ||||
-rw-r--r-- | chrome/browser/cocoa/tabpose_window.mm | 474 | ||||
-rw-r--r-- | chrome/browser/cocoa/tabpose_window_unittest.mm | 33 |
4 files changed, 531 insertions, 22 deletions
diff --git a/chrome/browser/cocoa/browser_window_controller.mm b/chrome/browser/cocoa/browser_window_controller.mm index 19b584d..d9373a8 100644 --- a/chrome/browser/cocoa/browser_window_controller.mm +++ b/chrome/browser/cocoa/browser_window_controller.mm @@ -1856,7 +1856,10 @@ willAnimateFromState:(bookmarks::VisualState)oldState activeArea.size.height += NSHeight([bookmarkBarView frame]); } - [TabposeWindow openTabposeFor:[self window] rect:activeArea slomo:slomo]; + [TabposeWindow openTabposeFor:[self window] + rect:activeArea + slomo:slomo + tabStripModel:browser_->tabstrip_model()]; } @end // @implementation BrowserWindowController(Fullscreen) diff --git a/chrome/browser/cocoa/tabpose_window.h b/chrome/browser/cocoa/tabpose_window.h index 0efb90c..3be0b32 100644 --- a/chrome/browser/cocoa/tabpose_window.h +++ b/chrome/browser/cocoa/tabpose_window.h @@ -10,12 +10,38 @@ #include "base/scoped_cftyperef.h" +#include "base/ref_counted.h" +#include "base/scoped_nsobject.h" +#include "base/scoped_ptr.h" +#include "base/scoped_vector.h" + +namespace tabpose { + +class Tile; +class TileSet; + +} + +namespace tabpose { + +enum WindowState { + kFadingIn, + kFadedIn, + kFadingOut, +}; + +} // namespace tabpose + +class TabStripModel; + // A TabposeWindow shows an overview of open tabs and lets the user select a new // active tab. The window blocks clicks on the tab strip and the download // shelf. Every open browser window has its own overlay, and they are // independent of each other. @interface TabposeWindow : NSWindow { @private + tabpose::WindowState state_; + // The root layer added to the content view. Covers the whole window. CALayer* rootLayer_; // weak @@ -23,14 +49,25 @@ CALayer* bgLayer_; // weak scoped_cftyperef<CGColorRef> gray_; + + TabStripModel* tabStripModel_; // weak + + // Stores all preview layers. The order in here matches the order in + // the tabstrip model. + scoped_nsobject<NSArray> allLayers_; + + // Manages the state of all layers. + scoped_ptr<tabpose::TileSet> tileSet_; } // Shows a TabposeWindow on top of |parent|, with |rect| being the active area. // If |slomo| is YES, then the appearance animation is shown in slow motion. // The window blocks all keyboard and mouse events and releases itself when // closed. -+ (id)openTabposeFor:(NSWindow*)parent rect:(NSRect)rect slomo:(BOOL)slomo; ++ (id)openTabposeFor:(NSWindow*)parent + rect:(NSRect)rect + slomo:(BOOL)slomo + tabStripModel:(TabStripModel*)tabStripModel; @end #endif // CHROME_BROWSER_COCOA_TABPOSE_WINDOW_H_ - diff --git a/chrome/browser/cocoa/tabpose_window.mm b/chrome/browser/cocoa/tabpose_window.mm index c69c5c1..1cad47a 100644 --- a/chrome/browser/cocoa/tabpose_window.mm +++ b/chrome/browser/cocoa/tabpose_window.mm @@ -6,8 +6,18 @@ #import <QuartzCore/QuartzCore.h> +#import "chrome/browser/cocoa/browser_window_controller.h" +#import "chrome/browser/cocoa/tab_strip_controller.h" + const int kTopGradientHeight = 15; +NSString* const kAnimationIdKey = @"AnimationId"; +NSString* const kAnimationIdFadeIn = @"FadeIn"; +NSString* const kAnimationIdFadeOut = @"FadeOut"; + +const CGFloat kDefaultAnimationDuration = 0.25; // In seconds. +const CGFloat kSlomoFactor = 4; + // CAGradientLayer is 10.6-only -- roll our own. @interface DarkGradientLayer : CALayer - (void)drawInContext:(CGContextRef)context; @@ -26,35 +36,356 @@ const int kTopGradientHeight = 15; } @end +namespace { + +class ScopedCAActionDisabler { + public: + ScopedCAActionDisabler() { + [CATransaction begin]; + [CATransaction setValue:[NSNumber numberWithBool:YES] + forKey:kCATransactionDisableActions]; + } + + ~ScopedCAActionDisabler() { + [CATransaction commit]; + } +}; + +class ScopedCAActionSetDuration { + public: + explicit ScopedCAActionSetDuration(CGFloat duration) { + [CATransaction begin]; + [CATransaction setValue:[NSNumber numberWithFloat:duration] + forKey:kCATransactionAnimationDuration]; + } + + ~ScopedCAActionSetDuration() { + [CATransaction commit]; + } +}; + +} // namespace + +// Given the number |n| of tiles with a desired aspect ratio of |a| and a +// desired distance |dx|, |dy| between tiles, returns how many tiles fit +// vertically into a rectangle with the dimensions |w_c|, |h_c|. This returns +// an exact solution, which is usually a fractional number. +static float FitNRectsWithAspectIntoBoundingSizeWithConstantPadding( + int n, double a, int w_c, int h_c, int dx, int dy) { + // We want to have the small rects have the same aspect ratio a as a full + // tab. Let w, h be the size of a small rect, and w_c, h_c the size of the + // container. dx, dy are the distances between small rects in x, y direction. + + // Geometry yields: + // w_c = nx * (w + dx) - dx <=> w = (w_c + d_x) / nx - d_x + // h_c = ny * (h + dy) - dy <=> h = (h_c + d_y) / ny - d_t + // Plugging this into + // a := tab_width / tab_height = w / h + // yields + // a = ((w_c - (nx - 1)*d_x)*ny) / (nx*(h_c - (ny - 1)*d_y)) + // Plugging in nx = n/ny and pen and paper (or wolfram alpha: + // http://www.wolframalpha.com/input/?i=(-sqrt((d+n-a+f+n)^2-4+(a+f%2Ba+h)+(-d+n-n+w))%2Ba+f+n-d+n)/(2+a+(f%2Bh)) , (solution for nx) + // http://www.wolframalpha.com/input/?i=+(-sqrt((a+f+n-d+n)^2-4+(d%2Bw)+(-a+f+n-a+h+n))-a+f+n%2Bd+n)/(2+(d%2Bw)) , (solution for ny) + // ) gives us nx and ny (but the wrong root -- s/-sqrt(FOO)/sqrt(FOO)/. + + // This function returns ny. + return (sqrt(pow(n * (a * dy - dx), 2) + + 4 * n * a * (dx + w_c) * (dy + h_c)) - + n * (a * dy - dx)) + / + (2 * (dx + w_c)); +} + +namespace tabpose { + +// A tile is what is shown for a single tab in tabpose mode. It consists of a +// title, favicon, thumbnail image, and pre- and postanimation rects. +// TODO(thakis): Right now, it only consists of a thumb rect. +class Tile { + public: + // 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_; } + + private: + friend class TileSet; + + // The thumb rect includes infobars, detached thumbnail bar, web contents, + // and devtools. + NSRect thumb_rect_; + NSRect start_thumb_rect_; +}; + +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; +} + +// A tileset is responsible for owning and laying out all |Tile|s shown in a +// tabpose window. +class TileSet { + public: + // 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); + void ResetSelectedIndex() { selected_index_ = initial_index_; } + + const Tile& selected_tile() const { return tiles_[selected_index()]; } + const Tile& tile_at(int index) const { return tiles_[index]; } + + private: + std::vector<Tile> tiles_; // Doesn't change often, hence values are fine. + + int selected_index_; + int initial_index_; +}; + +void TileSet::Build(TabStripModel* source_model) { + selected_index_ = initial_index_ = source_model->selected_index(); + tiles_.resize(source_model->count()); +} + +void TileSet::Layout(NSRect containing_rect) { + int tile_count = tiles_.size(); + + // Room around the tiles insde of |containing_rect|. + const int kSmallPaddingTop = 30; + const int kSmallPaddingLeft = 30; + const int kSmallPaddingRight = 30; + const int kSmallPaddingBottom = 30; + + // Room between the tiles. + const int kSmallPaddingX = 15; + const int kSmallPaddingY = 13; + + // 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, + kSmallPaddingX, kSmallPaddingY); + int count_y(roundf(fny)); + int count_x(ceilf(tile_count / float(count_y))); + int last_row_count_x = tile_count - count_x * (count_y - 1); + + // 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) / float(count_x) - + kSmallPaddingX); + int small_height = + floor((container_height + kSmallPaddingY) / float(count_y) - + kSmallPaddingY); + + // |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/float(small_height)) { + small_height = small_width / aspect; + CGFloat all_tiles_height = + (small_height + kSmallPaddingY) * count_y - kSmallPaddingY; + 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) / float(small_width); + CGFloat big_padding_x = kSmallPaddingX * scale_small_to_big; + CGFloat big_padding_y = kSmallPaddingY * 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) + + kSmallPaddingTop + smallExtraPaddingTop; + + tiles_[i].thumb_rect_.origin = NSMakePoint( + small_x, NSHeight(containing_rect) - small_y - small_height); + + // Compute the big, pre-zoom thumbnail rect. + tiles_[i].start_thumb_rect_.size = containing_rect.size; + + int big_x = col * (NSWidth(containing_rect) + big_padding_x); + int big_y = row * (NSHeight(containing_rect) + big_padding_y); + tiles_[i].start_thumb_rect_.origin = NSMakePoint(big_x, -big_y); + } + } + + // Go through last row and center it: + // X X X X + // X X X X + // X X + int last_row_empty_tiles_x = count_x - last_row_count_x; + CGFloat small_last_row_shift_x = + last_row_empty_tiles_x * (small_width + kSmallPaddingX) / 2; + CGFloat big_last_row_shift_x = + last_row_empty_tiles_x * (NSWidth(containing_rect) + big_padding_x) / 2; + for (int i = tile_count - last_row_count_x; i < tile_count; ++i) { + tiles_[i].thumb_rect_.origin.x += small_last_row_shift_x; + tiles_[i].start_thumb_rect_.origin.x += big_last_row_shift_x; + } +} + +void TileSet::set_selected_index(int index) { + CHECK_GE(index, 0); + CHECK_LT(index, static_cast<int>(tiles_.size())); + selected_index_ = index; +} + +} // namespace tabpose + +void AnimateCALayerFrameFromTo( + CALayer* layer, const NSRect& from, const NSRect& to, + NSTimeInterval duration, id boundsAnimationDelegate) { + // http://developer.apple.com/mac/library/qa/qa2008/qa1620.html + CABasicAnimation* animation; + + animation = [CABasicAnimation animationWithKeyPath:@"bounds"]; + animation.fromValue = [NSValue valueWithRect:from]; + animation.toValue = [NSValue valueWithRect:to]; + animation.duration = duration; + animation.timingFunction = + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; + animation.delegate = boundsAnimationDelegate; + + // Update the layer's bounds so the layer doesn't snap back when the animation + // completes. + layer.bounds = NSRectToCGRect(to); + + // Add the animation, overriding the implicit animation. + [layer addAnimation:animation forKey:@"bounds"]; + + // Prepare the animation from the current position to the new position. + NSPoint opoint = from.origin; + NSPoint point = to.origin; + + // Adapt to anchorPoint. + opoint.x += NSWidth(from) * layer.anchorPoint.x; + opoint.y += NSHeight(from) * layer.anchorPoint.y; + point.x += NSWidth(to) * layer.anchorPoint.x; + point.y += NSHeight(to) * layer.anchorPoint.y; + + animation = [CABasicAnimation animationWithKeyPath:@"position"]; + animation.fromValue = [NSValue valueWithPoint:opoint]; + animation.toValue = [NSValue valueWithPoint:point]; + animation.duration = duration; + animation.timingFunction = + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; + + // Update the layer's position so that the layer doesn't snap back when the + // animation completes. + layer.position = NSPointToCGPoint(point); + + // Add the animation, overriding the implicit animation. + [layer addAnimation:animation forKey:@"position"]; +} + @interface TabposeWindow (Private) -- (id)initForWindow:(NSWindow*)parent rect:(NSRect)rect slomo:(BOOL)slomo; -- (void)setUpLayers:(NSRect)bgLayerRect; +- (id)initForWindow:(NSWindow*)parent + rect:(NSRect)rect + slomo:(BOOL)slomo + tabStripModel:(TabStripModel*)tabStripModel; +- (void)setUpLayers:(NSRect)bgLayerRect slomo:(BOOL)slomo; +- (void)fadeAway:(BOOL)slomo; +- (void)selectTileAtIndex:(int)newIndex; @end @implementation TabposeWindow -+ (id)openTabposeFor:(NSWindow*)parent rect:(NSRect)rect slomo:(BOOL)slomo { ++ (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]; + return [[TabposeWindow alloc] + initForWindow:parent rect:rect slomo:slomo tabStripModel:tabStripModel]; } -- (id)initForWindow:(NSWindow*)parent rect:(NSRect)rect slomo:(BOOL)slomo { +- (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])) { + // TODO(thakis): Add a TabStripModelObserver to |tabStripModel_|. + tabStripModel_ = tabStripModel; + state_ = tabpose::kFadingIn; + tileSet_.reset(new tabpose::TileSet); [self setReleasedWhenClosed:YES]; [self setOpaque:NO]; [self setBackgroundColor:[NSColor clearColor]]; - [self setUpLayers:rect]; + [self setUpLayers:rect slomo:slomo]; + [self setAcceptsMouseMovedEvents:YES]; [parent addChildWindow:self ordered:NSWindowAbove]; [self makeKeyAndOrderFront:self]; } return self; } -- (void)setUpLayers:(NSRect)bgLayerRect { +- (CALayer*)selectedLayer { + return [allLayers_ objectAtIndex:tileSet_->selected_index()]; +} + +- (void)selectTileAtIndex:(int)newIndex { + // TODO(thakis): Have a nicer indicator for the current selection + // (a blue outline, probably). + int oldIndex = tileSet_->selected_index(); + CALayer* oldSelectedLayer = [allLayers_ objectAtIndex:oldIndex]; + oldSelectedLayer.backgroundColor = CGColorGetConstantColor(kCGColorBlack); + CALayer* newSelectedLayer = [allLayers_ objectAtIndex:newIndex]; + newSelectedLayer.backgroundColor = CGColorGetConstantColor(kCGColorWhite); + + tileSet_->set_selected_index(newIndex); +} + +- (void)setUpLayers:(NSRect)bgLayerRect slomo:(BOOL)slomo { // Root layer -- covers whole window. rootLayer_ = [CALayer layer]; [[self contentView] setLayer:rootLayer_]; @@ -77,6 +408,41 @@ const int kTopGradientHeight = 15; kTopGradientHeight); [gradientLayer setNeedsDisplay]; // Draw once. [bgLayer_ addSublayer:gradientLayer]; + + // Layers for the tab thumbnails. + tileSet_->Build(tabStripModel_); + tileSet_->Layout(bgLayerRect); + + allLayers_.reset( + [[NSMutableArray alloc] initWithCapacity:tabStripModel_->count()]); + for (int i = 0; i < tabStripModel_->count(); ++i) { + CALayer* layer = [CALayer layer]; + + // Background color as placeholder for now. + layer.backgroundColor = CGColorGetConstantColor(kCGColorBlack); + + AnimateCALayerFrameFromTo( + layer, + tileSet_->tile_at(i).GetStartRectRelativeTo(tileSet_->selected_tile()), + tileSet_->tile_at(i).thumb_rect(), + kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1), + i == tileSet_->selected_index() ? self : nil); + + // Add a delegate to one of the animations to get a notification once the + // animations are done. + if (i == tileSet_->selected_index()) { + CAAnimation* animation = [layer animationForKey:@"bounds"]; + DCHECK(animation); + [animation setValue:kAnimationIdFadeIn forKey:kAnimationIdKey]; + } + + layer.shadowRadius = 10; + layer.shadowOffset = CGSizeMake(0, -10); + + [bgLayer_ addSublayer:layer]; + [allLayers_ addObject:layer]; + } + [self selectTileAtIndex:tileSet_->selected_index()]; } - (BOOL)canBecomeKeyWindow { @@ -88,6 +454,9 @@ const int kTopGradientHeight = 15; } - (void)keyUp:(NSEvent*)event { + if (state_ == tabpose::kFadingOut) + return; + NSString* characters = [event characters]; if ([characters length] < 1) return; @@ -98,19 +467,36 @@ const int kTopGradientHeight = 15; case NSNewlineCharacter: case NSCarriageReturnCharacter: case ' ': + [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0]; + break; case '\e': // Escape - [self close]; + tileSet_->ResetSelectedIndex(); + [self fadeAway:([event modifierFlags] & NSShiftKeyMask) != 0]; break; + // TODO(thakis): Support moving the selection via arrow keys. } } +- (void)mouseMoved:(NSEvent*)event { + int newIndex = -1; + CGPoint p = NSPointToCGPoint([event locationInWindow]); + for (NSUInteger i = 0; i < [allLayers_ count]; ++i) { + CALayer* layer = [allLayers_ objectAtIndex:i]; + CGPoint lp = [layer convertPoint:p fromLayer:rootLayer_]; + if ([static_cast<CALayer*>([layer presentationLayer]) containsPoint:lp]) + newIndex = i; + } + if (newIndex >= 0) + [self selectTileAtIndex:newIndex]; +} + - (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 +514,72 @@ const int kTopGradientHeight = 15; return NO; } +- (void)fadeAway:(BOOL)slomo { + if (state_ == tabpose::kFadingOut) + return; + + state_ = tabpose::kFadingOut; + [self setAcceptsMouseMovedEvents:NO]; + + // Select chosen tab. + tabStripModel_->SelectTabContentsAt(tileSet_->selected_index(), + /*user_gesture=*/true); + + { + ScopedCAActionDisabler disableCAActions; + + // Move the selected layer on top of all other layers. + [self selectedLayer].zPosition = 1; + + // Running animations with shadows is slow, so turn shadows off before + // running the exit animation. + for (CALayer* layer in allLayers_.get()) + layer.shadowOpacity = 0.0; + } + + // Animate layers out, all in one transaction. + CGFloat duration = kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1); + ScopedCAActionSetDuration durationSetter(duration); + for (NSUInteger i = 0; i < [allLayers_ count]; ++i) { + CALayer* layer = [allLayers_ objectAtIndex:i]; + // |start_thumb_rect_| was relative to |initial_index_|, now this needs to + // be relative to |selectedIndex_| (whose start rect was relative to + // |initial_index_| too) + CGRect newFrame = NSRectToCGRect( + tileSet_->tile_at(i).GetStartRectRelativeTo(tileSet_->selected_tile())); + + // Add a delegate to one of the implicit animations to get a notification + // once the animations are done. + if (static_cast<int>(i) == tileSet_->selected_index()) { + CAAnimation* animation = [CAAnimation animation]; + animation.delegate = self; + [animation setValue:kAnimationIdFadeOut forKey:kAnimationIdKey]; + [layer addAnimation:animation forKey:@"frame"]; + } + + layer.frame = newFrame; + } +} + +- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished { + NSString* animationId = [animation valueForKey:kAnimationIdKey]; + if ([animationId isEqualToString:kAnimationIdFadeIn]) { + if (finished) { + // If the user clicks while the fade in animation is still running, + // |state_| is already kFadingOut. In that case, don't do anything. + DCHECK_EQ(tabpose::kFadingIn, state_); + state_ = tabpose::kFadedIn; + + // Running animations with shadows is slow, so turn shadows on only after + // the animation is done. + ScopedCAActionDisabler disableCAActions; + for (CALayer* layer in allLayers_.get()) + layer.shadowOpacity = 0.5; + } + } else if ([animationId isEqualToString:kAnimationIdFadeOut]) { + DCHECK_EQ(tabpose::kFadingOut, state_); + [self close]; + } +} + @end diff --git a/chrome/browser/cocoa/tabpose_window_unittest.mm b/chrome/browser/cocoa/tabpose_window_unittest.mm index 8f6f239..ad10250 100644 --- a/chrome/browser/cocoa/tabpose_window_unittest.mm +++ b/chrome/browser/cocoa/tabpose_window_unittest.mm @@ -4,9 +4,11 @@ #import "chrome/browser/cocoa/tabpose_window.h" -#include "base/scoped_nsobject.h" +#import "chrome/browser/browser_window.h" #import "chrome/browser/cocoa/browser_test_helper.h" #import "chrome/browser/cocoa/cocoa_test_helper.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/browser/tab_contents/tab_contents.h" #include "testing/gtest/include/gtest/gtest.h" class TabposeWindowTest : public CocoaTest { @@ -16,20 +18,33 @@ class TabposeWindowTest : public CocoaTest { // Check that this doesn't leak. TEST_F(TabposeWindowTest, TestShow) { - scoped_nsobject<NSWindow> parent([[NSWindow alloc] - initWithContentRect:NSMakeRect(100, 200, 300, 200) - styleMask:NSTitledWindowMask - backing:NSBackingStoreBuffered - defer:NO]); + Browser* browser = browser_helper_.browser(); + BrowserWindow* browser_window = browser_helper_.CreateBrowserWindow(); + NSWindow* parent = browser_window->GetNativeHandle(); + [parent orderFront:nil]; EXPECT_TRUE([parent isVisible]); + // Add a few tabs to the tab strip model. + TabStripModel* model = browser->tabstrip_model(); + SiteInstance* instance = + SiteInstance::CreateSiteInstance(browser_helper_.profile()); + for (int i = 0; i < 3; ++i) { + TabContents* tab_contents = + new TabContents(browser_helper_.profile(), instance, MSG_ROUTING_NONE, + NULL); + model->AppendTabContents(tab_contents, /*foreground=*/true); + } + base::ScopedNSAutoreleasePool pool; TabposeWindow* window = - [TabposeWindow openTabposeFor:parent.get() - rect:NSMakeRect(10, 20, 50, 60) - slomo:NO]; + [TabposeWindow openTabposeFor:parent + rect:NSMakeRect(10, 20, 250, 160) + slomo:NO + tabStripModel:model]; // Should release the window. [window mouseDown:nil]; + + browser_helper_.CloseBrowserWindow(); } |