| // 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/display/display_controller.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/header_painter.h" |
| #include "ash/wm/window_animations.h" |
| #include "ash/wm/window_positioner.h" |
| #include "ash/wm/window_properties.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_util.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_observer.h" |
| #include "ui/base/ui_base_types.h" |
| #include "ui/events/event.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; |
| |
| void MoveToDisplayForRestore(wm::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 = Shell::GetScreen()->GetDisplayNearestWindow( |
| window_state->window()).bounds(); |
| |
| if (!display_area.Intersects(restore_bounds)) { |
| DisplayController* display_controller = |
| Shell::GetInstance()->display_controller(); |
| const gfx::Display& display = |
| display_controller->GetDisplayMatching(restore_bounds); |
| aura::Window* new_root = |
| display_controller->GetRootWindowForDisplayId(display.id()); |
| if (new_root != window_state->window()->GetRootWindow()) { |
| aura::Window* new_container = |
| Shell::GetContainer(new_root, window_state->window()->parent()->id()); |
| new_container->AddChild(window_state->window()); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| WorkspaceLayoutManager::WorkspaceLayoutManager(aura::Window* window) |
| : BaseLayoutManager(window->GetRootWindow()), |
| shelf_(NULL), |
| window_(window), |
| work_area_in_parent_(ScreenAsh::GetDisplayWorkAreaBoundsInParent( |
| window->parent())) { |
| } |
| |
| WorkspaceLayoutManager::~WorkspaceLayoutManager() { |
| } |
| |
| void WorkspaceLayoutManager::SetShelf(internal::ShelfLayoutManager* shelf) { |
| shelf_ = shelf; |
| } |
| |
| void WorkspaceLayoutManager::OnWindowAddedToLayout(Window* child) { |
| AdjustWindowBoundsWhenAdded(wm::GetWindowState(child)); |
| BaseLayoutManager::OnWindowAddedToLayout(child); |
| UpdateDesktopVisibility(); |
| WindowPositioner::RearrangeVisibleWindowOnShow(child); |
| } |
| |
| void WorkspaceLayoutManager::OnWillRemoveWindowFromLayout(Window* child) { |
| BaseLayoutManager::OnWillRemoveWindowFromLayout(child); |
| if (child->TargetVisibility()) |
| WindowPositioner::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()) { |
| WindowPositioner::RearrangeVisibleWindowOnShow(child); |
| } else { |
| if (wm::GetWindowState(child)->IsFullscreen()) { |
| ash::Shell::GetInstance()->NotifyFullscreenStateChange( |
| false, child->GetRootWindow()); |
| } |
| WindowPositioner::RearrangeVisibleWindowOnHideOrRemove(child); |
| } |
| UpdateDesktopVisibility(); |
| } |
| |
| void WorkspaceLayoutManager::SetChildBounds( |
| Window* child, |
| const gfx::Rect& requested_bounds) { |
| if (!wm::GetWindowState(child)->tracked_by_workspace()) { |
| SetChildBoundsDirect(child, requested_bounds); |
| return; |
| } |
| gfx::Rect child_bounds(requested_bounds); |
| // Some windows rely on this to set their initial bounds. |
| if (!SetMaximizedOrFullscreenBounds(wm::GetWindowState(child))) { |
| // Non-maximized/full-screen windows have their size constrained to the |
| // work-area. |
| child_bounds.set_width(std::min(work_area_in_parent_.width(), |
| child_bounds.width())); |
| child_bounds.set_height( |
| std::min(work_area_in_parent_.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_in_parent_) { |
| AdjustAllWindowsBoundsForWorkAreaChange( |
| ADJUST_WINDOW_WORK_AREA_INSETS_CHANGED); |
| } |
| } |
| |
| void WorkspaceLayoutManager::OnWindowPropertyChanged(Window* window, |
| const void* key, |
| intptr_t old) { |
| if (key == aura::client::kAlwaysOnTopKey && |
| window->GetProperty(aura::client::kAlwaysOnTopKey)) { |
| GetRootWindowController(window->GetRootWindow())-> |
| always_on_top_controller()->GetContainer(window)->AddChild(window); |
| } |
| } |
| |
| void WorkspaceLayoutManager::OnTrackedByWorkspaceChanged( |
| wm::WindowState* window_state, |
| bool old){ |
| if (window_state->tracked_by_workspace()) |
| SetMaximizedOrFullscreenBounds(window_state); |
| } |
| |
| void WorkspaceLayoutManager::OnWindowShowTypeChanged( |
| wm::WindowState* window_state, |
| wm::WindowShowType old_type) { |
| ui::WindowShowState old_state = ToWindowShowState(old_type); |
| ui::WindowShowState new_state = window_state->GetShowState(); |
| if (old_state != ui::SHOW_STATE_MINIMIZED && |
| !window_state->HasRestoreBounds() && |
| window_state->IsMaximizedOrFullscreen() && |
| !wm::WindowState::IsMaximizedOrFullscreenState(old_state)) { |
| window_state->SaveCurrentBoundsForRestore(); |
| } |
| // 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) && |
| window_state->HasRestoreBounds() && |
| !window_state->always_restores_to_restore_bounds()) { |
| restore = window_state->GetRestoreBoundsInScreen(); |
| window_state->SaveCurrentBoundsForRestore(); |
| } |
| // Notify observers that fullscreen state may be changing. |
| if (old_state != new_state && |
| (new_state == ui::SHOW_STATE_FULLSCREEN || |
| old_state == ui::SHOW_STATE_FULLSCREEN)) { |
| ash::Shell::GetInstance()->NotifyFullscreenStateChange( |
| new_state == ui::SHOW_STATE_FULLSCREEN, |
| window_state->window()->GetRootWindow()); |
| } |
| |
| UpdateBoundsFromShowState(window_state, old_state); |
| ShowStateChanged(window_state, old_state); |
| |
| // Set the restore rectangle to the previously set restore rectangle. |
| if (!restore.IsEmpty()) |
| window_state->SetRestoreBoundsInScreen(restore); |
| } |
| |
| void WorkspaceLayoutManager::ShowStateChanged( |
| wm::WindowState* state, |
| ui::WindowShowState last_show_state) { |
| BaseLayoutManager::ShowStateChanged(state, last_show_state); |
| UpdateDesktopVisibility(); |
| } |
| |
| void WorkspaceLayoutManager::AdjustAllWindowsBoundsForWorkAreaChange( |
| AdjustWindowReason reason) { |
| work_area_in_parent_ = |
| ScreenAsh::GetDisplayWorkAreaBoundsInParent(window_->parent()); |
| BaseLayoutManager::AdjustAllWindowsBoundsForWorkAreaChange(reason); |
| } |
| |
| void WorkspaceLayoutManager::AdjustWindowBoundsForWorkAreaChange( |
| wm::WindowState* window_state, |
| AdjustWindowReason reason) { |
| if (!window_state->tracked_by_workspace()) |
| return; |
| |
| // Do not cross fade here: the window's layer hierarchy may be messed up for |
| // the transition between mirroring and extended. See also: crbug.com/267698 |
| // TODO(oshima): Differentiate display change and shelf visibility change, and |
| // bring back CrossFade animation. |
| if (window_state->IsMaximized() && |
| reason == ADJUST_WINDOW_WORK_AREA_INSETS_CHANGED) { |
| SetChildBoundsDirect(window_state->window(), |
| ScreenAsh::GetMaximizedWindowBoundsInParent( |
| window_state->window()->parent()->parent())); |
| return; |
| } |
| |
| if (SetMaximizedOrFullscreenBounds(window_state)) |
| return; |
| |
| gfx::Rect bounds = window_state->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_in_parent_); |
| break; |
| case ADJUST_WINDOW_WORK_AREA_INSETS_CHANGED: |
| ash::wm::AdjustBoundsToEnsureMinimumWindowVisibility( |
| work_area_in_parent_, &bounds); |
| break; |
| } |
| if (window_state->window()->bounds() != bounds) |
| window_state->window()->SetBounds(bounds); |
| } |
| |
| void WorkspaceLayoutManager::AdjustWindowBoundsWhenAdded( |
| wm::WindowState* window_state) { |
| // Don't adjust window bounds if the bounds are empty as this |
| // happens when a new views::Widget is created. |
| // 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 (window_state->window()->bounds().IsEmpty()) |
| return; |
| |
| if (!window_state->tracked_by_workspace()) |
| return; |
| |
| if (SetMaximizedOrFullscreenBounds(window_state)) |
| return; |
| |
| Window* window = window_state->window(); |
| gfx::Rect bounds = window->bounds(); |
| int min_width = bounds.width() * kMinimumPercentOnScreenArea; |
| int min_height = bounds.height() * kMinimumPercentOnScreenArea; |
| // 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 = ScreenAsh::GetDisplayBoundsInParent(window); |
| |
| ash::wm::AdjustBoundsToEnsureWindowVisibility( |
| display_area, min_width, min_height, &bounds); |
| if (window->bounds() != bounds) |
| window->SetBounds(bounds); |
| } |
| |
| void WorkspaceLayoutManager::UpdateDesktopVisibility() { |
| if (shelf_) |
| shelf_->UpdateVisibilityState(); |
| HeaderPainter::UpdateSoloWindowHeader(window_->GetRootWindow()); |
| } |
| |
| void WorkspaceLayoutManager::UpdateBoundsFromShowState( |
| wm::WindowState* window_state, |
| ui::WindowShowState last_show_state) { |
| aura::Window* window = window_state->window(); |
| // See comment in SetMaximizedOrFullscreenBounds() as to why we use parent in |
| // these calculation. |
| switch (window_state->GetShowState()) { |
| case ui::SHOW_STATE_DEFAULT: |
| case ui::SHOW_STATE_NORMAL: { |
| // Make sure that the part of the window is always visible |
| // when restored. |
| gfx::Rect bounds_in_parent; |
| if (window_state->HasRestoreBounds()) { |
| bounds_in_parent = window_state->GetRestoreBoundsInParent(); |
| ash::wm::AdjustBoundsToEnsureMinimumWindowVisibility( |
| work_area_in_parent_, &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_in_parent_, &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()) { |
| gfx::Rect new_bounds = BaseLayoutManager::BoundsWithScreenEdgeVisible( |
| window->parent()->parent(), |
| bounds_in_parent); |
| if (last_show_state == ui::SHOW_STATE_MINIMIZED) |
| SetChildBoundsDirect(window, new_bounds); |
| else |
| CrossFadeToBounds(window, new_bounds); |
| } |
| window_state->ClearRestoreBounds(); |
| break; |
| } |
| |
| case ui::SHOW_STATE_MAXIMIZED: { |
| MoveToDisplayForRestore(window_state); |
| gfx::Rect new_bounds = ScreenAsh::GetMaximizedWindowBoundsInParent( |
| window->parent()->parent()); |
| // If the window is restored from minimized state, do not make the cross |
| // fade animation and set the child bounds directly. The restoring |
| // animation will be done by ash/wm/window_animations.cc. |
| if (last_show_state == ui::SHOW_STATE_MINIMIZED) |
| SetChildBoundsDirect(window, new_bounds); |
| else |
| CrossFadeToBounds(window, new_bounds); |
| break; |
| } |
| |
| case ui::SHOW_STATE_FULLSCREEN: { |
| MoveToDisplayForRestore(window_state); |
| gfx::Rect new_bounds = ScreenAsh::GetDisplayBoundsInParent( |
| window->parent()->parent()); |
| if (window_state->animate_to_fullscreen() && |
| last_show_state != ui::SHOW_STATE_MINIMIZED) { |
| CrossFadeToBounds(window, new_bounds); |
| } else { |
| SetChildBoundsDirect(window, new_bounds); |
| } |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| |
| bool WorkspaceLayoutManager::SetMaximizedOrFullscreenBounds( |
| wm::WindowState* window_state) { |
| if (!window_state->tracked_by_workspace()) |
| 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 (window_state->IsMaximized()) { |
| SetChildBoundsDirect( |
| window_state->window(), ScreenAsh::GetMaximizedWindowBoundsInParent( |
| window_state->window()->parent()->parent())); |
| return true; |
| } |
| if (window_state->IsFullscreen()) { |
| SetChildBoundsDirect( |
| window_state->window(), |
| ScreenAsh::GetDisplayBoundsInParent( |
| window_state->window()->parent()->parent())); |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace internal |
| } // namespace ash |