#include "ash/app_list/views/apps_grid_view.h"
#include <algorithm>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "ash/app_list/app_list_item_util.h"
#include "ash/app_list/app_list_metrics.h"
#include "ash/app_list/app_list_model_provider.h"
#include "ash/app_list/app_list_util.h"
#include "ash/app_list/app_list_view_delegate.h"
#include "ash/app_list/apps_grid_row_change_animator.h"
#include "ash/app_list/grid_index.h"
#include "ash/app_list/model/app_list_folder_item.h"
#include "ash/app_list/model/app_list_item.h"
#include "ash/app_list/model/app_list_model.h"
#include "ash/app_list/views/app_list_a11y_announcer.h"
#include "ash/app_list/views/app_list_folder_controller.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_view_util.h"
#include "ash/app_list/views/apps_grid_context_menu.h"
#include "ash/app_list/views/apps_grid_view_folder_delegate.h"
#include "ash/app_list/views/ghost_image_view.h"
#include "ash/app_list/views/pulsing_block_view.h"
#include "ash/drag_drop/drag_drop_controller.h"
#include "ash/public/cpp/app_list/app_list_config.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "ash/public/cpp/app_list/app_list_types.h"
#include "ash/public/cpp/metrics_util.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "ui/aura/window.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/events/event.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/geometry/vector2d_f.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/animation_builder.h"
#include "ui/views/animation/animation_sequence_block.h"
#include "ui/views/controls/label.h"
#include "ui/views/view_observer.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
namespace ash {
namespace {
constexpr int kDragBufferPx = 20;
constexpr int kReorderDelay = 120;
constexpr int kFolderItemReparentDelay = 50;
constexpr int kMaximumTileSpacing = 96;
constexpr int kMaximumHorizontalTileSpacing = 128;
constexpr float kFadeAnimationOffsetRatio = 0.25f;
constexpr base::TimeDelta kFadeInAnimationDuration = base::Milliseconds(400);
constexpr base::TimeDelta kFadeOutAnimationDuration = base::Milliseconds(100);
constexpr base::TimeDelta kFolderItemFadeOutDuration = base::Milliseconds(100);
constexpr base::TimeDelta kFolderItemFadeInDuration = base::Milliseconds(300);
constexpr base::TimeDelta kFolderItemFadeInDelay = base::Milliseconds(300);
constexpr base::TimeDelta kItemBoundsBaseAnimationDuration =
base::Milliseconds(300);
constexpr base::TimeDelta kItemBoundsAnimationOffsetDuration =
base::Milliseconds(50);
bool IsOEMFolderItem(AppListItem* item) {
return IsFolderItem(item) && item->AsFolderItem()->folder_type() ==
AppListFolderItem::FOLDER_TYPE_OEM;
}
gfx::Rect ApplyTransformAtOrigin(const gfx::Rect& in_bounds,
const gfx::Transform& transform) {
gfx::Rect out_bounds;
out_bounds = transform.MapRect(out_bounds);
out_bounds.Offset(in_bounds.OffsetFromOrigin());
out_bounds.set_size(in_bounds.size());
return out_bounds;
}
AppsGridView::Pointer GetPointerTypeForDragAndDrop() {
if (Shell::Get()->drag_drop_controller()->event_source() ==
ui::mojom::DragEventSource::kMouse) {
return AppsGridView::MOUSE;
}
return AppsGridView::TOUCH;
}
}
constexpr int AppsGridView::kDefaultAnimationDuration;
AppsGridView::VisibleItemIndexRange::VisibleItemIndexRange() = default;
AppsGridView::VisibleItemIndexRange::VisibleItemIndexRange(size_t first_index,
size_t last_index)
: first_index(first_index), last_index(last_index) {}
AppsGridView::VisibleItemIndexRange::~VisibleItemIndexRange() = default;
class AppsGridView::FolderIconItemHider : public AppListItemObserver,
public views::ViewObserver {
public:
FolderIconItemHider(AppListItemView* folder_item_view,
AppListItem* item_icon_to_hide)
: item_view_(folder_item_view),
folder_item_(folder_item_view->item()->AsFolderItem()) {
item_view_->AddObserver(this);
item_view_->UpdateDraggedItem(item_icon_to_hide);
folder_item_->NotifyOfDraggedItem(item_icon_to_hide);
folder_item_observer_.Observe(folder_item_.get());
}
~FolderIconItemHider() override {
if (item_view_) {
item_view_->RemoveObserver(this);
item_view_->UpdateDraggedItem(nullptr);
}
if (folder_item_) {
folder_item_->NotifyOfDraggedItem(nullptr);
}
}
void OnViewIsDeleting(views::View* observed_view) override {
DCHECK_EQ(item_view_, observed_view);
item_view_ = nullptr;
folder_item_ = nullptr;
folder_item_observer_.Reset();
}
void ItemBeingDestroyed() override {
item_view_->RemoveObserver(this);
item_view_ = nullptr;
folder_item_ = nullptr;
folder_item_observer_.Reset();
}
private:
raw_ptr<AppListItemView> item_view_;
raw_ptr<AppListFolderItem> folder_item_;
base::ScopedObservation<AppListItem, AppListItemObserver>
folder_item_observer_{this};
};
class AppsGridView::DragViewHider : public views::ViewObserver {
public:
explicit DragViewHider(AppListItemView* drag_view) : drag_view_(drag_view) {
DCHECK(drag_view_->layer());
drag_view_->layer()->SetOpacity(0.0f);
view_observer_.Observe(drag_view_.get());
}
~DragViewHider() override {
if (drag_view_ && drag_view_->layer())
drag_view_->layer()->SetOpacity(1.0f);
}
void OnViewIsDeleting(views::View* view) override {
drag_view_ = nullptr;
view_observer_.Reset();
}
const views::View* drag_view() const { return drag_view_; }
private:
raw_ptr<AppListItemView> drag_view_;
base::ScopedObservation<views::View, views::ViewObserver> view_observer_{
this};
};
class AppsGridView::ScopedModelUpdate {
public:
explicit ScopedModelUpdate(AppsGridView* apps_grid_view)
: apps_grid_view_(apps_grid_view),
initial_grid_size_(apps_grid_view_->GetTileGridSize()) {
DCHECK(!apps_grid_view_->updating_model_);
apps_grid_view_->updating_model_ = true;
DCHECK(!apps_grid_view_->ignore_layout_);
apps_grid_view_->ignore_layout_ = true;
}
ScopedModelUpdate(const ScopedModelUpdate&) = delete;
ScopedModelUpdate& operator=(const ScopedModelUpdate&) = delete;
~ScopedModelUpdate() {
DCHECK(apps_grid_view_->updating_model_);
apps_grid_view_->updating_model_ = false;
DCHECK(apps_grid_view_->ignore_layout_);
apps_grid_view_->ignore_layout_ = false;
apps_grid_view_->ScheduleLayout(initial_grid_size_);
}
private:
const raw_ptr<AppsGridView> apps_grid_view_;
const gfx::Size initial_grid_size_;
};
class AnimationObserverToRestoreGrid : public ui::ImplicitAnimationObserver {
public:
explicit AnimationObserverToRestoreGrid(base::OnceClosure cb)
: animation_completion_callback_(std::move(cb)) {}
~AnimationObserverToRestoreGrid() override {
StopObservingImplicitAnimations();
}
void OnImplicitAnimationsCompleted() override {
if (animation_completion_callback_) {
std::move(animation_completion_callback_).Run();
}
delete this;
}
bool RequiresNotificationWhenAnimatorDestroyed() const override {
return true;
}
private:
base::OnceClosure animation_completion_callback_;
};
AppsGridView::AppsGridView(AppListA11yAnnouncer* a11y_announcer,
AppListViewDelegate* app_list_view_delegate,
AppsGridViewFolderDelegate* folder_delegate,
AppListFolderController* folder_controller,
AppListKeyboardController* keyboard_controller)
: folder_delegate_(folder_delegate),
folder_controller_(folder_controller),
a11y_announcer_(a11y_announcer),
app_list_view_delegate_(app_list_view_delegate),
keyboard_controller_(keyboard_controller) {
DCHECK(a11y_announcer_);
DCHECK(app_list_view_delegate_);
if (!folder_delegate_) {
DCHECK(folder_controller_);
}
SetPaintToLayer(ui::LAYER_NOT_DRAWN);
items_container_ = AddChildView(std::make_unique<views::View>());
items_container_->SetPaintToLayer();
items_container_->layer()->SetFillsBoundsOpaquely(false);
GetViewAccessibility().SetRole(ax::mojom::Role::kGroup);
if (!folder_delegate) {
GetViewAccessibility().SetName(
l10n_util::GetStringUTF16(IDS_ASH_LAUNCHER_APPS_GRID_A11Y_NAME),
ax::mojom::NameFrom::kAttribute);
}
if (!IsTabletMode()) {
context_menu_ = std::make_unique<AppsGridContextMenu>(
AppsGridContextMenu::GridType::kAppsGrid);
set_context_menu_controller(context_menu_.get());
}
row_change_animator_ = std::make_unique<AppsGridRowChangeAnimator>(this);
}
AppsGridView::~AppsGridView() {
DCHECK(!drag_item_);
if (model_) {
model_->RemoveObserver(this);
}
if (item_list_) {
item_list_->RemoveObserver(this);
}
set_context_menu_controller(nullptr);
MaybeAbortWholeGridAnimation();
folder_icon_item_hider_.reset();
view_model_.Clear();
pulsing_blocks_model_.Clear();
RemoveAllChildViews();
folder_to_open_after_drag_icon_animation_.clear();
weak_factory_.InvalidateWeakPtrs();
drag_image_layer_.reset();
}
void AppsGridView::UpdateAppListConfig(const AppListConfig* app_list_config) {
app_list_config_ = app_list_config;
for (size_t i = 0; i < view_model_.view_size(); ++i)
view_model_.view_at(i)->UpdateAppListConfig(app_list_config);
if (current_ghost_view_) {
CreateGhostImageView();
}
}
void AppsGridView::SetFixedTilePadding(int horizontal_padding,
int vertical_padding) {
has_fixed_tile_padding_ = true;
horizontal_tile_padding_ = horizontal_padding;
vertical_tile_padding_ = vertical_padding;
}
gfx::Size AppsGridView::GetTotalTileSize(int page) const {
gfx::Rect rect(GetTileViewSize());
rect.Inset(GetTilePadding(page));
return rect.size();
}
gfx::Size AppsGridView::GetMinimumTileGridSize(int cols,
int rows_per_page) const {
const gfx::Size tile_size = GetTileViewSize();
return gfx::Size(tile_size.width() * cols,
tile_size.height() * rows_per_page);
}
gfx::Size AppsGridView::GetMaximumTileGridSize(int cols,
int rows_per_page) const {
const gfx::Size tile_size = GetTileViewSize();
return gfx::Size(
tile_size.width() * cols + kMaximumHorizontalTileSpacing * (cols - 1),
tile_size.height() * rows_per_page +
kMaximumTileSpacing * (rows_per_page - 1));
}
void AppsGridView::ResetForShowApps() {
CancelDragWithNoDropAnimation();
layer()->SetOpacity(1.0f);
SetVisible(true);
if (item_list_) {
CHECK_EQ(item_list_->item_count(), view_model_.view_size());
}
}
void AppsGridView::EndDragCallback(
const ui::DropTargetEvent& event,
ui::mojom::DragOperation& output_drag_op,
std::unique_ptr<ui::LayerTreeOwner> drag_image_layer_owner) {
output_drag_op = ui::mojom::DragOperation::kMove;
if (drag_item_) {
drag_image_layer_ = std::move(drag_image_layer_owner);
EndDrag(false);
}
}
void AppsGridView::CancelDragWithNoDropAnimation() {
EndDrag(true);
drag_view_hider_.reset();
folder_icon_item_hider_.reset();
if (!folder_to_open_after_drag_icon_animation_.empty()) {
open_folder_info_.reset();
}
folder_to_open_after_drag_icon_animation_.clear();
drag_image_layer_.reset();
}
void AppsGridView::DisableFocusForShowingActiveFolder(bool disabled) {
for (const auto& entry : view_model_.entries())
entry.view->SetEnabled(!disabled);
SetViewIgnoredForAccessibility(this, disabled);
}
void AppsGridView::SetModel(AppListModel* model) {
if (model_) {
model_->RemoveObserver(this);
}
model_ = model;
if (model_) {
model_->AddObserver(this);
}
Update();
}
void AppsGridView::SetItemList(AppListItemList* item_list) {
DCHECK_GT(cols_, 0);
DCHECK(app_list_config_);
if (item_list_) {
item_list_->RemoveObserver(this);
}
item_list_ = item_list;
if (item_list_) {
item_list_->AddObserver(this);
}
Update();
}
bool AppsGridView::IsInFolder() const {
return !!folder_delegate_;
}
void AppsGridView::SetSelectedView(AppListItemView* view) {
if (IsSelectedView(view) || IsDraggedView(view)) {
return;
}
GridIndex index = GetIndexOfView(view);
if (IsValidIndex(index)) {
SetSelectedItemByIndex(index);
}
}
void AppsGridView::ClearSelectedView() {
selected_view_ = nullptr;
}
bool AppsGridView::IsSelectedView(const AppListItemView* view) const {
return selected_view_ == view;
}
void AppsGridView::UpdateDrag(Pointer pointer, const gfx::Point& point) {
if (folder_delegate_) {
UpdateDragStateInsideFolder(pointer, point);
}
if (!drag_item_) {
return;
}
if (open_folder_info_) {
if (drag_pointer_ == pointer) {
last_drag_point_ = point;
}
return;
}
MaybeStartCardifiedView();
last_drag_point_ = point;
const GridIndex last_drop_target = drop_target_;
DropTargetRegion last_drop_target_region = drop_target_region_;
UpdateDropTargetRegion();
const bool has_page_flip = MaybeStartPageFlip();
const bool is_scrolling = MaybeAutoScroll();
if (is_scrolling || has_page_flip) {
reorder_timer_.Stop();
if (last_drop_target_region == ON_ITEM)
SetAsFolderDroppingTarget(last_drop_target, false);
return;
}
if (last_drop_target != drop_target_ ||
last_drop_target_region != drop_target_region_) {
if (last_drop_target_region == ON_ITEM)
SetAsFolderDroppingTarget(last_drop_target, false);
if (drop_target_region_ == ON_ITEM && DraggedItemCanEnterFolder() &&
DropTargetIsValidFolder()) {
reorder_timer_.Stop();
MaybeCreateFolderDroppingAccessibilityEvent();
SetAsFolderDroppingTarget(drop_target_, true);
BeginHideCurrentGhostImageView();
} else if ((drop_target_region_ == ON_ITEM ||
drop_target_region_ == NEAR_ITEM) &&
!folder_delegate_) {
if (last_drop_target_region == BETWEEN_ITEMS) {
reorder_timer_.Stop();
}
reorder_timer_.Start(FROM_HERE, base::Milliseconds(kReorderDelay * 5),
this, &AppsGridView::OnReorderTimer);
} else if (drop_target_region_ != NO_TARGET) {
reorder_timer_.Start(FROM_HERE, base::Milliseconds(kReorderDelay), this,
&AppsGridView::OnReorderTimer);
}
}
}
void AppsGridView::EndDrag(bool cancel) {
DVLOG(1) << "EndDrag cancel=" << cancel;
if (!drag_item_) {
return;
}
AppListItem* drag_item = drag_item_;
const bool was_dragging = IsDragging();
std::string target_folder_id;
bool top_to_bottom_animation = reorder_placeholder_ < drop_target_;
if (IsDraggingForReparentInHiddenGridView()) {
EndDragForReparentInHiddenFolderGridView();
folder_delegate_->DispatchEndDragEventForReparent(
false ,
cancel );
return;
}
if (IsDraggingForReparentInRootLevelGridView()) {
if (!cancel) {
UpdateDropTargetRegion();
EndDragFromReparentItemInRootLevel(nullptr, false, false);
return;
}
DCHECK(!reparent_drag_cancellation_);
}
if (!cancel && was_dragging) {
UpdateDropTargetRegion();
if (drop_target_region_ == ON_ITEM && DraggedItemCanEnterFolder() &&
DropTargetIsValidFolder()) {
top_to_bottom_animation = true;
bool is_new_folder = false;
if (MoveItemToFolder(drag_item_, drop_target_, kMoveByDragIntoFolder,
&target_folder_id, &is_new_folder)) {
MaybeCreateFolderDroppingAccessibilityEvent();
if (is_new_folder) {
folder_to_open_after_drag_icon_animation_ = target_folder_id;
SetOpenFolderInfo(target_folder_id, drop_target_,
reorder_placeholder_);
}
DeprecatedLayoutImmediately();
}
} else if (IsValidIndex(drop_target_)) {
MaybeCreateDragReorderAccessibilityEvent();
MoveItemInModel(drag_item_, drop_target_);
RecordAppMovingTypeMetrics(folder_delegate_ ? kReorderByDragInFolder
: kReorderByDragInTopLevel);
}
}
SetAsFolderDroppingTarget(drop_target_, false);
ClearDragState();
UpdatePaging();
if (GetWidget()) {
DCHECK(!ignore_layout_);
base::AutoReset<bool> auto_reset(&ignore_layout_, true);
GetWidget()->LayoutRootViewIfNecessary();
}
if (cardified_state_)
MaybeEndCardifiedView();
else
AnimateToIdealBounds(top_to_bottom_animation);
if (!cancel) {
const size_t model_index = GetModelIndexOfItem(drag_item);
if (model_index < view_model_.view_size())
EnsureViewVisible(GetGridIndexFromIndexInViewModel(model_index));
}
BeginHideCurrentGhostImageView();
if (was_dragging)
SetFocusAfterEndDrag(drag_item);
AnimateDragIconToTargetPosition(drag_item, target_folder_id);
}
AppListItemView* AppsGridView::GetItemViewForItem(const std::string& item_id) {
const AppListItem* const item = item_list_->FindItem(item_id);
if (!item) {
return nullptr;
}
return GetItemViewAt(GetModelIndexOfItem(item));
}
AppListItemView* AppsGridView::GetItemViewAt(size_t index) const {
return (index < view_model_.view_size()) ? view_model_.view_at(index)
: nullptr;
}
void AppsGridView::UpdateDragFromReparentItem(Pointer pointer,
const gfx::Point& drag_point) {
DCHECK(drag_item_);
DCHECK(IsDraggingForReparentInRootLevelGridView());
UpdateDrag(pointer, drag_point);
}
void AppsGridView::SetOpenFolderInfo(const std::string& folder_id,
const GridIndex& target_folder_position,
const GridIndex& position_to_skip) {
GridIndex expected_folder_position = target_folder_position;
if (position_to_skip.IsValid() &&
position_to_skip < expected_folder_position &&
expected_folder_position.slot > 0) {
--expected_folder_position.slot;
}
open_folder_info_ = {.item_id = folder_id,
.grid_index = expected_folder_position};
}
void AppsGridView::ShowFolderForView(AppListItemView* folder_view,
bool new_folder) {
DCHECK(open_folder_info_);
if (!folder_view || !folder_view->is_folder()) {
open_folder_info_.reset();
return;
}
folder_controller_->ShowFolderForItemView(
folder_view,
new_folder,
base::BindOnce(&AppsGridView::FolderHidden, weak_factory_.GetWeakPtr(),
folder_view->item()->id()));
}
void AppsGridView::FolderHidden(const std::string& item_id) {
if (!open_folder_info_ || open_folder_info_->item_id != item_id) {
return;
}
AppListItemView* item_view = nullptr;
int model_index = -1;
for (size_t i = 0; i < view_model_.view_size(); ++i) {
AppListItemView* view = view_model_.view_at(i);
if (view == drag_view_) {
continue;
}
++model_index;
if (view->item()->id() == item_id) {
item_view = view;
break;
}
}
if (!item_view || GetGridIndexFromIndexInViewModel(model_index) ==
open_folder_info_->grid_index) {
open_folder_info_.reset();
OnFolderHideAnimationDone();
return;
}
PrepareItemsForBoundsAnimation();
reordering_folder_view_ = item_view;
views::AnimationBuilder animation;
animation.OnEnded(base::BindOnce(&AppsGridView::AnimateFolderItemViewIn,
weak_factory_.GetWeakPtr()));
animation.OnAborted(base::BindOnce(&AppsGridView::AnimateFolderItemViewIn,
weak_factory_.GetWeakPtr()));
gfx::Transform scale;
scale.Scale(0.5, 0.5);
scale = gfx::TransformAboutPivot(
gfx::RectF(item_view->GetLocalBounds()).CenterPoint(), scale);
animation.Once()
.SetDuration(kFolderItemFadeOutDuration)
.SetTransform(item_view->layer(), scale, gfx::Tween::FAST_OUT_LINEAR_IN)
.SetOpacity(item_view->layer(), 0.0f, gfx::Tween::FAST_OUT_LINEAR_IN);
}
void AppsGridView::AnimateFolderItemViewIn() {
const GridIndex before_index =
open_folder_info_ ? open_folder_info_->grid_index : GridIndex();
const GridIndex after_index =
GetIndexOfView(reordering_folder_view_.value_or(nullptr));
const bool top_to_bottom_animation = before_index < after_index;
open_folder_info_.reset();
AnimateToIdealBounds(top_to_bottom_animation);
if (!reordering_folder_view_) {
return;
}
views::AnimationBuilder()
.OnEnded(base::BindOnce(&AppsGridView::OnFolderHideAnimationDone,
weak_factory_.GetWeakPtr()))
.OnAborted(base::BindOnce(&AppsGridView::OnFolderHideAnimationDone,
weak_factory_.GetWeakPtr()))
.Once()
.At(kFolderItemFadeInDelay)
.SetDuration(kFolderItemFadeInDuration)
.SetTransform(reordering_folder_view_.value()->layer(), gfx::Transform(),
gfx::Tween::ACCEL_LIN_DECEL_100_3)
.SetOpacity(reordering_folder_view_.value()->layer(), 1.0f,
gfx::Tween::ACCEL_LIN_DECEL_100_3);
}
void AppsGridView::OnFolderHideAnimationDone() {
reordering_folder_view_.reset();
DestroyLayerItemsIfNotNeeded();
if (IsDraggingForReparentInRootLevelGridView()) {
MaybeStartCardifiedView();
UpdateDrag(drag_pointer_, last_drag_point_);
}
}
bool AppsGridView::IsDragging() const {
return drag_pointer_ != NONE;
}
bool AppsGridView::IsDraggedView(const AppListItemView* view) const {
return drag_item_ == view->item();
}
void AppsGridView::ClearDragState() {
current_ghost_location_ = GridIndex();
last_folder_dropping_a11y_event_location_ = GridIndex();
last_reorder_a11y_event_location_ = GridIndex();
drop_target_region_ = NO_TARGET;
drag_pointer_ = NONE;
drop_target_ = GridIndex();
reorder_placeholder_ = GridIndex();
drag_start_grid_view_ = gfx::Point();
if (folder_item_reparent_timer_.IsRunning())
folder_item_reparent_timer_.Stop();
MaybeStopPageFlip();
StopAutoScroll();
if (drag_item_) {
drag_item_->RemoveObserver(this);
}
drag_view_ = nullptr;
drag_item_ = nullptr;
drag_out_of_folder_container_ = false;
dragging_for_reparent_item_ = false;
extra_page_opened_ = false;
reparent_drag_cancellation_.Reset();
}
bool AppsGridView::IsAnimatingView(AppListItemView* view) const {
return view->layer() && view->layer()->GetAnimator()->is_animating();
}
gfx::Size AppsGridView::CalculatePreferredSize(
const views::SizeBounds& available_size) const {
return GetTileGridSize();
}
bool AppsGridView::GetDropFormats(
int* formats,
std::set<ui::ClipboardFormatType>* format_types) {
format_types->insert(GetAppItemFormatType());
return true;
}
bool AppsGridView::CanDrop(const OSExchangeData& data) {
if (ShouldContainerHandleDragEvents()) {
return false;
}
return WillAcceptDropEvent(data);
}
bool AppsGridView::WillAcceptDropEvent(const OSExchangeData& data) {
if (pulsing_blocks_model_.view_size()) {
return false;
}
auto app_id = GetAppInfoFromDropDataForAppType(data);
if (!app_id.has_value() || app_id->IsValid()) {
return false;
}
std::set<ui::ClipboardFormatType> format_types;
GetDropFormats(nullptr, &format_types);
return data.HasAnyFormat(0, format_types);
}
void AppsGridView::OnDragExited() {
if (folder_delegate_) {
if (drag_view_) {
folder_delegate_->ReparentItem(drag_pointer_, drag_view_,
last_drag_point_);
}
if (item_list_) {
item_list_->RemoveObserver(this);
}
item_list_ = nullptr;
dragging_for_reparent_item_ = true;
folder_delegate_->Close();
}
if (drag_view_) {
drag_view_->ClearItemDraggingState();
}
CancelDragWithNoDropAnimation();
}
void AppsGridView::ItemBeingDestroyed() {
DCHECK(drag_item_);
EndDrag(true);
DCHECK(!drag_item_);
}
void AppsGridView::OnDragEntered(const ui::DropTargetEvent& event) {
if (pulsing_blocks_model_.view_size()) {
return;
}
auto app_info = GetAppInfoFromDropDataForAppType(event.data());
if (!app_info || app_info->IsValid()) {
return;
}
drag_item_ = AppListModelProvider::Get()->model()->FindItem(app_info->app_id);
if (!drag_item_) {
return;
}
drag_item_->AddObserver(this);
drag_view_hider_.reset();
folder_icon_item_hider_.reset();
folder_to_open_after_drag_icon_animation_.clear();
PrepareItemsForBoundsAnimation();
drag_pointer_ = GetPointerTypeForDragAndDrop();
drag_view_ = GetItemViewAt(GetModelIndexOfItem(drag_item_));
if (drag_view_) {
drag_view_hider_ = std::make_unique<DragViewHider>(drag_view_);
drag_view_->RequestFocus();
drag_view_init_index_ = GetIndexOfView(drag_view_);
} else {
dragging_for_reparent_item_ = true;
}
const gfx::Size initial_grid_size = GetTileGridSize();
reorder_placeholder_ =
drag_view_ ? drag_view_init_index_
: GetGridIndexFromIndexInViewModel(view_model()->view_size());
UpdatePaging();
if (GetTileGridSize() != initial_grid_size) {
PreferredSizeChanged();
}
ExtractDragLocation(event.root_location(), &drag_start_grid_view_);
}
int AppsGridView::OnDragUpdated(const ui::DropTargetEvent& event) {
UpdateDrag(drag_pointer_, event.location());
return ui::DragDropTypes::DRAG_MOVE;
}
void AppsGridView::UpdateControlVisibility(AppListViewState app_list_state) {
SetVisible(app_list_state == AppListViewState::kFullscreenAllApps ||
app_list_state == AppListViewState::kFullscreenSearch);
}
views::View::DropCallback AppsGridView::GetDropCallback(
const ui::DropTargetEvent& event) {
return base::BindOnce(&AppsGridView::EndDragCallback, base::Unretained(this));
}
bool AppsGridView::OnKeyPressed(const ui::KeyEvent& event) {
if (event.key_code() == ui::VKEY_CONTROL) {
return true;
}
if (selected_view_ && IsArrowKeyEvent(event) && event.IsControlDown()) {
HandleKeyboardAppOperations(event.key_code(), event.IsShiftDown());
return true;
}
if (!IsUnhandledUpDownKeyEvent(event)) {
return false;
}
const bool arrow_up = event.key_code() == ui::VKEY_UP;
return HandleVerticalFocusMovement(arrow_up);
}
bool AppsGridView::OnKeyReleased(const ui::KeyEvent& event) {
if (event.IsControlDown() || !handling_keyboard_move_) {
return false;
}
handling_keyboard_move_ = false;
RecordAppMovingTypeMetrics(folder_delegate_ ? kReorderByKeyboardInFolder
: kReorderByKeyboardInTopLevel);
return false;
}
void AppsGridView::ViewHierarchyChanged(
const views::ViewHierarchyChangedDetails& details) {
if (!details.is_add && details.parent == items_container_.get()) {
CHECK(!view_model_.GetIndexOfView(details.child).has_value());
if (selected_view_.get() == details.child) {
selected_view_ = nullptr;
}
if (drag_view_.get() == details.child) {
drag_view_ = nullptr;
}
if (current_ghost_view_.get() == details.child) {
current_ghost_view_ = nullptr;
}
if (last_ghost_view_.get() == details.child) {
last_ghost_view_ = nullptr;
}
if (reordering_folder_view_ && *reordering_folder_view_ == details.child)
reordering_folder_view_.reset();
row_change_animator_->CancelAnimation(details.child);
}
}
void AppsGridView::Update() {
MaybeAbortWholeGridAnimation();
view_model_.Clear();
pulsing_blocks_model_.Clear();
items_container_->RemoveAllChildViews();
DCHECK(!selected_view_);
DCHECK(!drag_view_);
std::vector<AppListItemView*> item_views;
if (item_list_ && item_list_->item_count()) {
for (size_t i = 0; i < item_list_->item_count(); ++i) {
std::unique_ptr<AppListItemView> view = CreateViewForItemAtIndex(i);
view_model_.Add(view.get(), view_model_.view_size());
item_views.push_back(items_container_->AddChildView(std::move(view)));
}
}
UpdateColsAndRowsForFolder();
UpdatePaging();
UpdatePulsingBlockViews();
PreferredSizeChanged();
for (auto* item_view : item_views) {
item_view->InitializeIconLoader();
}
if (!folder_delegate_) {
RecordPageMetrics();
}
}
base::TimeDelta AppsGridView::GetPulsingBlockAnimationDelayForIndex(
int block_index) {
const int last_non_block_view_column = view_model_.view_size() % cols_;
const int block_index_in_view_model = view_model_.view_size() + block_index;
const base::TimeDelta staging_step_delay = base::Milliseconds(100);
return staging_step_delay *
((last_non_block_view_column + block_index) / cols_) +
staging_step_delay * (block_index_in_view_model % cols_);
}
void AppsGridView::OnSwapAnimationDone(views::View* placeholder,
AppListItemView* app_view) {
delete placeholder;
if (view_model_.GetIndexOfView(app_view).has_value() &&
!ItemViewsRequireLayers())
app_view->DestroyLayer();
UpdatePulsingBlockViews();
}
AppListItemView* AppsGridView::MaybeSwapPlaceholderAsset(size_t index) {
AppListItemView* view =
items_container_->AddChildViewAt(CreateViewForItemAtIndex(index), index);
view_model_.Add(view, index);
const bool placeholder_in_view_index = index == (view_model_.view_size() - 1);
const bool is_syncing =
model_ && model_->status() == AppListModelStatus::kStatusSyncing;
const bool should_animate_placeholder_swap =
pulsing_blocks_model_.view_size() > 0 && is_syncing &&
placeholder_in_view_index;
if (should_animate_placeholder_swap) {
PulsingBlockView* placeholder =
items_container_->AddChildView(std::make_unique<PulsingBlockView>(
app_list_config_->grid_icon_size(), base::TimeDelta(),
app_list_config_->grid_icon_dimension() / 2.f));
placeholder->SetBoundsRect(view->bounds());
placeholder->SetPaintToLayer();
view->EnsureLayer();
view->layer()->SetOpacity(0);
views::AnimationBuilder()
.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
.OnEnded(base::BindOnce(&AppsGridView::OnSwapAnimationDone,
weak_factory_.GetWeakPtr(), placeholder, view))
.OnAborted(base::BindOnce(&AppsGridView::OnSwapAnimationDone,
weak_factory_.GetWeakPtr(), placeholder,
view))
.Once()
.SetDuration(base::Milliseconds(200))
.SetOpacity(placeholder->layer(), 0.0f, gfx::Tween::LINEAR)
.SetOpacity(view->layer(), 1.0f, gfx::Tween::LINEAR);
} else {
UpdatePulsingBlockViews();
}
return view;
}
void AppsGridView::UpdatePulsingBlockViews() {
if (!model_ || model_->status() != AppListModelStatus::kStatusSyncing) {
pulsing_blocks_model_.Clear();
return;
}
const size_t desired_count =
GetNumberOfPulsingBlocksToShow(item_list_ ? item_list_->item_count() : 0);
if (pulsing_blocks_model_.view_size() == desired_count) {
return;
}
pulsing_blocks_model_.Clear();
while (pulsing_blocks_model_.view_size() < desired_count) {
base::TimeDelta time = GetPulsingBlockAnimationDelayForIndex(
pulsing_blocks_model_.view_size());
auto view = std::make_unique<PulsingBlockView>(
app_list_config_->grid_icon_size(), time,
app_list_config_->grid_icon_dimension() / 2.f);
pulsing_blocks_model_.Add(view.get(), pulsing_blocks_model_.view_size());
items_container_->AddChildView(std::move(view));
}
}
std::unique_ptr<AppListItemView> AppsGridView::CreateViewForItemAtIndex(
size_t index) {
DCHECK_LE(index, item_list_->item_count());
auto view = std::make_unique<AppListItemView>(
app_list_config_, this, item_list_->item_at(index),
app_list_view_delegate_, AppListItemView::Context::kAppsGridView);
if (ItemViewsRequireLayers()) {
view->EnsureLayer();
}
if (cardified_state_) {
view->EnterCardifyState();
}
return view;
}
void AppsGridView::SetSelectedItemByIndex(const GridIndex& index) {
if (GetIndexOfView(selected_view_) == index) {
return;
}
AppListItemView* new_selection = GetViewAtIndex(index);
if (!new_selection) {
return;
}
if (selected_view_) {
selected_view_->SchedulePaint();
}
EnsureViewVisible(index);
selected_view_ = new_selection;
selected_view_->SchedulePaint();
selected_view_->NotifyAccessibilityEventDeprecated(ax::mojom::Event::kFocus,
true);
if (selected_view_->HasNotificationBadge()) {
a11y_announcer_->AnnounceItemNotificationBadge(
selected_view_->title()->GetText());
}
}
int AppsGridView::GetIndexInViewModel(const GridIndex& index) const {
if (index.page == 0) {
return index.slot;
}
const int first_page_size = *TilesPerPage(0);
const int default_page_size = *TilesPerPage(1);
return first_page_size + (index.page - 1) * default_page_size + index.slot;
}
GridIndex AppsGridView::GetIndexOfView(const AppListItemView* view) const {
const auto model_index = view_model_.GetIndexOfView(view);
if (!model_index.has_value()) {
return GridIndex();
}
return GetGridIndexFromIndexInViewModel(model_index.value());
}
AppListItemView* AppsGridView::GetViewAtIndex(const GridIndex& index) const {
if (!IsValidIndex(index)) {
return nullptr;
}
const size_t model_index = GetIndexInViewModel(index);
return GetItemViewAt(model_index);
}
std::optional<int> AppsGridView::TilesPerPage(int page) const {
const std::optional<int> max_rows = GetMaxRowsInPage(page);
if (!max_rows.has_value()) {
return std::nullopt;
}
return *max_rows * cols();
}
bool AppsGridView::IsAnimatingCardifiedState() const {
return false;
}
bool AppsGridView::MaybeStartPageFlip() {
return false;
}
void AppsGridView::SetMaxColumnsInternal(int max_cols) {
if (max_cols_ == max_cols) {
return;
}
max_cols_ = max_cols;
if (IsInFolder()) {
UpdateColsAndRowsForFolder();
} else {
cols_ = max_cols_;
}
}
void AppsGridView::SetIdealBoundsForViewToGridIndex(
size_t view_index_in_model,
const GridIndex& view_grid_index) {
gfx::Rect tile_bounds = GetExpectedTileBounds(view_grid_index);
tile_bounds.Offset(CalculateTransitionOffset(view_grid_index.page));
if (view_index_in_model < view_model_.view_size()) {
view_model_.set_ideal_bounds(view_index_in_model, tile_bounds);
} else {
pulsing_blocks_model_.set_ideal_bounds(
view_index_in_model - view_model_.view_size(), tile_bounds);
}
}
void AppsGridView::CalculateIdealBounds() {
AppListItemView* view_with_locked_position = nullptr;
if (open_folder_info_)
view_with_locked_position = GetItemViewForItem(open_folder_info_->item_id);
std::set<GridIndex> reserved_slots;
reserved_slots.insert(reorder_placeholder_);
if (open_folder_info_) {
reserved_slots.insert(open_folder_info_->grid_index);
}
const size_t total_views =
view_model_.view_size() + pulsing_blocks_model_.view_size();
int slot_index = 0;
for (size_t i = 0; i < total_views; ++i) {
if (i < view_model_.view_size() && view_model_.view_at(i) == drag_view_) {
continue;
}
if (i < view_model_.view_size() &&
view_model_.view_at(i) == view_with_locked_position) {
SetIdealBoundsForViewToGridIndex(i, open_folder_info_->grid_index);
continue;
}
GridIndex view_index = GetGridIndexFromIndexInViewModel(slot_index);
while (reserved_slots.count(view_index)) {
++slot_index;
view_index = GetGridIndexFromIndexInViewModel(slot_index);
}
if (i < view_model_.view_size())
view_model_.view_at(i)->SetMostRecentGridIndex(view_index, cols_);
SetIdealBoundsForViewToGridIndex(i, view_index);
++slot_index;
}
}
void AppsGridView::AnimateToIdealBounds(bool is_animating_top_to_bottom) {
if (layer()->GetCompositor()) {
item_reorder_animation_tracker_ =
layer()->GetCompositor()->RequestNewCompositorMetricsTracker();
item_reorder_animation_tracker_->Start(
metrics_util::ForSmoothnessV3(base::BindRepeating(
&ReportItemDragReorderAnimationSmoothness, IsTabletMode())));
}
gfx::Rect visible_bounds(GetVisibleBounds());
gfx::Point visible_origin = visible_bounds.origin();
ConvertPointToTarget(this, items_container_, &visible_origin);
visible_bounds.set_origin(visible_origin);
CalculateIdealBounds();
std::unique_ptr<views::AnimationBuilder> animation;
auto init_animation = [&]() -> std::unique_ptr<views::AnimationBuilder> {
std::unique_ptr<views::AnimationBuilder> animation =
std::make_unique<views::AnimationBuilder>();
animation
->SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
.OnEnded(base::BindOnce(&AppsGridView::OnIdealBoundsAnimationDone,
weak_factory_.GetWeakPtr()))
.OnAborted(base::BindOnce(&AppsGridView::OnIdealBoundsAnimationDone,
weak_factory_.GetWeakPtr()))
.Once()
.SetDuration(kItemBoundsBaseAnimationDuration);
return animation;
};
base::AutoReset<bool> auto_reset(&setting_up_ideal_bounds_animation_, true);
const bool is_animating_multiple_rows = WillAnimateMultipleRows();
base::TimeDelta animation_duration = kItemBoundsBaseAnimationDuration;
int animation_duration_index = -1;
for (size_t i = 0; i < view_model_.view_size(); ++i) {
const size_t current_view_index =
is_animating_top_to_bottom ? i : view_model_.view_size() - 1 - i;
AppListItemView* view = GetItemViewAt(current_view_index);
const gfx::Rect& target_bounds =
view_model_.ideal_bounds(current_view_index);
gfx::Rect current_bounds = view->GetMirroredBounds();
if (view->bounds() == target_bounds) {
continue;
}
const bool current_visible = visible_bounds.Intersects(current_bounds);
const bool target_visible = visible_bounds.Intersects(target_bounds);
const bool visible =
!IsViewExplicitlyHidden(view) && (current_visible || target_visible);
if (!visible) {
view->SetBoundsRect(target_bounds);
continue;
}
const int view_row = view->most_recent_grid_index().slot / cols_;
const int view_slot = view->most_recent_grid_index().slot;
const int current_animation_duration_index =
is_animating_multiple_rows ? view_row : view_slot;
if (animation_duration_index != -1 &&
animation_duration_index != current_animation_duration_index) {
animation_duration += kItemBoundsAnimationOffsetDuration;
}
animation_duration_index = current_animation_duration_index;
if (view->has_pending_row_change()) {
view->EnsureLayer();
view->reset_has_pending_row_change();
if (!animation) {
animation = init_animation();
}
animation->GetCurrentSequence()
.At(base::TimeDelta())
.SetDuration(animation_duration);
row_change_animator_->AnimateBetweenRows(
view, current_bounds, target_bounds,
&animation->GetCurrentSequence());
} else {
view->EnsureLayer();
if (IsAnimatingView(view)) {
current_bounds =
ApplyTransformAtOrigin(current_bounds, view->layer()->transform());
}
gfx::Transform transform =
gfx::TransformBetweenRects(gfx::RectF(GetMirroredRect(target_bounds)),
gfx::RectF(current_bounds));
view->layer()->SetTransform(transform);
view->SetBoundsRect(target_bounds);
if (!animation) {
animation = init_animation();
}
animation->GetCurrentSequence()
.At(base::TimeDelta())
.SetDuration(animation_duration)
.SetTransform(view->layer(), gfx::Transform(),
gfx::Tween::ACCEL_40_DECEL_100_3);
}
}
}
bool AppsGridView::WillAnimateMultipleRows() {
for (size_t i = 0; i < view_model_.view_size(); ++i) {
if (GetItemViewAt(i)->has_pending_row_change()) {
return true;
}
}
return false;
}
void AppsGridView::ExtractDragLocation(const gfx::Point& root_location,
gfx::Point* drag_point) {
*drag_point = root_location;
DCHECK(GetWidget());
aura::Window::ConvertPointToTarget(
GetWidget()->GetNativeWindow()->GetRootWindow(),
GetWidget()->GetNativeWindow(), drag_point);
views::View::ConvertPointFromWidget(this, drag_point);
}
void AppsGridView::UpdateDropTargetRegion() {
DCHECK(drag_item_);
gfx::Point point = last_drag_point_;
point.set_x(GetMirroredXInView(point.x()));
if (IsPointWithinDragBuffer(point)) {
if (DragPointIsOverItem(point)) {
drop_target_region_ = ON_ITEM;
drop_target_ = GetNearestTileIndexForPoint(point);
return;
}
UpdateDropTargetForReorder(point);
drop_target_region_ = DragIsCloseToItem(point) ? NEAR_ITEM : BETWEEN_ITEMS;
return;
}
if (IsDraggingForReparentInRootLevelGridView()) {
drop_target_region_ = NO_TARGET;
return;
}
drop_target_ = drag_view_init_index_;
drop_target_region_ = DragIsCloseToItem(point) ? NEAR_ITEM : BETWEEN_ITEMS;
}
bool AppsGridView::DropTargetIsValidFolder() {
AppListItemView* target_view =
GetViewDisplayedAtSlotOnCurrentPage(drop_target_.slot);
if (!target_view) {
return false;
}
AppListItem* target_item = target_view->item();
if (target_item->IsFolderFull() || IsOEMFolderItem(target_item)) {
return false;
}
if (!IsValidIndex(drop_target_)) {
return false;
}
return true;
}
bool AppsGridView::DragPointIsOverItem(const gfx::Point& point) {
GridIndex nearest_tile_index(GetNearestTileIndexForPoint(point));
if (!IsValidIndex(nearest_tile_index) ||
nearest_tile_index == reorder_placeholder_) {
return false;
}
int distance_to_tile_center =
(point - GetExpectedTileBounds(nearest_tile_index).CenterPoint())
.Length();
if (distance_to_tile_center >
(app_list_config_->folder_bubble_radius() *
(cardified_state_ ? GetAppsGridCardifiedScale() : 1.0f))) {
return false;
}
return true;
}
void AppsGridView::AnimateDragIconToTargetPosition(
AppListItem* drag_item,
const std::string& target_folder_id) {
if (!drag_image_layer_) {
OnDragIconDropDone();
return;
}
AppListItemView* target_folder_view =
!target_folder_id.empty() ? GetItemViewForItem(target_folder_id)
: nullptr;
gfx::Rect drag_icon_drop_bounds;
if (target_folder_id.empty()) {
for (size_t i = 0; i < view_model_.view_size(); ++i) {
if (view_model_.view_at(i)->item() != drag_item) {
continue;
}
const gfx::Rect drag_view_ideal_bounds = view_model_.ideal_bounds(i);
drag_icon_drop_bounds = AppListItemView::GetIconBoundsForTargetViewBounds(
app_list_config_, drag_view_ideal_bounds,
drag_item->is_folder() ? app_list_config_->folder_icon_size()
: app_list_config_->grid_icon_size(),
1.0f);
break;
}
} else if (target_folder_view) {
drag_icon_drop_bounds =
GetTargetIconRectInFolder(drag_item, target_folder_view);
}
if (drag_icon_drop_bounds.IsEmpty()) {
OnDragIconDropDone();
return;
}
if (target_folder_view) {
DCHECK(target_folder_view->is_folder());
folder_icon_item_hider_ =
std::make_unique<FolderIconItemHider>(target_folder_view, drag_item);
}
drag_icon_drop_bounds =
items_container_->GetMirroredRect(drag_icon_drop_bounds);
views::View::ConvertRectToScreen(items_container_, &drag_icon_drop_bounds);
wm::ConvertRectFromScreen(
items_container_->GetWidget()->GetNativeWindow()->GetRootWindow(),
&drag_icon_drop_bounds);
ui::Layer* target_layer = drag_image_layer_->root();
if (target_layer) {
target_layer->GetAnimator()->AbortAllAnimations();
gfx::Rect current_bounds = target_layer->bounds();
if (current_bounds.IsEmpty()) {
OnDragIconDropDone();
return;
}
ui::ScopedLayerAnimationSettings animation_settings(
target_layer->GetAnimator());
animation_settings.SetTweenType(gfx::Tween::FAST_OUT_LINEAR_IN);
animation_settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET);
animation_settings.AddObserver(
new AnimationObserverToRestoreGrid(base::BindOnce(
&AppsGridView::OnDragIconDropDone, weak_factory_.GetWeakPtr())));
target_layer->SetTransform(gfx::TransformBetweenRects(
gfx::RectF(current_bounds), gfx::RectF(drag_icon_drop_bounds)));
}
}
void AppsGridView::OnDragIconDropDone() {
drag_view_hider_.reset();
folder_icon_item_hider_.reset();
drag_image_layer_.reset();
DestroyLayerItemsIfNotNeeded();
if (!folder_to_open_after_drag_icon_animation_.empty()) {
AppListItemView* folder_view =
GetItemViewForItem(folder_to_open_after_drag_icon_animation_);
folder_to_open_after_drag_icon_animation_.clear();
ShowFolderForView(folder_view, true);
}
}
bool AppsGridView::DraggedItemCanEnterFolder() {
if (!IsFolderItem(drag_item_) && !folder_delegate_) {
return true;
}
return false;
}
void AppsGridView::UpdateDropTargetForReorder(const gfx::Point& point) {
gfx::Rect bounds = GetContentsBounds();
bounds.Inset(GetTilePadding(GetSelectedPage()));
GridIndex nearest_tile_index = GetNearestTileIndexForPoint(point);
gfx::Point reorder_placeholder_center =
GetExpectedTileBounds(reorder_placeholder_).CenterPoint();
int x_offset_direction = 0;
if (nearest_tile_index == reorder_placeholder_) {
x_offset_direction = reorder_placeholder_center.x() <= point.x() ? -1 : 1;
} else {
x_offset_direction = reorder_placeholder_ < nearest_tile_index ? -1 : 1;
}
const gfx::Size total_tile_size = GetTotalTileSize(GetSelectedPage());
int row = nearest_tile_index.slot / cols_;
int x_offset = x_offset_direction *
(total_tile_size.width() / 2 -
app_list_config_->folder_bubble_radius() *
(cardified_state_ ? GetAppsGridCardifiedScale() : 1.0f));
const int selected_page = GetSelectedPage();
int col = (point.x() - bounds.x() + x_offset -
GetGridCenteringOffset(selected_page).x()) /
total_tile_size.width();
col = std::clamp(col, 0, cols_ - 1);
GridIndex max_target_index;
if (selected_page == GetTotalPages() - 1) {
max_target_index = GetGridIndexFromIndexInViewModel(
view_model()->view_size() -
(HasExtraSlotForReorderPlaceholder() ? 0 : 1));
} else {
max_target_index =
GridIndex(selected_page, *TilesPerPage(selected_page) - 1);
}
drop_target_ =
std::min(GridIndex(selected_page, row * cols_ + col), max_target_index);
DCHECK(IsValidIndex(drop_target_))
<< drop_target_.ToString() << " selected page " << selected_page
<< " row " << row << " col " << col << " " << max_target_index.ToString();
}
bool AppsGridView::DragIsCloseToItem(const gfx::Point& point) {
DCHECK(drag_item_);
GridIndex nearest_tile_index = GetNearestTileIndexForPoint(point);
if (nearest_tile_index == reorder_placeholder_) {
return false;
}
const int distance_to_tile_center =
(point - GetExpectedTileBounds(nearest_tile_index).CenterPoint())
.Length();
const int forty_percent_icon_spacing =
(app_list_config_->grid_tile_width() + horizontal_tile_padding_ * 2) *
0.4 * (cardified_state_ ? GetAppsGridCardifiedScale() : 1.0f);
const int double_icon_radius =
app_list_config_->folder_bubble_radius() * 2 *
(cardified_state_ ? GetAppsGridCardifiedScale() : 1.0f);
const int minimum_drag_distance_for_reorder =
std::min(forty_percent_icon_spacing, double_icon_radius);
if (distance_to_tile_center < minimum_drag_distance_for_reorder) {
return true;
}
return false;
}
void AppsGridView::OnReorderTimer() {
const GridIndex before_index = reorder_placeholder_;
reorder_placeholder_ = drop_target_;
const GridIndex after_index = reorder_placeholder_;
MaybeCreateDragReorderAccessibilityEvent();
AnimateToIdealBounds(before_index < after_index);
CreateGhostImageView();
}
void AppsGridView::OnFolderItemReparentTimer(Pointer pointer) {
DCHECK(folder_delegate_);
if (drag_out_of_folder_container_ && drag_view_) {
dragging_for_reparent_item_ = true;
item_list_->RemoveObserver(this);
item_list_ = nullptr;
}
}
void AppsGridView::UpdateDragStateInsideFolder(Pointer pointer,
const gfx::Point& drag_point) {
if (IsUnderOEMFolder()) {
return;
}
bool is_item_dragged_out_of_folder =
folder_delegate_->IsDragPointOutsideOfFolder(drag_point);
if (is_item_dragged_out_of_folder) {
if (!drag_out_of_folder_container_) {
folder_item_reparent_timer_.Start(
FROM_HERE, base::Milliseconds(kFolderItemReparentDelay),
base::BindOnce(&AppsGridView::OnFolderItemReparentTimer,
base::Unretained(this), pointer));
drag_out_of_folder_container_ = true;
}
} else {
folder_item_reparent_timer_.Stop();
drag_out_of_folder_container_ = false;
}
}
bool AppsGridView::IsDraggingForReparentInRootLevelGridView() const {
return (!folder_delegate_ && dragging_for_reparent_item_);
}
bool AppsGridView::IsDraggingForReparentInHiddenGridView() const {
return (folder_delegate_ && dragging_for_reparent_item_);
}
gfx::Rect AppsGridView::GetTargetIconRectInFolder(
AppListItem* drag_item,
AppListItemView* folder_item_view) {
const gfx::Rect view_ideal_bounds = view_model_.ideal_bounds(
view_model_.GetIndexOfView(folder_item_view).value());
const gfx::Rect icon_ideal_bounds =
folder_item_view->GetIconBoundsForTargetViewBounds(
app_list_config_, view_ideal_bounds,
folder_item_view->GetDragImage().size(), 1.0f);
AppListFolderItem* folder_item = folder_item_view->item()->AsFolderItem();
return folder_item->GetTargetIconRectInFolderForItem(
*app_list_config_, drag_item, icon_ideal_bounds);
}
bool AppsGridView::IsUnderOEMFolder() {
if (!folder_delegate_) {
return false;
}
return folder_delegate_->IsOEMFolder();
}
void AppsGridView::HandleKeyboardAppOperations(ui::KeyboardCode key_code,
bool folder) {
DCHECK(selected_view_);
if (drag_view_) {
return;
}
if (folder) {
if (folder_delegate_)
folder_delegate_->HandleKeyboardReparent(selected_view_, key_code);
else
HandleKeyboardFoldering(key_code);
} else {
HandleKeyboardMove(key_code);
}
}
void AppsGridView::HandleKeyboardFoldering(ui::KeyboardCode key_code) {
const GridIndex source_index = GetIndexOfView(selected_view_);
const GridIndex target_index = GetTargetGridIndexForKeyboardMove(key_code);
if (!CanMoveSelectedToTargetForKeyboardFoldering(target_index)) {
return;
}
const std::u16string moving_view_title(selected_view_->title()->GetText());
AppListItemView* target_view =
GetViewDisplayedAtSlotOnCurrentPage(target_index.slot);
const std::u16string target_view_title(target_view->title()->GetText());
const bool target_view_is_folder = target_view->is_folder();
std::string folder_id;
bool is_new_folder = false;
if (MoveItemToFolder(selected_view_->item(), target_index,
kMoveByKeyboardIntoFolder, &folder_id, &is_new_folder)) {
a11y_announcer_->AnnounceKeyboardFoldering(
moving_view_title, target_view_title, target_view_is_folder);
AppListItemView* folder_view = GetItemViewForItem(folder_id);
if (folder_view) {
if (is_new_folder) {
SetOpenFolderInfo(folder_id, target_index, source_index);
ShowFolderForView(folder_view, true);
} else {
folder_view->RequestFocus();
}
}
DeprecatedLayoutImmediately();
UpdatePaging();
}
}
bool AppsGridView::CanMoveSelectedToTargetForKeyboardFoldering(
const GridIndex& target_index) const {
DCHECK(selected_view_);
const AppListItem* selected_item = selected_view_->item();
if (selected_item->is_folder()) {
return false;
}
if (target_index.page != GetIndexOfView(selected_view_).page) {
return false;
}
return true;
}
bool AppsGridView::HandleVerticalFocusMovement(bool arrow_up) {
views::View* focused = GetFocusManager()->GetFocusedView();
if (!views::IsViewClass<AppListItemView>(focused)) {
return false;
}
const GridIndex source_index =
GetIndexOfView(static_cast<const AppListItemView*>(focused));
int target_page = source_index.page;
int target_row = source_index.slot / cols_ + (arrow_up ? -1 : 1);
int target_col = source_index.slot % cols_;
if (target_row < 0) {
--target_page;
target_row = (GetNumberOfItemsOnPage(target_page) - 1) / cols_;
} else if (target_row > (GetNumberOfItemsOnPage(target_page) - 1) / cols_) {
++target_page;
target_row = 0;
}
if (target_page < 0) {
if (keyboard_controller_ &&
keyboard_controller_->MoveFocusUpFromAppsGrid(target_col)) {
return true;
}
views::View* v = GetFocusManager()->GetNextFocusableView(
view_model_.view_at(0), nullptr, true,
false);
DCHECK(v);
v->RequestFocus();
return true;
}
if (target_page >= GetTotalPages()) {
views::View* v = GetFocusManager()->GetNextFocusableView(
view_model_.view_at(view_model_.view_size() - 1),
nullptr, false,
false);
DCHECK(v);
v->RequestFocus();
return true;
}
GridIndex target_index(target_page, target_row * cols_ + target_col);
target_index.slot =
std::min(GetNumberOfItemsOnPage(target_page) - 1, target_index.slot);
if (IsValidIndex(target_index)) {
GetViewAtIndex(target_index)->RequestFocus();
return true;
}
return false;
}
void AppsGridView::UpdateColsAndRowsForFolder() {
if (!folder_delegate_) {
return;
}
const int item_count = item_list_ ? item_list_->item_count() : 0;
if (item_count == 0) {
cols_ = 1;
} else {
int preferred_cols = std::sqrt(item_list_->item_count() - 1) + 1;
cols_ = std::clamp(preferred_cols, 1, max_cols_);
}
PreferredSizeChanged();
}
void AppsGridView::EndDragFromReparentItemInRootLevel(
AppListItemView* original_parent_item_view,
bool events_forwarded_to_drag_drop_host,
bool cancel_drag) {
DCHECK(!IsInFolder());
if (!drag_item_) {
return;
}
AppListItem* drag_item = drag_item_;
DCHECK(IsDraggingForReparentInRootLevelGridView());
bool cancel_reparent = cancel_drag || drop_target_region_ == NO_TARGET;
std::string target_folder_id;
const std::string original_folder_id = drag_item_->folder_id();
if (!events_forwarded_to_drag_drop_host && !cancel_reparent) {
UpdateDropTargetRegion();
if (drop_target_region_ == ON_ITEM && DropTargetIsValidFolder() &&
DraggedItemCanEnterFolder()) {
bool is_new_folder = false;
if (MoveItemToFolder(drag_item, drop_target_, kMoveIntoAnotherFolder,
&target_folder_id, &is_new_folder)) {
MaybeCreateFolderDroppingAccessibilityEvent();
DeprecatedLayoutImmediately();
if (is_new_folder) {
folder_to_open_after_drag_icon_animation_ = target_folder_id;
SetOpenFolderInfo(target_folder_id, drop_target_,
reorder_placeholder_);
}
} else {
cancel_reparent = true;
}
} else if (drop_target_region_ != NO_TARGET && IsValidIndex(drop_target_)) {
ReparentItemForReorder(drag_item, drop_target_);
RecordAppMovingTypeMetrics(kMoveByDragOutOfFolder);
MaybeCreateDragReorderAccessibilityEvent();
} else {
NOTREACHED();
}
}
if (cancel_reparent) {
target_folder_id = original_folder_id;
}
SetAsFolderDroppingTarget(drop_target_, false);
const GridIndex before_index = reorder_placeholder_;
const GridIndex after_index = drop_target_;
const bool top_to_bottom_animation = before_index < after_index;
ClearDragState();
UpdatePaging();
if (GetWidget()) {
DCHECK(!ignore_layout_);
base::AutoReset<bool> auto_reset(&ignore_layout_, true);
GetWidget()->LayoutRootViewIfNecessary();
}
if (cardified_state_)
MaybeEndCardifiedView();
else
AnimateToIdealBounds(top_to_bottom_animation);
BeginHideCurrentGhostImageView();
SetFocusAfterEndDrag(drag_item);
AnimateDragIconToTargetPosition(drag_item, target_folder_id);
}
void AppsGridView::EndDragForReparentInHiddenFolderGridView() {
SetAsFolderDroppingTarget(drop_target_, false);
ClearDragState();
BeginHideCurrentGhostImageView();
}
void AppsGridView::HandleKeyboardReparent(
AppListItemView* reparented_view,
AppListItemView* original_parent_item_view,
ui::KeyboardCode key_code) {
DCHECK(key_code == ui::VKEY_LEFT || key_code == ui::VKEY_RIGHT ||
key_code == ui::VKEY_UP || key_code == ui::VKEY_DOWN);
DCHECK(!folder_delegate_);
DCHECK(view_model_.GetIndexOfView(original_parent_item_view).has_value());
const std::string reparented_item_id = reparented_view->item()->id();
SetSelectedView(original_parent_item_view);
const GridIndex target_index = GetTargetGridIndexForKeyboardReparent(
GetIndexOfView(original_parent_item_view), key_code);
ReparentItemForReorder(reparented_view->item(), target_index);
const AppListItem* const item_after_reparent =
item_list_->FindItem(reparented_item_id);
DCHECK(item_after_reparent);
const int final_model_index = GetModelIndexOfItem(item_after_reparent);
const GridIndex final_grid_index =
GetGridIndexFromIndexInViewModel(final_model_index);
UpdatePaging();
DeprecatedLayoutImmediately();
EnsureViewVisible(final_grid_index);
GetViewAtIndex(final_grid_index)->RequestFocus();
AnnounceReorder(final_grid_index);
RecordAppMovingTypeMetrics(kMoveByKeyboardOutOfFolder);
}
bool AppsGridView::IsTabletMode() const {
return app_list_view_delegate_->IsInTabletMode();
}
views::AnimationBuilder AppsGridView::FadeOutVisibleItemsForReorder(
ReorderAnimationCallback done_callback) {
DCHECK(!IsUnderWholeGridAnimation());
CancelAllItemAnimations();
grid_animation_status_ = AppListGridAnimationStatus::kReorderFadeOut;
reorder_animation_tracker_.emplace(
layer()->GetCompositor()->RequestNewCompositorMetricsTracker());
reorder_animation_tracker_->Start(metrics_util::ForSmoothnessV3(
base::BindRepeating(&ReportReorderAnimationSmoothness, IsTabletMode())));
views::AnimationBuilder animation_builder;
grid_animation_abort_handle_ = animation_builder.GetAbortHandle();
if (fade_out_start_closure_for_test_)
animation_builder.OnStarted(std::move(fade_out_start_closure_for_test_));
animation_builder
.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
.OnEnded(base::BindOnce(&AppsGridView::OnFadeOutAnimationEnded,
weak_factory_.GetWeakPtr(), done_callback,
false))
.OnAborted(base::BindOnce(&AppsGridView::OnFadeOutAnimationEnded,
weak_factory_.GetWeakPtr(), done_callback,
true))
.Once()
.SetDuration(kFadeOutAnimationDuration)
.SetOpacity(layer(), 0.f, gfx::Tween::LINEAR);
return animation_builder;
}
views::AnimationBuilder AppsGridView::FadeInVisibleItemsForReorder(
ReorderAnimationCallback done_callback) {
DCHECK_EQ(AppListGridAnimationStatus::kReorderIntermediaryState,
grid_animation_status_);
DCHECK(!IsItemAnimationRunning());
if (needs_layout()) {
DeprecatedLayoutImmediately();
}
grid_animation_status_ = AppListGridAnimationStatus::kReorderFadeIn;
const std::optional<VisibleItemIndexRange> range = GetVisibleItemIndexRange();
views::AnimationBuilder animation_builder;
if (!range) {
animation_builder
.OnEnded(base::BindOnce(&AppsGridView::OnFadeInAnimationEnded,
weak_factory_.GetWeakPtr(), done_callback,
true))
.OnAborted(base::BindOnce(&AppsGridView::OnFadeInAnimationEnded,
weak_factory_.GetWeakPtr(), done_callback,
true))
.Once()
.SetDuration(base::TimeDelta());
return animation_builder;
}
for (size_t visible_view_index = range->first_index;
visible_view_index <= range->last_index; ++visible_view_index) {
view_model_.view_at(visible_view_index)->SetVisible(true);
}
grid_animation_abort_handle_ = animation_builder.GetAbortHandle();
animation_builder
.OnEnded(base::BindOnce(&AppsGridView::OnFadeInAnimationEnded,
weak_factory_.GetWeakPtr(), done_callback,
false))
.OnAborted(base::BindOnce(&AppsGridView::OnFadeInAnimationEnded,
weak_factory_.GetWeakPtr(), done_callback,
true))
.Once()
.SetDuration(kFadeInAnimationDuration)
.SetOpacity(layer(), 1.f, gfx::Tween::ACCEL_5_70_DECEL_90);
const int page_index =
GetGridIndexFromIndexInViewModel(range->first_index).page;
const int base_offset =
kFadeAnimationOffsetRatio * GetTotalTileSize(page_index).height();
const int base_row = range->first_index / cols_;
for (size_t visible_view_index = range->first_index;
visible_view_index <= range->last_index; ++visible_view_index) {
const int relative_row_index = visible_view_index / cols_ - base_row;
const int offset = (relative_row_index + 2) * base_offset;
views::View* animated_view = GetItemViewAt(visible_view_index);
PrepareForLayerAnimation(animated_view);
SlideViewIntoPositionWithSequenceBlock(
animated_view, offset,
std::nullopt, gfx::Tween::ACCEL_5_70_DECEL_90,
&animation_builder.GetCurrentSequence());
}
return animation_builder;
}
void AppsGridView::SlideVisibleItemsForHideContinueSection(int base_offset) {
DCHECK(IsTabletMode());
if (needs_layout()) {
DeprecatedLayoutImmediately();
}
const std::optional<VisibleItemIndexRange> range = GetVisibleItemIndexRange();
if (!range) {
return;
}
if (GetGridIndexFromIndexInViewModel(range->first_index).page != 0) {
return;
}
grid_animation_status_ = AppListGridAnimationStatus::kHideContinueSection;
views::AnimationBuilder animation_builder;
grid_animation_abort_handle_ = animation_builder.GetAbortHandle();
animation_builder
.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
.OnEnded(
base::BindOnce(&AppsGridView::OnHideContinueSectionAnimationEnded,
weak_factory_.GetWeakPtr()))
.OnAborted(
base::BindOnce(&AppsGridView::OnHideContinueSectionAnimationEnded,
weak_factory_.GetWeakPtr()))
.Once()
.SetDuration(base::Milliseconds(300));
for (size_t item_index = range->first_index; item_index <= range->last_index;
++item_index) {
const int row_index = item_index / cols_;
const int vertical_offset = std::max(0, base_offset * (3 - row_index) / 4);
views::View* icon = GetItemViewAt(item_index);
PrepareForLayerAnimation(icon);
SlideViewIntoPositionWithSequenceBlock(
icon, vertical_offset, std::nullopt,
gfx::Tween::ACCEL_LIN_DECEL_100_3,
&animation_builder.GetCurrentSequence());
}
}
void AppsGridView::OnHideContinueSectionAnimationEnded() {
grid_animation_status_ = AppListGridAnimationStatus::kEmpty;
DestroyLayerItemsIfNotNeeded();
}
bool AppsGridView::IsItemAnimationRunning() const {
for (size_t i = 0; i < view_model_.view_size(); ++i) {
AppListItemView* view = GetItemViewAt(i);
if (IsAnimatingView(view)) {
return true;
}
}
return false;
}
void AppsGridView::CancelAllItemAnimations() {
std::vector<ui::Layer*> item_layers;
for (size_t i = 0; i < view_model_.view_size(); ++i) {
AppListItemView* view = GetItemViewAt(i);
if (IsAnimatingView(view)) {
item_layers.push_back(view->layer());
}
}
for (auto* layer : item_layers) {
layer->GetAnimator()->StopAnimating();
}
}
void AppsGridView::AddReorderCallbackForTest(
TestReorderDoneCallbackType done_callback) {
DCHECK(done_callback);
reorder_animation_callback_queue_for_test_.push(std::move(done_callback));
}
void AppsGridView::AddFadeOutAnimationStartClosureForTest(
base::OnceClosure start_closure) {
DCHECK(start_closure);
DCHECK(!fade_out_done_closure_for_test_);
fade_out_start_closure_for_test_ = std::move(start_closure);
}
void AppsGridView::AddFadeOutAnimationDoneClosureForTest(
base::OnceClosure done_closure) {
DCHECK(done_closure);
DCHECK(!fade_out_done_closure_for_test_);
fade_out_done_closure_for_test_ = std::move(done_closure);
}
bool AppsGridView::HasAnyWaitingReorderDoneCallbackForTest() const {
return !reorder_animation_callback_queue_for_test_.empty();
}
void AppsGridView::MoveItemInModel(AppListItem* item, const GridIndex& target) {
const std::string item_id = item->id();
size_t current_item_list_index = 0;
bool found = item_list_->FindItemIndex(item_id, ¤t_item_list_index);
CHECK(found);
size_t target_item_list_index = GetIndexInViewModel(target);
{
ScopedModelUpdate update(this);
item_list_->MoveItem(current_item_list_index, target_item_list_index);
}
}
bool AppsGridView::MoveItemToFolder(AppListItem* item,
const GridIndex& target,
AppListAppMovingType move_type,
std::string* folder_id,
bool* is_new_folder) {
const std::string source_item_id = item->id();
const std::string source_folder_id = item->folder_id();
AppListItemView* target_view =
GetViewDisplayedAtSlotOnCurrentPage(target.slot);
DCHECK(target_view);
const std::string target_view_item_id = target_view->item()->id();
if (target_view_item_id == source_folder_id) {
return false;
}
*is_new_folder = !target_view->is_folder();
{
ScopedModelUpdate update(this);
*folder_id = model_->MergeItems(target_view_item_id, source_item_id);
}
if (folder_id->empty()) {
LOG(ERROR) << "Unable to merge into item id: " << target_view_item_id;
return false;
}
if (*is_new_folder)
base::RecordAction(base::UserMetricsAction("AppList_CreateFolder"));
MaybeRecordFolderDeleteUserAction(source_folder_id);
RecordAppMovingTypeMetrics(move_type);
return true;
}
void AppsGridView::ReparentItemForReorder(AppListItem* item,
const GridIndex& target) {
DCHECK(item->IsInFolder());
const std::string item_id = item->id();
const std::string source_folder_id = item->folder_id();
int target_item_index = GetIndexInViewModel(target);
syncer::StringOrdinal target_position;
if (target_item_index < static_cast<int>(item_list_->item_count()))
target_position = item_list_->item_at(target_item_index)->position();
{
ScopedModelUpdate update(this);
model_->MoveItemToRootAt(item, target_position);
}
MaybeRecordFolderDeleteUserAction(source_folder_id);
}
void AppsGridView::MaybeRecordFolderDeleteUserAction(
const std::string& folder_id) {
if (folder_id.empty()) {
return;
}
if (!model_->FindFolderItem(folder_id))
base::RecordAction(base::UserMetricsAction("AppList_DeleteFolder"));
}
void AppsGridView::CancelContextMenusOnCurrentPage() {
GridIndex start_index(GetSelectedPage(), 0);
if (!IsValidIndex(start_index)) {
return;
}
const size_t start = GetIndexInViewModel(start_index);
const std::optional<int> tiles_per_page = TilesPerPage(start_index.page);
const size_t end = tiles_per_page ? std::min(view_model_.view_size(),
start + *tiles_per_page)
: view_model_.view_size();
for (size_t i = start; i < end; ++i) {
GetItemViewAt(i)->CancelContextMenu();
}
}
void AppsGridView::DeleteItemViewAtIndex(size_t index) {
AppListItemView* item_view = GetItemViewAt(index);
view_model_.Remove(index);
if (item_view == drag_view_) {
drag_view_ = nullptr;
}
if (open_folder_info_ &&
open_folder_info_->item_id == item_view->item()->id()) {
open_folder_info_.reset();
}
delete item_view;
}
bool AppsGridView::IsPointWithinDragBuffer(const gfx::Point& point) const {
gfx::Rect rect(GetLocalBounds());
rect.Inset(-kDragBufferPx);
return rect.Contains(point);
}
void AppsGridView::ScheduleLayout(const gfx::Size& previous_grid_size) {
if (GetTileGridSize() != previous_grid_size) {
PreferredSizeChanged();
} else {
InvalidateLayout();
}
DCHECK(needs_layout());
}
void AppsGridView::OnListItemAdded(size_t index, AppListItem* item) {
const gfx::Size initial_grid_size = GetTileGridSize();
if (!updating_model_) {
EndDrag(true);
}
MaybeAbortWholeGridAnimation();
AppListItemView* view = MaybeSwapPlaceholderAsset(index);
if (item == drag_item_) {
drag_view_ = view;
drag_view_hider_ = std::make_unique<DragViewHider>(drag_view_);
}
view->InitializeIconLoader();
if (!updating_model_) {
UpdatePaging();
UpdateColsAndRowsForFolder();
UpdatePulsingBlockViews();
}
ScheduleLayout(initial_grid_size);
items_container_->NotifyAccessibilityEventDeprecated(
ax::mojom::Event::kChildrenChanged,
true);
if (item->GetMetadata()->app_status == AppStatus::kReady) {
std::string package_name = view->item()->GetMetadata()->promise_package_id;
auto found = pending_promise_apps_removals_.find(package_name);
if (found != pending_promise_apps_removals_.end()) {
view->AnimateInFromPromiseApp(
found->second,
base::BindRepeating(&AppsGridView::FinishAnimationForPromiseApps,
weak_factory_.GetWeakPtr(),
std::move(package_name)));
}
}
}
void AppsGridView::FinishAnimationForPromiseApps(
const std::string& pending_app_id) {
PendingAppsMap::iterator pending_app_found =
pending_promise_apps_removals_.find(pending_app_id);
if (pending_app_found != pending_promise_apps_removals_.end()) {
auto pending_app_scope(std::move(pending_app_found->second));
pending_promise_apps_removals_.erase(pending_app_found);
}
DestroyLayerItemsIfNotNeeded();
}
void AppsGridView::OnListItemRemoved(size_t index, AppListItem* item) {
const gfx::Size initial_grid_size = GetTileGridSize();
if (!updating_model_) {
EndDrag(true);
}
MaybeDuplicatePromiseAppForRemoval(GetItemViewAt(index));
MaybeAbortWholeGridAnimation();
DeleteItemViewAtIndex(GetModelIndexOfItem(item));
if (!updating_model_) {
UpdatePaging();
UpdateColsAndRowsForFolder();
UpdatePulsingBlockViews();
}
ScheduleLayout(initial_grid_size);
items_container_->NotifyAccessibilityEventDeprecated(
ax::mojom::Event::kChildrenChanged,
true);
}
void AppsGridView::MaybeDuplicatePromiseAppForRemoval(
AppListItemView* promise_app_view) {
if (!promise_app_view || !promise_app_view->is_promise_app()) {
return;
}
AppListItem* item = promise_app_view->item();
if (item->app_status() != AppStatus::kInstallSuccess ||
!promise_app_view->IsDrawn()) {
return;
}
bool existing_app_in_grid = false;
for (const auto& entry : view_model_.entries()) {
AppListItemView* view = views::AsViewClass<AppListItemView>(entry.view);
if (view == promise_app_view) {
continue;
}
if (view->item()->GetMetadata()->promise_package_id == item->id()) {
existing_app_in_grid = true;
break;
}
}
if (!existing_app_in_grid) {
AddPendingPromiseAppRemoval(item->id(),
promise_app_view->icon_image_model());
}
}
void AppsGridView::OnListItemMoved(size_t from_index,
size_t to_index,
AppListItem* item) {
if (!updating_model_) {
MaybeAbortWholeGridAnimation();
EndDrag(true);
}
size_t from_model_index = GetModelIndexOfItem(item);
view_model_.Move(from_model_index, to_index);
items_container_->ReorderChildView(view_model_.view_at(to_index), to_index);
items_container_->NotifyAccessibilityEventDeprecated(
ax::mojom::Event::kChildrenChanged, true );
if (!updating_model_) {
UpdatePaging();
UpdateColsAndRowsForFolder();
UpdatePulsingBlockViews();
}
if (!updating_model_ && GetWidget() && GetWidget()->IsVisible() &&
enable_item_move_animation_) {
AnimateToIdealBounds(from_index < to_index);
} else if (IsUnderWholeGridAnimation()) {
InvalidateLayout();
} else {
DeprecatedLayoutImmediately();
}
}
void AppsGridView::AddPendingPromiseAppRemoval(
const std::string& id,
const ui::ImageModel& default_image) {
auto found = pending_promise_apps_removals_.find(id);
if (found != pending_promise_apps_removals_.end()) {
return;
}
pending_promise_apps_removals_.emplace(id, default_image);
}
void AppsGridView::OnAppListModelStatusChanged() {
UpdatePulsingBlockViews();
InvalidateLayout();
}
void AppsGridView::DestroyLayerItemsIfNotNeeded() {
if (ItemViewsRequireLayers()) {
return;
}
for (const auto& entry : view_model_.entries()) {
AppListItemView* view = views::AsViewClass<AppListItemView>(entry.view);
row_change_animator_->CancelAnimation(view);
if (!view->AlwaysPaintsToLayer()) {
view->DestroyLayer();
}
}
}
bool AppsGridView::ItemViewsRequireLayers() const {
if (drag_item_ || drag_image_layer_) {
return true;
}
if (IsItemAnimationRunning()) {
return true;
}
if (IsUnderWholeGridAnimation()) {
return true;
}
if (reordering_folder_view_) {
return true;
}
if (setting_up_ideal_bounds_animation_) {
return true;
}
if (IsAnimatingCardifiedState()) {
return true;
}
return false;
}
GridIndex AppsGridView::GetNearestTileIndexForPoint(
const gfx::Point& point) const {
gfx::Rect bounds = GetContentsBounds();
const int current_page = GetSelectedPage();
bounds.Inset(GetTilePadding(current_page));
const gfx::Size total_tile_size = GetTotalTileSize(current_page);
const gfx::Vector2d grid_offset = GetGridCenteringOffset(current_page);
DCHECK_GT(total_tile_size.width(), 0);
int col = std::clamp(
(point.x() - bounds.x() - grid_offset.x()) / total_tile_size.width(), 0,
cols_ - 1);
DCHECK_GT(total_tile_size.height(), 0);
const int ideal_row =
(point.y() - bounds.y() - grid_offset.y()) / total_tile_size.height();
const std::optional<int> tiles_per_page = TilesPerPage(current_page);
const int row = tiles_per_page
? std::clamp(ideal_row, 0, *tiles_per_page / cols_ - 1)
: std::max(ideal_row, 0);
return GridIndex(current_page, row * cols_ + col);
}
gfx::Rect AppsGridView::GetExpectedTileBounds(const GridIndex& index) const {
if (!cols_) {
return gfx::Rect();
}
gfx::Rect bounds(GetContentsBounds());
bounds.Inset(GetTilePadding(index.page));
int row = index.slot / cols_;
int col = index.slot % cols_;
const gfx::Size total_tile_size = GetTotalTileSize(index.page);
gfx::Rect tile_bounds(gfx::Point(bounds.x() + col * total_tile_size.width(),
bounds.y() + row * total_tile_size.height()),
total_tile_size);
tile_bounds.Offset(GetGridCenteringOffset(index.page));
tile_bounds.Inset(-GetTilePadding(index.page));
return tile_bounds;
}
bool AppsGridView::IsViewHiddenForDrag(const views::View* view) const {
return drag_view_hider_ && drag_view_hider_->drag_view() == view;
}
bool AppsGridView::IsViewHiddenForFolderReorder(const views::View* view) const {
return reordering_folder_view_ && *reordering_folder_view_ == view;
}
bool AppsGridView::IsUnderWholeGridAnimation() const {
return grid_animation_status_ != AppListGridAnimationStatus::kEmpty;
}
bool AppsGridView::IsViewExplicitlyHidden(const views::View* view) const {
return IsViewHiddenForDrag(view) || IsViewHiddenForFolderReorder(view) ||
hidden_view_for_test_ == view;
}
void AppsGridView::MaybeAbortWholeGridAnimation() {
switch (grid_animation_status_) {
case AppListGridAnimationStatus::kEmpty:
case AppListGridAnimationStatus::kReorderIntermediaryState:
break;
case AppListGridAnimationStatus::kReorderFadeOut:
case AppListGridAnimationStatus::kReorderFadeIn:
case AppListGridAnimationStatus::kHideContinueSection:
DCHECK(grid_animation_abort_handle_);
grid_animation_abort_handle_.reset();
break;
}
}
AppListItemView* AppsGridView::GetViewDisplayedAtSlotOnCurrentPage(
int slot) const {
if (slot < 0) {
return nullptr;
}
gfx::Rect tile_rect =
GetExpectedTileBounds(GridIndex(GetSelectedPage(), slot));
tile_rect.Offset(CalculateTransitionOffset(GetSelectedPage()));
const auto& entries = view_model_.entries();
const auto iter = std::ranges::find_if(entries, [&](const auto& entry) {
return entry.view->bounds() == tile_rect && entry.view.get() != drag_view_;
});
return iter == entries.end() ? nullptr
: static_cast<AppListItemView*>(iter->view);
}
void AppsGridView::SetAsFolderDroppingTarget(const GridIndex& target_index,
bool is_target_folder) {
AppListItemView* target_view =
GetViewDisplayedAtSlotOnCurrentPage(target_index.slot);
if (target_view) {
target_view->SetAsAttemptedFolderTarget(is_target_folder);
if (is_target_folder)
target_view->OnDraggedViewEnter();
else
target_view->OnDraggedViewExit();
}
}
GridIndex AppsGridView::GetTargetGridIndexForKeyboardMove(
ui::KeyboardCode key_code) const {
DCHECK(key_code == ui::VKEY_LEFT || key_code == ui::VKEY_RIGHT ||
key_code == ui::VKEY_UP || key_code == ui::VKEY_DOWN);
DCHECK(selected_view_);
const GridIndex source_index = GetIndexOfView(selected_view_);
if (key_code == ui::VKEY_LEFT || key_code == ui::VKEY_RIGHT) {
const ui::KeyboardCode backward =
base::i18n::IsRTL() ? ui::VKEY_RIGHT : ui::VKEY_LEFT;
size_t target_model_index =
view_model_.GetIndexOfView(selected_view_).value();
if (target_model_index > 0 || key_code != backward)
target_model_index += (key_code == backward) ? -1 : 1;
if (target_model_index == view_model_.view_size()) {
return source_index;
}
return GetIndexOfView(
static_cast<const AppListItemView*>(GetItemViewAt(target_model_index)));
}
int target_page = source_index.page;
int target_row =
source_index.slot / cols_ + (key_code == ui::VKEY_UP ? -1 : 1);
if (target_row < 0) {
--target_page;
if (target_page < 0) {
return source_index;
}
target_row = (GetNumberOfItemsOnPage(target_page) - 1) / cols_;
} else if (target_row > (GetNumberOfItemsOnPage(target_page) - 1) / cols_) {
++target_page;
if (target_page >= GetTotalPages()) {
return source_index;
}
target_row = 0;
}
const int ideal_slot = target_row * cols_ + source_index.slot % cols_;
return GridIndex(
target_page,
std::min(GetNumberOfItemsOnPage(target_page) - 1, ideal_slot));
}
GridIndex AppsGridView::GetTargetGridIndexForKeyboardReparent(
const GridIndex& folder_index,
ui::KeyboardCode key_code) const {
DCHECK(!folder_delegate_) << "Reparenting target calculations occur from the "
"root AppsGridView, not the folder AppsGridView";
const ui::KeyboardCode backward =
base::i18n::IsRTL() ? ui::VKEY_RIGHT : ui::VKEY_LEFT;
if (key_code == backward) {
return folder_index;
}
GridIndex target_index = GetTargetGridIndexForKeyboardMove(key_code);
if (target_index == folder_index &&
(key_code != ui::VKEY_UP && key_code != backward)) {
if (IsPageFull(target_index.page))
return GridIndex(target_index.page + 1, 0);
return GridIndex(target_index.page, target_index.slot + 1);
}
if (target_index.page < folder_index.page) {
return folder_index;
}
if (target_index.page > folder_index.page) {
const std::optional<int> folder_page_size = TilesPerPage(folder_index.page);
DCHECK(folder_page_size);
if (folder_index.slot + 1 < *folder_page_size)
return GridIndex(folder_index.page, folder_index.slot + 1);
}
return target_index;
}
void AppsGridView::HandleKeyboardMove(ui::KeyboardCode key_code) {
DCHECK(selected_view_);
const GridIndex target_index = GetTargetGridIndexForKeyboardMove(key_code);
const GridIndex starting_index = GetIndexOfView(selected_view_);
if (target_index == starting_index || !IsValidIndex(target_index)) {
return;
}
handling_keyboard_move_ = true;
AppListItemView* original_selected_view = selected_view_;
const GridIndex original_selected_view_index =
GetIndexOfView(original_selected_view);
const bool swap_items =
folder_delegate_ || IsPageFull(target_index.page) ||
target_index.page == original_selected_view_index.page;
AppListItemView* target_view = GetViewAtIndex(target_index);
MoveItemInModel(selected_view_->item(), target_index);
if (swap_items) {
DCHECK(target_view);
MoveItemInModel(target_view->item(), original_selected_view_index);
}
int target_page = target_index.page;
if (!folder_delegate_) {
UpdatePaging();
target_page = std::min(GetTotalPages() - 1, target_index.page);
}
DeprecatedLayoutImmediately();
EnsureViewVisible(GridIndex(target_page, target_index.slot));
SetSelectedView(original_selected_view);
AnnounceReorder(target_index);
if (target_index.page != original_selected_view_index.page &&
!folder_delegate_) {
RecordPageSwitcherSource(kMoveAppWithKeyboard);
}
}
bool AppsGridView::IsValidIndex(const GridIndex& index) const {
const std::optional<int> tiles_per_page = TilesPerPage(index.page);
const int extra_valid_slots = HasExtraSlotForReorderPlaceholder() ? 1 : 0;
return index.page >= 0 && index.page < GetTotalPages() && index.slot >= 0 &&
(!tiles_per_page || index.slot < *tiles_per_page) &&
static_cast<size_t>(GetIndexInViewModel(index)) <
view_model_.view_size() + extra_valid_slots;
}
size_t AppsGridView::GetModelIndexOfItem(const AppListItem* item) const {
const auto& entries = view_model_.entries();
const auto iter = std::ranges::find(entries, item, [](const auto& entry) {
return static_cast<AppListItemView*>(entry.view)->item();
});
return static_cast<size_t>(std::distance(entries.begin(), iter));
}
int AppsGridView::GetNumberOfItemsOnPage(int page) const {
if (page < 0 || page >= GetTotalPages()) {
return 0;
}
if (page < GetTotalPages() - 1) {
return *TilesPerPage(page);
}
size_t item_count = view_model_.view_size();
int current_page = 0;
while (current_page < GetTotalPages() - 1) {
std::optional<int> tiles_per_page = TilesPerPage(current_page);
DCHECK(tiles_per_page);
item_count -= *tiles_per_page;
++current_page;
}
return item_count;
}
void AppsGridView::MaybeCreateFolderDroppingAccessibilityEvent() {
if (!drag_item_ || !drag_view_) {
return;
}
if (drop_target_region_ != ON_ITEM || !DropTargetIsValidFolder() ||
IsFolderItem(drag_item_) || folder_delegate_ ||
drop_target_ == last_folder_dropping_a11y_event_location_) {
return;
}
last_folder_dropping_a11y_event_location_ = drop_target_;
last_reorder_a11y_event_location_ = GridIndex();
AppListItemView* drop_view =
GetViewDisplayedAtSlotOnCurrentPage(drop_target_.slot);
DCHECK(drop_view);
a11y_announcer_->AnnounceFolderDrop(drag_view_->title()->GetText(),
drop_view->title()->GetText(),
drop_view->is_folder());
}
void AppsGridView::MaybeCreateDragReorderAccessibilityEvent() {
if (drop_target_region_ == ON_ITEM && !IsFolderItem(drag_item_)) {
return;
}
if (drag_out_of_folder_container_) {
return;
}
if (drop_target_ == GridIndex()) {
return;
}
if (last_reorder_a11y_event_location_ == drop_target_) {
return;
}
last_folder_dropping_a11y_event_location_ = GridIndex();
last_reorder_a11y_event_location_ = drop_target_;
AnnounceReorder(last_reorder_a11y_event_location_);
}
void AppsGridView::AnnounceReorder(const GridIndex& target_index) {
const int row =
((target_index.slot - (target_index.slot % cols_)) / cols_) + 1;
const int col = (target_index.slot % cols_) + 1;
if (!GetMaxRowsInPage(0)) {
a11y_announcer_->AnnounceAppsGridReorder(row, col);
} else {
const int page = target_index.page + 1;
a11y_announcer_->AnnounceAppsGridReorder(page, row, col);
}
}
void AppsGridView::CreateGhostImageView() {
if (!drag_item_) {
return;
}
if (reorder_placeholder_ == current_ghost_location_) {
return;
}
if (GetSelectedPage() != reorder_placeholder_.page) {
BeginHideCurrentGhostImageView();
return;
}
BeginHideCurrentGhostImageView();
current_ghost_location_ = reorder_placeholder_;
if (last_ghost_view_) {
delete last_ghost_view_;
}
last_ghost_view_ = current_ghost_view_;
auto current_ghost_view =
std::make_unique<GhostImageView>(reorder_placeholder_);
gfx::Rect ghost_view_bounds = GetExpectedTileBounds(reorder_placeholder_);
ghost_view_bounds.Offset(
CalculateTransitionOffset(reorder_placeholder_.page));
current_ghost_view->Init(ghost_view_bounds,
app_list_config_->grid_focus_corner_radius());
current_ghost_view_ =
items_container_->AddChildView(std::move(current_ghost_view));
current_ghost_view_->FadeIn();
StackCardsAtBottom();
}
void AppsGridView::BeginHideCurrentGhostImageView() {
current_ghost_location_ = GridIndex();
if (current_ghost_view_) {
current_ghost_view_->FadeOut();
}
}
void AppsGridView::PrepareItemsForBoundsAnimation() {
for (size_t i = 0; i < view_model_.view_size(); ++i)
view_model_.view_at(i)->EnsureLayer();
}
bool AppsGridView::HasExtraSlotForReorderPlaceholder() const {
return reorder_placeholder_.IsValid() && !drag_view_;
}
void AppsGridView::OnAppListItemViewActivated(
AppListItemView* pressed_item_view,
const ui::Event& event) {
if (IsDragging()) {
return;
}
if (IsFolderItem(pressed_item_view->item())) {
DCHECK(folder_controller_);
SetOpenFolderInfo(pressed_item_view->item()->id(),
GetIndexOfView(pressed_item_view), GridIndex());
ShowFolderForView(pressed_item_view, false);
return;
}
base::RecordAction(base::UserMetricsAction("AppList_ClickOnApp"));
RecordAppListByCollectionLaunched(pressed_item_view->item()->collection_id(),
false);
const std::string id = pressed_item_view->item()->id();
const bool is_above_the_fold = IsAboveTheFold(pressed_item_view);
app_list_view_delegate()->ActivateItem(id, event.flags(),
AppListLaunchedFrom::kLaunchedFromGrid,
is_above_the_fold);
}
bool AppsGridView::IsAboveTheFold(AppListItemView* item_view) {
return false;
}
void AppsGridView::OnFadeOutAnimationEnded(ReorderAnimationCallback callback,
bool aborted) {
grid_animation_status_ =
AppListGridAnimationStatus::kReorderIntermediaryState;
layer()->SetTransform(gfx::Transform());
if (aborted) {
layer()->SetOpacity(1.f);
} else {
for (size_t view_index = 0; view_index < view_model_.view_size();
++view_index) {
view_model_.view_at(view_index)->SetVisible(false);
}
}
base::AutoReset auto_reset(&enable_item_move_animation_, false);
callback.Run(aborted);
if (fade_out_done_closure_for_test_)
std::move(fade_out_done_closure_for_test_).Run();
if (aborted) {
grid_animation_status_ = AppListGridAnimationStatus::kEmpty;
MaybeRunNextReorderAnimationCallbackForTest(
true, AppListGridAnimationStatus::kReorderFadeOut);
reorder_animation_tracker_.reset();
}
}
void AppsGridView::OnFadeInAnimationEnded(ReorderAnimationCallback callback,
bool aborted) {
if (aborted) {
layer()->SetOpacity(1.f);
}
for (size_t view_index = 0; view_index < view_model_.view_size();
++view_index) {
view_model_.view_at(view_index)->SetVisible(true);
}
grid_animation_status_ = AppListGridAnimationStatus::kEmpty;
if (!aborted) {
reorder_animation_tracker_->Stop();
}
reorder_animation_tracker_.reset();
DestroyLayerItemsIfNotNeeded();
if (!callback.is_null()) {
callback.Run(aborted);
}
MaybeRunNextReorderAnimationCallbackForTest(
aborted, AppListGridAnimationStatus::kReorderFadeIn);
}
void AppsGridView::MaybeRunNextReorderAnimationCallbackForTest(
bool aborted,
AppListGridAnimationStatus animation_source) {
if (reorder_animation_callback_queue_for_test_.empty()) {
return;
}
TestReorderDoneCallbackType front_callback =
std::move(reorder_animation_callback_queue_for_test_.front());
reorder_animation_callback_queue_for_test_.pop();
std::move(front_callback).Run(aborted, animation_source);
}
void AppsGridView::OnIdealBoundsAnimationDone() {
if (item_reorder_animation_tracker_) {
item_reorder_animation_tracker_->Stop();
item_reorder_animation_tracker_.reset();
}
DestroyLayerItemsIfNotNeeded();
}
BEGIN_METADATA(AppsGridView)
END_METADATA
}