1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ash/wm/base_state.h"
6 
7 #include "ash/public/cpp/window_animation_types.h"
8 #include "ash/public/cpp/window_properties.h"
9 #include "ash/screen_util.h"
10 #include "ash/shell.h"
11 #include "ash/wm/overview/overview_controller.h"
12 #include "ash/wm/splitview/split_view_controller.h"
13 #include "ash/wm/splitview/split_view_utils.h"
14 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
15 #include "ash/wm/window_positioning_utils.h"
16 #include "ash/wm/wm_event.h"
17 #include "chromeos/ui/base/window_state_type.h"
18 #include "ui/aura/client/aura_constants.h"
19 #include "ui/aura/window.h"
20 
21 namespace ash {
22 
23 using ::chromeos::WindowStateType;
24 
BaseState(WindowStateType initial_state_type)25 BaseState::BaseState(WindowStateType initial_state_type)
26     : state_type_(initial_state_type) {}
27 BaseState::~BaseState() = default;
28 
OnWMEvent(WindowState * window_state,const WMEvent * event)29 void BaseState::OnWMEvent(WindowState* window_state, const WMEvent* event) {
30   if (event->IsWorkspaceEvent()) {
31     HandleWorkspaceEvents(window_state, event);
32     if (window_state->IsPip())
33       window_state->UpdatePipBounds();
34     return;
35   }
36   if ((window_state->IsTrustedPinned() || window_state->IsPinned()) &&
37       (event->type() != WM_EVENT_NORMAL && event->IsTransitionEvent())) {
38     // PIN state can be exited only by normal event.
39     return;
40   }
41 
42   if (event->IsCompoundEvent()) {
43     HandleCompoundEvents(window_state, event);
44     return;
45   }
46 
47   if (event->IsBoundsEvent()) {
48     HandleBoundsEvents(window_state, event);
49     return;
50   }
51   DCHECK(event->IsTransitionEvent());
52   HandleTransitionEvents(window_state, event);
53 }
54 
GetType() const55 WindowStateType BaseState::GetType() const {
56   return state_type_;
57 }
58 
59 // static
GetStateForTransitionEvent(const WMEvent * event)60 WindowStateType BaseState::GetStateForTransitionEvent(const WMEvent* event) {
61   switch (event->type()) {
62     case WM_EVENT_NORMAL:
63       return WindowStateType::kNormal;
64     case WM_EVENT_MAXIMIZE:
65       return WindowStateType::kMaximized;
66     case WM_EVENT_MINIMIZE:
67       return WindowStateType::kMinimized;
68     case WM_EVENT_FULLSCREEN:
69       return WindowStateType::kFullscreen;
70     case WM_EVENT_SNAP_LEFT:
71       return WindowStateType::kLeftSnapped;
72     case WM_EVENT_SNAP_RIGHT:
73       return WindowStateType::kRightSnapped;
74     case WM_EVENT_SHOW_INACTIVE:
75       return WindowStateType::kInactive;
76     case WM_EVENT_PIN:
77       return WindowStateType::kPinned;
78     case WM_EVENT_PIP:
79       return WindowStateType::kPip;
80     case WM_EVENT_TRUSTED_PIN:
81       return WindowStateType::kTrustedPinned;
82     default:
83       break;
84   }
85 #if !defined(NDEBUG)
86   if (event->IsWorkspaceEvent())
87     NOTREACHED() << "Can't get the state for Workspace event" << event->type();
88   if (event->IsCompoundEvent())
89     NOTREACHED() << "Can't get the state for Compound event:" << event->type();
90   if (event->IsBoundsEvent())
91     NOTREACHED() << "Can't get the state for Bounds event:" << event->type();
92 #endif
93   return WindowStateType::kNormal;
94 }
95 
96 // static
CenterWindow(WindowState * window_state)97 void BaseState::CenterWindow(WindowState* window_state) {
98   if (!window_state->IsNormalOrSnapped())
99     return;
100   aura::Window* window = window_state->window();
101   if (window_state->IsSnapped()) {
102     gfx::Rect center_in_screen = display::Screen::GetScreen()
103                                      ->GetDisplayNearestWindow(window)
104                                      .work_area();
105     gfx::Size size = window_state->HasRestoreBounds()
106                          ? window_state->GetRestoreBoundsInScreen().size()
107                          : window->bounds().size();
108     center_in_screen.ClampToCenteredSize(size);
109     window_state->SetRestoreBoundsInScreen(center_in_screen);
110     window_state->Restore();
111   } else {
112     gfx::Rect center_in_parent =
113         screen_util::GetDisplayWorkAreaBoundsInParent(window);
114     center_in_parent.ClampToCenteredSize(window->bounds().size());
115     const SetBoundsWMEvent event(center_in_parent,
116                                  /*animate=*/true);
117     window_state->OnWMEvent(&event);
118   }
119   // Centering window is treated as if a user moved and resized the window.
120   window_state->set_bounds_changed_by_user(true);
121 }
122 
123 // static
CycleSnap(WindowState * window_state,WMEventType event)124 void BaseState::CycleSnap(WindowState* window_state, WMEventType event) {
125   // For tablet mode, use |TabletModeWindowState::CycleTabletSnap|.
126   DCHECK(!Shell::Get()->tablet_mode_controller()->InTabletMode());
127 
128   WindowStateType desired_snap_state = event == WM_EVENT_CYCLE_SNAP_LEFT
129                                            ? WindowStateType::kLeftSnapped
130                                            : WindowStateType::kRightSnapped;
131   aura::Window* window = window_state->window();
132   // If |window| can be snapped but is not currently in |desired_snap_state|,
133   // then snap |window| to the side that corresponds to |desired_snap_state|.
134   if (window_state->CanSnap() &&
135       window_state->GetStateType() != desired_snap_state) {
136     if (Shell::Get()->overview_controller()->InOverviewSession()) {
137       // |window| must already be in split view, and so we do not need to check
138       // |SplitViewController::CanSnapWindow|, although in general it is more
139       // restrictive than |WindowState::CanSnap|.
140       DCHECK(SplitViewController::Get(window)->IsWindowInSplitView(window));
141       SplitViewController::Get(window)->SnapWindow(
142           window, desired_snap_state == WindowStateType::kLeftSnapped
143                       ? SplitViewController::LEFT
144                       : SplitViewController::RIGHT);
145     } else {
146       const WMEvent event(desired_snap_state == WindowStateType::kLeftSnapped
147                               ? WM_EVENT_SNAP_LEFT
148                               : WM_EVENT_SNAP_RIGHT);
149       window_state->OnWMEvent(&event);
150     }
151     return;
152   }
153   // If |window| is already in |desired_snap_state|, then unsnap |window|.
154   if (window_state->IsSnapped()) {
155     window_state->Restore();
156     return;
157   }
158   // If |window| cannot be snapped, then do a window bounce animation.
159   DCHECK(!window_state->CanSnap());
160   ::wm::AnimateWindow(window, ::wm::WINDOW_ANIMATION_TYPE_BOUNCE);
161 }
162 
UpdateMinimizedState(WindowState * window_state,WindowStateType previous_state_type)163 void BaseState::UpdateMinimizedState(WindowState* window_state,
164                                      WindowStateType previous_state_type) {
165   aura::Window* window = window_state->window();
166   if (window_state->IsMinimized()) {
167     // Save the previous show state when it is not minimized so that we can
168     // correctly restore it after exiting the minimized mode.
169     if (!IsMinimizedWindowStateType(previous_state_type)) {
170       // We must not save PIP to |kPreMinimizedShowStateKey|.
171       if (previous_state_type != WindowStateType::kPip)
172         window->SetProperty(aura::client::kPreMinimizedShowStateKey,
173                             ToWindowShowState(previous_state_type));
174       // We must not save MINIMIZED to |kPreMinimizedShowStateKey|.
175       else if (window->GetProperty(kPrePipWindowStateTypeKey) !=
176                WindowStateType::kMinimized)
177         window->SetProperty(
178             aura::client::kPreMinimizedShowStateKey,
179             ToWindowShowState(window->GetProperty(kPrePipWindowStateTypeKey)));
180     }
181     // Count minimizing a PIP window as dismissing it. Android apps in PIP mode
182     // don't exit when they are dismissed, they just go back to being a regular
183     // app, but minimized.
184     ::wm::SetWindowVisibilityAnimationType(
185         window, previous_state_type == WindowStateType::kPip
186                     ? WINDOW_VISIBILITY_ANIMATION_TYPE_FADE_IN_SLIDE_OUT
187                     : WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE);
188 
189     window->Hide();
190     if (window_state->IsActive())
191       window_state->Deactivate();
192   } else if ((window->layer()->GetTargetVisibility() ||
193               IsMinimizedWindowStateType(previous_state_type)) &&
194              !window->layer()->visible()) {
195     // The layer may be hidden if the window was previously minimized. Make
196     // sure it's visible.
197     window->Show();
198     if (IsMinimizedWindowStateType(previous_state_type) &&
199         !window_state->IsMaximizedOrFullscreenOrPinned()) {
200       window_state->set_unminimize_to_restore_bounds(false);
201     }
202   }
203 }
204 
GetSnappedWindowBoundsInParent(aura::Window * window,const WindowStateType state_type)205 gfx::Rect BaseState::GetSnappedWindowBoundsInParent(
206     aura::Window* window,
207     const WindowStateType state_type) {
208   gfx::Rect bounds_in_parent;
209   if (ShouldAllowSplitView()) {
210     bounds_in_parent =
211         SplitViewController::Get(window)->GetSnappedWindowBoundsInParent(
212             (state_type == WindowStateType::kLeftSnapped)
213                 ? SplitViewController::LEFT
214                 : SplitViewController::RIGHT,
215             window);
216   } else {
217     bounds_in_parent = (state_type == WindowStateType::kLeftSnapped)
218                            ? GetDefaultLeftSnappedWindowBoundsInParent(window)
219                            : GetDefaultRightSnappedWindowBoundsInParent(window);
220   }
221   return bounds_in_parent;
222 }
223 
224 }  // namespace ash
225