Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / app_list / cocoa / apps_collection_view_drag_manager.mm
blob74e23c4e0ece15dce5684e467edf25aa3d3ed28b
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/apps_collection_view_drag_manager.h"
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"
13 namespace {
15 // Distance cursor must travel in either x or y direction to start a drag.
16 const CGFloat kDragThreshold = 5;
18 }  // namespace
20 @interface AppsCollectionViewDragManager ()
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;
27 - (void)initiateDrag:(NSEvent*)theEvent;
28 - (void)updateDrag:(NSEvent*)theEvent;
29 - (void)completeDrag;
31 - (NSMenu*)menuForEvent:(NSEvent*)theEvent
32                  inPage:(NSCollectionView*)page;
34 @end
36 // An NSCollectionView that forwards mouse events to the factory they share.
37 @interface GridCollectionView : NSCollectionView {
38  @private
39   AppsCollectionViewDragManager* factory_;
42 @property(assign, nonatomic) AppsCollectionViewDragManager* factory;
44 @end
46 @implementation AppsCollectionViewDragManager
48 - (id)initWithCellSize:(NSSize)cellSize
49                   rows:(size_t)rows
50                columns:(size_t)columns
51         gridController:(AppsGridController*)gridController {
52   if ((self = [super init])) {
53     cellSize_ = cellSize;
54     rows_ = rows;
55     columns_ = columns;
56     gridController_ = gridController;
57   }
58   return self;
61 - (NSCollectionView*)makePageWithFrame:(NSRect)pageFrame {
62   base::scoped_nsobject<GridCollectionView> itemCollectionView(
63       [[GridCollectionView alloc] initWithFrame:pageFrame]);
64   [itemCollectionView setFactory:self];
65   [itemCollectionView setMaxNumberOfRows:rows_];
66   [itemCollectionView setMinItemSize:cellSize_];
67   [itemCollectionView setMaxItemSize:cellSize_];
68   [itemCollectionView setSelectable:YES];
69   [itemCollectionView setFocusRingType:NSFocusRingTypeNone];
70   [itemCollectionView setBackgroundColors:
71       [NSArray arrayWithObject:[NSColor clearColor]]];
72   [itemCollectionView setDelegate:gridController_];
74   base::scoped_nsobject<AppsGridViewItem> itemPrototype(
75       [[AppsGridViewItem alloc] initWithSize:cellSize_]);
76   [[itemPrototype button] setTarget:gridController_];
77   [[itemPrototype button] setAction:@selector(onItemClicked:)];
79   [itemCollectionView setItemPrototype:itemPrototype];
80   return itemCollectionView.autorelease();
83 - (void)onMouseDownInPage:(NSCollectionView*)page
84                 withEvent:(NSEvent*)theEvent {
85   size_t pageIndex = [gridController_ pageIndexForCollectionView:page];
86   itemHitIndex_ = [self itemIndexForPage:pageIndex
87                             hitWithEvent:theEvent];
88   if (itemHitIndex_ == NSNotFound)
89     return;
91   mouseDownLocation_ = [theEvent locationInWindow];
92   [[[gridController_ itemAtIndex:itemHitIndex_] view] mouseDown:theEvent];
95 - (void)onMouseDragged:(NSEvent*)theEvent {
96   if (itemHitIndex_ == NSNotFound)
97     return;
99   if (dragging_) {
100     [self updateDrag:theEvent];
101     return;
102   }
104   NSPoint mouseLocation = [theEvent locationInWindow];
105   CGFloat deltaX = mouseLocation.x - mouseDownLocation_.x;
106   CGFloat deltaY = mouseLocation.y - mouseDownLocation_.y;
107   if (deltaX * deltaX + deltaY * deltaY > kDragThreshold * kDragThreshold) {
108     [self initiateDrag:theEvent];
109     return;
110   }
112   [[[gridController_ itemAtIndex:itemHitIndex_] view] mouseDragged:theEvent];
115 - (void)onMouseUp:(NSEvent*)theEvent {
116   if (itemHitIndex_ == NSNotFound)
117     return;
119   if (dragging_) {
120     [self completeDrag];
121     return;
122   }
124   [[[gridController_ itemAtIndex:itemHitIndex_] view] mouseUp:theEvent];
127 - (size_t)itemIndexForPage:(size_t)pageIndex
128               hitWithEvent:(NSEvent*)theEvent {
129   NSCollectionView* page =
130       [gridController_ collectionViewAtPageIndex:pageIndex];
131   NSPoint pointInView = [page convertPoint:[theEvent locationInWindow]
132                                   fromView:nil];
134   const size_t itemsInPage = [[page content] count];
135   for (size_t indexInPage = 0; indexInPage < itemsInPage; ++indexInPage) {
136     if ([page mouse:pointInView
137              inRect:[page frameForItemAtIndex:indexInPage]]) {
138       return indexInPage + pageIndex * rows_ * columns_;
139     }
140   }
142   return NSNotFound;
145 - (void)initiateDrag:(NSEvent*)theEvent {
146   DCHECK_LT(itemHitIndex_, [gridController_ itemCount]);
147   dragging_ = YES;
149   if (!itemDragController_) {
150     itemDragController_.reset(
151         [[ItemDragController alloc] initWithGridCellSize:cellSize_]);
152     [[[gridController_ view] superview] addSubview:[itemDragController_ view]];
153   }
155   [itemDragController_ initiate:[gridController_ itemAtIndex:itemHitIndex_]
156               mouseDownLocation:mouseDownLocation_
157                 currentLocation:[theEvent locationInWindow]
158                       timestamp:[theEvent timestamp]];
160   itemDragIndex_ = itemHitIndex_;
161   [self updateDrag:theEvent];
164 - (void)updateDrag:(NSEvent*)theEvent {
165   [itemDragController_ update:[theEvent locationInWindow]
166                     timestamp:[theEvent timestamp]];
167   [gridController_ maybeChangePageForPoint:[theEvent locationInWindow]];
169   size_t visiblePage = [gridController_ visiblePage];
170   size_t itemIndexOver = [self itemIndexForPage:visiblePage
171                                    hitWithEvent:theEvent];
172   DCHECK_NE(0u, [gridController_ itemCount]);
173   if (itemIndexOver == NSNotFound) {
174     if (visiblePage != [gridController_ pageCount] - 1)
175       return;
177     // If nothing was hit, but the last page is shown, move to the end.
178     itemIndexOver = [gridController_ itemCount] - 1;
179   }
181   if (itemDragIndex_ == itemIndexOver)
182     return;
184   [gridController_ moveItemInView:itemDragIndex_
185                       toItemIndex:itemIndexOver];
186   // A new item may be created when moving between pages. Ensure it is hidden.
187   [[[gridController_ itemAtIndex:itemIndexOver] button] setHidden:YES];
188   itemDragIndex_ = itemIndexOver;
191 - (void)cancelDrag {
192   if (!dragging_)
193     return;
195   [gridController_ moveItemInView:itemDragIndex_
196                       toItemIndex:itemHitIndex_];
197   itemDragIndex_ = itemHitIndex_;
198   [self completeDrag];
199   itemHitIndex_ = NSNotFound;  // Ignore future mouse events for this drag.
202 - (void)completeDrag {
203   DCHECK_GE(itemDragIndex_, 0u);
204   [gridController_ cancelScrollTimer];
205   AppsGridViewItem* item = [gridController_ itemAtIndex:itemDragIndex_];
207   // The item could still be animating in the NSCollectionView, so ask it where
208   // it would place the item.
209   NSCollectionView* pageView = base::mac::ObjCCastStrict<NSCollectionView>(
210       [[item view] superview]);
211   size_t indexInPage = itemDragIndex_ % (rows_ * columns_);
212   NSPoint targetOrigin = [[[itemDragController_ view] superview]
213       convertPoint:[pageView frameForItemAtIndex:indexInPage].origin
214           fromView:pageView];
216   [itemDragController_ complete:item
217                    targetOrigin:targetOrigin];
218   [gridController_ moveItemWithIndex:itemHitIndex_
219                         toModelIndex:itemDragIndex_];
221   dragging_ = NO;
224 - (NSMenu*)menuForEvent:(NSEvent*)theEvent
225                  inPage:(NSCollectionView*)page {
226   size_t pageIndex = [gridController_ pageIndexForCollectionView:page];
227   size_t itemIndex = [self itemIndexForPage:pageIndex
228                                hitWithEvent:theEvent];
229   if (itemIndex == NSNotFound)
230     return nil;
232   return [[gridController_ itemAtIndex:itemIndex] contextMenu];
235 @end
237 @implementation GridCollectionView
239 @synthesize factory = factory_;
241 - (NSMenu*)menuForEvent:(NSEvent*)theEvent {
242   return [factory_ menuForEvent:theEvent
243                          inPage:self];
246 - (void)mouseDown:(NSEvent*)theEvent {
247   [factory_ onMouseDownInPage:self
248                     withEvent:theEvent];
251 - (void)mouseDragged:(NSEvent*)theEvent {
252   [factory_ onMouseDragged:theEvent];
255 - (void)mouseUp:(NSEvent*)theEvent {
256   [factory_ onMouseUp:theEvent];
259 - (BOOL)acceptsFirstResponder {
260   return NO;
263 @end