default_state.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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_state_util.h"
16#include "ash/wm/window_util.h"
17#include "ash/wm/wm_event.h"
18#include "ash/wm/workspace/workspace_window_resizer.h"
19#include "ui/aura/client/aura_constants.h"
20#include "ui/aura/window.h"
21#include "ui/aura/window_delegate.h"
22#include "ui/gfx/display.h"
23#include "ui/gfx/rect.h"
24
25namespace ash {
26namespace wm {
27namespace {
28
29// This specifies how much percent (30%) of a window rect
30// must be visible when the window is added to the workspace.
31const float kMinimumPercentOnScreenArea = 0.3f;
32
33bool IsPanel(aura::Window* window) {
34  return window->parent() &&
35         window->parent()->id() == kShellWindowId_PanelContainer;
36}
37
38void MoveToDisplayForRestore(WindowState* window_state) {
39  if (!window_state->HasRestoreBounds())
40    return;
41  const gfx::Rect& restore_bounds = window_state->GetRestoreBoundsInScreen();
42
43  // Move only if the restore bounds is outside of
44  // the display. There is no information about in which
45  // display it should be restored, so this is best guess.
46  // TODO(oshima): Restore information should contain the
47  // work area information like WindowResizer does for the
48  // last window location.
49  gfx::Rect display_area = Shell::GetScreen()->GetDisplayNearestWindow(
50      window_state->window()).bounds();
51
52  if (!display_area.Intersects(restore_bounds)) {
53    const gfx::Display& display =
54        Shell::GetScreen()->GetDisplayMatching(restore_bounds);
55    DisplayController* display_controller =
56        Shell::GetInstance()->display_controller();
57    aura::Window* new_root =
58        display_controller->GetRootWindowForDisplayId(display.id());
59    if (new_root != window_state->window()->GetRootWindow()) {
60      aura::Window* new_container =
61          Shell::GetContainer(new_root, window_state->window()->parent()->id());
62      new_container->AddChild(window_state->window());
63    }
64  }
65}
66
67}  // namespace;
68
69DefaultState::DefaultState(WindowStateType initial_state_type)
70    : state_type_(initial_state_type) {}
71DefaultState::~DefaultState() {}
72
73void DefaultState::OnWMEvent(WindowState* window_state,
74                             const WMEvent* event) {
75  if (ProcessWorkspaceEvents(window_state, event))
76    return;
77
78  if (ProcessCompoundEvents(window_state, event))
79    return;
80
81  WindowStateType next_state_type = WINDOW_STATE_TYPE_NORMAL;
82  switch (event->type()) {
83    case WM_EVENT_NORMAL:
84      next_state_type = WINDOW_STATE_TYPE_NORMAL;
85      break;
86    case WM_EVENT_MAXIMIZE:
87      next_state_type = WINDOW_STATE_TYPE_MAXIMIZED;
88      break;
89    case WM_EVENT_MINIMIZE:
90      next_state_type = WINDOW_STATE_TYPE_MINIMIZED;
91      break;
92    case WM_EVENT_FULLSCREEN:
93      next_state_type = WINDOW_STATE_TYPE_FULLSCREEN;
94      break;
95    case WM_EVENT_SNAP_LEFT:
96      next_state_type = WINDOW_STATE_TYPE_LEFT_SNAPPED;
97      break;
98    case WM_EVENT_SNAP_RIGHT:
99      next_state_type = WINDOW_STATE_TYPE_RIGHT_SNAPPED;
100      break;
101    case WM_EVENT_SET_BOUNDS:
102      SetBounds(window_state, static_cast<const SetBoundsEvent*>(event));
103      return;
104    case WM_EVENT_SHOW_INACTIVE:
105      next_state_type = WINDOW_STATE_TYPE_INACTIVE;
106      break;
107    case WM_EVENT_TOGGLE_MAXIMIZE_CAPTION:
108    case WM_EVENT_TOGGLE_MAXIMIZE:
109    case WM_EVENT_TOGGLE_VERTICAL_MAXIMIZE:
110    case WM_EVENT_TOGGLE_HORIZONTAL_MAXIMIZE:
111    case WM_EVENT_TOGGLE_FULLSCREEN:
112    case WM_EVENT_CENTER:
113      NOTREACHED() << "Compound event should not reach here:" << event;
114      return;
115    case WM_EVENT_ADDED_TO_WORKSPACE:
116    case WM_EVENT_WORKAREA_BOUNDS_CHANGED:
117    case WM_EVENT_DISPLAY_BOUNDS_CHANGED:
118      NOTREACHED() << "Workspace event should not reach here:" << event;
119      return;
120  }
121
122  WindowStateType current = window_state->GetStateType();
123
124  if (next_state_type == current && window_state->IsSnapped()) {
125    gfx::Rect snapped_bounds = event->type() == WM_EVENT_SNAP_LEFT ?
126        GetDefaultLeftSnappedWindowBoundsInParent(window_state->window()) :
127        GetDefaultRightSnappedWindowBoundsInParent(window_state->window());
128    window_state->SetBoundsDirectAnimated(snapped_bounds);
129    return;
130  }
131
132  EnterToNextState(window_state, next_state_type);
133}
134
135WindowStateType DefaultState::GetType() const {
136  return state_type_;
137}
138
139void DefaultState::AttachState(
140    WindowState* window_state,
141    WindowState::State* state_in_previous_mode) {
142  DCHECK_EQ(stored_window_state_, window_state);
143
144  ReenterToCurrentState(window_state, state_in_previous_mode);
145
146  // If the display has changed while in the another mode,
147  // we need to let windows know the change.
148  gfx::Display current_display = Shell::GetScreen()->
149      GetDisplayNearestWindow(window_state->window());
150  if (stored_display_state_.bounds() != current_display.bounds()) {
151    const WMEvent event(wm::WM_EVENT_DISPLAY_BOUNDS_CHANGED);
152    window_state->OnWMEvent(&event);
153  } else if (stored_display_state_.work_area() != current_display.work_area()) {
154    const WMEvent event(wm::WM_EVENT_WORKAREA_BOUNDS_CHANGED);
155    window_state->OnWMEvent(&event);
156  }
157}
158
159void DefaultState::DetachState(WindowState* window_state) {
160  stored_window_state_ = window_state;
161  aura::Window* window = window_state->window();
162  stored_bounds_ = window->bounds();
163  stored_restore_bounds_ = window_state->HasRestoreBounds() ?
164      window_state->GetRestoreBoundsInParent() : gfx::Rect();
165  // Remember the display state so that in case of the display change
166  // while in the other mode, we can perform necessary action to
167  // restore the window state to the proper state for the current
168  // display.
169  stored_display_state_ = Shell::GetScreen()->
170      GetDisplayNearestWindow(window_state->window());
171}
172
173// static
174bool DefaultState::ProcessCompoundEvents(WindowState* window_state,
175                                         const WMEvent* event) {
176  aura::Window* window = window_state->window();
177
178  switch (event->type()) {
179    case WM_EVENT_TOGGLE_MAXIMIZE_CAPTION:
180      if (window_state->IsFullscreen()) {
181        const wm::WMEvent event(wm::WM_EVENT_TOGGLE_FULLSCREEN);
182        window_state->OnWMEvent(&event);
183      } else if (window_state->IsMaximized()) {
184        window_state->Restore();
185      } else if (window_state->IsNormalOrSnapped()) {
186        if (window_state->CanMaximize())
187          window_state->Maximize();
188      }
189      return true;
190    case WM_EVENT_TOGGLE_MAXIMIZE:
191      if (window_state->IsFullscreen()) {
192        const wm::WMEvent event(wm::WM_EVENT_TOGGLE_FULLSCREEN);
193        window_state->OnWMEvent(&event);
194      } else if (window_state->IsMaximized()) {
195        window_state->Restore();
196      } else if (window_state->CanMaximize()) {
197        window_state->Maximize();
198      }
199      return true;
200    case WM_EVENT_TOGGLE_VERTICAL_MAXIMIZE: {
201      gfx::Rect work_area =
202          ScreenUtil::GetDisplayWorkAreaBoundsInParent(window);
203
204      // Maximize vertically if:
205      // - The window does not have a max height defined.
206      // - The window has the normal state type. Snapped windows are excluded
207      //   because they are already maximized vertically and reverting to the
208      //   restored bounds looks weird.
209      if (window->delegate()->GetMaximumSize().height() != 0 ||
210          !window_state->IsNormalStateType()) {
211        return true;
212      }
213      if (window_state->HasRestoreBounds() &&
214          (window->bounds().height() == work_area.height() &&
215           window->bounds().y() == work_area.y())) {
216        window_state->SetAndClearRestoreBounds();
217      } else {
218        window_state->SaveCurrentBoundsForRestore();
219        window->SetBounds(gfx::Rect(window->bounds().x(),
220                                    work_area.y(),
221                                    window->bounds().width(),
222                                    work_area.height()));
223      }
224      return true;
225    }
226    case WM_EVENT_TOGGLE_HORIZONTAL_MAXIMIZE: {
227      // Maximize horizontally if:
228      // - The window does not have a max width defined.
229      // - The window is snapped or has the normal state type.
230      if (window->delegate()->GetMaximumSize().width() != 0)
231        return true;
232      if (!window_state->IsNormalOrSnapped())
233        return true;
234      gfx::Rect work_area =
235          ScreenUtil::GetDisplayWorkAreaBoundsInParent(window);
236      if (window_state->IsNormalStateType() &&
237          window_state->HasRestoreBounds() &&
238          (window->bounds().width() == work_area.width() &&
239           window->bounds().x() == work_area.x())) {
240        window_state->SetAndClearRestoreBounds();
241      } else {
242        gfx::Rect new_bounds(work_area.x(),
243                             window->bounds().y(),
244                             work_area.width(),
245                             window->bounds().height());
246
247        gfx::Rect restore_bounds = window->bounds();
248        if (window_state->IsSnapped()) {
249          window_state->SetRestoreBoundsInParent(new_bounds);
250          window_state->Restore();
251
252          // The restore logic prevents a window from being restored to bounds
253          // which match the workspace bounds exactly so it is necessary to set
254          // the bounds again below.
255        }
256
257        window_state->SetRestoreBoundsInParent(restore_bounds);
258        window->SetBounds(new_bounds);
259      }
260      return true;
261    }
262    case WM_EVENT_TOGGLE_FULLSCREEN:
263      ToggleFullScreen(window_state, window_state->delegate());
264      return true;
265    case WM_EVENT_CENTER:
266      CenterWindow(window_state);
267      return true;
268    case WM_EVENT_NORMAL:
269    case WM_EVENT_MAXIMIZE:
270    case WM_EVENT_MINIMIZE:
271    case WM_EVENT_FULLSCREEN:
272    case WM_EVENT_SNAP_LEFT:
273    case WM_EVENT_SNAP_RIGHT:
274    case WM_EVENT_SET_BOUNDS:
275    case WM_EVENT_SHOW_INACTIVE:
276      break;
277    case WM_EVENT_ADDED_TO_WORKSPACE:
278    case WM_EVENT_WORKAREA_BOUNDS_CHANGED:
279    case WM_EVENT_DISPLAY_BOUNDS_CHANGED:
280      NOTREACHED() << "Workspace event should not reach here:" << event;
281      break;
282  }
283  return false;
284}
285
286bool DefaultState::ProcessWorkspaceEvents(WindowState* window_state,
287                                          const WMEvent* event) {
288  switch (event->type()) {
289    case WM_EVENT_ADDED_TO_WORKSPACE: {
290      // When a window is dragged and dropped onto a different
291      // root window, the bounds will be updated after they are added
292      // to the root window.
293      // If a window is opened as maximized or fullscreen, its bounds may be
294      // empty, so update the bounds now before checking empty.
295      if (window_state->is_dragged() ||
296          SetMaximizedOrFullscreenBounds(window_state)) {
297        return true;
298      }
299
300      aura::Window* window = window_state->window();
301      gfx::Rect bounds = window->bounds();
302
303      // Don't adjust window bounds if the bounds are empty as this
304      // happens when a new views::Widget is created.
305      if (bounds.IsEmpty())
306        return true;
307
308      // Use entire display instead of workarea because the workarea can
309      // be further shrunk by the docked area. The logic ensures 30%
310      // visibility which should be enough to see where the window gets
311      // moved.
312      gfx::Rect display_area = ScreenUtil::GetDisplayBoundsInParent(window);
313      int min_width = bounds.width() * kMinimumPercentOnScreenArea;
314      int min_height = bounds.height() * kMinimumPercentOnScreenArea;
315      AdjustBoundsToEnsureWindowVisibility(
316          display_area, min_width, min_height, &bounds);
317      window_state->AdjustSnappedBounds(&bounds);
318      if (window->bounds() != bounds)
319        window_state->SetBoundsConstrained(bounds);
320      return true;
321    }
322    case WM_EVENT_DISPLAY_BOUNDS_CHANGED: {
323      if (window_state->is_dragged() ||
324          SetMaximizedOrFullscreenBounds(window_state)) {
325        return true;
326      }
327      gfx::Rect work_area_in_parent =
328          ScreenUtil::GetDisplayWorkAreaBoundsInParent(window_state->window());
329      gfx::Rect bounds = window_state->window()->bounds();
330      // When display bounds has changed, make sure the entire window is fully
331      // visible.
332      bounds.AdjustToFit(work_area_in_parent);
333      window_state->AdjustSnappedBounds(&bounds);
334      if (window_state->window()->bounds() != bounds)
335        window_state->SetBoundsDirectAnimated(bounds);
336      return true;
337    }
338    case WM_EVENT_WORKAREA_BOUNDS_CHANGED: {
339      if (window_state->is_dragged() ||
340          SetMaximizedOrFullscreenBounds(window_state)) {
341        return true;
342      }
343      gfx::Rect work_area_in_parent =
344          ScreenUtil::GetDisplayWorkAreaBoundsInParent(window_state->window());
345      gfx::Rect bounds = window_state->window()->bounds();
346      AdjustBoundsToEnsureMinimumWindowVisibility(work_area_in_parent, &bounds);
347      window_state->AdjustSnappedBounds(&bounds);
348      if (window_state->window()->bounds() != bounds)
349        window_state->SetBoundsDirectAnimated(bounds);
350      return true;
351    }
352    case WM_EVENT_TOGGLE_MAXIMIZE_CAPTION:
353    case WM_EVENT_TOGGLE_MAXIMIZE:
354    case WM_EVENT_TOGGLE_VERTICAL_MAXIMIZE:
355    case WM_EVENT_TOGGLE_HORIZONTAL_MAXIMIZE:
356    case WM_EVENT_TOGGLE_FULLSCREEN:
357    case WM_EVENT_CENTER:
358    case WM_EVENT_NORMAL:
359    case WM_EVENT_MAXIMIZE:
360    case WM_EVENT_MINIMIZE:
361    case WM_EVENT_FULLSCREEN:
362    case WM_EVENT_SNAP_LEFT:
363    case WM_EVENT_SNAP_RIGHT:
364    case WM_EVENT_SET_BOUNDS:
365    case WM_EVENT_SHOW_INACTIVE:
366      break;
367  }
368  return false;
369}
370
371// static
372bool DefaultState::SetMaximizedOrFullscreenBounds(WindowState* window_state) {
373  DCHECK(!window_state->is_dragged());
374  if (window_state->IsMaximized()) {
375    window_state->SetBoundsDirect(
376        ScreenUtil::GetMaximizedWindowBoundsInParent(window_state->window()));
377    return true;
378  }
379  if (window_state->IsFullscreen()) {
380    window_state->SetBoundsDirect(
381        ScreenUtil::GetDisplayBoundsInParent(window_state->window()));
382    return true;
383  }
384  return false;
385}
386
387// static
388void DefaultState::SetBounds(WindowState* window_state,
389                             const SetBoundsEvent* event) {
390  if (window_state->is_dragged()) {
391    window_state->SetBoundsDirect(event->requested_bounds());
392  } else if (window_state->IsSnapped()) {
393    gfx::Rect work_area_in_parent =
394        ScreenUtil::GetDisplayWorkAreaBoundsInParent(window_state->window());
395    gfx::Rect child_bounds(event->requested_bounds());
396    AdjustBoundsSmallerThan(work_area_in_parent.size(), &child_bounds);
397    window_state->AdjustSnappedBounds(&child_bounds);
398    window_state->SetBoundsDirect(child_bounds);
399  } else if (!SetMaximizedOrFullscreenBounds(window_state)) {
400    window_state->SetBoundsConstrained(event->requested_bounds());
401  }
402}
403
404void DefaultState::EnterToNextState(WindowState* window_state,
405                                    WindowStateType next_state_type) {
406  // Do nothing if  we're already in the same state.
407  if (state_type_ == next_state_type)
408    return;
409
410  WindowStateType previous_state_type = state_type_;
411  state_type_ = next_state_type;
412
413  window_state->UpdateWindowShowStateFromStateType();
414  window_state->NotifyPreStateTypeChange(previous_state_type);
415
416  // This Docked/Snapped hack is due to the issue that IsDocked returns
417  // true for dragging window.  TODO(oshima): Make docked window a state
418  // and remove this hack.
419  if (window_state->window()->parent() &&
420      (window_state->IsSnapped() ||
421       (!window_state->IsDocked() && !IsPanel(window_state->window())))) {
422    if (!window_state->HasRestoreBounds() &&
423        (previous_state_type == WINDOW_STATE_TYPE_DEFAULT ||
424         previous_state_type == WINDOW_STATE_TYPE_NORMAL) &&
425        !window_state->IsMinimized() &&
426        !window_state->IsNormalStateType()) {
427      window_state->SaveCurrentBoundsForRestore();
428    }
429
430    // When restoring from a minimized state, we want to restore to the previous
431    // bounds. However, we want to maintain the restore bounds. (The restore
432    // bounds are set if a user maximized the window in one axis by double
433    // clicking the window border for example).
434    gfx::Rect restore_bounds_in_screen;
435    if (previous_state_type == WINDOW_STATE_TYPE_MINIMIZED &&
436        window_state->IsNormalStateType() &&
437        window_state->HasRestoreBounds() &&
438        !window_state->unminimize_to_restore_bounds()) {
439      restore_bounds_in_screen = window_state->GetRestoreBoundsInScreen();
440      window_state->SaveCurrentBoundsForRestore();
441    }
442
443    if (window_state->IsMaximizedOrFullscreen())
444      MoveToDisplayForRestore(window_state);
445
446    UpdateBoundsFromState(window_state, previous_state_type);
447
448    // Normal state should have no restore bounds unless it's
449    // unminimzied.
450    if (!restore_bounds_in_screen.IsEmpty())
451      window_state->SetRestoreBoundsInScreen(restore_bounds_in_screen);
452    else if (window_state->IsNormalStateType())
453      window_state->ClearRestoreBounds();
454  }
455  window_state->NotifyPostStateTypeChange(previous_state_type);
456}
457
458void DefaultState::ReenterToCurrentState(
459    WindowState* window_state,
460    WindowState::State* state_in_previous_mode) {
461  WindowStateType previous_state_type = state_in_previous_mode->GetType();
462  window_state->UpdateWindowShowStateFromStateType();
463  window_state->NotifyPreStateTypeChange(previous_state_type);
464
465  if ((state_type_ == wm::WINDOW_STATE_TYPE_NORMAL ||
466       state_type_ == wm::WINDOW_STATE_TYPE_DEFAULT) &&
467      !stored_bounds_.IsEmpty()) {
468    // Use the restore mechanism to set the bounds for
469    // the window in normal state. This also covers unminimize case.
470    window_state->SetRestoreBoundsInParent(stored_bounds_);
471  }
472
473  UpdateBoundsFromState(window_state, state_in_previous_mode->GetType());
474
475  // Then restore the restore bounds to their previous value.
476  if (!stored_restore_bounds_.IsEmpty())
477    window_state->SetRestoreBoundsInParent(stored_restore_bounds_);
478  else
479    window_state->ClearRestoreBounds();
480
481  window_state->NotifyPostStateTypeChange(previous_state_type);
482}
483
484void DefaultState::UpdateBoundsFromState(WindowState* window_state,
485                                         WindowStateType previous_state_type) {
486  aura::Window* window = window_state->window();
487  gfx::Rect bounds_in_parent;
488  switch (state_type_) {
489    case WINDOW_STATE_TYPE_LEFT_SNAPPED:
490    case WINDOW_STATE_TYPE_RIGHT_SNAPPED:
491      bounds_in_parent = state_type_ == WINDOW_STATE_TYPE_LEFT_SNAPPED ?
492          GetDefaultLeftSnappedWindowBoundsInParent(window_state->window()) :
493          GetDefaultRightSnappedWindowBoundsInParent(window_state->window());
494      break;
495    case WINDOW_STATE_TYPE_DEFAULT:
496    case WINDOW_STATE_TYPE_NORMAL: {
497      gfx::Rect work_area_in_parent =
498          ScreenUtil::GetDisplayWorkAreaBoundsInParent(window_state->window());
499      if (window_state->HasRestoreBounds())
500        bounds_in_parent = window_state->GetRestoreBoundsInParent();
501      else
502        bounds_in_parent = window->bounds();
503      // Make sure that part of the window is always visible.
504      AdjustBoundsToEnsureMinimumWindowVisibility(
505          work_area_in_parent, &bounds_in_parent);
506      break;
507    }
508    case WINDOW_STATE_TYPE_MAXIMIZED:
509      bounds_in_parent = ScreenUtil::GetMaximizedWindowBoundsInParent(window);
510      break;
511
512    case WINDOW_STATE_TYPE_FULLSCREEN:
513      bounds_in_parent = ScreenUtil::GetDisplayBoundsInParent(window);
514      break;
515
516    case WINDOW_STATE_TYPE_MINIMIZED:
517      break;
518    case WINDOW_STATE_TYPE_INACTIVE:
519    case WINDOW_STATE_TYPE_DETACHED:
520    case WINDOW_STATE_TYPE_END:
521    case WINDOW_STATE_TYPE_AUTO_POSITIONED:
522      return;
523  }
524
525  if (state_type_ != WINDOW_STATE_TYPE_MINIMIZED) {
526    if (previous_state_type == WINDOW_STATE_TYPE_MINIMIZED ||
527        window_state->IsFullscreen()) {
528      window_state->SetBoundsDirect(bounds_in_parent);
529    } else if (window_state->IsMaximized() ||
530               IsMaximizedOrFullscreenWindowStateType(previous_state_type)) {
531      window_state->SetBoundsDirectCrossFade(bounds_in_parent);
532    } else if (window_state->is_dragged()) {
533      // SetBoundsDirectAnimated does not work when the window gets reparented.
534      // TODO(oshima): Consider fixing it and reenable the animation.
535      window_state->SetBoundsDirect(bounds_in_parent);
536    } else {
537      window_state->SetBoundsDirectAnimated(bounds_in_parent);
538    }
539  }
540
541  if (window_state->IsMinimized()) {
542    // Save the previous show state so that we can correctly restore it.
543    window_state->window()->SetProperty(aura::client::kRestoreShowStateKey,
544                                        ToWindowShowState(previous_state_type));
545    ::wm::SetWindowVisibilityAnimationType(
546        window_state->window(), WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE);
547
548    // Hide the window.
549    window_state->window()->Hide();
550    // Activate another window.
551    if (window_state->IsActive())
552      window_state->Deactivate();
553  } else if ((window_state->window()->TargetVisibility() ||
554              previous_state_type == WINDOW_STATE_TYPE_MINIMIZED) &&
555             !window_state->window()->layer()->visible()) {
556    // The layer may be hidden if the window was previously minimized. Make
557    // sure it's visible.
558    window_state->window()->Show();
559    if (previous_state_type == WINDOW_STATE_TYPE_MINIMIZED &&
560        !window_state->IsMaximizedOrFullscreen()) {
561      window_state->set_unminimize_to_restore_bounds(false);
562    }
563  }
564}
565
566// static
567void DefaultState::CenterWindow(WindowState* window_state) {
568  if (!window_state->IsNormalOrSnapped())
569    return;
570  aura::Window* window = window_state->window();
571  if (window_state->IsSnapped()) {
572    gfx::Rect center_in_screen =
573        Shell::GetScreen()->GetDisplayNearestWindow(window).work_area();
574    gfx::Size size = window_state->HasRestoreBounds() ?
575        window_state->GetRestoreBoundsInScreen().size() :
576        window->bounds().size();
577    center_in_screen.ClampToCenteredSize(size);
578    window_state->SetRestoreBoundsInScreen(center_in_screen);
579    window_state->Restore();
580  } else {
581    gfx::Rect center_in_parent =
582        ScreenUtil::GetDisplayWorkAreaBoundsInParent(window);
583    center_in_parent.ClampToCenteredSize(window->bounds().size());
584    window_state->SetBoundsDirectAnimated(center_in_parent);
585  }
586  // Centering window is treated as if a user moved and resized the window.
587  window_state->set_bounds_changed_by_user(true);
588}
589
590}  // namespace wm
591}  // namespace ash
592