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_drag_indicators.h"
6 
7 #include <utility>
8 
9 #include "ash/display/screen_orientation_controller.h"
10 #include "ash/public/cpp/shell_window_ids.h"
11 #include "ash/public/cpp/window_animation_types.h"
12 #include "ash/screen_util.h"
13 #include "ash/shelf/shelf.h"
14 #include "ash/shell.h"
15 #include "ash/strings/grit/ash_strings.h"
16 #include "ash/style/ash_color_provider.h"
17 #include "ash/style/default_color_constants.h"
18 #include "ash/style/default_colors.h"
19 #include "ash/wm/splitview/split_view_constants.h"
20 #include "ash/wm/splitview/split_view_highlight_view.h"
21 #include "ash/wm/splitview/split_view_utils.h"
22 #include "ash/wm/window_animations.h"
23 #include "ash/wm/window_util.h"
24 #include "base/i18n/rtl.h"
25 #include "base/strings/utf_string_conversions.h"
26 #include "ui/aura/window.h"
27 #include "ui/aura/window_observer.h"
28 #include "ui/base/l10n/l10n_util.h"
29 #include "ui/compositor/scoped_layer_animation_settings.h"
30 #include "ui/display/display_observer.h"
31 #include "ui/views/background.h"
32 #include "ui/views/controls/label.h"
33 #include "ui/views/layout/box_layout.h"
34 #include "ui/views/view.h"
35 #include "ui/views/widget/widget.h"
36 #include "ui/wm/core/coordinate_conversion.h"
37 #include "ui/wm/core/window_animations.h"
38 
39 namespace ash {
40 
41 namespace {
42 
43 // When a preview is shown, the opposite highlight shall contract to this ratio
44 // of the screen length.
45 constexpr float kOtherHighlightScreenPrimaryAxisRatio = 0.03f;
46 
47 // Creates the widget responsible for displaying the indicators.
CreateWidget(aura::Window * root_window)48 std::unique_ptr<views::Widget> CreateWidget(aura::Window* root_window) {
49   auto widget = std::make_unique<views::Widget>();
50   views::Widget::InitParams params;
51   params.type = views::Widget::InitParams::TYPE_POPUP;
52   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
53   params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
54   params.accept_events = false;
55   params.layer_type = ui::LAYER_NOT_DRAWN;
56   params.parent =
57       Shell::GetContainer(root_window, kShellWindowId_OverlayContainer);
58   widget->set_focus_on_creation(false);
59   widget->Init(std::move(params));
60   return widget;
61 }
62 
63 // Computes the transform which rotates the labels |angle| degrees. The point
64 // of rotation is the relative center point of |bounds|.
ComputeRotateAroundCenterTransform(const gfx::Rect & bounds,double angle)65 gfx::Transform ComputeRotateAroundCenterTransform(const gfx::Rect& bounds,
66                                                   double angle) {
67   gfx::Transform transform;
68   const gfx::Vector2dF center_point_vector =
69       bounds.CenterPoint() - bounds.origin();
70   transform.Translate(center_point_vector);
71   transform.Rotate(angle);
72   transform.Translate(-center_point_vector);
73   return transform;
74 }
75 
76 // Returns the work area bounds that has no overlap with shelf.
GetWorkAreaBoundsNoOverlapWithShelf(aura::Window * root_window)77 gfx::Rect GetWorkAreaBoundsNoOverlapWithShelf(aura::Window* root_window) {
78   aura::Window* window =
79       root_window->GetChildById(kShellWindowId_OverlayContainer);
80   gfx::Rect bounds = screen_util::GetDisplayWorkAreaBoundsInParent(window);
81   ::wm::ConvertRectToScreen(root_window, &bounds);
82 
83   bounds.Subtract(Shelf::ForWindow(root_window)->GetIdealBounds());
84   return bounds;
85 }
86 
87 }  // namespace
88 
89 // static
GetSnapPosition(WindowDraggingState window_dragging_state)90 SplitViewController::SnapPosition SplitViewDragIndicators::GetSnapPosition(
91     WindowDraggingState window_dragging_state) {
92   switch (window_dragging_state) {
93     case WindowDraggingState::kToSnapLeft:
94       return SplitViewController::LEFT;
95     case WindowDraggingState::kToSnapRight:
96       return SplitViewController::RIGHT;
97     default:
98       return SplitViewController::NONE;
99   }
100 }
101 
102 // static
103 SplitViewDragIndicators::WindowDraggingState
ComputeWindowDraggingState(bool is_dragging,WindowDraggingState non_snap_state,SplitViewController::SnapPosition snap_position)104 SplitViewDragIndicators::ComputeWindowDraggingState(
105     bool is_dragging,
106     WindowDraggingState non_snap_state,
107     SplitViewController::SnapPosition snap_position) {
108   if (!is_dragging || !ShouldAllowSplitView())
109     return WindowDraggingState::kNoDrag;
110   switch (snap_position) {
111     case SplitViewController::NONE:
112       return non_snap_state;
113     case SplitViewController::LEFT:
114       return WindowDraggingState::kToSnapLeft;
115     case SplitViewController::RIGHT:
116       return WindowDraggingState::kToSnapRight;
117   }
118 }
119 
120 // View which contains a label and can be rotated. Used by and rotated by
121 // SplitViewDragIndicatorsView.
122 class SplitViewDragIndicators::RotatedImageLabelView : public views::View {
123  public:
RotatedImageLabelView(bool is_right_or_bottom)124   explicit RotatedImageLabelView(bool is_right_or_bottom)
125       : is_right_or_bottom_(is_right_or_bottom) {
126     // TODO(sammiequon): Remove this extra intermediate layer.
127     SetPaintToLayer(ui::LAYER_NOT_DRAWN);
128     layer()->SetFillsBoundsOpaquely(false);
129 
130     // Use |label_parent_| to add padding and rounded edges to the text. Create
131     // this extra view so that we can rotate the label, while having a slide
132     // animation at times on the whole thing.
133     label_parent_ = AddChildView(std::make_unique<views::View>());
134     label_parent_->SetPaintToLayer();
135     label_parent_->layer()->SetFillsBoundsOpaquely(false);
136     label_parent_->SetBackground(views::CreateRoundedRectBackground(
137         DeprecatedGetBaseLayerColor(
138             AshColorProvider::BaseLayerType::kTransparent80,
139             kSplitviewLabelBackgroundColor),
140         kSplitviewLabelRoundRectRadiusDp));
141     label_parent_->SetLayoutManager(std::make_unique<views::BoxLayout>(
142         views::BoxLayout::Orientation::kVertical,
143         gfx::Insets(kSplitviewLabelVerticalInsetDp,
144                     kSplitviewLabelHorizontalInsetDp)));
145 
146     label_ = label_parent_->AddChildView(std::make_unique<views::Label>(
147         base::string16(), views::style::CONTEXT_LABEL));
148     label_->SetEnabledColor(DeprecatedGetContentLayerColor(
149         AshColorProvider::ContentLayerType::kTextColorPrimary,
150         kSplitviewLabelEnabledColor));
151     label_->SetBackgroundColor(DeprecatedGetBaseLayerColor(
152         AshColorProvider::BaseLayerType::kTransparent80,
153         kSplitviewLabelBackgroundColor));
154   }
155 
156   ~RotatedImageLabelView() override = default;
157 
SetLabelText(const base::string16 & text)158   void SetLabelText(const base::string16& text) { label_->SetText(text); }
159 
160   // Called when the view's bounds are altered. Rotates the view by |angle|
161   // degrees.
OnBoundsUpdated(const gfx::Rect & bounds,double angle)162   void OnBoundsUpdated(const gfx::Rect& bounds, double angle) {
163     SetBoundsRect(bounds);
164     label_parent_->SetBoundsRect(gfx::Rect(bounds.size()));
165     label_parent_->SetTransform(
166         ComputeRotateAroundCenterTransform(bounds, angle));
167   }
168 
169   // Called to update the opacity of the labels view on |indicator_state|.
OnWindowDraggingStateChanged(WindowDraggingState window_dragging_state,WindowDraggingState previous_window_dragging_state,bool can_dragged_window_be_snapped)170   void OnWindowDraggingStateChanged(
171       WindowDraggingState window_dragging_state,
172       WindowDraggingState previous_window_dragging_state,
173       bool can_dragged_window_be_snapped) {
174     // No top label for dragging from the top in portrait orientation.
175     if (window_dragging_state == WindowDraggingState::kFromTop &&
176         !IsCurrentScreenOrientationLandscape() && !is_right_or_bottom_) {
177       return;
178     }
179 
180     // If there is no drag currently in this display, any label that is showing
181     // shall fade out with the corresponding indicator.
182     if (window_dragging_state == WindowDraggingState::kNoDrag ||
183         window_dragging_state == WindowDraggingState::kOtherDisplay) {
184       DoSplitviewOpacityAnimation(
185           layer(), SPLITVIEW_ANIMATION_TEXT_FADE_OUT_WITH_HIGHLIGHT);
186       return;
187     }
188 
189     // When a snap preview is shown, any label that is showing shall fade out.
190     if (GetSnapPosition(window_dragging_state) != SplitViewController::NONE) {
191       DoSplitviewOpacityAnimation(layer(), SPLITVIEW_ANIMATION_TEXT_FADE_OUT);
192       return;
193     }
194 
195     // Set the text according to |can_dragged_window_be_snapped|.
196     SetLabelText(l10n_util::GetStringUTF16(
197         can_dragged_window_be_snapped ? IDS_ASH_SPLIT_VIEW_GUIDANCE
198                                       : IDS_ASH_SPLIT_VIEW_CANNOT_SNAP));
199 
200     // When dragging begins in this display or comes in from another display, if
201     // there is now no snap preview, fade in with an indicator.
202     if (previous_window_dragging_state == WindowDraggingState::kNoDrag ||
203         previous_window_dragging_state == WindowDraggingState::kOtherDisplay) {
204       DoSplitviewOpacityAnimation(
205           layer(), SPLITVIEW_ANIMATION_TEXT_FADE_IN_WITH_HIGHLIGHT);
206       return;
207     }
208 
209     // If a snap preview was shown, the labels shall now fade in.
210     if (GetSnapPosition(previous_window_dragging_state) !=
211         SplitViewController::NONE) {
212       DoSplitviewOpacityAnimation(layer(), SPLITVIEW_ANIMATION_TEXT_FADE_IN);
213       return;
214     }
215   }
216 
217  protected:
CalculatePreferredSize() const218   gfx::Size CalculatePreferredSize() const override {
219     return label_parent_->GetPreferredSize();
220   }
221 
222  private:
223   // True if the label view is the right/bottom side one, false if it is the
224   // left/top one.
225   const bool is_right_or_bottom_;
226 
227   views::View* label_parent_ = nullptr;
228   views::Label* label_ = nullptr;
229 
230   DISALLOW_COPY_AND_ASSIGN(RotatedImageLabelView);
231 };
232 
233 // View which contains two highlights on each side indicator where a user should
234 // drag a selected window in order to initiate splitview. Each highlight has a
235 // label with instructions to further guide users. The highlights are on the
236 // left and right of the display in landscape mode, and on the top and bottom of
237 // the display in landscape mode. The highlights can expand and shrink if a
238 // window has entered a snap region to display the bounds of the window, if it
239 // were to get snapped.
240 class SplitViewDragIndicators::SplitViewDragIndicatorsView
241     : public views::View,
242       public aura::WindowObserver {
243  public:
SplitViewDragIndicatorsView()244   SplitViewDragIndicatorsView() {
245     left_highlight_view_ = AddChildView(
246         std::make_unique<SplitViewHighlightView>(/*is_right_or_bottom=*/false));
247     right_highlight_view_ = AddChildView(
248         std::make_unique<SplitViewHighlightView>(/*is_right_or_bottom=*/true));
249 
250     left_rotated_view_ = AddChildView(
251         std::make_unique<RotatedImageLabelView>(/*is_right_or_bottom=*/false));
252     right_rotated_view_ = AddChildView(
253         std::make_unique<RotatedImageLabelView>(/*is_right_or_bottom=*/true));
254 
255     // Nothing is shown initially.
256     left_highlight_view_->layer()->SetOpacity(0.f);
257     right_highlight_view_->layer()->SetOpacity(0.f);
258     left_rotated_view_->layer()->SetOpacity(0.f);
259     right_rotated_view_->layer()->SetOpacity(0.f);
260   }
261 
~SplitViewDragIndicatorsView()262   ~SplitViewDragIndicatorsView() override {
263     if (dragged_window_)
264       dragged_window_->RemoveObserver(this);
265   }
266 
left_highlight_view()267   SplitViewHighlightView* left_highlight_view() { return left_highlight_view_; }
268 
269   // Called by parent widget when the state machine changes. Handles setting the
270   // opacity and bounds of the highlights and labels.
OnWindowDraggingStateChanged(WindowDraggingState window_dragging_state)271   void OnWindowDraggingStateChanged(WindowDraggingState window_dragging_state) {
272     DCHECK_NE(window_dragging_state_, window_dragging_state);
273     previous_window_dragging_state_ = window_dragging_state_;
274     window_dragging_state_ = window_dragging_state;
275 
276     SplitViewController* split_view_controller =
277         SplitViewController::Get(GetWidget()->GetNativeWindow());
278     const bool previews_only =
279         window_dragging_state == WindowDraggingState::kFromShelf ||
280         window_dragging_state == WindowDraggingState::kFromTop ||
281         split_view_controller->InSplitViewMode();
282     const bool can_dragged_window_be_snapped =
283         dragged_window_ &&
284         split_view_controller->CanSnapWindow(dragged_window_);
285     if (!previews_only) {
286       left_rotated_view_->OnWindowDraggingStateChanged(
287           window_dragging_state, previous_window_dragging_state_,
288           can_dragged_window_be_snapped);
289       right_rotated_view_->OnWindowDraggingStateChanged(
290           window_dragging_state, previous_window_dragging_state_,
291           can_dragged_window_be_snapped);
292     }
293     left_highlight_view_->OnWindowDraggingStateChanged(
294         window_dragging_state, previous_window_dragging_state_, previews_only,
295         can_dragged_window_be_snapped);
296     right_highlight_view_->OnWindowDraggingStateChanged(
297         window_dragging_state, previous_window_dragging_state_, previews_only,
298         can_dragged_window_be_snapped);
299 
300     if (window_dragging_state != WindowDraggingState::kNoDrag ||
301         GetSnapPosition(previous_window_dragging_state_) !=
302             SplitViewController::NONE) {
303       Layout(previous_window_dragging_state_ != WindowDraggingState::kNoDrag);
304     }
305   }
306 
GetViewForIndicatorType(IndicatorType type)307   views::View* GetViewForIndicatorType(IndicatorType type) {
308     switch (type) {
309       case IndicatorType::kLeftHighlight:
310         return left_highlight_view_;
311       case IndicatorType::kLeftText:
312         return left_rotated_view_;
313       case IndicatorType::kRightHighlight:
314         return right_highlight_view_;
315       case IndicatorType::kRightText:
316         return right_rotated_view_;
317     }
318 
319     NOTREACHED();
320     return nullptr;
321   }
322 
SetDraggedWindow(aura::Window * dragged_window)323   void SetDraggedWindow(aura::Window* dragged_window) {
324     if (dragged_window_)
325       dragged_window_->RemoveObserver(this);
326     dragged_window_ = dragged_window;
327     if (dragged_window)
328       dragged_window->AddObserver(this);
329   }
330 
331   // views::View:
Layout()332   void Layout() override { Layout(/*animate=*/false); }
333 
334   // aura::WindowObserver:
OnWindowDestroyed(aura::Window * window)335   void OnWindowDestroyed(aura::Window* window) override {
336     DCHECK_EQ(dragged_window_, window);
337     dragged_window_ = nullptr;
338   }
339 
340  private:
341   // Layout the bounds of the highlight views and helper labels. One should
342   // animate when changing states, but not when bounds or orientation is
343   // changed.
Layout(bool animate)344   void Layout(bool animate) {
345     // TODO(xdai|afakhry): Attempt to simplify this logic.
346     const bool horizontal = SplitViewController::IsLayoutHorizontal();
347     const int display_width = horizontal ? width() : height();
348     const int display_height = horizontal ? height() : width();
349 
350     // Calculate the bounds of the two highlight regions.
351     const int highlight_width =
352         display_width * kHighlightScreenPrimaryAxisRatio;
353     const int highlight_height =
354         display_height - 2 * kHighlightScreenEdgePaddingDp;
355     gfx::Size highlight_size(highlight_width, highlight_height);
356 
357     // When one highlight expands to become a preview area, the other highlight
358     // contracts to this width.
359     const int other_highlight_width =
360         display_width * kOtherHighlightScreenPrimaryAxisRatio;
361 
362     // The origin of the right highlight view in horizontal split view layout,
363     // or the bottom highlight view in vertical split view layout.
364     gfx::Point right_bottom_origin(
365         display_width - highlight_width - kHighlightScreenEdgePaddingDp,
366         kHighlightScreenEdgePaddingDp);
367 
368     const gfx::Point highlight_padding_point(kHighlightScreenEdgePaddingDp,
369                                              kHighlightScreenEdgePaddingDp);
370     gfx::Rect left_highlight_bounds(highlight_padding_point, highlight_size);
371     gfx::Rect right_highlight_bounds(right_bottom_origin, highlight_size);
372     if (!horizontal) {
373       left_highlight_bounds.Transpose();
374       right_highlight_bounds.Transpose();
375     }
376 
377     // True when the drag ends in a snap area, meaning that the dragged window
378     // actually becomes snapped.
379     const bool drag_ending_in_snap =
380         window_dragging_state_ == WindowDraggingState::kNoDrag &&
381         GetSnapPosition(previous_window_dragging_state_) !=
382             SplitViewController::NONE;
383 
384     SplitViewController::SnapPosition snap_position =
385         GetSnapPosition(window_dragging_state_);
386     if (snap_position == SplitViewController::NONE)
387       snap_position = GetSnapPosition(previous_window_dragging_state_);
388 
389     gfx::Rect preview_area_bounds;
390     base::Optional<SplitviewAnimationType> left_highlight_animation_type;
391     base::Optional<SplitviewAnimationType> right_highlight_animation_type;
392     if (GetSnapPosition(window_dragging_state_) != SplitViewController::NONE ||
393         drag_ending_in_snap) {
394       // Get the preview area bounds from the split view controller.
395       preview_area_bounds =
396           SplitViewController::Get(GetWidget()->GetNativeWindow())
397               ->GetSnappedWindowBoundsInScreen(snap_position, dragged_window_);
398 
399       aura::Window* root_window =
400           GetWidget()->GetNativeWindow()->GetRootWindow();
401       wm::ConvertRectFromScreen(root_window, &preview_area_bounds);
402 
403       // Preview area should have no overlap with the shelf.
404       preview_area_bounds.Subtract(
405           Shelf::ForWindow(root_window)->GetIdealBounds());
406 
407       gfx::Rect work_area_bounds =
408           GetWorkAreaBoundsNoOverlapWithShelf(root_window);
409       wm::ConvertRectFromScreen(root_window, &work_area_bounds);
410       preview_area_bounds.set_y(preview_area_bounds.y() - work_area_bounds.y());
411       if (!drag_ending_in_snap) {
412         preview_area_bounds.Inset(kHighlightScreenEdgePaddingDp,
413                                   kHighlightScreenEdgePaddingDp);
414       }
415 
416       // Calculate the bounds of the other highlight, which is the one that
417       // shrinks and fades away, while the other one, the preview area, expands
418       // and takes up half the screen.
419       gfx::Rect other_bounds(
420           display_width - other_highlight_width - kHighlightScreenEdgePaddingDp,
421           kHighlightScreenEdgePaddingDp, other_highlight_width,
422           display_height - 2 * kHighlightScreenEdgePaddingDp);
423       if (!horizontal)
424         other_bounds.Transpose();
425 
426       if (SplitViewController::IsPhysicalLeftOrTop(snap_position)) {
427         left_highlight_bounds = preview_area_bounds;
428         right_highlight_bounds = other_bounds;
429         if (animate) {
430           if (drag_ending_in_snap) {
431             left_highlight_animation_type =
432                 SPLITVIEW_ANIMATION_PREVIEW_AREA_NIX_INSET;
433           } else {
434             left_highlight_animation_type =
435                 SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_IN;
436             right_highlight_animation_type =
437                 SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_SLIDE_OUT;
438           }
439         }
440       } else {
441         other_bounds.set_origin(highlight_padding_point);
442         left_highlight_bounds = other_bounds;
443         right_highlight_bounds = preview_area_bounds;
444         if (animate) {
445           if (drag_ending_in_snap) {
446             right_highlight_animation_type =
447                 SPLITVIEW_ANIMATION_PREVIEW_AREA_NIX_INSET;
448           } else {
449             left_highlight_animation_type =
450                 SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_SLIDE_OUT;
451             right_highlight_animation_type =
452                 SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_IN;
453           }
454         }
455       }
456     } else if (GetSnapPosition(previous_window_dragging_state_) !=
457                    SplitViewController::NONE &&
458                animate) {
459       if (SplitViewController::IsPhysicalLeftOrTop(snap_position)) {
460         left_highlight_animation_type =
461             SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_OUT;
462         right_highlight_animation_type =
463             SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_SLIDE_IN;
464       } else {
465         left_highlight_animation_type =
466             SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_SLIDE_IN;
467         right_highlight_animation_type =
468             SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_OUT;
469       }
470     }
471 
472     left_highlight_view_->SetBounds(GetMirroredRect(left_highlight_bounds),
473                                     left_highlight_animation_type);
474     right_highlight_view_->SetBounds(GetMirroredRect(right_highlight_bounds),
475                                      right_highlight_animation_type);
476 
477     // Calculate the bounds of the views which contain the guidance text and
478     // icon. Rotate the two views in horizontal split view layout.
479     const gfx::Size size(right_rotated_view_->GetPreferredSize().width(),
480                          kSplitviewLabelPreferredHeightDp);
481     if (!horizontal)
482       highlight_size.SetSize(highlight_size.height(), highlight_size.width());
483     gfx::Rect left_rotated_bounds(
484         highlight_size.width() / 2 - size.width() / 2,
485         highlight_size.height() / 2 - size.height() / 2, size.width(),
486         size.height());
487     gfx::Rect right_rotated_bounds = left_rotated_bounds;
488     left_rotated_bounds.Offset(highlight_padding_point.x(),
489                                highlight_padding_point.y());
490     if (!horizontal) {
491       right_bottom_origin.SetPoint(right_bottom_origin.y(),
492                                    right_bottom_origin.x());
493     }
494     right_rotated_bounds.Offset(right_bottom_origin.x(),
495                                 right_bottom_origin.y());
496 
497     // In portrait mode, there is no need to rotate the text and warning icon.
498     // In horizontal split view layout, rotate the left text 90 degrees
499     // clockwise in rtl and 90 degress anti clockwise in ltr. The right text is
500     // rotated 90 degrees in the opposite direction of the left text.
501     double left_rotation_angle = 0.0;
502     if (horizontal)
503       left_rotation_angle = 90.0 * (base::i18n::IsRTL() ? 1 : -1);
504 
505     left_rotated_view_->OnBoundsUpdated(left_rotated_bounds,
506                                         /*angle=*/left_rotation_angle);
507     right_rotated_view_->OnBoundsUpdated(right_rotated_bounds,
508                                          /*angle=*/-left_rotation_angle);
509 
510     if (drag_ending_in_snap) {
511       // Reset the label transforms, in preparation for the next drag (if any).
512       left_rotated_view_->layer()->SetTransform(gfx::Transform());
513       right_rotated_view_->layer()->SetTransform(gfx::Transform());
514       return;
515     }
516 
517     ui::Layer* preview_label_layer;
518     ui::Layer* other_highlight_label_layer;
519     if (snap_position == SplitViewController::NONE) {
520       preview_label_layer = nullptr;
521       other_highlight_label_layer = nullptr;
522     } else if (SplitViewController::IsPhysicalLeftOrTop(snap_position)) {
523       preview_label_layer = left_rotated_view_->layer();
524       other_highlight_label_layer = right_rotated_view_->layer();
525     } else {
526       preview_label_layer = right_rotated_view_->layer();
527       other_highlight_label_layer = left_rotated_view_->layer();
528     }
529 
530     // Slide out the labels when a snap preview appears. This code also adjusts
531     // the label transforms for things like display rotation while there is a
532     // snap preview.
533     if (GetSnapPosition(window_dragging_state_) != SplitViewController::NONE) {
534       // How far each label shall slide to stay centered in the corresponding
535       // highlight as it expands/contracts. Include distance traveled with zero
536       // opacity (whence a label still slides, not only for simplicity in
537       // calculating the values below, but also to facilitate that the label
538       // transform and the highlight transform have matching easing).
539       const float preview_label_distance =
540           0.5f * (preview_area_bounds.width() - highlight_width);
541       const float other_highlight_label_distance =
542           0.5f * (highlight_width - other_highlight_width);
543 
544       // Positive for right or down; negative for left or up.
545       float preview_label_delta, other_highlight_label_delta;
546       if (SplitViewController::IsPhysicalLeftOrTop(snap_position)) {
547         preview_label_delta = preview_label_distance;
548         other_highlight_label_delta = other_highlight_label_distance;
549       } else {
550         preview_label_delta = -preview_label_distance;
551         other_highlight_label_delta = -other_highlight_label_distance;
552       }
553 
554       // x-axis if |horizontal|; else y-axis.
555       gfx::Transform preview_label_transform, other_highlight_label_transform;
556       if (horizontal) {
557         preview_label_transform.Translate(preview_label_delta, 0.f);
558         other_highlight_label_transform.Translate(other_highlight_label_delta,
559                                                   0.f);
560       } else {
561         preview_label_transform.Translate(0.f, preview_label_delta);
562         other_highlight_label_transform.Translate(0.f,
563                                                   other_highlight_label_delta);
564       }
565 
566       if (animate) {
567         // Animate the labels sliding out.
568         DoSplitviewTransformAnimation(
569             preview_label_layer,
570             SPLITVIEW_ANIMATION_PREVIEW_AREA_TEXT_SLIDE_OUT,
571             preview_label_transform, /*animation_observer=*/nullptr);
572         DoSplitviewTransformAnimation(
573             other_highlight_label_layer,
574             SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_TEXT_SLIDE_OUT,
575             other_highlight_label_transform, /*animation_observer=*/nullptr);
576       } else {
577         // Put the labels where they belong.
578         preview_label_layer->SetTransform(preview_label_transform);
579         other_highlight_label_layer->SetTransform(
580             other_highlight_label_transform);
581       }
582       return;
583     }
584 
585     // Slide in the labels when a snap preview disappears because you drag
586     // inward. (Having reached this code, we know that the window is not
587     // becoming snapped, because that case is handled earlier and we bail out.)
588     if (GetSnapPosition(previous_window_dragging_state_) !=
589         SplitViewController::NONE) {
590       if (animate) {
591         // Animate the labels sliding in.
592         DoSplitviewTransformAnimation(
593             preview_label_layer, SPLITVIEW_ANIMATION_PREVIEW_AREA_TEXT_SLIDE_IN,
594             gfx::Transform(), /*animation_observer=*/nullptr);
595         DoSplitviewTransformAnimation(
596             other_highlight_label_layer,
597             SPLITVIEW_ANIMATION_OTHER_HIGHLIGHT_TEXT_SLIDE_IN, gfx::Transform(),
598             /*animation_observer=*/nullptr);
599       } else {
600         // Put the labels where they belong.
601         preview_label_layer->SetTransform(gfx::Transform());
602         other_highlight_label_layer->SetTransform(gfx::Transform());
603       }
604       return;
605     }
606   }
607 
608   SplitViewHighlightView* left_highlight_view_ = nullptr;
609   SplitViewHighlightView* right_highlight_view_ = nullptr;
610   RotatedImageLabelView* left_rotated_view_ = nullptr;
611   RotatedImageLabelView* right_rotated_view_ = nullptr;
612 
613   WindowDraggingState window_dragging_state_ = WindowDraggingState::kNoDrag;
614   WindowDraggingState previous_window_dragging_state_ =
615       WindowDraggingState::kNoDrag;
616 
617   aura::Window* dragged_window_ = nullptr;
618 
619   DISALLOW_COPY_AND_ASSIGN(SplitViewDragIndicatorsView);
620 };
621 
SplitViewDragIndicators(aura::Window * root_window)622 SplitViewDragIndicators::SplitViewDragIndicators(aura::Window* root_window) {
623   widget_ = CreateWidget(root_window);
624   widget_->SetBounds(GetWorkAreaBoundsNoOverlapWithShelf(root_window));
625   indicators_view_ =
626       widget_->SetContentsView(std::make_unique<SplitViewDragIndicatorsView>());
627   widget_->Show();
628 }
629 
~SplitViewDragIndicators()630 SplitViewDragIndicators::~SplitViewDragIndicators() {
631   // Allow some extra time for animations to finish.
632   aura::Window* window = widget_->GetNativeWindow();
633   if (window == nullptr)
634     return;
635   wm::SetWindowVisibilityAnimationType(
636       window, WINDOW_VISIBILITY_ANIMATION_TYPE_STEP_END);
637   AnimateOnChildWindowVisibilityChanged(window, /*visible=*/false);
638 }
639 
SetDraggedWindow(aura::Window * dragged_window)640 void SplitViewDragIndicators::SetDraggedWindow(aura::Window* dragged_window) {
641   DCHECK_EQ(WindowDraggingState::kNoDrag, current_window_dragging_state_);
642   indicators_view_->SetDraggedWindow(dragged_window);
643 }
644 
SetWindowDraggingState(WindowDraggingState window_dragging_state)645 void SplitViewDragIndicators::SetWindowDraggingState(
646     WindowDraggingState window_dragging_state) {
647   if (window_dragging_state == current_window_dragging_state_)
648     return;
649   current_window_dragging_state_ = window_dragging_state;
650   indicators_view_->OnWindowDraggingStateChanged(window_dragging_state);
651 }
652 
OnDisplayBoundsChanged()653 void SplitViewDragIndicators::OnDisplayBoundsChanged() {
654   aura::Window* root_window = widget_->GetNativeView()->GetRootWindow();
655   widget_->SetBounds(GetWorkAreaBoundsNoOverlapWithShelf(root_window));
656 }
657 
GetIndicatorTypeVisibilityForTesting(IndicatorType type) const658 bool SplitViewDragIndicators::GetIndicatorTypeVisibilityForTesting(
659     IndicatorType type) const {
660   return indicators_view_->GetViewForIndicatorType(type)->layer()->opacity() >
661          0.f;
662 }
663 
GetLeftHighlightViewBounds() const664 gfx::Rect SplitViewDragIndicators::GetLeftHighlightViewBounds() const {
665   return indicators_view_->left_highlight_view()->bounds();
666 }
667 
668 }  // namespace ash
669