1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ash/shelf/shelf_layout_manager.h"
13 #include "ash/ash_switches.h"
14 #include "ash/launcher/launcher.h"
15 #include "ash/launcher/launcher_types.h"
16 #include "ash/root_window_controller.h"
17 #include "ash/screen_ash.h"
18 #include "ash/session_state_delegate.h"
19 #include "ash/shelf/shelf_bezel_event_filter.h"
20 #include "ash/shelf/shelf_layout_manager_observer.h"
21 #include "ash/shelf/shelf_widget.h"
22 #include "ash/shell.h"
23 #include "ash/shell_window_ids.h"
24 #include "ash/system/status_area_widget.h"
25 #include "ash/wm/gestures/shelf_gesture_handler.h"
26 #include "ash/wm/lock_state_controller.h"
27 #include "ash/wm/mru_window_tracker.h"
28 #include "ash/wm/window_animations.h"
29 #include "ash/wm/window_state.h"
30 #include "ash/wm/window_util.h"
31 #include "ash/wm/workspace_controller.h"
32 #include "base/auto_reset.h"
33 #include "base/command_line.h"
34 #include "base/command_line.h"
35 #include "base/i18n/rtl.h"
36 #include "base/strings/string_number_conversions.h"
37 #include "base/strings/string_util.h"
38 #include "ui/aura/client/activation_client.h"
39 #include "ui/aura/client/cursor_client.h"
40 #include "ui/aura/root_window.h"
41 #include "ui/base/ui_base_switches.h"
42 #include "ui/compositor/layer.h"
43 #include "ui/compositor/layer_animation_observer.h"
44 #include "ui/compositor/layer_animator.h"
45 #include "ui/compositor/scoped_layer_animation_settings.h"
46 #include "ui/events/event.h"
47 #include "ui/events/event_handler.h"
48 #include "ui/gfx/screen.h"
49 #include "ui/views/widget/widget.h"
56 // Delay before showing the launcher. This is after the mouse stops moving.
57 const int kAutoHideDelayMS = 200;
59 // To avoid hiding the shelf when the mouse transitions from a message bubble
60 // into the shelf, the hit test area is enlarged by this amount of pixels to
61 // keep the shelf from hiding.
62 const int kNotificationBubbleGapHeight = 6;
64 // The maximum size of the region on the display opposing the shelf managed by
65 // this ShelfLayoutManager which can trigger showing the shelf.
67 // - Primary display is left of secondary display.
68 // - Shelf is left aligned
69 // - This ShelfLayoutManager manages the shelf for the secondary display.
70 // |kMaxAutoHideShowShelfRegionSize| refers to the maximum size of the region
71 // from the right edge of the primary display which can trigger showing the
72 // auto hidden shelf. The region is used to make it easier to trigger showing
73 // the auto hidden shelf when the shelf is on the boundary between displays.
74 const int kMaxAutoHideShowShelfRegionSize = 10;
76 ui::Layer* GetLayer(views::Widget* widget) {
77 return widget->GetNativeView()->layer();
80 bool IsDraggingTrayEnabled() {
81 static bool dragging_tray_allowed = CommandLine::ForCurrentProcess()->
82 HasSwitch(ash::switches::kAshEnableTrayDragging);
83 return dragging_tray_allowed;
89 const int ShelfLayoutManager::kWorkspaceAreaVisibleInset = 2;
92 const int ShelfLayoutManager::kWorkspaceAreaAutoHideInset = 5;
95 const int ShelfLayoutManager::kAutoHideSize = 3;
98 const int ShelfLayoutManager::kShelfSize = 47;
101 const int ShelfLayoutManager::kShelfItemInset = 3;
103 int ShelfLayoutManager::GetPreferredShelfSize() {
104 return ash::switches::UseAlternateShelfLayout() ?
105 ShelfLayoutManager::kShelfSize : kLauncherPreferredSize;
108 // ShelfLayoutManager::AutoHideEventFilter -------------------------------------
110 // Notifies ShelfLayoutManager any time the mouse moves.
111 class ShelfLayoutManager::AutoHideEventFilter : public ui::EventHandler {
113 explicit AutoHideEventFilter(ShelfLayoutManager* shelf);
114 virtual ~AutoHideEventFilter();
116 // Returns true if the last mouse event was a mouse drag.
117 bool in_mouse_drag() const { return in_mouse_drag_; }
119 // Overridden from ui::EventHandler:
120 virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
121 virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
124 ShelfLayoutManager* shelf_;
126 ShelfGestureHandler gesture_handler_;
127 DISALLOW_COPY_AND_ASSIGN(AutoHideEventFilter);
130 ShelfLayoutManager::AutoHideEventFilter::AutoHideEventFilter(
131 ShelfLayoutManager* shelf)
133 in_mouse_drag_(false) {
134 Shell::GetInstance()->AddPreTargetHandler(this);
137 ShelfLayoutManager::AutoHideEventFilter::~AutoHideEventFilter() {
138 Shell::GetInstance()->RemovePreTargetHandler(this);
141 void ShelfLayoutManager::AutoHideEventFilter::OnMouseEvent(
142 ui::MouseEvent* event) {
143 // This also checks IsShelfWindow() to make sure we don't attempt to hide the
144 // shelf if the mouse down occurs on the shelf.
145 in_mouse_drag_ = (event->type() == ui::ET_MOUSE_DRAGGED ||
146 (in_mouse_drag_ && event->type() != ui::ET_MOUSE_RELEASED &&
147 event->type() != ui::ET_MOUSE_CAPTURE_CHANGED)) &&
148 !shelf_->IsShelfWindow(static_cast<aura::Window*>(event->target()));
149 if (event->type() == ui::ET_MOUSE_MOVED)
150 shelf_->UpdateAutoHideState();
154 void ShelfLayoutManager::AutoHideEventFilter::OnGestureEvent(
155 ui::GestureEvent* event) {
156 if (shelf_->IsShelfWindow(static_cast<aura::Window*>(event->target()))) {
157 if (gesture_handler_.ProcessGestureEvent(*event))
158 event->StopPropagation();
162 // ShelfLayoutManager:UpdateShelfObserver --------------------------------------
164 // UpdateShelfObserver is used to delay updating the background until the
165 // animation completes.
166 class ShelfLayoutManager::UpdateShelfObserver
167 : public ui::ImplicitAnimationObserver {
169 explicit UpdateShelfObserver(ShelfLayoutManager* shelf) : shelf_(shelf) {
170 shelf_->update_shelf_observer_ = this;
177 virtual void OnImplicitAnimationsCompleted() OVERRIDE {
179 shelf_->UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE);
185 virtual ~UpdateShelfObserver() {
187 shelf_->update_shelf_observer_ = NULL;
190 // Shelf we're in. NULL if deleted before we're deleted.
191 ShelfLayoutManager* shelf_;
193 DISALLOW_COPY_AND_ASSIGN(UpdateShelfObserver);
196 // ShelfLayoutManager ----------------------------------------------------------
198 ShelfLayoutManager::ShelfLayoutManager(ShelfWidget* shelf)
199 : root_window_(shelf->GetNativeView()->GetRootWindow()),
200 updating_bounds_(false),
201 auto_hide_behavior_(SHELF_AUTO_HIDE_BEHAVIOR_NEVER),
202 alignment_(SHELF_ALIGNMENT_BOTTOM),
204 workspace_controller_(NULL),
205 window_overlaps_shelf_(false),
206 mouse_over_shelf_when_auto_hide_timer_started_(false),
207 bezel_event_filter_(new ShelfBezelEventFilter(this)),
208 gesture_drag_status_(GESTURE_DRAG_NONE),
209 gesture_drag_amount_(0.f),
210 gesture_drag_auto_hide_state_(SHELF_AUTO_HIDE_SHOWN),
211 update_shelf_observer_(NULL) {
212 Shell::GetInstance()->AddShellObserver(this);
213 Shell::GetInstance()->lock_state_controller()->AddObserver(this);
214 aura::client::GetActivationClient(root_window_)->AddObserver(this);
217 ShelfLayoutManager::~ShelfLayoutManager() {
218 if (update_shelf_observer_)
219 update_shelf_observer_->Detach();
221 FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_, WillDeleteShelf());
222 Shell::GetInstance()->RemoveShellObserver(this);
223 Shell::GetInstance()->lock_state_controller()->RemoveObserver(this);
224 aura::client::GetActivationClient(root_window_)->RemoveObserver(this);
227 void ShelfLayoutManager::SetAutoHideBehavior(ShelfAutoHideBehavior behavior) {
228 if (auto_hide_behavior_ == behavior)
230 auto_hide_behavior_ = behavior;
231 UpdateVisibilityState();
232 FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
233 OnAutoHideBehaviorChanged(root_window_,
234 auto_hide_behavior_));
237 void ShelfLayoutManager::PrepareForShutdown() {
238 // Clear all event filters, otherwise sometimes those filters may catch
239 // synthesized mouse event and cause crashes during the shutdown.
240 set_workspace_controller(NULL);
241 auto_hide_event_filter_.reset();
242 bezel_event_filter_.reset();
245 bool ShelfLayoutManager::IsVisible() const {
246 // status_area_widget() may be NULL during the shutdown.
247 return shelf_->status_area_widget() &&
248 shelf_->status_area_widget()->IsVisible() &&
249 (state_.visibility_state == SHELF_VISIBLE ||
250 (state_.visibility_state == SHELF_AUTO_HIDE &&
251 state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN));
254 bool ShelfLayoutManager::SetAlignment(ShelfAlignment alignment) {
255 if (alignment_ == alignment)
258 // This should not be called during the lock screen transitions.
259 DCHECK(!Shell::GetInstance()->session_state_delegate()->IsScreenLocked());
260 alignment_ = alignment;
261 shelf_->SetAlignment(alignment);
266 ShelfAlignment ShelfLayoutManager::GetAlignment() const {
267 // When the screen is locked, the shelf is forced into bottom alignment.
268 if (Shell::GetInstance()->session_state_delegate()->IsScreenLocked())
269 return SHELF_ALIGNMENT_BOTTOM;
273 gfx::Rect ShelfLayoutManager::GetIdealBounds() {
275 ScreenAsh::GetDisplayBoundsInParent(shelf_->GetNativeView()));
276 int width = 0, height = 0;
277 GetShelfSize(&width, &height);
278 return SelectValueForShelfAlignment(
279 gfx::Rect(bounds.x(), bounds.bottom() - height, bounds.width(), height),
280 gfx::Rect(bounds.x(), bounds.y(), width, bounds.height()),
281 gfx::Rect(bounds.right() - width, bounds.y(), width, bounds.height()),
282 gfx::Rect(bounds.x(), bounds.y(), bounds.width(), height));
285 void ShelfLayoutManager::LayoutShelf() {
286 TargetBounds target_bounds;
287 CalculateTargetBounds(state_, &target_bounds);
288 UpdateBoundsAndOpacity(target_bounds, false, NULL);
290 if (shelf_->launcher()) {
291 // This is not part of UpdateBoundsAndOpacity() because
292 // SetShelfViewBounds() sets the bounds immediately and does not animate.
293 // The height of the ShelfView for a horizontal shelf and the width of
294 // the ShelfView for a vertical shelf are set when |shelf_|'s bounds
295 // are changed via UpdateBoundsAndOpacity(). This sets the origin and the
296 // dimension in the other direction.
297 shelf_->launcher()->SetShelfViewBounds(
298 target_bounds.launcher_bounds_in_shelf);
302 ShelfVisibilityState ShelfLayoutManager::CalculateShelfVisibility() {
303 switch(auto_hide_behavior_) {
304 case SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS:
305 return SHELF_AUTO_HIDE;
306 case SHELF_AUTO_HIDE_BEHAVIOR_NEVER:
307 return SHELF_VISIBLE;
308 case SHELF_AUTO_HIDE_ALWAYS_HIDDEN:
311 return SHELF_VISIBLE;
314 void ShelfLayoutManager::UpdateVisibilityState() {
315 if (Shell::GetInstance()->session_state_delegate()->IsScreenLocked()) {
316 SetState(SHELF_VISIBLE);
318 // TODO(zelidrag): Verify shelf drag animation still shows on the device
319 // when we are in SHELF_AUTO_HIDE_ALWAYS_HIDDEN.
320 WorkspaceWindowState window_state(workspace_controller_->GetWindowState());
321 switch (window_state) {
322 case WORKSPACE_WINDOW_STATE_FULL_SCREEN:
323 if (FullscreenWithHiddenShelf()) {
324 SetState(SHELF_HIDDEN);
326 // The shelf is sometimes not hidden when in immersive fullscreen.
327 // Force the shelf to be auto hidden in this case.
328 SetState(SHELF_AUTO_HIDE);
331 case WORKSPACE_WINDOW_STATE_MAXIMIZED:
332 SetState(CalculateShelfVisibility());
334 case WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF:
335 case WORKSPACE_WINDOW_STATE_DEFAULT:
336 SetState(CalculateShelfVisibility());
337 SetWindowOverlapsShelf(window_state ==
338 WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF);
344 void ShelfLayoutManager::UpdateAutoHideState() {
345 ShelfAutoHideState auto_hide_state =
346 CalculateAutoHideState(state_.visibility_state);
347 if (auto_hide_state != state_.auto_hide_state) {
348 if (auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
349 // Hides happen immediately.
350 SetState(state_.visibility_state);
352 if (!auto_hide_timer_.IsRunning()) {
353 mouse_over_shelf_when_auto_hide_timer_started_ =
354 shelf_->GetWindowBoundsInScreen().Contains(
355 Shell::GetScreen()->GetCursorScreenPoint());
357 auto_hide_timer_.Start(
359 base::TimeDelta::FromMilliseconds(kAutoHideDelayMS),
360 this, &ShelfLayoutManager::UpdateAutoHideStateNow);
367 void ShelfLayoutManager::SetWindowOverlapsShelf(bool value) {
368 window_overlaps_shelf_ = value;
369 UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE);
372 void ShelfLayoutManager::AddObserver(ShelfLayoutManagerObserver* observer) {
373 observers_.AddObserver(observer);
376 void ShelfLayoutManager::RemoveObserver(ShelfLayoutManagerObserver* observer) {
377 observers_.RemoveObserver(observer);
380 ////////////////////////////////////////////////////////////////////////////////
381 // ShelfLayoutManager, Gesture dragging:
383 void ShelfLayoutManager::StartGestureDrag(const ui::GestureEvent& gesture) {
384 gesture_drag_status_ = GESTURE_DRAG_IN_PROGRESS;
385 gesture_drag_amount_ = 0.f;
386 gesture_drag_auto_hide_state_ = visibility_state() == SHELF_AUTO_HIDE ?
387 auto_hide_state() : SHELF_AUTO_HIDE_SHOWN;
388 UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE);
391 ShelfLayoutManager::DragState ShelfLayoutManager::UpdateGestureDrag(
392 const ui::GestureEvent& gesture) {
393 bool horizontal = IsHorizontalAlignment();
394 gesture_drag_amount_ += horizontal ? gesture.details().scroll_y() :
395 gesture.details().scroll_x();
398 // Start reveling the status menu when:
399 // - dragging up on an already visible shelf
400 // - dragging up on a hidden shelf, but it is currently completely visible.
401 if (horizontal && gesture.details().scroll_y() < 0) {
403 if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_HIDDEN && shelf_)
404 min_height = shelf_->GetContentsView()->GetPreferredSize().height();
406 if (min_height < shelf_->GetWindowBoundsInScreen().height() &&
407 gesture.root_location().x() >=
408 shelf_->status_area_widget()->GetWindowBoundsInScreen().x() &&
409 IsDraggingTrayEnabled())
416 void ShelfLayoutManager::CompleteGestureDrag(const ui::GestureEvent& gesture) {
417 bool horizontal = IsHorizontalAlignment();
418 bool should_change = false;
419 if (gesture.type() == ui::ET_GESTURE_SCROLL_END) {
420 // The visibility of the shelf changes only if the shelf was dragged X%
421 // along the correct axis. If the shelf was already visible, then the
422 // direction of the drag does not matter.
423 const float kDragHideThreshold = 0.4f;
424 gfx::Rect bounds = GetIdealBounds();
425 float drag_ratio = fabs(gesture_drag_amount_) /
426 (horizontal ? bounds.height() : bounds.width());
427 if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN) {
428 should_change = drag_ratio > kDragHideThreshold;
430 bool correct_direction = false;
431 switch (GetAlignment()) {
432 case SHELF_ALIGNMENT_BOTTOM:
433 case SHELF_ALIGNMENT_RIGHT:
434 correct_direction = gesture_drag_amount_ < 0;
436 case SHELF_ALIGNMENT_LEFT:
437 case SHELF_ALIGNMENT_TOP:
438 correct_direction = gesture_drag_amount_ > 0;
441 should_change = correct_direction && drag_ratio > kDragHideThreshold;
443 } else if (gesture.type() == ui::ET_SCROLL_FLING_START) {
444 if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN) {
445 should_change = horizontal ? fabs(gesture.details().velocity_y()) > 0 :
446 fabs(gesture.details().velocity_x()) > 0;
448 should_change = SelectValueForShelfAlignment(
449 gesture.details().velocity_y() < 0,
450 gesture.details().velocity_x() > 0,
451 gesture.details().velocity_x() < 0,
452 gesture.details().velocity_y() > 0);
458 if (!should_change) {
463 shelf_->Deactivate();
464 shelf_->status_area_widget()->Deactivate();
466 gesture_drag_auto_hide_state_ =
467 gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN ?
468 SHELF_AUTO_HIDE_HIDDEN : SHELF_AUTO_HIDE_SHOWN;
469 ShelfAutoHideBehavior new_auto_hide_behavior =
470 gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN ?
471 SHELF_AUTO_HIDE_BEHAVIOR_NEVER : SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
473 // When in fullscreen and the shelf is forced to be auto hidden, the auto hide
474 // behavior affects neither the visibility state nor the auto hide state. Set
475 // |gesture_drag_status_| to GESTURE_DRAG_COMPLETE_IN_PROGRESS to set the auto
476 // hide state to |gesture_drag_auto_hide_state_|.
477 gesture_drag_status_ = GESTURE_DRAG_COMPLETE_IN_PROGRESS;
478 if (auto_hide_behavior_ != new_auto_hide_behavior)
479 SetAutoHideBehavior(new_auto_hide_behavior);
481 UpdateVisibilityState();
482 gesture_drag_status_ = GESTURE_DRAG_NONE;
485 void ShelfLayoutManager::CancelGestureDrag() {
486 gesture_drag_status_ = GESTURE_DRAG_CANCEL_IN_PROGRESS;
487 UpdateVisibilityState();
488 gesture_drag_status_ = GESTURE_DRAG_NONE;
491 ////////////////////////////////////////////////////////////////////////////////
492 // ShelfLayoutManager, aura::LayoutManager implementation:
494 void ShelfLayoutManager::OnWindowResized() {
498 void ShelfLayoutManager::OnWindowAddedToLayout(aura::Window* child) {
501 void ShelfLayoutManager::OnWillRemoveWindowFromLayout(aura::Window* child) {
504 void ShelfLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) {
507 void ShelfLayoutManager::OnChildWindowVisibilityChanged(aura::Window* child,
511 void ShelfLayoutManager::SetChildBounds(aura::Window* child,
512 const gfx::Rect& requested_bounds) {
513 SetChildBoundsDirect(child, requested_bounds);
514 // We may contain other widgets (such as frame maximize bubble) but they don't
515 // effect the layout in anyway.
516 if (!updating_bounds_ &&
517 ((shelf_->GetNativeView() == child) ||
518 (shelf_->status_area_widget()->GetNativeView() == child))) {
523 void ShelfLayoutManager::OnLockStateChanged(bool locked) {
524 // Force the shelf to layout for alignment (bottom if locked, restore
525 // the previous alignment otherwise).
526 shelf_->SetAlignment(locked ? SHELF_ALIGNMENT_BOTTOM : alignment_);
527 UpdateVisibilityState();
531 void ShelfLayoutManager::OnWindowActivated(aura::Window* gained_active,
532 aura::Window* lost_active) {
533 UpdateAutoHideStateNow();
536 bool ShelfLayoutManager::IsHorizontalAlignment() const {
537 return GetAlignment() == SHELF_ALIGNMENT_BOTTOM ||
538 GetAlignment() == SHELF_ALIGNMENT_TOP;
541 bool ShelfLayoutManager::FullscreenWithHiddenShelf() const {
542 RootWindowController* controller = GetRootWindowController(root_window_);
545 const aura::Window* window = controller->GetTopmostFullscreenWindow();
548 return wm::GetWindowState(window)->hide_shelf_when_fullscreen();
552 ShelfLayoutManager* ShelfLayoutManager::ForLauncher(aura::Window* window) {
553 ShelfWidget* shelf = RootWindowController::ForLauncher(window)->shelf();
554 return shelf ? shelf->shelf_layout_manager() : NULL;
557 ////////////////////////////////////////////////////////////////////////////////
558 // ShelfLayoutManager, private:
560 ShelfLayoutManager::TargetBounds::TargetBounds() : opacity(0.0f) {}
561 ShelfLayoutManager::TargetBounds::~TargetBounds() {}
563 void ShelfLayoutManager::SetState(ShelfVisibilityState visibility_state) {
564 if (!shelf_->GetNativeView())
568 state.visibility_state = visibility_state;
569 state.auto_hide_state = CalculateAutoHideState(visibility_state);
570 state.is_screen_locked =
571 Shell::GetInstance()->session_state_delegate()->IsScreenLocked();
572 state.window_state = workspace_controller_ ?
573 workspace_controller_->GetWindowState() : WORKSPACE_WINDOW_STATE_DEFAULT;
575 // Force an update because gesture drags affect the shelf bounds and we
576 // should animate back to the normal bounds at the end of a gesture.
578 (gesture_drag_status_ == GESTURE_DRAG_CANCEL_IN_PROGRESS ||
579 gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS);
581 if (!force_update && state_.Equals(state))
582 return; // Nothing changed.
584 FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
585 WillChangeVisibilityState(visibility_state));
587 if (state.visibility_state == SHELF_AUTO_HIDE) {
588 // When state is SHELF_AUTO_HIDE we need to track when the mouse is over the
589 // launcher to unhide the shelf. AutoHideEventFilter does that for us.
590 if (!auto_hide_event_filter_)
591 auto_hide_event_filter_.reset(new AutoHideEventFilter(this));
593 auto_hide_event_filter_.reset(NULL);
598 State old_state = state_;
601 BackgroundAnimator::ChangeType change_type =
602 BackgroundAnimator::CHANGE_ANIMATE;
603 bool delay_background_change = false;
605 // Do not animate the background when:
606 // - Going from a hidden / auto hidden shelf in fullscreen to a visible shelf
607 // in maximized mode.
608 // - Going from an auto hidden shelf in maximized mode to a visible shelf in
610 if (state.visibility_state == SHELF_VISIBLE &&
611 state.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED &&
612 old_state.visibility_state != SHELF_VISIBLE) {
613 change_type = BackgroundAnimator::CHANGE_IMMEDIATE;
615 // Delay the animation when the shelf was hidden, and has just been made
616 // visible (e.g. using a gesture-drag).
617 if (state.visibility_state == SHELF_VISIBLE &&
618 old_state.visibility_state == SHELF_AUTO_HIDE &&
619 old_state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
620 delay_background_change = true;
624 if (delay_background_change) {
625 if (update_shelf_observer_)
626 update_shelf_observer_->Detach();
627 // UpdateShelfBackground deletes itself when the animation is done.
628 update_shelf_observer_ = new UpdateShelfObserver(this);
630 UpdateShelfBackground(change_type);
633 shelf_->SetDimsShelf(
634 state.visibility_state == SHELF_VISIBLE &&
635 state.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED);
637 TargetBounds target_bounds;
638 CalculateTargetBounds(state_, &target_bounds);
639 UpdateBoundsAndOpacity(target_bounds, true,
640 delay_background_change ? update_shelf_observer_ : NULL);
642 // OnAutoHideStateChanged Should be emitted when:
643 // - firstly state changed to auto-hide from other state
644 // - or, auto_hide_state has changed
645 if ((old_state.visibility_state != state_.visibility_state &&
646 state_.visibility_state == SHELF_AUTO_HIDE) ||
647 old_state.auto_hide_state != state_.auto_hide_state) {
648 FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
649 OnAutoHideStateChanged(state_.auto_hide_state));
653 void ShelfLayoutManager::UpdateBoundsAndOpacity(
654 const TargetBounds& target_bounds,
656 ui::ImplicitAnimationObserver* observer) {
657 base::AutoReset<bool> auto_reset_updating_bounds(&updating_bounds_, true);
659 ui::ScopedLayerAnimationSettings launcher_animation_setter(
660 GetLayer(shelf_)->GetAnimator());
661 ui::ScopedLayerAnimationSettings status_animation_setter(
662 GetLayer(shelf_->status_area_widget())->GetAnimator());
664 launcher_animation_setter.SetTransitionDuration(
665 base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS));
666 launcher_animation_setter.SetTweenType(gfx::Tween::EASE_OUT);
667 launcher_animation_setter.SetPreemptionStrategy(
668 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
669 status_animation_setter.SetTransitionDuration(
670 base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS));
671 status_animation_setter.SetTweenType(gfx::Tween::EASE_OUT);
672 status_animation_setter.SetPreemptionStrategy(
673 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
676 launcher_animation_setter.SetTransitionDuration(base::TimeDelta());
677 status_animation_setter.SetTransitionDuration(base::TimeDelta());
680 status_animation_setter.AddObserver(observer);
682 GetLayer(shelf_)->SetOpacity(target_bounds.opacity);
683 shelf_->SetBounds(ScreenAsh::ConvertRectToScreen(
684 shelf_->GetNativeView()->parent(),
685 target_bounds.shelf_bounds_in_root));
687 GetLayer(shelf_->status_area_widget())->SetOpacity(
688 target_bounds.status_opacity);
689 // TODO(harrym): Once status area widget is a child view of shelf
690 // this can be simplified.
691 gfx::Rect status_bounds = target_bounds.status_bounds_in_shelf;
692 status_bounds.set_x(status_bounds.x() +
693 target_bounds.shelf_bounds_in_root.x());
694 status_bounds.set_y(status_bounds.y() +
695 target_bounds.shelf_bounds_in_root.y());
696 shelf_->status_area_widget()->SetBounds(
697 ScreenAsh::ConvertRectToScreen(
698 shelf_->status_area_widget()->GetNativeView()->parent(),
700 Shell::GetInstance()->SetDisplayWorkAreaInsets(
701 root_window_, target_bounds.work_area_insets);
702 UpdateHitTestBounds();
705 void ShelfLayoutManager::StopAnimating() {
706 GetLayer(shelf_)->GetAnimator()->StopAnimating();
707 GetLayer(shelf_->status_area_widget())->GetAnimator()->StopAnimating();
710 void ShelfLayoutManager::GetShelfSize(int* width, int* height) {
711 *width = *height = 0;
712 gfx::Size status_size(
713 shelf_->status_area_widget()->GetWindowBoundsInScreen().size());
714 if (IsHorizontalAlignment())
715 *height = GetPreferredShelfSize();
717 *width = GetPreferredShelfSize();
720 void ShelfLayoutManager::AdjustBoundsBasedOnAlignment(int inset,
721 gfx::Rect* bounds) const {
722 bounds->Inset(SelectValueForShelfAlignment(
723 gfx::Insets(0, 0, inset, 0),
724 gfx::Insets(0, inset, 0, 0),
725 gfx::Insets(0, 0, 0, inset),
726 gfx::Insets(inset, 0, 0, 0)));
729 void ShelfLayoutManager::CalculateTargetBounds(
731 TargetBounds* target_bounds) {
732 const gfx::Rect available_bounds(GetAvailableBounds());
733 gfx::Rect status_size(
734 shelf_->status_area_widget()->GetWindowBoundsInScreen().size());
735 int shelf_width = 0, shelf_height = 0;
736 GetShelfSize(&shelf_width, &shelf_height);
737 if (IsHorizontalAlignment())
738 shelf_width = available_bounds.width();
740 shelf_height = available_bounds.height();
742 if (state.visibility_state == SHELF_AUTO_HIDE &&
743 state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
744 // Auto-hidden shelf always starts with the default size. If a gesture-drag
745 // is in progress, then the call to UpdateTargetBoundsForGesture() below
746 // takes care of setting the height properly.
747 if (IsHorizontalAlignment())
748 shelf_height = kAutoHideSize;
750 shelf_width = kAutoHideSize;
751 } else if (state.visibility_state == SHELF_HIDDEN ||
752 !keyboard_bounds_.IsEmpty()) {
753 if (IsHorizontalAlignment())
759 target_bounds->shelf_bounds_in_root = SelectValueForShelfAlignment(
760 gfx::Rect(available_bounds.x(), available_bounds.bottom() - shelf_height,
761 available_bounds.width(), shelf_height),
762 gfx::Rect(available_bounds.x(), available_bounds.y(),
763 shelf_width, available_bounds.height()),
764 gfx::Rect(available_bounds.right() - shelf_width, available_bounds.y(),
765 shelf_width, available_bounds.height()),
766 gfx::Rect(available_bounds.x(), available_bounds.y(),
767 available_bounds.width(), shelf_height));
769 int status_inset = std::max(0, GetPreferredShelfSize() -
770 PrimaryAxisValue(status_size.height(), status_size.width()));
772 if (ash::switches::UseAlternateShelfLayout()) {
774 if (IsHorizontalAlignment())
775 status_size.set_height(kShelfSize);
777 status_size.set_width(kShelfSize);
780 target_bounds->status_bounds_in_shelf = SelectValueForShelfAlignment(
781 gfx::Rect(base::i18n::IsRTL() ? 0 : shelf_width - status_size.width(),
782 status_inset, status_size.width(), status_size.height()),
783 gfx::Rect(shelf_width - (status_size.width() + status_inset),
784 shelf_height - status_size.height(), status_size.width(),
785 status_size.height()),
786 gfx::Rect(status_inset, shelf_height - status_size.height(),
787 status_size.width(), status_size.height()),
788 gfx::Rect(base::i18n::IsRTL() ? 0 : shelf_width - status_size.width(),
789 shelf_height - (status_size.height() + status_inset),
790 status_size.width(), status_size.height()));
792 target_bounds->work_area_insets = SelectValueForShelfAlignment(
793 gfx::Insets(0, 0, GetWorkAreaSize(state, shelf_height), 0),
794 gfx::Insets(0, GetWorkAreaSize(state, shelf_width), 0, 0),
795 gfx::Insets(0, 0, 0, GetWorkAreaSize(state, shelf_width)),
796 gfx::Insets(GetWorkAreaSize(state, shelf_height), 0, 0, 0));
798 // TODO(varkha): The functionality of managing insets for display areas
799 // should probably be pushed to a separate component. This would simplify or
800 // remove entirely the dependency on keyboard and dock.
802 // Also push in the work area inset for the keyboard if it is visible.
803 if (!keyboard_bounds_.IsEmpty()) {
804 gfx::Insets keyboard_insets(0, 0, keyboard_bounds_.height(), 0);
805 target_bounds->work_area_insets += keyboard_insets;
808 // Also push in the work area inset for the dock if it is visible.
809 if (!dock_bounds_.IsEmpty()) {
810 gfx::Insets dock_insets(
811 0, (dock_bounds_.x() > 0 ? 0 : dock_bounds_.width()),
812 0, (dock_bounds_.x() > 0 ? dock_bounds_.width() : 0));
813 target_bounds->work_area_insets += dock_insets;
816 target_bounds->opacity =
817 (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS ||
818 state.visibility_state == SHELF_VISIBLE ||
819 state.visibility_state == SHELF_AUTO_HIDE) ? 1.0f : 0.0f;
820 target_bounds->status_opacity =
821 (state.visibility_state == SHELF_AUTO_HIDE &&
822 state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN &&
823 gesture_drag_status_ != GESTURE_DRAG_IN_PROGRESS) ?
824 0.0f : target_bounds->opacity;
826 if (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS)
827 UpdateTargetBoundsForGesture(target_bounds);
829 // This needs to happen after calling UpdateTargetBoundsForGesture(), because
830 // that can change the size of the shelf.
831 target_bounds->launcher_bounds_in_shelf = SelectValueForShelfAlignment(
833 shelf_width - status_size.width(),
834 target_bounds->shelf_bounds_in_root.height()),
835 gfx::Rect(0, 0, target_bounds->shelf_bounds_in_root.width(),
836 shelf_height - status_size.height()),
837 gfx::Rect(0, 0, target_bounds->shelf_bounds_in_root.width(),
838 shelf_height - status_size.height()),
840 shelf_width - status_size.width(),
841 target_bounds->shelf_bounds_in_root.height()));
844 void ShelfLayoutManager::UpdateTargetBoundsForGesture(
845 TargetBounds* target_bounds) const {
846 CHECK_EQ(GESTURE_DRAG_IN_PROGRESS, gesture_drag_status_);
847 bool horizontal = IsHorizontalAlignment();
848 const gfx::Rect& available_bounds(root_window_->bounds());
849 int resistance_free_region = 0;
851 if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_HIDDEN &&
852 visibility_state() == SHELF_AUTO_HIDE &&
853 auto_hide_state() != SHELF_AUTO_HIDE_SHOWN) {
854 // If the shelf was hidden when the drag started (and the state hasn't
855 // changed since then, e.g. because the tray-menu was shown because of the
856 // drag), then allow the drag some resistance-free region at first to make
857 // sure the shelf sticks with the finger until the shelf is visible.
858 resistance_free_region = GetPreferredShelfSize() - kAutoHideSize;
861 bool resist = SelectValueForShelfAlignment(
862 gesture_drag_amount_ < -resistance_free_region,
863 gesture_drag_amount_ > resistance_free_region,
864 gesture_drag_amount_ < -resistance_free_region,
865 gesture_drag_amount_ > resistance_free_region);
867 float translate = 0.f;
869 float diff = fabsf(gesture_drag_amount_) - resistance_free_region;
870 diff = std::min(diff, sqrtf(diff));
871 if (gesture_drag_amount_ < 0)
872 translate = -resistance_free_region - diff;
874 translate = resistance_free_region + diff;
876 translate = gesture_drag_amount_;
880 // Move and size the launcher with the gesture.
881 int shelf_height = target_bounds->shelf_bounds_in_root.height() - translate;
882 shelf_height = std::max(shelf_height, kAutoHideSize);
883 target_bounds->shelf_bounds_in_root.set_height(shelf_height);
884 if (GetAlignment() == SHELF_ALIGNMENT_BOTTOM) {
885 target_bounds->shelf_bounds_in_root.set_y(
886 available_bounds.bottom() - shelf_height);
889 if (ash::switches::UseAlternateShelfLayout()) {
890 target_bounds->status_bounds_in_shelf.set_y(0);
892 // The statusbar should be in the center of the shelf.
893 gfx::Rect status_y = target_bounds->shelf_bounds_in_root;
895 status_y.ClampToCenteredSize(
896 target_bounds->status_bounds_in_shelf.size());
897 target_bounds->status_bounds_in_shelf.set_y(status_y.y());
900 // Move and size the launcher with the gesture.
901 int shelf_width = target_bounds->shelf_bounds_in_root.width();
902 bool right_aligned = GetAlignment() == SHELF_ALIGNMENT_RIGHT;
904 shelf_width -= translate;
906 shelf_width += translate;
907 shelf_width = std::max(shelf_width, kAutoHideSize);
908 target_bounds->shelf_bounds_in_root.set_width(shelf_width);
910 target_bounds->shelf_bounds_in_root.set_x(
911 available_bounds.right() - shelf_width);
914 if (ash::switches::UseAlternateShelfLayout()) {
916 target_bounds->status_bounds_in_shelf.set_x(0);
918 target_bounds->status_bounds_in_shelf.set_x(
919 target_bounds->shelf_bounds_in_root.width() -
922 // The statusbar should be in the center of the shelf.
923 gfx::Rect status_x = target_bounds->shelf_bounds_in_root;
925 status_x.ClampToCenteredSize(
926 target_bounds->status_bounds_in_shelf.size());
927 target_bounds->status_bounds_in_shelf.set_x(status_x.x());
932 void ShelfLayoutManager::UpdateShelfBackground(
933 BackgroundAnimator::ChangeType type) {
934 shelf_->SetPaintsBackground(GetShelfBackgroundType(), type);
937 ShelfBackgroundType ShelfLayoutManager::GetShelfBackgroundType() const {
938 if (state_.visibility_state != SHELF_AUTO_HIDE &&
939 state_.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED) {
940 return SHELF_BACKGROUND_MAXIMIZED;
943 if (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS ||
944 (!state_.is_screen_locked && window_overlaps_shelf_) ||
945 (state_.visibility_state == SHELF_AUTO_HIDE)) {
946 return SHELF_BACKGROUND_OVERLAP;
949 return SHELF_BACKGROUND_DEFAULT;
952 void ShelfLayoutManager::UpdateAutoHideStateNow() {
953 SetState(state_.visibility_state);
955 // If the state did not change, the auto hide timer may still be running.
959 void ShelfLayoutManager::StopAutoHideTimer() {
960 auto_hide_timer_.Stop();
961 mouse_over_shelf_when_auto_hide_timer_started_ = false;
964 gfx::Rect ShelfLayoutManager::GetAutoHideShowShelfRegionInScreen() const {
965 gfx::Rect shelf_bounds_in_screen = shelf_->GetWindowBoundsInScreen();
966 gfx::Vector2d offset = SelectValueForShelfAlignment(
967 gfx::Vector2d(0, shelf_bounds_in_screen.height()),
968 gfx::Vector2d(-kMaxAutoHideShowShelfRegionSize, 0),
969 gfx::Vector2d(shelf_bounds_in_screen.width(), 0),
970 gfx::Vector2d(0, -kMaxAutoHideShowShelfRegionSize));
972 gfx::Rect show_shelf_region_in_screen = shelf_bounds_in_screen;
973 show_shelf_region_in_screen += offset;
974 if (IsHorizontalAlignment())
975 show_shelf_region_in_screen.set_height(kMaxAutoHideShowShelfRegionSize);
977 show_shelf_region_in_screen.set_width(kMaxAutoHideShowShelfRegionSize);
979 // TODO: Figure out if we need any special handling when the keyboard is
981 return show_shelf_region_in_screen;
984 ShelfAutoHideState ShelfLayoutManager::CalculateAutoHideState(
985 ShelfVisibilityState visibility_state) const {
986 if (visibility_state != SHELF_AUTO_HIDE || !shelf_)
987 return SHELF_AUTO_HIDE_HIDDEN;
989 Shell* shell = Shell::GetInstance();
990 if (shell->GetAppListTargetVisibility())
991 return SHELF_AUTO_HIDE_SHOWN;
993 if (shelf_->status_area_widget() &&
994 shelf_->status_area_widget()->ShouldShowLauncher())
995 return SHELF_AUTO_HIDE_SHOWN;
997 if (shelf_->launcher() && shelf_->launcher()->IsShowingMenu())
998 return SHELF_AUTO_HIDE_SHOWN;
1000 if (shelf_->launcher() && shelf_->launcher()->IsShowingOverflowBubble())
1001 return SHELF_AUTO_HIDE_SHOWN;
1003 if (shelf_->IsActive() || shelf_->status_area_widget()->IsActive())
1004 return SHELF_AUTO_HIDE_SHOWN;
1006 const std::vector<aura::Window*> windows =
1007 ash::MruWindowTracker::BuildWindowList(false);
1009 // Process the window list and check if there are any visible windows.
1010 bool visible_window = false;
1011 for (size_t i = 0; i < windows.size(); ++i) {
1012 if (windows[i] && windows[i]->IsVisible() &&
1013 !wm::GetWindowState(windows[i])->IsMinimized() &&
1014 root_window_ == windows[i]->GetRootWindow()) {
1015 visible_window = true;
1019 // If there are no visible windows do not hide the shelf.
1020 if (!visible_window)
1021 return SHELF_AUTO_HIDE_SHOWN;
1023 if (gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS)
1024 return gesture_drag_auto_hide_state_;
1026 // Don't show if the user is dragging the mouse.
1027 if (auto_hide_event_filter_.get() && auto_hide_event_filter_->in_mouse_drag())
1028 return SHELF_AUTO_HIDE_HIDDEN;
1030 // Ignore the mouse position if mouse events are disabled.
1031 aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
1032 shelf_->GetNativeWindow()->GetRootWindow());
1033 if (!cursor_client->IsMouseEventsEnabled())
1034 return SHELF_AUTO_HIDE_HIDDEN;
1036 gfx::Rect shelf_region = shelf_->GetWindowBoundsInScreen();
1037 if (shelf_->status_area_widget() &&
1038 shelf_->status_area_widget()->IsMessageBubbleShown() &&
1040 // Increase the the hit test area to prevent the shelf from disappearing
1041 // when the mouse is over the bubble gap.
1042 ShelfAlignment alignment = GetAlignment();
1043 shelf_region.Inset(alignment == SHELF_ALIGNMENT_RIGHT ?
1044 -kNotificationBubbleGapHeight : 0,
1045 alignment == SHELF_ALIGNMENT_BOTTOM ?
1046 -kNotificationBubbleGapHeight : 0,
1047 alignment == SHELF_ALIGNMENT_LEFT ?
1048 -kNotificationBubbleGapHeight : 0,
1049 alignment == SHELF_ALIGNMENT_TOP ?
1050 -kNotificationBubbleGapHeight : 0);
1053 gfx::Point cursor_position_in_screen =
1054 Shell::GetScreen()->GetCursorScreenPoint();
1055 if (shelf_region.Contains(cursor_position_in_screen))
1056 return SHELF_AUTO_HIDE_SHOWN;
1058 // When the shelf is auto hidden and the shelf is on the boundary between two
1059 // displays, it is hard to trigger showing the shelf. For instance, if a
1060 // user's primary display is left of their secondary display, it is hard to
1061 // unautohide a left aligned shelf on the secondary display.
1062 // It is hard because:
1063 // - It is hard to stop the cursor in the shelf "light bar" and not overshoot.
1064 // - The cursor is warped to the other display if the cursor gets to the edge
1066 // Show the shelf if the cursor started on the shelf and the user overshot the
1067 // shelf slightly to make it easier to show the shelf in this situation. We
1068 // do not check |auto_hide_timer_|.IsRunning() because it returns false when
1069 // the timer's task is running.
1070 if ((state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN ||
1071 mouse_over_shelf_when_auto_hide_timer_started_) &&
1072 GetAutoHideShowShelfRegionInScreen().Contains(
1073 cursor_position_in_screen)) {
1074 return SHELF_AUTO_HIDE_SHOWN;
1077 return SHELF_AUTO_HIDE_HIDDEN;
1080 void ShelfLayoutManager::UpdateHitTestBounds() {
1081 gfx::Insets mouse_insets;
1082 gfx::Insets touch_insets;
1083 if (state_.visibility_state == SHELF_VISIBLE) {
1084 // Let clicks at the very top of the launcher through so windows can be
1085 // resized with the bottom-right corner and bottom edge.
1086 mouse_insets = GetInsetsForAlignment(kWorkspaceAreaVisibleInset);
1087 } else if (state_.visibility_state == SHELF_AUTO_HIDE) {
1088 // Extend the touch hit target out a bit to allow users to drag shelf out
1090 touch_insets = GetInsetsForAlignment(-kWorkspaceAreaAutoHideInset);
1093 if (shelf_ && shelf_->GetNativeWindow())
1094 shelf_->GetNativeWindow()->SetHitTestBoundsOverrideOuter(mouse_insets,
1096 shelf_->status_area_widget()->GetNativeWindow()->
1097 SetHitTestBoundsOverrideOuter(mouse_insets, touch_insets);
1100 bool ShelfLayoutManager::IsShelfWindow(aura::Window* window) {
1103 return (shelf_ && shelf_->GetNativeWindow()->Contains(window)) ||
1104 (shelf_->status_area_widget() &&
1105 shelf_->status_area_widget()->GetNativeWindow()->Contains(window));
1108 int ShelfLayoutManager::GetWorkAreaSize(const State& state, int size) const {
1109 if (state.visibility_state == SHELF_VISIBLE)
1111 if (state.visibility_state == SHELF_AUTO_HIDE)
1112 return kAutoHideSize;
1116 gfx::Rect ShelfLayoutManager::GetAvailableBounds() const {
1117 gfx::Rect bounds(root_window_->bounds());
1118 bounds.set_height(bounds.height() - keyboard_bounds_.height());
1122 void ShelfLayoutManager::OnKeyboardBoundsChanging(
1123 const gfx::Rect& keyboard_bounds) {
1124 keyboard_bounds_ = keyboard_bounds;
1128 void ShelfLayoutManager::OnDockBoundsChanging(
1129 const gfx::Rect& dock_bounds,
1130 DockedWindowLayoutManagerObserver::Reason reason) {
1131 // Skip shelf layout in case docked notification originates from this class.
1132 if (reason == DISPLAY_INSETS_CHANGED)
1134 if (dock_bounds_ != dock_bounds) {
1135 dock_bounds_ = dock_bounds;
1140 void ShelfLayoutManager::OnLockStateEvent(LockStateObserver::EventType event) {
1141 if (event == EVENT_LOCK_ANIMATION_STARTED) {
1142 // Hide the status area widget (using auto hide animation).
1143 base::AutoReset<ShelfVisibilityState> state(&state_.visibility_state,
1145 TargetBounds target_bounds;
1146 CalculateTargetBounds(state_, &target_bounds);
1147 UpdateBoundsAndOpacity(target_bounds, true, NULL);
1151 gfx::Insets ShelfLayoutManager::GetInsetsForAlignment(int distance) const {
1152 switch (GetAlignment()) {
1153 case SHELF_ALIGNMENT_BOTTOM:
1154 return gfx::Insets(distance, 0, 0, 0);
1155 case SHELF_ALIGNMENT_LEFT:
1156 return gfx::Insets(0, 0, 0, distance);
1157 case SHELF_ALIGNMENT_RIGHT:
1158 return gfx::Insets(0, distance, 0, 0);
1159 case SHELF_ALIGNMENT_TOP:
1160 return gfx::Insets(0, 0, distance, 0);
1163 return gfx::Insets();
1166 } // namespace internal