| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 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/common/wm/default_state.h" | |
| 6 | |
| 7 #include "ash/common/ash_switches.h" | |
| 8 #include "ash/common/wm/dock/docked_window_layout_manager.h" | |
| 9 #include "ash/common/wm/window_animation_types.h" | |
| 10 #include "ash/common/wm/window_parenting_utils.h" | |
| 11 #include "ash/common/wm/window_positioning_utils.h" | |
| 12 #include "ash/common/wm/window_state.h" | |
| 13 #include "ash/common/wm/window_state_delegate.h" | |
| 14 #include "ash/common/wm/window_state_util.h" | |
| 15 #include "ash/common/wm/wm_event.h" | |
| 16 #include "ash/common/wm/wm_screen_util.h" | |
| 17 #include "ash/common/wm_shell.h" | |
| 18 #include "ash/common/wm_window.h" | |
| 19 #include "ash/public/cpp/shell_window_ids.h" | |
| 20 #include "ash/root_window_controller.h" | |
| 21 #include "ui/display/display.h" | |
| 22 #include "ui/display/screen.h" | |
| 23 | |
| 24 namespace ash { | |
| 25 namespace wm { | |
| 26 namespace { | |
| 27 | |
| 28 // This specifies how much percent (30%) of a window rect | |
| 29 // must be visible when the window is added to the workspace. | |
| 30 const float kMinimumPercentOnScreenArea = 0.3f; | |
| 31 | |
| 32 // When a window that has restore bounds at least as large as a work area is | |
| 33 // unmaximized, inset the bounds slightly so that they are not exactly the same. | |
| 34 // This makes it easier to resize the window. | |
| 35 const int kMaximizedWindowInset = 10; // DIPs. | |
| 36 | |
| 37 bool IsMinimizedWindowState(const WindowStateType state_type) { | |
| 38 return state_type == WINDOW_STATE_TYPE_MINIMIZED || | |
| 39 state_type == WINDOW_STATE_TYPE_DOCKED_MINIMIZED; | |
| 40 } | |
| 41 | |
| 42 void MoveToDisplayForRestore(WindowState* window_state) { | |
| 43 if (!window_state->HasRestoreBounds()) | |
| 44 return; | |
| 45 const gfx::Rect restore_bounds = window_state->GetRestoreBoundsInScreen(); | |
| 46 | |
| 47 // Move only if the restore bounds is outside of | |
| 48 // the display. There is no information about in which | |
| 49 // display it should be restored, so this is best guess. | |
| 50 // TODO(oshima): Restore information should contain the | |
| 51 // work area information like WindowResizer does for the | |
| 52 // last window location. | |
| 53 gfx::Rect display_area = | |
| 54 window_state->window()->GetDisplayNearestWindow().bounds(); | |
| 55 | |
| 56 if (!display_area.Intersects(restore_bounds)) { | |
| 57 const display::Display& display = | |
| 58 display::Screen::GetScreen()->GetDisplayMatching(restore_bounds); | |
| 59 WmShell* shell = window_state->window()->GetShell(); | |
| 60 WmWindow* new_root = shell->GetRootWindowForDisplayId(display.id()); | |
| 61 if (new_root != window_state->window()->GetRootWindow()) { | |
| 62 WmWindow* new_container = new_root->GetChildByShellWindowId( | |
| 63 window_state->window()->GetParent()->GetShellWindowId()); | |
| 64 new_container->AddChild(window_state->window()); | |
| 65 } | |
| 66 } | |
| 67 } | |
| 68 | |
| 69 DockedWindowLayoutManager* GetDockedWindowLayoutManager(WmShell* shell) { | |
| 70 return DockedWindowLayoutManager::Get(shell->GetActiveWindow()); | |
| 71 } | |
| 72 | |
| 73 class ScopedPreferredAlignmentResetter { | |
| 74 public: | |
| 75 ScopedPreferredAlignmentResetter(DockedAlignment dock_alignment, | |
| 76 DockedWindowLayoutManager* dock_layout) | |
| 77 : docked_window_layout_manager_(dock_layout) { | |
| 78 docked_window_layout_manager_->set_preferred_alignment(dock_alignment); | |
| 79 } | |
| 80 ~ScopedPreferredAlignmentResetter() { | |
| 81 docked_window_layout_manager_->set_preferred_alignment( | |
| 82 DOCKED_ALIGNMENT_NONE); | |
| 83 } | |
| 84 | |
| 85 private: | |
| 86 DockedWindowLayoutManager* docked_window_layout_manager_; | |
| 87 | |
| 88 DISALLOW_COPY_AND_ASSIGN(ScopedPreferredAlignmentResetter); | |
| 89 }; | |
| 90 | |
| 91 class ScopedDockedLayoutEventSourceResetter { | |
| 92 public: | |
| 93 ScopedDockedLayoutEventSourceResetter(DockedWindowLayoutManager* dock_layout) | |
| 94 : docked_window_layout_manager_(dock_layout) { | |
| 95 docked_window_layout_manager_->set_event_source( | |
| 96 DOCKED_ACTION_SOURCE_KEYBOARD); | |
| 97 } | |
| 98 ~ScopedDockedLayoutEventSourceResetter() { | |
| 99 docked_window_layout_manager_->set_event_source( | |
| 100 DOCKED_ACTION_SOURCE_UNKNOWN); | |
| 101 } | |
| 102 | |
| 103 private: | |
| 104 DockedWindowLayoutManager* docked_window_layout_manager_; | |
| 105 | |
| 106 DISALLOW_COPY_AND_ASSIGN(ScopedDockedLayoutEventSourceResetter); | |
| 107 }; | |
| 108 | |
| 109 void CycleSnap(WindowState* window_state, WMEventType event) { | |
| 110 DCHECK(!ash::switches::DockedWindowsEnabled()); | |
| 111 | |
| 112 wm::WindowStateType desired_snap_state = | |
| 113 event == WM_EVENT_CYCLE_SNAP_DOCK_LEFT | |
| 114 ? wm::WINDOW_STATE_TYPE_LEFT_SNAPPED | |
| 115 : wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED; | |
| 116 | |
| 117 if (window_state->CanSnap() && | |
| 118 window_state->GetStateType() != desired_snap_state && | |
| 119 window_state->window()->GetType() != ui::wm::WINDOW_TYPE_PANEL) { | |
| 120 const wm::WMEvent event(desired_snap_state == | |
| 121 wm::WINDOW_STATE_TYPE_LEFT_SNAPPED | |
| 122 ? wm::WM_EVENT_SNAP_LEFT | |
| 123 : wm::WM_EVENT_SNAP_RIGHT); | |
| 124 window_state->OnWMEvent(&event); | |
| 125 return; | |
| 126 } | |
| 127 | |
| 128 if (window_state->IsSnapped()) { | |
| 129 window_state->Restore(); | |
| 130 return; | |
| 131 } | |
| 132 window_state->window()->Animate(::wm::WINDOW_ANIMATION_TYPE_BOUNCE); | |
| 133 } | |
| 134 | |
| 135 void CycleSnapDock(WindowState* window_state, WMEventType event) { | |
| 136 DCHECK(ash::switches::DockedWindowsEnabled()); | |
| 137 | |
| 138 DockedWindowLayoutManager* dock_layout = | |
| 139 GetDockedWindowLayoutManager(window_state->window()->GetShell()); | |
| 140 wm::WindowStateType desired_snap_state = | |
| 141 event == WM_EVENT_CYCLE_SNAP_DOCK_LEFT | |
| 142 ? wm::WINDOW_STATE_TYPE_LEFT_SNAPPED | |
| 143 : wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED; | |
| 144 DockedAlignment desired_dock_alignment = | |
| 145 event == WM_EVENT_CYCLE_SNAP_DOCK_LEFT ? DOCKED_ALIGNMENT_LEFT | |
| 146 : DOCKED_ALIGNMENT_RIGHT; | |
| 147 DockedAlignment current_dock_alignment = | |
| 148 dock_layout ? dock_layout->CalculateAlignment() : DOCKED_ALIGNMENT_NONE; | |
| 149 | |
| 150 if (!window_state->IsDocked() || | |
| 151 (current_dock_alignment != DOCKED_ALIGNMENT_NONE && | |
| 152 current_dock_alignment != desired_dock_alignment)) { | |
| 153 if (window_state->CanSnap() && | |
| 154 window_state->GetStateType() != desired_snap_state && | |
| 155 window_state->window()->GetType() != ui::wm::WINDOW_TYPE_PANEL) { | |
| 156 const wm::WMEvent event(desired_snap_state == | |
| 157 wm::WINDOW_STATE_TYPE_LEFT_SNAPPED | |
| 158 ? wm::WM_EVENT_SNAP_LEFT | |
| 159 : wm::WM_EVENT_SNAP_RIGHT); | |
| 160 window_state->OnWMEvent(&event); | |
| 161 return; | |
| 162 } | |
| 163 | |
| 164 if (dock_layout && | |
| 165 dock_layout->CanDockWindow(window_state->window(), | |
| 166 desired_dock_alignment)) { | |
| 167 if (window_state->IsDocked()) { | |
| 168 dock_layout->MaybeSetDesiredDockedAlignment(desired_dock_alignment); | |
| 169 return; | |
| 170 } | |
| 171 | |
| 172 ScopedDockedLayoutEventSourceResetter event_source_resetter(dock_layout); | |
| 173 ScopedPreferredAlignmentResetter alignmentResetter(desired_dock_alignment, | |
| 174 dock_layout); | |
| 175 const wm::WMEvent event(wm::WM_EVENT_DOCK); | |
| 176 window_state->OnWMEvent(&event); | |
| 177 return; | |
| 178 } | |
| 179 } | |
| 180 | |
| 181 if (window_state->IsDocked() || window_state->IsSnapped()) { | |
| 182 ScopedDockedLayoutEventSourceResetter event_source_resetter(dock_layout); | |
| 183 window_state->Restore(); | |
| 184 return; | |
| 185 } | |
| 186 window_state->window()->Animate(::wm::WINDOW_ANIMATION_TYPE_BOUNCE); | |
| 187 } | |
| 188 | |
| 189 } // namespace | |
| 190 | |
| 191 DefaultState::DefaultState(WindowStateType initial_state_type) | |
| 192 : state_type_(initial_state_type), stored_window_state_(nullptr) {} | |
| 193 DefaultState::~DefaultState() {} | |
| 194 | |
| 195 void DefaultState::OnWMEvent(WindowState* window_state, const WMEvent* event) { | |
| 196 if (ProcessWorkspaceEvents(window_state, event)) | |
| 197 return; | |
| 198 | |
| 199 // Do not change the PINNED window state if this is not unpin event. | |
| 200 if (window_state->IsTrustedPinned() && event->type() != WM_EVENT_NORMAL) | |
| 201 return; | |
| 202 | |
| 203 if (ProcessCompoundEvents(window_state, event)) | |
| 204 return; | |
| 205 | |
| 206 WindowStateType current_state_type = window_state->GetStateType(); | |
| 207 WindowStateType next_state_type = WINDOW_STATE_TYPE_NORMAL; | |
| 208 switch (event->type()) { | |
| 209 case WM_EVENT_NORMAL: | |
| 210 next_state_type = current_state_type == WINDOW_STATE_TYPE_DOCKED_MINIMIZED | |
| 211 ? WINDOW_STATE_TYPE_DOCKED | |
| 212 : WINDOW_STATE_TYPE_NORMAL; | |
| 213 break; | |
| 214 case WM_EVENT_MAXIMIZE: | |
| 215 next_state_type = WINDOW_STATE_TYPE_MAXIMIZED; | |
| 216 break; | |
| 217 case WM_EVENT_MINIMIZE: | |
| 218 next_state_type = current_state_type == WINDOW_STATE_TYPE_DOCKED | |
| 219 ? WINDOW_STATE_TYPE_DOCKED_MINIMIZED | |
| 220 : WINDOW_STATE_TYPE_MINIMIZED; | |
| 221 break; | |
| 222 case WM_EVENT_FULLSCREEN: | |
| 223 next_state_type = WINDOW_STATE_TYPE_FULLSCREEN; | |
| 224 break; | |
| 225 case WM_EVENT_SNAP_LEFT: | |
| 226 next_state_type = WINDOW_STATE_TYPE_LEFT_SNAPPED; | |
| 227 break; | |
| 228 case WM_EVENT_SNAP_RIGHT: | |
| 229 next_state_type = WINDOW_STATE_TYPE_RIGHT_SNAPPED; | |
| 230 break; | |
| 231 case WM_EVENT_DOCK: | |
| 232 next_state_type = WINDOW_STATE_TYPE_DOCKED; | |
| 233 break; | |
| 234 case WM_EVENT_SET_BOUNDS: | |
| 235 SetBounds(window_state, static_cast<const SetBoundsEvent*>(event)); | |
| 236 return; | |
| 237 case WM_EVENT_SHOW_INACTIVE: | |
| 238 next_state_type = WINDOW_STATE_TYPE_INACTIVE; | |
| 239 break; | |
| 240 case WM_EVENT_PIN: | |
| 241 case WM_EVENT_TRUSTED_PIN: | |
| 242 // If there already is a pinned window, it is not allowed to set it | |
| 243 // to this window. | |
| 244 // TODO(hidehiko): If a system modal window is openening, the pinning | |
| 245 // probably should fail. | |
| 246 if (WmShell::Get()->IsPinned()) { | |
| 247 LOG(ERROR) << "An PIN event will be failed since another window is " | |
| 248 << "already in pinned mode."; | |
| 249 next_state_type = current_state_type; | |
| 250 } else { | |
| 251 next_state_type = event->type() == WM_EVENT_PIN | |
| 252 ? WINDOW_STATE_TYPE_PINNED | |
| 253 : WINDOW_STATE_TYPE_TRUSTED_PINNED; | |
| 254 } | |
| 255 break; | |
| 256 case WM_EVENT_TOGGLE_MAXIMIZE_CAPTION: | |
| 257 case WM_EVENT_TOGGLE_MAXIMIZE: | |
| 258 case WM_EVENT_TOGGLE_VERTICAL_MAXIMIZE: | |
| 259 case WM_EVENT_TOGGLE_HORIZONTAL_MAXIMIZE: | |
| 260 case WM_EVENT_TOGGLE_FULLSCREEN: | |
| 261 case WM_EVENT_CYCLE_SNAP_DOCK_LEFT: | |
| 262 case WM_EVENT_CYCLE_SNAP_DOCK_RIGHT: | |
| 263 case WM_EVENT_CENTER: | |
| 264 NOTREACHED() << "Compound event should not reach here:" << event; | |
| 265 return; | |
| 266 case WM_EVENT_ADDED_TO_WORKSPACE: | |
| 267 case WM_EVENT_WORKAREA_BOUNDS_CHANGED: | |
| 268 case WM_EVENT_DISPLAY_BOUNDS_CHANGED: | |
| 269 NOTREACHED() << "Workspace event should not reach here:" << event; | |
| 270 return; | |
| 271 } | |
| 272 | |
| 273 if (next_state_type == current_state_type && window_state->IsSnapped()) { | |
| 274 gfx::Rect snapped_bounds = | |
| 275 event->type() == WM_EVENT_SNAP_LEFT | |
| 276 ? GetDefaultLeftSnappedWindowBoundsInParent(window_state->window()) | |
| 277 : GetDefaultRightSnappedWindowBoundsInParent( | |
| 278 window_state->window()); | |
| 279 window_state->SetBoundsDirectAnimated(snapped_bounds); | |
| 280 return; | |
| 281 } | |
| 282 | |
| 283 if (event->type() == WM_EVENT_SNAP_LEFT || | |
| 284 event->type() == WM_EVENT_SNAP_RIGHT) { | |
| 285 window_state->set_bounds_changed_by_user(true); | |
| 286 } | |
| 287 | |
| 288 EnterToNextState(window_state, next_state_type); | |
| 289 } | |
| 290 | |
| 291 WindowStateType DefaultState::GetType() const { | |
| 292 return state_type_; | |
| 293 } | |
| 294 | |
| 295 void DefaultState::AttachState(WindowState* window_state, | |
| 296 WindowState::State* state_in_previous_mode) { | |
| 297 DCHECK_EQ(stored_window_state_, window_state); | |
| 298 | |
| 299 ReenterToCurrentState(window_state, state_in_previous_mode); | |
| 300 | |
| 301 // If the display has changed while in the another mode, | |
| 302 // we need to let windows know the change. | |
| 303 display::Display current_display = | |
| 304 window_state->window()->GetDisplayNearestWindow(); | |
| 305 if (stored_display_state_.bounds() != current_display.bounds()) { | |
| 306 const WMEvent event(wm::WM_EVENT_DISPLAY_BOUNDS_CHANGED); | |
| 307 window_state->OnWMEvent(&event); | |
| 308 } else if (stored_display_state_.work_area() != current_display.work_area()) { | |
| 309 const WMEvent event(wm::WM_EVENT_WORKAREA_BOUNDS_CHANGED); | |
| 310 window_state->OnWMEvent(&event); | |
| 311 } | |
| 312 } | |
| 313 | |
| 314 void DefaultState::DetachState(WindowState* window_state) { | |
| 315 stored_window_state_ = window_state; | |
| 316 stored_bounds_ = window_state->window()->GetBounds(); | |
| 317 stored_restore_bounds_ = window_state->HasRestoreBounds() | |
| 318 ? window_state->GetRestoreBoundsInParent() | |
| 319 : gfx::Rect(); | |
| 320 // Remember the display state so that in case of the display change | |
| 321 // while in the other mode, we can perform necessary action to | |
| 322 // restore the window state to the proper state for the current | |
| 323 // display. | |
| 324 stored_display_state_ = window_state->window()->GetDisplayNearestWindow(); | |
| 325 } | |
| 326 | |
| 327 // static | |
| 328 bool DefaultState::ProcessCompoundEvents(WindowState* window_state, | |
| 329 const WMEvent* event) { | |
| 330 WmWindow* window = window_state->window(); | |
| 331 | |
| 332 switch (event->type()) { | |
| 333 case WM_EVENT_TOGGLE_MAXIMIZE_CAPTION: | |
| 334 if (window_state->IsFullscreen()) { | |
| 335 const wm::WMEvent event(wm::WM_EVENT_TOGGLE_FULLSCREEN); | |
| 336 window_state->OnWMEvent(&event); | |
| 337 } else if (window_state->IsMaximized()) { | |
| 338 window_state->Restore(); | |
| 339 } else if (window_state->IsNormalOrSnapped()) { | |
| 340 if (window_state->CanMaximize()) | |
| 341 window_state->Maximize(); | |
| 342 } | |
| 343 return true; | |
| 344 case WM_EVENT_TOGGLE_MAXIMIZE: | |
| 345 if (window_state->IsFullscreen()) { | |
| 346 const wm::WMEvent event(wm::WM_EVENT_TOGGLE_FULLSCREEN); | |
| 347 window_state->OnWMEvent(&event); | |
| 348 } else if (window_state->IsMaximized()) { | |
| 349 window_state->Restore(); | |
| 350 } else if (window_state->CanMaximize()) { | |
| 351 window_state->Maximize(); | |
| 352 } | |
| 353 return true; | |
| 354 case WM_EVENT_TOGGLE_VERTICAL_MAXIMIZE: { | |
| 355 gfx::Rect work_area = GetDisplayWorkAreaBoundsInParent(window); | |
| 356 | |
| 357 // Maximize vertically if: | |
| 358 // - The window does not have a max height defined. | |
| 359 // - The window has the normal state type. Snapped windows are excluded | |
| 360 // because they are already maximized vertically and reverting to the | |
| 361 // restored bounds looks weird. | |
| 362 if (window->GetMaximumSize().height() != 0 || | |
| 363 !window_state->IsNormalStateType()) { | |
| 364 return true; | |
| 365 } | |
| 366 if (window_state->HasRestoreBounds() && | |
| 367 (window->GetBounds().height() == work_area.height() && | |
| 368 window->GetBounds().y() == work_area.y())) { | |
| 369 window_state->SetAndClearRestoreBounds(); | |
| 370 } else { | |
| 371 window_state->SaveCurrentBoundsForRestore(); | |
| 372 window->SetBounds(gfx::Rect(window->GetBounds().x(), work_area.y(), | |
| 373 window->GetBounds().width(), | |
| 374 work_area.height())); | |
| 375 } | |
| 376 return true; | |
| 377 } | |
| 378 case WM_EVENT_TOGGLE_HORIZONTAL_MAXIMIZE: { | |
| 379 // Maximize horizontally if: | |
| 380 // - The window does not have a max width defined. | |
| 381 // - The window is snapped or has the normal state type. | |
| 382 if (window->GetMaximumSize().width() != 0) | |
| 383 return true; | |
| 384 if (!window_state->IsNormalOrSnapped()) | |
| 385 return true; | |
| 386 gfx::Rect work_area = GetDisplayWorkAreaBoundsInParent(window); | |
| 387 if (window_state->IsNormalStateType() && | |
| 388 window_state->HasRestoreBounds() && | |
| 389 (window->GetBounds().width() == work_area.width() && | |
| 390 window->GetBounds().x() == work_area.x())) { | |
| 391 window_state->SetAndClearRestoreBounds(); | |
| 392 } else { | |
| 393 gfx::Rect new_bounds(work_area.x(), window->GetBounds().y(), | |
| 394 work_area.width(), window->GetBounds().height()); | |
| 395 | |
| 396 gfx::Rect restore_bounds = window->GetBounds(); | |
| 397 if (window_state->IsSnapped()) { | |
| 398 window_state->SetRestoreBoundsInParent(new_bounds); | |
| 399 window_state->Restore(); | |
| 400 | |
| 401 // The restore logic prevents a window from being restored to bounds | |
| 402 // which match the workspace bounds exactly so it is necessary to set | |
| 403 // the bounds again below. | |
| 404 } | |
| 405 | |
| 406 window_state->SetRestoreBoundsInParent(restore_bounds); | |
| 407 window->SetBounds(new_bounds); | |
| 408 } | |
| 409 return true; | |
| 410 } | |
| 411 case WM_EVENT_TOGGLE_FULLSCREEN: | |
| 412 ToggleFullScreen(window_state, window_state->delegate()); | |
| 413 return true; | |
| 414 case WM_EVENT_CYCLE_SNAP_DOCK_LEFT: | |
| 415 case WM_EVENT_CYCLE_SNAP_DOCK_RIGHT: | |
| 416 if (ash::switches::DockedWindowsEnabled()) | |
| 417 CycleSnapDock(window_state, event->type()); | |
| 418 else | |
| 419 CycleSnap(window_state, event->type()); | |
| 420 return true; | |
| 421 case WM_EVENT_CENTER: | |
| 422 CenterWindow(window_state); | |
| 423 return true; | |
| 424 case WM_EVENT_NORMAL: | |
| 425 case WM_EVENT_MAXIMIZE: | |
| 426 case WM_EVENT_MINIMIZE: | |
| 427 case WM_EVENT_FULLSCREEN: | |
| 428 case WM_EVENT_PIN: | |
| 429 case WM_EVENT_TRUSTED_PIN: | |
| 430 case WM_EVENT_SNAP_LEFT: | |
| 431 case WM_EVENT_SNAP_RIGHT: | |
| 432 case WM_EVENT_SET_BOUNDS: | |
| 433 case WM_EVENT_SHOW_INACTIVE: | |
| 434 case WM_EVENT_DOCK: | |
| 435 break; | |
| 436 case WM_EVENT_ADDED_TO_WORKSPACE: | |
| 437 case WM_EVENT_WORKAREA_BOUNDS_CHANGED: | |
| 438 case WM_EVENT_DISPLAY_BOUNDS_CHANGED: | |
| 439 NOTREACHED() << "Workspace event should not reach here:" << event; | |
| 440 break; | |
| 441 } | |
| 442 return false; | |
| 443 } | |
| 444 | |
| 445 bool DefaultState::ProcessWorkspaceEvents(WindowState* window_state, | |
| 446 const WMEvent* event) { | |
| 447 switch (event->type()) { | |
| 448 case WM_EVENT_ADDED_TO_WORKSPACE: { | |
| 449 // When a window is dragged and dropped onto a different | |
| 450 // root window, the bounds will be updated after they are added | |
| 451 // to the root window. | |
| 452 // If a window is opened as maximized or fullscreen, its bounds may be | |
| 453 // empty, so update the bounds now before checking empty. | |
| 454 if (window_state->is_dragged() || | |
| 455 SetMaximizedOrFullscreenBounds(window_state)) { | |
| 456 return true; | |
| 457 } | |
| 458 | |
| 459 WmWindow* window = window_state->window(); | |
| 460 gfx::Rect bounds = window->GetBounds(); | |
| 461 | |
| 462 // Don't adjust window bounds if the bounds are empty as this | |
| 463 // happens when a new views::Widget is created. | |
| 464 if (bounds.IsEmpty()) | |
| 465 return true; | |
| 466 | |
| 467 // Only windows of type WINDOW_TYPE_NORMAL or WINDOW_TYPE_PANEL need to be | |
| 468 // adjusted to have minimum visibility, because they are positioned by the | |
| 469 // user and user should always be able to interact with them. Other | |
| 470 // windows are positioned programmatically. | |
| 471 if (!window_state->IsUserPositionable()) | |
| 472 return true; | |
| 473 | |
| 474 // Use entire display instead of workarea because the workarea can | |
| 475 // be further shrunk by the docked area. The logic ensures 30% | |
| 476 // visibility which should be enough to see where the window gets | |
| 477 // moved. | |
| 478 gfx::Rect display_area = GetDisplayBoundsInParent(window); | |
| 479 int min_width = bounds.width() * wm::kMinimumPercentOnScreenArea; | |
| 480 int min_height = bounds.height() * wm::kMinimumPercentOnScreenArea; | |
| 481 wm::AdjustBoundsToEnsureWindowVisibility(display_area, min_width, | |
| 482 min_height, &bounds); | |
| 483 window_state->AdjustSnappedBounds(&bounds); | |
| 484 if (window->GetBounds() != bounds) | |
| 485 window_state->SetBoundsConstrained(bounds); | |
| 486 return true; | |
| 487 } | |
| 488 case WM_EVENT_DISPLAY_BOUNDS_CHANGED: { | |
| 489 if (window_state->is_dragged() || | |
| 490 SetMaximizedOrFullscreenBounds(window_state)) { | |
| 491 return true; | |
| 492 } | |
| 493 gfx::Rect work_area_in_parent = | |
| 494 GetDisplayWorkAreaBoundsInParent(window_state->window()); | |
| 495 gfx::Rect bounds = window_state->window()->GetTargetBounds(); | |
| 496 // When display bounds has changed, make sure the entire window is fully | |
| 497 // visible. | |
| 498 bounds.AdjustToFit(work_area_in_parent); | |
| 499 window_state->AdjustSnappedBounds(&bounds); | |
| 500 if (window_state->window()->GetTargetBounds() != bounds) | |
| 501 window_state->SetBoundsDirectAnimated(bounds); | |
| 502 return true; | |
| 503 } | |
| 504 case WM_EVENT_WORKAREA_BOUNDS_CHANGED: { | |
| 505 // Don't resize the maximized window when the desktop is covered | |
| 506 // by fullscreen window. crbug.com/504299. | |
| 507 bool in_fullscreen = | |
| 508 window_state->window() | |
| 509 ->GetRootWindowController() | |
| 510 ->GetWorkspaceWindowState() == WORKSPACE_WINDOW_STATE_FULL_SCREEN; | |
| 511 if (in_fullscreen && window_state->IsMaximized()) | |
| 512 return true; | |
| 513 | |
| 514 if (window_state->is_dragged() || | |
| 515 SetMaximizedOrFullscreenBounds(window_state)) { | |
| 516 return true; | |
| 517 } | |
| 518 gfx::Rect work_area_in_parent = | |
| 519 GetDisplayWorkAreaBoundsInParent(window_state->window()); | |
| 520 gfx::Rect bounds = window_state->window()->GetTargetBounds(); | |
| 521 if (!window_state->window()->GetTransientParent()) { | |
| 522 wm::AdjustBoundsToEnsureMinimumWindowVisibility(work_area_in_parent, | |
| 523 &bounds); | |
| 524 } | |
| 525 window_state->AdjustSnappedBounds(&bounds); | |
| 526 if (window_state->window()->GetTargetBounds() != bounds) | |
| 527 window_state->SetBoundsDirectAnimated(bounds); | |
| 528 return true; | |
| 529 } | |
| 530 case WM_EVENT_TOGGLE_MAXIMIZE_CAPTION: | |
| 531 case WM_EVENT_TOGGLE_MAXIMIZE: | |
| 532 case WM_EVENT_TOGGLE_VERTICAL_MAXIMIZE: | |
| 533 case WM_EVENT_TOGGLE_HORIZONTAL_MAXIMIZE: | |
| 534 case WM_EVENT_TOGGLE_FULLSCREEN: | |
| 535 case WM_EVENT_CYCLE_SNAP_DOCK_LEFT: | |
| 536 case WM_EVENT_CYCLE_SNAP_DOCK_RIGHT: | |
| 537 case WM_EVENT_CENTER: | |
| 538 case WM_EVENT_NORMAL: | |
| 539 case WM_EVENT_MAXIMIZE: | |
| 540 case WM_EVENT_MINIMIZE: | |
| 541 case WM_EVENT_FULLSCREEN: | |
| 542 case WM_EVENT_PIN: | |
| 543 case WM_EVENT_TRUSTED_PIN: | |
| 544 case WM_EVENT_SNAP_LEFT: | |
| 545 case WM_EVENT_SNAP_RIGHT: | |
| 546 case WM_EVENT_SET_BOUNDS: | |
| 547 case WM_EVENT_SHOW_INACTIVE: | |
| 548 case WM_EVENT_DOCK: | |
| 549 break; | |
| 550 } | |
| 551 return false; | |
| 552 } | |
| 553 | |
| 554 // static | |
| 555 bool DefaultState::SetMaximizedOrFullscreenBounds(WindowState* window_state) { | |
| 556 DCHECK(!window_state->is_dragged()); | |
| 557 if (window_state->IsMaximized()) { | |
| 558 window_state->SetBoundsDirect( | |
| 559 GetMaximizedWindowBoundsInParent(window_state->window())); | |
| 560 return true; | |
| 561 } | |
| 562 if (window_state->IsFullscreen()) { | |
| 563 window_state->SetBoundsDirect( | |
| 564 GetDisplayBoundsInParent(window_state->window())); | |
| 565 return true; | |
| 566 } | |
| 567 return false; | |
| 568 } | |
| 569 | |
| 570 // static | |
| 571 void DefaultState::SetBounds(WindowState* window_state, | |
| 572 const SetBoundsEvent* event) { | |
| 573 if (window_state->is_dragged()) { | |
| 574 // TODO(oshima|varkha): This may be no longer needed, as the dragging | |
| 575 // happens in docked window container. crbug.com/485612. | |
| 576 window_state->SetBoundsDirect(event->requested_bounds()); | |
| 577 } else if (window_state->IsSnapped()) { | |
| 578 gfx::Rect work_area_in_parent = | |
| 579 GetDisplayWorkAreaBoundsInParent(window_state->window()); | |
| 580 gfx::Rect child_bounds(event->requested_bounds()); | |
| 581 wm::AdjustBoundsSmallerThan(work_area_in_parent.size(), &child_bounds); | |
| 582 window_state->AdjustSnappedBounds(&child_bounds); | |
| 583 window_state->SetBoundsDirect(child_bounds); | |
| 584 } else if (!SetMaximizedOrFullscreenBounds(window_state) || | |
| 585 window_state->allow_set_bounds_in_maximized()) { | |
| 586 window_state->SetBoundsConstrained(event->requested_bounds()); | |
| 587 } | |
| 588 } | |
| 589 | |
| 590 void DefaultState::EnterToNextState(WindowState* window_state, | |
| 591 WindowStateType next_state_type) { | |
| 592 // Do nothing if we're already in the same state. | |
| 593 if (state_type_ == next_state_type) | |
| 594 return; | |
| 595 | |
| 596 WindowStateType previous_state_type = state_type_; | |
| 597 state_type_ = next_state_type; | |
| 598 | |
| 599 window_state->UpdateWindowShowStateFromStateType(); | |
| 600 window_state->NotifyPreStateTypeChange(previous_state_type); | |
| 601 | |
| 602 if (window_state->window()->GetParent()) { | |
| 603 if (!window_state->HasRestoreBounds() && | |
| 604 (previous_state_type == WINDOW_STATE_TYPE_DEFAULT || | |
| 605 previous_state_type == WINDOW_STATE_TYPE_NORMAL) && | |
| 606 !window_state->IsMinimized() && !window_state->IsNormalStateType()) { | |
| 607 window_state->SaveCurrentBoundsForRestore(); | |
| 608 } | |
| 609 | |
| 610 // When restoring from a minimized state, we want to restore to the | |
| 611 // previous bounds. However, we want to maintain the restore bounds. | |
| 612 // (The restore bounds are set if a user maximized the window in one | |
| 613 // axis by double clicking the window border for example). | |
| 614 gfx::Rect restore_bounds_in_screen; | |
| 615 if (previous_state_type == WINDOW_STATE_TYPE_MINIMIZED && | |
| 616 window_state->IsNormalStateType() && window_state->HasRestoreBounds() && | |
| 617 !window_state->unminimize_to_restore_bounds()) { | |
| 618 restore_bounds_in_screen = window_state->GetRestoreBoundsInScreen(); | |
| 619 window_state->SaveCurrentBoundsForRestore(); | |
| 620 } | |
| 621 | |
| 622 if (window_state->IsMaximizedOrFullscreenOrPinned()) | |
| 623 MoveToDisplayForRestore(window_state); | |
| 624 | |
| 625 UpdateBoundsFromState(window_state, previous_state_type); | |
| 626 | |
| 627 // Normal state should have no restore bounds unless it's | |
| 628 // unminimized. | |
| 629 if (!restore_bounds_in_screen.IsEmpty()) | |
| 630 window_state->SetRestoreBoundsInScreen(restore_bounds_in_screen); | |
| 631 else if (window_state->IsNormalStateType()) | |
| 632 window_state->ClearRestoreBounds(); | |
| 633 } | |
| 634 window_state->NotifyPostStateTypeChange(previous_state_type); | |
| 635 | |
| 636 if (next_state_type == WINDOW_STATE_TYPE_PINNED || | |
| 637 previous_state_type == WINDOW_STATE_TYPE_PINNED || | |
| 638 next_state_type == WINDOW_STATE_TYPE_TRUSTED_PINNED || | |
| 639 previous_state_type == WINDOW_STATE_TYPE_TRUSTED_PINNED) { | |
| 640 WmShell::Get()->SetPinnedWindow(window_state->window()); | |
| 641 } | |
| 642 } | |
| 643 | |
| 644 void DefaultState::ReenterToCurrentState( | |
| 645 WindowState* window_state, | |
| 646 WindowState::State* state_in_previous_mode) { | |
| 647 WindowStateType previous_state_type = state_in_previous_mode->GetType(); | |
| 648 | |
| 649 // A state change should not move a window into or out of full screen or | |
| 650 // pinned since these are "special mode" the user wanted to be in and | |
| 651 // should be respected as such. | |
| 652 if (previous_state_type == wm::WINDOW_STATE_TYPE_FULLSCREEN || | |
| 653 previous_state_type == wm::WINDOW_STATE_TYPE_PINNED || | |
| 654 previous_state_type == wm::WINDOW_STATE_TYPE_TRUSTED_PINNED) { | |
| 655 state_type_ = previous_state_type; | |
| 656 } else if (state_type_ == wm::WINDOW_STATE_TYPE_FULLSCREEN || | |
| 657 state_type_ == wm::WINDOW_STATE_TYPE_PINNED || | |
| 658 state_type_ == wm::WINDOW_STATE_TYPE_TRUSTED_PINNED) { | |
| 659 state_type_ = previous_state_type; | |
| 660 } | |
| 661 | |
| 662 window_state->UpdateWindowShowStateFromStateType(); | |
| 663 window_state->NotifyPreStateTypeChange(previous_state_type); | |
| 664 | |
| 665 if ((state_type_ == wm::WINDOW_STATE_TYPE_NORMAL || | |
| 666 state_type_ == wm::WINDOW_STATE_TYPE_DEFAULT) && | |
| 667 !stored_bounds_.IsEmpty()) { | |
| 668 // Use the restore mechanism to set the bounds for | |
| 669 // the window in normal state. This also covers unminimize case. | |
| 670 window_state->SetRestoreBoundsInParent(stored_bounds_); | |
| 671 } | |
| 672 | |
| 673 UpdateBoundsFromState(window_state, state_in_previous_mode->GetType()); | |
| 674 | |
| 675 // Then restore the restore bounds to their previous value. | |
| 676 if (!stored_restore_bounds_.IsEmpty()) | |
| 677 window_state->SetRestoreBoundsInParent(stored_restore_bounds_); | |
| 678 else | |
| 679 window_state->ClearRestoreBounds(); | |
| 680 | |
| 681 window_state->NotifyPostStateTypeChange(previous_state_type); | |
| 682 } | |
| 683 | |
| 684 void DefaultState::UpdateBoundsFromState(WindowState* window_state, | |
| 685 WindowStateType previous_state_type) { | |
| 686 WmWindow* window = window_state->window(); | |
| 687 gfx::Rect bounds_in_parent; | |
| 688 switch (state_type_) { | |
| 689 case WINDOW_STATE_TYPE_LEFT_SNAPPED: | |
| 690 case WINDOW_STATE_TYPE_RIGHT_SNAPPED: | |
| 691 bounds_in_parent = | |
| 692 state_type_ == WINDOW_STATE_TYPE_LEFT_SNAPPED | |
| 693 ? GetDefaultLeftSnappedWindowBoundsInParent(window) | |
| 694 : GetDefaultRightSnappedWindowBoundsInParent(window); | |
| 695 break; | |
| 696 case WINDOW_STATE_TYPE_DOCKED: { | |
| 697 // TODO(afakhry): Remove in M58. | |
| 698 DCHECK(ash::switches::DockedWindowsEnabled()); | |
| 699 if (window->GetParent()->GetShellWindowId() != | |
| 700 kShellWindowId_DockedContainer) { | |
| 701 WmWindow* docked_container = | |
| 702 window->GetRootWindow()->GetChildByShellWindowId( | |
| 703 kShellWindowId_DockedContainer); | |
| 704 ReparentChildWithTransientChildren(window, window->GetParent(), | |
| 705 docked_container); | |
| 706 } | |
| 707 // Return early because we don't want to update the bounds of the | |
| 708 // window below; as the bounds are managed by the dock layout. | |
| 709 return; | |
| 710 } | |
| 711 case WINDOW_STATE_TYPE_DEFAULT: | |
| 712 case WINDOW_STATE_TYPE_NORMAL: { | |
| 713 gfx::Rect work_area_in_parent = GetDisplayWorkAreaBoundsInParent(window); | |
| 714 if (window_state->HasRestoreBounds()) { | |
| 715 bounds_in_parent = window_state->GetRestoreBoundsInParent(); | |
| 716 // Check if the |window|'s restored size is bigger than the working area | |
| 717 // This may happen if a window was resized to maximized bounds or if the | |
| 718 // display resolution changed while the window was maximized. | |
| 719 if (previous_state_type == WINDOW_STATE_TYPE_MAXIMIZED && | |
| 720 bounds_in_parent.width() >= work_area_in_parent.width() && | |
| 721 bounds_in_parent.height() >= work_area_in_parent.height()) { | |
| 722 bounds_in_parent = work_area_in_parent; | |
| 723 bounds_in_parent.Inset(kMaximizedWindowInset, kMaximizedWindowInset, | |
| 724 kMaximizedWindowInset, kMaximizedWindowInset); | |
| 725 } | |
| 726 } else { | |
| 727 bounds_in_parent = window->GetBounds(); | |
| 728 } | |
| 729 // Make sure that part of the window is always visible. | |
| 730 if (!window_state->is_dragged()) { | |
| 731 // Avoid doing this while the window is being dragged as its root | |
| 732 // window hasn't been updated yet in the case of dragging to another | |
| 733 // display. crbug.com/666836. | |
| 734 wm::AdjustBoundsToEnsureMinimumWindowVisibility(work_area_in_parent, | |
| 735 &bounds_in_parent); | |
| 736 } | |
| 737 break; | |
| 738 } | |
| 739 case WINDOW_STATE_TYPE_MAXIMIZED: | |
| 740 bounds_in_parent = GetMaximizedWindowBoundsInParent(window); | |
| 741 break; | |
| 742 | |
| 743 case WINDOW_STATE_TYPE_FULLSCREEN: | |
| 744 case WINDOW_STATE_TYPE_PINNED: | |
| 745 case WINDOW_STATE_TYPE_TRUSTED_PINNED: | |
| 746 bounds_in_parent = GetDisplayBoundsInParent(window); | |
| 747 break; | |
| 748 | |
| 749 case WINDOW_STATE_TYPE_DOCKED_MINIMIZED: | |
| 750 case WINDOW_STATE_TYPE_MINIMIZED: | |
| 751 break; | |
| 752 case WINDOW_STATE_TYPE_INACTIVE: | |
| 753 case WINDOW_STATE_TYPE_END: | |
| 754 case WINDOW_STATE_TYPE_AUTO_POSITIONED: | |
| 755 return; | |
| 756 } | |
| 757 | |
| 758 if (!window_state->IsMinimized()) { | |
| 759 if (IsMinimizedWindowState(previous_state_type) || | |
| 760 window_state->IsFullscreen() || window_state->IsPinned()) { | |
| 761 window_state->SetBoundsDirect(bounds_in_parent); | |
| 762 } else if (window_state->IsMaximized() || | |
| 763 IsMaximizedOrFullscreenOrPinnedWindowStateType( | |
| 764 previous_state_type)) { | |
| 765 window_state->SetBoundsDirectCrossFade(bounds_in_parent); | |
| 766 } else if (window_state->is_dragged()) { | |
| 767 // SetBoundsDirectAnimated does not work when the window gets reparented. | |
| 768 // TODO(oshima): Consider fixing it and reenable the animation. | |
| 769 window_state->SetBoundsDirect(bounds_in_parent); | |
| 770 } else { | |
| 771 window_state->SetBoundsDirectAnimated(bounds_in_parent); | |
| 772 } | |
| 773 } | |
| 774 | |
| 775 if (window_state->IsMinimized()) { | |
| 776 // Save the previous show state so that we can correctly restore it after | |
| 777 // exiting the minimized mode. | |
| 778 window->SetPreMinimizedShowState(ToWindowShowState(previous_state_type)); | |
| 779 window->SetVisibilityAnimationType( | |
| 780 WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE); | |
| 781 | |
| 782 // Hide the window. | |
| 783 window->Hide(); | |
| 784 // Activate another window. | |
| 785 if (window_state->IsActive()) | |
| 786 window_state->Deactivate(); | |
| 787 } else if ((window->GetTargetVisibility() || | |
| 788 IsMinimizedWindowState(previous_state_type)) && | |
| 789 !window->GetLayerVisible()) { | |
| 790 // The layer may be hidden if the window was previously minimized. Make | |
| 791 // sure it's visible. | |
| 792 window->Show(); | |
| 793 if (IsMinimizedWindowState(previous_state_type) && | |
| 794 !window_state->IsMaximizedOrFullscreenOrPinned()) { | |
| 795 window_state->set_unminimize_to_restore_bounds(false); | |
| 796 } | |
| 797 } | |
| 798 } | |
| 799 | |
| 800 // static | |
| 801 void DefaultState::CenterWindow(WindowState* window_state) { | |
| 802 if (!window_state->IsNormalOrSnapped()) | |
| 803 return; | |
| 804 WmWindow* window = window_state->window(); | |
| 805 if (window_state->IsSnapped()) { | |
| 806 gfx::Rect center_in_screen = window->GetDisplayNearestWindow().work_area(); | |
| 807 gfx::Size size = window_state->HasRestoreBounds() | |
| 808 ? window_state->GetRestoreBoundsInScreen().size() | |
| 809 : window->GetBounds().size(); | |
| 810 center_in_screen.ClampToCenteredSize(size); | |
| 811 window_state->SetRestoreBoundsInScreen(center_in_screen); | |
| 812 window_state->Restore(); | |
| 813 } else { | |
| 814 gfx::Rect center_in_parent = GetDisplayWorkAreaBoundsInParent(window); | |
| 815 center_in_parent.ClampToCenteredSize(window->GetBounds().size()); | |
| 816 window_state->SetBoundsDirectAnimated(center_in_parent); | |
| 817 } | |
| 818 // Centering window is treated as if a user moved and resized the window. | |
| 819 window_state->set_bounds_changed_by_user(true); | |
| 820 } | |
| 821 | |
| 822 } // namespace wm | |
| 823 } // namespace ash | |
| OLD | NEW |