blob: f11d0979891629640ea47dc72e1d5e9366573577 [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/wm/tablet_mode/tablet_mode_window_state.h"
#include <utility>
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_animation_types.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/public/cpp/window_state_type.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/screen_pinning_controller.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "ash/wm/tablet_mode/tablet_mode_window_manager.h"
#include "ash/wm/window_properties.h"
#include "ash/wm/window_state_util.h"
#include "ash/wm/window_util.h"
#include "ash/wm/wm_event.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
// Sets the restore bounds and show state overrides. These values take
// precedence over the restore bounds and restore show state (if set).
// If |bounds_override| is empty the values are cleared.
void SetWindowRestoreOverrides(aura::Window* window,
const gfx::Rect& bounds_override,
ui::WindowShowState window_state_override) {
if (bounds_override.IsEmpty()) {
window->ClearProperty(kRestoreWindowStateTypeOverrideKey);
window->ClearProperty(kRestoreBoundsOverrideKey);
return;
}
window->SetProperty(kRestoreWindowStateTypeOverrideKey,
ToWindowStateType(window_state_override));
window->SetProperty(kRestoreBoundsOverrideKey,
new gfx::Rect(bounds_override));
}
// Returns the biggest possible size for a window which is about to be
// maximized.
gfx::Size GetMaximumSizeOfWindow(wm::WindowState* window_state) {
DCHECK(window_state->CanMaximize() || window_state->CanResize());
gfx::Size workspace_size =
screen_util::GetMaximizedWindowBoundsInParent(window_state->window())
.size();
gfx::Size size = window_state->window()->delegate()
? window_state->window()->delegate()->GetMaximumSize()
: gfx::Size();
if (size.IsEmpty())
return workspace_size;
size.SetToMin(workspace_size);
return size;
}
// Returns the centered bounds of the given bounds in the work area.
gfx::Rect GetCenteredBounds(const gfx::Rect& bounds_in_parent,
wm::WindowState* state_object) {
gfx::Rect work_area_in_parent =
screen_util::GetDisplayWorkAreaBoundsInParent(state_object->window());
work_area_in_parent.ClampToCenteredSize(bounds_in_parent.size());
return work_area_in_parent;
}
// Returns the maximized/full screen and/or centered bounds of a window.
gfx::Rect GetBoundsInMaximizedMode(wm::WindowState* state_object) {
if (state_object->IsFullscreen() || state_object->IsPinned())
return screen_util::GetDisplayBoundsInParent(state_object->window());
if (state_object->GetStateType() == mojom::WindowStateType::LEFT_SNAPPED) {
return Shell::Get()
->split_view_controller()
->GetSnappedWindowBoundsInParent(state_object->window(),
SplitViewController::LEFT);
}
if (state_object->GetStateType() == mojom::WindowStateType::RIGHT_SNAPPED) {
return Shell::Get()
->split_view_controller()
->GetSnappedWindowBoundsInParent(state_object->window(),
SplitViewController::RIGHT);
}
gfx::Rect bounds_in_parent;
// Make the window as big as possible.
// Do no maximize the transient children, which should be stacked
// above the transient parent.
if ((state_object->CanMaximize() || state_object->CanResize()) &&
::wm::GetTransientParent(state_object->window()) == nullptr) {
bounds_in_parent.set_size(GetMaximumSizeOfWindow(state_object));
} else {
// We prefer the user given window dimensions over the current windows
// dimensions since they are likely to be the result from some other state
// object logic.
if (state_object->HasRestoreBounds())
bounds_in_parent = state_object->GetRestoreBoundsInParent();
else
bounds_in_parent = state_object->window()->bounds();
}
return GetCenteredBounds(bounds_in_parent, state_object);
}
gfx::Rect GetRestoreBounds(wm::WindowState* window_state) {
if (window_state->IsMinimized() || window_state->IsMaximized() ||
window_state->IsFullscreen()) {
gfx::Rect restore_bounds = window_state->GetRestoreBoundsInScreen();
if (!restore_bounds.IsEmpty())
return restore_bounds;
}
return window_state->window()->GetBoundsInScreen();
}
// Returns true if |window| is the source window of the current tab-dragging
// window.
bool IsTabDraggingSourceWindow(aura::Window* window) {
if (!window)
return false;
MruWindowTracker::WindowList window_list =
Shell::Get()->mru_window_tracker()->BuildMruWindowList();
if (window_list.empty())
return false;
// Find the window that's currently in tab-dragging process. There is at most
// one such window.
aura::Window* dragged_window = nullptr;
for (auto* window : window_list) {
if (wm::IsDraggingTabs(window)) {
dragged_window = window;
break;
}
}
if (!dragged_window)
return false;
return dragged_window->GetProperty(ash::kTabDraggingSourceWindowKey) ==
window;
}
} // namespace
// static
void TabletModeWindowState::UpdateWindowPosition(wm::WindowState* window_state,
bool animate) {
gfx::Rect bounds_in_parent = GetBoundsInMaximizedMode(window_state);
if (bounds_in_parent == window_state->window()->GetTargetBounds())
return;
if (animate)
window_state->SetBoundsDirectAnimated(bounds_in_parent);
else
window_state->SetBoundsDirect(bounds_in_parent);
}
TabletModeWindowState::TabletModeWindowState(aura::Window* window,
TabletModeWindowManager* creator,
bool defer_bounds_updates)
: window_(window),
creator_(creator),
current_state_type_(wm::GetWindowState(window)->GetStateType()),
defer_bounds_updates_(defer_bounds_updates) {
old_state_.reset(wm::GetWindowState(window)
->SetStateObject(std::unique_ptr<State>(this))
.release());
}
TabletModeWindowState::~TabletModeWindowState() {
creator_->WindowStateDestroyed(window_);
}
void TabletModeWindowState::LeaveTabletMode(wm::WindowState* window_state) {
// Note: When we return we will destroy ourselves with the |our_reference|.
std::unique_ptr<wm::WindowState::State> our_reference =
window_state->SetStateObject(std::move(old_state_));
}
void TabletModeWindowState::SetDeferBoundsUpdates(bool defer_bounds_updates) {
if (defer_bounds_updates_ == defer_bounds_updates)
return;
defer_bounds_updates_ = defer_bounds_updates;
if (!defer_bounds_updates_)
UpdateBounds(wm::GetWindowState(window_), true /* animated */);
}
void TabletModeWindowState::OnWMEvent(wm::WindowState* window_state,
const wm::WMEvent* event) {
// Ignore events that are sent during the exit transition.
if (ignore_wm_events_) {
return;
}
switch (event->type()) {
case wm::WM_EVENT_TOGGLE_FULLSCREEN:
ToggleFullScreen(window_state, window_state->delegate());
break;
case wm::WM_EVENT_FULLSCREEN:
UpdateWindow(window_state, mojom::WindowStateType::FULLSCREEN,
true /* animated */);
break;
case wm::WM_EVENT_PIN:
if (!Shell::Get()->screen_pinning_controller()->IsPinned())
UpdateWindow(window_state, mojom::WindowStateType::PINNED,
true /* animated */);
break;
case wm::WM_EVENT_PIP:
if (!window_state->IsPip()) {
UpdateWindow(window_state, mojom::WindowStateType::PIP,
true /* animated */);
}
break;
case wm::WM_EVENT_TRUSTED_PIN:
if (!Shell::Get()->screen_pinning_controller()->IsPinned())
UpdateWindow(window_state, mojom::WindowStateType::TRUSTED_PINNED,
true /* animated */);
break;
case wm::WM_EVENT_TOGGLE_MAXIMIZE_CAPTION:
case wm::WM_EVENT_TOGGLE_VERTICAL_MAXIMIZE:
case wm::WM_EVENT_TOGGLE_HORIZONTAL_MAXIMIZE:
case wm::WM_EVENT_TOGGLE_MAXIMIZE:
case wm::WM_EVENT_CYCLE_SNAP_LEFT:
case wm::WM_EVENT_CYCLE_SNAP_RIGHT:
case wm::WM_EVENT_CENTER:
case wm::WM_EVENT_NORMAL:
case wm::WM_EVENT_MAXIMIZE:
UpdateWindow(window_state, GetMaximizedOrCenteredWindowType(window_state),
true /* animated */);
return;
case wm::WM_EVENT_SNAP_LEFT:
// Set bounds_changed_by_user to true to avoid WindowPositioner to auto
// place the window.
window_state->set_bounds_changed_by_user(true);
UpdateWindow(window_state,
GetSnappedWindowStateType(
window_state, mojom::WindowStateType::LEFT_SNAPPED),
false /* animated */);
return;
case wm::WM_EVENT_SNAP_RIGHT:
// Set bounds_changed_by_user to true to avoid WindowPositioner to auto
// place the window.
window_state->set_bounds_changed_by_user(true);
UpdateWindow(window_state,
GetSnappedWindowStateType(
window_state, mojom::WindowStateType::RIGHT_SNAPPED),
false /* animated */);
return;
case wm::WM_EVENT_MINIMIZE:
UpdateWindow(window_state, mojom::WindowStateType::MINIMIZED,
true /* animated */);
return;
case wm::WM_EVENT_SHOW_INACTIVE:
case wm::WM_EVENT_SYSTEM_UI_AREA_CHANGED:
return;
case wm::WM_EVENT_SET_BOUNDS: {
gfx::Rect bounds_in_parent =
(static_cast<const wm::SetBoundsEvent*>(event))->requested_bounds();
if (bounds_in_parent.IsEmpty())
return;
if (wm::IsDraggingTabs(window_state->window()) ||
IsTabDraggingSourceWindow(window_state->window())) {
// If the window is the current tab-dragged window or the current tab-
// dragged window's source window, we may need to update its bounds
// during dragging.
window_state->SetBoundsDirect(bounds_in_parent);
} else if (current_state_type_ == mojom::WindowStateType::MAXIMIZED) {
// Having a maximized window, it could have been created with an empty
// size and the caller should get his size upon leaving the maximized
// mode. As such we set the restore bounds to the requested bounds.
window_state->SetRestoreBoundsInParent(bounds_in_parent);
} else if (current_state_type_ != mojom::WindowStateType::MINIMIZED &&
current_state_type_ != mojom::WindowStateType::FULLSCREEN &&
current_state_type_ != mojom::WindowStateType::PINNED &&
current_state_type_ !=
mojom::WindowStateType::TRUSTED_PINNED &&
current_state_type_ != mojom::WindowStateType::LEFT_SNAPPED &&
current_state_type_ != mojom::WindowStateType::RIGHT_SNAPPED) {
// In all other cases (except for minimized windows) we respect the
// requested bounds and center it to a fully visible area on the screen.
bounds_in_parent = GetCenteredBounds(bounds_in_parent, window_state);
if (bounds_in_parent != window_state->window()->bounds()) {
const wm::SetBoundsEvent* bounds_event =
static_cast<const wm::SetBoundsEvent*>(event);
if (window_state->window()->IsVisible() && bounds_event->animate())
window_state->SetBoundsDirectAnimated(bounds_in_parent);
else
window_state->SetBoundsDirect(bounds_in_parent);
}
}
break;
}
case wm::WM_EVENT_ADDED_TO_WORKSPACE:
if (current_state_type_ != mojom::WindowStateType::MAXIMIZED &&
current_state_type_ != mojom::WindowStateType::FULLSCREEN &&
current_state_type_ != mojom::WindowStateType::MINIMIZED) {
mojom::WindowStateType new_state =
GetMaximizedOrCenteredWindowType(window_state);
UpdateWindow(window_state, new_state, true /* animated */);
}
break;
case wm::WM_EVENT_WORKAREA_BOUNDS_CHANGED:
if (current_state_type_ != mojom::WindowStateType::MINIMIZED)
UpdateBounds(window_state, true /* animated */);
break;
case wm::WM_EVENT_DISPLAY_BOUNDS_CHANGED:
// Don't animate on a screen rotation - just snap to new size.
if (current_state_type_ != mojom::WindowStateType::MINIMIZED)
UpdateBounds(window_state, false /* animated */);
break;
}
}
mojom::WindowStateType TabletModeWindowState::GetType() const {
return current_state_type_;
}
void TabletModeWindowState::AttachState(
wm::WindowState* window_state,
wm::WindowState::State* previous_state) {
current_state_type_ = previous_state->GetType();
gfx::Rect restore_bounds = GetRestoreBounds(window_state);
if (!restore_bounds.IsEmpty()) {
// We do not want to do a session restore to our window states. Therefore
// we tell the window to use the current default states instead.
SetWindowRestoreOverrides(window_state->window(), restore_bounds,
window_state->GetShowState());
}
// Initialize the state to a good preset.
if (current_state_type_ != mojom::WindowStateType::MAXIMIZED &&
current_state_type_ != mojom::WindowStateType::MINIMIZED &&
current_state_type_ != mojom::WindowStateType::FULLSCREEN &&
current_state_type_ != mojom::WindowStateType::PINNED &&
current_state_type_ != mojom::WindowStateType::TRUSTED_PINNED) {
UpdateWindow(window_state, GetMaximizedOrCenteredWindowType(window_state),
true /* animated */);
}
}
void TabletModeWindowState::DetachState(wm::WindowState* window_state) {
// From now on, we can use the default session restore mechanism again.
SetWindowRestoreOverrides(window_state->window(), gfx::Rect(),
ui::SHOW_STATE_NORMAL);
}
void TabletModeWindowState::UpdateWindow(wm::WindowState* window_state,
mojom::WindowStateType target_state,
bool animated) {
DCHECK(target_state == mojom::WindowStateType::MINIMIZED ||
target_state == mojom::WindowStateType::MAXIMIZED ||
target_state == mojom::WindowStateType::PINNED ||
target_state == mojom::WindowStateType::TRUSTED_PINNED ||
(target_state == mojom::WindowStateType::NORMAL &&
(!window_state->CanMaximize() ||
!!::wm::GetTransientParent(window_state->window()))) ||
target_state == mojom::WindowStateType::FULLSCREEN ||
target_state == mojom::WindowStateType::LEFT_SNAPPED ||
target_state == mojom::WindowStateType::RIGHT_SNAPPED);
if (current_state_type_ == target_state) {
if (target_state == mojom::WindowStateType::MINIMIZED)
return;
// If the state type did not change, update it accordingly.
UpdateBounds(window_state, animated);
return;
}
const mojom::WindowStateType old_state_type = current_state_type_;
current_state_type_ = target_state;
window_state->UpdateWindowPropertiesFromStateType();
window_state->NotifyPreStateTypeChange(old_state_type);
if (target_state == mojom::WindowStateType::MINIMIZED) {
::wm::SetWindowVisibilityAnimationType(
window_state->window(), wm::WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE);
window_state->window()->Hide();
if (window_state->IsActive())
window_state->Deactivate();
} else {
UpdateBounds(window_state, animated);
}
if ((window_state->window()->layer()->GetTargetVisibility() ||
old_state_type == mojom::WindowStateType::MINIMIZED) &&
!window_state->window()->layer()->visible()) {
// The layer may be hidden if the window was previously minimized. Make
// sure it's visible.
window_state->window()->Show();
}
window_state->NotifyPostStateTypeChange(old_state_type);
if (old_state_type == mojom::WindowStateType::PINNED ||
target_state == mojom::WindowStateType::PINNED ||
old_state_type == mojom::WindowStateType::TRUSTED_PINNED ||
target_state == mojom::WindowStateType::TRUSTED_PINNED) {
Shell::Get()->screen_pinning_controller()->SetPinnedWindow(
window_state->window());
}
}
mojom::WindowStateType TabletModeWindowState::GetMaximizedOrCenteredWindowType(
wm::WindowState* window_state) {
return (window_state->CanMaximize() &&
::wm::GetTransientParent(window_state->window()) == nullptr)
? mojom::WindowStateType::MAXIMIZED
: mojom::WindowStateType::NORMAL;
}
mojom::WindowStateType TabletModeWindowState::GetSnappedWindowStateType(
wm::WindowState* window_state,
mojom::WindowStateType target_state) {
DCHECK(target_state == mojom::WindowStateType::LEFT_SNAPPED ||
target_state == mojom::WindowStateType::RIGHT_SNAPPED);
return CanSnapInSplitview(window_state->window())
? target_state
: GetMaximizedOrCenteredWindowType(window_state);
}
void TabletModeWindowState::UpdateBounds(wm::WindowState* window_state,
bool animated) {
if (defer_bounds_updates_)
return;
// Do not update window's bounds if it's in tab-dragging process. The bounds
// will be updated later when the drag ends.
if (wm::IsDraggingTabs(window_state->window()))
return;
// Do not update minimized windows bounds until it was unminimized.
if (current_state_type_ == mojom::WindowStateType::MINIMIZED)
return;
gfx::Rect bounds_in_parent = GetBoundsInMaximizedMode(window_state);
// If we have a target bounds rectangle, we center it and set it
// accordingly.
if (!bounds_in_parent.IsEmpty() &&
bounds_in_parent != window_state->window()->bounds()) {
if (!window_state->window()->IsVisible() || !animated) {
window_state->SetBoundsDirect(bounds_in_parent);
} else {
if (use_zero_animation_type_) {
window_state->SetBoundsDirectCrossFade(bounds_in_parent,
gfx::Tween::ZERO);
return;
}
// If we animate (to) tablet mode, we want to use the cross fade to
// avoid flashing.
if (window_state->IsMaximized())
window_state->SetBoundsDirectCrossFade(bounds_in_parent);
else if (window_state->IsSnapped())
window_state->SetBoundsDirect(bounds_in_parent);
else
window_state->SetBoundsDirectAnimated(bounds_in_parent);
}
}
}
} // namespace ash