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