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