chromium/ash/app_list/views/apps_container_view.h

// 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.

#ifndef ASH_APP_LIST_VIEWS_APPS_CONTAINER_VIEW_H_
#define ASH_APP_LIST_VIEWS_APPS_CONTAINER_VIEW_H_

#include <stddef.h>

#include <memory>
#include <set>

#include "ash/app_list/app_list_model_provider.h"
#include "ash/app_list/app_list_view_provider.h"
#include "ash/app_list/model/app_list_folder_item.h"
#include "ash/app_list/views/app_list_folder_controller.h"
#include "ash/app_list/views/app_list_nudge_controller.h"
#include "ash/app_list/views/app_list_page.h"
#include "ash/app_list/views/app_list_toast_container_view.h"
#include "ash/app_list/views/paged_apps_grid_view.h"
#include "ash/app_list/views/search_result_page_dialog_controller.h"
#include "ash/ash_export.h"
#include "ash/public/cpp/pagination/pagination_model_observer.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/views/controls/separator.h"
#include "ui/views/focus/focus_manager.h"

namespace ash {

class ApplicationDragAndDropHost;
class AppListFolderItem;
class AppListFolderView;
class AppListKeyboardController;
class AppListNudgeController;
class ContentsView;
class ContinueSectionView;
class FolderBackgroundView;
class PageSwitcher;
class SearchResultPageAnchoredDialog;

// AppsContainerView contains a root level AppsGridView to render the root level
// app items, and a AppListFolderView to render the app items inside the active
// folder. With productivity launcher, it also contains the continue section,
// recent apps, and an optional separator.
class ASH_EXPORT AppsContainerView
    : public AppListPage,
      public AppListModelProvider::Observer,
      public AppListFolderController,
      public PaginationModelObserver,
      public PagedAppsGridView::ContainerDelegate,
      public AppListToastContainerView::Delegate,
      public views::FocusChangeListener,
      public AppListViewProvider {
  METADATA_HEADER(AppsContainerView, AppListPage)

 public:
  explicit AppsContainerView(ContentsView* contents_view);

  AppsContainerView(const AppsContainerView&) = delete;
  AppsContainerView& operator=(const AppsContainerView&) = delete;

  ~AppsContainerView() override;

  // The horizontal margin for content within the apps container.
  static const int kHorizontalMargin;

  // Resets the app list to a state where it shows the main grid view. This is
  // called when the user opens the launcher for the first time or when the user
  // hides and then shows it.
  void ResetForShowApps();

  // Sets |drag_and_drop_host_| for the current app list in both
  // app_list_folder_view_ and root level apps_grid_view_.
  void SetDragAndDropHostOfCurrentAppList(
      ApplicationDragAndDropHost* drag_and_drop_host);

  // Returns true if it is currently showing an active folder page.
  bool IsInFolderView() const;

  // Updates the visibility of the items in this view according to
  // |app_list_state|.
  void UpdateControlVisibility(AppListViewState app_list_state);

  // Minimal margin for apps grid within the apps container. Set to ensure there
  // is enough space to fit page switcher next to the apps grid.
  int GetMinHorizontalMarginForAppsGrid() const;

  // The minimal top margin for the apps grid (measured from the top of the
  // search box to the top of the apps grid). This margin includes space for
  // search box and suggestion chips.
  // For productivity launcher UI, this will not include space for continue
  // section and recent apps.
  int GetMinTopMarginForAppsGrid(const gfx::Size& search_box_size) const;

  // Returns the ideal vertical margin for content within the apps container.
  // The actual margin may differ depending on available screen real-estate. For
  // example, margin may be smaller if the apps grid contents would not fit
  // within the ideal margin.
  int GetIdealVerticalMargin() const;

  // Calculates the apps container margins depending on the available content
  // bounds, and search box size.
  // |available_bounds| - The bounds available to lay out the full apps
  //      container.
  // |search_box_size| - The expected search box size. Used to determine the
  //      the amount of space in apps container available to the apps grid
  //
  // NOTE: This should not call into ContentsView::GetSearchBoxBounds*()
  // methods, as CalculateMarginsForAvailableBounds is used to calculate the
  // search box bounds.
  const gfx::Insets& CalculateMarginsForAvailableBounds(
      const gfx::Rect& available_bounds,
      const gfx::Size& search_box_size);

  // views::View overrides:
  void Layout(PassKey) override;
  bool OnKeyPressed(const ui::KeyEvent& event) override;
  void OnGestureEvent(ui::GestureEvent* event) override;
  void OnBoundsChanged(const gfx::Rect& old_bounds) override;
  void AddedToWidget() override;
  void RemovedFromWidget() override;
  bool GetDropFormats(int* formats,
                      std::set<ui::ClipboardFormatType>* format_types) override;
  bool CanDrop(const OSExchangeData& data) override;
  int OnDragUpdated(const ui::DropTargetEvent& event) override;
  void OnDragEntered(const ui::DropTargetEvent& event) override;
  void OnDragExited() override;
  DropCallback GetDropCallback(const ui::DropTargetEvent& event) override;

  // views::FocusChangeListener overrides:
  void OnWillChangeFocus(View* focused_before, View* focused_now) override {}
  void OnDidChangeFocus(View* focused_before, View* focused_now) override;

  // AppListPage overrides:
  void OnShown() override;
  void OnWillBeHidden() override;
  void OnHidden() override;
  void OnAnimationStarted(AppListState from_state,
                          AppListState to_state) override;
  void UpdatePageOpacityForState(AppListState state,
                                 float search_box_opacity) override;
  void UpdatePageBoundsForState(AppListState state,
                                const gfx::Rect& contents_bounds,
                                const gfx::Rect& search_box_bounds) override;
  gfx::Rect GetPageBoundsForState(
      AppListState state,
      const gfx::Rect& contents_bounds,
      const gfx::Rect& search_box_bounds) const override;

  // AppListModelProvider::Observer:
  void OnActiveAppListModelsChanged(AppListModel* model,
                                    SearchModel* search_model) override;

  // AppListFolderController:
  void ShowFolderForItemView(AppListItemView* folder_item_view,
                             bool focus_name_input,
                             base::OnceClosure hide_callback) override;
  void ShowApps(AppListItemView* folder_item_view, bool select_folder) override;
  void ReparentFolderItemTransit(AppListFolderItem* folder_item) override;
  void ReparentDragEnded() override;

  // PaginationModelObserver:
  void SelectedPageChanged(int old_selected, int new_selected) override;
  void TransitionChanged() override;
  void TransitionStarted() override;
  void TransitionEnded() override;
  void ScrollStarted() override;
  void ScrollEnded() override;

  // PagedAppsGridView::ContainerDelegate:
  bool IsPointWithinPageFlipBuffer(const gfx::Point& point) const override;
  bool IsPointWithinBottomDragBuffer(const gfx::Point& point,
                                     int page_flip_zone_size) const override;
  void OnCardifiedStateStarted() override;
  void OnCardifiedStateEnded() override;

  // AppListToastContainerView::Delegate:
  void OnNudgeRemoved() override;

  // Handles `AppListController::UpdateAppListWithNewSortingOrder()` for the
  // app list container.
  void UpdateForNewSortingOrder(
      const std::optional<AppListSortOrder>& new_order,
      bool animate,
      base::OnceClosure update_position_closure,
      base::OnceClosure animation_done_closure);

  // Updates the continue section visibility based on user preference.
  void UpdateContinueSectionVisibility();

  // Called by app list controller when the app list visibility is about to
  // change - when the app list is about to be shown, initiates zero state
  // search in order to update set of apps shown in recent apps and continue
  // section contents.
  void OnAppListVisibilityWillChange(bool visible);

  // Updates the nudge in `toast_container_` when app list visibility changes.
  void OnAppListVisibilityChanged(bool shown);

  // AppListViewProvider:
  ContinueSectionView* GetContinueSectionView() override;
  RecentAppsView* GetRecentAppsView() override;
  AppsGridView* GetAppsGridView() override;
  AppListToastContainerView* GetToastContainerView() override;

  views::Separator* separator() { return separator_; }
  PagedAppsGridView* apps_grid_view() { return apps_grid_view_; }
  FolderBackgroundView* folder_background_view() {
    return folder_background_view_;
  }
  AppListFolderView* app_list_folder_view() { return app_list_folder_view_; }
  PageSwitcher* page_switcher() { return page_switcher_; }

  views::View* scrollable_container_for_test() { return scrollable_container_; }

  AppListToastContainerView* toast_container() { return toast_container_; }

  AppListNudgeController* app_list_nudge_controller() {
    return app_list_nudge_controller_.get();
  }

  // Updates recent apps from app list model.
  void UpdateRecentApps();

  // Gets the height of the `separator_` including its vertical margin.
  int GetSeparatorHeight();

  SearchResultPageAnchoredDialog* dialog_for_test() {
    return dialog_controller_->dialog();
  }

 private:
  enum ShowState {
    SHOW_NONE,  // initial state
    SHOW_APPS,
    SHOW_ACTIVE_FOLDER,
    SHOW_ITEM_REPARENT,
  };

  class ContinueContainer;

  void SetShowState(ShowState show_state, bool show_apps_with_animation);

  // Updates the whole container opacity to match the app list state.
  void UpdateContainerOpacityForState(AppListState state);

  // Updates the y position of the apps container elements for the current app
  // list view position.
  // |view_state| - The app list view state.
  void UpdateContentsYPosition(AppListViewState view_state);

  // Suggestion chips and apps grid view become unfocusable if |disabled| is
  // true. This is used to trap focus within the folder when it is opened.
  void DisableFocusForShowingActiveFolder(bool disabled);

  // Returns expected apps container's y position based on the app list
  // 'view_state'.
  int GetAppListY(AppListViewState view_state);

  struct GridLayout {
    int columns;
    int rows;
    int first_page_rows;
  };
  // Returns the number of columns and rows |apps_grid_view_| should display,
  // depending on the current display work area size.
  GridLayout CalculateGridLayout() const;

  // Calculates the grid layout and updates the number of rows and columns shown
  // in the top level apps grid.
  void UpdateTopLevelGridDimensions();

  // Returns the space available to the apps grid for laying out its contents.
  gfx::Rect CalculateAvailableBoundsForAppsGrid(
      const gfx::Rect& contents_bounds) const;

  // Depending on the provided apps container contents bounds and grid layout,
  // updates `app_list_config_` to be used within the apps container, and passes
  // it on to child views that require it.
  void UpdateAppListConfig(const gfx::Rect& contents_bounds);

  // Updates the apps container UI to display contents from the active app list
  // model. Should be called to initialize the apps container contents, and
  // whenever the active app list model changes.
  void UpdateForActiveAppListModel();

  // Updates the bounds of the gradient mask to fit the current bounds of the
  // `scrollable_container_`.
  void UpdateGradientMaskBounds();

  // Creates a layer mask for gradient alpha and applies it to the
  // `scrollable_container_` layer. The gradient appears at the top and bottom
  // of the `scrollable_container_` to create a "fade out" effect when dragging
  // the whole page.
  void MaybeCreateGradientMask();

  // Removes the gradient mask from the `scrollable_container_`.
  void MaybeRemoveGradientMask();

  // Called when the animation to fade out app list items is completed.
  // `aborted` indicates whether the fade out animation is aborted.
  void OnAppsGridViewFadeOutAnimationEnded(
      const std::optional<AppListSortOrder>& new_order,
      bool aborted);

  // Called when the animation to fade in app list items is completed.
  // `aborted` indicates whether the fade in animation is aborted.
  void OnAppsGridViewFadeInAnimationEnded(bool aborted);

  // Called at the end of the reorder animation. In detail, it is executed in
  // the following scenarios:
  // (1) At the end of the fade out animation when the fade out is aborted, or
  // (2) At the end of the fade in animation.
  void OnReorderAnimationEnded();

  // Called after sort to handle focus.
  void HandleFocusAfterSort();

  // Called when the zero state search completes in order to update recent apps.
  void OnZeroStateSearchDone();

  // While true, the gradient mask will not be removed as a mask layer until
  // cardified state ends.
  bool keep_gradient_mask_for_cardified_state_ = false;

  const raw_ptr<ContentsView> contents_view_;

  // The app list config used to configure sizing and layout of apps grid items
  // within the apps container.
  std::unique_ptr<AppListConfig> app_list_config_;

  std::unique_ptr<AppListKeyboardController> app_list_keyboard_controller_;
  std::unique_ptr<AppListNudgeController> app_list_nudge_controller_;

  // Controller for showing a modal dialog in the continue section.
  std::unique_ptr<SearchResultPageDialogController> dialog_controller_;

  // Contains the |continue_section_| and the |apps_grid_view_|, which are views
  // that are affected by paging. Owned by views hierarchy.
  raw_ptr<views::View> scrollable_container_ = nullptr;

  // The views below are owned by views hierarchy.
  raw_ptr<ContinueContainer> continue_container_ = nullptr;
  raw_ptr<views::Separator> separator_ = nullptr;
  raw_ptr<AppListToastContainerView> toast_container_ = nullptr;
  raw_ptr<PagedAppsGridView> apps_grid_view_ = nullptr;
  raw_ptr<AppListFolderView, DanglingUntriaged> app_list_folder_view_ = nullptr;
  raw_ptr<PageSwitcher, DanglingUntriaged> page_switcher_ = nullptr;
  raw_ptr<FolderBackgroundView> folder_background_view_ = nullptr;

  ShowState show_state_ = SHOW_NONE;

  // Whether the apps container is the current active app list page.
  bool is_active_page_ = false;

  // The distance between y position of the scrollable_container_ and the
  // bottom of the search box view. This is used in dragging to avoid duplicate
  // calculation of apps grid view's y position.
  int scrollable_container_y_distance_ = 0;

  struct CachedContainerMargins {
    gfx::Size bounds_size;
    gfx::Size search_box_size;
    gfx::Insets margins;
  };
  // The last result returned by CalculateMarginsForAvailableBounds() -
  // subsequent calls to that method will return the result cached in
  // |cached_container_margins_|, provided the method arguments match the cached
  // arguments (otherwise the margins will be recalculated).
  CachedContainerMargins cached_container_margins_;

  // A closure to update item positions. It should run at the end of the fade
  // out animation when items are reordered.
  base::OnceClosure update_position_closure_;

  // A closure that runs at the end of the reorder animation.
  base::OnceClosure reorder_animation_done_closure_;

  base::WeakPtrFactory<AppsContainerView> weak_ptr_factory_{this};
};

}  // namespace ash

#endif  // ASH_APP_LIST_VIEWS_APPS_CONTAINER_VIEW_H_