root/ash/shelf/shelf_layout_manager.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GetLayer
  2. IsDraggingTrayEnabled
  3. GetPreferredShelfSize
  4. in_mouse_drag
  5. in_mouse_drag_
  6. OnMouseEvent
  7. OnGestureEvent
  8. UpdateShelfObserver
  9. Detach
  10. OnImplicitAnimationsCompleted
  11. UpdateShelfObserver
  12. duration_override_in_ms_
  13. SetAutoHideBehavior
  14. PrepareForShutdown
  15. IsVisible
  16. SetAlignment
  17. GetAlignment
  18. GetIdealBounds
  19. LayoutShelf
  20. CalculateShelfVisibility
  21. UpdateVisibilityState
  22. UpdateAutoHideState
  23. SetWindowOverlapsShelf
  24. AddObserver
  25. RemoveObserver
  26. OnGestureEdgeSwipe
  27. StartGestureDrag
  28. UpdateGestureDrag
  29. CompleteGestureDrag
  30. CancelGestureDrag
  31. SetAnimationDurationOverride
  32. OnWindowResized
  33. OnWindowAddedToLayout
  34. OnWillRemoveWindowFromLayout
  35. OnWindowRemovedFromLayout
  36. OnChildWindowVisibilityChanged
  37. SetChildBounds
  38. OnLockStateChanged
  39. OnMaximizeModeStarted
  40. OnMaximizeModeEnded
  41. OnWindowActivated
  42. IsHorizontalAlignment
  43. ForShelf
  44. SetState
  45. UpdateBoundsAndOpacity
  46. StopAnimating
  47. GetShelfSize
  48. AdjustBoundsBasedOnAlignment
  49. CalculateTargetBounds
  50. UpdateTargetBoundsForGesture
  51. UpdateShelfBackground
  52. GetShelfBackgroundType
  53. UpdateAutoHideStateNow
  54. StopAutoHideTimer
  55. GetAutoHideShowShelfRegionInScreen
  56. CalculateAutoHideState
  57. IsShelfWindow
  58. GetWorkAreaSize
  59. GetAvailableBounds
  60. OnKeyboardBoundsChanging
  61. OnDockBoundsChanging
  62. OnLockStateEvent

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/shelf/shelf_layout_manager.h"

#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>

#include "ash/accelerators/accelerator_commands.h"
#include "ash/ash_switches.h"
#include "ash/root_window_controller.h"
#include "ash/screen_util.h"
#include "ash/session_state_delegate.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_bezel_event_filter.h"
#include "ash/shelf/shelf_constants.h"
#include "ash/shelf/shelf_layout_manager_observer.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/system/status_area_widget.h"
#include "ash/wm/gestures/shelf_gesture_handler.h"
#include "ash/wm/lock_state_controller.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/window_animations.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "ash/wm/workspace_controller.h"
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/command_line.h"
#include "base/i18n/rtl.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/base/ui_base_switches.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/events/event.h"
#include "ui/events/event_handler.h"
#include "ui/gfx/screen.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/public/activation_client.h"

namespace ash {
namespace {

// Delay before showing the shelf. This is after the mouse stops moving.
const int kAutoHideDelayMS = 200;

// To avoid hiding the shelf when the mouse transitions from a message bubble
// into the shelf, the hit test area is enlarged by this amount of pixels to
// keep the shelf from hiding.
const int kNotificationBubbleGapHeight = 6;

// The maximum size of the region on the display opposing the shelf managed by
// this ShelfLayoutManager which can trigger showing the shelf.
// For instance:
// - Primary display is left of secondary display.
// - Shelf is left aligned
// - This ShelfLayoutManager manages the shelf for the secondary display.
// |kMaxAutoHideShowShelfRegionSize| refers to the maximum size of the region
// from the right edge of the primary display which can trigger showing the
// auto hidden shelf. The region is used to make it easier to trigger showing
// the auto hidden shelf when the shelf is on the boundary between displays.
const int kMaxAutoHideShowShelfRegionSize = 10;

ui::Layer* GetLayer(views::Widget* widget) {
  return widget->GetNativeView()->layer();
}

bool IsDraggingTrayEnabled() {
  static bool dragging_tray_allowed = CommandLine::ForCurrentProcess()->
      HasSwitch(ash::switches::kAshEnableTrayDragging);
  return dragging_tray_allowed;
}

}  // namespace

// static
const int ShelfLayoutManager::kWorkspaceAreaVisibleInset = 2;

// static
const int ShelfLayoutManager::kWorkspaceAreaAutoHideInset = 5;

// static
const int ShelfLayoutManager::kAutoHideSize = 3;

// static
const int ShelfLayoutManager::kShelfSize = 47;

// static
const int ShelfLayoutManager::kShelfItemInset = 3;

int ShelfLayoutManager::GetPreferredShelfSize() {
  return ash::switches::UseAlternateShelfLayout() ?
      ShelfLayoutManager::kShelfSize : kShelfPreferredSize;
}

// ShelfLayoutManager::AutoHideEventFilter -------------------------------------

// Notifies ShelfLayoutManager any time the mouse moves.
class ShelfLayoutManager::AutoHideEventFilter : public ui::EventHandler {
 public:
  explicit AutoHideEventFilter(ShelfLayoutManager* shelf);
  virtual ~AutoHideEventFilter();

  // Returns true if the last mouse event was a mouse drag.
  bool in_mouse_drag() const { return in_mouse_drag_; }

  // Overridden from ui::EventHandler:
  virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
  virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;

 private:
  ShelfLayoutManager* shelf_;
  bool in_mouse_drag_;
  ShelfGestureHandler gesture_handler_;
  DISALLOW_COPY_AND_ASSIGN(AutoHideEventFilter);
};

ShelfLayoutManager::AutoHideEventFilter::AutoHideEventFilter(
    ShelfLayoutManager* shelf)
    : shelf_(shelf),
      in_mouse_drag_(false) {
  Shell::GetInstance()->AddPreTargetHandler(this);
}

ShelfLayoutManager::AutoHideEventFilter::~AutoHideEventFilter() {
  Shell::GetInstance()->RemovePreTargetHandler(this);
}

void ShelfLayoutManager::AutoHideEventFilter::OnMouseEvent(
    ui::MouseEvent* event) {
  // This also checks IsShelfWindow() to make sure we don't attempt to hide the
  // shelf if the mouse down occurs on the shelf.
  in_mouse_drag_ = (event->type() == ui::ET_MOUSE_DRAGGED ||
                    (in_mouse_drag_ && event->type() != ui::ET_MOUSE_RELEASED &&
                     event->type() != ui::ET_MOUSE_CAPTURE_CHANGED)) &&
      !shelf_->IsShelfWindow(static_cast<aura::Window*>(event->target()));
  if (event->type() == ui::ET_MOUSE_MOVED)
    shelf_->UpdateAutoHideState();
  return;
}

void ShelfLayoutManager::AutoHideEventFilter::OnGestureEvent(
    ui::GestureEvent* event) {
  if (shelf_->IsShelfWindow(static_cast<aura::Window*>(event->target()))) {
    if (gesture_handler_.ProcessGestureEvent(*event))
      event->StopPropagation();
  }
}

// ShelfLayoutManager:UpdateShelfObserver --------------------------------------

// UpdateShelfObserver is used to delay updating the background until the
// animation completes.
class ShelfLayoutManager::UpdateShelfObserver
    : public ui::ImplicitAnimationObserver {
 public:
  explicit UpdateShelfObserver(ShelfLayoutManager* shelf) : shelf_(shelf) {
    shelf_->update_shelf_observer_ = this;
  }

  void Detach() {
    shelf_ = NULL;
  }

  virtual void OnImplicitAnimationsCompleted() OVERRIDE {
    if (shelf_)
      shelf_->UpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
    delete this;
  }

 private:
  virtual ~UpdateShelfObserver() {
    if (shelf_)
      shelf_->update_shelf_observer_ = NULL;
  }

  // Shelf we're in. NULL if deleted before we're deleted.
  ShelfLayoutManager* shelf_;

  DISALLOW_COPY_AND_ASSIGN(UpdateShelfObserver);
};

// ShelfLayoutManager ----------------------------------------------------------

ShelfLayoutManager::ShelfLayoutManager(ShelfWidget* shelf)
    : root_window_(shelf->GetNativeView()->GetRootWindow()),
      updating_bounds_(false),
      force_shelf_always_visibile_(
          Shell::GetInstance()->IsMaximizeModeWindowManagerEnabled()),
      auto_hide_behavior_(SHELF_AUTO_HIDE_BEHAVIOR_NEVER),
      alignment_(SHELF_ALIGNMENT_BOTTOM),
      shelf_(shelf),
      workspace_controller_(NULL),
      window_overlaps_shelf_(false),
      mouse_over_shelf_when_auto_hide_timer_started_(false),
      bezel_event_filter_(new ShelfBezelEventFilter(this)),
      gesture_drag_status_(GESTURE_DRAG_NONE),
      gesture_drag_amount_(0.f),
      gesture_drag_auto_hide_state_(SHELF_AUTO_HIDE_SHOWN),
      update_shelf_observer_(NULL),
      duration_override_in_ms_(0) {
  Shell::GetInstance()->AddShellObserver(this);
  Shell::GetInstance()->lock_state_controller()->AddObserver(this);
  aura::client::GetActivationClient(root_window_)->AddObserver(this);
}

ShelfLayoutManager::~ShelfLayoutManager() {
  if (update_shelf_observer_)
    update_shelf_observer_->Detach();

  FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_, WillDeleteShelf());
  Shell::GetInstance()->RemoveShellObserver(this);
  Shell::GetInstance()->lock_state_controller()->RemoveObserver(this);
  aura::client::GetActivationClient(root_window_)->RemoveObserver(this);
}

void ShelfLayoutManager::SetAutoHideBehavior(ShelfAutoHideBehavior behavior) {
  if (auto_hide_behavior_ == behavior)
    return;
  auto_hide_behavior_ = behavior;
  UpdateVisibilityState();
  FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
                    OnAutoHideBehaviorChanged(root_window_,
                                              auto_hide_behavior_));
}

void ShelfLayoutManager::PrepareForShutdown() {
  // Clear all event filters, otherwise sometimes those filters may catch
  // synthesized mouse event and cause crashes during the shutdown.
  set_workspace_controller(NULL);
  auto_hide_event_filter_.reset();
  bezel_event_filter_.reset();
}

bool ShelfLayoutManager::IsVisible() const {
  // status_area_widget() may be NULL during the shutdown.
  return shelf_->status_area_widget() &&
      shelf_->status_area_widget()->IsVisible() &&
      (state_.visibility_state == SHELF_VISIBLE ||
       (state_.visibility_state == SHELF_AUTO_HIDE &&
        state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN));
}

bool ShelfLayoutManager::SetAlignment(ShelfAlignment alignment) {
  if (alignment_ == alignment)
    return false;

  // This should not be called during the lock screen transitions.
  DCHECK(!Shell::GetInstance()->session_state_delegate()->IsScreenLocked());
  alignment_ = alignment;
  shelf_->SetAlignment(alignment);
  LayoutShelf();
  return true;
}

ShelfAlignment ShelfLayoutManager::GetAlignment() const {
  // When the screen is locked, the shelf is forced into bottom alignment.
  if (Shell::GetInstance()->session_state_delegate()->IsScreenLocked())
    return SHELF_ALIGNMENT_BOTTOM;
  return alignment_;
}

gfx::Rect ShelfLayoutManager::GetIdealBounds() {
  gfx::Rect bounds(
      ScreenUtil::GetDisplayBoundsInParent(shelf_->GetNativeView()));
  int width = 0, height = 0;
  GetShelfSize(&width, &height);
  return SelectValueForShelfAlignment(
      gfx::Rect(bounds.x(), bounds.bottom() - height, bounds.width(), height),
      gfx::Rect(bounds.x(), bounds.y(), width, bounds.height()),
      gfx::Rect(bounds.right() - width, bounds.y(), width, bounds.height()),
      gfx::Rect(bounds.x(), bounds.y(), bounds.width(), height));
}

void ShelfLayoutManager::LayoutShelf() {
  TargetBounds target_bounds;
  CalculateTargetBounds(state_, &target_bounds);
  UpdateBoundsAndOpacity(target_bounds, false, NULL);

  if (shelf_->shelf()) {
    // This is not part of UpdateBoundsAndOpacity() because
    // SetShelfViewBounds() sets the bounds immediately and does not animate.
    // The height of the ShelfView for a horizontal shelf and the width of
    // the ShelfView for a vertical shelf are set when |shelf_|'s bounds
    // are changed via UpdateBoundsAndOpacity(). This sets the origin and the
    // dimension in the other direction.
    shelf_->shelf()->SetShelfViewBounds(
        target_bounds.shelf_bounds_in_shelf);
  }
}

ShelfVisibilityState ShelfLayoutManager::CalculateShelfVisibility() {
  switch(auto_hide_behavior_) {
    case SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS:
      return SHELF_AUTO_HIDE;
    case SHELF_AUTO_HIDE_BEHAVIOR_NEVER:
      return SHELF_VISIBLE;
    case SHELF_AUTO_HIDE_ALWAYS_HIDDEN:
      return SHELF_HIDDEN;
  }
  return SHELF_VISIBLE;
}

void ShelfLayoutManager::UpdateVisibilityState() {
  // Bail out early when there is no |workspace_controller_|, which happens
  // during shutdown after PrepareForShutdown.
  if (!workspace_controller_)
    return;

  if (Shell::GetInstance()->session_state_delegate()->IsScreenLocked() ||
      force_shelf_always_visibile_) {
    SetState(SHELF_VISIBLE);
  } else {
    // TODO(zelidrag): Verify shelf drag animation still shows on the device
    // when we are in SHELF_AUTO_HIDE_ALWAYS_HIDDEN.
    WorkspaceWindowState window_state(workspace_controller_->GetWindowState());
    switch (window_state) {
      case WORKSPACE_WINDOW_STATE_FULL_SCREEN: {
        const aura::Window* fullscreen_window = GetRootWindowController(
            root_window_)->GetWindowForFullscreenMode();
        if (fullscreen_window && wm::GetWindowState(fullscreen_window)->
                hide_shelf_when_fullscreen()) {
          SetState(SHELF_HIDDEN);
        } else {
          // The shelf is sometimes not hidden when in immersive fullscreen.
          // Force the shelf to be auto hidden in this case.
          SetState(SHELF_AUTO_HIDE);
        }
        break;
      }

      case WORKSPACE_WINDOW_STATE_MAXIMIZED:
        SetState(CalculateShelfVisibility());
        break;

      case WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF:
      case WORKSPACE_WINDOW_STATE_DEFAULT:
        SetState(CalculateShelfVisibility());
        SetWindowOverlapsShelf(window_state ==
                               WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF);
        break;
    }
  }
}

void ShelfLayoutManager::UpdateAutoHideState() {
  ShelfAutoHideState auto_hide_state =
      CalculateAutoHideState(state_.visibility_state);
  if (auto_hide_state != state_.auto_hide_state) {
    if (auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
      // Hides happen immediately.
      SetState(state_.visibility_state);
    } else {
      if (!auto_hide_timer_.IsRunning()) {
        mouse_over_shelf_when_auto_hide_timer_started_ =
            shelf_->GetWindowBoundsInScreen().Contains(
                Shell::GetScreen()->GetCursorScreenPoint());
      }
      auto_hide_timer_.Start(
          FROM_HERE,
          base::TimeDelta::FromMilliseconds(kAutoHideDelayMS),
          this, &ShelfLayoutManager::UpdateAutoHideStateNow);
    }
  } else {
    StopAutoHideTimer();
  }
}

void ShelfLayoutManager::SetWindowOverlapsShelf(bool value) {
  window_overlaps_shelf_ = value;
  UpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
}

void ShelfLayoutManager::AddObserver(ShelfLayoutManagerObserver* observer) {
  observers_.AddObserver(observer);
}

void ShelfLayoutManager::RemoveObserver(ShelfLayoutManagerObserver* observer) {
  observers_.RemoveObserver(observer);
}

////////////////////////////////////////////////////////////////////////////////
// ShelfLayoutManager, Gesture functions:

void ShelfLayoutManager::OnGestureEdgeSwipe(const ui::GestureEvent& gesture) {
  if (force_shelf_always_visibile_)
    return;

  if (visibility_state() == SHELF_AUTO_HIDE) {
    gesture_drag_auto_hide_state_ = SHELF_AUTO_HIDE_SHOWN;
    gesture_drag_status_ = GESTURE_DRAG_COMPLETE_IN_PROGRESS;
    UpdateVisibilityState();
    gesture_drag_status_ = GESTURE_DRAG_NONE;
  }
}

void ShelfLayoutManager::StartGestureDrag(const ui::GestureEvent& gesture) {
  if (force_shelf_always_visibile_)
    return;
  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(BACKGROUND_CHANGE_ANIMATE);
}

ShelfLayoutManager::DragState ShelfLayoutManager::UpdateGestureDrag(
    const ui::GestureEvent& gesture) {
  if (force_shelf_always_visibile_)
    return DRAG_SHELF;
  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) {
  if (force_shelf_always_visibile_)
    return;
  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 (GetAlignment()) {
        case SHELF_ALIGNMENT_BOTTOM:
        case SHELF_ALIGNMENT_RIGHT:
          correct_direction = gesture_drag_amount_ < 0;
          break;
        case SHELF_ALIGNMENT_LEFT:
        case SHELF_ALIGNMENT_TOP:
          correct_direction = gesture_drag_amount_ > 0;
          break;
      }
      should_change = correct_direction && drag_ratio > kDragHideThreshold;
    }
  } else if (gesture.type() == ui::ET_SCROLL_FLING_START) {
    if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN) {
      should_change = horizontal ? fabs(gesture.details().velocity_y()) > 0 :
                                   fabs(gesture.details().velocity_x()) > 0;
    } else {
      should_change = SelectValueForShelfAlignment(
          gesture.details().velocity_y() < 0,
          gesture.details().velocity_x() > 0,
          gesture.details().velocity_x() < 0,
          gesture.details().velocity_y() > 0);
    }
  } else {
    NOTREACHED();
  }

  if (!should_change) {
    CancelGestureDrag();
    return;
  }
  if (shelf_) {
    shelf_->Deactivate();
    shelf_->status_area_widget()->Deactivate();
  }
  gesture_drag_auto_hide_state_ =
      gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN ?
      SHELF_AUTO_HIDE_HIDDEN : SHELF_AUTO_HIDE_SHOWN;
  ShelfAutoHideBehavior new_auto_hide_behavior =
      gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN ?
      SHELF_AUTO_HIDE_BEHAVIOR_NEVER : SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;

  // When in fullscreen and the shelf is forced to be auto hidden, the auto hide
  // behavior affects neither the visibility state nor the auto hide state. Set
  // |gesture_drag_status_| to GESTURE_DRAG_COMPLETE_IN_PROGRESS to set the auto
  // hide state to |gesture_drag_auto_hide_state_|.
  gesture_drag_status_ = GESTURE_DRAG_COMPLETE_IN_PROGRESS;
  if (auto_hide_behavior_ != new_auto_hide_behavior)
    SetAutoHideBehavior(new_auto_hide_behavior);
  else
    UpdateVisibilityState();
  gesture_drag_status_ = GESTURE_DRAG_NONE;
}

void ShelfLayoutManager::CancelGestureDrag() {
  gesture_drag_status_ = GESTURE_DRAG_CANCEL_IN_PROGRESS;
  UpdateVisibilityState();
  gesture_drag_status_ = GESTURE_DRAG_NONE;
}

void ShelfLayoutManager::SetAnimationDurationOverride(
    int duration_override_in_ms) {
  duration_override_in_ms_ = duration_override_in_ms;
}

////////////////////////////////////////////////////////////////////////////////
// ShelfLayoutManager, aura::LayoutManager implementation:

void ShelfLayoutManager::OnWindowResized() {
  LayoutShelf();
}

void ShelfLayoutManager::OnWindowAddedToLayout(aura::Window* child) {
}

void ShelfLayoutManager::OnWillRemoveWindowFromLayout(aura::Window* child) {
}

void ShelfLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) {
}

void ShelfLayoutManager::OnChildWindowVisibilityChanged(aura::Window* child,
                                                        bool visible) {
}

void ShelfLayoutManager::SetChildBounds(aura::Window* child,
                                        const gfx::Rect& requested_bounds) {
  SetChildBoundsDirect(child, requested_bounds);
  // We may contain other widgets (such as frame maximize bubble) but they don't
  // effect the layout in anyway.
  if (!updating_bounds_ &&
      ((shelf_->GetNativeView() == child) ||
       (shelf_->status_area_widget()->GetNativeView() == child))) {
    LayoutShelf();
  }
}

void ShelfLayoutManager::OnLockStateChanged(bool locked) {
  // Force the shelf to layout for alignment (bottom if locked, restore
  // the previous alignment otherwise).
  state_.is_screen_locked = locked;
  shelf_->SetAlignment(locked ? SHELF_ALIGNMENT_BOTTOM : alignment_);
  UpdateVisibilityState();
  LayoutShelf();
}

void ShelfLayoutManager::OnMaximizeModeStarted() {
  DCHECK(!force_shelf_always_visibile_);
  force_shelf_always_visibile_ = true;
  UpdateVisibilityState();
}

void ShelfLayoutManager::OnMaximizeModeEnded() {
  DCHECK(force_shelf_always_visibile_);
  // Note: At this time Ash::Shell::IsMaximizeModeWindowManagerEnabled() will
  // report true, even though it is in progress of shut down. To address this
  // |force_shelf_always_visibile_| will be read.
  force_shelf_always_visibile_ = false;
  UpdateVisibilityState();
}

void ShelfLayoutManager::OnWindowActivated(aura::Window* gained_active,
                                           aura::Window* lost_active) {
  UpdateAutoHideStateNow();
}

bool ShelfLayoutManager::IsHorizontalAlignment() const {
  return GetAlignment() == SHELF_ALIGNMENT_BOTTOM ||
         GetAlignment() == SHELF_ALIGNMENT_TOP;
}

// static
ShelfLayoutManager* ShelfLayoutManager::ForShelf(aura::Window* window) {
  ShelfWidget* shelf = RootWindowController::ForShelf(window)->shelf();
  return shelf ? shelf->shelf_layout_manager() : NULL;
}

////////////////////////////////////////////////////////////////////////////////
// ShelfLayoutManager, private:

ShelfLayoutManager::TargetBounds::TargetBounds() : opacity(0.0f) {}
ShelfLayoutManager::TargetBounds::~TargetBounds() {}

void ShelfLayoutManager::SetState(ShelfVisibilityState visibility_state) {
  if (!shelf_->GetNativeView())
    return;

  State state;
  state.visibility_state = visibility_state;
  state.auto_hide_state = CalculateAutoHideState(visibility_state);
  state.window_state = workspace_controller_ ?
      workspace_controller_->GetWindowState() : WORKSPACE_WINDOW_STATE_DEFAULT;

  // Force an update because gesture drags affect the shelf bounds and we
  // should animate back to the normal bounds at the end of a gesture.
  bool force_update =
      (gesture_drag_status_ == GESTURE_DRAG_CANCEL_IN_PROGRESS ||
       gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS);

  if (!force_update && state_.Equals(state))
    return;  // Nothing changed.

  FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
                    WillChangeVisibilityState(visibility_state));

  if (state.visibility_state == SHELF_AUTO_HIDE) {
    // When state is SHELF_AUTO_HIDE we need to track when the mouse is over the
    // shelf to unhide it. AutoHideEventFilter does that for us.
    if (!auto_hide_event_filter_)
      auto_hide_event_filter_.reset(new AutoHideEventFilter(this));
  } else {
    auto_hide_event_filter_.reset(NULL);
  }

  StopAutoHideTimer();

  State old_state = state_;
  state_ = state;

  BackgroundAnimatorChangeType change_type = BACKGROUND_CHANGE_ANIMATE;
  bool delay_background_change = false;

  // Do not animate the background when:
  // - Going from a hidden / auto hidden shelf in fullscreen to a visible shelf
  //   in maximized mode.
  // - Going from an auto hidden shelf in maximized mode to a visible shelf in
  //   maximized mode.
  if (state.visibility_state == SHELF_VISIBLE &&
      state.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED &&
      old_state.visibility_state != SHELF_VISIBLE) {
    change_type = BACKGROUND_CHANGE_IMMEDIATE;
  } else {
    // Delay the animation when the shelf was hidden, and has just been made
    // visible (e.g. using a gesture-drag).
    if (state.visibility_state == SHELF_VISIBLE &&
        old_state.visibility_state == SHELF_AUTO_HIDE &&
        old_state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
      delay_background_change = true;
    }
  }

  if (delay_background_change) {
    if (update_shelf_observer_)
      update_shelf_observer_->Detach();
    // UpdateShelfBackground deletes itself when the animation is done.
    update_shelf_observer_ = new UpdateShelfObserver(this);
  } else {
    UpdateShelfBackground(change_type);
  }

  shelf_->SetDimsShelf(
      state.visibility_state == SHELF_VISIBLE &&
      state.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED);

  TargetBounds target_bounds;
  CalculateTargetBounds(state_, &target_bounds);
  UpdateBoundsAndOpacity(target_bounds, true,
      delay_background_change ? update_shelf_observer_ : NULL);

  // OnAutoHideStateChanged Should be emitted when:
  //  - firstly state changed to auto-hide from other state
  //  - or, auto_hide_state has changed
  if ((old_state.visibility_state != state_.visibility_state &&
       state_.visibility_state == SHELF_AUTO_HIDE) ||
      old_state.auto_hide_state != state_.auto_hide_state) {
    FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
                      OnAutoHideStateChanged(state_.auto_hide_state));
  }
}

void ShelfLayoutManager::UpdateBoundsAndOpacity(
    const TargetBounds& target_bounds,
    bool animate,
    ui::ImplicitAnimationObserver* observer) {
  base::AutoReset<bool> auto_reset_updating_bounds(&updating_bounds_, true);

  ui::ScopedLayerAnimationSettings shelf_animation_setter(
      GetLayer(shelf_)->GetAnimator());
  ui::ScopedLayerAnimationSettings status_animation_setter(
      GetLayer(shelf_->status_area_widget())->GetAnimator());
  if (animate) {
    int duration = duration_override_in_ms_ ? duration_override_in_ms_ :
                                              kCrossFadeDurationMS;
    shelf_animation_setter.SetTransitionDuration(
        base::TimeDelta::FromMilliseconds(duration));
    shelf_animation_setter.SetTweenType(gfx::Tween::EASE_OUT);
    shelf_animation_setter.SetPreemptionStrategy(
        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
    status_animation_setter.SetTransitionDuration(
        base::TimeDelta::FromMilliseconds(duration));
    status_animation_setter.SetTweenType(gfx::Tween::EASE_OUT);
    status_animation_setter.SetPreemptionStrategy(
        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  } else {
    StopAnimating();
    shelf_animation_setter.SetTransitionDuration(base::TimeDelta());
    status_animation_setter.SetTransitionDuration(base::TimeDelta());
  }
  if (observer)
    status_animation_setter.AddObserver(observer);

  GetLayer(shelf_)->SetOpacity(target_bounds.opacity);
  shelf_->SetBounds(ScreenUtil::ConvertRectToScreen(
       shelf_->GetNativeView()->parent(),
       target_bounds.shelf_bounds_in_root));

  GetLayer(shelf_->status_area_widget())->SetOpacity(
      target_bounds.status_opacity);
  // TODO(harrym): Once status area widget is a child view of shelf
  // this can be simplified.
  gfx::Rect status_bounds = target_bounds.status_bounds_in_shelf;
  status_bounds.set_x(status_bounds.x() +
                      target_bounds.shelf_bounds_in_root.x());
  status_bounds.set_y(status_bounds.y() +
                      target_bounds.shelf_bounds_in_root.y());
  shelf_->status_area_widget()->SetBounds(
      ScreenUtil::ConvertRectToScreen(
          shelf_->status_area_widget()->GetNativeView()->parent(),
          status_bounds));
  SessionStateDelegate* session_state_delegate =
      Shell::GetInstance()->session_state_delegate();
  if (!state_.is_screen_locked &&
      (session_state_delegate->IsActiveUserSessionStarted() ||
       !keyboard_bounds_.IsEmpty())) {
    Shell::GetInstance()->SetDisplayWorkAreaInsets(
        root_window_, target_bounds.work_area_insets);
  }
}

void ShelfLayoutManager::StopAnimating() {
  GetLayer(shelf_)->GetAnimator()->StopAnimating();
  GetLayer(shelf_->status_area_widget())->GetAnimator()->StopAnimating();
}

void ShelfLayoutManager::GetShelfSize(int* width, int* height) {
  *width = *height = 0;
  gfx::Size status_size(
      shelf_->status_area_widget()->GetWindowBoundsInScreen().size());
  if (IsHorizontalAlignment())
    *height = GetPreferredShelfSize();
  else
    *width = GetPreferredShelfSize();
}

void ShelfLayoutManager::AdjustBoundsBasedOnAlignment(int inset,
                                                      gfx::Rect* bounds) const {
  bounds->Inset(SelectValueForShelfAlignment(
      gfx::Insets(0, 0, inset, 0),
      gfx::Insets(0, inset, 0, 0),
      gfx::Insets(0, 0, 0, inset),
      gfx::Insets(inset, 0, 0, 0)));
}

void ShelfLayoutManager::CalculateTargetBounds(
    const State& state,
    TargetBounds* target_bounds) {
  const gfx::Rect available_bounds(GetAvailableBounds());
  gfx::Rect status_size(
      shelf_->status_area_widget()->GetWindowBoundsInScreen().size());
  int shelf_width = 0, shelf_height = 0;
  GetShelfSize(&shelf_width, &shelf_height);
  if (IsHorizontalAlignment())
    shelf_width = available_bounds.width();
  else
    shelf_height = available_bounds.height();

  if (state.visibility_state == SHELF_AUTO_HIDE &&
      state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
    // Auto-hidden shelf always starts with the default size. If a gesture-drag
    // is in progress, then the call to UpdateTargetBoundsForGesture() below
    // takes care of setting the height properly.
    if (IsHorizontalAlignment())
      shelf_height = kAutoHideSize;
    else
      shelf_width = kAutoHideSize;
  } else if (state.visibility_state == SHELF_HIDDEN ||
      !keyboard_bounds_.IsEmpty()) {
    if (IsHorizontalAlignment())
      shelf_height = 0;
    else
      shelf_width = 0;
  }

  target_bounds->shelf_bounds_in_root = SelectValueForShelfAlignment(
      gfx::Rect(available_bounds.x(), available_bounds.bottom() - shelf_height,
                    available_bounds.width(), shelf_height),
      gfx::Rect(available_bounds.x(), available_bounds.y(),
                    shelf_width, available_bounds.height()),
      gfx::Rect(available_bounds.right() - shelf_width, available_bounds.y(),
                    shelf_width, available_bounds.height()),
      gfx::Rect(available_bounds.x(), available_bounds.y(),
                    available_bounds.width(), shelf_height));

  int status_inset = std::max(0, GetPreferredShelfSize() -
      PrimaryAxisValue(status_size.height(), status_size.width()));

  if (ash::switches::UseAlternateShelfLayout()) {
    status_inset = 0;
    if (IsHorizontalAlignment())
      status_size.set_height(kShelfSize);
    else
      status_size.set_width(kShelfSize);
  }

  target_bounds->status_bounds_in_shelf = SelectValueForShelfAlignment(
      gfx::Rect(base::i18n::IsRTL() ? 0 : shelf_width - status_size.width(),
                    status_inset, status_size.width(), status_size.height()),
      gfx::Rect(shelf_width - (status_size.width() + status_inset),
                    shelf_height - status_size.height(), status_size.width(),
                    status_size.height()),
      gfx::Rect(status_inset, shelf_height - status_size.height(),
                    status_size.width(), status_size.height()),
      gfx::Rect(base::i18n::IsRTL() ? 0 : shelf_width - status_size.width(),
                    shelf_height - (status_size.height() + status_inset),
                    status_size.width(), status_size.height()));

  target_bounds->work_area_insets = SelectValueForShelfAlignment(
      gfx::Insets(0, 0, GetWorkAreaSize(state, shelf_height), 0),
      gfx::Insets(0, GetWorkAreaSize(state, shelf_width), 0, 0),
      gfx::Insets(0, 0, 0, GetWorkAreaSize(state, shelf_width)),
      gfx::Insets(GetWorkAreaSize(state, shelf_height), 0, 0, 0));

  // TODO(varkha): The functionality of managing insets for display areas
  // should probably be pushed to a separate component. This would simplify or
  // remove entirely the dependency on keyboard and dock.

  // Also push in the work area inset for the keyboard if it is visible.
  if (!keyboard_bounds_.IsEmpty()) {
    gfx::Insets keyboard_insets(0, 0, keyboard_bounds_.height(), 0);
    target_bounds->work_area_insets += keyboard_insets;
  }

  // Also push in the work area inset for the dock if it is visible.
  if (!dock_bounds_.IsEmpty()) {
    gfx::Insets dock_insets(
        0, (dock_bounds_.x() > 0 ? 0 : dock_bounds_.width()),
        0, (dock_bounds_.x() > 0 ? dock_bounds_.width() : 0));
    target_bounds->work_area_insets += dock_insets;
  }

  target_bounds->opacity =
      (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS ||
       state.visibility_state == SHELF_VISIBLE ||
       state.visibility_state == SHELF_AUTO_HIDE) ? 1.0f : 0.0f;
  target_bounds->status_opacity =
      (state.visibility_state == SHELF_AUTO_HIDE &&
       state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN &&
       gesture_drag_status_ != GESTURE_DRAG_IN_PROGRESS) ?
      0.0f : target_bounds->opacity;

  if (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS)
    UpdateTargetBoundsForGesture(target_bounds);

  // This needs to happen after calling UpdateTargetBoundsForGesture(), because
  // that can change the size of the shelf.
  target_bounds->shelf_bounds_in_shelf = SelectValueForShelfAlignment(
      gfx::Rect(0, 0,
                shelf_width - status_size.width(),
                target_bounds->shelf_bounds_in_root.height()),
      gfx::Rect(0, 0, target_bounds->shelf_bounds_in_root.width(),
                shelf_height - status_size.height()),
      gfx::Rect(0, 0, target_bounds->shelf_bounds_in_root.width(),
                shelf_height - status_size.height()),
      gfx::Rect(0, 0,
                shelf_width - status_size.width(),
                target_bounds->shelf_bounds_in_root.height()));
}

void ShelfLayoutManager::UpdateTargetBoundsForGesture(
    TargetBounds* target_bounds) const {
  CHECK_EQ(GESTURE_DRAG_IN_PROGRESS, gesture_drag_status_);
  bool horizontal = IsHorizontalAlignment();
  const gfx::Rect& available_bounds(root_window_->bounds());
  int resistance_free_region = 0;

  if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_HIDDEN &&
      visibility_state() == SHELF_AUTO_HIDE &&
      auto_hide_state() != SHELF_AUTO_HIDE_SHOWN) {
    // If the shelf was hidden when the drag started (and the state hasn't
    // changed since then, e.g. because the tray-menu was shown because of the
    // drag), then allow the drag some resistance-free region at first to make
    // sure the shelf sticks with the finger until the shelf is visible.
    resistance_free_region = GetPreferredShelfSize() - kAutoHideSize;
  }

  bool resist = SelectValueForShelfAlignment(
      gesture_drag_amount_ < -resistance_free_region,
      gesture_drag_amount_ > resistance_free_region,
      gesture_drag_amount_ < -resistance_free_region,
      gesture_drag_amount_ > resistance_free_region);

  float translate = 0.f;
  if (resist) {
    float diff = fabsf(gesture_drag_amount_) - resistance_free_region;
    diff = std::min(diff, sqrtf(diff));
    if (gesture_drag_amount_ < 0)
      translate = -resistance_free_region - diff;
    else
      translate = resistance_free_region + diff;
  } else {
    translate = gesture_drag_amount_;
  }

  if (horizontal) {
    // Move and size the shelf with the gesture.
    int shelf_height = target_bounds->shelf_bounds_in_root.height() - translate;
    shelf_height = std::max(shelf_height, kAutoHideSize);
    target_bounds->shelf_bounds_in_root.set_height(shelf_height);
    if (GetAlignment() == SHELF_ALIGNMENT_BOTTOM) {
      target_bounds->shelf_bounds_in_root.set_y(
          available_bounds.bottom() - shelf_height);
    }

    if (ash::switches::UseAlternateShelfLayout()) {
      target_bounds->status_bounds_in_shelf.set_y(0);
    } else {
      // The statusbar should be in the center of the shelf.
      gfx::Rect status_y = target_bounds->shelf_bounds_in_root;
      status_y.set_y(0);
      status_y.ClampToCenteredSize(
          target_bounds->status_bounds_in_shelf.size());
      target_bounds->status_bounds_in_shelf.set_y(status_y.y());
    }
  } else {
    // Move and size the shelf with the gesture.
    int shelf_width = target_bounds->shelf_bounds_in_root.width();
    bool right_aligned = GetAlignment() == SHELF_ALIGNMENT_RIGHT;
    if (right_aligned)
      shelf_width -= translate;
    else
      shelf_width += translate;
    shelf_width = std::max(shelf_width, kAutoHideSize);
    target_bounds->shelf_bounds_in_root.set_width(shelf_width);
    if (right_aligned) {
      target_bounds->shelf_bounds_in_root.set_x(
          available_bounds.right() - shelf_width);
    }

    if (ash::switches::UseAlternateShelfLayout()) {
      if (right_aligned)
        target_bounds->status_bounds_in_shelf.set_x(0);
      else
        target_bounds->status_bounds_in_shelf.set_x(
            target_bounds->shelf_bounds_in_root.width() -
            kShelfSize);
    } else {
      // The statusbar should be in the center of the shelf.
      gfx::Rect status_x = target_bounds->shelf_bounds_in_root;
      status_x.set_x(0);
      status_x.ClampToCenteredSize(
          target_bounds->status_bounds_in_shelf.size());
      target_bounds->status_bounds_in_shelf.set_x(status_x.x());
    }
  }
}

void ShelfLayoutManager::UpdateShelfBackground(
    BackgroundAnimatorChangeType type) {
  const ShelfBackgroundType background_type(GetShelfBackgroundType());
  shelf_->SetPaintsBackground(background_type, type);
  FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
                    OnBackgroundUpdated(background_type, type));
}

ShelfBackgroundType ShelfLayoutManager::GetShelfBackgroundType() const {
  if (state_.visibility_state != SHELF_AUTO_HIDE &&
      state_.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED) {
    return SHELF_BACKGROUND_MAXIMIZED;
  }

  if (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS ||
      (!state_.is_screen_locked && window_overlaps_shelf_) ||
      (state_.visibility_state == SHELF_AUTO_HIDE)) {
    return SHELF_BACKGROUND_OVERLAP;
  }

  return SHELF_BACKGROUND_DEFAULT;
}

void ShelfLayoutManager::UpdateAutoHideStateNow() {
  SetState(state_.visibility_state);

  // If the state did not change, the auto hide timer may still be running.
  StopAutoHideTimer();
}

void ShelfLayoutManager::StopAutoHideTimer() {
  auto_hide_timer_.Stop();
  mouse_over_shelf_when_auto_hide_timer_started_ = false;
}

gfx::Rect ShelfLayoutManager::GetAutoHideShowShelfRegionInScreen() const {
  gfx::Rect shelf_bounds_in_screen = shelf_->GetWindowBoundsInScreen();
  gfx::Vector2d offset = SelectValueForShelfAlignment(
      gfx::Vector2d(0, shelf_bounds_in_screen.height()),
      gfx::Vector2d(-kMaxAutoHideShowShelfRegionSize, 0),
      gfx::Vector2d(shelf_bounds_in_screen.width(), 0),
      gfx::Vector2d(0, -kMaxAutoHideShowShelfRegionSize));

  gfx::Rect show_shelf_region_in_screen = shelf_bounds_in_screen;
  show_shelf_region_in_screen += offset;
  if (IsHorizontalAlignment())
    show_shelf_region_in_screen.set_height(kMaxAutoHideShowShelfRegionSize);
  else
    show_shelf_region_in_screen.set_width(kMaxAutoHideShowShelfRegionSize);

  // TODO: Figure out if we need any special handling when the keyboard is
  // visible.
  return show_shelf_region_in_screen;
}

ShelfAutoHideState ShelfLayoutManager::CalculateAutoHideState(
    ShelfVisibilityState visibility_state) const {
  if (force_shelf_always_visibile_)
    return SHELF_AUTO_HIDE_SHOWN;

  if (visibility_state != SHELF_AUTO_HIDE || !shelf_)
    return SHELF_AUTO_HIDE_HIDDEN;

  Shell* shell = Shell::GetInstance();
  if (shell->GetAppListTargetVisibility())
    return SHELF_AUTO_HIDE_SHOWN;

  if (shelf_->status_area_widget() &&
      shelf_->status_area_widget()->ShouldShowShelf())
    return SHELF_AUTO_HIDE_SHOWN;

  if (shelf_->shelf() && shelf_->shelf()->IsShowingMenu())
    return SHELF_AUTO_HIDE_SHOWN;

  if (shelf_->shelf() && shelf_->shelf()->IsShowingOverflowBubble())
    return SHELF_AUTO_HIDE_SHOWN;

  if (shelf_->IsActive() || shelf_->status_area_widget()->IsActive())
    return SHELF_AUTO_HIDE_SHOWN;

  const std::vector<aura::Window*> windows =
      ash::MruWindowTracker::BuildWindowList(false);

  // Process the window list and check if there are any visible windows.
  bool visible_window = false;
  for (size_t i = 0; i < windows.size(); ++i) {
    if (windows[i] && windows[i]->IsVisible() &&
        !wm::GetWindowState(windows[i])->IsMinimized() &&
        root_window_ == windows[i]->GetRootWindow()) {
      visible_window = true;
      break;
    }
  }
  // If there are no visible windows do not hide the shelf.
  if (!visible_window)
    return SHELF_AUTO_HIDE_SHOWN;

  if (gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS)
    return gesture_drag_auto_hide_state_;

  // Don't show if the user is dragging the mouse.
  if (auto_hide_event_filter_.get() && auto_hide_event_filter_->in_mouse_drag())
    return SHELF_AUTO_HIDE_HIDDEN;

  // Ignore the mouse position if mouse events are disabled.
  aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
      shelf_->GetNativeWindow()->GetRootWindow());
  if (!cursor_client->IsMouseEventsEnabled())
    return SHELF_AUTO_HIDE_HIDDEN;

  gfx::Rect shelf_region = shelf_->GetWindowBoundsInScreen();
  if (shelf_->status_area_widget() &&
      shelf_->status_area_widget()->IsMessageBubbleShown() &&
      IsVisible()) {
    // Increase the the hit test area to prevent the shelf from disappearing
    // when the mouse is over the bubble gap.
    ShelfAlignment alignment = GetAlignment();
    shelf_region.Inset(alignment == SHELF_ALIGNMENT_RIGHT ?
                           -kNotificationBubbleGapHeight : 0,
                       alignment == SHELF_ALIGNMENT_BOTTOM ?
                           -kNotificationBubbleGapHeight : 0,
                       alignment == SHELF_ALIGNMENT_LEFT ?
                           -kNotificationBubbleGapHeight : 0,
                       alignment == SHELF_ALIGNMENT_TOP ?
                           -kNotificationBubbleGapHeight : 0);
  }

  gfx::Point cursor_position_in_screen =
      Shell::GetScreen()->GetCursorScreenPoint();
  if (shelf_region.Contains(cursor_position_in_screen))
    return SHELF_AUTO_HIDE_SHOWN;

  // When the shelf is auto hidden and the shelf is on the boundary between two
  // displays, it is hard to trigger showing the shelf. For instance, if a
  // user's primary display is left of their secondary display, it is hard to
  // unautohide a left aligned shelf on the secondary display.
  // It is hard because:
  // - It is hard to stop the cursor in the shelf "light bar" and not overshoot.
  // - The cursor is warped to the other display if the cursor gets to the edge
  //   of the display.
  // Show the shelf if the cursor started on the shelf and the user overshot the
  // shelf slightly to make it easier to show the shelf in this situation. We
  // do not check |auto_hide_timer_|.IsRunning() because it returns false when
  // the timer's task is running.
  if ((state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN ||
       mouse_over_shelf_when_auto_hide_timer_started_) &&
      GetAutoHideShowShelfRegionInScreen().Contains(
          cursor_position_in_screen)) {
    return SHELF_AUTO_HIDE_SHOWN;
  }

  return SHELF_AUTO_HIDE_HIDDEN;
}

bool ShelfLayoutManager::IsShelfWindow(aura::Window* window) {
  if (!window)
    return false;
  return (shelf_ && shelf_->GetNativeWindow()->Contains(window)) ||
      (shelf_->status_area_widget() &&
       shelf_->status_area_widget()->GetNativeWindow()->Contains(window));
}

int ShelfLayoutManager::GetWorkAreaSize(const State& state, int size) const {
  if (state.visibility_state == SHELF_VISIBLE)
    return size;
  if (state.visibility_state == SHELF_AUTO_HIDE)
    return kAutoHideSize;
  return 0;
}

gfx::Rect ShelfLayoutManager::GetAvailableBounds() const {
  gfx::Rect bounds(root_window_->bounds());
  bounds.set_height(bounds.height() - keyboard_bounds_.height());
  return bounds;
}

void ShelfLayoutManager::OnKeyboardBoundsChanging(const gfx::Rect& new_bounds) {
  bool keyboard_is_about_to_hide = false;
  if (new_bounds.IsEmpty() && !keyboard_bounds_.IsEmpty())
    keyboard_is_about_to_hide = true;

  keyboard_bounds_ = new_bounds;
  OnWindowResized();

  SessionStateDelegate* session_state_delegate =
      Shell::GetInstance()->session_state_delegate();

  // On login screen if keyboard has been just hidden, update bounds just once
  // but ignore target_bounds.work_area_insets since shelf overlaps with login
  // window.
  if (!session_state_delegate->IsActiveUserSessionStarted() &&
      keyboard_is_about_to_hide) {
    Shell::GetInstance()->SetDisplayWorkAreaInsets(root_window_, gfx::Insets());
  }
}

void ShelfLayoutManager::OnDockBoundsChanging(
    const gfx::Rect& dock_bounds,
    DockedWindowLayoutManagerObserver::Reason reason) {
  // Skip shelf layout in case docked notification originates from this class.
  if (reason == DISPLAY_INSETS_CHANGED)
    return;
  if (dock_bounds_ != dock_bounds) {
    dock_bounds_ = dock_bounds;
    OnWindowResized();
    UpdateVisibilityState();
    UpdateShelfBackground(BACKGROUND_CHANGE_ANIMATE);
  }
}

void ShelfLayoutManager::OnLockStateEvent(LockStateObserver::EventType event) {
  if (event == EVENT_LOCK_ANIMATION_STARTED) {
    // Enter the screen locked state as the animation starts to prevent
    // layout changes as the screen locks.
    state_.is_screen_locked = true;
    // Hide the status area widget (using auto hide animation).
    base::AutoReset<ShelfVisibilityState> state(&state_.visibility_state,
                                                SHELF_HIDDEN);
    TargetBounds target_bounds;
    CalculateTargetBounds(state_, &target_bounds);
    UpdateBoundsAndOpacity(target_bounds, true, NULL);
    UpdateVisibilityState();
  }
}

}  // namespace ash

/* [<][>][^][v][top][bottom][index][help] */