// Copyright 2012 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_APP_LIST_ITEM_VIEW_H_
#define ASH_APP_LIST_VIEWS_APP_LIST_ITEM_VIEW_H_
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "ash/app_list/grid_index.h"
#include "ash/app_list/model/app_icon_load_helper.h"
#include "ash/app_list/model/app_list_item_observer.h"
#include "ash/app_list/views/app_list_item_view_grid_delegate.h"
#include "ash/app_list/views/apps_collection_section_view.h"
#include "ash/ash_export.h"
#include "ash/public/cpp/app_list/app_list_config.h"
#include "base/memory/raw_ptr.h"
#include "base/timer/timer.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/views/context_menu_controller.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/image_view.h"
namespace gfx {
class Point;
class Rect;
} // namespace gfx
namespace ui {
class SimpleMenuModel;
} // namespace ui
namespace views {
class Label;
} // namespace views
namespace ash {
class AppsGridContextMenu;
class AppListConfig;
class AppListItem;
class AppListItemViewGridDelegate;
class AppListMenuModelAdapter;
class AppListViewDelegate;
class DotIndicator;
class ProgressIndicator;
namespace test {
class AppsGridViewTest;
class AppListMainViewTest;
class RecentAppsViewTest;
} // namespace test
// An application icon and title. Commonly part of the AppsGridView, but may be
// used in other contexts. Supports dragging and keyboard selection via the
// AppListItemViewGridDelegate interface.
class ASH_EXPORT AppListItemView : public views::Button,
public views::ContextMenuController,
public AppListItemObserver,
public ui::ImplicitAnimationObserver {
METADATA_HEADER(AppListItemView, views::Button)
public:
// The types of context where the app list item view is shown.
enum class Context {
// The item is shown in an AppsGridView.
kAppsGridView,
// The item is shown in the RecentAppsView.
kRecentAppsView,
// The item is shown in AppsCollectionView.
kAppsCollection
};
// Describes the app list item view drag state.
enum class DragState {
// Item is not being dragged.
kNone,
// Drag is initialized for the item (the owning apps grid considers the view
// to be the dragged view), but the item is still not being dragged.
// Depending on mouse/touch drag timers, UI may be in either normal, or
// dragging state.
kInitialized,
// The item drag is in progress. While in this state, the owning apps grid
// view will generally hide the item view, and replace it with a drag icon
// widget. The UI should be in dragging state (scaled up and with title
// hidden).
kStarted,
};
AppListItemView(const AppListConfig* app_list_config,
AppListItemViewGridDelegate* grid_delegate,
AppListItem* item,
AppListViewDelegate* view_delegate,
Context context);
AppListItemView(const AppListItemView&) = delete;
AppListItemView& operator=(const AppListItemView&) = delete;
~AppListItemView() override;
// Initializes icon loader. Should be called after the view has been added to
// the apps grid view model - otherwise, if icon gets updated synchronously,
// it may update the item metadata before the view gets added to the view
// model. If the metadata update causes a position change, attempts to move
// the item in the view model could crash.
void InitializeIconLoader();
// Sets the app list config that should be used to size the app list icon, and
// margins within the app list item view. The owner should ensure the
// `AppListItemView` does not outlive the object referenced by
// `app_list_config_`.
void UpdateAppListConfig(const AppListConfig* app_list_config);
// Updates the currently dragged AppListItemView to update the `folder_icon_`.
void UpdateDraggedItem(const AppListItem* dragged_item);
// Updates and repaints the icon view, which could be either `icon_` or
// `folder_icon_`.
// For `icon_`, update the image icon from AppListItem if `update_item_icon`
// is true.
void UpdateIconView(bool update_item_icon);
// Sets the icon and host badge icon of this image.
void SetIconAndMaybeHostBadgeIcon(const gfx::ImageSkia& icon,
const gfx::ImageSkia& host_badge_icon);
// Returns the main app icon size for the associated item. This is the actual
// size of the main app icon that is painted in the grid.
gfx::Size GetIconSize() const;
// Whether the icon used on this item is a placeholder icon for a promise app.
// This is obtained from the value in the item's metadata.
bool ItemHasPlaceholderIcon();
void SetItemName(const std::u16string& display_name,
const std::u16string& full_name);
void SetItemAccessibleName(const std::u16string& name);
void SetHostBadgeIcon(const gfx::ImageSkia& host_badge_icon);
void CancelContextMenu();
void SetAsAttemptedFolderTarget(bool is_target_folder);
// Sets focus without a11y announcements or focus ring.
void SilentlyRequestFocus();
// Ensures that the item view is selected by `grid_delegate_`.
void EnsureSelected();
AppListItem* item() const { return item_weak_; }
views::Label* title() { return title_; }
// In a synchronous drag the item view isn't informed directly of the drag
// ending, so the runner of the drag should call this.
void OnSyncDragEnd();
// Returns the view that draws the item view icon.
views::View* GetIconView() const;
// Returns the icon bounds relative to AppListItemView.
gfx::Rect GetIconBounds() const;
// Returns the icon bounds in screen.
gfx::Rect GetIconBoundsInScreen() const;
// Returns the image of icon.
gfx::ImageSkia GetDragImage() const;
// Sets the icon's visibility.
void SetIconVisible(bool visible);
// Handles the icon's scaling and animation for a cardified grid.
void EnterCardifyState();
void ExitCardifyState();
// Returns the icon bounds for with |target_bounds| as the bounds of this view
// and given |icon_size| and the |icon_scale| if the icon was scaled from the
// original display size.
static gfx::Rect GetIconBoundsForTargetViewBounds(
const AppListConfig* config,
const gfx::Rect& target_bounds,
const gfx::Size& icon_size,
float icon_scale);
// Returns the host badge icon bounds using the centerpoint of
// `main_icon_bounds` and given `host_badge_icon_container_size and the
// `icon_scale` if the icon was scaled from the original display size.
static gfx::Rect GetHostBadgeIconBoundsForTargetViewBounds(
const gfx::Rect& main_icon_bounds,
const gfx::Size& host_badge_icon_container_size,
float icon_scale);
// Returns the title bounds for with |target_bounds| as the bounds of this
// view and given |title_size| and the |icon_scale| if the icon was scaled
// from the original display size.
static gfx::Rect GetTitleBoundsForTargetViewBounds(
const AppListConfig* config,
const gfx::Rect& target_bounds,
const gfx::Size& title_size,
float icon_scale);
// views::Button overrides:
void OnGestureEvent(ui::GestureEvent* event) override;
void OnThemeChanged() override;
// views::View overrides:
std::u16string GetTooltipText(const gfx::Point& p) const override;
// When a dragged view enters this view, a preview circle is shown for
// non-folder item while the icon is enlarged for folder item. When a
// dragged view exits this view, the reverse animation will be performed.
void OnDraggedViewEnter();
void OnDraggedViewExit();
// Enables background blur for folder icon if |enabled| is true.
void SetBackgroundBlurEnabled(bool enabled);
// Ensures this item view has its own layer.
void EnsureLayer();
bool HasNotificationBadge();
bool FireMouseDragTimerForTest();
bool FireTouchDragTimerForTest();
// Whether the context menu on a non-folder app item view is showing.
bool IsShowingAppMenu() const;
// Whether the item can be dragged within its `context_`.
bool IsItemDraggable() const;
bool is_folder() const { return is_folder_; }
bool IsNotificationIndicatorShownForTest() const;
AppListItemViewGridDelegate* grid_delegate_for_test() {
return grid_delegate_;
}
const ui::ImageModel& icon_image_model() const { return icon_image_model_; }
const gfx::ImageSkia icon_image_for_test() const {
return icon_image_model_.GetImage().AsImageSkia();
}
AppListMenuModelAdapter* item_menu_model_adapter() const {
return item_menu_model_adapter_.get();
}
AppsGridContextMenu* context_menu_for_folder() const {
return context_menu_for_folder_.get();
}
// Sets the callback which will run after the context menu is shown.
void SetContextMenuShownCallbackForTest(base::RepeatingClosure closure);
// Returns the bounds that would be used for the title if there was no blue
// dot for new install.
gfx::Rect GetDefaultTitleBoundsForTest();
// Sets the most recent grid index for this item view. Also sets
// `has_pending_row_change_` based on whether the grid index change is
// considered a row change for the purposes of animating item views between
// rows.
void SetMostRecentGridIndex(GridIndex new_grid_index, int columns);
// Whether the app list items need to keep layers at all times.
bool AlwaysPaintsToLayer();
// Initializes the view to simulate a completed promise app state, and runs
// animation to show the app list item view. Used when showing the app list
// item view in place of a promise app.
// `fallback_icon` - the icon that can be used for the app list item view if
// the actual app icon has not yet been loaded. Using the `fallback_icon`
// addresses a flash of the app item state with no icon immediately after
// adding the view to the apps grid.
void AnimateInFromPromiseApp(const ui::ImageModel& fallback_icon,
base::RepeatingClosure callback);
// Remove all dragging states from the view.
void ClearItemDraggingState();
GridIndex most_recent_grid_index() { return most_recent_grid_index_; }
bool has_pending_row_change() { return has_pending_row_change_; }
void reset_has_pending_row_change() { has_pending_row_change_ = false; }
const ui::Layer* icon_background_layer_for_test() const {
if (!icon_background_) {
return nullptr;
}
return icon_background_->layer();
}
bool is_icon_extended_for_test() const { return is_icon_extended_; }
bool is_promise_app() const { return is_promise_app_; }
std::optional<size_t> item_counter_count_for_test() const;
ProgressIndicator* GetProgressIndicatorForTest() const;
bool has_host_badge_for_test() const { return has_host_badge_; }
private:
class FolderIconView;
friend class AppsCollectionSectionViewTest;
friend class AppListFolderViewTest;
friend class AppListItemViewTest;
friend class AppListMainViewTest;
friend class test::AppsGridViewTest;
friend class RecentAppsViewTest;
enum UIState {
UI_STATE_NORMAL, // Normal UI (icon + label)
UI_STATE_DRAGGING, // Dragging UI (scaled icon only)
UI_STATE_DROPPING_IN_FOLDER, // Folder dropping preview UI
UI_STATE_TOUCH_DRAGGING, // Dragging UI for touch drag (non-scaled icon
// only)
};
// Callback used when a menu is closed.
void OnMenuClosed();
void SetUIState(UIState state);
// Scales up app icon if |scale_up| is true; otherwise, scale it back to
// normal size.
void ScaleAppIcon(bool scale_up);
// Scales app icon to |scale_factor| without animation.
void ScaleIconImmediatly(float scale_factor);
// Updates the bounds of the icon background layer.
void UpdateBackgroundLayerBounds();
// Sets |touch_dragging_| flag and updates UI.
void SetTouchDragging(bool touch_dragging);
// Sets |mouse_dragging_| flag and updates UI. Only to be called on
// |mouse_drag_timer_|.
void SetMouseDragging(bool mouse_dragging);
// Invoked when |mouse_drag_timer_| fires to show dragging UI.
void OnMouseDragTimer();
// Invoked when |touch_drag_timer_| fires to show dragging UI.
void OnTouchDragTimer(const gfx::Point& tap_down_location,
const gfx::Point& tap_down_root_location);
// Registers this view as a dragged view with the grid delegate.
bool InitiateDrag(const gfx::Point& location,
const gfx::Point& root_location);
// Callback invoked when a context menu is received after calling
// |AppListViewDelegate::GetContextMenuModel|.
void OnContextMenuModelReceived(
const gfx::Point& point,
ui::MenuSourceType source_type,
std::unique_ptr<ui::SimpleMenuModel> menu_model);
// views::ContextMenuController overrides:
void ShowContextMenuForViewImpl(views::View* source,
const gfx::Point& point,
ui::MenuSourceType source_type) override;
// views::Button overrides:
bool ShouldEnterPushedState(const ui::Event& event) override;
// views::View overrides:
void Layout(PassKey) override;
gfx::Size CalculatePreferredSize(
const views::SizeBounds& available_size) const override;
bool OnKeyPressed(const ui::KeyEvent& event) override;
bool OnMousePressed(const ui::MouseEvent& event) override;
void OnMouseReleased(const ui::MouseEvent& event) override;
void OnMouseCaptureLost() override;
bool OnMouseDragged(const ui::MouseEvent& event) override;
bool SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) override;
void OnFocus() override;
void OnBlur() override;
int GetDragOperations(const gfx::Point& press_pt) override;
void WriteDragData(const gfx::Point& press_pt, OSExchangeData* data) override;
void OnDragDone() override;
void ScrollRectToVisible(const gfx::Rect& rect) override;
// Called when the drag registered for this view starts moving.
// `drag_start_callback` passed to
// `AppListItemViewGridDelegate::InitiateDrag()`.
void OnDragStarted();
// Called when the drag registered for this view ends.
// `drag_end_callback` passed to
// `AppListItemViewGridDelegate::InitiateDrag()`.
void OnDragEnded();
// AppListItemObserver overrides:
void ItemIconChanged(AppListConfigType config_type) override;
void ItemNameChanged() override;
void ItemHostBadgeIconChanged() override;
void ItemBadgeVisibilityChanged() override;
void ItemBadgeColorChanged() override;
void ItemIsNewInstallChanged() override;
void ItemBeingDestroyed() override;
void ItemProgressUpdated() override;
void ItemAppStatusUpdated() override;
void ItemAppCollectionIdChanged() override;
// ui::ImplicitAnimationObserver:
void OnImplicitAnimationsCompleted() override;
// Called upon completion of the AppListItemView's show animation from a
// promise icon state.
void OnAnimatedInFromPromiseApp(base::RepeatingClosure callback);
// Whether the image view should show the icon from
// `fallback_icon_image_model_` instead of the icon from the app list item.
// Returns true during show animation from a promise icon state, if the actual
// app icon has not been loaded yet.
bool ShouldUseFallbackIconImageModel() const;
// Whether the image view has a placeholder icon in place. The placeholder
// icon is represented as a VectorIcon in the ImageModel. Depending on the
// case, the icon may use the `icon_image_model` or the
// `fallback_icon_image_model` (ie, when an animation in for the promise app
// is happening) for this calceulation.
bool ImageModelHasPlaceholderIcon() const;
// Calculates the transform between the icon scaled by |icon_scale| and the
// normal size icon.
gfx::Transform GetScaleTransform(float icon_scale);
// Updates the icon extended state if another app is dragged onto this item
// view, which could be either an app or a folder. `extend_icon` is true if
// the icon background is going to extend, shrink the background otherwise.
// `animate` specifies if the visual update should be animated or not.
void SetBackgroundExtendedState(bool extend_icon, bool animate);
// Returns the color ID for the app list item background, if the background
// needs to be shown.
ui::ColorId GetBackgroundLayerColorId() const;
void OnExtendingAnimationEnded(bool extend_icon);
// Returns the layer that paints the icon background.
ui::Layer* GetIconBackgroundLayer();
// Initialize the item drag operation if it is available at `location`.
bool MaybeStartTouchDrag(const gfx::Point& location);
// Updates the active `progress_indicator_` to reflect the current state of
// the item associated to this view.
void UpdateProgressIndicatorState();
// Updates the layer bounds for the `progress_indicator_` if any is currently
// active.
void UpdateProgressRingBounds();
// Returns the preferred inner icon size for a promise app depending on the
// current app_state. Different from `GetIconSize()` since
// `GetPreferredIconSizeForProgressRing()` is used to adjust padding for the
// promise ring.
gfx::Size GetPreferredIconSizeForProgressRing() const;
void UpdateAccessibleDescription();
// The app list config used to layout this view. The initial values is set
// during view construction, but can be changed by calling
// `UpdateAppListConfig()`.
raw_ptr<const AppListConfig, DanglingUntriaged> app_list_config_;
const bool is_folder_;
// Whether context menu options have been requested. Prevents multiple
// requests.
bool waiting_for_context_menu_options_ = false;
raw_ptr<AppListItem> item_weak_; // Owned by AppListModel. Can be nullptr.
// Handles dragging and item selection. Might be a stub for items that are not
// part of an apps grid.
const raw_ptr<AppListItemViewGridDelegate, DanglingUntriaged> grid_delegate_;
// AppListControllerImpl by another name.
const raw_ptr<AppListViewDelegate> view_delegate_;
// Set to true if the ImageSkia icon in AppListItem is drawn. The refreshed
// folder icons are directly drawn on FolderIconView instead of using the
// AppListItem icon.
const bool use_item_icon_;
// NOTE: Only one of `icon_` and `folder_icon_` is used for an item view.
// The icon view that uses the ImageSkia in AppListItem to draw the icon.
raw_ptr<views::ImageView> icon_ = nullptr;
// The folder icon view used for refreshed folders.
raw_ptr<FolderIconView> folder_icon_ = nullptr;
raw_ptr<views::Label> title_ = nullptr;
// The background layer added under the `icon_` layer to paint the background
// of the icon.
raw_ptr<views::View> icon_background_ = nullptr;
// Draws a dot next to the title for newly installed apps.
raw_ptr<views::View> new_install_dot_ = nullptr;
// The context menu model adapter used for app item view.
std::unique_ptr<AppListMenuModelAdapter> item_menu_model_adapter_;
// The context menu controller used for folder item view.
std::unique_ptr<AppsGridContextMenu> context_menu_for_folder_;
UIState ui_state_ = UI_STATE_NORMAL;
// True if scroll gestures should contribute to dragging.
bool touch_dragging_ = false;
// True if the app is enabled for drag/drop operation by mouse.
bool mouse_dragging_ = false;
// Whether AppsGridView should not be notified of a focus event, triggering
// A11y alerts and a focus ring.
bool focus_silently_ = false;
// Whether AppsGridView is in cardified state.
bool in_cardified_grid_ = false;
// The radius of preview circle for non-folder item.
int preview_circle_radius_ = 0;
// Whether `item_menu_model_adapter_` was cancelled as the result of a
// continuous drag gesture.
bool menu_close_initiated_from_drag_ = false;
// Whether `item_menu_model_adapter_` was shown via key event.
bool menu_show_initiated_from_key_ = false;
// A timer to defer showing drag UI when mouse is pressed.
base::OneShotTimer mouse_drag_timer_;
// A timer to defer showing drag UI when the app item is touch pressed.
base::OneShotTimer touch_drag_timer_;
// The bitmap image for this app list item.
ui::ImageModel icon_image_model_;
// The bitmap image for this app list item's host badge icon.
gfx::ImageSkia host_badge_icon_image_;
// The bitmap image for this app list item's main icon. This is separate from
// icon_->GetImage(), since the latter might contain the badge image in its
// imageSkia for shortcuts.
gfx::ImageSkia icon_image_;
// If set, the icon that will be used for the AppListItemView until the actual
// app icon loads. Used when animating an installed app into a place of a
// promise app, in which case the promise app icon is initially used as the
// app icon to prevent jankyness due to an empty icon while the app list item
// is being loaded.
ui::ImageModel fallback_icon_image_model_;
// Whether fallback icon should be preferred even if the actual app icon has
// been loaded - set while the animation from a promise icon state is in
// progress.
bool prefer_fallback_icon_ = false;
// The current item's drag state.
DragState drag_state_ = DragState::kNone;
// The scaling factor for displaying the app icon.
float icon_scale_ = 1.0f;
// Draws an indicator in the top right corner of the image to represent an
// active notification.
raw_ptr<DotIndicator> notification_indicator_ = nullptr;
// Indicates the context in which this view is shown.
const Context context_;
// Helper to trigger icon load.
std::optional<AppIconLoadHelper> icon_load_helper_;
// Called when the context menu is shown.
base::RepeatingClosure context_menu_shown_callback_;
// The most recent location of this item within the app grid.
GridIndex most_recent_grid_index_;
// Whether the last grid index update was a change in position between rows.
// Used to determine whether the animation between rows should be used.
bool has_pending_row_change_ = false;
// Whether the context menu removed focus on a view when opening. Used to
// determine if the focus should be restored on context menu close.
bool focus_removed_by_context_menu_ = false;
// Whether the `icon_` is in the extended state, where a dragged view entered
// this item view.
bool is_icon_extended_ = false;
// Whether the icon background animation is being setup. Used to prevent the
// background layer from being deleted during setup.
bool setting_up_icon_animation_ = false;
// Whether the app is a promise app (i.e. an app with pending or installing
// app status).
bool is_promise_app_ = false;
// Whether the app is a shortcut (i.e. a deeplink created with shortcut via
// Chrome or other third party installed apps) and should render the host
// badge icon.
bool has_host_badge_ = false;
// An object that draws and updates the progress ring around promise app
// icons.
std::unique_ptr<ProgressIndicator> progress_indicator_;
// If set, the progress indicator will be shown, and indicate the contained
// progress value. Used when animating the view in from a promise app state to
// simulate promise icon UI.
std::optional<float> forced_progress_indicator_value_;
base::WeakPtrFactory<AppListItemView> weak_ptr_factory_{this};
};
} // namespace ash
#endif // ASH_APP_LIST_VIEWS_APP_LIST_ITEM_VIEW_H_