| // 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/screen_util.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shelf/shelf_constants.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_resizer.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/focus_client.h" |
| #include "ui/aura/client/window_tree_client.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_delegate.h" |
| #include "ui/aura/window_event_dispatcher.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" |
| #include "ui/wm/core/window_util.h" |
| #include "ui/wm/public/activation_client.h" |
| |
| namespace ash { |
| |
| // 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 BackgroundAnimatorDelegate { |
| public: |
| explicit DockedBackgroundWidget(aura::Window* container) |
| : alignment_(DOCKED_ALIGNMENT_NONE), |
| background_animator_(this, 0, kShelfBackgroundAlpha), |
| alpha_(0), |
| opaque_background_(ui::LAYER_SOLID_COLOR), |
| visible_background_type_(SHELF_BACKGROUND_DEFAULT), |
| visible_background_change_type_(BACKGROUND_CHANGE_IMMEDIATE) { |
| 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 background type. Starts an animation to transition to |
| // |background_type| if the widget is visible. If the widget is not visible, |
| // the animation is postponed till the widget becomes visible. |
| void SetBackgroundType(ShelfBackgroundType background_type, |
| BackgroundAnimatorChangeType change_type) { |
| visible_background_type_ = background_type; |
| visible_background_change_type_ = change_type; |
| if (IsVisible()) |
| UpdateBackground(); |
| } |
| |
| // views::Widget: |
| virtual void OnNativeWidgetVisibilityChanged(bool visible) override { |
| views::Widget::OnNativeWidgetVisibilityChanged(visible); |
| UpdateBackground(); |
| } |
| |
| virtual void OnNativeWidgetPaint(gfx::Canvas* canvas) override { |
| const gfx::ImageSkia& shelf_background( |
| alignment_ == DOCKED_ALIGNMENT_LEFT ? |
| shelf_background_left_ : shelf_background_right_); |
| gfx::Rect rect = gfx::Rect(GetWindowBoundsInScreen().size()); |
| SkPaint paint; |
| paint.setAlpha(alpha_); |
| canvas->DrawImageInt(shelf_background, |
| 0, |
| 0, |
| shelf_background.width(), |
| shelf_background.height(), |
| alignment_ == DOCKED_ALIGNMENT_LEFT |
| ? rect.width() - shelf_background.width() |
| : 0, |
| 0, |
| shelf_background.width(), |
| rect.height(), |
| false, |
| paint); |
| canvas->DrawImageInt( |
| shelf_background, |
| alignment_ == DOCKED_ALIGNMENT_LEFT ? 0 : shelf_background.width() - 1, |
| 0, |
| 1, |
| shelf_background.height(), |
| alignment_ == DOCKED_ALIGNMENT_LEFT ? 0 : shelf_background.width(), |
| 0, |
| rect.width() - shelf_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.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); |
| SetVisibilityChangedAnimationsEnabled(false); |
| GetNativeWindow()->SetProperty(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(); |
| |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| gfx::ImageSkia shelf_background = |
| *rb.GetImageSkiaNamed(IDR_ASH_SHELF_BACKGROUND); |
| shelf_background_left_ = gfx::ImageSkiaOperations::CreateRotatedImage( |
| shelf_background, SkBitmapOperations::ROTATION_90_CW); |
| shelf_background_right_ = gfx::ImageSkiaOperations::CreateRotatedImage( |
| shelf_background, SkBitmapOperations::ROTATION_270_CW); |
| } |
| |
| // Transitions to |visible_background_type_| if the widget is visible and to |
| // SHELF_BACKGROUND_DEFAULT if it is not. |
| void UpdateBackground() { |
| ShelfBackgroundType background_type = IsVisible() ? |
| visible_background_type_ : SHELF_BACKGROUND_DEFAULT; |
| BackgroundAnimatorChangeType change_type = IsVisible() ? |
| visible_background_change_type_ : BACKGROUND_CHANGE_IMMEDIATE; |
| |
| 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())); |
| } |
| |
| DockedAlignment alignment_; |
| |
| // The animator for the background transitions. |
| 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 shelf_background_left_; |
| gfx::ImageSkia shelf_background_right_; |
| |
| // The background type to use when the widget is visible. When not visible, |
| // the widget uses SHELF_BACKGROUND_DEFAULT. |
| ShelfBackgroundType visible_background_type_; |
| |
| // Whether the widget should animate to |visible_background_type_|. |
| BackgroundAnimatorChangeType visible_background_change_type_; |
| |
| 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() == ui::wm::WINDOW_TYPE_POPUP || |
| ::wm::GetTransientParent(window)); |
| } |
| |
| // 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, |
| aura::Window* docked_container, |
| float delta) |
| : dragged_window_(dragged_window), |
| docked_container_(docked_container), |
| 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 = ScreenUtil::ConvertRectToScreen( |
| docked_container_, win1->GetTargetBounds()); |
| gfx::Rect win2_bounds = ScreenUtil::ConvertRectToScreen( |
| docked_container_, 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_; |
| aura::Window* docked_container_; |
| float delta_; |
| }; |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // A class that observes 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_->shelf()->shelf_widget()); |
| docked_layout_manager_->shelf()->shelf_widget()->GetNativeView() |
| ->AddObserver(this); |
| } |
| |
| virtual ~ShelfWindowObserver() { |
| if (docked_layout_manager_->shelf() && |
| docked_layout_manager_->shelf()->shelf_widget()) |
| docked_layout_manager_->shelf()->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_ = ScreenUtil::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) |
| : SnapToPixelLayoutManager(dock_container), |
| dock_container_(dock_container), |
| in_layout_(false), |
| dragged_window_(NULL), |
| is_dragged_window_docked_(false), |
| is_dragged_from_dock_(false), |
| shelf_(NULL), |
| workspace_controller_(workspace_controller), |
| in_fullscreen_(workspace_controller_->GetWindowState() == |
| WORKSPACE_WINDOW_STATE_FULL_SCREEN), |
| docked_width_(0), |
| alignment_(DOCKED_ALIGNMENT_NONE), |
| preferred_alignment_(DOCKED_ALIGNMENT_NONE), |
| event_source_(DOCKED_ACTION_SOURCE_UNKNOWN), |
| 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 (shelf_ && shelf_->shelf_widget()) { |
| ShelfLayoutManager* shelf_layout_manager = ShelfLayoutManager::ForShelf( |
| shelf_->shelf_widget()->GetNativeWindow()); |
| shelf_layout_manager->RemoveObserver(this); |
| shelf_observer_.reset(); |
| } |
| shelf_ = 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. |
| wm::WindowState* dragged_state = wm::GetWindowState(dragged_window_); |
| if (dragged_window_->parent() != dock_container_) { |
| dragged_window_->AddObserver(this); |
| dragged_state->AddObserver(this); |
| } else if (!IsAnyWindowDocked() && |
| dragged_state->drag_details() && |
| !(dragged_state->drag_details()->bounds_change & |
| WindowResizer::kBoundsChange_Resizes)) { |
| // If there are no other docked windows clear alignment when a docked window |
| // is moved (but not when it is resized or the window could get undocked |
| // when resized away from the edge while docked). |
| alignment_ = DOCKED_ALIGNMENT_NONE; |
| } |
| is_dragged_from_dock_ = window->parent() == dock_container_; |
| DCHECK(!is_dragged_window_docked_); |
| |
| // Resize all windows that are flush with the dock edge together if one of |
| // them gets resized. |
| if (dragged_window_->bounds().width() == docked_width_ && |
| (dragged_state->drag_details()->bounds_change & |
| WindowResizer::kBoundsChange_Resizes) && |
| (dragged_state->drag_details()->size_change_direction & |
| WindowResizer::kBoundsChangeDirection_Horizontal)) { |
| for (size_t i = 0; i < dock_container_->children().size(); ++i) { |
| aura::Window* window1(dock_container_->children()[i]); |
| if (IsUsedByLayout(window1) && |
| window1 != dragged_window_ && |
| window1->bounds().width() == docked_width_) { |
| wm::GetWindowState(window1)->set_bounds_changed_by_user(false); |
| } |
| } |
| } |
| } |
| |
| 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 { |
| // If this is the first window that got docked by a move update alignment. |
| if (alignment_ == DOCKED_ALIGNMENT_NONE) |
| alignment_ = GetEdgeNearestWindow(dragged_window_); |
| // 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::SetShelf(Shelf* shelf) { |
| DCHECK(!shelf_); |
| shelf_ = shelf; |
| if (shelf_->shelf_widget()) { |
| ShelfLayoutManager* shelf_layout_manager = ShelfLayoutManager::ForShelf( |
| shelf_->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 { |
| return CalculateAlignmentExcept(dragged_window_); |
| } |
| |
| DockedAlignment DockedWindowLayoutManager::CalculateAlignmentExcept( |
| const aura::Window* window) const { |
| // Find a child that is not the window being queried 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* child(dock_container_->children()[i]); |
| if (window != child && !IsPopupOrTransient(child)) |
| return alignment_; |
| } |
| // No docked windows remain other than possibly the window being queried. |
| // Return |NONE| to indicate that windows may get docked on either side. |
| return DOCKED_ALIGNMENT_NONE; |
| } |
| |
| bool DockedWindowLayoutManager::CanDockWindow( |
| aura::Window* window, |
| DockedAlignment desired_alignment) { |
| // Don't allow interactive docking of windows with transient parents such as |
| // modal browser dialogs. Prevent docking of panels attached to shelf during |
| // the drag. |
| wm::WindowState* window_state = wm::GetWindowState(window); |
| bool should_attach_to_shelf = window_state->drag_details() && |
| window_state->drag_details()->should_attach_to_shelf; |
| if (IsPopupOrTransient(window) || should_attach_to_shelf) |
| 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 && |
| (!window_state->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()) > work_area.height()) |
| return false; |
| // Cannot dock on the other size from an existing dock. |
| const DockedAlignment alignment = CalculateAlignmentExcept(window); |
| if (desired_alignment != DOCKED_ALIGNMENT_NONE && |
| alignment != DOCKED_ALIGNMENT_NONE && |
| alignment != desired_alignment) { |
| return false; |
| } |
| // Do not allow docking on the same side as shelf. |
| return IsDockedAlignmentValid(desired_alignment); |
| } |
| |
| bool DockedWindowLayoutManager::IsDockedAlignmentValid( |
| DockedAlignment alignment) const { |
| ShelfAlignment shelf_alignment = shelf_ ? shelf_->alignment() : |
| SHELF_ALIGNMENT_BOTTOM; |
| if ((alignment == DOCKED_ALIGNMENT_LEFT && |
| shelf_alignment == SHELF_ALIGNMENT_LEFT) || |
| (alignment == DOCKED_ALIGNMENT_RIGHT && |
| shelf_alignment == SHELF_ALIGNMENT_RIGHT)) { |
| return false; |
| } |
| return true; |
| } |
| |
| void DockedWindowLayoutManager::MaybeSetDesiredDockedAlignment( |
| DockedAlignment alignment) { |
| // If the requested alignment is |NONE| or there are no |
| // docked windows return early as we can't change whether there is a |
| // dock or not. If the requested alignment is the same as the current |
| // alignment return early as an optimization. |
| if (alignment == DOCKED_ALIGNMENT_NONE || |
| alignment_ == DOCKED_ALIGNMENT_NONE || |
| alignment_ == alignment || |
| !IsDockedAlignmentValid(alignment)) { |
| return; |
| } |
| alignment_ = alignment; |
| |
| Relayout(); |
| UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); |
| } |
| |
| 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. |
| // A window can be added without proper bounds when window is moved to another |
| // display via API or due to display configuration change, so the alignment |
| // is set based on which edge is closer in the new display. |
| if (alignment_ == DOCKED_ALIGNMENT_NONE) { |
| alignment_ = preferred_alignment_ != DOCKED_ALIGNMENT_NONE ? |
| preferred_alignment_ : GetEdgeNearestWindow(child); |
| } |
| MaybeMinimizeChildrenExcept(child); |
| child->AddObserver(this); |
| wm::GetWindowState(child)->AddObserver(this); |
| Relayout(); |
| UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); |
| |
| // Only keyboard-initiated actions are recorded here. Dragging cases |
| // are handled in FinishDragging. |
| if (event_source_ != DOCKED_ACTION_SOURCE_UNKNOWN) |
| RecordUmaAction(DOCKED_ACTION_DOCK, event_source_); |
| } |
| |
| 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; |
| |
| wm::WindowState* window_state = wm::GetWindowState(child); |
| if (visible && window_state->IsMinimized()) |
| window_state->Restore(); |
| Relayout(); |
| UpdateDockBounds(DockedWindowLayoutManagerObserver::CHILD_CHANGED); |
| } |
| |
| void DockedWindowLayoutManager::SetChildBounds( |
| aura::Window* child, |
| const gfx::Rect& requested_bounds) { |
| // The minimum constraints have to be applied first by the layout manager. |
| gfx::Rect actual_new_bounds(requested_bounds); |
| if (child->delegate()) { |
| const gfx::Size& min_size = child->delegate()->GetMinimumSize(); |
| actual_new_bounds.set_width( |
| std::max(min_size.width(), actual_new_bounds.width())); |
| actual_new_bounds.set_height( |
| std::max(min_size.height(), actual_new_bounds.height())); |
| } |
| SnapToPixelLayoutManager::SetChildBounds(child, actual_new_bounds); |
| if (IsPopupOrTransient(child)) |
| return; |
| // Whenever one of our windows is moved or resized enforce layout. |
| ShelfLayoutManager* shelf_layout = |
| ShelfLayoutManager::ForShelf(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 (!shelf_ || !shelf_->shelf_widget()) |
| return; |
| |
| if (alignment_ == DOCKED_ALIGNMENT_NONE) |
| return; |
| |
| // Do not allow shelf 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 = shelf_->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_->SetBackgroundType(background_type, change_type); |
| } |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // DockedWindowLayoutManager, WindowStateObserver implementation: |
| |
| void DockedWindowLayoutManager::OnPreWindowStateTypeChange( |
| wm::WindowState* window_state, |
| wm::WindowStateType 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->IsDocked()) { |
| if (window != dragged_window_) { |
| UndockWindow(window); |
| if (window_state->IsMaximizedOrFullscreen()) |
| RecordUmaAction(DOCKED_ACTION_MAXIMIZE, event_source_); |
| else |
| RecordUmaAction(DOCKED_ACTION_UNDOCK, event_source_); |
| } |
| } else if (window_state->IsMinimized()) { |
| MinimizeDockedWindow(window_state); |
| } else if (old_type == wm::WINDOW_STATE_TYPE_DOCKED_MINIMIZED) { |
| RestoreDockedWindow(window_state); |
| } else if (old_type == wm::WINDOW_STATE_TYPE_MINIMIZED) { |
| NOTREACHED() << "Minimized window in docked layout manager"; |
| } |
| } |
| |
| ///////////////////////////////////////////////////////////////////////////// |
| // 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 = ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT; |
| if (visible) { |
| animation_type = ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DROP; |
| ::wm::SetWindowVisibilityAnimationDuration( |
| window, base::TimeDelta::FromMilliseconds(kFadeDurationMs)); |
| } else if (wm::GetWindowState(window)->IsMinimized()) { |
| animation_type = WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE; |
| } |
| ::wm::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, event_source_); |
| } |
| |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // 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(); |
| bool gap_needed = !!child; |
| if (child) |
| available_room -= GetWindowHeightCloseTo(child, 0); |
| // 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) + |
| (gap_needed ? kMinDockGap : 0); |
| gap_needed = true; |
| 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, event_source_); |
| } |
| |
| 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, DOCKED_ALIGNMENT_NONE)) { |
| window_state->Restore(); |
| RecordUmaAction(DOCKED_ACTION_EVICT, event_source_); |
| return; |
| } |
| gfx::Rect bounds(window->bounds()); |
| bounds.set_y(work_area.bottom()); |
| window->SetBounds(bounds); |
| window->Show(); |
| MaybeMinimizeChildrenExcept(window); |
| RecordUmaAction(DOCKED_ACTION_RESTORE, event_source_); |
| } |
| |
| 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() == ui::wm::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; |
| } |
| |
| void DockedWindowLayoutManager::OnDraggedWindowUndocked() { |
| DCHECK (is_dragged_window_docked_); |
| is_dragged_window_docked_ = false; |
| } |
| |
| bool DockedWindowLayoutManager::IsAnyWindowDocked() { |
| return CalculateAlignment() != DOCKED_ALIGNMENT_NONE; |
| } |
| |
| DockedAlignment DockedWindowLayoutManager::GetEdgeNearestWindow( |
| const aura::Window* window) const { |
| const gfx::Rect& bounds(window->GetBoundsInScreen()); |
| const gfx::Rect container_bounds = dock_container_->GetBoundsInScreen(); |
| return (abs(bounds.x() - container_bounds.x()) < |
| abs(bounds.right() - container_bounds.right())) ? |
| DOCKED_ALIGNMENT_LEFT : DOCKED_ALIGNMENT_RIGHT; |
| } |
| |
| 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(); |
| int remaining_windows = visible_windows->size(); |
| int gap_height = remaining_windows > 1 ? kMinDockGap : 0; |
| |
| // 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 + gap_height) / remaining_windows - gap_height); |
| available_room -= (iter->height_ + gap_height); |
| remaining_windows--; |
| } |
| return available_room + gap_height; |
| } |
| |
| 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 = static_cast<float>(available_room) / |
| ((available_room > 0 || num_windows <= 1) ? |
| num_windows + 1 : num_windows - 1); |
| float y_pos = work_area.y() + ((delta > 0) ? delta : 0); |
| |
| // 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, |
| dock_container_, |
| delta)); |
| for (std::vector<WindowWithHeight>::iterator iter = visible_windows->begin(); |
| iter != visible_windows->end(); ++iter) { |
| aura::Window* window = iter->window(); |
| gfx::Rect bounds = ScreenUtil::ConvertRectToScreen( |
| dock_container_, 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 = GetEdgeNearestWindow(window); |
| |
| // 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 + kMinDockGap; |
| |
| // 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 = ScreenUtil::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 ash |