diff options
| author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
|---|---|---|
| committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
| commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
| tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/ash/shelf | |
Initial import.
Diffstat (limited to 'chromium/ash/shelf')
| -rw-r--r-- | chromium/ash/shelf/background_animator.cc | 61 | ||||
| -rw-r--r-- | chromium/ash/shelf/background_animator.h | 74 | ||||
| -rw-r--r-- | chromium/ash/shelf/shelf_bezel_event_filter.cc | 73 | ||||
| -rw-r--r-- | chromium/ash/shelf/shelf_bezel_event_filter.h | 39 | ||||
| -rw-r--r-- | chromium/ash/shelf/shelf_layout_manager.cc | 1130 | ||||
| -rw-r--r-- | chromium/ash/shelf/shelf_layout_manager.h | 401 | ||||
| -rw-r--r-- | chromium/ash/shelf/shelf_layout_manager_observer.h | 37 | ||||
| -rw-r--r-- | chromium/ash/shelf/shelf_layout_manager_unittest.cc | 1833 | ||||
| -rw-r--r-- | chromium/ash/shelf/shelf_types.h | 57 | ||||
| -rw-r--r-- | chromium/ash/shelf/shelf_widget.cc | 656 | ||||
| -rw-r--r-- | chromium/ash/shelf/shelf_widget.h | 117 | ||||
| -rw-r--r-- | chromium/ash/shelf/shelf_widget_unittest.cc | 195 |
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 |
