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