summaryrefslogtreecommitdiffstats
path: root/chromium/ash/shelf
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/ash/shelf
Initial import.
Diffstat (limited to 'chromium/ash/shelf')
-rw-r--r--chromium/ash/shelf/background_animator.cc61
-rw-r--r--chromium/ash/shelf/background_animator.h74
-rw-r--r--chromium/ash/shelf/shelf_bezel_event_filter.cc73
-rw-r--r--chromium/ash/shelf/shelf_bezel_event_filter.h39
-rw-r--r--chromium/ash/shelf/shelf_layout_manager.cc1130
-rw-r--r--chromium/ash/shelf/shelf_layout_manager.h401
-rw-r--r--chromium/ash/shelf/shelf_layout_manager_observer.h37
-rw-r--r--chromium/ash/shelf/shelf_layout_manager_unittest.cc1833
-rw-r--r--chromium/ash/shelf/shelf_types.h57
-rw-r--r--chromium/ash/shelf/shelf_widget.cc656
-rw-r--r--chromium/ash/shelf/shelf_widget.h117
-rw-r--r--chromium/ash/shelf/shelf_widget_unittest.cc195
12 files changed, 4673 insertions, 0 deletions
diff --git a/chromium/ash/shelf/background_animator.cc b/chromium/ash/shelf/background_animator.cc
new file mode 100644
index 00000000000..dedae6fa1df
--- /dev/null
+++ b/chromium/ash/shelf/background_animator.cc
@@ -0,0 +1,61 @@
+// 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),
+ animation_(this),
+ paints_background_(false),
+ alpha_(min_alpha) {
+ animation_.SetSlideDuration(kBackgroundDurationMS);
+}
+
+BackgroundAnimator::~BackgroundAnimator() {
+}
+
+void BackgroundAnimator::SetDuration(int time_in_ms) {
+ animation_.SetSlideDuration(time_in_ms);
+}
+
+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/chromium/ash/shelf/background_animator.h b/chromium/ash/shelf/background_animator.h
new file mode 100644
index 00000000000..dbe495a2bc3
--- /dev/null
+++ b/chromium/ash/shelf/background_animator.h
@@ -0,0 +1,74 @@
+// 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 the transition time in ms.
+ void SetDuration(int time_in_ms);
+
+ // 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/chromium/ash/shelf/shelf_bezel_event_filter.cc b/chromium/ash/shelf/shelf_bezel_event_filter.cc
new file mode 100644
index 00000000000..12ec940ec7b
--- /dev/null
+++ b/chromium/ash/shelf/shelf_bezel_event_filter.cc
@@ -0,0 +1,73 @@
+// Copyright 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_bezel_event_filter.h"
+
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+
+namespace ash {
+namespace internal {
+
+ShelfBezelEventFilter::ShelfBezelEventFilter(
+ ShelfLayoutManager* shelf)
+ : shelf_(shelf),
+ in_touch_drag_(false) {
+ Shell::GetInstance()->AddPreTargetHandler(this);
+}
+
+ShelfBezelEventFilter::~ShelfBezelEventFilter() {
+ Shell::GetInstance()->RemovePreTargetHandler(this);
+}
+
+void ShelfBezelEventFilter::OnGestureEvent(
+ ui::GestureEvent* event) {
+ gfx::Rect screen =
+ Shell::GetScreen()->GetDisplayNearestPoint(event->location()).bounds();
+ if ((!screen.Contains(event->location()) &&
+ IsShelfOnBezel(screen, event->location())) ||
+ in_touch_drag_) {
+ if (gesture_handler_.ProcessGestureEvent(*event)) {
+ switch (event->type()) {
+ case ui::ET_GESTURE_SCROLL_BEGIN:
+ in_touch_drag_ = true;
+ break;
+ case ui::ET_GESTURE_SCROLL_END:
+ case ui::ET_SCROLL_FLING_START:
+ in_touch_drag_ = false;
+ break;
+ default:
+ break;
+ }
+ event->StopPropagation();
+ }
+ }
+}
+
+bool ShelfBezelEventFilter::IsShelfOnBezel(
+ const gfx::Rect& screen,
+ const gfx::Point& point) const{
+ switch (shelf_->GetAlignment()) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ if (point.y() >= screen.bottom())
+ return true;
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ if (point.x() <= screen.x())
+ return true;
+ break;
+ case SHELF_ALIGNMENT_TOP:
+ if (point.y() <= screen.y())
+ return true;
+ break;
+ case SHELF_ALIGNMENT_RIGHT:
+ if (point.x() >= screen.right())
+ return true;
+ break;
+ }
+ return false;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/shelf/shelf_bezel_event_filter.h b/chromium/ash/shelf/shelf_bezel_event_filter.h
new file mode 100644
index 00000000000..5390c4ea150
--- /dev/null
+++ b/chromium/ash/shelf/shelf_bezel_event_filter.h
@@ -0,0 +1,39 @@
+// Copyright 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.
+
+#ifndef ASH_SHELF_SHELF_BEZEL_EVENT_FILTER_H_
+#define ASH_SHELF_SHELF_BEZEL_EVENT_FILTER_H_
+
+#include "ash/wm/gestures/shelf_gesture_handler.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/gfx/rect.h"
+
+namespace ash {
+namespace internal {
+class ShelfLayoutManager;
+
+// Detects and forwards touch gestures that occur on a bezel sensor to the
+// shelf.
+class ShelfBezelEventFilter : public ui::EventHandler {
+ public:
+ explicit ShelfBezelEventFilter(ShelfLayoutManager* shelf);
+ virtual ~ShelfBezelEventFilter();
+
+ // Overridden from ui::EventHandler:
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ private:
+ bool IsShelfOnBezel(const gfx::Rect& screen,
+ const gfx::Point& point) const;
+
+ ShelfLayoutManager* shelf_; // non-owned
+ bool in_touch_drag_;
+ ShelfGestureHandler gesture_handler_;
+ DISALLOW_COPY_AND_ASSIGN(ShelfBezelEventFilter);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SHELF_SHELF_BEZEL_EVENT_FILTER_H_
diff --git a/chromium/ash/shelf/shelf_layout_manager.cc b/chromium/ash/shelf/shelf_layout_manager.cc
new file mode 100644
index 00000000000..a507da648a8
--- /dev/null
+++ b/chromium/ash/shelf/shelf_layout_manager.cc
@@ -0,0 +1,1130 @@
+// 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 <cstring>
+#include <string>
+#include <vector>
+
+#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/session_state_delegate.h"
+#include "ash/shelf/shelf_bezel_event_filter.h"
+#include "ash/shelf/shelf_layout_manager_observer.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/wm/gestures/shelf_gesture_handler.h"
+#include "ash/wm/mru_window_tracker.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_animations.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace_controller.h"
+#include "base/auto_reset.h"
+#include "base/command_line.h"
+#include "base/command_line.h"
+#include "base/i18n/rtl.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/cursor_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/base/ui_base_switches.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;
+
+// The maximum size of the region on the display opposing the shelf managed by
+// this ShelfLayoutManager which can trigger showing the shelf.
+// For instance:
+// - Primary display is left of secondary display.
+// - Shelf is left aligned
+// - This ShelfLayoutManager manages the shelf for the secondary display.
+// |kMaxAutoHideShowShelfRegionSize| refers to the maximum size of the region
+// from the right edge of the primary display which can trigger showing the
+// auto hidden shelf. The region is used to make it easier to trigger showing
+// the auto hidden shelf when the shelf is on the boundary between displays.
+const int kMaxAutoHideShowShelfRegionSize = 10;
+
+// Const inset from the edget of the shelf to the edget of the status area.
+const int kStatusAreaInset = 3;
+
+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::kWorkspaceAreaVisibleInset = 2;
+
+// static
+const int ShelfLayoutManager::kWorkspaceAreaAutoHideInset = 5;
+
+// static
+const int ShelfLayoutManager::kAutoHideSize = 3;
+
+// static
+const int ShelfLayoutManager::kShelfSize = 47;
+
+int ShelfLayoutManager::GetPreferredShelfSize() {
+ return ash::switches::UseAlternateShelfLayout() ?
+ ShelfLayoutManager::kShelfSize : kLauncherPreferredSize;
+}
+
+// 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;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ private:
+ ShelfLayoutManager* shelf_;
+ bool in_mouse_drag_;
+ ShelfGestureHandler gesture_handler_;
+ 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;
+}
+
+void ShelfLayoutManager::AutoHideEventFilter::OnGestureEvent(
+ ui::GestureEvent* event) {
+ if (shelf_->IsShelfWindow(static_cast<aura::Window*>(event->target()))) {
+ if (gesture_handler_.ProcessGestureEvent(*event))
+ event->StopPropagation();
+ }
+}
+
+// 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()),
+ updating_bounds_(false),
+ auto_hide_behavior_(SHELF_AUTO_HIDE_BEHAVIOR_NEVER),
+ alignment_(SHELF_ALIGNMENT_BOTTOM),
+ shelf_(shelf),
+ workspace_controller_(NULL),
+ window_overlaps_shelf_(false),
+ mouse_over_shelf_when_auto_hide_timer_started_(false),
+ bezel_event_filter_(new ShelfBezelEventFilter(this)),
+ 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(ShelfLayoutManagerObserver, 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(ShelfLayoutManagerObserver, observers_,
+ OnAutoHideBehaviorChanged(root_window_,
+ auto_hide_behavior_));
+}
+
+void ShelfLayoutManager::PrepareForShutdown() {
+ // Clear all event filters, otherwise sometimes those filters may catch
+ // synthesized mouse event and cause crashes during the shutdown.
+ set_workspace_controller(NULL);
+ auto_hide_event_filter_.reset();
+ bezel_event_filter_.reset();
+}
+
+bool ShelfLayoutManager::IsVisible() const {
+ // status_area_widget() may be NULL during the shutdown.
+ return shelf_->status_area_widget() &&
+ 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;
+ shelf_->SetAlignment(alignment);
+ LayoutShelf();
+ return true;
+}
+
+gfx::Rect ShelfLayoutManager::GetIdealBounds() {
+ 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() {
+ TargetBounds target_bounds;
+ CalculateTargetBounds(state_, &target_bounds);
+ UpdateBoundsAndOpacity(target_bounds, false, NULL);
+
+ if (shelf_->launcher()) {
+ // This is not part of UpdateBoundsAndOpacity() because
+ // SetLauncherViewBounds() sets the bounds immediately and does not animate.
+ // The height of the LauncherView for a horizontal shelf and the width of
+ // the LauncherView for a vertical shelf are set when |shelf_|'s bounds
+ // are changed via UpdateBoundsAndOpacity(). This sets the origin and the
+ // dimension in the other direction.
+ shelf_->launcher()->SetLauncherViewBounds(
+ target_bounds.launcher_bounds_in_shelf);
+ }
+}
+
+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;
+}
+
+void ShelfLayoutManager::UpdateVisibilityState() {
+ if (Shell::GetInstance()->session_state_delegate()->IsScreenLocked()) {
+ SetState(SHELF_VISIBLE);
+ } else {
+ // TODO(zelidrag): Verify shelf drag animation still shows on the device
+ // when we are in SHELF_AUTO_HIDE_ALWAYS_HIDDEN.
+ WorkspaceWindowState window_state(workspace_controller_->GetWindowState());
+ switch (window_state) {
+ case WORKSPACE_WINDOW_STATE_FULL_SCREEN:
+ if (FullscreenWithMinimalChrome()) {
+ SetState(SHELF_AUTO_HIDE);
+ } else {
+ 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);
+ } else {
+ if (!auto_hide_timer_.IsRunning()) {
+ mouse_over_shelf_when_auto_hide_timer_started_ =
+ shelf_->GetWindowBoundsInScreen().Contains(
+ Shell::GetScreen()->GetCursorScreenPoint());
+ }
+ auto_hide_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kAutoHideDelayMS),
+ this, &ShelfLayoutManager::UpdateAutoHideStateNow);
+ }
+ } else {
+ StopAutoHideTimer();
+ }
+}
+
+void ShelfLayoutManager::SetWindowOverlapsShelf(bool value) {
+ window_overlaps_shelf_ = value;
+ UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE);
+}
+
+void ShelfLayoutManager::AddObserver(ShelfLayoutManagerObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void ShelfLayoutManager::RemoveObserver(ShelfLayoutManagerObserver* 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;
+ }
+ if (shelf_) {
+ shelf_->Deactivate();
+ shelf_->status_area_widget()->Deactivate();
+ }
+ gesture_drag_auto_hide_state_ =
+ gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN ?
+ SHELF_AUTO_HIDE_HIDDEN : SHELF_AUTO_HIDE_SHOWN;
+ ShelfAutoHideBehavior new_auto_hide_behavior =
+ gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN ?
+ SHELF_AUTO_HIDE_BEHAVIOR_NEVER : SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
+
+ // In fullscreen with minimal chrome, the auto hide behavior affects neither
+ // the visibility state nor the auto hide state. Set |gesture_drag_status_|
+ // to GESTURE_DRAG_COMPLETE_IN_PROGRESS to set the auto hide state to
+ // |gesture_drag_auto_hide_state_|.
+ gesture_drag_status_ = GESTURE_DRAG_COMPLETE_IN_PROGRESS;
+ if (auto_hide_behavior_ != new_auto_hide_behavior)
+ SetAutoHideBehavior(new_auto_hide_behavior);
+ else
+ UpdateVisibilityState();
+ gesture_drag_status_ = GESTURE_DRAG_NONE;
+}
+
+void ShelfLayoutManager::CancelGestureDrag() {
+ gesture_drag_status_ = GESTURE_DRAG_CANCEL_IN_PROGRESS;
+ UpdateVisibilityState();
+ gesture_drag_status_ = GESTURE_DRAG_NONE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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 (!updating_bounds_ &&
+ ((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;
+}
+
+bool ShelfLayoutManager::FullscreenWithMinimalChrome() const {
+ RootWindowController* controller = GetRootWindowController(root_window_);
+ if (!controller)
+ return false;
+ const aura::Window* window = controller->GetFullscreenWindow();
+ if (!window)
+ return false;
+ if (!window->GetProperty(kFullscreenUsesMinimalChromeKey))
+ return false;
+ return true;
+}
+
+// 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) {
+ if (!shelf_->GetNativeView())
+ return;
+
+ State state;
+ state.visibility_state = visibility_state;
+ state.auto_hide_state = CalculateAutoHideState(visibility_state);
+ state.is_screen_locked =
+ Shell::GetInstance()->session_state_delegate()->IsScreenLocked();
+ state.window_state = workspace_controller_ ?
+ workspace_controller_->GetWindowState() : WORKSPACE_WINDOW_STATE_DEFAULT;
+
+ // Force an update because gesture drags affect the shelf bounds and we
+ // should animate back to the normal bounds at the end of a gesture.
+ bool force_update =
+ (gesture_drag_status_ == GESTURE_DRAG_CANCEL_IN_PROGRESS ||
+ gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS);
+
+ if (!force_update && state_.Equals(state))
+ return; // Nothing changed.
+
+ FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, 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 (!auto_hide_event_filter_)
+ auto_hide_event_filter_.reset(new AutoHideEventFilter(this));
+ } else {
+ auto_hide_event_filter_.reset(NULL);
+ }
+
+ StopAutoHideTimer();
+
+ State old_state = state_;
+ state_ = state;
+
+ BackgroundAnimator::ChangeType change_type =
+ BackgroundAnimator::CHANGE_ANIMATE;
+ bool delay_background_change = false;
+
+ // Do not animate the background when:
+ // - Going from a hidden / auto hidden shelf in fullscreen to a visible shelf
+ // in maximized mode.
+ // - Going from an auto hidden shelf in maximized mode to a visible shelf in
+ // maximized mode.
+ if (state.visibility_state == SHELF_VISIBLE &&
+ state.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED &&
+ old_state.visibility_state != SHELF_VISIBLE) {
+ change_type = BackgroundAnimator::CHANGE_IMMEDIATE;
+ } else {
+ // Delay the animation when the shelf was hidden, and has just been made
+ // visible (e.g. using a gesture-drag).
+ if (state.visibility_state == SHELF_VISIBLE &&
+ old_state.visibility_state == SHELF_AUTO_HIDE &&
+ old_state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
+ delay_background_change = true;
+ }
+ }
+
+ if (delay_background_change) {
+ if (update_shelf_observer_)
+ update_shelf_observer_->Detach();
+ // UpdateShelfBackground deletes itself when the animation is done.
+ update_shelf_observer_ = new UpdateShelfObserver(this);
+ } else {
+ UpdateShelfBackground(change_type);
+ }
+
+ shelf_->SetDimsShelf(
+ state.visibility_state == SHELF_VISIBLE &&
+ state.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED);
+
+ TargetBounds target_bounds;
+ CalculateTargetBounds(state_, &target_bounds);
+ UpdateBoundsAndOpacity(target_bounds, true,
+ delay_background_change ? update_shelf_observer_ : NULL);
+
+ // OnAutoHideStateChanged Should be emitted when:
+ // - firstly state changed to auto-hide from other state
+ // - or, auto_hide_state has changed
+ if ((old_state.visibility_state != state_.visibility_state &&
+ state_.visibility_state == SHELF_AUTO_HIDE) ||
+ old_state.auto_hide_state != state_.auto_hide_state) {
+ FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
+ OnAutoHideStateChanged(state_.auto_hide_state));
+ }
+}
+
+void ShelfLayoutManager::UpdateBoundsAndOpacity(
+ const TargetBounds& target_bounds,
+ bool animate,
+ ui::ImplicitAnimationObserver* observer) {
+ base::AutoReset<bool> auto_reset_updating_bounds(&updating_bounds_, true);
+
+ ui::ScopedLayerAnimationSettings launcher_animation_setter(
+ GetLayer(shelf_)->GetAnimator());
+ ui::ScopedLayerAnimationSettings status_animation_setter(
+ GetLayer(shelf_->status_area_widget())->GetAnimator());
+ if (animate) {
+ launcher_animation_setter.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS));
+ launcher_animation_setter.SetTweenType(ui::Tween::EASE_OUT);
+ launcher_animation_setter.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ status_animation_setter.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS));
+ status_animation_setter.SetTweenType(ui::Tween::EASE_OUT);
+ status_animation_setter.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ } else {
+ StopAnimating();
+ launcher_animation_setter.SetTransitionDuration(base::TimeDelta());
+ status_animation_setter.SetTransitionDuration(base::TimeDelta());
+ }
+ if (observer)
+ status_animation_setter.AddObserver(observer);
+
+ GetLayer(shelf_)->SetOpacity(target_bounds.opacity);
+ shelf_->SetBounds(ScreenAsh::ConvertRectToScreen(
+ shelf_->GetNativeView()->parent(),
+ target_bounds.shelf_bounds_in_root));
+
+ GetLayer(shelf_->status_area_widget())->SetOpacity(
+ target_bounds.status_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();
+}
+
+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 = GetPreferredShelfSize();
+ else
+ *width = GetPreferredShelfSize();
+}
+
+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(GetAvailableBounds());
+ 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) {
+ // Auto-hidden shelf always starts with the default size. If a gesture-drag
+ // is in progress, then the call to UpdateTargetBoundsForGesture() below
+ // takes care of setting the height properly.
+ if (IsHorizontalAlignment())
+ shelf_height = kAutoHideSize;
+ else
+ shelf_width = kAutoHideSize;
+ } else if (state.visibility_state == SHELF_HIDDEN ||
+ !keyboard_bounds_.IsEmpty()) {
+ 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 = std::max(0, GetPreferredShelfSize() -
+ PrimaryAxisValue(status_size.height(), status_size.width()));
+
+ if (ash::switches::UseAlternateShelfLayout())
+ status_inset = kStatusAreaInset;
+
+ 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->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));
+
+ // TODO(varkha): The functionality of managing insets for display areas
+ // should probably be pushed to a separate component. This would simplify or
+ // remove entirely the dependency on keyboard and dock.
+
+ // Also push in the work area inset for the keyboard if it is visible.
+ if (!keyboard_bounds_.IsEmpty()) {
+ gfx::Insets keyboard_insets(0, 0, keyboard_bounds_.height(), 0);
+ target_bounds->work_area_insets += keyboard_insets;
+ }
+
+ // Also push in the work area inset for the dock if it is visible.
+ if (!dock_bounds_.IsEmpty()) {
+ gfx::Insets dock_insets(
+ 0, (dock_bounds_.x() > 0 ? 0 : dock_bounds_.width()),
+ 0, (dock_bounds_.x() > 0 ? dock_bounds_.width() : 0));
+ target_bounds->work_area_insets += dock_insets;
+ }
+
+ target_bounds->opacity =
+ (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS ||
+ state.visibility_state == SHELF_VISIBLE ||
+ state.visibility_state == SHELF_AUTO_HIDE) ? 1.0f : 0.0f;
+ target_bounds->status_opacity =
+ (state.visibility_state == SHELF_AUTO_HIDE &&
+ state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN &&
+ gesture_drag_status_ != GESTURE_DRAG_IN_PROGRESS) ?
+ 0.0f : target_bounds->opacity;
+
+ if (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS)
+ UpdateTargetBoundsForGesture(target_bounds);
+
+ // This needs to happen after calling UpdateTargetBoundsForGesture(), because
+ // that can change the size of the shelf.
+ target_bounds->launcher_bounds_in_shelf = SelectValueForShelfAlignment(
+ gfx::Rect(base::i18n::IsRTL() ? status_size.width() : 0, 0,
+ shelf_width - status_size.width(),
+ target_bounds->shelf_bounds_in_root.height()),
+ gfx::Rect(0, 0, target_bounds->shelf_bounds_in_root.width(),
+ shelf_height - status_size.height()),
+ gfx::Rect(0, 0, target_bounds->shelf_bounds_in_root.width(),
+ shelf_height - status_size.height()),
+ gfx::Rect(base::i18n::IsRTL() ? status_size.width() : 0, 0,
+ shelf_width - status_size.width(),
+ target_bounds->shelf_bounds_in_root.height()));
+}
+
+void ShelfLayoutManager::UpdateTargetBoundsForGesture(
+ TargetBounds* target_bounds) const {
+ CHECK_EQ(GESTURE_DRAG_IN_PROGRESS, gesture_drag_status_);
+ bool horizontal = IsHorizontalAlignment();
+ const gfx::Rect& available_bounds(root_window_->bounds());
+ 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 = GetPreferredShelfSize() - 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 and size the launcher with the gesture.
+ int shelf_height = target_bounds->shelf_bounds_in_root.height() - translate;
+ shelf_height = std::max(shelf_height, kAutoHideSize);
+ target_bounds->shelf_bounds_in_root.set_height(shelf_height);
+ if (alignment_ == SHELF_ALIGNMENT_BOTTOM) {
+ target_bounds->shelf_bounds_in_root.set_y(
+ available_bounds.bottom() - shelf_height);
+ }
+
+ if (ash::switches::UseAlternateShelfLayout()) {
+ target_bounds->status_bounds_in_shelf.set_y(kStatusAreaInset);
+ } else {
+ // The statusbar should be in the center of the shelf.
+ gfx::Rect status_y = target_bounds->shelf_bounds_in_root;
+ status_y.set_y(0);
+ status_y.ClampToCenteredSize(
+ target_bounds->status_bounds_in_shelf.size());
+ target_bounds->status_bounds_in_shelf.set_y(status_y.y());
+ }
+ } else {
+ // Move and size the launcher with the gesture.
+ int shelf_width = target_bounds->shelf_bounds_in_root.width();
+ if (alignment_ == SHELF_ALIGNMENT_RIGHT)
+ shelf_width -= translate;
+ else
+ shelf_width += translate;
+ shelf_width = std::max(shelf_width, kAutoHideSize);
+ target_bounds->shelf_bounds_in_root.set_width(shelf_width);
+ if (alignment_ == SHELF_ALIGNMENT_RIGHT) {
+ target_bounds->shelf_bounds_in_root.set_x(
+ available_bounds.right() - shelf_width);
+ }
+
+ if (ash::switches::UseAlternateShelfLayout()) {
+ if (alignment_ == SHELF_ALIGNMENT_RIGHT) {
+ target_bounds->shelf_bounds_in_root.set_x(
+ available_bounds.right() - shelf_width + kStatusAreaInset);
+ } else {
+ target_bounds->shelf_bounds_in_root.set_x(kStatusAreaInset);
+ }
+ } else {
+ // The statusbar should be in the center of the shelf.
+ gfx::Rect status_x = target_bounds->shelf_bounds_in_root;
+ status_x.set_x(0);
+ 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) {
+ shelf_->SetPaintsBackground(GetShelfBackgroundType(), type);
+}
+
+ShelfBackgroundType ShelfLayoutManager::GetShelfBackgroundType() const {
+ if (state_.visibility_state != SHELF_AUTO_HIDE &&
+ state_.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED) {
+ return SHELF_BACKGROUND_MAXIMIZED;
+ }
+
+ if (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS ||
+ (!state_.is_screen_locked && window_overlaps_shelf_) ||
+ (state_.visibility_state == SHELF_AUTO_HIDE)) {
+ return SHELF_BACKGROUND_OVERLAP;
+ }
+
+ return SHELF_BACKGROUND_DEFAULT;
+}
+
+void ShelfLayoutManager::UpdateAutoHideStateNow() {
+ SetState(state_.visibility_state);
+
+ // If the state did not change, the auto hide timer may still be running.
+ StopAutoHideTimer();
+}
+
+void ShelfLayoutManager::StopAutoHideTimer() {
+ auto_hide_timer_.Stop();
+ mouse_over_shelf_when_auto_hide_timer_started_ = false;
+}
+
+gfx::Rect ShelfLayoutManager::GetAutoHideShowShelfRegionInScreen() const {
+ gfx::Rect shelf_bounds_in_screen = shelf_->GetWindowBoundsInScreen();
+ gfx::Vector2d offset = SelectValueForShelfAlignment(
+ gfx::Vector2d(0, shelf_bounds_in_screen.height()),
+ gfx::Vector2d(-kMaxAutoHideShowShelfRegionSize, 0),
+ gfx::Vector2d(shelf_bounds_in_screen.width(), 0),
+ gfx::Vector2d(0, -kMaxAutoHideShowShelfRegionSize));
+
+ gfx::Rect show_shelf_region_in_screen = shelf_bounds_in_screen;
+ show_shelf_region_in_screen += offset;
+ if (IsHorizontalAlignment())
+ show_shelf_region_in_screen.set_height(kMaxAutoHideShowShelfRegionSize);
+ else
+ show_shelf_region_in_screen.set_width(kMaxAutoHideShowShelfRegionSize);
+
+ // TODO: Figure out if we need any special handling when the keyboard is
+ // visible.
+ return show_shelf_region_in_screen;
+}
+
+ShelfAutoHideState ShelfLayoutManager::CalculateAutoHideState(
+ ShelfVisibilityState visibility_state) const {
+ if (visibility_state != SHELF_AUTO_HIDE || !shelf_)
+ return SHELF_AUTO_HIDE_HIDDEN;
+
+ 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;
+
+ const std::vector<aura::Window*> windows =
+ ash::MruWindowTracker::BuildWindowList(false);
+
+ // Process the window list and check if there are any visible windows.
+ bool visible_window = false;
+ for (size_t i = 0; i < windows.size(); ++i) {
+ if (windows[i] && windows[i]->IsVisible() &&
+ !ash::wm::IsWindowMinimized(windows[i]) &&
+ root_window_ == windows[i]->GetRootWindow()) {
+ visible_window = true;
+ break;
+ }
+ }
+ // If there are no visible windows do not hide the shelf.
+ if (!visible_window)
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ if (gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS)
+ return gesture_drag_auto_hide_state_;
+
+ // Don't show if the user is dragging the mouse.
+ if (auto_hide_event_filter_.get() && auto_hide_event_filter_->in_mouse_drag())
+ return SHELF_AUTO_HIDE_HIDDEN;
+
+ // Ignore the mouse position if mouse events are disabled.
+ aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
+ shelf_->GetNativeWindow()->GetRootWindow());
+ if (!cursor_client->IsMouseEventsEnabled())
+ 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);
+ }
+
+ gfx::Point cursor_position_in_screen =
+ Shell::GetScreen()->GetCursorScreenPoint();
+ if (shelf_region.Contains(cursor_position_in_screen))
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ // When the shelf is auto hidden and the shelf is on the boundary between two
+ // displays, it is hard to trigger showing the shelf. For instance, if a
+ // user's primary display is left of their secondary display, it is hard to
+ // unautohide a left aligned shelf on the secondary display.
+ // It is hard because:
+ // - It is hard to stop the cursor in the shelf "light bar" and not overshoot.
+ // - The cursor is warped to the other display if the cursor gets to the edge
+ // of the display.
+ // Show the shelf if the cursor started on the shelf and the user overshot the
+ // shelf slightly to make it easier to show the shelf in this situation. We
+ // do not check |auto_hide_timer_|.IsRunning() because it returns false when
+ // the timer's task is running.
+ if ((state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN ||
+ mouse_over_shelf_when_auto_hide_timer_started_) &&
+ GetAutoHideShowShelfRegionInScreen().Contains(
+ cursor_position_in_screen)) {
+ return SHELF_AUTO_HIDE_SHOWN;
+ }
+
+ return SHELF_AUTO_HIDE_HIDDEN;
+}
+
+void ShelfLayoutManager::UpdateHitTestBounds() {
+ gfx::Insets mouse_insets;
+ gfx::Insets touch_insets;
+ 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.
+ mouse_insets = GetInsetsForAlignment(kWorkspaceAreaVisibleInset);
+ } else if (state_.visibility_state == SHELF_AUTO_HIDE) {
+ // Extend the touch hit target out a bit to allow users to drag shelf out
+ // while hidden.
+ touch_insets = GetInsetsForAlignment(-kWorkspaceAreaAutoHideInset);
+ }
+
+ if (shelf_ && shelf_->GetNativeWindow())
+ shelf_->GetNativeWindow()->SetHitTestBoundsOverrideOuter(mouse_insets,
+ touch_insets);
+ shelf_->status_area_widget()->GetNativeWindow()->
+ SetHitTestBoundsOverrideOuter(mouse_insets, touch_insets);
+}
+
+bool ShelfLayoutManager::IsShelfWindow(aura::Window* window) {
+ if (!window)
+ return false;
+ return (shelf_ && shelf_->GetNativeWindow()->Contains(window)) ||
+ (shelf_->status_area_widget() &&
+ 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;
+}
+
+gfx::Rect ShelfLayoutManager::GetAvailableBounds() const {
+ gfx::Rect bounds(root_window_->bounds());
+ bounds.set_height(bounds.height() - keyboard_bounds_.height());
+ return bounds;
+}
+
+void ShelfLayoutManager::OnKeyboardBoundsChanging(
+ const gfx::Rect& keyboard_bounds) {
+ keyboard_bounds_ = keyboard_bounds;
+ OnWindowResized();
+}
+
+void ShelfLayoutManager::OnDockBoundsChanging(
+ const gfx::Rect& dock_bounds) {
+ if (dock_bounds_ != dock_bounds) {
+ dock_bounds_ = dock_bounds;
+ OnWindowResized();
+ }
+}
+
+gfx::Insets ShelfLayoutManager::GetInsetsForAlignment(int distance) const {
+ switch (alignment_) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ return gfx::Insets(distance, 0, 0, 0);
+ case SHELF_ALIGNMENT_LEFT:
+ return gfx::Insets(0, 0, 0, distance);
+ case SHELF_ALIGNMENT_RIGHT:
+ return gfx::Insets(0, distance, 0, 0);
+ case SHELF_ALIGNMENT_TOP:
+ return gfx::Insets(0, 0, distance, 0);
+ }
+ NOTREACHED();
+ return gfx::Insets();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/shelf/shelf_layout_manager.h b/chromium/ash/shelf/shelf_layout_manager.h
new file mode 100644
index 00000000000..56d7032f3db
--- /dev/null
+++ b/chromium/ash/shelf/shelf_layout_manager.h
@@ -0,0 +1,401 @@
+// 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 <vector>
+
+#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 "ash/wm/dock/docked_window_layout_manager_observer.h"
+#include "ash/wm/workspace/workspace_types.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/observer_list.h"
+#include "base/timer/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"
+#include "ui/keyboard/keyboard_controller.h"
+#include "ui/keyboard/keyboard_controller_observer.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ui {
+class GestureEvent;
+class ImplicitAnimationObserver;
+}
+
+namespace ash {
+class ScreenAsh;
+class ShelfLayoutManagerObserver;
+class ShelfWidget;
+FORWARD_DECLARE_TEST(WebNotificationTrayTest, PopupAndFullscreen);
+
+namespace internal {
+
+class PanelLayoutManagerTest;
+class ShelfBezelEventFilter;
+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 DockedWindowLayoutManagerObserver,
+ public keyboard::KeyboardControllerObserver {
+ public:
+
+ // We reserve a small area on the edge of the workspace area to ensure that
+ // the resize handle at the edge of the window can be hit.
+ static const int kWorkspaceAreaVisibleInset;
+
+ // When autohidden we extend the touch hit target onto the screen so that the
+ // user can drag the shelf out.
+ static const int kWorkspaceAreaAutoHideInset;
+
+ // Size of the shelf when auto-hidden.
+ static const int kAutoHideSize;
+
+ // The size of the shelf when shown (currently only used in alternate
+ // settings see ash::switches::UseAlternateShelfLayout).
+ static const int kShelfSize;
+
+ // Returns the preferred size for the shelf (either kLauncherPreferredSize or
+ // kShelfSize).
+ static int GetPreferredShelfSize();
+
+ 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 updating_bounds() const { return updating_bounds_; }
+
+ // Clears internal data for shutdown process.
+ void PrepareForShutdown();
+
+ // 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();
+
+ // 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(ShelfLayoutManagerObserver* observer);
+ void RemoveObserver(ShelfLayoutManagerObserver* 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;
+
+ // Tests if the browser is currently in fullscreen mode with minimal
+ // Chrome. When minimal Chrome is present the shelf should be displayed.
+ bool FullscreenWithMinimalChrome() 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 PanelLayoutManagerTest;
+ friend class ShelfLayoutManagerTest;
+ FRIEND_TEST_ALL_PREFIXES(ash::WebNotificationTrayTest, PopupAndFullscreen);
+
+ struct TargetBounds {
+ TargetBounds();
+ ~TargetBounds();
+
+ float opacity;
+ float status_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),
+ window_state(WORKSPACE_WINDOW_STATE_DEFAULT),
+ 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.window_state == window_state &&
+ other.is_screen_locked == is_screen_locked;
+ }
+
+ ShelfVisibilityState visibility_state;
+ ShelfAutoHideState auto_hide_state;
+ WorkspaceWindowState window_state;
+ bool is_screen_locked;
+ };
+
+ // Sets the visibility of the shelf to |state|.
+ void SetState(ShelfVisibilityState visibility_state);
+
+ // Updates the bounds and opacity of the launcher and status widgets.
+ // If |observer| is specified, it will be called back when the animations, if
+ // any, are complete.
+ void UpdateBoundsAndOpacity(const TargetBounds& target_bounds,
+ bool animate,
+ ui::ImplicitAnimationObserver* observer);
+
+ // Stops any animations and progresses them to the end.
+ 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 how the shelf background is painted.
+ ShelfBackgroundType GetShelfBackgroundType() const;
+
+ // Updates the auto hide state immediately.
+ void UpdateAutoHideStateNow();
+
+ // Stops the auto hide timer and clears
+ // |mouse_over_shelf_when_auto_hide_timer_started_|.
+ void StopAutoHideTimer();
+
+ // Returns the bounds of an additional region which can trigger showing the
+ // shelf. This region exists to make it easier to trigger showing the shelf
+ // when the shelf is auto hidden and the shelf is on the boundary between
+ // two displays.
+ gfx::Rect GetAutoHideShowShelfRegionInScreen() const;
+
+ // 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;
+
+ // Return the bounds available in the parent, taking into account the bounds
+ // of the keyboard if necessary.
+ gfx::Rect GetAvailableBounds() const;
+
+ // Overridden from keyboard::KeyboardControllerObserver:
+ virtual void OnKeyboardBoundsChanging(
+ const gfx::Rect& keyboard_bounds) OVERRIDE;
+
+ // Overridden from dock::DockObserver:
+ virtual void OnDockBoundsChanging(const gfx::Rect& dock_bounds) OVERRIDE;
+
+ // Generates insets for inward edge based on the current shelf alignment.
+ gfx::Insets GetInsetsForAlignment(int distance) 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 UpdateBoundsAndOpacity() method. Used to prevent calling
+ // UpdateBoundsAndOpacity() again from SetChildBounds().
+ bool updating_bounds_;
+
+ // 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_;
+
+ // Whether the mouse was over the shelf when the auto hide timer started.
+ // False when neither the auto hide timer nor the timer task are running.
+ bool mouse_over_shelf_when_auto_hide_timer_started_;
+
+ // EventFilter used to detect when user moves the mouse over the launcher to
+ // trigger showing the launcher.
+ scoped_ptr<AutoHideEventFilter> auto_hide_event_filter_;
+
+ // EventFilter used to detect when user issues a gesture on a bezel sensor.
+ scoped_ptr<ShelfBezelEventFilter> bezel_event_filter_;
+
+ ObserverList<ShelfLayoutManagerObserver> 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_CANCEL_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_;
+
+ // The bounds of the keyboard.
+ gfx::Rect keyboard_bounds_;
+
+ // The bounds of the dock.
+ gfx::Rect dock_bounds_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShelfLayoutManager);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SHELF_SHELF_LAYOUT_MANAGER_H_
diff --git a/chromium/ash/shelf/shelf_layout_manager_observer.h b/chromium/ash/shelf/shelf_layout_manager_observer.h
new file mode 100644
index 00000000000..328e2095d48
--- /dev/null
+++ b/chromium/ash/shelf/shelf_layout_manager_observer.h
@@ -0,0 +1,37 @@
+// Copyright 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.
+
+#ifndef ASH_SHELF_SHELF_LAYOUT_MANAGER_OBSERVER_H_
+#define ASH_SHELF_SHELF_LAYOUT_MANAGER_OBSERVER_H_
+
+#include "ash/ash_export.h"
+#include "ash/shelf/shelf_types.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+
+class ASH_EXPORT ShelfLayoutManagerObserver {
+ public:
+ virtual ~ShelfLayoutManagerObserver() {}
+
+ // 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) {}
+
+ // Called when the auto hide behavior is changed.
+ virtual void OnAutoHideBehaviorChanged(aura::RootWindow* root_window,
+ ShelfAutoHideBehavior new_behavior) {}
+};
+
+} // namespace ash
+
+#endif // ASH_SHELF_SHELF_LAYOUT_MANAGER_OBSERVER_H_
diff --git a/chromium/ash/shelf/shelf_layout_manager_unittest.cc b/chromium/ash/shelf/shelf_layout_manager_unittest.cc
new file mode 100644
index 00000000000..e44fe347a68
--- /dev/null
+++ b/chromium/ash/shelf/shelf_layout_manager_unittest.cc
@@ -0,0 +1,1833 @@
+// 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_controller.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/session_state_delegate.h"
+#include "ash/shelf/shelf_layout_manager_observer.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.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_properties.h"
+#include "ash/wm/window_util.h"
+#include "base/command_line.h"
+#include "base/strings/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/compositor/scoped_animation_duration_scale_mode.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 which waits till the shelf finishes animating to the target size and
+// counts the number of animation steps.
+class ShelfAnimationWaiter : views::WidgetObserver {
+ public:
+ explicit ShelfAnimationWaiter(const gfx::Rect& target_bounds)
+ : target_bounds_(target_bounds),
+ animation_steps_(0),
+ done_waiting_(false) {
+ GetShelfWidget()->AddObserver(this);
+ }
+
+ virtual ~ShelfAnimationWaiter() {
+ GetShelfWidget()->RemoveObserver(this);
+ }
+
+ // Wait till the shelf finishes animating to its expected bounds.
+ void WaitTillDoneAnimating() {
+ if (IsDoneAnimating())
+ done_waiting_ = true;
+ else
+ base::MessageLoop::current()->Run();
+ }
+
+ // Returns true if the animation has completed and it was valid.
+ bool WasValidAnimation() const {
+ return done_waiting_ && animation_steps_ > 0;
+ }
+
+ private:
+ // Returns true if shelf has finished animating to the target size.
+ bool IsDoneAnimating() const {
+ ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
+ gfx::Rect current_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
+ int size = layout_manager->PrimaryAxisValue(current_bounds.height(),
+ current_bounds.width());
+ int desired_size = layout_manager->PrimaryAxisValue(target_bounds_.height(),
+ target_bounds_.width());
+ return (size == desired_size);
+ }
+
+ // views::WidgetObserver override.
+ virtual void OnWidgetBoundsChanged(views::Widget* widget,
+ const gfx::Rect& new_bounds) OVERRIDE {
+ if (done_waiting_)
+ return;
+
+ ++animation_steps_;
+ if (IsDoneAnimating()) {
+ done_waiting_ = true;
+ base::MessageLoop::current()->Quit();
+ }
+ }
+
+ gfx::Rect target_bounds_;
+ int animation_steps_;
+ bool done_waiting_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShelfAnimationWaiter);
+};
+
+class ShelfDragCallback {
+ public:
+ ShelfDragCallback(const gfx::Rect& not_visible, const gfx::Rect& visible)
+ : not_visible_bounds_(not_visible),
+ visible_bounds_(visible),
+ was_visible_on_drag_start_(false) {
+ EXPECT_EQ(not_visible_bounds_.bottom(), visible_bounds_.bottom());
+ }
+
+ virtual ~ShelfDragCallback() {
+ }
+
+ void ProcessScroll(ui::EventType type, const gfx::Vector2dF& delta) {
+ if (GetShelfLayoutManager()->visibility_state() == ash::SHELF_HIDDEN)
+ return;
+
+ if (type == ui::ET_GESTURE_SCROLL_BEGIN) {
+ scroll_ = gfx::Vector2dF();
+ was_visible_on_drag_start_ = GetShelfLayoutManager()->IsVisible();
+ return;
+ }
+
+ // The state of the shelf at the end of the gesture is tested separately.
+ if (type == ui::ET_GESTURE_SCROLL_END)
+ return;
+
+ if (type == ui::ET_GESTURE_SCROLL_UPDATE)
+ scroll_.Add(delta);
+
+ gfx::Rect shelf_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
+ if (GetShelfLayoutManager()->IsHorizontalAlignment()) {
+ EXPECT_EQ(not_visible_bounds_.bottom(), shelf_bounds.bottom());
+ EXPECT_EQ(visible_bounds_.bottom(), shelf_bounds.bottom());
+ } else if (SHELF_ALIGNMENT_RIGHT ==
+ GetShelfLayoutManager()->GetAlignment()){
+ EXPECT_EQ(not_visible_bounds_.right(), shelf_bounds.right());
+ EXPECT_EQ(visible_bounds_.right(), shelf_bounds.right());
+ } else if (SHELF_ALIGNMENT_LEFT ==
+ GetShelfLayoutManager()->GetAlignment()) {
+ EXPECT_EQ(not_visible_bounds_.x(), shelf_bounds.x());
+ EXPECT_EQ(visible_bounds_.x(), shelf_bounds.x());
+ }
+
+ // if the shelf is being dimmed test dimmer bounds as well.
+ if (GetShelfWidget()->GetDimsShelf())
+ EXPECT_EQ(GetShelfWidget()->GetWindowBoundsInScreen(),
+ GetShelfWidget()->GetDimmerBoundsForTest());
+
+ // The shelf should never be smaller than the hidden state.
+ EXPECT_GE(shelf_bounds.height(), not_visible_bounds_.height());
+ float scroll_delta = GetShelfLayoutManager()->PrimaryAxisValue(
+ scroll_.y(),
+ scroll_.x());
+ bool increasing_drag =
+ GetShelfLayoutManager()->SelectValueForShelfAlignment(
+ scroll_delta < 0,
+ scroll_delta > 0,
+ scroll_delta < 0,
+ scroll_delta > 0);
+ int shelf_size = GetShelfLayoutManager()->PrimaryAxisValue(
+ shelf_bounds.height(),
+ shelf_bounds.width());
+ int visible_bounds_size = GetShelfLayoutManager()->PrimaryAxisValue(
+ visible_bounds_.height(),
+ visible_bounds_.width());
+ int not_visible_bounds_size = GetShelfLayoutManager()->PrimaryAxisValue(
+ not_visible_bounds_.height(),
+ not_visible_bounds_.width());
+ if (was_visible_on_drag_start_) {
+ if (increasing_drag) {
+ // If dragging inwards from the visible state, then the shelf should
+ // increase in size, but not more than the scroll delta.
+ EXPECT_LE(visible_bounds_size, shelf_size);
+ EXPECT_LE(abs(shelf_size - visible_bounds_size),
+ abs(scroll_delta));
+ } else {
+ if (shelf_size > not_visible_bounds_size) {
+ // If dragging outwards from the visible state, then the shelf
+ // should decrease in size, until it reaches the minimum size.
+ EXPECT_EQ(shelf_size, visible_bounds_size - abs(scroll_delta));
+ }
+ }
+ } else {
+ if (fabs(scroll_delta) <
+ visible_bounds_size - not_visible_bounds_size) {
+ // Tests that the shelf sticks with the touch point during the drag
+ // until the shelf is completely visible.
+ EXPECT_EQ(shelf_size, not_visible_bounds_size + abs(scroll_delta));
+ } else {
+ // Tests that after the shelf is completely visible, the shelf starts
+ // resisting the drag.
+ EXPECT_LT(shelf_size, not_visible_bounds_size + abs(scroll_delta));
+ }
+ }
+ }
+
+ private:
+ const gfx::Rect not_visible_bounds_;
+ const gfx::Rect visible_bounds_;
+ gfx::Vector2dF scroll_;
+ bool was_visible_on_drag_start_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShelfDragCallback);
+};
+
+class ShelfLayoutObserverTest : public ShelfLayoutManagerObserver {
+ 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();
+ }
+
+ void RunGestureDragTests(gfx::Vector2d);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShelfLayoutManagerTest);
+};
+
+void ShelfLayoutManagerTest::RunGestureDragTests(gfx::Vector2d delta) {
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ 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();
+ shelf->LayoutShelf();
+
+ gfx::Rect shelf_shown = GetShelfWidget()->GetWindowBoundsInScreen();
+ gfx::Rect bounds_shelf = window->bounds();
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ shelf->LayoutShelf();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ gfx::Rect bounds_noshelf = window->bounds();
+ gfx::Rect shelf_hidden = GetShelfWidget()->GetWindowBoundsInScreen();
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ shelf->LayoutShelf();
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ const int kNumScrollSteps = 10;
+ ShelfDragCallback handler(shelf_hidden, shelf_shown);
+
+ // Swipe up on the shelf. This should not change any state.
+ gfx::Point start = GetShelfWidget()->GetWindowBoundsInScreen().CenterPoint();
+ gfx::Point end = start + delta;
+
+ // Swipe down on the shelf to hide it.
+ generator.GestureScrollSequenceWithCallback(start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ 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());
+
+ // Swipe up to show the shelf.
+ generator.GestureScrollSequenceWithCallback(end, start,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ 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(GetShelfWidget()->GetDimmerBoundsForTest(),
+ GetShelfWidget()->GetWindowBoundsInScreen());
+ EXPECT_EQ(shelf_shown.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe up again. The shelf should hide.
+ end = start - delta;
+ generator.GestureScrollSequenceWithCallback(start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ 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 = start + delta;
+ generator.GestureScrollSequenceWithCallback(end, start,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+
+ // Swipe down very little. It shouldn't change any state.
+ if (GetShelfLayoutManager()->IsHorizontalAlignment())
+ end.set_y(start.y() + shelf_shown.height() * 3 / 10);
+ else if (SHELF_ALIGNMENT_LEFT == GetShelfLayoutManager()->GetAlignment())
+ end.set_x(start.x() - shelf_shown.width() * 3 / 10);
+ else if (SHELF_ALIGNMENT_RIGHT == GetShelfLayoutManager()->GetAlignment())
+ end.set_x(start.x() + shelf_shown.width() * 3 / 10);
+ generator.GestureScrollSequenceWithCallback(start, end,
+ base::TimeDelta::FromMilliseconds(100), 1,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ 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 = start + delta;
+ generator.GestureScrollSequenceWithCallback(start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ 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(GetShelfWidget()->GetDimmerBoundsForTest(), gfx::Rect());
+ EXPECT_EQ(bounds_noshelf.ToString(), window->bounds().ToString());
+ EXPECT_EQ(shelf_hidden.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe up in extended hit region to show it.
+ gfx::Point extended_start = start;
+ if (GetShelfLayoutManager()->IsHorizontalAlignment())
+ extended_start.set_y(GetShelfWidget()->GetWindowBoundsInScreen().y() -1);
+ else if (SHELF_ALIGNMENT_LEFT == GetShelfLayoutManager()->GetAlignment())
+ extended_start.set_x(
+ GetShelfWidget()->GetWindowBoundsInScreen().right() + 1);
+ else if (SHELF_ALIGNMENT_RIGHT == GetShelfLayoutManager()->GetAlignment())
+ extended_start.set_x(GetShelfWidget()->GetWindowBoundsInScreen().x() - 1);
+ end = extended_start - delta;
+ generator.GestureScrollSequenceWithCallback(extended_start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ 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(GetShelfWidget()->GetDimmerBoundsForTest(),
+ GetShelfWidget()->GetWindowBoundsInScreen());
+ EXPECT_EQ(shelf_shown.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe down again to hide.
+ end = start + delta;
+ generator.GestureScrollSequenceWithCallback(start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ 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(GetShelfWidget()->GetDimmerBoundsForTest(), gfx::Rect());
+ EXPECT_EQ(bounds_noshelf.ToString(), window->bounds().ToString());
+ EXPECT_EQ(shelf_hidden.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe up outside the hit area. This should not change anything.
+ gfx::Point outside_start = gfx::Point(
+ (GetShelfWidget()->GetWindowBoundsInScreen().x() +
+ GetShelfWidget()->GetWindowBoundsInScreen().right())/2,
+ GetShelfWidget()->GetWindowBoundsInScreen().y() - 50);
+ end = outside_start + delta;
+ generator.GestureScrollSequence(outside_start,
+ end,
+ base::TimeDelta::FromMilliseconds(10),
+ kNumScrollSteps);
+ 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 from below the shelf where a bezel would be, this should show the
+ // shelf.
+ gfx::Point below_start = start;
+ if (GetShelfLayoutManager()->IsHorizontalAlignment())
+ below_start.set_y(GetShelfWidget()->GetWindowBoundsInScreen().bottom() + 1);
+ else if (SHELF_ALIGNMENT_LEFT == GetShelfLayoutManager()->GetAlignment())
+ below_start.set_x(
+ GetShelfWidget()->GetWindowBoundsInScreen().x() - 1);
+ else if (SHELF_ALIGNMENT_RIGHT == GetShelfLayoutManager()->GetAlignment())
+ below_start.set_x(GetShelfWidget()->GetWindowBoundsInScreen().right() + 1);
+ end = below_start - delta;
+ generator.GestureScrollSequence(below_start,
+ end,
+ base::TimeDelta::FromMilliseconds(10),
+ kNumScrollSteps);
+ 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(GetShelfWidget()->GetDimmerBoundsForTest(),
+ GetShelfWidget()->GetWindowBoundsInScreen());
+ EXPECT_EQ(shelf_shown.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe down again to hide.
+ end = start + delta;
+ generator.GestureScrollSequenceWithCallback(start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ 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(GetShelfWidget()->GetDimmerBoundsForTest(), gfx::Rect());
+ EXPECT_EQ(bounds_noshelf.ToString(), window->bounds().ToString());
+ EXPECT_EQ(shelf_hidden.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Enter into fullscreen with minimal chrome (immersive fullscreen).
+ widget->SetFullscreen(true);
+ window->SetProperty(ash::internal::kFullscreenUsesMinimalChromeKey, true);
+ shelf->UpdateVisibilityState();
+
+ gfx::Rect bounds_fullscreen = window->bounds();
+ EXPECT_TRUE(widget->IsFullscreen());
+ EXPECT_NE(bounds_noshelf.ToString(), bounds_fullscreen.ToString());
+
+ // Swipe up. This should show the shelf.
+ end = below_start - delta;
+ generator.GestureScrollSequenceWithCallback(below_start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_NEVER, shelf->auto_hide_behavior());
+ EXPECT_EQ(shelf_shown.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ(bounds_fullscreen.ToString(), window->bounds().ToString());
+
+ // Swipe up again. This should hide the shelf.
+ generator.GestureScrollSequenceWithCallback(below_start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ 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());
+ EXPECT_EQ(bounds_fullscreen.ToString(), window->bounds().ToString());
+
+ // Put the window into fullscreen without any chrome at all (eg tab
+ // fullscreen).
+ window->SetProperty(ash::internal::kFullscreenUsesMinimalChromeKey, false);
+ shelf->UpdateVisibilityState();
+ EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+
+ // Swipe-up. This should not change anything.
+ end = start - delta;
+ generator.GestureScrollSequenceWithCallback(below_start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ 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());
+
+ // Close actually, otherwise further event may be affected since widget
+ // is fullscreen status.
+ widget->Close();
+ RunAllPendingInMessageLoop();
+
+ // The shelf should be shown because there are no more visible windows.
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+
+ // Swipe-up to hide. This should have no effect because there are no visible
+ // windows.
+ end = below_start - delta;
+ generator.GestureScrollSequenceWithCallback(below_start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+}
+
+// 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();
+ gfx::Screen* screen = Shell::GetScreen();
+ gfx::Display display = screen->GetDisplayNearestWindow(
+ Shell::GetPrimaryRootWindow());
+ ASSERT_NE(-1, display.id());
+ // Bottom inset should be the max of widget heights.
+ EXPECT_EQ(shelf_height, display.GetWorkAreaInsets().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());
+ display = screen->GetDisplayNearestWindow(
+ Shell::GetPrimaryRootWindow());
+
+ EXPECT_EQ(0, display.GetWorkAreaInsets().bottom());
+
+ // Make sure the bounds of the two widgets changed.
+ EXPECT_GE(shelf->GetNativeView()->bounds().y(),
+ screen->GetPrimaryDisplay().bounds().bottom());
+ EXPECT_GE(shelf->status_area_widget()->GetNativeView()->bounds().y(),
+ screen->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());
+ display = screen->GetDisplayNearestWindow(
+ Shell::GetPrimaryRootWindow());
+ EXPECT_EQ(shelf_height, display.GetWorkAreaInsets().bottom());
+
+ // Make sure the bounds of the two widgets changed.
+ launcher_bounds = shelf->GetNativeView()->bounds();
+ int bottom =
+ screen->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());
+
+ // 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());
+ gfx::Display display = Shell::GetScreen()->GetDisplayNearestWindow(
+ Shell::GetPrimaryRootWindow());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().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());
+}
+
+// Test that switching to a different visibility state does not restart the
+// shelf show / hide animation if it is already running. (crbug.com/250918)
+TEST_F(ShelfLayoutManagerTest, SetStateWhileAnimating) {
+ ShelfWidget* shelf = GetShelfWidget();
+ SetState(shelf->shelf_layout_manager(), SHELF_VISIBLE);
+ gfx::Rect initial_shelf_bounds = shelf->GetWindowBoundsInScreen();
+ gfx::Rect initial_status_bounds =
+ shelf->status_area_widget()->GetWindowBoundsInScreen();
+
+ ui::ScopedAnimationDurationScaleMode normal_animation_duration(
+ ui::ScopedAnimationDurationScaleMode::SLOW_DURATION);
+ SetState(shelf->shelf_layout_manager(), SHELF_HIDDEN);
+ SetState(shelf->shelf_layout_manager(), SHELF_VISIBLE);
+
+ gfx::Rect current_shelf_bounds = shelf->GetWindowBoundsInScreen();
+ gfx::Rect current_status_bounds =
+ shelf->status_area_widget()->GetWindowBoundsInScreen();
+
+ const int small_change = initial_shelf_bounds.height() / 2;
+ EXPECT_LE(
+ std::abs(initial_shelf_bounds.height() - current_shelf_bounds.height()),
+ small_change);
+ EXPECT_LE(
+ std::abs(initial_status_bounds.height() - current_status_bounds.height()),
+ small_change);
+}
+
+// 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());
+}
+
+// Test the behavior of the shelf when it is auto hidden and it is on the
+// boundary between the primary and the secondary display.
+TEST_F(ShelfLayoutManagerTest, AutoHideShelfOnScreenBoundary) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("800x600,800x600");
+ DisplayLayout display_layout(DisplayLayout::RIGHT, 0);
+ Shell::GetInstance()->display_controller()->SetLayoutForCurrentDisplays(
+ display_layout);
+ // Put the primary monitor's shelf on the display boundary.
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->SetAlignment(SHELF_ALIGNMENT_RIGHT);
+
+ // Create a window because the shelf is always shown when no windows are
+ // visible.
+ CreateTestWidget();
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ ASSERT_EQ(root_windows[0],
+ GetShelfWidget()->GetNativeWindow()->GetRootWindow());
+
+ shelf->SetAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+
+ int right_edge = root_windows[0]->GetBoundsInScreen().right() - 1;
+ int y = root_windows[0]->GetBoundsInScreen().y();
+
+ // Start off the mouse nowhere near the shelf; the shelf should be hidden.
+ aura::test::EventGenerator& generator(GetEventGenerator());
+ generator.MoveMouseTo(right_edge - 50, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Moving the mouse over the light bar (but not to the edge of the screen)
+ // should show the shelf.
+ generator.MoveMouseTo(right_edge - 1, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ EXPECT_EQ(right_edge - 1, Shell::GetScreen()->GetCursorScreenPoint().x());
+
+ // Moving the mouse off the light bar should hide the shelf.
+ generator.MoveMouseTo(right_edge - 50, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Moving the mouse to the right edge of the screen crossing the light bar
+ // should show the shelf despite the mouse cursor getting warped to the
+ // secondary display.
+ generator.MoveMouseTo(right_edge - 1, y);
+ generator.MoveMouseTo(right_edge, y);
+ UpdateAutoHideStateNow();
+ EXPECT_NE(right_edge - 1, Shell::GetScreen()->GetCursorScreenPoint().x());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Hide the shelf.
+ generator.MoveMouseTo(right_edge - 50, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Moving the mouse to the right edge of the screen crossing the light bar and
+ // overshooting by a lot should keep the shelf hidden.
+ generator.MoveMouseTo(right_edge - 1, y);
+ generator.MoveMouseTo(right_edge + 50, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Moving the mouse to the right edge of the screen crossing the light bar and
+ // overshooting a bit should show the shelf.
+ generator.MoveMouseTo(right_edge - 1, y);
+ generator.MoveMouseTo(right_edge + 2, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Keeping the mouse close to the left edge of the secondary display after the
+ // shelf is shown should keep the shelf shown.
+ generator.MoveMouseTo(right_edge + 2, y + 1);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Moving the mouse far from the left edge of the secondary display should
+ // hide the shelf.
+ generator.MoveMouseTo(right_edge + 50, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Moving to the left edge of the secondary display without first crossing
+ // the primary display's right aligned shelf first should not show the shelf.
+ generator.MoveMouseTo(right_edge + 2, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, 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()->session_state_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()->session_state_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());
+}
+
+// Basic assertions around the dimming of the shelf.
+TEST_F(ShelfLayoutManagerTest, TestDimmingBehavior) {
+ // 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->shelf_widget()->DisableDimmingAnimationsForTest();
+
+ 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());
+
+ gfx::Point off_shelf = display_bounds.CenterPoint();
+ gfx::Point on_shelf =
+ shelf->shelf_widget()->GetWindowBoundsInScreen().CenterPoint();
+
+ // Test there is no dimming object active at this point.
+ generator.MoveMouseTo(on_shelf.x(), on_shelf.y());
+ EXPECT_EQ(-1, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.MoveMouseTo(off_shelf.x(), off_shelf.y());
+ EXPECT_EQ(-1, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // After maximization, the shelf should be visible and the dimmer created.
+ widget->Maximize();
+
+ on_shelf = shelf->shelf_widget()->GetWindowBoundsInScreen().CenterPoint();
+ EXPECT_LT(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // Moving the mouse off the shelf should dim the bar.
+ generator.MoveMouseTo(off_shelf.x(), off_shelf.y());
+ EXPECT_LT(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // Adding touch events outside the shelf should still keep the shelf in
+ // dimmed state.
+ generator.PressTouch();
+ generator.MoveTouch(off_shelf);
+ EXPECT_LT(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ // Move the touch into the shelf area should undim.
+ generator.MoveTouch(on_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.ReleaseTouch();
+ // And a release dims again.
+ EXPECT_LT(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // Moving the mouse on the shelf should undim the bar.
+ generator.MoveMouseTo(on_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // No matter what the touch events do, the shelf should stay undimmed.
+ generator.PressTouch();
+ generator.MoveTouch(off_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.MoveTouch(on_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.MoveTouch(off_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.MoveTouch(on_shelf);
+ generator.ReleaseTouch();
+
+ // After restore, the dimming object should be deleted again.
+ widget->Restore();
+ EXPECT_EQ(-1, shelf->shelf_widget()->GetDimmingAlphaForTest());
+}
+
+// Assertions around the dimming of the shelf in conjunction with menus.
+TEST_F(ShelfLayoutManagerTest, TestDimmingBehaviorWithMenus) {
+ // 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->shelf_widget()->DisableDimmingAnimationsForTest();
+
+ 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());
+
+ // After maximization, the shelf should be visible and the dimmer created.
+ widget->Maximize();
+
+ gfx::Point off_shelf = display_bounds.CenterPoint();
+ gfx::Point on_shelf =
+ shelf->shelf_widget()->GetWindowBoundsInScreen().CenterPoint();
+
+ // Moving the mouse on the shelf should undim the bar.
+ generator.MoveMouseTo(on_shelf.x(), on_shelf.y());
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // Simulate a menu opening.
+ shelf->shelf_widget()->ForceUndimming(true);
+
+ // Moving the mouse off the shelf should not dim the bar.
+ generator.MoveMouseTo(off_shelf.x(), off_shelf.y());
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // No matter what the touch events do, the shelf should stay undimmed.
+ generator.PressTouch();
+ generator.MoveTouch(off_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.MoveTouch(on_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.MoveTouch(off_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.ReleaseTouch();
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // "Closing the menu" should now turn off the menu since no event is inside
+ // the shelf any longer.
+ shelf->shelf_widget()->ForceUndimming(false);
+ EXPECT_LT(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // Moving the mouse again on the shelf which should undim the bar again.
+ // This time we check that the bar stays undimmed when the mouse remains on
+ // the bar and the "menu gets closed".
+ generator.MoveMouseTo(on_shelf.x(), on_shelf.y());
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ shelf->shelf_widget()->ForceUndimming(true);
+ generator.MoveMouseTo(off_shelf.x(), off_shelf.y());
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.MoveMouseTo(on_shelf.x(), on_shelf.y());
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ shelf->shelf_widget()->ForceUndimming(true);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+}
+
+// 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 gfx::Screen* screen = Shell::GetScreen();
+ gfx::Display display =
+ screen->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 = screen->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 = screen->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ launcher_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
+ display = screen->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 = screen->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 = screen->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ launcher_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
+ display = screen->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 = screen->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();
+ {
+ SCOPED_TRACE("BOTTOM");
+ RunGestureDragTests(gfx::Vector2d(0, 100));
+ }
+
+ {
+ SCOPED_TRACE("LEFT");
+ shelf->SetAlignment(SHELF_ALIGNMENT_LEFT);
+ RunGestureDragTests(gfx::Vector2d(-100, 0));
+ }
+
+ {
+ SCOPED_TRACE("RIGHT");
+ shelf->SetAlignment(SHELF_ALIGNMENT_RIGHT);
+ RunGestureDragTests(gfx::Vector2d(100, 0));
+ }
+}
+
+TEST_F(ShelfLayoutManagerTest, WindowVisibilityDisablesAutoHide) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("800x600,800x600");
+ 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();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Window closed => auto hide disabled.
+ dummy->CloseNow();
+ 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
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // either minimzed => normal autohide
+ window2->Minimize();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ window2->Restore();
+ window1->Minimize();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // both minimized => disable auto hide
+ window2->Minimize();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Test moving windows to/from other display.
+ window2->Restore();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ // Move to second display.
+ window2->SetBounds(gfx::Rect(850, 50, 50, 50));
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ // Move back to primary display.
+ window2->SetBounds(gfx::Rect(50, 50, 50, 50));
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+}
+
+// Test that the shelf animates back to its normal position upon a user
+// completing a gesture drag.
+TEST_F(ShelfLayoutManagerTest, ShelfAnimatesWhenGestureComplete) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ // Test the shelf animates back to its original visible bounds when it is
+ // dragged when there are no visible windows.
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ gfx::Rect visible_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
+ {
+ // Enable animations so that we can make sure that they occur.
+ ui::ScopedAnimationDurationScaleMode regular_animations(
+ ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ gfx::Rect shelf_bounds_in_screen =
+ GetShelfWidget()->GetWindowBoundsInScreen();
+ gfx::Point start(shelf_bounds_in_screen.CenterPoint());
+ gfx::Point end(start.x(), shelf_bounds_in_screen.bottom());
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ ShelfAnimationWaiter waiter(visible_bounds);
+ // Wait till the animation completes and check that it occurred.
+ waiter.WaitTillDoneAnimating();
+ EXPECT_TRUE(waiter.WasValidAnimation());
+ }
+
+ // Create a visible window so auto-hide behavior is enforced.
+ CreateTestWidget();
+
+ // Get the bounds of the shelf when it is hidden.
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ gfx::Rect auto_hidden_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
+
+ {
+ // Enable the animations so that we can make sure they do occur.
+ ui::ScopedAnimationDurationScaleMode regular_animations(
+ ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+
+ gfx::Point start =
+ GetShelfWidget()->GetWindowBoundsInScreen().CenterPoint();
+ gfx::Point end(start.x(), start.y() - 100);
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+
+ // Test that the shelf animates to the visible bounds after a swipe up on
+ // the auto hidden shelf.
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ ShelfAnimationWaiter waiter1(visible_bounds);
+ waiter1.WaitTillDoneAnimating();
+ EXPECT_TRUE(waiter1.WasValidAnimation());
+
+ // Test that the shelf animates to the auto hidden bounds after a swipe up
+ // on the visible shelf.
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ 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());
+ ShelfAnimationWaiter waiter2(auto_hidden_bounds);
+ waiter2.WaitTillDoneAnimating();
+ EXPECT_TRUE(waiter2.WasValidAnimation());
+ }
+}
+
+TEST_F(ShelfLayoutManagerTest, GestureRevealsTrayBubble) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ 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->CloseSystemBubble();
+ 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());
+ int area_when_shelf_shown =
+ widget_one->GetNativeWindow()->bounds().size().GetArea();
+
+ // Now hide the shelf.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ // Both windows should be resized according to the shelf status.
+ EXPECT_EQ(widget_one->GetNativeWindow()->bounds().ToString(),
+ widget_two->GetNativeWindow()->bounds().ToString());
+ // Resized to small.
+ EXPECT_LT(area_when_shelf_shown,
+ widget_one->GetNativeWindow()->bounds().size().GetArea());
+
+ // Now show the shelf.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+
+ // Again both windows should be of the same size.
+ EXPECT_EQ(widget_one->GetNativeWindow()->bounds().ToString(),
+ widget_two->GetNativeWindow()->bounds().ToString());
+ EXPECT_EQ(area_when_shelf_shown,
+ widget_one->GetNativeWindow()->bounds().size().GetArea());
+}
+
+// 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->CloseSystemBubble();
+ 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());
+ }
+ }
+}
+
+TEST_F(ShelfLayoutManagerTest, ShelfBackgroundColor) {
+ EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, GetShelfWidget()->GetBackgroundType());
+
+ scoped_ptr<aura::Window> w1(CreateTestWindow());
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+ EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, GetShelfWidget()->GetBackgroundType());
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_EQ(SHELF_BACKGROUND_MAXIMIZED, GetShelfWidget()->GetBackgroundType());
+
+ scoped_ptr<aura::Window> w2(CreateTestWindow());
+ w2->Show();
+ wm::ActivateWindow(w2.get());
+ // Overlaps with shelf.
+ w2->SetBounds(GetShelfLayoutManager()->GetIdealBounds());
+
+ // Still background is 'maximized'.
+ EXPECT_EQ(SHELF_BACKGROUND_MAXIMIZED, GetShelfWidget()->GetBackgroundType());
+
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ EXPECT_EQ(SHELF_BACKGROUND_OVERLAP, GetShelfWidget()->GetBackgroundType());
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, GetShelfWidget()->GetBackgroundType());
+
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_EQ(SHELF_BACKGROUND_MAXIMIZED, GetShelfWidget()->GetBackgroundType());
+ w1.reset();
+ EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, GetShelfWidget()->GetBackgroundType());
+}
+
+// Verify that the shelf doesn't have the opaque background if it's auto-hide
+// status.
+TEST_F(ShelfLayoutManagerTest, ShelfBackgroundColorAutoHide) {
+ EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, GetShelfWidget ()->GetBackgroundType());
+
+ GetShelfLayoutManager()->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ scoped_ptr<aura::Window> w1(CreateTestWindow());
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+ EXPECT_EQ(SHELF_BACKGROUND_OVERLAP, GetShelfWidget()->GetBackgroundType());
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_EQ(SHELF_BACKGROUND_OVERLAP, GetShelfWidget()->GetBackgroundType());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/shelf/shelf_types.h b/chromium/ash/shelf/shelf_types.h
new file mode 100644
index 00000000000..4ee3b94c09a
--- /dev/null
+++ b/chromium/ash/shelf/shelf_types.h
@@ -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.
+
+#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,
+};
+
+enum ShelfBackgroundType {
+ // The default transparent background.
+ SHELF_BACKGROUND_DEFAULT,
+
+ // The background when a window is overlapping.
+ SHELF_BACKGROUND_OVERLAP,
+
+ // The background when a window is maximized.
+ SHELF_BACKGROUND_MAXIMIZED,
+};
+
+} // namespace ash
+
+#endif // ASH_SHELF_SHELF_TYPES_H_
diff --git a/chromium/ash/shelf/shelf_widget.cc b/chromium/ash/shelf/shelf_widget.cc
new file mode 100644
index 00000000000..8c7733ff2d6
--- /dev/null
+++ b/chromium/ash/shelf/shelf_widget.cc
@@ -0,0 +1,656 @@
+// 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/ash_switches.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/session_state_delegate.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/tray/test_system_tray_delegate.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/events/event_constants.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.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 = 128;
+
+// The time to dim and un-dim.
+const int kTimeToDimMs = 3000; // Slow in dimming.
+const int kTimeToUnDimMs = 200; // Fast in activating.
+const int kTimeToSwitchBackgroundMs = 1000;
+
+// Class used to slightly dim shelf items when maximized and visible.
+class DimmerView : public views::View,
+ public views::WidgetDelegate,
+ ash::internal::BackgroundAnimatorDelegate {
+ public:
+ // If |disable_dimming_animations_for_test| is set, all alpha animations will
+ // be performed instantly.
+ DimmerView(ash::ShelfWidget* shelf_widget,
+ bool disable_dimming_animations_for_test);
+ virtual ~DimmerView();
+
+ // Called by |DimmerEventFilter| when the mouse |hovered| state changes.
+ void SetHovered(bool hovered);
+
+ // Force the dimmer to be undimmed.
+ void ForceUndimming(bool force);
+
+ // views::WidgetDelegate overrides:
+ virtual views::Widget* GetWidget() OVERRIDE {
+ return View::GetWidget();
+ }
+ virtual const views::Widget* GetWidget() const OVERRIDE {
+ return View::GetWidget();
+ }
+
+ // ash::internal::BackgroundAnimatorDelegate overrides:
+ virtual void UpdateBackground(int alpha) OVERRIDE {
+ alpha_ = alpha;
+ SchedulePaint();
+ }
+
+ // views::View overrides:
+ virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE;
+
+ // A function to test the current alpha used.
+ int get_dimming_alpha_for_test() { return alpha_; }
+
+ private:
+ // This class monitors mouse events to see if it is on top of the launcher.
+ class DimmerEventFilter : public ui::EventHandler {
+ public:
+ explicit DimmerEventFilter(DimmerView* owner);
+ virtual ~DimmerEventFilter();
+
+ // Overridden from ui::EventHandler:
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+
+ private:
+ // The owning class.
+ DimmerView* owner_;
+
+ // TRUE if the mouse is inside the shelf.
+ bool mouse_inside_;
+
+ // TRUE if a touch event is inside the shelf.
+ bool touch_inside_;
+
+ DISALLOW_COPY_AND_ASSIGN(DimmerEventFilter);
+ };
+
+ // The owning shelf.
+ ash::ShelfWidget* shelf_;
+
+ // The alpha to use for covering the shelf.
+ int alpha_;
+
+ // True if the event filter claims that we should not be dimmed.
+ bool is_hovered_;
+
+ // True if someone forces us not to be dimmed (e.g. a menu is open).
+ bool force_hovered_;
+
+ // True if animations should be suppressed for a test.
+ bool disable_dimming_animations_for_test_;
+
+ // The animator for the background transitions.
+ ash::internal::BackgroundAnimator background_animator_;
+
+ // Notification of entering / exiting of the shelf area by mouse.
+ scoped_ptr<DimmerEventFilter> event_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(DimmerView);
+};
+
+DimmerView::DimmerView(ash::ShelfWidget* shelf_widget,
+ bool disable_dimming_animations_for_test)
+ : shelf_(shelf_widget),
+ alpha_(kDimAlpha),
+ is_hovered_(false),
+ force_hovered_(false),
+ disable_dimming_animations_for_test_(disable_dimming_animations_for_test),
+ background_animator_(this, 0, kDimAlpha) {
+ event_filter_.reset(new DimmerEventFilter(this));
+ // Make sure it is undimmed at the beginning and then fire off the dimming
+ // animation.
+ background_animator_.SetPaintsBackground(false,
+ ash::internal::BackgroundAnimator::CHANGE_IMMEDIATE);
+ SetHovered(false);
+}
+
+DimmerView::~DimmerView() {
+}
+
+void DimmerView::SetHovered(bool hovered) {
+ // Remember the hovered state so that we can correct the state once a
+ // possible force state has disappeared.
+ is_hovered_ = hovered;
+ // Undimm also if we were forced to by e.g. an open menu.
+ hovered |= force_hovered_;
+ background_animator_.SetDuration(hovered ? kTimeToUnDimMs : kTimeToDimMs);
+ background_animator_.SetPaintsBackground(!hovered,
+ disable_dimming_animations_for_test_ ?
+ ash::internal::BackgroundAnimator::CHANGE_IMMEDIATE :
+ ash::internal::BackgroundAnimator::CHANGE_ANIMATE);
+}
+
+void DimmerView::ForceUndimming(bool force) {
+ bool previous = force_hovered_;
+ force_hovered_ = force;
+ // If the forced change does change the result we apply the change.
+ if (is_hovered_ || force_hovered_ != is_hovered_ || previous)
+ SetHovered(is_hovered_);
+}
+
+void DimmerView::OnPaintBackground(gfx::Canvas* canvas) {
+ SkPaint paint;
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ gfx::ImageSkia launcher_background =
+ *rb.GetImageNamed(IDR_AURA_LAUNCHER_DIMMING).ToImageSkia();
+
+ if (shelf_->GetAlignment() != ash::SHELF_ALIGNMENT_BOTTOM) {
+ 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));
+ }
+ paint.setAlpha(alpha_);
+ canvas->DrawImageInt(
+ launcher_background,
+ 0, 0, launcher_background.width(), launcher_background.height(),
+ 0, 0, width(), height(),
+ false,
+ paint);
+}
+
+DimmerView::DimmerEventFilter::DimmerEventFilter(DimmerView* owner)
+ : owner_(owner),
+ mouse_inside_(false),
+ touch_inside_(false) {
+ ash::Shell::GetInstance()->AddPreTargetHandler(this);
+}
+
+DimmerView::DimmerEventFilter::~DimmerEventFilter() {
+ ash::Shell::GetInstance()->RemovePreTargetHandler(this);
+}
+
+void DimmerView::DimmerEventFilter::OnMouseEvent(ui::MouseEvent* event) {
+ if (event->type() != ui::ET_MOUSE_MOVED &&
+ event->type() != ui::ET_MOUSE_DRAGGED)
+ return;
+ bool inside = owner_->GetBoundsInScreen().Contains(event->root_location());
+ if (mouse_inside_ || touch_inside_ != inside || touch_inside_)
+ owner_->SetHovered(inside || touch_inside_);
+ mouse_inside_ = inside;
+}
+
+void DimmerView::DimmerEventFilter::OnTouchEvent(ui::TouchEvent* event) {
+ bool touch_inside = false;
+ if (event->type() != ui::ET_TOUCH_RELEASED &&
+ event->type() != ui::ET_TOUCH_CANCELLED)
+ touch_inside = owner_->GetBoundsInScreen().Contains(event->root_location());
+
+ if (mouse_inside_ || touch_inside_ != mouse_inside_ || touch_inside)
+ owner_->SetHovered(mouse_inside_ || touch_inside);
+ touch_inside_ = touch_inside;
+}
+
+} // namespace
+
+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_;
+ }
+
+ ui::Layer* opaque_background() { return &opaque_background_; }
+
+ // Set if the shelf area is dimmed (eg when a window is maximized).
+ void SetDimmed(bool dimmed);
+ bool GetDimmed() const;
+
+ void SetParentLayer(ui::Layer* layer);
+
+ // views::View overrides:
+ virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE;
+
+ // views::WidgetDelegateView overrides:
+ virtual views::Widget* GetWidget() OVERRIDE {
+ return View::GetWidget();
+ }
+ virtual const views::Widget* GetWidget() const OVERRIDE {
+ return View::GetWidget();
+ }
+
+ virtual bool CanActivate() const OVERRIDE;
+ virtual void Layout() OVERRIDE;
+ virtual void ReorderChildLayers(ui::Layer* parent_layer) OVERRIDE;
+ virtual void OnBoundsChanged(const gfx::Rect& old_bounds) OVERRIDE;
+
+ // BackgroundAnimatorDelegate overrides:
+ virtual void UpdateBackground(int alpha) OVERRIDE;
+
+ // Force the shelf to be presented in an undimmed state.
+ void ForceUndimming(bool force);
+
+ // A function to test the current alpha used by the dimming bar. If there is
+ // no dimmer active, the function will return -1.
+ int GetDimmingAlphaForTest();
+
+ // A function to test the bounds of the dimming bar. Returns gfx::Rect() if
+ // the dimmer is inactive.
+ gfx::Rect GetDimmerBoundsForTest();
+
+ // Disable dimming animations for running tests. This needs to be called
+ // prior to the creation of of the |dimmer_|.
+ void disable_dimming_animations_for_test() {
+ disable_dimming_animations_for_test_ = true;
+ }
+
+ private:
+ ShelfWidget* shelf_;
+ scoped_ptr<views::Widget> dimmer_;
+ internal::FocusCycler* focus_cycler_;
+ int alpha_;
+ ui::Layer opaque_background_;
+
+ // The view which does the dimming.
+ DimmerView* dimmer_view_;
+
+ // True if dimming animations should be turned off.
+ bool disable_dimming_animations_for_test_;
+
+ DISALLOW_COPY_AND_ASSIGN(DelegateView);
+};
+
+ShelfWidget::DelegateView::DelegateView(ShelfWidget* shelf)
+ : shelf_(shelf),
+ focus_cycler_(NULL),
+ alpha_(0),
+ opaque_background_(ui::LAYER_SOLID_COLOR),
+ dimmer_view_(NULL),
+ disable_dimming_animations_for_test_(false) {
+ set_allow_deactivate_on_esc(true);
+ opaque_background_.SetColor(SK_ColorBLACK);
+ opaque_background_.SetBounds(GetLocalBounds());
+ opaque_background_.SetOpacity(0.0f);
+}
+
+ShelfWidget::DelegateView::~DelegateView() {
+}
+
+void ShelfWidget::DelegateView::SetDimmed(bool value) {
+ if (value == (dimmer_.get() != NULL))
+ return;
+
+ if (value) {
+ dimmer_.reset(new views::Widget);
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.can_activate = false;
+ params.accept_events = false;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = shelf_->GetNativeView();
+ dimmer_->Init(params);
+ dimmer_->GetNativeWindow()->SetName("ShelfDimmer");
+ dimmer_->SetBounds(shelf_->GetWindowBoundsInScreen());
+ // The launcher should not take focus when it is initially shown.
+ dimmer_->set_focus_on_creation(false);
+ dimmer_view_ = new DimmerView(shelf_, disable_dimming_animations_for_test_);
+ dimmer_->SetContentsView(dimmer_view_);
+ dimmer_->GetNativeView()->SetName("ShelfDimmerView");
+ dimmer_->Show();
+ } else {
+ dimmer_view_ = NULL;
+ dimmer_.reset(NULL);
+ }
+}
+
+bool ShelfWidget::DelegateView::GetDimmed() const {
+ return dimmer_.get() && dimmer_->IsVisible();
+}
+
+void ShelfWidget::DelegateView::SetParentLayer(ui::Layer* layer) {
+ layer->Add(&opaque_background_);
+ ReorderLayers();
+}
+
+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);
+}
+
+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::ReorderChildLayers(ui::Layer* parent_layer) {
+ views::View::ReorderChildLayers(parent_layer);
+ parent_layer->StackAtBottom(&opaque_background_);
+}
+
+void ShelfWidget::DelegateView::OnBoundsChanged(const gfx::Rect& old_bounds) {
+ opaque_background_.SetBounds(GetLocalBounds());
+ if (dimmer_)
+ dimmer_->SetBounds(GetBoundsInScreen());
+}
+
+void ShelfWidget::DelegateView::ForceUndimming(bool force) {
+ if (GetDimmed())
+ dimmer_view_->ForceUndimming(force);
+}
+
+int ShelfWidget::DelegateView::GetDimmingAlphaForTest() {
+ if (GetDimmed())
+ return dimmer_view_->get_dimming_alpha_for_test();
+ return -1;
+}
+
+gfx::Rect ShelfWidget::DelegateView::GetDimmerBoundsForTest() {
+ if (GetDimmed())
+ return dimmer_view_->GetBoundsInScreen();
+ return gfx::Rect();
+}
+
+void ShelfWidget::DelegateView::UpdateBackground(int alpha) {
+ alpha_ = alpha;
+ SchedulePaint();
+}
+
+ShelfWidget::ShelfWidget(aura::Window* shelf_container,
+ aura::Window* status_container,
+ internal::WorkspaceController* workspace_controller)
+ : 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.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ 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_);
+ delegate_view_->SetParentLayer(GetLayer());
+
+ status_area_widget_ = new internal::StatusAreaWidget(status_container);
+ status_area_widget_->CreateTrayViews();
+ if (Shell::GetInstance()->session_state_delegate()->
+ IsActiveUserSessionStarted()) {
+ 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(
+ ShelfBackgroundType background_type,
+ internal::BackgroundAnimator::ChangeType change_type) {
+ ui::Layer* opaque_background = delegate_view_->opaque_background();
+ float target_opacity =
+ (background_type == SHELF_BACKGROUND_MAXIMIZED) ? 1.0f : 0.0f;
+ scoped_ptr<ui::ScopedLayerAnimationSettings> opaque_background_animation;
+ if (change_type != internal::BackgroundAnimator::CHANGE_IMMEDIATE) {
+ opaque_background_animation.reset(new ui::ScopedLayerAnimationSettings(
+ opaque_background->GetAnimator()));
+ opaque_background_animation->SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kTimeToSwitchBackgroundMs));
+ }
+ opaque_background->SetOpacity(target_opacity);
+
+ // TODO(mukai): use ui::Layer on both opaque_background and normal background
+ // retire background_animator_ at all. It would be simpler.
+ background_animator_.SetPaintsBackground(
+ background_type != SHELF_BACKGROUND_DEFAULT,
+ change_type);
+}
+
+ShelfBackgroundType ShelfWidget::GetBackgroundType() const {
+ if (delegate_view_->opaque_background()->GetTargetOpacity() == 1.0f)
+ return SHELF_BACKGROUND_MAXIMIZED;
+ if (background_animator_.paints_background())
+ return SHELF_BACKGROUND_OVERLAP;
+
+ return SHELF_BACKGROUND_DEFAULT;
+}
+
+// static
+bool ShelfWidget::ShelfAlignmentAllowed() {
+ if (!ash::switches::ShowShelfAlignmentMenu())
+ return false;
+ user::LoginStatus login_status =
+ Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus();
+
+ switch (login_status) {
+ case user::LOGGED_IN_USER:
+ case user::LOGGED_IN_OWNER:
+ return true;
+ case user::LOGGED_IN_LOCKED:
+ case user::LOGGED_IN_PUBLIC:
+ case user::LOGGED_IN_LOCALLY_MANAGED:
+ case user::LOGGED_IN_GUEST:
+ case user::LOGGED_IN_RETAIL_MODE:
+ case user::LOGGED_IN_KIOSK_APP:
+ case user::LOGGED_IN_NONE:
+ return false;
+ }
+
+ DCHECK(false);
+ return false;
+}
+
+ShelfAlignment ShelfWidget::GetAlignment() const {
+ return shelf_layout_manager_->GetAlignment();
+}
+
+void ShelfWidget::SetAlignment(ShelfAlignment alignment) {
+ if (launcher_)
+ launcher_->SetAlignment(alignment);
+ status_area_widget_->SetShelfAlignment(alignment);
+ delegate_view_->SchedulePaint();
+}
+
+void ShelfWidget::SetDimsShelf(bool dimming) {
+ delegate_view_->SetDimmed(dimming);
+ // Repaint all children, allowing updates to reflect dimmed state eg:
+ // status area background, app list button and overflow button.
+ if (launcher_)
+ launcher_->SchedulePaint();
+ status_area_widget_->GetContentsView()->SchedulePaint();
+}
+
+bool ShelfWidget::GetDimsShelf() const {
+ return delegate_view_->GetDimmed();
+}
+
+void ShelfWidget::CreateLauncher() {
+ if (launcher_)
+ return;
+
+ Shell* shell = Shell::GetInstance();
+ // This needs to be called before launcher_model().
+ LauncherDelegate* launcher_delegate = shell->GetLauncherDelegate();
+ if (!launcher_delegate)
+ return; // Not ready to create Launcher
+
+ 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();
+
+ launcher_->SetVisible(
+ shell->session_state_delegate()->IsActiveUserSessionStarted());
+ shelf_layout_manager_->LayoutShelf();
+ Show();
+}
+
+bool ShelfWidget::IsLauncherVisible() const {
+ return launcher_.get() && launcher_->IsVisible();
+}
+
+void ShelfWidget::SetLauncherVisibility(bool visible) {
+ if (launcher_)
+ 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::ShutdownStatusAreaWidget() {
+ if (status_area_widget_)
+ status_area_widget_->Shutdown();
+ status_area_widget_ = NULL;
+}
+
+void ShelfWidget::ForceUndimming(bool force) {
+ delegate_view_->ForceUndimming(force);
+}
+
+void ShelfWidget::OnWidgetActivationChanged(views::Widget* widget,
+ bool active) {
+ activating_as_fallback_ = false;
+ if (active)
+ delegate_view_->SetPaneFocusAndFocusDefault();
+ else
+ delegate_view_->GetFocusManager()->ClearFocus();
+}
+
+int ShelfWidget::GetDimmingAlphaForTest() {
+ if (delegate_view_)
+ return delegate_view_->GetDimmingAlphaForTest();
+ return -1;
+}
+
+gfx::Rect ShelfWidget::GetDimmerBoundsForTest() {
+ if (delegate_view_)
+ return delegate_view_->GetDimmerBoundsForTest();
+ return gfx::Rect();
+}
+
+void ShelfWidget::DisableDimmingAnimationsForTest() {
+ DCHECK(delegate_view_);
+ return delegate_view_->disable_dimming_animations_for_test();
+}
+
+} // namespace ash
diff --git a/chromium/ash/shelf/shelf_widget.h b/chromium/ash/shelf/shelf_widget.h
new file mode 100644
index 00000000000..c7adb742555
--- /dev/null
+++ b/chromium/ash/shelf/shelf_widget.h
@@ -0,0 +1,117 @@
+// 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();
+
+ // Returns if shelf alignment option is enabled, and the user is able
+ // to adjust the alignment (guest and supervised mode users cannot for
+ // example).
+ static bool ShelfAlignmentAllowed();
+
+ void SetAlignment(ShelfAlignment alignmnet);
+ ShelfAlignment GetAlignment() const;
+
+ // Sets the shelf's background type.
+ void SetPaintsBackground(
+ ShelfBackgroundType background_type,
+ internal::BackgroundAnimator::ChangeType change_type);
+ ShelfBackgroundType GetBackgroundType() const;
+
+ // 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; }
+
+ aura::Window* window_container() { return window_container_; }
+
+ // TODO(harrym): Remove when Status Area Widget is a child view.
+ void ShutdownStatusAreaWidget();
+
+ // Force the shelf to be presented in an undimmed state.
+ void ForceUndimming(bool force);
+
+ // Overridden from views::WidgetObserver:
+ virtual void OnWidgetActivationChanged(
+ views::Widget* widget, bool active) OVERRIDE;
+
+ // A function to test the current alpha used by the dimming bar. If there is
+ // no dimmer active, the function will return -1.
+ int GetDimmingAlphaForTest();
+
+ // A function to test the bounds of the dimming bar. Returns gfx::Rect() if
+ // the dimmer is inactive.
+ gfx::Rect GetDimmerBoundsForTest();
+
+ // Disable dimming animations for running tests.
+ void DisableDimmingAnimationsForTest();
+
+ 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/chromium/ash/shelf/shelf_widget_unittest.cc b/chromium/ash/shelf/shelf_widget_unittest.cc
new file mode 100644
index 00000000000..f043f342fe6
--- /dev/null
+++ b/chromium/ash/shelf/shelf_widget_unittest.cc
@@ -0,0 +1,195 @@
+// 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/root_window_controller.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 352x400");
+ }
+ {
+ SCOPED_TRACE("Single Left");
+ TestLauncherAlignment(Shell::GetPrimaryRootWindow(),
+ SHELF_ALIGNMENT_LEFT,
+ "48,0 352x400");
+ }
+ if (!SupportsMultipleDisplays())
+ return;
+
+ 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 252x300");
+ }
+ {
+ SCOPED_TRACE("Primary Left");
+ TestLauncherAlignment(root_windows[0],
+ SHELF_ALIGNMENT_LEFT,
+ "48,0 252x300");
+ }
+ {
+ 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 452x500");
+ }
+ {
+ SCOPED_TRACE("Secondary Left");
+ TestLauncherAlignment(root_windows[1],
+ SHELF_ALIGNMENT_LEFT,
+ "348,0 452x500");
+ }
+}
+
+// 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);
+}
+
+#if defined(OS_CHROMEOS)
+// Verifies launcher is created with correct size after user login and when its
+// container and status widget has finished sizing.
+// See http://crbug.com/252533
+TEST_F(ShelfWidgetTest, LauncherInitiallySizedAfterLogin) {
+ SetUserLoggedIn(false);
+ UpdateDisplay("300x200,400x300");
+
+ ShelfWidget* shelf = NULL;
+ Shell::RootWindowControllerList controllers(
+ Shell::GetAllRootWindowControllers());
+ for (Shell::RootWindowControllerList::const_iterator i = controllers.begin();
+ i != controllers.end();
+ ++i) {
+ if (!(*i)->shelf()->launcher()) {
+ shelf = (*i)->shelf();
+ break;
+ }
+ }
+ ASSERT_TRUE(shelf != NULL);
+
+ SetUserLoggedIn(true);
+ Shell::GetInstance()->CreateLauncher();
+
+ Launcher* launcher = shelf->launcher();
+ ASSERT_TRUE(launcher != NULL);
+
+ const int status_width =
+ shelf->status_area_widget()->GetWindowBoundsInScreen().width();
+ EXPECT_GT(status_width, 0);
+ EXPECT_EQ(status_width,
+ shelf->GetContentsView()->width() -
+ launcher->GetLauncherViewForTest()->width());
+}
+#endif
+
+} // namespace ash