| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013 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/dock/docked_window_layout_manager.h" | |
| 6 | |
| 7 #include "ash/animation/animation_change_type.h" | |
| 8 #include "ash/common/shelf/shelf_background_animator.h" | |
| 9 #include "ash/common/shelf/shelf_background_animator_observer.h" | |
| 10 #include "ash/common/shelf/shelf_constants.h" | |
| 11 #include "ash/common/shelf/wm_shelf.h" | |
| 12 #include "ash/common/shelf/wm_shelf_observer.h" | |
| 13 #include "ash/common/wm/overview/window_selector_controller.h" | |
| 14 #include "ash/common/wm/window_animation_types.h" | |
| 15 #include "ash/common/wm/window_parenting_utils.h" | |
| 16 #include "ash/common/wm/window_resizer.h" | |
| 17 #include "ash/common/wm/window_state.h" | |
| 18 #include "ash/common/wm_shell.h" | |
| 19 #include "ash/common/wm_window.h" | |
| 20 #include "ash/public/cpp/shell_window_ids.h" | |
| 21 #include "ash/resources/grit/ash_resources.h" | |
| 22 #include "ash/root_window_controller.h" | |
| 23 #include "ash/wm/window_state_aura.h" | |
| 24 #include "base/auto_reset.h" | |
| 25 #include "base/metrics/histogram_macros.h" | |
| 26 #include "third_party/skia/include/core/SkColor.h" | |
| 27 #include "ui/base/resource/resource_bundle.h" | |
| 28 #include "ui/compositor/scoped_layer_animation_settings.h" | |
| 29 #include "ui/display/display.h" | |
| 30 #include "ui/display/screen.h" | |
| 31 #include "ui/views/background.h" | |
| 32 #include "ui/wm/core/coordinate_conversion.h" | |
| 33 #include "ui/wm/core/window_animations.h" | |
| 34 | |
| 35 namespace ash { | |
| 36 | |
| 37 // Minimum, maximum width of the dock area and a width of the gap | |
| 38 // static | |
| 39 const int DockedWindowLayoutManager::kMaxDockWidth = 360; | |
| 40 // static | |
| 41 const int DockedWindowLayoutManager::kMinDockWidth = 200; | |
| 42 // static | |
| 43 const int DockedWindowLayoutManager::kMinDockGap = 2; | |
| 44 // static | |
| 45 const int DockedWindowLayoutManager::kIdealWidth = 250; | |
| 46 const int kMinimumHeight = 250; | |
| 47 const int kSlideDurationMs = 120; | |
| 48 const int kFadeDurationMs = 60; | |
| 49 const int kMinimizeDurationMs = 720; | |
| 50 | |
| 51 class DockedBackgroundWidget : public views::Widget, | |
| 52 public WmShelfObserver, | |
| 53 public ShelfBackgroundAnimatorObserver { | |
| 54 public: | |
| 55 explicit DockedBackgroundWidget(DockedWindowLayoutManager* manager) | |
| 56 : manager_(manager), | |
| 57 alignment_(DOCKED_ALIGNMENT_NONE), | |
| 58 background_animator_(SHELF_BACKGROUND_DEFAULT, | |
| 59 nullptr, | |
| 60 WmShell::Get()->wallpaper_controller()), | |
| 61 opaque_background_(ui::LAYER_SOLID_COLOR), | |
| 62 visible_background_type_(manager_->shelf()->GetBackgroundType()), | |
| 63 visible_background_change_type_(AnimationChangeType::IMMEDIATE) { | |
| 64 manager_->shelf()->AddObserver(this); | |
| 65 InitWidget(manager_->dock_container()); | |
| 66 | |
| 67 background_animator_.AddObserver(this); | |
| 68 } | |
| 69 | |
| 70 ~DockedBackgroundWidget() override { | |
| 71 background_animator_.RemoveObserver(this); | |
| 72 manager_->shelf()->RemoveObserver(this); | |
| 73 } | |
| 74 | |
| 75 // Sets widget bounds and sizes opaque background layer to fill the widget. | |
| 76 void SetBackgroundBounds(const gfx::Rect& bounds, DockedAlignment alignment) { | |
| 77 SetBounds(bounds); | |
| 78 opaque_background_.SetBounds(gfx::Rect(bounds.size())); | |
| 79 alignment_ = alignment; | |
| 80 } | |
| 81 | |
| 82 private: | |
| 83 // views::Widget: | |
| 84 void OnNativeWidgetVisibilityChanged(bool visible) override { | |
| 85 views::Widget::OnNativeWidgetVisibilityChanged(visible); | |
| 86 UpdateBackground(); | |
| 87 } | |
| 88 | |
| 89 // ShelfBackgroundAnimatorObserver: | |
| 90 void UpdateShelfBackground(SkColor color) override { | |
| 91 opaque_background_.SetColor(color); | |
| 92 } | |
| 93 | |
| 94 // WmShelfObserver: | |
| 95 void OnBackgroundTypeChanged(ShelfBackgroundType background_type, | |
| 96 AnimationChangeType change_type) override { | |
| 97 // Sets the background type. Starts an animation to transition to | |
| 98 // |background_type| if the widget is visible. If the widget is not visible, | |
| 99 // the animation is postponed till the widget becomes visible. | |
| 100 visible_background_type_ = background_type; | |
| 101 visible_background_change_type_ = change_type; | |
| 102 if (IsVisible()) | |
| 103 UpdateBackground(); | |
| 104 } | |
| 105 | |
| 106 void InitWidget(WmWindow* parent) { | |
| 107 views::Widget::InitParams params; | |
| 108 params.type = views::Widget::InitParams::TYPE_POPUP; | |
| 109 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; | |
| 110 params.keep_on_top = false; | |
| 111 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; | |
| 112 params.accept_events = false; | |
| 113 set_focus_on_creation(false); | |
| 114 parent->GetRootWindowController()->ConfigureWidgetInitParamsForContainer( | |
| 115 this, parent->GetShellWindowId(), ¶ms); | |
| 116 Init(params); | |
| 117 SetVisibilityChangedAnimationsEnabled(false); | |
| 118 WmWindow* wm_window = WmWindow::Get(this->GetNativeWindow()); | |
| 119 wm_window->SetLockedToRoot(true); | |
| 120 opaque_background_.SetColor(SK_ColorBLACK); | |
| 121 opaque_background_.SetBounds(gfx::Rect(GetWindowBoundsInScreen().size())); | |
| 122 opaque_background_.SetOpacity(0.0f); | |
| 123 wm_window->GetLayer()->Add(&opaque_background_); | |
| 124 | |
| 125 // This background should be explicitly stacked below any windows already in | |
| 126 // the dock, otherwise the z-order is set by the order in which windows were | |
| 127 // added to the container, and UpdateStacking only manages user windows, not | |
| 128 // the background widget. | |
| 129 parent->StackChildAtBottom(wm_window); | |
| 130 } | |
| 131 | |
| 132 // Transitions to |visible_background_type_| if the widget is visible and to | |
| 133 // SHELF_BACKGROUND_DEFAULT if it is not. | |
| 134 void UpdateBackground() { | |
| 135 ShelfBackgroundType background_type = | |
| 136 IsVisible() ? visible_background_type_ : SHELF_BACKGROUND_DEFAULT; | |
| 137 AnimationChangeType change_type = IsVisible() | |
| 138 ? visible_background_change_type_ | |
| 139 : AnimationChangeType::IMMEDIATE; | |
| 140 background_animator_.PaintBackground(background_type, change_type); | |
| 141 SchedulePaintInRect(gfx::Rect(GetWindowBoundsInScreen().size())); | |
| 142 } | |
| 143 | |
| 144 DockedWindowLayoutManager* manager_; | |
| 145 | |
| 146 DockedAlignment alignment_; | |
| 147 | |
| 148 // The animator for the background transitions. | |
| 149 ShelfBackgroundAnimator background_animator_; | |
| 150 | |
| 151 // TODO(bruthig): Remove opaque_background_ (see https://crbug.com/621551). | |
| 152 // Solid black background that can be made fully opaque. | |
| 153 ui::Layer opaque_background_; | |
| 154 | |
| 155 // The background type to use when the widget is visible. When not visible, | |
| 156 // the widget uses SHELF_BACKGROUND_DEFAULT. | |
| 157 ShelfBackgroundType visible_background_type_; | |
| 158 | |
| 159 // Whether the widget should animate to |visible_background_type_|. | |
| 160 AnimationChangeType visible_background_change_type_; | |
| 161 | |
| 162 DISALLOW_COPY_AND_ASSIGN(DockedBackgroundWidget); | |
| 163 }; | |
| 164 | |
| 165 namespace { | |
| 166 | |
| 167 // Returns true if a window is a popup or a transient child. | |
| 168 bool IsPopupOrTransient(const WmWindow* window) { | |
| 169 return (window->GetType() == ui::wm::WINDOW_TYPE_POPUP || | |
| 170 window->GetTransientParent()); | |
| 171 } | |
| 172 | |
| 173 // Certain windows (minimized, hidden or popups) are not docked and are ignored | |
| 174 // by layout logic even when they are children of a docked container. | |
| 175 bool IsWindowDocked(const WmWindow* window) { | |
| 176 return (window->IsVisible() && !window->GetWindowState()->IsMinimized() && | |
| 177 !IsPopupOrTransient(window)); | |
| 178 } | |
| 179 | |
| 180 void UndockWindow(WmWindow* window) { | |
| 181 gfx::Rect previous_bounds = window->GetBounds(); | |
| 182 WmWindow* old_parent = window->GetParent(); | |
| 183 window->SetParentUsingContext(window, gfx::Rect()); | |
| 184 if (window->GetParent() != old_parent) { | |
| 185 wm::ReparentTransientChildrenOfChild(window, old_parent, | |
| 186 window->GetParent()); | |
| 187 } | |
| 188 // Start maximize or fullscreen (affecting packaged apps) animation from | |
| 189 // previous window bounds. | |
| 190 window->GetLayer()->SetBounds(previous_bounds); | |
| 191 } | |
| 192 | |
| 193 // Returns width that is as close as possible to |target_width| while being | |
| 194 // consistent with docked min and max restrictions and respects the |window|'s | |
| 195 // minimum and maximum size. | |
| 196 int GetWindowWidthCloseTo(const WmWindow* window, int target_width) { | |
| 197 if (!window->GetWindowState()->CanResize()) { | |
| 198 DCHECK_LE(window->GetBounds().width(), | |
| 199 DockedWindowLayoutManager::kMaxDockWidth); | |
| 200 return window->GetBounds().width(); | |
| 201 } | |
| 202 int width = std::max( | |
| 203 DockedWindowLayoutManager::kMinDockWidth, | |
| 204 std::min(target_width, DockedWindowLayoutManager::kMaxDockWidth)); | |
| 205 width = std::max(width, window->GetMinimumSize().width()); | |
| 206 if (window->GetMaximumSize().width() != 0) | |
| 207 width = std::min(width, window->GetMaximumSize().width()); | |
| 208 DCHECK_LE(width, DockedWindowLayoutManager::kMaxDockWidth); | |
| 209 return width; | |
| 210 } | |
| 211 | |
| 212 // Returns height that is as close as possible to |target_height| while | |
| 213 // respecting the |window|'s minimum and maximum size. | |
| 214 int GetWindowHeightCloseTo(const WmWindow* window, int target_height) { | |
| 215 if (!window->GetWindowState()->CanResize()) | |
| 216 return window->GetBounds().height(); | |
| 217 int minimum_height = | |
| 218 std::max(kMinimumHeight, window->GetMinimumSize().height()); | |
| 219 int maximum_height = window->GetMaximumSize().height(); | |
| 220 if (minimum_height) | |
| 221 target_height = std::max(target_height, minimum_height); | |
| 222 if (maximum_height) | |
| 223 target_height = std::min(target_height, maximum_height); | |
| 224 return target_height; | |
| 225 } | |
| 226 | |
| 227 } // namespace | |
| 228 | |
| 229 struct DockedWindowLayoutManager::WindowWithHeight { | |
| 230 explicit WindowWithHeight(WmWindow* window) | |
| 231 : window(window), height(window->GetBounds().height()) {} | |
| 232 WmWindow* window; | |
| 233 int height; | |
| 234 }; | |
| 235 | |
| 236 // A functor used to sort the windows in order of their minimum height. | |
| 237 struct DockedWindowLayoutManager::CompareMinimumHeight { | |
| 238 bool operator()(const WindowWithHeight& win1, const WindowWithHeight& win2) { | |
| 239 return GetWindowHeightCloseTo(win1.window, 0) < | |
| 240 GetWindowHeightCloseTo(win2.window, 0); | |
| 241 } | |
| 242 }; | |
| 243 | |
| 244 // A functor used to sort the windows in order of their center Y position. | |
| 245 // |delta| is a pre-calculated distance from the bottom of one window to the top | |
| 246 // of the next. Its value can be positive (gap) or negative (overlap). | |
| 247 // Half of |delta| is used as a transition point at which windows could ideally | |
| 248 // swap positions. | |
| 249 struct DockedWindowLayoutManager::CompareWindowPos { | |
| 250 CompareWindowPos(WmWindow* dragged_window, | |
| 251 WmWindow* docked_container, | |
| 252 float delta) | |
| 253 : dragged_window_(dragged_window), | |
| 254 docked_container_(docked_container), | |
| 255 delta_(delta / 2) {} | |
| 256 | |
| 257 bool operator()(const WindowWithHeight& window_with_height1, | |
| 258 const WindowWithHeight& window_with_height2) { | |
| 259 // Use target coordinates since animations may be active when windows are | |
| 260 // reordered. | |
| 261 WmWindow* win1(window_with_height1.window); | |
| 262 WmWindow* win2(window_with_height2.window); | |
| 263 gfx::Rect win1_bounds = | |
| 264 docked_container_->ConvertRectToScreen(win1->GetTargetBounds()); | |
| 265 gfx::Rect win2_bounds = | |
| 266 docked_container_->ConvertRectToScreen(win2->GetTargetBounds()); | |
| 267 win1_bounds.set_height(window_with_height1.height); | |
| 268 win2_bounds.set_height(window_with_height2.height); | |
| 269 // If one of the windows is the |dragged_window_| attempt to make an | |
| 270 // earlier swap between the windows than just based on their centers. | |
| 271 // This is possible if the dragged window is at least as tall as the other | |
| 272 // window. | |
| 273 if (win1 == dragged_window_) | |
| 274 return compare_two_windows(win1_bounds, win2_bounds); | |
| 275 if (win2 == dragged_window_) | |
| 276 return !compare_two_windows(win2_bounds, win1_bounds); | |
| 277 // Otherwise just compare the centers. | |
| 278 return win1_bounds.CenterPoint().y() < win2_bounds.CenterPoint().y(); | |
| 279 } | |
| 280 | |
| 281 // Based on center point tries to deduce where the drag is coming from. | |
| 282 // When dragging from below up the transition point is lower. | |
| 283 // When dragging from above down the transition point is higher. | |
| 284 bool compare_bounds(const gfx::Rect& dragged, const gfx::Rect& other) { | |
| 285 if (dragged.CenterPoint().y() < other.CenterPoint().y()) | |
| 286 return dragged.CenterPoint().y() < other.y() - delta_; | |
| 287 return dragged.CenterPoint().y() < other.bottom() + delta_; | |
| 288 } | |
| 289 | |
| 290 // Performs comparison both ways and selects stable result. | |
| 291 bool compare_two_windows(const gfx::Rect& bounds1, const gfx::Rect& bounds2) { | |
| 292 // Try comparing windows in both possible orders and see if the comparison | |
| 293 // is stable. | |
| 294 bool result1 = compare_bounds(bounds1, bounds2); | |
| 295 bool result2 = compare_bounds(bounds2, bounds1); | |
| 296 if (result1 != result2) | |
| 297 return result1; | |
| 298 | |
| 299 // Otherwise it is not possible to be sure that the windows will not bounce. | |
| 300 // In this case just compare the centers. | |
| 301 return bounds1.CenterPoint().y() < bounds2.CenterPoint().y(); | |
| 302 } | |
| 303 | |
| 304 private: | |
| 305 WmWindow* dragged_window_; | |
| 306 WmWindow* docked_container_; | |
| 307 float delta_; | |
| 308 }; | |
| 309 | |
| 310 //////////////////////////////////////////////////////////////////////////////// | |
| 311 // A class that observes shelf for bounds changes. | |
| 312 class DockedWindowLayoutManager::ShelfWindowObserver | |
| 313 : public aura::WindowObserver { | |
| 314 public: | |
| 315 explicit ShelfWindowObserver(DockedWindowLayoutManager* docked_layout_manager) | |
| 316 : docked_layout_manager_(docked_layout_manager) { | |
| 317 DCHECK(docked_layout_manager_->shelf()->GetWindow()); | |
| 318 docked_layout_manager_->shelf()->GetWindow()->aura_window()->AddObserver( | |
| 319 this); | |
| 320 } | |
| 321 | |
| 322 ~ShelfWindowObserver() override { | |
| 323 if (docked_layout_manager_->shelf() && | |
| 324 docked_layout_manager_->shelf()->GetWindow()) { | |
| 325 docked_layout_manager_->shelf() | |
| 326 ->GetWindow() | |
| 327 ->aura_window() | |
| 328 ->RemoveObserver(this); | |
| 329 } | |
| 330 } | |
| 331 | |
| 332 // aura::WindowObserver: | |
| 333 void OnWindowBoundsChanged(aura::Window* window, | |
| 334 const gfx::Rect& old_bounds, | |
| 335 const gfx::Rect& new_bounds) override { | |
| 336 shelf_bounds_in_screen_ = new_bounds; | |
| 337 ::wm::ConvertRectToScreen(window->parent(), &shelf_bounds_in_screen_); | |
| 338 | |
| 339 // When the shelf is auto-hidden, it has an invisible height of 3px used | |
| 340 // as a hit region which is specific to Chrome OS MD (for non-MD, the 3 | |
| 341 // pixels are visible). In computing the work area we should consider a | |
| 342 // hidden shelf as having a height of 0 (for non-MD, shelf height is 3). | |
| 343 if (docked_layout_manager_->shelf()->GetAutoHideState() == | |
| 344 ShelfAutoHideState::SHELF_AUTO_HIDE_HIDDEN) { | |
| 345 shelf_bounds_in_screen_.set_height( | |
| 346 GetShelfConstant(SHELF_INSETS_FOR_AUTO_HIDE)); | |
| 347 } | |
| 348 docked_layout_manager_->OnShelfBoundsChanged(); | |
| 349 } | |
| 350 | |
| 351 const gfx::Rect& shelf_bounds_in_screen() const { | |
| 352 return shelf_bounds_in_screen_; | |
| 353 } | |
| 354 | |
| 355 private: | |
| 356 DockedWindowLayoutManager* docked_layout_manager_; | |
| 357 gfx::Rect shelf_bounds_in_screen_; | |
| 358 | |
| 359 DISALLOW_COPY_AND_ASSIGN(ShelfWindowObserver); | |
| 360 }; | |
| 361 | |
| 362 //////////////////////////////////////////////////////////////////////////////// | |
| 363 // DockedWindowLayoutManager public implementation: | |
| 364 DockedWindowLayoutManager::DockedWindowLayoutManager(WmWindow* dock_container) | |
| 365 : dock_container_(dock_container), | |
| 366 root_window_controller_(dock_container->GetRootWindowController()), | |
| 367 in_layout_(false), | |
| 368 dragged_window_(nullptr), | |
| 369 is_dragged_window_docked_(false), | |
| 370 is_dragged_from_dock_(false), | |
| 371 shelf_(nullptr), | |
| 372 in_fullscreen_(root_window_controller_->GetWorkspaceWindowState() == | |
| 373 wm::WORKSPACE_WINDOW_STATE_FULL_SCREEN), | |
| 374 docked_width_(0), | |
| 375 in_overview_(false), | |
| 376 alignment_(DOCKED_ALIGNMENT_NONE), | |
| 377 preferred_alignment_(DOCKED_ALIGNMENT_NONE), | |
| 378 event_source_(DOCKED_ACTION_SOURCE_UNKNOWN), | |
| 379 last_active_window_(nullptr), | |
| 380 last_action_time_(base::Time::Now()), | |
| 381 background_widget_(nullptr) { | |
| 382 DCHECK(dock_container); | |
| 383 dock_container_->GetShell()->AddShellObserver(this); | |
| 384 dock_container->GetShell()->AddActivationObserver(this); | |
| 385 display::Screen::GetScreen()->AddObserver(this); | |
| 386 } | |
| 387 | |
| 388 DockedWindowLayoutManager::~DockedWindowLayoutManager() { | |
| 389 Shutdown(); | |
| 390 } | |
| 391 | |
| 392 // static | |
| 393 DockedWindowLayoutManager* DockedWindowLayoutManager::Get(WmWindow* window) { | |
| 394 if (!window) | |
| 395 return nullptr; | |
| 396 | |
| 397 WmWindow* root = window->GetRootWindow(); | |
| 398 return static_cast<DockedWindowLayoutManager*>( | |
| 399 root->GetChildByShellWindowId(kShellWindowId_DockedContainer) | |
| 400 ->GetLayoutManager()); | |
| 401 } | |
| 402 | |
| 403 void DockedWindowLayoutManager::Shutdown() { | |
| 404 background_widget_.reset(); | |
| 405 shelf_observer_.reset(); | |
| 406 shelf_ = nullptr; | |
| 407 for (WmWindow* child : dock_container_->GetChildren()) { | |
| 408 child->aura_window()->RemoveObserver(this); | |
| 409 child->GetWindowState()->RemoveObserver(this); | |
| 410 } | |
| 411 dock_container_->GetShell()->RemoveActivationObserver(this); | |
| 412 dock_container_->GetShell()->RemoveShellObserver(this); | |
| 413 display::Screen::GetScreen()->RemoveObserver(this); | |
| 414 } | |
| 415 | |
| 416 void DockedWindowLayoutManager::AddObserver( | |
| 417 DockedWindowLayoutManagerObserver* observer) { | |
| 418 observer_list_.AddObserver(observer); | |
| 419 } | |
| 420 | |
| 421 void DockedWindowLayoutManager::RemoveObserver( | |
| 422 DockedWindowLayoutManagerObserver* observer) { | |
| 423 observer_list_.RemoveObserver(observer); | |
| 424 } | |
| 425 | |
| 426 void DockedWindowLayoutManager::StartDragging(WmWindow* window) { | |
| 427 DCHECK(!dragged_window_); | |
| 428 dragged_window_ = window; | |
| 429 DCHECK(!IsPopupOrTransient(window)); | |
| 430 // Start observing a window unless it is docked container's child in which | |
| 431 // case it is already observed. | |
| 432 wm::WindowState* dragged_state = dragged_window_->GetWindowState(); | |
| 433 if (dragged_window_->GetParent() != dock_container_) { | |
| 434 dragged_window_->aura_window()->AddObserver(this); | |
| 435 dragged_state->AddObserver(this); | |
| 436 } else if (!IsAnyWindowDocked() && dragged_state->drag_details() && | |
| 437 !(dragged_state->drag_details()->bounds_change & | |
| 438 WindowResizer::kBoundsChange_Resizes)) { | |
| 439 // If there are no other docked windows clear alignment when a docked window | |
| 440 // is moved (but not when it is resized or the window could get undocked | |
| 441 // when resized away from the edge while docked). | |
| 442 alignment_ = DOCKED_ALIGNMENT_NONE; | |
| 443 } | |
| 444 is_dragged_from_dock_ = window->GetParent() == dock_container_; | |
| 445 DCHECK(!is_dragged_window_docked_); | |
| 446 | |
| 447 // Resize all windows that are flush with the dock edge together if one of | |
| 448 // them gets resized. | |
| 449 if (dragged_window_->GetBounds().width() == docked_width_ && | |
| 450 (dragged_state->drag_details()->bounds_change & | |
| 451 WindowResizer::kBoundsChange_Resizes) && | |
| 452 (dragged_state->drag_details()->size_change_direction & | |
| 453 WindowResizer::kBoundsChangeDirection_Horizontal)) { | |
| 454 for (WmWindow* window1 : dock_container_->GetChildren()) { | |
| 455 if (IsWindowDocked(window1) && window1 != dragged_window_ && | |
| 456 window1->GetBounds().width() == docked_width_) { | |
| 457 window1->GetWindowState()->set_bounds_changed_by_user(false); | |
| 458 } | |
| 459 } | |
| 460 } | |
| 461 } | |
| 462 | |
| 463 void DockedWindowLayoutManager::DockDraggedWindow(WmWindow* window) { | |
| 464 DCHECK(!IsPopupOrTransient(window)); | |
| 465 OnDraggedWindowDocked(window); | |
| 466 Relayout(); | |
| 467 } | |
| 468 | |
| 469 void DockedWindowLayoutManager::UndockDraggedWindow() { | |
| 470 DCHECK(!IsPopupOrTransient(dragged_window_)); | |
| 471 OnDraggedWindowUndocked(); | |
| 472 Relayout(); | |
| 473 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); | |
| 474 is_dragged_from_dock_ = false; | |
| 475 } | |
| 476 | |
| 477 void DockedWindowLayoutManager::FinishDragging(DockedAction action, | |
| 478 DockedActionSource source) { | |
| 479 DCHECK(dragged_window_); | |
| 480 DCHECK(!IsPopupOrTransient(dragged_window_)); | |
| 481 if (is_dragged_window_docked_) | |
| 482 OnDraggedWindowUndocked(); | |
| 483 DCHECK(!is_dragged_window_docked_); | |
| 484 // Stop observing a window unless it is docked container's child in which | |
| 485 // case it needs to keep being observed after the drag completes. | |
| 486 if (dragged_window_->GetParent() != dock_container_) { | |
| 487 dragged_window_->aura_window()->RemoveObserver(this); | |
| 488 dragged_window_->GetWindowState()->RemoveObserver(this); | |
| 489 if (last_active_window_ == dragged_window_) | |
| 490 last_active_window_ = nullptr; | |
| 491 } else { | |
| 492 // If this is the first window that got docked by a move update alignment. | |
| 493 if (alignment_ == DOCKED_ALIGNMENT_NONE) | |
| 494 alignment_ = GetEdgeNearestWindow(dragged_window_); | |
| 495 // A window is no longer dragged and is a child. | |
| 496 // When a window becomes a child at drag start this is | |
| 497 // the only opportunity we will have to enforce a window | |
| 498 // count limit so do it here. | |
| 499 MaybeMinimizeChildrenExcept(dragged_window_); | |
| 500 } | |
| 501 dragged_window_ = nullptr; | |
| 502 dragged_bounds_ = gfx::Rect(); | |
| 503 Relayout(); | |
| 504 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); | |
| 505 RecordUmaAction(action, source); | |
| 506 } | |
| 507 | |
| 508 void DockedWindowLayoutManager::SetShelf(WmShelf* shelf) { | |
| 509 DCHECK(!shelf_); | |
| 510 shelf_ = shelf; | |
| 511 shelf_observer_.reset(new ShelfWindowObserver(this)); | |
| 512 } | |
| 513 | |
| 514 DockedAlignment DockedWindowLayoutManager::GetAlignmentOfWindow( | |
| 515 const WmWindow* window) const { | |
| 516 const gfx::Rect& bounds(window->GetBoundsInScreen()); | |
| 517 | |
| 518 // Test overlap with an existing docked area first. | |
| 519 if (docked_bounds_.Intersects(bounds) && | |
| 520 alignment_ != DOCKED_ALIGNMENT_NONE) { | |
| 521 // A window is being added to other docked windows (on the same side). | |
| 522 return alignment_; | |
| 523 } | |
| 524 | |
| 525 const gfx::Rect container_bounds = dock_container_->GetBoundsInScreen(); | |
| 526 if (bounds.x() <= container_bounds.x() && | |
| 527 bounds.right() > container_bounds.x()) { | |
| 528 return DOCKED_ALIGNMENT_LEFT; | |
| 529 } else if (bounds.x() < container_bounds.right() && | |
| 530 bounds.right() >= container_bounds.right()) { | |
| 531 return DOCKED_ALIGNMENT_RIGHT; | |
| 532 } | |
| 533 return DOCKED_ALIGNMENT_NONE; | |
| 534 } | |
| 535 | |
| 536 DockedAlignment DockedWindowLayoutManager::CalculateAlignment() const { | |
| 537 return CalculateAlignmentExcept(dragged_window_); | |
| 538 } | |
| 539 | |
| 540 DockedAlignment DockedWindowLayoutManager::CalculateAlignmentExcept( | |
| 541 const WmWindow* window) const { | |
| 542 // Find a child that is not the window being queried and is not a popup. | |
| 543 // If such exists the current alignment is returned - even if some of the | |
| 544 // children are hidden or minimized (so they can be restored without losing | |
| 545 // the docked state). | |
| 546 for (WmWindow* child : dock_container_->GetChildren()) { | |
| 547 if (window != child && !IsPopupOrTransient(child)) | |
| 548 return alignment_; | |
| 549 } | |
| 550 // No docked windows remain other than possibly the window being queried. | |
| 551 // Return |NONE| to indicate that windows may get docked on either side. | |
| 552 return DOCKED_ALIGNMENT_NONE; | |
| 553 } | |
| 554 | |
| 555 bool DockedWindowLayoutManager::CanDockWindow( | |
| 556 WmWindow* window, | |
| 557 DockedAlignment desired_alignment) { | |
| 558 // Don't allow interactive docking of windows with transient parents such as | |
| 559 // modal browser dialogs. Prevent docking of panels attached to shelf during | |
| 560 // the drag. | |
| 561 wm::WindowState* window_state = window->GetWindowState(); | |
| 562 bool should_attach_to_shelf = | |
| 563 window_state->drag_details() && | |
| 564 window_state->drag_details()->should_attach_to_shelf; | |
| 565 if (IsPopupOrTransient(window) || should_attach_to_shelf) | |
| 566 return false; | |
| 567 // If a window is wide and cannot be resized down to maximum width allowed | |
| 568 // then it cannot be docked. | |
| 569 // TODO(varkha). Prevent windows from changing size programmatically while | |
| 570 // they are docked. The size will take effect only once a window is undocked. | |
| 571 // See http://crbug.com/307792. | |
| 572 if (window->GetBounds().width() > kMaxDockWidth && | |
| 573 (!window_state->CanResize() || | |
| 574 (window->GetMinimumSize().width() != 0 && | |
| 575 window->GetMinimumSize().width() > kMaxDockWidth))) { | |
| 576 return false; | |
| 577 } | |
| 578 // If a window is tall and cannot be resized down to maximum height allowed | |
| 579 // then it cannot be docked. | |
| 580 const gfx::Rect work_area = | |
| 581 dock_container_->GetDisplayNearestWindow().work_area(); | |
| 582 if (GetWindowHeightCloseTo(window, work_area.height()) > work_area.height()) | |
| 583 return false; | |
| 584 // Cannot dock on the other size from an existing dock. | |
| 585 const DockedAlignment alignment = CalculateAlignmentExcept(window); | |
| 586 if (desired_alignment != DOCKED_ALIGNMENT_NONE && | |
| 587 alignment != DOCKED_ALIGNMENT_NONE && alignment != desired_alignment) { | |
| 588 return false; | |
| 589 } | |
| 590 // Do not allow docking on the same side as shelf. | |
| 591 return IsDockedAlignmentValid(desired_alignment); | |
| 592 } | |
| 593 | |
| 594 bool DockedWindowLayoutManager::IsDockedAlignmentValid( | |
| 595 DockedAlignment alignment) const { | |
| 596 ShelfAlignment shelf_alignment = | |
| 597 shelf_ ? shelf_->GetAlignment() : SHELF_ALIGNMENT_BOTTOM; | |
| 598 if ((alignment == DOCKED_ALIGNMENT_LEFT && | |
| 599 shelf_alignment == SHELF_ALIGNMENT_LEFT) || | |
| 600 (alignment == DOCKED_ALIGNMENT_RIGHT && | |
| 601 shelf_alignment == SHELF_ALIGNMENT_RIGHT)) { | |
| 602 return false; | |
| 603 } | |
| 604 return true; | |
| 605 } | |
| 606 | |
| 607 void DockedWindowLayoutManager::MaybeSetDesiredDockedAlignment( | |
| 608 DockedAlignment alignment) { | |
| 609 // If the requested alignment is |NONE| or there are no | |
| 610 // docked windows return early as we can't change whether there is a | |
| 611 // dock or not. If the requested alignment is the same as the current | |
| 612 // alignment return early as an optimization. | |
| 613 if (alignment == DOCKED_ALIGNMENT_NONE || | |
| 614 alignment_ == DOCKED_ALIGNMENT_NONE || alignment_ == alignment || | |
| 615 !IsDockedAlignmentValid(alignment)) { | |
| 616 return; | |
| 617 } | |
| 618 alignment_ = alignment; | |
| 619 | |
| 620 Relayout(); | |
| 621 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); | |
| 622 } | |
| 623 | |
| 624 void DockedWindowLayoutManager::OnShelfBoundsChanged() { | |
| 625 Relayout(); | |
| 626 UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_INSETS_CHANGED); | |
| 627 } | |
| 628 | |
| 629 //////////////////////////////////////////////////////////////////////////////// | |
| 630 // DockedWindowLayoutManager, aura::LayoutManager implementation: | |
| 631 void DockedWindowLayoutManager::OnWindowResized() { | |
| 632 MaybeMinimizeChildrenExcept(dragged_window_); | |
| 633 Relayout(); | |
| 634 // When screen resizes update the insets even when dock width or alignment | |
| 635 // does not change. | |
| 636 UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_RESIZED); | |
| 637 } | |
| 638 | |
| 639 void DockedWindowLayoutManager::OnWindowAddedToLayout(WmWindow* child) { | |
| 640 if (IsPopupOrTransient(child)) | |
| 641 return; | |
| 642 // Dragged windows are already observed by StartDragging and do not change | |
| 643 // docked alignment during the drag. | |
| 644 if (child == dragged_window_) | |
| 645 return; | |
| 646 // If this is the first window getting docked - update alignment. | |
| 647 // A window can be added without proper bounds when window is moved to another | |
| 648 // display via API or due to display configuration change, so the alignment | |
| 649 // is set based on which edge is closer in the new display. | |
| 650 if (alignment_ == DOCKED_ALIGNMENT_NONE) { | |
| 651 alignment_ = preferred_alignment_ != DOCKED_ALIGNMENT_NONE | |
| 652 ? preferred_alignment_ | |
| 653 : GetEdgeNearestWindow(child); | |
| 654 } | |
| 655 MaybeMinimizeChildrenExcept(child); | |
| 656 child->aura_window()->AddObserver(this); | |
| 657 child->GetWindowState()->AddObserver(this); | |
| 658 Relayout(); | |
| 659 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); | |
| 660 | |
| 661 // Only keyboard-initiated actions are recorded here. Dragging cases | |
| 662 // are handled in FinishDragging. | |
| 663 if (event_source_ != DOCKED_ACTION_SOURCE_UNKNOWN) | |
| 664 RecordUmaAction(DOCKED_ACTION_DOCK, event_source_); | |
| 665 } | |
| 666 | |
| 667 void DockedWindowLayoutManager::OnWindowRemovedFromLayout(WmWindow* child) { | |
| 668 if (IsPopupOrTransient(child)) | |
| 669 return; | |
| 670 // Dragged windows are stopped being observed by FinishDragging and do not | |
| 671 // change alignment during the drag. They also cannot be set to be the | |
| 672 // |last_active_window_|. | |
| 673 if (child == dragged_window_) | |
| 674 return; | |
| 675 // If this is the last window, set alignment and maximize the workspace. | |
| 676 if (!IsAnyWindowDocked()) { | |
| 677 alignment_ = DOCKED_ALIGNMENT_NONE; | |
| 678 UpdateDockedWidth(0); | |
| 679 } | |
| 680 if (last_active_window_ == child) | |
| 681 last_active_window_ = nullptr; | |
| 682 child->aura_window()->RemoveObserver(this); | |
| 683 child->GetWindowState()->RemoveObserver(this); | |
| 684 Relayout(); | |
| 685 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); | |
| 686 } | |
| 687 | |
| 688 void DockedWindowLayoutManager::OnChildWindowVisibilityChanged(WmWindow* child, | |
| 689 bool visible) { | |
| 690 if (IsPopupOrTransient(child)) | |
| 691 return; | |
| 692 | |
| 693 wm::WindowState* window_state = child->GetWindowState(); | |
| 694 if (visible && window_state->IsMinimized()) | |
| 695 window_state->Restore(); | |
| 696 Relayout(); | |
| 697 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); | |
| 698 } | |
| 699 | |
| 700 void DockedWindowLayoutManager::SetChildBounds( | |
| 701 WmWindow* child, | |
| 702 const gfx::Rect& requested_bounds) { | |
| 703 // The minimum constraints have to be applied first by the layout manager. | |
| 704 gfx::Rect actual_new_bounds(requested_bounds); | |
| 705 if (child->HasNonClientArea()) { | |
| 706 const gfx::Size min_size = child->GetMinimumSize(); | |
| 707 actual_new_bounds.set_width( | |
| 708 std::max(min_size.width(), actual_new_bounds.width())); | |
| 709 actual_new_bounds.set_height( | |
| 710 std::max(min_size.height(), actual_new_bounds.height())); | |
| 711 } | |
| 712 if (IsWindowDocked(child) && child != dragged_window_) | |
| 713 return; | |
| 714 wm::WmSnapToPixelLayoutManager::SetChildBounds(child, actual_new_bounds); | |
| 715 if (IsPopupOrTransient(child)) | |
| 716 return; | |
| 717 // Whenever one of our windows is moved or resized enforce layout. | |
| 718 if (shelf_) | |
| 719 shelf_->UpdateVisibilityState(); | |
| 720 } | |
| 721 | |
| 722 //////////////////////////////////////////////////////////////////////////////// | |
| 723 // DockedWindowLayoutManager, display::DisplayObserver implementation: | |
| 724 | |
| 725 void DockedWindowLayoutManager::OnDisplayMetricsChanged( | |
| 726 const display::Display& display, | |
| 727 uint32_t changed_metrics) { | |
| 728 if (dock_container_->GetDisplayNearestWindow().id() != display.id()) | |
| 729 return; | |
| 730 | |
| 731 Relayout(); | |
| 732 UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_INSETS_CHANGED); | |
| 733 MaybeMinimizeChildrenExcept(dragged_window_); | |
| 734 } | |
| 735 | |
| 736 ///////////////////////////////////////////////////////////////////////////// | |
| 737 // DockedWindowLayoutManager, WindowStateObserver implementation: | |
| 738 | |
| 739 void DockedWindowLayoutManager::OnPreWindowStateTypeChange( | |
| 740 wm::WindowState* window_state, | |
| 741 wm::WindowStateType old_type) { | |
| 742 WmWindow* window = window_state->window(); | |
| 743 if (IsPopupOrTransient(window)) | |
| 744 return; | |
| 745 // The window property will still be set, but no actual change will occur | |
| 746 // until OnFullscreenStateChange is called when exiting fullscreen. | |
| 747 if (in_fullscreen_) | |
| 748 return; | |
| 749 if (!window_state->IsDocked()) { | |
| 750 if (window != dragged_window_) { | |
| 751 UndockWindow(window); | |
| 752 if (window_state->IsMaximizedOrFullscreenOrPinned()) | |
| 753 RecordUmaAction(DOCKED_ACTION_MAXIMIZE, event_source_); | |
| 754 else | |
| 755 RecordUmaAction(DOCKED_ACTION_UNDOCK, event_source_); | |
| 756 } | |
| 757 } else if (window_state->IsMinimized()) { | |
| 758 MinimizeDockedWindow(window_state); | |
| 759 } else if (old_type == wm::WINDOW_STATE_TYPE_DOCKED_MINIMIZED) { | |
| 760 RestoreDockedWindow(window_state); | |
| 761 } else if (old_type == wm::WINDOW_STATE_TYPE_MINIMIZED) { | |
| 762 NOTREACHED() << "Minimized window in docked layout manager"; | |
| 763 } | |
| 764 } | |
| 765 | |
| 766 ///////////////////////////////////////////////////////////////////////////// | |
| 767 // DockedWindowLayoutManager, WindowObserver implementation: | |
| 768 | |
| 769 void DockedWindowLayoutManager::OnWindowBoundsChanged( | |
| 770 aura::Window* window, | |
| 771 const gfx::Rect& old_bounds, | |
| 772 const gfx::Rect& new_bounds) { | |
| 773 // Only relayout if the dragged window would get docked. | |
| 774 if (WmWindow::Get(window) == dragged_window_ && is_dragged_window_docked_) | |
| 775 Relayout(); | |
| 776 } | |
| 777 | |
| 778 void DockedWindowLayoutManager::OnWindowVisibilityChanging(aura::Window* window, | |
| 779 bool visible) { | |
| 780 if (IsPopupOrTransient(WmWindow::Get(window))) | |
| 781 return; | |
| 782 int animation_type = ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT; | |
| 783 if (visible) { | |
| 784 animation_type = ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DROP; | |
| 785 ::wm::SetWindowVisibilityAnimationDuration( | |
| 786 window, base::TimeDelta::FromMilliseconds(kFadeDurationMs)); | |
| 787 } else if (wm::GetWindowState(window)->IsMinimized()) { | |
| 788 animation_type = wm::WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE; | |
| 789 } | |
| 790 ::wm::SetWindowVisibilityAnimationType(window, animation_type); | |
| 791 } | |
| 792 | |
| 793 void DockedWindowLayoutManager::OnWindowDestroying(aura::Window* window) { | |
| 794 if (dragged_window_ == WmWindow::Get(window)) { | |
| 795 FinishDragging(DOCKED_ACTION_NONE, DOCKED_ACTION_SOURCE_UNKNOWN); | |
| 796 DCHECK(!dragged_window_); | |
| 797 DCHECK(!is_dragged_window_docked_); | |
| 798 } | |
| 799 if (WmWindow::Get(window) == last_active_window_) | |
| 800 last_active_window_ = nullptr; | |
| 801 RecordUmaAction(DOCKED_ACTION_CLOSE, event_source_); | |
| 802 } | |
| 803 | |
| 804 //////////////////////////////////////////////////////////////////////////////// | |
| 805 // DockedWindowLayoutManager, WmActivationObserver implementation: | |
| 806 | |
| 807 void DockedWindowLayoutManager::OnWindowActivated(WmWindow* gained_active, | |
| 808 WmWindow* lost_active) { | |
| 809 if (gained_active && IsPopupOrTransient(gained_active)) | |
| 810 return; | |
| 811 // Ignore if the window that is not managed by this was activated. | |
| 812 WmWindow* ancestor = nullptr; | |
| 813 for (WmWindow* parent = gained_active; parent; parent = parent->GetParent()) { | |
| 814 if (parent->GetParent() == dock_container_) { | |
| 815 ancestor = parent; | |
| 816 break; | |
| 817 } | |
| 818 } | |
| 819 if (ancestor) { | |
| 820 // Window activation from overview mode may unminimize a window and require | |
| 821 // layout update. | |
| 822 MaybeMinimizeChildrenExcept(gained_active); | |
| 823 Relayout(); | |
| 824 UpdateStacking(ancestor); | |
| 825 } | |
| 826 } | |
| 827 | |
| 828 //////////////////////////////////////////////////////////////////////////////// | |
| 829 // DockedWindowLayoutManager, ShellObserver implementation: | |
| 830 | |
| 831 void DockedWindowLayoutManager::OnShelfAlignmentChanged(WmWindow* root_window) { | |
| 832 if (!shelf_ || alignment_ == DOCKED_ALIGNMENT_NONE || | |
| 833 root_window != shelf_->GetWindow()->GetRootWindow()) { | |
| 834 return; | |
| 835 } | |
| 836 | |
| 837 // Do not allow shelf and dock on the same side. Switch side that | |
| 838 // the dock is attached to and move all dock windows to that new side. | |
| 839 ShelfAlignment shelf_alignment = shelf_->GetAlignment(); | |
| 840 if (alignment_ == DOCKED_ALIGNMENT_LEFT && | |
| 841 shelf_alignment == SHELF_ALIGNMENT_LEFT) { | |
| 842 alignment_ = DOCKED_ALIGNMENT_RIGHT; | |
| 843 } else if (alignment_ == DOCKED_ALIGNMENT_RIGHT && | |
| 844 shelf_alignment == SHELF_ALIGNMENT_RIGHT) { | |
| 845 alignment_ = DOCKED_ALIGNMENT_LEFT; | |
| 846 } | |
| 847 Relayout(); | |
| 848 UpdateDockBounds(DockedWindowLayoutManagerObserver::SHELF_ALIGNMENT_CHANGED); | |
| 849 } | |
| 850 | |
| 851 void DockedWindowLayoutManager::OnFullscreenStateChanged( | |
| 852 bool is_fullscreen, | |
| 853 WmWindow* root_window) { | |
| 854 if (root_window != dock_container_->GetRootWindow()) | |
| 855 return; | |
| 856 | |
| 857 // Entering fullscreen mode (including immersive) hides docked windows. | |
| 858 in_fullscreen_ = root_window_controller_->GetWorkspaceWindowState() == | |
| 859 wm::WORKSPACE_WINDOW_STATE_FULL_SCREEN; | |
| 860 { | |
| 861 // prevent Relayout from getting called multiple times during this | |
| 862 base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true); | |
| 863 // Use a copy of children array because a call to MinimizeDockedWindow or | |
| 864 // RestoreDockedWindow can change order. | |
| 865 for (WmWindow* window : dock_container_->GetChildren()) { | |
| 866 if (IsPopupOrTransient(window)) | |
| 867 continue; | |
| 868 wm::WindowState* window_state = window->GetWindowState(); | |
| 869 if (in_fullscreen_) { | |
| 870 if (window->IsVisible()) | |
| 871 MinimizeDockedWindow(window_state); | |
| 872 } else { | |
| 873 if (!window_state->IsMinimized()) | |
| 874 RestoreDockedWindow(window_state); | |
| 875 } | |
| 876 } | |
| 877 } | |
| 878 Relayout(); | |
| 879 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); | |
| 880 } | |
| 881 | |
| 882 void DockedWindowLayoutManager::OnOverviewModeStarting() { | |
| 883 in_overview_ = true; | |
| 884 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); | |
| 885 } | |
| 886 | |
| 887 void DockedWindowLayoutManager::OnOverviewModeEnded() { | |
| 888 in_overview_ = false; | |
| 889 UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); | |
| 890 } | |
| 891 | |
| 892 //////////////////////////////////////////////////////////////////////////////// | |
| 893 // DockedWindowLayoutManager private implementation: | |
| 894 | |
| 895 void DockedWindowLayoutManager::MaybeMinimizeChildrenExcept(WmWindow* child) { | |
| 896 WindowSelectorController* window_selector_controller = | |
| 897 WmShell::Get()->window_selector_controller(); | |
| 898 if (window_selector_controller->IsRestoringMinimizedWindows()) | |
| 899 return; | |
| 900 // Minimize any windows that don't fit without overlap. | |
| 901 const gfx::Rect work_area = | |
| 902 dock_container_->GetDisplayNearestWindow().work_area(); | |
| 903 int available_room = work_area.height(); | |
| 904 bool gap_needed = !!child; | |
| 905 if (child) | |
| 906 available_room -= GetWindowHeightCloseTo(child, 0); | |
| 907 // Use a copy of children array because a call to Minimize can change order. | |
| 908 std::vector<WmWindow*> children(dock_container_->GetChildren()); | |
| 909 for (auto iter = children.rbegin(); iter != children.rend(); ++iter) { | |
| 910 WmWindow* window(*iter); | |
| 911 if (window == child || !IsWindowDocked(window)) | |
| 912 continue; | |
| 913 int room_needed = | |
| 914 GetWindowHeightCloseTo(window, 0) + (gap_needed ? kMinDockGap : 0); | |
| 915 gap_needed = true; | |
| 916 if (available_room > room_needed) { | |
| 917 available_room -= room_needed; | |
| 918 } else { | |
| 919 // Slow down minimizing animations. Lock duration so that it is not | |
| 920 // overridden by other ScopedLayerAnimationSettings down the stack. | |
| 921 ui::ScopedLayerAnimationSettings settings( | |
| 922 window->GetLayer()->GetAnimator()); | |
| 923 settings.SetTransitionDuration( | |
| 924 base::TimeDelta::FromMilliseconds(kMinimizeDurationMs)); | |
| 925 settings.LockTransitionDuration(); | |
| 926 window->GetWindowState()->Minimize(); | |
| 927 } | |
| 928 } | |
| 929 } | |
| 930 | |
| 931 void DockedWindowLayoutManager::MinimizeDockedWindow( | |
| 932 wm::WindowState* window_state) { | |
| 933 DCHECK(!IsPopupOrTransient(window_state->window())); | |
| 934 window_state->window()->Hide(); | |
| 935 if (window_state->IsActive()) | |
| 936 window_state->Deactivate(); | |
| 937 RecordUmaAction(DOCKED_ACTION_MINIMIZE, event_source_); | |
| 938 } | |
| 939 | |
| 940 void DockedWindowLayoutManager::RestoreDockedWindow( | |
| 941 wm::WindowState* window_state) { | |
| 942 WmWindow* window = window_state->window(); | |
| 943 DCHECK(!IsPopupOrTransient(window)); | |
| 944 | |
| 945 // Evict the window if it can no longer be docked because of its height. | |
| 946 if (!CanDockWindow(window, DOCKED_ALIGNMENT_NONE)) { | |
| 947 window_state->Restore(); | |
| 948 RecordUmaAction(DOCKED_ACTION_EVICT, event_source_); | |
| 949 return; | |
| 950 } | |
| 951 | |
| 952 // Always place restored window at the bottom shuffling the other windows up. | |
| 953 // TODO(varkha): add a separate container for docked windows to keep track | |
| 954 // of ordering. | |
| 955 const gfx::Rect work_area = | |
| 956 dock_container_->GetDisplayNearestWindow().work_area(); | |
| 957 gfx::Rect bounds(window->GetBounds()); | |
| 958 bounds.set_y(work_area.bottom()); | |
| 959 window->SetBounds(bounds); | |
| 960 window->Show(); | |
| 961 MaybeMinimizeChildrenExcept(window); | |
| 962 RecordUmaAction(DOCKED_ACTION_RESTORE, event_source_); | |
| 963 } | |
| 964 | |
| 965 void DockedWindowLayoutManager::RecordUmaAction(DockedAction action, | |
| 966 DockedActionSource source) { | |
| 967 if (action == DOCKED_ACTION_NONE) | |
| 968 return; | |
| 969 UMA_HISTOGRAM_ENUMERATION("Ash.Dock.Action", action, DOCKED_ACTION_COUNT); | |
| 970 UMA_HISTOGRAM_ENUMERATION("Ash.Dock.ActionSource", source, | |
| 971 DOCKED_ACTION_SOURCE_COUNT); | |
| 972 base::Time time_now = base::Time::Now(); | |
| 973 base::TimeDelta time_between_use = time_now - last_action_time_; | |
| 974 UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.Dock.TimeBetweenUse", | |
| 975 time_between_use.InSeconds(), 1, | |
| 976 base::TimeDelta::FromHours(10).InSeconds(), 100); | |
| 977 last_action_time_ = time_now; | |
| 978 int docked_all_count = 0; | |
| 979 int docked_visible_count = 0; | |
| 980 int docked_panels_count = 0; | |
| 981 int large_windows_count = 0; | |
| 982 for (WmWindow* window : dock_container_->GetChildren()) { | |
| 983 if (IsPopupOrTransient(window)) | |
| 984 continue; | |
| 985 docked_all_count++; | |
| 986 if (!IsWindowDocked(window)) | |
| 987 continue; | |
| 988 docked_visible_count++; | |
| 989 if (window->GetType() == ui::wm::WINDOW_TYPE_PANEL) | |
| 990 docked_panels_count++; | |
| 991 const wm::WindowState* window_state = window->GetWindowState(); | |
| 992 if (window_state->HasRestoreBounds()) { | |
| 993 const gfx::Rect restore_bounds = window_state->GetRestoreBoundsInScreen(); | |
| 994 if (restore_bounds.width() > kMaxDockWidth) | |
| 995 large_windows_count++; | |
| 996 } | |
| 997 } | |
| 998 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsAll", docked_all_count); | |
| 999 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsLarge", large_windows_count); | |
| 1000 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsPanels", docked_panels_count); | |
| 1001 UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsVisible", docked_visible_count); | |
| 1002 } | |
| 1003 | |
| 1004 void DockedWindowLayoutManager::UpdateDockedWidth(int width) { | |
| 1005 if (docked_width_ == width) | |
| 1006 return; | |
| 1007 docked_width_ = width; | |
| 1008 UMA_HISTOGRAM_COUNTS_10000("Ash.Dock.Width", docked_width_); | |
| 1009 } | |
| 1010 | |
| 1011 void DockedWindowLayoutManager::OnDraggedWindowDocked(WmWindow* window) { | |
| 1012 DCHECK(!is_dragged_window_docked_); | |
| 1013 is_dragged_window_docked_ = true; | |
| 1014 } | |
| 1015 | |
| 1016 void DockedWindowLayoutManager::OnDraggedWindowUndocked() { | |
| 1017 DCHECK(is_dragged_window_docked_); | |
| 1018 is_dragged_window_docked_ = false; | |
| 1019 } | |
| 1020 | |
| 1021 bool DockedWindowLayoutManager::IsAnyWindowDocked() { | |
| 1022 return CalculateAlignment() != DOCKED_ALIGNMENT_NONE; | |
| 1023 } | |
| 1024 | |
| 1025 DockedAlignment DockedWindowLayoutManager::GetEdgeNearestWindow( | |
| 1026 const WmWindow* window) const { | |
| 1027 const gfx::Rect bounds(window->GetBoundsInScreen()); | |
| 1028 const gfx::Rect container_bounds = dock_container_->GetBoundsInScreen(); | |
| 1029 // Give one pixel preference for docking on the right side to a window that | |
| 1030 // has odd width and is centered in a screen that has even width (or vice | |
| 1031 // versa). This only matters to the tests but could be a source of flakiness. | |
| 1032 return (abs(bounds.x() - container_bounds.x()) + 1 < | |
| 1033 abs(bounds.right() - container_bounds.right())) | |
| 1034 ? DOCKED_ALIGNMENT_LEFT | |
| 1035 : DOCKED_ALIGNMENT_RIGHT; | |
| 1036 } | |
| 1037 | |
| 1038 void DockedWindowLayoutManager::Relayout() { | |
| 1039 // Suppress layouts during overview mode while restoring minimized windows so | |
| 1040 // that docked animations are not interfering with the overview mode. | |
| 1041 WindowSelectorController* window_selector_controller = | |
| 1042 WmShell::Get()->window_selector_controller(); | |
| 1043 if (in_layout_ || (window_selector_controller->IsRestoringMinimizedWindows())) | |
| 1044 return; | |
| 1045 if (alignment_ == DOCKED_ALIGNMENT_NONE && !is_dragged_window_docked_) | |
| 1046 return; | |
| 1047 base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true); | |
| 1048 | |
| 1049 WmWindow* active_window = nullptr; | |
| 1050 std::vector<WindowWithHeight> visible_windows; | |
| 1051 for (WmWindow* window : dock_container_->GetChildren()) { | |
| 1052 if (!IsWindowDocked(window) || window == dragged_window_) | |
| 1053 continue; | |
| 1054 | |
| 1055 // If the shelf is currently hidden (full-screen mode), hide window until | |
| 1056 // full-screen mode is exited. | |
| 1057 if (in_fullscreen_) { | |
| 1058 // The call to Hide does not set the minimize property, so the window will | |
| 1059 // be restored when the shelf becomes visible again. | |
| 1060 window->Hide(); | |
| 1061 continue; | |
| 1062 } | |
| 1063 if (window->IsFocused() || | |
| 1064 window->Contains(window->GetShell()->GetFocusedWindow())) { | |
| 1065 DCHECK(!active_window); | |
| 1066 active_window = window; | |
| 1067 } | |
| 1068 visible_windows.push_back(WindowWithHeight(window)); | |
| 1069 } | |
| 1070 // Consider docked dragged_window_ when fanning out other child windows. | |
| 1071 if (is_dragged_window_docked_) { | |
| 1072 visible_windows.push_back(WindowWithHeight(dragged_window_)); | |
| 1073 DCHECK(!active_window); | |
| 1074 active_window = dragged_window_; | |
| 1075 } | |
| 1076 | |
| 1077 // Position docked windows as well as the window being dragged. | |
| 1078 gfx::Rect work_area = dock_container_->GetDisplayNearestWindow().work_area(); | |
| 1079 if (shelf_observer_) | |
| 1080 work_area.Subtract(shelf_observer_->shelf_bounds_in_screen()); | |
| 1081 int available_room = | |
| 1082 CalculateWindowHeightsAndRemainingRoom(work_area, &visible_windows); | |
| 1083 FanOutChildren(work_area, CalculateIdealWidth(visible_windows), | |
| 1084 available_room, &visible_windows); | |
| 1085 | |
| 1086 // After the first Relayout allow the windows to change their order easier | |
| 1087 // since we know they are docked. | |
| 1088 is_dragged_from_dock_ = true; | |
| 1089 UpdateStacking(active_window); | |
| 1090 } | |
| 1091 | |
| 1092 int DockedWindowLayoutManager::CalculateWindowHeightsAndRemainingRoom( | |
| 1093 const gfx::Rect& work_area, | |
| 1094 std::vector<WindowWithHeight>* visible_windows) { | |
| 1095 int available_room = work_area.height(); | |
| 1096 int remaining_windows = visible_windows->size(); | |
| 1097 int gap_height = remaining_windows > 1 ? kMinDockGap : 0; | |
| 1098 | |
| 1099 // Sort windows by their minimum heights and calculate target heights. | |
| 1100 std::sort(visible_windows->begin(), visible_windows->end(), | |
| 1101 CompareMinimumHeight()); | |
| 1102 // Distribute the free space among the docked windows. Since the windows are | |
| 1103 // sorted (tall windows first) we can now assume that any window which | |
| 1104 // required more space than the current window will have already been | |
| 1105 // accounted for previously in this loop, so we can safely give that window | |
| 1106 // its proportional share of the remaining space. | |
| 1107 for (std::vector<WindowWithHeight>::reverse_iterator iter = | |
| 1108 visible_windows->rbegin(); | |
| 1109 iter != visible_windows->rend(); ++iter) { | |
| 1110 iter->height = GetWindowHeightCloseTo( | |
| 1111 iter->window, | |
| 1112 (available_room + gap_height) / remaining_windows - gap_height); | |
| 1113 available_room -= (iter->height + gap_height); | |
| 1114 remaining_windows--; | |
| 1115 } | |
| 1116 return available_room + gap_height; | |
| 1117 } | |
| 1118 | |
| 1119 int DockedWindowLayoutManager::CalculateIdealWidth( | |
| 1120 const std::vector<WindowWithHeight>& visible_windows) { | |
| 1121 int smallest_max_width = kMaxDockWidth; | |
| 1122 int largest_min_width = kMinDockWidth; | |
| 1123 // Ideal width of the docked area is as close to kIdealWidth as possible | |
| 1124 // while still respecting the minimum and maximum width restrictions on the | |
| 1125 // individual docked windows as well as the width that was possibly set by a | |
| 1126 // user (which needs to be preserved when dragging and rearranging windows). | |
| 1127 for (std::vector<WindowWithHeight>::const_iterator iter = | |
| 1128 visible_windows.begin(); | |
| 1129 iter != visible_windows.end(); ++iter) { | |
| 1130 const WmWindow* window = iter->window; | |
| 1131 int min_window_width = window->GetBounds().width(); | |
| 1132 int max_window_width = min_window_width; | |
| 1133 if (!window->GetWindowState()->bounds_changed_by_user()) { | |
| 1134 min_window_width = GetWindowWidthCloseTo(window, kMinDockWidth); | |
| 1135 max_window_width = GetWindowWidthCloseTo(window, kMaxDockWidth); | |
| 1136 } | |
| 1137 largest_min_width = std::max(largest_min_width, min_window_width); | |
| 1138 smallest_max_width = std::min(smallest_max_width, max_window_width); | |
| 1139 } | |
| 1140 int ideal_width = | |
| 1141 std::max(largest_min_width, std::min(smallest_max_width, kIdealWidth)); | |
| 1142 // Restrict docked area width regardless of window restrictions. | |
| 1143 ideal_width = std::max(std::min(ideal_width, kMaxDockWidth), kMinDockWidth); | |
| 1144 return ideal_width; | |
| 1145 } | |
| 1146 | |
| 1147 void DockedWindowLayoutManager::FanOutChildren( | |
| 1148 const gfx::Rect& work_area, | |
| 1149 int ideal_docked_width, | |
| 1150 int available_room, | |
| 1151 std::vector<WindowWithHeight>* visible_windows) { | |
| 1152 gfx::Rect dock_bounds = dock_container_->GetBoundsInScreen(); | |
| 1153 | |
| 1154 // Calculate initial vertical offset and the gap or overlap between windows. | |
| 1155 const int num_windows = visible_windows->size(); | |
| 1156 const float delta = | |
| 1157 static_cast<float>(available_room) / | |
| 1158 ((available_room > 0 || num_windows <= 1) ? num_windows + 1 | |
| 1159 : num_windows - 1); | |
| 1160 float y_pos = work_area.y() + ((delta > 0) ? delta : 0); | |
| 1161 | |
| 1162 // Docked area is shown only if there is at least one non-dragged visible | |
| 1163 // docked window. | |
| 1164 int new_width = ideal_docked_width; | |
| 1165 if (visible_windows->empty() || | |
| 1166 (visible_windows->size() == 1 && | |
| 1167 (*visible_windows)[0].window == dragged_window_)) { | |
| 1168 new_width = 0; | |
| 1169 } | |
| 1170 UpdateDockedWidth(new_width); | |
| 1171 // Sort windows by their center positions and fan out overlapping | |
| 1172 // windows. | |
| 1173 std::sort(visible_windows->begin(), visible_windows->end(), | |
| 1174 CompareWindowPos(is_dragged_from_dock_ ? dragged_window_ : nullptr, | |
| 1175 dock_container_, delta)); | |
| 1176 for (std::vector<WindowWithHeight>::iterator iter = visible_windows->begin(); | |
| 1177 iter != visible_windows->end(); ++iter) { | |
| 1178 WmWindow* window = iter->window; | |
| 1179 gfx::Rect bounds = | |
| 1180 dock_container_->ConvertRectToScreen(window->GetTargetBounds()); | |
| 1181 // A window is extended or shrunk to be as close as possible to the ideal | |
| 1182 // docked area width. Windows that were resized by a user are kept at their | |
| 1183 // existing size. | |
| 1184 // This also enforces the min / max restrictions on the docked area width. | |
| 1185 bounds.set_width(GetWindowWidthCloseTo( | |
| 1186 window, window->GetWindowState()->bounds_changed_by_user() | |
| 1187 ? bounds.width() | |
| 1188 : ideal_docked_width)); | |
| 1189 DCHECK_LE(bounds.width(), ideal_docked_width); | |
| 1190 | |
| 1191 DockedAlignment alignment = alignment_; | |
| 1192 if (alignment == DOCKED_ALIGNMENT_NONE && window == dragged_window_) | |
| 1193 alignment = GetEdgeNearestWindow(window); | |
| 1194 | |
| 1195 // Fan out windows evenly distributing the overlap or remaining free space. | |
| 1196 bounds.set_height(iter->height); | |
| 1197 bounds.set_y( | |
| 1198 std::max(work_area.y(), std::min(work_area.bottom() - bounds.height(), | |
| 1199 static_cast<int>(y_pos + 0.5)))); | |
| 1200 y_pos += bounds.height() + delta + kMinDockGap; | |
| 1201 | |
| 1202 // All docked windows other than the one currently dragged remain stuck | |
| 1203 // to the screen edge (flush with the edge or centered in the dock area). | |
| 1204 switch (alignment) { | |
| 1205 case DOCKED_ALIGNMENT_LEFT: | |
| 1206 bounds.set_x(dock_bounds.x() + | |
| 1207 (ideal_docked_width - bounds.width()) / 2); | |
| 1208 break; | |
| 1209 case DOCKED_ALIGNMENT_RIGHT: | |
| 1210 bounds.set_x(dock_bounds.right() - | |
| 1211 (ideal_docked_width + bounds.width()) / 2); | |
| 1212 break; | |
| 1213 case DOCKED_ALIGNMENT_NONE: | |
| 1214 break; | |
| 1215 } | |
| 1216 if (window == dragged_window_) { | |
| 1217 dragged_bounds_ = bounds; | |
| 1218 continue; | |
| 1219 } | |
| 1220 // If the following asserts it is probably because not all the children | |
| 1221 // have been removed when dock was closed. | |
| 1222 DCHECK_NE(alignment_, DOCKED_ALIGNMENT_NONE); | |
| 1223 bounds = dock_container_->ConvertRectFromScreen(bounds); | |
| 1224 if (bounds != window->GetTargetBounds()) { | |
| 1225 ui::Layer* layer = window->GetLayer(); | |
| 1226 ui::ScopedLayerAnimationSettings slide_settings(layer->GetAnimator()); | |
| 1227 slide_settings.SetPreemptionStrategy( | |
| 1228 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); | |
| 1229 slide_settings.SetTransitionDuration( | |
| 1230 base::TimeDelta::FromMilliseconds(kSlideDurationMs)); | |
| 1231 window->SetBoundsDirect(bounds); | |
| 1232 } | |
| 1233 } | |
| 1234 } | |
| 1235 | |
| 1236 void DockedWindowLayoutManager::UpdateDockBounds( | |
| 1237 DockedWindowLayoutManagerObserver::Reason reason) { | |
| 1238 int docked_width = in_overview_ ? 0 : docked_width_; | |
| 1239 int dock_inset = docked_width + (docked_width > 0 ? kMinDockGap : 0); | |
| 1240 const gfx::Rect work_area = | |
| 1241 dock_container_->GetDisplayNearestWindow().work_area(); | |
| 1242 gfx::Rect bounds = gfx::Rect( | |
| 1243 alignment_ == DOCKED_ALIGNMENT_RIGHT && dock_inset > 0 | |
| 1244 ? dock_container_->GetBounds().right() - dock_inset | |
| 1245 : dock_container_->GetBounds().x(), | |
| 1246 dock_container_->GetBounds().y(), dock_inset, work_area.height()); | |
| 1247 docked_bounds_ = | |
| 1248 bounds + dock_container_->GetBoundsInScreen().OffsetFromOrigin(); | |
| 1249 for (auto& observer : observer_list_) | |
| 1250 observer.OnDockBoundsChanging(bounds, reason); | |
| 1251 // Show or hide background for docked area. | |
| 1252 gfx::Rect background_bounds(docked_bounds_); | |
| 1253 if (shelf_observer_) | |
| 1254 background_bounds.Subtract(shelf_observer_->shelf_bounds_in_screen()); | |
| 1255 if (docked_width > 0) { | |
| 1256 // TODO: |shelf_| should not be null by the time we get here, but it may | |
| 1257 // be in mash as startup sequence doesn't yet match that of ash. Once | |
| 1258 // |shelf_| is created at same time as ash we can remove conditional. | |
| 1259 // http://crbug.com/632099 | |
| 1260 if (shelf_) { | |
| 1261 if (!background_widget_) | |
| 1262 background_widget_.reset(new DockedBackgroundWidget(this)); | |
| 1263 background_widget_->SetBackgroundBounds(background_bounds, alignment_); | |
| 1264 background_widget_->Show(); | |
| 1265 } | |
| 1266 } else if (background_widget_) { | |
| 1267 background_widget_->Hide(); | |
| 1268 } | |
| 1269 } | |
| 1270 | |
| 1271 void DockedWindowLayoutManager::UpdateStacking(WmWindow* active_window) { | |
| 1272 if (!active_window) { | |
| 1273 if (!last_active_window_) | |
| 1274 return; | |
| 1275 active_window = last_active_window_; | |
| 1276 } | |
| 1277 | |
| 1278 // Windows are stacked like a deck of cards: | |
| 1279 // ,------. | |
| 1280 // |,------.| | |
| 1281 // |,------.| | |
| 1282 // | active | | |
| 1283 // | window | | |
| 1284 // |`------'| | |
| 1285 // |`------'| | |
| 1286 // `------' | |
| 1287 // Use the middle of each window to figure out how to stack the window. | |
| 1288 // This allows us to update the stacking when a window is being dragged around | |
| 1289 // by the titlebar. | |
| 1290 std::map<int, WmWindow*> window_ordering; | |
| 1291 for (WmWindow* child : dock_container_->GetChildren()) { | |
| 1292 if (!IsWindowDocked(child) || | |
| 1293 (child == dragged_window_ && !is_dragged_window_docked_)) { | |
| 1294 continue; | |
| 1295 } | |
| 1296 gfx::Rect bounds = child->GetBounds(); | |
| 1297 window_ordering.insert( | |
| 1298 std::make_pair(bounds.y() + bounds.height() / 2, child)); | |
| 1299 } | |
| 1300 int active_center_y = active_window->GetBounds().CenterPoint().y(); | |
| 1301 | |
| 1302 WmWindow* previous_window = nullptr; | |
| 1303 for (std::map<int, WmWindow*>::const_iterator it = window_ordering.begin(); | |
| 1304 it != window_ordering.end() && it->first < active_center_y; ++it) { | |
| 1305 if (previous_window) | |
| 1306 dock_container_->StackChildAbove(it->second, previous_window); | |
| 1307 previous_window = it->second; | |
| 1308 } | |
| 1309 for (std::map<int, WmWindow*>::const_reverse_iterator it = | |
| 1310 window_ordering.rbegin(); | |
| 1311 it != window_ordering.rend() && it->first > active_center_y; ++it) { | |
| 1312 if (previous_window) | |
| 1313 dock_container_->StackChildAbove(it->second, previous_window); | |
| 1314 previous_window = it->second; | |
| 1315 } | |
| 1316 | |
| 1317 if (previous_window && active_window->GetParent() == dock_container_) | |
| 1318 dock_container_->StackChildAbove(active_window, previous_window); | |
| 1319 if (active_window != dragged_window_) | |
| 1320 last_active_window_ = active_window; | |
| 1321 } | |
| 1322 | |
| 1323 //////////////////////////////////////////////////////////////////////////////// | |
| 1324 // keyboard::KeyboardControllerObserver implementation: | |
| 1325 | |
| 1326 void DockedWindowLayoutManager::OnKeyboardBoundsChanging( | |
| 1327 const gfx::Rect& keyboard_bounds) { | |
| 1328 // This bounds change will have caused a change to the Shelf which does not | |
| 1329 // propagate automatically to this class, so manually recalculate bounds. | |
| 1330 Relayout(); | |
| 1331 UpdateDockBounds(DockedWindowLayoutManagerObserver::KEYBOARD_BOUNDS_CHANGING); | |
| 1332 } | |
| 1333 | |
| 1334 void DockedWindowLayoutManager::OnKeyboardClosed() {} | |
| 1335 | |
| 1336 } // namespace ash | |
| OLD | NEW |