1 // Copyright 2018 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_utils.h"
6 
7 #include <utility>
8 
9 #include "ash/home_screen/home_launcher_gesture_handler.h"
10 #include "ash/home_screen/home_screen_controller.h"
11 #include "ash/public/cpp/shelf_config.h"
12 #include "ash/public/cpp/shell_window_ids.h"
13 #include "ash/public/cpp/window_properties.h"
14 #include "ash/scoped_animation_disabler.h"
15 #include "ash/screen_util.h"
16 #include "ash/shelf/shelf.h"
17 #include "ash/shelf/shelf_layout_manager.h"
18 #include "ash/shell.h"
19 #include "ash/wm/mru_window_tracker.h"
20 #include "ash/wm/overview/cleanup_animation_observer.h"
21 #include "ash/wm/overview/delayed_animation_observer_impl.h"
22 #include "ash/wm/overview/overview_controller.h"
23 #include "ash/wm/overview/overview_grid.h"
24 #include "ash/wm/overview/overview_item.h"
25 #include "ash/wm/overview/scoped_overview_animation_settings.h"
26 #include "ash/wm/splitview/split_view_controller.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_state.h"
30 #include "ash/wm/window_transient_descendant_iterator.h"
31 #include "ash/wm/window_util.h"
32 #include "ash/wm/wm_event.h"
33 #include "ash/wm/work_area_insets.h"
34 #include "base/no_destructor.h"
35 #include "ui/aura/client/aura_constants.h"
36 #include "ui/aura/window.h"
37 #include "ui/gfx/canvas.h"
38 #include "ui/gfx/scoped_canvas.h"
39 #include "ui/gfx/transform_util.h"
40 #include "ui/views/background.h"
41 #include "ui/views/view.h"
42 #include "ui/views/widget/widget.h"
43 #include "ui/wm/core/coordinate_conversion.h"
44 #include "ui/wm/core/window_animations.h"
45 
46 namespace ash {
47 
CanCoverAvailableWorkspace(aura::Window * window)48 bool CanCoverAvailableWorkspace(aura::Window* window) {
49   SplitViewController* split_view_controller = SplitViewController::Get(window);
50   if (split_view_controller->InSplitViewMode())
51     return split_view_controller->CanSnapWindow(window);
52   return WindowState::Get(window)->IsMaximizedOrFullscreenOrPinned();
53 }
54 
ShouldAnimateWallpaper(aura::Window * root_window)55 bool ShouldAnimateWallpaper(aura::Window* root_window) {
56   // |overview_session| will be null on overview exit because we call this
57   // after the animations are done running. Check the mru window list windows in
58   // this case to see if they cover the workspace.
59   OverviewSession* overview_session =
60       Shell::Get()->overview_controller()->overview_session();
61   if (overview_session) {
62     // Never animate when doing app dragging or when immediately exiting.
63     const auto enter_exit_type = overview_session->enter_exit_overview_type();
64     if (enter_exit_type == OverviewEnterExitType::kImmediateEnter ||
65         enter_exit_type == OverviewEnterExitType::kImmediateExit) {
66       return false;
67     }
68 
69     OverviewGrid* grid = overview_session->GetGridWithRootWindow(root_window);
70     // If one of the windows covers the workspace, we do not need to animate.
71     for (const auto& overview_item : grid->window_list()) {
72       if (CanCoverAvailableWorkspace(overview_item->GetWindow()))
73         return false;
74     }
75 
76     return true;
77   }
78 
79   auto windows =
80       Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kActiveDesk);
81   for (auto* window : windows) {
82     if (window->GetRootWindow() == root_window &&
83         CanCoverAvailableWorkspace(window))
84       return false;
85   }
86   return true;
87 }
88 
FadeInWidgetToOverview(views::Widget * widget,OverviewAnimationType animation_type,bool observe)89 void FadeInWidgetToOverview(views::Widget* widget,
90                             OverviewAnimationType animation_type,
91                             bool observe) {
92   aura::Window* window = widget->GetNativeWindow();
93   if (window->layer()->GetTargetOpacity() == 1.f)
94     return;
95 
96   gfx::Transform original_transform = window->transform();
97 
98   // Fade in the widget from its current opacity.
99   ScopedOverviewAnimationSettings scoped_overview_animation_settings(
100       animation_type, window);
101   window->layer()->SetOpacity(1.0f);
102 
103   if (observe) {
104     auto enter_observer = std::make_unique<EnterAnimationObserver>();
105     scoped_overview_animation_settings.AddObserver(enter_observer.get());
106     Shell::Get()->overview_controller()->AddEnterAnimationObserver(
107         std::move(enter_observer));
108   }
109 }
110 
FadeOutWidgetFromOverview(std::unique_ptr<views::Widget> widget,OverviewAnimationType animation_type)111 void FadeOutWidgetFromOverview(std::unique_ptr<views::Widget> widget,
112                                OverviewAnimationType animation_type) {
113   // The overview controller may be nullptr on shutdown.
114   OverviewController* controller = Shell::Get()->overview_controller();
115   if (!controller) {
116     widget->SetOpacity(0.f);
117     return;
118   }
119 
120   // Fade out the widget from its current opacity. This animation continues past
121   // the lifetime of overview mode items.
122   ScopedOverviewAnimationSettings animation_settings(animation_type,
123                                                      widget->GetNativeWindow());
124   // CleanupAnimationObserver will delete itself (and the widget) when the
125   // opacity animation is complete. Ownership over the observer is passed to the
126   // overview controller which has longer lifetime so that animations can
127   // continue even after the overview mode is shut down.
128   views::Widget* widget_ptr = widget.get();
129   auto observer = std::make_unique<CleanupAnimationObserver>(std::move(widget));
130   animation_settings.AddObserver(observer.get());
131   controller->AddExitAnimationObserver(std::move(observer));
132   widget_ptr->SetOpacity(0.f);
133 }
134 
ImmediatelyCloseWidgetOnExit(std::unique_ptr<views::Widget> widget)135 void ImmediatelyCloseWidgetOnExit(std::unique_ptr<views::Widget> widget) {
136   widget->GetNativeWindow()->SetProperty(aura::client::kAnimationsDisabledKey,
137                                          true);
138   widget->Close();
139   widget.reset();
140 }
141 
GetTargetBoundsInScreen(aura::Window * window)142 gfx::RectF GetTargetBoundsInScreen(aura::Window* window) {
143   gfx::RectF bounds;
144   for (auto* window_iter :
145        window_util::GetVisibleTransientTreeIterator(window)) {
146     // Ignore other window types when computing bounding box of overview target
147     // item.
148     if (window_iter != window &&
149         window_iter->type() != aura::client::WINDOW_TYPE_NORMAL) {
150       continue;
151     }
152     gfx::RectF target_bounds(window_iter->GetTargetBounds());
153     ::wm::TranslateRectToScreen(window_iter->parent(), &target_bounds);
154     bounds.Union(target_bounds);
155   }
156   return bounds;
157 }
158 
SetTransform(aura::Window * window,const gfx::Transform & transform)159 void SetTransform(aura::Window* window, const gfx::Transform& transform) {
160   gfx::PointF target_origin(GetTargetBoundsInScreen(window).origin());
161   for (auto* window_iter :
162        window_util::GetVisibleTransientTreeIterator(window)) {
163     aura::Window* parent_window = window_iter->parent();
164     gfx::RectF original_bounds(window_iter->GetTargetBounds());
165     ::wm::TranslateRectToScreen(parent_window, &original_bounds);
166     gfx::Transform new_transform =
167         TransformAboutPivot(gfx::Point(target_origin.x() - original_bounds.x(),
168                                        target_origin.y() - original_bounds.y()),
169                             transform);
170     window_iter->SetTransform(new_transform);
171   }
172 }
173 
MaximizeIfSnapped(aura::Window * window)174 void MaximizeIfSnapped(aura::Window* window) {
175   auto* window_state = WindowState::Get(window);
176   if (window_state && window_state->IsSnapped()) {
177     ScopedAnimationDisabler disabler(window);
178     WMEvent event(WM_EVENT_MAXIMIZE);
179     window_state->OnWMEvent(&event);
180   }
181 }
182 
GetGridBoundsInScreen(aura::Window * target_root)183 gfx::Rect GetGridBoundsInScreen(aura::Window* target_root) {
184   return GetGridBoundsInScreen(target_root,
185                                /*window_dragging_state=*/base::nullopt,
186                                /*divider_changed=*/false,
187                                /*account_for_hotseat=*/true);
188 }
189 
GetGridBoundsInScreen(aura::Window * target_root,base::Optional<SplitViewDragIndicators::WindowDraggingState> window_dragging_state,bool divider_changed,bool account_for_hotseat)190 gfx::Rect GetGridBoundsInScreen(
191     aura::Window* target_root,
192     base::Optional<SplitViewDragIndicators::WindowDraggingState>
193         window_dragging_state,
194     bool divider_changed,
195     bool account_for_hotseat) {
196   auto* split_view_controller = SplitViewController::Get(target_root);
197   auto state = split_view_controller->state();
198 
199   // If we are in splitview mode already just use the given state, otherwise
200   // convert |window_dragging_state| to a split view state.
201   if (!split_view_controller->InSplitViewMode() && window_dragging_state) {
202     switch (*window_dragging_state) {
203       case SplitViewDragIndicators::WindowDraggingState::kToSnapLeft:
204         state = SplitViewController::State::kLeftSnapped;
205         break;
206       case SplitViewDragIndicators::WindowDraggingState::kToSnapRight:
207         state = SplitViewController::State::kRightSnapped;
208         break;
209       default:
210         break;
211     }
212   }
213 
214   gfx::Rect bounds;
215   gfx::Rect work_area =
216       WorkAreaInsets::ForWindow(target_root)->ComputeStableWorkArea();
217   base::Optional<SplitViewController::SnapPosition> opposite_position =
218       base::nullopt;
219   switch (state) {
220     case SplitViewController::State::kLeftSnapped:
221       bounds = split_view_controller->GetSnappedWindowBoundsInScreen(
222           SplitViewController::RIGHT, /*window_for_minimum_size=*/nullptr);
223       opposite_position = base::make_optional(SplitViewController::RIGHT);
224       break;
225     case SplitViewController::State::kRightSnapped:
226       bounds = split_view_controller->GetSnappedWindowBoundsInScreen(
227           SplitViewController::LEFT, /*window_for_minimum_size=*/nullptr);
228       opposite_position = base::make_optional(SplitViewController::LEFT);
229       break;
230     case SplitViewController::State::kNoSnap:
231       bounds = work_area;
232       break;
233     case SplitViewController::State::kBothSnapped:
234       // When this function is called, SplitViewController should have already
235       // handled the state change.
236       NOTREACHED();
237   }
238 
239   // Hotseat is overlaps the work area / split view bounds when extended, but in
240   // some cases we don't want its bounds in our calculations.
241   if (account_for_hotseat) {
242     Shelf* shelf = Shelf::ForWindow(target_root);
243     const bool hotseat_extended =
244         shelf->shelf_layout_manager()->hotseat_state() ==
245         HotseatState::kExtended;
246     // When a window is dragged from the top of the screen, overview gets
247     // entered immediately but the window does not get deactivated right away so
248     // the hotseat state does not get updated until the window gets dragged a
249     // bit. In this case, determine whether the hotseat will be extended to
250     // avoid doing a expensive double grid layout.
251     auto* overview_session =
252         Shell::Get()->overview_controller()->overview_session();
253     const bool hotseat_will_extend =
254         overview_session &&
255         overview_session->enter_exit_overview_type() ==
256             OverviewEnterExitType::kImmediateEnter &&
257         !split_view_controller->InSplitViewMode();
258     if (hotseat_extended || hotseat_will_extend) {
259       // Use the default hotseat size here to avoid the possible re-layout
260       // due to the update in HotseatWidget::is_forced_dense_.
261       const int hotseat_bottom_inset =
262           ShelfConfig::Get()->GetHotseatSize(
263               /*density=*/HotseatDensity::kNormal) +
264           ShelfConfig::Get()->hotseat_bottom_padding();
265 
266       bounds.Inset(0, 0, 0, hotseat_bottom_inset);
267     }
268   }
269 
270   if (!divider_changed)
271     return bounds;
272 
273   DCHECK(opposite_position);
274   const bool horizontal = SplitViewController::IsLayoutHorizontal();
275   const int min_length =
276       (horizontal ? work_area.width() : work_area.height()) / 3;
277   const int current_length = horizontal ? bounds.width() : bounds.height();
278 
279   if (current_length > min_length)
280     return bounds;
281 
282   // Clamp bounds' length to the minimum length.
283   if (horizontal)
284     bounds.set_width(min_length);
285   else
286     bounds.set_height(min_length);
287 
288   if (SplitViewController::IsPhysicalLeftOrTop(*opposite_position)) {
289     // If we are shifting to the left or top we need to update the origin as
290     // well.
291     const int offset = min_length - current_length;
292     bounds.Offset(horizontal ? gfx::Vector2d(-offset, 0)
293                              : gfx::Vector2d(0, -offset));
294   }
295 
296   return bounds;
297 }
298 
GetSplitviewBoundsMaintainingAspectRatio()299 base::Optional<gfx::RectF> GetSplitviewBoundsMaintainingAspectRatio() {
300   if (!ShouldAllowSplitView())
301     return base::nullopt;
302   if (!Shell::Get()->tablet_mode_controller()->InTabletMode())
303     return base::nullopt;
304   auto* overview_session =
305       Shell::Get()->overview_controller()->overview_session();
306   DCHECK(overview_session);
307   aura::Window* root_window = Shell::GetPrimaryRootWindow();
308   DCHECK(overview_session->GetGridWithRootWindow(root_window)
309              ->split_view_drag_indicators());
310   auto window_dragging_state =
311       overview_session->GetGridWithRootWindow(root_window)
312           ->split_view_drag_indicators()
313           ->current_window_dragging_state();
314   if (!SplitViewController::Get(root_window)->InSplitViewMode() &&
315       SplitViewDragIndicators::GetSnapPosition(window_dragging_state) ==
316           SplitViewController::NONE) {
317     return base::nullopt;
318   }
319 
320   // The hotseat bounds do not affect splitview after a window is snapped, so
321   // the aspect ratio should reflect it and not worry about the hotseat.
322   return base::make_optional(gfx::RectF(GetGridBoundsInScreen(
323       root_window, base::make_optional(window_dragging_state),
324       /*divider_changed=*/false, /*account_for_hotseat=*/false)));
325 }
326 
ShouldUseTabletModeGridLayout()327 bool ShouldUseTabletModeGridLayout() {
328   return Shell::Get()->tablet_mode_controller()->InTabletMode();
329 }
330 
ToStableSizeRoundedRect(const gfx::RectF & rect)331 gfx::Rect ToStableSizeRoundedRect(const gfx::RectF& rect) {
332   return gfx::Rect(gfx::ToRoundedPoint(rect.origin()),
333                    gfx::ToRoundedSize(rect.size()));
334 }
335 
336 }  // namespace ash
337