blob: 1de425b97d5a2dfc3d3af94752fee2fa15a0c6bd [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ui/app_list/cocoa/item_drag_controller.h"
#include "base/logging.h"
#import "ui/app_list/cocoa/apps_grid_view_item.h"
#include "ui/base/cocoa/window_size_constants.h"
// Scale to transform the grid cell when a drag starts. Note that 1.5 ensures
// that integers are used for the layer bounds when the grid cell dimensions
// are even.
const CGFloat kDraggingIconScale = 1.5;
const NSTimeInterval kAnimationDuration = 0.2;
@interface ItemDragController ()
- (void)animateTransformFrom:(CATransform3D)fromValue
useDelegate:(BOOL)useDelegate;
- (void)clearAnimations;
@end
@implementation ItemDragController
- (id)initWithGridCellSize:(NSSize)size {
if ((self = [super init])) {
NSRect frameRect = NSMakeRect(0,
0,
size.width * kDraggingIconScale,
size.height * kDraggingIconScale);
base::scoped_nsobject<NSView> dragView(
[[NSView alloc] initWithFrame:frameRect]);
[dragView setWantsLayer:YES];
[dragView setHidden:YES];
dragLayer_.reset([[CALayer layer] retain]);
[dragLayer_ setFrame:NSRectToCGRect(frameRect)];
[[dragView layer] addSublayer:dragLayer_];
[self setView:dragView];
}
return self;
}
- (void)initiate:(AppsGridViewItem*)item
mouseDownLocation:(NSPoint)mouseDownLocation
currentLocation:(NSPoint)currentLocation
timestamp:(NSTimeInterval)eventTimestamp {
[self clearAnimations];
NSView* itemView = [item view];
NSPoint pointInGridCell = [itemView convertPoint:mouseDownLocation
fromView:nil];
mouseOffset_ = NSMakePoint(pointInGridCell.x - NSMidX([itemView bounds]),
NSMidY([itemView bounds]) - pointInGridCell.y);
NSBitmapImageRep* imageRep = [item dragRepresentationForRestore:NO];
[dragLayer_ setContents:reinterpret_cast<id>([imageRep CGImage])];
[dragLayer_ setTransform:CATransform3DIdentity];
// Add a grow animation to the layer.
CATransform3D growFrom = CATransform3DScale(CATransform3DIdentity,
1.0 / kDraggingIconScale,
1.0 / kDraggingIconScale,
1.0);
[self animateTransformFrom:growFrom
useDelegate:NO];
growStart_ = eventTimestamp;
[[self view] setHidden:NO];
}
- (void)update:(NSPoint)currentLocation
timestamp:(NSTimeInterval)eventTimestamp {
NSPoint pointInSuperview = [[[self view] superview]
convertPoint:currentLocation
fromView:nil];
NSRect rect = [[self view] bounds];
NSPoint anchor = NSMakePoint(NSMidX(rect), NSMidY(rect));
// If the grow animation is still in progress, make the point of the image
// that was clicked appear stuck to the mouse cursor.
CGFloat progress = (eventTimestamp - growStart_) / kAnimationDuration;
CGFloat currentIconScale = progress < 1.0 ?
1.0 + (kDraggingIconScale - 1.0) * progress :
kDraggingIconScale;
pointInSuperview.x -= (mouseOffset_.x * currentIconScale + anchor.x);
pointInSuperview.y -= (mouseOffset_.y * currentIconScale + anchor.y);
[[self view] setFrameOrigin:pointInSuperview];
}
- (void)complete:(AppsGridViewItem*)item
targetOrigin:(NSPoint)targetOrigin {
[self clearAnimations];
NSView* itemView = [item view];
NSBitmapImageRep* imageRep = [item dragRepresentationForRestore:YES];
[dragLayer_ setContents:reinterpret_cast<id>([imageRep CGImage])];
[dragLayer_ setTransform:CATransform3DScale(CATransform3DIdentity,
1.0 / kDraggingIconScale,
1.0 / kDraggingIconScale,
1.0)];
// Retain the button so it can be unhidden when the animation completes. Note
// that the |item| and corresponding button can differ from the |item| passed
// to initiate(), if it moved to a new page during the drag. At this point the
// destination page is known, so retain the button.
buttonToRestore_.reset([[item button] retain]);
// Add the shrink animation for the layer.
[self animateTransformFrom:CATransform3DIdentity
useDelegate:YES];
shrinking_ = YES;
// Also animate the translation, on the view.
// TODO(tapted): This should be merged into the scale transform, instead of
// using a separate NSViewAnimation.
NSRect startRect = [[self view] frame];
// The final position needs to be adjusted since it shrinks from each side.
NSRect targetRect = NSMakeRect(
targetOrigin.x - NSMidX([itemView bounds]) * (kDraggingIconScale - 1),
targetOrigin.y - NSMidY([itemView bounds]) * (kDraggingIconScale - 1),
startRect.size.width,
startRect.size.height);
NSDictionary* animationDict = @{
NSViewAnimationTargetKey: [self view],
NSViewAnimationStartFrameKey: [NSValue valueWithRect:startRect],
NSViewAnimationEndFrameKey: [NSValue valueWithRect:targetRect]
};
base::scoped_nsobject<NSViewAnimation> translate([[NSViewAnimation alloc]
initWithViewAnimations:[NSArray arrayWithObject:animationDict]]);
[translate setDuration:kAnimationDuration];
[translate startAnimation];
}
- (void)animateTransformFrom:(CATransform3D)fromValue
useDelegate:(BOOL)useDelegate {
CABasicAnimation* animation =
[CABasicAnimation animationWithKeyPath:@"transform"];
[animation setFromValue:[NSValue valueWithCATransform3D:fromValue]];
if (useDelegate)
[animation setDelegate:self];
[animation setDuration:kAnimationDuration];
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:kAnimationDuration]
forKey:kCATransactionAnimationDuration];
[dragLayer_ addAnimation:animation
forKey:@"transform"];
[CATransaction commit];
}
- (void)clearAnimations {
[dragLayer_ removeAllAnimations];
if (!shrinking_)
return;
DCHECK(buttonToRestore_);
[buttonToRestore_ setHidden:NO];
buttonToRestore_.reset();
shrinking_ = NO;
}
- (void)animationDidStop:(CAAnimation*)anim
finished:(BOOL)finished {
if (!finished)
return;
DCHECK(shrinking_);
[self clearAnimations];
[dragLayer_ setContents:nil];
[[self view] setHidden:YES];
}
@end