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/splitview/split_view_utils.h"
6 
7 #include "ash/accessibility/accessibility_controller_impl.h"
8 #include "ash/public/cpp/ash_features.h"
9 #include "ash/public/cpp/ash_switches.h"
10 #include "ash/public/cpp/toast_data.h"
11 #include "ash/screen_util.h"
12 #include "ash/shell.h"
13 #include "ash/strings/grit/ash_strings.h"
14 #include "ash/system/toast/toast_manager_impl.h"
15 #include "ash/wm/mru_window_tracker.h"
16 #include "ash/wm/overview/overview_controller.h"
17 #include "ash/wm/screen_pinning_controller.h"
18 #include "ash/wm/splitview/split_view_constants.h"
19 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
20 #include "ash/wm/window_state.h"
21 #include "base/command_line.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/compositor/layer.h"
24 #include "ui/compositor/layer_animation_observer.h"
25 #include "ui/compositor/layer_animator.h"
26 #include "ui/compositor/scoped_layer_animation_settings.h"
27 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
28 #include "ui/views/widget/widget.h"
29 #include "ui/views/widget/widget_delegate.h"
30 #include "ui/wm/core/transient_window_manager.h"
31 
32 namespace ash {
33 
34 namespace {
35 
36 using ::chromeos::WindowStateType;
37 
38 // The animation speed at which the highlights fade in or out.
39 constexpr base::TimeDelta kHighlightsFadeInOut =
40     base::TimeDelta::FromMilliseconds(250);
41 // The animation speed which the other highlight fades in or out.
42 constexpr base::TimeDelta kOtherFadeInOut =
43     base::TimeDelta::FromMilliseconds(133);
44 // The delay before the other highlight starts fading in.
45 constexpr base::TimeDelta kOtherFadeInDelay =
46     base::TimeDelta::FromMilliseconds(117);
47 // The animation speed at which the preview area fades out (when you snap a
48 // window).
49 constexpr base::TimeDelta kPreviewAreaFadeOut =
50     base::TimeDelta::FromMilliseconds(67);
51 // The time duration for the indicator label opacity animations.
52 constexpr base::TimeDelta kLabelAnimation =
53     base::TimeDelta::FromMilliseconds(83);
54 // The delay before the indicator labels start fading in.
55 constexpr base::TimeDelta kLabelAnimationDelay =
56     base::TimeDelta::FromMilliseconds(167);
57 
58 // Toast data.
59 constexpr char kAppCannotSnapToastId[] = "split_view_app_cannot_snap";
60 constexpr int kAppCannotSnapToastDurationMs = 2500;
61 
62 // Gets the duration, tween type and delay before animation based on |type|.
GetAnimationValuesForType(SplitviewAnimationType type,base::TimeDelta * out_duration,gfx::Tween::Type * out_tween_type,ui::LayerAnimator::PreemptionStrategy * out_preemption_strategy,base::TimeDelta * out_delay)63 void GetAnimationValuesForType(
64     SplitviewAnimationType type,
65     base::TimeDelta* out_duration,
66     gfx::Tween::Type* out_tween_type,
67     ui::LayerAnimator::PreemptionStrategy* out_preemption_strategy,
68     base::TimeDelta* out_delay) {
69   *out_preemption_strategy = ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET;
70   switch (type) {
71     case SPLITVIEW_ANIMATION_HIGHLIGHT_FADE_IN:
72     case SPLITVIEW_ANIMATION_HIGHLIGHT_FADE_IN_CANNOT_SNAP:
73     case SPLITVIEW_ANIMATION_HIGHLIGHT_FADE_OUT:
74     case SPLITVIEW_ANIMATION_PREVIEW_AREA_FADE_IN:
75     case SPLITVIEW_ANIMATION_OVERVIEW_ITEM_FADE_IN:
76     case SPLITVIEW_ANIMATION_OVERVIEW_ITEM_FADE_OUT:
77     case SPLITVIEW_ANIMATION_TEXT_FADE_IN_WITH_HIGHLIGHT:
78     case SPLITVIEW_ANIMATION_TEXT_FADE_OUT_WITH_HIGHLIGHT:
79     case SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_IN:
80     case SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_OUT:
81     case SPLITVIEW_ANIMATION_PREVIEW_AREA_TEXT_SLIDE_IN:
82     case SPLITVIEW_ANIMATION_PREVIEW_AREA_TEXT_SLIDE_OUT:
83       *out_duration = kHighlightsFadeInOut;
84       *out_tween_type = gfx::Tween::FAST_OUT_SLOW_IN;
85       return;
86     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_FADE_IN:
87     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_FADE_IN_CANNOT_SNAP:
88     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_SLIDE_IN:
89     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_TEXT_SLIDE_IN:
90       *out_delay = kOtherFadeInDelay;
91       *out_duration = kOtherFadeInOut;
92       *out_tween_type = gfx::Tween::LINEAR_OUT_SLOW_IN;
93       *out_preemption_strategy = ui::LayerAnimator::ENQUEUE_NEW_ANIMATION;
94       return;
95     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_FADE_OUT:
96     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_SLIDE_OUT:
97     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_TEXT_SLIDE_OUT:
98       *out_duration = kOtherFadeInOut;
99       *out_tween_type = gfx::Tween::FAST_OUT_LINEAR_IN;
100       return;
101     case SPLITVIEW_ANIMATION_PREVIEW_AREA_FADE_OUT:
102     case SPLITVIEW_ANIMATION_PREVIEW_AREA_NIX_INSET:
103       *out_duration = kPreviewAreaFadeOut;
104       *out_tween_type = gfx::Tween::FAST_OUT_LINEAR_IN;
105       return;
106     case SPLITVIEW_ANIMATION_TEXT_FADE_IN:
107       *out_delay = kLabelAnimationDelay;
108       *out_duration = kLabelAnimation;
109       *out_tween_type = gfx::Tween::LINEAR_OUT_SLOW_IN;
110       *out_preemption_strategy = ui::LayerAnimator::ENQUEUE_NEW_ANIMATION;
111       return;
112     case SPLITVIEW_ANIMATION_TEXT_FADE_OUT:
113       *out_duration = kLabelAnimation;
114       *out_tween_type = gfx::Tween::FAST_OUT_LINEAR_IN;
115       return;
116     case SPLITVIEW_ANIMATION_SET_WINDOW_TRANSFORM:
117       *out_duration = kSplitviewWindowTransformDuration;
118       *out_tween_type = gfx::Tween::FAST_OUT_SLOW_IN;
119       *out_preemption_strategy =
120           ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET;
121       return;
122   }
123 
124   NOTREACHED();
125 }
126 
127 // Helper function to apply animation values to |settings|.
ApplyAnimationSettings(ui::ScopedLayerAnimationSettings * settings,ui::LayerAnimator * animator,ui::LayerAnimationElement::AnimatableProperties animated_property,base::TimeDelta duration,gfx::Tween::Type tween,ui::LayerAnimator::PreemptionStrategy preemption_strategy,base::TimeDelta delay)128 void ApplyAnimationSettings(
129     ui::ScopedLayerAnimationSettings* settings,
130     ui::LayerAnimator* animator,
131     ui::LayerAnimationElement::AnimatableProperties animated_property,
132     base::TimeDelta duration,
133     gfx::Tween::Type tween,
134     ui::LayerAnimator::PreemptionStrategy preemption_strategy,
135     base::TimeDelta delay) {
136   DCHECK_EQ(settings->GetAnimator(), animator);
137   settings->SetTransitionDuration(duration);
138   settings->SetTweenType(tween);
139   settings->SetPreemptionStrategy(preemption_strategy);
140   if (!delay.is_zero())
141     animator->SchedulePauseForProperties(delay, animated_property);
142 }
143 
144 // Returns BubbleDialogDelegateView if |transient_window| is a bubble dialog.
AsBubbleDialogDelegate(aura::Window * transient_window)145 views::BubbleDialogDelegate* AsBubbleDialogDelegate(
146     aura::Window* transient_window) {
147   views::Widget* widget =
148       views::Widget::GetWidgetForNativeWindow(transient_window);
149   if (!widget || !widget->widget_delegate())
150     return nullptr;
151   return widget->widget_delegate()->AsBubbleDialogDelegate();
152 }
153 
154 }  // namespace
155 
WindowTransformAnimationObserver(aura::Window * window)156 WindowTransformAnimationObserver::WindowTransformAnimationObserver(
157     aura::Window* window)
158     : window_(window) {
159   window_->AddObserver(this);
160 }
161 
~WindowTransformAnimationObserver()162 WindowTransformAnimationObserver::~WindowTransformAnimationObserver() {
163   if (window_)
164     window_->RemoveObserver(this);
165 }
166 
OnImplicitAnimationsCompleted()167 void WindowTransformAnimationObserver::OnImplicitAnimationsCompleted() {
168   // After window transform animation is done and if the window's transform is
169   // set to identity transform, force to relayout all its transient bubble
170   // dialogs.
171   if (!window_->layer()->GetTargetTransform().IsIdentity()) {
172     delete this;
173     return;
174   }
175 
176   for (auto* transient_window :
177        ::wm::TransientWindowManager::GetOrCreate(window_)
178            ->transient_children()) {
179     // For now we only care about bubble dialog type transient children.
180     views::BubbleDialogDelegate* bubble_delegate_view =
181         AsBubbleDialogDelegate(transient_window);
182     if (bubble_delegate_view)
183       bubble_delegate_view->OnAnchorBoundsChanged();
184   }
185 
186   delete this;
187 }
188 
OnWindowDestroying(aura::Window * window)189 void WindowTransformAnimationObserver::OnWindowDestroying(
190     aura::Window* window) {
191   delete this;
192 }
193 
DoSplitviewOpacityAnimation(ui::Layer * layer,SplitviewAnimationType type)194 void DoSplitviewOpacityAnimation(ui::Layer* layer,
195                                  SplitviewAnimationType type) {
196   float target_opacity = 0.f;
197   switch (type) {
198     case SPLITVIEW_ANIMATION_HIGHLIGHT_FADE_OUT:
199     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_FADE_OUT:
200     case SPLITVIEW_ANIMATION_OVERVIEW_ITEM_FADE_OUT:
201     case SPLITVIEW_ANIMATION_PREVIEW_AREA_FADE_OUT:
202     case SPLITVIEW_ANIMATION_TEXT_FADE_OUT:
203     case SPLITVIEW_ANIMATION_TEXT_FADE_OUT_WITH_HIGHLIGHT:
204       target_opacity = 0.f;
205       break;
206     case SPLITVIEW_ANIMATION_PREVIEW_AREA_FADE_IN:
207       target_opacity = features::IsDarkLightModeEnabled()
208                            ? kDarkLightPreviewAreaHighlightOpacity
209                            : kPreviewAreaHighlightOpacity;
210       break;
211     case SPLITVIEW_ANIMATION_HIGHLIGHT_FADE_IN:
212     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_FADE_IN:
213       target_opacity = features::IsDarkLightModeEnabled()
214                            ? kDarkLightHighlightOpacity
215                            : kHighlightOpacity;
216       break;
217     case SPLITVIEW_ANIMATION_HIGHLIGHT_FADE_IN_CANNOT_SNAP:
218     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_FADE_IN_CANNOT_SNAP:
219       target_opacity = features::IsDarkLightModeEnabled()
220                            ? kDarkLightHighlightCannotSnapOpacity
221                            : kHighlightOpacity;
222       break;
223     case SPLITVIEW_ANIMATION_OVERVIEW_ITEM_FADE_IN:
224     case SPLITVIEW_ANIMATION_TEXT_FADE_IN:
225     case SPLITVIEW_ANIMATION_TEXT_FADE_IN_WITH_HIGHLIGHT:
226       target_opacity = 1.f;
227       break;
228     default:
229       NOTREACHED() << "Not a valid split view opacity animation type.";
230       return;
231   }
232 
233   if (layer->GetTargetOpacity() == target_opacity)
234     return;
235 
236   base::TimeDelta duration;
237   gfx::Tween::Type tween;
238   ui::LayerAnimator::PreemptionStrategy preemption_strategy;
239   base::TimeDelta delay;
240   GetAnimationValuesForType(type, &duration, &tween, &preemption_strategy,
241                             &delay);
242 
243   ui::LayerAnimator* animator = layer->GetAnimator();
244   ui::ScopedLayerAnimationSettings settings(animator);
245   ApplyAnimationSettings(&settings, animator,
246                          ui::LayerAnimationElement::OPACITY, duration, tween,
247                          preemption_strategy, delay);
248   layer->SetOpacity(target_opacity);
249 }
250 
DoSplitviewTransformAnimation(ui::Layer * layer,SplitviewAnimationType type,const gfx::Transform & target_transform,std::unique_ptr<ui::ImplicitAnimationObserver> animation_observer)251 void DoSplitviewTransformAnimation(
252     ui::Layer* layer,
253     SplitviewAnimationType type,
254     const gfx::Transform& target_transform,
255     std::unique_ptr<ui::ImplicitAnimationObserver> animation_observer) {
256   if (layer->GetTargetTransform() == target_transform)
257     return;
258 
259   switch (type) {
260     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_TEXT_SLIDE_IN:
261     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_TEXT_SLIDE_OUT:
262     case SPLITVIEW_ANIMATION_PREVIEW_AREA_NIX_INSET:
263     case SPLITVIEW_ANIMATION_PREVIEW_AREA_TEXT_SLIDE_IN:
264     case SPLITVIEW_ANIMATION_PREVIEW_AREA_TEXT_SLIDE_OUT:
265     case SPLITVIEW_ANIMATION_SET_WINDOW_TRANSFORM:
266       break;
267     default:
268       NOTREACHED() << "Not a valid split view transform type.";
269       return;
270   }
271 
272   base::TimeDelta duration;
273   gfx::Tween::Type tween;
274   ui::LayerAnimator::PreemptionStrategy preemption_strategy;
275   base::TimeDelta delay;
276   GetAnimationValuesForType(type, &duration, &tween, &preemption_strategy,
277                             &delay);
278 
279   ui::LayerAnimator* animator = layer->GetAnimator();
280   ui::ScopedLayerAnimationSettings settings(animator);
281   if (animation_observer.get())
282     settings.AddObserver(animation_observer.release());
283   ApplyAnimationSettings(&settings, animator,
284                          ui::LayerAnimationElement::TRANSFORM, duration, tween,
285                          preemption_strategy, delay);
286   layer->SetTransform(target_transform);
287 }
288 
DoSplitviewClipRectAnimation(ui::Layer * layer,SplitviewAnimationType type,const gfx::Rect & target_clip_rect,std::unique_ptr<ui::ImplicitAnimationObserver> animation_observer)289 void DoSplitviewClipRectAnimation(
290     ui::Layer* layer,
291     SplitviewAnimationType type,
292     const gfx::Rect& target_clip_rect,
293     std::unique_ptr<ui::ImplicitAnimationObserver> animation_observer) {
294   ui::LayerAnimator* animator = layer->GetAnimator();
295   if (animator->GetTargetClipRect() == target_clip_rect)
296     return;
297 
298   switch (type) {
299     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_SLIDE_IN:
300     case SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_SLIDE_OUT:
301     case SPLITVIEW_ANIMATION_PREVIEW_AREA_NIX_INSET:
302     case SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_IN:
303     case SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_OUT:
304       break;
305     default:
306       NOTREACHED() << "Not a valid split view clip rect type.";
307       return;
308   }
309 
310   base::TimeDelta duration;
311   gfx::Tween::Type tween;
312   ui::LayerAnimator::PreemptionStrategy preemption_strategy;
313   base::TimeDelta delay;
314   GetAnimationValuesForType(type, &duration, &tween, &preemption_strategy,
315                             &delay);
316 
317   ui::ScopedLayerAnimationSettings settings(animator);
318   if (animation_observer.get())
319     settings.AddObserver(animation_observer.release());
320   ApplyAnimationSettings(&settings, animator, ui::LayerAnimationElement::CLIP,
321                          duration, tween, preemption_strategy, delay);
322   layer->SetClipRect(target_clip_rect);
323 }
324 
MaybeRestoreSplitView(bool refresh_snapped_windows)325 void MaybeRestoreSplitView(bool refresh_snapped_windows) {
326   if (!ShouldAllowSplitView() ||
327       !Shell::Get()->tablet_mode_controller()->InTabletMode()) {
328     return;
329   }
330 
331   // Search for snapped windows to detect if the now active user session, or
332   // desk were in split view. In case multiple windows were snapped to one side,
333   // one window after another, there may be multiple windows in a LEFT_SNAPPED
334   // state or multiple windows in a RIGHT_SNAPPED state. For each of those two
335   // state types that belongs to multiple windows, the relevant window will be
336   // listed first among those windows, and a null check in the loop body below
337   // will filter out the rest of them.
338   // TODO(amusbach): The windows that were in split view may have later been
339   // destroyed or changed to non-snapped states. Then the following for loop
340   // could snap windows that were not in split view. Also, a window may have
341   // become full screen, and if so, then it would be better not to reactivate
342   // split view. See https://crbug.com/944134.
343   SplitViewController* split_view_controller =
344       SplitViewController::Get(Shell::GetPrimaryRootWindow());
345 
346   if (refresh_snapped_windows) {
347     const MruWindowTracker::WindowList windows =
348         Shell::Get()->mru_window_tracker()->BuildWindowListIgnoreModal(
349             kActiveDesk);
350     for (aura::Window* window : windows) {
351       if (!split_view_controller->CanSnapWindow(window)) {
352         // Since we are in tablet mode, and this window is not snappable, we
353         // should maximize it.
354         WindowState::Get(window)->Maximize();
355         continue;
356       }
357 
358       switch (WindowState::Get(window)->GetStateType()) {
359         case WindowStateType::kLeftSnapped:
360           if (!split_view_controller->left_window()) {
361             split_view_controller->SnapWindow(window,
362                                               SplitViewController::LEFT);
363           }
364           break;
365 
366         case WindowStateType::kRightSnapped:
367           if (!split_view_controller->right_window()) {
368             split_view_controller->SnapWindow(window,
369                                               SplitViewController::RIGHT);
370           }
371           break;
372 
373         default:
374           break;
375       }
376 
377       if (split_view_controller->state() ==
378           SplitViewController::State::kBothSnapped)
379         break;
380     }
381   }
382 
383   // Ensure that overview mode is active if and only if there is a window
384   // snapped to one side but no window snapped to the other side.
385   OverviewController* overview_controller = Shell::Get()->overview_controller();
386   SplitViewController::State state = split_view_controller->state();
387   if (state == SplitViewController::State::kLeftSnapped ||
388       state == SplitViewController::State::kRightSnapped) {
389     overview_controller->StartOverview();
390   } else {
391     overview_controller->EndOverview();
392   }
393 }
394 
IsClamshellSplitViewModeEnabled()395 bool IsClamshellSplitViewModeEnabled() {
396   return base::FeatureList::IsEnabled(features::kDragToSnapInClamshellMode);
397 }
398 
AreMultiDisplayOverviewAndSplitViewEnabled()399 bool AreMultiDisplayOverviewAndSplitViewEnabled() {
400   return base::FeatureList::IsEnabled(
401       features::kMultiDisplayOverviewAndSplitView);
402 }
403 
ShouldAllowSplitView()404 bool ShouldAllowSplitView() {
405   if (!Shell::Get()->tablet_mode_controller()->InTabletMode() &&
406       !IsClamshellSplitViewModeEnabled()) {
407     return false;
408   }
409 
410   // Don't allow split view if we're in pinned mode.
411   if (Shell::Get()->screen_pinning_controller()->IsPinned())
412     return false;
413 
414   // TODO(crubg.com/853588): Disallow window dragging and split screen while
415   // ChromeVox is on until they are in a usable state.
416   if (Shell::Get()->accessibility_controller()->spoken_feedback().enabled())
417     return false;
418 
419   return true;
420 }
421 
ShowAppCannotSnapToast()422 void ShowAppCannotSnapToast() {
423   Shell::Get()->toast_manager()->Show(ToastData(
424       kAppCannotSnapToastId,
425       l10n_util::GetStringUTF16(IDS_ASH_SPLIT_VIEW_CANNOT_SNAP),
426       kAppCannotSnapToastDurationMs, base::Optional<base::string16>()));
427 }
428 
GetSnapPositionForLocation(aura::Window * root_window,const gfx::Point & location_in_screen,const base::Optional<gfx::Point> & initial_location_in_screen,int snap_distance_from_edge,int minimum_drag_distance,int horizontal_edge_inset,int vertical_edge_inset)429 SplitViewController::SnapPosition GetSnapPositionForLocation(
430     aura::Window* root_window,
431     const gfx::Point& location_in_screen,
432     const base::Optional<gfx::Point>& initial_location_in_screen,
433     int snap_distance_from_edge,
434     int minimum_drag_distance,
435     int horizontal_edge_inset,
436     int vertical_edge_inset) {
437   if (!ShouldAllowSplitView())
438     return SplitViewController::NONE;
439 
440   const bool horizontal = SplitViewController::IsLayoutHorizontal();
441   const bool right_side_up = SplitViewController::IsLayoutRightSideUp();
442 
443   // Check to see if the current event location |location_in_screen| is within
444   // the drag indicators bounds.
445   const gfx::Rect work_area(
446       screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
447           root_window));
448   SplitViewController::SnapPosition snap_position = SplitViewController::NONE;
449   if (horizontal) {
450     gfx::Rect area(work_area);
451     area.Inset(horizontal_edge_inset, 0);
452     if (location_in_screen.x() <= area.x()) {
453       snap_position = right_side_up ? SplitViewController::LEFT
454                                     : SplitViewController::RIGHT;
455     } else if (location_in_screen.x() >= area.right() - 1) {
456       snap_position = right_side_up ? SplitViewController::RIGHT
457                                     : SplitViewController::LEFT;
458     }
459   } else {
460     gfx::Rect area(work_area);
461     area.Inset(0, vertical_edge_inset);
462     if (location_in_screen.y() <= area.y()) {
463       snap_position = right_side_up ? SplitViewController::LEFT
464                                     : SplitViewController::RIGHT;
465     } else if (location_in_screen.y() >= area.bottom() - 1) {
466       snap_position = right_side_up ? SplitViewController::RIGHT
467                                     : SplitViewController::LEFT;
468     }
469   }
470 
471   if (snap_position == SplitViewController::NONE)
472     return snap_position;
473 
474   // To avoid accidental snap, the window needs to be dragged inside
475   // |snap_distance_from_edge| from edge or dragged toward the edge for at least
476   // |minimum_drag_distance| until it's dragged into |horizontal_edge_inset| or
477   // |vertical_edge_inset| region.
478   // The window should always be snapped if inside |snap_distance_from_edge|
479   // from edge.
480   bool drag_end_near_edge = false;
481   gfx::Rect area(work_area);
482   area.Inset(snap_distance_from_edge, snap_distance_from_edge);
483   if (horizontal ? location_in_screen.x() < area.x() ||
484                        location_in_screen.x() > area.right()
485                  : location_in_screen.y() < area.y() ||
486                        location_in_screen.y() > area.bottom()) {
487     drag_end_near_edge = true;
488   }
489 
490   if (!drag_end_near_edge && initial_location_in_screen) {
491     // Check how far the window has been dragged.
492     const auto distance = location_in_screen - *initial_location_in_screen;
493     const int primary_axis_distance = horizontal ? distance.x() : distance.y();
494     const bool is_left_or_top =
495         SplitViewController::IsPhysicalLeftOrTop(snap_position);
496     if ((is_left_or_top && primary_axis_distance > -minimum_drag_distance) ||
497         (!is_left_or_top && primary_axis_distance < minimum_drag_distance)) {
498       snap_position = SplitViewController::NONE;
499     }
500   }
501 
502   return snap_position;
503 }
504 
GetSnapPosition(aura::Window * root_window,aura::Window * window,const gfx::Point & location_in_screen,const gfx::Point & initial_location_in_screen,int snap_distance_from_edge,int minimum_drag_distance,int horizontal_edge_inset,int vertical_edge_inset)505 SplitViewController::SnapPosition GetSnapPosition(
506     aura::Window* root_window,
507     aura::Window* window,
508     const gfx::Point& location_in_screen,
509     const gfx::Point& initial_location_in_screen,
510     int snap_distance_from_edge,
511     int minimum_drag_distance,
512     int horizontal_edge_inset,
513     int vertical_edge_inset) {
514   if (!SplitViewController::Get(root_window)->CanSnapWindow(window)) {
515     return SplitViewController::NONE;
516   }
517 
518   base::Optional<gfx::Point> initial_location_in_current_screen = base::nullopt;
519   if (window->GetRootWindow() == root_window)
520     initial_location_in_current_screen = initial_location_in_screen;
521 
522   return GetSnapPositionForLocation(
523       root_window, location_in_screen, initial_location_in_current_screen,
524       snap_distance_from_edge, minimum_drag_distance, horizontal_edge_inset,
525       vertical_edge_inset);
526 }
527 
528 }  // namespace ash
529