| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/app_list/views/apps_container_view.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/app_list/app_list_model_provider.h" |
| #include "ash/app_list/app_list_util.h" |
| #include "ash/app_list/views/app_list_a11y_announcer.h" |
| #include "ash/app_list/views/app_list_folder_view.h" |
| #include "ash/app_list/views/app_list_item_view.h" |
| #include "ash/app_list/views/app_list_keyboard_controller.h" |
| #include "ash/app_list/views/app_list_main_view.h" |
| #include "ash/app_list/views/app_list_nudge_controller.h" |
| #include "ash/app_list/views/app_list_toast_container_view.h" |
| #include "ash/app_list/views/app_list_toast_view.h" |
| #include "ash/app_list/views/app_list_view.h" |
| #include "ash/app_list/views/contents_view.h" |
| #include "ash/app_list/views/continue_section_view.h" |
| #include "ash/app_list/views/folder_background_view.h" |
| #include "ash/app_list/views/page_switcher.h" |
| #include "ash/app_list/views/recent_apps_view.h" |
| #include "ash/app_list/views/search_box_view.h" |
| #include "ash/app_list/views/search_result_page_dialog_controller.h" |
| #include "ash/app_list/views/suggestion_chip_container_view.h" |
| #include "ash/constants/ash_features.h" |
| #include "ash/keyboard/ui/keyboard_ui_controller.h" |
| #include "ash/public/cpp/app_list/app_list_config.h" |
| #include "ash/public/cpp/app_list/app_list_model_delegate.h" |
| #include "ash/public/cpp/app_list/app_list_switches.h" |
| #include "ash/public/cpp/shelf_config.h" |
| #include "ash/public/cpp/style/color_provider.h" |
| #include "ash/search_box/search_box_constants.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "base/bind.h" |
| #include "base/check.h" |
| #include "base/command_line.h" |
| #include "base/cxx17_backports.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/time/time.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/color/color_id.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/layer_animation_element.h" |
| #include "ui/compositor/layer_animator.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/events/event.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/gfx/geometry/transform.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/strings/grit/ui_strings.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/animation/animation_builder.h" |
| #include "ui/views/controls/button/label_button.h" |
| #include "ui/views/controls/separator.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| #include "ui/views/focus/focus_manager.h" |
| #include "ui/views/layout/flex_layout.h" |
| #include "ui/views/view_class_properties.h" |
| #include "ui/views/view_utils.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // The number of rows for portrait mode with mode productivity launcher |
| // enabled. |
| constexpr int kPreferredGridRowsInPortraitProductivityLauncher = 5; |
| |
| // The number of columns for portrait mode with productivity launcher enabled. |
| constexpr int kPreferredGridColumnsInPortraitProductivityLauncher = 5; |
| |
| // The long apps grid dimension when productivity launcher is not enabled: |
| // * number of columns in landscape mode |
| // * number of rows in portrait mode |
| constexpr int kPreferredGridColumns = 5; |
| |
| // The short apps grid dimension when productivity launcher is not enabled: |
| // * number of rows in landscape mode |
| // * number of columns in portrait mode |
| constexpr int kPreferredGridRows = 4; |
| |
| // Range of the height of centerline above screen bottom that all apps should |
| // change opacity. NOTE: this is used to change page switcher's opacity as |
| // well. |
| constexpr float kAppsOpacityChangeStart = 8.0f; |
| constexpr float kAppsOpacityChangeEnd = 144.0f; |
| |
| // The amount by which the apps container UI should be offset downwards when |
| // shown on non apps page UI. |
| constexpr int kNonAppsStateVerticalOffset = 24; |
| |
| // The opacity the apps container UI should have when shown on non apps page UI. |
| constexpr float kNonAppsStateOpacity = 0.1; |
| |
| // The ratio of allowed bounds for apps grid view to its maximum margin. |
| constexpr int kAppsGridMarginRatio = 16; |
| constexpr int kAppsGridMarginRatioForSmallWidth = 12; |
| constexpr int kAppsGridMarginRatioForSmallHeight = 24; |
| |
| // The margins within the apps container for app list folder view. |
| constexpr int kFolderMargin = 16; |
| |
| // The suggestion chip container height. |
| constexpr int kSuggestionChipContainerHeight = 32; |
| |
| // The suggestion chip container top margin. |
| constexpr int kSuggestionChipContainerTopMargin = 16; |
| |
| // The horizontal margin between the apps grid view and the page switcher. |
| constexpr int kGridToPageSwitcherMargin = 8; |
| |
| // Minimal horizontal distance from the page switcher to apps container bounds. |
| constexpr int kPageSwitcherEndMargin = 16; |
| |
| // The minimum amount of vertical margin between the apps container edges and |
| // the its contents. |
| constexpr int kMinimumVerticalContainerMargin = 24; |
| |
| // The vertical margin above the `AppsGridView`. The space between suggestion |
| // chips and the app grid. With Productivity launcher, the space between the |
| // search box and the app grid. |
| constexpr int kAppGridTopMargin = 24; |
| |
| // The number of columns available for the ContinueSectionView. |
| constexpr int kContinueColumnCount = 4; |
| |
| // The vertical spacing between recent apps and continue section view. |
| constexpr int kRecentAppsTopMargin = 16; |
| |
| // The vertical spacing above and below the separator when using kRegular/kDense |
| // AppListConfigType. |
| constexpr int kRegularSeparatorVerticalInset = 16; |
| constexpr int kDenseSeparatorVerticalInset = 8; |
| |
| // The width of the separator. |
| constexpr int kSeparatorWidth = 240; |
| |
| // The actual height of the fadeout gradient mask at the top and bottom of the |
| // `scrollable_container_`. |
| constexpr int kDefaultFadeoutMaskHeight = 16; |
| |
| // Max amount of time to wait for zero state results when refreshing recent apps |
| // and continue section when launcher becomes visible. |
| constexpr base::TimeDelta kZeroStateSearchTimeout = base::Milliseconds(16); |
| |
| } // namespace |
| |
| // A view that contains continue section, recent apps and a separator view, |
| // which is shown when any of other views is shown. |
| // The view is intended to be a wrapper around suggested content views that |
| // makes applying identical transforms to suggested content views easier. |
| class AppsContainerView::ContinueContainer : public views::View { |
| public: |
| ContinueContainer(AppListKeyboardController* keyboard_controller, |
| AppListViewDelegate* view_delegate, |
| views::Separator* separator) |
| : view_delegate_(view_delegate), separator_(separator) { |
| DCHECK(view_delegate_); |
| DCHECK(separator_); |
| SetPaintToLayer(ui::LAYER_NOT_DRAWN); |
| |
| SetLayoutManager(std::make_unique<views::FlexLayout>()) |
| ->SetOrientation(views::LayoutOrientation::kVertical); |
| |
| continue_section_ = AddChildView(std::make_unique<ContinueSectionView>( |
| view_delegate, kContinueColumnCount, |
| /*tablet_mode=*/true)); |
| continue_section_->SetPaintToLayer(); |
| continue_section_->layer()->SetFillsBoundsOpaquely(false); |
| |
| recent_apps_ = AddChildView( |
| std::make_unique<RecentAppsView>(keyboard_controller, view_delegate)); |
| recent_apps_->SetPaintToLayer(); |
| recent_apps_->layer()->SetFillsBoundsOpaquely(false); |
| |
| UpdateRecentAppsMargins(); |
| UpdateContinueSectionVisibility(); |
| } |
| |
| // views::View: |
| void ChildVisibilityChanged(views::View* child) override { |
| if (child == recent_apps_ || child == continue_section_) |
| UpdateSeparatorVisibility(); |
| |
| if (child == continue_section_) |
| UpdateRecentAppsMargins(); |
| } |
| |
| bool HasRecentApps() const { return recent_apps_->GetVisible(); } |
| |
| void UpdateAppListConfig(AppListConfig* config) { |
| recent_apps_->UpdateAppListConfig(config); |
| } |
| |
| void UpdateContinueSectionVisibility() { |
| // The continue section view and recent apps view manage their own |
| // visibility internally. |
| continue_section_->UpdateElementsVisibility(); |
| recent_apps_->UpdateVisibility(); |
| UpdateSeparatorVisibility(); |
| } |
| |
| // Animates a fade-in for the continue section, recent apps and separator. |
| void FadeInViews() { |
| continue_section_->layer()->SetOpacity(0.0f); |
| recent_apps_->layer()->SetOpacity(0.0f); |
| separator_->layer()->SetOpacity(0.0f); |
| |
| views::AnimationBuilder() |
| .SetPreemptionStrategy(ui::LayerAnimator::PreemptionStrategy:: |
| IMMEDIATELY_ANIMATE_TO_NEW_TARGET) |
| .Once() |
| .At(base::Milliseconds(100)) |
| .SetOpacity(continue_section_, 1.0f) |
| .SetOpacity(recent_apps_, 1.0f) |
| .SetOpacity(separator_, 1.0f) |
| .SetDuration(base::Milliseconds(200)); |
| } |
| |
| ContinueSectionView* continue_section() { return continue_section_; } |
| RecentAppsView* recent_apps() { return recent_apps_; } |
| |
| private: |
| void UpdateRecentAppsMargins() { |
| // Remove recent apps top margin if continue section is hidden. |
| recent_apps_->SetProperty( |
| views::kMarginsKey, |
| gfx::Insets::TLBR( |
| continue_section_->GetVisible() ? kRecentAppsTopMargin : 0, 0, 0, |
| 0)); |
| } |
| |
| void UpdateSeparatorVisibility() { |
| separator_->SetVisible(recent_apps_->GetVisible() || |
| continue_section_->GetVisible()); |
| } |
| |
| AppListViewDelegate* const view_delegate_; |
| ContinueSectionView* continue_section_ = nullptr; |
| RecentAppsView* recent_apps_ = nullptr; |
| views::Separator* separator_ = nullptr; |
| }; |
| |
| AppsContainerView::AppsContainerView(ContentsView* contents_view) |
| : contents_view_(contents_view), |
| app_list_keyboard_controller_( |
| std::make_unique<AppListKeyboardController>(this)), |
| app_list_nudge_controller_(std::make_unique<AppListNudgeController>()) { |
| AppListModelProvider::Get()->AddObserver(this); |
| |
| SetPaintToLayer(ui::LAYER_NOT_DRAWN); |
| |
| scrollable_container_ = AddChildView(std::make_unique<views::View>()); |
| scrollable_container_->SetPaintToLayer(ui::LAYER_NOT_DRAWN); |
| AppListViewDelegate* view_delegate = |
| contents_view_->GetAppListMainView()->view_delegate(); |
| |
| // The bounds of the |scrollable_container_| will visually clip the |
| // |continue_container_| and |apps_grid_view_| layers. |
| scrollable_container_->layer()->SetMasksToBounds(true); |
| |
| AppListA11yAnnouncer* a11y_announcer = |
| contents_view->app_list_view()->a11y_announcer(); |
| if (features::IsProductivityLauncherEnabled()) { |
| separator_ = scrollable_container_->AddChildView( |
| std::make_unique<views::Separator>()); |
| separator_->SetColorId(ui::kColorAshSystemUIMenuSeparator); |
| separator_->SetPreferredSize( |
| gfx::Size(kSeparatorWidth, views::Separator::kThickness)); |
| // Initially set the vertical inset to kRegularSeparatorVerticalInset. The |
| // value will be updated in `AppsContainerView::UpdateAppListConfig()` |
| separator_->SetProperty(views::kMarginsKey, |
| gfx::Insets::VH(kRegularSeparatorVerticalInset, 0)); |
| separator_->SetPaintToLayer(); |
| separator_->layer()->SetFillsBoundsOpaquely(false); |
| // Visibility for `separator_` will be managed by the `continue_container_`. |
| separator_->SetVisible(false); |
| |
| dialog_controller_ = std::make_unique<SearchResultPageDialogController>( |
| contents_view_->GetSearchBoxView()); |
| |
| continue_container_ = |
| scrollable_container_->AddChildView(std::make_unique<ContinueContainer>( |
| app_list_keyboard_controller_.get(), view_delegate, separator_)); |
| continue_container_->continue_section()->SetNudgeController( |
| app_list_nudge_controller_.get()); |
| // Update the suggestion tasks after the app list nudge controller is set in |
| // continue section. |
| continue_container_->continue_section()->UpdateSuggestionTasks(); |
| |
| // Add a empty container view. A toast view should be added to |
| // `toast_container_` when the app list starts temporary sorting. |
| if (features::IsLauncherAppSortEnabled()) { |
| toast_container_ = scrollable_container_->AddChildView( |
| std::make_unique<AppListToastContainerView>( |
| app_list_nudge_controller_.get(), |
| app_list_keyboard_controller_.get(), a11y_announcer, |
| view_delegate, |
| /*delegate=*/this, /*tablet_mode=*/true)); |
| toast_container_->SetPaintToLayer(ui::LAYER_NOT_DRAWN); |
| } |
| } else { |
| // Add child view at index 0 so focus traversal goes to suggestion chips |
| // before the views in the scrollable_container. |
| suggestion_chip_container_view_ = AddChildViewAt( |
| std::make_unique<SuggestionChipContainerView>(contents_view), 0); |
| } |
| |
| apps_grid_view_ = |
| scrollable_container_->AddChildView(std::make_unique<PagedAppsGridView>( |
| contents_view, a11y_announcer, |
| /*folder_delegate=*/nullptr, |
| /*folder_controller=*/this, |
| /*container_delegate=*/this, app_list_keyboard_controller_.get())); |
| apps_grid_view_->Init(); |
| apps_grid_view_->pagination_model()->AddObserver(this); |
| if (features::IsProductivityLauncherEnabled()) |
| apps_grid_view_->set_margin_for_gradient_mask(kDefaultFadeoutMaskHeight); |
| |
| // Page switcher should be initialized after AppsGridView. |
| auto page_switcher = std::make_unique<PageSwitcher>( |
| apps_grid_view_->pagination_model(), true /* vertical */, |
| contents_view->app_list_view()->is_tablet_mode()); |
| page_switcher_ = AddChildView(std::move(page_switcher)); |
| |
| auto app_list_folder_view = std::make_unique<AppListFolderView>( |
| this, apps_grid_view_, contents_view_, a11y_announcer, view_delegate); |
| folder_background_view_ = AddChildView( |
| std::make_unique<FolderBackgroundView>(app_list_folder_view.get())); |
| |
| app_list_folder_view_ = AddChildView(std::move(app_list_folder_view)); |
| // The folder view is initially hidden. |
| app_list_folder_view_->SetVisible(false); |
| |
| // NOTE: At this point, the apps grid folder and recent apps grids are not |
| // fully initialized - they require an `app_list_config_` instance (because |
| // they contain AppListItemView), which in turn requires widget, and the |
| // view's contents bounds to be correctly calculated. The initialization |
| // will be completed in `OnBoundsChanged()` when the apps container bounds are |
| // first set. |
| } |
| |
| AppsContainerView::~AppsContainerView() { |
| AppListModelProvider::Get()->RemoveObserver(this); |
| apps_grid_view_->pagination_model()->RemoveObserver(this); |
| |
| // Make sure |page_switcher_| is deleted before |apps_grid_view_| because |
| // |page_switcher_| uses the PaginationModel owned by |apps_grid_view_|. |
| delete page_switcher_; |
| |
| // App list folder view, if shown, may reference/observe a root apps grid view |
| // item (associated with the item for which the folder is shown). Delete |
| // `app_list_folder_view_` explicitly to ensure it's deleted before |
| // `apps_grid_view_`. |
| delete app_list_folder_view_; |
| } |
| |
| void AppsContainerView::UpdateTopLevelGridDimensions() { |
| const GridLayout grid_layout = CalculateGridLayout(); |
| apps_grid_view_->SetMaxColumnsAndRows( |
| /*max_columns=*/grid_layout.columns, |
| /*max_rows_on_first_page=*/grid_layout.first_page_rows, |
| /*max_rows=*/grid_layout.rows); |
| } |
| |
| gfx::Rect AppsContainerView::CalculateAvailableBoundsForAppsGrid( |
| const gfx::Rect& contents_bounds) const { |
| gfx::Rect available_bounds = contents_bounds; |
| // Reserve horizontal margins to accommodate page switcher. |
| available_bounds.Inset( |
| gfx::Insets::VH(0, GetMinHorizontalMarginForAppsGrid())); |
| // Reserve vertical space for search box and suggestion chips. |
| available_bounds.Inset(gfx::Insets().set_top(GetMinTopMarginForAppsGrid( |
| contents_view_->GetSearchBoxSize(AppListState::kStateApps)))); |
| // Remove space for vertical margins at the top and bottom of the apps |
| // container. |
| if (features::IsProductivityLauncherEnabled()) { |
| available_bounds.Inset(gfx::Insets::VH(GetIdealVerticalMargin(), 0)); |
| } else { |
| available_bounds.Inset(gfx::Insets::VH(kMinimumVerticalContainerMargin, 0)); |
| } |
| |
| return available_bounds; |
| } |
| |
| void AppsContainerView::UpdateAppListConfig(const gfx::Rect& contents_bounds) { |
| // For productivity launcher, the rows for this grid layout will be ignored |
| // during creation of a new config. |
| GridLayout grid_layout = CalculateGridLayout(); |
| |
| const gfx::Rect available_bounds = |
| CalculateAvailableBoundsForAppsGrid(contents_bounds); |
| |
| std::unique_ptr<AppListConfig> new_config = |
| AppListConfigProvider::Get().CreateForFullscreenAppList( |
| display::Screen::GetScreen() |
| ->GetDisplayNearestView(GetWidget()->GetNativeView()) |
| .work_area() |
| .size(), |
| grid_layout.rows, grid_layout.columns, available_bounds.size(), |
| app_list_config_.get()); |
| |
| // `CreateForFullscreenAppList()` will create a new config only if it differs |
| // from the current `app_list_config_`. Nothing to do if the old |
| // `AppListConfig` can be used for the updated apps container bounds. |
| if (!new_config) |
| return; |
| |
| // Keep old config around until child views have been updated to use the new |
| // config. |
| auto old_config = std::move(app_list_config_); |
| app_list_config_ = std::move(new_config); |
| |
| // Invalidate the cached container margins - app list config change generally |
| // changes preferred apps grid margins, which can influence the container |
| // margins. |
| cached_container_margins_ = CachedContainerMargins(); |
| |
| if (separator_) { |
| const int separator_vertical_inset = |
| app_list_config_->type() == AppListConfigType::kRegular |
| ? kRegularSeparatorVerticalInset |
| : kDenseSeparatorVerticalInset; |
| separator_->SetProperty(views::kMarginsKey, |
| gfx::Insets::VH(separator_vertical_inset, 0)); |
| } |
| |
| apps_grid_view()->UpdateAppListConfig(app_list_config_.get()); |
| app_list_folder_view()->UpdateAppListConfig(app_list_config_.get()); |
| if (continue_container_) |
| continue_container_->UpdateAppListConfig(app_list_config_.get()); |
| } |
| |
| void AppsContainerView::OnActiveAppListModelsChanged( |
| AppListModel* model, |
| SearchModel* search_model) { |
| // Nothing to do if the apps grid views have not yet been initialized. |
| if (!app_list_config_) |
| return; |
| |
| UpdateForActiveAppListModel(); |
| } |
| |
| void AppsContainerView::ShowFolderForItemView(AppListItemView* folder_item_view, |
| bool focus_name_input, |
| base::OnceClosure hide_callback) { |
| // Prevent new animations from starting if there are currently animations |
| // pending. This fixes crbug.com/357099. |
| if (app_list_folder_view_->IsAnimationRunning()) |
| return; |
| |
| DCHECK(folder_item_view->is_folder()); |
| |
| UMA_HISTOGRAM_ENUMERATION("Apps.AppListFolderOpened", |
| kFullscreenAppListFolders, kMaxFolderOpened); |
| |
| app_list_folder_view_->ConfigureForFolderItemView(folder_item_view, |
| std::move(hide_callback)); |
| SetShowState(SHOW_ACTIVE_FOLDER, false); |
| |
| // If there is no selected view in the root grid when a folder is opened, |
| // silently focus the first item in the folder to avoid showing the selection |
| // highlight or announcing to A11y, but still ensuring the arrow keys navigate |
| // from the first item. |
| if (focus_name_input) { |
| app_list_folder_view_->FocusNameInput(); |
| } else { |
| const bool silently = !apps_grid_view()->has_selected_view(); |
| app_list_folder_view_->FocusFirstItem(silently); |
| } |
| // Disable all the items behind the folder so that they will not be reached |
| // during focus traversal. |
| DisableFocusForShowingActiveFolder(true); |
| } |
| |
| void AppsContainerView::ShowApps(AppListItemView* folder_item_view, |
| bool select_folder) { |
| DVLOG(1) << __FUNCTION__; |
| if (app_list_folder_view_->IsAnimationRunning()) |
| return; |
| |
| const bool animate = !!folder_item_view; |
| SetShowState(SHOW_APPS, animate); |
| DisableFocusForShowingActiveFolder(false); |
| if (folder_item_view) { |
| // Focus `folder_item_view` but only show the selection highlight if there |
| // was already one showing. |
| if (select_folder) |
| folder_item_view->RequestFocus(); |
| else |
| folder_item_view->SilentlyRequestFocus(); |
| } |
| } |
| |
| void AppsContainerView::ResetForShowApps() { |
| DVLOG(1) << __FUNCTION__; |
| UpdateSuggestionChips(); |
| UpdateRecentApps(/*needs_layout=*/false); |
| SetShowState(SHOW_APPS, false); |
| DisableFocusForShowingActiveFolder(false); |
| } |
| |
| void AppsContainerView::SetDragAndDropHostOfCurrentAppList( |
| ApplicationDragAndDropHost* drag_and_drop_host) { |
| apps_grid_view()->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host); |
| app_list_folder_view()->items_grid_view()->SetDragAndDropHostOfCurrentAppList( |
| drag_and_drop_host); |
| } |
| |
| void AppsContainerView::ReparentFolderItemTransit( |
| AppListFolderItem* folder_item) { |
| if (app_list_folder_view_->IsAnimationRunning()) |
| return; |
| SetShowState(SHOW_ITEM_REPARENT, false); |
| DisableFocusForShowingActiveFolder(false); |
| } |
| |
| bool AppsContainerView::IsInFolderView() const { |
| return show_state_ == SHOW_ACTIVE_FOLDER; |
| } |
| |
| void AppsContainerView::ReparentDragEnded() { |
| DVLOG(1) << __FUNCTION__; |
| // The container will be showing apps if the folder was deleted mid-drag. |
| if (show_state_ == SHOW_APPS) |
| return; |
| DCHECK_EQ(SHOW_ITEM_REPARENT, show_state_); |
| show_state_ = AppsContainerView::SHOW_APPS; |
| } |
| |
| void AppsContainerView::OnAppListVisibilityWillChange(bool visible) { |
| // Start zero state search to refresh contents of the continue section and |
| // recent apps (which are only shown for productivity launcher). |
| // NOTE: Request another layout after recent apps get updated to handle the |
| // case when recent apps get updated during app list state change animation. |
| // The apps container layout may get dropped by the app list contents view, |
| // so invalidating recent apps layout when recent apps visibiltiy changes |
| // will not work well). |
| // TODO(https://crbug.com/1306613): Remove explicit layout once the linked |
| // issue is fixed. |
| if (visible && features::IsProductivityLauncherEnabled()) { |
| contents_view_->GetAppListMainView()->view_delegate()->StartZeroStateSearch( |
| base::BindOnce(&AppsContainerView::UpdateRecentApps, |
| weak_ptr_factory_.GetWeakPtr(), |
| /*needs_layout=*/true), |
| kZeroStateSearchTimeout); |
| } |
| } |
| |
| void AppsContainerView::OnAppListVisibilityChanged(bool shown) { |
| if (toast_container_) { |
| // Updates the visibility state in toast container. |
| AppListToastContainerView::VisibilityState state = |
| shown ? (is_active_page_ |
| ? AppListToastContainerView::VisibilityState::kShown |
| : AppListToastContainerView::VisibilityState:: |
| kShownInBackground) |
| : AppListToastContainerView::VisibilityState::kHidden; |
| toast_container_->UpdateVisibilityState(state); |
| |
| // Check if the reorder nudge view needs update if the app list is showing. |
| if (shown) |
| toast_container_->MaybeUpdateReorderNudgeView(); |
| } |
| |
| // Layout requests may get ignored by apps container's view hierarchy while |
| // app list animation is in progress - relayout the container if it needs |
| // layout at this point. |
| // TODO(https://crbug.com/1306613): Remove explicit layout once the linked |
| // issue gets fixed. |
| if (shown && needs_layout()) |
| Layout(); |
| } |
| |
| // PaginationModelObserver: |
| void AppsContainerView::SelectedPageChanged(int old_selected, |
| int new_selected) { |
| // There is no |continue_container_| to translate when productivity launcher |
| // is not enabled, so return early. |
| if (!features::IsProductivityLauncherEnabled()) |
| return; |
| |
| // |continue_container_| is hidden above the grid when not on the first page. |
| gfx::Transform transform; |
| gfx::Vector2dF translate; |
| translate.set_y(-scrollable_container_->bounds().height() * new_selected); |
| transform.Translate(translate); |
| continue_container_->layer()->SetTransform(transform); |
| separator_->layer()->SetTransform(transform); |
| if (toast_container_) |
| toast_container_->layer()->SetTransform(transform); |
| } |
| |
| void AppsContainerView::TransitionChanged() { |
| // There is no |continue_container_| to translate when productivity launcher |
| // is not enabled, so return early. |
| if (!features::IsProductivityLauncherEnabled()) |
| return; |
| |
| auto* pagination_model = apps_grid_view_->pagination_model(); |
| const PaginationModel::Transition& transition = |
| pagination_model->transition(); |
| if (!pagination_model->is_valid_page(transition.target_page)) |
| return; |
| |
| // Because |continue_container_| only shows on the first page, only update its |
| // transform if its page is involved in the transition. Otherwise, there is |
| // no need to transform the |continue_container_| because it will remain |
| // hidden throughout the transition. |
| if (transition.target_page == 0 || pagination_model->selected_page() == 0) { |
| const int page_height = scrollable_container_->bounds().height(); |
| gfx::Vector2dF translate; |
| |
| if (transition.target_page == 0) { |
| // Scroll the continue section down from above. |
| translate.set_y(-page_height + page_height * transition.progress); |
| } else { |
| // Scroll the continue section upwards |
| translate.set_y(-page_height * transition.progress); |
| } |
| gfx::Transform transform; |
| transform.Translate(translate); |
| continue_container_->layer()->SetTransform(transform); |
| separator_->layer()->SetTransform(transform); |
| if (toast_container_) |
| toast_container_->layer()->SetTransform(transform); |
| } |
| } |
| |
| void AppsContainerView::TransitionStarted() { |
| MaybeCreateGradientMask(); |
| } |
| |
| void AppsContainerView::TransitionEnded() { |
| // TODO(crbug.com/1285184): Sometimes gradient mask is not removed because |
| // this function does not get called in some cases. |
| |
| // Gradient mask is no longer necessary once transition is finished. |
| MaybeRemoveGradientMask(); |
| } |
| |
| void AppsContainerView::ScrollStarted() { |
| MaybeCreateGradientMask(); |
| } |
| |
| void AppsContainerView::ScrollEnded() { |
| // Need to reset the mask because transition will not happen in some |
| // cases. (See https://crbug.com/1049275) |
| MaybeRemoveGradientMask(); |
| } |
| |
| // PagedAppsGridView::ContainerDelegate: |
| bool AppsContainerView::IsPointWithinPageFlipBuffer( |
| const gfx::Point& point_in_apps_grid) const { |
| // The page flip buffer is the work area bounds excluding shelf bounds, which |
| // is the same as AppsContainerView's bounds. |
| gfx::Point point = point_in_apps_grid; |
| ConvertPointToTarget(apps_grid_view_, this, &point); |
| return this->GetContentsBounds().Contains(point); |
| } |
| |
| bool AppsContainerView::IsPointWithinBottomDragBuffer( |
| const gfx::Point& point, |
| int page_flip_zone_size) const { |
| // The bottom drag buffer is between the bottom of apps grid and top of shelf. |
| gfx::Point point_in_parent = point; |
| ConvertPointToTarget(apps_grid_view_, this, &point_in_parent); |
| gfx::Rect parent_rect = this->GetContentsBounds(); |
| const int kBottomDragBufferMax = parent_rect.bottom(); |
| const int kBottomDragBufferMin = scrollable_container_->bounds().bottom() - |
| apps_grid_view_->GetInsets().bottom() - |
| page_flip_zone_size; |
| |
| return point_in_parent.y() > kBottomDragBufferMin && |
| point_in_parent.y() < kBottomDragBufferMax; |
| } |
| |
| void AppsContainerView::MaybeCreateGradientMask() { |
| if (!features::IsBackgroundBlurEnabled()) |
| return; |
| |
| if (scrollable_container_->layer()->gradient_mask().IsEmpty()) |
| UpdateGradientMaskBounds(); |
| } |
| |
| void AppsContainerView::MaybeRemoveGradientMask() { |
| if (!scrollable_container_->layer()->gradient_mask().IsEmpty() && |
| !keep_gradient_mask_for_cardified_state_) { |
| scrollable_container_->layer()->SetGradientMask( |
| gfx::LinearGradient::GetEmpty()); |
| } |
| } |
| |
| void AppsContainerView::OnCardifiedStateStarted() { |
| keep_gradient_mask_for_cardified_state_ = true; |
| MaybeCreateGradientMask(); |
| } |
| |
| void AppsContainerView::OnCardifiedStateEnded() { |
| keep_gradient_mask_for_cardified_state_ = false; |
| MaybeRemoveGradientMask(); |
| } |
| |
| void AppsContainerView::OnNudgeRemoved() { |
| const int continue_container_height = |
| continue_container_->GetPreferredSize().height(); |
| const int toast_container_height = |
| toast_container_ ? toast_container_->GetPreferredSize().height() : 0; |
| |
| apps_grid_view_->ConfigureFirstPagePadding( |
| continue_container_height + toast_container_height + GetSeparatorHeight(), |
| continue_container_->HasRecentApps()); |
| UpdateTopLevelGridDimensions(); |
| |
| apps_grid_view_->AnimateOnNudgeRemoved(); |
| } |
| |
| void AppsContainerView::UpdateForNewSortingOrder( |
| const absl::optional<AppListSortOrder>& new_order, |
| bool animate, |
| base::OnceClosure update_position_closure, |
| base::OnceClosure animation_done_closure) { |
| DCHECK(features::IsLauncherAppSortEnabled()); |
| DCHECK_EQ(animate, !update_position_closure.is_null()); |
| DCHECK(!animation_done_closure || animate); |
| |
| // A11y announcements must happen before animations, otherwise the undo |
| // guidance is spoken first because focus moves immediately to the undo button |
| // on the toast. |
| if (new_order) { |
| toast_container_->AnnounceSortOrder(*new_order); |
| } else if (animate) { |
| toast_container_->AnnounceUndoSort(); |
| } |
| |
| if (!animate) { |
| // Reordering is not required so update the undo toast and return early. |
| app_list_nudge_controller_->OnTemporarySortOrderChanged(new_order); |
| toast_container_->OnTemporarySortOrderChanged(new_order); |
| HandleFocusAfterSort(); |
| return; |
| } |
| |
| // If app list sort order change is animated, hide any open folders as part of |
| // animation. If the update is not animated, e.g. when committing sort order, |
| // keep the folder open to prevent folder closure when apps within the folder |
| // are reordered, or whe the folder gets renamed. |
| SetShowState(SHOW_APPS, /*show_apps_with_animation=*/false); |
| DisableFocusForShowingActiveFolder(false); |
| |
| // If `apps_grid_view_` is under page transition animation, finish the |
| // animation before starting the reorder animation. |
| ash::PaginationModel* pagination_model = apps_grid_view_->pagination_model(); |
| if (pagination_model->has_transition()) |
| pagination_model->FinishAnimation(); |
| |
| // Abort the old reorder animation if any before closure update to avoid data |
| // races on closures. |
| apps_grid_view_->MaybeAbortWholeGridAnimation(); |
| DCHECK(!update_position_closure_); |
| update_position_closure_ = std::move(update_position_closure); |
| DCHECK(!reorder_animation_done_closure_); |
| reorder_animation_done_closure_ = std::move(animation_done_closure); |
| |
| views::AnimationBuilder animation_builder = |
| apps_grid_view_->FadeOutVisibleItemsForReorder(base::BindRepeating( |
| &AppsContainerView::OnAppsGridViewFadeOutAnimationEnded, |
| weak_ptr_factory_.GetWeakPtr(), new_order)); |
| |
| // Configure the toast fade out animation if the toast is going to be hidden. |
| const bool current_toast_visible = toast_container_->IsToastVisible(); |
| const bool target_toast_visible = |
| toast_container_->GetVisibilityForSortOrder(new_order); |
| if (current_toast_visible && !target_toast_visible) { |
| animation_builder.GetCurrentSequence().SetOpacity(toast_container_->layer(), |
| 0.f); |
| } |
| } |
| |
| void AppsContainerView::UpdateContinueSectionVisibility() { |
| if (!continue_container_) |
| return; |
| |
| // Get the continue container's height before Layout(). |
| const int initial_height = continue_container_->height(); |
| |
| // Update continue container visibility and bounds. |
| continue_container_->UpdateContinueSectionVisibility(); |
| Layout(); |
| |
| // Only play animations if the tablet mode app list is visible. This function |
| // can be called in clamshell mode when the tablet app list is cached. |
| if (!contents_view_->app_list_view()->is_tablet_mode()) |
| return; |
| |
| // The change in continue container height is the amount by which the apps |
| // grid view will be offset. |
| const int vertical_offset = initial_height - continue_container_->height(); |
| |
| AppListViewDelegate* view_delegate = |
| contents_view_->GetAppListMainView()->view_delegate(); |
| if (view_delegate->ShouldHideContinueSection()) { |
| // Continue section is being hidden. Slide each row of app icons up with a |
| // different offset per row. |
| apps_grid_view_->SlideVisibleItemsForHideContinueSection(vertical_offset); |
| |
| // Don't try to fade out the views on hide because they are already |
| // invisible. |
| return; |
| } |
| |
| // Continue section is being shown. Transform the apps grid view up to its |
| // original pre-Layout() position. |
| gfx::Transform transform; |
| transform.Translate(0, vertical_offset); |
| apps_grid_view_->SetTransform(transform); |
| |
| // Animate to the identity transform to slide the apps grid view down to its |
| // final position. |
| views::AnimationBuilder() |
| .SetPreemptionStrategy(ui::LayerAnimator::PreemptionStrategy:: |
| IMMEDIATELY_ANIMATE_TO_NEW_TARGET) |
| .Once() |
| .SetTransform(apps_grid_view_, gfx::Transform(), |
| gfx::Tween::ACCEL_LIN_DECEL_100_3) |
| .SetDuration(base::Milliseconds(300)); |
| |
| // Fade in the continue tasks and recent apps views. |
| continue_container_->FadeInViews(); |
| } |
| |
| ContinueSectionView* AppsContainerView::GetContinueSectionView() { |
| if (!continue_container_) |
| return nullptr; |
| return continue_container_->continue_section(); |
| } |
| |
| RecentAppsView* AppsContainerView::GetRecentAppsView() { |
| if (!continue_container_) |
| return nullptr; |
| return continue_container_->recent_apps(); |
| } |
| |
| AppsGridView* AppsContainerView::GetAppsGridView() { |
| return apps_grid_view_; |
| } |
| |
| AppListToastContainerView* AppsContainerView::GetToastContainerView() { |
| return toast_container_; |
| } |
| |
| void AppsContainerView::UpdateControlVisibility(AppListViewState app_list_state, |
| bool is_in_drag) { |
| if (app_list_state == AppListViewState::kClosed) |
| return; |
| |
| SetCanProcessEventsWithinSubtree( |
| app_list_state == AppListViewState::kFullscreenAllApps || |
| app_list_state == AppListViewState::kPeeking); |
| |
| apps_grid_view_->UpdateControlVisibility(app_list_state, is_in_drag); |
| page_switcher_->SetVisible( |
| is_in_drag || app_list_state == AppListViewState::kFullscreenAllApps || |
| app_list_state == AppListViewState::kFullscreenSearch); |
| |
| // Ignore button press during dragging to avoid app list item views' opacity |
| // being set to wrong value. |
| page_switcher_->set_ignore_button_press(is_in_drag); |
| |
| if (suggestion_chip_container_view_) { |
| suggestion_chip_container_view_->SetVisible( |
| app_list_state == AppListViewState::kFullscreenAllApps || |
| app_list_state == AppListViewState::kPeeking || is_in_drag); |
| } |
| } |
| |
| void AppsContainerView::AnimateOpacity(AppListViewState current_view_state, |
| AppListViewState target_view_state, |
| const OpacityAnimator& animator) { |
| if (suggestion_chip_container_view_) { |
| const bool target_suggestion_chip_visibility = |
| target_view_state == AppListViewState::kFullscreenAllApps || |
| target_view_state == AppListViewState::kPeeking; |
| animator.Run(suggestion_chip_container_view_, |
| target_suggestion_chip_visibility); |
| } |
| |
| if (!apps_grid_view_->layer()->GetAnimator()->IsAnimatingProperty( |
| ui::LayerAnimationElement::OPACITY)) { |
| apps_grid_view_->UpdateOpacity(true /*restore_opacity*/, |
| kAppsOpacityChangeStart, |
| kAppsOpacityChangeEnd); |
| apps_grid_view_->layer()->SetOpacity( |
| current_view_state != AppListViewState::kClosed ? 1.0f : 0.0f); |
| } |
| |
| const bool target_grid_visibility = |
| target_view_state == AppListViewState::kFullscreenAllApps || |
| target_view_state == AppListViewState::kFullscreenSearch; |
| animator.Run(apps_grid_view_, target_grid_visibility); |
| animator.Run(page_switcher_, target_grid_visibility); |
| } |
| |
| void AppsContainerView::AnimateYPosition(AppListViewState target_view_state, |
| const TransformAnimator& animator, |
| float default_offset) { |
| const int target_suggestion_chip_y = |
| GetExpectedSuggestionChipY(target_view_state); |
| |
| if (suggestion_chip_container_view_) { |
| suggestion_chip_container_view_->SetY(target_suggestion_chip_y); |
| animator.Run(default_offset, suggestion_chip_container_view_->layer()); |
| } |
| |
| scrollable_container_->SetY(target_suggestion_chip_y + chip_grid_y_distance_); |
| animator.Run(default_offset, scrollable_container_->layer()); |
| page_switcher_->SetY(target_suggestion_chip_y + chip_grid_y_distance_); |
| animator.Run(default_offset, page_switcher_->layer()); |
| } |
| |
| void AppsContainerView::OnTabletModeChanged(bool started) { |
| if (suggestion_chip_container_view_) |
| suggestion_chip_container_view_->OnTabletModeChanged(started); |
| apps_grid_view_->OnTabletModeChanged(started); |
| app_list_folder_view_->OnTabletModeChanged(started); |
| page_switcher_->set_is_tablet_mode(started); |
| } |
| |
| void AppsContainerView::Layout() { |
| gfx::Rect rect(GetContentsBounds()); |
| if (rect.IsEmpty()) |
| return; |
| |
| // Layout suggestion chips. |
| gfx::Rect chip_container_rect = rect; |
| chip_container_rect.set_y(GetExpectedSuggestionChipY( |
| contents_view_->app_list_view()->app_list_state())); |
| |
| if (suggestion_chip_container_view_) { |
| chip_container_rect.set_height(kSuggestionChipContainerHeight); |
| chip_container_rect.Inset(gfx::Insets::VH(0, GetIdealHorizontalMargin())); |
| suggestion_chip_container_view_->SetBoundsRect(chip_container_rect); |
| } else { |
| chip_container_rect.set_height(0); |
| } |
| |
| // Set bounding box for the folder view - the folder may overlap with |
| // suggestion chips, but not the search box. |
| gfx::Rect folder_bounding_box = rect; |
| int top_folder_inset = chip_container_rect.y(); |
| int bottom_folder_inset = kFolderMargin; |
| |
| if (features::IsProductivityLauncherEnabled()) |
| top_folder_inset += kFolderMargin; |
| |
| // Account for the hotseat which overlaps with contents bounds in tablet mode. |
| if (contents_view_->app_list_view()->is_tablet_mode()) |
| bottom_folder_inset += ShelfConfig::Get()->hotseat_bottom_padding(); |
| |
| folder_bounding_box.Inset(gfx::Insets::TLBR( |
| top_folder_inset, kFolderMargin, bottom_folder_inset, kFolderMargin)); |
| app_list_folder_view_->SetBoundingBox(folder_bounding_box); |
| |
| // Leave the same available bounds for the apps grid view in both |
| // fullscreen and peeking state to avoid resizing the view during |
| // animation and dragging, which is an expensive operation. |
| rect.set_y(chip_container_rect.bottom()); |
| rect.set_height( |
| rect.height() - |
| GetExpectedSuggestionChipY(AppListViewState::kFullscreenAllApps) - |
| chip_container_rect.height()); |
| |
| // Layout apps grid. |
| const gfx::Insets grid_insets = apps_grid_view_->GetInsets(); |
| const gfx::Insets margins = CalculateMarginsForAvailableBounds( |
| GetContentsBounds(), |
| contents_view_->GetSearchBoxSize(AppListState::kStateApps)); |
| gfx::Rect grid_rect = rect; |
| grid_rect.Inset(gfx::Insets::TLBR(kAppGridTopMargin, margins.left(), |
| margins.bottom(), margins.right())); |
| // The grid rect insets are added to calculated margins. Given that the |
| // grid bounds rect should include insets, they have to be removed from |
| // added margins. |
| grid_rect.Inset(-grid_insets); |
| |
| gfx::Rect scrollable_bounds = grid_rect; |
| // With productivity launcher enabled, add space to the top of the |
| // `scrollable_container_` bounds to make room for the gradient mask to be |
| // placed above the continue section. |
| if (features::IsProductivityLauncherEnabled()) |
| scrollable_bounds.Inset( |
| gfx::Insets::TLBR(-kDefaultFadeoutMaskHeight, 0, 0, 0)); |
| scrollable_container_->SetBoundsRect(scrollable_bounds); |
| |
| if (!scrollable_container_->layer()->gradient_mask().IsEmpty()) |
| UpdateGradientMaskBounds(); |
| |
| bool separator_need_centering = false; |
| bool first_page_config_changed = false; |
| if (features::IsProductivityLauncherEnabled()) { |
| const int continue_container_height = |
| continue_container_->GetPreferredSize().height(); |
| continue_container_->SetBoundsRect(gfx::Rect(0, kDefaultFadeoutMaskHeight, |
| grid_rect.width(), |
| continue_container_height)); |
| const int toast_container_height = |
| toast_container_ ? toast_container_->GetPreferredSize().height() : 0; |
| if (toast_container_) { |
| toast_container_->SetBoundsRect(gfx::Rect( |
| 0, continue_container_->bounds().bottom() + GetSeparatorHeight(), |
| grid_rect.width(), toast_container_height)); |
| } |
| |
| // When no views are shown between the recent apps and the apps grid, |
| // vertically center the separator between them. |
| if (toast_container_height == 0 && continue_container_->HasRecentApps()) |
| separator_need_centering = true; |
| |
| // Setting this offset prevents the app items in the grid from overlapping |
| // with the continue section. |
| first_page_config_changed = apps_grid_view_->ConfigureFirstPagePadding( |
| continue_container_height + toast_container_height + |
| GetSeparatorHeight(), |
| continue_container_->HasRecentApps()); |
| } |
| |
| // Make sure that UpdateTopLevelGridDimensions() happens after setting the |
| // apps grid's first page offset, because it can change the number of rows |
| // shown in the grid. |
| UpdateTopLevelGridDimensions(); |
| |
| gfx::Rect apps_grid_bounds(grid_rect.size()); |
| // Set the apps grid bounds y to make room for the top gradient mask. |
| if (features::IsProductivityLauncherEnabled()) |
| apps_grid_bounds.set_y(kDefaultFadeoutMaskHeight); |
| |
| if (apps_grid_view_->bounds() != apps_grid_bounds) { |
| apps_grid_view_->SetBoundsRect(apps_grid_bounds); |
| } else if (first_page_config_changed) { |
| // Apps grid layout depends on the continue container bounds, so explicitly |
| // call layout to ensure apps grid view gets laid out even if its bounds do |
| // not change. |
| apps_grid_view_->Layout(); |
| } |
| |
| if (separator_) { |
| if (separator_need_centering) { |
| // Center the separator between the recent apps and the first row of the |
| // apps grid. This is done after the apps grid layout so the correct |
| // tile padding is used. |
| const int centering_offset = |
| continue_container_->bounds().bottom() + |
| apps_grid_view_->GetUnscaledFirstPageTilePadding() + |
| GetSeparatorHeight() / 2; |
| separator_->SetBoundsRect( |
| gfx::Rect(gfx::Point((grid_rect.width() - kSeparatorWidth) / 2, |
| centering_offset), |
| gfx::Size(kSeparatorWidth, 1))); |
| } else { |
| separator_->SetBoundsRect(gfx::Rect( |
| (grid_rect.width() - kSeparatorWidth) / 2, |
| continue_container_->bounds().bottom() + |
| separator_->GetProperty(views::kMarginsKey)->height() / 2, |
| kSeparatorWidth, 1)); |
| } |
| } |
| |
| // Record the distance of y position between suggestion chip container |
| // and apps grid view to avoid duplicate calculation of apps grid view's |
| // y position during dragging. |
| chip_grid_y_distance_ = scrollable_container_->y() - chip_container_rect.y(); |
| |
| // Layout page switcher. |
| const int page_switcher_width = page_switcher_->GetPreferredSize().width(); |
| const gfx::Rect page_switcher_bounds( |
| grid_rect.right() + kGridToPageSwitcherMargin, grid_rect.y(), |
| page_switcher_width, grid_rect.height()); |
| page_switcher_->SetBoundsRect(page_switcher_bounds); |
| |
| switch (show_state_) { |
| case SHOW_APPS: |
| break; |
| case SHOW_ACTIVE_FOLDER: { |
| app_list_folder_view_->UpdatePreferredBounds(); |
| folder_background_view_->SetBoundsRect(rect); |
| app_list_folder_view_->SetBoundsRect( |
| app_list_folder_view_->preferred_bounds()); |
| break; |
| } |
| case SHOW_ITEM_REPARENT: |
| case SHOW_NONE: |
| break; |
| } |
| } |
| |
| bool AppsContainerView::OnKeyPressed(const ui::KeyEvent& event) { |
| if (show_state_ == SHOW_APPS) |
| return apps_grid_view_->OnKeyPressed(event); |
| else |
| return app_list_folder_view_->OnKeyPressed(event); |
| } |
| |
| const char* AppsContainerView::GetClassName() const { |
| return "AppsContainerView"; |
| } |
| |
| void AppsContainerView::OnBoundsChanged(const gfx::Rect& old_bounds) { |
| const bool creating_initial_config = !app_list_config_; |
| |
| // The size and layout of apps grid items depend on the dimensions of the |
| // display on which the apps container is shown. Given that the apps container |
| // is shown in fullscreen app list view (and covers complete app list view |
| // bounds), changes in the `AppsContainerView` bounds can be used as a proxy |
| // to detect display size changes. |
| UpdateAppListConfig(GetContentsBounds()); |
| DCHECK(app_list_config_); |
| UpdateTopLevelGridDimensions(); |
| |
| // Finish initialization of views that require app list config. |
| if (creating_initial_config) |
| UpdateForActiveAppListModel(); |
| } |
| |
| void AppsContainerView::AddedToWidget() { |
| GetFocusManager()->AddFocusChangeListener(this); |
| } |
| |
| void AppsContainerView::RemovedFromWidget() { |
| GetFocusManager()->RemoveFocusChangeListener(this); |
| } |
| |
| void AppsContainerView::OnDidChangeFocus(View* focused_before, |
| View* focused_now) { |
| // Ensure that `continue_container_` is visible (the first page is active) |
| // after moving focus down from the last row on 2nd+ page to the search box |
| // and then to `continue_container_`. |
| if (!is_active_page_) |
| return; |
| if (!continue_container_ || !continue_container_->Contains(focused_now)) |
| return; |
| if (apps_grid_view_->pagination_model()->selected_page() != 0) |
| apps_grid_view_->pagination_model()->SelectPage(0, /*animate=*/false); |
| } |
| |
| void AppsContainerView::OnGestureEvent(ui::GestureEvent* event) { |
| // Ignore tap/long-press, allow those to pass to the ancestor view. |
| if (event->type() == ui::ET_GESTURE_TAP || |
| event->type() == ui::ET_GESTURE_LONG_PRESS) { |
| return; |
| } |
| |
| // Will forward events to |apps_grid_view_| if they occur in the same y-region |
| if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN && |
| event->location().y() <= apps_grid_view_->bounds().y()) { |
| return; |
| } |
| |
| // If a folder is currently opening or closing, we should ignore the event. |
| // This is here until the animation for pagination while closing folders is |
| // fixed: https://crbug.com/875133 |
| if (app_list_folder_view_->IsAnimationRunning()) { |
| event->SetHandled(); |
| return; |
| } |
| |
| // Temporary event for use by |apps_grid_view_| |
| ui::GestureEvent grid_event(*event); |
| ConvertEventToTarget(apps_grid_view_, &grid_event); |
| apps_grid_view_->OnGestureEvent(&grid_event); |
| |
| // If the temporary event was handled, we don't want to handle it again. |
| if (grid_event.handled()) |
| event->SetHandled(); |
| } |
| |
| void AppsContainerView::OnShown() { |
| DVLOG(1) << __FUNCTION__; |
| // Explicitly hide the virtual keyboard before showing the apps container |
| // view. This prevents the virtual keyboard's "transient blur" feature from |
| // kicking in - if a text input loses focus, and a text input gains it within |
| // seconds, the virtual keyboard gets reshown. This is undesirable behavior |
| // for the app list (where search box gets focus by default). |
| if (keyboard::KeyboardUIController::HasInstance()) |
| keyboard::KeyboardUIController::Get()->HideKeyboardExplicitlyBySystem(); |
| |
| GetViewAccessibility().OverrideIsLeaf(false); |
| is_active_page_ = true; |
| |
| // Update the continue section. |
| if (continue_container_) |
| continue_container_->continue_section()->SetShownInBackground(false); |
| |
| // Updates the visibility state in toast container. |
| if (toast_container_) { |
| toast_container_->UpdateVisibilityState( |
| AppListToastContainerView::VisibilityState::kShown); |
| } |
| if (dialog_controller_) |
| dialog_controller_->Reset(/*enabled=*/true); |
| } |
| |
| void AppsContainerView::OnWillBeHidden() { |
| DVLOG(1) << __FUNCTION__; |
| if (show_state_ == SHOW_ACTIVE_FOLDER) |
| app_list_folder_view_->CloseFolderPage(); |
| else |
| apps_grid_view_->CancelDragWithNoDropAnimation(); |
| } |
| |
| void AppsContainerView::OnHidden() { |
| // Apps container view is shown faded behind the search results UI - hide its |
| // contents from the screen reader as the apps grid is not normally |
| // actionable in this state. |
| GetViewAccessibility().OverrideIsLeaf(true); |
| |
| is_active_page_ = false; |
| |
| // Update the continue section. |
| if (continue_container_) |
| continue_container_->continue_section()->SetShownInBackground(true); |
| |
| // Updates the visibility state in toast container. |
| if (toast_container_) { |
| toast_container_->UpdateVisibilityState( |
| AppListToastContainerView::VisibilityState::kShownInBackground); |
| } |
| if (dialog_controller_) |
| dialog_controller_->Reset(/*enabled=*/false); |
| } |
| |
| void AppsContainerView::OnAnimationStarted(AppListState from_state, |
| AppListState to_state) { |
| gfx::Rect contents_bounds = GetDefaultContentsBounds(); |
| |
| const gfx::Rect from_rect = |
| GetPageBoundsForState(from_state, contents_bounds, gfx::Rect()); |
| const gfx::Rect to_rect = |
| GetPageBoundsForState(to_state, contents_bounds, gfx::Rect()); |
| if (from_rect != to_rect) { |
| DCHECK_EQ(from_rect.size(), to_rect.size()); |
| DCHECK_EQ(from_rect.x(), to_rect.x()); |
| |
| SetBoundsRect(to_rect); |
| |
| gfx::Transform initial_transform; |
| initial_transform.Translate(0, from_rect.y() - to_rect.y()); |
| layer()->SetTransform(initial_transform); |
| |
| auto settings = contents_view_->CreateTransitionAnimationSettings(layer()); |
| layer()->SetTransform(gfx::Transform()); |
| } |
| |
| // Set the page opacity. |
| auto settings = contents_view_->CreateTransitionAnimationSettings(layer()); |
| UpdateContainerOpacityForState(to_state); |
| } |
| |
| void AppsContainerView::UpdatePageOpacityForState(AppListState state, |
| float search_box_opacity, |
| bool restore_opacity) { |
| UpdateContainerOpacityForState(state); |
| |
| UpdateContentsOpacity(restore_opacity); |
| } |
| |
| void AppsContainerView::UpdatePageBoundsForState( |
| AppListState state, |
| const gfx::Rect& contents_bounds, |
| const gfx::Rect& search_box_bounds) { |
| AppListPage::UpdatePageBoundsForState(state, contents_bounds, |
| search_box_bounds); |
| |
| UpdateContentsYPosition(contents_view_->app_list_view()->app_list_state()); |
| } |
| |
| gfx::Rect AppsContainerView::GetPageBoundsForState( |
| AppListState state, |
| const gfx::Rect& contents_bounds, |
| const gfx::Rect& search_box_bounds) const { |
| if (state == AppListState::kStateApps) |
| return contents_bounds; |
| |
| gfx::Rect bounds = contents_bounds; |
| bounds.Offset(0, kNonAppsStateVerticalOffset); |
| return bounds; |
| } |
| |
| int AppsContainerView::GetMinHorizontalMarginForAppsGrid() const { |
| return kPageSwitcherEndMargin + kGridToPageSwitcherMargin + |
| page_switcher_->GetPreferredSize().width(); |
| } |
| |
| int AppsContainerView::GetMinTopMarginForAppsGrid( |
| const gfx::Size& search_box_size) const { |
| const int suggestion_chip_container_size = |
| features::IsProductivityLauncherEnabled() |
| ? 0 |
| : kSuggestionChipContainerHeight + kSuggestionChipContainerTopMargin; |
| |
| return search_box_size.height() + kAppGridTopMargin + |
| suggestion_chip_container_size; |
| } |
| |
| int AppsContainerView::GetIdealHorizontalMargin() const { |
| if (features::IsProductivityLauncherEnabled()) |
| return 24; |
| const int available_width = GetContentsBounds().width(); |
| if (available_width >= |
| kAppsGridMarginRatio * GetMinHorizontalMarginForAppsGrid()) { |
| return available_width / kAppsGridMarginRatio; |
| } |
| return available_width / kAppsGridMarginRatioForSmallWidth; |
| } |
| |
| int AppsContainerView::GetIdealVerticalMargin() const { |
| if (!features::IsProductivityLauncherEnabled()) |
| return GetContentsBounds().height() / kAppsGridMarginRatio; |
| |
| const int screen_height = |
| display::Screen::GetScreen() |
| ->GetDisplayNearestView(GetWidget()->GetNativeView()) |
| .bounds() |
| .height(); |
| const float margin_ratio = (screen_height <= 800) |
| ? kAppsGridMarginRatioForSmallHeight |
| : kAppsGridMarginRatio; |
| |
| return std::max(kMinimumVerticalContainerMargin, |
| static_cast<int>(screen_height / margin_ratio)); |
| } |
| |
| const gfx::Insets& AppsContainerView::CalculateMarginsForAvailableBounds( |
| const gfx::Rect& available_bounds, |
| const gfx::Size& search_box_size) { |
| if (cached_container_margins_.bounds_size == available_bounds.size() && |
| cached_container_margins_.search_box_size == search_box_size) { |
| return cached_container_margins_.margins; |
| } |
| |
| // For productivity launcher, the `grid_layout`'s rows will be ignored because |
| // the vertical margin will be constant. |
| const GridLayout grid_layout = CalculateGridLayout(); |
| const gfx::Size min_grid_size = apps_grid_view()->GetMinimumTileGridSize( |
| grid_layout.columns, grid_layout.rows); |
| const gfx::Size max_grid_size = apps_grid_view()->GetMaximumTileGridSize( |
| grid_layout.columns, grid_layout.rows); |
| |
| int available_height = available_bounds.height(); |
| // Add search box, and suggestion chips container height (with its margins to |
| // search box and apps grid) to non apps grid size. |
| // NOTE: Not removing bottom apps grid inset because they are included into |
| // the total margin values. |
| available_height -= GetMinTopMarginForAppsGrid(search_box_size); |
| |
| // Calculates margin value to ensure the apps grid size is within required |
| // bounds. |
| // |ideal_margin|: The value the margin would have with no restrictions on |
| // grid size. |
| // |available_size|: The available size for apps grid in the dimension where |
| // margin is applied. |
| // |min_size|: The min allowed size for apps grid in the dimension where |
| // margin is applied. |
| // |max_size|: The max allowed size for apps grid in the dimension where |
| // margin is applied. |
| const auto calculate_margin = [](int ideal_margin, int available_size, |
| int min_size, int max_size) -> int { |
| const int ideal_size = available_size - 2 * ideal_margin; |
| if (ideal_size < min_size) |
| return ideal_margin - (min_size - ideal_size + 1) / 2; |
| if (ideal_size > max_size) |
| return ideal_margin + (ideal_size - max_size) / 2; |
| return ideal_margin; |
| }; |
| |
| int vertical_margin = 0; |
| if (features::IsProductivityLauncherEnabled()) { |
| // Productivity launcher does not have a preset number of rows per page. |
| // Instead of adjusting the margins to fit a set number of rows, the grid |
| // will change the number of rows to fit within the provided space. |
| vertical_margin = GetIdealVerticalMargin(); |
| } else { |
| vertical_margin = |
| calculate_margin(GetIdealVerticalMargin(), available_height, |
| min_grid_size.height(), max_grid_size.height()); |
| } |
| |
| const int horizontal_margin = |
| calculate_margin(GetIdealHorizontalMargin(), available_bounds.width(), |
| min_grid_size.width(), max_grid_size.width()); |
| |
| const int min_horizontal_margin = GetMinHorizontalMarginForAppsGrid(); |
| |
| cached_container_margins_.margins = gfx::Insets::TLBR( |
| std::max(vertical_margin, kMinimumVerticalContainerMargin), |
| std::max(horizontal_margin, min_horizontal_margin), |
| std::max(vertical_margin, kMinimumVerticalContainerMargin), |
| std::max(horizontal_margin, min_horizontal_margin)); |
| cached_container_margins_.bounds_size = available_bounds.size(); |
| cached_container_margins_.search_box_size = search_box_size; |
| |
| return cached_container_margins_.margins; |
| } |
| |
| void AppsContainerView::UpdateRecentApps(bool needs_layout) { |
| RecentAppsView* recent_apps = GetRecentAppsView(); |
| if (!recent_apps || !app_list_config_) |
| return; |
| |
| AppListModelProvider* const model_provider = AppListModelProvider::Get(); |
| recent_apps->SetModels(model_provider->search_model(), |
| model_provider->model()); |
| if (needs_layout) |
| Layout(); |
| } |
| |
| void AppsContainerView::UpdateSuggestionChips() { |
| if (!suggestion_chip_container_view_) |
| return; |
| |
| suggestion_chip_container_view_->SetResults( |
| AppListModelProvider::Get()->search_model()->results()); |
| } |
| |
| base::ScopedClosureRunner AppsContainerView::DisableSuggestionChipsBlur() { |
| if (!suggestion_chip_container_view_) |
| return base::ScopedClosureRunner(base::DoNothing()); |
| |
| ++suggestion_chips_blur_disabler_count_; |
| |
| if (suggestion_chips_blur_disabler_count_ == 1) |
| suggestion_chip_container_view_->SetBlurDisabled(true); |
| |
| return base::ScopedClosureRunner( |
| base::BindOnce(&AppsContainerView::OnSuggestionChipsBlurDisablerReleased, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void AppsContainerView::SetShowState(ShowState show_state, |
| bool show_apps_with_animation) { |
| if (show_state_ == show_state) |
| return; |
| |
| show_state_ = show_state; |
| |
| // Layout before showing animation because the animation's target bounds are |
| // calculated based on the layout. |
| Layout(); |
| |
| switch (show_state_) { |
| case SHOW_APPS: |
| page_switcher_->SetCanProcessEventsWithinSubtree(true); |
| folder_background_view_->SetVisible(false); |
| apps_grid_view_->ResetForShowApps(); |
| app_list_folder_view_->ResetItemsGridForClose(); |
| if (show_apps_with_animation) { |
| app_list_folder_view_->ScheduleShowHideAnimation(false, false); |
| } else { |
| app_list_folder_view_->HideViewImmediately(); |
| } |
| break; |
| case SHOW_ACTIVE_FOLDER: |
| page_switcher_->SetCanProcessEventsWithinSubtree(false); |
| folder_background_view_->SetVisible(true); |
| app_list_folder_view_->ScheduleShowHideAnimation(true, false); |
| break; |
| case SHOW_ITEM_REPARENT: |
| page_switcher_->SetCanProcessEventsWithinSubtree(true); |
| folder_background_view_->SetVisible(false); |
| app_list_folder_view_->ScheduleShowHideAnimation(false, true); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void AppsContainerView::UpdateContainerOpacityForState(AppListState state) { |
| const float target_opacity = |
| state == AppListState::kStateApps ? 1.0f : kNonAppsStateOpacity; |
| if (layer()->GetTargetOpacity() != target_opacity) |
| layer()->SetOpacity(target_opacity); |
| } |
| |
| void AppsContainerView::UpdateContentsOpacity(bool restore_opacity) { |
| apps_grid_view_->UpdateOpacity(restore_opacity, kAppsOpacityChangeStart, |
| kAppsOpacityChangeEnd); |
| |
| // Updates the opacity of page switcher buttons. The same rule as all apps in |
| // AppsGridView. |
| AppListView* app_list_view = contents_view_->app_list_view(); |
| int screen_bottom = app_list_view->GetScreenBottom(); |
| gfx::Rect switcher_bounds = page_switcher_->GetBoundsInScreen(); |
| float centerline_above_work_area = |
| std::max<float>(screen_bottom - switcher_bounds.CenterPoint().y(), 0.f); |
| float opacity = |
| std::min(std::max((centerline_above_work_area - kAppsOpacityChangeStart) / |
| (kAppsOpacityChangeEnd - kAppsOpacityChangeStart), |
| 0.f), |
| 1.0f); |
| page_switcher_->layer()->SetOpacity(restore_opacity ? 1.0f : opacity); |
| |
| if (suggestion_chip_container_view_) { |
| // Changes the opacity of suggestion chips between 0 and 1 when app list |
| // transition progress changes. |
| float chips_opacity = contents_view_->app_list_view()->app_list_state() != |
| AppListViewState::kClosed |
| ? 1.0f |
| : 0.0f; |
| suggestion_chip_container_view_->layer()->SetOpacity( |
| restore_opacity ? 1.0 : chips_opacity); |
| } |
| } |
| |
| void AppsContainerView::UpdateContentsYPosition(AppListViewState state) { |
| const int current_suggestion_chip_y = GetExpectedSuggestionChipY(state); |
| if (suggestion_chip_container_view_) |
| suggestion_chip_container_view_->SetY(current_suggestion_chip_y); |
| scrollable_container_->SetY(current_suggestion_chip_y + |
| chip_grid_y_distance_); |
| page_switcher_->SetY(current_suggestion_chip_y + chip_grid_y_distance_); |
| } |
| |
| void AppsContainerView::DisableFocusForShowingActiveFolder(bool disabled) { |
| if (suggestion_chip_container_view_) { |
| suggestion_chip_container_view_->DisableFocusForShowingActiveFolder( |
| disabled); |
| } |
| if (auto* recent_apps = GetRecentAppsView(); recent_apps) { |
| recent_apps->DisableFocusForShowingActiveFolder(disabled); |
| } |
| if (auto* continue_section = GetContinueSectionView(); continue_section) { |
| continue_section->DisableFocusForShowingActiveFolder(disabled); |
| } |
| if (toast_container_) { |
| toast_container_->DisableFocusForShowingActiveFolder(disabled); |
| } |
| apps_grid_view_->DisableFocusForShowingActiveFolder(disabled); |
| |
| // Ignore the page switcher in accessibility tree so that buttons inside it |
| // will not be accessed by ChromeVox. |
| SetViewIgnoredForAccessibility(page_switcher_, disabled); |
| } |
| |
| int AppsContainerView::GetExpectedSuggestionChipY(AppListViewState state) { |
| const gfx::Rect search_box_bounds = |
| contents_view_->GetSearchBoxBoundsForViewState(AppListState::kStateApps, |
| state); |
| |
| if (!suggestion_chip_container_view_) |
| return search_box_bounds.bottom(); |
| |
| return search_box_bounds.bottom() + kSuggestionChipContainerTopMargin; |
| } |
| |
| AppsContainerView::GridLayout AppsContainerView::CalculateGridLayout() const { |
| DCHECK(GetWidget()); |
| |
| // Adapt columns and rows based on the display/root window size. |
| const gfx::Size size = |
| display::Screen::GetScreen() |
| ->GetDisplayNearestView(GetWidget()->GetNativeView()) |
| .work_area() |
| .size(); |
| const bool is_portrait_mode = size.height() > size.width(); |
| const int available_height = |
| CalculateAvailableBoundsForAppsGrid(GetContentsBounds()).height(); |
| |
| int preferred_columns = 0; |
| int preferred_rows = 0; |
| int preferred_rows_first_page = 0; |
| |
| if (is_portrait_mode) { |
| preferred_rows = features::IsProductivityLauncherEnabled() |
| ? kPreferredGridRowsInPortraitProductivityLauncher |
| : kPreferredGridColumns; |
| preferred_rows_first_page = preferred_rows; |
| preferred_columns = |
| features::IsProductivityLauncherEnabled() |
| ? kPreferredGridColumnsInPortraitProductivityLauncher |
| : kPreferredGridRows; |
| } else { |
| preferred_rows = kPreferredGridRows; |
| preferred_rows_first_page = preferred_rows; |
| |
| // In landscape mode, the first page should show the preferred number of |
| // rows as well as an additional row for recent apps when possible. |
| if (continue_container_ && continue_container_->HasRecentApps()) |
| preferred_rows_first_page++; |
| |
| preferred_columns = kPreferredGridColumns; |
| } |
| |
| GridLayout result; |
| result.columns = preferred_columns; |
| result.rows = |
| apps_grid_view_->CalculateMaxRows(available_height, preferred_rows); |
| result.first_page_rows = apps_grid_view_->CalculateFirstPageMaxRows( |
| available_height, preferred_rows_first_page); |
| return result; |
| } |
| |
| void AppsContainerView::UpdateForActiveAppListModel() { |
| AppListModel* const model = AppListModelProvider::Get()->model(); |
| apps_grid_view_->SetModel(model); |
| apps_grid_view_->SetItemList(model->top_level_item_list()); |
| UpdateRecentApps(/*needs_layout=*/false); |
| UpdateSuggestionChips(); |
| |
| // If model changes, close the folder view if it's open, as the associated |
| // item list is about to go away. |
| SetShowState(SHOW_APPS, false); |
| } |
| |
| void AppsContainerView::OnSuggestionChipsBlurDisablerReleased() { |
| DCHECK_GT(suggestion_chips_blur_disabler_count_, 0u); |
| --suggestion_chips_blur_disabler_count_; |
| |
| if (suggestion_chips_blur_disabler_count_ == 0) |
| suggestion_chip_container_view_->SetBlurDisabled(false); |
| } |
| |
| void AppsContainerView::UpdateGradientMaskBounds() { |
| if (scrollable_container_->bounds().IsEmpty()) |
| return; |
| |
| // Vertical linear gradient from top to bottom. |
| gfx::LinearGradient gradient_mask(/*angle=*/-90); |
| float fade_in_out_fraction = static_cast<float>(kDefaultFadeoutMaskHeight) / |
| scrollable_container_->bounds().height(); |
| // Fade in section. |
| gradient_mask.AddStep(/*fraction=*/0, /*alpha=*/0); |
| gradient_mask.AddStep(fade_in_out_fraction, 255); |
| // Fade out section |
| gradient_mask.AddStep((1 - fade_in_out_fraction), 255); |
| gradient_mask.AddStep(1, 0); |
| |
| if (gradient_mask != scrollable_container_->layer()->gradient_mask()) |
| scrollable_container_->layer()->SetGradientMask(gradient_mask); |
| } |
| |
| void AppsContainerView::OnAppsGridViewFadeOutAnimationEnded( |
| const absl::optional<AppListSortOrder>& new_order, |
| bool abort) { |
| // Update item positions after the fade out animation but before the fade in |
| // animation. NOTE: `update_position_closure_` can be empty in some edge |
| // cases. For example, the app list is set with a new order denoted by Order |
| // A. Then before the fade out animation is completed, the app list order is |
| // reset with the old value. In this case, `update_position_closure_` for |
| // Order A is never called. As a result, the closure for resetting the order |
| // is empty. |
| // Also update item positions only when the fade out animation ends normally. |
| // Because a fade out animation is aborted when: |
| // (1) Another reorder animation starts, or |
| // (2) The apps grid's view model updates due to the reasons such as app |
| // installation or model reset. |
| // It is meaningless to update item positions in either case. |
| if (update_position_closure_ && !abort) |
| std::move(update_position_closure_).Run(); |
| |
| // Record the undo toast's visibility before update. |
| const bool old_toast_visible = toast_container_->IsToastVisible(); |
| |
| toast_container_->OnTemporarySortOrderChanged(new_order); |
| HandleFocusAfterSort(); |
| |
| // Skip the fade in animation if the fade out animation is aborted. |
| if (abort) { |
| OnReorderAnimationEnded(); |
| return; |
| } |
| |
| const bool target_toast_visible = toast_container_->IsToastVisible(); |
| const bool toast_visibility_change = |
| (old_toast_visible != target_toast_visible); |
| |
| // When the undo toast's visibility changes, the apps grid's bounds should |
| // change. Meanwhile, the fade in animation relies on the apps grid's bounds |
| // (because of calculating the visible items). Therefore trigger layout before |
| // starting the fade in animation. |
| if (toast_visibility_change) |
| Layout(); |
| |
| ash::PaginationModel* pagination_model = apps_grid_view_->pagination_model(); |
| bool page_change = (pagination_model->selected_page() != 0); |
| if (page_change) { |
| // Ensure that the undo toast is within the view port after reorder. |
| pagination_model->SelectPage(0, /*animate=*/false); |
| } |
| |
| views::AnimationBuilder animation_builder = |
| apps_grid_view_->FadeInVisibleItemsForReorder(base::BindRepeating( |
| &AppsContainerView::OnAppsGridViewFadeInAnimationEnded, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| // Fade in the undo toast when: |
| // (1) The toast's visibility becomes true from false, or |
| // (2) The apps page is scrolled to show the toast. |
| const bool should_fade_in_toast = |
| (target_toast_visible && (page_change || toast_visibility_change)); |
| |
| if (!should_fade_in_toast) |
| return; |
| |
| // Hide the toast to prepare for the fade in animation, |
| toast_container_->layer()->SetOpacity(0.f); |
| |
| animation_builder.GetCurrentSequence().SetOpacity( |
| toast_container_->layer(), 1.f, gfx::Tween::ACCEL_5_70_DECEL_90); |
| |
| // Continue section should be faded in only when the page changes. |
| if (page_change) { |
| continue_container_->layer()->SetOpacity(0.f); |
| animation_builder.GetCurrentSequence().SetOpacity( |
| continue_container_->layer(), 1.f, gfx::Tween::ACCEL_5_70_DECEL_90); |
| } |
| } |
| |
| void AppsContainerView::OnAppsGridViewFadeInAnimationEnded(bool aborted) { |
| if (aborted) { |
| // Ensure that children are visible when the fade in animation is aborted. |
| toast_container_->layer()->SetOpacity(1.f); |
| continue_container_->layer()->SetOpacity(1.f); |
| } |
| |
| OnReorderAnimationEnded(); |
| } |
| |
| void AppsContainerView::OnReorderAnimationEnded() { |
| update_position_closure_.Reset(); |
| |
| if (reorder_animation_done_closure_) |
| std::move(reorder_animation_done_closure_).Run(); |
| } |
| |
| void AppsContainerView::HandleFocusAfterSort() { |
| // As the sort update on AppsContainerView can be called in both clamshell |
| // mode and tablet mode, return early if it's currently in clamshell mode |
| // because the AppsContainerView isn't visible. |
| if (!contents_view_->app_list_view()->is_tablet_mode()) |
| return; |
| |
| // If the sort is done and the toast is visible and not fading out, request |
| // the focus on the undo button on the toast. Otherwise request the focus on |
| // the search box. |
| if (toast_container_->IsToastVisible()) { |
| toast_container_->toast_view()->toast_button()->RequestFocus(); |
| } else { |
| contents_view_->GetSearchBoxView()->search_box()->RequestFocus(); |
| } |
| } |
| |
| int AppsContainerView::GetSeparatorHeight() { |
| if (!separator_ || !separator_->GetVisible()) |
| return 0; |
| return separator_->GetProperty(views::kMarginsKey)->height() + |
| views::Separator::kThickness; |
| } |
| |
| } // namespace ash |