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