summaryrefslogtreecommitdiffstats
path: root/ash/shelf
diff options
context:
space:
mode:
authorharrym@chromium.org <harrym@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-09 02:50:58 +0000
committerharrym@chromium.org <harrym@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-09 02:50:58 +0000
commit478c6c3aa0165dfd120993c5ec0757d594e70fe6 (patch)
tree3dd95bfe585e857e3672c9cfa1e705e6cdd19416 /ash/shelf
parent62ad3097ac2d96a2e8c65769a997702e7f0a755b (diff)
downloadchromium_src-478c6c3aa0165dfd120993c5ec0757d594e70fe6.zip
chromium_src-478c6c3aa0165dfd120993c5ec0757d594e70fe6.tar.gz
chromium_src-478c6c3aa0165dfd120993c5ec0757d594e70fe6.tar.bz2
Refactor: Shelf Widget
Refactor the classes related with displaying the shelf (background behind the launcher/status area widget) to be a separate class. Removing background delegates from the launcher and status area widget (represented on tray views). TBR=ben@chromium.org BUG=163002 Review URL: https://chromiumcodereview.appspot.com/12313118 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@187122 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ash/shelf')
-rw-r--r--ash/shelf/background_animator.cc57
-rw-r--r--ash/shelf/background_animator.h71
-rw-r--r--ash/shelf/shelf_layout_manager.cc923
-rw-r--r--ash/shelf/shelf_layout_manager.h332
-rw-r--r--ash/shelf/shelf_layout_manager_unittest.cc1108
-rw-r--r--ash/shelf/shelf_types.h46
-rw-r--r--ash/shelf/shelf_widget.cc312
-rw-r--r--ash/shelf/shelf_widget.h104
-rw-r--r--ash/shelf/shelf_widget_unittest.cc156
9 files changed, 3109 insertions, 0 deletions
diff --git a/ash/shelf/background_animator.cc b/ash/shelf/background_animator.cc
new file mode 100644
index 0000000..7b968c8
--- /dev/null
+++ b/ash/shelf/background_animator.cc
@@ -0,0 +1,57 @@
+// 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/background_animator.h"
+
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Duration of the background animation.
+const int kBackgroundDurationMS = 1000;
+
+}
+
+BackgroundAnimator::BackgroundAnimator(BackgroundAnimatorDelegate* delegate,
+ int min_alpha,
+ int max_alpha)
+ : delegate_(delegate),
+ min_alpha_(min_alpha),
+ max_alpha_(max_alpha),
+ ALLOW_THIS_IN_INITIALIZER_LIST(animation_(this)),
+ paints_background_(false),
+ alpha_(min_alpha) {
+ animation_.SetSlideDuration(kBackgroundDurationMS);
+}
+
+BackgroundAnimator::~BackgroundAnimator() {
+}
+
+void BackgroundAnimator::SetPaintsBackground(bool value, ChangeType type) {
+ if (paints_background_ == value)
+ return;
+ paints_background_ = value;
+ if (type == CHANGE_IMMEDIATE && !animation_.is_animating()) {
+ animation_.Reset(value ? 1.0f : 0.0f);
+ AnimationProgressed(&animation_);
+ return;
+ }
+ if (paints_background_)
+ animation_.Show();
+ else
+ animation_.Hide();
+}
+
+void BackgroundAnimator::AnimationProgressed(const ui::Animation* animation) {
+ int alpha = animation->CurrentValueBetween(min_alpha_, max_alpha_);
+ if (alpha_ == alpha)
+ return;
+ alpha_ = alpha;
+ delegate_->UpdateBackground(alpha_);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/ash/shelf/background_animator.h b/ash/shelf/background_animator.h
new file mode 100644
index 0000000..7e87fd7
--- /dev/null
+++ b/ash/shelf/background_animator.h
@@ -0,0 +1,71 @@
+// 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.
+
+#ifndef ASH_SHELF_BACKGROUND_ANIMATOR_H_
+#define ASH_SHELF_BACKGROUND_ANIMATOR_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/base/animation/slide_animation.h"
+
+namespace ash {
+namespace internal {
+
+// Delegate is notified any time the background changes.
+class ASH_EXPORT BackgroundAnimatorDelegate {
+ public:
+ virtual void UpdateBackground(int alpha) = 0;
+
+ protected:
+ virtual ~BackgroundAnimatorDelegate() {}
+};
+
+// BackgroundAnimator is used by the shelf to animate the background (alpha).
+class ASH_EXPORT BackgroundAnimator : public ui::AnimationDelegate {
+ public:
+ // How the background can be changed.
+ enum ChangeType {
+ CHANGE_ANIMATE,
+ CHANGE_IMMEDIATE
+ };
+
+ BackgroundAnimator(BackgroundAnimatorDelegate* delegate,
+ int min_alpha,
+ int max_alpha);
+ virtual ~BackgroundAnimator();
+
+ // Sets whether a background is rendered. Initial value is false. If |type|
+ // is |CHANGE_IMMEDIATE| and an animation is not in progress this notifies
+ // the delegate immediately (synchronously from this method).
+ void SetPaintsBackground(bool value, ChangeType type);
+ bool paints_background() const { return paints_background_; }
+
+ // Current alpha.
+ int alpha() const { return alpha_; }
+
+ // ui::AnimationDelegate overrides:
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
+
+ private:
+ BackgroundAnimatorDelegate* delegate_;
+
+ const int min_alpha_;
+ const int max_alpha_;
+
+ ui::SlideAnimation animation_;
+
+ // Whether the background is painted.
+ bool paints_background_;
+
+ // Current alpha value of the background.
+ int alpha_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackgroundAnimator);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SHELF_BACKGROUND_ANIMATOR_H_
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
diff --git a/ash/shelf/shelf_layout_manager.h b/ash/shelf/shelf_layout_manager.h
new file mode 100644
index 0000000..40e59a7
--- /dev/null
+++ b/ash/shelf/shelf_layout_manager.h
@@ -0,0 +1,332 @@
+// 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.
+
+#ifndef ASH_SHELF_SHELF_LAYOUT_MANAGER_H_
+#define ASH_SHELF_SHELF_LAYOUT_MANAGER_H_
+
+#include "ash/ash_export.h"
+#include "ash/launcher/launcher.h"
+#include "ash/shelf/background_animator.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shell_observer.h"
+#include "ash/system/status_area_widget.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/observer_list.h"
+#include "base/timer.h"
+#include "ui/aura/client/activation_change_observer.h"
+#include "ui/aura/layout_manager.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/rect.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ui {
+class GestureEvent;
+}
+
+namespace ash {
+class ScreenAsh;
+class ShelfWidget;
+namespace internal {
+
+class ShelfLayoutManagerTest;
+class StatusAreaWidget;
+class WorkspaceController;
+
+// ShelfLayoutManager is the layout manager responsible for the launcher and
+// status widgets. The launcher is given the total available width and told the
+// width of the status area. This allows the launcher to draw the background and
+// layout to the status area.
+// To respond to bounds changes in the status area StatusAreaLayoutManager works
+// closely with ShelfLayoutManager.
+class ASH_EXPORT ShelfLayoutManager :
+ public aura::LayoutManager,
+ public ash::ShellObserver,
+ public aura::client::ActivationChangeObserver {
+ public:
+ class ASH_EXPORT Observer {
+ public:
+ // Called when the target ShelfLayoutManager will be deleted.
+ virtual void WillDeleteShelf() {}
+
+ // Called when the visibility change is scheduled.
+ virtual void WillChangeVisibilityState(ShelfVisibilityState new_state) {}
+
+ // Called when the auto hide state is changed.
+ virtual void OnAutoHideStateChanged(ShelfAutoHideState new_state) {}
+ };
+
+ // We reserve a small area at the bottom of the workspace area to ensure that
+ // the bottom-of-window resize handle can be hit.
+ static const int kWorkspaceAreaBottomInset;
+
+ // Size of the shelf when auto-hidden.
+ static const int kAutoHideSize;
+
+ explicit ShelfLayoutManager(ShelfWidget* shelf);
+ virtual ~ShelfLayoutManager();
+
+ // Sets the ShelfAutoHideBehavior. See enum description for details.
+ void SetAutoHideBehavior(ShelfAutoHideBehavior behavior);
+ ShelfAutoHideBehavior auto_hide_behavior() const {
+ return auto_hide_behavior_;
+ }
+
+ // Sets the alignment. Returns true if the alignment is changed. Otherwise,
+ // returns false.
+ bool SetAlignment(ShelfAlignment alignment);
+ ShelfAlignment GetAlignment() const { return alignment_; }
+
+ void set_workspace_controller(WorkspaceController* controller) {
+ workspace_controller_ = controller;
+ }
+
+ bool in_layout() const { return in_layout_; }
+
+ // Returns whether the shelf and its contents (launcher, status) are visible
+ // on the screen.
+ bool IsVisible() const;
+
+ // Returns the ideal bounds of the shelf assuming it is visible.
+ gfx::Rect GetIdealBounds();
+
+ // Stops any animations and sets the bounds of the launcher and status
+ // widgets.
+ void LayoutShelf();
+
+ // Returns shelf visibility state based on current value of auto hide
+ // behavior setting.
+ ShelfVisibilityState CalculateShelfVisibility();
+
+ // Returns shelf visibility state based on current value of auto hide
+ // behavior setting.
+ ShelfVisibilityState CalculateShelfVisibilityWhileDragging();
+
+ // Updates the visibility state.
+ void UpdateVisibilityState();
+
+ // Invoked by the shelf/launcher when the auto-hide state may have changed.
+ void UpdateAutoHideState();
+
+ ShelfVisibilityState visibility_state() const {
+ return state_.visibility_state;
+ }
+ ShelfAutoHideState auto_hide_state() const { return state_.auto_hide_state; }
+
+ ShelfWidget* shelf_widget() { return shelf_; }
+
+ // Sets whether any windows overlap the shelf. If a window overlaps the shelf
+ // the shelf renders slightly differently.
+ void SetWindowOverlapsShelf(bool value);
+ bool window_overlaps_shelf() const { return window_overlaps_shelf_; }
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Gesture dragging related functions:
+ void StartGestureDrag(const ui::GestureEvent& gesture);
+ enum DragState {
+ DRAG_SHELF,
+ DRAG_TRAY
+ };
+ // Returns DRAG_SHELF if the gesture should continue to drag the entire shelf.
+ // Returns DRAG_TRAY if the gesture can start dragging the tray-bubble from
+ // this point on.
+ DragState UpdateGestureDrag(const ui::GestureEvent& gesture);
+ void CompleteGestureDrag(const ui::GestureEvent& gesture);
+ void CancelGestureDrag();
+
+ // Overridden from aura::LayoutManager:
+ virtual void OnWindowResized() OVERRIDE;
+ virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visible) OVERRIDE;
+ virtual void SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) OVERRIDE;
+
+ // Overridden from ash::ShellObserver:
+ virtual void OnLockStateChanged(bool locked) OVERRIDE;
+
+ // Overriden from aura::client::ActivationChangeObserver:
+ virtual void OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) OVERRIDE;
+
+ // TODO(harrym|oshima): These templates will be moved to
+ // new Shelf class.
+ // A helper function that provides a shortcut for choosing
+ // values specific to a shelf alignment.
+ template<typename T>
+ T SelectValueForShelfAlignment(T bottom, T left, T right, T top) const {
+ switch (alignment_) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ return bottom;
+ case SHELF_ALIGNMENT_LEFT:
+ return left;
+ case SHELF_ALIGNMENT_RIGHT:
+ return right;
+ case SHELF_ALIGNMENT_TOP:
+ return top;
+ }
+ NOTREACHED();
+ return right;
+ }
+
+ template<typename T>
+ T PrimaryAxisValue(T horizontal, T vertical) const {
+ return IsHorizontalAlignment() ? horizontal : vertical;
+ }
+
+ // Is the shelf's alignment horizontal?
+ bool IsHorizontalAlignment() const;
+
+ // Returns a ShelfLayoutManager on the display which has a launcher for
+ // given |window|. See RootWindowController::ForLauncher for more info.
+ static ShelfLayoutManager* ForLauncher(aura::Window* window);
+
+ private:
+ class AutoHideEventFilter;
+ class UpdateShelfObserver;
+ friend class ash::ScreenAsh;
+ friend class ShelfLayoutManagerTest;
+
+ struct TargetBounds {
+ TargetBounds();
+ ~TargetBounds();
+
+ float opacity;
+ gfx::Rect shelf_bounds_in_root;
+ gfx::Rect launcher_bounds_in_shelf;
+ gfx::Rect status_bounds_in_shelf;
+ gfx::Insets work_area_insets;
+ };
+
+ struct State {
+ State() : visibility_state(SHELF_VISIBLE),
+ auto_hide_state(SHELF_AUTO_HIDE_HIDDEN),
+ is_screen_locked(false) {}
+
+ // Returns true if the two states are considered equal. As
+ // |auto_hide_state| only matters if |visibility_state| is
+ // |SHELF_AUTO_HIDE|, Equals() ignores the |auto_hide_state| as
+ // appropriate.
+ bool Equals(const State& other) const {
+ return other.visibility_state == visibility_state &&
+ (visibility_state != SHELF_AUTO_HIDE ||
+ other.auto_hide_state == auto_hide_state) &&
+ other.is_screen_locked == is_screen_locked;
+ }
+
+ ShelfVisibilityState visibility_state;
+ ShelfAutoHideState auto_hide_state;
+ bool is_screen_locked;
+ };
+
+ // Sets the visibility of the shelf to |state|.
+ void SetState(ShelfVisibilityState visibility_state);
+
+ // Stops any animations.
+ void StopAnimating();
+
+ // Returns the width (if aligned to the side) or height (if aligned to the
+ // bottom).
+ void GetShelfSize(int* width, int* height);
+
+ // Insets |bounds| by |inset| on the edge the shelf is aligned to.
+ void AdjustBoundsBasedOnAlignment(int inset, gfx::Rect* bounds) const;
+
+ // Calculates the target bounds assuming visibility of |visible|.
+ void CalculateTargetBounds(const State& state, TargetBounds* target_bounds);
+
+ // Updates the target bounds if a gesture-drag is in progress. This is only
+ // used by |CalculateTargetBounds()|.
+ void UpdateTargetBoundsForGesture(TargetBounds* target_bounds) const;
+
+ // Updates the background of the shelf.
+ void UpdateShelfBackground(BackgroundAnimator::ChangeType type);
+
+ // Returns whether the launcher should draw a background.
+ bool GetLauncherPaintsBackground() const;
+
+ // Updates the auto hide state immediately.
+ void UpdateAutoHideStateNow();
+
+ // Returns the AutoHideState. This value is determined from the launcher and
+ // tray.
+ ShelfAutoHideState CalculateAutoHideState(
+ ShelfVisibilityState visibility_state) const;
+
+ // Updates the hit test bounds override for launcher and status area.
+ void UpdateHitTestBounds();
+
+ // Returns true if |window| is a descendant of the shelf.
+ bool IsShelfWindow(aura::Window* window);
+
+ int GetWorkAreaSize(const State& state, int size) const;
+
+ // The RootWindow is cached so that we don't invoke Shell::GetInstance() from
+ // our destructor. We avoid that as at the time we're deleted Shell is being
+ // deleted too.
+ aura::RootWindow* root_window_;
+
+ // True when inside LayoutShelf method. Used to prevent calling LayoutShelf
+ // again from SetChildBounds().
+ bool in_layout_;
+
+ // See description above setter.
+ ShelfAutoHideBehavior auto_hide_behavior_;
+
+ ShelfAlignment alignment_;
+
+ // Current state.
+ State state_;
+
+ ShelfWidget* shelf_;
+
+ WorkspaceController* workspace_controller_;
+
+ // Do any windows overlap the shelf? This is maintained by WorkspaceManager.
+ bool window_overlaps_shelf_;
+
+ base::OneShotTimer<ShelfLayoutManager> auto_hide_timer_;
+
+ // EventFilter used to detect when user moves the mouse over the launcher to
+ // trigger showing the launcher.
+ scoped_ptr<AutoHideEventFilter> event_filter_;
+
+ ObserverList<Observer> observers_;
+
+ // The shelf reacts to gesture-drags, and can be set to auto-hide for certain
+ // gestures. Some shelf behaviour (e.g. visibility state, background color
+ // etc.) are affected by various stages of the drag. The enum keeps track of
+ // the present status of the gesture drag.
+ enum GestureDragStatus {
+ GESTURE_DRAG_NONE,
+ GESTURE_DRAG_IN_PROGRESS,
+ GESTURE_DRAG_COMPLETE_IN_PROGRESS
+ };
+ GestureDragStatus gesture_drag_status_;
+
+ // Tracks the amount of the drag. The value is only valid when
+ // |gesture_drag_status_| is set to GESTURE_DRAG_IN_PROGRESS.
+ float gesture_drag_amount_;
+
+ // Manage the auto-hide state during the gesture.
+ ShelfAutoHideState gesture_drag_auto_hide_state_;
+
+ // Used to delay updating shelf background.
+ UpdateShelfObserver* update_shelf_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShelfLayoutManager);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SHELF_SHELF_LAYOUT_MANAGER_H_
diff --git a/ash/shelf/shelf_layout_manager_unittest.cc b/ash/shelf/shelf_layout_manager_unittest.cc
new file mode 100644
index 0000000..fe900a3
--- /dev/null
+++ b/ash/shelf/shelf_layout_manager_unittest.cc
@@ -0,0 +1,1108 @@
+// 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 "ash/accelerators/accelerator_controller.h"
+#include "ash/accelerators/accelerator_table.h"
+#include "ash/ash_switches.h"
+#include "ash/display/display_manager.h"
+#include "ash/focus_cycler.h"
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_view.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/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/window_util.h"
+#include "base/command_line.h"
+#include "base/utf_string_conversions.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/window.h"
+#include "ui/base/animation/animation_container_element.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+void StepWidgetLayerAnimatorToEnd(views::Widget* widget) {
+ ui::AnimationContainerElement* element =
+ static_cast<ui::AnimationContainerElement*>(
+ widget->GetNativeView()->layer()->GetAnimator());
+ element->Step(base::TimeTicks::Now() + base::TimeDelta::FromSeconds(1));
+}
+
+ShelfWidget* GetShelfWidget() {
+ return Shell::GetPrimaryRootWindowController()->shelf();
+}
+
+ShelfLayoutManager* GetShelfLayoutManager() {
+ return Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager();
+}
+
+SystemTray* GetSystemTray() {
+ return Shell::GetPrimaryRootWindowController()->GetSystemTray();
+}
+
+class ShelfLayoutObserverTest : public ShelfLayoutManager::Observer {
+ public:
+ ShelfLayoutObserverTest()
+ : changed_auto_hide_state_(false) {
+ }
+
+ virtual ~ShelfLayoutObserverTest() {}
+
+ bool changed_auto_hide_state() const { return changed_auto_hide_state_; }
+
+ private:
+ virtual void OnAutoHideStateChanged(
+ ShelfAutoHideState new_state) OVERRIDE {
+ changed_auto_hide_state_ = true;
+ }
+
+ bool changed_auto_hide_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShelfLayoutObserverTest);
+};
+
+// Trivial item implementation that tracks its views for testing.
+class TestItem : public SystemTrayItem {
+ public:
+ TestItem()
+ : SystemTrayItem(GetSystemTray()),
+ tray_view_(NULL),
+ default_view_(NULL),
+ detailed_view_(NULL),
+ notification_view_(NULL) {}
+
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE {
+ tray_view_ = new views::View;
+ // Add a label so it has non-zero width.
+ tray_view_->SetLayoutManager(new views::FillLayout);
+ tray_view_->AddChildView(new views::Label(UTF8ToUTF16("Tray")));
+ return tray_view_;
+ }
+
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE {
+ default_view_ = new views::View;
+ default_view_->SetLayoutManager(new views::FillLayout);
+ default_view_->AddChildView(new views::Label(UTF8ToUTF16("Default")));
+ return default_view_;
+ }
+
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE {
+ detailed_view_ = new views::View;
+ detailed_view_->SetLayoutManager(new views::FillLayout);
+ detailed_view_->AddChildView(new views::Label(UTF8ToUTF16("Detailed")));
+ return detailed_view_;
+ }
+
+ virtual views::View* CreateNotificationView(
+ user::LoginStatus status) OVERRIDE {
+ notification_view_ = new views::View;
+ return notification_view_;
+ }
+
+ virtual void DestroyTrayView() OVERRIDE {
+ tray_view_ = NULL;
+ }
+
+ virtual void DestroyDefaultView() OVERRIDE {
+ default_view_ = NULL;
+ }
+
+ virtual void DestroyDetailedView() OVERRIDE {
+ detailed_view_ = NULL;
+ }
+
+ virtual void DestroyNotificationView() OVERRIDE {
+ notification_view_ = NULL;
+ }
+
+ virtual void UpdateAfterLoginStatusChange(
+ user::LoginStatus status) OVERRIDE {}
+
+ views::View* tray_view() const { return tray_view_; }
+ views::View* default_view() const { return default_view_; }
+ views::View* detailed_view() const { return detailed_view_; }
+ views::View* notification_view() const { return notification_view_; }
+
+ private:
+ views::View* tray_view_;
+ views::View* default_view_;
+ views::View* detailed_view_;
+ views::View* notification_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestItem);
+};
+
+} // namespace
+
+class ShelfLayoutManagerTest : public ash::test::AshTestBase {
+ public:
+ ShelfLayoutManagerTest() {}
+
+ void SetState(ShelfLayoutManager* shelf,
+ ShelfVisibilityState state) {
+ shelf->SetState(state);
+ }
+
+ void UpdateAutoHideStateNow() {
+ GetShelfLayoutManager()->UpdateAutoHideStateNow();
+ }
+
+ aura::Window* CreateTestWindow() {
+ aura::Window* window = new aura::Window(NULL);
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ window->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window->Init(ui::LAYER_TEXTURED);
+ SetDefaultParentByPrimaryRootWindow(window);
+ return window;
+ }
+
+ views::Widget* CreateTestWidgetWithParams(
+ const views::Widget::InitParams& params) {
+ views::Widget* out = new views::Widget;
+ out->Init(params);
+ out->Show();
+ return out;
+ }
+
+ // Create a simple widget attached to the current context (will
+ // delete on TearDown).
+ views::Widget* CreateTestWidget() {
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ return CreateTestWidgetWithParams(params);
+ }
+
+ // Overridden from AshTestBase:
+ virtual void SetUp() OVERRIDE {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ ash::switches::kAshEnableTrayDragging);
+ test::AshTestBase::SetUp();
+ }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShelfLayoutManagerTest);
+};
+
+// Fails on Mac only. Need to be implemented. http://crbug.com/111279.
+#if defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_SetVisible DISABLED_SetVisible
+#else
+#define MAYBE_SetVisible SetVisible
+#endif
+// Makes sure SetVisible updates work area and widget appropriately.
+TEST_F(ShelfLayoutManagerTest, MAYBE_SetVisible) {
+ ShelfWidget* shelf = GetShelfWidget();
+ ShelfLayoutManager* manager = shelf->shelf_layout_manager();
+ // Force an initial layout.
+ manager->LayoutShelf();
+ EXPECT_EQ(SHELF_VISIBLE, manager->visibility_state());
+
+ gfx::Rect status_bounds(
+ shelf->status_area_widget()->GetWindowBoundsInScreen());
+ gfx::Rect launcher_bounds(
+ shelf->GetWindowBoundsInScreen());
+ int shelf_height = manager->GetIdealBounds().height();
+
+ const gfx::Display& display = Shell::GetInstance()->display_manager()->
+ GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ ASSERT_NE(-1, display.id());
+ // Bottom inset should be the max of widget heights.
+ EXPECT_EQ(shelf_height,
+ display.bounds().bottom() - display.work_area().bottom());
+
+ // Hide the shelf.
+ SetState(manager, SHELF_HIDDEN);
+ // Run the animation to completion.
+ StepWidgetLayerAnimatorToEnd(shelf);
+ StepWidgetLayerAnimatorToEnd(shelf->status_area_widget());
+ EXPECT_EQ(SHELF_HIDDEN, manager->visibility_state());
+ EXPECT_EQ(0,
+ display.bounds().bottom() - display.work_area().bottom());
+
+ // Make sure the bounds of the two widgets changed.
+ EXPECT_GE(shelf->GetNativeView()->bounds().y(),
+ Shell::GetScreen()->GetPrimaryDisplay().bounds().bottom());
+ EXPECT_GE(shelf->status_area_widget()->GetNativeView()->bounds().y(),
+ Shell::GetScreen()->GetPrimaryDisplay().bounds().bottom());
+
+ // And show it again.
+ SetState(manager, SHELF_VISIBLE);
+ // Run the animation to completion.
+ StepWidgetLayerAnimatorToEnd(shelf);
+ StepWidgetLayerAnimatorToEnd(shelf->status_area_widget());
+ EXPECT_EQ(SHELF_VISIBLE, manager->visibility_state());
+ EXPECT_EQ(shelf_height,
+ display.bounds().bottom() - display.work_area().bottom());
+
+ // Make sure the bounds of the two widgets changed.
+ launcher_bounds = shelf->GetNativeView()->bounds();
+ int bottom =
+ Shell::GetScreen()->GetPrimaryDisplay().bounds().bottom() - shelf_height;
+ EXPECT_EQ(launcher_bounds.y(),
+ bottom + (manager->GetIdealBounds().height() -
+ launcher_bounds.height()) / 2);
+ status_bounds = shelf->status_area_widget()->GetNativeView()->bounds();
+ EXPECT_EQ(status_bounds.y(),
+ bottom + shelf_height - status_bounds.height());
+}
+
+// Makes sure LayoutShelf invoked while animating cleans things up.
+TEST_F(ShelfLayoutManagerTest, LayoutShelfWhileAnimating) {
+ ShelfWidget* shelf = GetShelfWidget();
+ // Force an initial layout.
+ shelf->shelf_layout_manager()->LayoutShelf();
+ EXPECT_EQ(SHELF_VISIBLE, shelf->shelf_layout_manager()->visibility_state());
+
+ const gfx::Display& display = Shell::GetInstance()->display_manager()->
+ GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+
+ // Hide the shelf.
+ SetState(shelf->shelf_layout_manager(), SHELF_HIDDEN);
+ shelf->shelf_layout_manager()->LayoutShelf();
+ EXPECT_EQ(SHELF_HIDDEN, shelf->shelf_layout_manager()->visibility_state());
+ EXPECT_EQ(0, display.bounds().bottom() - display.work_area().bottom());
+
+ // Make sure the bounds of the two widgets changed.
+ EXPECT_GE(shelf->GetNativeView()->bounds().y(),
+ Shell::GetScreen()->GetPrimaryDisplay().bounds().bottom());
+ EXPECT_GE(shelf->status_area_widget()->GetNativeView()->bounds().y(),
+ Shell::GetScreen()->GetPrimaryDisplay().bounds().bottom());
+}
+
+// Makes sure the launcher is sized when the status area changes size.
+TEST_F(ShelfLayoutManagerTest, LauncherUpdatedWhenStatusAreaChangesSize) {
+ Launcher* launcher = Launcher::ForPrimaryDisplay();
+ ASSERT_TRUE(launcher);
+ ShelfWidget* shelf_widget = GetShelfWidget();
+ ASSERT_TRUE(shelf_widget);
+ ASSERT_TRUE(shelf_widget->status_area_widget());
+ shelf_widget->status_area_widget()->SetBounds(
+ gfx::Rect(0, 0, 200, 200));
+ EXPECT_EQ(200, shelf_widget->GetContentsView()->width() -
+ launcher->GetLauncherViewForTest()->width());
+}
+
+
+#if defined(OS_WIN)
+// RootWindow and Display can't resize on Windows Ash. http://crbug.com/165962
+#define MAYBE_AutoHide DISABLED_AutoHide
+#else
+#define MAYBE_AutoHide AutoHide
+#endif
+
+// Various assertions around auto-hide.
+TEST_F(ShelfLayoutManagerTest, MAYBE_AutoHide) {
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ aura::test::EventGenerator generator(root, root);
+ generator.MoveMouseTo(0, 0);
+
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->SetAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ // Widget is now owned by the parent window.
+ widget->Init(params);
+ widget->Maximize();
+ widget->Show();
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // LayoutShelf() forces the animation to completion, at which point the
+ // launcher should go off the screen.
+ shelf->LayoutShelf();
+ EXPECT_EQ(root->bounds().bottom() - ShelfLayoutManager::kAutoHideSize,
+ GetShelfWidget()->GetWindowBoundsInScreen().y());
+ EXPECT_EQ(root->bounds().bottom() - ShelfLayoutManager::kAutoHideSize,
+ Shell::GetScreen()->GetDisplayNearestWindow(
+ root).work_area().bottom());
+
+ // Move the mouse to the bottom of the screen.
+ generator.MoveMouseTo(0, root->bounds().bottom() - 1);
+
+ // Shelf should be shown again (but it shouldn't have changed the work area).
+ SetState(shelf, SHELF_AUTO_HIDE);
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ shelf->LayoutShelf();
+ EXPECT_EQ(root->bounds().bottom() - shelf->GetIdealBounds().height(),
+ GetShelfWidget()->GetWindowBoundsInScreen().y());
+ EXPECT_EQ(root->bounds().bottom() - ShelfLayoutManager::kAutoHideSize,
+ Shell::GetScreen()->GetDisplayNearestWindow(
+ root).work_area().bottom());
+
+ // Move mouse back up.
+ generator.MoveMouseTo(0, 0);
+ SetState(shelf, SHELF_AUTO_HIDE);
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ shelf->LayoutShelf();
+ EXPECT_EQ(root->bounds().bottom() - ShelfLayoutManager::kAutoHideSize,
+ GetShelfWidget()->GetWindowBoundsInScreen().y());
+
+ // Drag mouse to bottom of screen.
+ generator.PressLeftButton();
+ generator.MoveMouseTo(0, root->bounds().bottom() - 1);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ generator.ReleaseLeftButton();
+ generator.MoveMouseTo(1, root->bounds().bottom() - 1);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ generator.PressLeftButton();
+ generator.MoveMouseTo(1, root->bounds().bottom() - 1);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+}
+
+// Assertions around the lock screen showing.
+TEST_F(ShelfLayoutManagerTest, VisibleWhenLockScreenShowing) {
+ // Since ShelfLayoutManager queries for mouse location, move the mouse so
+ // it isn't over the shelf.
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(), gfx::Point());
+ generator.MoveMouseTo(0, 0);
+
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->SetAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ // Widget is now owned by the parent window.
+ widget->Init(params);
+ widget->Maximize();
+ widget->Show();
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ // LayoutShelf() forces the animation to completion, at which point the
+ // launcher should go off the screen.
+ shelf->LayoutShelf();
+ EXPECT_EQ(root->bounds().bottom() - ShelfLayoutManager::kAutoHideSize,
+ GetShelfWidget()->GetWindowBoundsInScreen().y());
+
+ aura::Window* lock_container = Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ internal::kShellWindowId_LockScreenContainer);
+
+ views::Widget* lock_widget = new views::Widget;
+ views::Widget::InitParams lock_params(
+ views::Widget::InitParams::TYPE_WINDOW);
+ lock_params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ lock_params.parent = lock_container;
+ // Widget is now owned by the parent window.
+ lock_widget->Init(lock_params);
+ lock_widget->Maximize();
+ lock_widget->Show();
+
+ // Lock the screen.
+ Shell::GetInstance()->delegate()->LockScreen();
+ shelf->UpdateVisibilityState();
+ // Showing a widget in the lock screen should force the shelf to be visibile.
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+
+ Shell::GetInstance()->delegate()->UnlockScreen();
+ shelf->UpdateVisibilityState();
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+}
+
+// Assertions around SetAutoHideBehavior.
+TEST_F(ShelfLayoutManagerTest, SetAutoHideBehavior) {
+ // Since ShelfLayoutManager queries for mouse location, move the mouse so
+ // it isn't over the shelf.
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(), gfx::Point());
+ generator.MoveMouseTo(0, 0);
+
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ // Widget is now owned by the parent window.
+ widget->Init(params);
+ widget->Show();
+ aura::Window* window = widget->GetNativeWindow();
+ gfx::Rect display_bounds(
+ Shell::GetScreen()->GetDisplayNearestWindow(window).bounds());
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+
+ widget->Maximize();
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ EXPECT_EQ(Shell::GetScreen()->GetDisplayNearestWindow(
+ window).work_area().bottom(),
+ widget->GetWorkAreaBoundsInScreen().bottom());
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(Shell::GetScreen()->GetDisplayNearestWindow(
+ window).work_area().bottom(),
+ widget->GetWorkAreaBoundsInScreen().bottom());
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ EXPECT_EQ(Shell::GetScreen()->GetDisplayNearestWindow(
+ window).work_area().bottom(),
+ widget->GetWorkAreaBoundsInScreen().bottom());
+}
+
+// Verifies the shelf is visible when status/launcher is focused.
+TEST_F(ShelfLayoutManagerTest, VisibleWhenStatusOrLauncherFocused) {
+ // Since ShelfLayoutManager queries for mouse location, move the mouse so
+ // it isn't over the shelf.
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(), gfx::Point());
+ generator.MoveMouseTo(0, 0);
+
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ // Widget is now owned by the parent window.
+ widget->Init(params);
+ widget->Show();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Focus the launcher. Have to go through the focus cycler as normal focus
+ // requests to it do nothing.
+ GetShelfWidget()->GetFocusCycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ widget->Activate();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Trying to activate the status should fail, since we only allow activating
+ // it when the user is using the keyboard (i.e. through FocusCycler).
+ GetShelfWidget()->status_area_widget()->Activate();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ GetShelfWidget()->GetFocusCycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+}
+
+// Makes sure shelf will be visible when app list opens as shelf is in
+// SHELF_VISIBLE state,and toggling app list won't change shelf
+// visibility state.
+TEST_F(ShelfLayoutManagerTest, OpenAppListWithShelfVisibleState) {
+ Shell* shell = Shell::GetInstance();
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->LayoutShelf();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+
+ // Create a normal unmaximized windowm shelf should be visible.
+ aura::Window* window = CreateTestWindow();
+ window->SetBounds(gfx::Rect(0, 0, 100, 100));
+ window->Show();
+ EXPECT_FALSE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+
+ // Toggle app list to show, and the shelf stays visible.
+ shell->ToggleAppList(NULL);
+ EXPECT_TRUE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+
+ // Toggle app list to hide, and the shelf stays visible.
+ shell->ToggleAppList(NULL);
+ EXPECT_FALSE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+}
+
+// Makes sure shelf will be shown with SHELF_AUTO_HIDE_SHOWN state
+// when app list opens as shelf is in SHELF_AUTO_HIDE state, and
+// toggling app list won't change shelf visibility state.
+TEST_F(ShelfLayoutManagerTest, OpenAppListWithShelfAutoHideState) {
+ Shell* shell = Shell::GetInstance();
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->LayoutShelf();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ // Create a window and show it in maximized state.
+ aura::Window* window = CreateTestWindow();
+ window->SetBounds(gfx::Rect(0, 0, 100, 100));
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ window->Show();
+ wm::ActivateWindow(window);
+
+ EXPECT_FALSE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+
+ // Toggle app list to show.
+ shell->ToggleAppList(NULL);
+ // The shelf's auto hide state won't be changed until the timer fires, so
+ // calling shell->UpdateShelfVisibility() is kind of manually helping it to
+ // update the state.
+ shell->UpdateShelfVisibility();
+ EXPECT_TRUE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Toggle app list to hide.
+ shell->ToggleAppList(NULL);
+ EXPECT_FALSE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+}
+
+// Makes sure shelf will be hidden when app list opens as shelf is in HIDDEN
+// state, and toggling app list won't change shelf visibility state.
+TEST_F(ShelfLayoutManagerTest, OpenAppListWithShelfHiddenState) {
+ Shell* shell = Shell::GetInstance();
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ // For shelf to be visible, app list is not open in initial state.
+ shelf->LayoutShelf();
+
+ // Create a window and make it full screen.
+ aura::Window* window = CreateTestWindow();
+ window->SetBounds(gfx::Rect(0, 0, 100, 100));
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ window->Show();
+ wm::ActivateWindow(window);
+
+ // App list and shelf is not shown.
+ EXPECT_FALSE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state());
+
+ // Toggle app list to show.
+ shell->ToggleAppList(NULL);
+ EXPECT_TRUE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state());
+
+ // Toggle app list to hide.
+ shell->ToggleAppList(NULL);
+ EXPECT_FALSE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state());
+}
+
+#if defined(OS_WIN)
+// RootWindow and Display can't resize on Windows Ash. http://crbug.com/165962
+#define MAYBE_SetAlignment DISABLED_SetAlignment
+#else
+#define MAYBE_SetAlignment SetAlignment
+#endif
+
+// Tests SHELF_ALIGNMENT_(LEFT, RIGHT, TOP).
+TEST_F(ShelfLayoutManagerTest, MAYBE_SetAlignment) {
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ // Force an initial layout.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ shelf->LayoutShelf();
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+
+ shelf->SetAlignment(SHELF_ALIGNMENT_LEFT);
+ gfx::Rect launcher_bounds(
+ GetShelfWidget()->GetWindowBoundsInScreen());
+ const internal::DisplayManager* manager =
+ Shell::GetInstance()->display_manager();
+ gfx::Display display =
+ manager->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ ASSERT_NE(-1, display.id());
+ EXPECT_EQ(shelf->GetIdealBounds().width(),
+ display.GetWorkAreaInsets().left());
+ EXPECT_GE(
+ launcher_bounds.width(),
+ GetShelfWidget()->GetContentsView()->GetPreferredSize().width());
+ EXPECT_EQ(SHELF_ALIGNMENT_LEFT, GetSystemTray()->shelf_alignment());
+ StatusAreaWidget* status_area_widget = GetShelfWidget()->status_area_widget();
+ gfx::Rect status_bounds(status_area_widget->GetWindowBoundsInScreen());
+ EXPECT_GE(status_bounds.width(),
+ status_area_widget->GetContentsView()->GetPreferredSize().width());
+ EXPECT_EQ(shelf->GetIdealBounds().width(),
+ display.GetWorkAreaInsets().left());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().top());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().bottom());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().right());
+ EXPECT_EQ(display.bounds().x(), launcher_bounds.x());
+ EXPECT_EQ(display.bounds().y(), launcher_bounds.y());
+ EXPECT_EQ(display.bounds().height(), launcher_bounds.height());
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ display = manager->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ EXPECT_EQ(ShelfLayoutManager::kAutoHideSize,
+ display.GetWorkAreaInsets().left());
+ EXPECT_EQ(ShelfLayoutManager::kAutoHideSize, display.work_area().x());
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ shelf->SetAlignment(SHELF_ALIGNMENT_RIGHT);
+ display = manager->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ launcher_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
+ display = manager->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ ASSERT_NE(-1, display.id());
+ EXPECT_EQ(shelf->GetIdealBounds().width(),
+ display.GetWorkAreaInsets().right());
+ EXPECT_GE(launcher_bounds.width(),
+ GetShelfWidget()->GetContentsView()->GetPreferredSize().width());
+ EXPECT_EQ(SHELF_ALIGNMENT_RIGHT, GetSystemTray()->shelf_alignment());
+ status_bounds = gfx::Rect(status_area_widget->GetWindowBoundsInScreen());
+ EXPECT_GE(status_bounds.width(),
+ status_area_widget->GetContentsView()->GetPreferredSize().width());
+ EXPECT_EQ(shelf->GetIdealBounds().width(),
+ display.GetWorkAreaInsets().right());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().top());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().bottom());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().left());
+ EXPECT_EQ(display.work_area().right(), launcher_bounds.x());
+ EXPECT_EQ(display.bounds().y(), launcher_bounds.y());
+ EXPECT_EQ(display.bounds().height(), launcher_bounds.height());
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ display = manager->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ EXPECT_EQ(ShelfLayoutManager::kAutoHideSize,
+ display.GetWorkAreaInsets().right());
+ EXPECT_EQ(ShelfLayoutManager::kAutoHideSize,
+ display.bounds().right() - display.work_area().right());
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ shelf->SetAlignment(SHELF_ALIGNMENT_TOP);
+ display = manager->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ launcher_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
+ display = manager->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ ASSERT_NE(-1, display.id());
+ EXPECT_EQ(shelf->GetIdealBounds().height(),
+ display.GetWorkAreaInsets().top());
+ EXPECT_GE(launcher_bounds.height(),
+ GetShelfWidget()->GetContentsView()->GetPreferredSize().height());
+ EXPECT_EQ(SHELF_ALIGNMENT_TOP, GetSystemTray()->shelf_alignment());
+ status_bounds = gfx::Rect(status_area_widget->GetWindowBoundsInScreen());
+ EXPECT_GE(status_bounds.height(),
+ status_area_widget->GetContentsView()->GetPreferredSize().height());
+ EXPECT_EQ(shelf->GetIdealBounds().height(),
+ display.GetWorkAreaInsets().top());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().right());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().bottom());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().left());
+ EXPECT_EQ(display.work_area().y(), launcher_bounds.bottom());
+ EXPECT_EQ(display.bounds().x(), launcher_bounds.x());
+ EXPECT_EQ(display.bounds().width(), launcher_bounds.width());
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ display = manager->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ EXPECT_EQ(ShelfLayoutManager::kAutoHideSize,
+ display.GetWorkAreaInsets().top());
+ EXPECT_EQ(ShelfLayoutManager::kAutoHideSize,
+ display.work_area().y() - display.bounds().y());
+}
+
+#if defined(OS_WIN)
+// RootWindow and Display can't resize on Windows Ash. http://crbug.com/165962
+#define MAYBE_GestureDrag DISABLED_GestureDrag
+#else
+#define MAYBE_GestureDrag GestureDrag
+#endif
+
+TEST_F(ShelfLayoutManagerTest, MAYBE_GestureDrag) {
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ shelf->LayoutShelf();
+
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ widget->Init(params);
+ widget->Show();
+ widget->Maximize();
+
+ aura::Window* window = widget->GetNativeWindow();
+
+ gfx::Rect shelf_shown = GetShelfWidget()->GetWindowBoundsInScreen();
+ gfx::Rect bounds_shelf = window->bounds();
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+
+ // Swipe up on the shelf. This should not change any state.
+ gfx::Point start = GetShelfWidget()->GetWindowBoundsInScreen().CenterPoint();
+ gfx::Point end(start.x(), start.y() + 100);
+
+ // Swipe down on the shelf to hide it.
+ end.set_y(start.y() + 100);
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+ EXPECT_NE(bounds_shelf.ToString(), window->bounds().ToString());
+ EXPECT_NE(shelf_shown.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ gfx::Rect bounds_noshelf = window->bounds();
+ gfx::Rect shelf_hidden = GetShelfWidget()->GetWindowBoundsInScreen();
+
+ // Swipe up to show the shelf.
+ generator.GestureScrollSequence(end, start,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_NEVER, shelf->auto_hide_behavior());
+ EXPECT_EQ(bounds_shelf.ToString(), window->bounds().ToString());
+ EXPECT_EQ(shelf_shown.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe up again. The shelf should hide.
+ end.set_y(start.y() - 100);
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+ EXPECT_EQ(shelf_hidden.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe up yet again to show it.
+ end.set_y(start.y() + 100);
+ generator.GestureScrollSequence(end, start,
+ base::TimeDelta::FromMilliseconds(10), 1);
+
+ // Swipe down very little. It shouldn't change any state.
+ end.set_y(start.y() + shelf_shown.height() * 3 / 10);
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(100), 1);
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_NEVER, shelf->auto_hide_behavior());
+ EXPECT_EQ(bounds_shelf.ToString(), window->bounds().ToString());
+ EXPECT_EQ(shelf_shown.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe down again to hide.
+ end.set_y(start.y() + 100);
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+ EXPECT_EQ(bounds_noshelf.ToString(), window->bounds().ToString());
+ EXPECT_EQ(shelf_hidden.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe up yet again to show it.
+ end.set_y(start.y() + 100);
+ generator.GestureScrollSequence(end, start,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_NEVER, shelf->auto_hide_behavior());
+
+ // Tap on the shelf itself. This should not change anything.
+ generator.GestureTapAt(start);
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_NEVER, shelf->auto_hide_behavior());
+
+ // Now, tap on the desktop region (above the shelf). This should hide the
+ // shelf.
+ gfx::Point tap = start + gfx::Vector2d(0, -90);
+ generator.GestureTapAt(tap);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+
+ // Make the window fullscreen.
+ widget->SetFullscreen(true);
+ gfx::Rect bounds_fullscreen = window->bounds();
+ EXPECT_TRUE(widget->IsFullscreen());
+ EXPECT_NE(bounds_noshelf.ToString(), bounds_fullscreen.ToString());
+ EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state());
+
+ // Swipe-up. This should not change anything.
+ generator.GestureScrollSequence(end, start,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+ EXPECT_EQ(bounds_fullscreen.ToString(), window->bounds().ToString());
+}
+
+TEST_F(ShelfLayoutManagerTest, WindowVisibilityDisablesAutoHide) {
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->LayoutShelf();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ // Create a visible window so auto-hide behavior is enforced
+ views::Widget* dummy = CreateTestWidget();
+
+ // Window visible => auto hide behaves normally.
+ shelf->UpdateVisibilityState();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Window minimized => auto hide disabled.
+ dummy->Minimize();
+ shelf->UpdateVisibilityState();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Window closed => auto hide disabled.
+ dummy->CloseNow();
+ shelf->UpdateVisibilityState();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Multiple window test
+ views::Widget* window1 = CreateTestWidget();
+ views::Widget* window2 = CreateTestWidget();
+
+ // both visible => normal autohide
+ shelf->UpdateVisibilityState();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // either minimzed => normal autohide
+ window2->Minimize();
+ shelf->UpdateVisibilityState();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ window2->Restore();
+ window1->Minimize();
+ shelf->UpdateVisibilityState();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // both minimzed => disable auto hide
+ window2->Minimize();
+ shelf->UpdateVisibilityState();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+}
+
+#if defined(OS_WIN)
+// RootWindow and Display can't resize on Windows Ash. http://crbug.com/165962
+#define MAYBE_GestureRevealsTrayBubble DISABLED_GestureRevealsTrayBubble
+#else
+#define MAYBE_GestureRevealsTrayBubble GestureRevealsTrayBubble
+#endif
+
+TEST_F(ShelfLayoutManagerTest, MAYBE_GestureRevealsTrayBubble) {
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->LayoutShelf();
+
+ // Create a visible window so auto-hide behavior is enforced.
+ CreateTestWidget();
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ SystemTray* tray = GetSystemTray();
+
+ // First, make sure the shelf is visible.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ EXPECT_FALSE(tray->HasSystemBubble());
+
+ // Now, drag up on the tray to show the bubble.
+ gfx::Point start = GetShelfWidget()->status_area_widget()->
+ GetWindowBoundsInScreen().CenterPoint();
+ gfx::Point end(start.x(), start.y() - 100);
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_TRUE(tray->HasSystemBubble());
+ tray->CloseBubbleForTest();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(tray->HasSystemBubble());
+
+ // Drag again, but only a small amount, and slowly. The bubble should not be
+ // visible.
+ end.set_y(start.y() - 30);
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(500), 100);
+ EXPECT_FALSE(tray->HasSystemBubble());
+
+ // Now, hide the shelf.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ // Start a drag from the bezel, and drag up to show both the shelf and the
+ // tray bubble.
+ start.set_y(start.y() + 100);
+ end.set_y(start.y() - 400);
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ EXPECT_TRUE(tray->HasSystemBubble());
+}
+
+TEST_F(ShelfLayoutManagerTest, ShelfFlickerOnTrayActivation) {
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+
+ // Create a visible window so auto-hide behavior is enforced.
+ CreateTestWidget();
+
+ // Turn on auto-hide for the shelf.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Show the status menu. That should make the shelf visible again.
+ Shell::GetInstance()->accelerator_controller()->PerformAction(
+ SHOW_SYSTEM_TRAY_BUBBLE, ui::Accelerator());
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ EXPECT_TRUE(GetSystemTray()->HasSystemBubble());
+
+ // Now activate the tray (using the keyboard, instead of using the mouse to
+ // make sure the mouse does not alter the auto-hide state in the shelf).
+ // This should not trigger any auto-hide state change in the shelf.
+ ShelfLayoutObserverTest observer;
+ shelf->AddObserver(&observer);
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ generator.PressKey(ui::VKEY_SPACE, 0);
+ generator.ReleaseKey(ui::VKEY_SPACE, 0);
+ EXPECT_TRUE(GetSystemTray()->HasSystemBubble());
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ EXPECT_FALSE(observer.changed_auto_hide_state());
+
+ shelf->RemoveObserver(&observer);
+}
+
+TEST_F(ShelfLayoutManagerTest, WorkAreaChangeWorkspace) {
+ // Make sure the shelf is always visible.
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ shelf->LayoutShelf();
+
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ views::Widget* widget_one = CreateTestWidgetWithParams(params);
+ widget_one->Maximize();
+
+ views::Widget* widget_two = CreateTestWidgetWithParams(params);
+ widget_two->Maximize();
+ widget_two->Activate();
+
+ // Both windows are maximized. They should be of the same size.
+ EXPECT_EQ(widget_one->GetNativeWindow()->bounds().ToString(),
+ widget_two->GetNativeWindow()->bounds().ToString());
+
+ // Now hide the shelf.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ // The active maximized window will get resized to the new work area. However,
+ // the inactive window should not get resized.
+ EXPECT_NE(widget_one->GetNativeWindow()->bounds().ToString(),
+ widget_two->GetNativeWindow()->bounds().ToString());
+
+ // Activate the first window. Now, both windows should be of the same size
+ // again.
+ widget_one->Activate();
+ EXPECT_EQ(widget_one->GetNativeWindow()->bounds().ToString(),
+ widget_two->GetNativeWindow()->bounds().ToString());
+
+ // Now show the shelf.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+
+ // The active maximized window will get resized to the new work area. However,
+ // the inactive window should not get resized.
+ EXPECT_NE(widget_one->GetNativeWindow()->bounds().ToString(),
+ widget_two->GetNativeWindow()->bounds().ToString());
+
+ // Activate the first window. Now, both windows should be of the same size
+ // again.
+ widget_two->Activate();
+ EXPECT_EQ(widget_one->GetNativeWindow()->bounds().ToString(),
+ widget_two->GetNativeWindow()->bounds().ToString());
+}
+
+// Confirm that the shelf is dimmed only when content is maximized and
+// shelf is not autohidden.
+TEST_F(ShelfLayoutManagerTest, Dimming) {
+ GetShelfLayoutManager()->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ scoped_ptr<aura::Window> w1(CreateTestWindow());
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+
+ // Normal window doesn't dim shelf.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ ShelfWidget* shelf = GetShelfWidget();
+ EXPECT_FALSE(shelf->GetDimsShelf());
+
+ // Maximized window does.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_TRUE(shelf->GetDimsShelf());
+
+ // Change back to normal stops dimming.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ EXPECT_FALSE(shelf->GetDimsShelf());
+
+ // Changing back to maximized dims again.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_TRUE(shelf->GetDimsShelf());
+
+ // Changing shelf to autohide stops dimming.
+ GetShelfLayoutManager()->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_FALSE(shelf->GetDimsShelf());
+}
+
+// Make sure that the shelf will not hide if the mouse is between a bubble and
+// the shelf.
+TEST_F(ShelfLayoutManagerTest, BubbleEnlargesShelfMouseHitArea) {
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ StatusAreaWidget* status_area_widget =
+ Shell::GetPrimaryRootWindowController()->shelf()->status_area_widget();
+ SystemTray* tray = GetSystemTray();
+
+ // Create a visible window so auto-hide behavior is enforced.
+ CreateTestWidget();
+
+ shelf->LayoutShelf();
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+
+ // Make two iterations - first without a message bubble which should make
+ // the shelf disappear and then with a message bubble which should keep it
+ // visible.
+ for (int i = 0; i < 2; i++) {
+ // Make sure the shelf is visible and position the mouse over it. Then
+ // allow auto hide.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ EXPECT_FALSE(status_area_widget->IsMessageBubbleShown());
+ gfx::Point center =
+ status_area_widget->GetWindowBoundsInScreen().CenterPoint();
+ generator.MoveMouseTo(center.x(), center.y());
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_TRUE(shelf->IsVisible());
+ if (!i) {
+ // In our first iteration we make sure there is no bubble.
+ tray->CloseBubbleForTest();
+ EXPECT_FALSE(status_area_widget->IsMessageBubbleShown());
+ } else {
+ // In our second iteration we show a bubble.
+ TestItem *item = new TestItem;
+ tray->AddTrayItem(item);
+ tray->ShowNotificationView(item);
+ EXPECT_TRUE(status_area_widget->IsMessageBubbleShown());
+ }
+ // Move the pointer over the edge of the shelf.
+ generator.MoveMouseTo(
+ center.x(), status_area_widget->GetWindowBoundsInScreen().y() - 8);
+ shelf->UpdateVisibilityState();
+ if (i) {
+ EXPECT_TRUE(shelf->IsVisible());
+ EXPECT_TRUE(status_area_widget->IsMessageBubbleShown());
+ } else {
+ EXPECT_FALSE(shelf->IsVisible());
+ EXPECT_FALSE(status_area_widget->IsMessageBubbleShown());
+ }
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/ash/shelf/shelf_types.h b/ash/shelf/shelf_types.h
new file mode 100644
index 0000000..82cf630
--- /dev/null
+++ b/ash/shelf/shelf_types.h
@@ -0,0 +1,46 @@
+// 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.
+
+#ifndef ASH_SHELF_SHELF_TYPES_H_
+#define ASH_SHELF_SHELF_TYPES_H_
+
+namespace ash {
+
+enum ShelfAlignment {
+ SHELF_ALIGNMENT_BOTTOM,
+ SHELF_ALIGNMENT_LEFT,
+ SHELF_ALIGNMENT_RIGHT,
+ SHELF_ALIGNMENT_TOP,
+};
+
+enum ShelfAutoHideBehavior {
+ // Always auto-hide.
+ SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS,
+
+ // Never auto-hide.
+ SHELF_AUTO_HIDE_BEHAVIOR_NEVER,
+
+ // Always hide.
+ SHELF_AUTO_HIDE_ALWAYS_HIDDEN,
+};
+
+enum ShelfVisibilityState {
+ // Always visible.
+ SHELF_VISIBLE,
+
+ // A couple of pixels are reserved at the bottom for the shelf.
+ SHELF_AUTO_HIDE,
+
+ // Nothing is shown. Used for fullscreen windows.
+ SHELF_HIDDEN,
+};
+
+enum ShelfAutoHideState {
+ SHELF_AUTO_HIDE_SHOWN,
+ SHELF_AUTO_HIDE_HIDDEN,
+};
+
+} // namespace ash
+
+#endif // ASH_SHELF_SHELF_TYPES_H_
diff --git a/ash/shelf/shelf_widget.cc b/ash/shelf/shelf_widget.cc
new file mode 100644
index 0000000..74dfb5c
--- /dev/null
+++ b/ash/shelf/shelf_widget.cc
@@ -0,0 +1,312 @@
+// 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_widget.h"
+
+#include "ash/focus_cycler.h"
+#include "ash/launcher/launcher_delegate.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/launcher/launcher_navigator.h"
+#include "ash/launcher/launcher_view.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/status_area_layout_manager.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/workspace_controller.h"
+#include "grit/ash_resources.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/skbitmap_operations.h"
+#include "ui/views/accessible_pane_view.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace {
+// Size of black border at bottom (or side) of launcher.
+const int kNumBlackPixels = 3;
+// Alpha to paint dimming image with.
+const int kDimAlpha = 96;
+}
+
+namespace ash {
+
+// The contents view of the Shelf. This view contains LauncherView and
+// sizes it to the width of the shelf minus the size of the status area.
+class ShelfWidget::DelegateView : public views::WidgetDelegate,
+ public views::AccessiblePaneView,
+ public internal::BackgroundAnimatorDelegate {
+ public:
+ explicit DelegateView(ShelfWidget* shelf);
+ virtual ~DelegateView();
+
+ void set_focus_cycler(internal::FocusCycler* focus_cycler) {
+ focus_cycler_ = focus_cycler;
+ }
+ internal::FocusCycler* focus_cycler() {
+ return focus_cycler_;
+ }
+
+ // Set if the shelf area is dimmed (eg when a window is maximized).
+ void SetDimmed(bool dimmed) {
+ if (dimmed_ != dimmed) {
+ dimmed_ = dimmed;
+ SchedulePaint();
+ }
+ }
+ bool GetDimmed() const { return dimmed_; }
+
+ // views::View overrides:
+ void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE;
+
+ // views::WidgetDelegateView overrides:
+ views::Widget* GetWidget() OVERRIDE {
+ return View::GetWidget();
+ }
+ const views::Widget* GetWidget() const OVERRIDE {
+ return View::GetWidget();
+ }
+
+ bool CanActivate() const OVERRIDE;
+ void Layout() OVERRIDE;
+
+ // BackgroundAnimatorDelegate overrides:
+ void UpdateBackground(int alpha) OVERRIDE;
+
+ private:
+ ShelfWidget* shelf_;
+ internal::FocusCycler* focus_cycler_;
+ int alpha_;
+ bool dimmed_;
+
+ DISALLOW_COPY_AND_ASSIGN(DelegateView);
+};
+
+ShelfWidget::DelegateView::DelegateView(ShelfWidget* shelf)
+ : shelf_(shelf),
+ focus_cycler_(NULL),
+ alpha_(0),
+ dimmed_(false) {
+}
+
+ShelfWidget::DelegateView::~DelegateView() {
+}
+
+void ShelfWidget::DelegateView::OnPaintBackground(gfx::Canvas* canvas) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ gfx::ImageSkia launcher_background =
+ *rb.GetImageSkiaNamed(IDR_AURA_LAUNCHER_BACKGROUND);
+ if (SHELF_ALIGNMENT_BOTTOM != shelf_->GetAlignment())
+ launcher_background = gfx::ImageSkiaOperations::CreateRotatedImage(
+ launcher_background,
+ shelf_->shelf_layout_manager()->SelectValueForShelfAlignment(
+ SkBitmapOperations::ROTATION_90_CW,
+ SkBitmapOperations::ROTATION_90_CW,
+ SkBitmapOperations::ROTATION_270_CW,
+ SkBitmapOperations::ROTATION_180_CW));
+
+ gfx::Rect black_rect =
+ shelf_->shelf_layout_manager()->SelectValueForShelfAlignment(
+ gfx::Rect(0, height() - kNumBlackPixels, width(), kNumBlackPixels),
+ gfx::Rect(0, 0, kNumBlackPixels, height()),
+ gfx::Rect(width() - kNumBlackPixels, 0, kNumBlackPixels, height()),
+ gfx::Rect(0, 0, width(), kNumBlackPixels));
+
+ SkPaint paint;
+ paint.setAlpha(alpha_);
+ canvas->DrawImageInt(
+ launcher_background,
+ 0, 0, launcher_background.width(), launcher_background.height(),
+ 0, 0, width(), height(),
+ false,
+ paint);
+ canvas->FillRect(black_rect, SK_ColorBLACK);
+
+ if (dimmed_) {
+ gfx::ImageSkia background_image =
+ *rb.GetImageSkiaNamed(IDR_AURA_LAUNCHER_DIMMING);
+ if (SHELF_ALIGNMENT_BOTTOM != shelf_->GetAlignment())
+ background_image = gfx::ImageSkiaOperations::CreateRotatedImage(
+ background_image,
+ shelf_->shelf_layout_manager()->SelectValueForShelfAlignment(
+ SkBitmapOperations::ROTATION_90_CW,
+ SkBitmapOperations::ROTATION_90_CW,
+ SkBitmapOperations::ROTATION_270_CW,
+ SkBitmapOperations::ROTATION_180_CW));
+
+ SkPaint paint;
+ paint.setAlpha(kDimAlpha);
+ canvas->DrawImageInt(
+ background_image,
+ 0, 0, background_image.width(), background_image.height(),
+ 0, 0, width(), height(),
+ false,
+ paint);
+ }
+}
+
+bool ShelfWidget::DelegateView::CanActivate() const {
+ // Allow to activate as fallback.
+ if (shelf_->activating_as_fallback_)
+ return true;
+ // Allow to activate from the focus cycler.
+ if (focus_cycler_ && focus_cycler_->widget_activating() == GetWidget())
+ return true;
+ // Disallow activating in other cases, especially when using mouse.
+ return false;
+}
+
+void ShelfWidget::DelegateView::Layout() {
+ for(int i = 0; i < child_count(); ++i) {
+ if (shelf_->shelf_layout_manager()->IsHorizontalAlignment()) {
+ child_at(i)->SetBounds(child_at(i)->x(), child_at(i)->y(),
+ child_at(i)->width(), height());
+ } else {
+ child_at(i)->SetBounds(child_at(i)->x(), child_at(i)->y(),
+ width(), child_at(i)->height());
+ }
+ }
+}
+
+void ShelfWidget::DelegateView::UpdateBackground(int alpha) {
+ alpha_ = alpha;
+ SchedulePaint();
+}
+
+ShelfWidget::ShelfWidget(
+ aura::Window* shelf_container,
+ aura::Window* status_container,
+ internal::WorkspaceController* workspace_controller) :
+ launcher_(NULL),
+ delegate_view_(new DelegateView(this)),
+ background_animator_(delegate_view_, 0, kLauncherBackgroundAlpha),
+ activating_as_fallback_(false),
+ window_container_(shelf_container) {
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.transparent = true;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = shelf_container;
+ params.delegate = delegate_view_;
+ Init(params);
+
+ // The shelf should not take focus when initially shown.
+ set_focus_on_creation(false);
+ SetContentsView(delegate_view_);
+
+ status_area_widget_ = new internal::StatusAreaWidget(status_container);
+ status_area_widget_->CreateTrayViews();
+ if (Shell::GetInstance()->delegate()->IsSessionStarted())
+ status_area_widget_->Show();
+ Shell::GetInstance()->focus_cycler()->AddWidget(status_area_widget_);
+
+ shelf_layout_manager_ = new internal::ShelfLayoutManager(this);
+ shelf_container->SetLayoutManager(shelf_layout_manager_);
+ shelf_layout_manager_->set_workspace_controller(workspace_controller);
+ workspace_controller->SetShelf(shelf_layout_manager_);
+
+ status_container->SetLayoutManager(
+ new internal::StatusAreaLayoutManager(this));
+
+ views::Widget::AddObserver(this);
+}
+
+ShelfWidget::~ShelfWidget() {
+ RemoveObserver(this);
+}
+
+void ShelfWidget::SetPaintsBackground(
+ bool value,
+ internal::BackgroundAnimator::ChangeType change_type) {
+ background_animator_.SetPaintsBackground(value, change_type);
+}
+
+ShelfAlignment ShelfWidget::GetAlignment() const {
+ return shelf_layout_manager_->GetAlignment();
+}
+
+void ShelfWidget::SetAlignment(ShelfAlignment alignment) {
+ shelf_layout_manager_->SetAlignment(alignment);
+ shelf_layout_manager_->LayoutShelf();
+ delegate_view_->SchedulePaint();
+}
+
+void ShelfWidget::SetDimsShelf(bool dimming) {
+ delegate_view_->SetDimmed(dimming);
+}
+
+bool ShelfWidget::GetDimsShelf() const {
+ return delegate_view_->GetDimmed();
+}
+
+void ShelfWidget::CreateLauncher() {
+ if (!launcher_.get()) {
+ Shell* shell = Shell::GetInstance();
+ // This needs to be called before launcher_model().
+ shell->GetLauncherDelegate();
+ launcher_.reset(new Launcher(shell->launcher_model(),
+ shell->GetLauncherDelegate(),
+ this));
+
+ SetFocusCycler(shell->focus_cycler());
+
+ // Inform the root window controller.
+ internal::RootWindowController::ForWindow(window_container_)->
+ OnLauncherCreated();
+
+ ShellDelegate* delegate = shell->delegate();
+ if (delegate)
+ launcher_->SetVisible(delegate->IsSessionStarted());
+
+ Show();
+ }
+}
+
+bool ShelfWidget::IsLauncherVisible() const {
+ return launcher_.get() && launcher_->IsVisible();
+}
+
+void ShelfWidget::SetLauncherVisibility(bool visible) {
+ if (launcher_.get())
+ launcher_->SetVisible(visible);
+}
+
+void ShelfWidget::SetFocusCycler(internal::FocusCycler* focus_cycler) {
+ delegate_view_->set_focus_cycler(focus_cycler);
+ if (focus_cycler)
+ focus_cycler->AddWidget(this);
+}
+
+internal::FocusCycler* ShelfWidget::GetFocusCycler() {
+ return delegate_view_->focus_cycler();
+}
+
+void ShelfWidget::OnWidgetActivationChanged(views::Widget* widget,
+ bool active) {
+ activating_as_fallback_ = false;
+ if (active)
+ delegate_view_->SetPaneFocusAndFocusDefault();
+ else
+ delegate_view_->GetFocusManager()->ClearFocus();
+}
+
+void ShelfWidget::ShutdownStatusAreaWidget() {
+ if (status_area_widget_)
+ status_area_widget_->Shutdown();
+ status_area_widget_ = NULL;
+}
+
+} // namespace ash
+
diff --git a/ash/shelf/shelf_widget.h b/ash/shelf/shelf_widget.h
new file mode 100644
index 0000000..df63ec0
--- /dev/null
+++ b/ash/shelf/shelf_widget.h
@@ -0,0 +1,104 @@
+// 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.
+
+#ifndef ASH_SHELF_SHELF_WIDGET_H_
+#define ASH_SHELF_SHELF_WIDGET_H_
+
+#include "ash/ash_export.h"
+#include "ash/shelf/background_animator.h"
+#include "ash/shelf/shelf_types.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+class Launcher;
+
+namespace internal {
+class FocusCycler;
+class StatusAreaWidget;
+class ShelfLayoutManager;
+class WorkspaceController;
+}
+
+class ASH_EXPORT ShelfWidget : public views::Widget,
+ public views::WidgetObserver {
+ public:
+ ShelfWidget(
+ aura::Window* shelf_container,
+ aura::Window* status_container,
+ internal::WorkspaceController* workspace_controller);
+ virtual ~ShelfWidget();
+
+ void SetAlignment(ShelfAlignment alignmnet);
+ ShelfAlignment GetAlignment() const;
+
+ ShelfAutoHideBehavior GetAutoHideBehavior() const;
+ void SetAutoHideBehavior(ShelfAutoHideBehavior behavior);
+
+ // Sets whether the shelf paints a background. Default is false, but is set
+ // to true if a window overlaps the shelf.
+ void SetPaintsBackground(
+ bool value,
+ internal::BackgroundAnimator::ChangeType change_type);
+ bool paints_background() const {
+ return background_animator_.paints_background();
+ }
+
+ // Causes shelf items to be slightly dimmed (eg when a window is maximized).
+ void SetDimsShelf(bool dimming);
+ bool GetDimsShelf() const;
+
+ internal::ShelfLayoutManager* shelf_layout_manager() {
+ return shelf_layout_manager_;
+ }
+ Launcher* launcher() const { return launcher_.get(); }
+ internal::StatusAreaWidget* status_area_widget() const {
+ return status_area_widget_;
+ }
+
+ void CreateLauncher();
+
+ // Set visibility of the launcher component of the shelf.
+ void SetLauncherVisibility(bool visible);
+ bool IsLauncherVisible() const;
+
+ // Sets the focus cycler. Also adds the launcher to the cycle.
+ void SetFocusCycler(internal::FocusCycler* focus_cycler);
+ internal::FocusCycler* GetFocusCycler();
+
+ // Called by the activation delegate, before the launcher is activated
+ // when no other windows are visible.
+ void WillActivateAsFallback() { activating_as_fallback_ = true; }
+
+ // Overridden from views::WidgetObserver:
+ virtual void OnWidgetActivationChanged(
+ views::Widget* widget, bool active) OVERRIDE;
+
+ aura::Window* window_container() { return window_container_; }
+
+ // TODO(harrym): Remove when Status Area Widget is a child view.
+ void ShutdownStatusAreaWidget();
+
+ private:
+ class DelegateView;
+
+ internal::ShelfLayoutManager* shelf_layout_manager_;
+ scoped_ptr<Launcher> launcher_;
+ internal::StatusAreaWidget* status_area_widget_;
+
+ // delegate_view_ is attached to window_container_ and is cleaned up
+ // during CloseChildWindows of the associated RootWindowController.
+ DelegateView* delegate_view_;
+ internal::BackgroundAnimator background_animator_;
+ bool activating_as_fallback_;
+ aura::Window* window_container_;
+};
+
+} // namespace ash
+
+#endif // ASH_SHELF_SHELF_WIDGET_H_
diff --git a/ash/shelf/shelf_widget_unittest.cc b/ash/shelf/shelf_widget_unittest.cc
new file mode 100644
index 0000000..bea1c64
--- /dev/null
+++ b/ash/shelf/shelf_widget_unittest.cc
@@ -0,0 +1,156 @@
+// 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/shelf/shelf_widget.h"
+
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_button.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/launcher/launcher_view.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/launcher_view_test_api.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/root_window.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/corewm/corewm_switches.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+namespace {
+ShelfWidget* GetShelfWidget() {
+ return Launcher::ForPrimaryDisplay()->shelf_widget();
+}
+
+internal::ShelfLayoutManager* GetShelfLayoutManager() {
+ return GetShelfWidget()->shelf_layout_manager();
+}
+
+} // namespace
+
+typedef test::AshTestBase ShelfWidgetTest;
+
+// Launcher can't be activated on mouse click, but it is activable from
+// the focus cycler or as fallback.
+TEST_F(ShelfWidgetTest, ActivateAsFallback) {
+ // TODO(mtomasz): make this test work with the FocusController.
+ if (views::corewm::UseFocusController())
+ return;
+
+ Launcher* launcher = Launcher::ForPrimaryDisplay();
+ ShelfWidget* shelf_widget = launcher->shelf_widget();
+ EXPECT_FALSE(shelf_widget->CanActivate());
+
+ shelf_widget->WillActivateAsFallback();
+ EXPECT_TRUE(shelf_widget->CanActivate());
+
+ wm::ActivateWindow(shelf_widget->GetNativeWindow());
+ EXPECT_FALSE(shelf_widget->CanActivate());
+}
+
+void TestLauncherAlignment(aura::RootWindow* root,
+ ShelfAlignment alignment,
+ const std::string& expected) {
+ Shell::GetInstance()->SetShelfAlignment(alignment, root);
+ gfx::Screen* screen = gfx::Screen::GetScreenFor(root);
+ EXPECT_EQ(expected,
+ screen->GetDisplayNearestWindow(root).work_area().ToString());
+}
+
+TEST_F(ShelfWidgetTest, TestAlignment) {
+ Launcher* launcher = Launcher::ForPrimaryDisplay();
+ UpdateDisplay("400x400");
+ ASSERT_TRUE(launcher);
+ {
+ SCOPED_TRACE("Single Bottom");
+ TestLauncherAlignment(Shell::GetPrimaryRootWindow(),
+ SHELF_ALIGNMENT_BOTTOM,
+ "0,0 400x352");
+ }
+ {
+ SCOPED_TRACE("Single Right");
+ TestLauncherAlignment(Shell::GetPrimaryRootWindow(),
+ SHELF_ALIGNMENT_RIGHT,
+ "0,0 348x400");
+ }
+ {
+ SCOPED_TRACE("Single Left");
+ TestLauncherAlignment(Shell::GetPrimaryRootWindow(),
+ SHELF_ALIGNMENT_LEFT,
+ "52,0 348x400");
+ }
+ UpdateDisplay("300x300,500x500");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ {
+ SCOPED_TRACE("Primary Bottom");
+ TestLauncherAlignment(root_windows[0],
+ SHELF_ALIGNMENT_BOTTOM,
+ "0,0 300x252");
+ }
+ {
+ SCOPED_TRACE("Primary Right");
+ TestLauncherAlignment(root_windows[0],
+ SHELF_ALIGNMENT_RIGHT,
+ "0,0 248x300");
+ }
+ {
+ SCOPED_TRACE("Primary Left");
+ TestLauncherAlignment(root_windows[0],
+ SHELF_ALIGNMENT_LEFT,
+ "52,0 248x300");
+ }
+ if (Shell::IsLauncherPerDisplayEnabled()) {
+ {
+ SCOPED_TRACE("Secondary Bottom");
+ TestLauncherAlignment(root_windows[1],
+ SHELF_ALIGNMENT_BOTTOM,
+ "300,0 500x452");
+ }
+ {
+ SCOPED_TRACE("Secondary Right");
+ TestLauncherAlignment(root_windows[1],
+ SHELF_ALIGNMENT_RIGHT,
+ "300,0 448x500");
+ }
+ {
+ SCOPED_TRACE("Secondary Left");
+ TestLauncherAlignment(root_windows[1],
+ SHELF_ALIGNMENT_LEFT,
+ "352,0 448x500");
+ }
+ }
+}
+
+// Makes sure the launcher is initially sized correctly.
+TEST_F(ShelfWidgetTest, LauncherInitiallySized) {
+ ShelfWidget* shelf_widget = GetShelfWidget();
+ Launcher* launcher = shelf_widget->launcher();
+ ASSERT_TRUE(launcher);
+ internal::ShelfLayoutManager* shelf_layout_manager = GetShelfLayoutManager();
+ ASSERT_TRUE(shelf_layout_manager);
+ ASSERT_TRUE(shelf_widget->status_area_widget());
+ int status_width = shelf_widget->status_area_widget()->
+ GetWindowBoundsInScreen().width();
+ // Test only makes sense if the status is > 0, which it better be.
+ EXPECT_GT(status_width, 0);
+ EXPECT_EQ(status_width, shelf_widget->GetContentsView()->width() -
+ launcher->GetLauncherViewForTest()->width());
+}
+
+// Verifies when the shell is deleted with a full screen window we don't crash.
+TEST_F(ShelfWidgetTest, DontReferenceLauncherAfterDeletion) {
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ // Widget is now owned by the parent window.
+ widget->Init(params);
+ widget->SetFullscreen(true);
+}
+
+} // namespace ash