#include "ash/wm/overview/overview_window_drag_controller.h"
#include <algorithm>
#include <array>
#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/scoped_windows_mover.h"
#include "ash/wm/snap_group/snap_group.h"
#include "ash/wm/snap_group/snap_group_controller.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/memory/raw_ptr.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 {
constexpr float kDragToCloseDistanceThresholdDp = 160.f;
constexpr float kMinimumDragDistanceDp = 5.f;
constexpr float kDistanceFromEdgeDp = 16.f;
constexpr float kMinimumDragToSnapDistanceDp = 96.f;
constexpr float kFlingToCloseVelocityThreshold = 2000.f;
constexpr float kItemMinOpacity = 0.4f;
constexpr float kScaleFactorForMinimumSideLength = 0.5f;
constexpr int kVerticalOverlappedLengthToActivateNewDeskButton = 15;
constexpr base::TimeDelta kOcclusionPauseDurationForDrag =
base::Milliseconds(300);
constexpr base::TimeDelta kScaleUpNewDeskButtonGracePeriod =
base::Milliseconds(500);
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();
}
bool DraggedItemIsVisibleOnAllDesks(OverviewItemBase* item) {
aura::Window* const dragged_window = item->GetWindow();
return dragged_window &&
desks_util::IsWindowVisibleOnAllWorkspaces(dragged_window);
}
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);
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);
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);
}
}
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);
}
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);
}
}
}
class OverviewItemMoveHelper : public aura::WindowObserver {
public:
OverviewItemMoveHelper(aura::Window::Windows windows,
aura::Window* item_window,
const gfx::RectF& target_item_bounds)
: windows_(std::move(windows)),
item_window_(item_window),
target_item_bounds_(target_item_bounds) {
CHECK(base::Contains(windows_, item_window_));
for (auto window : windows_) {
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(
true);
}
DCHECK(windows_.empty());
}
void OnWindowDestroyed(aura::Window* window) override {
DCHECK(base::Contains(windows_, window));
ResetWindowAndDeleteIfEmpty(window);
}
void OnWindowAddedToRootWindow(aura::Window* window) override {
DCHECK(base::Contains(windows_, window));
bool is_item_window = item_window_ == window;
OverviewController* overview_controller = OverviewController::Get();
if (overview_controller->InOverviewSession()) {
OverviewSession* session = overview_controller->overview_session();
session->AddItemInMruOrder(window, false,
false, false,
false);
if (is_item_window) {
OverviewItemBase* item = session->GetOverviewItemForWindow(window);
DCHECK(item);
item->SetBounds(target_item_bounds_, OVERVIEW_ANIMATION_NONE);
item->set_should_restack_on_animation_end(true);
} else if (overview_controller->InOverviewSession()) {
overview_controller->overview_session()->PositionWindows(
false);
}
}
ResetWindowAndDeleteIfEmpty(window);
}
private:
void ResetWindowAndDeleteIfEmpty(aura::Window* window) {
window->RemoveObserver(this);
if (item_window_ == window) {
item_window_ = nullptr;
}
std::erase(windows_, window);
if (windows_.empty()) {
delete this;
}
}
aura::Window::Windows windows_;
raw_ptr<aura::Window> item_window_;
const gfx::RectF target_item_bounds_;
};
}
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() {
if (Shell::HasInstance()) {
Shell::Get()->mouse_cursor_filter()->HideSharedEdgeIndicator();
}
}
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_;
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;
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_);
for (const std::unique_ptr<OverviewGrid>& grid :
overview_session_->grid_list()) {
if (auto* desks_bar_view = grid->desks_bar_view();
desks_bar_view && desks_bar_view->IsZeroState()) {
desks_bar_view->UpdateNewMiniViews(false,
true);
}
}
item_->UpdateShadowTypeForDrag(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(
true,
SplitViewDragIndicators::WindowDraggingState::kFromOverview,
SnapPosition::kNone));
item_->HideCannotSnapWarning(true);
SplitViewController::Get(item_->root_window())
->OnWindowDragStarted(dragged_window);
}
if (virtual_desks_bar_enabled_) {
gfx::SizeF item_no_header_size = original_scaled_size_;
item_no_header_size.Enlarge(
float{-kDraggingEnlargeDp},
float{-kDraggingEnlargeDp - kWindowMiniViewHeaderHeight});
overview_grid->MaybeUpdateDesksWidgetBounds();
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;
}
}
return CompleteDrag(location_in_screen);
}
void OverviewWindowDragController::ActivateDraggedWindow() {
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_);
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) {
RecordPartialOverviewMetrics(item_);
overview_session_->SelectWindow(event_source_item_);
item_ = nullptr;
event_source_item_ = nullptr;
} else if (split_view_controller->CanSnapWindow(
item_->GetWindow(), chromeos::kDefaultSnapRatio)) {
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_);
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(true);
}
}
base::flat_set<OverviewItemBase*> ignored_items;
if (item_->GetWindow()->is_destroying()) {
ignored_items.insert(item_);
}
overview_session_->PositionWindows(true, ignored_items);
overview_session_->float_container_stacker()->OnDragFinished(
item_->GetWindow());
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(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);
gfx::RectF bounds(item_->target_bounds());
const gfx::PointF centerpoint =
location_in_screen - (initial_event_location_ - initial_centerpoint_);
if (virtual_desks_bar_enabled_ &&
item_->overview_grid()->IntersectsWithDesksBar(
gfx::ToRoundedPoint(location_in_screen),
false, false)) {
item_->SetOpacity(original_opacity_);
StartNormalDragMode(location_in_screen);
ContinueNormalDrag(location_in_screen);
return;
}
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);
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);
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(y_distance < 0);
RecordDragToClose(kSwipeToCloseSuccessful);
return DragResult::kSuccessfulDragToClose;
}
item_->UpdateShadowTypeForDrag(false);
item_->SetOpacity(original_opacity_);
overview_session_->PositionWindows(true);
RecordDragToClose(kSwipeToCloseCanceled);
return DragResult::kCanceledDragToClose;
}
void OverviewWindowDragController::ContinueNormalDrag(
const gfx::PointF& location_in_screen) {
DCHECK_EQ(current_drag_behavior_, DragBehavior::kNormalDrag);
gfx::RectF bounds(item_->target_bounds());
gfx::PointF centerpoint =
location_in_screen - (initial_event_location_ - initial_centerpoint_);
auto* overview_grid = GetCurrentGrid();
if (virtual_desks_bar_enabled_) {
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)) {
overview_grid->IntersectsWithDesksBar(
gfx::ToRoundedPoint(location_in_screen),
!DraggedItemIsVisibleOnAllDesks(item_), false);
float value = 0.f;
if (centerpoint.y() < desks_bar_data.desks_bar_bounds.y() ||
centerpoint.y() > desks_bar_data.desks_bar_bounds.bottom()) {
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()) {
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);
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(),
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);
if (auto* desks_bar_view = overview_grid->desks_bar_view()) {
auto* new_desk_button = desks_bar_view->new_desk_button();
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(false);
const gfx::Point rounded_screen_point =
gfx::ToRoundedPoint(location_in_screen);
if (is_eligible_for_drag_to_snap_) {
aura::Window* window = item_->GetWindow();
SplitViewController::Get(item_->root_window())
->OnWindowDragEnded(
window, snap_position_, rounded_screen_point,
WindowSnapActionSource::kDragOrSelectOverviewWindowToSnap);
UpdateDragIndicatorsAndOverviewGrid(location_in_screen);
overview_session_->ResetSplitViewDragIndicatorsWindowDraggingStates();
item_->UpdateCannotSnapWarningVisibility(true);
}
absl::Cleanup at_exit_runner = [] {
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_);
if (current_grid->MaybeDropItemOnDeskMiniViewOrNewDeskButton(
rounded_screen_point, item_)) {
item_ = nullptr;
event_source_item_ = nullptr;
overview_session_->PositionWindows(true);
RecordNormalDrag(kToDesk, is_dragged_to_other_display);
return DragResult::kDragToDesk;
}
}
if (is_eligible_for_drag_to_snap_ && snap_position_ != SnapPosition::kNone) {
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),
false, false);
if (is_dragged_to_other_display &&
!(dragged_item_is_visible_on_all_desks &&
item_intersects_other_display_desk_bar)) {
int64_t target_display_id =
display::Screen::Get()->GetDisplayNearestWindow(target_root).id();
ScopedWindowsMover mover(target_display_id);
aura::Window* window = item_->GetWindow();
const gfx::RectF target_item_bounds = item_->target_bounds();
if (auto* snap_group =
SnapGroupController::Get()->GetSnapGroupForGivenWindow(window)) {
mover.add_window(snap_group->window1() == window ? snap_group->window2()
: snap_group->window1());
SnapGroupController::Get()->RemoveSnapGroup(
snap_group, SnapGroupExitPoint::kMoveToAnotherDisplay);
item_ = overview_session_->GetOverviewItemForWindow(window);
}
if (!WindowState::Get(window)->allow_set_bounds_direct()) {
overview_session_->RemoveItem(item_, false,
false);
}
item_ = nullptr;
event_source_item_ = nullptr;
mover.add_window(window);
new OverviewItemMoveHelper(mover.windows(), window, target_item_bounds);
} else {
item_->set_should_restack_on_animation_end(true);
overview_session_->PositionWindows(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(
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::Get();
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());
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()) {
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_),
kDistanceFromEdgeDp,
kMinimumDragToSnapDistanceDp,
area.width() *
kHighlightScreenPrimaryAxisRatio +
kHighlightScreenEdgePaddingDp,
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();
item_ = nullptr;
event_source_item_ = nullptr;
split_view_controller->SnapWindow(
window, snap_position,
WindowSnapActionSource::kDragOrSelectOverviewWindowToSnap,
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::Get()->InTabletMode();
if (is_dragged_to_other_display) {
DCHECK(!is_touch_dragging_);
if (!is_tablet) {
constexpr std::array<OverviewDragAction, kNormalDragActionEnumSize>
kDrag = {OverviewDragAction::kToGridOtherDisplayClamshellMouse,
OverviewDragAction::kToDeskOtherDisplayClamshellMouse,
OverviewDragAction::kToSnapOtherDisplayClamshellMouse};
RecordDrag(kDrag[action]);
}
} else if (is_tablet) {
if (is_touch_dragging_) {
constexpr std::array<OverviewDragAction, kNormalDragActionEnumSize>
kDrag = {OverviewDragAction::kToGridSameDisplayTabletTouch,
OverviewDragAction::kToDeskSameDisplayTabletTouch,
OverviewDragAction::kToSnapSameDisplayTabletTouch};
RecordDrag(kDrag[action]);
}
} else {
constexpr std::array<OverviewDragAction, kNormalDragActionEnumSize>
kMouseDrag = {OverviewDragAction::kToGridSameDisplayClamshellMouse,
OverviewDragAction::kToDeskSameDisplayClamshellMouse,
OverviewDragAction::kToSnapSameDisplayClamshellMouse};
constexpr std::array<OverviewDragAction, kNormalDragActionEnumSize>
kTouchDrag = {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 std::array<OverviewDragAction, kDragToCloseActionEnumSize>
kClamshellDrag = {
OverviewDragAction::kSwipeToCloseSuccessfulClamshellTouch,
OverviewDragAction::kSwipeToCloseCanceledClamshellTouch,
OverviewDragAction::kFlingToCloseClamshellTouch};
constexpr std::array<OverviewDragAction, kDragToCloseActionEnumSize>
kTabletDrag = {OverviewDragAction::kSwipeToCloseSuccessfulTabletTouch,
OverviewDragAction::kSwipeToCloseCanceledTabletTouch,
OverviewDragAction::kFlingToCloseTabletTouch};
RecordDrag(display::Screen::Get()->InTabletMode() ? kTabletDrag[action]
: kClamshellDrag[action]);
}
void OverviewWindowDragController::MaybeScaleUpNewDeskButton() {
if (!item_ || !item_->overview_grid()) {
return;
}
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;
}
overview_session_->SuspendReposition();
desks_bar_view->UpdateDeskIconButtonState(
new_desk_button, DeskIconButton::State::kActive);
overview_session_->ResumeReposition();
}
}