summaryrefslogtreecommitdiff
path: root/chromium/ash/wm/dock/docked_window_layout_manager.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/ash/wm/dock/docked_window_layout_manager.cc')
-rw-r--r--chromium/ash/wm/dock/docked_window_layout_manager.cc1240
1 files changed, 1240 insertions, 0 deletions
diff --git a/chromium/ash/wm/dock/docked_window_layout_manager.cc b/chromium/ash/wm/dock/docked_window_layout_manager.cc
new file mode 100644
index 00000000000..3298f05602f
--- /dev/null
+++ b/chromium/ash/wm/dock/docked_window_layout_manager.cc
@@ -0,0 +1,1240 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/wm/dock/docked_window_layout_manager.h"
+
+#include "ash/ash_switches.h"
+#include "ash/launcher/launcher.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/window_animations.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_state.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace_controller.h"
+#include "base/auto_reset.h"
+#include "base/command_line.h"
+#include "base/metrics/histogram.h"
+#include "grit/ash_resources.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/focus_client.h"
+#include "ui/aura/client/window_tree_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/background.h"
+
+namespace ash {
+namespace internal {
+
+// Minimum, maximum width of the dock area and a width of the gap
+// static
+const int DockedWindowLayoutManager::kMaxDockWidth = 360;
+// static
+const int DockedWindowLayoutManager::kMinDockWidth = 200;
+// static
+const int DockedWindowLayoutManager::kMinDockGap = 2;
+// static
+const int DockedWindowLayoutManager::kIdealWidth = 250;
+const int kMinimumHeight = 250;
+const int kSlideDurationMs = 120;
+const int kFadeDurationMs = 60;
+const int kMinimizeDurationMs = 720;
+
+class DockedBackgroundWidget : public views::Widget,
+ public internal::BackgroundAnimatorDelegate {
+ public:
+ explicit DockedBackgroundWidget(aura::Window* container)
+ : alignment_(DOCKED_ALIGNMENT_NONE),
+ background_animator_(this, 0, kLauncherBackgroundAlpha),
+ alpha_(0),
+ opaque_background_(ui::LAYER_SOLID_COLOR) {
+ InitWidget(container);
+ }
+
+ // Sets widget bounds and sizes opaque background layer to fill the widget.
+ void SetBackgroundBounds(const gfx::Rect bounds, DockedAlignment alignment) {
+ SetBounds(bounds);
+ opaque_background_.SetBounds(gfx::Rect(bounds.size()));
+ alignment_ = alignment;
+ }
+
+ // Sets the docked area background type and starts transition animation.
+ void SetPaintsBackground(
+ ShelfBackgroundType background_type,
+ BackgroundAnimatorChangeType change_type) {
+ float target_opacity =
+ (background_type == SHELF_BACKGROUND_MAXIMIZED) ? 1.0f : 0.0f;
+ scoped_ptr<ui::ScopedLayerAnimationSettings> opaque_background_animation;
+ if (change_type != BACKGROUND_CHANGE_IMMEDIATE) {
+ opaque_background_animation.reset(new ui::ScopedLayerAnimationSettings(
+ opaque_background_.GetAnimator()));
+ opaque_background_animation->SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kTimeToSwitchBackgroundMs));
+ }
+ opaque_background_.SetOpacity(target_opacity);
+
+ // TODO(varkha): use ui::Layer on both opaque_background and normal
+ // background retire background_animator_ at all. It would be simpler.
+ // See also ShelfWidget::SetPaintsBackground.
+ background_animator_.SetPaintsBackground(
+ background_type != SHELF_BACKGROUND_DEFAULT,
+ change_type);
+ SchedulePaintInRect(gfx::Rect(GetWindowBoundsInScreen().size()));
+ }
+
+ // views::Widget:
+ virtual void OnNativeWidgetPaint(gfx::Canvas* canvas) OVERRIDE {
+ const gfx::ImageSkia& launcher_background(
+ alignment_ == DOCKED_ALIGNMENT_LEFT ?
+ launcher_background_left_ : launcher_background_right_);
+ gfx::Rect rect = gfx::Rect(GetWindowBoundsInScreen().size());
+ SkPaint paint;
+ paint.setAlpha(alpha_);
+ canvas->DrawImageInt(
+ launcher_background,
+ 0, 0, launcher_background.width(), launcher_background.height(),
+ alignment_ == DOCKED_ALIGNMENT_LEFT ?
+ rect.width() - launcher_background.width() : 0, 0,
+ launcher_background.width(), rect.height(),
+ false,
+ paint);
+ canvas->DrawImageInt(
+ launcher_background,
+ alignment_ == DOCKED_ALIGNMENT_LEFT ?
+ 0 : launcher_background.width() - 1, 0,
+ 1, launcher_background.height(),
+ alignment_ == DOCKED_ALIGNMENT_LEFT ?
+ 0 : launcher_background.width(), 0,
+ rect.width() - launcher_background.width(), rect.height(),
+ false,
+ paint);
+ }
+
+ // BackgroundAnimatorDelegate:
+ virtual void UpdateBackground(int alpha) OVERRIDE {
+ alpha_ = alpha;
+ SchedulePaintInRect(gfx::Rect(GetWindowBoundsInScreen().size()));
+ }
+
+ private:
+ void InitWidget(aura::Window* parent) {
+ views::Widget::InitParams params;
+ params.type = views::Widget::InitParams::TYPE_POPUP;
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.can_activate = false;
+ params.keep_on_top = false;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = parent;
+ params.accept_events = false;
+ set_focus_on_creation(false);
+ Init(params);
+ GetNativeWindow()->SetProperty(internal::kStayInSameRootWindowKey, true);
+ opaque_background_.SetColor(SK_ColorBLACK);
+ opaque_background_.SetBounds(gfx::Rect(GetWindowBoundsInScreen().size()));
+ opaque_background_.SetOpacity(0.0f);
+ GetNativeWindow()->layer()->Add(&opaque_background_);
+ Hide();
+
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ gfx::ImageSkia launcher_background =
+ *rb.GetImageSkiaNamed(IDR_AURA_LAUNCHER_BACKGROUND);
+ launcher_background_left_ = gfx::ImageSkiaOperations::CreateRotatedImage(
+ launcher_background, SkBitmapOperations::ROTATION_90_CW);
+ launcher_background_right_ = gfx::ImageSkiaOperations::CreateRotatedImage(
+ launcher_background, SkBitmapOperations::ROTATION_270_CW);
+ }
+
+ DockedAlignment alignment_;
+
+ // The animator for the background transitions.
+ internal::BackgroundAnimator background_animator_;
+
+ // The alpha to use for drawing image assets covering the docked background.
+ int alpha_;
+
+ // Solid black background that can be made fully opaque.
+ ui::Layer opaque_background_;
+
+ // Backgrounds created from shelf background by 90 or 270 degree rotation.
+ gfx::ImageSkia launcher_background_left_;
+ gfx::ImageSkia launcher_background_right_;
+
+ DISALLOW_COPY_AND_ASSIGN(DockedBackgroundWidget);
+};
+
+namespace {
+
+// Returns true if a window is a popup or a transient child.
+bool IsPopupOrTransient(const aura::Window* window) {
+ return (window->type() == aura::client::WINDOW_TYPE_POPUP ||
+ window->transient_parent());
+}
+
+// Certain windows (minimized, hidden or popups) do not matter to docking.
+bool IsUsedByLayout(const aura::Window* window) {
+ return (window->IsVisible() &&
+ !wm::GetWindowState(window)->IsMinimized() &&
+ !IsPopupOrTransient(window));
+}
+
+void UndockWindow(aura::Window* window) {
+ gfx::Rect previous_bounds = window->bounds();
+ aura::Window* old_parent = window->parent();
+ aura::client::ParentWindowWithContext(window, window, gfx::Rect());
+ if (window->parent() != old_parent)
+ wm::ReparentTransientChildrenOfChild(window, old_parent, window->parent());
+ // Start maximize or fullscreen (affecting packaged apps) animation from
+ // previous window bounds.
+ window->layer()->SetBounds(previous_bounds);
+}
+
+// Returns width that is as close as possible to |target_width| while being
+// consistent with docked min and max restrictions and respects the |window|'s
+// minimum and maximum size.
+int GetWindowWidthCloseTo(const aura::Window* window, int target_width) {
+ if (!wm::GetWindowState(window)->CanResize()) {
+ DCHECK_LE(window->bounds().width(),
+ DockedWindowLayoutManager::kMaxDockWidth);
+ return window->bounds().width();
+ }
+ int width = std::max(DockedWindowLayoutManager::kMinDockWidth,
+ std::min(target_width,
+ DockedWindowLayoutManager::kMaxDockWidth));
+ if (window->delegate()) {
+ if (window->delegate()->GetMinimumSize().width() != 0)
+ width = std::max(width, window->delegate()->GetMinimumSize().width());
+ if (window->delegate()->GetMaximumSize().width() != 0)
+ width = std::min(width, window->delegate()->GetMaximumSize().width());
+ }
+ DCHECK_LE(width, DockedWindowLayoutManager::kMaxDockWidth);
+ return width;
+}
+
+// Returns height that is as close as possible to |target_height| while
+// respecting the |window|'s minimum and maximum size.
+int GetWindowHeightCloseTo(const aura::Window* window, int target_height) {
+ if (!wm::GetWindowState(window)->CanResize())
+ return window->bounds().height();
+ int minimum_height = kMinimumHeight;
+ int maximum_height = 0;
+ const aura::WindowDelegate* delegate(window->delegate());
+ if (delegate) {
+ if (delegate->GetMinimumSize().height() != 0) {
+ minimum_height = std::max(kMinimumHeight,
+ delegate->GetMinimumSize().height());
+ }
+ if (delegate->GetMaximumSize().height() != 0)
+ maximum_height = delegate->GetMaximumSize().height();
+ }
+ if (minimum_height)
+ target_height = std::max(target_height, minimum_height);
+ if (maximum_height)
+ target_height = std::min(target_height, maximum_height);
+ return target_height;
+}
+
+// A functor used to sort the windows in order of their minimum height.
+struct CompareMinimumHeight {
+ bool operator()(WindowWithHeight win1, WindowWithHeight win2) {
+ return GetWindowHeightCloseTo(win1.window(), 0) <
+ GetWindowHeightCloseTo(win2.window(), 0);
+ }
+};
+
+// A functor used to sort the windows in order of their center Y position.
+// |delta| is a pre-calculated distance from the bottom of one window to the top
+// of the next. Its value can be positive (gap) or negative (overlap).
+// Half of |delta| is used as a transition point at which windows could ideally
+// swap positions.
+struct CompareWindowPos {
+ CompareWindowPos(aura::Window* dragged_window, float delta)
+ : dragged_window_(dragged_window),
+ delta_(delta / 2) {}
+
+ bool operator()(WindowWithHeight window_with_height1,
+ WindowWithHeight window_with_height2) {
+ // Use target coordinates since animations may be active when windows are
+ // reordered.
+ aura::Window* win1(window_with_height1.window());
+ aura::Window* win2(window_with_height2.window());
+ gfx::Rect win1_bounds = ScreenAsh::ConvertRectToScreen(
+ win1->parent(), win1->GetTargetBounds());
+ gfx::Rect win2_bounds = ScreenAsh::ConvertRectToScreen(
+ win2->parent(), win2->GetTargetBounds());
+ win1_bounds.set_height(window_with_height1.height_);
+ win2_bounds.set_height(window_with_height2.height_);
+ // If one of the windows is the |dragged_window_| attempt to make an
+ // earlier swap between the windows than just based on their centers.
+ // This is possible if the dragged window is at least as tall as the other
+ // window.
+ if (win1 == dragged_window_)
+ return compare_two_windows(win1_bounds, win2_bounds);
+ if (win2 == dragged_window_)
+ return !compare_two_windows(win2_bounds, win1_bounds);
+ // Otherwise just compare the centers.
+ return win1_bounds.CenterPoint().y() < win2_bounds.CenterPoint().y();
+ }
+
+ // Based on center point tries to deduce where the drag is coming from.
+ // When dragging from below up the transition point is lower.
+ // When dragging from above down the transition point is higher.
+ bool compare_bounds(const gfx::Rect dragged, const gfx::Rect other) {
+ if (dragged.CenterPoint().y() < other.CenterPoint().y())
+ return dragged.CenterPoint().y() < other.y() - delta_;
+ return dragged.CenterPoint().y() < other.bottom() + delta_;
+ }
+
+ // Performs comparison both ways and selects stable result.
+ bool compare_two_windows(const gfx::Rect bounds1, const gfx::Rect bounds2) {
+ // Try comparing windows in both possible orders and see if the comparison
+ // is stable.
+ bool result1 = compare_bounds(bounds1, bounds2);
+ bool result2 = compare_bounds(bounds2, bounds1);
+ if (result1 != result2)
+ return result1;
+
+ // Otherwise it is not possible to be sure that the windows will not bounce.
+ // In this case just compare the centers.
+ return bounds1.CenterPoint().y() < bounds2.CenterPoint().y();
+ }
+
+ private:
+ aura::Window* dragged_window_;
+ float delta_;
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// A class that observes launcher shelf for bounds changes.
+class DockedWindowLayoutManager::ShelfWindowObserver : public WindowObserver {
+ public:
+ explicit ShelfWindowObserver(
+ DockedWindowLayoutManager* docked_layout_manager)
+ : docked_layout_manager_(docked_layout_manager) {
+ DCHECK(docked_layout_manager_->launcher()->shelf_widget());
+ docked_layout_manager_->launcher()->shelf_widget()->GetNativeView()
+ ->AddObserver(this);
+ }
+
+ virtual ~ShelfWindowObserver() {
+ if (docked_layout_manager_->launcher() &&
+ docked_layout_manager_->launcher()->shelf_widget())
+ docked_layout_manager_->launcher()->shelf_widget()->GetNativeView()
+ ->RemoveObserver(this);
+ }
+
+ // aura::WindowObserver:
+ virtual void OnWindowBoundsChanged(aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE {
+ shelf_bounds_in_screen_ = ScreenAsh::ConvertRectToScreen(
+ window->parent(), new_bounds);
+ docked_layout_manager_->OnShelfBoundsChanged();
+ }
+
+ const gfx::Rect& shelf_bounds_in_screen() const {
+ return shelf_bounds_in_screen_;
+ }
+
+ private:
+ DockedWindowLayoutManager* docked_layout_manager_;
+ gfx::Rect shelf_bounds_in_screen_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShelfWindowObserver);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// DockedWindowLayoutManager public implementation:
+DockedWindowLayoutManager::DockedWindowLayoutManager(
+ aura::Window* dock_container, WorkspaceController* workspace_controller)
+ : dock_container_(dock_container),
+ in_layout_(false),
+ dragged_window_(NULL),
+ is_dragged_window_docked_(false),
+ is_dragged_from_dock_(false),
+ launcher_(NULL),
+ workspace_controller_(workspace_controller),
+ in_fullscreen_(workspace_controller_->GetWindowState() ==
+ WORKSPACE_WINDOW_STATE_FULL_SCREEN),
+ docked_width_(0),
+ alignment_(DOCKED_ALIGNMENT_NONE),
+ last_active_window_(NULL),
+ last_action_time_(base::Time::Now()),
+ background_widget_(new DockedBackgroundWidget(dock_container_)) {
+ DCHECK(dock_container);
+ aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())->
+ AddObserver(this);
+ Shell::GetInstance()->AddShellObserver(this);
+}
+
+DockedWindowLayoutManager::~DockedWindowLayoutManager() {
+ Shutdown();
+}
+
+void DockedWindowLayoutManager::Shutdown() {
+ if (launcher_ && launcher_->shelf_widget()) {
+ ShelfLayoutManager* shelf_layout_manager = ShelfLayoutManager::ForLauncher(
+ launcher_->shelf_widget()->GetNativeWindow());
+ shelf_layout_manager->RemoveObserver(this);
+ shelf_observer_.reset();
+ }
+ launcher_ = NULL;
+ for (size_t i = 0; i < dock_container_->children().size(); ++i) {
+ aura::Window* child = dock_container_->children()[i];
+ child->RemoveObserver(this);
+ wm::GetWindowState(child)->RemoveObserver(this);
+ }
+ aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())->
+ RemoveObserver(this);
+ Shell::GetInstance()->RemoveShellObserver(this);
+}
+
+void DockedWindowLayoutManager::AddObserver(
+ DockedWindowLayoutManagerObserver* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void DockedWindowLayoutManager::RemoveObserver(
+ DockedWindowLayoutManagerObserver* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+void DockedWindowLayoutManager::StartDragging(aura::Window* window) {
+ DCHECK(!dragged_window_);
+ dragged_window_ = window;
+ DCHECK(!IsPopupOrTransient(window));
+ // Start observing a window unless it is docked container's child in which
+ // case it is already observed.
+ if (dragged_window_->parent() != dock_container_) {
+ dragged_window_->AddObserver(this);
+ wm::GetWindowState(dragged_window_)->AddObserver(this);
+ }
+ is_dragged_from_dock_ = window->parent() == dock_container_;
+ DCHECK(!is_dragged_window_docked_);
+}
+
+void DockedWindowLayoutManager::DockDraggedWindow(aura::Window* window) {
+ DCHECK(!IsPopupOrTransient(window));
+ OnDraggedWindowDocked(window);
+ Relayout();
+}
+
+void DockedWindowLayoutManager::UndockDraggedWindow() {
+ DCHECK(!IsPopupOrTransient(dragged_window_));
+ OnDraggedWindowUndocked();
+ Relayout();
+ UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED);
+ is_dragged_from_dock_ = false;
+}
+
+void DockedWindowLayoutManager::FinishDragging(DockedAction action,
+ DockedActionSource source) {
+ DCHECK(dragged_window_);
+ DCHECK(!IsPopupOrTransient(dragged_window_));
+ if (is_dragged_window_docked_)
+ OnDraggedWindowUndocked();
+ DCHECK (!is_dragged_window_docked_);
+ // Stop observing a window unless it is docked container's child in which
+ // case it needs to keep being observed after the drag completes.
+ if (dragged_window_->parent() != dock_container_) {
+ dragged_window_->RemoveObserver(this);
+ wm::GetWindowState(dragged_window_)->RemoveObserver(this);
+ if (last_active_window_ == dragged_window_)
+ last_active_window_ = NULL;
+ } else {
+ // A window is no longer dragged and is a child.
+ // When a window becomes a child at drag start this is
+ // the only opportunity we will have to enforce a window
+ // count limit so do it here.
+ MaybeMinimizeChildrenExcept(dragged_window_);
+ }
+ dragged_window_ = NULL;
+ dragged_bounds_ = gfx::Rect();
+ Relayout();
+ UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED);
+ RecordUmaAction(action, source);
+}
+
+void DockedWindowLayoutManager::SetLauncher(ash::Launcher* launcher) {
+ DCHECK(!launcher_);
+ launcher_ = launcher;
+ if (launcher_->shelf_widget()) {
+ ShelfLayoutManager* shelf_layout_manager = ShelfLayoutManager::ForLauncher(
+ launcher_->shelf_widget()->GetNativeWindow());
+ shelf_layout_manager->AddObserver(this);
+ shelf_observer_.reset(new ShelfWindowObserver(this));
+ }
+}
+
+DockedAlignment DockedWindowLayoutManager::GetAlignmentOfWindow(
+ const aura::Window* window) const {
+ const gfx::Rect& bounds(window->GetBoundsInScreen());
+
+ // Test overlap with an existing docked area first.
+ if (docked_bounds_.Intersects(bounds) &&
+ alignment_ != DOCKED_ALIGNMENT_NONE) {
+ // A window is being added to other docked windows (on the same side).
+ return alignment_;
+ }
+
+ const gfx::Rect container_bounds = dock_container_->GetBoundsInScreen();
+ if (bounds.x() <= container_bounds.x() &&
+ bounds.right() > container_bounds.x()) {
+ return DOCKED_ALIGNMENT_LEFT;
+ } else if (bounds.x() < container_bounds.right() &&
+ bounds.right() >= container_bounds.right()) {
+ return DOCKED_ALIGNMENT_RIGHT;
+ }
+ return DOCKED_ALIGNMENT_NONE;
+}
+
+DockedAlignment DockedWindowLayoutManager::CalculateAlignment() const {
+ // Find a child that is not being dragged and is not a popup.
+ // If such exists the current alignment is returned - even if some of the
+ // children are hidden or minimized (so they can be restored without losing
+ // the docked state).
+ for (size_t i = 0; i < dock_container_->children().size(); ++i) {
+ aura::Window* window(dock_container_->children()[i]);
+ if (window != dragged_window_ && !IsPopupOrTransient(window))
+ return alignment_;
+ }
+ // No docked windows remain other than possibly the window being dragged.
+ // Return |NONE| to indicate that windows may get docked on either side.
+ return DOCKED_ALIGNMENT_NONE;
+}
+
+bool DockedWindowLayoutManager::CanDockWindow(aura::Window* window,
+ SnapType edge) {
+ if (!switches::UseDockedWindows())
+ return false;
+ // Don't allow interactive docking of windows with transient parents such as
+ // modal browser dialogs.
+ if (IsPopupOrTransient(window))
+ return false;
+ // If a window is wide and cannot be resized down to maximum width allowed
+ // then it cannot be docked.
+ // TODO(varkha). Prevent windows from changing size programmatically while
+ // they are docked. The size will take effect only once a window is undocked.
+ // See http://crbug.com/307792.
+ if (window->bounds().width() > kMaxDockWidth &&
+ (!wm::GetWindowState(window)->CanResize() ||
+ (window->delegate() &&
+ window->delegate()->GetMinimumSize().width() != 0 &&
+ window->delegate()->GetMinimumSize().width() > kMaxDockWidth))) {
+ return false;
+ }
+ // If a window is tall and cannot be resized down to maximum height allowed
+ // then it cannot be docked.
+ const gfx::Rect work_area =
+ Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area();
+ if (GetWindowHeightCloseTo(window, work_area.height() - 2 * kMinDockGap) >
+ work_area.height() - 2 * kMinDockGap) {
+ return false;
+ }
+ // Cannot dock on the other size from an existing dock.
+ const DockedAlignment alignment = CalculateAlignment();
+ if ((edge == SNAP_LEFT && alignment == DOCKED_ALIGNMENT_RIGHT) ||
+ (edge == SNAP_RIGHT && alignment == DOCKED_ALIGNMENT_LEFT)) {
+ return false;
+ }
+ // Do not allow docking on the same side as launcher shelf.
+ ShelfAlignment shelf_alignment = SHELF_ALIGNMENT_BOTTOM;
+ if (launcher_)
+ shelf_alignment = launcher_->alignment();
+ if ((edge == SNAP_LEFT && shelf_alignment == SHELF_ALIGNMENT_LEFT) ||
+ (edge == SNAP_RIGHT && shelf_alignment == SHELF_ALIGNMENT_RIGHT)) {
+ return false;
+ }
+ return true;
+}
+
+void DockedWindowLayoutManager::OnShelfBoundsChanged() {
+ Relayout();
+ UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_INSETS_CHANGED);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DockedWindowLayoutManager, aura::LayoutManager implementation:
+void DockedWindowLayoutManager::OnWindowResized() {
+ MaybeMinimizeChildrenExcept(dragged_window_);
+ Relayout();
+ // When screen resizes update the insets even when dock width or alignment
+ // does not change.
+ UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_RESIZED);
+}
+
+void DockedWindowLayoutManager::OnWindowAddedToLayout(aura::Window* child) {
+ if (IsPopupOrTransient(child))
+ return;
+ // Dragged windows are already observed by StartDragging and do not change
+ // docked alignment during the drag.
+ if (child == dragged_window_)
+ return;
+ // If this is the first window getting docked - update alignment.
+ if (alignment_ == DOCKED_ALIGNMENT_NONE) {
+ alignment_ = GetAlignmentOfWindow(child);
+ DCHECK(alignment_ != DOCKED_ALIGNMENT_NONE);
+ }
+ MaybeMinimizeChildrenExcept(child);
+ child->AddObserver(this);
+ wm::GetWindowState(child)->AddObserver(this);
+ Relayout();
+ UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED);
+}
+
+void DockedWindowLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) {
+ if (IsPopupOrTransient(child))
+ return;
+ // Dragged windows are stopped being observed by FinishDragging and do not
+ // change alignment during the drag. They also cannot be set to be the
+ // |last_active_window_|.
+ if (child == dragged_window_)
+ return;
+ // If this is the last window, set alignment and maximize the workspace.
+ if (!IsAnyWindowDocked()) {
+ alignment_ = DOCKED_ALIGNMENT_NONE;
+ UpdateDockedWidth(0);
+ }
+ if (last_active_window_ == child)
+ last_active_window_ = NULL;
+ child->RemoveObserver(this);
+ wm::GetWindowState(child)->RemoveObserver(this);
+ Relayout();
+ UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED);
+}
+
+void DockedWindowLayoutManager::OnChildWindowVisibilityChanged(
+ aura::Window* child,
+ bool visible) {
+ if (IsPopupOrTransient(child))
+ return;
+ if (visible)
+ wm::GetWindowState(child)->Restore();
+ Relayout();
+ UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED);
+}
+
+void DockedWindowLayoutManager::SetChildBounds(
+ aura::Window* child,
+ const gfx::Rect& requested_bounds) {
+ // Whenever one of our windows is moved or resized enforce layout.
+ SetChildBoundsDirect(child, requested_bounds);
+ if (IsPopupOrTransient(child))
+ return;
+ ShelfLayoutManager* shelf_layout = internal::ShelfLayoutManager::ForLauncher(
+ dock_container_);
+ if (shelf_layout)
+ shelf_layout->UpdateVisibilityState();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DockedWindowLayoutManager, ash::ShellObserver implementation:
+
+void DockedWindowLayoutManager::OnDisplayWorkAreaInsetsChanged() {
+ Relayout();
+ UpdateDockBounds(DockedWindowLayoutManagerObserver::DISPLAY_INSETS_CHANGED);
+ MaybeMinimizeChildrenExcept(dragged_window_);
+}
+
+void DockedWindowLayoutManager::OnFullscreenStateChanged(
+ bool is_fullscreen, aura::Window* root_window) {
+ if (dock_container_->GetRootWindow() != root_window)
+ return;
+ // Entering fullscreen mode (including immersive) hides docked windows.
+ in_fullscreen_ = workspace_controller_->GetWindowState() ==
+ WORKSPACE_WINDOW_STATE_FULL_SCREEN;
+ {
+ // prevent Relayout from getting called multiple times during this
+ base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true);
+ // Use a copy of children array because a call to MinimizeDockedWindow or
+ // RestoreDockedWindow can change order.
+ aura::Window::Windows children(dock_container_->children());
+ for (aura::Window::Windows::const_iterator iter = children.begin();
+ iter != children.end(); ++iter) {
+ aura::Window* window(*iter);
+ if (IsPopupOrTransient(window))
+ continue;
+ wm::WindowState* window_state = wm::GetWindowState(window);
+ if (in_fullscreen_) {
+ if (window->IsVisible())
+ MinimizeDockedWindow(window_state);
+ } else {
+ if (!window_state->IsMinimized())
+ RestoreDockedWindow(window_state);
+ }
+ }
+ }
+ Relayout();
+ UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED);
+}
+
+void DockedWindowLayoutManager::OnShelfAlignmentChanged(
+ aura::Window* root_window) {
+ if (dock_container_->GetRootWindow() != root_window)
+ return;
+
+ if (!launcher_ || !launcher_->shelf_widget())
+ return;
+
+ if (alignment_ == DOCKED_ALIGNMENT_NONE)
+ return;
+
+ // Do not allow launcher and dock on the same side. Switch side that
+ // the dock is attached to and move all dock windows to that new side.
+ ShelfAlignment shelf_alignment = launcher_->shelf_widget()->GetAlignment();
+ if (alignment_ == DOCKED_ALIGNMENT_LEFT &&
+ shelf_alignment == SHELF_ALIGNMENT_LEFT) {
+ alignment_ = DOCKED_ALIGNMENT_RIGHT;
+ } else if (alignment_ == DOCKED_ALIGNMENT_RIGHT &&
+ shelf_alignment == SHELF_ALIGNMENT_RIGHT) {
+ alignment_ = DOCKED_ALIGNMENT_LEFT;
+ }
+ Relayout();
+ UpdateDockBounds(DockedWindowLayoutManagerObserver::SHELF_ALIGNMENT_CHANGED);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// DockedWindowLayoutManager, ShelfLayoutManagerObserver implementation:
+void DockedWindowLayoutManager::OnBackgroundUpdated(
+ ShelfBackgroundType background_type,
+ BackgroundAnimatorChangeType change_type) {
+ background_widget_->SetPaintsBackground(background_type, change_type);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// DockedWindowLayoutManager, WindowStateObserver implementation:
+
+void DockedWindowLayoutManager::OnWindowShowTypeChanged(
+ wm::WindowState* window_state,
+ wm::WindowShowType old_type) {
+ aura::Window* window = window_state->window();
+ if (IsPopupOrTransient(window))
+ return;
+ // The window property will still be set, but no actual change will occur
+ // until OnFullscreenStateChange is called when exiting fullscreen.
+ if (in_fullscreen_)
+ return;
+ if (window_state->IsMinimized()) {
+ MinimizeDockedWindow(window_state);
+ } else if (window_state->IsMaximizedOrFullscreen() ||
+ window_state->IsSnapped()) {
+ if (window != dragged_window_) {
+ UndockWindow(window);
+ RecordUmaAction(DOCKED_ACTION_MAXIMIZE, DOCKED_ACTION_SOURCE_UNKNOWN);
+ }
+ } else if (old_type == wm::SHOW_TYPE_MINIMIZED) {
+ RestoreDockedWindow(window_state);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// DockedWindowLayoutManager, WindowObserver implementation:
+
+void DockedWindowLayoutManager::OnWindowBoundsChanged(
+ aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ // Only relayout if the dragged window would get docked.
+ if (window == dragged_window_ && is_dragged_window_docked_)
+ Relayout();
+}
+
+void DockedWindowLayoutManager::OnWindowVisibilityChanging(
+ aura::Window* window, bool visible) {
+ if (IsPopupOrTransient(window))
+ return;
+ int animation_type = views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT;
+ if (visible) {
+ animation_type = views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_DROP;
+ views::corewm::SetWindowVisibilityAnimationDuration(
+ window, base::TimeDelta::FromMilliseconds(kFadeDurationMs));
+ } else if (wm::GetWindowState(window)->IsMinimized()) {
+ animation_type = WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE;
+ }
+ views::corewm::SetWindowVisibilityAnimationType(window, animation_type);
+}
+
+void DockedWindowLayoutManager::OnWindowDestroying(aura::Window* window) {
+ if (dragged_window_ == window) {
+ FinishDragging(DOCKED_ACTION_NONE, DOCKED_ACTION_SOURCE_UNKNOWN);
+ DCHECK(!dragged_window_);
+ DCHECK (!is_dragged_window_docked_);
+ }
+ if (window == last_active_window_)
+ last_active_window_ = NULL;
+ RecordUmaAction(DOCKED_ACTION_CLOSE, DOCKED_ACTION_SOURCE_UNKNOWN);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// DockedWindowLayoutManager, aura::client::ActivationChangeObserver
+// implementation:
+
+void DockedWindowLayoutManager::OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) {
+ if (gained_active && IsPopupOrTransient(gained_active))
+ return;
+ // Ignore if the window that is not managed by this was activated.
+ aura::Window* ancestor = NULL;
+ for (aura::Window* parent = gained_active;
+ parent; parent = parent->parent()) {
+ if (parent->parent() == dock_container_) {
+ ancestor = parent;
+ break;
+ }
+ }
+ if (ancestor)
+ UpdateStacking(ancestor);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DockedWindowLayoutManager private implementation:
+
+void DockedWindowLayoutManager::MaybeMinimizeChildrenExcept(
+ aura::Window* child) {
+ // Minimize any windows that don't fit without overlap.
+ const gfx::Rect work_area =
+ Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area();
+ int available_room = work_area.height() - kMinDockGap;
+ if (child)
+ available_room -= (GetWindowHeightCloseTo(child, 0) + kMinDockGap);
+ // Use a copy of children array because a call to Minimize can change order.
+ aura::Window::Windows children(dock_container_->children());
+ aura::Window::Windows::const_reverse_iterator iter = children.rbegin();
+ while (iter != children.rend()) {
+ aura::Window* window(*iter++);
+ if (window == child || !IsUsedByLayout(window))
+ continue;
+ int room_needed = GetWindowHeightCloseTo(window, 0) + kMinDockGap;
+ if (available_room > room_needed) {
+ available_room -= room_needed;
+ } else {
+ // Slow down minimizing animations. Lock duration so that it is not
+ // overridden by other ScopedLayerAnimationSettings down the stack.
+ ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
+ settings.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kMinimizeDurationMs));
+ settings.LockTransitionDuration();
+ wm::GetWindowState(window)->Minimize();
+ }
+ }
+}
+
+void DockedWindowLayoutManager::MinimizeDockedWindow(
+ wm::WindowState* window_state) {
+ DCHECK(!IsPopupOrTransient(window_state->window()));
+ window_state->window()->Hide();
+ if (window_state->IsActive())
+ window_state->Deactivate();
+ RecordUmaAction(DOCKED_ACTION_MINIMIZE, DOCKED_ACTION_SOURCE_UNKNOWN);
+}
+
+void DockedWindowLayoutManager::RestoreDockedWindow(
+ wm::WindowState* window_state) {
+ aura::Window* window = window_state->window();
+ DCHECK(!IsPopupOrTransient(window));
+ // Always place restored window at the bottom shuffling the other windows up.
+ // TODO(varkha): add a separate container for docked windows to keep track
+ // of ordering.
+ gfx::Display display = Shell::GetScreen()->GetDisplayNearestWindow(
+ dock_container_);
+ const gfx::Rect work_area = display.work_area();
+
+ // Evict the window if it can no longer be docked because of its height.
+ if (!CanDockWindow(window, SNAP_NONE)) {
+ UndockWindow(window);
+ RecordUmaAction(DOCKED_ACTION_EVICT, DOCKED_ACTION_SOURCE_UNKNOWN);
+ return;
+ }
+ gfx::Rect bounds(window->bounds());
+ bounds.set_y(work_area.bottom());
+ window->SetBounds(bounds);
+ window->Show();
+ MaybeMinimizeChildrenExcept(window);
+ RecordUmaAction(DOCKED_ACTION_RESTORE, DOCKED_ACTION_SOURCE_UNKNOWN);
+}
+
+void DockedWindowLayoutManager::RecordUmaAction(DockedAction action,
+ DockedActionSource source) {
+ if (action == DOCKED_ACTION_NONE)
+ return;
+ UMA_HISTOGRAM_ENUMERATION("Ash.Dock.Action", action, DOCKED_ACTION_COUNT);
+ UMA_HISTOGRAM_ENUMERATION("Ash.Dock.ActionSource", source,
+ DOCKED_ACTION_SOURCE_COUNT);
+ base::Time time_now = base::Time::Now();
+ base::TimeDelta time_between_use = time_now - last_action_time_;
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.Dock.TimeBetweenUse",
+ time_between_use.InSeconds(),
+ 1,
+ base::TimeDelta::FromHours(10).InSeconds(),
+ 100);
+ last_action_time_ = time_now;
+ int docked_all_count = 0;
+ int docked_visible_count = 0;
+ int docked_panels_count = 0;
+ int large_windows_count = 0;
+ for (size_t i = 0; i < dock_container_->children().size(); ++i) {
+ const aura::Window* window(dock_container_->children()[i]);
+ if (IsPopupOrTransient(window))
+ continue;
+ docked_all_count++;
+ if (!IsUsedByLayout(window))
+ continue;
+ docked_visible_count++;
+ if (window->type() == aura::client::WINDOW_TYPE_PANEL)
+ docked_panels_count++;
+ const wm::WindowState* window_state = wm::GetWindowState(window);
+ if (window_state->HasRestoreBounds()) {
+ const gfx::Rect restore_bounds = window_state->GetRestoreBoundsInScreen();
+ if (restore_bounds.width() > kMaxDockWidth)
+ large_windows_count++;
+ }
+ }
+ UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsAll", docked_all_count);
+ UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsLarge", large_windows_count);
+ UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsPanels", docked_panels_count);
+ UMA_HISTOGRAM_COUNTS_100("Ash.Dock.ItemsVisible", docked_visible_count);
+}
+
+void DockedWindowLayoutManager::UpdateDockedWidth(int width) {
+ if (docked_width_ == width)
+ return;
+ docked_width_ = width;
+ UMA_HISTOGRAM_COUNTS_10000("Ash.Dock.Width", docked_width_);
+}
+
+void DockedWindowLayoutManager::OnDraggedWindowDocked(aura::Window* window) {
+ DCHECK(!is_dragged_window_docked_);
+ is_dragged_window_docked_ = true;
+
+ // If there are no other docked windows update alignment.
+ if (!IsAnyWindowDocked())
+ alignment_ = DOCKED_ALIGNMENT_NONE;
+}
+
+void DockedWindowLayoutManager::OnDraggedWindowUndocked() {
+ // If this is the first window getting docked - update alignment.
+ if (!IsAnyWindowDocked())
+ alignment_ = GetAlignmentOfWindow(dragged_window_);
+
+ DCHECK (is_dragged_window_docked_);
+ is_dragged_window_docked_ = false;
+}
+
+bool DockedWindowLayoutManager::IsAnyWindowDocked() {
+ return CalculateAlignment() != DOCKED_ALIGNMENT_NONE;
+}
+
+void DockedWindowLayoutManager::Relayout() {
+ if (in_layout_)
+ return;
+ if (alignment_ == DOCKED_ALIGNMENT_NONE && !is_dragged_window_docked_)
+ return;
+ base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true);
+
+ gfx::Rect dock_bounds = dock_container_->GetBoundsInScreen();
+ aura::Window* active_window = NULL;
+ std::vector<WindowWithHeight> visible_windows;
+ for (size_t i = 0; i < dock_container_->children().size(); ++i) {
+ aura::Window* window(dock_container_->children()[i]);
+
+ if (!IsUsedByLayout(window) || window == dragged_window_)
+ continue;
+
+ // If the shelf is currently hidden (full-screen mode), hide window until
+ // full-screen mode is exited.
+ if (in_fullscreen_) {
+ // The call to Hide does not set the minimize property, so the window will
+ // be restored when the shelf becomes visible again.
+ window->Hide();
+ continue;
+ }
+ if (window->HasFocus() ||
+ window->Contains(
+ aura::client::GetFocusClient(window)->GetFocusedWindow())) {
+ DCHECK(!active_window);
+ active_window = window;
+ }
+ visible_windows.push_back(WindowWithHeight(window));
+ }
+ // Consider docked dragged_window_ when fanning out other child windows.
+ if (is_dragged_window_docked_) {
+ visible_windows.push_back(WindowWithHeight(dragged_window_));
+ DCHECK(!active_window);
+ active_window = dragged_window_;
+ }
+
+ // Position docked windows as well as the window being dragged.
+ gfx::Rect work_area =
+ Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area();
+ if (shelf_observer_)
+ work_area.Subtract(shelf_observer_->shelf_bounds_in_screen());
+ int available_room = CalculateWindowHeightsAndRemainingRoom(work_area,
+ &visible_windows);
+ FanOutChildren(work_area,
+ CalculateIdealWidth(visible_windows),
+ available_room,
+ &visible_windows);
+
+ // After the first Relayout allow the windows to change their order easier
+ // since we know they are docked.
+ is_dragged_from_dock_ = true;
+ UpdateStacking(active_window);
+}
+
+int DockedWindowLayoutManager::CalculateWindowHeightsAndRemainingRoom(
+ const gfx::Rect work_area,
+ std::vector<WindowWithHeight>* visible_windows) {
+ int available_room = work_area.height() - kMinDockGap;
+ int remaining_windows = visible_windows->size();
+
+ // Sort windows by their minimum heights and calculate target heights.
+ std::sort(visible_windows->begin(), visible_windows->end(),
+ CompareMinimumHeight());
+ // Distribute the free space among the docked windows. Since the windows are
+ // sorted (tall windows first) we can now assume that any window which
+ // required more space than the current window will have already been
+ // accounted for previously in this loop, so we can safely give that window
+ // its proportional share of the remaining space.
+ for (std::vector<WindowWithHeight>::reverse_iterator iter =
+ visible_windows->rbegin();
+ iter != visible_windows->rend(); ++iter) {
+ iter->height_ = GetWindowHeightCloseTo(
+ iter->window(), available_room / remaining_windows - kMinDockGap);
+ available_room -= (iter->height_ + kMinDockGap);
+ remaining_windows--;
+ }
+ return available_room;
+}
+
+int DockedWindowLayoutManager::CalculateIdealWidth(
+ const std::vector<WindowWithHeight>& visible_windows) {
+ int smallest_max_width = kMaxDockWidth;
+ int largest_min_width = kMinDockWidth;
+ // Ideal width of the docked area is as close to kIdealWidth as possible
+ // while still respecting the minimum and maximum width restrictions on the
+ // individual docked windows as well as the width that was possibly set by a
+ // user (which needs to be preserved when dragging and rearranging windows).
+ for (std::vector<WindowWithHeight>::const_iterator iter =
+ visible_windows.begin();
+ iter != visible_windows.end(); ++iter) {
+ const aura::Window* window = iter->window();
+ int min_window_width = window->bounds().width();
+ int max_window_width = min_window_width;
+ if (!wm::GetWindowState(window)->bounds_changed_by_user()) {
+ min_window_width = GetWindowWidthCloseTo(window, kMinDockWidth);
+ max_window_width = GetWindowWidthCloseTo(window, kMaxDockWidth);
+ }
+ largest_min_width = std::max(largest_min_width, min_window_width);
+ smallest_max_width = std::min(smallest_max_width, max_window_width);
+ }
+ int ideal_width = std::max(largest_min_width,
+ std::min(smallest_max_width, kIdealWidth));
+ // Restrict docked area width regardless of window restrictions.
+ ideal_width = std::max(std::min(ideal_width, kMaxDockWidth), kMinDockWidth);
+ return ideal_width;
+}
+
+void DockedWindowLayoutManager::FanOutChildren(
+ const gfx::Rect& work_area,
+ int ideal_docked_width,
+ int available_room,
+ std::vector<WindowWithHeight>* visible_windows) {
+ gfx::Rect dock_bounds = dock_container_->GetBoundsInScreen();
+
+ // Calculate initial vertical offset and the gap or overlap between windows.
+ const int num_windows = visible_windows->size();
+ const float delta = kMinDockGap + (float)available_room /
+ ((available_room > 0 || num_windows <= 1) ?
+ num_windows + 1 : num_windows - 1);
+ float y_pos = work_area.y() + ((delta > 0) ? delta : kMinDockGap);
+
+ // Docked area is shown only if there is at least one non-dragged visible
+ // docked window.
+ int new_width = ideal_docked_width;
+ if (visible_windows->empty() ||
+ (visible_windows->size() == 1 &&
+ (*visible_windows)[0].window() == dragged_window_)) {
+ new_width = 0;
+ }
+ UpdateDockedWidth(new_width);
+ // Sort windows by their center positions and fan out overlapping
+ // windows.
+ std::sort(visible_windows->begin(), visible_windows->end(),
+ CompareWindowPos(is_dragged_from_dock_ ? dragged_window_ : NULL,
+ delta));
+ for (std::vector<WindowWithHeight>::iterator iter = visible_windows->begin();
+ iter != visible_windows->end(); ++iter) {
+ aura::Window* window = iter->window();
+ gfx::Rect bounds = ScreenAsh::ConvertRectToScreen(
+ window->parent(), window->GetTargetBounds());
+ // A window is extended or shrunk to be as close as possible to the ideal
+ // docked area width. Windows that were resized by a user are kept at their
+ // existing size.
+ // This also enforces the min / max restrictions on the docked area width.
+ bounds.set_width(GetWindowWidthCloseTo(
+ window,
+ wm::GetWindowState(window)->bounds_changed_by_user() ?
+ bounds.width() : ideal_docked_width));
+ DCHECK_LE(bounds.width(), ideal_docked_width);
+
+ DockedAlignment alignment = alignment_;
+ if (alignment == DOCKED_ALIGNMENT_NONE && window == dragged_window_) {
+ alignment = GetAlignmentOfWindow(window);
+ if (alignment == DOCKED_ALIGNMENT_NONE)
+ bounds.set_size(gfx::Size());
+ }
+
+ // Fan out windows evenly distributing the overlap or remaining free space.
+ bounds.set_height(iter->height_);
+ bounds.set_y(std::max(work_area.y(),
+ std::min(work_area.bottom() - bounds.height(),
+ static_cast<int>(y_pos + 0.5))));
+ y_pos += bounds.height() + delta;
+
+ // All docked windows other than the one currently dragged remain stuck
+ // to the screen edge (flush with the edge or centered in the dock area).
+ switch (alignment) {
+ case DOCKED_ALIGNMENT_LEFT:
+ bounds.set_x(dock_bounds.x() +
+ (ideal_docked_width - bounds.width()) / 2);
+ break;
+ case DOCKED_ALIGNMENT_RIGHT:
+ bounds.set_x(dock_bounds.right() -
+ (ideal_docked_width + bounds.width()) / 2);
+ break;
+ case DOCKED_ALIGNMENT_NONE:
+ break;
+ }
+ if (window == dragged_window_) {
+ dragged_bounds_ = bounds;
+ continue;
+ }
+ // If the following asserts it is probably because not all the children
+ // have been removed when dock was closed.
+ DCHECK_NE(alignment_, DOCKED_ALIGNMENT_NONE);
+ bounds = ScreenAsh::ConvertRectFromScreen(dock_container_, bounds);
+ if (bounds != window->GetTargetBounds()) {
+ ui::Layer* layer = window->layer();
+ ui::ScopedLayerAnimationSettings slide_settings(layer->GetAnimator());
+ slide_settings.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ slide_settings.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kSlideDurationMs));
+ SetChildBoundsDirect(window, bounds);
+ }
+ }
+}
+
+void DockedWindowLayoutManager::UpdateDockBounds(
+ DockedWindowLayoutManagerObserver::Reason reason) {
+ int dock_inset = docked_width_ + (docked_width_ > 0 ? kMinDockGap : 0);
+ const gfx::Rect work_area =
+ Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area();
+ gfx::Rect bounds = gfx::Rect(
+ alignment_ == DOCKED_ALIGNMENT_RIGHT && dock_inset > 0 ?
+ dock_container_->bounds().right() - dock_inset:
+ dock_container_->bounds().x(),
+ dock_container_->bounds().y(),
+ dock_inset,
+ work_area.height());
+ docked_bounds_ = bounds +
+ dock_container_->GetBoundsInScreen().OffsetFromOrigin();
+ FOR_EACH_OBSERVER(
+ DockedWindowLayoutManagerObserver,
+ observer_list_,
+ OnDockBoundsChanging(bounds, reason));
+ // Show or hide background for docked area.
+ gfx::Rect background_bounds(docked_bounds_);
+ if (shelf_observer_)
+ background_bounds.Subtract(shelf_observer_->shelf_bounds_in_screen());
+ background_widget_->SetBackgroundBounds(background_bounds, alignment_);
+ if (docked_width_ > 0)
+ background_widget_->Show();
+ else
+ background_widget_->Hide();
+}
+
+void DockedWindowLayoutManager::UpdateStacking(aura::Window* active_window) {
+ if (!active_window) {
+ if (!last_active_window_)
+ return;
+ active_window = last_active_window_;
+ }
+
+ // Windows are stacked like a deck of cards:
+ // ,------.
+ // |,------.|
+ // |,------.|
+ // | active |
+ // | window |
+ // |`------'|
+ // |`------'|
+ // `------'
+ // Use the middle of each window to figure out how to stack the window.
+ // This allows us to update the stacking when a window is being dragged around
+ // by the titlebar.
+ std::map<int, aura::Window*> window_ordering;
+ for (aura::Window::Windows::const_iterator it =
+ dock_container_->children().begin();
+ it != dock_container_->children().end(); ++it) {
+ if (!IsUsedByLayout(*it) ||
+ ((*it) == dragged_window_ && !is_dragged_window_docked_)) {
+ continue;
+ }
+ gfx::Rect bounds = (*it)->bounds();
+ window_ordering.insert(std::make_pair(bounds.y() + bounds.height() / 2,
+ *it));
+ }
+ int active_center_y = active_window->bounds().CenterPoint().y();
+
+ aura::Window* previous_window = NULL;
+ for (std::map<int, aura::Window*>::const_iterator it =
+ window_ordering.begin();
+ it != window_ordering.end() && it->first < active_center_y; ++it) {
+ if (previous_window)
+ dock_container_->StackChildAbove(it->second, previous_window);
+ previous_window = it->second;
+ }
+ for (std::map<int, aura::Window*>::const_reverse_iterator it =
+ window_ordering.rbegin();
+ it != window_ordering.rend() && it->first > active_center_y; ++it) {
+ if (previous_window)
+ dock_container_->StackChildAbove(it->second, previous_window);
+ previous_window = it->second;
+ }
+
+ if (previous_window && active_window->parent() == dock_container_)
+ dock_container_->StackChildAbove(active_window, previous_window);
+ if (active_window != dragged_window_)
+ last_active_window_ = active_window;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// keyboard::KeyboardControllerObserver implementation:
+
+void DockedWindowLayoutManager::OnKeyboardBoundsChanging(
+ const gfx::Rect& keyboard_bounds) {
+ // This bounds change will have caused a change to the Shelf which does not
+ // propagate automatically to this class, so manually recalculate bounds.
+ Relayout();
+ UpdateDockBounds(DockedWindowLayoutManagerObserver::KEYBOARD_BOUNDS_CHANGING);
+}
+
+} // namespace internal
+} // namespace ash