apps_collection_view_drag_manager.mm revision a93a17c8d99d686bd4a1511e5504e5e6cc9fcadf
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- (BOOL)acceptsFirstResponder {
237  return NO;
238}
239
240@end
241