blob: 241267822b9dba8970539249532eccbc4275a48b [file] [log] [blame]
// 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/wm/shelf_layout_manager.h"
#include <algorithm>
#include <cmath>
#include "ash/launcher/launcher.h"
#include "ash/screen_ash.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/shell_window_ids.h"
#include "ash/system/status_area_widget.h"
#include "ash/wm/workspace_controller.h"
#include "base/auto_reset.h"
#include "base/i18n/rtl.h"
#include "ui/aura/client/activation_client.h"
#include "ui/aura/event_filter.h"
#include "ui/aura/root_window.h"
#include "ui/base/events/event.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/screen.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace internal {
namespace {
// Delay before showing the launcher. This is after the mouse stops moving.
const int kAutoHideDelayMS = 200;
ui::Layer* GetLayer(views::Widget* widget) {
return widget->GetNativeView()->layer();
}
} // namespace
// static
const int ShelfLayoutManager::kWorkspaceAreaBottomInset = 2;
// static
const int ShelfLayoutManager::kAutoHideSize = 2;
// Notifies ShelfLayoutManager any time the mouse moves.
class ShelfLayoutManager::AutoHideEventFilter : public aura::EventFilter {
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 aura::EventFilter:
virtual bool PreHandleKeyEvent(aura::Window* target,
ui::KeyEvent* event) OVERRIDE;
virtual bool PreHandleMouseEvent(aura::Window* target,
ui::MouseEvent* event) OVERRIDE;
virtual ui::TouchStatus PreHandleTouchEvent(
aura::Window* target,
ui::TouchEvent* event) OVERRIDE;
virtual ui::EventResult PreHandleGestureEvent(
aura::Window* target,
ui::GestureEvent* event) OVERRIDE;
private:
ShelfLayoutManager* shelf_;
bool in_mouse_drag_;
DISALLOW_COPY_AND_ASSIGN(AutoHideEventFilter);
};
ShelfLayoutManager::AutoHideEventFilter::AutoHideEventFilter(
ShelfLayoutManager* shelf)
: shelf_(shelf),
in_mouse_drag_(false) {
Shell::GetInstance()->AddEnvEventFilter(this);
}
ShelfLayoutManager::AutoHideEventFilter::~AutoHideEventFilter() {
Shell::GetInstance()->RemoveEnvEventFilter(this);
}
bool ShelfLayoutManager::AutoHideEventFilter::PreHandleKeyEvent(
aura::Window* target,
ui::KeyEvent* event) {
return false; // Always let the event propagate.
}
bool ShelfLayoutManager::AutoHideEventFilter::PreHandleMouseEvent(
aura::Window* target,
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(target);
if (event->type() == ui::ET_MOUSE_MOVED)
shelf_->UpdateAutoHideState();
return false; // Not handled.
}
ui::TouchStatus ShelfLayoutManager::AutoHideEventFilter::PreHandleTouchEvent(
aura::Window* target,
ui::TouchEvent* event) {
return ui::TOUCH_STATUS_UNKNOWN; // Not handled.
}
ui::EventResult
ShelfLayoutManager::AutoHideEventFilter::PreHandleGestureEvent(
aura::Window* target,
ui::GestureEvent* event) {
return ui::ER_UNHANDLED; // Not handled.
}
////////////////////////////////////////////////////////////////////////////////
// ShelfLayoutManager, public:
ShelfLayoutManager::ShelfLayoutManager(views::Widget* status)
: root_window_(Shell::GetPrimaryRootWindow()),
in_layout_(false),
auto_hide_behavior_(SHELF_AUTO_HIDE_BEHAVIOR_DEFAULT),
alignment_(SHELF_ALIGNMENT_BOTTOM),
launcher_(NULL),
status_(status),
workspace_controller_(NULL),
window_overlaps_shelf_(false),
gesture_drag_status_(GESTURE_DRAG_NONE),
gesture_drag_amount_(0.f),
gesture_drag_auto_hide_state_(AUTO_HIDE_SHOWN) {
Shell::GetInstance()->AddShellObserver(this);
aura::client::GetActivationClient(root_window_)->AddObserver(this);
}
ShelfLayoutManager::~ShelfLayoutManager() {
FOR_EACH_OBSERVER(Observer, observers_, WillDeleteShelf());
Shell::GetInstance()->RemoveShellObserver(this);
aura::client::GetActivationClient(root_window_)->RemoveObserver(this);
}
void ShelfLayoutManager::SetAutoHideBehavior(ShelfAutoHideBehavior behavior) {
if (auto_hide_behavior_ == behavior)
return;
auto_hide_behavior_ = behavior;
UpdateVisibilityState();
FOR_EACH_OBSERVER(Observer, observers_,
OnAutoHideStateChanged(state_.auto_hide_state));
}
bool ShelfLayoutManager::IsVisible() const {
return status_->IsVisible() && (state_.visibility_state == VISIBLE ||
(state_.visibility_state == AUTO_HIDE &&
state_.auto_hide_state == AUTO_HIDE_SHOWN));
}
void ShelfLayoutManager::SetLauncher(Launcher* launcher) {
if (launcher == launcher_)
return;
launcher_ = launcher;
if (launcher_)
launcher_->SetAlignment(alignment_);
LayoutShelf();
}
bool ShelfLayoutManager::SetAlignment(ShelfAlignment alignment) {
if (alignment_ == alignment)
return false;
alignment_ = alignment;
if (launcher_)
launcher_->SetAlignment(alignment);
StatusAreaWidget* status_area_widget =
Shell::GetInstance()->status_area_widget();
if (status_area_widget)
Shell::GetInstance()->status_area_widget()->SetShelfAlignment(alignment);
LayoutShelf();
return true;
}
gfx::Rect ShelfLayoutManager::GetIdealBounds() {
// TODO(oshima): this is wrong. Figure out what display shelf is on
// and everything should be based on it.
gfx::Rect bounds(ScreenAsh::GetDisplayBoundsInParent(
status_->GetNativeView()));
int width = 0, height = 0;
GetShelfSize(&width, &height);
switch (alignment_) {
case SHELF_ALIGNMENT_BOTTOM:
return gfx::Rect(bounds.x(), bounds.bottom() - height,
bounds.width(), height);
case SHELF_ALIGNMENT_LEFT:
return gfx::Rect(bounds.x(), bounds.y(), width, bounds.height());
case SHELF_ALIGNMENT_RIGHT:
return gfx::Rect(bounds.right() - width, bounds.y(), width,
bounds.height());
}
NOTREACHED();
return gfx::Rect();
}
void ShelfLayoutManager::LayoutShelf() {
AutoReset<bool> auto_reset_in_layout(&in_layout_, true);
StopAnimating();
TargetBounds target_bounds;
CalculateTargetBounds(state_, &target_bounds);
if (launcher_widget()) {
GetLayer(launcher_widget())->SetOpacity(target_bounds.opacity);
launcher_widget()->SetBounds(
ScreenAsh::ConvertRectToScreen(
launcher_widget()->GetNativeView()->parent(),
target_bounds.launcher_bounds_in_root));
launcher_->SetStatusSize(target_bounds.status_bounds_in_root.size());
}
GetLayer(status_)->SetOpacity(target_bounds.opacity);
status_->SetBounds(
ScreenAsh::ConvertRectToScreen(
status_->GetNativeView()->parent(),
target_bounds.status_bounds_in_root));
Shell::GetInstance()->SetDisplayWorkAreaInsets(
Shell::GetPrimaryRootWindow(),
target_bounds.work_area_insets);
UpdateHitTestBounds();
}
void ShelfLayoutManager::UpdateVisibilityState() {
ShellDelegate* delegate = Shell::GetInstance()->delegate();
if (delegate && delegate->IsScreenLocked()) {
SetState(VISIBLE);
} else if (gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS) {
SetState(AUTO_HIDE);
} else {
WorkspaceWindowState window_state(workspace_controller_->GetWindowState());
switch (window_state) {
case WORKSPACE_WINDOW_STATE_FULL_SCREEN:
SetState(HIDDEN);
break;
case WORKSPACE_WINDOW_STATE_MAXIMIZED:
SetState(auto_hide_behavior_ != SHELF_AUTO_HIDE_BEHAVIOR_NEVER ?
AUTO_HIDE : VISIBLE);
break;
case WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF:
case WORKSPACE_WINDOW_STATE_DEFAULT:
SetState(auto_hide_behavior_ == SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS ?
AUTO_HIDE : VISIBLE);
SetWindowOverlapsShelf(window_state ==
WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF);
}
}
}
void ShelfLayoutManager::UpdateAutoHideState() {
AutoHideState auto_hide_state =
CalculateAutoHideState(state_.visibility_state);
if (auto_hide_state != state_.auto_hide_state) {
if (auto_hide_state == AUTO_HIDE_HIDDEN) {
// Hides happen immediately.
SetState(state_.visibility_state);
FOR_EACH_OBSERVER(Observer, observers_,
OnAutoHideStateChanged(auto_hide_state));
} else {
auto_hide_timer_.Stop();
auto_hide_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kAutoHideDelayMS),
this, &ShelfLayoutManager::UpdateAutoHideStateNow);
FOR_EACH_OBSERVER(Observer, observers_, OnAutoHideStateChanged(
CalculateAutoHideState(state_.visibility_state)));
}
} else {
auto_hide_timer_.Stop();
}
}
void ShelfLayoutManager::SetWindowOverlapsShelf(bool value) {
window_overlaps_shelf_ = value;
UpdateShelfBackground(internal::BackgroundAnimator::CHANGE_ANIMATE);
}
void ShelfLayoutManager::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void ShelfLayoutManager::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
////////////////////////////////////////////////////////////////////////////////
// ShelfLayoutManager, Gesture dragging:
void ShelfLayoutManager::StartGestureDrag(const ui::GestureEvent& gesture) {
gesture_drag_status_ = GESTURE_DRAG_IN_PROGRESS;
gesture_drag_amount_ = 0.f;
gesture_drag_auto_hide_state_ = visibility_state() == AUTO_HIDE ?
auto_hide_state() : AUTO_HIDE_SHOWN;
UpdateShelfBackground(internal::BackgroundAnimator::CHANGE_ANIMATE);
}
void ShelfLayoutManager::UpdateGestureDrag(const ui::GestureEvent& gesture) {
bool horizontal = alignment() == SHELF_ALIGNMENT_BOTTOM;
gesture_drag_amount_ += horizontal ? gesture.details().scroll_y() :
gesture.details().scroll_x();
LayoutShelf();
}
void ShelfLayoutManager::CompleteGestureDrag(const ui::GestureEvent& gesture) {
bool horizontal = alignment() == SHELF_ALIGNMENT_BOTTOM;
bool should_change = false;
if (gesture.type() == ui::ET_GESTURE_SCROLL_END) {
// If the shelf was dragged X% towards the correct direction, then it is
// hidden/shown.
const float kDragHideThreshold = 0.4f;
gfx::Rect bounds = GetIdealBounds();
float drag_amount = gesture_drag_auto_hide_state_ == AUTO_HIDE_SHOWN ?
gesture_drag_amount_ : -gesture_drag_amount_;
if (horizontal)
should_change = drag_amount > kDragHideThreshold * bounds.height();
else if (alignment() == SHELF_ALIGNMENT_LEFT)
should_change = -drag_amount > kDragHideThreshold * bounds.width();
else
should_change = drag_amount > kDragHideThreshold * bounds.width();
} else if (gesture.type() == ui::ET_SCROLL_FLING_START) {
if (horizontal)
should_change = gesture.details().velocity_y() > 0;
else if (alignment() == SHELF_ALIGNMENT_LEFT)
should_change = gesture.details().velocity_x() < 0;
else
should_change = gesture.details().velocity_x() > 0;
if (gesture_drag_auto_hide_state_ == AUTO_HIDE_HIDDEN)
should_change = !should_change;
} else {
NOTREACHED();
}
if (!should_change) {
CancelGestureDrag();
return;
}
gesture_drag_status_ = GESTURE_DRAG_COMPLETE_IN_PROGRESS;
gesture_drag_auto_hide_state_ =
gesture_drag_auto_hide_state_ == AUTO_HIDE_SHOWN ? AUTO_HIDE_HIDDEN :
AUTO_HIDE_SHOWN;
if (launcher_widget())
launcher_widget()->Deactivate();
status_->Deactivate();
if (auto_hide_behavior_ != SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS)
SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
else
UpdateVisibilityState();
gesture_drag_status_ = GESTURE_DRAG_NONE;
}
void ShelfLayoutManager::CancelGestureDrag() {
gesture_drag_status_ = GESTURE_DRAG_COMPLETE_IN_PROGRESS;
ui::ScopedLayerAnimationSettings
launcher_settings(GetLayer(launcher_widget())->GetAnimator()),
status_settings(GetLayer(status_)->GetAnimator());
LayoutShelf();
UpdateVisibilityState();
UpdateShelfBackground(internal::BackgroundAnimator::CHANGE_ANIMATE);
gesture_drag_status_ = GESTURE_DRAG_NONE;
}
////////////////////////////////////////////////////////////////////////////////
// ShelfLayoutManager, aura::LayoutManager implementation:
void ShelfLayoutManager::OnWindowResized() {
LayoutShelf();
}
void ShelfLayoutManager::OnWindowAddedToLayout(aura::Window* child) {
}
void ShelfLayoutManager::OnWillRemoveWindowFromLayout(aura::Window* child) {
}
void ShelfLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) {
}
void ShelfLayoutManager::OnChildWindowVisibilityChanged(aura::Window* child,
bool visible) {
}
void ShelfLayoutManager::SetChildBounds(aura::Window* child,
const gfx::Rect& requested_bounds) {
SetChildBoundsDirect(child, requested_bounds);
if (!in_layout_)
LayoutShelf();
}
void ShelfLayoutManager::OnLockStateChanged(bool locked) {
UpdateVisibilityState();
}
void ShelfLayoutManager::OnWindowActivated(aura::Window* active,
aura::Window* old_active) {
UpdateAutoHideStateNow();
}
////////////////////////////////////////////////////////////////////////////////
// ShelfLayoutManager, private:
ShelfLayoutManager::TargetBounds::TargetBounds() : opacity(0.0f) {}
gfx::Rect ShelfLayoutManager::GetMaximizedWindowBounds(
aura::Window* window) {
gfx::Rect bounds(ScreenAsh::GetDisplayBoundsInParent(window));
if (auto_hide_behavior_ == SHELF_AUTO_HIDE_BEHAVIOR_DEFAULT ||
auto_hide_behavior_ == SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS) {
AdjustBoundsBasedOnAlignment(kAutoHideSize, &bounds);
return bounds;
}
// SHELF_AUTO_HIDE_BEHAVIOR_NEVER maximized windows don't get any taller.
return GetUnmaximizedWorkAreaBounds(window);
}
gfx::Rect ShelfLayoutManager::GetUnmaximizedWorkAreaBounds(
aura::Window* window) {
gfx::Rect bounds(ScreenAsh::GetDisplayBoundsInParent(window));
int size;
if (auto_hide_behavior_ == SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS) {
size = kAutoHideSize;
} else {
int width, height;
GetShelfSize(&width, &height);
size = std::max(width, height);
}
AdjustBoundsBasedOnAlignment(size, &bounds);
return bounds;
}
void ShelfLayoutManager::SetState(VisibilityState visibility_state) {
ShellDelegate* delegate = Shell::GetInstance()->delegate();
State state;
state.visibility_state = visibility_state;
state.auto_hide_state = CalculateAutoHideState(visibility_state);
state.is_screen_locked = delegate && delegate->IsScreenLocked();
if (state_.Equals(state))
return; // Nothing changed.
FOR_EACH_OBSERVER(Observer, observers_,
WillChangeVisibilityState(visibility_state));
if (state.visibility_state == AUTO_HIDE) {
// When state is AUTO_HIDE we need to track when the mouse is over the
// launcher to unhide the shelf. AutoHideEventFilter does that for us.
if (!event_filter_.get())
event_filter_.reset(new AutoHideEventFilter(this));
} else {
event_filter_.reset(NULL);
}
auto_hide_timer_.Stop();
// Animating the background when transitioning from auto-hide & hidden to
// visibile is janking. Update the background immediately in this case.
internal::BackgroundAnimator::ChangeType change_type =
(state_.visibility_state == AUTO_HIDE &&
state_.auto_hide_state == AUTO_HIDE_HIDDEN &&
state.visibility_state == VISIBLE) ?
internal::BackgroundAnimator::CHANGE_IMMEDIATE :
internal::BackgroundAnimator::CHANGE_ANIMATE;
StopAnimating();
state_ = state;
TargetBounds target_bounds;
CalculateTargetBounds(state_, &target_bounds);
if (launcher_widget()) {
ui::ScopedLayerAnimationSettings launcher_animation_setter(
GetLayer(launcher_widget())->GetAnimator());
launcher_animation_setter.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(130));
launcher_animation_setter.SetTweenType(ui::Tween::EASE_OUT);
GetLayer(launcher_widget())->SetBounds(
target_bounds.launcher_bounds_in_root);
GetLayer(launcher_widget())->SetOpacity(target_bounds.opacity);
}
ui::ScopedLayerAnimationSettings status_animation_setter(
GetLayer(status_)->GetAnimator());
status_animation_setter.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(130));
status_animation_setter.SetTweenType(ui::Tween::EASE_OUT);
GetLayer(status_)->SetBounds(target_bounds.status_bounds_in_root);
GetLayer(status_)->SetOpacity(target_bounds.opacity);
Shell::GetInstance()->SetDisplayWorkAreaInsets(
Shell::GetPrimaryRootWindow(),
target_bounds.work_area_insets);
UpdateHitTestBounds();
UpdateShelfBackground(change_type);
}
void ShelfLayoutManager::StopAnimating() {
if (launcher_widget())
GetLayer(launcher_widget())->GetAnimator()->StopAnimating();
GetLayer(status_)->GetAnimator()->StopAnimating();
}
void ShelfLayoutManager::GetShelfSize(int* width, int* height) {
*width = *height = 0;
gfx::Size status_size(status_->GetWindowBoundsInScreen().size());
gfx::Size launcher_size = launcher_ ?
launcher_widget()->GetContentsView()->GetPreferredSize() : gfx::Size();
if (alignment_ == SHELF_ALIGNMENT_BOTTOM)
*height = std::max(launcher_size.height(), status_size.height());
else
*width = std::max(launcher_size.width(), status_size.width());
}
void ShelfLayoutManager::AdjustBoundsBasedOnAlignment(int inset,
gfx::Rect* bounds) const {
switch (alignment_) {
case SHELF_ALIGNMENT_BOTTOM:
bounds->Inset(gfx::Insets(0, 0, inset, 0));
break;
case SHELF_ALIGNMENT_LEFT:
bounds->Inset(gfx::Insets(0, inset, 0, 0));
break;
case SHELF_ALIGNMENT_RIGHT:
bounds->Inset(gfx::Insets(0, 0, 0, inset));
break;
}
}
void ShelfLayoutManager::CalculateTargetBounds(
const State& state,
TargetBounds* target_bounds) {
const gfx::Rect& available_bounds(
status_->GetNativeView()->GetRootWindow()->bounds());
gfx::Rect status_size(status_->GetWindowBoundsInScreen().size());
gfx::Size launcher_size = launcher_ ?
launcher_widget()->GetContentsView()->GetPreferredSize() : gfx::Size();
int shelf_size = 0;
int shelf_width = 0, shelf_height = 0;
GetShelfSize(&shelf_width, &shelf_height);
if (state.visibility_state == VISIBLE ||
(state.visibility_state == AUTO_HIDE &&
state.auto_hide_state == AUTO_HIDE_SHOWN)) {
shelf_size = std::max(shelf_width, shelf_height);
} else if (state.visibility_state == AUTO_HIDE &&
state.auto_hide_state == AUTO_HIDE_HIDDEN) {
shelf_size = kAutoHideSize;
}
if (alignment_ == SHELF_ALIGNMENT_BOTTOM) {
int y = available_bounds.bottom();
y -= shelf_size;
// The status widget should extend to the bottom and right edges.
target_bounds->status_bounds_in_root = gfx::Rect(
base::i18n::IsRTL() ? available_bounds.x() :
available_bounds.right() - status_size.width(),
y + shelf_height - status_size.height(),
status_size.width(), status_size.height());
if (launcher_widget()) {
target_bounds->launcher_bounds_in_root = gfx::Rect(
available_bounds.x(),
y + (shelf_height - launcher_size.height()) / 2,
available_bounds.width(),
launcher_size.height());
}
target_bounds->work_area_insets.Set(
0, 0, GetWorkAreaSize(state, shelf_height), 0);
} else {
int x = (alignment_ == SHELF_ALIGNMENT_LEFT) ?
available_bounds.x() + shelf_size - shelf_width :
available_bounds.right() - shelf_size;
target_bounds->status_bounds_in_root = gfx::Rect(
x, available_bounds.bottom() - status_size.height(),
shelf_width, status_size.height());
if (launcher_widget()) {
target_bounds->launcher_bounds_in_root = gfx::Rect(
x,
available_bounds.y(),
launcher_size.width(),
available_bounds.height());
}
if (alignment_ == SHELF_ALIGNMENT_LEFT) {
target_bounds->work_area_insets.Set(
0, GetWorkAreaSize(state, shelf_width), 0, 0);
} else {
target_bounds->work_area_insets.Set(
0, 0, 0, GetWorkAreaSize(state, shelf_width));
}
}
target_bounds->opacity =
(gesture_drag_status_ != GESTURE_DRAG_NONE ||
state.visibility_state == VISIBLE ||
state.visibility_state == AUTO_HIDE) ? 1.0f : 0.0f;
if (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS)
UpdateTargetBoundsForGesture(target_bounds);
}
void ShelfLayoutManager::UpdateTargetBoundsForGesture(
TargetBounds* target_bounds) const {
CHECK_EQ(GESTURE_DRAG_IN_PROGRESS, gesture_drag_status_);
bool horizontal = alignment() == SHELF_ALIGNMENT_BOTTOM;
const int kFreeMoveRegion = 40;
int resistance_free_region = kFreeMoveRegion;
if (gesture_drag_auto_hide_state_ == AUTO_HIDE_HIDDEN) {
// If the shelf was hidden when the drag started, then allow the drag some
// resistance-free region at first to make sure the shelf sticks with the
// finger until the shelf is visible.
resistance_free_region += horizontal ?
target_bounds->launcher_bounds_in_root.height() :
target_bounds->launcher_bounds_in_root.width();
resistance_free_region -= kAutoHideSize;
}
bool resist = false;
if (horizontal)
resist = gesture_drag_amount_ < -resistance_free_region;
else if (alignment() == SHELF_ALIGNMENT_LEFT)
resist = gesture_drag_amount_ > resistance_free_region;
else
resist = 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, 5 * sqrtf(diff));
if (gesture_drag_amount_ < 0)
translate = -resistance_free_region - diff;
else
translate = resistance_free_region + diff;
} else {
translate = gesture_drag_amount_;
}
if (horizontal) {
// Move the launcher with the gesture.
target_bounds->launcher_bounds_in_root.Offset(0, translate);
if (translate > 0) {
// When dragging down, the statusbar should move.
target_bounds->status_bounds_in_root.Offset(0, translate);
} else {
// When dragging up, the launcher height should increase.
resistance_free_region -= kFreeMoveRegion;
float move = std::max(translate,
-static_cast<float>(resistance_free_region));
target_bounds->launcher_bounds_in_root.set_height(
target_bounds->launcher_bounds_in_root.height() + move - translate);
// The statusbar should move up, but very little.
target_bounds->status_bounds_in_root.Offset(0,
move - sqrtf(move - translate));
}
} else {
// Move the launcher with the gesture.
if (alignment() == SHELF_ALIGNMENT_RIGHT)
target_bounds->launcher_bounds_in_root.Offset(translate, 0);
if ((translate > 0 && alignment() == SHELF_ALIGNMENT_RIGHT) ||
(translate < 0 && alignment() == SHELF_ALIGNMENT_LEFT)) {
// When dragging towards the edge, the statusbar should move.
target_bounds->status_bounds_in_root.Offset(translate, 0);
} else {
// When dragging away from the edge, the launcher width should increase.
resistance_free_region -= kFreeMoveRegion;
float move = alignment() == SHELF_ALIGNMENT_RIGHT ?
std::max(translate, -static_cast<float>(resistance_free_region)) :
std::min(translate, static_cast<float>(resistance_free_region));
if (alignment() == SHELF_ALIGNMENT_RIGHT) {
target_bounds->launcher_bounds_in_root.set_width(
target_bounds->launcher_bounds_in_root.width() + move - translate);
// The statusbar should move, but very little.
target_bounds->status_bounds_in_root.Offset(
move - sqrtf(move - translate), 0);
} else {
target_bounds->launcher_bounds_in_root.set_width(
target_bounds->launcher_bounds_in_root.width() - move + translate);
// The statusbar should move, but very little.
target_bounds->status_bounds_in_root.Offset(sqrtf(translate - move), 0);
}
}
}
}
void ShelfLayoutManager::UpdateShelfBackground(
BackgroundAnimator::ChangeType type) {
bool launcher_paints = GetLauncherPaintsBackground();
if (launcher_)
launcher_->SetPaintsBackground(launcher_paints, type);
// The status area normally draws a background, but we don't want it to draw a
// background when the launcher does.
StatusAreaWidget* status_area_widget =
Shell::GetInstance()->status_area_widget();
if (status_area_widget)
status_area_widget->SetPaintsBackground(!launcher_paints, type);
}
bool ShelfLayoutManager::GetLauncherPaintsBackground() const {
return gesture_drag_status_ != GESTURE_DRAG_NONE ||
(!state_.is_screen_locked && window_overlaps_shelf_) ||
state_.visibility_state == AUTO_HIDE;
}
void ShelfLayoutManager::UpdateAutoHideStateNow() {
SetState(state_.visibility_state);
}
ShelfLayoutManager::AutoHideState ShelfLayoutManager::CalculateAutoHideState(
VisibilityState visibility_state) const {
if (visibility_state != AUTO_HIDE || !launcher_widget())
return AUTO_HIDE_HIDDEN;
if (gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS)
return gesture_drag_auto_hide_state_;
Shell* shell = Shell::GetInstance();
if (shell->GetAppListTargetVisibility())
return AUTO_HIDE_SHOWN;
if (shell->status_area_widget() &&
shell->status_area_widget()->should_show_launcher())
return AUTO_HIDE_SHOWN;
if (launcher_ && launcher_->IsShowingMenu())
return AUTO_HIDE_SHOWN;
if (launcher_ && launcher_->IsShowingOverflowBubble())
return AUTO_HIDE_SHOWN;
if (launcher_widget()->IsActive() || status_->IsActive())
return AUTO_HIDE_SHOWN;
// Don't show if the user is dragging the mouse.
if (event_filter_.get() && event_filter_->in_mouse_drag())
return AUTO_HIDE_HIDDEN;
bool mouse_over_launcher =
launcher_widget()->GetWindowBoundsInScreen().Contains(
gfx::Screen::GetCursorScreenPoint());
return mouse_over_launcher ? AUTO_HIDE_SHOWN : AUTO_HIDE_HIDDEN;
}
void ShelfLayoutManager::UpdateHitTestBounds() {
gfx::Insets insets;
// Only modify the hit test when the shelf is visible, so we don't mess with
// hover hit testing in the auto-hide state.
if (state_.visibility_state == VISIBLE) {
// Let clicks at the very top of the launcher through so windows can be
// resized with the bottom-right corner and bottom edge.
switch (alignment_) {
case SHELF_ALIGNMENT_BOTTOM:
insets.Set(kWorkspaceAreaBottomInset, 0, 0, 0);
break;
case SHELF_ALIGNMENT_LEFT:
insets.Set(0, 0, 0, kWorkspaceAreaBottomInset);
break;
case SHELF_ALIGNMENT_RIGHT:
insets.Set(0, kWorkspaceAreaBottomInset, 0, 0);
break;
}
}
if (launcher_widget() && launcher_widget()->GetNativeWindow()) {
launcher_widget()->GetNativeWindow()->SetHitTestBoundsOverrideOuter(
insets, 1);
}
status_->GetNativeWindow()->SetHitTestBoundsOverrideOuter( insets, 1);
}
bool ShelfLayoutManager::IsShelfWindow(aura::Window* window) {
if (!window)
return false;
return (launcher_widget() &&
launcher_widget()->GetNativeWindow()->Contains(window)) ||
(status_ && status_->GetNativeWindow()->Contains(window));
}
int ShelfLayoutManager::GetWorkAreaSize(const State& state, int size) const {
if (state.visibility_state == VISIBLE)
return size;
if (state.visibility_state == AUTO_HIDE)
return kAutoHideSize;
return 0;
}
} // namespace internal
} // namespace ash