Index: ui/app_list/views/apps_grid_view.cc |
diff --git a/ui/app_list/views/apps_grid_view.cc b/ui/app_list/views/apps_grid_view.cc |
index 7889f92878064dd906eec65208495de0ab66f659..190eb900c9e48ee2402f1e9b3024e3f69355772a 100644 |
--- a/ui/app_list/views/apps_grid_view.cc |
+++ b/ui/app_list/views/apps_grid_view.cc |
@@ -94,7 +94,7 @@ const int kFolderItemReparentDelay = 50; |
// Radius of the circle, in which if entered, show folder dropping preview |
// UI. |
-const int kFolderDroppingCircleRadius = 15; |
+const int kFolderDroppingCircleRadius = 39; |
// RowMoveAnimationDelegate is used when moving an item into a different row. |
// Before running the animation, the item's layer is re-created and kept in |
@@ -189,12 +189,6 @@ class ItemMoveAnimationDelegate : public gfx::AnimationDelegate { |
DISALLOW_COPY_AND_ASSIGN(ItemMoveAnimationDelegate); |
}; |
-// Gets the distance between the centers of the |rect_1| and |rect_2|. |
-int GetDistanceBetweenRects(gfx::Rect rect_1, |
- gfx::Rect rect_2) { |
- return (rect_1.CenterPoint() - rect_2.CenterPoint()).Length(); |
-} |
- |
// Returns true if the |item| is an folder item. |
bool IsFolderItem(AppListItem* item) { |
return (item->GetItemType() == AppListFolderItem::kItemType); |
@@ -206,6 +200,10 @@ bool IsOEMFolderItem(AppListItem* item) { |
AppListFolderItem::FOLDER_TYPE_OEM; |
} |
+int ClampToRange(int value, int min, int max) { |
+ return std::min(std::max(value, min), max); |
+} |
+ |
} // namespace |
#if defined(OS_WIN) |
@@ -488,6 +486,7 @@ void AppsGridView::InitiateDrag(AppListItemView* view, |
drag_view_init_index_ = GetIndexOfView(drag_view_); |
drag_view_offset_ = event.location(); |
drag_start_page_ = pagination_model_.selected_page(); |
+ reorder_placeholder_ = drag_view_init_index_; |
ExtractDragLocation(event, &drag_start_grid_view_); |
drag_view_start_ = gfx::Point(drag_view_->x(), drag_view_->y()); |
} |
@@ -615,16 +614,15 @@ void AppsGridView::UpdateDrag(Pointer pointer, const gfx::Point& point) { |
if (drag_pointer_ != pointer) |
return; |
+ drag_view_->SetPosition(drag_view_start_ + drag_vector); |
+ |
last_drag_point_ = point; |
const Index last_reorder_drop_target = reorder_drop_target_; |
const Index last_folder_drop_target = folder_drop_target_; |
DropAttempt last_drop_attempt = drop_attempt_; |
- CalculateDropTarget(last_drag_point_, false); |
+ CalculateDropTarget(); |
- if (IsPointWithinDragBuffer(last_drag_point_)) |
- MaybeStartPageFlipTimer(last_drag_point_); |
- else |
- StopPageFlipTimer(); |
+ MaybeStartPageFlipTimer(last_drag_point_); |
if (page_switcher_view_) { |
gfx::Point page_switcher_point(last_drag_point_); |
@@ -633,14 +631,6 @@ void AppsGridView::UpdateDrag(Pointer pointer, const gfx::Point& point) { |
page_switcher_view_->UpdateUIForDragPoint(page_switcher_point); |
} |
- if (!EnableFolderDragDropUI()) { |
- if (last_reorder_drop_target != reorder_drop_target_) |
- AnimateToIdealBounds(); |
- drag_view_->SetPosition(drag_view_start_ + drag_vector); |
- return; |
- } |
- |
- // Update drag with folder UI enabled. |
if (last_folder_drop_target != folder_drop_target_ || |
last_reorder_drop_target != reorder_drop_target_ || |
last_drop_attempt != drop_attempt_) { |
@@ -659,8 +649,6 @@ void AppsGridView::UpdateDrag(Pointer pointer, const gfx::Point& point) { |
// Reset the previous drop target. |
SetAsFolderDroppingTarget(last_folder_drop_target, false); |
} |
- |
- drag_view_->SetPosition(drag_view_start_ + drag_vector); |
} |
void AppsGridView::EndDrag(bool cancel) { |
@@ -699,7 +687,7 @@ void AppsGridView::EndDrag(bool cancel) { |
if (!cancel && dragging()) { |
// Regular drag ending path, ie, not for reparenting. |
- CalculateDropTarget(last_drag_point_, true); |
+ CalculateDropTarget(); |
if (EnableFolderDragDropUI() && drop_attempt_ == DROP_FOR_FOLDER && |
IsValidIndex(folder_drop_target_)) { |
MoveItemToFolder(drag_view_, folder_drop_target_); |
@@ -784,6 +772,10 @@ void AppsGridView::InitiateDragFromReparentItemInRootLevelGridView( |
DCHECK(original_drag_view && !drag_view_); |
DCHECK(!dragging_for_reparent_item_); |
+ // Since the item is new, its placeholder is conceptually at the back of the |
+ // entire apps grid. |
+ reorder_placeholder_ = GetLastViewIndex(); |
+ |
// Create a new AppListItemView to duplicate the original_drag_view in the |
// folder's grid view. |
AppListItemView* view = new AppListItemView(this, original_drag_view->item()); |
@@ -833,6 +825,7 @@ void AppsGridView::ClearDragState() { |
drag_pointer_ = NONE; |
reorder_drop_target_ = Index(); |
folder_drop_target_ = Index(); |
+ reorder_placeholder_ = Index(); |
drag_start_grid_view_ = gfx::Point(); |
drag_start_page_ = -1; |
drag_view_offset_ = gfx::Point(); |
@@ -1115,6 +1108,12 @@ views::View* AppsGridView::GetViewAtIndex(const Index& index) const { |
return view_model_.view_at(model_index); |
} |
+AppsGridView::Index AppsGridView::GetLastViewIndex() const { |
+ DCHECK_LT(0, view_model_.view_size()); |
+ int view_index = view_model_.view_size() - 1; |
+ return Index(view_index / tiles_per_page(), view_index % tiles_per_page()); |
+} |
+ |
void AppsGridView::MoveSelected(int page_delta, |
int slot_x_delta, |
int slot_y_delta) { |
@@ -1178,17 +1177,13 @@ void AppsGridView::CalculateIdealBounds() { |
view_model_.view_size() + pulsing_blocks_model_.view_size(); |
int slot_index = 0; |
for (int i = 0; i < total_views; ++i) { |
- if (i < view_model_.view_size() && view_model_.view_at(i) == drag_view_) { |
- if (EnableFolderDragDropUI() && drop_attempt_ == DROP_FOR_FOLDER) |
- ++slot_index; |
+ if (i < view_model_.view_size() && view_model_.view_at(i) == drag_view_) |
continue; |
- } |
Index view_index = GetIndexFromModelIndex(slot_index); |
- // Leaves a blank space in the grid for the current reorder drop target. |
- if (reorder_drop_target_ == view_index && |
- drop_attempt_ == DROP_FOR_REORDER) { |
+ // Leaves a blank space in the grid for the current reorder placeholder. |
+ if (reorder_placeholder_ == view_index) { |
++slot_index; |
view_index = GetIndexFromModelIndex(slot_index); |
} |
@@ -1341,78 +1336,87 @@ void AppsGridView::ExtractDragLocation(const ui::LocatedEvent& event, |
#endif |
} |
-void AppsGridView::CalculateDropTarget(const gfx::Point& drag_point, |
- bool use_page_button_hovering) { |
- if (EnableFolderDragDropUI()) { |
- CalculateDropTargetWithFolderEnabled(drag_point, use_page_button_hovering); |
- return; |
- } |
- |
- drop_attempt_ = DROP_FOR_REORDER; |
+void AppsGridView::CalculateDropTarget() { |
+ DCHECK(drag_view_); |
- int current_page = pagination_model_.selected_page(); |
- gfx::Point point(drag_point); |
- if (!IsPointWithinDragBuffer(drag_point)) { |
- point = drag_start_grid_view_; |
- current_page = drag_start_page_; |
- } |
- |
- if (use_page_button_hovering && page_switcher_view_ && |
- page_switcher_view_->bounds().Contains(point)) { |
- gfx::Point page_switcher_point(point); |
- views::View::ConvertPointToTarget(this, page_switcher_view_, |
- &page_switcher_point); |
- int page = page_switcher_view_->GetPageForPoint(page_switcher_point); |
- if (pagination_model_.is_valid_page(page)) { |
- reorder_drop_target_.page = page; |
- reorder_drop_target_.slot = tiles_per_page() - 1; |
+ gfx::Point point = drag_view_->icon()->bounds().CenterPoint(); |
+ views::View::ConvertPointToTarget(drag_view_, this, &point); |
+ if (!IsPointWithinDragBuffer(point)) { |
+ // Reset the reorder target to the original position if the cursor is |
+ // outside the drag buffer. |
+ if (IsDraggingForReparentInRootLevelGridView()) { |
+ drop_attempt_ = DROP_FOR_NONE; |
+ return; |
} |
- } else { |
- gfx::Rect bounds(GetContentsBounds()); |
- const int drop_row = (point.y() - bounds.y()) / kPreferredTileHeight; |
- const int drop_col = std::min(cols_ - 1, |
- (point.x() - bounds.x()) / kPreferredTileWidth); |
- reorder_drop_target_.page = current_page; |
- reorder_drop_target_.slot = std::max( |
- 0, std::min(tiles_per_page() - 1, drop_row * cols_ + drop_col)); |
+ reorder_drop_target_ = drag_view_init_index_; |
+ drop_attempt_ = DROP_FOR_REORDER; |
+ return; |
} |
- // Limits to the last possible slot on last page. |
- if (reorder_drop_target_.page == pagination_model_.total_pages() - 1) { |
- reorder_drop_target_.slot = |
- std::min((view_model_.view_size() - 1) % tiles_per_page(), |
- reorder_drop_target_.slot); |
+ if (EnableFolderDragDropUI() && |
+ CalculateFolderDropTarget(point, &folder_drop_target_)) { |
+ drop_attempt_ = DROP_FOR_FOLDER; |
+ return; |
} |
-} |
- |
-void AppsGridView::CalculateDropTargetWithFolderEnabled( |
- const gfx::Point& drag_point, |
- bool use_page_button_hovering) { |
- gfx::Point point(drag_point); |
- if (!IsPointWithinDragBuffer(drag_point)) { |
- point = drag_start_grid_view_; |
+ drop_attempt_ = DROP_FOR_REORDER; |
+ CalculateReorderDropTarget(point, &reorder_drop_target_); |
+} |
+ |
+bool AppsGridView::CalculateFolderDropTarget(const gfx::Point& point, |
+ Index* drop_target) const { |
+ Index nearest_tile_index(GetNearestTileIndexForPoint(point)); |
+ int distance_to_tile_center = |
+ (point - GetExpectedTileBounds(nearest_tile_index.slot).CenterPoint()) |
+ .Length(); |
+ if (nearest_tile_index != reorder_placeholder_ && |
+ distance_to_tile_center < kFolderDroppingCircleRadius && |
+ !IsFolderItem(drag_view_->item()) && |
+ CanDropIntoTarget(nearest_tile_index)) { |
+ *drop_target = nearest_tile_index; |
+ return true; |
} |
- if (use_page_button_hovering && page_switcher_view_ && |
- page_switcher_view_->bounds().Contains(point)) { |
- gfx::Point page_switcher_point(point); |
- views::View::ConvertPointToTarget(this, page_switcher_view_, |
- &page_switcher_point); |
- int page = page_switcher_view_->GetPageForPoint(page_switcher_point); |
- if (pagination_model_.is_valid_page(page)) |
- drop_attempt_ = DROP_FOR_NONE; |
+ return false; |
+} |
+ |
+void AppsGridView::CalculateReorderDropTarget(const gfx::Point& point, |
+ Index* drop_target) const { |
+ gfx::Rect bounds = GetContentsBounds(); |
+ Index grid_index = GetNearestTileIndexForPoint(point); |
+ gfx::Point reorder_placeholder_center = |
+ GetExpectedTileBounds(reorder_placeholder_.slot).CenterPoint(); |
+ |
+ int x_offset_direction = 0; |
+ if (grid_index == reorder_placeholder_) { |
+ x_offset_direction = reorder_placeholder_center.x() < point.x() ? -1 : 1; |
} else { |
- DCHECK(drag_view_); |
- // Try to find the nearest target for folder dropping or re-ordering. |
- CalculateNearestTileForDragView(); |
+ x_offset_direction = reorder_placeholder_ < grid_index ? -1 : 1; |
} |
+ |
+ int row = grid_index.slot / cols_; |
+ |
+ // Offset the target column based on the direction of the target. This will |
+ // result in earlier targets getting their reorder zone shifted backwards |
+ // and later targets getting their reorder zones shifted forwards. |
+ // |
+ // This makes eordering feel like the user is slotting items into the spaces |
+ // between apps. |
+ int x_offset = x_offset_direction * |
+ (kPreferredTileWidth - kFolderDroppingCircleRadius) / 2; |
+ int col = (point.x() - bounds.x() + x_offset) / kPreferredTileWidth; |
+ col = ClampToRange(col, 0, cols_ - 1); |
+ *drop_target = |
+ std::min(Index(pagination_model_.selected_page(), row * cols_ + col), |
+ GetLastViewIndex()); |
} |
void AppsGridView::OnReorderTimer() { |
- if (drop_attempt_ == DROP_FOR_REORDER) |
+ if (drop_attempt_ == DROP_FOR_REORDER) { |
+ reorder_placeholder_ = reorder_drop_target_; |
AnimateToIdealBounds(); |
+ } |
} |
void AppsGridView::OnFolderItemReparentTimer() { |
@@ -1513,7 +1517,7 @@ void AppsGridView::EndDragFromReparentItemInRootLevel( |
DCHECK(IsDraggingForReparentInRootLevelGridView()); |
bool cancel_reparent = cancel_drag || drop_attempt_ == DROP_FOR_NONE; |
if (!events_forwarded_to_drag_drop_host && !cancel_reparent) { |
- CalculateDropTarget(last_drag_point_, true); |
+ CalculateDropTarget(); |
if (drop_attempt_ == DROP_FOR_REORDER && |
IsValidIndex(reorder_drop_target_)) { |
ReparentItemForReorder(drag_view_, reorder_drop_target_); |
@@ -1999,7 +2003,7 @@ void AppsGridView::TotalPagesChanged() { |
void AppsGridView::SelectedPageChanged(int old_selected, int new_selected) { |
if (dragging()) { |
- CalculateDropTarget(last_drag_point_, true); |
+ CalculateDropTarget(); |
Layout(); |
MaybeStartPageFlipTimer(last_drag_point_); |
} else { |
@@ -2042,15 +2046,14 @@ void AppsGridView::OnImplicitAnimationsCompleted() { |
bool AppsGridView::EnableFolderDragDropUI() { |
// Enable drag and drop folder UI only if it is at the app list root level |
- // and the switch is on and the target folder can still accept new items. |
- return model_->folders_enabled() && !folder_delegate_ && |
- CanDropIntoTarget(folder_drop_target_); |
+ // and the switch is on. |
+ return model_->folders_enabled() && !folder_delegate_; |
} |
-bool AppsGridView::CanDropIntoTarget(const Index& drop_target) { |
- views::View* target_view = GetViewAtSlotOnCurrentPage(drop_target.slot); |
+bool AppsGridView::CanDropIntoTarget(const Index& drop_target) const { |
+ views::View* target_view = GetViewAtIndex(drop_target); |
if (!target_view) |
- return true; |
+ return false; |
AppListItem* target_item = |
static_cast<AppListItemView*>(target_view)->item(); |
@@ -2061,124 +2064,14 @@ bool AppsGridView::CanDropIntoTarget(const Index& drop_target) { |
!IsOEMFolderItem(target_item); |
} |
-// TODO(jennyz): Optimize the calculation for finding nearest tile. |
-void AppsGridView::CalculateNearestTileForDragView() { |
- Index nearest_tile; |
- nearest_tile.page = -1; |
- nearest_tile.slot = -1; |
- int d_min = -1; |
- |
- // Calculate the top left tile |drag_view| intersects. |
- gfx::Point pt = drag_view_->bounds().origin(); |
- CalculateNearestTileForVertex(pt, &nearest_tile, &d_min); |
- |
- // Calculate the top right tile |drag_view| intersects. |
- pt = drag_view_->bounds().top_right(); |
- CalculateNearestTileForVertex(pt, &nearest_tile, &d_min); |
- |
- // Calculate the bottom left tile |drag_view| intersects. |
- pt = drag_view_->bounds().bottom_left(); |
- CalculateNearestTileForVertex(pt, &nearest_tile, &d_min); |
- |
- // Calculate the bottom right tile |drag_view| intersects. |
- pt = drag_view_->bounds().bottom_right(); |
- CalculateNearestTileForVertex(pt, &nearest_tile, &d_min); |
- |
- const int d_folder_dropping = |
- kFolderDroppingCircleRadius + kGridIconDimension / 2; |
- const int d_reorder = kReorderDroppingCircleRadius + kGridIconDimension / 2; |
- |
- // If user drags an item across pages to the last page, and targets it |
- // to the last empty slot on it, push the last item for re-ordering. |
- if (IsLastPossibleDropTarget(nearest_tile) && d_min < d_reorder) { |
- drop_attempt_ = DROP_FOR_REORDER; |
- nearest_tile.slot = nearest_tile.slot - 1; |
- reorder_drop_target_ = nearest_tile; |
- return; |
- } |
- |
- if (IsValidIndex(nearest_tile)) { |
- if (d_min < d_folder_dropping) { |
- views::View* target_view = GetViewAtSlotOnCurrentPage(nearest_tile.slot); |
- if (target_view && |
- !IsFolderItem(static_cast<AppListItemView*>(drag_view_)->item())) { |
- // If a non-folder item is dragged to the target slot with an item |
- // sitting on it, attempt to drop the dragged item into the folder |
- // containing the item on nearest_tile. |
- drop_attempt_ = DROP_FOR_FOLDER; |
- folder_drop_target_ = nearest_tile; |
- return; |
- } else { |
- // If the target slot is blank, or the dragged item is a folder, attempt |
- // to re-order. |
- drop_attempt_ = DROP_FOR_REORDER; |
- reorder_drop_target_ = nearest_tile; |
- return; |
- } |
- } else if (d_min < d_reorder) { |
- // Entering the re-order circle of the slot. |
- drop_attempt_ = DROP_FOR_REORDER; |
- reorder_drop_target_ = nearest_tile; |
- return; |
- } |
- } |
- |
- // If |drag_view| is not entering the re-order or folder dropping region of |
- // any items, cancel any previous re-order or folder dropping timer. |
- drop_attempt_ = DROP_FOR_NONE; |
- reorder_timer_.Stop(); |
- folder_dropping_timer_.Stop(); |
- |
- // When dragging for reparent a folder item, it should go back to its parent |
- // folder item if there is no drop target. |
- if (IsDraggingForReparentInRootLevelGridView()) { |
- DCHECK(activated_folder_item_view_); |
- folder_drop_target_ = GetIndexOfView(activated_folder_item_view_); |
- } |
-} |
- |
-void AppsGridView::CalculateNearestTileForVertex(const gfx::Point& vertex, |
- Index* nearest_tile, |
- int* d_min) { |
- Index target_index; |
- gfx::Rect target_bounds = GetTileBoundsForPoint(vertex, &target_index); |
- |
- if (target_bounds.IsEmpty() || target_index == *nearest_tile) |
- return; |
- |
- // Do not count the tile, where drag_view_ used to sit on and is still moving |
- // on top of it, in calculating nearest tile for drag_view_. |
- views::View* target_view = GetViewAtSlotOnCurrentPage(target_index.slot); |
- if (target_index == drag_view_init_index_ && !target_view && |
- !IsDraggingForReparentInRootLevelGridView()) { |
- return; |
- } |
- |
- int d_center = GetDistanceBetweenRects(drag_view_->bounds(), target_bounds); |
- if (*d_min < 0 || d_center < *d_min) { |
- *d_min = d_center; |
- *nearest_tile = target_index; |
- } |
-} |
- |
-gfx::Rect AppsGridView::GetTileBoundsForPoint(const gfx::Point& point, |
- Index *tile_index) { |
- // Check if |point| is outside of contents bounds. |
- gfx::Rect bounds(GetContentsBounds()); |
- if (!bounds.Contains(point)) |
- return gfx::Rect(); |
- |
- // Calculate which tile |point| is enclosed in. |
- int x = point.x(); |
- int y = point.y(); |
- int col = (x - bounds.x()) / kPreferredTileWidth; |
- int row = (y - bounds.y()) / kPreferredTileHeight; |
- gfx::Rect tile_rect = GetExpectedTileBounds(row, col); |
- |
- // Check if |point| is outside a valid item's tile. |
- Index index(pagination_model_.selected_page(), row * cols_ + col); |
- *tile_index = index; |
- return tile_rect; |
+AppsGridView::Index AppsGridView::GetNearestTileIndexForPoint( |
+ const gfx::Point& point) const { |
+ gfx::Rect bounds = GetContentsBounds(); |
+ int col = |
+ ClampToRange((point.x() - bounds.x()) / kPreferredTileWidth, 0, cols_); |
+ int row = ClampToRange( |
+ (point.y() - bounds.y()) / kPreferredTileHeight, 0, rows_per_page_); |
+ return Index(pagination_model_.selected_page(), row * cols_ + col); |
} |
gfx::Size AppsGridView::GetTileGridSize() const { |
@@ -2187,6 +2080,10 @@ gfx::Size AppsGridView::GetTileGridSize() const { |
return bounds.size(); |
} |
+gfx::Rect AppsGridView::GetExpectedTileBounds(int slot) const { |
+ return GetExpectedTileBounds(slot / cols_, slot % cols_); |
+} |
+ |
gfx::Rect AppsGridView::GetExpectedTileBounds(int row, int col) const { |
gfx::Rect bounds(GetContentsBounds()); |
gfx::Size tile_size(kPreferredTileWidth, kPreferredTileHeight); |
@@ -2195,12 +2092,6 @@ gfx::Rect AppsGridView::GetExpectedTileBounds(int row, int col) const { |
tile_size); |
} |
-bool AppsGridView::IsLastPossibleDropTarget(const Index& index) const { |
- int last_possible_slot = view_model_.view_size() % tiles_per_page(); |
- return (index.page == pagination_model_.total_pages() - 1 && |
- index.slot == last_possible_slot + 1); |
-} |
- |
views::View* AppsGridView::GetViewAtSlotOnCurrentPage(int slot) { |
if (slot < 0) |
return NULL; |