paged_apps_grid_view.cc 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588
  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/paged_apps_grid_view.h"
  5. #include <algorithm>
  6. #include <memory>
  7. #include <utility>
  8. #include "ash/app_list/app_list_metrics.h"
  9. #include "ash/app_list/app_list_util.h"
  10. #include "ash/app_list/apps_grid_row_change_animator.h"
  11. #include "ash/app_list/model/app_list_item.h"
  12. #include "ash/app_list/views/app_list_folder_view.h"
  13. #include "ash/app_list/views/app_list_item_view.h"
  14. #include "ash/app_list/views/app_list_main_view.h"
  15. #include "ash/app_list/views/app_list_view.h"
  16. #include "ash/app_list/views/contents_view.h"
  17. #include "ash/app_list/views/ghost_image_view.h"
  18. #include "ash/constants/ash_features.h"
  19. #include "ash/public/cpp/app_list/app_list_color_provider.h"
  20. #include "ash/public/cpp/app_list/app_list_config.h"
  21. #include "ash/public/cpp/pagination/pagination_controller.h"
  22. #include "ash/style/dark_light_mode_controller_impl.h"
  23. #include "base/bind.h"
  24. #include "base/callback.h"
  25. #include "base/check.h"
  26. #include "base/cxx17_backports.h"
  27. #include "base/metrics/histogram_macros.h"
  28. #include "cc/paint/paint_flags.h"
  29. #include "third_party/skia/include/core/SkColor.h"
  30. #include "ui/accessibility/ax_node_data.h"
  31. #include "ui/compositor/animation_throughput_reporter.h"
  32. #include "ui/compositor/layer.h"
  33. #include "ui/compositor/paint_recorder.h"
  34. #include "ui/compositor/scoped_layer_animation_settings.h"
  35. #include "ui/events/event.h"
  36. #include "ui/events/types/event_type.h"
  37. #include "ui/gfx/canvas.h"
  38. #include "ui/gfx/geometry/insets.h"
  39. #include "ui/gfx/geometry/point_f.h"
  40. #include "ui/gfx/geometry/rect.h"
  41. #include "ui/gfx/geometry/size.h"
  42. #include "ui/gfx/geometry/transform.h"
  43. #include "ui/gfx/geometry/transform_util.h"
  44. #include "ui/gfx/geometry/vector2d.h"
  45. #include "ui/gfx/geometry/vector2d_f.h"
  46. #include "ui/views/animation/animation_builder.h"
  47. #include "ui/views/animation/bounds_animator.h"
  48. #include "ui/views/view.h"
  49. #include "ui/views/view_model_utils.h"
  50. namespace ash {
  51. namespace {
  52. // Presentation time histogram for apps grid scroll by dragging.
  53. constexpr char kPageDragScrollInClamshellHistogram[] =
  54. "Apps.PaginationTransition.DragScroll.PresentationTime.ClamshellMode";
  55. constexpr char kPageDragScrollInClamshellMaxLatencyHistogram[] =
  56. "Apps.PaginationTransition.DragScroll.PresentationTime.MaxLatency."
  57. "ClamshellMode";
  58. constexpr char kPageDragScrollInTabletHistogram[] =
  59. "Apps.PaginationTransition.DragScroll.PresentationTime.TabletMode";
  60. constexpr char kPageDragScrollInTabletMaxLatencyHistogram[] =
  61. "Apps.PaginationTransition.DragScroll.PresentationTime.MaxLatency."
  62. "TabletMode";
  63. // Delay in milliseconds to do the page flip in fullscreen app list.
  64. constexpr base::TimeDelta kPageFlipDelay = base::Milliseconds(500);
  65. // Duration for page transition.
  66. constexpr base::TimeDelta kPageTransitionDuration = base::Milliseconds(250);
  67. // The size of the zone within which app list item drag events will trigger a
  68. // page flip.
  69. constexpr int kPageFlipZoneSize = 20;
  70. // The height of the fadeout mask at vertical edges of the apps grid view.
  71. constexpr int kDefaultFadeoutMaskHeight = 16;
  72. // Duration for overscroll page transition.
  73. constexpr base::TimeDelta kOverscrollPageTransitionDuration =
  74. base::Milliseconds(50);
  75. // Vertical padding between the apps grid pages.
  76. constexpr int kPaddingBetweenPages = 48;
  77. // Vertical padding between the apps grid pages in cardified state.
  78. constexpr int kCardifiedPaddingBetweenPages = 12;
  79. constexpr int kCardifiedPaddingBetweenPagesProdLauncher = 8;
  80. // Horizontal padding of the apps grid page in cardified state.
  81. constexpr int kCardifiedHorizontalPadding = 16;
  82. // The radius of the corner of the background cards in the apps grid.
  83. constexpr int kBackgroundCardCornerRadius = 12;
  84. constexpr int kBackgroundCardCornerRadiusProdLauncher = 16;
  85. // The opacity for the background cards when hidden.
  86. constexpr float kBackgroundCardOpacityHide = 0.0f;
  87. // Animation curve used for entering and exiting cardified state.
  88. constexpr gfx::Tween::Type kCardifiedStateTweenType =
  89. gfx::Tween::LINEAR_OUT_SLOW_IN;
  90. // The minimum amount of space that should exist vertically between two app
  91. // tiles in the root grid.
  92. constexpr int kMinVerticalPaddingBetweenTiles = 8;
  93. // The maximum amount of space that should exist vetically between two app
  94. // tiles in the root grid.
  95. constexpr int kMaxVerticalPaddingBetweenTiles = 96;
  96. // The amount of vetical space between the edge of the background card and the
  97. // closest app tile.
  98. constexpr int kBackgroundCardVerticalPadding = 8;
  99. // The amount of horizontal space between the edge of the background card and
  100. // the closest app tile.
  101. constexpr int kBackgroundCardHorizontalPadding = 16;
  102. constexpr int kBackgroundCardBorderStrokeWidth = 1.0f;
  103. int GetFadeoutMaskHeight() {
  104. // The fadeout mask layer is shown only if background blur is enabled - if
  105. // fadeout mask is not shown, return 0 here so the apps grid is not shown in
  106. // the fadeout zone during drag.
  107. return features::IsBackgroundBlurEnabled() ? kDefaultFadeoutMaskHeight : 0;
  108. }
  109. } // namespace
  110. class PagedAppsGridView::BackgroundCardLayer : public ui::Layer,
  111. public ui::LayerDelegate {
  112. public:
  113. BackgroundCardLayer() : Layer(ui::LAYER_TEXTURED) {
  114. SetFillsBoundsOpaquely(false);
  115. set_delegate(this);
  116. }
  117. BackgroundCardLayer(const BackgroundCardLayer&) = delete;
  118. BackgroundCardLayer& operator=(const BackgroundCardLayer&) = delete;
  119. ~BackgroundCardLayer() override = default;
  120. void SetIsActivePage(bool is_active_page) {
  121. is_active_page_ = is_active_page;
  122. SchedulePaint(parent()->bounds());
  123. }
  124. private:
  125. // ui::LayerDelegate:
  126. void OnPaintLayer(const ui::PaintContext& context) override {
  127. ui::PaintRecorder recorder(context, size());
  128. gfx::Canvas* canvas = recorder.canvas();
  129. gfx::RectF card_size((gfx::SizeF(size())));
  130. const int corner_radius = features::IsProductivityLauncherEnabled()
  131. ? kBackgroundCardCornerRadiusProdLauncher
  132. : kBackgroundCardCornerRadius;
  133. // Draw a solid rounded rect as the background.
  134. cc::PaintFlags flags;
  135. auto* color_provider = AppListColorProvider::Get();
  136. SkColor fill_color =
  137. is_active_page_ ? color_provider->GetGridBackgroundCardActiveColor()
  138. : color_provider->GetGridBackgroundCardInactiveColor();
  139. flags.setColor(fill_color);
  140. flags.setStyle(cc::PaintFlags::kFill_Style);
  141. flags.setAntiAlias(true);
  142. canvas->DrawRoundRect(card_size, corner_radius, flags);
  143. if (features::IsProductivityLauncherEnabled() && is_active_page_) {
  144. // Draw a border around the active page.
  145. const bool dark_mode =
  146. DarkLightModeControllerImpl::Get()->IsDarkModeEnabled();
  147. flags.setColor(dark_mode ? SK_ColorWHITE : SK_ColorBLACK);
  148. flags.setAlpha(dark_mode ? 0x29 /*16%*/ : 0x1F /*12%*/);
  149. flags.setStyle(cc::PaintFlags::kStroke_Style);
  150. flags.setStrokeWidth(kBackgroundCardBorderStrokeWidth);
  151. flags.setAntiAlias(true);
  152. card_size.Inset(gfx::InsetsF(kBackgroundCardBorderStrokeWidth / 2.0f));
  153. canvas->DrawRoundRect(card_size, corner_radius, flags);
  154. }
  155. }
  156. void OnDeviceScaleFactorChanged(float old_device_scale_factor,
  157. float new_device_scale_factor) override {}
  158. bool is_active_page_ = false;
  159. };
  160. PagedAppsGridView::PagedAppsGridView(
  161. ContentsView* contents_view,
  162. AppListA11yAnnouncer* a11y_announcer,
  163. AppsGridViewFolderDelegate* folder_delegate,
  164. AppListFolderController* folder_controller,
  165. ContainerDelegate* container_delegate,
  166. AppListKeyboardController* keyboard_controller)
  167. : AppsGridView(a11y_announcer,
  168. contents_view->GetAppListMainView()->view_delegate(),
  169. folder_delegate,
  170. folder_controller,
  171. keyboard_controller),
  172. contents_view_(contents_view),
  173. container_delegate_(container_delegate),
  174. page_flip_delay_(kPageFlipDelay),
  175. is_productivity_launcher_enabled_(
  176. features::IsProductivityLauncherEnabled()) {
  177. DCHECK(contents_view_);
  178. SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
  179. view_structure_.Init(
  180. (IsInFolder() || features::IsProductivityLauncherEnabled())
  181. ? PagedViewStructure::Mode::kFullPages
  182. : PagedViewStructure::Mode::kPartialPages);
  183. pagination_model_.SetTransitionDurations(kPageTransitionDuration,
  184. kOverscrollPageTransitionDuration);
  185. pagination_model_.AddObserver(this);
  186. pagination_controller_ = std::make_unique<PaginationController>(
  187. &pagination_model_,
  188. IsInFolder() ? PaginationController::SCROLL_AXIS_HORIZONTAL
  189. : PaginationController::SCROLL_AXIS_VERTICAL,
  190. IsInFolder()
  191. ? base::DoNothing()
  192. : base::BindRepeating(&AppListRecordPageSwitcherSourceByEventType),
  193. IsTabletMode());
  194. }
  195. PagedAppsGridView::~PagedAppsGridView() {
  196. pagination_model_.RemoveObserver(this);
  197. }
  198. void PagedAppsGridView::OnTabletModeChanged(bool started) {
  199. pagination_controller_->set_is_tablet_mode(started);
  200. // Enable/Disable folder icons's background blur based on tablet mode.
  201. for (const auto& entry : view_model()->entries()) {
  202. auto* item_view = static_cast<AppListItemView*>(entry.view);
  203. if (item_view->item() && item_view->item()->is_folder())
  204. item_view->SetBackgroundBlurEnabled(started);
  205. }
  206. // Prevent context menus from remaining open after a transition
  207. CancelContextMenusOnCurrentPage();
  208. // Abort the running reorder animation when the tablet mode updates.
  209. MaybeAbortWholeGridAnimation();
  210. }
  211. void PagedAppsGridView::HandleScrollFromParentView(const gfx::Vector2d& offset,
  212. ui::EventType type) {
  213. // If |pagination_model_| is empty, don't handle scroll events.
  214. if (pagination_model_.total_pages() <= 0)
  215. return;
  216. // Maybe switch pages.
  217. pagination_controller_->OnScroll(offset, type);
  218. }
  219. void PagedAppsGridView::UpdateOpacity(bool restore_opacity,
  220. float apps_opacity_change_start,
  221. float apps_opacity_change_end) {
  222. if (view_structure_.pages().empty())
  223. return;
  224. // Do not update opacity when reorder animation is running.
  225. if (IsUnderWholeGridAnimation())
  226. return;
  227. // Return early if the opacity is locked.
  228. if (lock_opacity_)
  229. return;
  230. // App list view state animations animate the apps grid view opacity rather
  231. // than individual items' opacity. This method (used during app list view
  232. // drag) sets up opacity for individual grid item, and assumes that the apps
  233. // grid view is fully opaque.
  234. layer()->SetOpacity(1.0f);
  235. // First it should prepare the layers for all of the app items in the current
  236. // page when necessary, or destroy all of the layers when they become
  237. // unnecessary. Do not dynamically ensure/destroy layers of individual items
  238. // since the creation/destruction of the layer requires to repaint the parent
  239. // view (i.e. this class).
  240. if (restore_opacity) {
  241. // If item layers are still required (e.g. during drag), only update the
  242. // opacity (the layers will be deleted when they are no longer needed).
  243. if (ItemViewsRequireLayers()) {
  244. for (const auto& entry : view_model()->entries()) {
  245. if (!IsViewExplicitlyHidden(entry.view) && entry.view->layer())
  246. entry.view->layer()->SetOpacity(1.0f);
  247. }
  248. return;
  249. }
  250. // Layers are not necessary. Destroy them, and return. No need to update
  251. // opacity. This needs to be done on all views within |view_model_| because
  252. // some item view might have been moved out from the current page. See also
  253. // https://crbug.com/990529.
  254. for (const auto& entry : view_model()->entries())
  255. entry.view->DestroyLayer();
  256. return;
  257. }
  258. // Updates the opacity of the apps in current page. The opacity of the app
  259. // starting at 0.f when the centerline of the app is |kAllAppsOpacityStartPx|
  260. // above the bottom of work area and transitioning to 1.0f by the time the
  261. // centerline reaches |kAllAppsOpacityEndPx| above the work area bottom.
  262. AppListView* app_list_view = contents_view_->app_list_view();
  263. const int selected_page = pagination_model_.selected_page();
  264. // `pagination_model_` may have an extra page during app list item drag (if
  265. // the user is trying to add the app to a new page).
  266. if (selected_page == static_cast<int>(view_structure_.pages().size())) {
  267. CHECK(extra_page_opened_);
  268. return;
  269. }
  270. auto current_page = view_structure_.pages()[selected_page];
  271. // Ensure layers and update their opacity.
  272. for (AppListItemView* item_view : current_page)
  273. item_view->EnsureLayer();
  274. float centerline_above_work_area = 0.f;
  275. float opacity = 0.f;
  276. for (size_t i = 0; i < current_page.size(); i += cols()) {
  277. AppListItemView* item_view = current_page[i];
  278. gfx::Rect view_bounds = item_view->GetLocalBounds();
  279. views::View::ConvertRectToScreen(item_view, &view_bounds);
  280. centerline_above_work_area = std::max<float>(
  281. app_list_view->GetScreenBottom() - view_bounds.CenterPoint().y(), 0.f);
  282. opacity =
  283. base::clamp((centerline_above_work_area - apps_opacity_change_start) /
  284. (apps_opacity_change_end - apps_opacity_change_start),
  285. 0.f, 1.0f);
  286. if (opacity == item_view->layer()->opacity())
  287. continue;
  288. const size_t end_index = std::min(current_page.size() - 1, i + cols() - 1);
  289. for (size_t j = i; j <= end_index; ++j) {
  290. if (!IsViewHiddenForDrag(current_page[j]))
  291. current_page[j]->layer()->SetOpacity(opacity);
  292. }
  293. }
  294. }
  295. void PagedAppsGridView::SetMaxColumnsAndRows(int max_columns,
  296. int max_rows_on_first_page,
  297. int max_rows) {
  298. DCHECK_LE(max_rows_on_first_page, max_rows);
  299. const int first_page_size = TilesPerPage(0);
  300. const int default_page_size = TilesPerPage(1);
  301. max_rows_on_first_page_ = max_rows_on_first_page;
  302. max_rows_ = max_rows;
  303. SetMaxColumnsInternal(max_columns);
  304. // Update paging and pulsing blocks if the page sizes have changed.
  305. if (item_list() && (TilesPerPage(0) != first_page_size ||
  306. TilesPerPage(1) != default_page_size)) {
  307. view_structure_.LoadFromMetadata();
  308. UpdatePaging();
  309. UpdatePulsingBlockViews();
  310. PreferredSizeChanged();
  311. }
  312. }
  313. ////////////////////////////////////////////////////////////////////////////////
  314. // ui::EventHandler:
  315. void PagedAppsGridView::OnGestureEvent(ui::GestureEvent* event) {
  316. // If a tap/long-press occurs within a valid tile, it is usually a mistake and
  317. // should not close the launcher in clamshell mode. Otherwise, we should let
  318. // those events pass to the ancestor views.
  319. if (!IsTabletMode() && (event->type() == ui::ET_GESTURE_TAP ||
  320. event->type() == ui::ET_GESTURE_LONG_PRESS)) {
  321. if (EventIsBetweenOccupiedTiles(event)) {
  322. contents_view_->app_list_view()->CloseKeyboardIfVisible();
  323. event->SetHandled();
  324. }
  325. return;
  326. }
  327. if (!ShouldHandleDragEvent(*event))
  328. return;
  329. // Scroll begin events should not be passed to ancestor views from apps grid
  330. // in our current design. This prevents both ignoring horizontal scrolls in
  331. // app list, and closing open folders.
  332. if (pagination_controller_->OnGestureEvent(*event, GetContentsBounds()) ||
  333. event->type() == ui::ET_GESTURE_SCROLL_BEGIN) {
  334. event->SetHandled();
  335. }
  336. }
  337. void PagedAppsGridView::OnMouseEvent(ui::MouseEvent* event) {
  338. if (IsTabletMode() || !event->IsLeftMouseButton())
  339. return;
  340. gfx::PointF point_in_root = event->root_location_f();
  341. switch (event->type()) {
  342. case ui::ET_MOUSE_PRESSED:
  343. if (!EventIsBetweenOccupiedTiles(event))
  344. break;
  345. event->SetHandled();
  346. mouse_drag_start_point_ = point_in_root;
  347. last_mouse_drag_point_ = point_in_root;
  348. // Manually send the press event to the AppListView to update drag root
  349. // location
  350. contents_view_->app_list_view()->OnMouseEvent(event);
  351. break;
  352. case ui::ET_MOUSE_DRAGGED:
  353. if (!ShouldHandleDragEvent(*event)) {
  354. // We need to send mouse drag/release events to AppListView explicitly
  355. // because AppsGridView handles the mouse press event and gets captured.
  356. // Then AppListView cannot receive mouse drag/release events implcitly.
  357. // Send the fabricated mouse press event to AppListView if AppsGridView
  358. // is not in mouse drag yet.
  359. gfx::Point drag_location_in_app_list;
  360. if (!is_in_mouse_drag_) {
  361. ui::MouseEvent press_event(
  362. *event, static_cast<views::View*>(this),
  363. static_cast<views::View*>(contents_view_->app_list_view()),
  364. ui::ET_MOUSE_PRESSED, event->flags());
  365. contents_view_->app_list_view()->OnMouseEvent(&press_event);
  366. is_in_mouse_drag_ = true;
  367. }
  368. drag_location_in_app_list = event->location();
  369. ConvertPointToTarget(this, contents_view_->app_list_view(),
  370. &drag_location_in_app_list);
  371. event->set_location(drag_location_in_app_list);
  372. contents_view_->app_list_view()->OnMouseEvent(event);
  373. break;
  374. }
  375. event->SetHandled();
  376. if (!is_in_mouse_drag_) {
  377. if (abs(point_in_root.y() - mouse_drag_start_point_.y()) <
  378. kMouseDragThreshold) {
  379. break;
  380. }
  381. pagination_controller_->StartMouseDrag(point_in_root -
  382. mouse_drag_start_point_);
  383. is_in_mouse_drag_ = true;
  384. }
  385. if (!is_in_mouse_drag_)
  386. break;
  387. pagination_controller_->UpdateMouseDrag(
  388. point_in_root - last_mouse_drag_point_, GetContentsBounds());
  389. last_mouse_drag_point_ = point_in_root;
  390. break;
  391. case ui::ET_MOUSE_RELEASED: {
  392. // Calculate |should_handle| before resetting |mouse_drag_start_point_|
  393. // because ShouldHandleDragEvent depends on its value.
  394. const bool should_handle = ShouldHandleDragEvent(*event);
  395. is_in_mouse_drag_ = false;
  396. mouse_drag_start_point_ = gfx::PointF();
  397. last_mouse_drag_point_ = gfx::PointF();
  398. if (!should_handle) {
  399. gfx::Point drag_location_in_app_list = event->location();
  400. ConvertPointToTarget(this, contents_view_->app_list_view(),
  401. &drag_location_in_app_list);
  402. event->set_location(drag_location_in_app_list);
  403. contents_view_->app_list_view()->OnMouseEvent(event);
  404. break;
  405. }
  406. event->SetHandled();
  407. pagination_controller_->EndMouseDrag(*event);
  408. break;
  409. }
  410. default:
  411. return;
  412. }
  413. }
  414. ////////////////////////////////////////////////////////////////////////////////
  415. // views::View:
  416. void PagedAppsGridView::Layout() {
  417. if (ignore_layout())
  418. return;
  419. if (bounds_animator()->IsAnimating())
  420. bounds_animator()->Cancel();
  421. if (GetContentsBounds().IsEmpty())
  422. return;
  423. // Update cached tile padding first, as grid size calculations depend on the
  424. // cached padding value.
  425. UpdateTilePadding();
  426. // Prepare |page_size| * number-of-pages for |items_container_|, and sets the
  427. // origin properly to show the correct page.
  428. const gfx::Size page_size = GetPageSize();
  429. const int pages = pagination_model_.total_pages();
  430. const int current_page = pagination_model_.selected_page();
  431. if (pagination_controller_->scroll_axis() ==
  432. PaginationController::SCROLL_AXIS_HORIZONTAL) {
  433. const int page_width = page_size.width() + GetPaddingBetweenPages();
  434. items_container()->SetBoundsRect(gfx::Rect(-page_width * current_page, 0,
  435. page_width * pages,
  436. GetContentsBounds().height()));
  437. } else {
  438. const int page_height = page_size.height() + GetPaddingBetweenPages();
  439. items_container()->SetBoundsRect(gfx::Rect(0, -page_height * current_page,
  440. GetContentsBounds().width(),
  441. page_height * pages));
  442. }
  443. CalculateIdealBounds();
  444. for (size_t i = 0; i < view_model()->view_size(); ++i) {
  445. AppListItemView* view = GetItemViewAt(i);
  446. view->SetBoundsRect(view_model()->ideal_bounds(i));
  447. }
  448. if (cardified_state_) {
  449. DCHECK(!background_cards_.empty());
  450. // Make sure that the background cards render behind everything
  451. // else in the items container.
  452. for (size_t i = 0; i < background_cards_.size(); ++i) {
  453. ui::Layer* const background_card = background_cards_[i].get();
  454. background_card->SetBounds(BackgroundCardBounds(i));
  455. items_container()->layer()->StackAtBottom(background_card);
  456. }
  457. MaskContainerToBackgroundBounds();
  458. }
  459. views::ViewModelUtils::SetViewBoundsToIdealBounds(pulsing_blocks_model());
  460. }
  461. void PagedAppsGridView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
  462. node_data->AddBoolAttribute(ax::mojom::BoolAttribute::kClipsChildren, true);
  463. AppsGridView::GetAccessibleNodeData(node_data);
  464. }
  465. void PagedAppsGridView::OnThemeChanged() {
  466. views::View::OnThemeChanged();
  467. for (auto& card : background_cards_)
  468. card.get()->SchedulePaint(card->parent()->bounds());
  469. }
  470. ////////////////////////////////////////////////////////////////////////////////
  471. // AppsGridView:
  472. gfx::Size PagedAppsGridView::GetTileViewSize() const {
  473. const AppListConfig* config = app_list_config();
  474. return gfx::ScaleToRoundedSize(
  475. gfx::Size(config->grid_tile_width(), config->grid_tile_height()),
  476. (cardified_state_ ? GetAppsGridCardifiedScale() : 1.0f));
  477. }
  478. gfx::Insets PagedAppsGridView::GetTilePadding(int page) const {
  479. return gfx::Insets::VH(
  480. page == 0 ? -first_page_vertical_tile_padding_ : -vertical_tile_padding_,
  481. -horizontal_tile_padding_);
  482. }
  483. gfx::Size PagedAppsGridView::GetTileGridSize() const {
  484. return GetTileGridSizeForPage(1);
  485. }
  486. int PagedAppsGridView::GetPaddingBetweenPages() const {
  487. if (features::IsProductivityLauncherEnabled()) {
  488. return cardified_state_ ? kCardifiedPaddingBetweenPagesProdLauncher +
  489. 2 * kBackgroundCardVerticalPadding
  490. : kPaddingBetweenPages;
  491. }
  492. // In cardified state, padding between pages should be fixed and it should
  493. // include background card padding.
  494. return cardified_state_
  495. ? kCardifiedPaddingBetweenPages + 2 * vertical_tile_padding_
  496. : kPaddingBetweenPages;
  497. }
  498. int PagedAppsGridView::GetTotalPages() const {
  499. return pagination_model_.total_pages();
  500. }
  501. int PagedAppsGridView::GetSelectedPage() const {
  502. return pagination_model_.selected_page();
  503. }
  504. bool PagedAppsGridView::IsScrollAxisVertical() const {
  505. return pagination_controller_->scroll_axis() ==
  506. PaginationController::SCROLL_AXIS_VERTICAL;
  507. }
  508. void PagedAppsGridView::UpdateBorder() {
  509. if (IsInFolder())
  510. return;
  511. if (!features::IsProductivityLauncherEnabled())
  512. SetBorder(
  513. views::CreateEmptyBorder(gfx::Insets::VH(GetFadeoutMaskHeight(), 0)));
  514. }
  515. void PagedAppsGridView::MaybeStartCardifiedView() {
  516. if (!cardified_state_)
  517. StartAppsGridCardifiedView();
  518. }
  519. void PagedAppsGridView::MaybeEndCardifiedView() {
  520. if (cardified_state_)
  521. EndAppsGridCardifiedView();
  522. }
  523. void PagedAppsGridView::MaybeStartPageFlip() {
  524. MaybeStartPageFlipTimer(last_drag_point());
  525. if (cardified_state_) {
  526. int hovered_page = GetPageFlipTargetForDrag(last_drag_point());
  527. if (hovered_page == -1)
  528. hovered_page = pagination_model_.selected_page();
  529. SetHighlightedBackgroundCard(hovered_page);
  530. }
  531. }
  532. void PagedAppsGridView::MaybeStopPageFlip() {
  533. StopPageFlipTimer();
  534. }
  535. bool PagedAppsGridView::MaybeAutoScroll() {
  536. // Paged view does not auto-scroll.
  537. return false;
  538. }
  539. void PagedAppsGridView::SetFocusAfterEndDrag(AppListItem* drag_item) {
  540. // Leave focus on the dragged item. Pressing tab or an arrow key will
  541. // highlight that item.
  542. AppListItemView* drag_view = GetItemViewAt(GetModelIndexOfItem(drag_item));
  543. if (drag_view)
  544. drag_view->SilentlyRequestFocus();
  545. }
  546. void PagedAppsGridView::RecordAppMovingTypeMetrics(AppListAppMovingType type) {
  547. UMA_HISTOGRAM_ENUMERATION("Apps.AppListAppMovingType", type,
  548. kMaxAppListAppMovingType);
  549. }
  550. int PagedAppsGridView::GetMaxRowsInPage(int page) const {
  551. return page == 0 ? max_rows_on_first_page_ : max_rows_;
  552. }
  553. gfx::Vector2d PagedAppsGridView::GetGridCenteringOffset(int page) const {
  554. int y_offset = page == 0 ? GetTotalTopPaddingOnFirstPage() : 0;
  555. if (!cardified_state_)
  556. return gfx::Vector2d(0, y_offset);
  557. // The grid's y is set to start below the gradient mask, so subtract
  558. // this in centering the cardified state between gradient masks.
  559. y_offset -= margin_for_gradient_mask_;
  560. const gfx::Rect contents_bounds = GetContentsBounds();
  561. const gfx::Size grid_size = GetTileGridSizeForPage(page);
  562. return gfx::Vector2d(
  563. (contents_bounds.width() - grid_size.width()) / 2,
  564. (contents_bounds.height() + y_offset - grid_size.height()) / 2);
  565. }
  566. void PagedAppsGridView::UpdatePaging() {
  567. if (!IsInFolder() && !features::IsProductivityLauncherEnabled()) {
  568. pagination_model_.SetTotalPages(view_structure_.total_pages());
  569. return;
  570. }
  571. // Folders have the same number of tiles on every page, while the root
  572. // level grid can have a different number of tiles per page.
  573. size_t tiles = view_model()->view_size();
  574. int total_pages = 1;
  575. size_t tiles_on_page = TilesPerPage(0);
  576. while (tiles > tiles_on_page) {
  577. tiles -= tiles_on_page;
  578. ++total_pages;
  579. tiles_on_page = TilesPerPage(total_pages - 1);
  580. }
  581. pagination_model_.SetTotalPages(total_pages);
  582. }
  583. void PagedAppsGridView::RecordPageMetrics() {
  584. DCHECK(!IsInFolder());
  585. UMA_HISTOGRAM_COUNTS_100("Apps.NumberOfPages", GetTotalPages());
  586. // There are no empty slots with ProductivityLauncher enabled.
  587. if (features::IsProductivityLauncherEnabled())
  588. return;
  589. // Calculate the number of pages that have empty slots.
  590. int page_count = 0;
  591. const auto& pages = view_structure_.pages();
  592. for (size_t i = 0; i < pages.size(); ++i) {
  593. if (static_cast<int>(pages[i].size()) < TilesPerPage(i))
  594. ++page_count;
  595. }
  596. UMA_HISTOGRAM_COUNTS_100("Apps.NumberOfPagesNotFull", page_count);
  597. }
  598. const gfx::Vector2d PagedAppsGridView::CalculateTransitionOffset(
  599. int page_of_view) const {
  600. // If there is a transition, calculates offset for current and target page.
  601. const int current_page = GetSelectedPage();
  602. const PaginationModel::Transition& transition =
  603. pagination_model_.transition();
  604. const bool is_valid = pagination_model_.is_valid_page(transition.target_page);
  605. int multiplier = page_of_view;
  606. if (is_valid && abs(transition.target_page - current_page) > 1) {
  607. if (page_of_view == transition.target_page) {
  608. if (transition.target_page > current_page)
  609. multiplier = current_page + 1;
  610. else
  611. multiplier = current_page - 1;
  612. } else if (page_of_view != current_page) {
  613. multiplier = -1;
  614. }
  615. }
  616. const gfx::Size page_size = GetPageSize();
  617. if (IsScrollAxisVertical()) {
  618. const int page_height = page_size.height() + GetPaddingBetweenPages();
  619. return gfx::Vector2d(0, page_height * multiplier);
  620. }
  621. // Page size including padding pixels. A tile.x + page_width means the same
  622. // tile slot in the next page.
  623. const int page_width = page_size.width() + GetPaddingBetweenPages();
  624. return gfx::Vector2d(page_width * multiplier, 0);
  625. }
  626. void PagedAppsGridView::EnsureViewVisible(const GridIndex& index) {
  627. if (pagination_model_.has_transition())
  628. return;
  629. if (IsValidIndex(index))
  630. pagination_model_.SelectPage(index.page, false);
  631. }
  632. absl::optional<PagedAppsGridView::VisibleItemIndexRange>
  633. PagedAppsGridView::GetVisibleItemIndexRange() const {
  634. // Expect that there is no active page transitions. Otherwise, the return
  635. // value can be obsolete.
  636. DCHECK(!pagination_model_.has_transition());
  637. const int selected_page = pagination_model_.selected_page();
  638. if (selected_page < 0)
  639. return absl::nullopt;
  640. // Get the selected page's item count.
  641. const int on_page_item_count = GetNumberOfItemsOnPage(selected_page);
  642. // Return early if the selected page is empty.
  643. if (!on_page_item_count)
  644. return absl::nullopt;
  645. // Calculate the index of the first view on the selected page.
  646. int start_view_index = 0;
  647. for (int page_index = 0; page_index < selected_page; ++page_index)
  648. start_view_index += GetNumberOfItemsOnPage(page_index);
  649. return VisibleItemIndexRange(start_view_index,
  650. start_view_index + on_page_item_count - 1);
  651. }
  652. base::ScopedClosureRunner PagedAppsGridView::LockAppsGridOpacity() {
  653. lock_opacity_ = true;
  654. base::OnceClosure reset_closure = base::BindOnce(
  655. [](base::WeakPtr<PagedAppsGridView> weak_ptr) {
  656. if (!weak_ptr)
  657. return;
  658. weak_ptr->lock_opacity_ = false;
  659. },
  660. weak_ptr_factory_.GetWeakPtr());
  661. return base::ScopedClosureRunner(std::move(reset_closure));
  662. }
  663. ////////////////////////////////////////////////////////////////////////////////
  664. // PaginationModelObserver:
  665. void PagedAppsGridView::TotalPagesChanged(int previous_page_count,
  666. int new_page_count) {
  667. // Don't record from folder.
  668. if (IsInFolder())
  669. return;
  670. // Initial setup for the AppList starts with -1 pages. Ignore the page count
  671. // change resulting from the initialization of the view.
  672. if (previous_page_count <= 0)
  673. return;
  674. // Ignore page count changes after item list has been reset (e.g. during
  675. // shutdown).
  676. if (!item_list() || !item_list()->item_count())
  677. return;
  678. if (previous_page_count < new_page_count) {
  679. AppListPageCreationType type = AppListPageCreationType::kSyncOrInstall;
  680. if (handling_keyboard_move())
  681. type = AppListPageCreationType::kMovingAppWithKeyboard;
  682. else if (IsDragging())
  683. type = AppListPageCreationType::kDraggingApp;
  684. UMA_HISTOGRAM_ENUMERATION("Apps.AppList.AppsGridAddPage", type);
  685. }
  686. }
  687. void PagedAppsGridView::SelectedPageChanged(int old_selected,
  688. int new_selected) {
  689. items_container()->layer()->SetTransform(gfx::Transform());
  690. if (IsDragging()) {
  691. Layout();
  692. UpdateDropTargetRegion();
  693. MaybeStartPageFlipTimer(last_drag_point());
  694. } else {
  695. // If the selected view is no longer on the page, select the first item in
  696. // the page relative to the page swap in order to keep keyboard focus
  697. // movement predictable.
  698. if (selected_view() &&
  699. GetIndexOfView(selected_view()).page != new_selected) {
  700. GridIndex new_index(new_selected,
  701. (old_selected < new_selected)
  702. ? 0
  703. : (GetNumberOfItemsOnPage(new_selected) - 1));
  704. GetViewAtIndex(new_index)->RequestFocus();
  705. } else {
  706. ClearSelectedView();
  707. }
  708. Layout();
  709. }
  710. }
  711. void PagedAppsGridView::TransitionStarting() {
  712. // Drag ends and animation starts.
  713. presentation_time_recorder_.reset();
  714. CancelContextMenusOnCurrentPage();
  715. }
  716. void PagedAppsGridView::TransitionStarted() {
  717. if (abs(pagination_model_.transition().target_page -
  718. pagination_model_.selected_page()) > 1) {
  719. Layout();
  720. }
  721. pagination_metrics_tracker_ =
  722. GetWidget()->GetCompositor()->RequestNewThroughputTracker();
  723. pagination_metrics_tracker_->Start(metrics_util::ForSmoothness(
  724. base::BindRepeating(&ReportPaginationSmoothness, IsTabletMode())));
  725. }
  726. void PagedAppsGridView::TransitionChanged() {
  727. const PaginationModel::Transition& transition =
  728. pagination_model_.transition();
  729. if (!pagination_model_.is_valid_page(transition.target_page))
  730. return;
  731. // Sets the transform to locate the scrolled content.
  732. const gfx::Size page_size = GetPageSize();
  733. gfx::Vector2dF translate;
  734. const int dir =
  735. transition.target_page > pagination_model_.selected_page() ? -1 : 1;
  736. if (pagination_controller_->scroll_axis() ==
  737. PaginationController::SCROLL_AXIS_HORIZONTAL) {
  738. const int page_width = page_size.width() + GetPaddingBetweenPages();
  739. translate.set_x(page_width * transition.progress * dir);
  740. } else {
  741. const int page_height = page_size.height() + GetPaddingBetweenPages();
  742. translate.set_y(page_height * transition.progress * dir);
  743. }
  744. gfx::Transform transform;
  745. transform.Translate(translate);
  746. items_container()->layer()->SetTransform(transform);
  747. if (presentation_time_recorder_)
  748. presentation_time_recorder_->RequestNext();
  749. }
  750. void PagedAppsGridView::TransitionEnded() {
  751. pagination_metrics_tracker_->Stop();
  752. }
  753. void PagedAppsGridView::ScrollStarted() {
  754. DCHECK(!presentation_time_recorder_);
  755. if (IsTabletMode()) {
  756. presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
  757. GetWidget()->GetCompositor(), kPageDragScrollInTabletHistogram,
  758. kPageDragScrollInTabletMaxLatencyHistogram);
  759. } else {
  760. presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
  761. GetWidget()->GetCompositor(), kPageDragScrollInClamshellHistogram,
  762. kPageDragScrollInClamshellMaxLatencyHistogram);
  763. }
  764. }
  765. void PagedAppsGridView::ScrollEnded() {
  766. // Scroll can end without triggering state animation.
  767. presentation_time_recorder_.reset();
  768. }
  769. bool PagedAppsGridView::DoesIntersectRect(const views::View* target,
  770. const gfx::Rect& rect) const {
  771. gfx::Rect target_bounds(target->GetLocalBounds());
  772. if (features::IsProductivityLauncherEnabled() && GetSelectedPage() == 0) {
  773. // Allow events to pass to the continue section and recent apps.
  774. target_bounds.Inset(gfx::Insets::TLBR(first_page_offset_, 0, 0, 0));
  775. }
  776. return target_bounds.Intersects(rect);
  777. }
  778. bool PagedAppsGridView::FirePageFlipTimerForTest() {
  779. if (!page_flip_timer_.IsRunning())
  780. return false;
  781. page_flip_timer_.FireNow();
  782. return true;
  783. }
  784. gfx::Rect PagedAppsGridView::GetBackgroundCardBoundsForTesting(
  785. size_t card_index) {
  786. DCHECK_LT(card_index, background_cards_.size());
  787. gfx::Rect bounds_in_items_container = items_container()->GetMirroredRect(
  788. background_cards_[card_index]->bounds());
  789. gfx::Point origin_in_apps_grid = bounds_in_items_container.origin();
  790. views::View::ConvertPointToTarget(items_container(), this,
  791. &origin_in_apps_grid);
  792. return gfx::Rect(origin_in_apps_grid, bounds_in_items_container.size());
  793. }
  794. ui::Layer* PagedAppsGridView::GetBackgroundCardLayerForTesting(
  795. size_t card_index) const {
  796. DCHECK_LT(card_index, background_cards_.size());
  797. return background_cards_[card_index].get();
  798. }
  799. ////////////////////////////////////////////////////////////////////////////////
  800. // private:
  801. gfx::Size PagedAppsGridView::GetPageSize() const {
  802. // Calculate page size as the tile grid size on non-leading page (which may
  803. // have reduced number of tiles to accommodate continue section and recent
  804. // apps UI in the apps container).
  805. // NOTE: The the actual page size is always the same. To get the size of just
  806. // app grid tiles on the leading page, GetTileGridSizeForPage(0) can be
  807. // called.
  808. return GetTileGridSizeForPage(1);
  809. }
  810. gfx::Size PagedAppsGridView::GetTileGridSizeForPage(int page) const {
  811. gfx::Rect rect(GetTotalTileSize(page));
  812. const int rows = TilesPerPage(page) / cols();
  813. rect.set_size(gfx::Size(rect.width() * cols(), rect.height() * rows));
  814. rect.Inset(-GetTilePadding(page));
  815. return rect.size();
  816. }
  817. bool PagedAppsGridView::ShouldHandleDragEvent(const ui::LocatedEvent& event) {
  818. // If |pagination_model_| is empty, don't handle scroll events.
  819. if (pagination_model_.total_pages() <= 0)
  820. return false;
  821. DCHECK(event.IsGestureEvent() || event.IsMouseEvent());
  822. // If the event is a scroll down in clamshell mode on the first page, don't
  823. // let |pagination_controller_| handle it. Unless it occurs in a folder.
  824. auto calculate_offset = [this](const ui::LocatedEvent& event) -> int {
  825. if (event.IsGestureEvent())
  826. return event.AsGestureEvent()->details().scroll_y_hint();
  827. gfx::PointF root_location = event.root_location_f();
  828. return root_location.y() - mouse_drag_start_point_.y();
  829. };
  830. if (!IsInFolder() &&
  831. (event.IsMouseEvent() || event.type() == ui::ET_GESTURE_SCROLL_BEGIN) &&
  832. !IsTabletMode() &&
  833. ((pagination_model_.selected_page() == 0 &&
  834. calculate_offset(event) > 0) ||
  835. contents_view_->app_list_view()->is_in_drag())) {
  836. return false;
  837. }
  838. return true;
  839. }
  840. bool PagedAppsGridView::IsValidPageFlipTarget(int page) const {
  841. if (pagination_model_.is_valid_page(page))
  842. return true;
  843. // If the user wants to drag an app to the next new page and has not done so
  844. // during the dragging session, then it is the right target because a new page
  845. // will be created in OnPageFlipTimer().
  846. return !features::IsProductivityLauncherEnabled() && !IsInFolder() &&
  847. !extra_page_opened_ && pagination_model_.total_pages() == page;
  848. }
  849. int PagedAppsGridView::GetPageFlipTargetForDrag(const gfx::Point& drag_point) {
  850. int new_page_flip_target = -1;
  851. // Drag zones are at the edges of the scroll axis.
  852. if (IsScrollAxisVertical()) {
  853. if (features::IsProductivityLauncherEnabled()) {
  854. if (background_cards_.empty() ||
  855. !container_delegate_->IsPointWithinPageFlipBuffer(drag_point)) {
  856. return new_page_flip_target;
  857. }
  858. gfx::RectF background_card_rect_in_grid(
  859. background_cards_[GetSelectedPage()]->bounds());
  860. View::ConvertRectToTarget(items_container(), this,
  861. &background_card_rect_in_grid);
  862. // Set page flip target when the drag is above or below the background
  863. // card of the currently selected page.
  864. if (drag_point.y() < background_card_rect_in_grid.y()) {
  865. new_page_flip_target = pagination_model_.selected_page() - 1;
  866. } else if (drag_point.y() > background_card_rect_in_grid.bottom()) {
  867. new_page_flip_target = pagination_model_.selected_page() + 1;
  868. }
  869. } else {
  870. if (drag_point.y() < kPageFlipZoneSize + GetInsets().top()) {
  871. new_page_flip_target = pagination_model_.selected_page() - 1;
  872. } else if (container_delegate_->IsPointWithinBottomDragBuffer(
  873. drag_point, kPageFlipZoneSize)) {
  874. // If the drag point is within the drag buffer, but not over the shelf.
  875. new_page_flip_target = pagination_model_.selected_page() + 1;
  876. }
  877. }
  878. } else {
  879. // TODO(xiyuan): Fix this for RTL.
  880. if (new_page_flip_target == -1 && drag_point.x() < kPageFlipZoneSize)
  881. new_page_flip_target = pagination_model_.selected_page() - 1;
  882. if (new_page_flip_target == -1 &&
  883. drag_point.x() > width() - kPageFlipZoneSize) {
  884. new_page_flip_target = pagination_model_.selected_page() + 1;
  885. }
  886. }
  887. return new_page_flip_target;
  888. }
  889. void PagedAppsGridView::MaybeStartPageFlipTimer(const gfx::Point& drag_point) {
  890. if (!container_delegate_->IsPointWithinPageFlipBuffer(drag_point))
  891. StopPageFlipTimer();
  892. int new_page_flip_target = GetPageFlipTargetForDrag(drag_point);
  893. if (new_page_flip_target == page_flip_target_)
  894. return;
  895. StopPageFlipTimer();
  896. if (IsValidPageFlipTarget(new_page_flip_target)) {
  897. page_flip_target_ = new_page_flip_target;
  898. if (page_flip_target_ != pagination_model_.selected_page()) {
  899. page_flip_timer_.Start(FROM_HERE, page_flip_delay_, this,
  900. &PagedAppsGridView::OnPageFlipTimer);
  901. }
  902. }
  903. }
  904. void PagedAppsGridView::OnPageFlipTimer() {
  905. DCHECK(IsValidPageFlipTarget(page_flip_target_));
  906. if (pagination_model_.total_pages() == page_flip_target_) {
  907. // Create a new page because the user requests to put an item to a new page.
  908. extra_page_opened_ = true;
  909. pagination_model_.SetTotalPages(pagination_model_.total_pages() + 1);
  910. }
  911. pagination_model_.SelectPage(page_flip_target_, true);
  912. if (!IsInFolder())
  913. RecordPageSwitcherSource(kDragAppToBorder, IsTabletMode());
  914. BeginHideCurrentGhostImageView();
  915. }
  916. void PagedAppsGridView::StopPageFlipTimer() {
  917. page_flip_timer_.Stop();
  918. page_flip_target_ = -1;
  919. }
  920. void PagedAppsGridView::MaybeAbortExistingCardifiedAnimations() {
  921. if (cardified_animation_abort_handle_) {
  922. cardified_animation_abort_handle_.reset();
  923. }
  924. }
  925. void PagedAppsGridView::StartAppsGridCardifiedView() {
  926. if (IsInFolder())
  927. return;
  928. DCHECK(!cardified_state_);
  929. MaybeAbortExistingCardifiedAnimations();
  930. RemoveAllBackgroundCards();
  931. // Calculate background bounds for a normal grid so it animates from the
  932. // normal to the cardified bounds with the icons.
  933. // Add an extra card for the peeking page in the last page if item drag is
  934. // allowed to create new pages. This hints users that apps can be dragged past
  935. // the last existing page.
  936. const int peeking_card_count =
  937. features::IsProductivityLauncherEnabled() ? 0 : 1;
  938. for (int i = 0; i < pagination_model_.total_pages() + peeking_card_count; i++)
  939. AppendBackgroundCard();
  940. cardified_state_ = true;
  941. UpdateTilePadding();
  942. container_delegate_->OnCardifiedStateStarted();
  943. AnimateCardifiedState();
  944. }
  945. void PagedAppsGridView::EndAppsGridCardifiedView() {
  946. if (IsInFolder())
  947. return;
  948. DCHECK(cardified_state_);
  949. MaybeAbortExistingCardifiedAnimations();
  950. cardified_state_ = false;
  951. // Update the padding between tiles, so we can animate back the apps grid
  952. // elements to their original positions.
  953. UpdateTilePadding();
  954. AnimateCardifiedState();
  955. layer()->SetClipRect(gfx::Rect());
  956. }
  957. void PagedAppsGridView::AnimateCardifiedState() {
  958. if (GetWidget()) {
  959. // Normally Layout() cancels any animations. At this point there may be a
  960. // pending Layout(), force it now so that one isn't triggered part way
  961. // through the animation. Further, ignore this layout so that the position
  962. // isn't reset.
  963. DCHECK(!ignore_layout_);
  964. base::AutoReset<bool> auto_reset(&ignore_layout_, true);
  965. GetWidget()->LayoutRootViewIfNecessary();
  966. }
  967. // Resizing of AppListItemView icons can invalidate layout and cause a layout
  968. // while exiting cardified state. Keep ignoring layouts when exiting
  969. // cardified state, so that any row change animations that need to take place
  970. // while exiting do not get interrupted by a layout.
  971. if (!cardified_state_)
  972. ignore_layout_ = true;
  973. CalculateIdealBounds();
  974. // Cache the current item container position, as RecenterItemsContainer() may
  975. // change it.
  976. gfx::Point start_position = items_container()->origin();
  977. RecenterItemsContainer();
  978. // Apps might be animating due to drag reorder. Cancel any active animations
  979. // so that the cardified state animation can be applied.
  980. bounds_animator()->Cancel();
  981. gfx::Vector2d translate_offset(
  982. 0, start_position.y() - items_container()->origin().y());
  983. // Create background card animation metric reporters.
  984. std::vector<std::unique_ptr<ui::AnimationThroughputReporter>> reporters;
  985. for (auto& background_card : background_cards_) {
  986. reporters.push_back(std::make_unique<ui::AnimationThroughputReporter>(
  987. background_card->GetAnimator(),
  988. metrics_util::ForSmoothness(base::BindRepeating(
  989. &ReportCardifiedSmoothness, cardified_state_))));
  990. }
  991. views::AnimationBuilder animations;
  992. cardified_animation_abort_handle_ = animations.GetAbortHandle();
  993. animations
  994. .OnEnded(base::BindOnce(&PagedAppsGridView::MaybeCallOnBoundsAnimatorDone,
  995. weak_ptr_factory_.GetWeakPtr()))
  996. .OnAborted(
  997. base::BindOnce(&PagedAppsGridView::MaybeCallOnBoundsAnimatorDone,
  998. weak_ptr_factory_.GetWeakPtr()))
  999. .SetPreemptionStrategy(
  1000. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  1001. .Once()
  1002. .SetDuration(base::Milliseconds(kDefaultAnimationDuration));
  1003. DCHECK(!bounds_animation_for_cardified_state_in_progress_);
  1004. bounds_animation_for_cardified_state_in_progress_ = true;
  1005. AnimateAppListItemsForCardifiedState(&animations.GetCurrentSequence(),
  1006. translate_offset);
  1007. if (current_ghost_view_) {
  1008. auto index = current_ghost_view_->index();
  1009. gfx::Rect current_bounds = current_ghost_view_->bounds();
  1010. current_bounds.Offset(translate_offset);
  1011. gfx::Rect target_bounds = GetExpectedTileBounds(index);
  1012. target_bounds.Offset(CalculateTransitionOffset(index.page));
  1013. current_ghost_view_->SetBoundsRect(target_bounds);
  1014. gfx::Transform transform = gfx::TransformBetweenRects(
  1015. gfx::RectF(items_container()->GetMirroredRect(target_bounds)),
  1016. gfx::RectF(items_container()->GetMirroredRect(current_bounds)));
  1017. current_ghost_view_->layer()->SetTransform(transform);
  1018. animations.GetCurrentSequence().SetTransform(current_ghost_view_->layer(),
  1019. gfx::Transform(),
  1020. kCardifiedStateTweenType);
  1021. }
  1022. for (size_t i = 0; i < background_cards_.size(); i++) {
  1023. auto& background_card = background_cards_[i];
  1024. // Reposition card bounds to compensate for the translation offset.
  1025. gfx::Rect background_bounds = background_card->bounds();
  1026. background_bounds.Offset(translate_offset);
  1027. background_card->SetBounds(background_bounds);
  1028. if (cardified_state_) {
  1029. const bool is_active_page =
  1030. background_cards_[pagination_model_.selected_page()] ==
  1031. background_card;
  1032. background_card->SetIsActivePage(is_active_page);
  1033. } else {
  1034. animations.GetCurrentSequence().SetOpacity(background_card.get(),
  1035. kBackgroundCardOpacityHide);
  1036. }
  1037. animations.GetCurrentSequence().SetBounds(background_card.get(),
  1038. BackgroundCardBounds(i),
  1039. kCardifiedStateTweenType);
  1040. }
  1041. highlighted_page_ = pagination_model_.selected_page();
  1042. }
  1043. void PagedAppsGridView::AnimateAppListItemsForCardifiedState(
  1044. views::AnimationSequenceBlock* animation_sequence,
  1045. const gfx::Vector2d& translate_offset) {
  1046. // Check that at least one item needs animating to avoid creating an animation
  1047. // builder when no views need animating.
  1048. bool items_need_animating = false;
  1049. for (size_t i = 0; i < view_model()->view_size(); ++i) {
  1050. AppListItemView* item_view = view_model()->view_at(i);
  1051. if (!IsViewExplicitlyHidden(item_view)) {
  1052. items_need_animating = true;
  1053. break;
  1054. }
  1055. }
  1056. if (!items_need_animating)
  1057. return;
  1058. for (size_t i = 0; i < view_model()->view_size(); ++i) {
  1059. AppListItemView* entry_view = view_model()->view_at(i);
  1060. // Reposition view bounds to compensate for the translation offset.
  1061. gfx::Rect current_bounds = entry_view->bounds();
  1062. current_bounds.Offset(translate_offset);
  1063. entry_view->EnsureLayer();
  1064. if (cardified_state_)
  1065. entry_view->EnterCardifyState();
  1066. else
  1067. entry_view->ExitCardifyState();
  1068. gfx::Rect target_bounds(view_model()->ideal_bounds(i));
  1069. if (entry_view->has_pending_row_change()) {
  1070. entry_view->reset_has_pending_row_change();
  1071. row_change_animator_->AnimateBetweenRows(entry_view, current_bounds,
  1072. target_bounds);
  1073. continue;
  1074. }
  1075. entry_view->SetBoundsRect(target_bounds);
  1076. // Skip animating the item view if it is already hidden.
  1077. if (IsViewExplicitlyHidden(entry_view))
  1078. continue;
  1079. // View bounds are currently |target_bounds|. Transform the view so it
  1080. // appears in |current_bounds|. Note that bounds are flipped by views in
  1081. // RTL UI direction, which is not taken into account by
  1082. // `gfx::TransformBetweenRects()` - use mirrored rects to calculate
  1083. // transition transform when needed.
  1084. gfx::Transform transform = gfx::TransformBetweenRects(
  1085. gfx::RectF(items_container()->GetMirroredRect(target_bounds)),
  1086. gfx::RectF(items_container()->GetMirroredRect(current_bounds)));
  1087. entry_view->layer()->SetTransform(transform);
  1088. animation_sequence->SetTransform(entry_view->layer(), gfx::Transform(),
  1089. kCardifiedStateTweenType);
  1090. }
  1091. }
  1092. void PagedAppsGridView::MaybeCallOnBoundsAnimatorDone() {
  1093. DCHECK(bounds_animation_for_cardified_state_in_progress_);
  1094. bounds_animation_for_cardified_state_in_progress_ = false;
  1095. DestroyLayerItemsIfNotNeeded();
  1096. if (layer()->opacity() == 0.0f)
  1097. SetVisible(false);
  1098. if (cardified_state_) {
  1099. MaskContainerToBackgroundBounds();
  1100. } else {
  1101. OnCardifiedStateEnded();
  1102. }
  1103. }
  1104. void PagedAppsGridView::OnCardifiedStateEnded() {
  1105. ignore_layout_ = false;
  1106. RemoveAllBackgroundCards();
  1107. if (cardified_state_ended_test_callback_)
  1108. cardified_state_ended_test_callback_.Run();
  1109. container_delegate_->OnCardifiedStateEnded();
  1110. }
  1111. void PagedAppsGridView::RecenterItemsContainer() {
  1112. const int pages = pagination_model_.total_pages();
  1113. const int current_page = pagination_model_.selected_page();
  1114. const int page_height = GetPageSize().height() + GetPaddingBetweenPages();
  1115. items_container()->SetBoundsRect(gfx::Rect(0, -page_height * current_page,
  1116. GetContentsBounds().width(),
  1117. page_height * pages));
  1118. }
  1119. gfx::Rect PagedAppsGridView::BackgroundCardBounds(int new_page_index) {
  1120. // The size of the grid excluding the outer padding.
  1121. const gfx::Size grid_size = GetTileGridSizeForPage(new_page_index);
  1122. // The size for the background card that will be displayed. The outer padding
  1123. // of the grid needs to be added.
  1124. gfx::Size background_card_size;
  1125. if (features::IsProductivityLauncherEnabled()) {
  1126. background_card_size =
  1127. grid_size + gfx::Size(2 * kBackgroundCardHorizontalPadding,
  1128. 2 * kBackgroundCardVerticalPadding);
  1129. } else {
  1130. const int vertical_tile_padding = new_page_index == 0
  1131. ? first_page_vertical_tile_padding_
  1132. : vertical_tile_padding_;
  1133. background_card_size =
  1134. grid_size + gfx::Size(2 * horizontal_tile_padding_,
  1135. 2 * std::max(kMinVerticalPaddingBetweenTiles,
  1136. vertical_tile_padding));
  1137. }
  1138. // Add a padding on the sides to make space for pagination preview, but make
  1139. // sure the padding doesn't exceed the tile padding (otherwise the background
  1140. // bounds would clip the apps grid bounds).
  1141. const int extra_padding_for_cardified_state =
  1142. features::IsProductivityLauncherEnabled()
  1143. ? 0
  1144. : std::min(horizontal_tile_padding_, kCardifiedHorizontalPadding);
  1145. const int horizontal_padding =
  1146. (GetContentsBounds().width() - background_card_size.width()) / 2 +
  1147. extra_padding_for_cardified_state;
  1148. int y_offset = new_page_index == 0 ? GetTotalTopPaddingOnFirstPage() : 0;
  1149. if (features::IsProductivityLauncherEnabled()) {
  1150. // Subtract the amount that the apps grid view is offset by to accommodate
  1151. // for the top gradient mask. This will visually center the background card
  1152. // between the top and bottom gradient masks.
  1153. y_offset -= margin_for_gradient_mask_;
  1154. } else {
  1155. y_offset = std::max(y_offset, GetFadeoutMaskHeight());
  1156. }
  1157. int vertical_padding = y_offset + (GetContentsBounds().height() - y_offset -
  1158. background_card_size.height()) /
  1159. 2;
  1160. const int padding_between_pages = GetPaddingBetweenPages();
  1161. // The space that each page occupies in the items container. This is the size
  1162. // of the grid without outer padding plus the padding between pages.
  1163. const int total_page_height = GetPageSize().height() + padding_between_pages;
  1164. // We position a new card in the last place in items container view.
  1165. const int vertical_page_start_offset = total_page_height * new_page_index;
  1166. return gfx::Rect(
  1167. horizontal_padding, vertical_padding + vertical_page_start_offset,
  1168. background_card_size.width() - 2 * extra_padding_for_cardified_state,
  1169. background_card_size.height());
  1170. }
  1171. void PagedAppsGridView::AppendBackgroundCard() {
  1172. background_cards_.push_back(std::make_unique<BackgroundCardLayer>());
  1173. ui::Layer* current_layer = background_cards_.back().get();
  1174. current_layer->SetBounds(BackgroundCardBounds(background_cards_.size() - 1));
  1175. current_layer->SetVisible(true);
  1176. items_container()->layer()->Add(current_layer);
  1177. }
  1178. void PagedAppsGridView::RemoveBackgroundCard() {
  1179. items_container()->layer()->Remove(background_cards_.back().get());
  1180. background_cards_.pop_back();
  1181. }
  1182. void PagedAppsGridView::MaskContainerToBackgroundBounds() {
  1183. DCHECK(!background_cards_.empty());
  1184. // Mask apps grid container layer to the background card width. Optionally
  1185. // also include extra height to ensure the top gradient mask is shown as well.
  1186. layer()->SetClipRect(
  1187. gfx::Rect(background_cards_[0]->bounds().x(), -margin_for_gradient_mask_,
  1188. background_cards_[0]->bounds().width(),
  1189. layer()->bounds().height() + margin_for_gradient_mask_));
  1190. }
  1191. void PagedAppsGridView::RemoveAllBackgroundCards() {
  1192. for (auto& card : background_cards_)
  1193. items_container()->layer()->Remove(card.get());
  1194. background_cards_.clear();
  1195. }
  1196. void PagedAppsGridView::SetHighlightedBackgroundCard(int new_highlighted_page) {
  1197. if (!IsValidPageFlipTarget(new_highlighted_page))
  1198. return;
  1199. if (new_highlighted_page != highlighted_page_) {
  1200. background_cards_[highlighted_page_]->SetIsActivePage(false);
  1201. if (static_cast<int>(background_cards_.size()) == new_highlighted_page)
  1202. AppendBackgroundCard();
  1203. background_cards_[new_highlighted_page]->SetIsActivePage(true);
  1204. highlighted_page_ = new_highlighted_page;
  1205. }
  1206. }
  1207. void PagedAppsGridView::UpdateTilePadding() {
  1208. if (has_fixed_tile_padding_) {
  1209. // With fixed padding, all padding should remain constant. Set
  1210. // 'first_page_vertical_tile_padding_' here because it is unique to this
  1211. // class.
  1212. first_page_vertical_tile_padding_ = vertical_tile_padding_;
  1213. return;
  1214. }
  1215. gfx::Size content_size = GetContentsBounds().size();
  1216. const gfx::Size tile_size = GetTileViewSize();
  1217. if (cardified_state_) {
  1218. content_size =
  1219. gfx::ScaleToRoundedSize(content_size, GetAppsGridCardifiedScale()) -
  1220. gfx::Size(2 * kCardifiedHorizontalPadding, 0);
  1221. content_size.set_width(
  1222. std::max(content_size.width(), cols() * tile_size.width()));
  1223. content_size.set_height(
  1224. std::max(content_size.height(), max_rows_ * tile_size.height()));
  1225. }
  1226. const auto calculate_tile_padding = [](int content_size, int num_tiles,
  1227. int tile_size, int offset) -> int {
  1228. return num_tiles > 1 ? (content_size - offset - num_tiles * tile_size) /
  1229. ((num_tiles - 1) * 2)
  1230. : 0;
  1231. };
  1232. // Item tiles should be evenly distributed in this view.
  1233. horizontal_tile_padding_ = calculate_tile_padding(
  1234. content_size.width(), cols(), tile_size.width(), 0);
  1235. // Calculate padding for default page size, to ensure the spacing between
  1236. // pages remains the same when the selected page changes from/to the first
  1237. // page.
  1238. vertical_tile_padding_ = calculate_tile_padding(
  1239. content_size.height(), max_rows_, tile_size.height(), 0);
  1240. // NOTE: The padding on the first page can be different than other pages
  1241. // depending on `first_page_offset_` and `max_rows_on_first_page_`.
  1242. // When shown under recent apps, assume an extra row when calculating padding,
  1243. // as an extra leading tile padding will get added above the first row of apps
  1244. // (as a margin to recent apps container).
  1245. // Adjust `first_page_offset_` by removing space required for recent apps
  1246. // (assumes that recent apps tile size matches the apps grid tile size, and
  1247. // that recent apps container has a single row of apps) so padding is
  1248. // calculated assuming recent apps container is part of the apps grid.
  1249. if (shown_under_recent_apps_) {
  1250. first_page_vertical_tile_padding_ = calculate_tile_padding(
  1251. content_size.height(), max_rows_on_first_page_ + 1, tile_size.height(),
  1252. std::max(0, first_page_offset_ - tile_size.height()));
  1253. } else {
  1254. first_page_vertical_tile_padding_ =
  1255. calculate_tile_padding(content_size.height(), max_rows_on_first_page_,
  1256. tile_size.height(), first_page_offset_);
  1257. }
  1258. if (!cardified_state_ && unscaled_first_page_vertical_tile_padding_ !=
  1259. first_page_vertical_tile_padding_) {
  1260. unscaled_first_page_vertical_tile_padding_ =
  1261. first_page_vertical_tile_padding_;
  1262. }
  1263. }
  1264. bool PagedAppsGridView::ConfigureFirstPagePadding(
  1265. int offset,
  1266. bool shown_under_recent_apps) {
  1267. if (offset == first_page_offset_ &&
  1268. shown_under_recent_apps == shown_under_recent_apps_) {
  1269. return false;
  1270. }
  1271. first_page_offset_ = offset;
  1272. shown_under_recent_apps_ = shown_under_recent_apps;
  1273. return true;
  1274. }
  1275. int PagedAppsGridView::CalculateFirstPageMaxRows(int available_height,
  1276. int preferred_rows) {
  1277. // When shown under recent apps, calculate max rows as if recent apps
  1278. // container is part of the grid, i.e. calculate number of rows as if grid
  1279. // allows for an extra row of apps.
  1280. const int space_for_recent_apps =
  1281. shown_under_recent_apps_ ? app_list_config()->grid_tile_height() : 0;
  1282. const int max_rows = CalculateMaxRows(
  1283. available_height -
  1284. std::max(0, first_page_offset_ - space_for_recent_apps),
  1285. preferred_rows);
  1286. // If `shown_under_recent_apps_`, subtract a row from the result of
  1287. // `CalculateMaxRows()` which was calculated assuming there's an extra row of
  1288. // apps added for recent apps.
  1289. return shown_under_recent_apps_ ? std::max(0, max_rows - 1) : max_rows;
  1290. }
  1291. int PagedAppsGridView::CalculateMaxRows(int available_height,
  1292. int preferred_rows) {
  1293. if (!features::IsProductivityLauncherEnabled() || !app_list_config())
  1294. return preferred_rows;
  1295. const int tile_height = app_list_config()->grid_tile_height();
  1296. const int padding_for_preferred_rows =
  1297. (available_height - preferred_rows * tile_height) / (preferred_rows - 1);
  1298. int final_row_count = preferred_rows;
  1299. if (padding_for_preferred_rows < kMinVerticalPaddingBetweenTiles) {
  1300. // The padding with the preferred number of rows is too small. So find the
  1301. // max number of rows which will fit within the available space.
  1302. // I.e. max n where:
  1303. // n * tile_height + (n - 1) * min_padding <= available_height
  1304. final_row_count = (available_height + kMinVerticalPaddingBetweenTiles) /
  1305. (tile_height + kMinVerticalPaddingBetweenTiles);
  1306. } else if (padding_for_preferred_rows > kMaxVerticalPaddingBetweenTiles) {
  1307. // The padding with the preferred number of rows is too large. So find the
  1308. // min number of rows which will fit within the available space.
  1309. // I.e. min n, with padding as close to max as possible where:
  1310. // n* tile_height + (n - 1) * padding <= available_height
  1311. // padding <= max_padding
  1312. final_row_count = std::ceil(
  1313. static_cast<float>(available_height + kMaxVerticalPaddingBetweenTiles) /
  1314. (tile_height + kMaxVerticalPaddingBetweenTiles));
  1315. }
  1316. // Unit tests may create artificially small screens resulting in
  1317. // `final_row_count` of 0. Return 1 row to avoid divide-by-zero in layout.
  1318. return std::max(final_row_count, 1);
  1319. }
  1320. void PagedAppsGridView::AnimateOnNudgeRemoved() {
  1321. UpdateTilePadding();
  1322. if (GetWidget()) {
  1323. // Normally Layout() cancels any animations. At this point there may be a
  1324. // pending Layout(), force it now so that one isn't triggered part way
  1325. // through the animation. Further, ignore this layout so that the position
  1326. // isn't reset.
  1327. base::AutoReset<bool> auto_reset(&ignore_layout_, true);
  1328. GetWidget()->LayoutRootViewIfNecessary();
  1329. }
  1330. PrepareItemsForBoundsAnimation();
  1331. AnimateToIdealBounds();
  1332. }
  1333. void PagedAppsGridView::SetCardifiedStateEndedTestCallback(
  1334. base::RepeatingClosure cardified_ended_callback) {
  1335. cardified_state_ended_test_callback_ = std::move(cardified_ended_callback);
  1336. }
  1337. int PagedAppsGridView::GetTotalTopPaddingOnFirstPage() const {
  1338. // Add the page offset that accommodates continue section content, and if
  1339. // shown under recent apps, additional tile padding above the first row of
  1340. // apps.
  1341. return first_page_offset_ +
  1342. (shown_under_recent_apps_ ? 2 * first_page_vertical_tile_padding_ : 0);
  1343. }
  1344. void PagedAppsGridView::StackCardsAtBottom() {
  1345. for (size_t i = 0; i < background_cards_.size(); ++i) {
  1346. items_container()->layer()->StackAtBottom(background_cards_[i].get());
  1347. }
  1348. }
  1349. } // namespace ash