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