// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/wm/default_state.h"
#include "ash/public/cpp/metrics_util.h"
#include "ash/root_window_controller.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/float/float_controller.h"
#include "ash/wm/pip/pip_controller.h"
#include "ash/wm/screen_pinning_controller.h"
#include "ash/wm/splitview/split_view_metrics_controller.h"
#include "ash/wm/window_positioning_utils.h"
#include "ash/wm/window_state_delegate.h"
#include "ash/wm/window_state_util.h"
#include "ash/wm/window_util.h"
#include "ash/wm/wm_event.h"
#include "ash/wm/workspace_controller.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "chromeos/ui/base/window_state_type.h"
#include "chromeos/ui/wm/window_util.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/compositor/animation_throughput_reporter.h"
#include "ui/compositor/layer.h"
#include "ui/display/display_observer.h"
#include "ui/display/screen.h"
#include "ui/display/types/display_constants.h"
#include "ui/wm/core/window_animations.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
using ::chromeos::WindowStateType;
// When a window that has restore bounds at least as large as a work area is
// unmaximized, inset the bounds slightly so that they are not exactly the same.
// This makes it easier to resize the window.
const int kMaximizedWindowInset = 10; // DIPs.
constexpr char kSnapWindowSmoothnessHistogramName[] =
"Ash.Window.AnimationSmoothness.Snap";
constexpr char kSnapWindowDeviceOrientationHistogramName[] =
"Ash.Window.Snap.DeviceOrientation";
gfx::Size GetWindowMaximumSize(aura::Window* window) {
return window->delegate() ? window->delegate()->GetMaximumSize()
: gfx::Size();
}
// Moves the window to the specified display if necessary.
void MoveWindowToDisplayAsNeeded(aura::Window* window, int64_t display_id) {
if (!window || display_id == display::kInvalidDisplayId) {
return;
}
aura::Window* root = Shell::GetRootWindowForDisplayId(display_id);
if (!root || root == window->GetRootWindow()) {
// No need to move unless window is rooted in a different display.
return;
}
root->GetChildById(window->parent()->GetId())->AddChild(window);
}
// Ensures the window is moved to the correct display when entering the
// next state, taking into account whether it's restoring or not.
void EnsureWindowInCorrectDisplay(WindowState* window_state,
WindowStateType previous_state_type) {
if (window_state->IsMinimized()) {
return;
}
// When restoring, we want to use the restore bounds to calculate which
// display it should be moved to, hence the check for IsRestoring() and
// HasRestoreBounds(), otherwise we move the window according to its current
// bounds. A special case is when we come out of minimized state, where the
// current bounds is the bounds of the new state we're going back to, so we
// use that to move the window.
// The check for IsMaximizedOrFullscreenOrPinned() was moved from
// EnterToNextState(), which preserves some legacy behavior that may not be
// relevant anymore. It may be needed for the case of maximizing to another
// display then restoring, which should remain on the other display.
// TODO(aluh): Look into removing check for IsMaximizedOrFullscreenOrPinned().
const gfx::Rect window_bounds =
((window_state->IsMaximizedOrFullscreenOrPinned() ||
window_state->IsRestoring(previous_state_type)) &&
window_state->HasRestoreBounds() &&
!chromeos::IsMinimizedWindowStateType(previous_state_type))
? window_state->GetRestoreBoundsInScreen()
: window_state->GetCurrentBoundsInScreen();
// Move only if the window 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 = display::Screen::GetScreen()
->GetDisplayNearestWindow(window_state->window())
.bounds();
if (!display_area.Intersects(window_bounds)) {
int64_t display_id =
display::Screen::GetScreen()->GetDisplayMatching(window_bounds).id();
MoveWindowToDisplayAsNeeded(window_state->window(), display_id);
}
}
// Returns true if next state should be entered from the current state.
bool ShouldEnterNextState(WindowStateType current_state,
WindowStateType next_state,
WindowState* window_state) {
if (current_state != next_state) {
return true;
}
// This handles the case where a window is already fullscreen on a display
// and we want to fullscreen it on a different display.
// TODO(aluh): Consider handling earlier, before call to EnterToNextState(),
// so we don't have to special case here. May run into tricky restore
// state/bounds corner cases.
if (next_state == chromeos::WindowStateType::kFullscreen &&
window_state->GetFullscreenTargetDisplayId() !=
display::kInvalidDisplayId) {
return true;
}
return false;
}
} // namespace
DefaultState::DefaultState(WindowStateType initial_state_type)
: BaseState(initial_state_type) {}
DefaultState::~DefaultState() = default;
void DefaultState::AttachState(WindowState* window_state,
WindowState::State* state_in_previous_mode) {
DCHECK_EQ(stored_window_state_, window_state);
// If previous state is unminimized but window state is minimized, sync window
// state to unminimized.
if (window_state->IsMinimized() && !chromeos::IsMinimizedWindowStateType(
state_in_previous_mode->GetType())) {
aura::Window* window = window_state->window();
window->SetProperty(
aura::client::kShowStateKey,
window->GetProperty(aura::client::kRestoreShowStateKey));
}
ReenterToCurrentState(window_state, state_in_previous_mode);
// If the display has changed while in the another mode,
// we need to let windows know the change.
display::Display current_display =
display::Screen::GetScreen()->GetDisplayNearestWindow(
window_state->window());
if (stored_display_state_.bounds() != current_display.bounds()) {
const DisplayMetricsChangedWMEvent event(
display::DisplayObserver::DISPLAY_METRIC_BOUNDS);
window_state->OnWMEvent(&event);
} else if (stored_display_state_.work_area() != current_display.work_area()) {
const DisplayMetricsChangedWMEvent event(
display::DisplayObserver::DISPLAY_METRIC_WORK_AREA);
window_state->OnWMEvent(&event);
}
}
void DefaultState::DetachState(WindowState* window_state) {
stored_window_state_ = window_state;
stored_bounds_ = window_state->window()->bounds();
stored_restore_bounds_ = window_state->HasRestoreBounds()
? window_state->GetRestoreBoundsInParent()
: gfx::Rect();
// Remember the display state so that in case of the display change
// while in the other mode, we can perform necessary action to
// restore the window state to the proper state for the current
// display.
stored_display_state_ = display::Screen::GetScreen()->GetDisplayNearestWindow(
window_state->window());
}
void DefaultState::HandleWorkspaceEvents(WindowState* window_state,
const WMEvent* event) {
switch (event->type()) {
case WM_EVENT_ADDED_TO_WORKSPACE: {
// 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 a window is opened as maximized or fullscreen, its bounds may be
// empty, so update the bounds now before checking empty.
// TODO(minch): Check whether we can consolidate with the check inside
// UpdateBoundsForDisplayOrWorkAreaBoundsChange before doing the
// adjustment.
if (window_state->is_dragged() ||
window_state->allow_set_bounds_direct() ||
SetMaximizedOrFullscreenBounds(window_state)) {
return;
}
aura::Window* window = window_state->window();
gfx::Rect bounds = window->bounds();
// When window is added to a workspace, |bounds| may be not the original
// not-changed-by-user bounds, for example a resized bounds truncated by
// available workarea. If the window is visible on all desks, its
// bounds are global across workspaces so don't restore to pre-added
// bounds.
if (window_state->pre_added_to_workspace_window_bounds() &&
!desks_util::IsWindowVisibleOnAllWorkspaces(window)) {
bounds = *window_state->pre_added_to_workspace_window_bounds();
}
// Don't adjust window bounds if the bounds are empty as this
// happens when a new views::Widget is created.
if (bounds.IsEmpty())
return;
// Only windows of type WINDOW_TYPE_NORMAL need to be adjusted to have
// minimum visibility, because they are positioned by the user and the
// user should always be able to interact with them. Other windows are
// positioned programmatically.
if (!window_state->IsUserPositionable())
return;
// Use entire display instead of workarea. The logic ensures 30%
// visibility which should be enough to see where the window gets
// moved.
const gfx::Rect display_area =
screen_util::GetDisplayBoundsInParent(window);
AdjustBoundsToEnsureMinimumWindowVisibility(
display_area, /*client_controlled=*/false, &bounds);
window_state->AdjustSnappedBoundsForDisplayWorkspaceChange(&bounds);
window_state->SetBoundsConstrained(bounds);
return;
}
case WM_EVENT_DISPLAY_METRICS_CHANGED: {
const DisplayMetricsChangedWMEvent* display_event =
event->AsDisplayMetricsChangedWMEvent();
if (display_event->display_bounds_changed()) {
// When display bounds has changed, make sure the entire window is fully
// visible.
UpdateBoundsForDisplayOrWorkAreaBoundsChange(
window_state, /*ensure_full_window_visibility=*/true);
} else if (display_event->work_area_changed()) {
// Don't resize the maximized window when the desktop is covered
// by fullscreen window. crbug.com/504299.
// TODO(afakhry): Decide whether we want the active desk's workspace, or
// the workspace of the desk of `window_state->window()`.
// For now use the active desk's.
auto* workspace_controller = GetActiveWorkspaceController(
window_state->window()->GetRootWindow());
DCHECK(workspace_controller);
const bool in_fullscreen = workspace_controller->GetWindowState() ==
WorkspaceWindowState::kFullscreen;
if (in_fullscreen && window_state->IsMaximized()) {
return;
}
UpdateBoundsForDisplayOrWorkAreaBoundsChange(
window_state,
/*ensure_full_window_visibility=*/false);
}
return;
}
default:
NOTREACHED() << "Unknown event:" << event->type();
}
}
void DefaultState::HandleCompoundEvents(WindowState* window_state,
const WMEvent* event) {
aura::Window* window = window_state->window();
switch (event->type()) {
case WM_EVENT_TOGGLE_MAXIMIZE_CAPTION:
ToggleMaximizeCaption(window_state);
return;
case WM_EVENT_TOGGLE_MAXIMIZE:
ToggleMaximize(window_state);
return;
case WM_EVENT_TOGGLE_VERTICAL_MAXIMIZE: {
gfx::Rect work_area =
screen_util::GetDisplayWorkAreaBoundsInParent(window);
// Maximize vertically if:
// - The window does not have a max height defined.
// - The window is floated or has the normal state type. Snapped windows
// are excluded because they are already maximized vertically and
// reverting to the restored bounds looks weird.
if (GetWindowMaximumSize(window).height() != 0)
return;
if (!window_state->IsNormalStateType() && !window_state->IsFloated())
return;
if (!window_state->VerticallyShrinkWindow(work_area)) {
gfx::Rect restore_bounds = window->GetTargetBounds();
const gfx::Rect new_bounds =
gfx::Rect(window->bounds().x(), work_area.y(),
window->bounds().width(), work_area.height());
window_state->SetBoundsDirectCrossFade(new_bounds);
if (!window_state->HasRestoreBounds())
window_state->SetRestoreBoundsInParent(restore_bounds);
}
return;
}
case WM_EVENT_TOGGLE_HORIZONTAL_MAXIMIZE: {
// Maximize horizontally if:
// - The window does not have a max width defined.
// - The window is snapped or floated or has the normal state type.
if (GetWindowMaximumSize(window).width() != 0)
return;
if (!window_state->IsNormalOrSnapped() && !window_state->IsFloated())
return;
gfx::Rect work_area =
screen_util::GetDisplayWorkAreaBoundsInParent(window);
if (!window_state->HorizontallyShrinkWindow(work_area)) {
gfx::Rect new_bounds(work_area.x(), window->bounds().y(),
work_area.width(), window->bounds().height());
gfx::Rect restore_bounds = window->GetTargetBounds();
if (window_state->IsSnapped()) {
window_state->SetRestoreBoundsInParent(window->bounds());
window_state->Restore();
// The restore logic prevents a window from being restored to bounds
// which match the workspace bounds exactly so it is necessary to set
// the bounds again below.
}
if (!window_state->HasRestoreBounds())
window_state->SetRestoreBoundsInParent(restore_bounds);
window_state->SetBoundsDirectCrossFade(new_bounds);
}
return;
}
case WM_EVENT_TOGGLE_FULLSCREEN:
ToggleFullScreen(window_state, window_state->delegate());
return;
case WM_EVENT_CYCLE_SNAP_PRIMARY:
case WM_EVENT_CYCLE_SNAP_SECONDARY:
CycleSnap(window_state, event->type());
return;
default:
NOTREACHED() << "Unknown event:" << event->type();
}
}
void DefaultState::HandleBoundsEvents(WindowState* window_state,
const WMEvent* event) {
switch (event->type()) {
case WM_EVENT_SET_BOUNDS: {
const SetBoundsWMEvent* set_bounds_event =
static_cast<const SetBoundsWMEvent*>(event);
SetBounds(window_state, set_bounds_event);
} break;
default:
NOTREACHED() << "Unknown event:" << event->type();
}
}
void DefaultState::HandleTransitionEvents(WindowState* window_state,
const WMEvent* event) {
WindowStateType current_state_type = window_state->GetStateType();
WindowStateType next_state_type =
GetStateForTransitionEvent(window_state, event);
if (event->IsPinEvent()) {
// If there already is a pinned window, it is not allowed to set it
// to this window.
// TODO(hidehiko): If a system modal window is openening, the pinning
// probably should fail.
if (Shell::Get()->screen_pinning_controller()->IsPinned()) {
LOG(ERROR) << "An PIN event will be failed since another window is "
<< "already in pinned mode.";
next_state_type = current_state_type;
}
}
const WMEventType type = event->type();
// Not all windows can be floated.
if (type == WM_EVENT_FLOAT &&
!chromeos::wm::CanFloatWindow(window_state->window())) {
return;
}
if ((type == WM_EVENT_SNAP_PRIMARY || type == WM_EVENT_SNAP_SECONDARY) &&
window_state->CanSnap()) {
HandleWindowSnapping(window_state, type,
event->AsSnapEvent()->snap_action_source());
}
if (next_state_type == current_state_type && window_state->IsSnapped()) {
DCHECK(window_state->snap_ratio());
gfx::Rect snapped_bounds =
GetSnappedWindowBoundsInParent(window_state->window(),
event->type() == WM_EVENT_SNAP_PRIMARY
? WindowStateType::kPrimarySnapped
: WindowStateType::kSecondarySnapped,
*window_state->snap_ratio());
window_state->SetBoundsDirectAnimated(snapped_bounds);
return;
}
if (IsSnappedWindowStateType(next_state_type)) {
const bool is_restoring =
window_state->window()->GetProperty(aura::client::kIsRestoringKey) ||
type == WM_EVENT_RESTORE;
if (is_restoring) {
window_state->RecordWindowSnapActionSource(
WindowSnapActionSource::kSnapByWindowStateRestore);
} else {
CHECK(event->IsSnapEvent());
window_state->RecordWindowSnapActionSource(
event->AsSnapEvent()->snap_action_source());
}
}
std::optional<chromeos::FloatStartLocation> float_start_location =
event->AsFloatEvent()
? std::make_optional(event->AsFloatEvent()->float_start_location())
: std::nullopt;
EnterToNextState(window_state, next_state_type, float_start_location);
}
bool DefaultState::SetMaximizedOrFullscreenBounds(WindowState* window_state) {
DCHECK(!window_state->is_dragged());
DCHECK(!window_state->allow_set_bounds_direct());
if (window_state->IsMaximized()) {
window_state->SetBoundsDirect(
screen_util::GetMaximizedWindowBoundsInParent(window_state->window()));
return true;
}
if (window_state->IsFullscreen() || window_state->IsPinned()) {
window_state->SetBoundsDirect(
screen_util::GetFullscreenWindowBoundsInParent(window_state->window()));
return true;
}
return false;
}
void DefaultState::SetBounds(WindowState* window_state,
const SetBoundsWMEvent* event) {
// TODO(andreaorru|oshima): Fix dragging code so that if a window is dragging
// tabs, it contains drag details, and `is_dragged` is true for its state.
// Then we can simplify this condition and remove `IsDraggingTabs`.
bool is_dragged = window_state->is_dragged() ||
window_util::IsDraggingTabs(window_state->window());
if (is_dragged || window_state->allow_set_bounds_direct()) {
if (event->animate()) {
window_state->SetBoundsDirectAnimated(event->requested_bounds_in_parent(),
event->duration());
} else {
// TODO(oshima|varkha): Is this still needed? crbug.com/485612.
window_state->SetBoundsDirect(event->requested_bounds_in_parent());
}
} else if (!SetMaximizedOrFullscreenBounds(window_state)) {
if (event->animate()) {
window_state->SetBoundsDirectAnimated(event->requested_bounds_in_parent(),
event->duration());
} else {
window_state->SetBoundsConstrained(event->requested_bounds_in_parent());
// Update the restore size if the bounds is updated by PIP itself.
if (window_state->IsPip() && window_state->HasRestoreBounds()) {
gfx::Rect restore_bounds = window_state->GetRestoreBoundsInScreen();
restore_bounds.set_size(
window_state->window()->GetTargetBounds().size());
window_state->SetRestoreBoundsInScreen(restore_bounds);
}
}
}
}
void DefaultState::EnterToNextState(
WindowState* window_state,
WindowStateType next_state_type,
std::optional<chromeos::FloatStartLocation> float_start_location) {
if (!ShouldEnterNextState(state_type_, next_state_type, window_state)) {
return;
}
const bool is_previous_normal_type =
window_state->IsNonVerticalOrHorizontalMaximizedNormalState();
WindowStateType previous_state_type = state_type_;
state_type_ = next_state_type;
window_state->UpdateWindowPropertiesFromStateType();
window_state->NotifyPreStateTypeChange(previous_state_type);
auto* const float_controller = Shell::Get()->float_controller();
auto* window = window_state->window();
if (state_type_ == WindowStateType::kFloated) {
DCHECK_EQ(next_state_type, WindowStateType::kFloated);
// Add window to float container.
float_controller->FloatImpl(window);
}
// Unfloat floated window when exiting float state to another state.
if (previous_state_type == WindowStateType::kFloated) {
float_controller->UnfloatImpl(window);
}
// Don't update the window if the window is detached from parent.
// This can happen during dragging.
// TODO(oshima): This was added for DOCKED windows. Investigate if
// we still need this.
gfx::Rect restore_bounds_in_screen;
if (window_state->window()->parent()) {
// Save the current bounds as the restore bounds if changing from normal
// state (not horizontal/vertical maximized) to other window states.
if (is_previous_normal_type && !window_state->IsNormalStateType()) {
window_state->SaveCurrentBoundsForRestore();
}
// When restoring from the minimized state to horizontal/vertical maximized.
// We want to restore to the previous horizontal/vertical maximized bounds
// and keep its restore bounds.(E.g, double clicking the window border will
// set the window to be horizontal/vertical maximized and set the restore
// bounds).
if (previous_state_type == WindowStateType::kMinimized &&
window_state->IsVerticalOrHorizontalMaximized() &&
!window_state->unminimize_to_restore_bounds()) {
restore_bounds_in_screen = window_state->GetRestoreBoundsInScreen();
window_state->SaveCurrentBoundsForRestore();
}
UpdateBoundsFromState(window_state, previous_state_type,
float_start_location);
UpdateMinimizedState(window_state, previous_state_type);
}
window_state->NotifyPostStateTypeChange(previous_state_type);
if (!restore_bounds_in_screen.IsEmpty()) {
// Set the restore bounds back after unminimize the window to normal state.
// Usually normal state window should have no restore bounds unless it was
// horizontal/vertical maximized before minimize.
window_state->SetRestoreBoundsInScreen(restore_bounds_in_screen);
} else if (window_state->window_state_restore_history().empty()) {
// Clear the restore bounds when restore history stack has been cleared to
// keep them consistent. Do this after window state updates as restore
// history stack will be updated during the process.
window_state->ClearRestoreBounds();
}
if (IsPinnedWindowStateType(next_state_type) ||
IsPinnedWindowStateType(previous_state_type)) {
Shell::Get()->screen_pinning_controller()->SetPinnedWindow(
window_state->window());
if (window_state->delegate())
window_state->delegate()->ToggleLockedFullscreen(window_state);
}
}
void DefaultState::ReenterToCurrentState(
WindowState* window_state,
WindowState::State* state_in_previous_mode) {
WindowStateType previous_state_type = state_in_previous_mode->GetType();
// A state change should not move a window into or out of full screen or
// pinned or float since these are "special mode" the user wanted to be in and
// should be respected as such.
if (IsFullscreenOrPinnedWindowStateType(previous_state_type) ||
IsFullscreenOrPinnedWindowStateType(state_type_) ||
previous_state_type == WindowStateType::kFloated ||
state_type_ == WindowStateType::kFloated) {
state_type_ = previous_state_type;
}
window_state->UpdateWindowPropertiesFromStateType();
window_state->NotifyPreStateTypeChange(previous_state_type);
if (IsNormalWindowStateType(state_type_) && !stored_bounds_.IsEmpty()) {
// Use the restore mechanism to set the bounds for
// the window in normal state. This also covers unminimize case.
window_state->SetRestoreBoundsInParent(stored_bounds_);
}
UpdateBoundsFromState(window_state, state_in_previous_mode->GetType(),
/*float_start_location=*/std::nullopt);
UpdateMinimizedState(window_state, state_in_previous_mode->GetType());
// Then restore the restore bounds to their previous value.
if (!stored_restore_bounds_.IsEmpty())
window_state->SetRestoreBoundsInParent(stored_restore_bounds_);
else
window_state->ClearRestoreBounds();
window_state->NotifyPostStateTypeChange(previous_state_type);
}
void DefaultState::UpdateBoundsFromState(
WindowState* window_state,
WindowStateType previous_state_type,
std::optional<chromeos::FloatStartLocation> float_start_location) {
aura::Window* window = window_state->window();
gfx::Rect bounds_in_parent;
// A window can be rooted in a different display than its bounds, in cases
// such as creating a new window with bounds in a different display, or
// restoring to a previous state that was in a different display.
EnsureWindowInCorrectDisplay(window_state, previous_state_type);
switch (state_type_) {
case WindowStateType::kPrimarySnapped:
case WindowStateType::kSecondarySnapped:
DCHECK(window_state->snap_ratio());
bounds_in_parent = GetSnappedWindowBoundsInParent(
window_state->window(), state_type_, *window_state->snap_ratio());
base::UmaHistogramEnumeration(
kSnapWindowDeviceOrientationHistogramName,
display::Screen::GetScreen()
->GetDisplayNearestWindow(window)
.is_landscape()
? SplitViewMetricsController::DeviceOrientation::kLandscape
: SplitViewMetricsController::DeviceOrientation::kPortrait);
break;
case WindowStateType::kDefault:
case WindowStateType::kNormal: {
gfx::Rect work_area_in_parent =
screen_util::GetDisplayWorkAreaBoundsInParent(window);
if (window_state->HasRestoreBounds()) {
bounds_in_parent = window_state->GetRestoreBoundsInParent();
// Check if the |window|'s restored size is bigger than the working area
// This may happen if a window was resized to maximized bounds or if the
// display resolution changed while the window was maximized.
if (previous_state_type == WindowStateType::kMaximized &&
bounds_in_parent.width() >= work_area_in_parent.width() &&
bounds_in_parent.height() >= work_area_in_parent.height()) {
bounds_in_parent = work_area_in_parent;
bounds_in_parent.Inset(kMaximizedWindowInset);
}
} else {
bounds_in_parent = window->bounds();
}
// Make sure that part of the window is always visible.
if (!window_state->is_dragged()) {
// Avoid doing this while the window is being dragged as its root
// window hasn't been updated yet in the case of dragging to another
// display. crbug.com/666836.
AdjustBoundsToEnsureMinimumWindowVisibility(work_area_in_parent,
/*client_controlled=*/false,
&bounds_in_parent);
}
break;
}
case WindowStateType::kMaximized:
bounds_in_parent = screen_util::GetMaximizedWindowBoundsInParent(window);
break;
case WindowStateType::kFullscreen:
case WindowStateType::kPinned:
case WindowStateType::kTrustedPinned:
MoveWindowToDisplayAsNeeded(window_state->window(),
window_state->GetFullscreenTargetDisplayId());
bounds_in_parent = screen_util::GetFullscreenWindowBoundsInParent(window);
break;
case WindowStateType::kMinimized:
break;
case WindowStateType::kFloated: {
// When a floated window is previously minimized, un-minimize will restore
// the float state with previous floated bounds, without re-calculating
// preferred bounds.
if (previous_state_type == WindowStateType::kMinimized) {
bounds_in_parent = window->bounds();
} else {
// Default state can be used for always on top windows in tablet mode,
// which are not managed by the tablet mode window manager. Float state
// is not allowed for always on top but this may be called when a
// floated window has been put into always on top and we have not yet
// exited float state yet. See http://b/317064996 for more details.
// TODO(http://b/325282588): `DefaultState` should be for clamshell
// (non-ARC apps) only. See if `TabletModeWindowState` can handle
// always-on-top window gracefully.
bounds_in_parent =
window->GetProperty(aura::client::kZOrderingKey) !=
ui::ZOrderLevel::kNormal
? window->bounds()
: Shell::Get()
->float_controller()
->GetFloatWindowClamshellBounds(
window,
float_start_location.value_or(
chromeos::FloatStartLocation::kBottomRight));
}
break;
}
case WindowStateType::kInactive:
case WindowStateType::kPip:
return;
}
if (window_state->IsMinimized())
return;
if (bool to_float = state_type_ == WindowStateType::kFloated;
to_float || previous_state_type == WindowStateType::kFloated) {
// Float and unfloat have their own animation.
window_state->SetBoundsDirectCrossFade(bounds_in_parent, to_float);
} else if (IsMinimizedWindowStateType(previous_state_type) ||
window_state->IsFullscreen() || window_state->IsPinned() ||
window_state->bounds_animation_type() ==
WindowState::BoundsChangeAnimationType::kNone) {
window_state->SetBoundsDirect(bounds_in_parent);
} else if (window_state->IsMaximized() ||
IsMaximizedOrFullscreenOrPinnedWindowStateType(
previous_state_type)) {
window_state->SetBoundsDirectCrossFade(bounds_in_parent);
} else if (window_state->is_dragged()) {
// SetBoundsDirectAnimated does not work when the window gets reparented.
// TODO(oshima): Consider fixing it and re-enable the animation.
window_state->SetBoundsDirect(bounds_in_parent);
} else {
// Record smoothness of the snapping animation if the size of the window
// changes.
std::optional<ui::AnimationThroughputReporter> reporter;
if (window_state->IsSnapped() &&
bounds_in_parent.size() != window->bounds().size()) {
reporter.emplace(
window_state->window()->layer()->GetAnimator(),
metrics_util::ForSmoothnessV3(base::BindRepeating([](int smoothness) {
UMA_HISTOGRAM_PERCENTAGE(kSnapWindowSmoothnessHistogramName,
smoothness);
})));
}
window_state->SetBoundsDirectAnimated(bounds_in_parent);
}
}
void DefaultState::UpdateBoundsForDisplayOrWorkAreaBoundsChange(
WindowState* window_state,
bool ensure_full_window_visibility) {
if (window_state->is_dragged() || window_state->allow_set_bounds_direct() ||
window_state->ignore_keyboard_bounds_change() ||
SetMaximizedOrFullscreenBounds(window_state)) {
return;
}
const gfx::Rect work_area_in_parent =
screen_util::GetDisplayWorkAreaBoundsInParent(window_state->window());
gfx::Rect bounds = window_state->window()->GetTargetBounds();
if (ensure_full_window_visibility)
bounds.AdjustToFit(work_area_in_parent);
else if (!wm::GetTransientParent(window_state->window()) &&
!(window_state->IsPip() &&
Shell::Get()->pip_controller()->is_tucked())) {
AdjustBoundsToEnsureMinimumWindowVisibility(
work_area_in_parent, /*client_controlled=*/false, &bounds);
}
window_state->AdjustSnappedBoundsForDisplayWorkspaceChange(&bounds);
if (window_state->window()->GetTargetBounds() == bounds)
return;
if (window_state->bounds_animation_type() ==
WindowState::BoundsChangeAnimationType::kNone) {
window_state->SetBoundsDirect(bounds);
} else {
window_state->SetBoundsDirectAnimated(bounds);
}
}
} // namespace ash