item_drag_controller.mm revision 68043e1e95eeb07d5cae7aca370b26518b0867d6
1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "ui/app_list/cocoa/item_drag_controller.h"
6
7#include "base/logging.h"
8#import "ui/app_list/cocoa/apps_grid_view_item.h"
9#include "ui/base/cocoa/window_size_constants.h"
10
11// Scale to transform the grid cell when a drag starts. Note that 1.5 ensures
12// that integers are used for the layer bounds when the grid cell dimensions
13// are even.
14const CGFloat kDraggingIconScale = 1.5;
15
16const NSTimeInterval kAnimationDuration = 0.2;
17
18
19@interface ItemDragController ()
20
21- (void)animateTransformFrom:(CATransform3D)fromValue
22                 useDelegate:(BOOL)useDelegate;
23
24- (void)clearAnimations;
25
26@end
27
28@implementation ItemDragController
29
30- (id)initWithGridCellSize:(NSSize)size {
31  if ((self = [super init])) {
32    NSRect frameRect = NSMakeRect(0,
33                                  0,
34                                  size.width * kDraggingIconScale,
35                                  size.height * kDraggingIconScale);
36    base::scoped_nsobject<NSView> dragView(
37        [[NSView alloc] initWithFrame:frameRect]);
38    [dragView setWantsLayer:YES];
39    [dragView setHidden:YES];
40
41    dragLayer_.reset([[CALayer layer] retain]);
42    [dragLayer_ setFrame:NSRectToCGRect(frameRect)];
43    [[dragView layer] addSublayer:dragLayer_];
44
45    [self setView:dragView];
46  }
47  return self;
48}
49
50- (void)initiate:(AppsGridViewItem*)item
51    mouseDownLocation:(NSPoint)mouseDownLocation
52      currentLocation:(NSPoint)currentLocation
53            timestamp:(NSTimeInterval)eventTimestamp {
54  [self clearAnimations];
55  NSView* itemView = [item view];
56  NSPoint pointInGridCell = [itemView convertPoint:mouseDownLocation
57                                          fromView:nil];
58  mouseOffset_ = NSMakePoint(pointInGridCell.x - NSMidX([itemView bounds]),
59                             NSMidY([itemView bounds]) - pointInGridCell.y);
60
61  NSBitmapImageRep* imageRep = [item dragRepresentationForRestore:NO];
62  [dragLayer_ setContents:reinterpret_cast<id>([imageRep CGImage])];
63  [dragLayer_ setTransform:CATransform3DIdentity];
64
65  // Add a grow animation to the layer.
66  CATransform3D growFrom = CATransform3DScale(CATransform3DIdentity,
67                                              1.0 / kDraggingIconScale,
68                                              1.0 / kDraggingIconScale,
69                                              1.0);
70  [self animateTransformFrom:growFrom
71                 useDelegate:NO];
72
73  growStart_ = eventTimestamp;
74  [[self view] setHidden:NO];
75}
76
77- (void)update:(NSPoint)currentLocation
78     timestamp:(NSTimeInterval)eventTimestamp {
79  NSPoint pointInSuperview = [[[self view] superview]
80      convertPoint:currentLocation
81          fromView:nil];
82  NSRect rect = [[self view] bounds];
83  NSPoint anchor = NSMakePoint(NSMidX(rect), NSMidY(rect));
84
85  // If the grow animation is still in progress, make the point of the image
86  // that was clicked appear stuck to the mouse cursor.
87  CGFloat progress = (eventTimestamp - growStart_) / kAnimationDuration;
88  CGFloat currentIconScale = progress < 1.0 ?
89      1.0 + (kDraggingIconScale - 1.0) * progress :
90      kDraggingIconScale;
91
92  pointInSuperview.x -= (mouseOffset_.x * currentIconScale + anchor.x);
93  pointInSuperview.y -= (mouseOffset_.y * currentIconScale + anchor.y);
94  [[self view] setFrameOrigin:pointInSuperview];
95}
96
97- (void)complete:(AppsGridViewItem*)item
98    targetOrigin:(NSPoint)targetOrigin {
99  [self clearAnimations];
100
101  NSView* itemView = [item view];
102  NSBitmapImageRep* imageRep = [item dragRepresentationForRestore:YES];
103
104  [dragLayer_ setContents:reinterpret_cast<id>([imageRep CGImage])];
105  [dragLayer_ setTransform:CATransform3DScale(CATransform3DIdentity,
106                                              1.0 / kDraggingIconScale,
107                                              1.0 / kDraggingIconScale,
108                                              1.0)];
109
110  // Retain the button so it can be unhidden when the animation completes. Note
111  // that the |item| and corresponding button can differ from the |item| passed
112  // to initiate(), if it moved to a new page during the drag. At this point the
113  // destination page is known, so retain the button.
114  buttonToRestore_.reset([[item button] retain]);
115
116  // Add the shrink animation for the layer.
117  [self animateTransformFrom:CATransform3DIdentity
118                 useDelegate:YES];
119  shrinking_ = YES;
120
121  // Also animate the translation, on the view.
122  // TODO(tapted): This should be merged into the scale transform, instead of
123  // using a separate NSViewAnimation.
124  NSRect startRect = [[self view] frame];
125
126  // The final position needs to be adjusted since it shrinks from each side.
127  NSRect targetRect = NSMakeRect(
128      targetOrigin.x - NSMidX([itemView bounds]) * (kDraggingIconScale - 1),
129      targetOrigin.y - NSMidY([itemView bounds]) * (kDraggingIconScale - 1),
130      startRect.size.width,
131      startRect.size.height);
132
133  NSDictionary* animationDict = @{
134      NSViewAnimationTargetKey:     [self view],
135      NSViewAnimationStartFrameKey: [NSValue valueWithRect:startRect],
136      NSViewAnimationEndFrameKey:   [NSValue valueWithRect:targetRect]
137  };
138
139  base::scoped_nsobject<NSViewAnimation> translate([[NSViewAnimation alloc]
140      initWithViewAnimations:[NSArray arrayWithObject:animationDict]]);
141  [translate setDuration:kAnimationDuration];
142  [translate startAnimation];
143}
144
145- (void)animateTransformFrom:(CATransform3D)fromValue
146                 useDelegate:(BOOL)useDelegate {
147  CABasicAnimation* animation =
148      [CABasicAnimation animationWithKeyPath:@"transform"];
149  [animation setFromValue:[NSValue valueWithCATransform3D:fromValue]];
150  if (useDelegate)
151    [animation setDelegate:self];
152
153  [animation setDuration:kAnimationDuration];
154  [CATransaction begin];
155  [CATransaction setValue:[NSNumber numberWithFloat:kAnimationDuration]
156                   forKey:kCATransactionAnimationDuration];
157  [dragLayer_ addAnimation:animation
158                    forKey:@"transform"];
159  [CATransaction commit];
160}
161
162- (void)clearAnimations {
163  [dragLayer_ removeAllAnimations];
164  if (!shrinking_)
165    return;
166
167  DCHECK(buttonToRestore_);
168  [buttonToRestore_ setHidden:NO];
169  buttonToRestore_.reset();
170  shrinking_ = NO;
171}
172
173- (void)animationDidStop:(CAAnimation*)anim
174                finished:(BOOL)finished {
175  if (!finished)
176    return;
177
178  DCHECK(shrinking_);
179  [self clearAnimations];
180  [dragLayer_ setContents:nil];
181  [[self view] setHidden:YES];
182}
183
184@end
185