blob: 8275c97e295422ded9cef1a175f0935dbf4a4148 [file] [log] [blame]
// Copyright 2014 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/overview/window_grid.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/overview/scoped_transform_overview_window.h"
#include "ash/wm/overview/window_selector.h"
#include "ash/wm/overview/window_selector_item.h"
#include "ash/wm/overview/window_selector_panels.h"
#include "ash/wm/overview/window_selector_window.h"
#include "ash/wm/window_state.h"
#include "base/memory/scoped_vector.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/vector2d.h"
#include "ui/views/background.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_animations.h"
namespace ash {
namespace {
// An observer which holds onto the passed widget until the animation is
// complete.
class CleanupWidgetAfterAnimationObserver
: public ui::ImplicitAnimationObserver {
public:
explicit CleanupWidgetAfterAnimationObserver(
scoped_ptr<views::Widget> widget);
virtual ~CleanupWidgetAfterAnimationObserver();
// ui::ImplicitAnimationObserver:
virtual void OnImplicitAnimationsCompleted() OVERRIDE;
private:
scoped_ptr<views::Widget> widget_;
DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver);
};
CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
scoped_ptr<views::Widget> widget)
: widget_(widget.Pass()) {
}
CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
}
void CleanupWidgetAfterAnimationObserver::OnImplicitAnimationsCompleted() {
delete this;
}
// A comparator for locating a given target window.
struct WindowSelectorItemComparator
: public std::unary_function<WindowSelectorItem*, bool> {
explicit WindowSelectorItemComparator(const aura::Window* target_window)
: target(target_window) {
}
bool operator()(WindowSelectorItem* window) const {
return window->HasSelectableWindow(target);
}
const aura::Window* target;
};
// A comparator for locating a WindowSelectorItem given a targeted window.
struct WindowSelectorItemTargetComparator
: public std::unary_function<WindowSelectorItem*, bool> {
explicit WindowSelectorItemTargetComparator(const aura::Window* target_window)
: target(target_window) {
}
bool operator()(WindowSelectorItem* window) const {
return window->Contains(target);
}
const aura::Window* target;
};
// Conceptually the window overview is a table or grid of cells having this
// fixed aspect ratio. The number of columns is determined by maximizing the
// area of them based on the number of window_list.
const float kCardAspectRatio = 4.0f / 3.0f;
// The minimum number of cards along the major axis (i.e. horizontally on a
// landscape orientation).
const int kMinCardsMajor = 3;
const int kOverviewSelectorTransitionMilliseconds = 100;
// The color and opacity of the overview selector.
const SkColor kWindowOverviewSelectionColor = SK_ColorBLACK;
const unsigned char kWindowOverviewSelectorOpacity = 128;
// Returns the vector for the fade in animation.
gfx::Vector2d GetSlideVectorForFadeIn(WindowSelector::Direction direction,
const gfx::Rect& bounds) {
gfx::Vector2d vector;
switch (direction) {
case WindowSelector::DOWN:
vector.set_y(bounds.width());
break;
case WindowSelector::RIGHT:
vector.set_x(bounds.height());
break;
case WindowSelector::UP:
vector.set_y(-bounds.width());
break;
case WindowSelector::LEFT:
vector.set_x(-bounds.height());
break;
}
return vector;
}
} // namespace
WindowGrid::WindowGrid(aura::Window* root_window,
const std::vector<aura::Window*>& windows,
WindowSelector* window_selector)
: root_window_(root_window),
window_selector_(window_selector) {
WindowSelectorPanels* panels_item = NULL;
for (aura::Window::Windows::const_iterator iter = windows.begin();
iter != windows.end(); ++iter) {
if ((*iter)->GetRootWindow() != root_window)
continue;
(*iter)->AddObserver(this);
observed_windows_.insert(*iter);
if ((*iter)->type() == ui::wm::WINDOW_TYPE_PANEL &&
wm::GetWindowState(*iter)->panel_attached()) {
// Attached panel windows are grouped into a single overview item per
// grid.
if (!panels_item) {
panels_item = new WindowSelectorPanels(root_window_);
window_list_.push_back(panels_item);
}
panels_item->AddWindow(*iter);
} else {
window_list_.push_back(new WindowSelectorWindow(*iter));
}
}
if (window_list_.empty())
return;
}
WindowGrid::~WindowGrid() {
for (std::set<aura::Window*>::iterator iter = observed_windows_.begin();
iter != observed_windows_.end(); iter++) {
(*iter)->RemoveObserver(this);
}
}
void WindowGrid::PrepareForOverview() {
for (ScopedVector<WindowSelectorItem>::iterator iter = window_list_.begin();
iter != window_list_.end(); ++iter) {
(*iter)->PrepareForOverview();
}
}
void WindowGrid::PositionWindows(bool animate) {
CHECK(!window_list_.empty());
gfx::Size window_size;
gfx::Rect total_bounds = ScreenUtil::ConvertRectToScreen(
root_window_,
ScreenUtil::GetDisplayWorkAreaBoundsInParent(
Shell::GetContainer(root_window_, kShellWindowId_DefaultContainer)));
// Find the minimum number of windows per row that will fit all of the
// windows on screen.
num_columns_ = std::max(
total_bounds.width() > total_bounds.height() ? kMinCardsMajor : 1,
static_cast<int>(ceil(sqrt(total_bounds.width() * window_list_.size() /
(kCardAspectRatio * total_bounds.height())))));
int num_rows = ((window_list_.size() + num_columns_ - 1) / num_columns_);
window_size.set_width(std::min(
static_cast<int>(total_bounds.width() / num_columns_),
static_cast<int>(total_bounds.height() * kCardAspectRatio / num_rows)));
window_size.set_height(window_size.width() / kCardAspectRatio);
// Calculate the X and Y offsets necessary to center the grid.
int x_offset = total_bounds.x() + ((window_list_.size() >= num_columns_ ? 0 :
(num_columns_ - window_list_.size()) * window_size.width()) +
(total_bounds.width() - num_columns_ * window_size.width())) / 2;
int y_offset = total_bounds.y() + (total_bounds.height() -
num_rows * window_size.height()) / 2;
for (size_t i = 0; i < window_list_.size(); ++i) {
gfx::Transform transform;
int column = i % num_columns_;
int row = i / num_columns_;
gfx::Rect target_bounds(window_size.width() * column + x_offset,
window_size.height() * row + y_offset,
window_size.width(),
window_size.height());
window_list_[i]->SetBounds(root_window_, target_bounds, animate);
}
// If we have less than |kMinCardsMajor| windows, adjust the column_ value to
// reflect how many "real" columns we have.
if (num_columns_ > window_list_.size())
num_columns_ = window_list_.size();
// If the selection widget is active, reposition it without any animation.
if (selection_widget_)
MoveSelectionWidgetToTarget(animate);
}
bool WindowGrid::Move(WindowSelector::Direction direction) {
bool recreate_selection_widget = false;
bool out_of_bounds = false;
if (!selection_widget_) {
switch (direction) {
case WindowSelector::LEFT:
selected_index_ = window_list_.size() - 1;
break;
case WindowSelector::UP:
selected_index_ =
(window_list_.size() / num_columns_) * num_columns_ - 1;
break;
case WindowSelector::RIGHT:
case WindowSelector::DOWN:
selected_index_ = 0;
break;
}
} else {
switch (direction) {
case WindowSelector::RIGHT:
if (selected_index_ >= window_list_.size() - 1)
out_of_bounds = true;
selected_index_++;
if (selected_index_ % num_columns_ == 0)
recreate_selection_widget = true;
break;
case WindowSelector::LEFT:
if (selected_index_ == 0)
out_of_bounds = true;
selected_index_--;
if ((selected_index_ + 1) % num_columns_ == 0)
recreate_selection_widget = true;
break;
case WindowSelector::DOWN:
selected_index_ += num_columns_;
if (selected_index_ >= window_list_.size()) {
selected_index_ = (selected_index_ + 1) % num_columns_;
if (selected_index_ == 0)
out_of_bounds = true;
recreate_selection_widget = true;
}
break;
case WindowSelector::UP:
if (selected_index_ == 0)
out_of_bounds = true;
if (selected_index_ < num_columns_) {
selected_index_ += num_columns_ *
((window_list_.size() - selected_index_) / num_columns_) - 1;
recreate_selection_widget = true;
} else {
selected_index_ -= num_columns_;
}
break;
}
}
MoveSelectionWidget(direction, recreate_selection_widget, out_of_bounds);
return out_of_bounds;
}
WindowSelectorItem* WindowGrid::SelectedWindow() const {
CHECK(selected_index_ < window_list_.size());
return window_list_[selected_index_];
}
bool WindowGrid::Contains(const aura::Window* window) const {
return std::find_if(window_list_.begin(), window_list_.end(),
WindowSelectorItemTargetComparator(window)) !=
window_list_.end();
}
void WindowGrid::OnWindowDestroying(aura::Window* window) {
window->RemoveObserver(this);
observed_windows_.erase(window);
ScopedVector<WindowSelectorItem>::iterator iter =
std::find_if(window_list_.begin(), window_list_.end(),
WindowSelectorItemComparator(window));
DCHECK(iter != window_list_.end());
(*iter)->RemoveWindow(window);
// If there are still windows in this selector entry then the overview is
// still active and the active selection remains the same.
if (!(*iter)->empty())
return;
size_t removed_index = iter - window_list_.begin();
window_list_.erase(iter);
if (empty()) {
// If the grid is now empty, notify the window selector so that it erases us
// from its grid list.
window_selector_->OnGridEmpty(this);
return;
}
// If selecting, update the selection index.
if (selection_widget_) {
bool send_focus_alert = selected_index_ == removed_index;
if (selected_index_ >= removed_index && selected_index_ != 0)
selected_index_--;
if (send_focus_alert)
SelectedWindow()->SendFocusAlert();
}
PositionWindows(true);
}
void WindowGrid::OnWindowBoundsChanged(aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds) {
ScopedVector<WindowSelectorItem>::const_iterator iter =
std::find_if(window_list_.begin(), window_list_.end(),
WindowSelectorItemTargetComparator(window));
DCHECK(iter != window_list_.end());
// Immediately finish any active bounds animation.
window->layer()->GetAnimator()->StopAnimatingProperty(
ui::LayerAnimationElement::BOUNDS);
// Recompute the transform for the window.
(*iter)->RecomputeWindowTransforms();
}
void WindowGrid::InitSelectionWidget(WindowSelector::Direction direction) {
selection_widget_.reset(new views::Widget);
views::Widget::InitParams params;
params.type = views::Widget::InitParams::TYPE_POPUP;
params.keep_on_top = false;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
params.parent = Shell::GetContainer(root_window_,
kShellWindowId_DefaultContainer);
params.accept_events = false;
selection_widget_->set_focus_on_creation(false);
selection_widget_->Init(params);
// Disable the "bounce in" animation when showing the window.
::wm::SetWindowVisibilityAnimationTransition(
selection_widget_->GetNativeWindow(), ::wm::ANIMATE_NONE);
// The selection widget should not activate the shelf when passing under it.
ash::wm::GetWindowState(selection_widget_->GetNativeWindow())->
set_ignored_by_shelf(true);
views::View* content_view = new views::View;
content_view->set_background(
views::Background::CreateSolidBackground(kWindowOverviewSelectionColor));
selection_widget_->SetContentsView(content_view);
selection_widget_->GetNativeWindow()->parent()->StackChildAtBottom(
selection_widget_->GetNativeWindow());
selection_widget_->Show();
// New selection widget starts with 0 opacity and then fades in.
selection_widget_->GetNativeWindow()->layer()->SetOpacity(0);
const gfx::Rect target_bounds = SelectedWindow()->target_bounds();
gfx::Vector2d fade_out_direction =
GetSlideVectorForFadeIn(direction, target_bounds);
gfx::Display dst_display = gfx::Screen::GetScreenFor(root_window_)->
GetDisplayMatching(target_bounds);
selection_widget_->GetNativeWindow()->SetBoundsInScreen(
target_bounds - fade_out_direction, dst_display);
}
void WindowGrid::MoveSelectionWidget(WindowSelector::Direction direction,
bool recreate_selection_widget,
bool out_of_bounds) {
// If the selection widget is already active, fade it out in the selection
// direction.
if (selection_widget_ && (recreate_selection_widget || out_of_bounds)) {
// Animate the old selection widget and then destroy it.
views::Widget* old_selection = selection_widget_.get();
gfx::Vector2d fade_out_direction =
GetSlideVectorForFadeIn(
direction, old_selection->GetNativeWindow()->bounds());
ui::ScopedLayerAnimationSettings animation_settings(
old_selection->GetNativeWindow()->layer()->GetAnimator());
animation_settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(
kOverviewSelectorTransitionMilliseconds));
animation_settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
animation_settings.SetTweenType(gfx::Tween::FAST_OUT_LINEAR_IN);
// CleanupWidgetAfterAnimationObserver will delete itself (and the
// widget) when the movement animation is complete.
animation_settings.AddObserver(
new CleanupWidgetAfterAnimationObserver(selection_widget_.Pass()));
old_selection->SetOpacity(0);
old_selection->GetNativeWindow()->SetBounds(
old_selection->GetNativeWindow()->bounds() + fade_out_direction);
old_selection->Hide();
}
if (out_of_bounds)
return;
if (!selection_widget_)
InitSelectionWidget(direction);
// Send an a11y alert so that if ChromeVox is enabled, the item label is
// read.
SelectedWindow()->SendFocusAlert();
// The selection widget is moved to the newly selected item in the same
// grid.
MoveSelectionWidgetToTarget(true);
}
void WindowGrid::MoveSelectionWidgetToTarget(bool animate) {
if (animate) {
ui::ScopedLayerAnimationSettings animation_settings(
selection_widget_->GetNativeWindow()->layer()->GetAnimator());
animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
kOverviewSelectorTransitionMilliseconds));
animation_settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
animation_settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
selection_widget_->SetBounds(SelectedWindow()->target_bounds());
selection_widget_->SetOpacity(kWindowOverviewSelectorOpacity);
return;
}
selection_widget_->SetBounds(SelectedWindow()->target_bounds());
selection_widget_->SetOpacity(kWindowOverviewSelectorOpacity);
}
} // namespace ash