blob: 2fd56af26cbc9c4ba608936d3c21b34485ecf17f [file] [log] [blame]
// Copyright (c) 2012 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/wm/workspace/workspace_layout_manager.h"
#include "ash/root_window_controller.h"
#include "ash/screen_ash.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shell.h"
#include "ash/wm/always_on_top_controller.h"
#include "ash/wm/base_layout_manager.h"
#include "ash/wm/frame_painter.h"
#include "ash/wm/property_util.h"
#include "ash/wm/window_animations.h"
#include "ash/wm/window_properties.h"
#include "ash/wm/window_util.h"
#include "ash/wm/workspace/auto_window_management.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/root_window.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/base/events/event.h"
#include "ui/base/ui_base_types.h"
#include "ui/views/corewm/window_util.h"
using aura::Window;
namespace ash {
namespace internal {
namespace {
// This specifies how much percent 30% of a window rect (width / height)
// must be visible when the window is added to the workspace.
const float kMinimumPercentOnScreenArea = 0.3f;
bool IsMaximizedState(ui::WindowShowState state) {
return state == ui::SHOW_STATE_MAXIMIZED ||
state == ui::SHOW_STATE_FULLSCREEN;
}
} // namespace
WorkspaceLayoutManager::WorkspaceLayoutManager(aura::Window* window)
: BaseLayoutManager(window->GetRootWindow()),
shelf_(NULL),
window_(window),
work_area_(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
window->parent())) {
}
WorkspaceLayoutManager::~WorkspaceLayoutManager() {
}
void WorkspaceLayoutManager::SetShelf(internal::ShelfLayoutManager* shelf) {
shelf_ = shelf;
}
void WorkspaceLayoutManager::OnWindowAddedToLayout(Window* child) {
// Adjust window bounds in case that the new child is given the bounds that
// is out of the workspace. Exclude the case where bounds is empty
// (this happens when a views::Widget is created), or the window
// is added with the bounds because a user explicitly moved to
// this position (drag and drop for example).
if (!child->bounds().IsEmpty() &&
!wm::HasUserChangedWindowPositionOrSize(child))
AdjustWindowBoundsWhenAdded(child);
BaseLayoutManager::OnWindowAddedToLayout(child);
UpdateDesktopVisibility();
RearrangeVisibleWindowOnShow(child);
}
void WorkspaceLayoutManager::OnWillRemoveWindowFromLayout(Window* child) {
BaseLayoutManager::OnWillRemoveWindowFromLayout(child);
if (child->TargetVisibility())
RearrangeVisibleWindowOnHideOrRemove(child);
}
void WorkspaceLayoutManager::OnWindowRemovedFromLayout(Window* child) {
BaseLayoutManager::OnWindowRemovedFromLayout(child);
UpdateDesktopVisibility();
}
void WorkspaceLayoutManager::OnChildWindowVisibilityChanged(Window* child,
bool visible) {
BaseLayoutManager::OnChildWindowVisibilityChanged(child, visible);
if (child->TargetVisibility())
RearrangeVisibleWindowOnShow(child);
else
RearrangeVisibleWindowOnHideOrRemove(child);
UpdateDesktopVisibility();
}
void WorkspaceLayoutManager::SetChildBounds(
Window* child,
const gfx::Rect& requested_bounds) {
if (!GetTrackedByWorkspace(child)) {
SetChildBoundsDirect(child, requested_bounds);
return;
}
gfx::Rect child_bounds(requested_bounds);
// Some windows rely on this to set their initial bounds.
if (!SetMaximizedOrFullscreenBounds(child)) {
// Non-maximized/full-screen windows have their size constrained to the
// work-area.
child_bounds.set_width(std::min(work_area_.width(), child_bounds.width()));
child_bounds.set_height(
std::min(work_area_.height(), child_bounds.height()));
SetChildBoundsDirect(child, child_bounds);
}
UpdateDesktopVisibility();
}
void WorkspaceLayoutManager::OnDisplayWorkAreaInsetsChanged() {
const gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
window_->parent()));
if (work_area != work_area_) {
AdjustAllWindowsBoundsForWorkAreaChange(
ADJUST_WINDOW_WORK_AREA_INSETS_CHANGED);
}
}
void WorkspaceLayoutManager::OnWindowPropertyChanged(Window* window,
const void* key,
intptr_t old) {
if (key == aura::client::kShowStateKey) {
ui::WindowShowState old_state = static_cast<ui::WindowShowState>(old);
ui::WindowShowState new_state =
window->GetProperty(aura::client::kShowStateKey);
if (old_state != ui::SHOW_STATE_MINIMIZED &&
GetRestoreBoundsInScreen(window) == NULL &&
IsMaximizedState(new_state) &&
!IsMaximizedState(old_state)) {
SetRestoreBoundsInParent(window, window->bounds());
}
// When restoring from a minimized state, we want to restore to the
// previous (maybe L/R maximized) state. Since we do also want to keep the
// restore rectangle, we set the restore rectangle to the rectangle we want
// to restore to and restore it after we switched so that it is preserved.
gfx::Rect restore;
if (old_state == ui::SHOW_STATE_MINIMIZED &&
(new_state == ui::SHOW_STATE_NORMAL ||
new_state == ui::SHOW_STATE_DEFAULT) &&
GetRestoreBoundsInScreen(window) &&
!GetWindowAlwaysRestoresToRestoreBounds(window)) {
restore = *GetRestoreBoundsInScreen(window);
SetRestoreBoundsInScreen(window, window->GetBoundsInScreen());
}
UpdateBoundsFromShowState(window);
ShowStateChanged(window, old_state);
// Set the restore rectangle to the previously set restore rectangle.
if (!restore.IsEmpty())
SetRestoreBoundsInScreen(window, restore);
}
if (key == internal::kWindowTrackedByWorkspaceKey &&
GetTrackedByWorkspace(window)) {
SetMaximizedOrFullscreenBounds(window);
}
if (key == aura::client::kAlwaysOnTopKey &&
window->GetProperty(aura::client::kAlwaysOnTopKey)) {
internal::AlwaysOnTopController* controller =
GetRootWindowController(window->GetRootWindow())->
always_on_top_controller();
controller->GetContainer(window)->AddChild(window);
}
}
void WorkspaceLayoutManager::ShowStateChanged(
Window* window,
ui::WindowShowState last_show_state) {
BaseLayoutManager::ShowStateChanged(window, last_show_state);
UpdateDesktopVisibility();
}
void WorkspaceLayoutManager::AdjustAllWindowsBoundsForWorkAreaChange(
AdjustWindowReason reason) {
work_area_ = ScreenAsh::GetDisplayWorkAreaBoundsInParent(window_->parent());
BaseLayoutManager::AdjustAllWindowsBoundsForWorkAreaChange(reason);
}
void WorkspaceLayoutManager::AdjustWindowBoundsForWorkAreaChange(
Window* window,
AdjustWindowReason reason) {
if (!GetTrackedByWorkspace(window))
return;
// Use cross fade transition for the maximized window if the adjustment
// happens due to the shelf's visibility change. Otherwise the background
// can be seen slightly between the bottom edge of resized-window and
// the animating shelf.
// TODO(mukai): this cause slight blur at the window frame because of the
// cross fade. I think this is better, but should reconsider if someone
// raises voice for this.
if (wm::IsWindowMaximized(window) &&
reason == ADJUST_WINDOW_WORK_AREA_INSETS_CHANGED) {
CrossFadeToBounds(window, ScreenAsh::GetMaximizedWindowBoundsInParent(
window->parent()->parent()));
return;
}
if (SetMaximizedOrFullscreenBounds(window))
return;
gfx::Rect bounds = window->bounds();
switch (reason) {
case ADJUST_WINDOW_DISPLAY_SIZE_CHANGED:
// The work area may be smaller than the full screen. Put as much of the
// window as possible within the display area.
bounds.AdjustToFit(work_area_);
break;
case ADJUST_WINDOW_WORK_AREA_INSETS_CHANGED:
ash::wm::AdjustBoundsToEnsureMinimumWindowVisibility(work_area_, &bounds);
break;
}
if (window->bounds() != bounds)
window->SetBounds(bounds);
}
void WorkspaceLayoutManager::AdjustWindowBoundsWhenAdded(
Window* window) {
if (!GetTrackedByWorkspace(window))
return;
if (SetMaximizedOrFullscreenBounds(window))
return;
gfx::Rect bounds = window->bounds();
int min_width = bounds.width() * kMinimumPercentOnScreenArea;
int min_height = bounds.height() * kMinimumPercentOnScreenArea;
ash::wm::AdjustBoundsToEnsureWindowVisibility(
work_area_, min_width, min_height, &bounds);
if (window->bounds() != bounds)
window->SetBounds(bounds);
}
void WorkspaceLayoutManager::UpdateDesktopVisibility() {
if (shelf_)
shelf_->UpdateVisibilityState();
FramePainter::UpdateSoloWindowHeader(window_->GetRootWindow());
}
void WorkspaceLayoutManager::UpdateBoundsFromShowState(Window* window) {
// See comment in SetMaximizedOrFullscreenBounds() as to why we use parent in
// these calculation.
switch (window->GetProperty(aura::client::kShowStateKey)) {
case ui::SHOW_STATE_DEFAULT:
case ui::SHOW_STATE_NORMAL: {
const gfx::Rect* restore = GetRestoreBoundsInScreen(window);
// Make sure that the part of the window is always visible
// when restored.
gfx::Rect bounds_in_parent;
if (restore) {
bounds_in_parent =
ScreenAsh::ConvertRectFromScreen(window->parent()->parent(),
*restore);
ash::wm::AdjustBoundsToEnsureMinimumWindowVisibility(
work_area_, &bounds_in_parent);
} else {
// Minimized windows have no restore bounds.
// Use the current bounds instead.
bounds_in_parent = window->bounds();
ash::wm::AdjustBoundsToEnsureMinimumWindowVisibility(
work_area_, &bounds_in_parent);
// Don't start animation if the bounds didn't change.
if (bounds_in_parent == window->bounds())
bounds_in_parent.SetRect(0, 0, 0, 0);
}
if (!bounds_in_parent.IsEmpty()) {
CrossFadeToBounds(
window,
BaseLayoutManager::BoundsWithScreenEdgeVisible(
window->parent()->parent(),
bounds_in_parent));
}
ClearRestoreBounds(window);
break;
}
case ui::SHOW_STATE_MAXIMIZED:
CrossFadeToBounds(window, ScreenAsh::GetMaximizedWindowBoundsInParent(
window->parent()->parent()));
break;
case ui::SHOW_STATE_FULLSCREEN:
CrossFadeToBounds(window, ScreenAsh::GetDisplayBoundsInParent(
window->parent()->parent()));
break;
default:
break;
}
}
bool WorkspaceLayoutManager::SetMaximizedOrFullscreenBounds(
aura::Window* window) {
if (!GetTrackedByWorkspace(window))
return false;
// During animations there is a transform installed on the workspace
// windows. For this reason this code uses the parent so that the transform is
// ignored.
if (wm::IsWindowMaximized(window)) {
SetChildBoundsDirect(
window, ScreenAsh::GetMaximizedWindowBoundsInParent(
window->parent()->parent()));
return true;
}
if (wm::IsWindowFullscreen(window)) {
SetChildBoundsDirect(
window,
ScreenAsh::GetDisplayBoundsInParent(window->parent()->parent()));
return true;
}
return false;
}
} // namespace internal
} // namespace ash