summaryrefslogtreecommitdiffstats
path: root/ash/shelf/shelf_layout_manager.cc
diff options
context:
space:
mode:
Diffstat (limited to 'ash/shelf/shelf_layout_manager.cc')
-rw-r--r--ash/shelf/shelf_layout_manager.cc923
1 files changed, 923 insertions, 0 deletions
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
new file mode 100644
index 0000000..56c50a4
--- /dev/null
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -0,0 +1,923 @@
+// Copyright (c) 2012 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/shelf/shelf_layout_manager.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "ash/ash_switches.h"
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_types.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_cycle_controller.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace_controller.h"
+#include "ash/wm/workspace/workspace_animations.h"
+#include "base/auto_reset.h"
+#include "base/command_line.h"
+#include "base/i18n/rtl.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Delay before showing the launcher. This is after the mouse stops moving.
+const int kAutoHideDelayMS = 200;
+
+// To avoid hiding the shelf when the mouse transitions from a message bubble
+// into the shelf, the hit test area is enlarged by this amount of pixels to
+// keep the shelf from hiding.
+const int kNotificationBubbleGapHeight = 6;
+
+ui::Layer* GetLayer(views::Widget* widget) {
+ return widget->GetNativeView()->layer();
+}
+
+bool IsDraggingTrayEnabled() {
+ static bool dragging_tray_allowed = CommandLine::ForCurrentProcess()->
+ HasSwitch(ash::switches::kAshEnableTrayDragging);
+ return dragging_tray_allowed;
+}
+
+} // namespace
+
+// static
+const int ShelfLayoutManager::kWorkspaceAreaBottomInset = 2;
+
+// static
+const int ShelfLayoutManager::kAutoHideSize = 3;
+
+// ShelfLayoutManager::AutoHideEventFilter -------------------------------------
+
+// Notifies ShelfLayoutManager any time the mouse moves.
+class ShelfLayoutManager::AutoHideEventFilter : public ui::EventHandler {
+ public:
+ explicit AutoHideEventFilter(ShelfLayoutManager* shelf);
+ virtual ~AutoHideEventFilter();
+
+ // Returns true if the last mouse event was a mouse drag.
+ bool in_mouse_drag() const { return in_mouse_drag_; }
+
+ // Overridden from ui::EventHandler:
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+
+ private:
+ ShelfLayoutManager* shelf_;
+ bool in_mouse_drag_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutoHideEventFilter);
+};
+
+ShelfLayoutManager::AutoHideEventFilter::AutoHideEventFilter(
+ ShelfLayoutManager* shelf)
+ : shelf_(shelf),
+ in_mouse_drag_(false) {
+ Shell::GetInstance()->AddPreTargetHandler(this);
+}
+
+ShelfLayoutManager::AutoHideEventFilter::~AutoHideEventFilter() {
+ Shell::GetInstance()->RemovePreTargetHandler(this);
+}
+
+void ShelfLayoutManager::AutoHideEventFilter::OnMouseEvent(
+ ui::MouseEvent* event) {
+ // This also checks IsShelfWindow() to make sure we don't attempt to hide the
+ // shelf if the mouse down occurs on the shelf.
+ in_mouse_drag_ = (event->type() == ui::ET_MOUSE_DRAGGED ||
+ (in_mouse_drag_ && event->type() != ui::ET_MOUSE_RELEASED &&
+ event->type() != ui::ET_MOUSE_CAPTURE_CHANGED)) &&
+ !shelf_->IsShelfWindow(static_cast<aura::Window*>(event->target()));
+ if (event->type() == ui::ET_MOUSE_MOVED)
+ shelf_->UpdateAutoHideState();
+ return;
+}
+
+// ShelfLayoutManager:UpdateShelfObserver --------------------------------------
+
+// UpdateShelfObserver is used to delay updating the background until the
+// animation completes.
+class ShelfLayoutManager::UpdateShelfObserver
+ : public ui::ImplicitAnimationObserver {
+ public:
+ explicit UpdateShelfObserver(ShelfLayoutManager* shelf) : shelf_(shelf) {
+ shelf_->update_shelf_observer_ = this;
+ }
+
+ void Detach() {
+ shelf_ = NULL;
+ }
+
+ virtual void OnImplicitAnimationsCompleted() OVERRIDE {
+ if (shelf_) {
+ shelf_->UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE);
+ }
+ delete this;
+ }
+
+ private:
+ virtual ~UpdateShelfObserver() {
+ if (shelf_)
+ shelf_->update_shelf_observer_ = NULL;
+ }
+
+ // Shelf we're in. NULL if deleted before we're deleted.
+ ShelfLayoutManager* shelf_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateShelfObserver);
+};
+
+// ShelfLayoutManager ----------------------------------------------------------
+
+ShelfLayoutManager::ShelfLayoutManager(ShelfWidget* shelf)
+ : root_window_(shelf->GetNativeView()->GetRootWindow()),
+ in_layout_(false),
+ auto_hide_behavior_(SHELF_AUTO_HIDE_BEHAVIOR_NEVER),
+ alignment_(SHELF_ALIGNMENT_BOTTOM),
+ shelf_(shelf),
+ workspace_controller_(NULL),
+ window_overlaps_shelf_(false),
+ gesture_drag_status_(GESTURE_DRAG_NONE),
+ gesture_drag_amount_(0.f),
+ gesture_drag_auto_hide_state_(SHELF_AUTO_HIDE_SHOWN),
+ update_shelf_observer_(NULL) {
+ Shell::GetInstance()->AddShellObserver(this);
+ aura::client::GetActivationClient(root_window_)->AddObserver(this);
+}
+
+ShelfLayoutManager::~ShelfLayoutManager() {
+ if (update_shelf_observer_)
+ update_shelf_observer_->Detach();
+
+ FOR_EACH_OBSERVER(Observer, observers_, WillDeleteShelf());
+ Shell::GetInstance()->RemoveShellObserver(this);
+ aura::client::GetActivationClient(root_window_)->RemoveObserver(this);
+}
+
+void ShelfLayoutManager::SetAutoHideBehavior(ShelfAutoHideBehavior behavior) {
+ if (auto_hide_behavior_ == behavior)
+ return;
+ auto_hide_behavior_ = behavior;
+ UpdateVisibilityState();
+ FOR_EACH_OBSERVER(Observer, observers_,
+ OnAutoHideStateChanged(state_.auto_hide_state));
+}
+
+bool ShelfLayoutManager::IsVisible() const {
+ return shelf_->status_area_widget()->IsVisible() &&
+ (state_.visibility_state == SHELF_VISIBLE ||
+ (state_.visibility_state == SHELF_AUTO_HIDE &&
+ state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN));
+}
+
+bool ShelfLayoutManager::SetAlignment(ShelfAlignment alignment) {
+ if (alignment_ == alignment)
+ return false;
+
+ alignment_ = alignment;
+ if (shelf_->launcher())
+ shelf_->launcher()->SetAlignment(alignment);
+ shelf_->status_area_widget()->SetShelfAlignment(alignment);
+ LayoutShelf();
+ return true;
+}
+
+gfx::Rect ShelfLayoutManager::GetIdealBounds() {
+ // TODO(oshima): this is wrong. Figure out what display shelf is on
+ // and everything should be based on it.
+ gfx::Rect bounds(
+ ScreenAsh::GetDisplayBoundsInParent(shelf_->GetNativeView()));
+ int width = 0, height = 0;
+ GetShelfSize(&width, &height);
+ return SelectValueForShelfAlignment(
+ gfx::Rect(bounds.x(), bounds.bottom() - height, bounds.width(), height),
+ gfx::Rect(bounds.x(), bounds.y(), width, bounds.height()),
+ gfx::Rect(bounds.right() - width, bounds.y(), width, bounds.height()),
+ gfx::Rect(bounds.x(), bounds.y(), bounds.width(), height));
+}
+
+void ShelfLayoutManager::LayoutShelf() {
+ base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true);
+ StopAnimating();
+ TargetBounds target_bounds;
+ CalculateTargetBounds(state_, &target_bounds);
+ GetLayer(shelf_)->SetOpacity(target_bounds.opacity);
+ shelf_->SetBounds(
+ ScreenAsh::ConvertRectToScreen(
+ shelf_->GetNativeView()->parent(),
+ target_bounds.shelf_bounds_in_root));
+ if (shelf_->launcher())
+ shelf_->launcher()->SetLauncherViewBounds(
+ target_bounds.launcher_bounds_in_shelf);
+ GetLayer(shelf_->status_area_widget())->SetOpacity(target_bounds.opacity);
+ // TODO(harrym): Once status area widget is a child view of shelf
+ // this can be simplified.
+ gfx::Rect status_bounds = target_bounds.status_bounds_in_shelf;
+ status_bounds.set_x(status_bounds.x() +
+ target_bounds.shelf_bounds_in_root.x());
+ status_bounds.set_y(status_bounds.y() +
+ target_bounds.shelf_bounds_in_root.y());
+ shelf_->status_area_widget()->SetBounds(
+ ScreenAsh::ConvertRectToScreen(
+ shelf_->status_area_widget()->GetNativeView()->parent(),
+ status_bounds));
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(
+ root_window_, target_bounds.work_area_insets);
+ UpdateHitTestBounds();
+}
+
+ShelfVisibilityState ShelfLayoutManager::CalculateShelfVisibility() {
+ switch(auto_hide_behavior_) {
+ case SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS:
+ return SHELF_AUTO_HIDE;
+ case SHELF_AUTO_HIDE_BEHAVIOR_NEVER:
+ return SHELF_VISIBLE;
+ case SHELF_AUTO_HIDE_ALWAYS_HIDDEN:
+ return SHELF_HIDDEN;
+ }
+ return SHELF_VISIBLE;
+}
+
+ShelfVisibilityState
+ShelfLayoutManager::CalculateShelfVisibilityWhileDragging() {
+ switch(auto_hide_behavior_) {
+ case SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS:
+ case SHELF_AUTO_HIDE_BEHAVIOR_NEVER:
+ return SHELF_AUTO_HIDE;
+ case SHELF_AUTO_HIDE_ALWAYS_HIDDEN:
+ return SHELF_HIDDEN;
+ }
+ return SHELF_VISIBLE;
+}
+
+void ShelfLayoutManager::UpdateVisibilityState() {
+ ShellDelegate* delegate = Shell::GetInstance()->delegate();
+ if (delegate && delegate->IsScreenLocked()) {
+ SetState(SHELF_VISIBLE);
+ } else if (gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS) {
+ // TODO(zelidrag): Verify shelf drag animation still shows on the device
+ // when we are in SHELF_AUTO_HIDE_ALWAYS_HIDDEN.
+ SetState(CalculateShelfVisibilityWhileDragging());
+ } else if (GetRootWindowController(root_window_)->IsImmersiveMode()) {
+ // The user choosing immersive mode indicates he or she wants to maximize
+ // screen real-estate for content, so always auto-hide the shelf.
+ DCHECK_NE(auto_hide_behavior_, SHELF_AUTO_HIDE_ALWAYS_HIDDEN);
+ SetState(SHELF_AUTO_HIDE);
+ } else {
+ WorkspaceWindowState window_state(workspace_controller_->GetWindowState());
+ switch (window_state) {
+ case WORKSPACE_WINDOW_STATE_FULL_SCREEN:
+ SetState(SHELF_HIDDEN);
+ break;
+
+ case WORKSPACE_WINDOW_STATE_MAXIMIZED:
+ SetState(CalculateShelfVisibility());
+ break;
+
+ case WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF:
+ case WORKSPACE_WINDOW_STATE_DEFAULT:
+ SetState(CalculateShelfVisibility());
+ SetWindowOverlapsShelf(window_state ==
+ WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF);
+ break;
+ }
+ }
+}
+
+void ShelfLayoutManager::UpdateAutoHideState() {
+ ShelfAutoHideState auto_hide_state =
+ CalculateAutoHideState(state_.visibility_state);
+ if (auto_hide_state != state_.auto_hide_state) {
+ if (auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
+ // Hides happen immediately.
+ SetState(state_.visibility_state);
+ FOR_EACH_OBSERVER(Observer, observers_,
+ OnAutoHideStateChanged(auto_hide_state));
+ } else {
+ auto_hide_timer_.Stop();
+ auto_hide_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kAutoHideDelayMS),
+ this, &ShelfLayoutManager::UpdateAutoHideStateNow);
+ FOR_EACH_OBSERVER(Observer, observers_, OnAutoHideStateChanged(
+ CalculateAutoHideState(state_.visibility_state)));
+ }
+ } else {
+ auto_hide_timer_.Stop();
+ }
+}
+
+void ShelfLayoutManager::SetWindowOverlapsShelf(bool value) {
+ window_overlaps_shelf_ = value;
+ UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE);
+}
+
+void ShelfLayoutManager::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void ShelfLayoutManager::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ShelfLayoutManager, Gesture dragging:
+
+void ShelfLayoutManager::StartGestureDrag(const ui::GestureEvent& gesture) {
+ gesture_drag_status_ = GESTURE_DRAG_IN_PROGRESS;
+ gesture_drag_amount_ = 0.f;
+ gesture_drag_auto_hide_state_ = visibility_state() == SHELF_AUTO_HIDE ?
+ auto_hide_state() : SHELF_AUTO_HIDE_SHOWN;
+ UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE);
+}
+
+ShelfLayoutManager::DragState ShelfLayoutManager::UpdateGestureDrag(
+ const ui::GestureEvent& gesture) {
+ bool horizontal = IsHorizontalAlignment();
+ gesture_drag_amount_ += horizontal ? gesture.details().scroll_y() :
+ gesture.details().scroll_x();
+ LayoutShelf();
+
+ // Start reveling the status menu when:
+ // - dragging up on an already visible shelf
+ // - dragging up on a hidden shelf, but it is currently completely visible.
+ if (horizontal && gesture.details().scroll_y() < 0) {
+ int min_height = 0;
+ if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_HIDDEN && shelf_)
+ min_height = shelf_->GetContentsView()->GetPreferredSize().height();
+
+ if (min_height < shelf_->GetWindowBoundsInScreen().height() &&
+ gesture.root_location().x() >=
+ shelf_->status_area_widget()->GetWindowBoundsInScreen().x() &&
+ IsDraggingTrayEnabled())
+ return DRAG_TRAY;
+ }
+
+ return DRAG_SHELF;
+}
+
+void ShelfLayoutManager::CompleteGestureDrag(const ui::GestureEvent& gesture) {
+ bool horizontal = IsHorizontalAlignment();
+ bool should_change = false;
+ if (gesture.type() == ui::ET_GESTURE_SCROLL_END) {
+ // The visibility of the shelf changes only if the shelf was dragged X%
+ // along the correct axis. If the shelf was already visible, then the
+ // direction of the drag does not matter.
+ const float kDragHideThreshold = 0.4f;
+ gfx::Rect bounds = GetIdealBounds();
+ float drag_ratio = fabs(gesture_drag_amount_) /
+ (horizontal ? bounds.height() : bounds.width());
+ if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN) {
+ should_change = drag_ratio > kDragHideThreshold;
+ } else {
+ bool correct_direction = false;
+ switch (alignment_) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ case SHELF_ALIGNMENT_RIGHT:
+ correct_direction = gesture_drag_amount_ < 0;
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ case SHELF_ALIGNMENT_TOP:
+ correct_direction = gesture_drag_amount_ > 0;
+ break;
+ }
+ should_change = correct_direction && drag_ratio > kDragHideThreshold;
+ }
+ } else if (gesture.type() == ui::ET_SCROLL_FLING_START) {
+ if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN) {
+ should_change = horizontal ? fabs(gesture.details().velocity_y()) > 0 :
+ fabs(gesture.details().velocity_x()) > 0;
+ } else {
+ should_change = SelectValueForShelfAlignment(
+ gesture.details().velocity_y() < 0,
+ gesture.details().velocity_x() > 0,
+ gesture.details().velocity_x() < 0,
+ gesture.details().velocity_y() > 0);
+ }
+ } else {
+ NOTREACHED();
+ }
+
+ if (!should_change) {
+ CancelGestureDrag();
+ return;
+ }
+
+ gesture_drag_auto_hide_state_ =
+ gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN ?
+ SHELF_AUTO_HIDE_HIDDEN : SHELF_AUTO_HIDE_SHOWN;
+ if (shelf_)
+ shelf_->Deactivate();
+ shelf_->status_area_widget()->Deactivate();
+ if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_HIDDEN &&
+ auto_hide_behavior_ != SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS) {
+ gesture_drag_status_ = GESTURE_DRAG_NONE;
+ SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ } else if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN &&
+ auto_hide_behavior_ != SHELF_AUTO_HIDE_BEHAVIOR_NEVER) {
+ gesture_drag_status_ = GESTURE_DRAG_NONE;
+ SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ } else {
+ gesture_drag_status_ = GESTURE_DRAG_COMPLETE_IN_PROGRESS;
+ UpdateVisibilityState();
+ gesture_drag_status_ = GESTURE_DRAG_NONE;
+ }
+}
+
+void ShelfLayoutManager::CancelGestureDrag() {
+ gesture_drag_status_ = GESTURE_DRAG_NONE;
+ ui::ScopedLayerAnimationSettings
+ launcher_settings(GetLayer(shelf_)->GetAnimator()),
+ status_settings(GetLayer(shelf_->status_area_widget())->GetAnimator());
+ LayoutShelf();
+ UpdateVisibilityState();
+ UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ShelfLayoutManager, aura::LayoutManager implementation:
+
+void ShelfLayoutManager::OnWindowResized() {
+ LayoutShelf();
+}
+
+void ShelfLayoutManager::OnWindowAddedToLayout(aura::Window* child) {
+}
+
+void ShelfLayoutManager::OnWillRemoveWindowFromLayout(aura::Window* child) {
+}
+
+void ShelfLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) {
+}
+
+void ShelfLayoutManager::OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visible) {
+}
+
+void ShelfLayoutManager::SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) {
+ SetChildBoundsDirect(child, requested_bounds);
+ // We may contain other widgets (such as frame maximize bubble) but they don't
+ // effect the layout in anyway.
+ if (!in_layout_ &&
+ ((shelf_->GetNativeView() == child) ||
+ (shelf_->status_area_widget()->GetNativeView() == child))) {
+ LayoutShelf();
+ }
+}
+
+void ShelfLayoutManager::OnLockStateChanged(bool locked) {
+ UpdateVisibilityState();
+}
+
+void ShelfLayoutManager::OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) {
+ UpdateAutoHideStateNow();
+}
+
+bool ShelfLayoutManager::IsHorizontalAlignment() const {
+ return alignment_ == SHELF_ALIGNMENT_BOTTOM ||
+ alignment_ == SHELF_ALIGNMENT_TOP;
+}
+
+// static
+ShelfLayoutManager* ShelfLayoutManager::ForLauncher(aura::Window* window) {
+ ShelfWidget* shelf = RootWindowController::ForLauncher(window)->shelf();
+ return shelf ? shelf->shelf_layout_manager() : NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ShelfLayoutManager, private:
+
+ShelfLayoutManager::TargetBounds::TargetBounds() : opacity(0.0f) {}
+ShelfLayoutManager::TargetBounds::~TargetBounds() {}
+
+void ShelfLayoutManager::SetState(ShelfVisibilityState visibility_state) {
+ ShellDelegate* delegate = Shell::GetInstance()->delegate();
+ State state;
+ state.visibility_state = visibility_state;
+ state.auto_hide_state = CalculateAutoHideState(visibility_state);
+ state.is_screen_locked = delegate && delegate->IsScreenLocked();
+
+ // It's possible for SetState() when a window becomes maximized but the state
+ // won't have changed value. Do the dimming check before the early exit.
+ if (workspace_controller_) {
+ shelf_->SetDimsShelf(
+ (state.visibility_state == SHELF_VISIBLE) &&
+ workspace_controller_->GetWindowState() ==
+ WORKSPACE_WINDOW_STATE_MAXIMIZED);
+ }
+
+ if (state_.Equals(state))
+ return; // Nothing changed.
+
+ FOR_EACH_OBSERVER(Observer, observers_,
+ WillChangeVisibilityState(visibility_state));
+
+ if (state.visibility_state == SHELF_AUTO_HIDE) {
+ // When state is SHELF_AUTO_HIDE we need to track when the mouse is over the
+ // launcher to unhide the shelf. AutoHideEventFilter does that for us.
+ if (!event_filter_.get())
+ event_filter_.reset(new AutoHideEventFilter(this));
+ } else {
+ event_filter_.reset(NULL);
+ }
+
+ auto_hide_timer_.Stop();
+
+ // Animating the background when transitioning from auto-hide & hidden to
+ // visible is janky. Update the background immediately in this case.
+ BackgroundAnimator::ChangeType change_type =
+ (state_.visibility_state == SHELF_AUTO_HIDE &&
+ state_.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN &&
+ state.visibility_state == SHELF_VISIBLE) ?
+ BackgroundAnimator::CHANGE_IMMEDIATE : BackgroundAnimator::CHANGE_ANIMATE;
+ StopAnimating();
+
+ State old_state = state_;
+ state_ = state;
+ TargetBounds target_bounds;
+ CalculateTargetBounds(state_, &target_bounds);
+
+ ui::ScopedLayerAnimationSettings launcher_animation_setter(
+ GetLayer(shelf_)->GetAnimator());
+ launcher_animation_setter.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kWorkspaceSwitchTimeMS));
+ launcher_animation_setter.SetTweenType(ui::Tween::EASE_OUT);
+ GetLayer(shelf_)->SetBounds(
+ target_bounds.shelf_bounds_in_root);
+ GetLayer(shelf_)->SetOpacity(target_bounds.opacity);
+ ui::ScopedLayerAnimationSettings status_animation_setter(
+ GetLayer(shelf_->status_area_widget())->GetAnimator());
+ status_animation_setter.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kWorkspaceSwitchTimeMS));
+ status_animation_setter.SetTweenType(ui::Tween::EASE_OUT);
+
+ // Delay updating the background when going from SHELF_AUTO_HIDE_SHOWN to
+ // SHELF_AUTO_HIDE_HIDDEN until the shelf animates out. Otherwise during the
+ // animation you see the background change.
+ // Also delay the animation when the shelf was hidden, and has just been made
+ // visible (e.g. using a gesture-drag).
+ bool delay_shelf_update =
+ state.visibility_state == SHELF_AUTO_HIDE &&
+ state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN &&
+ old_state.visibility_state == SHELF_AUTO_HIDE;
+
+ if (state.visibility_state == SHELF_VISIBLE &&
+ old_state.visibility_state == SHELF_AUTO_HIDE &&
+ old_state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN)
+ delay_shelf_update = true;
+
+ if (delay_shelf_update) {
+ if (update_shelf_observer_)
+ update_shelf_observer_->Detach();
+ // UpdateShelfBackground deletes itself when the animation is done.
+ update_shelf_observer_ = new UpdateShelfObserver(this);
+ status_animation_setter.AddObserver(update_shelf_observer_);
+ }
+ ui::Layer* layer = GetLayer(shelf_->status_area_widget());
+ // TODO(harrym): Remove when status_area is view (crbug.com/180422).
+ gfx::Rect status_bounds = target_bounds.status_bounds_in_shelf;
+ status_bounds.set_x(status_bounds.x() +
+ target_bounds.shelf_bounds_in_root.x());
+ status_bounds.set_y(status_bounds.y() +
+ target_bounds.shelf_bounds_in_root.y());
+ layer->SetBounds(status_bounds);
+ layer->SetOpacity(target_bounds.opacity);
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(
+ root_window_, target_bounds.work_area_insets);
+ UpdateHitTestBounds();
+ if (!delay_shelf_update)
+ UpdateShelfBackground(change_type);
+}
+
+void ShelfLayoutManager::StopAnimating() {
+ GetLayer(shelf_)->GetAnimator()->StopAnimating();
+ GetLayer(shelf_->status_area_widget())->GetAnimator()->StopAnimating();
+}
+
+void ShelfLayoutManager::GetShelfSize(int* width, int* height) {
+ *width = *height = 0;
+ gfx::Size status_size(
+ shelf_->status_area_widget()->GetWindowBoundsInScreen().size());
+ if (IsHorizontalAlignment())
+ *height = std::max(kLauncherPreferredSize, status_size.height());
+ else
+ *width = std::max(kLauncherPreferredSize, status_size.width());
+}
+
+void ShelfLayoutManager::AdjustBoundsBasedOnAlignment(int inset,
+ gfx::Rect* bounds) const {
+ bounds->Inset(SelectValueForShelfAlignment(
+ gfx::Insets(0, 0, inset, 0),
+ gfx::Insets(0, inset, 0, 0),
+ gfx::Insets(0, 0, 0, inset),
+ gfx::Insets(inset, 0, 0, 0)));
+}
+
+void ShelfLayoutManager::CalculateTargetBounds(
+ const State& state,
+ TargetBounds* target_bounds) {
+ const gfx::Rect& available_bounds(root_window_->bounds());
+ gfx::Rect status_size(
+ shelf_->status_area_widget()->GetWindowBoundsInScreen().size());
+ int shelf_width = 0, shelf_height = 0;
+ GetShelfSize(&shelf_width, &shelf_height);
+ if (IsHorizontalAlignment())
+ shelf_width = available_bounds.width();
+ else
+ shelf_height = available_bounds.height();
+
+ if (state.visibility_state == SHELF_AUTO_HIDE &&
+ state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
+ // Keep the launcher to its full height when dragging is in progress.
+ if (gesture_drag_status_ == GESTURE_DRAG_NONE) {
+ if (IsHorizontalAlignment())
+ shelf_height = kAutoHideSize;
+ else
+ shelf_width = kAutoHideSize;
+ }
+ } else if (state.visibility_state == SHELF_HIDDEN) {
+ if (IsHorizontalAlignment())
+ shelf_height = 0;
+ else
+ shelf_width = 0;
+ }
+ target_bounds->shelf_bounds_in_root = SelectValueForShelfAlignment(
+ gfx::Rect(available_bounds.x(), available_bounds.bottom() - shelf_height,
+ available_bounds.width(), shelf_height),
+ gfx::Rect(available_bounds.x(), available_bounds.y(),
+ shelf_width, available_bounds.height()),
+ gfx::Rect(available_bounds.right() - shelf_width, available_bounds.y(),
+ shelf_width, available_bounds.height()),
+ gfx::Rect(available_bounds.x(), available_bounds.y(),
+ available_bounds.width(), shelf_height));
+
+ int status_inset = (kLauncherPreferredSize -
+ PrimaryAxisValue(status_size.height(), status_size.width()));
+
+ target_bounds->status_bounds_in_shelf = SelectValueForShelfAlignment(
+ gfx::Rect(base::i18n::IsRTL() ? 0 : shelf_width - status_size.width(),
+ status_inset, status_size.width(), status_size.height()),
+ gfx::Rect(shelf_width - (status_size.width() + status_inset),
+ shelf_height - status_size.height(), status_size.width(),
+ status_size.height()),
+ gfx::Rect(status_inset, shelf_height - status_size.height(),
+ status_size.width(), status_size.height()),
+ gfx::Rect(base::i18n::IsRTL() ? 0 : shelf_width - status_size.width(),
+ shelf_height - (status_size.height() + status_inset),
+ status_size.width(), status_size.height()));
+
+ target_bounds->launcher_bounds_in_shelf = SelectValueForShelfAlignment(
+ gfx::Rect(base::i18n::IsRTL() ? status_size.width() : 0, 0,
+ shelf_width - status_size.width(), shelf_height),
+ gfx::Rect(0, 0, shelf_width, shelf_height - status_size.height()),
+ gfx::Rect(0, 0, shelf_width, shelf_height - status_size.height()),
+ gfx::Rect(base::i18n::IsRTL() ? status_size.width() : 0, 0,
+ shelf_width - status_size.width(), shelf_height));
+
+ target_bounds->work_area_insets = SelectValueForShelfAlignment(
+ gfx::Insets(0, 0, GetWorkAreaSize(state, shelf_height), 0),
+ gfx::Insets(0, GetWorkAreaSize(state, shelf_width), 0, 0),
+ gfx::Insets(0, 0, 0, GetWorkAreaSize(state, shelf_width)),
+ gfx::Insets(GetWorkAreaSize(state, shelf_height), 0, 0, 0));
+
+ target_bounds->opacity =
+ (gesture_drag_status_ != GESTURE_DRAG_NONE ||
+ state.visibility_state == SHELF_VISIBLE ||
+ state.visibility_state == SHELF_AUTO_HIDE) ? 1.0f : 0.0f;
+ if (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS)
+ UpdateTargetBoundsForGesture(target_bounds);
+}
+
+void ShelfLayoutManager::UpdateTargetBoundsForGesture(
+ TargetBounds* target_bounds) const {
+ CHECK_EQ(GESTURE_DRAG_IN_PROGRESS, gesture_drag_status_);
+ bool horizontal = IsHorizontalAlignment();
+ int resistance_free_region = 0;
+
+ if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_HIDDEN &&
+ visibility_state() == SHELF_AUTO_HIDE &&
+ auto_hide_state() != SHELF_AUTO_HIDE_SHOWN) {
+ // If the shelf was hidden when the drag started (and the state hasn't
+ // changed since then, e.g. because the tray-menu was shown because of the
+ // drag), then allow the drag some resistance-free region at first to make
+ // sure the shelf sticks with the finger until the shelf is visible.
+ resistance_free_region += horizontal ?
+ target_bounds->shelf_bounds_in_root.height() :
+ target_bounds->shelf_bounds_in_root.width();
+ resistance_free_region -= kAutoHideSize;
+ }
+
+ bool resist = SelectValueForShelfAlignment(
+ gesture_drag_amount_ < -resistance_free_region,
+ gesture_drag_amount_ > resistance_free_region,
+ gesture_drag_amount_ < -resistance_free_region,
+ gesture_drag_amount_ > resistance_free_region);
+
+ float translate = 0.f;
+ if (resist) {
+ float diff = fabsf(gesture_drag_amount_) - resistance_free_region;
+ diff = std::min(diff, sqrtf(diff));
+ if (gesture_drag_amount_ < 0)
+ translate = -resistance_free_region - diff;
+ else
+ translate = resistance_free_region + diff;
+ } else {
+ translate = gesture_drag_amount_;
+ }
+
+ if (horizontal) {
+ // Move the launcher with the gesture.
+ target_bounds->shelf_bounds_in_root.Offset(0, translate);
+
+ if (translate < 0) {
+ // When dragging up, the launcher height should increase.
+ float move = std::max(translate,
+ -static_cast<float>(resistance_free_region));
+ target_bounds->shelf_bounds_in_root.set_height(
+ target_bounds->shelf_bounds_in_root.height() + move - translate);
+
+ // The statusbar should be in the center.
+ gfx::Rect status_y = target_bounds->shelf_bounds_in_root;
+ status_y.ClampToCenteredSize(
+ target_bounds->status_bounds_in_shelf.size());
+ target_bounds->status_bounds_in_shelf.set_y(status_y.y());
+ }
+ } else {
+ // Move the launcher with the gesture.
+ if (alignment_ == SHELF_ALIGNMENT_RIGHT)
+ target_bounds->shelf_bounds_in_root.Offset(translate, 0);
+
+ if ((translate > 0 && alignment_ == SHELF_ALIGNMENT_RIGHT) ||
+ (translate < 0 && alignment_ == SHELF_ALIGNMENT_LEFT)) {
+ // When dragging towards the edge, the statusbar should move.
+ target_bounds->status_bounds_in_shelf.Offset(translate, 0);
+ } else {
+ // When dragging away from the edge, the launcher width should increase.
+ float move = alignment_ == SHELF_ALIGNMENT_RIGHT ?
+ std::max(translate, -static_cast<float>(resistance_free_region)) :
+ std::min(translate, static_cast<float>(resistance_free_region));
+
+ if (alignment_ == SHELF_ALIGNMENT_RIGHT) {
+ target_bounds->shelf_bounds_in_root.set_width(
+ target_bounds->shelf_bounds_in_root.width() + move - translate);
+ } else {
+ target_bounds->shelf_bounds_in_root.set_width(
+ target_bounds->shelf_bounds_in_root.width() - move + translate);
+ }
+
+ // The statusbar should be in the center.
+ gfx::Rect status_x = target_bounds->shelf_bounds_in_root;
+ status_x.ClampToCenteredSize(
+ target_bounds->status_bounds_in_shelf.size());
+ target_bounds->status_bounds_in_shelf.set_x(status_x.x());
+ }
+ }
+}
+
+void ShelfLayoutManager::UpdateShelfBackground(
+ BackgroundAnimator::ChangeType type) {
+ bool launcher_paints = GetLauncherPaintsBackground();
+ shelf_->SetPaintsBackground(launcher_paints, type);
+}
+
+bool ShelfLayoutManager::GetLauncherPaintsBackground() const {
+ return gesture_drag_status_ != GESTURE_DRAG_NONE ||
+ (!state_.is_screen_locked && window_overlaps_shelf_) ||
+ (state_.visibility_state == SHELF_AUTO_HIDE) ;
+}
+
+void ShelfLayoutManager::UpdateAutoHideStateNow() {
+ SetState(state_.visibility_state);
+}
+
+ShelfAutoHideState ShelfLayoutManager::CalculateAutoHideState(
+ ShelfVisibilityState visibility_state) const {
+ if (visibility_state != SHELF_AUTO_HIDE || !shelf_)
+ return SHELF_AUTO_HIDE_HIDDEN;
+
+ if (gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS)
+ return gesture_drag_auto_hide_state_;
+
+ Shell* shell = Shell::GetInstance();
+ if (shell->GetAppListTargetVisibility())
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ if (shelf_->status_area_widget() &&
+ shelf_->status_area_widget()->ShouldShowLauncher())
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ if (shelf_->launcher() && shelf_->launcher()->IsShowingMenu())
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ if (shelf_->launcher() && shelf_->launcher()->IsShowingOverflowBubble())
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ if (shelf_->IsActive() || shelf_->status_area_widget()->IsActive())
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ // Don't show if the user is dragging the mouse.
+ if (event_filter_.get() && event_filter_->in_mouse_drag())
+ return SHELF_AUTO_HIDE_HIDDEN;
+
+ gfx::Rect shelf_region = shelf_->GetWindowBoundsInScreen();
+ if (shelf_->status_area_widget() &&
+ shelf_->status_area_widget()->IsMessageBubbleShown() &&
+ IsVisible()) {
+ // Increase the the hit test area to prevent the shelf from disappearing
+ // when the mouse is over the bubble gap.
+ shelf_region.Inset(alignment_ == SHELF_ALIGNMENT_RIGHT ?
+ -kNotificationBubbleGapHeight : 0,
+ alignment_ == SHELF_ALIGNMENT_BOTTOM ?
+ -kNotificationBubbleGapHeight : 0,
+ alignment_ == SHELF_ALIGNMENT_LEFT ?
+ -kNotificationBubbleGapHeight : 0,
+ alignment_ == SHELF_ALIGNMENT_TOP ?
+ -kNotificationBubbleGapHeight : 0);
+ }
+
+ if (shelf_region.Contains(Shell::GetScreen()->GetCursorScreenPoint()))
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ const std::vector<aura::Window*> windows =
+ ash::WindowCycleController::BuildWindowList(NULL);
+
+ // Process the window list and check if there are any visible windows.
+ for (size_t i = 0; i < windows.size(); ++i) {
+ if (windows[i] && windows[i]->IsVisible() &&
+ !ash::wm::IsWindowMinimized(windows[i]))
+ return SHELF_AUTO_HIDE_HIDDEN;
+ }
+
+ // If there are no visible windows do not hide the shelf.
+ return SHELF_AUTO_HIDE_SHOWN;
+}
+
+void ShelfLayoutManager::UpdateHitTestBounds() {
+ gfx::Insets insets;
+ // Only modify the hit test when the shelf is visible, so we don't mess with
+ // hover hit testing in the auto-hide state.
+ if (state_.visibility_state == SHELF_VISIBLE) {
+ // Let clicks at the very top of the launcher through so windows can be
+ // resized with the bottom-right corner and bottom edge.
+ switch (alignment_) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ insets.Set(kWorkspaceAreaBottomInset, 0, 0, 0);
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ insets.Set(0, 0, 0, kWorkspaceAreaBottomInset);
+ break;
+ case SHELF_ALIGNMENT_RIGHT:
+ insets.Set(0, kWorkspaceAreaBottomInset, 0, 0);
+ break;
+ case SHELF_ALIGNMENT_TOP:
+ insets.Set(0, 0, kWorkspaceAreaBottomInset, 0);
+ break;
+ }
+ }
+ if (shelf_ && shelf_->GetNativeWindow()) {
+ shelf_->GetNativeWindow()->SetHitTestBoundsOverrideOuter(
+ insets, 1);
+ }
+ shelf_->status_area_widget()->GetNativeWindow()->
+ SetHitTestBoundsOverrideOuter(insets, 1);
+}
+
+bool ShelfLayoutManager::IsShelfWindow(aura::Window* window) {
+ if (!window)
+ return false;
+ return (shelf_ &&
+ shelf_->GetNativeWindow()->Contains(window)) ||
+ (shelf_->status_area_widget()->GetNativeWindow()->Contains(window));
+}
+
+int ShelfLayoutManager::GetWorkAreaSize(const State& state, int size) const {
+ if (state.visibility_state == SHELF_VISIBLE)
+ return size;
+ if (state.visibility_state == SHELF_AUTO_HIDE)
+ return kAutoHideSize;
+ return 0;
+}
+
+} // namespace internal
+} // namespace ash