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