1 // Copyright (c) 2012 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.
5 #include "ash/wm/workspace/workspace_layout_manager.h"
7 #include "ash/ash_switches.h"
8 #include "ash/root_window_controller.h"
9 #include "ash/screen_ash.h"
10 #include "ash/session_state_delegate.h"
11 #include "ash/shell.h"
12 #include "ash/wm/always_on_top_controller.h"
13 #include "ash/wm/base_layout_manager.h"
14 #include "ash/wm/property_util.h"
15 #include "ash/wm/window_animations.h"
16 #include "ash/wm/window_properties.h"
17 #include "ash/wm/window_util.h"
18 #include "ash/wm/workspace/workspace.h"
19 #include "ash/wm/workspace/workspace_manager.h"
20 #include "ash/wm/workspace/workspace_window_resizer.h"
21 #include "base/auto_reset.h"
22 #include "base/command_line.h"
23 #include "ui/aura/client/aura_constants.h"
24 #include "ui/aura/root_window.h"
25 #include "ui/aura/window.h"
26 #include "ui/aura/window_observer.h"
27 #include "ui/base/events/event.h"
28 #include "ui/base/ui_base_types.h"
29 #include "ui/views/corewm/window_util.h"
39 // This specifies how much percent (2/3=66%) of a window must be visible when
40 // the window is added to the workspace.
41 const float kMinimumPercentOnScreenArea
= 0.66f
;
43 typedef std::map
<const aura::Window
*, gfx::Rect
> BoundsMap
;
45 // Adds an entry from |window| to its bounds and recursively invokes this for
47 void BuildWindowBoundsMap(const aura::Window
* window
, BoundsMap
* bounds_map
) {
48 (*bounds_map
)[window
] = window
->bounds();
49 for (size_t i
= 0; i
< window
->children().size(); ++i
)
50 BuildWindowBoundsMap(window
->children()[i
], bounds_map
);
53 // Resets |window|s bounds from |bounds_map| if currently empty. Recursively
54 // invokes this for all children.
55 void ResetBoundsIfNecessary(const BoundsMap
& bounds_map
, aura::Window
* window
) {
56 if (window
->bounds().IsEmpty() && window
->GetTargetBounds().IsEmpty()) {
57 BoundsMap::const_iterator i
= bounds_map
.find(window
);
58 if (i
!= bounds_map
.end())
59 window
->SetBounds(i
->second
);
61 for (size_t i
= 0; i
< window
->children().size(); ++i
)
62 ResetBoundsIfNecessary(bounds_map
, window
->children()[i
]);
65 // Resets |window|s bounds from |bounds_map| if |window| is marked as a
66 // constrained window. Recursively invokes this for all children.
67 // TODO(sky): this should key off window type.
68 void ResetConstrainedWindowBoundsIfNecessary(const BoundsMap
& bounds_map
,
69 aura::Window
* window
) {
70 if (window
->GetProperty(aura::client::kConstrainedWindowKey
)) {
71 BoundsMap::const_iterator i
= bounds_map
.find(window
);
72 if (i
!= bounds_map
.end())
73 window
->SetBounds(i
->second
);
75 for (size_t i
= 0; i
< window
->children().size(); ++i
)
76 ResetConstrainedWindowBoundsIfNecessary(bounds_map
, window
->children()[i
]);
79 bool IsMaximizedState(ui::WindowShowState state
) {
80 return state
== ui::SHOW_STATE_MAXIMIZED
||
81 state
== ui::SHOW_STATE_FULLSCREEN
;
86 WorkspaceLayoutManager::WorkspaceLayoutManager(Workspace
* workspace
)
87 : root_window_(workspace
->window()->GetRootWindow()),
88 workspace_(workspace
),
89 work_area_(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
90 workspace
->window()->parent())) {
91 Shell::GetInstance()->AddShellObserver(this);
92 root_window_
->AddObserver(this);
95 WorkspaceLayoutManager::~WorkspaceLayoutManager() {
97 root_window_
->RemoveObserver(this);
98 for (WindowSet::const_iterator i
= windows_
.begin(); i
!= windows_
.end(); ++i
)
99 (*i
)->RemoveObserver(this);
100 Shell::GetInstance()->RemoveShellObserver(this);
103 void WorkspaceLayoutManager::OnWindowAddedToLayout(Window
* child
) {
104 // Adjust window bounds in case that the new child is out of the workspace.
105 AdjustWindowSizeForScreenChange(child
, ADJUST_WINDOW_WINDOW_ADDED
);
107 windows_
.insert(child
);
108 child
->AddObserver(this);
110 // Only update the bounds if the window has a show state that depends on the
112 if (wm::IsWindowMaximized(child
) || wm::IsWindowFullscreen(child
))
113 UpdateBoundsFromShowState(child
);
115 workspace_manager()->OnWindowAddedToWorkspace(workspace_
, child
);
118 void WorkspaceLayoutManager::OnWillRemoveWindowFromLayout(Window
* child
) {
119 windows_
.erase(child
);
120 child
->RemoveObserver(this);
121 workspace_manager()->OnWillRemoveWindowFromWorkspace(workspace_
, child
);
124 void WorkspaceLayoutManager::OnWindowRemovedFromLayout(Window
* child
) {
125 workspace_manager()->OnWindowRemovedFromWorkspace(workspace_
, child
);
128 void WorkspaceLayoutManager::OnChildWindowVisibilityChanged(Window
* child
,
130 if (visible
&& wm::IsWindowMinimized(child
)) {
131 // Attempting to show a minimized window. Unminimize it.
132 child
->SetProperty(aura::client::kShowStateKey
,
133 child
->GetProperty(aura::client::kRestoreShowStateKey
));
134 child
->ClearProperty(aura::client::kRestoreShowStateKey
);
136 workspace_manager()->OnWorkspaceChildWindowVisibilityChanged(workspace_
,
140 void WorkspaceLayoutManager::SetChildBounds(
142 const gfx::Rect
& requested_bounds
) {
143 if (!GetTrackedByWorkspace(child
)) {
144 SetChildBoundsDirect(child
, requested_bounds
);
147 gfx::Rect
child_bounds(requested_bounds
);
148 // Some windows rely on this to set their initial bounds.
149 if (!SetMaximizedOrFullscreenBounds(child
)) {
150 // Non-maximized/full-screen windows have their size constrained to the
152 child_bounds
.set_width(std::min(work_area_
.width(), child_bounds
.width()));
153 child_bounds
.set_height(
154 std::min(work_area_
.height(), child_bounds
.height()));
155 SetChildBoundsDirect(child
, child_bounds
);
157 workspace_manager()->OnWorkspaceWindowChildBoundsChanged(workspace_
, child
);
160 void WorkspaceLayoutManager::OnDisplayWorkAreaInsetsChanged() {
161 if (workspace_manager()->active_workspace_
== workspace_
) {
162 const gfx::Rect
work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
163 workspace_
->window()->parent()));
164 if (work_area
!= work_area_
)
165 AdjustWindowSizesForScreenChange(ADJUST_WINDOW_DISPLAY_INSETS_CHANGED
);
169 void WorkspaceLayoutManager::OnWindowPropertyChanged(Window
* window
,
172 if (key
== aura::client::kShowStateKey
) {
173 ui::WindowShowState old_state
= static_cast<ui::WindowShowState
>(old
);
174 ui::WindowShowState new_state
=
175 window
->GetProperty(aura::client::kShowStateKey
);
176 if (old_state
!= ui::SHOW_STATE_MINIMIZED
&&
177 GetRestoreBoundsInScreen(window
) == NULL
&&
178 IsMaximizedState(new_state
) &&
179 !IsMaximizedState(old_state
)) {
180 SetRestoreBoundsInParent(window
, window
->bounds());
182 // When restoring from a minimized state, we want to restore to the
183 // previous (maybe L/R maximized) state. Since we do also want to keep the
184 // restore rectangle, we set the restore rectangle to the rectangle we want
185 // to restore to and restore it after we switched so that it is preserved.
187 if (old_state
== ui::SHOW_STATE_MINIMIZED
&&
188 (new_state
== ui::SHOW_STATE_NORMAL
||
189 new_state
== ui::SHOW_STATE_DEFAULT
) &&
190 GetRestoreBoundsInScreen(window
) &&
191 !GetWindowAlwaysRestoresToRestoreBounds(window
)) {
192 restore
= *GetRestoreBoundsInScreen(window
);
193 SetRestoreBoundsInScreen(window
, window
->GetBoundsInScreen());
196 // If the new state requires |window| to be in a workspace, clone the layer.
197 // WorkspaceManager will use it (and take ownership of it) when animating.
198 // Ideally we could use that of BaseLayoutManager, but that proves
199 // problematic. In particular when restoring we need to animate on top of
200 // the workspace animating in.
201 ui::Layer
* cloned_layer
= NULL
;
202 BoundsMap bounds_map
;
203 if (wm::IsActiveWindow(window
) &&
204 ((new_state
== ui::SHOW_STATE_FULLSCREEN
&&
205 wm::IsWindowStateNormal(old_state
)) ||
206 (new_state
!= ui::SHOW_STATE_FULLSCREEN
&&
207 old_state
== ui::SHOW_STATE_FULLSCREEN
&&
208 new_state
!= ui::SHOW_STATE_MINIMIZED
))) {
209 BuildWindowBoundsMap(window
, &bounds_map
);
210 cloned_layer
= views::corewm::RecreateWindowLayers(window
, false);
211 // Constrained windows don't get their bounds reset when we update the
212 // window bounds. Leaving them empty is unexpected, so we reset them now.
213 ResetConstrainedWindowBoundsIfNecessary(bounds_map
, window
);
215 UpdateBoundsFromShowState(window
);
218 // Even though we just set the bounds not all descendants may have valid
219 // bounds. For example, constrained windows don't resize with the parent.
220 // Ensure that all windows that had a bounds before we cloned the layer
221 // have a bounds now.
222 ResetBoundsIfNecessary(bounds_map
, window
);
225 ShowStateChanged(window
, old_state
, cloned_layer
);
227 // Set the restore rectangle to the previously set restore rectangle.
228 if (!restore
.IsEmpty())
229 SetRestoreBoundsInScreen(window
, restore
);
232 if (key
== internal::kWindowTrackedByWorkspaceKey
&&
233 GetTrackedByWorkspace(window
)) {
234 workspace_manager()->OnTrackedByWorkspaceChanged(workspace_
, window
);
235 if (wm::IsWindowMaximized(window
)) {
236 SetChildBoundsDirect(
237 window
, ScreenAsh::GetMaximizedWindowBoundsInParent(
238 window
->parent()->parent()));
242 if (key
== aura::client::kAlwaysOnTopKey
&&
243 window
->GetProperty(aura::client::kAlwaysOnTopKey
)) {
244 internal::AlwaysOnTopController
* controller
=
245 GetRootWindowController(window
->GetRootWindow())->
246 always_on_top_controller();
247 controller
->GetContainer(window
)->AddChild(window
);
251 void WorkspaceLayoutManager::OnWindowDestroying(aura::Window
* window
) {
252 if (root_window_
== window
) {
253 root_window_
->RemoveObserver(this);
258 void WorkspaceLayoutManager::OnWindowBoundsChanged(
259 aura::Window
* window
,
260 const gfx::Rect
& old_bounds
,
261 const gfx::Rect
& new_bounds
) {
262 if (root_window_
== window
)
263 AdjustWindowSizesForScreenChange(ADJUST_WINDOW_SCREEN_SIZE_CHANGED
);
266 void WorkspaceLayoutManager::ShowStateChanged(
268 ui::WindowShowState last_show_state
,
269 ui::Layer
* cloned_layer
) {
270 if (wm::IsWindowMinimized(window
)) {
271 DCHECK(!cloned_layer
);
272 // Save the previous show state so that we can correctly restore it.
273 window
->SetProperty(aura::client::kRestoreShowStateKey
, last_show_state
);
274 views::corewm::SetWindowVisibilityAnimationType(
275 window
, WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE
);
276 workspace_manager()->OnWorkspaceWindowShowStateChanged(
277 workspace_
, window
, last_show_state
, NULL
);
279 if (wm::IsActiveWindow(window
))
280 wm::DeactivateWindow(window
);
282 if ((window
->TargetVisibility() ||
283 last_show_state
== ui::SHOW_STATE_MINIMIZED
) &&
284 !window
->layer()->visible()) {
285 // The layer may be hidden if the window was previously minimized. Make
286 // sure it's visible.
289 if (last_show_state
== ui::SHOW_STATE_MINIMIZED
&&
290 !wm::IsWindowMaximized(window
) &&
291 !wm::IsWindowFullscreen(window
)) {
292 window
->ClearProperty(internal::kWindowRestoresToRestoreBounds
);
294 workspace_manager()->OnWorkspaceWindowShowStateChanged(
295 workspace_
, window
, last_show_state
, cloned_layer
);
299 void WorkspaceLayoutManager::AdjustWindowSizesForScreenChange(
300 AdjustWindowReason reason
) {
301 // Don't do any adjustments of the insets while we are in screen locked mode.
302 // This would happen if the launcher was auto hidden before the login screen
303 // was shown and then gets shown when the login screen gets presented.
304 if (reason
== ADJUST_WINDOW_DISPLAY_INSETS_CHANGED
&&
305 Shell::GetInstance()->session_state_delegate()->IsScreenLocked())
307 work_area_
= ScreenAsh::GetDisplayWorkAreaBoundsInParent(
308 workspace_
->window()->parent());
309 // If a user plugs an external display into a laptop running Aura the
310 // display size will change. Maximized windows need to resize to match.
311 // We also do this when developers running Aura on a desktop manually resize
313 // We also need to do this when the work area insets changes.
314 for (WindowSet::const_iterator it
= windows_
.begin();
315 it
!= windows_
.end();
317 AdjustWindowSizeForScreenChange(*it
, reason
);
321 void WorkspaceLayoutManager::AdjustWindowSizeForScreenChange(
323 AdjustWindowReason reason
) {
324 if (GetTrackedByWorkspace(window
) &&
325 !SetMaximizedOrFullscreenBounds(window
)) {
326 if (reason
== ADJUST_WINDOW_SCREEN_SIZE_CHANGED
) {
327 // The work area may be smaller than the full screen. Put as much of the
328 // window as possible within the display area.
329 gfx::Rect bounds
= window
->bounds();
330 bounds
.AdjustToFit(work_area_
);
331 window
->SetBounds(bounds
);
332 } else if (reason
== ADJUST_WINDOW_DISPLAY_INSETS_CHANGED
) {
333 gfx::Rect bounds
= window
->bounds();
334 ash::wm::AdjustBoundsToEnsureMinimumWindowVisibility(work_area_
, &bounds
);
335 if (window
->bounds() != bounds
)
336 window
->SetBounds(bounds
);
337 } else if (reason
== ADJUST_WINDOW_WINDOW_ADDED
) {
338 gfx::Rect bounds
= window
->bounds();
339 int min_width
= bounds
.width() * kMinimumPercentOnScreenArea
;
340 int min_height
= bounds
.height() * kMinimumPercentOnScreenArea
;
341 ash::wm::AdjustBoundsToEnsureWindowVisibility(
342 work_area_
, min_width
, min_height
, &bounds
);
343 if (window
->bounds() != bounds
)
344 window
->SetBounds(bounds
);
349 void WorkspaceLayoutManager::UpdateBoundsFromShowState(Window
* window
) {
350 // See comment in SetMaximizedOrFullscreenBounds() as to why we use parent in
351 // these calculation.
352 switch (window
->GetProperty(aura::client::kShowStateKey
)) {
353 case ui::SHOW_STATE_DEFAULT
:
354 case ui::SHOW_STATE_NORMAL
: {
355 const gfx::Rect
* restore
= GetRestoreBoundsInScreen(window
);
357 gfx::Rect bounds_in_parent
=
358 ScreenAsh::ConvertRectFromScreen(window
->parent()->parent(),
360 SetChildBoundsDirect(
362 BaseLayoutManager::BoundsWithScreenEdgeVisible(
363 window
->parent()->parent(),
366 ClearRestoreBounds(window
);
370 case ui::SHOW_STATE_MAXIMIZED
:
371 case ui::SHOW_STATE_FULLSCREEN
:
372 SetMaximizedOrFullscreenBounds(window
);
380 bool WorkspaceLayoutManager::SetMaximizedOrFullscreenBounds(
381 aura::Window
* window
) {
382 if (!GetTrackedByWorkspace(window
))
385 // During animations there is a transform installed on the workspace
386 // windows. For this reason this code uses the parent so that the transform is
388 if (wm::IsWindowMaximized(window
)) {
389 SetChildBoundsDirect(
390 window
, ScreenAsh::GetMaximizedWindowBoundsInParent(
391 window
->parent()->parent()));
394 if (wm::IsWindowFullscreen(window
)) {
395 SetChildBoundsDirect(
397 ScreenAsh::GetDisplayBoundsInParent(window
->parent()->parent()));
403 WorkspaceManager
* WorkspaceLayoutManager::workspace_manager() {
404 return workspace_
->workspace_manager();
407 } // namespace internal