default_state.cc revision 010d83a9304c5a91596085d917d248abff47903a
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  if (previous_state_type == wm::WINDOW_STATE_TYPE_FULLSCREEN) {
463    // A state change should not move a window out of full screen since full
464    // screen is a "special mode" the user wanted to be in and should be
465    // respected as such.
466    state_type_ = wm::WINDOW_STATE_TYPE_FULLSCREEN;
467  }
468  window_state->UpdateWindowShowStateFromStateType();
469  window_state->NotifyPreStateTypeChange(previous_state_type);
470
471  if ((state_type_ == wm::WINDOW_STATE_TYPE_NORMAL ||
472       state_type_ == wm::WINDOW_STATE_TYPE_DEFAULT) &&
473      !stored_bounds_.IsEmpty()) {
474    // Use the restore mechanism to set the bounds for
475    // the window in normal state. This also covers unminimize case.
476    window_state->SetRestoreBoundsInParent(stored_bounds_);
477  }
478
479  UpdateBoundsFromState(window_state, state_in_previous_mode->GetType());
480
481  // Then restore the restore bounds to their previous value.
482  if (!stored_restore_bounds_.IsEmpty())
483    window_state->SetRestoreBoundsInParent(stored_restore_bounds_);
484  else
485    window_state->ClearRestoreBounds();
486
487  window_state->NotifyPostStateTypeChange(previous_state_type);
488}
489
490void DefaultState::UpdateBoundsFromState(WindowState* window_state,
491                                         WindowStateType previous_state_type) {
492  aura::Window* window = window_state->window();
493  gfx::Rect bounds_in_parent;
494  switch (state_type_) {
495    case WINDOW_STATE_TYPE_LEFT_SNAPPED:
496    case WINDOW_STATE_TYPE_RIGHT_SNAPPED:
497      bounds_in_parent = state_type_ == WINDOW_STATE_TYPE_LEFT_SNAPPED ?
498          GetDefaultLeftSnappedWindowBoundsInParent(window_state->window()) :
499          GetDefaultRightSnappedWindowBoundsInParent(window_state->window());
500      break;
501    case WINDOW_STATE_TYPE_DEFAULT:
502    case WINDOW_STATE_TYPE_NORMAL: {
503      gfx::Rect work_area_in_parent =
504          ScreenUtil::GetDisplayWorkAreaBoundsInParent(window_state->window());
505      if (window_state->HasRestoreBounds())
506        bounds_in_parent = window_state->GetRestoreBoundsInParent();
507      else
508        bounds_in_parent = window->bounds();
509      // Make sure that part of the window is always visible.
510      AdjustBoundsToEnsureMinimumWindowVisibility(
511          work_area_in_parent, &bounds_in_parent);
512      break;
513    }
514    case WINDOW_STATE_TYPE_MAXIMIZED:
515      bounds_in_parent = ScreenUtil::GetMaximizedWindowBoundsInParent(window);
516      break;
517
518    case WINDOW_STATE_TYPE_FULLSCREEN:
519      bounds_in_parent = ScreenUtil::GetDisplayBoundsInParent(window);
520      break;
521
522    case WINDOW_STATE_TYPE_MINIMIZED:
523      break;
524    case WINDOW_STATE_TYPE_INACTIVE:
525    case WINDOW_STATE_TYPE_DETACHED:
526    case WINDOW_STATE_TYPE_END:
527    case WINDOW_STATE_TYPE_AUTO_POSITIONED:
528      return;
529  }
530
531  if (state_type_ != WINDOW_STATE_TYPE_MINIMIZED) {
532    if (previous_state_type == WINDOW_STATE_TYPE_MINIMIZED ||
533        window_state->IsFullscreen()) {
534      window_state->SetBoundsDirect(bounds_in_parent);
535    } else if (window_state->IsMaximized() ||
536               IsMaximizedOrFullscreenWindowStateType(previous_state_type)) {
537      window_state->SetBoundsDirectCrossFade(bounds_in_parent);
538    } else if (window_state->is_dragged()) {
539      // SetBoundsDirectAnimated does not work when the window gets reparented.
540      // TODO(oshima): Consider fixing it and reenable the animation.
541      window_state->SetBoundsDirect(bounds_in_parent);
542    } else {
543      window_state->SetBoundsDirectAnimated(bounds_in_parent);
544    }
545  }
546
547  if (window_state->IsMinimized()) {
548    // Save the previous show state so that we can correctly restore it.
549    window_state->window()->SetProperty(aura::client::kRestoreShowStateKey,
550                                        ToWindowShowState(previous_state_type));
551    ::wm::SetWindowVisibilityAnimationType(
552        window_state->window(), WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE);
553
554    // Hide the window.
555    window_state->window()->Hide();
556    // Activate another window.
557    if (window_state->IsActive())
558      window_state->Deactivate();
559  } else if ((window_state->window()->TargetVisibility() ||
560              previous_state_type == WINDOW_STATE_TYPE_MINIMIZED) &&
561             !window_state->window()->layer()->visible()) {
562    // The layer may be hidden if the window was previously minimized. Make
563    // sure it's visible.
564    window_state->window()->Show();
565    if (previous_state_type == WINDOW_STATE_TYPE_MINIMIZED &&
566        !window_state->IsMaximizedOrFullscreen()) {
567      window_state->set_unminimize_to_restore_bounds(false);
568    }
569  }
570}
571
572// static
573void DefaultState::CenterWindow(WindowState* window_state) {
574  if (!window_state->IsNormalOrSnapped())
575    return;
576  aura::Window* window = window_state->window();
577  if (window_state->IsSnapped()) {
578    gfx::Rect center_in_screen =
579        Shell::GetScreen()->GetDisplayNearestWindow(window).work_area();
580    gfx::Size size = window_state->HasRestoreBounds() ?
581        window_state->GetRestoreBoundsInScreen().size() :
582        window->bounds().size();
583    center_in_screen.ClampToCenteredSize(size);
584    window_state->SetRestoreBoundsInScreen(center_in_screen);
585    window_state->Restore();
586  } else {
587    gfx::Rect center_in_parent =
588        ScreenUtil::GetDisplayWorkAreaBoundsInParent(window);
589    center_in_parent.ClampToCenteredSize(window->bounds().size());
590    window_state->SetBoundsDirectAnimated(center_in_parent);
591  }
592  // Centering window is treated as if a user moved and resized the window.
593  window_state->set_bounds_changed_by_user(true);
594}
595
596}  // namespace wm
597}  // namespace ash
598