apps_collection_view_drag_manager.mm revision b2df76ea8fec9e32f6f3718986dba0d95315b29c
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/apps_collection_view_drag_manager.h" 6 7#include "base/logging.h" 8#include "base/mac/foundation_util.h" 9#import "ui/app_list/cocoa/apps_grid_controller.h" 10#import "ui/app_list/cocoa/apps_grid_view_item.h" 11#import "ui/app_list/cocoa/item_drag_controller.h" 12 13namespace { 14 15// Distance cursor must travel in either x or y direction to start a drag. 16const CGFloat kDragThreshold = 5; 17 18} // namespace 19 20@interface AppsCollectionViewDragManager () 21 22// Returns the item index that |theEvent| would hit in the page at |pageIndex| 23// or NSNotFound if no item is hit. 24- (size_t)itemIndexForPage:(size_t)pageIndex 25 hitWithEvent:(NSEvent*)theEvent; 26 27- (void)initiateDrag:(NSEvent*)theEvent; 28- (void)updateDrag:(NSEvent*)theEvent; 29- (void)completeDrag; 30 31@end 32 33// An NSCollectionView that forwards mouse events to the factory they share. 34@interface GridCollectionView : NSCollectionView { 35 @private 36 AppsCollectionViewDragManager* factory_; 37} 38 39@property(assign, nonatomic) AppsCollectionViewDragManager* factory; 40 41@end 42 43@implementation AppsCollectionViewDragManager 44 45- (id)initWithCellSize:(NSSize)cellSize 46 rows:(size_t)rows 47 columns:(size_t)columns 48 gridController:(AppsGridController*)gridController { 49 if ((self = [super init])) { 50 cellSize_ = cellSize; 51 rows_ = rows; 52 columns_ = columns; 53 gridController_ = gridController; 54 } 55 return self; 56} 57 58- (NSCollectionView*)makePageWithFrame:(NSRect)pageFrame { 59 scoped_nsobject<GridCollectionView> itemCollectionView( 60 [[GridCollectionView alloc] initWithFrame:pageFrame]); 61 [itemCollectionView setFactory:self]; 62 [itemCollectionView setMaxNumberOfRows:rows_]; 63 [itemCollectionView setMinItemSize:cellSize_]; 64 [itemCollectionView setMaxItemSize:cellSize_]; 65 [itemCollectionView setSelectable:YES]; 66 [itemCollectionView setFocusRingType:NSFocusRingTypeNone]; 67 [itemCollectionView setBackgroundColors: 68 [NSArray arrayWithObject:[NSColor clearColor]]]; 69 70 scoped_nsobject<AppsGridViewItem> itemPrototype( 71 [[AppsGridViewItem alloc] initWithSize:cellSize_]); 72 [[itemPrototype button] setTarget:gridController_]; 73 [[itemPrototype button] setAction:@selector(onItemClicked:)]; 74 75 [itemCollectionView setItemPrototype:itemPrototype]; 76 return itemCollectionView.autorelease(); 77} 78 79- (void)onMouseDownInPage:(NSCollectionView*)page 80 withEvent:(NSEvent*)theEvent { 81 size_t pageIndex = [gridController_ pageIndexForCollectionView:page]; 82 itemHitIndex_ = [self itemIndexForPage:pageIndex 83 hitWithEvent:theEvent]; 84 if (itemHitIndex_ == NSNotFound) 85 return; 86 87 mouseDownLocation_ = [theEvent locationInWindow]; 88 [[[gridController_ itemAtIndex:itemHitIndex_] view] mouseDown:theEvent]; 89} 90 91- (void)onMouseDragged:(NSEvent*)theEvent { 92 if (itemHitIndex_ == NSNotFound) 93 return; 94 95 if (dragging_) { 96 [self updateDrag:theEvent]; 97 return; 98 } 99 100 NSPoint mouseLocation = [theEvent locationInWindow]; 101 CGFloat deltaX = mouseLocation.x - mouseDownLocation_.x; 102 CGFloat deltaY = mouseLocation.y - mouseDownLocation_.y; 103 if (deltaX * deltaX + deltaY * deltaY > kDragThreshold * kDragThreshold) { 104 [self initiateDrag:theEvent]; 105 return; 106 } 107 108 [[[gridController_ itemAtIndex:itemHitIndex_] view] mouseDragged:theEvent]; 109} 110 111- (void)onMouseUp:(NSEvent*)theEvent { 112 if (itemHitIndex_ == NSNotFound) 113 return; 114 115 if (dragging_) { 116 [self completeDrag]; 117 return; 118 } 119 120 [[[gridController_ itemAtIndex:itemHitIndex_] view] mouseUp:theEvent]; 121} 122 123- (size_t)itemIndexForPage:(size_t)pageIndex 124 hitWithEvent:(NSEvent*)theEvent { 125 NSCollectionView* page = 126 [gridController_ collectionViewAtPageIndex:pageIndex]; 127 NSPoint pointInView = [page convertPoint:[theEvent locationInWindow] 128 fromView:nil]; 129 130 const size_t itemsInPage = [[page content] count]; 131 for (size_t indexInPage = 0; indexInPage < itemsInPage; ++indexInPage) { 132 if ([page mouse:pointInView 133 inRect:[page frameForItemAtIndex:indexInPage]]) { 134 return indexInPage + pageIndex * rows_ * columns_; 135 } 136 } 137 138 return NSNotFound; 139} 140 141- (void)initiateDrag:(NSEvent*)theEvent { 142 DCHECK_LT(itemHitIndex_, [gridController_ itemCount]); 143 dragging_ = YES; 144 145 if (!itemDragController_) { 146 itemDragController_.reset( 147 [[ItemDragController alloc] initWithGridCellSize:cellSize_]); 148 [[gridController_ view] addSubview:[itemDragController_ view]]; 149 } 150 151 [itemDragController_ initiate:[gridController_ itemAtIndex:itemHitIndex_] 152 mouseDownLocation:mouseDownLocation_ 153 currentLocation:[theEvent locationInWindow] 154 timestamp:[theEvent timestamp]]; 155 156 itemDragIndex_ = itemHitIndex_; 157 [self updateDrag:theEvent]; 158} 159 160- (void)updateDrag:(NSEvent*)theEvent { 161 [itemDragController_ update:[theEvent locationInWindow] 162 timestamp:[theEvent timestamp]]; 163 size_t visiblePage = [gridController_ visiblePage]; 164 size_t itemIndexOver = [self itemIndexForPage:visiblePage 165 hitWithEvent:theEvent]; 166 DCHECK_NE(0u, [gridController_ itemCount]); 167 if (itemIndexOver == NSNotFound) { 168 if (visiblePage != [gridController_ pageCount] - 1) 169 return; 170 171 // If nothing was hit, but the last page is shown, move to the end. 172 itemIndexOver = [gridController_ itemCount] - 1; 173 } 174 175 if (itemDragIndex_ == itemIndexOver) 176 return; 177 178 [gridController_ moveItemInView:itemDragIndex_ 179 toItemIndex:itemIndexOver]; 180 // A new item may be created when moving between pages. Ensure it is hidden. 181 [[[gridController_ itemAtIndex:itemIndexOver] button] setHidden:YES]; 182 itemDragIndex_ = itemIndexOver; 183} 184 185- (void)cancelDrag { 186 if (!dragging_) 187 return; 188 189 [gridController_ moveItemInView:itemDragIndex_ 190 toItemIndex:itemHitIndex_]; 191 itemDragIndex_ = itemHitIndex_; 192 [self completeDrag]; 193 itemHitIndex_ = NSNotFound; // Ignore future mouse events for this drag. 194} 195 196- (void)completeDrag { 197 DCHECK_GE(itemDragIndex_, 0u); 198 AppsGridViewItem* item = [gridController_ itemAtIndex:itemDragIndex_]; 199 200 // The item could still be animating in the NSCollectionView, so ask it where 201 // it would place the item. 202 NSCollectionView* pageView = base::mac::ObjCCastStrict<NSCollectionView>( 203 [[item view] superview]); 204 size_t indexInPage = itemDragIndex_ % (rows_ * columns_); 205 NSPoint targetOrigin = [[gridController_ view] 206 convertPoint:[pageView frameForItemAtIndex:indexInPage].origin 207 fromView:pageView]; 208 209 [itemDragController_ complete:item 210 targetOrigin:targetOrigin]; 211 [gridController_ moveItemWithIndex:itemHitIndex_ 212 toModelIndex:itemDragIndex_]; 213 214 dragging_ = NO; 215} 216 217@end 218 219@implementation GridCollectionView 220 221@synthesize factory = factory_; 222 223- (void)mouseDown:(NSEvent*)theEvent { 224 [factory_ onMouseDownInPage:self 225 withEvent:theEvent]; 226} 227 228- (void)mouseDragged:(NSEvent*)theEvent { 229 [factory_ onMouseDragged:theEvent]; 230} 231 232- (void)mouseUp:(NSEvent*)theEvent { 233 [factory_ onMouseUp:theEvent]; 234} 235 236@end 237