Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / ui / app_list / views / apps_grid_view.cc
index f150eb6..f9305d5 100644 (file)
@@ -13,6 +13,7 @@
 #include "ui/app_list/app_list_folder_item.h"
 #include "ui/app_list/app_list_item.h"
 #include "ui/app_list/app_list_switches.h"
+#include "ui/app_list/pagination_controller.h"
 #include "ui/app_list/views/app_list_drag_and_drop_host.h"
 #include "ui/app_list/views/app_list_folder_view.h"
 #include "ui/app_list/views/app_list_item_view.h"
@@ -23,6 +24,8 @@
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/events/event.h"
 #include "ui/gfx/animation/animation.h"
+#include "ui/gfx/geometry/vector2d.h"
+#include "ui/gfx/geometry/vector2d_conversions.h"
 #include "ui/views/border.h"
 #include "ui/views/view_model_utils.h"
 #include "ui/views/widget/widget.h"
@@ -57,7 +60,7 @@ namespace {
 const int kDragBufferPx = 20;
 
 // Padding space in pixels for fixed layout.
-const int kLeftRightPadding = 20;
+const int kLeftRightPadding = 23;
 const int kTopPadding = 1;
 
 // Padding space in pixels between pages.
@@ -67,6 +70,13 @@ const int kPagePadding = 40;
 const int kPreferredTileWidth = 88;
 const int kPreferredTileHeight = 98;
 
+const int kExperimentalPreferredTileWidth = 90;
+const int kExperimentalPrefferedTileHeight = 90;
+
+// Padding on each side of a tile.
+const int kExperimentalTileLeftRightPadding = 15;
+const int kExperimentalTileTopBottomPadding = 11;
+
 // Width in pixels of the area on the sides that triggers a page flip.
 const int kPageFlipZoneSize = 40;
 
@@ -90,8 +100,24 @@ 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;
 
+// Returns the size of a tile view excluding its padding.
+gfx::Size GetTileViewSize() {
+  return switches::IsExperimentalAppListEnabled()
+             ? gfx::Size(kExperimentalPreferredTileWidth,
+                         kExperimentalPrefferedTileHeight)
+             : gfx::Size(kPreferredTileWidth, kPreferredTileHeight);
+}
+
+// Returns the size of a tile view inccluding its padding.
+gfx::Size GetTotalTileSize() {
+  gfx::Size size = GetTileViewSize();
+  if (switches::IsExperimentalAppListEnabled())
+    size.Enlarge(2 * kExperimentalTileLeftRightPadding,
+                 2 * kExperimentalTileTopBottomPadding);
+  return size;
+}
 
 // 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
@@ -186,12 +212,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);
@@ -203,6 +223,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)
@@ -363,8 +387,17 @@ AppsGridView::AppsGridView(AppsGridViewDelegate* delegate)
                                            kOverscrollPageTransitionDurationMs);
 
   pagination_model_.AddObserver(this);
-  page_switcher_view_ = new PageSwitcher(&pagination_model_);
-  AddChildView(page_switcher_view_);
+  // The experimental app list transitions vertically.
+  PaginationController::ScrollAxis scroll_axis =
+      app_list::switches::IsExperimentalAppListEnabled()
+          ? PaginationController::SCROLL_AXIS_VERTICAL
+          : PaginationController::SCROLL_AXIS_HORIZONTAL;
+  pagination_controller_.reset(
+      new PaginationController(&pagination_model_, scroll_axis));
+  if (!switches::IsExperimentalAppListEnabled()) {
+    page_switcher_view_ = new PageSwitcher(&pagination_model_);
+    AddChildView(page_switcher_view_);
+  }
 }
 
 AppsGridView::~AppsGridView() {
@@ -392,7 +425,10 @@ void AppsGridView::SetLayout(int cols, int rows_per_page) {
   rows_per_page_ = rows_per_page;
 
   SetBorder(views::Border::CreateEmptyBorder(
-      kTopPadding, kLeftRightPadding, 0, kLeftRightPadding));
+      switches::IsExperimentalAppListEnabled() ? 0 : kTopPadding,
+      kLeftRightPadding,
+      0,
+      kLeftRightPadding));
 }
 
 void AppsGridView::ResetForShowApps() {
@@ -476,6 +512,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());
 }
@@ -603,30 +640,25 @@ 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_drop_target = drop_target_;
+  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();
-
-  gfx::Point page_switcher_point(last_drag_point_);
-  views::View::ConvertPointToTarget(this, page_switcher_view_,
-                                    &page_switcher_point);
-  page_switcher_view_->UpdateUIForDragPoint(page_switcher_point);
+  MaybeStartPageFlipTimer(last_drag_point_);
 
-  if (!EnableFolderDragDropUI()) {
-    if (last_drop_target != drop_target_)
-      AnimateToIdealBounds();
-    drag_view_->SetPosition(drag_view_start_ + drag_vector);
-    return;
+  if (page_switcher_view_) {
+    gfx::Point page_switcher_point(last_drag_point_);
+    views::View::ConvertPointToTarget(
+        this, page_switcher_view_, &page_switcher_point);
+    page_switcher_view_->UpdateUIForDragPoint(page_switcher_point);
   }
 
-  // Update drag with folder UI enabled.
-  if (last_drop_target != drop_target_ ||
+  if (last_folder_drop_target != folder_drop_target_ ||
+      last_reorder_drop_target != reorder_drop_target_ ||
       last_drop_attempt != drop_attempt_) {
     if (drop_attempt_ == DROP_FOR_REORDER) {
       folder_dropping_timer_.Stop();
@@ -641,10 +673,8 @@ void AppsGridView::UpdateDrag(Pointer pointer, const gfx::Point& point) {
     }
 
     // Reset the previous drop target.
-    SetAsFolderDroppingTarget(last_drop_target, false);
+    SetAsFolderDroppingTarget(last_folder_drop_target, false);
   }
-
-  drag_view_->SetPosition(drag_view_start_ + drag_vector);
 }
 
 void AppsGridView::EndDrag(bool cancel) {
@@ -683,16 +713,12 @@ void AppsGridView::EndDrag(bool cancel) {
 
     if (!cancel && dragging()) {
       // Regular drag ending path, ie, not for reparenting.
-      CalculateDropTarget(last_drag_point_, true);
-      if (IsValidIndex(drop_target_)) {
-        if (!EnableFolderDragDropUI()) {
-            MoveItemInModel(drag_view_, drop_target_);
-        } else {
-          if (drop_attempt_ == DROP_FOR_REORDER)
-            MoveItemInModel(drag_view_, drop_target_);
-          else if (drop_attempt_ == DROP_FOR_FOLDER)
-            MoveItemToFolder(drag_view_, drop_target_);
-        }
+      CalculateDropTarget();
+      if (EnableFolderDragDropUI() && drop_attempt_ == DROP_FOR_FOLDER &&
+          IsValidIndex(folder_drop_target_)) {
+        MoveItemToFolder(drag_view_, folder_drop_target_);
+      } else if (IsValidIndex(reorder_drop_target_)) {
+        MoveItemInModel(drag_view_, reorder_drop_target_);
       }
     }
   }
@@ -704,7 +730,7 @@ void AppsGridView::EndDrag(bool cancel) {
     if (landed_in_drag_and_drop_host) {
       // Move the item directly to the target location, avoiding the "zip back"
       // animation if the user was pinning it to the shelf.
-      int i = drop_target_.slot;
+      int i = reorder_drop_target_.slot;
       gfx::Rect bounds = view_model_.ideal_bounds(i);
       drag_view_->SetBoundsRect(bounds);
     }
@@ -718,7 +744,7 @@ void AppsGridView::EndDrag(bool cancel) {
   // is Run().
   CleanUpSynchronousDrag();
 
-  SetAsFolderDroppingTarget(drop_target_, false);
+  SetAsFolderDroppingTarget(folder_drop_target_, false);
   ClearDragState();
   AnimateToIdealBounds();
 
@@ -772,6 +798,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());
@@ -819,7 +849,9 @@ bool AppsGridView::IsDraggedView(const views::View* view) const {
 void AppsGridView::ClearDragState() {
   drop_attempt_ = DROP_FOR_NONE;
   drag_pointer_ = NONE;
-  drop_target_ = Index();
+  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();
@@ -846,11 +878,12 @@ void AppsGridView::SetDragAndDropHostOfCurrentAppList(
   drag_and_drop_host_ = drag_and_drop_host;
 }
 
-void AppsGridView::Prerender(int page_index) {
+void AppsGridView::Prerender() {
   Layout();
-  int start = std::max(0, (page_index - kPrerenderPages) * tiles_per_page());
+  int selected_page = std::max(0, pagination_model_.selected_page());
+  int start = std::max(0, (selected_page - kPrerenderPages) * tiles_per_page());
   int end = std::min(view_model_.view_size(),
-                     (page_index + 1 + kPrerenderPages) * tiles_per_page());
+                     (selected_page + 1 + kPrerenderPages) * tiles_per_page());
   for (int i = start; i < end; i++) {
     AppListItemView* v = static_cast<AppListItemView*>(view_model_.view_at(i));
     v->Prerender();
@@ -863,14 +896,12 @@ bool AppsGridView::IsAnimatingView(views::View* view) {
 
 gfx::Size AppsGridView::GetPreferredSize() const {
   const gfx::Insets insets(GetInsets());
-  const gfx::Size tile_size = gfx::Size(kPreferredTileWidth,
-                                        kPreferredTileHeight);
-  const int page_switcher_height =
-      page_switcher_view_->GetPreferredSize().height();
-  return gfx::Size(
-      tile_size.width() * cols_ + insets.width(),
-      tile_size.height() * rows_per_page_ +
-          page_switcher_height + insets.height());
+  int page_switcher_height = 0;
+  if (page_switcher_view_)
+    page_switcher_height = page_switcher_view_->GetPreferredSize().height();
+  gfx::Size size = GetTileGridSize();
+  size.Enlarge(insets.width(), insets.height() + page_switcher_height);
+  return size;
 }
 
 bool AppsGridView::GetDropFormats(
@@ -901,12 +932,14 @@ void AppsGridView::Layout() {
   }
   views::ViewModelUtils::SetViewBoundsToIdealBounds(pulsing_blocks_model_);
 
-  const int page_switcher_height =
-      page_switcher_view_->GetPreferredSize().height();
-  gfx::Rect rect(GetContentsBounds());
-  rect.set_y(rect.bottom() - page_switcher_height);
-  rect.set_height(page_switcher_height);
-  page_switcher_view_->SetBoundsRect(rect);
+  if (page_switcher_view_) {
+    const int page_switcher_height =
+        page_switcher_view_->GetPreferredSize().height();
+    gfx::Rect rect(GetContentsBounds());
+    rect.set_y(rect.bottom() - page_switcher_height);
+    rect.set_height(page_switcher_height);
+    page_switcher_view_->SetBoundsRect(rect);
+  }
 }
 
 bool AppsGridView::OnKeyPressed(const ui::KeyEvent& event) {
@@ -953,6 +986,11 @@ bool AppsGridView::OnKeyReleased(const ui::KeyEvent& event) {
   return handled;
 }
 
+bool AppsGridView::OnMouseWheel(const ui::MouseWheelEvent& event) {
+  return pagination_controller_->OnScroll(
+      gfx::Vector2d(event.x_offset(), event.y_offset()));
+}
+
 void AppsGridView::ViewHierarchyChanged(
     const ViewHierarchyChangedDetails& details) {
   if (!details.is_add && details.parent == this) {
@@ -971,6 +1009,22 @@ void AppsGridView::ViewHierarchyChanged(
   }
 }
 
+void AppsGridView::OnGestureEvent(ui::GestureEvent* event) {
+  if (pagination_controller_->OnGestureEvent(*event, GetContentsBounds()))
+    event->SetHandled();
+}
+
+void AppsGridView::OnScrollEvent(ui::ScrollEvent* event) {
+  if (event->type() == ui::ET_SCROLL_FLING_CANCEL)
+    return;
+
+  gfx::Vector2dF offset(event->x_offset(), event->y_offset());
+  if (pagination_controller_->OnScroll(gfx::ToFlooredVector2d(offset))) {
+    event->SetHandled();
+    event->StopPropagation();
+  }
+}
+
 void AppsGridView::Update() {
   DCHECK(!selected_view_ && !drag_view_);
   view_model_.Clear();
@@ -1012,8 +1066,7 @@ void AppsGridView::UpdatePulsingBlockViews() {
   }
 
   while (pulsing_blocks_model_.view_size() < desired) {
-    views::View* view = new PulsingBlockView(
-        gfx::Size(kPreferredTileWidth, kPreferredTileHeight), true);
+    views::View* view = new PulsingBlockView(GetTotalTileSize(), true);
     pulsing_blocks_model_.Add(view, 0);
     AddChildView(view);
   }
@@ -1080,6 +1133,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) {
@@ -1123,19 +1182,12 @@ void AppsGridView::MoveSelected(int page_delta,
 }
 
 void AppsGridView::CalculateIdealBounds() {
-  gfx::Rect rect(GetContentsBounds());
-  if (rect.IsEmpty())
-    return;
-
-  gfx::Size tile_size(kPreferredTileWidth, kPreferredTileHeight);
+  gfx::Size grid_size = GetTileGridSize();
 
-  gfx::Rect grid_rect(gfx::Size(tile_size.width() * cols_,
-                                tile_size.height() * rows_per_page_));
-  grid_rect.Intersect(rect);
-
-  // Page width including padding pixels. A tile.x + page_width means the same
-  // tile slot in the next page.
-  const int page_width = grid_rect.width() + kPagePadding;
+  // Page size including padding pixels. A tile.x + page_width means the same
+  // tile slot in the next page; similarly for tile.y + page_height.
+  const int page_width = grid_size.width() + kPagePadding;
+  const int page_height = grid_size.height() + kPagePadding;
 
   // If there is a transition, calculates offset for current and target page.
   const int current_page = pagination_model_.selected_page();
@@ -1143,53 +1195,59 @@ void AppsGridView::CalculateIdealBounds() {
       pagination_model_.transition();
   const bool is_valid = pagination_model_.is_valid_page(transition.target_page);
 
-  // Transition to right means negative offset.
+  // Transition to previous page means negative offset.
   const int dir = transition.target_page > current_page ? -1 : 1;
-  const int transition_offset = is_valid ?
-      transition.progress * page_width * dir : 0;
 
   const int total_views =
       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);
 
-    if (drop_target_ == view_index) {
-      if (EnableFolderDragDropUI() && drop_attempt_ == DROP_FOR_FOLDER) {
-        view_index = GetIndexFromModelIndex(slot_index);
-      } else if (!EnableFolderDragDropUI() ||
-                 drop_attempt_ == DROP_FOR_REORDER) {
-        ++slot_index;
-        view_index = GetIndexFromModelIndex(slot_index);
-      }
+    // Leaves a blank space in the grid for the current reorder placeholder.
+    if (reorder_placeholder_ == view_index) {
+      ++slot_index;
+      view_index = GetIndexFromModelIndex(slot_index);
     }
 
-    // Decides an x_offset for current item.
+    // Decide the x or y offset for current item.
     int x_offset = 0;
-    if (view_index.page < current_page)
-      x_offset = -page_width;
-    else if (view_index.page > current_page)
-      x_offset = page_width;
-
-    if (is_valid) {
-      if (view_index.page == current_page ||
-          view_index.page == transition.target_page) {
-        x_offset += transition_offset;
+    int y_offset = 0;
+
+    if (pagination_controller_->scroll_axis() ==
+        PaginationController::SCROLL_AXIS_HORIZONTAL) {
+      if (view_index.page < current_page)
+        x_offset = -page_width;
+      else if (view_index.page > current_page)
+        x_offset = page_width;
+
+      if (is_valid) {
+        if (view_index.page == current_page ||
+            view_index.page == transition.target_page) {
+          x_offset += transition.progress * page_width * dir;
+        }
+      }
+    } else {
+      if (view_index.page < current_page)
+        y_offset = -page_height;
+      else if (view_index.page > current_page)
+        y_offset = page_height;
+
+      if (is_valid) {
+        if (view_index.page == current_page ||
+            view_index.page == transition.target_page) {
+          y_offset += transition.progress * page_height * dir;
+        }
       }
     }
 
     const int row = view_index.slot / cols_;
     const int col = view_index.slot % cols_;
-    gfx::Rect tile_slot(
-        gfx::Point(grid_rect.x() + col * tile_size.width() + x_offset,
-                   grid_rect.y() + row * tile_size.height()),
-        tile_size);
+    gfx::Rect tile_slot = GetExpectedTileBounds(row, col);
+    tile_slot.Offset(x_offset, y_offset);
     if (i < view_model_.view_size()) {
       view_model_.set_ideal_bounds(i, tile_slot);
     } else {
@@ -1220,7 +1278,7 @@ void AppsGridView::AnimateToIdealBounds() {
     const bool visible = current_visible || target_visible;
 
     const int y_diff = target.y() - current.y();
-    if (visible && y_diff && y_diff % kPreferredTileHeight == 0) {
+    if (visible && y_diff && y_diff % GetTotalTileSize().height() == 0) {
       AnimationBetweenRows(view,
                            current_visible,
                            current,
@@ -1262,12 +1320,13 @@ void AppsGridView::AnimationBetweenRows(views::View* view,
     view->layer()->SetOpacity(0.f);
   }
 
+  gfx::Size total_tile_size = GetTotalTileSize();
   gfx::Rect current_out(current);
-  current_out.Offset(dir * kPreferredTileWidth, 0);
+  current_out.Offset(dir * total_tile_size.width(), 0);
 
   gfx::Rect target_in(target);
   if (animate_target)
-    target_in.Offset(-dir * kPreferredTileWidth, 0);
+    target_in.Offset(-dir * total_tile_size.width(), 0);
   view->SetBoundsRect(target_in);
   bounds_animator_.AnimateViewTo(view, target);
 
@@ -1303,77 +1362,90 @@ 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);
+void AppsGridView::CalculateDropTarget() {
+  DCHECK(drag_view_);
+
+  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;
+    }
+
+    reorder_drop_target_ = drag_view_init_index_;
+    drop_attempt_ = DROP_FOR_REORDER;
     return;
   }
 
-  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 (EnableFolderDragDropUI() &&
+      CalculateFolderDropTarget(point, &folder_drop_target_)) {
+    drop_attempt_ = DROP_FOR_FOLDER;
+    return;
   }
 
-  if (use_page_button_hovering &&
-      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_target_.page = page;
-      drop_target_.slot = tiles_per_page() - 1;
-    }
-  } 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);
+  drop_attempt_ = DROP_FOR_REORDER;
+  CalculateReorderDropTarget(point, &reorder_drop_target_);
+}
 
-    drop_target_.page = current_page;
-    drop_target_.slot = std::max(0, std::min(
-        tiles_per_page() - 1,
-        drop_row * cols_ + drop_col));
+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;
+    DCHECK(IsValidIndex(*drop_target));
+    return true;
   }
 
-  // Limits to the last possible slot on last page.
-  if (drop_target_.page == pagination_model_.total_pages() - 1) {
-    drop_target_.slot = std::min(
-        (view_model_.view_size() - 1) % tiles_per_page(),
-        drop_target_.slot);
-  }
+  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();
 
-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_;
-  }
-
-  if (use_page_button_hovering &&
-      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;
+  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.
-    drop_target_ = GetNearestTileForDragView();
+    x_offset_direction = reorder_placeholder_ < grid_index ? -1 : 1;
   }
+
+  gfx::Size total_tile_size = GetTotalTileSize();
+  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 *
+                 (total_tile_size.width() - kFolderDroppingCircleRadius) / 2;
+  int col = (point.x() - bounds.x() + x_offset) / total_tile_size.width();
+  col = ClampToRange(col, 0, cols_ - 1);
+  *drop_target =
+      std::min(Index(pagination_model_.selected_page(), row * cols_ + col),
+               GetLastViewIndex());
+  DCHECK(IsValidIndex(*drop_target));
 }
 
 void AppsGridView::OnReorderTimer() {
-  if (drop_attempt_ == DROP_FOR_REORDER)
+  if (drop_attempt_ == DROP_FOR_REORDER) {
+    reorder_placeholder_ = reorder_drop_target_;
     AnimateToIdealBounds();
+  }
 }
 
 void AppsGridView::OnFolderItemReparentTimer() {
@@ -1392,7 +1464,7 @@ void AppsGridView::OnFolderItemReparentTimer() {
 
 void AppsGridView::OnFolderDroppingTimer() {
   if (drop_attempt_ == DROP_FOR_FOLDER)
-    SetAsFolderDroppingTarget(drop_target_, true);
+    SetAsFolderDroppingTarget(folder_drop_target_, true);
 }
 
 void AppsGridView::UpdateDragStateInsideFolder(Pointer pointer,
@@ -1474,13 +1546,15 @@ 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);
-    if (IsValidIndex(drop_target_)) {
-      if (drop_attempt_ == DROP_FOR_REORDER) {
-        ReparentItemForReorder(drag_view_, drop_target_);
-      } else if (drop_attempt_ == DROP_FOR_FOLDER) {
-        ReparentItemToAnotherFolder(drag_view_, drop_target_);
-      }
+    CalculateDropTarget();
+    if (drop_attempt_ == DROP_FOR_REORDER &&
+        IsValidIndex(reorder_drop_target_)) {
+      ReparentItemForReorder(drag_view_, reorder_drop_target_);
+    } else if (drop_attempt_ == DROP_FOR_FOLDER &&
+               IsValidIndex(folder_drop_target_)) {
+      ReparentItemToAnotherFolder(drag_view_, folder_drop_target_);
+    } else {
+      NOTREACHED();
     }
     SetViewHidden(drag_view_, false /* show */, true /* no animate */);
   }
@@ -1489,7 +1563,7 @@ void AppsGridView::EndDragFromReparentItemInRootLevel(
   // is Run().
   CleanUpSynchronousDrag();
 
-  SetAsFolderDroppingTarget(drop_target_, false);
+  SetAsFolderDroppingTarget(folder_drop_target_, false);
   if (cancel_reparent) {
     CancelFolderItemReparent(drag_view_);
   } else {
@@ -1516,7 +1590,7 @@ void AppsGridView::EndDragForReparentInHiddenFolderGridView() {
   // is Run().
   CleanUpSynchronousDrag();
 
-  SetAsFolderDroppingTarget(drop_target_, false);
+  SetAsFolderDroppingTarget(folder_drop_target_, false);
   ClearDragState();
 }
 
@@ -1597,21 +1671,30 @@ void AppsGridView::MaybeStartPageFlipTimer(const gfx::Point& drag_point) {
     StopPageFlipTimer();
   int new_page_flip_target = -1;
 
-  if (page_switcher_view_->bounds().Contains(drag_point)) {
-    gfx::Point page_switcher_point(drag_point);
-    views::View::ConvertPointToTarget(this, page_switcher_view_,
-                                      &page_switcher_point);
-    new_page_flip_target =
-        page_switcher_view_->GetPageForPoint(page_switcher_point);
-  }
+  // Drag zones are at the edges of the scroll axis.
+  if (pagination_controller_->scroll_axis() ==
+      PaginationController::SCROLL_AXIS_VERTICAL) {
+    if (drag_point.y() < kPageFlipZoneSize)
+      new_page_flip_target = pagination_model_.selected_page() - 1;
+    else if (drag_point.y() > height() - kPageFlipZoneSize)
+      new_page_flip_target = pagination_model_.selected_page() + 1;
+  } else {
+    if (page_switcher_view_->bounds().Contains(drag_point)) {
+      gfx::Point page_switcher_point(drag_point);
+      views::View::ConvertPointToTarget(
+          this, page_switcher_view_, &page_switcher_point);
+      new_page_flip_target =
+          page_switcher_view_->GetPageForPoint(page_switcher_point);
+    }
 
-  // TODO(xiyuan): Fix this for RTL.
-  if (new_page_flip_target == -1 && drag_point.x() < kPageFlipZoneSize)
-    new_page_flip_target = pagination_model_.selected_page() - 1;
+    // TODO(xiyuan): Fix this for RTL.
+    if (new_page_flip_target == -1 && drag_point.x() < kPageFlipZoneSize)
+      new_page_flip_target = pagination_model_.selected_page() - 1;
 
-  if (new_page_flip_target == -1 &&
-      drag_point.x() > width() - kPageFlipZoneSize) {
-    new_page_flip_target = pagination_model_.selected_page() + 1;
+    if (new_page_flip_target == -1 &&
+        drag_point.x() > width() - kPageFlipZoneSize) {
+      new_page_flip_target = pagination_model_.selected_page() + 1;
+    }
   }
 
   if (new_page_flip_target == page_flip_target_)
@@ -1951,7 +2034,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 {
@@ -1994,15 +2077,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(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();
@@ -2013,142 +2095,39 @@ bool AppsGridView::CanDropIntoTarget(const Index& drop_target) {
          !IsOEMFolderItem(target_item);
 }
 
-// TODO(jennyz): Optimize the calculation for finding nearest tile.
-AppsGridView::Index AppsGridView::GetNearestTileForDragView() {
-  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;
-    return nearest_tile;
-  }
-
-  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;
-        return nearest_tile;
-      } else {
-        // If the target slot is blank, or the dragged item is a folder, attempt
-        // to re-order.
-        drop_attempt_ = DROP_FOR_REORDER;
-        return nearest_tile;
-      }
-    } else if (d_min < d_reorder) {
-      // Entering the re-order circle of the slot.
-      drop_attempt_ = DROP_FOR_REORDER;
-      return nearest_tile;
-    }
-  }
-
-  // If |drag_view| is not entering the re-order or fold dropping region of
-  // any items, cancel any previous re-order or folder dropping timer, and
-  // return itself.
-  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_);
-    return GetIndexOfView(activated_folder_item_view_);
-  }
-
-  return GetIndexOfView(drag_view_);
+AppsGridView::Index AppsGridView::GetNearestTileIndexForPoint(
+    const gfx::Point& point) const {
+  gfx::Rect bounds = GetContentsBounds();
+  gfx::Size total_tile_size = GetTotalTileSize();
+  int col = ClampToRange(
+      (point.x() - bounds.x()) / total_tile_size.width(), 0, cols_ - 1);
+  int row = ClampToRange((point.y() - bounds.y()) / total_tile_size.height(),
+                         0,
+                         rows_per_page_ - 1);
+  return Index(pagination_model_.selected_page(), row * cols_ + col);
 }
 
-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::Size AppsGridView::GetTileGridSize() const {
+  gfx::Rect bounds = GetExpectedTileBounds(0, 0);
+  bounds.Union(GetExpectedTileBounds(rows_per_page_ - 1, cols_ - 1));
+  if (switches::IsExperimentalAppListEnabled())
+    bounds.Inset(-kExperimentalTileLeftRightPadding,
+                 -kExperimentalTileTopBottomPadding);
+  return bounds.size();
 }
 
-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 = GetTileBounds(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;
+gfx::Rect AppsGridView::GetExpectedTileBounds(int slot) const {
+  return GetExpectedTileBounds(slot / cols_, slot % cols_);
 }
 
-gfx::Rect AppsGridView::GetTileBounds(int row, int col) const {
+gfx::Rect AppsGridView::GetExpectedTileBounds(int row, int col) const {
   gfx::Rect bounds(GetContentsBounds());
-  gfx::Size tile_size(kPreferredTileWidth, kPreferredTileHeight);
-  gfx::Rect grid_rect(gfx::Size(tile_size.width() * cols_,
-                                tile_size.height() * rows_per_page_));
-  grid_rect.Intersect(bounds);
-  gfx::Rect tile_rect(
-      gfx::Point(grid_rect.x() + col * tile_size.width(),
-                 grid_rect.y() + row * tile_size.height()),
-      tile_size);
-  return tile_rect;
-}
-
-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);
+  gfx::Size total_tile_size = GetTotalTileSize();
+  gfx::Rect tile_bounds(gfx::Point(bounds.x() + col * total_tile_size.width(),
+                                   bounds.y() + row * total_tile_size.height()),
+                        total_tile_size);
+  tile_bounds.ClampToCenteredSize(GetTileViewSize());
+  return tile_bounds;
 }
 
 views::View* AppsGridView::GetViewAtSlotOnCurrentPage(int slot) {
@@ -2158,7 +2137,7 @@ views::View* AppsGridView::GetViewAtSlotOnCurrentPage(int slot) {
   // Calculate the original bound of the tile at |index|.
   int row = slot / cols_;
   int col = slot % cols_;
-  gfx::Rect tile_rect = GetTileBounds(row, col);
+  gfx::Rect tile_rect = GetExpectedTileBounds(row, col);
 
   for (int i = 0; i < view_model_.view_size(); ++i) {
     views::View* view = view_model_.view_at(i);