default_state.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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/wm/default_state.h"
6
7#include "ash/display/display_controller.h"
8#include "ash/screen_util.h"
9#include "ash/shell.h"
10#include "ash/shell_window_ids.h"
11#include "ash/wm/coordinate_conversion.h"
12#include "ash/wm/window_animations.h"
13#include "ash/wm/window_state.h"
14#include "ash/wm/window_state_delegate.h"
15#include "ash/wm/window_util.h"
16#include "ash/wm/workspace/workspace_window_resizer.h"
17#include "ui/aura/client/aura_constants.h"
18#include "ui/aura/window.h"
19#include "ui/aura/window_delegate.h"
20#include "ui/gfx/display.h"
21#include "ui/gfx/rect.h"
22
23namespace ash {
24namespace wm {
25namespace {
26
27bool IsPanel(aura::Window* window) {
28  return window->parent() &&
29      window->parent()->id() == internal::kShellWindowId_DockedContainer;
30}
31
32gfx::Rect BoundsWithScreenEdgeVisible(
33    aura::Window* window,
34    const gfx::Rect& restore_bounds) {
35  gfx::Rect max_bounds =
36      ash::ScreenUtil::GetMaximizedWindowBoundsInParent(window);
37  // If the restore_bounds are more than 1 grid step away from the size the
38  // window would be when maximized, inset it.
39  max_bounds.Inset(ash::internal::WorkspaceWindowResizer::kScreenEdgeInset,
40                   ash::internal::WorkspaceWindowResizer::kScreenEdgeInset);
41  if (restore_bounds.Contains(max_bounds))
42    return max_bounds;
43  return restore_bounds;
44}
45
46void MoveToDisplayForRestore(WindowState* window_state) {
47  if (!window_state->HasRestoreBounds())
48    return;
49  const gfx::Rect& restore_bounds = window_state->GetRestoreBoundsInScreen();
50
51  // Move only if the restore bounds is outside of
52  // the display. There is no information about in which
53  // display it should be restored, so this is best guess.
54  // TODO(oshima): Restore information should contain the
55  // work area information like WindowResizer does for the
56  // last window location.
57  gfx::Rect display_area = Shell::GetScreen()->GetDisplayNearestWindow(
58      window_state->window()).bounds();
59
60  if (!display_area.Intersects(restore_bounds)) {
61    const gfx::Display& display =
62        Shell::GetScreen()->GetDisplayMatching(restore_bounds);
63    DisplayController* display_controller =
64        Shell::GetInstance()->display_controller();
65    aura::Window* new_root =
66        display_controller->GetRootWindowForDisplayId(display.id());
67    if (new_root != window_state->window()->GetRootWindow()) {
68      aura::Window* new_container =
69          Shell::GetContainer(new_root, window_state->window()->parent()->id());
70      new_container->AddChild(window_state->window());
71    }
72  }
73}
74
75}  // namespace;
76
77DefaultState::DefaultState() {}
78DefaultState::~DefaultState() {}
79
80void DefaultState::OnWMEvent(WindowState* window_state,
81                             WMEvent event) {
82  if (ProcessCompoundEvents(window_state, event))
83    return;
84
85  WindowShowType next_show_type = SHOW_TYPE_NORMAL;
86  switch (event) {
87    case NORMAL:
88      next_show_type = SHOW_TYPE_NORMAL;
89      break;
90    case MAXIMIZE:
91      next_show_type = SHOW_TYPE_MAXIMIZED;
92      break;
93    case MINIMIZE:
94      next_show_type = SHOW_TYPE_MINIMIZED;
95      break;
96    case FULLSCREEN:
97      next_show_type = SHOW_TYPE_FULLSCREEN;
98      break;
99    case SNAP_LEFT:
100      next_show_type = SHOW_TYPE_LEFT_SNAPPED;
101      break;
102    case SNAP_RIGHT:
103      next_show_type = SHOW_TYPE_RIGHT_SNAPPED;
104      break;
105    case SHOW_INACTIVE:
106      next_show_type = SHOW_TYPE_INACTIVE;
107      break;
108    case TOGGLE_MAXIMIZE_CAPTION:
109    case TOGGLE_MAXIMIZE:
110    case TOGGLE_VERTICAL_MAXIMIZE:
111    case TOGGLE_HORIZONTAL_MAXIMIZE:
112    case TOGGLE_FULLSCREEN:
113      NOTREACHED() << "Compound event should not reach here:" << event;
114      return;
115  }
116
117  WindowShowType current = window_state->window_show_type();
118  if (current != next_show_type) {
119    window_state->UpdateWindowShowType(next_show_type);
120    window_state->NotifyPreShowTypeChange(current);
121    // TODO(oshima): Make docked window a state.
122    if (!window_state->IsDocked() && !IsPanel(window_state->window()))
123      UpdateBoundsFromShowType(window_state, current);
124    window_state->NotifyPostShowTypeChange(current);
125  }
126};
127
128// static
129bool DefaultState::ProcessCompoundEvents(WindowState* window_state,
130                                         WMEvent event) {
131  aura::Window* window = window_state->window();
132
133  switch (event) {
134    case TOGGLE_MAXIMIZE_CAPTION:
135      if (window_state->IsFullscreen()) {
136        window_state->ToggleFullscreen();
137      } else if (window_state->IsMaximized()) {
138        window_state->Restore();
139      } else if (window_state->IsNormalShowType() ||
140                 window_state->IsSnapped()) {
141        if (window_state->CanMaximize())
142          window_state->Maximize();
143      }
144      return true;
145    case TOGGLE_MAXIMIZE:
146      if (window_state->IsFullscreen())
147        window_state->ToggleFullscreen();
148      else if (window_state->IsMaximized())
149        window_state->Restore();
150      else if (window_state->CanMaximize())
151        window_state->Maximize();
152      return true;
153    case TOGGLE_VERTICAL_MAXIMIZE: {
154      gfx::Rect work_area =
155          ScreenUtil::GetDisplayWorkAreaBoundsInParent(window);
156
157      // Maximize vertically if:
158      // - The window does not have a max height defined.
159      // - The window has the normal show type. Snapped windows are excluded
160      //   because they are already maximized vertically and reverting to the
161      //   restored bounds looks weird.
162      if (window->delegate()->GetMaximumSize().height() != 0 ||
163          !window_state->IsNormalShowType()) {
164        return true;
165      }
166      if (window_state->HasRestoreBounds() &&
167          (window->bounds().height() == work_area.height() &&
168           window->bounds().y() == work_area.y())) {
169        window_state->SetAndClearRestoreBounds();
170      } else {
171        window_state->SaveCurrentBoundsForRestore();
172        window->SetBounds(gfx::Rect(window->bounds().x(),
173                                    work_area.y(),
174                                    window->bounds().width(),
175                                    work_area.height()));
176      }
177      return true;
178    }
179    case TOGGLE_HORIZONTAL_MAXIMIZE: {
180      // Maximize horizontally if:
181      // - The window does not have a max width defined.
182      // - The window is snapped or has the normal show type.
183      if (window->delegate()->GetMaximumSize().width() != 0)
184        return true;
185      if (!window_state->IsNormalShowType() && !window_state->IsSnapped())
186        return true;
187      gfx::Rect work_area =
188          ScreenUtil::GetDisplayWorkAreaBoundsInParent(window);
189      if (window_state->IsNormalShowType() &&
190          window_state->HasRestoreBounds() &&
191          (window->bounds().width() == work_area.width() &&
192           window->bounds().x() == work_area.x())) {
193        window_state->SetAndClearRestoreBounds();
194      } else {
195        gfx::Rect new_bounds(work_area.x(),
196                             window->bounds().y(),
197                             work_area.width(),
198                             window->bounds().height());
199
200        gfx::Rect restore_bounds = window->bounds();
201        if (window_state->IsSnapped()) {
202          window_state->SetRestoreBoundsInParent(new_bounds);
203          window_state->Restore();
204
205          // The restore logic prevents a window from being restored to bounds
206          // which match the workspace bounds exactly so it is necessary to set
207          // the bounds again below.
208        }
209
210        window_state->SetRestoreBoundsInParent(restore_bounds);
211        window->SetBounds(new_bounds);
212      }
213      return true;
214    }
215    case TOGGLE_FULLSCREEN: {
216      // Window which cannot be maximized should not be fullscreened.
217      // It can, however, be restored if it was fullscreened.
218      bool is_fullscreen = window_state->IsFullscreen();
219      if (!is_fullscreen && !window_state->CanMaximize())
220        return true;
221      if (window_state->delegate() &&
222          window_state->delegate()->ToggleFullscreen(window_state)) {
223        return true;
224      }
225      if (is_fullscreen) {
226        window_state->Restore();
227      } else {
228        //
229        window_state->window()->SetProperty(aura::client::kShowStateKey,
230                                            ui::SHOW_STATE_FULLSCREEN);
231      }
232      return true;
233    }
234    case NORMAL:
235    case MAXIMIZE:
236    case MINIMIZE:
237    case FULLSCREEN:
238    case SNAP_LEFT:
239    case SNAP_RIGHT:
240    case SHOW_INACTIVE:
241      break;
242  }
243  return false;
244}
245
246// static
247void DefaultState::UpdateBoundsFromShowType(WindowState* window_state,
248                                            WindowShowType old_show_type) {
249  aura::Window* window = window_state->window();
250  // Do nothing If this is not yet added to the container.
251  if (!window->parent())
252    return;
253
254  if (old_show_type != SHOW_TYPE_MINIMIZED &&
255      !window_state->HasRestoreBounds() &&
256      window_state->IsMaximizedOrFullscreen() &&
257      !IsMaximizedOrFullscreenWindowShowType(old_show_type)) {
258    window_state->SaveCurrentBoundsForRestore();
259  }
260
261  // When restoring from a minimized state, we want to restore to the previous
262  // bounds. However, we want to maintain the restore bounds. (The restore
263  // bounds are set if a user maximized the window in one axis by double
264  // clicking the window border for example).
265  gfx::Rect restore;
266  if (old_show_type == SHOW_TYPE_MINIMIZED &&
267      window_state->IsNormalShowState() &&
268      window_state->HasRestoreBounds() &&
269      !window_state->unminimize_to_restore_bounds()) {
270    restore = window_state->GetRestoreBoundsInScreen();
271    window_state->SaveCurrentBoundsForRestore();
272  }
273
274  if (window_state->IsMaximizedOrFullscreen())
275    MoveToDisplayForRestore(window_state);
276
277  WindowShowType show_type = window_state->window_show_type();
278  gfx::Rect bounds_in_parent;
279  switch (show_type) {
280    case SHOW_TYPE_DEFAULT:
281    case SHOW_TYPE_NORMAL:
282    case SHOW_TYPE_LEFT_SNAPPED:
283    case SHOW_TYPE_RIGHT_SNAPPED: {
284      gfx::Rect work_area_in_parent =
285          ScreenUtil::GetDisplayWorkAreaBoundsInParent(window_state->window());
286
287      if (window_state->HasRestoreBounds())
288        bounds_in_parent = window_state->GetRestoreBoundsInParent();
289      else
290        bounds_in_parent = window->bounds();
291      // Make sure that part of the window is always visible.
292      AdjustBoundsToEnsureMinimumWindowVisibility(
293          work_area_in_parent, &bounds_in_parent);
294
295      if (show_type == SHOW_TYPE_LEFT_SNAPPED ||
296          show_type == SHOW_TYPE_RIGHT_SNAPPED) {
297        window_state->AdjustSnappedBounds(&bounds_in_parent);
298      } else {
299        bounds_in_parent = BoundsWithScreenEdgeVisible(
300            window,
301            bounds_in_parent);
302      }
303      break;
304    }
305    case SHOW_TYPE_MAXIMIZED:
306      bounds_in_parent = ScreenUtil::GetMaximizedWindowBoundsInParent(window);
307      break;
308
309    case SHOW_TYPE_FULLSCREEN:
310      bounds_in_parent = ScreenUtil::GetDisplayBoundsInParent(window);
311      break;
312
313    case SHOW_TYPE_MINIMIZED:
314      break;
315    case SHOW_TYPE_INACTIVE:
316    case SHOW_TYPE_DETACHED:
317    case SHOW_TYPE_END:
318    case SHOW_TYPE_AUTO_POSITIONED:
319      return;
320  }
321
322  if (show_type != SHOW_TYPE_MINIMIZED) {
323    if (old_show_type == SHOW_TYPE_MINIMIZED ||
324        (window_state->IsFullscreen() &&
325         !window_state->animate_to_fullscreen())) {
326      window_state->SetBoundsDirect(bounds_in_parent);
327    } else if (window_state->IsMaximizedOrFullscreen() ||
328               IsMaximizedOrFullscreenWindowShowType(old_show_type)) {
329      CrossFadeToBounds(window, bounds_in_parent);
330    } else {
331      window_state->SetBoundsDirectAnimated(bounds_in_parent);
332    }
333  }
334
335  if (window_state->IsMinimized()) {
336    // Save the previous show state so that we can correctly restore it.
337    window_state->window()->SetProperty(aura::client::kRestoreShowStateKey,
338                                        ToWindowShowState(old_show_type));
339    views::corewm::SetWindowVisibilityAnimationType(
340        window_state->window(), WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE);
341
342    // Hide the window.
343    window_state->window()->Hide();
344    // Activate another window.
345    if (window_state->IsActive())
346      window_state->Deactivate();
347  } else if ((window_state->window()->TargetVisibility() ||
348              old_show_type == SHOW_TYPE_MINIMIZED) &&
349             !window_state->window()->layer()->visible()) {
350    // The layer may be hidden if the window was previously minimized. Make
351    // sure it's visible.
352    window_state->window()->Show();
353    if (old_show_type == SHOW_TYPE_MINIMIZED &&
354        !window_state->IsMaximizedOrFullscreen()) {
355      window_state->set_unminimize_to_restore_bounds(false);
356    }
357  }
358
359  if (window_state->IsNormalShowState())
360    window_state->ClearRestoreBounds();
361
362  // Set the restore rectangle to the previously set restore rectangle.
363  if (!restore.IsEmpty())
364    window_state->SetRestoreBoundsInScreen(restore);
365}
366
367}  // namespace wm
368}  // namespace ash
369