diff options
Diffstat (limited to 'chromium/ash/wm/dock/docked_window_layout_manager.cc')
-rw-r--r-- | chromium/ash/wm/dock/docked_window_layout_manager.cc | 1240 |
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 |