| // Copyright 2017 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/base_state.h" |
| |
| #include "ash/public/cpp/window_animation_types.h" |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/screen_util.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/wm/overview/overview_controller.h" |
| #include "ash/wm/snap_group/snap_group.h" |
| #include "ash/wm/snap_group/snap_group_controller.h" |
| #include "ash/wm/splitview/split_view_controller.h" |
| #include "ash/wm/splitview/split_view_types.h" |
| #include "ash/wm/splitview/split_view_utils.h" |
| #include "ash/wm/window_positioning_utils.h" |
| #include "ash/wm/window_util.h" |
| #include "ash/wm/wm_metrics.h" |
| #include "chromeos/ui/base/window_state_type.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/window.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/display/screen.h" |
| |
| namespace ash { |
| |
| using ::chromeos::WindowStateType; |
| |
| BaseState::BaseState(WindowStateType initial_state_type) |
| : state_type_(initial_state_type) {} |
| BaseState::~BaseState() = default; |
| |
| void BaseState::OnWMEvent(WindowState* window_state, const WMEvent* event) { |
| if (event->IsWorkspaceEvent()) { |
| HandleWorkspaceEvents(window_state, event); |
| if (window_state->IsSnapped() && !window_state->CanSnap()) |
| window_state->Restore(); |
| return; |
| } |
| if ((window_state->IsTrustedPinned() || window_state->IsPinned()) && |
| (event->type() != WM_EVENT_NORMAL && event->type() != WM_EVENT_RESTORE && |
| event->IsTransitionEvent())) { |
| // PIN state can be exited only by normal event or restore event. |
| return; |
| } |
| |
| if (event->IsCompoundEvent()) { |
| HandleCompoundEvents(window_state, event); |
| return; |
| } |
| |
| if (event->IsBoundsEvent()) { |
| HandleBoundsEvents(window_state, event); |
| return; |
| } |
| DCHECK(event->IsTransitionEvent()); |
| HandleTransitionEvents(window_state, event); |
| } |
| |
| WindowStateType BaseState::GetType() const { |
| return state_type_; |
| } |
| |
| // static |
| WindowStateType BaseState::GetStateForTransitionEvent(WindowState* window_state, |
| const WMEvent* event) { |
| switch (event->type()) { |
| case WM_EVENT_NORMAL: |
| if (window_state->window()->GetProperty(aura::client::kIsRestoringKey)) |
| return window_state->GetRestoreWindowState(); |
| return WindowStateType::kNormal; |
| case WM_EVENT_MAXIMIZE: |
| return WindowStateType::kMaximized; |
| case WM_EVENT_MINIMIZE: |
| return WindowStateType::kMinimized; |
| case WM_EVENT_FULLSCREEN: |
| return WindowStateType::kFullscreen; |
| case WM_EVENT_SNAP_PRIMARY: |
| return WindowStateType::kPrimarySnapped; |
| case WM_EVENT_SNAP_SECONDARY: |
| return WindowStateType::kSecondarySnapped; |
| case WM_EVENT_RESTORE: |
| return window_state->GetRestoreWindowState(); |
| case WM_EVENT_SHOW_INACTIVE: |
| return WindowStateType::kInactive; |
| case WM_EVENT_PIN: |
| return WindowStateType::kPinned; |
| case WM_EVENT_PIP: |
| return WindowStateType::kPip; |
| case WM_EVENT_FLOAT: |
| return WindowStateType::kFloated; |
| case WM_EVENT_TRUSTED_PIN: |
| return WindowStateType::kTrustedPinned; |
| default: |
| break; |
| } |
| #if !defined(NDEBUG) |
| if (event->IsWorkspaceEvent()) |
| NOTREACHED() << "Can't get the state for Workspace event" << event->type(); |
| if (event->IsCompoundEvent()) |
| NOTREACHED() << "Can't get the state for Compound event:" << event->type(); |
| if (event->IsBoundsEvent()) |
| NOTREACHED() << "Can't get the state for Bounds event:" << event->type(); |
| #endif |
| return WindowStateType::kNormal; |
| } |
| |
| // static |
| void BaseState::CycleSnap(WindowState* window_state, WMEventType event) { |
| auto* shell = Shell::Get(); |
| // For tablet mode, use `TabletModeWindowState::CycleTabletSnap`. |
| DCHECK(!display::Screen::GetScreen()->InTabletMode()); |
| |
| WindowStateType desired_snap_state = event == WM_EVENT_CYCLE_SNAP_PRIMARY |
| ? WindowStateType::kPrimarySnapped |
| : WindowStateType::kSecondarySnapped; |
| aura::Window* window = window_state->window(); |
| // If |window| can be snapped but is not currently in |desired_snap_state|, |
| // then snap |window| to the side that corresponds to |desired_snap_state|. |
| if (window_state->CanSnap() && |
| window_state->GetStateType() != desired_snap_state) { |
| const bool is_desired_primary_snapped = |
| desired_snap_state == WindowStateType::kPrimarySnapped; |
| if (shell->overview_controller()->InOverviewSession() && |
| !window_util::IsInFasterSplitScreenSetupSession(window)) { |
| // |window| must already be in split view, and so we do not need to check |
| // |SplitViewController::CanSnapWindow|, although in general it is more |
| // restrictive than |WindowState::CanSnap|. |
| DCHECK(SplitViewController::Get(window)->IsWindowInSplitView(window)); |
| SplitViewController::Get(window)->SnapWindow( |
| window, |
| is_desired_primary_snapped ? SnapPosition::kPrimary |
| : SnapPosition::kSecondary, |
| WindowSnapActionSource::kKeyboardShortcutToSnap); |
| } else { |
| const WindowSnapWMEvent wm_event( |
| is_desired_primary_snapped ? WM_EVENT_SNAP_PRIMARY |
| : WM_EVENT_SNAP_SECONDARY, |
| WindowSnapActionSource::kKeyboardShortcutToSnap); |
| window_state->OnWMEvent(&wm_event); |
| } |
| window_state->ReadOutWindowCycleSnapAction( |
| is_desired_primary_snapped ? IDS_WM_SNAP_WINDOW_TO_LEFT_ON_SHORTCUT |
| : IDS_WM_SNAP_WINDOW_TO_RIGHT_ON_SHORTCUT); |
| return; |
| } |
| // If |window| is already in |desired_snap_state|, then unsnap |window|. |
| if (window_state->IsSnapped()) { |
| window_state->Restore(); |
| window_state->ReadOutWindowCycleSnapAction( |
| IDS_WM_RESTORE_SNAPPED_WINDOW_ON_SHORTCUT); |
| return; |
| } |
| // If |window| cannot be snapped, then do a window bounce animation. |
| DCHECK(!window_state->CanSnap()); |
| ::wm::AnimateWindow(window, ::wm::WINDOW_ANIMATION_TYPE_BOUNCE); |
| } |
| |
| void BaseState::UpdateMinimizedState(WindowState* window_state, |
| WindowStateType previous_state_type) { |
| aura::Window* window = window_state->window(); |
| if (window_state->IsMinimized()) { |
| // Count minimizing a PIP window as dismissing it. Android apps in PIP mode |
| // don't exit when they are dismissed, they just go back to being a regular |
| // app, but minimized. |
| ::wm::SetWindowVisibilityAnimationType( |
| window, previous_state_type == WindowStateType::kPip |
| ? WINDOW_VISIBILITY_ANIMATION_TYPE_FADE_IN_SLIDE_OUT |
| : WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE); |
| |
| window->Hide(); |
| if (window_state->IsActive()) |
| window_state->Deactivate(); |
| } else if ((window->layer()->GetTargetVisibility() || |
| IsMinimizedWindowStateType(previous_state_type)) && |
| !window->layer()->visible()) { |
| // The layer may be hidden if the window was previously minimized. Make |
| // sure it's visible. |
| window->Show(); |
| if (IsMinimizedWindowStateType(previous_state_type) && |
| !window_state->IsMaximizedOrFullscreenOrPinned()) { |
| window_state->set_unminimize_to_restore_bounds(false); |
| } |
| } |
| } |
| |
| gfx::Rect BaseState::GetSnappedWindowBoundsInParent( |
| aura::Window* window, |
| const WindowStateType state_type, |
| float snap_ratio) { |
| CHECK(chromeos::IsSnappedWindowStateType(state_type)); |
| if (auto* snap_group_controller = SnapGroupController::Get()) { |
| if (auto* snap_group = |
| snap_group_controller->GetSnapGroupForGivenWindow(window)) { |
| // If `window` belongs to a snap group, the snap group should manage its |
| // bounds. Bounds in root are the same as in parent for snapped windows. |
| return snap_group->GetSnappedWindowBoundsInRoot(window, state_type, |
| snap_ratio); |
| } |
| } |
| |
| if (auto* split_view_controller = SplitViewController::Get(window); |
| split_view_controller->IsWindowInSplitView(window) || |
| Shell::Get()->IsInTabletMode()) { |
| // In tablet mode `SplitViewController` always manages snapped windows, in |
| // clamshell state it only manages windows in split view. |
| return split_view_controller->GetSnappedWindowBoundsInParent( |
| (state_type == WindowStateType::kPrimarySnapped) |
| ? SnapPosition::kPrimary |
| : SnapPosition::kSecondary, |
| window, snap_ratio); |
| } |
| |
| return ash::GetSnappedWindowBoundsInParent( |
| window, |
| state_type == WindowStateType::kPrimarySnapped ? SnapViewType::kPrimary |
| : SnapViewType::kSecondary, |
| snap_ratio); |
| } |
| |
| void BaseState::HandleWindowSnapping( |
| WindowState* window_state, |
| WMEventType event_type, |
| WindowSnapActionSource snap_action_source) { |
| DCHECK(event_type == WM_EVENT_SNAP_PRIMARY || |
| event_type == WM_EVENT_SNAP_SECONDARY); |
| DCHECK(window_state->CanSnap()); |
| |
| window_state->SetBoundsChangedByUser(true); |
| aura::Window* window = window_state->window(); |
| // `SplitViewController` will decide if the window needs to be snapped in |
| // split view. |
| SplitViewController::Get(window)->OnSnapEvent(window, event_type, |
| snap_action_source); |
| } |
| |
| } // namespace ash |