| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifdef UNSAFE_BUFFERS_BUILD |
| // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. |
| #pragma allow_unsafe_buffers |
| #endif |
| |
| #include "ash/wm/overview/overview_window_drag_controller.h" |
| |
| #include <algorithm> |
| |
| #include "ash/display/mouse_cursor_event_filter.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/screen_util.h" |
| #include "ash/shell.h" |
| #include "ash/wm/desks/desk_icon_button.h" |
| #include "ash/wm/desks/desks_util.h" |
| #include "ash/wm/desks/overview_desk_bar_view.h" |
| #include "ash/wm/float/float_controller.h" |
| #include "ash/wm/overview/overview_constants.h" |
| #include "ash/wm/overview/overview_controller.h" |
| #include "ash/wm/overview/overview_grid.h" |
| #include "ash/wm/overview/overview_item.h" |
| #include "ash/wm/overview/overview_item_base.h" |
| #include "ash/wm/overview/overview_item_view.h" |
| #include "ash/wm/overview/overview_session.h" |
| #include "ash/wm/overview/overview_utils.h" |
| #include "ash/wm/overview/scoped_float_container_stacker.h" |
| #include "ash/wm/snap_group/snap_group_metrics.h" |
| #include "ash/wm/splitview/split_view_constants.h" |
| #include "ash/wm/splitview/split_view_drag_indicators.h" |
| #include "ash/wm/splitview/split_view_types.h" |
| #include "ash/wm/splitview/split_view_utils.h" |
| #include "ash/wm/window_positioning_utils.h" |
| #include "ash/wm/window_util.h" |
| #include "ash/wm/wm_constants.h" |
| #include "base/auto_reset.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "chromeos/ui/frame/caption_buttons/snap_controller.h" |
| #include "third_party/abseil-cpp/absl/cleanup/cleanup.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_observer.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/presentation_time_recorder.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/wm/core/coordinate_conversion.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // The amount of distance from the start of drag the item needs to be dragged |
| // vertically for it to be closed on release. |
| constexpr float kDragToCloseDistanceThresholdDp = 160.f; |
| |
| // The minimum distance that will be considered as a drag event. |
| constexpr float kMinimumDragDistanceDp = 5.f; |
| // Items dragged to within |kDistanceFromEdgeDp| of the screen will get snapped |
| // even if they have not moved by |kMinimumDragToSnapDistanceDp|. |
| constexpr float kDistanceFromEdgeDp = 16.f; |
| // The minimum distance that an item must be moved before it is snapped. This |
| // prevents accidental snaps. |
| constexpr float kMinimumDragToSnapDistanceDp = 96.f; |
| |
| // Flings with less velocity than this will not close the dragged item. |
| constexpr float kFlingToCloseVelocityThreshold = 2000.f; |
| constexpr float kItemMinOpacity = 0.4f; |
| |
| // The scale factor used to calculate the minimum side length for the overview |
| // item bounds on the desks bar. |
| constexpr float kScaleFactorForMinimumSideLength = 0.5f; |
| |
| // The minimum vertical overlapped length between the overview item and new desk |
| // button in order to activate the new desk button. |
| constexpr int kVerticalOverlappedLengthToActivateNewDeskButton = 15; |
| |
| // Amount of time we wait to unpause the occlusion tracker after a overview item |
| // is finished dragging. Waits a bit longer than the overview item animation. |
| constexpr base::TimeDelta kOcclusionPauseDurationForDrag = |
| base::Milliseconds(300); |
| |
| constexpr base::TimeDelta kScaleUpNewDeskButtonGracePeriod = |
| base::Milliseconds(500); |
| |
| // The UMA histogram that records presentation time for window dragging |
| // operation in overview mode. |
| constexpr char kOverviewWindowDragHistogram[] = |
| "Ash.Overview.WindowDrag.PresentationTime.TabletMode"; |
| constexpr char kOverviewWindowDragMaxLatencyHistogram[] = |
| "Ash.Overview.WindowDrag.PresentationTime.MaxLatency.TabletMode"; |
| |
| bool g_skip_new_desk_button_scale_up_for_test = false; |
| |
| bool GetVirtualDesksBarEnabled(OverviewItemBase* item) { |
| return desks_util::ShouldDesksBarBeCreated() && |
| item->overview_grid()->desks_bar_view(); |
| } |
| |
| // Returns whether |item|'s window is visible on all desks. |
| bool DraggedItemIsVisibleOnAllDesks(OverviewItemBase* item) { |
| aura::Window* const dragged_window = item->GetWindow(); |
| return dragged_window && |
| desks_util::IsWindowVisibleOnAllWorkspaces(dragged_window); |
| } |
| |
| // Returns the scaled-down size of the dragged item that should be used when |
| // it's dragged over the OverviewDeskBarView that belongs to |overview_grid|. |
| // |window_original_size| is the size of the item's window before it was scaled |
| // up for dragging. |
| gfx::SizeF GetItemSizeWhenOnDesksBar(OverviewGrid* overview_grid, |
| const gfx::SizeF& window_original_size) { |
| DCHECK(overview_grid); |
| const OverviewDeskBarView* desks_bar_view = overview_grid->desks_bar_view(); |
| DCHECK(desks_bar_view); |
| |
| const int expanded_desks_bar_height = DeskBarViewBase::GetPreferredBarHeight( |
| overview_grid->root_window(), DeskBarViewBase::Type::kOverview, |
| DeskBarViewBase::State::kExpanded); |
| |
| // We should always use the expanded desks bar height here even if the desks |
| // bar is actually in zero state to calculate `scale_factor`. Because if zero |
| // state bar height is used here, the dragged window could become too small |
| // during the drag. |
| const float scale_factor = static_cast<float>(expanded_desks_bar_height) / |
| overview_grid->root_window()->bounds().height(); |
| gfx::SizeF scaled_size = gfx::ScaleSize(window_original_size, scale_factor); |
| |
| // Adjust the scaled size to ensure that its smaller side length is equal or |
| // larger than the `minimum_size_length`, and then adjust the larger size |
| // length to preserve the ratio of the original size. |
| const float minimum_size_length = |
| expanded_desks_bar_height * kScaleFactorForMinimumSideLength; |
| const float scaled_size_height = scaled_size.height(); |
| const float scaled_size_width = scaled_size.width(); |
| if (scaled_size_height < minimum_size_length || |
| scaled_size_width < minimum_size_length) { |
| if (scaled_size_height < scaled_size_width) { |
| scaled_size.set_height(minimum_size_length); |
| scaled_size.set_width(scaled_size_width / scaled_size_height * |
| minimum_size_length); |
| } else { |
| scaled_size.set_width(minimum_size_length); |
| scaled_size.set_height(scaled_size_height / scaled_size_width * |
| minimum_size_length); |
| } |
| } |
| |
| // Add the margins overview mode adds around the window's contents. |
| scaled_size.Enlarge(kDraggingEnlargeDp, |
| kDraggingEnlargeDp + kWindowMiniViewHeaderHeight); |
| return scaled_size; |
| } |
| |
| float GetManhattanDistanceX(float point_x, const gfx::RectF& rect) { |
| return std::max(rect.x() - point_x, point_x - rect.right()); |
| } |
| |
| float GetManhattanDistanceY(float point_y, const gfx::RectF& rect) { |
| return std::max(rect.y() - point_y, point_y - rect.bottom()); |
| } |
| |
| void RecordDrag(OverviewDragAction action) { |
| base::UmaHistogramEnumeration("Ash.Overview.WindowDrag.Workflow", action); |
| } |
| |
| // Restores the new desk button state back to the |
| // `DeskIconButton::State::kExpanded` on drag ended on all `OverviewGrid`s. |
| void MaybeRestoreNewDeskButtonState() { |
| OverviewSession* overview_session = |
| OverviewController::Get()->overview_session(); |
| if (!overview_session || overview_session->is_shutting_down()) { |
| return; |
| } |
| |
| for (aura::Window* root : Shell::GetAllRootWindows()) { |
| OverviewGrid* overview_grid = overview_session->GetGridWithRootWindow(root); |
| if (auto* desks_bar_view = overview_grid->desks_bar_view()) { |
| desks_bar_view->UpdateDeskIconButtonState( |
| desks_bar_view->new_desk_button(), DeskIconButton::State::kExpanded); |
| } |
| } |
| } |
| |
| // Helps with handling the workflow where you drag an overview item from one |
| // grid and drop into another grid. The challenge is that if the item represents |
| // an ARC window, that window will be moved to the target root asynchronously. |
| // |OverviewItemMoveHelper| observes the window until it moves to the target |
| // root. Then |OverviewItemMoveHelper| self destructs and adds a new item to |
| // represent the window on the target root. |
| class OverviewItemMoveHelper : public aura::WindowObserver { |
| public: |
| // |target_item_bounds| is the bounds of the dragged overview item when the |
| // drag ends. |target_item_bounds| is used to put the new item where the old |
| // item ended, so it looks like it is the same item. Then the item is animated |
| // from there to its proper position in the grid. |
| OverviewItemMoveHelper(aura::Window* window, |
| const gfx::RectF& target_item_bounds) |
| : window_(window), target_item_bounds_(target_item_bounds) { |
| window->AddObserver(this); |
| } |
| OverviewItemMoveHelper(const OverviewItemMoveHelper&) = delete; |
| OverviewItemMoveHelper& operator=(const OverviewItemMoveHelper&) = delete; |
| ~OverviewItemMoveHelper() override { |
| OverviewController* overview_controller = OverviewController::Get(); |
| if (overview_controller->InOverviewSession()) { |
| overview_controller->overview_session()->PositionWindows( |
| /*animate=*/true); |
| } |
| } |
| |
| // aura::WindowObserver: |
| void OnWindowDestroyed(aura::Window* window) override { |
| DCHECK_EQ(window_, window); |
| delete this; |
| } |
| void OnWindowAddedToRootWindow(aura::Window* window) override { |
| DCHECK_EQ(window_, window); |
| window->RemoveObserver(this); |
| OverviewController* overview_controller = OverviewController::Get(); |
| if (overview_controller->InOverviewSession()) { |
| // OverviewSession::AddItemInMruOrder() will add |window| to the grid |
| // associated with |window|'s root. Do not reposition or restack as we |
| // will soon handle them both anyway. |
| OverviewSession* session = overview_controller->overview_session(); |
| session->AddItemInMruOrder(window, /*reposition=*/false, |
| /*animate=*/false, /*restack=*/false, |
| /*use_spawn_animation=*/false); |
| OverviewItemBase* item = session->GetOverviewItemForWindow(window); |
| DCHECK(item); |
| item->SetBounds(target_item_bounds_, OVERVIEW_ANIMATION_NONE); |
| item->set_should_restack_on_animation_end(true); |
| // The destructor will call OverviewSession::PositionWindows(). |
| } |
| delete this; |
| } |
| |
| private: |
| const raw_ptr<aura::Window> window_; |
| const gfx::RectF target_item_bounds_; |
| }; |
| |
| } // namespace |
| |
| OverviewWindowDragController::OverviewWindowDragController( |
| OverviewSession* overview_session, |
| OverviewItemBase* item, |
| bool is_touch_dragging, |
| OverviewItemBase* event_source_item) |
| : overview_session_(overview_session), |
| item_(item), |
| event_source_item_(event_source_item), |
| display_count_(Shell::GetAllRootWindows().size()), |
| is_touch_dragging_(is_touch_dragging), |
| is_eligible_for_drag_to_snap_( |
| IsEligibleForDraggingToSnapInOverview(item)), |
| virtual_desks_bar_enabled_(GetVirtualDesksBarEnabled(item)) { |
| CHECK(!OverviewController::Get()->IsInStartAnimation()); |
| CHECK(!SplitViewController::Get(item_->root_window())->IsDividerAnimating()); |
| } |
| |
| OverviewWindowDragController::~OverviewWindowDragController() { |
| // This object is deleted using `DeleteSoon()`, so the shell may be destroyed |
| // already during shutdown. |
| if (Shell::HasInstance()) { |
| Shell::Get()->mouse_cursor_filter()->HideSharedEdgeIndicator(); |
| } |
| } |
| |
| // static |
| base::AutoReset<bool> |
| OverviewWindowDragController::SkipNewDeskButtonScaleUpDurationForTesting() { |
| return {&g_skip_new_desk_button_scale_up_for_test, true}; |
| } |
| |
| void OverviewWindowDragController::InitiateDrag( |
| const gfx::PointF& location_in_screen) { |
| initial_event_location_ = location_in_screen; |
| initial_centerpoint_ = item_->target_bounds().CenterPoint(); |
| original_opacity_ = item_->GetOpacity(); |
| current_drag_behavior_ = DragBehavior::kUndefined; |
| occlusion_pauser_ = OverviewController::Get()->PauseOcclusionTracker( |
| kOcclusionPauseDurationForDrag); |
| DCHECK(!presentation_time_recorder_); |
| |
| presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder( |
| item_->root_window()->layer()->GetCompositor(), |
| kOverviewWindowDragHistogram, kOverviewWindowDragMaxLatencyHistogram); |
| } |
| |
| void OverviewWindowDragController::Drag(const gfx::PointF& location_in_screen) { |
| if (!did_move_) { |
| gfx::Vector2dF distance = location_in_screen - initial_event_location_; |
| // Do not start dragging if the distance from |location_in_screen| to |
| // |initial_event_location_| is not greater than |kMinimumDragDistanceDp|. |
| if (std::abs(distance.x()) < kMinimumDragDistanceDp && |
| std::abs(distance.y()) < kMinimumDragDistanceDp) { |
| return; |
| } |
| |
| if (is_touch_dragging_ && std::abs(distance.x()) < std::abs(distance.y())) { |
| StartDragToCloseMode(); |
| } else if (is_eligible_for_drag_to_snap_ || virtual_desks_bar_enabled_) { |
| StartNormalDragMode(location_in_screen); |
| } else { |
| return; |
| } |
| } |
| |
| if (current_drag_behavior_ == DragBehavior::kDragToClose) |
| ContinueDragToClose(location_in_screen); |
| else if (current_drag_behavior_ == DragBehavior::kNormalDrag) |
| ContinueNormalDrag(location_in_screen); |
| |
| if (presentation_time_recorder_) |
| presentation_time_recorder_->RequestNext(); |
| } |
| |
| OverviewWindowDragController::DragResult |
| OverviewWindowDragController::CompleteDrag( |
| const gfx::PointF& location_in_screen) { |
| per_grid_desks_bar_data_.clear(); |
| DragResult result = DragResult::kNeverDisambiguated; |
| |
| switch (current_drag_behavior_) { |
| case DragBehavior::kNoDrag: |
| NOTREACHED(); |
| |
| case DragBehavior::kUndefined: |
| ActivateDraggedWindow(); |
| break; |
| |
| case DragBehavior::kNormalDrag: |
| result = CompleteNormalDrag(location_in_screen); |
| break; |
| |
| case DragBehavior::kDragToClose: |
| result = CompleteDragToClose(location_in_screen); |
| break; |
| } |
| |
| did_move_ = false; |
| |
| // `item_` may be null if `CompleteNormalDrag()` resulted in moving the |
| // window into another desk. At this point, we can just pass in a nullptr and |
| // the `FloatContainerStacker` will reset the stacking. Also, |
| // `ActivateDraggedWindow()` above may have started the session shutdown, so |
| // the `FloatContainerStacker` may be null. |
| if (auto* float_container_stacker = |
| overview_session_->float_container_stacker()) { |
| float_container_stacker->OnDragFinished(item_ ? item_->GetWindow() |
| : nullptr); |
| } |
| |
| item_ = nullptr; |
| event_source_item_ = nullptr; |
| current_drag_behavior_ = DragBehavior::kNoDrag; |
| occlusion_pauser_.reset(); |
| presentation_time_recorder_.reset(); |
| return result; |
| } |
| |
| void OverviewWindowDragController::StartNormalDragMode( |
| const gfx::PointF& location_in_screen) { |
| CHECK(is_eligible_for_drag_to_snap_ || virtual_desks_bar_enabled_); |
| |
| did_move_ = true; |
| current_drag_behavior_ = DragBehavior::kNormalDrag; |
| Shell::Get()->mouse_cursor_filter()->ShowSharedEdgeIndicator( |
| item_->root_window()); |
| const gfx::SizeF window_original_size(item_->GetWindow()->bounds().size()); |
| item_->ScaleUpSelectedItem( |
| OVERVIEW_ANIMATION_LAYOUT_OVERVIEW_ITEMS_IN_OVERVIEW); |
| original_scaled_size_ = item_->target_bounds().size(); |
| auto* overview_grid = item_->overview_grid(); |
| overview_grid->AddDropTargetForDraggingFromThisGrid(item_); |
| |
| // Expand all desks bars on all displays when normal drag starts if it is in |
| // zero state. |
| for (const std::unique_ptr<OverviewGrid>& grid : |
| overview_session_->grid_list()) { |
| // The bar may be null if we have no desks in tablet mode. |
| if (auto* desks_bar_view = grid->desks_bar_view(); |
| desks_bar_view && desks_bar_view->IsZeroState()) { |
| desks_bar_view->UpdateNewMiniViews(/*initializing_bar_view=*/false, |
| /*expanding_bar_view=*/true); |
| } |
| } |
| |
| item_->UpdateShadowTypeForDrag(/*is_dragging=*/true); |
| aura::Window* dragged_window = item_->GetWindow(); |
| |
| if (is_eligible_for_drag_to_snap_) { |
| overview_session_->SetSplitViewDragIndicatorsDraggedWindow(dragged_window); |
| overview_session_->UpdateSplitViewDragIndicatorsWindowDraggingStates( |
| GetRootWindowBeingDraggedIn(), |
| SplitViewDragIndicators::ComputeWindowDraggingState( |
| /*is_dragging=*/true, |
| SplitViewDragIndicators::WindowDraggingState::kFromOverview, |
| SnapPosition::kNone)); |
| item_->HideCannotSnapWarning(/*animate=*/true); |
| |
| // Update the split view divider bar status if necessary. If splitview is |
| // active when dragging the `dragged_window`, the split divider bar should |
| // be placed below the dragged window during dragging. |
| SplitViewController::Get(item_->root_window()) |
| ->OnWindowDragStarted(dragged_window); |
| } |
| |
| if (virtual_desks_bar_enabled_) { |
| // Calculate the item bounds minus the header and margins (which are |
| // invisible). Use this for the shrink bounds so that the item starts |
| // shrinking when the visible top-edge of the item aligns with the |
| // bottom-edge of the desks bar (may be different edges if we are dragging |
| // from different directions). |
| gfx::SizeF item_no_header_size = original_scaled_size_; |
| item_no_header_size.Enlarge( |
| float{-kDraggingEnlargeDp}, |
| float{-kDraggingEnlargeDp - kWindowMiniViewHeaderHeight}); |
| |
| // We must update the desks bar widget bounds before we cache its bounds |
| // below, in case it needs to be pushed down due to splitview indicators. |
| // Note that when drag is just getting started, the window hasn't moved to |
| // another display, so it's ok to use the item's |overview_grid|. |
| overview_grid->MaybeUpdateDesksWidgetBounds(); |
| |
| // Calculate cached values for usage during drag for each grid. |
| for (const auto& grid : overview_session_->grid_list()) { |
| GridDesksBarData& grid_desks_bar_data = |
| per_grid_desks_bar_data_[grid.get()]; |
| |
| grid_desks_bar_data.on_desks_bar_item_size = |
| GetItemSizeWhenOnDesksBar(grid.get(), window_original_size); |
| grid_desks_bar_data.desks_bar_bounds = grid_desks_bar_data.shrink_bounds = |
| gfx::RectF(grid->desks_bar_view()->GetBoundsInScreen()); |
| const int expanded_height = DeskBarViewBase::GetPreferredBarHeight( |
| grid->root_window(), DeskBarViewBase::Type::kOverview, |
| DeskBarViewBase::State::kExpanded); |
| grid_desks_bar_data.desks_bar_bounds.set_height(expanded_height); |
| grid_desks_bar_data.shrink_bounds.set_height(expanded_height); |
| grid_desks_bar_data.shrink_bounds.Inset(gfx::InsetsF::VH( |
| -item_no_header_size.height() / 2, -item_no_header_size.width() / 2)); |
| grid_desks_bar_data.shrink_region_distance = |
| grid_desks_bar_data.desks_bar_bounds.origin() - |
| grid_desks_bar_data.shrink_bounds.origin(); |
| } |
| } |
| |
| overview_session_->float_container_stacker()->OnDragStarted(dragged_window); |
| } |
| |
| OverviewWindowDragController::DragResult OverviewWindowDragController::Fling( |
| const gfx::PointF& location_in_screen, |
| float velocity_x, |
| float velocity_y) { |
| if (current_drag_behavior_ == DragBehavior::kDragToClose || |
| current_drag_behavior_ == DragBehavior::kUndefined) { |
| if (std::abs(velocity_y) > kFlingToCloseVelocityThreshold) { |
| item_->AnimateAndCloseItem( |
| (location_in_screen - initial_event_location_).y() < 0); |
| did_move_ = false; |
| item_ = nullptr; |
| event_source_item_ = nullptr; |
| current_drag_behavior_ = DragBehavior::kNoDrag; |
| occlusion_pauser_.reset(); |
| RecordDragToClose(kFlingToClose); |
| return DragResult::kSuccessfulDragToClose; |
| } |
| } |
| |
| // If the fling velocity was not high enough, or flings should be ignored, |
| // treat it as a scroll end event. |
| return CompleteDrag(location_in_screen); |
| } |
| |
| void OverviewWindowDragController::ActivateDraggedWindow() { |
| // If no drag was initiated (e.g., a click/tap on the overview window), |
| // activate the window. If the split view is active and has a left window, |
| // snap the current window to right. If the split view is active and has a |
| // right window, snap the current window to left. If split view is active |
| // and the selected window cannot be snapped, exit splitview and activate |
| // the selected window, and also exit the overview. |
| SplitViewController* split_view_controller = |
| SplitViewController::Get(item_->root_window()); |
| SplitViewController::State split_state = split_view_controller->state(); |
| if (!is_eligible_for_drag_to_snap_ || |
| split_state == SplitViewController::State::kNoSnap) { |
| overview_session_->SelectWindow(event_source_item_); |
| // Explicitly set `item_` to null to avoid being accessed after been |
| // released in `OverviewGrid::RemoveItem()`. See UaF reported in |
| // b/301368132. |
| item_ = nullptr; |
| event_source_item_ = nullptr; |
| } else if (auto* split_view_overview_session = |
| RootWindowController::ForWindow(item_->GetWindow()) |
| ->split_view_overview_session(); |
| split_view_overview_session) { |
| // If `SplitViewOverviewSession` is active, activate the window; |
| // `AutoSnapController` will handle the autosnap. |
| RecordPartialOverviewMetrics(item_); |
| overview_session_->SelectWindow(event_source_item_); |
| item_ = nullptr; |
| event_source_item_ = nullptr; |
| } else if (split_view_controller->CanSnapWindow( |
| item_->GetWindow(), chromeos::kDefaultSnapRatio)) { |
| // Used for overview items that are being dragged to snap. Since the |
| // window is already activated, `AutoSnapController::OnWindowActivating()` |
| // will not work above. |
| RecordPartialOverviewMetrics(item_); |
| SnapWindow(split_view_controller, |
| split_state == SplitViewController::State::kPrimarySnapped |
| ? SnapPosition::kSecondary |
| : SnapPosition::kPrimary); |
| } else { |
| split_view_controller->EndSplitView(); |
| overview_session_->SelectWindow(event_source_item_); |
| // Same as above, explicitly set `item_` to nullptr to avoid UaF. |
| item_ = nullptr; |
| event_source_item_ = nullptr; |
| ShowAppCannotSnapToast(); |
| } |
| |
| current_drag_behavior_ = DragBehavior::kNoDrag; |
| occlusion_pauser_.reset(); |
| } |
| |
| void OverviewWindowDragController::ResetGesture() { |
| if (current_drag_behavior_ == DragBehavior::kNormalDrag) { |
| CHECK(item_->overview_grid()->drop_target()); |
| |
| Shell::Get()->mouse_cursor_filter()->HideSharedEdgeIndicator(); |
| item_->DestroyMirrorsForDragging(); |
| overview_session_->RemoveDropTargets(); |
| if (is_eligible_for_drag_to_snap_) { |
| SplitViewController::Get(item_->root_window())->OnWindowDragCanceled(); |
| overview_session_->ResetSplitViewDragIndicatorsWindowDraggingStates(); |
| item_->UpdateCannotSnapWarningVisibility(/*animate=*/true); |
| } |
| } |
| |
| // No need to position windows that are being destroyed. |
| base::flat_set<OverviewItemBase*> ignored_items; |
| if (item_->GetWindow()->is_destroying()) { |
| ignored_items.insert(item_); |
| } |
| overview_session_->PositionWindows(/*animate=*/true, ignored_items); |
| overview_session_->float_container_stacker()->OnDragFinished( |
| item_->GetWindow()); |
| // This function gets called after a long press release, which bypasses |
| // CompleteDrag but stops dragging as well, so reset |item_|. |
| item_ = nullptr; |
| event_source_item_ = nullptr; |
| current_drag_behavior_ = DragBehavior::kNoDrag; |
| occlusion_pauser_.reset(); |
| } |
| |
| void OverviewWindowDragController::ResetOverviewSession() { |
| overview_session_ = nullptr; |
| new_desk_button_scale_up_timer_.Stop(); |
| } |
| |
| void OverviewWindowDragController::StartDragToCloseMode() { |
| DCHECK(is_touch_dragging_); |
| |
| did_move_ = true; |
| current_drag_behavior_ = DragBehavior::kDragToClose; |
| overview_session_->GetGridWithRootWindow(item_->root_window()) |
| ->StartNudge(item_); |
| |
| item_->UpdateShadowTypeForDrag(/*is_dragging=*/true); |
| overview_session_->float_container_stacker()->OnDragStarted( |
| item_->GetWindow()); |
| } |
| |
| void OverviewWindowDragController::ContinueDragToClose( |
| const gfx::PointF& location_in_screen) { |
| DCHECK_EQ(current_drag_behavior_, DragBehavior::kDragToClose); |
| |
| // Update the dragged |item_|'s bounds accordingly. The distance from the new |
| // location to the new centerpoint should be the same it was initially. |
| gfx::RectF bounds(item_->target_bounds()); |
| const gfx::PointF centerpoint = |
| location_in_screen - (initial_event_location_ - initial_centerpoint_); |
| |
| // If the drag location intersects with the desk bar, then we should cancel |
| // the drag-to-close mode and start the normal drag mode. |
| if (virtual_desks_bar_enabled_ && |
| item_->overview_grid()->IntersectsWithDesksBar( |
| gfx::ToRoundedPoint(location_in_screen), |
| /*update_desks_bar_drag_details=*/false, /*for_drop=*/false)) { |
| item_->SetOpacity(original_opacity_); |
| StartNormalDragMode(location_in_screen); |
| ContinueNormalDrag(location_in_screen); |
| return; |
| } |
| |
| // Update |item_|'s opacity based on its distance. |item_|'s x coordinate |
| // should not change while in drag to close state. |
| float val = std::abs(location_in_screen.y() - initial_event_location_.y()) / |
| kDragToCloseDistanceThresholdDp; |
| overview_session_->GetGridWithRootWindow(item_->root_window()) |
| ->UpdateNudge(item_, val); |
| val = std::clamp(val, 0.f, 1.f); |
| float opacity = original_opacity_; |
| if (opacity > kItemMinOpacity) |
| opacity = original_opacity_ - val * (original_opacity_ - kItemMinOpacity); |
| |
| item_->SetOpacity(opacity); |
| |
| // When dragging to close, only update the y component. |
| bounds.set_y(centerpoint.y() - bounds.height() / 2.f); |
| item_->SetBounds(bounds, OVERVIEW_ANIMATION_NONE); |
| } |
| |
| OverviewWindowDragController::DragResult |
| OverviewWindowDragController::CompleteDragToClose( |
| const gfx::PointF& location_in_screen) { |
| DCHECK_EQ(current_drag_behavior_, DragBehavior::kDragToClose); |
| |
| // Close the window if it has been dragged enough, otherwise reposition it and |
| // set its opacity back to its original value. |
| overview_session_->GetGridWithRootWindow(item_->root_window())->EndNudge(); |
| const float y_distance = (location_in_screen - initial_event_location_).y(); |
| if (std::abs(y_distance) > kDragToCloseDistanceThresholdDp) { |
| item_->AnimateAndCloseItem(/*up=*/y_distance < 0); |
| RecordDragToClose(kSwipeToCloseSuccessful); |
| return DragResult::kSuccessfulDragToClose; |
| } |
| |
| item_->UpdateShadowTypeForDrag(/*is_dragging=*/false); |
| |
| item_->SetOpacity(original_opacity_); |
| overview_session_->PositionWindows(/*animate=*/true); |
| RecordDragToClose(kSwipeToCloseCanceled); |
| return DragResult::kCanceledDragToClose; |
| } |
| |
| void OverviewWindowDragController::ContinueNormalDrag( |
| const gfx::PointF& location_in_screen) { |
| DCHECK_EQ(current_drag_behavior_, DragBehavior::kNormalDrag); |
| |
| // Update the dragged |item_|'s bounds accordingly. The distance from the new |
| // location to the new centerpoint should be the same it was initially unless |
| // the item is over the DeskBarView, in which case we scale it down and center |
| // it around the drag location. |
| gfx::RectF bounds(item_->target_bounds()); |
| gfx::PointF centerpoint = |
| location_in_screen - (initial_event_location_ - initial_centerpoint_); |
| |
| auto* overview_grid = GetCurrentGrid(); |
| |
| // If virtual desks is enabled, we want to gradually shrink the dragged item |
| // as it gets closer to get dropped into a desk mini view. |
| if (virtual_desks_bar_enabled_) { |
| // TODO(sammiequon): There is a slight jump especially if we drag from the |
| // corner of a larger overview item, but this is necessary for the time |
| // being to prevent jumps from happening while shrinking. Investigate if we |
| // can satisfy all cases. |
| centerpoint = location_in_screen; |
| |
| const auto iter = per_grid_desks_bar_data_.find(overview_grid); |
| DCHECK(iter != per_grid_desks_bar_data_.end()); |
| const GridDesksBarData& desks_bar_data = iter->second; |
| |
| if (desks_bar_data.shrink_bounds.Contains(location_in_screen)) { |
| // Update the mini views borders by checking if |location_in_screen| |
| // intersects. Only update the borders if the dragged item is not visible |
| // on all desks. |
| overview_grid->IntersectsWithDesksBar( |
| gfx::ToRoundedPoint(location_in_screen), |
| /*update_desks_bar_drag_details=*/ |
| !DraggedItemIsVisibleOnAllDesks(item_), /*for_drop=*/false); |
| |
| float value = 0.f; |
| if (centerpoint.y() < desks_bar_data.desks_bar_bounds.y() || |
| centerpoint.y() > desks_bar_data.desks_bar_bounds.bottom()) { |
| // Coming vertically, this is the main use case. This is a ratio of the |
| // distance from |centerpoint| to the closest edge of |desk_bar_bounds| |
| // to the distance from |shrink_bounds| to |desk_bar_bounds|. |
| value = GetManhattanDistanceY(centerpoint.y(), |
| desks_bar_data.desks_bar_bounds) / |
| desks_bar_data.shrink_region_distance.y(); |
| } else if (centerpoint.x() < desks_bar_data.desks_bar_bounds.x() || |
| centerpoint.x() > desks_bar_data.desks_bar_bounds.right()) { |
| // Coming horizontally, this only happens if we are in landscape split |
| // view and someone drags an item to the other half, then up, then into |
| // the desks bar. Works same as vertically except using x-coordinates. |
| value = GetManhattanDistanceX(centerpoint.x(), |
| desks_bar_data.desks_bar_bounds) / |
| desks_bar_data.shrink_region_distance.x(); |
| } |
| value = std::clamp(value, 0.f, 1.f); |
| const gfx::SizeF size_value = |
| gfx::Tween::SizeFValueBetween(1.f - value, original_scaled_size_, |
| desks_bar_data.on_desks_bar_item_size); |
| bounds.set_size(size_value); |
| } else { |
| bounds.set_size(original_scaled_size_); |
| } |
| } |
| |
| if (is_eligible_for_drag_to_snap_) { |
| UpdateDragIndicatorsAndOverviewGrid(location_in_screen); |
| // The newly updated indicator state may cause the desks widget to be pushed |
| // down to make room for the top splitview guidance indicator when in |
| // portrait orientation in tablet mode. |
| overview_grid->MaybeUpdateDesksWidgetBounds(); |
| } |
| |
| if (!overview_grid->drop_target() && |
| (!is_eligible_for_drag_to_snap_ || |
| SplitViewDragIndicators::GetSnapPosition( |
| overview_grid->split_view_drag_indicators() |
| ->current_window_dragging_state()) == SnapPosition::kNone)) { |
| overview_grid->AddDropTargetNotForDraggingFromThisGrid(item_->GetWindow(), |
| /*animate=*/true); |
| } |
| overview_session_->UpdateDropTargetsBackgroundVisibilities( |
| item_, location_in_screen); |
| |
| bounds.set_x(centerpoint.x() - bounds.width() / 2.f); |
| bounds.set_y(centerpoint.y() - bounds.height() / 2.f); |
| item_->SetBounds(bounds, OVERVIEW_ANIMATION_NONE); |
| |
| // The bar may be null if we have no desks in tablet mode. |
| if (auto* desks_bar_view = overview_grid->desks_bar_view()) { |
| auto* new_desk_button = desks_bar_view->new_desk_button(); |
| |
| // The header of window is shown during dragging. Overview item should be |
| // hovered on the new desk button with |
| // `kVerticalOverlappedLengthToActivateNewDeskButton` overlapped vertical |
| // area in order to activate the new desk button. There could be a lot of |
| // mistriggers with header shown if the new desk button is activated when |
| // the overview item intersects with it. |
| gfx::Rect effective_hovered_bounds(gfx::ToEnclosedRect(bounds)); |
| effective_hovered_bounds.Inset(gfx::Insets::TLBR( |
| kVerticalOverlappedLengthToActivateNewDeskButton, 0, 0, 0)); |
| const bool is_hovered_on_new_desk_button = |
| new_desk_button->GetBoundsInScreen().Intersects( |
| effective_hovered_bounds); |
| |
| if (!is_hovered_on_new_desk_button) { |
| new_desk_button_scale_up_timer_.Stop(); |
| } else if (!new_desk_button_scale_up_timer_.IsRunning() && |
| new_desk_button->state() == DeskIconButton::State::kExpanded) { |
| if (g_skip_new_desk_button_scale_up_for_test) { |
| MaybeScaleUpNewDeskButton(); |
| } else { |
| new_desk_button_scale_up_timer_.Start( |
| FROM_HERE, kScaleUpNewDeskButtonGracePeriod, this, |
| &OverviewWindowDragController::MaybeScaleUpNewDeskButton); |
| } |
| } |
| } |
| |
| if (display_count_ > 1u) |
| item_->UpdateMirrorsForDragging(is_touch_dragging_); |
| } |
| |
| OverviewWindowDragController::DragResult |
| OverviewWindowDragController::CompleteNormalDrag( |
| const gfx::PointF& location_in_screen) { |
| DCHECK_EQ(current_drag_behavior_, DragBehavior::kNormalDrag); |
| auto* item_overview_grid = item_->overview_grid(); |
| CHECK(item_overview_grid->drop_target()); |
| Shell::Get()->mouse_cursor_filter()->HideSharedEdgeIndicator(); |
| item_->DestroyMirrorsForDragging(); |
| overview_session_->RemoveDropTargets(); |
| |
| item_->UpdateShadowTypeForDrag(/*is_dragging=*/false); |
| |
| const gfx::Point rounded_screen_point = |
| gfx::ToRoundedPoint(location_in_screen); |
| if (is_eligible_for_drag_to_snap_) { |
| // Update the split view divider bar status if necessary. The divider bar |
| // should be placed above the dragged window after drag ends. Note here the |
| // passed parameters |snap_position_| and |location_in_screen| won't be used |
| // in this function for this case, but they are passed in as placeholders. |
| aura::Window* window = item_->GetWindow(); |
| SplitViewController::Get(item_->root_window()) |
| ->OnWindowDragEnded( |
| window, snap_position_, rounded_screen_point, |
| WindowSnapActionSource::kDragOrSelectOverviewWindowToSnap); |
| |
| // Update window grid bounds and |snap_position_| in case the screen |
| // orientation was changed. |
| UpdateDragIndicatorsAndOverviewGrid(location_in_screen); |
| overview_session_->ResetSplitViewDragIndicatorsWindowDraggingStates(); |
| item_->UpdateCannotSnapWarningVisibility(/*animate=*/true); |
| } |
| |
| // This function has multiple exit positions, at each we must update the desks |
| // bar widget bounds. We can't do this before we attempt dropping the window |
| // on a desk mini_view, since this will change where it is relative to the |
| // current |location_in_screen|. |
| absl::Cleanup at_exit_runner = [] { |
| // Overview might have exited if we snapped windows on both sides. |
| auto* overview_controller = OverviewController::Get(); |
| if (!overview_controller->InOverviewSession()) |
| return; |
| |
| for (auto& grid : overview_controller->overview_session()->grid_list()) |
| grid->MaybeUpdateDesksWidgetBounds(); |
| }; |
| |
| aura::Window* target_root = GetRootWindowBeingDraggedIn(); |
| const bool is_dragged_to_other_display = target_root != item_->root_window(); |
| auto* current_grid = GetCurrentGrid(); |
| if (virtual_desks_bar_enabled_) { |
| item_->SetOpacity(original_opacity_); |
| |
| // Attempt to move a window to a different desk. |
| if (current_grid->MaybeDropItemOnDeskMiniViewOrNewDeskButton( |
| rounded_screen_point, item_)) { |
| // Window was successfully moved to another desk, and |item_| was |
| // removed from the grid. It may never be accessed after this. |
| item_ = nullptr; |
| event_source_item_ = nullptr; |
| overview_session_->PositionWindows(/*animate=*/true); |
| RecordNormalDrag(kToDesk, is_dragged_to_other_display); |
| return DragResult::kDragToDesk; |
| } |
| } |
| |
| // Snap a window if appropriate. |
| if (is_eligible_for_drag_to_snap_ && snap_position_ != SnapPosition::kNone) { |
| // Overview grid will be updated after window is snapped in splitview. |
| SnapWindow(SplitViewController::Get(target_root), snap_position_); |
| RecordNormalDrag(kToSnap, is_dragged_to_other_display); |
| MaybeRestoreNewDeskButtonState(); |
| return DragResult::kSnap; |
| } |
| |
| DCHECK(item_); |
| const bool dragged_item_is_visible_on_all_desks = |
| DraggedItemIsVisibleOnAllDesks(item_); |
| const bool item_intersects_other_display_desk_bar = |
| virtual_desks_bar_enabled_ && |
| current_grid->IntersectsWithDesksBar( |
| gfx::ToRoundedPoint(location_in_screen), |
| /*update_desks_bar_drag_details=*/false, /*for_drop=*/false); |
| |
| // Drop a window into overview because we have not done anything else with it. |
| // If the window is visible on all desks, only move it to another display if |
| // it doesn't intersect the other grid's desk bar. |
| if (is_dragged_to_other_display && |
| !(dragged_item_is_visible_on_all_desks && |
| item_intersects_other_display_desk_bar)) { |
| // Get the window and bounds from |item_| before removing it from its grid. |
| aura::Window* window = item_->GetWindow(); |
| const gfx::RectF target_item_bounds = item_->target_bounds(); |
| // Remove |item_| from overview. Leave the repositioning to the |
| // |OverviewItemMoveHelper|. |
| overview_session_->RemoveItem(item_, /*item_destroying=*/false, |
| /*reposition=*/false); |
| item_ = nullptr; |
| event_source_item_ = nullptr; |
| // The |OverviewItemMoveHelper| will self destruct when we move |window| to |
| // |target_root|. |
| new OverviewItemMoveHelper(window, target_item_bounds); |
| // Move |window| to |target_root|. The |OverviewItemMoveHelper| will take |
| // care of the rest. |
| window_util::MoveWindowToDisplay(window, |
| display::Screen::GetScreen() |
| ->GetDisplayNearestWindow(target_root) |
| .id()); |
| } else { |
| item_->set_should_restack_on_animation_end(true); |
| overview_session_->PositionWindows(/*animate=*/true); |
| MaybeRestoreNewDeskButtonState(); |
| } |
| RecordNormalDrag(kToGrid, is_dragged_to_other_display); |
| return DragResult::kDropIntoOverview; |
| } |
| |
| void OverviewWindowDragController::UpdateDragIndicatorsAndOverviewGrid( |
| const gfx::PointF& location_in_screen) { |
| CHECK(is_eligible_for_drag_to_snap_); |
| snap_position_ = GetSnapPosition(location_in_screen); |
| overview_session_->UpdateSplitViewDragIndicatorsWindowDraggingStates( |
| GetRootWindowBeingDraggedIn(), |
| SplitViewDragIndicators::ComputeWindowDraggingState( |
| /*is_dragging=*/true, |
| SplitViewDragIndicators::WindowDraggingState::kFromOverview, |
| snap_position_)); |
| overview_session_->RearrangeDuringDrag(item_); |
| } |
| |
| aura::Window* OverviewWindowDragController::GetRootWindowBeingDraggedIn() |
| const { |
| if (is_touch_dragging_) { |
| return item_->root_window(); |
| } |
| |
| auto* screen = display::Screen::GetScreen(); |
| CHECK(screen); |
| auto display = screen->GetDisplayNearestPoint(screen->GetCursorScreenPoint()); |
| return Shell::GetRootWindowForDisplayId(display.id()); |
| } |
| |
| SnapPosition OverviewWindowDragController::GetSnapPosition( |
| const gfx::PointF& location_in_screen) const { |
| CHECK(item_); |
| CHECK(is_eligible_for_drag_to_snap_); |
| gfx::Rect area = |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| GetRootWindowBeingDraggedIn()); |
| |
| // If split view mode is active at the moment, and dragging an overview window |
| // to snap it to a position that already has a snapped window in place, we |
| // should show the preview window as soon as the window past the split divider |
| // bar. |
| aura::Window* root_window = GetRootWindowBeingDraggedIn(); |
| SplitViewController* split_view_controller = |
| SplitViewController::Get(root_window); |
| if (!split_view_controller->CanSnapWindow(item_->GetWindow(), |
| chromeos::kDefaultSnapRatio)) { |
| return SnapPosition::kNone; |
| } |
| if (split_view_controller->InSplitViewMode()) { |
| // If we're trying to snap to a position that already has a snapped window: |
| aura::Window* default_snapped_window = |
| split_view_controller->GetDefaultSnappedWindow(); |
| if (gfx::RectF(default_snapped_window->GetBoundsInScreen()) |
| .Contains(location_in_screen)) { |
| return split_view_controller->GetPositionOfSnappedWindow( |
| default_snapped_window); |
| } |
| } |
| |
| return ::ash::GetSnapPosition( |
| root_window, item_->GetWindow(), gfx::ToRoundedPoint(location_in_screen), |
| gfx::ToRoundedPoint(initial_event_location_), |
| /*snap_distance_from_edge=*/kDistanceFromEdgeDp, |
| /*minimum_drag_distance=*/kMinimumDragToSnapDistanceDp, |
| /*horizontal_edge_inset=*/area.width() * |
| kHighlightScreenPrimaryAxisRatio + |
| kHighlightScreenEdgePaddingDp, |
| /*vertical_edge_inset=*/area.height() * kHighlightScreenPrimaryAxisRatio + |
| kHighlightScreenEdgePaddingDp); |
| } |
| |
| void OverviewWindowDragController::SnapWindow( |
| SplitViewController* split_view_controller, |
| SnapPosition snap_position) { |
| DCHECK_NE(snap_position, SnapPosition::kNone); |
| |
| CHECK(!SplitViewController::Get(item_->root_window())->IsDividerAnimating()); |
| aura::Window* window = item_->GetWindow(); |
| |
| // If `window` is currently fullscreen, snapping it will trigger a work area |
| // change, which triggers `OverviewSession::OnDisplayMetricsChanged`. Display |
| // changes normally end dragging for simplicity, but we need `item` to be |
| // nullptr before that happens so we can skip resetting the window gesture. |
| // See crbug.com/1330042 for more details. `item_` will be deleted after |
| // SplitViewController::SnapWindow(). |
| item_ = nullptr; |
| event_source_item_ = nullptr; |
| split_view_controller->SnapWindow( |
| window, snap_position, |
| WindowSnapActionSource::kDragOrSelectOverviewWindowToSnap, |
| /*activate_window=*/true); |
| } |
| |
| OverviewGrid* OverviewWindowDragController::GetCurrentGrid() const { |
| return overview_session_->GetGridWithRootWindow( |
| GetRootWindowBeingDraggedIn()); |
| } |
| |
| void OverviewWindowDragController::RecordNormalDrag( |
| NormalDragAction action, |
| bool is_dragged_to_other_display) const { |
| const bool is_tablet = display::Screen::GetScreen()->InTabletMode(); |
| if (is_dragged_to_other_display) { |
| DCHECK(!is_touch_dragging_); |
| if (!is_tablet) { |
| constexpr OverviewDragAction kDrag[kNormalDragActionEnumSize] = { |
| OverviewDragAction::kToGridOtherDisplayClamshellMouse, |
| OverviewDragAction::kToDeskOtherDisplayClamshellMouse, |
| OverviewDragAction::kToSnapOtherDisplayClamshellMouse}; |
| RecordDrag(kDrag[action]); |
| } |
| } else if (is_tablet) { |
| if (is_touch_dragging_) { |
| constexpr OverviewDragAction kDrag[kNormalDragActionEnumSize] = { |
| OverviewDragAction::kToGridSameDisplayTabletTouch, |
| OverviewDragAction::kToDeskSameDisplayTabletTouch, |
| OverviewDragAction::kToSnapSameDisplayTabletTouch}; |
| RecordDrag(kDrag[action]); |
| } |
| } else { |
| constexpr OverviewDragAction kMouseDrag[kNormalDragActionEnumSize] = { |
| OverviewDragAction::kToGridSameDisplayClamshellMouse, |
| OverviewDragAction::kToDeskSameDisplayClamshellMouse, |
| OverviewDragAction::kToSnapSameDisplayClamshellMouse}; |
| constexpr OverviewDragAction kTouchDrag[kNormalDragActionEnumSize] = { |
| OverviewDragAction::kToGridSameDisplayClamshellTouch, |
| OverviewDragAction::kToDeskSameDisplayClamshellTouch, |
| OverviewDragAction::kToSnapSameDisplayClamshellTouch}; |
| RecordDrag(is_touch_dragging_ ? kTouchDrag[action] : kMouseDrag[action]); |
| } |
| } |
| |
| void OverviewWindowDragController::RecordDragToClose( |
| DragToCloseAction action) const { |
| DCHECK(is_touch_dragging_); |
| constexpr OverviewDragAction kClamshellDrag[kDragToCloseActionEnumSize] = { |
| OverviewDragAction::kSwipeToCloseSuccessfulClamshellTouch, |
| OverviewDragAction::kSwipeToCloseCanceledClamshellTouch, |
| OverviewDragAction::kFlingToCloseClamshellTouch}; |
| constexpr OverviewDragAction kTabletDrag[kDragToCloseActionEnumSize] = { |
| OverviewDragAction::kSwipeToCloseSuccessfulTabletTouch, |
| OverviewDragAction::kSwipeToCloseCanceledTabletTouch, |
| OverviewDragAction::kFlingToCloseTabletTouch}; |
| RecordDrag(display::Screen::GetScreen()->InTabletMode() |
| ? kTabletDrag[action] |
| : kClamshellDrag[action]); |
| } |
| |
| void OverviewWindowDragController::MaybeScaleUpNewDeskButton() { |
| if (!item_ || !item_->overview_grid()) { |
| return; |
| } |
| |
| // When there's only one window and it's snapped, overview mode will be |
| // ended. Thus we need to check whether `overview_session_` is being |
| // shutting down or not here before triggering `UpdateDeskIconButtonState`. |
| if (!overview_session_) { |
| return; |
| } |
| |
| auto* overview_grid = |
| overview_session_->GetGridWithRootWindow(GetRootWindowBeingDraggedIn()); |
| auto* desks_bar_view = overview_grid->desks_bar_view(); |
| auto* new_desk_button = desks_bar_view->new_desk_button(); |
| |
| if (!new_desk_button->GetEnabled()) { |
| return; |
| } |
| |
| // Do not reposition the windows while changing the desk icon button. This |
| // could cause items to shift around mid drag. |
| overview_session_->SuspendReposition(); |
| desks_bar_view->UpdateDeskIconButtonState( |
| new_desk_button, /*target_state=*/DeskIconButton::State::kActive); |
| overview_session_->ResumeReposition(); |
| } |
| |
| } // namespace ash |