blob: 2b5a5893d21d621e2cbe1f67c7383667f6d6b197 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/common/wm/default_state.h"
#include "ash/common/wm/dock/docked_window_layout_manager.h"
#include "ash/common/wm/window_animation_types.h"
#include "ash/common/wm/window_parenting_utils.h"
#include "ash/common/wm/window_positioning_utils.h"
#include "ash/common/wm/window_state.h"
#include "ash/common/wm/window_state_delegate.h"
#include "ash/common/wm/window_state_util.h"
#include "ash/common/wm/wm_event.h"
#include "ash/common/wm/wm_screen_util.h"
#include "ash/common/wm_root_window_controller.h"
#include "ash/common/wm_shell.h"
#include "ash/common/wm_window.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
namespace ash {
namespace wm {
namespace {
// This specifies how much percent (30%) of a window rect
// must be visible when the window is added to the workspace.
const float kMinimumPercentOnScreenArea = 0.3f;
// When a window that has restore bounds at least as large as a work area is
// unmaximized, inset the bounds slightly so that they are not exactly the same.
// This makes it easier to resize the window.
const int kMaximizedWindowInset = 10; // DIPs.
bool IsMinimizedWindowState(const WindowStateType state_type) {
return state_type == WINDOW_STATE_TYPE_MINIMIZED ||
state_type == WINDOW_STATE_TYPE_DOCKED_MINIMIZED;
}
void MoveToDisplayForRestore(WindowState* window_state) {
if (!window_state->HasRestoreBounds())
return;
const gfx::Rect restore_bounds = window_state->GetRestoreBoundsInScreen();
// Move only if the restore bounds is outside of
// the display. There is no information about in which
// display it should be restored, so this is best guess.
// TODO(oshima): Restore information should contain the
// work area information like WindowResizer does for the
// last window location.
gfx::Rect display_area =
window_state->window()->GetDisplayNearestWindow().bounds();
if (!display_area.Intersects(restore_bounds)) {
const display::Display& display =
display::Screen::GetScreen()->GetDisplayMatching(restore_bounds);
WmShell* shell = window_state->window()->GetShell();
WmWindow* new_root = shell->GetRootWindowForDisplayId(display.id());
if (new_root != window_state->window()->GetRootWindow()) {
WmWindow* new_container = new_root->GetChildByShellWindowId(
window_state->window()->GetParent()->GetShellWindowId());
new_container->AddChild(window_state->window());
}
}
}
DockedWindowLayoutManager* GetDockedWindowLayoutManager(WmShell* shell) {
return DockedWindowLayoutManager::Get(shell->GetActiveWindow());
}
class ScopedPreferredAlignmentResetter {
public:
ScopedPreferredAlignmentResetter(DockedAlignment dock_alignment,
DockedWindowLayoutManager* dock_layout)
: docked_window_layout_manager_(dock_layout) {
docked_window_layout_manager_->set_preferred_alignment(dock_alignment);
}
~ScopedPreferredAlignmentResetter() {
docked_window_layout_manager_->set_preferred_alignment(
DOCKED_ALIGNMENT_NONE);
}
private:
DockedWindowLayoutManager* docked_window_layout_manager_;
DISALLOW_COPY_AND_ASSIGN(ScopedPreferredAlignmentResetter);
};
class ScopedDockedLayoutEventSourceResetter {
public:
ScopedDockedLayoutEventSourceResetter(DockedWindowLayoutManager* dock_layout)
: docked_window_layout_manager_(dock_layout) {
docked_window_layout_manager_->set_event_source(
DOCKED_ACTION_SOURCE_KEYBOARD);
}
~ScopedDockedLayoutEventSourceResetter() {
docked_window_layout_manager_->set_event_source(
DOCKED_ACTION_SOURCE_UNKNOWN);
}
private:
DockedWindowLayoutManager* docked_window_layout_manager_;
DISALLOW_COPY_AND_ASSIGN(ScopedDockedLayoutEventSourceResetter);
};
void CycleSnapDock(WindowState* window_state, WMEventType event) {
DockedWindowLayoutManager* dock_layout =
GetDockedWindowLayoutManager(window_state->window()->GetShell());
wm::WindowStateType desired_snap_state =
event == WM_EVENT_CYCLE_SNAP_DOCK_LEFT
? wm::WINDOW_STATE_TYPE_LEFT_SNAPPED
: wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED;
DockedAlignment desired_dock_alignment =
event == WM_EVENT_CYCLE_SNAP_DOCK_LEFT ? DOCKED_ALIGNMENT_LEFT
: DOCKED_ALIGNMENT_RIGHT;
DockedAlignment current_dock_alignment =
dock_layout ? dock_layout->CalculateAlignment() : DOCKED_ALIGNMENT_NONE;
if (!window_state->IsDocked() ||
(current_dock_alignment != DOCKED_ALIGNMENT_NONE &&
current_dock_alignment != desired_dock_alignment)) {
if (window_state->CanSnap() &&
window_state->GetStateType() != desired_snap_state &&
window_state->window()->GetType() != ui::wm::WINDOW_TYPE_PANEL) {
const wm::WMEvent event(desired_snap_state ==
wm::WINDOW_STATE_TYPE_LEFT_SNAPPED
? wm::WM_EVENT_SNAP_LEFT
: wm::WM_EVENT_SNAP_RIGHT);
window_state->OnWMEvent(&event);
return;
}
if (dock_layout &&
dock_layout->CanDockWindow(window_state->window(),
desired_dock_alignment)) {
if (window_state->IsDocked()) {
dock_layout->MaybeSetDesiredDockedAlignment(desired_dock_alignment);
return;
}
ScopedDockedLayoutEventSourceResetter event_source_resetter(dock_layout);
ScopedPreferredAlignmentResetter alignmentResetter(desired_dock_alignment,
dock_layout);
const wm::WMEvent event(wm::WM_EVENT_DOCK);
window_state->OnWMEvent(&event);
return;
}
}
if (window_state->IsDocked() || window_state->IsSnapped()) {
ScopedDockedLayoutEventSourceResetter event_source_resetter(dock_layout);
window_state->Restore();
return;
}
window_state->window()->Animate(::wm::WINDOW_ANIMATION_TYPE_BOUNCE);
}
} // namespace
DefaultState::DefaultState(WindowStateType initial_state_type)
: state_type_(initial_state_type), stored_window_state_(nullptr) {}
DefaultState::~DefaultState() {}
void DefaultState::OnWMEvent(WindowState* window_state, const WMEvent* event) {
if (ProcessWorkspaceEvents(window_state, event))
return;
if (ProcessCompoundEvents(window_state, event))
return;
WindowStateType current_state_type = window_state->GetStateType();
WindowStateType next_state_type = WINDOW_STATE_TYPE_NORMAL;
switch (event->type()) {
case WM_EVENT_NORMAL:
next_state_type = current_state_type == WINDOW_STATE_TYPE_DOCKED_MINIMIZED
? WINDOW_STATE_TYPE_DOCKED
: WINDOW_STATE_TYPE_NORMAL;
break;
case WM_EVENT_MAXIMIZE:
next_state_type = WINDOW_STATE_TYPE_MAXIMIZED;
break;
case WM_EVENT_MINIMIZE:
next_state_type = current_state_type == WINDOW_STATE_TYPE_DOCKED
? WINDOW_STATE_TYPE_DOCKED_MINIMIZED
: WINDOW_STATE_TYPE_MINIMIZED;
break;
case WM_EVENT_FULLSCREEN:
next_state_type = WINDOW_STATE_TYPE_FULLSCREEN;
break;
case WM_EVENT_SNAP_LEFT:
next_state_type = WINDOW_STATE_TYPE_LEFT_SNAPPED;
break;
case WM_EVENT_SNAP_RIGHT:
next_state_type = WINDOW_STATE_TYPE_RIGHT_SNAPPED;
break;
case WM_EVENT_DOCK:
next_state_type = WINDOW_STATE_TYPE_DOCKED;
break;
case WM_EVENT_SET_BOUNDS:
SetBounds(window_state, static_cast<const SetBoundsEvent*>(event));
return;
case WM_EVENT_SHOW_INACTIVE:
next_state_type = WINDOW_STATE_TYPE_INACTIVE;
break;
case WM_EVENT_PIN:
case WM_EVENT_TRUSTED_PIN:
// If there already is a pinned window, it is not allowed to set it
// to this window.
// TODO(hidehiko): If a system modal window is openening, the pinning
// probably should fail.
if (WmShell::Get()->IsPinned()) {
LOG(ERROR) << "An PIN event will be failed since another window is "
<< "already in pinned mode.";
next_state_type = current_state_type;
} else {
next_state_type = event->type() == WM_EVENT_PIN
? WINDOW_STATE_TYPE_PINNED
: WINDOW_STATE_TYPE_TRUSTED_PINNED;
}
break;
case WM_EVENT_TOGGLE_MAXIMIZE_CAPTION:
case WM_EVENT_TOGGLE_MAXIMIZE:
case WM_EVENT_TOGGLE_VERTICAL_MAXIMIZE:
case WM_EVENT_TOGGLE_HORIZONTAL_MAXIMIZE:
case WM_EVENT_TOGGLE_FULLSCREEN:
case WM_EVENT_CYCLE_SNAP_DOCK_LEFT:
case WM_EVENT_CYCLE_SNAP_DOCK_RIGHT:
case WM_EVENT_CENTER:
NOTREACHED() << "Compound event should not reach here:" << event;
return;
case WM_EVENT_ADDED_TO_WORKSPACE:
case WM_EVENT_WORKAREA_BOUNDS_CHANGED:
case WM_EVENT_DISPLAY_BOUNDS_CHANGED:
NOTREACHED() << "Workspace event should not reach here:" << event;
return;
}
if (next_state_type == current_state_type && window_state->IsSnapped()) {
gfx::Rect snapped_bounds =
event->type() == WM_EVENT_SNAP_LEFT
? GetDefaultLeftSnappedWindowBoundsInParent(window_state->window())
: GetDefaultRightSnappedWindowBoundsInParent(
window_state->window());
window_state->SetBoundsDirectAnimated(snapped_bounds);
return;
}
EnterToNextState(window_state, next_state_type);
}
WindowStateType DefaultState::GetType() const {
return state_type_;
}
void DefaultState::AttachState(WindowState* window_state,
WindowState::State* state_in_previous_mode) {
DCHECK_EQ(stored_window_state_, window_state);
ReenterToCurrentState(window_state, state_in_previous_mode);
// If the display has changed while in the another mode,
// we need to let windows know the change.
display::Display current_display =
window_state->window()->GetDisplayNearestWindow();
if (stored_display_state_.bounds() != current_display.bounds()) {
const WMEvent event(wm::WM_EVENT_DISPLAY_BOUNDS_CHANGED);
window_state->OnWMEvent(&event);
} else if (stored_display_state_.work_area() != current_display.work_area()) {
const WMEvent event(wm::WM_EVENT_WORKAREA_BOUNDS_CHANGED);
window_state->OnWMEvent(&event);
}
}
void DefaultState::DetachState(WindowState* window_state) {
stored_window_state_ = window_state;
stored_bounds_ = window_state->window()->GetBounds();
stored_restore_bounds_ = window_state->HasRestoreBounds()
? window_state->GetRestoreBoundsInParent()
: gfx::Rect();
// Remember the display state so that in case of the display change
// while in the other mode, we can perform necessary action to
// restore the window state to the proper state for the current
// display.
stored_display_state_ = window_state->window()->GetDisplayNearestWindow();
}
// static
bool DefaultState::ProcessCompoundEvents(WindowState* window_state,
const WMEvent* event) {
WmWindow* window = window_state->window();
switch (event->type()) {
case WM_EVENT_TOGGLE_MAXIMIZE_CAPTION:
if (window_state->IsFullscreen()) {
const wm::WMEvent event(wm::WM_EVENT_TOGGLE_FULLSCREEN);
window_state->OnWMEvent(&event);
} else if (window_state->IsMaximized()) {
window_state->Restore();
} else if (window_state->IsNormalOrSnapped()) {
if (window_state->CanMaximize())
window_state->Maximize();
}
return true;
case WM_EVENT_TOGGLE_MAXIMIZE:
if (window_state->IsFullscreen()) {
const wm::WMEvent event(wm::WM_EVENT_TOGGLE_FULLSCREEN);
window_state->OnWMEvent(&event);
} else if (window_state->IsMaximized()) {
window_state->Restore();
} else if (window_state->CanMaximize()) {
window_state->Maximize();
}
return true;
case WM_EVENT_TOGGLE_VERTICAL_MAXIMIZE: {
gfx::Rect work_area = GetDisplayWorkAreaBoundsInParent(window);
// Maximize vertically if:
// - The window does not have a max height defined.
// - The window has the normal state type. Snapped windows are excluded
// because they are already maximized vertically and reverting to the
// restored bounds looks weird.
if (window->GetMaximumSize().height() != 0 ||
!window_state->IsNormalStateType()) {
return true;
}
if (window_state->HasRestoreBounds() &&
(window->GetBounds().height() == work_area.height() &&
window->GetBounds().y() == work_area.y())) {
window_state->SetAndClearRestoreBounds();
} else {
window_state->SaveCurrentBoundsForRestore();
window->SetBounds(gfx::Rect(window->GetBounds().x(), work_area.y(),
window->GetBounds().width(),
work_area.height()));
}
return true;
}
case WM_EVENT_TOGGLE_HORIZONTAL_MAXIMIZE: {
// Maximize horizontally if:
// - The window does not have a max width defined.
// - The window is snapped or has the normal state type.
if (window->GetMaximumSize().width() != 0)
return true;
if (!window_state->IsNormalOrSnapped())
return true;
gfx::Rect work_area = GetDisplayWorkAreaBoundsInParent(window);
if (window_state->IsNormalStateType() &&
window_state->HasRestoreBounds() &&
(window->GetBounds().width() == work_area.width() &&
window->GetBounds().x() == work_area.x())) {
window_state->SetAndClearRestoreBounds();
} else {
gfx::Rect new_bounds(work_area.x(), window->GetBounds().y(),
work_area.width(), window->GetBounds().height());
gfx::Rect restore_bounds = window->GetBounds();
if (window_state->IsSnapped()) {
window_state->SetRestoreBoundsInParent(new_bounds);
window_state->Restore();
// The restore logic prevents a window from being restored to bounds
// which match the workspace bounds exactly so it is necessary to set
// the bounds again below.
}
window_state->SetRestoreBoundsInParent(restore_bounds);
window->SetBounds(new_bounds);
}
return true;
}
case WM_EVENT_TOGGLE_FULLSCREEN:
ToggleFullScreen(window_state, window_state->delegate());
return true;
case WM_EVENT_CYCLE_SNAP_DOCK_LEFT:
case WM_EVENT_CYCLE_SNAP_DOCK_RIGHT:
CycleSnapDock(window_state, event->type());
return true;
case WM_EVENT_CENTER:
CenterWindow(window_state);
return true;
case WM_EVENT_NORMAL:
case WM_EVENT_MAXIMIZE:
case WM_EVENT_MINIMIZE:
case WM_EVENT_FULLSCREEN:
case WM_EVENT_PIN:
case WM_EVENT_TRUSTED_PIN:
case WM_EVENT_SNAP_LEFT:
case WM_EVENT_SNAP_RIGHT:
case WM_EVENT_SET_BOUNDS:
case WM_EVENT_SHOW_INACTIVE:
case WM_EVENT_DOCK:
break;
case WM_EVENT_ADDED_TO_WORKSPACE:
case WM_EVENT_WORKAREA_BOUNDS_CHANGED:
case WM_EVENT_DISPLAY_BOUNDS_CHANGED:
NOTREACHED() << "Workspace event should not reach here:" << event;
break;
}
return false;
}
bool DefaultState::ProcessWorkspaceEvents(WindowState* window_state,
const WMEvent* event) {
switch (event->type()) {
case WM_EVENT_ADDED_TO_WORKSPACE: {
// When a window is dragged and dropped onto a different
// root window, the bounds will be updated after they are added
// to the root window.
// If a window is opened as maximized or fullscreen, its bounds may be
// empty, so update the bounds now before checking empty.
if (window_state->is_dragged() ||
SetMaximizedOrFullscreenBounds(window_state)) {
return true;
}
WmWindow* window = window_state->window();
gfx::Rect bounds = window->GetBounds();
// Don't adjust window bounds if the bounds are empty as this
// happens when a new views::Widget is created.
if (bounds.IsEmpty())
return true;
// Only windows of type WINDOW_TYPE_NORMAL or WINDOW_TYPE_PANEL need to be
// adjusted to have minimum visibility, because they are positioned by the
// user and user should always be able to interact with them. Other
// windows are positioned programmatically.
if (!window_state->IsUserPositionable())
return true;
// Use entire display instead of workarea because the workarea can
// be further shrunk by the docked area. The logic ensures 30%
// visibility which should be enough to see where the window gets
// moved.
gfx::Rect display_area = GetDisplayBoundsInParent(window);
int min_width = bounds.width() * wm::kMinimumPercentOnScreenArea;
int min_height = bounds.height() * wm::kMinimumPercentOnScreenArea;
wm::AdjustBoundsToEnsureWindowVisibility(display_area, min_width,
min_height, &bounds);
window_state->AdjustSnappedBounds(&bounds);
if (window->GetBounds() != bounds)
window_state->SetBoundsConstrained(bounds);
return true;
}
case WM_EVENT_DISPLAY_BOUNDS_CHANGED: {
if (window_state->is_dragged() ||
SetMaximizedOrFullscreenBounds(window_state)) {
return true;
}
gfx::Rect work_area_in_parent =
GetDisplayWorkAreaBoundsInParent(window_state->window());
gfx::Rect bounds = window_state->window()->GetBounds();
// When display bounds has changed, make sure the entire window is fully
// visible.
bounds.AdjustToFit(work_area_in_parent);
window_state->AdjustSnappedBounds(&bounds);
if (window_state->window()->GetBounds() != bounds)
window_state->SetBoundsDirectAnimated(bounds);
return true;
}
case WM_EVENT_WORKAREA_BOUNDS_CHANGED: {
// Don't resize the maximized window when the desktop is covered
// by fullscreen window. crbug.com/504299.
bool in_fullscreen =
window_state->window()
->GetRootWindowController()
->GetWorkspaceWindowState() == WORKSPACE_WINDOW_STATE_FULL_SCREEN;
if (in_fullscreen && window_state->IsMaximized())
return true;
if (window_state->is_dragged() ||
SetMaximizedOrFullscreenBounds(window_state)) {
return true;
}
gfx::Rect work_area_in_parent =
GetDisplayWorkAreaBoundsInParent(window_state->window());
gfx::Rect bounds = window_state->window()->GetBounds();
wm::AdjustBoundsToEnsureMinimumWindowVisibility(work_area_in_parent,
&bounds);
window_state->AdjustSnappedBounds(&bounds);
if (window_state->window()->GetBounds() != bounds)
window_state->SetBoundsDirectAnimated(bounds);
return true;
}
case WM_EVENT_TOGGLE_MAXIMIZE_CAPTION:
case WM_EVENT_TOGGLE_MAXIMIZE:
case WM_EVENT_TOGGLE_VERTICAL_MAXIMIZE:
case WM_EVENT_TOGGLE_HORIZONTAL_MAXIMIZE:
case WM_EVENT_TOGGLE_FULLSCREEN:
case WM_EVENT_CYCLE_SNAP_DOCK_LEFT:
case WM_EVENT_CYCLE_SNAP_DOCK_RIGHT:
case WM_EVENT_CENTER:
case WM_EVENT_NORMAL:
case WM_EVENT_MAXIMIZE:
case WM_EVENT_MINIMIZE:
case WM_EVENT_FULLSCREEN:
case WM_EVENT_PIN:
case WM_EVENT_TRUSTED_PIN:
case WM_EVENT_SNAP_LEFT:
case WM_EVENT_SNAP_RIGHT:
case WM_EVENT_SET_BOUNDS:
case WM_EVENT_SHOW_INACTIVE:
case WM_EVENT_DOCK:
break;
}
return false;
}
// static
bool DefaultState::SetMaximizedOrFullscreenBounds(WindowState* window_state) {
DCHECK(!window_state->is_dragged());
if (window_state->IsMaximized()) {
window_state->SetBoundsDirect(
GetMaximizedWindowBoundsInParent(window_state->window()));
return true;
}
if (window_state->IsFullscreen()) {
window_state->SetBoundsDirect(
GetDisplayBoundsInParent(window_state->window()));
return true;
}
return false;
}
// static
void DefaultState::SetBounds(WindowState* window_state,
const SetBoundsEvent* event) {
if (window_state->is_dragged()) {
// TODO(oshima|varkha): This may be no longer needed, as the dragging
// happens in docked window container. crbug.com/485612.
window_state->SetBoundsDirect(event->requested_bounds());
} else if (window_state->IsSnapped()) {
gfx::Rect work_area_in_parent =
GetDisplayWorkAreaBoundsInParent(window_state->window());
gfx::Rect child_bounds(event->requested_bounds());
wm::AdjustBoundsSmallerThan(work_area_in_parent.size(), &child_bounds);
window_state->AdjustSnappedBounds(&child_bounds);
window_state->SetBoundsDirect(child_bounds);
} else if (!SetMaximizedOrFullscreenBounds(window_state) ||
window_state->allow_set_bounds_in_maximized()) {
window_state->SetBoundsConstrained(event->requested_bounds());
}
}
void DefaultState::EnterToNextState(WindowState* window_state,
WindowStateType next_state_type) {
// Do nothing if we're already in the same state.
if (state_type_ == next_state_type)
return;
WindowStateType previous_state_type = state_type_;
state_type_ = next_state_type;
window_state->UpdateWindowShowStateFromStateType();
window_state->NotifyPreStateTypeChange(previous_state_type);
if (window_state->window()->GetParent()) {
if (!window_state->HasRestoreBounds() &&
(previous_state_type == WINDOW_STATE_TYPE_DEFAULT ||
previous_state_type == WINDOW_STATE_TYPE_NORMAL) &&
!window_state->IsMinimized() && !window_state->IsNormalStateType()) {
window_state->SaveCurrentBoundsForRestore();
}
// When restoring from a minimized state, we want to restore to the
// previous bounds. However, we want to maintain the restore bounds.
// (The restore bounds are set if a user maximized the window in one
// axis by double clicking the window border for example).
gfx::Rect restore_bounds_in_screen;
if (previous_state_type == WINDOW_STATE_TYPE_MINIMIZED &&
window_state->IsNormalStateType() && window_state->HasRestoreBounds() &&
!window_state->unminimize_to_restore_bounds()) {
restore_bounds_in_screen = window_state->GetRestoreBoundsInScreen();
window_state->SaveCurrentBoundsForRestore();
}
if (window_state->IsMaximizedOrFullscreenOrPinned())
MoveToDisplayForRestore(window_state);
UpdateBoundsFromState(window_state, previous_state_type);
// Normal state should have no restore bounds unless it's
// unminimized.
if (!restore_bounds_in_screen.IsEmpty())
window_state->SetRestoreBoundsInScreen(restore_bounds_in_screen);
else if (window_state->IsNormalStateType())
window_state->ClearRestoreBounds();
}
window_state->NotifyPostStateTypeChange(previous_state_type);
if (next_state_type == WINDOW_STATE_TYPE_PINNED ||
previous_state_type == WINDOW_STATE_TYPE_PINNED ||
next_state_type == WINDOW_STATE_TYPE_TRUSTED_PINNED ||
previous_state_type == WINDOW_STATE_TYPE_TRUSTED_PINNED) {
WmShell::Get()->SetPinnedWindow(window_state->window());
}
}
void DefaultState::ReenterToCurrentState(
WindowState* window_state,
WindowState::State* state_in_previous_mode) {
WindowStateType previous_state_type = state_in_previous_mode->GetType();
// A state change should not move a window into or out of full screen or
// pinned since these are "special mode" the user wanted to be in and
// should be respected as such.
if (previous_state_type == wm::WINDOW_STATE_TYPE_FULLSCREEN ||
previous_state_type == wm::WINDOW_STATE_TYPE_PINNED ||
previous_state_type == wm::WINDOW_STATE_TYPE_TRUSTED_PINNED) {
state_type_ = previous_state_type;
} else if (state_type_ == wm::WINDOW_STATE_TYPE_FULLSCREEN ||
state_type_ == wm::WINDOW_STATE_TYPE_PINNED ||
state_type_ == wm::WINDOW_STATE_TYPE_TRUSTED_PINNED) {
state_type_ = previous_state_type;
}
window_state->UpdateWindowShowStateFromStateType();
window_state->NotifyPreStateTypeChange(previous_state_type);
if ((state_type_ == wm::WINDOW_STATE_TYPE_NORMAL ||
state_type_ == wm::WINDOW_STATE_TYPE_DEFAULT) &&
!stored_bounds_.IsEmpty()) {
// Use the restore mechanism to set the bounds for
// the window in normal state. This also covers unminimize case.
window_state->SetRestoreBoundsInParent(stored_bounds_);
}
UpdateBoundsFromState(window_state, state_in_previous_mode->GetType());
// Then restore the restore bounds to their previous value.
if (!stored_restore_bounds_.IsEmpty())
window_state->SetRestoreBoundsInParent(stored_restore_bounds_);
else
window_state->ClearRestoreBounds();
window_state->NotifyPostStateTypeChange(previous_state_type);
}
void DefaultState::UpdateBoundsFromState(WindowState* window_state,
WindowStateType previous_state_type) {
WmWindow* window = window_state->window();
gfx::Rect bounds_in_parent;
switch (state_type_) {
case WINDOW_STATE_TYPE_LEFT_SNAPPED:
case WINDOW_STATE_TYPE_RIGHT_SNAPPED:
bounds_in_parent =
state_type_ == WINDOW_STATE_TYPE_LEFT_SNAPPED
? GetDefaultLeftSnappedWindowBoundsInParent(window)
: GetDefaultRightSnappedWindowBoundsInParent(window);
break;
case WINDOW_STATE_TYPE_DOCKED: {
if (window->GetParent()->GetShellWindowId() !=
kShellWindowId_DockedContainer) {
WmWindow* docked_container =
window->GetRootWindow()->GetChildByShellWindowId(
kShellWindowId_DockedContainer);
ReparentChildWithTransientChildren(window, window->GetParent(),
docked_container);
}
// Return early because we don't want to update the bounds of the
// window below; as the bounds are managed by the dock layout.
return;
}
case WINDOW_STATE_TYPE_DEFAULT:
case WINDOW_STATE_TYPE_NORMAL: {
gfx::Rect work_area_in_parent = GetDisplayWorkAreaBoundsInParent(window);
if (window_state->HasRestoreBounds()) {
bounds_in_parent = window_state->GetRestoreBoundsInParent();
// Check if the |window|'s restored size is bigger than the working area
// This may happen if a window was resized to maximized bounds or if the
// display resolution changed while the window was maximized.
if (previous_state_type == WINDOW_STATE_TYPE_MAXIMIZED &&
bounds_in_parent.width() >= work_area_in_parent.width() &&
bounds_in_parent.height() >= work_area_in_parent.height()) {
bounds_in_parent = work_area_in_parent;
bounds_in_parent.Inset(kMaximizedWindowInset, kMaximizedWindowInset,
kMaximizedWindowInset, kMaximizedWindowInset);
}
} else {
bounds_in_parent = window->GetBounds();
}
// Make sure that part of the window is always visible.
wm::AdjustBoundsToEnsureMinimumWindowVisibility(work_area_in_parent,
&bounds_in_parent);
break;
}
case WINDOW_STATE_TYPE_MAXIMIZED:
bounds_in_parent = GetMaximizedWindowBoundsInParent(window);
break;
case WINDOW_STATE_TYPE_FULLSCREEN:
case WINDOW_STATE_TYPE_PINNED:
case WINDOW_STATE_TYPE_TRUSTED_PINNED:
bounds_in_parent = GetDisplayBoundsInParent(window);
break;
case WINDOW_STATE_TYPE_DOCKED_MINIMIZED:
case WINDOW_STATE_TYPE_MINIMIZED:
break;
case WINDOW_STATE_TYPE_INACTIVE:
case WINDOW_STATE_TYPE_END:
case WINDOW_STATE_TYPE_AUTO_POSITIONED:
return;
}
if (!window_state->IsMinimized()) {
if (IsMinimizedWindowState(previous_state_type) ||
window_state->IsFullscreen() || window_state->IsPinned()) {
window_state->SetBoundsDirect(bounds_in_parent);
} else if (window_state->IsMaximized() ||
IsMaximizedOrFullscreenOrPinnedWindowStateType(
previous_state_type)) {
window_state->SetBoundsDirectCrossFade(bounds_in_parent);
} else if (window_state->is_dragged()) {
// SetBoundsDirectAnimated does not work when the window gets reparented.
// TODO(oshima): Consider fixing it and reenable the animation.
window_state->SetBoundsDirect(bounds_in_parent);
} else {
window_state->SetBoundsDirectAnimated(bounds_in_parent);
}
}
if (window_state->IsMinimized()) {
// Save the previous show state so that we can correctly restore it.
window->SetRestoreShowState(ToWindowShowState(previous_state_type));
window->SetVisibilityAnimationType(
WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE);
// Hide the window.
window->Hide();
// Activate another window.
if (window_state->IsActive())
window_state->Deactivate();
} else if ((window->GetTargetVisibility() ||
IsMinimizedWindowState(previous_state_type)) &&
!window->GetLayerVisible()) {
// The layer may be hidden if the window was previously minimized. Make
// sure it's visible.
window->Show();
if (IsMinimizedWindowState(previous_state_type) &&
!window_state->IsMaximizedOrFullscreenOrPinned()) {
window_state->set_unminimize_to_restore_bounds(false);
}
}
}
// static
void DefaultState::CenterWindow(WindowState* window_state) {
if (!window_state->IsNormalOrSnapped())
return;
WmWindow* window = window_state->window();
if (window_state->IsSnapped()) {
gfx::Rect center_in_screen = window->GetDisplayNearestWindow().work_area();
gfx::Size size = window_state->HasRestoreBounds()
? window_state->GetRestoreBoundsInScreen().size()
: window->GetBounds().size();
center_in_screen.ClampToCenteredSize(size);
window_state->SetRestoreBoundsInScreen(center_in_screen);
window_state->Restore();
} else {
gfx::Rect center_in_parent = GetDisplayWorkAreaBoundsInParent(window);
center_in_parent.ClampToCenteredSize(window->GetBounds().size());
window_state->SetBoundsDirectAnimated(center_in_parent);
}
// Centering window is treated as if a user moved and resized the window.
window_state->set_bounds_changed_by_user(true);
}
} // namespace wm
} // namespace ash