paged_view_structure.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/app_list/paged_view_structure.h"
  5. #include <algorithm>
  6. #include "ash/app_list/model/app_list_item.h"
  7. #include "ash/app_list/model/app_list_item_list.h"
  8. #include "ash/app_list/model/app_list_model.h"
  9. #include "ash/app_list/views/app_list_item_view.h"
  10. #include "ash/app_list/views/apps_grid_view.h"
  11. #include "ash/constants/ash_features.h"
  12. #include "base/check_op.h"
  13. #include "base/containers/contains.h"
  14. #include "ui/views/view_model.h"
  15. namespace ash {
  16. PagedViewStructure::ScopedSanitizeLock::ScopedSanitizeLock(
  17. PagedViewStructure* view_structure)
  18. : view_structure_(view_structure) {
  19. ++view_structure_->sanitize_locks_;
  20. }
  21. PagedViewStructure::ScopedSanitizeLock::~ScopedSanitizeLock() {
  22. --view_structure_->sanitize_locks_;
  23. view_structure_->Sanitize();
  24. }
  25. PagedViewStructure::PagedViewStructure(AppsGridView* apps_grid_view)
  26. : apps_grid_view_(apps_grid_view) {}
  27. PagedViewStructure::~PagedViewStructure() {
  28. DCHECK_EQ(0, sanitize_locks_);
  29. }
  30. void PagedViewStructure::Init(Mode mode) {
  31. mode_ = mode;
  32. }
  33. std::unique_ptr<PagedViewStructure::ScopedSanitizeLock>
  34. PagedViewStructure::GetSanitizeLock() {
  35. return std::make_unique<ScopedSanitizeLock>(this);
  36. }
  37. void PagedViewStructure::AllowEmptyPages() {
  38. empty_pages_allowed_ = true;
  39. }
  40. void PagedViewStructure::LoadFromOther(const PagedViewStructure& other) {
  41. DCHECK_EQ(apps_grid_view_, other.apps_grid_view_);
  42. mode_ = other.mode_;
  43. pages_ = other.pages_;
  44. Sanitize();
  45. }
  46. void PagedViewStructure::LoadFromMetadata() {
  47. const auto* view_model = apps_grid_view_->view_model();
  48. pages_.clear();
  49. pages_.emplace_back();
  50. if (mode_ == Mode::kSinglePage) {
  51. // Copy the view model to a single page.
  52. pages_[0].reserve(view_model->view_size());
  53. for (size_t i = 0; i < view_model->view_size(); ++i) {
  54. pages_[0].push_back(view_model->view_at(i));
  55. }
  56. return;
  57. }
  58. if (mode_ == Mode::kFullPages) {
  59. // Copy the view model to N full pages.
  60. for (size_t i = 0; i < view_model->view_size(); ++i) {
  61. if (pages_.back().size() ==
  62. static_cast<size_t>(TilesPerPage(pages_.size() - 1))) {
  63. pages_.emplace_back();
  64. }
  65. pages_.back().push_back(view_model->view_at(i));
  66. }
  67. return;
  68. }
  69. int model_index = 0;
  70. const size_t item_count = apps_grid_view_->item_list_
  71. ? apps_grid_view_->item_list_->item_count()
  72. : 0;
  73. for (size_t i = 0; i < item_count; ++i) {
  74. const auto* item = apps_grid_view_->item_list_->item_at(i);
  75. if (item->is_page_break()) {
  76. // Create a new page if a "page break" item is detected and current page
  77. // is not empty. Otherwise, ignore the "page break" item.
  78. if (!pages_.back().empty())
  79. pages_.emplace_back();
  80. continue;
  81. }
  82. // Create a new page if the current page is full.
  83. const size_t current_page_max_items = TilesPerPage(pages_.size() - 1);
  84. if (sanitize_locks_ == 0 && pages_.back().size() == current_page_max_items)
  85. pages_.emplace_back();
  86. pages_.back().emplace_back(
  87. apps_grid_view_->view_model()->view_at(model_index++));
  88. }
  89. // Remove trailing empty page if exist.
  90. if (!empty_pages_allowed_ && sanitize_locks_ == 0 && pages_.back().empty())
  91. pages_.pop_back();
  92. }
  93. void PagedViewStructure::SaveToMetadata() {
  94. // When ignoring page breaks we don't need to add or remove page breaks from
  95. // the data model.
  96. if (mode_ == Mode::kFullPages || mode_ == Mode::kSinglePage)
  97. return;
  98. auto* item_list = apps_grid_view_->item_list_;
  99. size_t item_index = 0;
  100. AppListModel* model = apps_grid_view_->model_;
  101. for (const auto& page : pages_) {
  102. // Skip all "page break" items before current page.
  103. while (item_index < item_list->item_count() &&
  104. item_list->item_at(item_index)->is_page_break()) {
  105. ++item_index;
  106. }
  107. // A "page break" item may exist between two app items in a full page after
  108. // moving an item to the page from another page or folder. The last item in
  109. // the page was pushed to the next page by ClearOverflow() while the "page
  110. // break" item behind still exist. In this case, we need to remove the "page
  111. // break" item.
  112. for (size_t i = 0;
  113. i < page.size() && item_index < item_list->item_count();) {
  114. const auto* item = item_list->item_at(item_index);
  115. if (item->is_page_break()) {
  116. // Remove AppListItemListObserver temporarily to avoid |pages_| being
  117. // reloaded.
  118. item_list->RemoveObserver(apps_grid_view_);
  119. // Do not increase |item_index| after this call because it modifies
  120. // |item_list|.
  121. model->DeleteItem(item->id());
  122. item_list->AddObserver(apps_grid_view_);
  123. continue;
  124. }
  125. DCHECK_EQ(item, page[i]->item());
  126. ++i;
  127. ++item_index;
  128. }
  129. // When removing launcher spaces is enabled, all launcher pages expect for
  130. // the last one should be full (i.e. no empty spaces). Therefore page break
  131. // items are useless. It is why we should only create page break items when
  132. // the feature flag is disabled.
  133. if (!features::IsProductivityLauncherEnabled() &&
  134. item_index < item_list->item_count() &&
  135. !item_list->item_at(item_index)->is_page_break()) {
  136. // Remove AppListItemListObserver temporarily to avoid |pages_| being
  137. // reloaded.
  138. item_list->RemoveObserver(apps_grid_view_);
  139. // There's no "page break" item at the end of current page, so add one to
  140. // push overflowing items to next page.
  141. model->AddPageBreakItemAfter(item_list->item_at(item_index - 1));
  142. item_list->AddObserver(apps_grid_view_);
  143. }
  144. }
  145. // Note that we do not remove redundant "page break" items here because the
  146. // item list we can access here may not be complete (e.g. Devices that do not
  147. // support ARC++ or Crostini apps filter out those items.). We leave this
  148. // operation to AppListSyncableService which has complete item list.
  149. }
  150. void PagedViewStructure::Move(AppListItemView* view,
  151. const GridIndex& target_index) {
  152. // Do not sanitize view structure after Remove() call.
  153. std::unique_ptr<ScopedSanitizeLock> sanitize_lock = GetSanitizeLock();
  154. Remove(view);
  155. Add(view, target_index);
  156. }
  157. void PagedViewStructure::Remove(AppListItemView* view) {
  158. for (auto& page : pages_) {
  159. auto iter = std::find(page.begin(), page.end(), view);
  160. if (iter != page.end()) {
  161. page.erase(iter);
  162. break;
  163. }
  164. }
  165. Sanitize();
  166. }
  167. void PagedViewStructure::Add(AppListItemView* view,
  168. const GridIndex& target_index) {
  169. const int view_structure_size = total_pages();
  170. if (target_index.page < view_structure_size) {
  171. // Adding to an existing page.
  172. CHECK_LE(target_index.slot, items_on_page(target_index.page));
  173. } else {
  174. // Adding to a new page at the end.
  175. CHECK_EQ(target_index.page, view_structure_size);
  176. CHECK_EQ(target_index.slot, 0);
  177. }
  178. if (target_index.page == view_structure_size)
  179. pages_.emplace_back();
  180. auto& page = pages_[target_index.page];
  181. page.insert(page.begin() + target_index.slot, view);
  182. Sanitize();
  183. }
  184. GridIndex PagedViewStructure::GetIndexFromModelIndex(int model_index) const {
  185. if (mode_ == Mode::kSinglePage)
  186. return GridIndex(0, model_index);
  187. if (mode_ == Mode::kFullPages) {
  188. int current_page = 0;
  189. while (model_index >= TilesPerPage(current_page)) {
  190. model_index -= TilesPerPage(current_page);
  191. ++current_page;
  192. }
  193. return GridIndex(current_page, model_index);
  194. }
  195. AppListItemView* view = apps_grid_view_->view_model()->view_at(model_index);
  196. for (size_t i = 0; i < pages_.size(); ++i) {
  197. auto& page = pages_[i];
  198. for (size_t j = 0; j < page.size(); ++j) {
  199. if (page[j] == view)
  200. return GridIndex(i, j);
  201. }
  202. }
  203. return GetLastTargetIndex();
  204. }
  205. int PagedViewStructure::GetModelIndexFromIndex(const GridIndex& index) const {
  206. if (mode_ == Mode::kSinglePage) {
  207. DCHECK_EQ(index.page, 0);
  208. return index.slot;
  209. }
  210. if (mode_ == Mode::kFullPages) {
  211. int model_index = 0;
  212. for (int i = 0; i < index.page; i++) {
  213. model_index += TilesPerPage(i);
  214. }
  215. model_index += index.slot;
  216. return model_index;
  217. }
  218. auto* view_model = apps_grid_view_->view_model();
  219. if (index.page >= total_pages() || index.slot >= items_on_page(index.page))
  220. return view_model->view_size();
  221. AppListItemView* view = pages_[index.page][index.slot];
  222. return view_model->GetIndexOfView(view).value();
  223. }
  224. GridIndex PagedViewStructure::GetLastTargetIndex() const {
  225. if (apps_grid_view_->view_model()->view_size() == 0)
  226. return GridIndex(0, 0);
  227. if (mode_ == Mode::kSinglePage || mode_ == Mode::kFullPages) {
  228. size_t view_index = apps_grid_view_->view_model()->view_size();
  229. // If a view in the current view model is being dragged, then ignore it.
  230. if (apps_grid_view_->drag_view())
  231. --view_index;
  232. return GetIndexFromModelIndex(view_index);
  233. }
  234. int last_page_index = total_pages() - 1;
  235. int target_slot = CalculateTargetSlot(pages_.back());
  236. if (target_slot == TilesPerPage(last_page_index)) {
  237. // The last page is full, so the last target visual index is the first slot
  238. // in the next new page.
  239. target_slot = 0;
  240. ++last_page_index;
  241. }
  242. return GridIndex(last_page_index, target_slot);
  243. }
  244. GridIndex PagedViewStructure::GetLastTargetIndexOfPage(int page_index) const {
  245. if (mode_ == Mode::kSinglePage) {
  246. DCHECK_EQ(page_index, 0);
  247. return GetLastTargetIndex();
  248. }
  249. if (mode_ == Mode::kFullPages) {
  250. if (page_index == apps_grid_view_->GetTotalPages() - 1)
  251. return GetLastTargetIndex();
  252. return GridIndex(page_index, TilesPerPage(page_index) - 1);
  253. }
  254. const int page_size = total_pages();
  255. DCHECK_LT(0u, apps_grid_view_->view_model()->view_size());
  256. DCHECK_LE(page_index, page_size);
  257. if (page_index == page_size)
  258. return GridIndex(page_index, 0);
  259. int target_slot = CalculateTargetSlot(pages_[page_index]);
  260. if (target_slot == TilesPerPage(page_index)) {
  261. // The specified page is full, so the last target visual index is the last
  262. // slot in the page_index.
  263. --target_slot;
  264. }
  265. return GridIndex(page_index, target_slot);
  266. }
  267. int PagedViewStructure::GetTargetModelIndexForMove(
  268. AppListItem* moved_item,
  269. const GridIndex& index) const {
  270. if (mode_ == Mode::kSinglePage || mode_ == Mode::kFullPages)
  271. return GetModelIndexFromIndex(index);
  272. int target_model_index = 0;
  273. const int max_page = std::min(index.page, total_pages());
  274. for (int i = 0; i < max_page; ++i) {
  275. auto& page = pages_[i];
  276. target_model_index += page.size();
  277. // Skip the item view to be moved in the page if found.
  278. // Decrement |target_model_index| if |moved_view| is in this page because it
  279. // is represented by a placeholder.
  280. auto it =
  281. std::find_if(page.begin(), page.end(), [&](AppListItemView* item_view) {
  282. return item_view->item() == moved_item;
  283. });
  284. if (it != page.end())
  285. --target_model_index;
  286. }
  287. // If the target visual index is in the same page, do not skip the item view
  288. // because the following item views will fill the gap in the page.
  289. target_model_index += index.slot;
  290. return target_model_index;
  291. }
  292. int PagedViewStructure::GetTargetItemListIndexForMove(
  293. AppListItem* moved_item,
  294. const GridIndex& index) const {
  295. if (mode_ == Mode::kFullPages)
  296. return GetModelIndexFromIndex(index);
  297. if (mode_ == Mode::kSinglePage) {
  298. DCHECK_EQ(index.page, 0);
  299. GridIndex current_index(0, 0);
  300. size_t current_item_index = 0;
  301. // Skip the leading "page break" items.
  302. const auto* item_list = apps_grid_view_->item_list_;
  303. while (current_item_index < item_list->item_count() &&
  304. item_list->item_at(current_item_index)->is_page_break()) {
  305. ++current_item_index;
  306. }
  307. while (current_item_index < item_list->item_count() &&
  308. current_index != index) {
  309. if (!item_list->item_at(current_item_index)->is_page_break())
  310. ++current_index.slot;
  311. ++current_item_index;
  312. }
  313. DCHECK_EQ(current_index, index);
  314. return current_item_index;
  315. }
  316. GridIndex current_index(0, 0);
  317. size_t current_item_index = 0;
  318. size_t offset = 0;
  319. const auto* item_list = apps_grid_view_->item_list_;
  320. // Skip the leading "page break" items.
  321. while (current_item_index < item_list->item_count() &&
  322. item_list->item_at(current_item_index)->is_page_break()) {
  323. ++current_item_index;
  324. }
  325. while (current_item_index < item_list->item_count()) {
  326. while (current_item_index < item_list->item_count() &&
  327. !item_list->item_at(current_item_index)->is_page_break() &&
  328. current_index != index) {
  329. if (moved_item && moved_item == item_list->item_at(current_item_index) &&
  330. current_index.page < index.page) {
  331. // If the item view is moved to a following page, we need to skip the
  332. // item view. If the view is moved to the same page, do not skip the
  333. // item view because the following item views will fill the gap left
  334. // after dragging complete.
  335. offset = 1;
  336. }
  337. ++current_index.slot;
  338. ++current_item_index;
  339. }
  340. if (current_index == index)
  341. return current_item_index - offset;
  342. // Skip the "page break" items at the end of the page.
  343. while (current_item_index < item_list->item_count() &&
  344. item_list->item_at(current_item_index)->is_page_break()) {
  345. ++current_item_index;
  346. }
  347. ++current_index.page;
  348. current_index.slot = 0;
  349. }
  350. DCHECK_EQ(current_index, index);
  351. return current_item_index - offset;
  352. }
  353. bool PagedViewStructure::IsValidReorderTargetIndex(
  354. const GridIndex& index) const {
  355. if (apps_grid_view_->IsValidIndex(index))
  356. return true;
  357. // The user can drag an item view to another page's end. Also covers the case
  358. // where a dragged folder item is being reparented to the last target index of
  359. // the root level grid.
  360. if ((index.page < total_pages() ||
  361. (index.page == total_pages() && mode_ == Mode::kPartialPages)) &&
  362. GetLastTargetIndexOfPage(index.page) == index) {
  363. return true;
  364. }
  365. return false;
  366. }
  367. void PagedViewStructure::AppendPage() {
  368. DCHECK_NE(mode_, Mode::kSinglePage);
  369. pages_.emplace_back();
  370. }
  371. bool PagedViewStructure::IsFullPage(int page_index) const {
  372. if (page_index >= total_pages())
  373. return false;
  374. return static_cast<int>(pages_[page_index].size()) ==
  375. TilesPerPage(page_index);
  376. }
  377. int PagedViewStructure::CalculateTargetSlot(const Page& page) const {
  378. size_t target_slot = page.size();
  379. if (base::Contains(page, apps_grid_view_->drag_view()))
  380. --target_slot;
  381. return static_cast<int>(target_slot);
  382. }
  383. void PagedViewStructure::Sanitize() {
  384. if (sanitize_locks_ == 0)
  385. ClearOverflow();
  386. if (!empty_pages_allowed_ && sanitize_locks_ == 0)
  387. ClearEmptyPages();
  388. }
  389. void PagedViewStructure::ClearOverflow() {
  390. std::vector<AppListItemView*> overflow_views;
  391. auto iter = pages_.begin();
  392. while (iter != pages_.end() || !overflow_views.empty()) {
  393. if (iter == pages_.end()) {
  394. // Add additional page if overflowing item views remain.
  395. pages_.emplace_back();
  396. iter = pages_.end() - 1;
  397. }
  398. const size_t max_item_views =
  399. TilesPerPage(static_cast<int>(iter - pages_.begin()));
  400. auto& page = *iter;
  401. if (!overflow_views.empty()) {
  402. // Put overflowing item views in current page.
  403. page.insert(page.begin(), overflow_views.begin(), overflow_views.end());
  404. overflow_views.clear();
  405. }
  406. if (page.size() > max_item_views) {
  407. // Remove overflowing item views from current page.
  408. overflow_views.insert(overflow_views.begin(),
  409. page.begin() + max_item_views, page.end());
  410. page.erase(page.begin() + max_item_views, page.end());
  411. }
  412. ++iter;
  413. }
  414. }
  415. void PagedViewStructure::ClearEmptyPages() {
  416. auto iter = pages_.begin();
  417. while (iter != pages_.end()) {
  418. if (iter->empty()) {
  419. // Remove empty page.
  420. iter = pages_.erase(iter);
  421. } else {
  422. ++iter;
  423. }
  424. }
  425. }
  426. int PagedViewStructure::TilesPerPage(int page) const {
  427. return apps_grid_view_->TilesPerPage(page);
  428. }
  429. } // namespace ash