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