blob: 07073ff6447d3150ad3ff0bed4b4070806ca56e9 [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/auto_window_management.h"
#include "ash/ash_switches.h"
#include "ash/shell.h"
#include "ash/wm/property_util.h"
#include "ash/wm/window_animations.h"
#include "ash/wm/window_util.h"
#include "base/command_line.h"
#include "ui/aura/window.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/screen.h"
namespace ash {
namespace internal {
namespace {
// The time in milliseconds which should be used to visually move a window
// through an automatic "intelligent" window management option.
const int kWindowAutoMoveDurationMS = 125;
// Check if any management should be performed (with a given |window|).
bool UseAutoWindowManager(const aura::Window* window) {
return GetTrackedByWorkspace(window) &&
wm::IsWindowPositionManaged(window);
}
// Check if a given |window| can be managed. This includes that it's state is
// not minimized/maximized/the user has changed it's size by hand already.
// It furthermore checks for the WindowIsManaged status.
bool WindowPositionCanBeManaged(const aura::Window* window) {
return (wm::IsWindowPositionManaged(window) &&
!wm::IsWindowMinimized(window) &&
!wm::IsWindowMaximized(window) &&
!wm::HasUserChangedWindowPositionOrSize(window));
}
// Given a |window|, return the only |other_window| which has an impact on
// the automated windows location management. If there is more then one window,
// false is returned, but the |other_window| will be set to the first one
// found.
// If the return value is true a single window was found.
bool GetOtherVisibleAndManageableWindow(const aura::Window* window,
aura::Window** other_window) {
*other_window = NULL;
const aura::Window::Windows& windows = window->parent()->children();
// Find a single open managed window.
for (size_t i = 0; i < windows.size(); i++) {
aura::Window* iterated_window = windows[i];
if (window != iterated_window &&
iterated_window->type() == aura::client::WINDOW_TYPE_NORMAL &&
iterated_window->TargetVisibility() &&
wm::IsWindowPositionManaged(iterated_window)) {
// Bail if we find a second usable window.
if (*other_window)
return false;
*other_window = iterated_window;
}
}
return *other_window != NULL;
}
// Get the work area for a given |window|.
gfx::Rect GetWorkAreaForWindow(aura::Window* window) {
#if defined(OS_WIN)
// On Win 8, the host window can't be resized, so
// use window's bounds instead.
// TODO(oshima): Emulate host window resize on win8.
gfx::Rect work_area = gfx::Rect(window->parent()->bounds().size());
work_area.Inset(Shell::GetScreen()->GetDisplayMatching(
work_area).GetWorkAreaInsets());
return work_area;
#else
return Shell::GetScreen()->GetDisplayNearestWindow(window).work_area();
#endif
}
// Move the given |bounds| on the available |parent_width| to the
// direction. If |move_right| is true, the rectangle gets moved to the right
// corner, otherwise to the left one.
bool MoveRectToOneSide(int parent_width, bool move_right, gfx::Rect* bounds) {
if (move_right) {
if (parent_width > bounds->right()) {
bounds->set_x(parent_width - bounds->width());
return true;
}
} else {
if (0 < bounds->x()) {
bounds->set_x(0);
return true;
}
}
return false;
}
// Move a |window| to a new |bound|. Animate if desired by user.
// Note: The function will do nothing if the bounds did not change.
void SetBoundsAnimated(aura::Window* window, const gfx::Rect& bounds) {
if (bounds == window->GetTargetBounds())
return;
if (views::corewm::WindowAnimationsDisabled(window)) {
window->SetBounds(bounds);
return;
}
ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kWindowAutoMoveDurationMS));
window->SetBounds(bounds);
}
// Move |window| into the center of the screen - or restore it to the previous
// position.
void AutoPlaceSingleWindow(aura::Window* window, bool animated) {
gfx::Rect work_area = GetWorkAreaForWindow(window);
gfx::Rect bounds = window->bounds();
const gfx::Rect* user_defined_area =
ash::wm::GetPreAutoManageWindowBounds(window);
if (user_defined_area) {
bounds = *user_defined_area;
ash::wm::AdjustBoundsToEnsureMinimumWindowVisibility(work_area, &bounds);
} else {
// Center the window (only in x).
bounds.set_x((work_area.width() - bounds.width()) / 2);
}
if (animated)
SetBoundsAnimated(window, bounds);
else
window->SetBounds(bounds);
}
} // namespace
void RearrangeVisibleWindowOnHideOrRemove(const aura::Window* removed_window) {
if (!UseAutoWindowManager(removed_window))
return;
// Find a single open browser window.
aura::Window* other_shown_window = NULL;
if (!GetOtherVisibleAndManageableWindow(removed_window,
&other_shown_window) ||
!WindowPositionCanBeManaged(other_shown_window))
return;
AutoPlaceSingleWindow(other_shown_window, true);
}
void RearrangeVisibleWindowOnShow(aura::Window* added_window) {
if (!UseAutoWindowManager(added_window) ||
wm::HasUserChangedWindowPositionOrSize(added_window) ||
!added_window->TargetVisibility())
return;
// Find a single open managed window.
aura::Window* other_shown_window = NULL;
if (!GetOtherVisibleAndManageableWindow(added_window,
&other_shown_window)) {
// It could be that this window is the first window joining the workspace.
if (!WindowPositionCanBeManaged(added_window) || other_shown_window)
return;
// Since we might be going from 0 to 1 window, we have to arrange the new
// window to a good default.
AutoPlaceSingleWindow(added_window, false);
return;
}
// When going from one to two windows both windows loose their "positioned
// by user" flags.
ash::wm::SetUserHasChangedWindowPositionOrSize(added_window, false);
ash::wm::SetUserHasChangedWindowPositionOrSize(other_shown_window, false);
if (WindowPositionCanBeManaged(other_shown_window)) {
gfx::Rect work_area = GetWorkAreaForWindow(added_window);
// Push away the other window after remembering its current position.
gfx::Rect other_bounds = other_shown_window->bounds();
ash::wm::SetPreAutoManageWindowBounds(other_shown_window, other_bounds);
bool move_right = other_bounds.CenterPoint().x() > work_area.width() / 2;
if (MoveRectToOneSide(work_area.width(), move_right, &other_bounds))
SetBoundsAnimated(other_shown_window, other_bounds);
// Remember the current location of the new window and push it also to the
// opposite location (if needed).
// Since it is just coming into view, we do not need to animate it.
gfx::Rect added_bounds = added_window->bounds();
ash::wm::SetPreAutoManageWindowBounds(added_window, added_bounds);
if (MoveRectToOneSide(work_area.width(), !move_right, &added_bounds))
added_window->SetBounds(added_bounds);
}
}
} // namespace internal
} // namespace ash