From 2b911c89c6564db12888e3d40b8ba30f7ea3486f Mon Sep 17 00:00:00 2001 From: "thakis@chromium.org" <thakis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> Date: Wed, 15 Dec 2010 21:48:33 +0000 Subject: [PATCH] Mac: zoom title and favicon during tabpose zoom animation BUG=50307 TEST=Zoom out in slomo. Favicon and tab titles should fly in like the thumbnails. Zoom back in, they should still look correct. Review URL: http://codereview.chromium.org/5764002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@69323 0039d316-1c4b-4281-b951-d872f2087c98 --- chrome/browser/ui/cocoa/tabpose_window.mm | 150 +++++++++++++++------- 1 file changed, 106 insertions(+), 44 deletions(-) diff --git a/chrome/browser/ui/cocoa/tabpose_window.mm b/chrome/browser/ui/cocoa/tabpose_window.mm index 76865815c5d9b..7f96f5945505a 100644 --- a/chrome/browser/ui/cocoa/tabpose_window.mm +++ b/chrome/browser/ui/cocoa/tabpose_window.mm @@ -369,6 +369,17 @@ static float FitNRectsWithAspectIntoBoundingSizeWithConstantPadding( namespace tabpose { +CGFloat ScaleWithOrigin(CGFloat x, CGFloat origin, CGFloat scale) { + return (x - origin) * scale + origin; +} + +NSRect ScaleRectWithOrigin(NSRect r, NSPoint p, CGFloat scale) { + return NSMakeRect(ScaleWithOrigin(NSMinX(r), p.x, scale), + ScaleWithOrigin(NSMinY(r), p.y, scale), + NSWidth(r) * scale, + NSHeight(r) * scale); +} + // A tile is what is shown for a single tab in tabpose mode. It consists of a // title, favicon, thumbnail image, and pre- and postanimation rects. class Tile { @@ -432,7 +443,9 @@ NSRect Tile::GetStartRectRelativeTo(const Tile& tile) const { NSRect Tile::GetFaviconStartRectRelativeTo(const Tile& tile) const { NSRect thumb_start = GetStartRectRelativeTo(tile); - NSRect rect = favicon_rect_; + CGFloat scale_to_start = NSWidth(thumb_start) / NSWidth(thumb_rect_); + NSRect rect = + ScaleRectWithOrigin(favicon_rect_, thumb_rect_.origin, scale_to_start); rect.origin.x += NSMinX(thumb_start) - NSMinX(thumb_rect_); rect.origin.y += NSMinY(thumb_start) - NSMinY(thumb_rect_); return rect; @@ -449,7 +462,9 @@ SkBitmap Tile::favicon() const { NSRect Tile::GetTitleStartRectRelativeTo(const Tile& tile) const { NSRect thumb_start = GetStartRectRelativeTo(tile); - NSRect rect = title_rect_; + CGFloat scale_to_start = NSWidth(thumb_start) / NSWidth(thumb_rect_); + NSRect rect = + ScaleRectWithOrigin(title_rect_, thumb_rect_.origin, scale_to_start); rect.origin.x += NSMinX(thumb_start) - NSMinX(thumb_rect_); rect.origin.y += NSMinY(thumb_start) - NSMinY(thumb_rect_); return rect; @@ -826,8 +841,10 @@ void TileSet::MoveTileFromTo(int from_index, int to_index) { } // namespace tabpose -void AnimateCALayerFrameFromTo( - CALayer* layer, const NSRect& from, const NSRect& to, +void AnimateScaledCALayerFrameFromTo( + CALayer* layer, + const NSRect& from, CGFloat from_scale, + const NSRect& to, CGFloat to_scale, NSTimeInterval duration, id boundsAnimationDelegate) { // http://developer.apple.com/mac/library/qa/qa2008/qa1620.html CABasicAnimation* animation; @@ -852,10 +869,10 @@ void AnimateCALayerFrameFromTo( 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; + opoint.x += NSWidth(from) * from_scale * layer.anchorPoint.x; + opoint.y += NSHeight(from) * from_scale * layer.anchorPoint.y; + point.x += NSWidth(to) * to_scale * layer.anchorPoint.x; + point.y += NSHeight(to) * to_scale * layer.anchorPoint.y; animation = [CABasicAnimation animationWithKeyPath:@"position"]; animation.fromValue = [NSValue valueWithPoint:opoint]; @@ -872,6 +889,13 @@ void AnimateCALayerFrameFromTo( [layer addAnimation:animation forKey:@"position"]; } +void AnimateCALayerFrameFromTo( + CALayer* layer, const NSRect& from, const NSRect& to, + NSTimeInterval duration, id boundsAnimationDelegate) { + AnimateScaledCALayerFrameFromTo( + layer, from, 1.0, to, 1.0, duration, boundsAnimationDelegate); +} + void AnimateCALayerOpacityFromTo( CALayer* layer, double from, double to, NSTimeInterval duration) { CABasicAnimation* animation; @@ -1015,15 +1039,34 @@ void AnimateCALayerOpacityFromTo( [bgLayer_ addSublayer:faviconLayer]; [allFaviconLayers_ addObject:faviconLayer]; + // CATextLayers can't animate their fontSize property, at least on 10.5. + // Animate transform.scale instead. + + // The scaling should have its origin in the layer's upper left corner. + // This needs to be set before |AnimateCALayerFrameFromTo()| is called. CATextLayer* titleLayer = [CATextLayer layer]; + titleLayer.anchorPoint = CGPointMake(0, 1); if (showZoom) { - AnimateCALayerFrameFromTo( - titleLayer, - tile.GetTitleStartRectRelativeTo(tileSet_->selected_tile()), - tile.title_rect(), - interval, - nil); - AnimateCALayerOpacityFromTo(titleLayer, 0.0, 1.0, interval); + NSRect fromRect = + tile.GetTitleStartRectRelativeTo(tileSet_->selected_tile()); + NSRect toRect = tile.title_rect(); + CGFloat scale = NSWidth(fromRect) / NSWidth(toRect); + fromRect.size = toRect.size; + + // Add scale animation. + CABasicAnimation* scaleAnimation = + [CABasicAnimation animationWithKeyPath:@"transform.scale"]; + scaleAnimation.fromValue = [NSNumber numberWithDouble:scale]; + scaleAnimation.toValue = [NSNumber numberWithDouble:1.0]; + scaleAnimation.duration = interval; + scaleAnimation.timingFunction = + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; + [titleLayer addAnimation:scaleAnimation forKey:@"transform.scale"]; + + // Add the position and opacity animations. + AnimateScaledCALayerFrameFromTo( + titleLayer, fromRect, scale, toRect, 1.0, interval, nil); + AnimateCALayerOpacityFromTo(faviconLayer, 0.0, 1.0, interval); } else { titleLayer.frame = NSRectToCGRect(tile.title_rect()); } @@ -1239,6 +1282,52 @@ void AnimateCALayerOpacityFromTo( return action == @selector(commandDispatch:) && tag == IDC_TABPOSE; } +- (void)fadeAwayTileAtIndex:(int)index { + const tabpose::Tile& tile = tileSet_->tile_at(index); + CALayer* layer = [allThumbnailLayers_ objectAtIndex:index]; + // Add a delegate to one of the implicit animations to get a notification + // once the animations are done. + if (static_cast<int>(index) == tileSet_->selected_index()) { + CAAnimation* animation = [CAAnimation animation]; + animation.delegate = self; + [animation setValue:kAnimationIdFadeOut forKey:kAnimationIdKey]; + [layer addAnimation:animation forKey:@"frame"]; + } + + // Thumbnail. + layer.frame = NSRectToCGRect( + tile.GetStartRectRelativeTo(tileSet_->selected_tile())); + + if (static_cast<int>(index) == tileSet_->selected_index()) { + // Redraw layer at big resolution, so that zoom-in isn't blocky. + [layer setNeedsDisplay]; + } + + // Title. + CALayer* faviconLayer = [allFaviconLayers_ objectAtIndex:index]; + faviconLayer.frame = NSRectToCGRect( + tile.GetFaviconStartRectRelativeTo(tileSet_->selected_tile())); + faviconLayer.opacity = 0; + + // Favicon. + // The |fontSize| cannot be animated directly, animate the layer's scale + // instead. |transform.scale| affects the rendered width, so keep the small + // bounds. + CALayer* titleLayer = [allTitleLayers_ objectAtIndex:index]; + NSRect titleRect = tile.title_rect(); + NSRect titleToRect = + tile.GetTitleStartRectRelativeTo(tileSet_->selected_tile()); + CGFloat scale = NSWidth(titleToRect) / NSWidth(titleRect); + titleToRect.origin.x += + NSWidth(titleRect) * scale * titleLayer.anchorPoint.x; + titleToRect.origin.y += + NSHeight(titleRect) * scale * titleLayer.anchorPoint.y; + titleLayer.position = NSPointToCGPoint(titleToRect.origin); + [titleLayer setValue:[NSNumber numberWithDouble:scale] + forKeyPath:@"transform.scale"]; + titleLayer.opacity = 0; +} + - (void)fadeAway:(BOOL)slomo { if (state_ == tabpose::kFadingOut) return; @@ -1271,35 +1360,8 @@ void AnimateCALayerOpacityFromTo( CGFloat duration = 1.3 * kDefaultAnimationDuration * (slomo ? kSlomoFactor : 1); ScopedCAActionSetDuration durationSetter(duration); - for (int i = 0; i < tabStripModel_->count(); ++i) { - const tabpose::Tile& tile = tileSet_->tile_at(i); - CALayer* layer = [allThumbnailLayers_ objectAtIndex:i]; - // 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 = NSRectToCGRect( - tile.GetStartRectRelativeTo(tileSet_->selected_tile())); - - if (static_cast<int>(i) == tileSet_->selected_index()) { - // Redraw layer at big resolution, so that zoom-in isn't blocky. - [layer setNeedsDisplay]; - } - - CALayer* faviconLayer = [allFaviconLayers_ objectAtIndex:i]; - faviconLayer.frame = NSRectToCGRect( - tile.GetFaviconStartRectRelativeTo(tileSet_->selected_tile())); - faviconLayer.opacity = 0; - CALayer* titleLayer = [allTitleLayers_ objectAtIndex:i]; - titleLayer.frame = NSRectToCGRect( - tile.GetTitleStartRectRelativeTo(tileSet_->selected_tile())); - titleLayer.opacity = 0; - } + for (int i = 0; i < tabStripModel_->count(); ++i) + [self fadeAwayTileAtIndex:i]; } - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)finished { -- GitLab