contents_view.cc 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971
  1. // Copyright (c) 2012 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/contents_view.h"
  5. #include <algorithm>
  6. #include <vector>
  7. #include "ash/app_list/app_list_view_delegate.h"
  8. #include "ash/app_list/views/app_list_folder_view.h"
  9. #include "ash/app_list/views/app_list_main_view.h"
  10. #include "ash/app_list/views/app_list_view.h"
  11. #include "ash/app_list/views/apps_container_view.h"
  12. #include "ash/app_list/views/apps_grid_view.h"
  13. #include "ash/app_list/views/assistant/assistant_page_view.h"
  14. #include "ash/app_list/views/expand_arrow_view.h"
  15. #include "ash/app_list/views/paged_apps_grid_view.h"
  16. #include "ash/app_list/views/search_box_view.h"
  17. #include "ash/app_list/views/search_result_list_view.h"
  18. #include "ash/app_list/views/search_result_page_view.h"
  19. #include "ash/app_list/views/search_result_tile_item_list_view.h"
  20. #include "ash/constants/ash_features.h"
  21. #include "ash/keyboard/ui/keyboard_ui_controller.h"
  22. #include "ash/public/cpp/app_list/app_list_config.h"
  23. #include "ash/public/cpp/app_list/app_list_features.h"
  24. #include "ash/public/cpp/app_list/app_list_switches.h"
  25. #include "base/bind.h"
  26. #include "base/check_op.h"
  27. #include "base/cxx17_backports.h"
  28. #include "base/notreached.h"
  29. #include "ui/base/l10n/l10n_util.h"
  30. #include "ui/compositor/layer.h"
  31. #include "ui/compositor/layer_animation_observer.h"
  32. #include "ui/compositor/scoped_animation_duration_scale_mode.h"
  33. #include "ui/compositor/scoped_layer_animation_settings.h"
  34. #include "ui/display/display.h"
  35. #include "ui/display/screen.h"
  36. #include "ui/events/event.h"
  37. #include "ui/strings/grit/ui_strings.h"
  38. #include "ui/views/accessibility/view_accessibility.h"
  39. #include "ui/views/animation/ink_drop.h"
  40. #include "ui/views/view_constants_aura.h"
  41. #include "ui/views/view_model.h"
  42. #include "ui/views/widget/widget.h"
  43. namespace ash {
  44. namespace {
  45. // The contents view height threshold under which search box should be shown in
  46. // dense layout.
  47. constexpr int kDenseLayoutHeightThreshold = 600;
  48. // The preferred search box height.
  49. constexpr int kSearchBoxHeight = 48;
  50. // The preferred search box height when the vertical app list contents space
  51. // is condensed - normally `kSearchBoxHeight` would be used.
  52. constexpr int kSearchBoxHeightForDenseLayout = 40;
  53. // The top search box margin (measured from the app list view top bound) when
  54. // app list view is in peeking/half state on non-apps page.
  55. constexpr int kDefaultSearchBoxTopMarginInPeekingState = 24;
  56. // The top search box margin (measured from the app list view top bound) when
  57. // app list view is in peeking state on the apps page.
  58. constexpr int kSearchBoxTopMarginInPeekingAppsPage = 84;
  59. // The range of app list transition progress in which the expand arrow'
  60. // opacity changes from 0 to 1.
  61. constexpr float kExpandArrowOpacityStartProgress = 0.61;
  62. constexpr float kExpandArrowOpacityEndProgress = 1;
  63. constexpr int kSearchBarMinWidth = 440;
  64. // Range of the fraction of app list from collapsed to peeking that search box
  65. // should change opacity.
  66. constexpr float kSearchBoxOpacityStartProgress = 0.11f;
  67. constexpr float kSearchBoxOpacityEndProgress = 1.0f;
  68. // Duration for page transition.
  69. constexpr base::TimeDelta kPageTransitionDuration = base::Milliseconds(250);
  70. // Duration for overscroll page transition.
  71. constexpr base::TimeDelta kOverscrollPageTransitionDuration =
  72. base::Milliseconds(50);
  73. // Calculates opacity value for the current app list progress.
  74. // |progress| - The target app list view progress - a value in [0.0, 2.0]
  75. // interval that describes the app list view position relative to
  76. // peeking and fullscreen state.
  77. // |transition_start| - The app list view progress at which opacity equals 0.0.
  78. // |transition_end| - The app list view progress at which opacity equals 1.0.
  79. float GetOpacityForProgress(float progress,
  80. float transition_start,
  81. float transition_end) {
  82. return base::clamp(
  83. (progress - transition_start) / (transition_end - transition_start), 0.0f,
  84. 1.0f);
  85. }
  86. } // namespace
  87. ContentsView::ContentsView(AppListView* app_list_view)
  88. : app_list_view_(app_list_view) {
  89. pagination_model_.SetTransitionDurations(kPageTransitionDuration,
  90. kOverscrollPageTransitionDuration);
  91. pagination_model_.AddObserver(this);
  92. }
  93. ContentsView::~ContentsView() {
  94. pagination_model_.RemoveObserver(this);
  95. }
  96. // static
  97. int ContentsView::GetPeekingSearchBoxTopMarginOnPage(AppListState page) {
  98. return page == AppListState::kStateApps
  99. ? kSearchBoxTopMarginInPeekingAppsPage
  100. : kDefaultSearchBoxTopMarginInPeekingState;
  101. }
  102. void ContentsView::Init() {
  103. AppListViewDelegate* view_delegate = GetAppListMainView()->view_delegate();
  104. apps_container_view_ = AddLauncherPage(
  105. std::make_unique<AppsContainerView>(this), AppListState::kStateApps);
  106. // Search results UI.
  107. auto search_result_page_view = std::make_unique<SearchResultPageView>();
  108. search_result_page_view->InitializeContainers(
  109. view_delegate, GetAppListMainView(), GetSearchBoxView());
  110. expand_arrow_view_ =
  111. AddChildView(std::make_unique<ExpandArrowView>(this, app_list_view_));
  112. search_result_page_view_ = AddLauncherPage(std::move(search_result_page_view),
  113. AppListState::kStateSearchResults);
  114. auto assistant_page_view = std::make_unique<AssistantPageView>(
  115. view_delegate->GetAssistantViewDelegate());
  116. assistant_page_view->SetVisible(false);
  117. assistant_page_view_ = AddLauncherPage(std::move(assistant_page_view),
  118. AppListState::kStateEmbeddedAssistant);
  119. int initial_page_index = GetPageIndexForState(AppListState::kStateApps);
  120. DCHECK_GE(initial_page_index, 0);
  121. page_before_search_ = initial_page_index;
  122. // Must only call SetTotalPages once all the launcher pages have been added
  123. // (as it will trigger a SelectedPageChanged call).
  124. pagination_model_.SetTotalPages(app_list_pages_.size());
  125. // Page 0 is selected by SetTotalPages and needs to be 'hidden' when selecting
  126. // the initial page.
  127. app_list_pages_[GetActivePageIndex()]->OnWillBeHidden();
  128. pagination_model_.SelectPage(initial_page_index, false);
  129. // Update suggestion chips after valid page is selected to prevent the update
  130. // from being ignored.
  131. apps_container_view_->UpdateSuggestionChips();
  132. ActivePageChanged();
  133. // Hide the search results initially.
  134. ShowSearchResults(false);
  135. }
  136. void ContentsView::ResetForShow() {
  137. target_page_for_last_view_state_update_ = absl::nullopt;
  138. apps_container_view_->ResetForShowApps();
  139. // SearchBoxView::ResetForShow() before SetActiveState(). It clears the search
  140. // query internally, which can show the search results page through
  141. // QueryChanged(). Since it wants to reset to kStateApps, first reset the
  142. // search box and then set its active state to kStateApps.
  143. GetSearchBoxView()->ResetForShow();
  144. // Make sure the default visibilities of the pages. This should be done before
  145. // SetActiveState() since it checks the visibility of the pages.
  146. apps_container_view_->SetVisible(true);
  147. search_result_page_view_->SetVisible(false);
  148. if (assistant_page_view_)
  149. assistant_page_view_->SetVisible(false);
  150. SetActiveState(AppListState::kStateApps, /*animate=*/false);
  151. // In side shelf, the opacity of the contents is not animated so set it to the
  152. // final state. In tablet mode, opacity of the elements is controlled by the
  153. // AppListControllerImpl which expects these elements to be opaque.
  154. // Otherwise the contents animate from 0 to 1 so set the initial opacity to 0.
  155. if (app_list_view_->is_side_shelf() || app_list_view_->is_tablet_mode()) {
  156. AnimateToViewState(AppListViewState::kFullscreenAllApps, base::TimeDelta());
  157. } else if (!last_target_view_state_.has_value() ||
  158. *last_target_view_state_ != AppListViewState::kClosed) {
  159. AnimateToViewState(AppListViewState::kClosed, base::TimeDelta());
  160. }
  161. }
  162. void ContentsView::CancelDrag() {
  163. if (apps_container_view_->apps_grid_view()->has_dragged_item())
  164. apps_container_view_->apps_grid_view()->EndDrag(true);
  165. if (apps_container_view_->app_list_folder_view()
  166. ->items_grid_view()
  167. ->has_dragged_item()) {
  168. apps_container_view_->app_list_folder_view()->items_grid_view()->EndDrag(
  169. true);
  170. }
  171. }
  172. void ContentsView::SetDragAndDropHostOfCurrentAppList(
  173. ApplicationDragAndDropHost* drag_and_drop_host) {
  174. apps_container_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
  175. }
  176. void ContentsView::OnAppListViewTargetStateChanged(
  177. AppListViewState target_state) {
  178. target_view_state_ = target_state;
  179. if (target_state == AppListViewState::kClosed) {
  180. CancelDrag();
  181. expand_arrow_view_->MaybeEnableHintingAnimation(false);
  182. return;
  183. }
  184. UpdateExpandArrowBehavior(target_state);
  185. }
  186. void ContentsView::OnTabletModeChanged(bool started) {
  187. apps_container_view_->OnTabletModeChanged(started);
  188. UpdateExpandArrowOpacity(GetActiveState(), target_view_state(),
  189. kPageTransitionDuration);
  190. }
  191. void ContentsView::SetActiveState(AppListState state) {
  192. SetActiveState(state, true /*animate*/);
  193. }
  194. void ContentsView::SetActiveState(AppListState state, bool animate) {
  195. if (IsStateActive(state))
  196. return;
  197. // The primary way to set the state to search or Assistant results should be
  198. // via |ShowSearchResults| or |ShowEmbeddedAssistantUI|.
  199. DCHECK(state != AppListState::kStateSearchResults &&
  200. state != AppListState::kStateEmbeddedAssistant);
  201. const int page_index = GetPageIndexForState(state);
  202. page_before_search_ = page_index;
  203. SetActiveStateInternal(page_index, animate);
  204. }
  205. int ContentsView::GetActivePageIndex() const {
  206. // The active page is changed at the beginning of an animation, not the end.
  207. return pagination_model_.SelectedTargetPage();
  208. }
  209. AppListState ContentsView::GetActiveState() const {
  210. return GetStateForPageIndex(GetActivePageIndex());
  211. }
  212. bool ContentsView::IsStateActive(AppListState state) const {
  213. int active_page_index = GetActivePageIndex();
  214. return active_page_index >= 0 &&
  215. GetPageIndexForState(state) == active_page_index;
  216. }
  217. int ContentsView::GetPageIndexForState(AppListState state) const {
  218. // Find the index of the view corresponding to the given state.
  219. std::map<AppListState, int>::const_iterator it = state_to_view_.find(state);
  220. if (it == state_to_view_.end())
  221. return -1;
  222. return it->second;
  223. }
  224. AppListState ContentsView::GetStateForPageIndex(int index) const {
  225. std::map<int, AppListState>::const_iterator it = view_to_state_.find(index);
  226. if (it == view_to_state_.end())
  227. return AppListState::kInvalidState;
  228. return it->second;
  229. }
  230. int ContentsView::NumLauncherPages() const {
  231. return pagination_model_.total_pages();
  232. }
  233. gfx::Size ContentsView::AdjustSearchBoxSizeToFitMargins(
  234. const gfx::Size& preferred_size) const {
  235. const int padded_width = GetContentsBounds().width() -
  236. 2 * apps_container_view_->GetIdealHorizontalMargin();
  237. return gfx::Size(
  238. base::clamp(padded_width, kSearchBarMinWidth, preferred_size.width()),
  239. preferred_size.height());
  240. }
  241. void ContentsView::SetActiveStateInternal(int page_index, bool animate) {
  242. if (!GetPageView(page_index)->GetVisible())
  243. return;
  244. app_list_pages_[GetActivePageIndex()]->OnWillBeHidden();
  245. // Start animating to the new page. Disable animation for tests.
  246. bool should_animate = animate && !set_active_state_without_animation_ &&
  247. !ui::ScopedAnimationDurationScaleMode::is_zero();
  248. // There's a chance of selecting page during the transition animation. To
  249. // reschedule the new animation from the beginning, |pagination_model_| needs
  250. // to finish the ongoing animation here.
  251. if (should_animate && pagination_model_.has_transition() &&
  252. pagination_model_.transition().target_page != page_index) {
  253. pagination_model_.FinishAnimation();
  254. // If the pending animation was animating from the current target page, the
  255. // target page might have got hidden as the animation was finished. Make
  256. // sure the page is reshown in that case.
  257. GetPageView(page_index)->SetVisible(true);
  258. }
  259. pagination_model_.SelectPage(page_index, should_animate);
  260. ActivePageChanged();
  261. UpdateExpandArrowOpacity(
  262. GetActiveState(), target_view_state(),
  263. should_animate ? kPageTransitionDuration : base::TimeDelta());
  264. if (!should_animate)
  265. Layout();
  266. }
  267. void ContentsView::ActivePageChanged() {
  268. AppListState state = AppListState::kInvalidState;
  269. std::map<int, AppListState>::const_iterator it =
  270. view_to_state_.find(GetActivePageIndex());
  271. if (it != view_to_state_.end())
  272. state = it->second;
  273. app_list_pages_[GetActivePageIndex()]->OnWillBeShown();
  274. GetAppListMainView()->view_delegate()->OnAppListPageChanged(state);
  275. UpdateSearchBoxVisibility(state);
  276. app_list_view_->UpdateWindowTitle();
  277. }
  278. void ContentsView::ShowSearchResults(bool show) {
  279. int search_page = GetPageIndexForState(AppListState::kStateSearchResults);
  280. DCHECK_GE(search_page, 0);
  281. // Hide or Show results
  282. search_result_page_view()->SetVisible(show);
  283. SetActiveStateInternal(show ? search_page : page_before_search_,
  284. true /*animate*/);
  285. if (show)
  286. search_result_page_view()->UpdateResultContainersVisibility();
  287. }
  288. bool ContentsView::IsShowingSearchResults() const {
  289. return IsStateActive(AppListState::kStateSearchResults);
  290. }
  291. void ContentsView::ShowEmbeddedAssistantUI(bool show) {
  292. const int assistant_page =
  293. GetPageIndexForState(AppListState::kStateEmbeddedAssistant);
  294. DCHECK_GE(assistant_page, 0);
  295. const int current_page = pagination_model_.SelectedTargetPage();
  296. // When closing the Assistant UI we return to the last page before the
  297. // search box.
  298. const int next_page = show ? assistant_page : page_before_search_;
  299. // Show or hide results.
  300. if (current_page != next_page) {
  301. GetPageView(current_page)->SetVisible(false);
  302. GetPageView(next_page)->SetVisible(true);
  303. }
  304. SetActiveStateInternal(next_page, true /*animate*/);
  305. // Sometimes the page stays in |assistant_page|, but the preferred bounds
  306. // might change meanwhile.
  307. if (show && current_page == assistant_page) {
  308. GetPageView(assistant_page)
  309. ->UpdatePageBoundsForState(
  310. AppListState::kStateEmbeddedAssistant, GetContentsBounds(),
  311. GetSearchBoxBounds(AppListState::kStateEmbeddedAssistant));
  312. }
  313. // If |next_page| is kStateApps, we need to set app_list_view to
  314. // kPeeking and layout the suggestion chips.
  315. if (next_page == GetPageIndexForState(AppListState::kStateApps)) {
  316. GetSearchBoxView()->ClearSearch();
  317. GetSearchBoxView()->SetSearchBoxActive(false, ui::ET_UNKNOWN);
  318. apps_container_view_->Layout();
  319. }
  320. }
  321. bool ContentsView::IsShowingEmbeddedAssistantUI() const {
  322. return IsStateActive(AppListState::kStateEmbeddedAssistant);
  323. }
  324. void ContentsView::InitializeSearchBoxAnimation(AppListState current_state,
  325. AppListState target_state) {
  326. SearchBoxView* search_box = GetSearchBoxView();
  327. if (!search_box->GetWidget())
  328. return;
  329. search_box->UpdateLayout(target_state,
  330. GetSearchBoxSize(target_state).height());
  331. search_box->UpdateBackground(target_state);
  332. gfx::Rect target_bounds = GetSearchBoxBounds(target_state);
  333. target_bounds = search_box->GetViewBoundsForSearchBoxContentsBounds(
  334. ConvertRectToWidgetWithoutTransform(target_bounds));
  335. // The search box animation is conducted as transform animation. Initially
  336. // search box changes its bounds to the target bounds but sets the transform
  337. // to be original bounds. Note that this transform shouldn't be animated
  338. // through ui::LayerAnimator since intermediate transformed bounds might not
  339. // match with other animation and that could look janky.
  340. search_box->GetWidget()->SetBounds(target_bounds);
  341. UpdateSearchBoxAnimation(0.0f, current_state, target_state);
  342. }
  343. void ContentsView::UpdateSearchBoxAnimation(double progress,
  344. AppListState current_state,
  345. AppListState target_state) {
  346. SearchBoxView* search_box = GetSearchBoxView();
  347. if (!search_box->GetWidget())
  348. return;
  349. gfx::Rect previous_bounds = GetSearchBoxBounds(current_state);
  350. previous_bounds = search_box->GetViewBoundsForSearchBoxContentsBounds(
  351. ConvertRectToWidgetWithoutTransform(previous_bounds));
  352. gfx::Rect target_bounds = GetSearchBoxBounds(target_state);
  353. target_bounds = search_box->GetViewBoundsForSearchBoxContentsBounds(
  354. ConvertRectToWidgetWithoutTransform(target_bounds));
  355. gfx::Rect current_bounds =
  356. gfx::Tween::RectValueBetween(progress, previous_bounds, target_bounds);
  357. gfx::Transform transform;
  358. if (current_bounds != target_bounds) {
  359. transform.Translate(current_bounds.origin() - target_bounds.origin());
  360. transform.Scale(
  361. static_cast<float>(current_bounds.width()) / target_bounds.width(),
  362. static_cast<float>(current_bounds.height()) / target_bounds.height());
  363. }
  364. search_box->GetWidget()->GetLayer()->SetTransform(transform);
  365. // Update search box view layer.
  366. const float current_radius =
  367. search_box->GetSearchBoxBorderCornerRadiusForState(current_state);
  368. const float target_radius =
  369. search_box->GetSearchBoxBorderCornerRadiusForState(target_state);
  370. search_box->layer()->SetClipRect(search_box->GetContentsBounds());
  371. search_box->layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(
  372. gfx::Tween::FloatValueBetween(progress, current_radius, target_radius)));
  373. }
  374. void ContentsView::UpdateExpandArrowBehavior(AppListViewState target_state) {
  375. const bool expand_arrow_enabled = target_state == AppListViewState::kPeeking;
  376. // The expand arrow is only focusable and has InkDropMode on in peeking
  377. // state.
  378. expand_arrow_view_->SetFocusBehavior(
  379. expand_arrow_enabled ? FocusBehavior::ALWAYS : FocusBehavior::NEVER);
  380. views::InkDrop::Get(expand_arrow_view_)
  381. ->SetMode(expand_arrow_enabled ? views::InkDropHost::InkDropMode::ON
  382. : views::InkDropHost::InkDropMode::OFF);
  383. // Allow ChromeVox to focus the expand arrow only when peeking launcher.
  384. expand_arrow_view_->GetViewAccessibility().OverrideIsIgnored(
  385. !expand_arrow_enabled);
  386. expand_arrow_view_->NotifyAccessibilityEvent(ax::mojom::Event::kTreeChanged,
  387. true);
  388. expand_arrow_view_->MaybeEnableHintingAnimation(expand_arrow_enabled);
  389. }
  390. void ContentsView::UpdateExpandArrowOpacity(
  391. AppListState target_state,
  392. AppListViewState target_app_list_view_state,
  393. base::TimeDelta transition_duration) {
  394. const bool target_visibility =
  395. target_state == AppListState::kStateApps &&
  396. target_app_list_view_state != AppListViewState::kClosed &&
  397. !app_list_view_->is_side_shelf() && !app_list_view_->is_tablet_mode();
  398. float target_opacity = target_visibility ? 1.0f : 0.0f;
  399. // Update the target opacity for when the app list view is in drag.
  400. if (target_opacity && app_list_view_->is_in_drag()) {
  401. // NOTE: The expand arrow is only shown for apps page, so it's OK to use the
  402. // app list drag progress for apps page.
  403. const float progress = app_list_view_->GetAppListTransitionProgress(
  404. AppListView::kProgressFlagNone);
  405. target_opacity =
  406. GetOpacityForProgress(progress, kExpandArrowOpacityStartProgress,
  407. kExpandArrowOpacityEndProgress);
  408. }
  409. ui::Layer* const layer = expand_arrow_view_->layer();
  410. if (transition_duration.is_zero()) {
  411. layer->SetOpacity(target_opacity);
  412. return;
  413. }
  414. if (target_opacity == layer->GetTargetOpacity())
  415. return;
  416. ui::ScopedLayerAnimationSettings animation(layer->GetAnimator());
  417. animation.SetTransitionDuration(transition_duration);
  418. animation.SetTweenType(gfx::Tween::EASE_IN);
  419. animation.SetPreemptionStrategy(
  420. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  421. layer->SetOpacity(target_opacity);
  422. }
  423. void ContentsView::UpdateSearchBoxVisibility(AppListState current_state) {
  424. auto* search_box_widget = GetSearchBoxView()->GetWidget();
  425. if (search_box_widget) {
  426. // Hide search box widget in order to click on the embedded Assistant UI.
  427. const bool show_search_box =
  428. current_state != AppListState::kStateEmbeddedAssistant;
  429. show_search_box ? search_box_widget->Show() : search_box_widget->Hide();
  430. }
  431. }
  432. AppListPage* ContentsView::GetPageView(int index) const {
  433. DCHECK_GT(static_cast<int>(app_list_pages_.size()), index);
  434. return app_list_pages_[index];
  435. }
  436. SearchBoxView* ContentsView::GetSearchBoxView() const {
  437. return GetAppListMainView()->search_box_view();
  438. }
  439. AppListMainView* ContentsView::GetAppListMainView() const {
  440. return app_list_view_->app_list_main_view();
  441. }
  442. void ContentsView::AddLauncherPageInternal(std::unique_ptr<AppListPage> view,
  443. AppListState state) {
  444. view->set_contents_view(this);
  445. app_list_pages_.push_back(AddChildView(std::move(view)));
  446. int page_index = app_list_pages_.size() - 1;
  447. bool success =
  448. state_to_view_.insert(std::make_pair(state, page_index)).second;
  449. success = success &&
  450. view_to_state_.insert(std::make_pair(page_index, state)).second;
  451. // There shouldn't be duplicates in either map.
  452. DCHECK(success);
  453. }
  454. gfx::Rect ContentsView::GetSearchBoxBounds(AppListState state) const {
  455. if (app_list_view_->is_in_drag()) {
  456. return GetSearchBoxExpectedBoundsForProgress(
  457. state, app_list_view_->GetAppListTransitionProgress(
  458. state == AppListState::kStateSearchResults
  459. ? AppListView::kProgressFlagSearchResults
  460. : AppListView::kProgressFlagNone));
  461. }
  462. return GetSearchBoxBoundsForViewState(state, target_view_state());
  463. }
  464. gfx::Size ContentsView::GetSearchBoxSize(AppListState state) const {
  465. AppListPage* page = GetPageView(GetPageIndexForState(state));
  466. gfx::Size size_preferred_by_page = page->GetPreferredSearchBoxSize();
  467. if (!size_preferred_by_page.IsEmpty())
  468. return AdjustSearchBoxSizeToFitMargins(size_preferred_by_page);
  469. gfx::Size preferred_size = GetSearchBoxView()->GetPreferredSize();
  470. // Reduce the search box size in fullscreen view state when the work area
  471. // height is less than 600 dip - the goal is to increase the amount of space
  472. // available to the apps grid.
  473. if (!features::IsProductivityLauncherEnabled() &&
  474. GetContentsBounds().height() < kDenseLayoutHeightThreshold) {
  475. preferred_size.set_height(kSearchBoxHeightForDenseLayout);
  476. } else {
  477. preferred_size.set_height(kSearchBoxHeight);
  478. }
  479. return AdjustSearchBoxSizeToFitMargins(preferred_size);
  480. }
  481. gfx::Rect ContentsView::GetSearchBoxBoundsForViewState(
  482. AppListState state,
  483. AppListViewState view_state) const {
  484. gfx::Size size = GetSearchBoxSize(state);
  485. return gfx::Rect(gfx::Point((width() - size.width()) / 2,
  486. GetSearchBoxTopForViewState(state, view_state)),
  487. size);
  488. }
  489. gfx::Rect ContentsView::GetSearchBoxExpectedBoundsForProgress(
  490. AppListState state,
  491. float progress) const {
  492. AppListViewState baseline_state = state == AppListState::kStateSearchResults
  493. ? AppListViewState::kHalf
  494. : AppListViewState::kPeeking;
  495. gfx::Rect bounds = GetSearchBoxBoundsForViewState(state, baseline_state);
  496. if (progress <= 1) {
  497. bounds.set_y(gfx::Tween::IntValueBetween(progress, 0, bounds.y()));
  498. } else {
  499. const int fullscreen_y = GetSearchBoxTopForViewState(
  500. state, AppListViewState::kFullscreenAllApps);
  501. bounds.set_y(
  502. gfx::Tween::IntValueBetween(progress - 1, bounds.y(), fullscreen_y));
  503. }
  504. return bounds;
  505. }
  506. bool ContentsView::Back() {
  507. // If the virtual keyboard is visible, dismiss the keyboard and return early
  508. auto* const keyboard_controller = keyboard::KeyboardUIController::Get();
  509. if (keyboard_controller->IsKeyboardVisible()) {
  510. keyboard_controller->HideKeyboardByUser();
  511. return true;
  512. }
  513. AppListState state = view_to_state_[GetActivePageIndex()];
  514. switch (state) {
  515. case AppListState::kStateApps: {
  516. PaginationModel* pagination_model =
  517. apps_container_view_->apps_grid_view()->pagination_model();
  518. if (apps_container_view_->IsInFolderView()) {
  519. apps_container_view_->app_list_folder_view()->CloseFolderPage();
  520. } else if (app_list_view_->is_tablet_mode() &&
  521. pagination_model->total_pages() > 0 &&
  522. pagination_model->selected_page() > 0) {
  523. bool animate = !ui::ScopedAnimationDurationScaleMode::is_zero();
  524. pagination_model->SelectPage(0, animate);
  525. } else {
  526. // Close the app list when Back() is called from the apps page.
  527. return false;
  528. }
  529. break;
  530. }
  531. case AppListState::kStateSearchResults:
  532. GetSearchBoxView()->ClearSearchAndDeactivateSearchBox();
  533. ShowSearchResults(false);
  534. break;
  535. case AppListState::kStateEmbeddedAssistant:
  536. ShowEmbeddedAssistantUI(false);
  537. break;
  538. case AppListState::kStateStart_DEPRECATED:
  539. case AppListState::kInvalidState:
  540. NOTREACHED();
  541. break;
  542. }
  543. return true;
  544. }
  545. void ContentsView::Layout() {
  546. const gfx::Rect rect = GetContentsBounds();
  547. if (rect.IsEmpty())
  548. return;
  549. // Layout expand arrow.
  550. gfx::Rect arrow_rect(GetContentsBounds());
  551. const gfx::Size arrow_size(expand_arrow_view_->GetPreferredSize());
  552. arrow_rect.set_height(arrow_size.height());
  553. arrow_rect.ClampToCenteredSize(arrow_size);
  554. expand_arrow_view_->SetBoundsRect(arrow_rect);
  555. expand_arrow_view_->SchedulePaint();
  556. if (pagination_model_.has_transition())
  557. return;
  558. UpdateYPositionAndOpacity();
  559. const AppListState current_state =
  560. GetStateForPageIndex(pagination_model_.selected_page());
  561. SearchBoxView* const search_box = GetSearchBoxView();
  562. const int search_box_height = GetSearchBoxSize(current_state).height();
  563. search_box->UpdateLayout(current_state, search_box_height);
  564. search_box->UpdateBackground(current_state);
  565. // Reset the transform which can be set through animation
  566. search_box->GetWidget()->GetLayer()->SetTransform(gfx::Transform());
  567. }
  568. const char* ContentsView::GetClassName() const {
  569. return "ContentsView";
  570. }
  571. void ContentsView::TotalPagesChanged(int previous_page_count,
  572. int new_page_count) {}
  573. void ContentsView::SelectedPageChanged(int old_selected, int new_selected) {
  574. if (old_selected >= 0)
  575. app_list_pages_[old_selected]->OnHidden();
  576. if (new_selected >= 0)
  577. app_list_pages_[new_selected]->OnShown();
  578. }
  579. void ContentsView::TransitionStarted() {
  580. const int current_page = pagination_model_.selected_page();
  581. const int target_page = pagination_model_.transition().target_page;
  582. const AppListState current_state = GetStateForPageIndex(current_page);
  583. const AppListState target_state = GetStateForPageIndex(target_page);
  584. for (AppListPage* page : app_list_pages_)
  585. page->OnAnimationStarted(current_state, target_state);
  586. InitializeSearchBoxAnimation(current_state, target_state);
  587. }
  588. void ContentsView::TransitionChanged() {
  589. const int current_page = pagination_model_.selected_page();
  590. const int target_page = pagination_model_.transition().target_page;
  591. const AppListState current_state = GetStateForPageIndex(current_page);
  592. const AppListState target_state = GetStateForPageIndex(target_page);
  593. const double progress = pagination_model_.transition().progress;
  594. for (AppListPage* page : app_list_pages_) {
  595. if (!page->GetVisible() ||
  596. !ShouldLayoutPage(page, current_state, target_state)) {
  597. continue;
  598. }
  599. page->OnAnimationUpdated(progress, current_state, target_state);
  600. }
  601. // Update search box's transform gradually. See the comment in
  602. // InitiateSearchBoxAnimation for why it's not animated through
  603. // ui::LayerAnimator.
  604. UpdateSearchBoxAnimation(progress, current_state, target_state);
  605. }
  606. void ContentsView::UpdateYPositionAndOpacity() {
  607. const int current_page = pagination_model_.has_transition()
  608. ? pagination_model_.transition().target_page
  609. : pagination_model_.selected_page();
  610. const AppListState current_state = GetStateForPageIndex(current_page);
  611. UpdateExpandArrowOpacity(current_state, target_view_state(),
  612. base::TimeDelta());
  613. expand_arrow_view_->SchedulePaint();
  614. SearchBoxView* search_box = GetSearchBoxView();
  615. const gfx::Rect search_box_bounds = GetSearchBoxBounds(current_state);
  616. const gfx::Rect search_rect =
  617. search_box->GetViewBoundsForSearchBoxContentsBounds(
  618. ConvertRectToWidgetWithoutTransform(search_box_bounds));
  619. search_box->GetWidget()->SetBounds(search_rect);
  620. float progress = 0.0f;
  621. if (app_list_view_->is_in_drag()) {
  622. progress = app_list_view_->GetAppListTransitionProgress(
  623. current_state == AppListState::kStateSearchResults
  624. ? AppListView::kProgressFlagSearchResults
  625. : AppListView::kProgressFlagNone);
  626. } else {
  627. progress = AppListView::GetTransitionProgressForState(target_view_state());
  628. }
  629. const bool restore_opacity = !app_list_view_->is_in_drag() &&
  630. target_view_state() != AppListViewState::kClosed;
  631. const float search_box_opacity =
  632. restore_opacity
  633. ? 1.0f
  634. : GetOpacityForProgress(progress, kSearchBoxOpacityStartProgress,
  635. kSearchBoxOpacityEndProgress);
  636. search_box->layer()->SetOpacity(search_box_opacity);
  637. for (AppListPage* page : app_list_pages_) {
  638. page->UpdatePageBoundsForState(current_state, GetContentsBounds(),
  639. search_box_bounds);
  640. page->UpdatePageOpacityForState(current_state, search_box_opacity,
  641. restore_opacity);
  642. }
  643. // If in drag, reset the transforms that might have been set in
  644. // AnimateToViewState().
  645. if (app_list_view_->is_in_drag()) {
  646. search_box->layer()->SetTransform(gfx::Transform());
  647. expand_arrow_view_->layer()->SetTransform(gfx::Transform());
  648. }
  649. target_page_for_last_view_state_update_ = current_state;
  650. }
  651. void ContentsView::AnimateToViewState(AppListViewState target_view_state,
  652. const base::TimeDelta& duration) {
  653. const AppListState target_page =
  654. GetStateForPageIndex(pagination_model_.has_transition()
  655. ? pagination_model_.transition().target_page
  656. : pagination_model_.selected_page());
  657. // Animates layer's opacity.
  658. // |duration| - The default transition duration. The actual transition gets
  659. // halved when animating to hidden state.
  660. // |view| - The view to animate.
  661. // |target_visibility| - The target layer visibility.
  662. auto animate_opacity = [](base::TimeDelta duration, views::View* view,
  663. bool target_visibility) {
  664. ui::Layer* const layer = view->layer();
  665. ui::ScopedLayerAnimationSettings animation(layer->GetAnimator());
  666. // Speed up the search box and contents view animation when closing the app
  667. // list, so they are not visible when the app list moves under the shelf.
  668. animation.SetTransitionDuration(duration / (target_visibility ? 1 : 2));
  669. animation.SetTweenType(gfx::Tween::EASE_IN);
  670. animation.SetPreemptionStrategy(
  671. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  672. layer->SetOpacity(target_visibility ? 1.0f : 0.0f);
  673. };
  674. // Fade in or out the contents view, the search box.
  675. const bool closing = target_view_state == AppListViewState::kClosed;
  676. animate_opacity(duration, GetSearchBoxView(), !closing /*target_visibility*/);
  677. // Fade in or out the expand arrow. Speed up the animation when closing the
  678. // app list to match the animation duration used for search box - see
  679. // `animate_opacity()`.
  680. UpdateExpandArrowOpacity(target_page, target_view_state,
  681. duration / (closing ? 2 : 1));
  682. // Animates layer's vertical position (using transform animation).
  683. // |layer| - The layer to transform.
  684. // |y_offset| - The initial vertical offset - the layer's vertical offset will
  685. // be animated to 0.
  686. auto animate_transform = [](base::TimeDelta duration, float y_offset,
  687. ui::Layer* layer) {
  688. gfx::Transform transform;
  689. transform.Translate(0, y_offset);
  690. layer->SetTransform(transform);
  691. auto settings = std::make_unique<ui::ScopedLayerAnimationSettings>(
  692. layer->GetAnimator());
  693. settings->SetTweenType(gfx::Tween::EASE_OUT);
  694. settings->SetTransitionDuration(duration);
  695. settings->SetPreemptionStrategy(
  696. ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET);
  697. layer->SetTransform(gfx::Transform());
  698. };
  699. // Animate the app list contents to the target state. The transform is
  700. // performed by setting the target view bounds (for search box and the app
  701. // list pages), applying a transform that positions the views into their
  702. // current states, and finally setting up transform animation to the identity
  703. // transform (to move the layers into their target bounds).
  704. // Note that pages are positioned relative to the search box view, and the
  705. // vertical offset from the search box remains constant through out the
  706. // animation, so it's sufficient to calculate the target search box view
  707. // offset and apply the transform to the whole contents view.
  708. const gfx::Rect target_search_box_bounds =
  709. GetSearchBoxBoundsForViewState(target_page, target_view_state);
  710. SearchBoxView* search_box = GetSearchBoxView();
  711. const gfx::Rect target_search_box_widget_bounds =
  712. search_box->GetViewBoundsForSearchBoxContentsBounds(
  713. ConvertRectToWidgetWithoutTransform(target_search_box_bounds));
  714. search_box->GetWidget()->SetBounds(target_search_box_widget_bounds);
  715. // Even though the target bounds are calculated for the target page, use the
  716. // last page for which app list view state was updated - in case page
  717. // transition is in progress, the total search box position change can be
  718. // described as composite of:
  719. // 1. Change in contents view padding due to app list view state change.
  720. // 2. Change in contents view padding due to page change.
  721. // Only the first part is expected to be handled by this animation, and this
  722. // uses the last used page as reference.
  723. // The second change will be handled by the page transition animation.
  724. const AppListState selected_page =
  725. target_page_for_last_view_state_update_.value_or(
  726. GetStateForPageIndex(pagination_model_.selected_page()));
  727. const int progress_baseline_flag =
  728. selected_page == AppListState::kStateSearchResults
  729. ? AppListView::kProgressFlagSearchResults
  730. : AppListView::kProgressFlagNone;
  731. const float progress = app_list_view_->GetAppListTransitionProgress(
  732. AppListView::kProgressFlagWithTransform | progress_baseline_flag);
  733. const gfx::Rect current_search_box_bounds =
  734. GetSearchBoxExpectedBoundsForProgress(selected_page, progress);
  735. const int y_offset =
  736. current_search_box_bounds.y() -
  737. GetSearchBoxBoundsForViewState(selected_page, target_view_state).y();
  738. // For search box, animate the search_box view layer instead of the widget
  739. // layer to avoid conflict with pagination model transitions (which update the
  740. // search box widget layer transform as the transition progresses).
  741. animate_transform(duration, y_offset, search_box->layer());
  742. // Update app list page bounds to their target values. This assumes that
  743. // potential in-progress pagination transition does not directly animate page
  744. // bounds.
  745. for (AppListPage* page : app_list_pages_) {
  746. page->UpdatePageBoundsForState(target_page, GetContentsBounds(),
  747. target_search_box_bounds);
  748. page->AnimateOpacity(progress, target_view_state,
  749. base::BindRepeating(animate_opacity, duration));
  750. page->AnimateYPosition(target_view_state,
  751. base::BindRepeating(animate_transform, duration),
  752. y_offset);
  753. }
  754. // Assistant page and search results page may host native views (e.g. for
  755. // card assistant results). These windows are descendants of the app list
  756. // view window layer rather than the page layers, so they have to be
  757. // animated separately from their associated page.
  758. for (auto* child_window : GetWidget()->GetNativeWindow()->children()) {
  759. View* host_view = child_window->GetProperty(views::kHostViewKey);
  760. if (!host_view)
  761. continue;
  762. animate_transform(duration, y_offset, child_window->layer());
  763. }
  764. last_target_view_state_ = target_view_state;
  765. target_page_for_last_view_state_update_ = target_page;
  766. // Schedule expand arrow repaint to ensure the view picks up the new target
  767. // state.
  768. if (target_view_state != AppListViewState::kClosed)
  769. expand_arrow_view()->SchedulePaint();
  770. animate_transform(
  771. duration,
  772. expand_arrow_view()->CalculateOffsetFromCurrentAppListProgress(progress),
  773. expand_arrow_view()->layer());
  774. }
  775. std::unique_ptr<ui::ScopedLayerAnimationSettings>
  776. ContentsView::CreateTransitionAnimationSettings(ui::Layer* layer) const {
  777. DCHECK(pagination_model_.has_transition());
  778. auto settings =
  779. std::make_unique<ui::ScopedLayerAnimationSettings>(layer->GetAnimator());
  780. settings->SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
  781. settings->SetTransitionDuration(
  782. pagination_model_.GetTransitionAnimationSlideDuration());
  783. return settings;
  784. }
  785. bool ContentsView::ShouldLayoutPage(AppListPage* page,
  786. AppListState current_state,
  787. AppListState target_state) const {
  788. if (page == apps_container_view_ || page == search_result_page_view_) {
  789. return ((current_state == AppListState::kStateSearchResults &&
  790. target_state == AppListState::kStateApps) ||
  791. (current_state == AppListState::kStateApps &&
  792. target_state == AppListState::kStateSearchResults));
  793. }
  794. if (page == assistant_page_view_) {
  795. return current_state == AppListState::kStateEmbeddedAssistant ||
  796. target_state == AppListState::kStateEmbeddedAssistant;
  797. }
  798. return false;
  799. }
  800. gfx::Rect ContentsView::ConvertRectToWidgetWithoutTransform(
  801. const gfx::Rect& rect) {
  802. gfx::Rect widget_rect = rect;
  803. for (const views::View* v = this; v; v = v->parent()) {
  804. widget_rect.Offset(v->GetMirroredPosition().OffsetFromOrigin());
  805. }
  806. return widget_rect;
  807. }
  808. int ContentsView::GetSearchBoxTopForViewState(
  809. AppListState state,
  810. AppListViewState view_state) const {
  811. switch (view_state) {
  812. case AppListViewState::kClosed:
  813. return 0;
  814. case AppListViewState::kFullscreenAllApps:
  815. case AppListViewState::kFullscreenSearch:
  816. return apps_container_view_
  817. ->CalculateMarginsForAvailableBounds(
  818. GetContentsBounds(), GetSearchBoxSize(AppListState::kStateApps))
  819. .top();
  820. case AppListViewState::kPeeking:
  821. case AppListViewState::kHalf:
  822. return GetPeekingSearchBoxTopMarginOnPage(state);
  823. }
  824. }
  825. } // namespace ash