| // 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(); |
| break; |
| } |
| } |
| |
| 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(); |
| break; |
| } |
| } |
| |
| 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) { |
| 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 |