OLD | NEW |
| (Empty) |
1 // Copyright 2014 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/common/wm/default_state.h" | |
6 | |
7 #include "ash/common/ash_switches.h" | |
8 #include "ash/common/wm/dock/docked_window_layout_manager.h" | |
9 #include "ash/common/wm/window_animation_types.h" | |
10 #include "ash/common/wm/window_parenting_utils.h" | |
11 #include "ash/common/wm/window_positioning_utils.h" | |
12 #include "ash/common/wm/window_state.h" | |
13 #include "ash/common/wm/window_state_delegate.h" | |
14 #include "ash/common/wm/window_state_util.h" | |
15 #include "ash/common/wm/wm_event.h" | |
16 #include "ash/common/wm/wm_screen_util.h" | |
17 #include "ash/common/wm_shell.h" | |
18 #include "ash/common/wm_window.h" | |
19 #include "ash/public/cpp/shell_window_ids.h" | |
20 #include "ash/root_window_controller.h" | |
21 #include "ui/display/display.h" | |
22 #include "ui/display/screen.h" | |
23 | |
24 namespace ash { | |
25 namespace wm { | |
26 namespace { | |
27 | |
28 // This specifies how much percent (30%) of a window rect | |
29 // must be visible when the window is added to the workspace. | |
30 const float kMinimumPercentOnScreenArea = 0.3f; | |
31 | |
32 // When a window that has restore bounds at least as large as a work area is | |
33 // unmaximized, inset the bounds slightly so that they are not exactly the same. | |
34 // This makes it easier to resize the window. | |
35 const int kMaximizedWindowInset = 10; // DIPs. | |
36 | |
37 bool IsMinimizedWindowState(const WindowStateType state_type) { | |
38 return state_type == WINDOW_STATE_TYPE_MINIMIZED || | |
39 state_type == WINDOW_STATE_TYPE_DOCKED_MINIMIZED; | |
40 } | |
41 | |
42 void MoveToDisplayForRestore(WindowState* window_state) { | |
43 if (!window_state->HasRestoreBounds()) | |
44 return; | |
45 const gfx::Rect restore_bounds = window_state->GetRestoreBoundsInScreen(); | |
46 | |
47 // Move only if the restore bounds is outside of | |
48 // the display. There is no information about in which | |
49 // display it should be restored, so this is best guess. | |
50 // TODO(oshima): Restore information should contain the | |
51 // work area information like WindowResizer does for the | |
52 // last window location. | |
53 gfx::Rect display_area = | |
54 window_state->window()->GetDisplayNearestWindow().bounds(); | |
55 | |
56 if (!display_area.Intersects(restore_bounds)) { | |
57 const display::Display& display = | |
58 display::Screen::GetScreen()->GetDisplayMatching(restore_bounds); | |
59 WmShell* shell = window_state->window()->GetShell(); | |
60 WmWindow* new_root = shell->GetRootWindowForDisplayId(display.id()); | |
61 if (new_root != window_state->window()->GetRootWindow()) { | |
62 WmWindow* new_container = new_root->GetChildByShellWindowId( | |
63 window_state->window()->GetParent()->GetShellWindowId()); | |
64 new_container->AddChild(window_state->window()); | |
65 } | |
66 } | |
67 } | |
68 | |
69 DockedWindowLayoutManager* GetDockedWindowLayoutManager(WmShell* shell) { | |
70 return DockedWindowLayoutManager::Get(shell->GetActiveWindow()); | |
71 } | |
72 | |
73 class ScopedPreferredAlignmentResetter { | |
74 public: | |
75 ScopedPreferredAlignmentResetter(DockedAlignment dock_alignment, | |
76 DockedWindowLayoutManager* dock_layout) | |
77 : docked_window_layout_manager_(dock_layout) { | |
78 docked_window_layout_manager_->set_preferred_alignment(dock_alignment); | |
79 } | |
80 ~ScopedPreferredAlignmentResetter() { | |
81 docked_window_layout_manager_->set_preferred_alignment( | |
82 DOCKED_ALIGNMENT_NONE); | |
83 } | |
84 | |
85 private: | |
86 DockedWindowLayoutManager* docked_window_layout_manager_; | |
87 | |
88 DISALLOW_COPY_AND_ASSIGN(ScopedPreferredAlignmentResetter); | |
89 }; | |
90 | |
91 class ScopedDockedLayoutEventSourceResetter { | |
92 public: | |
93 ScopedDockedLayoutEventSourceResetter(DockedWindowLayoutManager* dock_layout) | |
94 : docked_window_layout_manager_(dock_layout) { | |
95 docked_window_layout_manager_->set_event_source( | |
96 DOCKED_ACTION_SOURCE_KEYBOARD); | |
97 } | |
98 ~ScopedDockedLayoutEventSourceResetter() { | |
99 docked_window_layout_manager_->set_event_source( | |
100 DOCKED_ACTION_SOURCE_UNKNOWN); | |
101 } | |
102 | |
103 private: | |
104 DockedWindowLayoutManager* docked_window_layout_manager_; | |
105 | |
106 DISALLOW_COPY_AND_ASSIGN(ScopedDockedLayoutEventSourceResetter); | |
107 }; | |
108 | |
109 void CycleSnap(WindowState* window_state, WMEventType event) { | |
110 DCHECK(!ash::switches::DockedWindowsEnabled()); | |
111 | |
112 wm::WindowStateType desired_snap_state = | |
113 event == WM_EVENT_CYCLE_SNAP_DOCK_LEFT | |
114 ? wm::WINDOW_STATE_TYPE_LEFT_SNAPPED | |
115 : wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED; | |
116 | |
117 if (window_state->CanSnap() && | |
118 window_state->GetStateType() != desired_snap_state && | |
119 window_state->window()->GetType() != ui::wm::WINDOW_TYPE_PANEL) { | |
120 const wm::WMEvent event(desired_snap_state == | |
121 wm::WINDOW_STATE_TYPE_LEFT_SNAPPED | |
122 ? wm::WM_EVENT_SNAP_LEFT | |
123 : wm::WM_EVENT_SNAP_RIGHT); | |
124 window_state->OnWMEvent(&event); | |
125 return; | |
126 } | |
127 | |
128 if (window_state->IsSnapped()) { | |
129 window_state->Restore(); | |
130 return; | |
131 } | |
132 window_state->window()->Animate(::wm::WINDOW_ANIMATION_TYPE_BOUNCE); | |
133 } | |
134 | |
135 void CycleSnapDock(WindowState* window_state, WMEventType event) { | |
136 DCHECK(ash::switches::DockedWindowsEnabled()); | |
137 | |
138 DockedWindowLayoutManager* dock_layout = | |
139 GetDockedWindowLayoutManager(window_state->window()->GetShell()); | |
140 wm::WindowStateType desired_snap_state = | |
141 event == WM_EVENT_CYCLE_SNAP_DOCK_LEFT | |
142 ? wm::WINDOW_STATE_TYPE_LEFT_SNAPPED | |
143 : wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED; | |
144 DockedAlignment desired_dock_alignment = | |
145 event == WM_EVENT_CYCLE_SNAP_DOCK_LEFT ? DOCKED_ALIGNMENT_LEFT | |
146 : DOCKED_ALIGNMENT_RIGHT; | |
147 DockedAlignment current_dock_alignment = | |
148 dock_layout ? dock_layout->CalculateAlignment() : DOCKED_ALIGNMENT_NONE; | |
149 | |
150 if (!window_state->IsDocked() || | |
151 (current_dock_alignment != DOCKED_ALIGNMENT_NONE && | |
152 current_dock_alignment != desired_dock_alignment)) { | |
153 if (window_state->CanSnap() && | |
154 window_state->GetStateType() != desired_snap_state && | |
155 window_state->window()->GetType() != ui::wm::WINDOW_TYPE_PANEL) { | |
156 const wm::WMEvent event(desired_snap_state == | |
157 wm::WINDOW_STATE_TYPE_LEFT_SNAPPED | |
158 ? wm::WM_EVENT_SNAP_LEFT | |
159 : wm::WM_EVENT_SNAP_RIGHT); | |
160 window_state->OnWMEvent(&event); | |
161 return; | |
162 } | |
163 | |
164 if (dock_layout && | |
165 dock_layout->CanDockWindow(window_state->window(), | |
166 desired_dock_alignment)) { | |
167 if (window_state->IsDocked()) { | |
168 dock_layout->MaybeSetDesiredDockedAlignment(desired_dock_alignment); | |
169 return; | |
170 } | |
171 | |
172 ScopedDockedLayoutEventSourceResetter event_source_resetter(dock_layout); | |
173 ScopedPreferredAlignmentResetter alignmentResetter(desired_dock_alignment, | |
174 dock_layout); | |
175 const wm::WMEvent event(wm::WM_EVENT_DOCK); | |
176 window_state->OnWMEvent(&event); | |
177 return; | |
178 } | |
179 } | |
180 | |
181 if (window_state->IsDocked() || window_state->IsSnapped()) { | |
182 ScopedDockedLayoutEventSourceResetter event_source_resetter(dock_layout); | |
183 window_state->Restore(); | |
184 return; | |
185 } | |
186 window_state->window()->Animate(::wm::WINDOW_ANIMATION_TYPE_BOUNCE); | |
187 } | |
188 | |
189 } // namespace | |
190 | |
191 DefaultState::DefaultState(WindowStateType initial_state_type) | |
192 : state_type_(initial_state_type), stored_window_state_(nullptr) {} | |
193 DefaultState::~DefaultState() {} | |
194 | |
195 void DefaultState::OnWMEvent(WindowState* window_state, const WMEvent* event) { | |
196 if (ProcessWorkspaceEvents(window_state, event)) | |
197 return; | |
198 | |
199 // Do not change the PINNED window state if this is not unpin event. | |
200 if (window_state->IsTrustedPinned() && event->type() != WM_EVENT_NORMAL) | |
201 return; | |
202 | |
203 if (ProcessCompoundEvents(window_state, event)) | |
204 return; | |
205 | |
206 WindowStateType current_state_type = window_state->GetStateType(); | |
207 WindowStateType next_state_type = WINDOW_STATE_TYPE_NORMAL; | |
208 switch (event->type()) { | |
209 case WM_EVENT_NORMAL: | |
210 next_state_type = current_state_type == WINDOW_STATE_TYPE_DOCKED_MINIMIZED | |
211 ? WINDOW_STATE_TYPE_DOCKED | |
212 : WINDOW_STATE_TYPE_NORMAL; | |
213 break; | |
214 case WM_EVENT_MAXIMIZE: | |
215 next_state_type = WINDOW_STATE_TYPE_MAXIMIZED; | |
216 break; | |
217 case WM_EVENT_MINIMIZE: | |
218 next_state_type = current_state_type == WINDOW_STATE_TYPE_DOCKED | |
219 ? WINDOW_STATE_TYPE_DOCKED_MINIMIZED | |
220 : WINDOW_STATE_TYPE_MINIMIZED; | |
221 break; | |
222 case WM_EVENT_FULLSCREEN: | |
223 next_state_type = WINDOW_STATE_TYPE_FULLSCREEN; | |
224 break; | |
225 case WM_EVENT_SNAP_LEFT: | |
226 next_state_type = WINDOW_STATE_TYPE_LEFT_SNAPPED; | |
227 break; | |
228 case WM_EVENT_SNAP_RIGHT: | |
229 next_state_type = WINDOW_STATE_TYPE_RIGHT_SNAPPED; | |
230 break; | |
231 case WM_EVENT_DOCK: | |
232 next_state_type = WINDOW_STATE_TYPE_DOCKED; | |
233 break; | |
234 case WM_EVENT_SET_BOUNDS: | |
235 SetBounds(window_state, static_cast<const SetBoundsEvent*>(event)); | |
236 return; | |
237 case WM_EVENT_SHOW_INACTIVE: | |
238 next_state_type = WINDOW_STATE_TYPE_INACTIVE; | |
239 break; | |
240 case WM_EVENT_PIN: | |
241 case WM_EVENT_TRUSTED_PIN: | |
242 // If there already is a pinned window, it is not allowed to set it | |
243 // to this window. | |
244 // TODO(hidehiko): If a system modal window is openening, the pinning | |
245 // probably should fail. | |
246 if (WmShell::Get()->IsPinned()) { | |
247 LOG(ERROR) << "An PIN event will be failed since another window is " | |
248 << "already in pinned mode."; | |
249 next_state_type = current_state_type; | |
250 } else { | |
251 next_state_type = event->type() == WM_EVENT_PIN | |
252 ? WINDOW_STATE_TYPE_PINNED | |
253 : WINDOW_STATE_TYPE_TRUSTED_PINNED; | |
254 } | |
255 break; | |
256 case WM_EVENT_TOGGLE_MAXIMIZE_CAPTION: | |
257 case WM_EVENT_TOGGLE_MAXIMIZE: | |
258 case WM_EVENT_TOGGLE_VERTICAL_MAXIMIZE: | |
259 case WM_EVENT_TOGGLE_HORIZONTAL_MAXIMIZE: | |
260 case WM_EVENT_TOGGLE_FULLSCREEN: | |
261 case WM_EVENT_CYCLE_SNAP_DOCK_LEFT: | |
262 case WM_EVENT_CYCLE_SNAP_DOCK_RIGHT: | |
263 case WM_EVENT_CENTER: | |
264 NOTREACHED() << "Compound event should not reach here:" << event; | |
265 return; | |
266 case WM_EVENT_ADDED_TO_WORKSPACE: | |
267 case WM_EVENT_WORKAREA_BOUNDS_CHANGED: | |
268 case WM_EVENT_DISPLAY_BOUNDS_CHANGED: | |
269 NOTREACHED() << "Workspace event should not reach here:" << event; | |
270 return; | |
271 } | |
272 | |
273 if (next_state_type == current_state_type && window_state->IsSnapped()) { | |
274 gfx::Rect snapped_bounds = | |
275 event->type() == WM_EVENT_SNAP_LEFT | |
276 ? GetDefaultLeftSnappedWindowBoundsInParent(window_state->window()) | |
277 : GetDefaultRightSnappedWindowBoundsInParent( | |
278 window_state->window()); | |
279 window_state->SetBoundsDirectAnimated(snapped_bounds); | |
280 return; | |
281 } | |
282 | |
283 if (event->type() == WM_EVENT_SNAP_LEFT || | |
284 event->type() == WM_EVENT_SNAP_RIGHT) { | |
285 window_state->set_bounds_changed_by_user(true); | |
286 } | |
287 | |
288 EnterToNextState(window_state, next_state_type); | |
289 } | |
290 | |
291 WindowStateType DefaultState::GetType() const { | |
292 return state_type_; | |
293 } | |
294 | |
295 void DefaultState::AttachState(WindowState* window_state, | |
296 WindowState::State* state_in_previous_mode) { | |
297 DCHECK_EQ(stored_window_state_, window_state); | |
298 | |
299 ReenterToCurrentState(window_state, state_in_previous_mode); | |
300 | |
301 // If the display has changed while in the another mode, | |
302 // we need to let windows know the change. | |
303 display::Display current_display = | |
304 window_state->window()->GetDisplayNearestWindow(); | |
305 if (stored_display_state_.bounds() != current_display.bounds()) { | |
306 const WMEvent event(wm::WM_EVENT_DISPLAY_BOUNDS_CHANGED); | |
307 window_state->OnWMEvent(&event); | |
308 } else if (stored_display_state_.work_area() != current_display.work_area()) { | |
309 const WMEvent event(wm::WM_EVENT_WORKAREA_BOUNDS_CHANGED); | |
310 window_state->OnWMEvent(&event); | |
311 } | |
312 } | |
313 | |
314 void DefaultState::DetachState(WindowState* window_state) { | |
315 stored_window_state_ = window_state; | |
316 stored_bounds_ = window_state->window()->GetBounds(); | |
317 stored_restore_bounds_ = window_state->HasRestoreBounds() | |
318 ? window_state->GetRestoreBoundsInParent() | |
319 : gfx::Rect(); | |
320 // Remember the display state so that in case of the display change | |
321 // while in the other mode, we can perform necessary action to | |
322 // restore the window state to the proper state for the current | |
323 // display. | |
324 stored_display_state_ = window_state->window()->GetDisplayNearestWindow(); | |
325 } | |
326 | |
327 // static | |
328 bool DefaultState::ProcessCompoundEvents(WindowState* window_state, | |
329 const WMEvent* event) { | |
330 WmWindow* window = window_state->window(); | |
331 | |
332 switch (event->type()) { | |
333 case WM_EVENT_TOGGLE_MAXIMIZE_CAPTION: | |
334 if (window_state->IsFullscreen()) { | |
335 const wm::WMEvent event(wm::WM_EVENT_TOGGLE_FULLSCREEN); | |
336 window_state->OnWMEvent(&event); | |
337 } else if (window_state->IsMaximized()) { | |
338 window_state->Restore(); | |
339 } else if (window_state->IsNormalOrSnapped()) { | |
340 if (window_state->CanMaximize()) | |
341 window_state->Maximize(); | |
342 } | |
343 return true; | |
344 case WM_EVENT_TOGGLE_MAXIMIZE: | |
345 if (window_state->IsFullscreen()) { | |
346 const wm::WMEvent event(wm::WM_EVENT_TOGGLE_FULLSCREEN); | |
347 window_state->OnWMEvent(&event); | |
348 } else if (window_state->IsMaximized()) { | |
349 window_state->Restore(); | |
350 } else if (window_state->CanMaximize()) { | |
351 window_state->Maximize(); | |
352 } | |
353 return true; | |
354 case WM_EVENT_TOGGLE_VERTICAL_MAXIMIZE: { | |
355 gfx::Rect work_area = GetDisplayWorkAreaBoundsInParent(window); | |
356 | |
357 // Maximize vertically if: | |
358 // - The window does not have a max height defined. | |
359 // - The window has the normal state type. Snapped windows are excluded | |
360 // because they are already maximized vertically and reverting to the | |
361 // restored bounds looks weird. | |
362 if (window->GetMaximumSize().height() != 0 || | |
363 !window_state->IsNormalStateType()) { | |
364 return true; | |
365 } | |
366 if (window_state->HasRestoreBounds() && | |
367 (window->GetBounds().height() == work_area.height() && | |
368 window->GetBounds().y() == work_area.y())) { | |
369 window_state->SetAndClearRestoreBounds(); | |
370 } else { | |
371 window_state->SaveCurrentBoundsForRestore(); | |
372 window->SetBounds(gfx::Rect(window->GetBounds().x(), work_area.y(), | |
373 window->GetBounds().width(), | |
374 work_area.height())); | |
375 } | |
376 return true; | |
377 } | |
378 case WM_EVENT_TOGGLE_HORIZONTAL_MAXIMIZE: { | |
379 // Maximize horizontally if: | |
380 // - The window does not have a max width defined. | |
381 // - The window is snapped or has the normal state type. | |
382 if (window->GetMaximumSize().width() != 0) | |
383 return true; | |
384 if (!window_state->IsNormalOrSnapped()) | |
385 return true; | |
386 gfx::Rect work_area = GetDisplayWorkAreaBoundsInParent(window); | |
387 if (window_state->IsNormalStateType() && | |
388 window_state->HasRestoreBounds() && | |
389 (window->GetBounds().width() == work_area.width() && | |
390 window->GetBounds().x() == work_area.x())) { | |
391 window_state->SetAndClearRestoreBounds(); | |
392 } else { | |
393 gfx::Rect new_bounds(work_area.x(), window->GetBounds().y(), | |
394 work_area.width(), window->GetBounds().height()); | |
395 | |
396 gfx::Rect restore_bounds = window->GetBounds(); | |
397 if (window_state->IsSnapped()) { | |
398 window_state->SetRestoreBoundsInParent(new_bounds); | |
399 window_state->Restore(); | |
400 | |
401 // The restore logic prevents a window from being restored to bounds | |
402 // which match the workspace bounds exactly so it is necessary to set | |
403 // the bounds again below. | |
404 } | |
405 | |
406 window_state->SetRestoreBoundsInParent(restore_bounds); | |
407 window->SetBounds(new_bounds); | |
408 } | |
409 return true; | |
410 } | |
411 case WM_EVENT_TOGGLE_FULLSCREEN: | |
412 ToggleFullScreen(window_state, window_state->delegate()); | |
413 return true; | |
414 case WM_EVENT_CYCLE_SNAP_DOCK_LEFT: | |
415 case WM_EVENT_CYCLE_SNAP_DOCK_RIGHT: | |
416 if (ash::switches::DockedWindowsEnabled()) | |
417 CycleSnapDock(window_state, event->type()); | |
418 else | |
419 CycleSnap(window_state, event->type()); | |
420 return true; | |
421 case WM_EVENT_CENTER: | |
422 CenterWindow(window_state); | |
423 return true; | |
424 case WM_EVENT_NORMAL: | |
425 case WM_EVENT_MAXIMIZE: | |
426 case WM_EVENT_MINIMIZE: | |
427 case WM_EVENT_FULLSCREEN: | |
428 case WM_EVENT_PIN: | |
429 case WM_EVENT_TRUSTED_PIN: | |
430 case WM_EVENT_SNAP_LEFT: | |
431 case WM_EVENT_SNAP_RIGHT: | |
432 case WM_EVENT_SET_BOUNDS: | |
433 case WM_EVENT_SHOW_INACTIVE: | |
434 case WM_EVENT_DOCK: | |
435 break; | |
436 case WM_EVENT_ADDED_TO_WORKSPACE: | |
437 case WM_EVENT_WORKAREA_BOUNDS_CHANGED: | |
438 case WM_EVENT_DISPLAY_BOUNDS_CHANGED: | |
439 NOTREACHED() << "Workspace event should not reach here:" << event; | |
440 break; | |
441 } | |
442 return false; | |
443 } | |
444 | |
445 bool DefaultState::ProcessWorkspaceEvents(WindowState* window_state, | |
446 const WMEvent* event) { | |
447 switch (event->type()) { | |
448 case WM_EVENT_ADDED_TO_WORKSPACE: { | |
449 // When a window is dragged and dropped onto a different | |
450 // root window, the bounds will be updated after they are added | |
451 // to the root window. | |
452 // If a window is opened as maximized or fullscreen, its bounds may be | |
453 // empty, so update the bounds now before checking empty. | |
454 if (window_state->is_dragged() || | |
455 SetMaximizedOrFullscreenBounds(window_state)) { | |
456 return true; | |
457 } | |
458 | |
459 WmWindow* window = window_state->window(); | |
460 gfx::Rect bounds = window->GetBounds(); | |
461 | |
462 // Don't adjust window bounds if the bounds are empty as this | |
463 // happens when a new views::Widget is created. | |
464 if (bounds.IsEmpty()) | |
465 return true; | |
466 | |
467 // Only windows of type WINDOW_TYPE_NORMAL or WINDOW_TYPE_PANEL need to be | |
468 // adjusted to have minimum visibility, because they are positioned by the | |
469 // user and user should always be able to interact with them. Other | |
470 // windows are positioned programmatically. | |
471 if (!window_state->IsUserPositionable()) | |
472 return true; | |
473 | |
474 // Use entire display instead of workarea because the workarea can | |
475 // be further shrunk by the docked area. The logic ensures 30% | |
476 // visibility which should be enough to see where the window gets | |
477 // moved. | |
478 gfx::Rect display_area = GetDisplayBoundsInParent(window); | |
479 int min_width = bounds.width() * wm::kMinimumPercentOnScreenArea; | |
480 int min_height = bounds.height() * wm::kMinimumPercentOnScreenArea; | |
481 wm::AdjustBoundsToEnsureWindowVisibility(display_area, min_width, | |
482 min_height, &bounds); | |
483 window_state->AdjustSnappedBounds(&bounds); | |
484 if (window->GetBounds() != bounds) | |
485 window_state->SetBoundsConstrained(bounds); | |
486 return true; | |
487 } | |
488 case WM_EVENT_DISPLAY_BOUNDS_CHANGED: { | |
489 if (window_state->is_dragged() || | |
490 SetMaximizedOrFullscreenBounds(window_state)) { | |
491 return true; | |
492 } | |
493 gfx::Rect work_area_in_parent = | |
494 GetDisplayWorkAreaBoundsInParent(window_state->window()); | |
495 gfx::Rect bounds = window_state->window()->GetTargetBounds(); | |
496 // When display bounds has changed, make sure the entire window is fully | |
497 // visible. | |
498 bounds.AdjustToFit(work_area_in_parent); | |
499 window_state->AdjustSnappedBounds(&bounds); | |
500 if (window_state->window()->GetTargetBounds() != bounds) | |
501 window_state->SetBoundsDirectAnimated(bounds); | |
502 return true; | |
503 } | |
504 case WM_EVENT_WORKAREA_BOUNDS_CHANGED: { | |
505 // Don't resize the maximized window when the desktop is covered | |
506 // by fullscreen window. crbug.com/504299. | |
507 bool in_fullscreen = | |
508 window_state->window() | |
509 ->GetRootWindowController() | |
510 ->GetWorkspaceWindowState() == WORKSPACE_WINDOW_STATE_FULL_SCREEN; | |
511 if (in_fullscreen && window_state->IsMaximized()) | |
512 return true; | |
513 | |
514 if (window_state->is_dragged() || | |
515 SetMaximizedOrFullscreenBounds(window_state)) { | |
516 return true; | |
517 } | |
518 gfx::Rect work_area_in_parent = | |
519 GetDisplayWorkAreaBoundsInParent(window_state->window()); | |
520 gfx::Rect bounds = window_state->window()->GetTargetBounds(); | |
521 if (!window_state->window()->GetTransientParent()) { | |
522 wm::AdjustBoundsToEnsureMinimumWindowVisibility(work_area_in_parent, | |
523 &bounds); | |
524 } | |
525 window_state->AdjustSnappedBounds(&bounds); | |
526 if (window_state->window()->GetTargetBounds() != bounds) | |
527 window_state->SetBoundsDirectAnimated(bounds); | |
528 return true; | |
529 } | |
530 case WM_EVENT_TOGGLE_MAXIMIZE_CAPTION: | |
531 case WM_EVENT_TOGGLE_MAXIMIZE: | |
532 case WM_EVENT_TOGGLE_VERTICAL_MAXIMIZE: | |
533 case WM_EVENT_TOGGLE_HORIZONTAL_MAXIMIZE: | |
534 case WM_EVENT_TOGGLE_FULLSCREEN: | |
535 case WM_EVENT_CYCLE_SNAP_DOCK_LEFT: | |
536 case WM_EVENT_CYCLE_SNAP_DOCK_RIGHT: | |
537 case WM_EVENT_CENTER: | |
538 case WM_EVENT_NORMAL: | |
539 case WM_EVENT_MAXIMIZE: | |
540 case WM_EVENT_MINIMIZE: | |
541 case WM_EVENT_FULLSCREEN: | |
542 case WM_EVENT_PIN: | |
543 case WM_EVENT_TRUSTED_PIN: | |
544 case WM_EVENT_SNAP_LEFT: | |
545 case WM_EVENT_SNAP_RIGHT: | |
546 case WM_EVENT_SET_BOUNDS: | |
547 case WM_EVENT_SHOW_INACTIVE: | |
548 case WM_EVENT_DOCK: | |
549 break; | |
550 } | |
551 return false; | |
552 } | |
553 | |
554 // static | |
555 bool DefaultState::SetMaximizedOrFullscreenBounds(WindowState* window_state) { | |
556 DCHECK(!window_state->is_dragged()); | |
557 if (window_state->IsMaximized()) { | |
558 window_state->SetBoundsDirect( | |
559 GetMaximizedWindowBoundsInParent(window_state->window())); | |
560 return true; | |
561 } | |
562 if (window_state->IsFullscreen()) { | |
563 window_state->SetBoundsDirect( | |
564 GetDisplayBoundsInParent(window_state->window())); | |
565 return true; | |
566 } | |
567 return false; | |
568 } | |
569 | |
570 // static | |
571 void DefaultState::SetBounds(WindowState* window_state, | |
572 const SetBoundsEvent* event) { | |
573 if (window_state->is_dragged()) { | |
574 // TODO(oshima|varkha): This may be no longer needed, as the dragging | |
575 // happens in docked window container. crbug.com/485612. | |
576 window_state->SetBoundsDirect(event->requested_bounds()); | |
577 } else if (window_state->IsSnapped()) { | |
578 gfx::Rect work_area_in_parent = | |
579 GetDisplayWorkAreaBoundsInParent(window_state->window()); | |
580 gfx::Rect child_bounds(event->requested_bounds()); | |
581 wm::AdjustBoundsSmallerThan(work_area_in_parent.size(), &child_bounds); | |
582 window_state->AdjustSnappedBounds(&child_bounds); | |
583 window_state->SetBoundsDirect(child_bounds); | |
584 } else if (!SetMaximizedOrFullscreenBounds(window_state) || | |
585 window_state->allow_set_bounds_in_maximized()) { | |
586 window_state->SetBoundsConstrained(event->requested_bounds()); | |
587 } | |
588 } | |
589 | |
590 void DefaultState::EnterToNextState(WindowState* window_state, | |
591 WindowStateType next_state_type) { | |
592 // Do nothing if we're already in the same state. | |
593 if (state_type_ == next_state_type) | |
594 return; | |
595 | |
596 WindowStateType previous_state_type = state_type_; | |
597 state_type_ = next_state_type; | |
598 | |
599 window_state->UpdateWindowShowStateFromStateType(); | |
600 window_state->NotifyPreStateTypeChange(previous_state_type); | |
601 | |
602 if (window_state->window()->GetParent()) { | |
603 if (!window_state->HasRestoreBounds() && | |
604 (previous_state_type == WINDOW_STATE_TYPE_DEFAULT || | |
605 previous_state_type == WINDOW_STATE_TYPE_NORMAL) && | |
606 !window_state->IsMinimized() && !window_state->IsNormalStateType()) { | |
607 window_state->SaveCurrentBoundsForRestore(); | |
608 } | |
609 | |
610 // When restoring from a minimized state, we want to restore to the | |
611 // previous bounds. However, we want to maintain the restore bounds. | |
612 // (The restore bounds are set if a user maximized the window in one | |
613 // axis by double clicking the window border for example). | |
614 gfx::Rect restore_bounds_in_screen; | |
615 if (previous_state_type == WINDOW_STATE_TYPE_MINIMIZED && | |
616 window_state->IsNormalStateType() && window_state->HasRestoreBounds() && | |
617 !window_state->unminimize_to_restore_bounds()) { | |
618 restore_bounds_in_screen = window_state->GetRestoreBoundsInScreen(); | |
619 window_state->SaveCurrentBoundsForRestore(); | |
620 } | |
621 | |
622 if (window_state->IsMaximizedOrFullscreenOrPinned()) | |
623 MoveToDisplayForRestore(window_state); | |
624 | |
625 UpdateBoundsFromState(window_state, previous_state_type); | |
626 | |
627 // Normal state should have no restore bounds unless it's | |
628 // unminimized. | |
629 if (!restore_bounds_in_screen.IsEmpty()) | |
630 window_state->SetRestoreBoundsInScreen(restore_bounds_in_screen); | |
631 else if (window_state->IsNormalStateType()) | |
632 window_state->ClearRestoreBounds(); | |
633 } | |
634 window_state->NotifyPostStateTypeChange(previous_state_type); | |
635 | |
636 if (next_state_type == WINDOW_STATE_TYPE_PINNED || | |
637 previous_state_type == WINDOW_STATE_TYPE_PINNED || | |
638 next_state_type == WINDOW_STATE_TYPE_TRUSTED_PINNED || | |
639 previous_state_type == WINDOW_STATE_TYPE_TRUSTED_PINNED) { | |
640 WmShell::Get()->SetPinnedWindow(window_state->window()); | |
641 } | |
642 } | |
643 | |
644 void DefaultState::ReenterToCurrentState( | |
645 WindowState* window_state, | |
646 WindowState::State* state_in_previous_mode) { | |
647 WindowStateType previous_state_type = state_in_previous_mode->GetType(); | |
648 | |
649 // A state change should not move a window into or out of full screen or | |
650 // pinned since these are "special mode" the user wanted to be in and | |
651 // should be respected as such. | |
652 if (previous_state_type == wm::WINDOW_STATE_TYPE_FULLSCREEN || | |
653 previous_state_type == wm::WINDOW_STATE_TYPE_PINNED || | |
654 previous_state_type == wm::WINDOW_STATE_TYPE_TRUSTED_PINNED) { | |
655 state_type_ = previous_state_type; | |
656 } else if (state_type_ == wm::WINDOW_STATE_TYPE_FULLSCREEN || | |
657 state_type_ == wm::WINDOW_STATE_TYPE_PINNED || | |
658 state_type_ == wm::WINDOW_STATE_TYPE_TRUSTED_PINNED) { | |
659 state_type_ = previous_state_type; | |
660 } | |
661 | |
662 window_state->UpdateWindowShowStateFromStateType(); | |
663 window_state->NotifyPreStateTypeChange(previous_state_type); | |
664 | |
665 if ((state_type_ == wm::WINDOW_STATE_TYPE_NORMAL || | |
666 state_type_ == wm::WINDOW_STATE_TYPE_DEFAULT) && | |
667 !stored_bounds_.IsEmpty()) { | |
668 // Use the restore mechanism to set the bounds for | |
669 // the window in normal state. This also covers unminimize case. | |
670 window_state->SetRestoreBoundsInParent(stored_bounds_); | |
671 } | |
672 | |
673 UpdateBoundsFromState(window_state, state_in_previous_mode->GetType()); | |
674 | |
675 // Then restore the restore bounds to their previous value. | |
676 if (!stored_restore_bounds_.IsEmpty()) | |
677 window_state->SetRestoreBoundsInParent(stored_restore_bounds_); | |
678 else | |
679 window_state->ClearRestoreBounds(); | |
680 | |
681 window_state->NotifyPostStateTypeChange(previous_state_type); | |
682 } | |
683 | |
684 void DefaultState::UpdateBoundsFromState(WindowState* window_state, | |
685 WindowStateType previous_state_type) { | |
686 WmWindow* window = window_state->window(); | |
687 gfx::Rect bounds_in_parent; | |
688 switch (state_type_) { | |
689 case WINDOW_STATE_TYPE_LEFT_SNAPPED: | |
690 case WINDOW_STATE_TYPE_RIGHT_SNAPPED: | |
691 bounds_in_parent = | |
692 state_type_ == WINDOW_STATE_TYPE_LEFT_SNAPPED | |
693 ? GetDefaultLeftSnappedWindowBoundsInParent(window) | |
694 : GetDefaultRightSnappedWindowBoundsInParent(window); | |
695 break; | |
696 case WINDOW_STATE_TYPE_DOCKED: { | |
697 // TODO(afakhry): Remove in M58. | |
698 DCHECK(ash::switches::DockedWindowsEnabled()); | |
699 if (window->GetParent()->GetShellWindowId() != | |
700 kShellWindowId_DockedContainer) { | |
701 WmWindow* docked_container = | |
702 window->GetRootWindow()->GetChildByShellWindowId( | |
703 kShellWindowId_DockedContainer); | |
704 ReparentChildWithTransientChildren(window, window->GetParent(), | |
705 docked_container); | |
706 } | |
707 // Return early because we don't want to update the bounds of the | |
708 // window below; as the bounds are managed by the dock layout. | |
709 return; | |
710 } | |
711 case WINDOW_STATE_TYPE_DEFAULT: | |
712 case WINDOW_STATE_TYPE_NORMAL: { | |
713 gfx::Rect work_area_in_parent = GetDisplayWorkAreaBoundsInParent(window); | |
714 if (window_state->HasRestoreBounds()) { | |
715 bounds_in_parent = window_state->GetRestoreBoundsInParent(); | |
716 // Check if the |window|'s restored size is bigger than the working area | |
717 // This may happen if a window was resized to maximized bounds or if the | |
718 // display resolution changed while the window was maximized. | |
719 if (previous_state_type == WINDOW_STATE_TYPE_MAXIMIZED && | |
720 bounds_in_parent.width() >= work_area_in_parent.width() && | |
721 bounds_in_parent.height() >= work_area_in_parent.height()) { | |
722 bounds_in_parent = work_area_in_parent; | |
723 bounds_in_parent.Inset(kMaximizedWindowInset, kMaximizedWindowInset, | |
724 kMaximizedWindowInset, kMaximizedWindowInset); | |
725 } | |
726 } else { | |
727 bounds_in_parent = window->GetBounds(); | |
728 } | |
729 // Make sure that part of the window is always visible. | |
730 if (!window_state->is_dragged()) { | |
731 // Avoid doing this while the window is being dragged as its root | |
732 // window hasn't been updated yet in the case of dragging to another | |
733 // display. crbug.com/666836. | |
734 wm::AdjustBoundsToEnsureMinimumWindowVisibility(work_area_in_parent, | |
735 &bounds_in_parent); | |
736 } | |
737 break; | |
738 } | |
739 case WINDOW_STATE_TYPE_MAXIMIZED: | |
740 bounds_in_parent = GetMaximizedWindowBoundsInParent(window); | |
741 break; | |
742 | |
743 case WINDOW_STATE_TYPE_FULLSCREEN: | |
744 case WINDOW_STATE_TYPE_PINNED: | |
745 case WINDOW_STATE_TYPE_TRUSTED_PINNED: | |
746 bounds_in_parent = GetDisplayBoundsInParent(window); | |
747 break; | |
748 | |
749 case WINDOW_STATE_TYPE_DOCKED_MINIMIZED: | |
750 case WINDOW_STATE_TYPE_MINIMIZED: | |
751 break; | |
752 case WINDOW_STATE_TYPE_INACTIVE: | |
753 case WINDOW_STATE_TYPE_END: | |
754 case WINDOW_STATE_TYPE_AUTO_POSITIONED: | |
755 return; | |
756 } | |
757 | |
758 if (!window_state->IsMinimized()) { | |
759 if (IsMinimizedWindowState(previous_state_type) || | |
760 window_state->IsFullscreen() || window_state->IsPinned()) { | |
761 window_state->SetBoundsDirect(bounds_in_parent); | |
762 } else if (window_state->IsMaximized() || | |
763 IsMaximizedOrFullscreenOrPinnedWindowStateType( | |
764 previous_state_type)) { | |
765 window_state->SetBoundsDirectCrossFade(bounds_in_parent); | |
766 } else if (window_state->is_dragged()) { | |
767 // SetBoundsDirectAnimated does not work when the window gets reparented. | |
768 // TODO(oshima): Consider fixing it and reenable the animation. | |
769 window_state->SetBoundsDirect(bounds_in_parent); | |
770 } else { | |
771 window_state->SetBoundsDirectAnimated(bounds_in_parent); | |
772 } | |
773 } | |
774 | |
775 if (window_state->IsMinimized()) { | |
776 // Save the previous show state so that we can correctly restore it after | |
777 // exiting the minimized mode. | |
778 window->SetPreMinimizedShowState(ToWindowShowState(previous_state_type)); | |
779 window->SetVisibilityAnimationType( | |
780 WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE); | |
781 | |
782 // Hide the window. | |
783 window->Hide(); | |
784 // Activate another window. | |
785 if (window_state->IsActive()) | |
786 window_state->Deactivate(); | |
787 } else if ((window->GetTargetVisibility() || | |
788 IsMinimizedWindowState(previous_state_type)) && | |
789 !window->GetLayerVisible()) { | |
790 // The layer may be hidden if the window was previously minimized. Make | |
791 // sure it's visible. | |
792 window->Show(); | |
793 if (IsMinimizedWindowState(previous_state_type) && | |
794 !window_state->IsMaximizedOrFullscreenOrPinned()) { | |
795 window_state->set_unminimize_to_restore_bounds(false); | |
796 } | |
797 } | |
798 } | |
799 | |
800 // static | |
801 void DefaultState::CenterWindow(WindowState* window_state) { | |
802 if (!window_state->IsNormalOrSnapped()) | |
803 return; | |
804 WmWindow* window = window_state->window(); | |
805 if (window_state->IsSnapped()) { | |
806 gfx::Rect center_in_screen = window->GetDisplayNearestWindow().work_area(); | |
807 gfx::Size size = window_state->HasRestoreBounds() | |
808 ? window_state->GetRestoreBoundsInScreen().size() | |
809 : window->GetBounds().size(); | |
810 center_in_screen.ClampToCenteredSize(size); | |
811 window_state->SetRestoreBoundsInScreen(center_in_screen); | |
812 window_state->Restore(); | |
813 } else { | |
814 gfx::Rect center_in_parent = GetDisplayWorkAreaBoundsInParent(window); | |
815 center_in_parent.ClampToCenteredSize(window->GetBounds().size()); | |
816 window_state->SetBoundsDirectAnimated(center_in_parent); | |
817 } | |
818 // Centering window is treated as if a user moved and resized the window. | |
819 window_state->set_bounds_changed_by_user(true); | |
820 } | |
821 | |
822 } // namespace wm | |
823 } // namespace ash | |
OLD | NEW |