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.
5 #import "ui/app_list/cocoa/item_drag_controller.h"
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"
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
14 const CGFloat kDraggingIconScale = 1.5;
16 const NSTimeInterval kAnimationDuration = 0.2;
19 @interface ItemDragController ()
21 - (void)animateTransformFrom:(CATransform3D)fromValue
22 useDelegate:(BOOL)useDelegate;
24 - (void)clearAnimations;
28 @implementation ItemDragController
30 - (id)initWithGridCellSize:(NSSize)size {
31 if ((self = [super init])) {
32 NSRect frameRect = NSMakeRect(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];
41 dragLayer_.reset([[CALayer layer] retain]);
42 [dragLayer_ setFrame:NSRectToCGRect(frameRect)];
43 [[dragView layer] addSublayer:dragLayer_];
45 [self setView:dragView];
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
58 mouseOffset_ = NSMakePoint(pointInGridCell.x - NSMidX([itemView bounds]),
59 NSMidY([itemView bounds]) - pointInGridCell.y);
61 NSBitmapImageRep* imageRep = [item dragRepresentationForRestore:NO];
62 [dragLayer_ setContents:reinterpret_cast<id>([imageRep CGImage])];
63 [dragLayer_ setTransform:CATransform3DIdentity];
65 // Add a grow animation to the layer.
66 CATransform3D growFrom = CATransform3DScale(CATransform3DIdentity,
67 1.0 / kDraggingIconScale,
68 1.0 / kDraggingIconScale,
70 [self animateTransformFrom:growFrom
73 growStart_ = eventTimestamp;
74 [[self view] setHidden:NO];
77 - (void)update:(NSPoint)currentLocation
78 timestamp:(NSTimeInterval)eventTimestamp {
79 NSPoint pointInSuperview = [[[self view] superview]
80 convertPoint:currentLocation
82 NSRect rect = [[self view] bounds];
83 NSPoint anchor = NSMakePoint(NSMidX(rect), NSMidY(rect));
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 :
92 pointInSuperview.x -= (mouseOffset_.x * currentIconScale + anchor.x);
93 pointInSuperview.y -= (mouseOffset_.y * currentIconScale + anchor.y);
94 [[self view] setFrameOrigin:pointInSuperview];
97 - (void)complete:(AppsGridViewItem*)item
98 targetOrigin:(NSPoint)targetOrigin {
99 [self clearAnimations];
101 NSView* itemView = [item view];
102 NSBitmapImageRep* imageRep = [item dragRepresentationForRestore:YES];
104 [dragLayer_ setContents:reinterpret_cast<id>([imageRep CGImage])];
105 [dragLayer_ setTransform:CATransform3DScale(CATransform3DIdentity,
106 1.0 / kDraggingIconScale,
107 1.0 / kDraggingIconScale,
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]);
116 // Add the shrink animation for the layer.
117 [self animateTransformFrom:CATransform3DIdentity
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];
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);
133 NSDictionary* animationDict = @{
134 NSViewAnimationTargetKey: [self view],
135 NSViewAnimationStartFrameKey: [NSValue valueWithRect:startRect],
136 NSViewAnimationEndFrameKey: [NSValue valueWithRect:targetRect]
139 base::scoped_nsobject<NSViewAnimation> translate([[NSViewAnimation alloc]
140 initWithViewAnimations:[NSArray arrayWithObject:animationDict]]);
141 [translate setDuration:kAnimationDuration];
142 [translate startAnimation];
145 - (void)animateTransformFrom:(CATransform3D)fromValue
146 useDelegate:(BOOL)useDelegate {
147 CABasicAnimation* animation =
148 [CABasicAnimation animationWithKeyPath:@"transform"];
149 [animation setFromValue:[NSValue valueWithCATransform3D:fromValue]];
151 [animation setDelegate:self];
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];
162 - (void)clearAnimations {
163 [dragLayer_ removeAllAnimations];
167 DCHECK(buttonToRestore_);
168 [buttonToRestore_ setHidden:NO];
169 buttonToRestore_.reset();
173 - (void)animationDidStop:(CAAnimation*)anim
174 finished:(BOOL)finished {
179 [self clearAnimations];
180 [dragLayer_ setContents:nil];
181 [[self view] setHidden:YES];