scrollable_apps_grid_view.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. // Copyright 2021 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/views/scrollable_apps_grid_view.h"
  5. #include <memory>
  6. #include <string>
  7. #include "ash/app_list/app_list_view_delegate.h"
  8. #include "ash/app_list/model/app_list_item.h"
  9. #include "ash/app_list/views/app_list_item_view.h"
  10. #include "ash/public/cpp/app_list/app_list_config.h"
  11. #include "base/bind.h"
  12. #include "base/metrics/histogram_macros.h"
  13. #include "base/time/time.h"
  14. #include "ui/base/metadata/metadata_impl_macros.h"
  15. #include "ui/views/animation/bounds_animator.h"
  16. #include "ui/views/controls/scroll_view.h"
  17. #include "ui/views/focus/focus_manager.h"
  18. #include "ui/views/view_model_utils.h"
  19. #include "ui/views/widget/widget.h"
  20. namespace ash {
  21. namespace {
  22. // TODO(crbug.com/1211608): Add this to AppListConfig.
  23. const int kVerticalTilePadding = 8;
  24. // Vertical margin in DIPs inside the top and bottom of scroll view where
  25. // auto-scroll will be triggered during drags.
  26. constexpr int kAutoScrollViewMargin = 32;
  27. // Vertical margin in DIPs outside the top and bottom of the widget where
  28. // auto-scroll will trigger. Points outside this margin will not auto-scroll.
  29. constexpr int kAutoScrollWidgetMargin = 8;
  30. // How often to auto-scroll when the mouse is held in the auto-scroll margin.
  31. constexpr base::TimeDelta kAutoScrollInterval = base::Hertz(60.0);
  32. // How much to auto-scroll the view per second. Empirically chosen.
  33. const int kAutoScrollDipsPerSecond = 400;
  34. } // namespace
  35. ScrollableAppsGridView::ScrollableAppsGridView(
  36. AppListA11yAnnouncer* a11y_announcer,
  37. AppListViewDelegate* view_delegate,
  38. AppsGridViewFolderDelegate* folder_delegate,
  39. views::ScrollView* parent_scroll_view,
  40. AppListFolderController* folder_controller,
  41. AppListKeyboardController* keyboard_controller)
  42. : AppsGridView(a11y_announcer,
  43. view_delegate,
  44. folder_delegate,
  45. folder_controller,
  46. keyboard_controller),
  47. scroll_view_(parent_scroll_view) {
  48. DCHECK(scroll_view_);
  49. view_structure_.Init(PagedViewStructure::Mode::kSinglePage);
  50. }
  51. ScrollableAppsGridView::~ScrollableAppsGridView() {
  52. EndDrag(/*cancel=*/true);
  53. }
  54. void ScrollableAppsGridView::SetMaxColumns(int max_cols) {
  55. SetMaxColumnsInternal(max_cols);
  56. }
  57. void ScrollableAppsGridView::Layout() {
  58. if (ignore_layout())
  59. return;
  60. if (bounds_animator()->IsAnimating())
  61. bounds_animator()->Cancel();
  62. if (GetContentsBounds().IsEmpty())
  63. return;
  64. // TODO(crbug.com/1211608): Use FillLayout on the items container.
  65. items_container()->SetBoundsRect(GetContentsBounds());
  66. CalculateIdealBounds();
  67. for (size_t i = 0; i < view_model()->view_size(); ++i) {
  68. AppListItemView* view = GetItemViewAt(i);
  69. view->SetBoundsRect(view_model()->ideal_bounds(i));
  70. }
  71. views::ViewModelUtils::SetViewBoundsToIdealBounds(pulsing_blocks_model());
  72. }
  73. gfx::Size ScrollableAppsGridView::GetTileViewSize() const {
  74. const AppListConfig* config = app_list_config();
  75. return gfx::Size(config->grid_tile_width(), config->grid_tile_height());
  76. }
  77. gfx::Insets ScrollableAppsGridView::GetTilePadding(int page) const {
  78. if (has_fixed_tile_padding_)
  79. return gfx::Insets::VH(-vertical_tile_padding_, -horizontal_tile_padding_);
  80. int content_width = GetContentsBounds().width();
  81. int tile_width = app_list_config()->grid_tile_width();
  82. int width_to_distribute = content_width - cols() * tile_width;
  83. // While calculating tile padding, assume no padding between a tile and a
  84. // container bounds.
  85. DCHECK_GT(cols(), 1);
  86. const int spaces_between_items = cols() - 1;
  87. // Each column has padding on left and on right, so a space between two tiles
  88. // is double the tile padding size.
  89. const int horizontal_tile_padding =
  90. width_to_distribute / (spaces_between_items * 2);
  91. return gfx::Insets::VH(-kVerticalTilePadding, -horizontal_tile_padding);
  92. }
  93. gfx::Size ScrollableAppsGridView::GetTileGridSize() const {
  94. // AppListItemList may contain page break items, so use the view_model().
  95. size_t items = view_model()->view_size();
  96. // Tests sometimes start with 0 items. Ensure space for at least 1 item.
  97. if (items == 0) {
  98. items = 1;
  99. }
  100. const bool is_last_row_full = (items % cols() == 0);
  101. const int rows = is_last_row_full ? items / cols() : items / cols() + 1;
  102. gfx::Size tile_size = GetTotalTileSize(/*page=*/0);
  103. gfx::Rect grid(tile_size.width() * cols(), tile_size.height() * rows);
  104. grid.Inset(-GetTilePadding(/*page=*/0));
  105. return grid.size();
  106. }
  107. int ScrollableAppsGridView::GetTotalPages() const {
  108. return 1;
  109. }
  110. int ScrollableAppsGridView::GetSelectedPage() const {
  111. return 0;
  112. }
  113. bool ScrollableAppsGridView::IsScrollAxisVertical() const {
  114. return true;
  115. }
  116. bool ScrollableAppsGridView::MaybeAutoScroll() {
  117. ScrollDirection direction;
  118. if (!IsPointInAutoScrollMargin(last_drag_point(), &direction)) {
  119. // Drag isn't in auto-scroll margin.
  120. StopAutoScroll();
  121. return false;
  122. }
  123. if (!CanAutoScrollView(direction)) {
  124. // Scroll view already at top or bottom.
  125. StopAutoScroll();
  126. return false;
  127. }
  128. if (auto_scroll_timer_.IsRunning()) {
  129. // The user triggered a drag update while the mouse was in the auto-scroll
  130. // zone. Don't scroll for this drag update, but keep auto-scroll going.
  131. return true;
  132. }
  133. // Scroll at a constant rate, regardless of when the timer actually fired.
  134. const base::TimeTicks now = base::TimeTicks::Now();
  135. const base::TimeDelta time_delta = last_auto_scroll_time_.is_null()
  136. ? kAutoScrollInterval
  137. : now - last_auto_scroll_time_;
  138. const int y_offset = time_delta.InSecondsF() * kAutoScrollDipsPerSecond;
  139. // Scroll by `y_offset` in the appropriate direction.
  140. const int old_scroll_y = scroll_view_->GetVisibleRect().y();
  141. const int target_scroll_y = direction == ScrollDirection::kUp
  142. ? old_scroll_y - y_offset
  143. : old_scroll_y + y_offset;
  144. scroll_view_->ScrollToPosition(scroll_view_->vertical_scroll_bar(),
  145. target_scroll_y);
  146. // The final scroll position may not match the target scroll position because
  147. // the scroll might have been clamped to the top or bottom.
  148. int final_scroll_y = scroll_view_->GetVisibleRect().y();
  149. // Adjust the last drag point because scrolling has changed the position of
  150. // the apps grid. This ensures that auto-scrolling continues to happen even if
  151. // the user doesn't move the mouse.
  152. gfx::Point drag_point = last_drag_point();
  153. drag_point.Offset(0, final_scroll_y - old_scroll_y);
  154. set_last_drag_point(drag_point);
  155. // Auto-scroll again after `kAutoScrollInterval`.
  156. last_auto_scroll_time_ = now;
  157. auto_scroll_timer_.Start(
  158. FROM_HERE, kAutoScrollInterval,
  159. base::BindOnce(
  160. base::IgnoreResult(&ScrollableAppsGridView::MaybeAutoScroll),
  161. base::Unretained(this)));
  162. return true;
  163. }
  164. void ScrollableAppsGridView::StopAutoScroll() {
  165. auto_scroll_timer_.Stop();
  166. last_auto_scroll_time_ = {};
  167. }
  168. bool ScrollableAppsGridView::IsPointInAutoScrollMargin(
  169. const gfx::Point& point_in_grid_view,
  170. ScrollDirection* direction) const {
  171. gfx::Point point_in_scroll_view = point_in_grid_view;
  172. ConvertPointToTarget(this, scroll_view_, &point_in_scroll_view);
  173. // Points to the left or right of the scroll view do not autoscroll.
  174. if (point_in_scroll_view.x() < 0 ||
  175. point_in_scroll_view.x() > scroll_view_->width()) {
  176. return false;
  177. }
  178. // Points too far above or below the widget do not autoscroll. This helps
  179. // prevent scrolling when the user is dragging into the shelf.
  180. gfx::Point point_in_screen = point_in_grid_view;
  181. ConvertPointToScreen(this, &point_in_screen);
  182. gfx::Rect widget_bounds = GetWidget()->GetWindowBoundsInScreen();
  183. if (point_in_screen.y() < widget_bounds.y() - kAutoScrollWidgetMargin ||
  184. point_in_screen.y() > widget_bounds.bottom() + kAutoScrollWidgetMargin) {
  185. return false;
  186. }
  187. if (point_in_scroll_view.y() < kAutoScrollViewMargin) {
  188. *direction = ScrollDirection::kUp;
  189. return true;
  190. }
  191. const int view_bottom = scroll_view_->height();
  192. if (point_in_scroll_view.y() > view_bottom - kAutoScrollViewMargin) {
  193. *direction = ScrollDirection::kDown;
  194. return true;
  195. }
  196. return false;
  197. }
  198. bool ScrollableAppsGridView::CanAutoScrollView(
  199. ScrollDirection direction) const {
  200. const gfx::Rect visible_rect = scroll_view_->GetVisibleRect();
  201. if (direction == ScrollDirection::kUp) {
  202. // Can scroll up if the visible rect is not at the top of the contents.
  203. return visible_rect.y() > 0;
  204. }
  205. // Can scroll down if the visible rect is not at the bottom of the contents.
  206. return visible_rect.bottom() < scroll_view_->contents()->height();
  207. }
  208. void ScrollableAppsGridView::HandleScrollFromParentView(
  209. const gfx::Vector2d& offset,
  210. ui::EventType type) {
  211. // AppListView uses a paged apps grid view, so this must be a folder opened
  212. // in the fullscreen launcher.
  213. DCHECK(IsInFolder());
  214. // Scroll events in the folder view title area should scroll the view.
  215. scroll_view_->vertical_scroll_bar()->OnScroll(/*dx=*/0, offset.y());
  216. }
  217. void ScrollableAppsGridView::SetFocusAfterEndDrag(AppListItem* drag_item) {
  218. auto* focus_manager = GetFocusManager();
  219. if (!focus_manager) // Does not exist during widget close.
  220. return;
  221. // Release focus from the dragged item (so it won't stay selected).
  222. focus_manager->ClearFocus();
  223. // When a folder is open, don't move focus to search box, since it may be
  224. // behind the folder.
  225. if (IsInFolder())
  226. return;
  227. // Focus the first focusable view in the widget (the search box).
  228. focus_manager->AdvanceFocus(/*reverse=*/false);
  229. }
  230. void ScrollableAppsGridView::RecordAppMovingTypeMetrics(
  231. AppListAppMovingType type) {
  232. UMA_HISTOGRAM_ENUMERATION("Apps.AppListBubbleAppMovingType", type,
  233. kMaxAppListAppMovingType);
  234. }
  235. int ScrollableAppsGridView::GetMaxRowsInPage(int page) const {
  236. // Return an arbitrary large number, chosen to be small enough
  237. // that cols*rows_per_page will not overflow.
  238. return 100000;
  239. }
  240. gfx::Vector2d ScrollableAppsGridView::GetGridCenteringOffset(int page) const {
  241. return gfx::Vector2d();
  242. }
  243. void ScrollableAppsGridView::EnsureViewVisible(const GridIndex& index) {
  244. // If called after usesr action that changes the grid size, make sure grid
  245. // view ancestor layout is up to date before attempting scroll.
  246. GetWidget()->LayoutRootViewIfNecessary();
  247. AppListItemView* view = GetViewAtIndex(index);
  248. if (view)
  249. view->ScrollViewToVisible();
  250. }
  251. absl::optional<ScrollableAppsGridView::VisibleItemIndexRange>
  252. ScrollableAppsGridView::GetVisibleItemIndexRange() const {
  253. // Indicate the first row on which item views are visible.
  254. absl::optional<int> first_visible_row;
  255. // Indicate the first invisible row that is right after the last visible row.
  256. absl::optional<int> first_invisible_row;
  257. const gfx::Rect scroll_view_visible_rect = scroll_view_->GetVisibleRect();
  258. for (size_t view_index = 0; view_index < view_model()->view_size();
  259. view_index += cols()) {
  260. // Calculate an item view's bounds in the scroll content's coordinates.
  261. gfx::Point item_view_local_origin;
  262. views::View* item_view = view_model()->view_at(view_index);
  263. views::View::ConvertPointToTarget(item_view, scroll_view_->contents(),
  264. &item_view_local_origin);
  265. gfx::Rect item_view_bounds_in_scroll_view =
  266. gfx::Rect(item_view_local_origin, item_view->size());
  267. // Calculate the overlapped area between the item view's bounds and the
  268. // visible area.
  269. item_view_bounds_in_scroll_view.InclusiveIntersect(
  270. scroll_view_visible_rect);
  271. // An item is deemed to visible if the overlapped area is not empty.
  272. const bool is_current_row_visible =
  273. !item_view_bounds_in_scroll_view.IsEmpty();
  274. const int current_row = view_index / cols();
  275. if (is_current_row_visible) {
  276. // Already find the first visible row so continue.
  277. if (first_visible_row)
  278. continue;
  279. first_visible_row = current_row;
  280. } else if (first_visible_row) {
  281. DCHECK(!first_invisible_row);
  282. first_invisible_row = current_row;
  283. break;
  284. }
  285. }
  286. if (!first_visible_row)
  287. return absl::nullopt;
  288. VisibleItemIndexRange result;
  289. result.first_index = *first_visible_row * cols();
  290. // If `first_invisible_row` is not found, it means that the last item view
  291. // in the view model is visible.
  292. result.last_index = first_invisible_row ? *first_invisible_row * cols() - 1
  293. : view_model()->view_size() - 1;
  294. return result;
  295. }
  296. base::ScopedClosureRunner ScrollableAppsGridView::LockAppsGridOpacity() {
  297. // Do nothing.
  298. return base::ScopedClosureRunner();
  299. }
  300. const gfx::Vector2d ScrollableAppsGridView::CalculateTransitionOffset(
  301. int page_of_view) const {
  302. // The ScrollableAppsGridView has no page transitions.
  303. return gfx::Vector2d();
  304. }
  305. BEGIN_METADATA(ScrollableAppsGridView, AppsGridView)
  306. END_METADATA
  307. } // namespace ash