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