Commit 9772c3d2 authored by Weidong Guo's avatar Weidong Guo Committed by Commit Bot

Implement UI-side syncing of apps grid gap

Changes:
1. Load item list with "page break" items into paged view structure
   which will be displayed to the user.
2. Add "page break" item based on the changed paged view structure after
   user operations (Add, Remove, Move an item).
3. Since model index (item view's index in view model) is no longer the
   same as item index (item's index in item list), separate them.

BUG=848917

Change-Id: I5d4c750ab87045a0e4930e549a066f24a1c1cf66
Reviewed-on: https://chromium-review.googlesource.com/1083492Reviewed-by: default avatarMichael Wasserman <msw@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Reviewed-by: default avatarSteven Bennetts <stevenjb@chromium.org>
Commit-Queue: Weidong Guo <weidongg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#564973}
parent 6ebbe0b8
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "ui/app_list/paged_view_structure.h" #include "ui/app_list/paged_view_structure.h"
#include "ash/app_list/model/app_list_item.h"
#include "ui/app_list/views/app_list_item_view.h" #include "ui/app_list/views/app_list_item_view.h"
#include "ui/app_list/views/apps_grid_view.h" #include "ui/app_list/views/apps_grid_view.h"
#include "ui/views/view_model.h" #include "ui/views/view_model.h"
...@@ -19,21 +20,63 @@ PagedViewStructure::PagedViewStructure(const PagedViewStructure& other) = ...@@ -19,21 +20,63 @@ PagedViewStructure::PagedViewStructure(const PagedViewStructure& other) =
PagedViewStructure::~PagedViewStructure() = default; PagedViewStructure::~PagedViewStructure() = default;
void PagedViewStructure::LoadFromMetadata() { void PagedViewStructure::LoadFromMetadata() {
// TODO(weidongg): Change the fake loading here and implement loading view
// structure from position and page position in metadata.
auto* view_model = apps_grid_view_->view_model(); auto* view_model = apps_grid_view_->view_model();
const auto* item_list = apps_grid_view_->item_list_;
int model_index = 0;
pages_.clear(); pages_.clear();
pages_.emplace_back(); pages_.emplace_back();
auto& page = pages_[0]; for (size_t i = 0; i < item_list->item_count(); ++i) {
for (int i = 0; i < view_model->view_size(); ++i) { const auto* item = item_list->item_at(i);
page.emplace_back(view_model->view_at(i)); auto* current_page = &pages_.back();
if (item->is_page_break()) {
// Create a new page if a "page break" item is detected and current page
// is not empty. Otherwise, ignore the "page break" item.
if (!current_page->empty())
pages_.emplace_back();
continue;
}
// Create a new page if the current page is full.
const size_t current_page_max_items =
apps_grid_view_->TilesPerPage(pages_.size() - 1);
if (current_page->size() == current_page_max_items) {
pages_.emplace_back();
current_page = &pages_.back();
}
current_page->emplace_back(view_model->view_at(model_index++));
} }
Sanitize();
// Remove trailing empty page if exist.
if (pages_.back().empty())
pages_.erase(pages_.end() - 1);
} }
void PagedViewStructure::SaveToMetadata() { void PagedViewStructure::SaveToMetadata() {
// TODO(weidongg): Implement saving page position change of each item into auto* item_list = apps_grid_view_->item_list_;
// metadata. size_t item_index = 0;
for (const auto& page : pages_) {
// Skip all "page break" items before current page and after previous page.
while (item_index < item_list->item_count() &&
item_list->item_at(item_index)->is_page_break()) {
++item_index;
}
item_index += page.size();
if (item_index < item_list->item_count() &&
!item_list->item_at(item_index)->is_page_break()) {
// There's no "page break" item at the end of current page, so add one to
// push overflowing items to next page.
apps_grid_view_->model_->AddPageBreakItemAfter(
item_list->item_at(item_index - 1));
}
}
// Note that we do not remove redundant "page break" items here because the
// item list we can access here may not be complete (e.g. Devices that do not
// support ARC++ or Crostini apps filter out those items.). We leave this
// operation to AppListSyncableService which has complete item list.
} }
bool PagedViewStructure::Sanitize() { bool PagedViewStructure::Sanitize() {
...@@ -209,6 +252,51 @@ int PagedViewStructure::GetTargetModelIndexForMove( ...@@ -209,6 +252,51 @@ int PagedViewStructure::GetTargetModelIndexForMove(
return target_model_index; return target_model_index;
} }
int PagedViewStructure::GetTargetItemIndexForMove(
AppListItemView* moved_view,
const GridIndex& index) const {
GridIndex current_index(0, 0);
size_t current_item_index = 0;
size_t offset = 0;
const auto* item_list = apps_grid_view_->item_list_;
// Skip the leading "page break" items.
while (current_item_index < item_list->item_count() &&
item_list->item_at(current_item_index)->is_page_break()) {
++current_item_index;
}
while (current_item_index < item_list->item_count()) {
while (current_item_index < item_list->item_count() &&
!item_list->item_at(current_item_index)->is_page_break() &&
current_index != index) {
if (moved_view->item() == item_list->item_at(current_item_index) &&
current_index.page < index.page) {
// If the item view is moved to a following page, we need to skip the
// item view. If the view is moved to the same page, do not skip the
// item view because the following item views will fill the gap left
// after dragging complete.
offset = 1;
}
++current_index.slot;
++current_item_index;
}
if (current_index == index)
return current_item_index - offset;
// Skip the "page break" items at the end of the page.
while (current_item_index < item_list->item_count() &&
item_list->item_at(current_item_index)->is_page_break()) {
++current_item_index;
}
++current_index.page;
current_index.slot = 0;
}
DCHECK(current_index == index);
return current_item_index - offset;
}
bool PagedViewStructure::IsValidReorderTargetIndex( bool PagedViewStructure::IsValidReorderTargetIndex(
const GridIndex& index) const { const GridIndex& index) const {
if (apps_grid_view_->IsValidIndex(index)) if (apps_grid_view_->IsValidIndex(index))
......
...@@ -65,6 +65,11 @@ class APP_LIST_EXPORT PagedViewStructure { ...@@ -65,6 +65,11 @@ class APP_LIST_EXPORT PagedViewStructure {
int GetTargetModelIndexForMove(AppListItemView* moved_view, int GetTargetModelIndexForMove(AppListItemView* moved_view,
const GridIndex& index) const; const GridIndex& index) const;
// Returns the target item index if moving the item view to specified target
// visual index.
int GetTargetItemIndexForMove(AppListItemView* moved_view,
const GridIndex& index) const;
// Returns true if the visual index is valid position to which an item view // Returns true if the visual index is valid position to which an item view
// can be moved. // can be moved.
bool IsValidReorderTargetIndex(const GridIndex& index) const; bool IsValidReorderTargetIndex(const GridIndex& index) const;
......
...@@ -33,16 +33,13 @@ const char AppListTestModel::kItemType[] = "TestItem"; ...@@ -33,16 +33,13 @@ const char AppListTestModel::kItemType[] = "TestItem";
// AppListTestModel::AppListTestItem // AppListTestModel::AppListTestItem
AppListTestModel::AppListTestItem::AppListTestItem( AppListTestModel::AppListTestItem::AppListTestItem(const std::string& id,
const std::string& id, AppListTestModel* model)
AppListTestModel* model) : AppListItem(id), model_(model) {
: AppListItem(id),
model_(model) {
SetIcon(CreateImageSkia(kGridIconDimension, kGridIconDimension)); SetIcon(CreateImageSkia(kGridIconDimension, kGridIconDimension));
} }
AppListTestModel::AppListTestItem::~AppListTestItem() { AppListTestModel::AppListTestItem::~AppListTestItem() = default;
}
void AppListTestModel::AppListTestItem::Activate(int event_flags) { void AppListTestModel::AppListTestItem::Activate(int event_flags) {
model_->ItemActivated(this); model_->ItemActivated(this);
...@@ -71,9 +68,7 @@ void AppListTestModel::AppListTestItem::SetPosition( ...@@ -71,9 +68,7 @@ void AppListTestModel::AppListTestItem::SetPosition(
// AppListTestModel // AppListTestModel
AppListTestModel::AppListTestModel() AppListTestModel::AppListTestModel()
: activate_count_(0), : activate_count_(0), last_activated_(nullptr) {}
last_activated_(NULL) {
}
AppListItem* AppListTestModel::AddItem(AppListItem* item) { AppListItem* AppListTestModel::AddItem(AppListItem* item) {
return AppListModel::AddItem(base::WrapUnique(item)); return AppListModel::AddItem(base::WrapUnique(item));
...@@ -89,7 +84,6 @@ void AppListTestModel::MoveItemToFolder(AppListItem* item, ...@@ -89,7 +84,6 @@ void AppListTestModel::MoveItemToFolder(AppListItem* item,
AppListModel::MoveItemToFolder(item, folder_id); AppListModel::MoveItemToFolder(item, folder_id);
} }
std::string AppListTestModel::GetItemName(int id) { std::string AppListTestModel::GetItemName(int id) {
return base::StringPrintf("Item %d", id); return base::StringPrintf("Item %d", id);
} }
...@@ -138,7 +132,8 @@ std::string AppListTestModel::GetModelContent() { ...@@ -138,7 +132,8 @@ std::string AppListTestModel::GetModelContent() {
for (size_t i = 0; i < top_level_item_list()->item_count(); ++i) { for (size_t i = 0; i < top_level_item_list()->item_count(); ++i) {
if (i > 0) if (i > 0)
content += ','; content += ',';
content += top_level_item_list()->item_at(i)->id(); AppListItem* item = top_level_item_list()->item_at(i);
content += item->is_page_break() ? "PageBreakItem" : item->id();
} }
return content; return content;
} }
......
...@@ -394,8 +394,14 @@ void AppsGridView::ResetForShowApps() { ...@@ -394,8 +394,14 @@ void AppsGridView::ResetForShowApps() {
for (int i = 0; i < view_model_.view_size(); ++i) { for (int i = 0; i < view_model_.view_size(); ++i) {
view_model_.view_at(i)->SetVisible(true); view_model_.view_at(i)->SetVisible(true);
} }
CHECK_EQ(item_list_->item_count(),
static_cast<size_t>(view_model_.view_size())); // The number of non-page-break-items should be the same as item views.
int item_count = 0;
for (size_t i = 0; i < item_list_->item_count(); ++i) {
if (!item_list_->item_at(i)->is_page_break())
++item_count;
}
CHECK_EQ(item_count, view_model_.view_size());
} }
void AppsGridView::DisableFocusForShowingActiveFolder(bool disabled) { void AppsGridView::DisableFocusForShowingActiveFolder(bool disabled) {
...@@ -638,7 +644,7 @@ void AppsGridView::EndDrag(bool cancel) { ...@@ -638,7 +644,7 @@ void AppsGridView::EndDrag(bool cancel) {
ClearDragState(); ClearDragState();
UpdatePaging(); UpdatePaging();
AnimateToIdealBounds(); AnimateToIdealBounds();
if (IsAppsGridGapEnabled()) if (!cancel && IsAppsGridGapEnabled())
view_structure_.SaveToMetadata(); view_structure_.SaveToMetadata();
StopPageFlipTimer(); StopPageFlipTimer();
...@@ -934,8 +940,11 @@ void AppsGridView::Update() { ...@@ -934,8 +940,11 @@ void AppsGridView::Update() {
if (!item_list_ || !item_list_->item_count()) if (!item_list_ || !item_list_->item_count())
return; return;
for (size_t i = 0; i < item_list_->item_count(); ++i) { for (size_t i = 0; i < item_list_->item_count(); ++i) {
// Skip "page break" items.
if (item_list_->item_at(i)->is_page_break())
continue;
AppListItemView* view = CreateViewForItemAtIndex(i); AppListItemView* view = CreateViewForItemAtIndex(i);
view_model_.Add(view, i); view_model_.Add(view, view_model_.view_size());
AddChildView(view); AddChildView(view);
} }
if (IsAppsGridGapEnabled()) if (IsAppsGridGapEnabled())
...@@ -1909,11 +1918,14 @@ void AppsGridView::OnPageFlipTimer() { ...@@ -1909,11 +1918,14 @@ void AppsGridView::OnPageFlipTimer() {
void AppsGridView::MoveItemInModel(AppListItemView* item_view, void AppsGridView::MoveItemInModel(AppListItemView* item_view,
const GridIndex& target) { const GridIndex& target) {
int current_model_index = view_model_.GetIndexOfView(item_view); int current_model_index = view_model_.GetIndexOfView(item_view);
size_t current_item_index;
item_list_->FindItemIndex(item_view->item()->id(), &current_item_index);
DCHECK_GE(current_model_index, 0); DCHECK_GE(current_model_index, 0);
int target_model_index = GetTargetModelIndexForMove(item_view, target); int target_model_index = GetTargetModelIndexForMove(item_view, target);
size_t target_item_index = GetTargetItemIndexForMove(item_view, target);
// The same model index does not guarantee the same visual index, so move the // The same item index does not guarantee the same visual index, so move the
// item visual index here. // item visual index here.
if (IsAppsGridGapEnabled()) if (IsAppsGridGapEnabled())
view_structure_.Move(item_view, target); view_structure_.Move(item_view, target);
...@@ -1922,11 +1934,11 @@ void AppsGridView::MoveItemInModel(AppListItemView* item_view, ...@@ -1922,11 +1934,11 @@ void AppsGridView::MoveItemInModel(AppListItemView* item_view,
ReorderChildView(item_view, ReorderChildView(item_view,
GetAppListItemViewIndexOffset() + target_model_index); GetAppListItemViewIndexOffset() + target_model_index);
if (target_model_index == current_model_index) if (target_item_index == current_item_index)
return; return;
item_list_->RemoveObserver(this); item_list_->RemoveObserver(this);
item_list_->MoveItem(current_model_index, target_model_index); item_list_->MoveItem(current_item_index, target_item_index);
view_model_.Move(current_model_index, target_model_index); view_model_.Move(current_model_index, target_model_index);
item_list_->AddObserver(this); item_list_->AddObserver(this);
...@@ -1961,31 +1973,33 @@ void AppsGridView::MoveItemToFolder(AppListItemView* item_view, ...@@ -1961,31 +1973,33 @@ void AppsGridView::MoveItemToFolder(AppListItemView* item_view,
// view with the new folder item view. // view with the new folder item view.
size_t folder_item_index; size_t folder_item_index;
if (item_list_->FindItemIndex(folder_item_id, &folder_item_index)) { if (item_list_->FindItemIndex(folder_item_id, &folder_item_index)) {
int target_view_index = view_model_.GetIndexOfView(target_view); int target_model_index = view_model_.GetIndexOfView(target_view);
GridIndex target_index = GetIndexOfView(target_view); GridIndex target_index = GetIndexOfView(target_view);
gfx::Rect target_view_bounds = target_view->bounds(); gfx::Rect target_view_bounds = target_view->bounds();
DeleteItemViewAtIndex(target_view_index, false /* sanitize */); DeleteItemViewAtIndex(target_model_index, false /* sanitize */);
AppListItemView* target_folder_view = AppListItemView* target_folder_view =
CreateViewForItemAtIndex(folder_item_index); CreateViewForItemAtIndex(folder_item_index);
target_folder_view->SetBoundsRect(target_view_bounds); target_folder_view->SetBoundsRect(target_view_bounds);
view_model_.Add(target_folder_view, target_view_index); view_model_.Add(target_folder_view, target_model_index);
if (IsAppsGridGapEnabled()) if (IsAppsGridGapEnabled())
view_structure_.Add(target_folder_view, target_index); view_structure_.Add(target_folder_view, target_index);
// use |folder_item_index| instead of |target_view_index| because the // If drag view is in front of the position where it will be moved to, we
// dragged item has not yet been removed from |view_model_| and // should skip it.
// |target_view_index| is 1 greater than |folder_item_index| if target int offset = (drag_view_ &&
// item is behind the dragged item. view_model_.GetIndexOfView(drag_view_) < target_model_index)
AddChildViewAt(target_folder_view, ? 1
GetAppListItemViewIndexOffset() + folder_item_index); : 0;
AddChildViewAt(target_folder_view, GetAppListItemViewIndexOffset() +
target_model_index - offset);
} else { } else {
LOG(ERROR) << "Folder no longer in item_list: " << folder_item_id; LOG(ERROR) << "Folder no longer in item_list: " << folder_item_id;
} }
} }
// Fade out the drag_view_ and delete it when animation ends. // Fade out the drag_view_ and delete it when animation ends.
int drag_view_index = view_model_.GetIndexOfView(drag_view_); int drag_model_index = view_model_.GetIndexOfView(drag_view_);
view_model_.Remove(drag_view_index); view_model_.Remove(drag_model_index);
if (IsAppsGridGapEnabled()) if (IsAppsGridGapEnabled())
view_structure_.Remove(drag_view_); view_structure_.Remove(drag_view_);
bounds_animator_.AnimateViewTo(drag_view_, drag_view_->bounds()); bounds_animator_.AnimateViewTo(drag_view_, drag_view_->bounds());
...@@ -2006,6 +2020,7 @@ void AppsGridView::ReparentItemForReorder(AppListItemView* item_view, ...@@ -2006,6 +2020,7 @@ void AppsGridView::ReparentItemForReorder(AppListItemView* item_view,
static_cast<AppListFolderItem*>(item_list_->FindItem(source_folder_id)); static_cast<AppListFolderItem*>(item_list_->FindItem(source_folder_id));
int target_model_index = GetTargetModelIndexForMove(item_view, target); int target_model_index = GetTargetModelIndexForMove(item_view, target);
int target_item_index = GetTargetItemIndexForMove(item_view, target);
// Remove the source folder view if there is only 1 item in it, since the // Remove the source folder view if there is only 1 item in it, since the
// source folder will be deleted after its only child item removed from it. // source folder will be deleted after its only child item removed from it.
...@@ -2015,8 +2030,10 @@ void AppsGridView::ReparentItemForReorder(AppListItemView* item_view, ...@@ -2015,8 +2030,10 @@ void AppsGridView::ReparentItemForReorder(AppListItemView* item_view,
DeleteItemViewAtIndex(deleted_folder_index, false /* sanitize */); DeleteItemViewAtIndex(deleted_folder_index, false /* sanitize */);
// Adjust |target_model_index| if it is beyond the deleted folder index. // Adjust |target_model_index| if it is beyond the deleted folder index.
if (target_model_index > deleted_folder_index) if (target_model_index > deleted_folder_index) {
--target_model_index; --target_model_index;
--target_item_index;
}
} }
// Move the item from its parent folder to top level item list. // Move the item from its parent folder to top level item list.
...@@ -2024,8 +2041,8 @@ void AppsGridView::ReparentItemForReorder(AppListItemView* item_view, ...@@ -2024,8 +2041,8 @@ void AppsGridView::ReparentItemForReorder(AppListItemView* item_view,
// to be, not the item location we want to insert before. // to be, not the item location we want to insert before.
int current_model_index = view_model_.GetIndexOfView(item_view); int current_model_index = view_model_.GetIndexOfView(item_view);
syncer::StringOrdinal target_position; syncer::StringOrdinal target_position;
if (target_model_index < static_cast<int>(item_list_->item_count())) if (target_item_index < static_cast<int>(item_list_->item_count()))
target_position = item_list_->item_at(target_model_index)->position(); target_position = item_list_->item_at(target_item_index)->position();
model_->MoveItemToFolderAt(reparent_item, "", target_position); model_->MoveItemToFolderAt(reparent_item, "", target_position);
view_model_.Move(current_model_index, target_model_index); view_model_.Move(current_model_index, target_model_index);
if (IsAppsGridGapEnabled()) if (IsAppsGridGapEnabled())
...@@ -2068,10 +2085,11 @@ bool AppsGridView::ReparentItemToAnotherFolder(AppListItemView* item_view, ...@@ -2068,10 +2085,11 @@ bool AppsGridView::ReparentItemToAnotherFolder(AppListItemView* item_view,
// Remove the source folder view if there is only 1 item in it, since the // Remove the source folder view if there is only 1 item in it, since the
// source folder will be deleted after its only child item merged into the // source folder will be deleted after its only child item merged into the
// target item. // target item.
if (source_folder->ChildItemCount() == 1u) if (source_folder->ChildItemCount() == 1u) {
DeleteItemViewAtIndex( DeleteItemViewAtIndex(
view_model_.GetIndexOfView(activated_folder_item_view()), view_model_.GetIndexOfView(activated_folder_item_view()),
false /* sanitize */); false /* sanitize */);
}
// Move item to the target folder. // Move item to the target folder.
std::string target_id_after_merge = std::string target_id_after_merge =
...@@ -2091,17 +2109,17 @@ bool AppsGridView::ReparentItemToAnotherFolder(AppListItemView* item_view, ...@@ -2091,17 +2109,17 @@ bool AppsGridView::ReparentItemToAnotherFolder(AppListItemView* item_view,
// Save the target view's bounds before deletion, which will be used as // Save the target view's bounds before deletion, which will be used as
// new folder view's bounds. // new folder view's bounds.
gfx::Rect target_rect = target_view->bounds(); gfx::Rect target_rect = target_view->bounds();
int target_view_index = view_model_.GetIndexOfView(target_view); int target_model_index = view_model_.GetIndexOfView(target_view);
GridIndex target_index = GetIndexOfView(target_view); GridIndex target_index = GetIndexOfView(target_view);
DeleteItemViewAtIndex(target_view_index, false /* sanitize */); DeleteItemViewAtIndex(target_model_index, false /* sanitize */);
AppListItemView* new_folder_view = AppListItemView* new_folder_view =
CreateViewForItemAtIndex(new_folder_index); CreateViewForItemAtIndex(new_folder_index);
new_folder_view->SetBoundsRect(target_rect); new_folder_view->SetBoundsRect(target_rect);
view_model_.Add(new_folder_view, target_view_index); view_model_.Add(new_folder_view, target_model_index);
if (IsAppsGridGapEnabled()) if (IsAppsGridGapEnabled())
view_structure_.Add(new_folder_view, target_index); view_structure_.Add(new_folder_view, target_index);
AddChildViewAt(new_folder_view, AddChildViewAt(new_folder_view,
GetAppListItemViewIndexOffset() + new_folder_index); GetAppListItemViewIndexOffset() + target_model_index);
} else { } else {
LOG(ERROR) << "Folder no longer in item_list: " << new_folder_id; LOG(ERROR) << "Folder no longer in item_list: " << new_folder_id;
} }
...@@ -2112,8 +2130,8 @@ bool AppsGridView::ReparentItemToAnotherFolder(AppListItemView* item_view, ...@@ -2112,8 +2130,8 @@ bool AppsGridView::ReparentItemToAnotherFolder(AppListItemView* item_view,
item_list_->AddObserver(this); item_list_->AddObserver(this);
// Fade out the drag_view_ and delete it when animation ends. // Fade out the drag_view_ and delete it when animation ends.
int drag_view_index = view_model_.GetIndexOfView(drag_view_); int drag_model_index = view_model_.GetIndexOfView(drag_view_);
view_model_.Remove(drag_view_index); view_model_.Remove(drag_model_index);
if (IsAppsGridGapEnabled()) if (IsAppsGridGapEnabled())
view_structure_.Remove(drag_view_); view_structure_.Remove(drag_view_);
bounds_animator_.AnimateViewTo(drag_view_, drag_view_->bounds()); bounds_animator_.AnimateViewTo(drag_view_, drag_view_->bounds());
...@@ -2140,6 +2158,8 @@ void AppsGridView::RemoveLastItemFromReparentItemFolderIfNecessary( ...@@ -2140,6 +2158,8 @@ void AppsGridView::RemoveLastItemFromReparentItemFolderIfNecessary(
// last item view's bounds. // last item view's bounds.
gfx::Rect folder_rect = activated_folder_item_view()->bounds(); gfx::Rect folder_rect = activated_folder_item_view()->bounds();
GridIndex target_index = GetIndexOfView(activated_folder_item_view()); GridIndex target_index = GetIndexOfView(activated_folder_item_view());
int target_model_index =
view_model_.GetIndexOfView(activated_folder_item_view());
// Delete view associated with the folder item to be removed. // Delete view associated with the folder item to be removed.
DeleteItemViewAtIndex( DeleteItemViewAtIndex(
...@@ -2153,17 +2173,17 @@ void AppsGridView::RemoveLastItemFromReparentItemFolderIfNecessary( ...@@ -2153,17 +2173,17 @@ void AppsGridView::RemoveLastItemFromReparentItemFolderIfNecessary(
// Create a new item view for the last item in folder. // Create a new item view for the last item in folder.
size_t last_item_index; size_t last_item_index;
if (!item_list_->FindItemIndex(last_item->id(), &last_item_index) || if (!item_list_->FindItemIndex(last_item->id(), &last_item_index) ||
last_item_index > static_cast<size_t>(view_model_.view_size())) { last_item_index > item_list_->item_count()) {
NOTREACHED(); NOTREACHED();
return; return;
} }
AppListItemView* last_item_view = CreateViewForItemAtIndex(last_item_index); AppListItemView* last_item_view = CreateViewForItemAtIndex(last_item_index);
last_item_view->SetBoundsRect(folder_rect); last_item_view->SetBoundsRect(folder_rect);
view_model_.Add(last_item_view, last_item_index); view_model_.Add(last_item_view, target_model_index);
if (IsAppsGridGapEnabled()) if (IsAppsGridGapEnabled())
view_structure_.Add(last_item_view, target_index); view_structure_.Add(last_item_view, target_index);
AddChildViewAt(last_item_view, AddChildViewAt(last_item_view,
GetAppListItemViewIndexOffset() + last_item_index); GetAppListItemViewIndexOffset() + target_model_index);
} }
void AppsGridView::CancelFolderItemReparent(AppListItemView* drag_item_view) { void AppsGridView::CancelFolderItemReparent(AppListItemView* drag_item_view) {
...@@ -2269,8 +2289,9 @@ void AppsGridView::OnListItemAdded(size_t index, AppListItem* item) { ...@@ -2269,8 +2289,9 @@ void AppsGridView::OnListItemAdded(size_t index, AppListItem* item) {
EndDrag(true); EndDrag(true);
AppListItemView* view = CreateViewForItemAtIndex(index); AppListItemView* view = CreateViewForItemAtIndex(index);
view_model_.Add(view, index); int model_index = GetTargetModelIndexFromItemIndex(index);
AddChildViewAt(view, GetAppListItemViewIndexOffset() + index); view_model_.Add(view, model_index);
AddChildViewAt(view, GetAppListItemViewIndexOffset() + model_index);
// Ensure that AppListItems that are added to the AppListItemList are not // Ensure that AppListItems that are added to the AppListItemList are not
// shown while in PEEKING. The visibility of the app icons will be updated // shown while in PEEKING. The visibility of the app icons will be updated
...@@ -2289,7 +2310,7 @@ void AppsGridView::OnListItemAdded(size_t index, AppListItem* item) { ...@@ -2289,7 +2310,7 @@ void AppsGridView::OnListItemAdded(size_t index, AppListItem* item) {
void AppsGridView::OnListItemRemoved(size_t index, AppListItem* item) { void AppsGridView::OnListItemRemoved(size_t index, AppListItem* item) {
EndDrag(true); EndDrag(true);
DeleteItemViewAtIndex(index, true /* sanitize */); DeleteItemViewAtIndex(GetModelIndexOfItem(item), true /* sanitize */);
if (IsAppsGridGapEnabled()) if (IsAppsGridGapEnabled())
view_structure_.LoadFromMetadata(); view_structure_.LoadFromMetadata();
...@@ -2304,9 +2325,15 @@ void AppsGridView::OnListItemMoved(size_t from_index, ...@@ -2304,9 +2325,15 @@ void AppsGridView::OnListItemMoved(size_t from_index,
size_t to_index, size_t to_index,
AppListItem* item) { AppListItem* item) {
EndDrag(true); EndDrag(true);
view_model_.Move(from_index, to_index);
ReorderChildView(view_model_.view_at(to_index), // The item is updated in the item list but the view_model is not updated, so
GetAppListItemViewIndexOffset() + to_index); // get current model index by looking up view_model and predict the target
// model index based on its current item index.
int from_model_index = GetModelIndexOfItem(item);
int to_model_index = GetTargetModelIndexFromItemIndex(to_index);
view_model_.Move(from_model_index, to_model_index);
ReorderChildView(view_model_.view_at(to_model_index),
GetAppListItemViewIndexOffset() + to_model_index);
if (IsAppsGridGapEnabled()) if (IsAppsGridGapEnabled())
view_structure_.LoadFromMetadata(); view_structure_.LoadFromMetadata();
...@@ -2316,10 +2343,11 @@ void AppsGridView::OnListItemMoved(size_t from_index, ...@@ -2316,10 +2343,11 @@ void AppsGridView::OnListItemMoved(size_t from_index,
} }
void AppsGridView::OnAppListItemHighlight(size_t index, bool highlight) { void AppsGridView::OnAppListItemHighlight(size_t index, bool highlight) {
AppListItemView* view = GetItemViewAt(index); int model_index = GetModelIndexOfItem(item_list_->item_at(index));
AppListItemView* view = GetItemViewAt(model_index);
view->SetItemIsHighlighted(highlight); view->SetItemIsHighlighted(highlight);
if (highlight) if (highlight)
EnsureViewVisible(GetIndexFromModelIndex(index)); EnsureViewVisible(GetIndexFromModelIndex(model_index));
} }
void AppsGridView::TotalPagesChanged() {} void AppsGridView::TotalPagesChanged() {}
...@@ -2535,6 +2563,15 @@ int AppsGridView::GetTargetModelIndexForMove(AppListItemView* moved_view, ...@@ -2535,6 +2563,15 @@ int AppsGridView::GetTargetModelIndexForMove(AppListItemView* moved_view,
return GetModelIndexFromIndex(index); return GetModelIndexFromIndex(index);
} }
size_t AppsGridView::GetTargetItemIndexForMove(AppListItemView* moved_view,
const GridIndex& index) const {
if (IsAppsGridGapEnabled())
return view_structure_.GetTargetItemIndexForMove(moved_view, index);
// Model index is the same as item index when apps grid gap is disabled.
return GetModelIndexFromIndex(index);
}
bool AppsGridView::IsValidIndex(const GridIndex& index) const { bool AppsGridView::IsValidIndex(const GridIndex& index) const {
return index.page >= 0 && index.page < pagination_model_.total_pages() && return index.page >= 0 && index.page < pagination_model_.total_pages() &&
index.slot >= 0 && index.slot < TilesPerPage(index.page) && index.slot >= 0 && index.slot < TilesPerPage(index.page) &&
...@@ -2609,4 +2646,26 @@ void AppsGridView::CalculateIdealBoundsWithGridGap() { ...@@ -2609,4 +2646,26 @@ void AppsGridView::CalculateIdealBoundsWithGridGap() {
} }
} }
int AppsGridView::GetModelIndexOfItem(const AppListItem* item) {
for (int i = 0; i < view_model_.view_size(); ++i) {
if (view_model_.view_at(i)->item() == item) {
return i;
}
}
return view_model_.view_size();
}
int AppsGridView::GetTargetModelIndexFromItemIndex(size_t item_index) {
if (!IsAppsGridGapEnabled())
return item_index;
CHECK(item_index <= item_list_->item_count());
int target_model_index = 0;
for (size_t i = 0; i < item_index; ++i) {
if (!item_list_->item_at(i)->is_page_break())
++target_model_index;
}
return target_model_index;
}
} // namespace app_list } // namespace app_list
...@@ -556,6 +556,11 @@ class APP_LIST_EXPORT AppsGridView : public views::View, ...@@ -556,6 +556,11 @@ class APP_LIST_EXPORT AppsGridView : public views::View,
int GetTargetModelIndexForMove(AppListItemView* moved_view, int GetTargetModelIndexForMove(AppListItemView* moved_view,
const GridIndex& index) const; const GridIndex& index) const;
// Returns the target item index if moving the item view to specified target
// visual index.
size_t GetTargetItemIndexForMove(AppListItemView* moved_view,
const GridIndex& index) const;
// Returns true if an item view exists in the visual index. // Returns true if an item view exists in the visual index.
bool IsValidIndex(const GridIndex& index) const; bool IsValidIndex(const GridIndex& index) const;
...@@ -569,6 +574,14 @@ class APP_LIST_EXPORT AppsGridView : public views::View, ...@@ -569,6 +574,14 @@ class APP_LIST_EXPORT AppsGridView : public views::View,
// Calculates the item views' bounds when apps grid gap is enabled. // Calculates the item views' bounds when apps grid gap is enabled.
void CalculateIdealBoundsWithGridGap(); void CalculateIdealBoundsWithGridGap();
// Returns model index of the item view of the specified item.
int GetModelIndexOfItem(const AppListItem* item);
// Returns the target model index based on item index. (Item index is the
// index of an item in item list.) This should be used when the item is
// updated in item list but its item view has not been updated in view model.
int GetTargetModelIndexFromItemIndex(size_t item_index);
AppListModel* model_ = nullptr; // Owned by AppListView. AppListModel* model_ = nullptr; // Owned by AppListView.
AppListItemList* item_list_ = nullptr; // Not owned. AppListItemList* item_list_ = nullptr; // Not owned.
......
...@@ -298,7 +298,7 @@ class AppsGridViewTest : public views::ViewsTestBase, ...@@ -298,7 +298,7 @@ class AppsGridViewTest : public views::ViewsTestBase,
nullptr; // Owned by |apps_grid_view_|. nullptr; // Owned by |apps_grid_view_|.
ExpandArrowView* expand_arrow_view_ = nullptr; // Owned by |apps_grid_view_|. ExpandArrowView* expand_arrow_view_ = nullptr; // Owned by |apps_grid_view_|.
std::unique_ptr<AppListTestViewDelegate> delegate_; std::unique_ptr<AppListTestViewDelegate> delegate_;
AppListTestModel* model_ = nullptr; // Owned by |delegate_|. AppListTestModel* model_ = nullptr; // Owned by |delegate_|.
SearchModel* search_model_ = nullptr; // Owned by |delegate_|. SearchModel* search_model_ = nullptr; // Owned by |delegate_|.
std::unique_ptr<AppsGridViewTestApi> test_api_; std::unique_ptr<AppsGridViewTestApi> test_api_;
bool is_rtl_ = false; bool is_rtl_ = false;
...@@ -1191,6 +1191,7 @@ TEST_P(AppsGridGapTest, MoveAnItemToNewEmptyPage) { ...@@ -1191,6 +1191,7 @@ TEST_P(AppsGridGapTest, MoveAnItemToNewEmptyPage) {
EXPECT_EQ(view_model->view_at(1), EXPECT_EQ(view_model->view_at(1),
test_api_->GetViewAtVisualIndex(0 /* page */, 1 /* slot */)); test_api_->GetViewAtVisualIndex(0 /* page */, 1 /* slot */));
EXPECT_EQ("Item 1", view_model->view_at(1)->item()->id()); EXPECT_EQ("Item 1", view_model->view_at(1)->item()->id());
EXPECT_EQ(std::string("Item 0,Item 1"), model_->GetModelContent());
// Drag the first item to the page bottom. // Drag the first item to the page bottom.
gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint(); gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
...@@ -1210,6 +1211,8 @@ TEST_P(AppsGridGapTest, MoveAnItemToNewEmptyPage) { ...@@ -1210,6 +1211,8 @@ TEST_P(AppsGridGapTest, MoveAnItemToNewEmptyPage) {
EXPECT_EQ(view_model->view_at(1), EXPECT_EQ(view_model->view_at(1),
test_api_->GetViewAtVisualIndex(1 /* page */, 0 /* slot */)); test_api_->GetViewAtVisualIndex(1 /* page */, 0 /* slot */));
EXPECT_EQ("Item 0", view_model->view_at(1)->item()->id()); EXPECT_EQ("Item 0", view_model->view_at(1)->item()->id());
EXPECT_EQ(std::string("Item 1,PageBreakItem,Item 0"),
model_->GetModelContent());
} }
TEST_P(AppsGridGapTest, MoveLastItemToCreateFolderInNextPage) { TEST_P(AppsGridGapTest, MoveLastItemToCreateFolderInNextPage) {
...@@ -1229,6 +1232,7 @@ TEST_P(AppsGridGapTest, MoveLastItemToCreateFolderInNextPage) { ...@@ -1229,6 +1232,7 @@ TEST_P(AppsGridGapTest, MoveLastItemToCreateFolderInNextPage) {
EXPECT_EQ(view_model->view_at(1), EXPECT_EQ(view_model->view_at(1),
test_api_->GetViewAtVisualIndex(0 /* page */, 1 /* slot */)); test_api_->GetViewAtVisualIndex(0 /* page */, 1 /* slot */));
EXPECT_EQ("Item 1", view_model->view_at(1)->item()->id()); EXPECT_EQ("Item 1", view_model->view_at(1)->item()->id());
EXPECT_EQ(std::string("Item 0,Item 1"), model_->GetModelContent());
// Drag the first item to next page and drag the second item to overlap with // Drag the first item to next page and drag the second item to overlap with
// the first item. // the first item.
...@@ -1247,7 +1251,13 @@ TEST_P(AppsGridGapTest, MoveLastItemToCreateFolderInNextPage) { ...@@ -1247,7 +1251,13 @@ TEST_P(AppsGridGapTest, MoveLastItemToCreateFolderInNextPage) {
EXPECT_EQ(1, view_model->view_size()); EXPECT_EQ(1, view_model->view_size());
EXPECT_EQ(view_model->view_at(0), EXPECT_EQ(view_model->view_at(0),
test_api_->GetViewAtVisualIndex(0 /* page */, 0 /* slot */)); test_api_->GetViewAtVisualIndex(0 /* page */, 0 /* slot */));
EXPECT_TRUE(view_model->view_at(0)->item()->is_folder()); const AppListItem* folder_item = view_model->view_at(0)->item();
EXPECT_TRUE(folder_item->is_folder());
// The "page break" item remains, but it will be removed later in
// AppListSyncableService.
EXPECT_EQ(std::string("PageBreakItem," + folder_item->id()),
model_->GetModelContent());
} }
TEST_P(AppsGridGapTest, MoveLastItemForReorderInNextPage) { TEST_P(AppsGridGapTest, MoveLastItemForReorderInNextPage) {
...@@ -1267,6 +1277,7 @@ TEST_P(AppsGridGapTest, MoveLastItemForReorderInNextPage) { ...@@ -1267,6 +1277,7 @@ TEST_P(AppsGridGapTest, MoveLastItemForReorderInNextPage) {
EXPECT_EQ(view_model->view_at(1), EXPECT_EQ(view_model->view_at(1),
test_api_->GetViewAtVisualIndex(0 /* page */, 1 /* slot */)); test_api_->GetViewAtVisualIndex(0 /* page */, 1 /* slot */));
EXPECT_EQ("Item 1", view_model->view_at(1)->item()->id()); EXPECT_EQ("Item 1", view_model->view_at(1)->item()->id());
EXPECT_EQ(std::string("Item 0,Item 1"), model_->GetModelContent());
// Drag the first item to next page and drag the second item to the left of // Drag the first item to next page and drag the second item to the left of
// the first item. // the first item.
...@@ -1290,6 +1301,11 @@ TEST_P(AppsGridGapTest, MoveLastItemForReorderInNextPage) { ...@@ -1290,6 +1301,11 @@ TEST_P(AppsGridGapTest, MoveLastItemForReorderInNextPage) {
EXPECT_EQ(view_model->view_at(1), EXPECT_EQ(view_model->view_at(1),
test_api_->GetViewAtVisualIndex(0 /* page */, 1 /* slot */)); test_api_->GetViewAtVisualIndex(0 /* page */, 1 /* slot */));
EXPECT_EQ("Item 0", view_model->view_at(1)->item()->id()); EXPECT_EQ("Item 0", view_model->view_at(1)->item()->id());
// The "page break" item remains, but it will be removed later in
// AppListSyncableService.
EXPECT_EQ(std::string("PageBreakItem,Item 1,Item 0"),
model_->GetModelContent());
} }
TEST_P(AppsGridGapTest, MoveLastItemToNewEmptyPage) { TEST_P(AppsGridGapTest, MoveLastItemToNewEmptyPage) {
...@@ -1306,6 +1322,7 @@ TEST_P(AppsGridGapTest, MoveLastItemToNewEmptyPage) { ...@@ -1306,6 +1322,7 @@ TEST_P(AppsGridGapTest, MoveLastItemToNewEmptyPage) {
EXPECT_EQ(view_model->view_at(0), EXPECT_EQ(view_model->view_at(0),
test_api_->GetViewAtVisualIndex(0 /* page */, 0 /* slot */)); test_api_->GetViewAtVisualIndex(0 /* page */, 0 /* slot */));
EXPECT_EQ("Item 0", view_model->view_at(0)->item()->id()); EXPECT_EQ("Item 0", view_model->view_at(0)->item()->id());
EXPECT_EQ(std::string("Item 0"), model_->GetModelContent());
// Drag the item to next page. // Drag the item to next page.
gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint(); gfx::Point from = GetItemRectOnCurrentPageAt(0, 0).CenterPoint();
...@@ -1324,6 +1341,7 @@ TEST_P(AppsGridGapTest, MoveLastItemToNewEmptyPage) { ...@@ -1324,6 +1341,7 @@ TEST_P(AppsGridGapTest, MoveLastItemToNewEmptyPage) {
EXPECT_EQ(view_model->view_at(0), EXPECT_EQ(view_model->view_at(0),
test_api_->GetViewAtVisualIndex(0 /* page */, 0 /* slot */)); test_api_->GetViewAtVisualIndex(0 /* page */, 0 /* slot */));
EXPECT_EQ("Item 0", view_model->view_at(0)->item()->id()); EXPECT_EQ("Item 0", view_model->view_at(0)->item()->id());
EXPECT_EQ(std::string("Item 0"), model_->GetModelContent());
} }
TEST_P(AppsGridGapTest, MoveItemToPreviousFullPage) { TEST_P(AppsGridGapTest, MoveItemToPreviousFullPage) {
...@@ -1348,6 +1366,13 @@ TEST_P(AppsGridGapTest, MoveItemToPreviousFullPage) { ...@@ -1348,6 +1366,13 @@ TEST_P(AppsGridGapTest, MoveItemToPreviousFullPage) {
EXPECT_EQ("Item " + std::to_string(kApps - 1), EXPECT_EQ("Item " + std::to_string(kApps - 1),
view_model->view_at(kApps - 1)->item()->id()); view_model->view_at(kApps - 1)->item()->id());
// There's no "page break" item between Item 19 and 20, although there are two
// pages. It will only be added after user operations.
EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3,Item 4,Item 5,Item 6,Item "
"7,Item 8,Item 9,Item 10,Item 11,Item 12,Item 13,Item "
"14,Item 15,Item 16,Item 17,Item 18,Item 19,Item 20"),
model_->GetModelContent());
// Drag the last item to the first item's left position in previous page. // Drag the last item to the first item's left position in previous page.
gfx::Point from = test_api_->GetItemTileRectAtVisualIndex(1, 0).CenterPoint(); gfx::Point from = test_api_->GetItemTileRectAtVisualIndex(1, 0).CenterPoint();
gfx::Rect tile_rect = test_api_->GetItemTileRectAtVisualIndex(0, 0); gfx::Rect tile_rect = test_api_->GetItemTileRectAtVisualIndex(0, 0);
...@@ -1376,6 +1401,13 @@ TEST_P(AppsGridGapTest, MoveItemToPreviousFullPage) { ...@@ -1376,6 +1401,13 @@ TEST_P(AppsGridGapTest, MoveItemToPreviousFullPage) {
test_api_->GetViewAtVisualIndex(1 /* page */, 0 /* slot */)); test_api_->GetViewAtVisualIndex(1 /* page */, 0 /* slot */));
EXPECT_EQ("Item " + std::to_string(kApps - 2), EXPECT_EQ("Item " + std::to_string(kApps - 2),
view_model->view_at(kApps - 1)->item()->id()); view_model->view_at(kApps - 1)->item()->id());
// A "page break" item is added to split the pages.
EXPECT_EQ(
std::string("Item 20,Item 0,Item 1,Item 2,Item 3,Item 4,Item 5,Item "
"6,Item 7,Item 8,Item 9,Item 10,Item 11,Item 12,Item 13,Item "
"14,Item 15,Item 16,Item 17,Item 18,PageBreakItem,Item 19"),
model_->GetModelContent());
} }
} // namespace test } // namespace test
......
...@@ -67,8 +67,7 @@ int ViewModelBase::GetIndexOfView(const View* view) const { ...@@ -67,8 +67,7 @@ int ViewModelBase::GetIndexOfView(const View* view) const {
return -1; return -1;
} }
ViewModelBase::ViewModelBase() { ViewModelBase::ViewModelBase() = default;
}
void ViewModelBase::AddUnsafe(View* view, int index) { void ViewModelBase::AddUnsafe(View* view, int index) {
DCHECK_LE(index, static_cast<int>(entries_.size())); DCHECK_LE(index, static_cast<int>(entries_.size()));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment