| // 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 <algorithm> |
| #include <memory> |
| |
| #include "ash/keyboard/keyboard_observer_register.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/screen_util.h" |
| #include "ash/session/session_controller.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shell.h" |
| #include "ash/wm/always_on_top_controller.h" |
| #include "ash/wm/fullscreen_window_finder.h" |
| #include "ash/wm/screen_pinning_controller.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 "ash/wm/wm_event.h" |
| #include "ash/wm/workspace/backdrop_controller.h" |
| #include "ash/wm/workspace/backdrop_delegate.h" |
| #include "base/command_line.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/base/ui_base_switches.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/keyboard/keyboard_controller.h" |
| #include "ui/keyboard/keyboard_controller_observer.h" |
| #include "ui/wm/core/coordinate_conversion.h" |
| #include "ui/wm/public/activation_client.h" |
| |
| namespace ash { |
| |
| WorkspaceLayoutManager::WorkspaceLayoutManager(aura::Window* window) |
| : window_(window), |
| root_window_(window->GetRootWindow()), |
| root_window_controller_(RootWindowController::ForWindow(root_window_)), |
| work_area_in_parent_( |
| screen_util::GetDisplayWorkAreaBoundsInParent(window_)), |
| is_fullscreen_(wm::GetWindowForFullscreenMode(window) != nullptr), |
| keyboard_observer_(this) { |
| Shell::Get()->AddShellObserver(this); |
| Shell::Get()->activation_client()->AddObserver(this); |
| root_window_->AddObserver(this); |
| display::Screen::GetScreen()->AddObserver(this); |
| DCHECK(window->GetProperty(kSnapChildrenToPixelBoundary)); |
| backdrop_controller_ = std::make_unique<BackdropController>(window_); |
| } |
| |
| WorkspaceLayoutManager::~WorkspaceLayoutManager() { |
| if (root_window_) |
| root_window_->RemoveObserver(this); |
| for (aura::Window* window : windows_) { |
| wm::WindowState* window_state = wm::GetWindowState(window); |
| window_state->RemoveObserver(this); |
| window->RemoveObserver(this); |
| } |
| display::Screen::GetScreen()->RemoveObserver(this); |
| Shell::Get()->activation_client()->RemoveObserver(this); |
| Shell::Get()->RemoveShellObserver(this); |
| } |
| |
| void WorkspaceLayoutManager::SetBackdropDelegate( |
| std::unique_ptr<BackdropDelegate> delegate) { |
| backdrop_controller_->SetBackdropDelegate(std::move(delegate)); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // WorkspaceLayoutManager, aura::LayoutManager implementation: |
| |
| void WorkspaceLayoutManager::OnWindowResized() {} |
| |
| void WorkspaceLayoutManager::OnWindowAddedToLayout(aura::Window* child) { |
| wm::WindowState* window_state = wm::GetWindowState(child); |
| wm::WMEvent event(wm::WM_EVENT_ADDED_TO_WORKSPACE); |
| window_state->OnWMEvent(&event); |
| windows_.insert(child); |
| child->AddObserver(this); |
| window_state->AddObserver(this); |
| UpdateShelfVisibility(); |
| UpdateFullscreenState(); |
| |
| backdrop_controller_->OnWindowAddedToLayout(child); |
| WindowPositioner::RearrangeVisibleWindowOnShow(child); |
| if (Shell::Get()->screen_pinning_controller()->IsPinned()) |
| wm::GetWindowState(child)->DisableAlwaysOnTop(nullptr); |
| } |
| |
| void WorkspaceLayoutManager::OnWillRemoveWindowFromLayout(aura::Window* child) { |
| windows_.erase(child); |
| child->RemoveObserver(this); |
| wm::WindowState* window_state = wm::GetWindowState(child); |
| window_state->RemoveObserver(this); |
| |
| // When a window is removing from a workspace layout, it is going to be added |
| // to a new workspace layout or destroyed. |
| if (!window_state->pre_added_to_workspace_window_bounds()) { |
| if (window_state->pre_auto_manage_window_bounds()) { |
| window_state->SetPreAddedToWorkspaceWindowBounds( |
| *window_state->pre_auto_manage_window_bounds()); |
| } else { |
| window_state->SetPreAddedToWorkspaceWindowBounds(child->bounds()); |
| } |
| } |
| |
| if (child->layer()->GetTargetVisibility()) |
| WindowPositioner::RearrangeVisibleWindowOnHideOrRemove(child); |
| } |
| |
| void WorkspaceLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) { |
| UpdateShelfVisibility(); |
| UpdateFullscreenState(); |
| backdrop_controller_->OnWindowRemovedFromLayout(child); |
| } |
| |
| void WorkspaceLayoutManager::OnChildWindowVisibilityChanged(aura::Window* child, |
| bool visible) { |
| wm::WindowState* window_state = wm::GetWindowState(child); |
| // Attempting to show a minimized window. Unminimize it. |
| if (visible && window_state->IsMinimized()) |
| window_state->Unminimize(); |
| |
| if (child->layer()->GetTargetVisibility()) |
| WindowPositioner::RearrangeVisibleWindowOnShow(child); |
| else |
| WindowPositioner::RearrangeVisibleWindowOnHideOrRemove(child); |
| UpdateFullscreenState(); |
| UpdateShelfVisibility(); |
| backdrop_controller_->OnChildWindowVisibilityChanged(child, visible); |
| } |
| |
| void WorkspaceLayoutManager::SetChildBounds(aura::Window* child, |
| const gfx::Rect& requested_bounds) { |
| wm::SetBoundsEvent event(wm::WM_EVENT_SET_BOUNDS, requested_bounds); |
| wm::GetWindowState(child)->OnWMEvent(&event); |
| UpdateShelfVisibility(); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // WorkspaceLayoutManager, keyboard::KeyboardControllerObserver implementation: |
| |
| void WorkspaceLayoutManager::OnKeyboardWorkspaceDisplacingBoundsChanged( |
| const gfx::Rect& new_bounds) { |
| aura::Window* window = wm::GetActiveWindow(); |
| if (!window) |
| return; |
| |
| window = window->GetToplevelWindow(); |
| if (!window_->Contains(window)) |
| return; |
| |
| wm::WindowState* window_state = wm::GetWindowState(window); |
| if (window_state->ignore_keyboard_bounds_change()) |
| return; |
| |
| if (!new_bounds.IsEmpty()) { |
| // Store existing bounds to be restored before resizing for keyboard if it |
| // is not already stored. |
| if (!window_state->HasRestoreBounds()) |
| window_state->SaveCurrentBoundsForRestore(); |
| |
| gfx::Rect window_bounds(window->GetTargetBounds()); |
| ::wm::ConvertRectToScreen(window_, &window_bounds); |
| int vertical_displacement = |
| std::max(0, window_bounds.bottom() - new_bounds.y()); |
| int shift = std::min(vertical_displacement, |
| window_bounds.y() - work_area_in_parent_.y()); |
| if (shift > 0) { |
| gfx::Point origin(window_bounds.x(), window_bounds.y() - shift); |
| SetChildBounds(window, gfx::Rect(origin, window_bounds.size())); |
| } |
| } else if (window_state->HasRestoreBounds()) { |
| // Keyboard hidden, restore original bounds if they exist. If the user has |
| // resized or dragged the window in the meantime, WorkspaceWindowResizer |
| // will have cleared the restore bounds and this code will not accidentally |
| // override user intent. |
| window_state->SetAndClearRestoreBounds(); |
| } |
| } |
| |
| void WorkspaceLayoutManager::OnKeyboardClosed() { |
| keyboard_observer_.RemoveAll(); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // WorkspaceLayoutManager, aura::WindowObserver implementation: |
| |
| void WorkspaceLayoutManager::OnWindowHierarchyChanged( |
| const HierarchyChangeParams& params) { |
| if (!wm::GetWindowState(params.target)->IsActive()) |
| return; |
| // If the window is already tracked by the workspace this update would be |
| // redundant as the fullscreen and shelf state would have been handled in |
| // OnWindowAddedToLayout. |
| if (windows_.find(params.target) != windows_.end()) |
| return; |
| |
| // If the active window has moved to this root window then update the |
| // fullscreen state. |
| // TODO(flackr): Track the active window leaving this root window and update |
| // the fullscreen state accordingly. |
| if (params.new_parent && params.new_parent->GetRootWindow() == root_window_) { |
| UpdateFullscreenState(); |
| UpdateShelfVisibility(); |
| } |
| } |
| |
| void WorkspaceLayoutManager::OnWindowPropertyChanged(aura::Window* window, |
| const void* key, |
| intptr_t old) { |
| if (key == aura::client::kAlwaysOnTopKey) { |
| if (window->GetProperty(aura::client::kAlwaysOnTopKey)) { |
| aura::Window* container = |
| root_window_controller_->always_on_top_controller()->GetContainer( |
| window); |
| if (window->parent() != container) |
| container->AddChild(window); |
| } |
| } else if (key == kBackdropWindowMode) { |
| backdrop_controller_->UpdateBackdrop(); |
| } |
| } |
| |
| void WorkspaceLayoutManager::OnWindowStackingChanged(aura::Window* window) { |
| UpdateShelfVisibility(); |
| UpdateFullscreenState(); |
| backdrop_controller_->OnWindowStackingChanged(window); |
| } |
| |
| void WorkspaceLayoutManager::OnWindowDestroying(aura::Window* window) { |
| if (root_window_ == window) { |
| root_window_->RemoveObserver(this); |
| root_window_ = nullptr; |
| } |
| } |
| |
| void WorkspaceLayoutManager::OnWindowBoundsChanged( |
| aura::Window* window, |
| const gfx::Rect& old_bounds, |
| const gfx::Rect& new_bounds, |
| ui::PropertyChangeReason reason) { |
| if (root_window_ == window) { |
| const wm::WMEvent wm_event(wm::WM_EVENT_DISPLAY_BOUNDS_CHANGED); |
| AdjustAllWindowsBoundsForWorkAreaChange(&wm_event); |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // WorkspaceLayoutManager, wm::ActivationChangeObserver implementation: |
| |
| void WorkspaceLayoutManager::OnWindowActivating(ActivationReason reason, |
| aura::Window* gaining_active, |
| aura::Window* losing_active) { |
| wm::WindowState* window_state = |
| gaining_active ? wm::GetWindowState(gaining_active) : nullptr; |
| if (window_state && window_state->IsMinimized() && |
| !gaining_active->IsVisible()) { |
| window_state->Unminimize(); |
| DCHECK(!window_state->IsMinimized()); |
| } |
| } |
| |
| void WorkspaceLayoutManager::OnWindowActivated(ActivationReason reason, |
| aura::Window* gained_active, |
| aura::Window* lost_active) { |
| UpdateFullscreenState(); |
| UpdateShelfVisibility(); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // WorkspaceLayoutManager, wm::WindowStateObserver implementation: |
| |
| void WorkspaceLayoutManager::OnPostWindowStateTypeChange( |
| wm::WindowState* window_state, |
| mojom::WindowStateType old_type) { |
| // Notify observers that fullscreen state may be changing. |
| if (window_state->IsFullscreen() || |
| old_type == mojom::WindowStateType::FULLSCREEN) { |
| UpdateFullscreenState(); |
| } |
| |
| UpdateShelfVisibility(); |
| backdrop_controller_->OnPostWindowStateTypeChange(window_state, old_type); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // WorkspaceLayoutManager, display::DisplayObserver implementation: |
| |
| void WorkspaceLayoutManager::OnDisplayMetricsChanged( |
| const display::Display& display, |
| uint32_t changed_metrics) { |
| if (display::Screen::GetScreen()->GetDisplayNearestWindow(window_).id() != |
| display.id()) { |
| return; |
| } |
| |
| const gfx::Rect work_area( |
| screen_util::GetDisplayWorkAreaBoundsInParent(window_)); |
| if (work_area != work_area_in_parent_) { |
| const wm::WMEvent event(wm::WM_EVENT_WORKAREA_BOUNDS_CHANGED); |
| AdjustAllWindowsBoundsForWorkAreaChange(&event); |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // WorkspaceLayoutManager, ShellObserver implementation: |
| |
| void WorkspaceLayoutManager::OnFullscreenStateChanged( |
| bool is_fullscreen, |
| aura::Window* root_window) { |
| if (root_window != root_window_ || is_fullscreen_ == is_fullscreen) |
| return; |
| |
| is_fullscreen_ = is_fullscreen; |
| if (Shell::Get()->screen_pinning_controller()->IsPinned()) { |
| // If this is in pinned mode, then this event does not trigger the |
| // always-on-top state change, because it is kept disabled regardless of |
| // the fullscreen state change. |
| return; |
| } |
| |
| UpdateAlwaysOnTop(is_fullscreen_ ? wm::GetWindowForFullscreenMode(window_) |
| : nullptr); |
| } |
| |
| void WorkspaceLayoutManager::OnPinnedStateChanged(aura::Window* pinned_window) { |
| const bool is_pinned = Shell::Get()->screen_pinning_controller()->IsPinned(); |
| if (!is_pinned && is_fullscreen_) { |
| // On exiting from pinned mode, if the workspace is still in fullscreen |
| // mode, then this event does not trigger the restoring yet. On exiting |
| // from fullscreen, the temporarily disabled always-on-top property will be |
| // restored. |
| return; |
| } |
| |
| UpdateAlwaysOnTop(is_pinned ? pinned_window : nullptr); |
| } |
| |
| void WorkspaceLayoutManager::OnVirtualKeyboardStateChanged( |
| bool activated, |
| aura::Window* root_window) { |
| UpdateKeyboardObserverFromStateChanged(activated, root_window, root_window_, |
| &keyboard_observer_); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // WorkspaceLayoutManager, private: |
| |
| void WorkspaceLayoutManager::AdjustAllWindowsBoundsForWorkAreaChange( |
| const wm::WMEvent* event) { |
| DCHECK(event->type() == wm::WM_EVENT_DISPLAY_BOUNDS_CHANGED || |
| event->type() == wm::WM_EVENT_WORKAREA_BOUNDS_CHANGED); |
| |
| work_area_in_parent_ = screen_util::GetDisplayWorkAreaBoundsInParent(window_); |
| |
| // Don't do any adjustments of the insets while we are in screen locked mode. |
| // This would happen if the launcher was auto hidden before the login screen |
| // was shown and then gets shown when the login screen gets presented. |
| if (event->type() == wm::WM_EVENT_WORKAREA_BOUNDS_CHANGED && |
| Shell::Get()->session_controller()->IsScreenLocked()) |
| return; |
| |
| // If a user plugs an external display into a laptop running Aura the |
| // display size will change. Maximized windows need to resize to match. |
| // We also do this when developers running Aura on a desktop manually resize |
| // the host window. |
| // We also need to do this when the work area insets changes. |
| for (aura::Window* window : windows_) |
| wm::GetWindowState(window)->OnWMEvent(event); |
| } |
| |
| void WorkspaceLayoutManager::UpdateShelfVisibility() { |
| root_window_controller_->shelf()->UpdateVisibilityState(); |
| } |
| |
| void WorkspaceLayoutManager::UpdateFullscreenState() { |
| // TODO(flackr): The fullscreen state is currently tracked per workspace |
| // but the shell notification implies a per root window state. Currently |
| // only windows in the default workspace container will go fullscreen but |
| // this should really be tracked by the RootWindowController since |
| // technically any container could get a fullscreen window. |
| if (window_->id() != kShellWindowId_DefaultContainer) |
| return; |
| bool is_fullscreen = wm::GetWindowForFullscreenMode(window_) != nullptr; |
| if (is_fullscreen != is_fullscreen_) { |
| Shell::Get()->NotifyFullscreenStateChanged(is_fullscreen, root_window_); |
| is_fullscreen_ = is_fullscreen; |
| } |
| } |
| |
| void WorkspaceLayoutManager::UpdateAlwaysOnTop(aura::Window* window_on_top) { |
| // Changing always on top state may change window's parent. Iterate on a copy |
| // of |windows_| to avoid invalidating an iterator. Since both workspace and |
| // always_on_top containers' layouts are managed by this class all the |
| // appropriate windows will be included in the iteration. |
| WindowSet windows(windows_); |
| for (aura::Window* window : windows) { |
| wm::WindowState* window_state = wm::GetWindowState(window); |
| if (window_on_top) |
| window_state->DisableAlwaysOnTop(window_on_top); |
| else |
| window_state->RestoreAlwaysOnTop(); |
| } |
| } |
| |
| } // namespace ash |