1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ash/wm/overview/overview_window_drag_controller.h"
6
7 #include <algorithm>
8 #include <memory>
9 #include <utility>
10
11 #include "ash/display/mouse_cursor_event_filter.h"
12 #include "ash/public/cpp/ash_features.h"
13 #include "ash/public/cpp/presentation_time_recorder.h"
14 #include "ash/screen_util.h"
15 #include "ash/shell.h"
16 #include "ash/wm/desks/desk_preview_view.h"
17 #include "ash/wm/desks/desks_bar_view.h"
18 #include "ash/wm/desks/desks_util.h"
19 #include "ash/wm/overview/overview_constants.h"
20 #include "ash/wm/overview/overview_controller.h"
21 #include "ash/wm/overview/overview_grid.h"
22 #include "ash/wm/overview/overview_item.h"
23 #include "ash/wm/overview/overview_session.h"
24 #include "ash/wm/overview/overview_utils.h"
25 #include "ash/wm/splitview/split_view_constants.h"
26 #include "ash/wm/splitview/split_view_drag_indicators.h"
27 #include "ash/wm/splitview/split_view_utils.h"
28 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
29 #include "ash/wm/window_positioning_utils.h"
30 #include "ash/wm/window_util.h"
31 #include "base/metrics/histogram_functions.h"
32 #include "base/numerics/ranges.h"
33 #include "base/numerics/safe_conversions.h"
34 #include "ui/aura/window.h"
35 #include "ui/aura/window_observer.h"
36 #include "ui/display/display.h"
37 #include "ui/wm/core/coordinate_conversion.h"
38
39 namespace ash {
40
41 namespace {
42
43 // The amount of distance from the start of drag the item needs to be dragged
44 // vertically for it to be closed on release.
45 constexpr float kDragToCloseDistanceThresholdDp = 160.f;
46
47 // The minimum distance that will be considered as a drag event.
48 constexpr float kMinimumDragDistanceDp = 5.f;
49 // Items dragged to within |kDistanceFromEdgeDp| of the screen will get snapped
50 // even if they have not moved by |kMinimumDragToSnapDistanceDp|.
51 constexpr float kDistanceFromEdgeDp = 16.f;
52 // The minimum distance that an item must be moved before it is snapped. This
53 // prevents accidental snaps.
54 constexpr float kMinimumDragToSnapDistanceDp = 96.f;
55
56 // Flings with less velocity than this will not close the dragged item.
57 constexpr float kFlingToCloseVelocityThreshold = 2000.f;
58 constexpr float kItemMinOpacity = 0.4f;
59
60 // Amount of time we wait to unpause the occlusion tracker after a overview item
61 // is finished dragging. Waits a bit longer than the overview item animation.
62 constexpr base::TimeDelta kOcclusionPauseDurationForDrag =
63 base::TimeDelta::FromMilliseconds(300);
64
65 // The UMA histogram that records presentation time for window dragging
66 // operation in overview mode.
67 constexpr char kOverviewWindowDragHistogram[] =
68 "Ash.Overview.WindowDrag.PresentationTime.TabletMode";
69 constexpr char kOverviewWindowDragMaxLatencyHistogram[] =
70 "Ash.Overview.WindowDrag.PresentationTime.MaxLatency.TabletMode";
71
UnpauseOcclusionTracker()72 void UnpauseOcclusionTracker() {
73 Shell::Get()->overview_controller()->UnpauseOcclusionTracker(
74 kOcclusionPauseDurationForDrag);
75 }
76
GetVirtualDesksBarEnabled(OverviewItem * item)77 bool GetVirtualDesksBarEnabled(OverviewItem* item) {
78 return desks_util::ShouldDesksBarBeCreated() &&
79 item->overview_grid()->IsDesksBarViewActive();
80 }
81
82 // Returns the scaled-down size of the dragged item that should be used when
83 // it's dragged over the DesksBarView that belongs to |overview_grid|.
84 // |window_original_size| is the size of the item's window before it was scaled
85 // up for dragging.
GetItemSizeWhenOnDesksBar(OverviewGrid * overview_grid,const gfx::SizeF & window_original_size)86 gfx::SizeF GetItemSizeWhenOnDesksBar(OverviewGrid* overview_grid,
87 const gfx::SizeF& window_original_size) {
88 DCHECK(overview_grid);
89 const DesksBarView* desks_bar_view = overview_grid->desks_bar_view();
90 DCHECK(desks_bar_view);
91
92 const float scale_factor = desks_bar_view->GetOnHoverWindowSizeScaleFactor();
93 gfx::SizeF scaled_size = gfx::ScaleSize(window_original_size, scale_factor);
94 // Add the margins overview mode adds around the window's contents.
95 scaled_size.Enlarge(2 * kWindowMargin, 2 * kWindowMargin + kHeaderHeightDp);
96 return scaled_size;
97 }
98
GetManhattanDistanceX(float point_x,const gfx::RectF & rect)99 float GetManhattanDistanceX(float point_x, const gfx::RectF& rect) {
100 return std::max(rect.x() - point_x, point_x - rect.right());
101 }
102
GetManhattanDistanceY(float point_y,const gfx::RectF & rect)103 float GetManhattanDistanceY(float point_y, const gfx::RectF& rect) {
104 return std::max(rect.y() - point_y, point_y - rect.bottom());
105 }
106
RecordDrag(OverviewDragAction action)107 void RecordDrag(OverviewDragAction action) {
108 base::UmaHistogramEnumeration("Ash.Overview.WindowDrag.Workflow", action);
109 }
110
111 // Runs the given |callback| when this object goes out of scope.
112 class AtScopeExitRunner {
113 public:
AtScopeExitRunner(base::OnceClosure callback)114 explicit AtScopeExitRunner(base::OnceClosure callback)
115 : callback_(std::move(callback)) {
116 DCHECK(!callback_.is_null());
117 }
118
~AtScopeExitRunner()119 ~AtScopeExitRunner() { std::move(callback_).Run(); }
120
121 private:
122 base::OnceClosure callback_;
123
124 DISALLOW_COPY_AND_ASSIGN(AtScopeExitRunner);
125 };
126
127 // Helps with handling the workflow where you drag an overview item from one
128 // grid and drop into another grid. The challenge is that if the item represents
129 // an ARC window, that window will be moved to the target root asynchronously.
130 // |OverviewItemMoveHelper| observes the window until it moves to the target
131 // root. Then |OverviewItemMoveHelper| self destructs and adds a new item to
132 // represent the window on the target root.
133 class OverviewItemMoveHelper : public aura::WindowObserver {
134 public:
135 // |target_item_bounds| is the bounds of the dragged overview item when the
136 // drag ends. |target_item_bounds| is used to put the new item where the old
137 // item ended, so it looks like it is the same item. Then the item is animated
138 // from there to its proper position in the grid.
OverviewItemMoveHelper(aura::Window * window,const gfx::RectF & target_item_bounds)139 OverviewItemMoveHelper(aura::Window* window,
140 const gfx::RectF& target_item_bounds)
141 : window_(window), target_item_bounds_(target_item_bounds) {
142 window->AddObserver(this);
143 }
144 OverviewItemMoveHelper(const OverviewItemMoveHelper&) = delete;
145 OverviewItemMoveHelper& operator=(const OverviewItemMoveHelper&) = delete;
~OverviewItemMoveHelper()146 ~OverviewItemMoveHelper() override {
147 OverviewController* overview_controller =
148 Shell::Get()->overview_controller();
149 if (overview_controller->InOverviewSession()) {
150 overview_controller->overview_session()->PositionWindows(
151 /*animate=*/true);
152 }
153 }
154
155 // aura::WindowObserver:
OnWindowDestroyed(aura::Window * window)156 void OnWindowDestroyed(aura::Window* window) override {
157 DCHECK_EQ(window_, window);
158 delete this;
159 }
OnWindowAddedToRootWindow(aura::Window * window)160 void OnWindowAddedToRootWindow(aura::Window* window) override {
161 DCHECK_EQ(window_, window);
162 window->RemoveObserver(this);
163 OverviewController* overview_controller =
164 Shell::Get()->overview_controller();
165 if (overview_controller->InOverviewSession()) {
166 // OverviewSession::AddItemInMruOrder() will add |window| to the grid
167 // associated with |window|'s root. Do not reposition or restack as we
168 // will soon handle them both anyway.
169 OverviewSession* session = overview_controller->overview_session();
170 session->AddItemInMruOrder(window, /*reposition=*/false,
171 /*animate=*/false, /*restack=*/false);
172 OverviewItem* item = session->GetOverviewItemForWindow(window);
173 DCHECK(item);
174 item->SetBounds(target_item_bounds_, OVERVIEW_ANIMATION_NONE);
175 item->set_should_restack_on_animation_end(true);
176 // The destructor will call OverviewSession::PositionWindows().
177 }
178 delete this;
179 }
180
181 private:
182 aura::Window* const window_;
183 const gfx::RectF target_item_bounds_;
184 };
185
186 } // namespace
187
OverviewWindowDragController(OverviewSession * overview_session,OverviewItem * item,bool is_touch_dragging)188 OverviewWindowDragController::OverviewWindowDragController(
189 OverviewSession* overview_session,
190 OverviewItem* item,
191 bool is_touch_dragging)
192 : overview_session_(overview_session),
193 item_(item),
194 display_count_(Shell::GetAllRootWindows().size()),
195 is_touch_dragging_(is_touch_dragging),
196 should_allow_split_view_(ShouldAllowSplitView()),
197 virtual_desks_bar_enabled_(GetVirtualDesksBarEnabled(item)),
198 are_multi_display_overview_and_splitview_enabled_(
199 AreMultiDisplayOverviewAndSplitViewEnabled()) {
200 DCHECK(!Shell::Get()->overview_controller()->IsInStartAnimation());
201 DCHECK(!SplitViewController::Get(Shell::GetPrimaryRootWindow())
202 ->IsDividerAnimating());
203 }
204
205 OverviewWindowDragController::~OverviewWindowDragController() = default;
206
InitiateDrag(const gfx::PointF & location_in_screen)207 void OverviewWindowDragController::InitiateDrag(
208 const gfx::PointF& location_in_screen) {
209 initial_event_location_ = location_in_screen;
210 initial_centerpoint_ = item_->target_bounds().CenterPoint();
211 original_opacity_ = item_->GetOpacity();
212 current_drag_behavior_ = DragBehavior::kUndefined;
213 Shell::Get()->overview_controller()->PauseOcclusionTracker();
214 DCHECK(!presentation_time_recorder_);
215
216 presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
217 item_->root_window()->layer()->GetCompositor(),
218 kOverviewWindowDragHistogram, kOverviewWindowDragMaxLatencyHistogram);
219 }
220
Drag(const gfx::PointF & location_in_screen)221 void OverviewWindowDragController::Drag(const gfx::PointF& location_in_screen) {
222 if (!did_move_) {
223 gfx::Vector2dF distance = location_in_screen - initial_event_location_;
224 // Do not start dragging if the distance from |location_in_screen| to
225 // |initial_event_location_| is not greater than |kMinimumDragDistanceDp|.
226 if (std::abs(distance.x()) < kMinimumDragDistanceDp &&
227 std::abs(distance.y()) < kMinimumDragDistanceDp) {
228 return;
229 }
230
231 if (is_touch_dragging_ && std::abs(distance.x()) < std::abs(distance.y()))
232 StartDragToCloseMode();
233 else if (should_allow_split_view_ || virtual_desks_bar_enabled_)
234 StartNormalDragMode(location_in_screen);
235 else
236 return;
237 }
238
239 if (current_drag_behavior_ == DragBehavior::kDragToClose)
240 ContinueDragToClose(location_in_screen);
241 else if (current_drag_behavior_ == DragBehavior::kNormalDrag)
242 ContinueNormalDrag(location_in_screen);
243
244 if (presentation_time_recorder_)
245 presentation_time_recorder_->RequestNext();
246 }
247
248 OverviewWindowDragController::DragResult
CompleteDrag(const gfx::PointF & location_in_screen)249 OverviewWindowDragController::CompleteDrag(
250 const gfx::PointF& location_in_screen) {
251 per_grid_desks_bar_data_.clear();
252 DragResult result = DragResult::kNeverDisambiguated;
253 switch (current_drag_behavior_) {
254 case DragBehavior::kNoDrag:
255 NOTREACHED();
256 break;
257
258 case DragBehavior::kUndefined:
259 ActivateDraggedWindow();
260 break;
261
262 case DragBehavior::kNormalDrag:
263 result = CompleteNormalDrag(location_in_screen);
264 break;
265
266 case DragBehavior::kDragToClose:
267 result = CompleteDragToClose(location_in_screen);
268 break;
269 }
270
271 did_move_ = false;
272 item_ = nullptr;
273 current_drag_behavior_ = DragBehavior::kNoDrag;
274 UnpauseOcclusionTracker();
275 presentation_time_recorder_.reset();
276 return result;
277 }
278
StartNormalDragMode(const gfx::PointF & location_in_screen)279 void OverviewWindowDragController::StartNormalDragMode(
280 const gfx::PointF& location_in_screen) {
281 DCHECK(should_allow_split_view_ || virtual_desks_bar_enabled_);
282
283 did_move_ = true;
284 current_drag_behavior_ = DragBehavior::kNormalDrag;
285 if (are_multi_display_overview_and_splitview_enabled_) {
286 Shell::Get()->mouse_cursor_filter()->ShowSharedEdgeIndicator(
287 item_->root_window());
288 }
289 const gfx::SizeF window_original_size(item_->GetWindow()->bounds().size());
290 item_->ScaleUpSelectedItem(
291 OVERVIEW_ANIMATION_LAYOUT_OVERVIEW_ITEMS_IN_OVERVIEW);
292 original_scaled_size_ = item_->target_bounds().size();
293 auto* overview_grid = item_->overview_grid();
294 overview_grid->AddDropTargetForDraggingFromThisGrid(item_);
295
296 if (should_allow_split_view_) {
297 overview_session_->SetSplitViewDragIndicatorsDraggedWindow(
298 item_->GetWindow());
299 overview_session_->UpdateSplitViewDragIndicatorsWindowDraggingStates(
300 GetRootWindowBeingDraggedIn(),
301 SplitViewDragIndicators::ComputeWindowDraggingState(
302 /*is_dragging=*/true,
303 SplitViewDragIndicators::WindowDraggingState::kFromOverview,
304 SplitViewController::NONE));
305 item_->HideCannotSnapWarning();
306
307 // Update the split view divider bar status if necessary. If splitview is
308 // active when dragging the overview window, the split divider bar should be
309 // placed below the dragged window during dragging.
310 SplitViewController::Get(Shell::GetPrimaryRootWindow())
311 ->OnWindowDragStarted(item_->GetWindow());
312 }
313
314 if (virtual_desks_bar_enabled_) {
315 // Calculate the item bounds minus the header and margins (which are
316 // invisible). Use this for the shrink bounds so that the item starts
317 // shrinking when the visible top-edge of the item aligns with the
318 // bottom-edge of the desks bar (may be different edges if we are dragging
319 // from different directions).
320 gfx::SizeF item_no_header_size = original_scaled_size_;
321 item_no_header_size.Enlarge(float{-kWindowMargin * 2},
322 float{-kWindowMargin * 2 - kHeaderHeightDp});
323
324 // We must update the desks bar widget bounds before we cache its bounds
325 // below, in case it needs to be pushed down due to splitview indicators.
326 // Note that when drag is just getting started, the window hasn't moved to
327 // another display, so it's ok to use the item's |overview_grid|.
328 overview_grid->MaybeUpdateDesksWidgetBounds();
329
330 // Calculate cached values for usage during drag for each grid.
331 for (const auto& grid : overview_session_->grid_list()) {
332 GridDesksBarData& grid_desks_bar_data =
333 per_grid_desks_bar_data_[grid.get()];
334
335 grid_desks_bar_data.on_desks_bar_item_size =
336 GetItemSizeWhenOnDesksBar(grid.get(), window_original_size);
337 grid_desks_bar_data.desks_bar_bounds = grid_desks_bar_data.shrink_bounds =
338 gfx::RectF(grid->desks_bar_view()->GetBoundsInScreen());
339 grid_desks_bar_data.shrink_bounds.Inset(
340 -item_no_header_size.width() / 2, -item_no_header_size.height() / 2);
341 grid_desks_bar_data.shrink_region_distance =
342 grid_desks_bar_data.desks_bar_bounds.origin() -
343 grid_desks_bar_data.shrink_bounds.origin();
344 }
345 }
346 }
347
Fling(const gfx::PointF & location_in_screen,float velocity_x,float velocity_y)348 OverviewWindowDragController::DragResult OverviewWindowDragController::Fling(
349 const gfx::PointF& location_in_screen,
350 float velocity_x,
351 float velocity_y) {
352 if (current_drag_behavior_ == DragBehavior::kDragToClose ||
353 current_drag_behavior_ == DragBehavior::kUndefined) {
354 if (std::abs(velocity_y) > kFlingToCloseVelocityThreshold) {
355 item_->AnimateAndCloseWindow(
356 (location_in_screen - initial_event_location_).y() < 0);
357 did_move_ = false;
358 item_ = nullptr;
359 current_drag_behavior_ = DragBehavior::kNoDrag;
360 UnpauseOcclusionTracker();
361 RecordDragToClose(kFlingToClose);
362 return DragResult::kSuccessfulDragToClose;
363 }
364 }
365
366 // If the fling velocity was not high enough, or flings should be ignored,
367 // treat it as a scroll end event.
368 return CompleteDrag(location_in_screen);
369 }
370
ActivateDraggedWindow()371 void OverviewWindowDragController::ActivateDraggedWindow() {
372 // If no drag was initiated (e.g., a click/tap on the overview window),
373 // activate the window. If the split view is active and has a left window,
374 // snap the current window to right. If the split view is active and has a
375 // right window, snap the current window to left. If split view is active
376 // and the selected window cannot be snapped, exit splitview and activate
377 // the selected window, and also exit the overview.
378 SplitViewController* split_view_controller =
379 SplitViewController::Get(item_->root_window());
380 SplitViewController::State split_state = split_view_controller->state();
381 if (!should_allow_split_view_ ||
382 split_state == SplitViewController::State::kNoSnap) {
383 overview_session_->SelectWindow(item_);
384 } else if (split_view_controller->CanSnapWindow(item_->GetWindow())) {
385 SnapWindow(split_view_controller,
386 split_state == SplitViewController::State::kLeftSnapped
387 ? SplitViewController::RIGHT
388 : SplitViewController::LEFT);
389 } else {
390 split_view_controller->EndSplitView();
391 overview_session_->SelectWindow(item_);
392 ShowAppCannotSnapToast();
393 }
394 current_drag_behavior_ = DragBehavior::kNoDrag;
395 UnpauseOcclusionTracker();
396 }
397
ResetGesture()398 void OverviewWindowDragController::ResetGesture() {
399 if (current_drag_behavior_ == DragBehavior::kNormalDrag) {
400 DCHECK(item_->overview_grid()->drop_target_widget());
401
402 if (are_multi_display_overview_and_splitview_enabled_) {
403 Shell::Get()->mouse_cursor_filter()->HideSharedEdgeIndicator();
404 item_->DestroyPhantomsForDragging();
405 }
406 overview_session_->RemoveDropTargets();
407 if (should_allow_split_view_) {
408 SplitViewController::Get(Shell::GetPrimaryRootWindow())
409 ->OnWindowDragCanceled();
410 overview_session_->ResetSplitViewDragIndicatorsWindowDraggingStates();
411 item_->UpdateCannotSnapWarningVisibility();
412 }
413 }
414 overview_session_->PositionWindows(/*animate=*/true);
415 // This function gets called after a long press release, which bypasses
416 // CompleteDrag but stops dragging as well, so reset |item_|.
417 item_ = nullptr;
418 current_drag_behavior_ = DragBehavior::kNoDrag;
419 UnpauseOcclusionTracker();
420 }
421
ResetOverviewSession()422 void OverviewWindowDragController::ResetOverviewSession() {
423 overview_session_ = nullptr;
424 }
425
StartDragToCloseMode()426 void OverviewWindowDragController::StartDragToCloseMode() {
427 DCHECK(is_touch_dragging_);
428
429 did_move_ = true;
430 current_drag_behavior_ = DragBehavior::kDragToClose;
431 overview_session_->GetGridWithRootWindow(item_->root_window())
432 ->StartNudge(item_);
433 }
434
ContinueDragToClose(const gfx::PointF & location_in_screen)435 void OverviewWindowDragController::ContinueDragToClose(
436 const gfx::PointF& location_in_screen) {
437 DCHECK_EQ(current_drag_behavior_, DragBehavior::kDragToClose);
438
439 // Update the dragged |item_|'s bounds accordingly. The distance from the new
440 // location to the new centerpoint should be the same it was initially.
441 gfx::RectF bounds(item_->target_bounds());
442 const gfx::PointF centerpoint =
443 location_in_screen - (initial_event_location_ - initial_centerpoint_);
444
445 // If the drag location intersects with the desk bar, then we should cancel
446 // the drag-to-close mode and start the normal drag mode.
447 if (virtual_desks_bar_enabled_ &&
448 item_->overview_grid()->IntersectsWithDesksBar(
449 gfx::ToRoundedPoint(location_in_screen),
450 /*update_desks_bar_drag_details=*/false, /*for_drop=*/false)) {
451 item_->SetOpacity(original_opacity_);
452 StartNormalDragMode(location_in_screen);
453 ContinueNormalDrag(location_in_screen);
454 return;
455 }
456
457 // Update |item_|'s opacity based on its distance. |item_|'s x coordinate
458 // should not change while in drag to close state.
459 float val = std::abs(location_in_screen.y() - initial_event_location_.y()) /
460 kDragToCloseDistanceThresholdDp;
461 overview_session_->GetGridWithRootWindow(item_->root_window())
462 ->UpdateNudge(item_, val);
463 val = base::ClampToRange(val, 0.f, 1.f);
464 float opacity = original_opacity_;
465 if (opacity > kItemMinOpacity)
466 opacity = original_opacity_ - val * (original_opacity_ - kItemMinOpacity);
467 item_->SetOpacity(opacity);
468
469 // When dragging to close, only update the y component.
470 bounds.set_y(centerpoint.y() - bounds.height() / 2.f);
471 item_->SetBounds(bounds, OVERVIEW_ANIMATION_NONE);
472 }
473
474 OverviewWindowDragController::DragResult
CompleteDragToClose(const gfx::PointF & location_in_screen)475 OverviewWindowDragController::CompleteDragToClose(
476 const gfx::PointF& location_in_screen) {
477 DCHECK_EQ(current_drag_behavior_, DragBehavior::kDragToClose);
478
479 // Close the window if it has been dragged enough, otherwise reposition it and
480 // set its opacity back to its original value.
481 overview_session_->GetGridWithRootWindow(item_->root_window())->EndNudge();
482 const float y_distance = (location_in_screen - initial_event_location_).y();
483 if (std::abs(y_distance) > kDragToCloseDistanceThresholdDp) {
484 item_->AnimateAndCloseWindow(/*up=*/y_distance < 0);
485 RecordDragToClose(kSwipeToCloseSuccessful);
486 return DragResult::kSuccessfulDragToClose;
487 }
488
489 item_->SetOpacity(original_opacity_);
490 overview_session_->PositionWindows(/*animate=*/true);
491 RecordDragToClose(kSwipeToCloseCanceled);
492 return DragResult::kCanceledDragToClose;
493 }
494
ContinueNormalDrag(const gfx::PointF & location_in_screen)495 void OverviewWindowDragController::ContinueNormalDrag(
496 const gfx::PointF& location_in_screen) {
497 DCHECK_EQ(current_drag_behavior_, DragBehavior::kNormalDrag);
498
499 // Update the dragged |item_|'s bounds accordingly. The distance from the new
500 // location to the new centerpoint should be the same it was initially unless
501 // the item is over the DeskBarView, in which case we scale it down and center
502 // it around the drag location.
503 gfx::RectF bounds(item_->target_bounds());
504 gfx::PointF centerpoint =
505 location_in_screen - (initial_event_location_ - initial_centerpoint_);
506
507 // If virtual desks is enabled, we want to gradually shrink the dragged item
508 // as it gets closer to get dropped into a desk mini view.
509 auto* overview_grid = GetCurrentGrid();
510 if (virtual_desks_bar_enabled_) {
511 // TODO(sammiequon): There is a slight jump especially if we drag from the
512 // corner of a larger overview item, but this is necessary for the time
513 // being to prevent jumps from happening while shrinking. Investigate if we
514 // can satisfy all cases.
515 centerpoint = location_in_screen;
516 // To make the dragged window contents appear centered around the drag
517 // location, we need to take into account the margins applied on the
518 // target bounds, and offset up the centerpoint by half that amount, so
519 // that the transformed bounds of the window contents move up to be
520 // centered around the cursor.
521 centerpoint.Offset(0, (-kWindowMargin - kHeaderHeightDp) / 2);
522
523 const auto iter = per_grid_desks_bar_data_.find(overview_grid);
524 DCHECK(iter != per_grid_desks_bar_data_.end());
525 const GridDesksBarData& desks_bar_data = iter->second;
526
527 if (desks_bar_data.shrink_bounds.Contains(location_in_screen)) {
528 // Update the mini views borders by checking if |location_in_screen|
529 // intersects.
530 overview_grid->IntersectsWithDesksBar(
531 gfx::ToRoundedPoint(location_in_screen),
532 /*update_desks_bar_drag_details=*/true, /*for_drop=*/false);
533
534 float value = 0.f;
535 if (centerpoint.y() < desks_bar_data.desks_bar_bounds.y() ||
536 centerpoint.y() > desks_bar_data.desks_bar_bounds.bottom()) {
537 // Coming vertically, this is the main use case. This is a ratio of the
538 // distance from |centerpoint| to the closest edge of |desk_bar_bounds|
539 // to the distance from |shrink_bounds| to |desk_bar_bounds|.
540 value = GetManhattanDistanceY(centerpoint.y(),
541 desks_bar_data.desks_bar_bounds) /
542 desks_bar_data.shrink_region_distance.y();
543 } else if (centerpoint.x() < desks_bar_data.desks_bar_bounds.x() ||
544 centerpoint.x() > desks_bar_data.desks_bar_bounds.right()) {
545 // Coming horizontally, this only happens if we are in landscape split
546 // view and someone drags an item to the other half, then up, then into
547 // the desks bar. Works same as vertically except using x-coordinates.
548 value = GetManhattanDistanceX(centerpoint.x(),
549 desks_bar_data.desks_bar_bounds) /
550 desks_bar_data.shrink_region_distance.x();
551 }
552 value = base::ClampToRange(value, 0.f, 1.f);
553 const gfx::SizeF size_value =
554 gfx::Tween::SizeFValueBetween(1.f - value, original_scaled_size_,
555 desks_bar_data.on_desks_bar_item_size);
556 bounds.set_size(size_value);
557 } else {
558 bounds.set_size(original_scaled_size_);
559 }
560 }
561
562 if (should_allow_split_view_) {
563 UpdateDragIndicatorsAndOverviewGrid(location_in_screen);
564 // The newly updated indicator state may cause the desks widget to be pushed
565 // down to make room for the top splitview guidance indicator when in
566 // portrait orientation in tablet mode.
567 overview_grid->MaybeUpdateDesksWidgetBounds();
568 }
569 if (are_multi_display_overview_and_splitview_enabled_) {
570 OverviewGrid* overview_grid =
571 overview_session_->GetGridWithRootWindow(GetRootWindowBeingDraggedIn());
572 if (!overview_grid->GetDropTarget() &&
573 (!should_allow_split_view_ ||
574 SplitViewDragIndicators::GetSnapPosition(
575 overview_grid->split_view_drag_indicators()
576 ->current_window_dragging_state()) ==
577 SplitViewController::NONE)) {
578 overview_grid->AddDropTargetNotForDraggingFromThisGrid(item_->GetWindow(),
579 /*animate=*/true);
580 }
581 }
582 overview_session_->UpdateDropTargetsBackgroundVisibilities(
583 item_, location_in_screen);
584
585 bounds.set_x(centerpoint.x() - bounds.width() / 2.f);
586 bounds.set_y(centerpoint.y() - bounds.height() / 2.f);
587 item_->SetBounds(bounds, OVERVIEW_ANIMATION_NONE);
588 if (are_multi_display_overview_and_splitview_enabled_ && display_count_ > 1u)
589 item_->UpdatePhantomsForDragging(is_touch_dragging_);
590 }
591
592 OverviewWindowDragController::DragResult
CompleteNormalDrag(const gfx::PointF & location_in_screen)593 OverviewWindowDragController::CompleteNormalDrag(
594 const gfx::PointF& location_in_screen) {
595 DCHECK_EQ(current_drag_behavior_, DragBehavior::kNormalDrag);
596 auto* item_overview_grid = item_->overview_grid();
597 DCHECK(item_overview_grid->drop_target_widget());
598 if (are_multi_display_overview_and_splitview_enabled_) {
599 Shell::Get()->mouse_cursor_filter()->HideSharedEdgeIndicator();
600 item_->DestroyPhantomsForDragging();
601 }
602 overview_session_->RemoveDropTargets();
603
604 const gfx::Point rounded_screen_point =
605 gfx::ToRoundedPoint(location_in_screen);
606 if (should_allow_split_view_) {
607 // Update the split view divider bar stuatus if necessary. The divider bar
608 // should be placed above the dragged window after drag ends. Note here the
609 // passed parameters |snap_position_| and |location_in_screen| won't be used
610 // in this function for this case, but they are passed in as placeholders.
611 SplitViewController::Get(Shell::GetPrimaryRootWindow())
612 ->OnWindowDragEnded(item_->GetWindow(), snap_position_,
613 rounded_screen_point);
614
615 // Update window grid bounds and |snap_position_| in case the screen
616 // orientation was changed.
617 UpdateDragIndicatorsAndOverviewGrid(location_in_screen);
618 overview_session_->ResetSplitViewDragIndicatorsWindowDraggingStates();
619 item_->UpdateCannotSnapWarningVisibility();
620 }
621
622 // This function has multiple exit positions, at each we must update the desks
623 // bar widget bounds. We can't do this before we attempt dropping the window
624 // on a desk mini_view, since this will change where it is relative to the
625 // current |location_in_screen|.
626 AtScopeExitRunner at_exit_runner{base::BindOnce([]() {
627 // Overview might have exited if we snapped windows on both sides.
628 auto* overview_controller = Shell::Get()->overview_controller();
629 if (!overview_controller->InOverviewSession())
630 return;
631
632 for (auto& grid : overview_controller->overview_session()->grid_list())
633 grid->MaybeUpdateDesksWidgetBounds();
634 })};
635
636 aura::Window* target_root = GetRootWindowBeingDraggedIn();
637 const bool is_dragged_to_other_display =
638 AreMultiDisplayOverviewAndSplitViewEnabled() &&
639 target_root != item_->root_window();
640 if (virtual_desks_bar_enabled_) {
641 item_->SetOpacity(original_opacity_);
642
643 // Attempt to move a window to a different desk.
644 if (GetCurrentGrid()->MaybeDropItemOnDeskMiniView(rounded_screen_point,
645 item_)) {
646 // Window was successfully moved to another desk, and |item_| was
647 // removed from the grid. It may never be accessed after this.
648 item_ = nullptr;
649 overview_session_->PositionWindows(/*animate=*/true);
650 RecordNormalDrag(kToDesk, is_dragged_to_other_display);
651 return DragResult::kDragToDesk;
652 }
653 }
654
655 // Snap a window if appropriate.
656 if (should_allow_split_view_ && snap_position_ != SplitViewController::NONE) {
657 SnapWindow(SplitViewController::Get(target_root), snap_position_);
658 overview_session_->PositionWindows(/*animate=*/true);
659 RecordNormalDrag(kToSnap, is_dragged_to_other_display);
660 return DragResult::kSnap;
661 }
662
663 // Drop a window into overview because we have not done anything else with it.
664 DCHECK(item_);
665 if (is_dragged_to_other_display) {
666 // Get the window and bounds from |item_| before removing it from its grid.
667 aura::Window* window = item_->GetWindow();
668 const gfx::RectF target_item_bounds = item_->target_bounds();
669 // Remove |item_| from overview. Leave the repositioning to the
670 // |OverviewItemMoveHelper|.
671 overview_session_->RemoveItem(item_, /*item_destroying=*/false,
672 /*reposition=*/false);
673 item_ = nullptr;
674 // The |OverviewItemMoveHelper| will self destruct when we move |window| to
675 // |target_root|.
676 new OverviewItemMoveHelper(window, target_item_bounds);
677 // Move |window| to |target_root|. The |OverviewItemMoveHelper| will take
678 // care of the rest.
679 window_util::MoveWindowToDisplay(window,
680 display::Screen::GetScreen()
681 ->GetDisplayNearestWindow(target_root)
682 .id());
683 } else {
684 item_->set_should_restack_on_animation_end(true);
685 overview_session_->PositionWindows(/*animate=*/true);
686 }
687 RecordNormalDrag(kToGrid, is_dragged_to_other_display);
688 return DragResult::kDropIntoOverview;
689 }
690
UpdateDragIndicatorsAndOverviewGrid(const gfx::PointF & location_in_screen)691 void OverviewWindowDragController::UpdateDragIndicatorsAndOverviewGrid(
692 const gfx::PointF& location_in_screen) {
693 DCHECK(should_allow_split_view_);
694 snap_position_ = GetSnapPosition(location_in_screen);
695 overview_session_->UpdateSplitViewDragIndicatorsWindowDraggingStates(
696 GetRootWindowBeingDraggedIn(),
697 SplitViewDragIndicators::ComputeWindowDraggingState(
698 /*is_dragging=*/true,
699 SplitViewDragIndicators::WindowDraggingState::kFromOverview,
700 snap_position_));
701 overview_session_->RearrangeDuringDrag(item_);
702 }
703
GetRootWindowBeingDraggedIn() const704 aura::Window* OverviewWindowDragController::GetRootWindowBeingDraggedIn()
705 const {
706 return is_touch_dragging_
707 ? item_->root_window()
708 : Shell::GetRootWindowForDisplayId(
709 Shell::Get()->cursor_manager()->GetDisplay().id());
710 }
711
GetSnapPosition(const gfx::PointF & location_in_screen) const712 SplitViewController::SnapPosition OverviewWindowDragController::GetSnapPosition(
713 const gfx::PointF& location_in_screen) const {
714 DCHECK(item_);
715 DCHECK(should_allow_split_view_);
716 gfx::Rect area =
717 screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
718 GetRootWindowBeingDraggedIn());
719
720 // If split view mode is active at the moment, and dragging an overview window
721 // to snap it to a position that already has a snapped window in place, we
722 // should show the preview window as soon as the window past the split divider
723 // bar.
724 SplitViewController* split_view_controller =
725 SplitViewController::Get(GetRootWindowBeingDraggedIn());
726 if (!split_view_controller->CanSnapWindow(item_->GetWindow()))
727 return SplitViewController::NONE;
728 if (split_view_controller->InSplitViewMode()) {
729 const int position =
730 base::ClampRound(SplitViewController::IsLayoutHorizontal()
731 ? location_in_screen.x() - area.x()
732 : location_in_screen.y() - area.y());
733 SplitViewController::SnapPosition default_snap_position =
734 split_view_controller->default_snap_position();
735 // If we're trying to snap to a position that already has a snapped window:
736 const bool is_default_snap_position_left_or_top =
737 SplitViewController::IsPhysicalLeftOrTop(default_snap_position);
738 const bool is_drag_position_left_or_top =
739 position < split_view_controller->divider_position();
740 if (is_default_snap_position_left_or_top == is_drag_position_left_or_top)
741 return default_snap_position;
742 }
743
744 return ::ash::GetSnapPosition(
745 GetRootWindowBeingDraggedIn(), item_->GetWindow(),
746 gfx::ToRoundedPoint(location_in_screen),
747 gfx::ToRoundedPoint(initial_event_location_),
748 /*snap_distance_from_edge=*/kDistanceFromEdgeDp,
749 /*minimum_drag_distance=*/kMinimumDragToSnapDistanceDp,
750 /*horizontal_edge_inset=*/area.width() *
751 kHighlightScreenPrimaryAxisRatio +
752 kHighlightScreenEdgePaddingDp,
753 /*vertical_edge_inset=*/area.height() * kHighlightScreenPrimaryAxisRatio +
754 kHighlightScreenEdgePaddingDp);
755 }
756
SnapWindow(SplitViewController * split_view_controller,SplitViewController::SnapPosition snap_position)757 void OverviewWindowDragController::SnapWindow(
758 SplitViewController* split_view_controller,
759 SplitViewController::SnapPosition snap_position) {
760 DCHECK_NE(snap_position, SplitViewController::NONE);
761
762 // |item_| will be deleted after SplitViewController::SnapWindow().
763 DCHECK(!SplitViewController::Get(Shell::GetPrimaryRootWindow())
764 ->IsDividerAnimating());
765 aura::Window* window = item_->GetWindow();
766 split_view_controller->SnapWindow(window, snap_position,
767 /*use_divider_spawn_animation=*/true);
768 item_ = nullptr;
769 wm::ActivateWindow(window);
770 }
771
GetCurrentGrid() const772 OverviewGrid* OverviewWindowDragController::GetCurrentGrid() const {
773 return are_multi_display_overview_and_splitview_enabled_
774 ? overview_session_->GetGridWithRootWindow(
775 GetRootWindowBeingDraggedIn())
776 : item_->overview_grid();
777 }
778
RecordNormalDrag(NormalDragAction action,bool is_dragged_to_other_display) const779 void OverviewWindowDragController::RecordNormalDrag(
780 NormalDragAction action,
781 bool is_dragged_to_other_display) const {
782 const bool is_tablet = Shell::Get()->tablet_mode_controller()->InTabletMode();
783 if (is_dragged_to_other_display) {
784 DCHECK(!is_touch_dragging_);
785 if (!is_tablet) {
786 constexpr OverviewDragAction kDrag[kNormalDragActionEnumSize] = {
787 OverviewDragAction::kToGridOtherDisplayClamshellMouse,
788 OverviewDragAction::kToDeskOtherDisplayClamshellMouse,
789 OverviewDragAction::kToSnapOtherDisplayClamshellMouse};
790 RecordDrag(kDrag[action]);
791 }
792 } else if (is_tablet) {
793 if (is_touch_dragging_) {
794 constexpr OverviewDragAction kDrag[kNormalDragActionEnumSize] = {
795 OverviewDragAction::kToGridSameDisplayTabletTouch,
796 OverviewDragAction::kToDeskSameDisplayTabletTouch,
797 OverviewDragAction::kToSnapSameDisplayTabletTouch};
798 RecordDrag(kDrag[action]);
799 }
800 } else {
801 constexpr OverviewDragAction kMouseDrag[kNormalDragActionEnumSize] = {
802 OverviewDragAction::kToGridSameDisplayClamshellMouse,
803 OverviewDragAction::kToDeskSameDisplayClamshellMouse,
804 OverviewDragAction::kToSnapSameDisplayClamshellMouse};
805 constexpr OverviewDragAction kTouchDrag[kNormalDragActionEnumSize] = {
806 OverviewDragAction::kToGridSameDisplayClamshellTouch,
807 OverviewDragAction::kToDeskSameDisplayClamshellTouch,
808 OverviewDragAction::kToSnapSameDisplayClamshellTouch};
809 RecordDrag(is_touch_dragging_ ? kTouchDrag[action] : kMouseDrag[action]);
810 }
811 }
812
RecordDragToClose(DragToCloseAction action) const813 void OverviewWindowDragController::RecordDragToClose(
814 DragToCloseAction action) const {
815 DCHECK(is_touch_dragging_);
816 constexpr OverviewDragAction kClamshellDrag[kDragToCloseActionEnumSize] = {
817 OverviewDragAction::kSwipeToCloseSuccessfulClamshellTouch,
818 OverviewDragAction::kSwipeToCloseCanceledClamshellTouch,
819 OverviewDragAction::kFlingToCloseClamshellTouch};
820 constexpr OverviewDragAction kTabletDrag[kDragToCloseActionEnumSize] = {
821 OverviewDragAction::kSwipeToCloseSuccessfulTabletTouch,
822 OverviewDragAction::kSwipeToCloseCanceledTabletTouch,
823 OverviewDragAction::kFlingToCloseTabletTouch};
824 RecordDrag(Shell::Get()->tablet_mode_controller()->InTabletMode()
825 ? kTabletDrag[action]
826 : kClamshellDrag[action]);
827 }
828
829 } // namespace ash
830