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.
4
5#include "ash/shelf/shelf_layout_manager.h"
6
7#include <algorithm>
8#include <cmath>
9#include <cstring>
10#include <string>
11#include <vector>
12
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"
50
51namespace ash {
52namespace internal {
53
54namespace {
55
56// Delay before showing the launcher. This is after the mouse stops moving.
57const int kAutoHideDelayMS = 200;
58
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.
62const int kNotificationBubbleGapHeight = 6;
63
64// The maximum size of the region on the display opposing the shelf managed by
65// this ShelfLayoutManager which can trigger showing the shelf.
66// For instance:
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.
74const int kMaxAutoHideShowShelfRegionSize = 10;
75
76ui::Layer* GetLayer(views::Widget* widget) {
77  return widget->GetNativeView()->layer();
78}
79
80bool IsDraggingTrayEnabled() {
81  static bool dragging_tray_allowed = CommandLine::ForCurrentProcess()->
82      HasSwitch(ash::switches::kAshEnableTrayDragging);
83  return dragging_tray_allowed;
84}
85
86}  // namespace
87
88// static
89const int ShelfLayoutManager::kWorkspaceAreaVisibleInset = 2;
90
91// static
92const int ShelfLayoutManager::kWorkspaceAreaAutoHideInset = 5;
93
94// static
95const int ShelfLayoutManager::kAutoHideSize = 3;
96
97// static
98const int ShelfLayoutManager::kShelfSize = 47;
99
100// static
101const int ShelfLayoutManager::kShelfItemInset = 3;
102
103int ShelfLayoutManager::GetPreferredShelfSize() {
104  return ash::switches::UseAlternateShelfLayout() ?
105      ShelfLayoutManager::kShelfSize : kLauncherPreferredSize;
106}
107
108// ShelfLayoutManager::AutoHideEventFilter -------------------------------------
109
110// Notifies ShelfLayoutManager any time the mouse moves.
111class ShelfLayoutManager::AutoHideEventFilter : public ui::EventHandler {
112 public:
113  explicit AutoHideEventFilter(ShelfLayoutManager* shelf);
114  virtual ~AutoHideEventFilter();
115
116  // Returns true if the last mouse event was a mouse drag.
117  bool in_mouse_drag() const { return in_mouse_drag_; }
118
119  // Overridden from ui::EventHandler:
120  virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
121  virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
122
123 private:
124  ShelfLayoutManager* shelf_;
125  bool in_mouse_drag_;
126  ShelfGestureHandler gesture_handler_;
127  DISALLOW_COPY_AND_ASSIGN(AutoHideEventFilter);
128};
129
130ShelfLayoutManager::AutoHideEventFilter::AutoHideEventFilter(
131    ShelfLayoutManager* shelf)
132    : shelf_(shelf),
133      in_mouse_drag_(false) {
134  Shell::GetInstance()->AddPreTargetHandler(this);
135}
136
137ShelfLayoutManager::AutoHideEventFilter::~AutoHideEventFilter() {
138  Shell::GetInstance()->RemovePreTargetHandler(this);
139}
140
141void 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();
151  return;
152}
153
154void 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();
159  }
160}
161
162// ShelfLayoutManager:UpdateShelfObserver --------------------------------------
163
164// UpdateShelfObserver is used to delay updating the background until the
165// animation completes.
166class ShelfLayoutManager::UpdateShelfObserver
167    : public ui::ImplicitAnimationObserver {
168 public:
169  explicit UpdateShelfObserver(ShelfLayoutManager* shelf) : shelf_(shelf) {
170    shelf_->update_shelf_observer_ = this;
171  }
172
173  void Detach() {
174    shelf_ = NULL;
175  }
176
177  virtual void OnImplicitAnimationsCompleted() OVERRIDE {
178    if (shelf_)
179      shelf_->UpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
180    delete this;
181  }
182
183 private:
184  virtual ~UpdateShelfObserver() {
185    if (shelf_)
186      shelf_->update_shelf_observer_ = NULL;
187  }
188
189  // Shelf we're in. NULL if deleted before we're deleted.
190  ShelfLayoutManager* shelf_;
191
192  DISALLOW_COPY_AND_ASSIGN(UpdateShelfObserver);
193};
194
195// ShelfLayoutManager ----------------------------------------------------------
196
197ShelfLayoutManager::ShelfLayoutManager(ShelfWidget* shelf)
198    : root_window_(shelf->GetNativeView()->GetRootWindow()),
199      updating_bounds_(false),
200      auto_hide_behavior_(SHELF_AUTO_HIDE_BEHAVIOR_NEVER),
201      alignment_(SHELF_ALIGNMENT_BOTTOM),
202      shelf_(shelf),
203      workspace_controller_(NULL),
204      window_overlaps_shelf_(false),
205      mouse_over_shelf_when_auto_hide_timer_started_(false),
206      bezel_event_filter_(new ShelfBezelEventFilter(this)),
207      gesture_drag_status_(GESTURE_DRAG_NONE),
208      gesture_drag_amount_(0.f),
209      gesture_drag_auto_hide_state_(SHELF_AUTO_HIDE_SHOWN),
210      update_shelf_observer_(NULL) {
211  Shell::GetInstance()->AddShellObserver(this);
212  Shell::GetInstance()->lock_state_controller()->AddObserver(this);
213  aura::client::GetActivationClient(root_window_)->AddObserver(this);
214}
215
216ShelfLayoutManager::~ShelfLayoutManager() {
217  if (update_shelf_observer_)
218    update_shelf_observer_->Detach();
219
220  FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_, WillDeleteShelf());
221  Shell::GetInstance()->RemoveShellObserver(this);
222  Shell::GetInstance()->lock_state_controller()->RemoveObserver(this);
223  aura::client::GetActivationClient(root_window_)->RemoveObserver(this);
224}
225
226void ShelfLayoutManager::SetAutoHideBehavior(ShelfAutoHideBehavior behavior) {
227  if (auto_hide_behavior_ == behavior)
228    return;
229  auto_hide_behavior_ = behavior;
230  UpdateVisibilityState();
231  FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
232                    OnAutoHideBehaviorChanged(root_window_,
233                                              auto_hide_behavior_));
234}
235
236void ShelfLayoutManager::PrepareForShutdown() {
237  // Clear all event filters, otherwise sometimes those filters may catch
238  // synthesized mouse event and cause crashes during the shutdown.
239  set_workspace_controller(NULL);
240  auto_hide_event_filter_.reset();
241  bezel_event_filter_.reset();
242}
243
244bool ShelfLayoutManager::IsVisible() const {
245  // status_area_widget() may be NULL during the shutdown.
246  return shelf_->status_area_widget() &&
247      shelf_->status_area_widget()->IsVisible() &&
248      (state_.visibility_state == SHELF_VISIBLE ||
249       (state_.visibility_state == SHELF_AUTO_HIDE &&
250        state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN));
251}
252
253bool ShelfLayoutManager::SetAlignment(ShelfAlignment alignment) {
254  if (alignment_ == alignment)
255    return false;
256
257  // This should not be called during the lock screen transitions.
258  DCHECK(!Shell::GetInstance()->session_state_delegate()->IsScreenLocked());
259  alignment_ = alignment;
260  shelf_->SetAlignment(alignment);
261  LayoutShelf();
262  return true;
263}
264
265ShelfAlignment ShelfLayoutManager::GetAlignment() const {
266  // When the screen is locked, the shelf is forced into bottom alignment.
267  if (Shell::GetInstance()->session_state_delegate()->IsScreenLocked())
268    return SHELF_ALIGNMENT_BOTTOM;
269  return alignment_;
270}
271
272gfx::Rect ShelfLayoutManager::GetIdealBounds() {
273  gfx::Rect bounds(
274      ScreenAsh::GetDisplayBoundsInParent(shelf_->GetNativeView()));
275  int width = 0, height = 0;
276  GetShelfSize(&width, &height);
277  return SelectValueForShelfAlignment(
278      gfx::Rect(bounds.x(), bounds.bottom() - height, bounds.width(), height),
279      gfx::Rect(bounds.x(), bounds.y(), width, bounds.height()),
280      gfx::Rect(bounds.right() - width, bounds.y(), width, bounds.height()),
281      gfx::Rect(bounds.x(), bounds.y(), bounds.width(), height));
282}
283
284void ShelfLayoutManager::LayoutShelf() {
285  TargetBounds target_bounds;
286  CalculateTargetBounds(state_, &target_bounds);
287  UpdateBoundsAndOpacity(target_bounds, false, NULL);
288
289  if (shelf_->launcher()) {
290    // This is not part of UpdateBoundsAndOpacity() because
291    // SetShelfViewBounds() sets the bounds immediately and does not animate.
292    // The height of the ShelfView for a horizontal shelf and the width of
293    // the ShelfView for a vertical shelf are set when |shelf_|'s bounds
294    // are changed via UpdateBoundsAndOpacity(). This sets the origin and the
295    // dimension in the other direction.
296    shelf_->launcher()->SetShelfViewBounds(
297        target_bounds.launcher_bounds_in_shelf);
298  }
299}
300
301ShelfVisibilityState ShelfLayoutManager::CalculateShelfVisibility() {
302  switch(auto_hide_behavior_) {
303    case SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS:
304      return SHELF_AUTO_HIDE;
305    case SHELF_AUTO_HIDE_BEHAVIOR_NEVER:
306      return SHELF_VISIBLE;
307    case SHELF_AUTO_HIDE_ALWAYS_HIDDEN:
308      return SHELF_HIDDEN;
309  }
310  return SHELF_VISIBLE;
311}
312
313void ShelfLayoutManager::UpdateVisibilityState() {
314  // Bail out early when there is no |workspace_controller_|, which happens
315  // during shutdown after PrepareForShutdown.
316  if (!workspace_controller_)
317    return;
318
319  if (Shell::GetInstance()->session_state_delegate()->IsScreenLocked()) {
320    SetState(SHELF_VISIBLE);
321  } else {
322    // TODO(zelidrag): Verify shelf drag animation still shows on the device
323    // when we are in SHELF_AUTO_HIDE_ALWAYS_HIDDEN.
324    WorkspaceWindowState window_state(workspace_controller_->GetWindowState());
325    switch (window_state) {
326      case WORKSPACE_WINDOW_STATE_FULL_SCREEN: {
327        const aura::Window* fullscreen_window = GetRootWindowController(
328            root_window_)->GetWindowForFullscreenMode();
329        if (fullscreen_window && wm::GetWindowState(fullscreen_window)->
330                hide_shelf_when_fullscreen()) {
331          SetState(SHELF_HIDDEN);
332        } else {
333          // The shelf is sometimes not hidden when in immersive fullscreen.
334          // Force the shelf to be auto hidden in this case.
335          SetState(SHELF_AUTO_HIDE);
336        }
337        break;
338      }
339
340      case WORKSPACE_WINDOW_STATE_MAXIMIZED:
341        SetState(CalculateShelfVisibility());
342        break;
343
344      case WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF:
345      case WORKSPACE_WINDOW_STATE_DEFAULT:
346        SetState(CalculateShelfVisibility());
347        SetWindowOverlapsShelf(window_state ==
348                               WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF);
349        break;
350    }
351  }
352}
353
354void ShelfLayoutManager::UpdateAutoHideState() {
355  ShelfAutoHideState auto_hide_state =
356      CalculateAutoHideState(state_.visibility_state);
357  if (auto_hide_state != state_.auto_hide_state) {
358    if (auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
359      // Hides happen immediately.
360      SetState(state_.visibility_state);
361    } else {
362      if (!auto_hide_timer_.IsRunning()) {
363        mouse_over_shelf_when_auto_hide_timer_started_ =
364            shelf_->GetWindowBoundsInScreen().Contains(
365                Shell::GetScreen()->GetCursorScreenPoint());
366      }
367      auto_hide_timer_.Start(
368          FROM_HERE,
369          base::TimeDelta::FromMilliseconds(kAutoHideDelayMS),
370          this, &ShelfLayoutManager::UpdateAutoHideStateNow);
371    }
372  } else {
373    StopAutoHideTimer();
374  }
375}
376
377void ShelfLayoutManager::SetWindowOverlapsShelf(bool value) {
378  window_overlaps_shelf_ = value;
379  UpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
380}
381
382void ShelfLayoutManager::AddObserver(ShelfLayoutManagerObserver* observer) {
383  observers_.AddObserver(observer);
384}
385
386void ShelfLayoutManager::RemoveObserver(ShelfLayoutManagerObserver* observer) {
387  observers_.RemoveObserver(observer);
388}
389
390////////////////////////////////////////////////////////////////////////////////
391// ShelfLayoutManager, Gesture dragging:
392
393void ShelfLayoutManager::StartGestureDrag(const ui::GestureEvent& gesture) {
394  gesture_drag_status_ = GESTURE_DRAG_IN_PROGRESS;
395  gesture_drag_amount_ = 0.f;
396  gesture_drag_auto_hide_state_ = visibility_state() == SHELF_AUTO_HIDE ?
397      auto_hide_state() : SHELF_AUTO_HIDE_SHOWN;
398  UpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
399}
400
401ShelfLayoutManager::DragState ShelfLayoutManager::UpdateGestureDrag(
402    const ui::GestureEvent& gesture) {
403  bool horizontal = IsHorizontalAlignment();
404  gesture_drag_amount_ += horizontal ? gesture.details().scroll_y() :
405                                       gesture.details().scroll_x();
406  LayoutShelf();
407
408  // Start reveling the status menu when:
409  //   - dragging up on an already visible shelf
410  //   - dragging up on a hidden shelf, but it is currently completely visible.
411  if (horizontal && gesture.details().scroll_y() < 0) {
412    int min_height = 0;
413    if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_HIDDEN && shelf_)
414      min_height = shelf_->GetContentsView()->GetPreferredSize().height();
415
416    if (min_height < shelf_->GetWindowBoundsInScreen().height() &&
417        gesture.root_location().x() >=
418        shelf_->status_area_widget()->GetWindowBoundsInScreen().x() &&
419        IsDraggingTrayEnabled())
420      return DRAG_TRAY;
421  }
422
423  return DRAG_SHELF;
424}
425
426void ShelfLayoutManager::CompleteGestureDrag(const ui::GestureEvent& gesture) {
427  bool horizontal = IsHorizontalAlignment();
428  bool should_change = false;
429  if (gesture.type() == ui::ET_GESTURE_SCROLL_END) {
430    // The visibility of the shelf changes only if the shelf was dragged X%
431    // along the correct axis. If the shelf was already visible, then the
432    // direction of the drag does not matter.
433    const float kDragHideThreshold = 0.4f;
434    gfx::Rect bounds = GetIdealBounds();
435    float drag_ratio = fabs(gesture_drag_amount_) /
436                       (horizontal ?  bounds.height() : bounds.width());
437    if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN) {
438      should_change = drag_ratio > kDragHideThreshold;
439    } else {
440      bool correct_direction = false;
441      switch (GetAlignment()) {
442        case SHELF_ALIGNMENT_BOTTOM:
443        case SHELF_ALIGNMENT_RIGHT:
444          correct_direction = gesture_drag_amount_ < 0;
445          break;
446        case SHELF_ALIGNMENT_LEFT:
447        case SHELF_ALIGNMENT_TOP:
448          correct_direction = gesture_drag_amount_ > 0;
449          break;
450      }
451      should_change = correct_direction && drag_ratio > kDragHideThreshold;
452    }
453  } else if (gesture.type() == ui::ET_SCROLL_FLING_START) {
454    if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN) {
455      should_change = horizontal ? fabs(gesture.details().velocity_y()) > 0 :
456                                   fabs(gesture.details().velocity_x()) > 0;
457    } else {
458      should_change = SelectValueForShelfAlignment(
459          gesture.details().velocity_y() < 0,
460          gesture.details().velocity_x() > 0,
461          gesture.details().velocity_x() < 0,
462          gesture.details().velocity_y() > 0);
463    }
464  } else {
465    NOTREACHED();
466  }
467
468  if (!should_change) {
469    CancelGestureDrag();
470    return;
471  }
472  if (shelf_) {
473    shelf_->Deactivate();
474    shelf_->status_area_widget()->Deactivate();
475  }
476  gesture_drag_auto_hide_state_ =
477      gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN ?
478      SHELF_AUTO_HIDE_HIDDEN : SHELF_AUTO_HIDE_SHOWN;
479  ShelfAutoHideBehavior new_auto_hide_behavior =
480      gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN ?
481      SHELF_AUTO_HIDE_BEHAVIOR_NEVER : SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
482
483  // When in fullscreen and the shelf is forced to be auto hidden, the auto hide
484  // behavior affects neither the visibility state nor the auto hide state. Set
485  // |gesture_drag_status_| to GESTURE_DRAG_COMPLETE_IN_PROGRESS to set the auto
486  // hide state to |gesture_drag_auto_hide_state_|.
487  gesture_drag_status_ = GESTURE_DRAG_COMPLETE_IN_PROGRESS;
488  if (auto_hide_behavior_ != new_auto_hide_behavior)
489    SetAutoHideBehavior(new_auto_hide_behavior);
490  else
491    UpdateVisibilityState();
492  gesture_drag_status_ = GESTURE_DRAG_NONE;
493}
494
495void ShelfLayoutManager::CancelGestureDrag() {
496  gesture_drag_status_ = GESTURE_DRAG_CANCEL_IN_PROGRESS;
497  UpdateVisibilityState();
498  gesture_drag_status_ = GESTURE_DRAG_NONE;
499}
500
501////////////////////////////////////////////////////////////////////////////////
502// ShelfLayoutManager, aura::LayoutManager implementation:
503
504void ShelfLayoutManager::OnWindowResized() {
505  LayoutShelf();
506}
507
508void ShelfLayoutManager::OnWindowAddedToLayout(aura::Window* child) {
509}
510
511void ShelfLayoutManager::OnWillRemoveWindowFromLayout(aura::Window* child) {
512}
513
514void ShelfLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) {
515}
516
517void ShelfLayoutManager::OnChildWindowVisibilityChanged(aura::Window* child,
518                                                        bool visible) {
519}
520
521void ShelfLayoutManager::SetChildBounds(aura::Window* child,
522                                        const gfx::Rect& requested_bounds) {
523  SetChildBoundsDirect(child, requested_bounds);
524  // We may contain other widgets (such as frame maximize bubble) but they don't
525  // effect the layout in anyway.
526  if (!updating_bounds_ &&
527      ((shelf_->GetNativeView() == child) ||
528       (shelf_->status_area_widget()->GetNativeView() == child))) {
529    LayoutShelf();
530  }
531}
532
533void ShelfLayoutManager::OnLockStateChanged(bool locked) {
534  // Force the shelf to layout for alignment (bottom if locked, restore
535  // the previous alignment otherwise).
536  shelf_->SetAlignment(locked ? SHELF_ALIGNMENT_BOTTOM : alignment_);
537  UpdateVisibilityState();
538  LayoutShelf();
539}
540
541void ShelfLayoutManager::OnWindowActivated(aura::Window* gained_active,
542                                           aura::Window* lost_active) {
543  UpdateAutoHideStateNow();
544}
545
546bool ShelfLayoutManager::IsHorizontalAlignment() const {
547  return GetAlignment() == SHELF_ALIGNMENT_BOTTOM ||
548         GetAlignment() == SHELF_ALIGNMENT_TOP;
549}
550
551// static
552ShelfLayoutManager* ShelfLayoutManager::ForLauncher(aura::Window* window) {
553  ShelfWidget* shelf = RootWindowController::ForLauncher(window)->shelf();
554  return shelf ? shelf->shelf_layout_manager() : NULL;
555}
556
557////////////////////////////////////////////////////////////////////////////////
558// ShelfLayoutManager, private:
559
560ShelfLayoutManager::TargetBounds::TargetBounds() : opacity(0.0f) {}
561ShelfLayoutManager::TargetBounds::~TargetBounds() {}
562
563void ShelfLayoutManager::SetState(ShelfVisibilityState visibility_state) {
564  if (!shelf_->GetNativeView())
565    return;
566
567  State state;
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;
574
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.
577  bool force_update =
578      (gesture_drag_status_ == GESTURE_DRAG_CANCEL_IN_PROGRESS ||
579       gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS);
580
581  if (!force_update && state_.Equals(state))
582    return;  // Nothing changed.
583
584  FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
585                    WillChangeVisibilityState(visibility_state));
586
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));
592  } else {
593    auto_hide_event_filter_.reset(NULL);
594  }
595
596  StopAutoHideTimer();
597
598  State old_state = state_;
599  state_ = state;
600
601  BackgroundAnimatorChangeType change_type = BACKGROUND_CHANGE_ANIMATE;
602  bool delay_background_change = false;
603
604  // Do not animate the background when:
605  // - Going from a hidden / auto hidden shelf in fullscreen to a visible shelf
606  //   in maximized mode.
607  // - Going from an auto hidden shelf in maximized mode to a visible shelf in
608  //   maximized mode.
609  if (state.visibility_state == SHELF_VISIBLE &&
610      state.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED &&
611      old_state.visibility_state != SHELF_VISIBLE) {
612    change_type = BACKGROUND_CHANGE_IMMEDIATE;
613  } else {
614    // Delay the animation when the shelf was hidden, and has just been made
615    // visible (e.g. using a gesture-drag).
616    if (state.visibility_state == SHELF_VISIBLE &&
617        old_state.visibility_state == SHELF_AUTO_HIDE &&
618        old_state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
619      delay_background_change = true;
620    }
621  }
622
623  if (delay_background_change) {
624    if (update_shelf_observer_)
625      update_shelf_observer_->Detach();
626    // UpdateShelfBackground deletes itself when the animation is done.
627    update_shelf_observer_ = new UpdateShelfObserver(this);
628  } else {
629    UpdateShelfBackground(change_type);
630  }
631
632  shelf_->SetDimsShelf(
633      state.visibility_state == SHELF_VISIBLE &&
634      state.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED);
635
636  TargetBounds target_bounds;
637  CalculateTargetBounds(state_, &target_bounds);
638  UpdateBoundsAndOpacity(target_bounds, true,
639      delay_background_change ? update_shelf_observer_ : NULL);
640
641  // OnAutoHideStateChanged Should be emitted when:
642  //  - firstly state changed to auto-hide from other state
643  //  - or, auto_hide_state has changed
644  if ((old_state.visibility_state != state_.visibility_state &&
645       state_.visibility_state == SHELF_AUTO_HIDE) ||
646      old_state.auto_hide_state != state_.auto_hide_state) {
647    FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
648                      OnAutoHideStateChanged(state_.auto_hide_state));
649  }
650}
651
652void ShelfLayoutManager::UpdateBoundsAndOpacity(
653    const TargetBounds& target_bounds,
654    bool animate,
655    ui::ImplicitAnimationObserver* observer) {
656  base::AutoReset<bool> auto_reset_updating_bounds(&updating_bounds_, true);
657
658  ui::ScopedLayerAnimationSettings launcher_animation_setter(
659      GetLayer(shelf_)->GetAnimator());
660  ui::ScopedLayerAnimationSettings status_animation_setter(
661      GetLayer(shelf_->status_area_widget())->GetAnimator());
662  if (animate) {
663    launcher_animation_setter.SetTransitionDuration(
664        base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS));
665    launcher_animation_setter.SetTweenType(gfx::Tween::EASE_OUT);
666    launcher_animation_setter.SetPreemptionStrategy(
667        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
668    status_animation_setter.SetTransitionDuration(
669        base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS));
670    status_animation_setter.SetTweenType(gfx::Tween::EASE_OUT);
671    status_animation_setter.SetPreemptionStrategy(
672        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
673  } else {
674    StopAnimating();
675    launcher_animation_setter.SetTransitionDuration(base::TimeDelta());
676    status_animation_setter.SetTransitionDuration(base::TimeDelta());
677  }
678  if (observer)
679    status_animation_setter.AddObserver(observer);
680
681  GetLayer(shelf_)->SetOpacity(target_bounds.opacity);
682  shelf_->SetBounds(ScreenAsh::ConvertRectToScreen(
683       shelf_->GetNativeView()->parent(),
684       target_bounds.shelf_bounds_in_root));
685
686  GetLayer(shelf_->status_area_widget())->SetOpacity(
687      target_bounds.status_opacity);
688  // TODO(harrym): Once status area widget is a child view of shelf
689  // this can be simplified.
690  gfx::Rect status_bounds = target_bounds.status_bounds_in_shelf;
691  status_bounds.set_x(status_bounds.x() +
692                      target_bounds.shelf_bounds_in_root.x());
693  status_bounds.set_y(status_bounds.y() +
694                      target_bounds.shelf_bounds_in_root.y());
695  shelf_->status_area_widget()->SetBounds(
696      ScreenAsh::ConvertRectToScreen(
697          shelf_->status_area_widget()->GetNativeView()->parent(),
698          status_bounds));
699  Shell::GetInstance()->SetDisplayWorkAreaInsets(
700      root_window_, target_bounds.work_area_insets);
701  UpdateHitTestBounds();
702}
703
704void ShelfLayoutManager::StopAnimating() {
705  GetLayer(shelf_)->GetAnimator()->StopAnimating();
706  GetLayer(shelf_->status_area_widget())->GetAnimator()->StopAnimating();
707}
708
709void ShelfLayoutManager::GetShelfSize(int* width, int* height) {
710  *width = *height = 0;
711  gfx::Size status_size(
712      shelf_->status_area_widget()->GetWindowBoundsInScreen().size());
713  if (IsHorizontalAlignment())
714    *height = GetPreferredShelfSize();
715  else
716    *width = GetPreferredShelfSize();
717}
718
719void ShelfLayoutManager::AdjustBoundsBasedOnAlignment(int inset,
720                                                      gfx::Rect* bounds) const {
721  bounds->Inset(SelectValueForShelfAlignment(
722      gfx::Insets(0, 0, inset, 0),
723      gfx::Insets(0, inset, 0, 0),
724      gfx::Insets(0, 0, 0, inset),
725      gfx::Insets(inset, 0, 0, 0)));
726}
727
728void ShelfLayoutManager::CalculateTargetBounds(
729    const State& state,
730    TargetBounds* target_bounds) {
731  const gfx::Rect available_bounds(GetAvailableBounds());
732  gfx::Rect status_size(
733      shelf_->status_area_widget()->GetWindowBoundsInScreen().size());
734  int shelf_width = 0, shelf_height = 0;
735  GetShelfSize(&shelf_width, &shelf_height);
736  if (IsHorizontalAlignment())
737    shelf_width = available_bounds.width();
738  else
739    shelf_height = available_bounds.height();
740
741  if (state.visibility_state == SHELF_AUTO_HIDE &&
742      state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
743    // Auto-hidden shelf always starts with the default size. If a gesture-drag
744    // is in progress, then the call to UpdateTargetBoundsForGesture() below
745    // takes care of setting the height properly.
746    if (IsHorizontalAlignment())
747      shelf_height = kAutoHideSize;
748    else
749      shelf_width = kAutoHideSize;
750  } else if (state.visibility_state == SHELF_HIDDEN ||
751      !keyboard_bounds_.IsEmpty()) {
752    if (IsHorizontalAlignment())
753      shelf_height = 0;
754    else
755      shelf_width = 0;
756  }
757
758  target_bounds->shelf_bounds_in_root = SelectValueForShelfAlignment(
759      gfx::Rect(available_bounds.x(), available_bounds.bottom() - shelf_height,
760                    available_bounds.width(), shelf_height),
761      gfx::Rect(available_bounds.x(), available_bounds.y(),
762                    shelf_width, available_bounds.height()),
763      gfx::Rect(available_bounds.right() - shelf_width, available_bounds.y(),
764                    shelf_width, available_bounds.height()),
765      gfx::Rect(available_bounds.x(), available_bounds.y(),
766                    available_bounds.width(), shelf_height));
767
768  int status_inset = std::max(0, GetPreferredShelfSize() -
769      PrimaryAxisValue(status_size.height(), status_size.width()));
770
771  if (ash::switches::UseAlternateShelfLayout()) {
772    status_inset = 0;
773    if (IsHorizontalAlignment())
774      status_size.set_height(kShelfSize);
775    else
776      status_size.set_width(kShelfSize);
777  }
778
779  target_bounds->status_bounds_in_shelf = SelectValueForShelfAlignment(
780      gfx::Rect(base::i18n::IsRTL() ? 0 : shelf_width - status_size.width(),
781                    status_inset, status_size.width(), status_size.height()),
782      gfx::Rect(shelf_width - (status_size.width() + status_inset),
783                    shelf_height - status_size.height(), status_size.width(),
784                    status_size.height()),
785      gfx::Rect(status_inset, shelf_height - status_size.height(),
786                    status_size.width(), status_size.height()),
787      gfx::Rect(base::i18n::IsRTL() ? 0 : shelf_width - status_size.width(),
788                    shelf_height - (status_size.height() + status_inset),
789                    status_size.width(), status_size.height()));
790
791  target_bounds->work_area_insets = SelectValueForShelfAlignment(
792      gfx::Insets(0, 0, GetWorkAreaSize(state, shelf_height), 0),
793      gfx::Insets(0, GetWorkAreaSize(state, shelf_width), 0, 0),
794      gfx::Insets(0, 0, 0, GetWorkAreaSize(state, shelf_width)),
795      gfx::Insets(GetWorkAreaSize(state, shelf_height), 0, 0, 0));
796
797  // TODO(varkha): The functionality of managing insets for display areas
798  // should probably be pushed to a separate component. This would simplify or
799  // remove entirely the dependency on keyboard and dock.
800
801  // Also push in the work area inset for the keyboard if it is visible.
802  if (!keyboard_bounds_.IsEmpty()) {
803    gfx::Insets keyboard_insets(0, 0, keyboard_bounds_.height(), 0);
804    target_bounds->work_area_insets += keyboard_insets;
805  }
806
807  // Also push in the work area inset for the dock if it is visible.
808  if (!dock_bounds_.IsEmpty()) {
809    gfx::Insets dock_insets(
810        0, (dock_bounds_.x() > 0 ? 0 : dock_bounds_.width()),
811        0, (dock_bounds_.x() > 0 ? dock_bounds_.width() : 0));
812    target_bounds->work_area_insets += dock_insets;
813  }
814
815  target_bounds->opacity =
816      (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS ||
817       state.visibility_state == SHELF_VISIBLE ||
818       state.visibility_state == SHELF_AUTO_HIDE) ? 1.0f : 0.0f;
819  target_bounds->status_opacity =
820      (state.visibility_state == SHELF_AUTO_HIDE &&
821       state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN &&
822       gesture_drag_status_ != GESTURE_DRAG_IN_PROGRESS) ?
823      0.0f : target_bounds->opacity;
824
825  if (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS)
826    UpdateTargetBoundsForGesture(target_bounds);
827
828  // This needs to happen after calling UpdateTargetBoundsForGesture(), because
829  // that can change the size of the shelf.
830  target_bounds->launcher_bounds_in_shelf = SelectValueForShelfAlignment(
831      gfx::Rect(0, 0,
832                shelf_width - status_size.width(),
833                target_bounds->shelf_bounds_in_root.height()),
834      gfx::Rect(0, 0, target_bounds->shelf_bounds_in_root.width(),
835                shelf_height - status_size.height()),
836      gfx::Rect(0, 0, target_bounds->shelf_bounds_in_root.width(),
837                shelf_height - status_size.height()),
838      gfx::Rect(0, 0,
839                shelf_width - status_size.width(),
840                target_bounds->shelf_bounds_in_root.height()));
841}
842
843void ShelfLayoutManager::UpdateTargetBoundsForGesture(
844    TargetBounds* target_bounds) const {
845  CHECK_EQ(GESTURE_DRAG_IN_PROGRESS, gesture_drag_status_);
846  bool horizontal = IsHorizontalAlignment();
847  const gfx::Rect& available_bounds(root_window_->bounds());
848  int resistance_free_region = 0;
849
850  if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_HIDDEN &&
851      visibility_state() == SHELF_AUTO_HIDE &&
852      auto_hide_state() != SHELF_AUTO_HIDE_SHOWN) {
853    // If the shelf was hidden when the drag started (and the state hasn't
854    // changed since then, e.g. because the tray-menu was shown because of the
855    // drag), then allow the drag some resistance-free region at first to make
856    // sure the shelf sticks with the finger until the shelf is visible.
857    resistance_free_region = GetPreferredShelfSize() - kAutoHideSize;
858  }
859
860  bool resist = SelectValueForShelfAlignment(
861      gesture_drag_amount_ < -resistance_free_region,
862      gesture_drag_amount_ > resistance_free_region,
863      gesture_drag_amount_ < -resistance_free_region,
864      gesture_drag_amount_ > resistance_free_region);
865
866  float translate = 0.f;
867  if (resist) {
868    float diff = fabsf(gesture_drag_amount_) - resistance_free_region;
869    diff = std::min(diff, sqrtf(diff));
870    if (gesture_drag_amount_ < 0)
871      translate = -resistance_free_region - diff;
872    else
873      translate = resistance_free_region + diff;
874  } else {
875    translate = gesture_drag_amount_;
876  }
877
878  if (horizontal) {
879    // Move and size the launcher with the gesture.
880    int shelf_height = target_bounds->shelf_bounds_in_root.height() - translate;
881    shelf_height = std::max(shelf_height, kAutoHideSize);
882    target_bounds->shelf_bounds_in_root.set_height(shelf_height);
883    if (GetAlignment() == SHELF_ALIGNMENT_BOTTOM) {
884      target_bounds->shelf_bounds_in_root.set_y(
885          available_bounds.bottom() - shelf_height);
886    }
887
888    if (ash::switches::UseAlternateShelfLayout()) {
889      target_bounds->status_bounds_in_shelf.set_y(0);
890    } else {
891      // The statusbar should be in the center of the shelf.
892      gfx::Rect status_y = target_bounds->shelf_bounds_in_root;
893      status_y.set_y(0);
894      status_y.ClampToCenteredSize(
895          target_bounds->status_bounds_in_shelf.size());
896      target_bounds->status_bounds_in_shelf.set_y(status_y.y());
897    }
898  } else {
899    // Move and size the launcher with the gesture.
900    int shelf_width = target_bounds->shelf_bounds_in_root.width();
901    bool right_aligned = GetAlignment() == SHELF_ALIGNMENT_RIGHT;
902    if (right_aligned)
903      shelf_width -= translate;
904    else
905      shelf_width += translate;
906    shelf_width = std::max(shelf_width, kAutoHideSize);
907    target_bounds->shelf_bounds_in_root.set_width(shelf_width);
908    if (right_aligned) {
909      target_bounds->shelf_bounds_in_root.set_x(
910          available_bounds.right() - shelf_width);
911    }
912
913    if (ash::switches::UseAlternateShelfLayout()) {
914      if (right_aligned)
915        target_bounds->status_bounds_in_shelf.set_x(0);
916      else
917        target_bounds->status_bounds_in_shelf.set_x(
918            target_bounds->shelf_bounds_in_root.width() -
919            kShelfSize);
920    } else {
921      // The statusbar should be in the center of the shelf.
922      gfx::Rect status_x = target_bounds->shelf_bounds_in_root;
923      status_x.set_x(0);
924      status_x.ClampToCenteredSize(
925          target_bounds->status_bounds_in_shelf.size());
926      target_bounds->status_bounds_in_shelf.set_x(status_x.x());
927    }
928  }
929}
930
931void ShelfLayoutManager::UpdateShelfBackground(
932    BackgroundAnimatorChangeType type) {
933  const ShelfBackgroundType background_type(GetShelfBackgroundType());
934  shelf_->SetPaintsBackground(background_type, type);
935  FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
936                    OnBackgroundUpdated(background_type, type));
937}
938
939ShelfBackgroundType ShelfLayoutManager::GetShelfBackgroundType() const {
940  if (state_.visibility_state != SHELF_AUTO_HIDE &&
941      state_.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED) {
942    return SHELF_BACKGROUND_MAXIMIZED;
943  }
944
945  if (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS ||
946      (!state_.is_screen_locked && window_overlaps_shelf_) ||
947      (state_.visibility_state == SHELF_AUTO_HIDE)) {
948    return SHELF_BACKGROUND_OVERLAP;
949  }
950
951  return SHELF_BACKGROUND_DEFAULT;
952}
953
954void ShelfLayoutManager::UpdateAutoHideStateNow() {
955  SetState(state_.visibility_state);
956
957  // If the state did not change, the auto hide timer may still be running.
958  StopAutoHideTimer();
959}
960
961void ShelfLayoutManager::StopAutoHideTimer() {
962  auto_hide_timer_.Stop();
963  mouse_over_shelf_when_auto_hide_timer_started_ = false;
964}
965
966gfx::Rect ShelfLayoutManager::GetAutoHideShowShelfRegionInScreen() const {
967  gfx::Rect shelf_bounds_in_screen = shelf_->GetWindowBoundsInScreen();
968  gfx::Vector2d offset = SelectValueForShelfAlignment(
969      gfx::Vector2d(0, shelf_bounds_in_screen.height()),
970      gfx::Vector2d(-kMaxAutoHideShowShelfRegionSize, 0),
971      gfx::Vector2d(shelf_bounds_in_screen.width(), 0),
972      gfx::Vector2d(0, -kMaxAutoHideShowShelfRegionSize));
973
974  gfx::Rect show_shelf_region_in_screen = shelf_bounds_in_screen;
975  show_shelf_region_in_screen += offset;
976  if (IsHorizontalAlignment())
977    show_shelf_region_in_screen.set_height(kMaxAutoHideShowShelfRegionSize);
978  else
979    show_shelf_region_in_screen.set_width(kMaxAutoHideShowShelfRegionSize);
980
981  // TODO: Figure out if we need any special handling when the keyboard is
982  // visible.
983  return show_shelf_region_in_screen;
984}
985
986ShelfAutoHideState ShelfLayoutManager::CalculateAutoHideState(
987    ShelfVisibilityState visibility_state) const {
988  if (visibility_state != SHELF_AUTO_HIDE || !shelf_)
989    return SHELF_AUTO_HIDE_HIDDEN;
990
991  Shell* shell = Shell::GetInstance();
992  if (shell->GetAppListTargetVisibility())
993    return SHELF_AUTO_HIDE_SHOWN;
994
995  if (shelf_->status_area_widget() &&
996      shelf_->status_area_widget()->ShouldShowLauncher())
997    return SHELF_AUTO_HIDE_SHOWN;
998
999  if (shelf_->launcher() && shelf_->launcher()->IsShowingMenu())
1000    return SHELF_AUTO_HIDE_SHOWN;
1001
1002  if (shelf_->launcher() && shelf_->launcher()->IsShowingOverflowBubble())
1003    return SHELF_AUTO_HIDE_SHOWN;
1004
1005  if (shelf_->IsActive() || shelf_->status_area_widget()->IsActive())
1006    return SHELF_AUTO_HIDE_SHOWN;
1007
1008  const std::vector<aura::Window*> windows =
1009      ash::MruWindowTracker::BuildWindowList(false);
1010
1011  // Process the window list and check if there are any visible windows.
1012  bool visible_window = false;
1013  for (size_t i = 0; i < windows.size(); ++i) {
1014    if (windows[i] && windows[i]->IsVisible() &&
1015        !wm::GetWindowState(windows[i])->IsMinimized() &&
1016        root_window_ == windows[i]->GetRootWindow()) {
1017      visible_window = true;
1018      break;
1019    }
1020  }
1021  // If there are no visible windows do not hide the shelf.
1022  if (!visible_window)
1023    return SHELF_AUTO_HIDE_SHOWN;
1024
1025  if (gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS)
1026    return gesture_drag_auto_hide_state_;
1027
1028  // Don't show if the user is dragging the mouse.
1029  if (auto_hide_event_filter_.get() && auto_hide_event_filter_->in_mouse_drag())
1030    return SHELF_AUTO_HIDE_HIDDEN;
1031
1032  // Ignore the mouse position if mouse events are disabled.
1033  aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
1034      shelf_->GetNativeWindow()->GetRootWindow());
1035  if (!cursor_client->IsMouseEventsEnabled())
1036    return SHELF_AUTO_HIDE_HIDDEN;
1037
1038  gfx::Rect shelf_region = shelf_->GetWindowBoundsInScreen();
1039  if (shelf_->status_area_widget() &&
1040      shelf_->status_area_widget()->IsMessageBubbleShown() &&
1041      IsVisible()) {
1042    // Increase the the hit test area to prevent the shelf from disappearing
1043    // when the mouse is over the bubble gap.
1044    ShelfAlignment alignment = GetAlignment();
1045    shelf_region.Inset(alignment == SHELF_ALIGNMENT_RIGHT ?
1046                           -kNotificationBubbleGapHeight : 0,
1047                       alignment == SHELF_ALIGNMENT_BOTTOM ?
1048                           -kNotificationBubbleGapHeight : 0,
1049                       alignment == SHELF_ALIGNMENT_LEFT ?
1050                           -kNotificationBubbleGapHeight : 0,
1051                       alignment == SHELF_ALIGNMENT_TOP ?
1052                           -kNotificationBubbleGapHeight : 0);
1053  }
1054
1055  gfx::Point cursor_position_in_screen =
1056      Shell::GetScreen()->GetCursorScreenPoint();
1057  if (shelf_region.Contains(cursor_position_in_screen))
1058    return SHELF_AUTO_HIDE_SHOWN;
1059
1060  // When the shelf is auto hidden and the shelf is on the boundary between two
1061  // displays, it is hard to trigger showing the shelf. For instance, if a
1062  // user's primary display is left of their secondary display, it is hard to
1063  // unautohide a left aligned shelf on the secondary display.
1064  // It is hard because:
1065  // - It is hard to stop the cursor in the shelf "light bar" and not overshoot.
1066  // - The cursor is warped to the other display if the cursor gets to the edge
1067  //   of the display.
1068  // Show the shelf if the cursor started on the shelf and the user overshot the
1069  // shelf slightly to make it easier to show the shelf in this situation. We
1070  // do not check |auto_hide_timer_|.IsRunning() because it returns false when
1071  // the timer's task is running.
1072  if ((state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN ||
1073       mouse_over_shelf_when_auto_hide_timer_started_) &&
1074      GetAutoHideShowShelfRegionInScreen().Contains(
1075          cursor_position_in_screen)) {
1076    return SHELF_AUTO_HIDE_SHOWN;
1077  }
1078
1079  return SHELF_AUTO_HIDE_HIDDEN;
1080}
1081
1082void ShelfLayoutManager::UpdateHitTestBounds() {
1083  gfx::Insets mouse_insets;
1084  gfx::Insets touch_insets;
1085  if (state_.visibility_state == SHELF_VISIBLE) {
1086    // Let clicks at the very top of the launcher through so windows can be
1087    // resized with the bottom-right corner and bottom edge.
1088    mouse_insets = GetInsetsForAlignment(kWorkspaceAreaVisibleInset);
1089  } else if (state_.visibility_state == SHELF_AUTO_HIDE) {
1090    // Extend the touch hit target out a bit to allow users to drag shelf out
1091    // while hidden.
1092    touch_insets = GetInsetsForAlignment(-kWorkspaceAreaAutoHideInset);
1093  }
1094
1095  if (shelf_ && shelf_->GetNativeWindow())
1096    shelf_->GetNativeWindow()->SetHitTestBoundsOverrideOuter(mouse_insets,
1097                                                             touch_insets);
1098  shelf_->status_area_widget()->GetNativeWindow()->
1099      SetHitTestBoundsOverrideOuter(mouse_insets, touch_insets);
1100}
1101
1102bool ShelfLayoutManager::IsShelfWindow(aura::Window* window) {
1103  if (!window)
1104    return false;
1105  return (shelf_ && shelf_->GetNativeWindow()->Contains(window)) ||
1106      (shelf_->status_area_widget() &&
1107       shelf_->status_area_widget()->GetNativeWindow()->Contains(window));
1108}
1109
1110int ShelfLayoutManager::GetWorkAreaSize(const State& state, int size) const {
1111  if (state.visibility_state == SHELF_VISIBLE)
1112    return size;
1113  if (state.visibility_state == SHELF_AUTO_HIDE)
1114    return kAutoHideSize;
1115  return 0;
1116}
1117
1118gfx::Rect ShelfLayoutManager::GetAvailableBounds() const {
1119  gfx::Rect bounds(root_window_->bounds());
1120  bounds.set_height(bounds.height() - keyboard_bounds_.height());
1121  return bounds;
1122}
1123
1124void ShelfLayoutManager::OnKeyboardBoundsChanging(
1125    const gfx::Rect& keyboard_bounds) {
1126  keyboard_bounds_ = keyboard_bounds;
1127  OnWindowResized();
1128}
1129
1130void ShelfLayoutManager::OnDockBoundsChanging(
1131    const gfx::Rect& dock_bounds,
1132    DockedWindowLayoutManagerObserver::Reason reason) {
1133  // Skip shelf layout in case docked notification originates from this class.
1134  if (reason == DISPLAY_INSETS_CHANGED)
1135    return;
1136  if (dock_bounds_ != dock_bounds) {
1137    dock_bounds_ = dock_bounds;
1138    OnWindowResized();
1139    UpdateVisibilityState();
1140    UpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
1141  }
1142}
1143
1144void ShelfLayoutManager::OnLockStateEvent(LockStateObserver::EventType event) {
1145  if (event == EVENT_LOCK_ANIMATION_STARTED) {
1146    // Hide the status area widget (using auto hide animation).
1147    base::AutoReset<ShelfVisibilityState> state(&state_.visibility_state,
1148                                                SHELF_HIDDEN);
1149    TargetBounds target_bounds;
1150    CalculateTargetBounds(state_, &target_bounds);
1151    UpdateBoundsAndOpacity(target_bounds, true, NULL);
1152  }
1153}
1154
1155gfx::Insets ShelfLayoutManager::GetInsetsForAlignment(int distance) const {
1156  switch (GetAlignment()) {
1157    case SHELF_ALIGNMENT_BOTTOM:
1158      return gfx::Insets(distance, 0, 0, 0);
1159    case SHELF_ALIGNMENT_LEFT:
1160      return gfx::Insets(0, 0, 0, distance);
1161    case SHELF_ALIGNMENT_RIGHT:
1162      return gfx::Insets(0, distance, 0, 0);
1163    case SHELF_ALIGNMENT_TOP:
1164      return gfx::Insets(0, 0, distance, 0);
1165  }
1166  NOTREACHED();
1167  return gfx::Insets();
1168}
1169
1170}  // namespace internal
1171}  // namespace ash
1172