blob: 74e23c4e0ece15dce5684e467edf25aa3d3ed28b [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/apps_collection_view_drag_manager.h"
#include "base/logging.h"
#include "base/mac/foundation_util.h"
#import "ui/app_list/cocoa/apps_grid_controller.h"
#import "ui/app_list/cocoa/apps_grid_view_item.h"
#import "ui/app_list/cocoa/item_drag_controller.h"
namespace {
// Distance cursor must travel in either x or y direction to start a drag.
const CGFloat kDragThreshold = 5;
} // namespace
@interface AppsCollectionViewDragManager ()
// Returns the item index that |theEvent| would hit in the page at |pageIndex|
// or NSNotFound if no item is hit.
- (size_t)itemIndexForPage:(size_t)pageIndex
hitWithEvent:(NSEvent*)theEvent;
- (void)initiateDrag:(NSEvent*)theEvent;
- (void)updateDrag:(NSEvent*)theEvent;
- (void)completeDrag;
- (NSMenu*)menuForEvent:(NSEvent*)theEvent
inPage:(NSCollectionView*)page;
@end
// An NSCollectionView that forwards mouse events to the factory they share.
@interface GridCollectionView : NSCollectionView {
@private
AppsCollectionViewDragManager* factory_;
}
@property(assign, nonatomic) AppsCollectionViewDragManager* factory;
@end
@implementation AppsCollectionViewDragManager
- (id)initWithCellSize:(NSSize)cellSize
rows:(size_t)rows
columns:(size_t)columns
gridController:(AppsGridController*)gridController {
if ((self = [super init])) {
cellSize_ = cellSize;
rows_ = rows;
columns_ = columns;
gridController_ = gridController;
}
return self;
}
- (NSCollectionView*)makePageWithFrame:(NSRect)pageFrame {
base::scoped_nsobject<GridCollectionView> itemCollectionView(
[[GridCollectionView alloc] initWithFrame:pageFrame]);
[itemCollectionView setFactory:self];
[itemCollectionView setMaxNumberOfRows:rows_];
[itemCollectionView setMinItemSize:cellSize_];
[itemCollectionView setMaxItemSize:cellSize_];
[itemCollectionView setSelectable:YES];
[itemCollectionView setFocusRingType:NSFocusRingTypeNone];
[itemCollectionView setBackgroundColors:
[NSArray arrayWithObject:[NSColor clearColor]]];
[itemCollectionView setDelegate:gridController_];
base::scoped_nsobject<AppsGridViewItem> itemPrototype(
[[AppsGridViewItem alloc] initWithSize:cellSize_]);
[[itemPrototype button] setTarget:gridController_];
[[itemPrototype button] setAction:@selector(onItemClicked:)];
[itemCollectionView setItemPrototype:itemPrototype];
return itemCollectionView.autorelease();
}
- (void)onMouseDownInPage:(NSCollectionView*)page
withEvent:(NSEvent*)theEvent {
size_t pageIndex = [gridController_ pageIndexForCollectionView:page];
itemHitIndex_ = [self itemIndexForPage:pageIndex
hitWithEvent:theEvent];
if (itemHitIndex_ == NSNotFound)
return;
mouseDownLocation_ = [theEvent locationInWindow];
[[[gridController_ itemAtIndex:itemHitIndex_] view] mouseDown:theEvent];
}
- (void)onMouseDragged:(NSEvent*)theEvent {
if (itemHitIndex_ == NSNotFound)
return;
if (dragging_) {
[self updateDrag:theEvent];
return;
}
NSPoint mouseLocation = [theEvent locationInWindow];
CGFloat deltaX = mouseLocation.x - mouseDownLocation_.x;
CGFloat deltaY = mouseLocation.y - mouseDownLocation_.y;
if (deltaX * deltaX + deltaY * deltaY > kDragThreshold * kDragThreshold) {
[self initiateDrag:theEvent];
return;
}
[[[gridController_ itemAtIndex:itemHitIndex_] view] mouseDragged:theEvent];
}
- (void)onMouseUp:(NSEvent*)theEvent {
if (itemHitIndex_ == NSNotFound)
return;
if (dragging_) {
[self completeDrag];
return;
}
[[[gridController_ itemAtIndex:itemHitIndex_] view] mouseUp:theEvent];
}
- (size_t)itemIndexForPage:(size_t)pageIndex
hitWithEvent:(NSEvent*)theEvent {
NSCollectionView* page =
[gridController_ collectionViewAtPageIndex:pageIndex];
NSPoint pointInView = [page convertPoint:[theEvent locationInWindow]
fromView:nil];
const size_t itemsInPage = [[page content] count];
for (size_t indexInPage = 0; indexInPage < itemsInPage; ++indexInPage) {
if ([page mouse:pointInView
inRect:[page frameForItemAtIndex:indexInPage]]) {
return indexInPage + pageIndex * rows_ * columns_;
}
}
return NSNotFound;
}
- (void)initiateDrag:(NSEvent*)theEvent {
DCHECK_LT(itemHitIndex_, [gridController_ itemCount]);
dragging_ = YES;
if (!itemDragController_) {
itemDragController_.reset(
[[ItemDragController alloc] initWithGridCellSize:cellSize_]);
[[[gridController_ view] superview] addSubview:[itemDragController_ view]];
}
[itemDragController_ initiate:[gridController_ itemAtIndex:itemHitIndex_]
mouseDownLocation:mouseDownLocation_
currentLocation:[theEvent locationInWindow]
timestamp:[theEvent timestamp]];
itemDragIndex_ = itemHitIndex_;
[self updateDrag:theEvent];
}
- (void)updateDrag:(NSEvent*)theEvent {
[itemDragController_ update:[theEvent locationInWindow]
timestamp:[theEvent timestamp]];
[gridController_ maybeChangePageForPoint:[theEvent locationInWindow]];
size_t visiblePage = [gridController_ visiblePage];
size_t itemIndexOver = [self itemIndexForPage:visiblePage
hitWithEvent:theEvent];
DCHECK_NE(0u, [gridController_ itemCount]);
if (itemIndexOver == NSNotFound) {
if (visiblePage != [gridController_ pageCount] - 1)
return;
// If nothing was hit, but the last page is shown, move to the end.
itemIndexOver = [gridController_ itemCount] - 1;
}
if (itemDragIndex_ == itemIndexOver)
return;
[gridController_ moveItemInView:itemDragIndex_
toItemIndex:itemIndexOver];
// A new item may be created when moving between pages. Ensure it is hidden.
[[[gridController_ itemAtIndex:itemIndexOver] button] setHidden:YES];
itemDragIndex_ = itemIndexOver;
}
- (void)cancelDrag {
if (!dragging_)
return;
[gridController_ moveItemInView:itemDragIndex_
toItemIndex:itemHitIndex_];
itemDragIndex_ = itemHitIndex_;
[self completeDrag];
itemHitIndex_ = NSNotFound; // Ignore future mouse events for this drag.
}
- (void)completeDrag {
DCHECK_GE(itemDragIndex_, 0u);
[gridController_ cancelScrollTimer];
AppsGridViewItem* item = [gridController_ itemAtIndex:itemDragIndex_];
// The item could still be animating in the NSCollectionView, so ask it where
// it would place the item.
NSCollectionView* pageView = base::mac::ObjCCastStrict<NSCollectionView>(
[[item view] superview]);
size_t indexInPage = itemDragIndex_ % (rows_ * columns_);
NSPoint targetOrigin = [[[itemDragController_ view] superview]
convertPoint:[pageView frameForItemAtIndex:indexInPage].origin
fromView:pageView];
[itemDragController_ complete:item
targetOrigin:targetOrigin];
[gridController_ moveItemWithIndex:itemHitIndex_
toModelIndex:itemDragIndex_];
dragging_ = NO;
}
- (NSMenu*)menuForEvent:(NSEvent*)theEvent
inPage:(NSCollectionView*)page {
size_t pageIndex = [gridController_ pageIndexForCollectionView:page];
size_t itemIndex = [self itemIndexForPage:pageIndex
hitWithEvent:theEvent];
if (itemIndex == NSNotFound)
return nil;
return [[gridController_ itemAtIndex:itemIndex] contextMenu];
}
@end
@implementation GridCollectionView
@synthesize factory = factory_;
- (NSMenu*)menuForEvent:(NSEvent*)theEvent {
return [factory_ menuForEvent:theEvent
inPage:self];
}
- (void)mouseDown:(NSEvent*)theEvent {
[factory_ onMouseDownInPage:self
withEvent:theEvent];
}
- (void)mouseDragged:(NSEvent*)theEvent {
[factory_ onMouseDragged:theEvent];
}
- (void)mouseUp:(NSEvent*)theEvent {
[factory_ onMouseUp:theEvent];
}
- (BOOL)acceptsFirstResponder {
return NO;
}
@end