| Index: ash/wm/overview/window_grid.cc
|
| diff --git a/ash/wm/overview/window_grid.cc b/ash/wm/overview/window_grid.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..8da89ecb120dac75841fe9814e4b456e3bbe2219
|
| --- /dev/null
|
| +++ b/ash/wm/overview/window_grid.cc
|
| @@ -0,0 +1,372 @@
|
| +// 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/window_selector.h"
|
| +#include "ash/wm/overview/window_selector_item.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/vector2d.h"
|
| +#include "ui/views/background.h"
|
| +#include "ui/views/view.h"
|
| +#include "ui/views/widget/widget.h"
|
| +
|
| +namespace ash {
|
| +namespace {
|
| +
|
| +// An observer which holds onto the passed widget until the animation is
|
| +// complete.
|
| +class CleanupWidgetAfterAnimationObserver : public ui::LayerAnimationObserver {
|
| + public:
|
| + explicit CleanupWidgetAfterAnimationObserver(
|
| + scoped_ptr<views::Widget> widget);
|
| +
|
| + // ui::LayerAnimationObserver:
|
| + virtual void OnLayerAnimationEnded(
|
| + ui::LayerAnimationSequence* sequence) OVERRIDE;
|
| + virtual void OnLayerAnimationAborted(
|
| + ui::LayerAnimationSequence* sequence) OVERRIDE;
|
| + virtual void OnLayerAnimationScheduled(
|
| + ui::LayerAnimationSequence* sequence) OVERRIDE;
|
| +
|
| + private:
|
| + virtual ~CleanupWidgetAfterAnimationObserver();
|
| +
|
| + scoped_ptr<views::Widget> widget_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver);
|
| +};
|
| +
|
| +CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
|
| + scoped_ptr<views::Widget> widget)
|
| + : widget_(widget.Pass()) {
|
| + widget_->GetNativeWindow()->layer()->GetAnimator()->AddObserver(this);
|
| +}
|
| +
|
| +CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
|
| + widget_->GetNativeWindow()->layer()->GetAnimator()->RemoveObserver(this);
|
| +}
|
| +
|
| +void CleanupWidgetAfterAnimationObserver::OnLayerAnimationEnded(
|
| + ui::LayerAnimationSequence* sequence) {
|
| + delete this;
|
| +}
|
| +
|
| +void CleanupWidgetAfterAnimationObserver::OnLayerAnimationAborted(
|
| + ui::LayerAnimationSequence* sequence) {
|
| + delete this;
|
| +}
|
| +
|
| +void CleanupWidgetAfterAnimationObserver::OnLayerAnimationScheduled(
|
| + ui::LayerAnimationSequence* sequence) {
|
| +}
|
| +
|
| +// 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;
|
| +};
|
| +
|
| +// 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;
|
| +
|
| +// In the conceptual overview table, the window margin is the space reserved
|
| +// around the window within the cell. This margin does not overlap so the
|
| +// closest distance between adjacent window_list will be twice this amount.
|
| +const int kWindowMargin = 30;
|
| +
|
| +const int kOverviewSelectorTransitionMilliseconds = 500;
|
| +
|
| +// The color and opacity of the overview selector.
|
| +const SkColor kWindowOverviewSelectionColor = SK_ColorBLACK;
|
| +const unsigned char kWindowOverviewSelectorOpacity = 128;
|
| +
|
| +// Initializes the selection widget.
|
| +views::Widget* CreateSelectionWidget(aura::Window* root_window) {
|
| + views::Widget* selection_widget_ = 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);
|
| + 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());
|
| +
|
| + return selection_widget_;
|
| +}
|
| +
|
| +// Returns the vector for the fade in animation.
|
| +gfx::Vector2d GetVectorFor(WindowSelector::Direction direction,
|
| + 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,
|
| + std::vector<WindowSelectorItem*> window_list)
|
| + : root_window_(root_window),
|
| + window_list_(window_list),
|
| + selected_index_(0) {
|
| + PositionWindows(true);
|
| +}
|
| +
|
| +WindowGrid::~WindowGrid() {
|
| +}
|
| +
|
| +WindowSelectorItem* WindowGrid::RemoveWindow(aura::Window* window) {
|
| + ScopedVector<WindowSelectorItem>::iterator iter =
|
| + std::find_if(window_list_.begin(), window_list_.end(),
|
| + WindowSelectorItemComparator(window));
|
| + if (iter == window_list_.end())
|
| + return NULL;
|
| +
|
| + WindowSelectorItem* removed_item = *iter;
|
| +
|
| + removed_item->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 (!removed_item->empty())
|
| + return NULL;
|
| +
|
| + // We need to get this value before we erase the window to prevent unspecified
|
| + // behavior.
|
| + size_t removed_index = iter - window_list_.begin();
|
| +
|
| + window_list_.erase(iter);
|
| +
|
| + if (!empty()) {
|
| + PositionWindows(true);
|
| +
|
| + // If existing, reposition the selection widget.
|
| + if (selection_widget_) {
|
| + if (selected_index_ >= removed_index) {
|
| + if (selected_index_ != 0) {
|
| + selected_index_--;
|
| + }
|
| + }
|
| + selection_widget_->SetBounds(GetSelectionBounds());
|
| + }
|
| + }
|
| +
|
| + return removed_item;
|
| +}
|
| +
|
| +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 window_list per row that will fit all of the
|
| + // window_list on screen.
|
| + 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())))));
|
| + rows_ = ((window_list_.size() + columns_ - 1) / columns_);
|
| + window_size.set_width(std::min(
|
| + static_cast<int>(total_bounds.width() / columns_),
|
| + static_cast<int>(total_bounds.height() * kCardAspectRatio / 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() >= columns_ ? 0 :
|
| + (columns_ - window_list_.size()) * window_size.width()) +
|
| + (total_bounds.width() - columns_ * window_size.width())) / 2;
|
| + int y_offset = total_bounds.y() + (total_bounds.height() -
|
| + rows_ * window_size.height()) / 2;
|
| + for (size_t i = 0; i < window_list_.size(); ++i) {
|
| + gfx::Transform transform;
|
| + int column = i % columns_;
|
| + int row = i / columns_;
|
| + gfx::Rect target_bounds(window_size.width() * column + x_offset,
|
| + window_size.height() * row + y_offset,
|
| + window_size.width(),
|
| + window_size.height());
|
| + target_bounds.Inset(kWindowMargin, kWindowMargin);
|
| + 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 (columns_ > window_list_.size())
|
| + columns_ = window_list_.size();
|
| +}
|
| +
|
| +bool WindowGrid::Move(WindowSelector::Direction direction) {
|
| + bool out_of_bounds = MoveSelectedIndex(direction);
|
| +
|
| + if (selection_widget_) {
|
| + gfx::Vector2d fade_out_direction =
|
| + GetVectorFor(direction, selection_widget_->GetNativeWindow()->bounds());
|
| + // If the selection widget is already active, fade it out in the selection
|
| + // direction.
|
| + views::Widget* old_selection = selection_widget_.get();
|
| +
|
| + // CleanupWidgetAfterAnimationObserver will delete itself (and the
|
| + // widget) when the animation is complete.
|
| + {
|
| + new CleanupWidgetAfterAnimationObserver(selection_widget_.Pass());
|
| + 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);
|
| + old_selection->SetOpacity(0);
|
| + old_selection->GetNativeWindow()->SetBounds(
|
| + old_selection->GetNativeWindow()->bounds() + fade_out_direction);
|
| + old_selection->Hide();
|
| + }
|
| + }
|
| + if (!out_of_bounds) {
|
| + // The selected window is in this grid, animate a new selection widget.
|
| + const gfx::Rect target_bounds = GetSelectionBounds();
|
| + gfx::Vector2d fade_out_direction =
|
| + GetVectorFor(direction, target_bounds);
|
| + gfx::Display dst_display = gfx::Screen::GetScreenFor(
|
| + root_window_)->GetDisplayMatching(target_bounds);
|
| +
|
| + selection_widget_.reset(CreateSelectionWidget(root_window_));
|
| + selection_widget_->Show();
|
| + // New selection widget starts with 0 opacity and fades in.
|
| + selection_widget_->GetNativeWindow()->layer()->SetOpacity(0);
|
| + selection_widget_->GetNativeWindow()->SetBoundsInScreen(
|
| + target_bounds - fade_out_direction, dst_display);
|
| + ui::ScopedLayerAnimationSettings animation_settings(
|
| + selection_widget_->GetNativeWindow()->layer()->GetAnimator());
|
| + animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
|
| + kOverviewSelectorTransitionMilliseconds));
|
| + animation_settings.SetPreemptionStrategy(
|
| + ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
|
| + selection_widget_->SetBounds(target_bounds);
|
| + selection_widget_->SetOpacity(kWindowOverviewSelectorOpacity);
|
| + SelectedWindow()->window_label()->GetContentsView()->
|
| + NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, true);
|
| + } else {
|
| + selection_widget_.reset();
|
| + selected_index_ = 0;
|
| + }
|
| + return out_of_bounds;
|
| +}
|
| +
|
| +WindowSelectorItem* WindowGrid::SelectedWindow() const {
|
| + CHECK(selected_index_ < window_list_.size());
|
| + return window_list_[selected_index_];
|
| +}
|
| +
|
| +bool WindowGrid::MoveSelectedIndex(WindowSelector::Direction direction) {
|
| + bool out_of_bounds = false;
|
| + // If the selection widget is active, update the index.
|
| + if (selection_widget_) {
|
| + switch (direction) {
|
| + case WindowSelector::RIGHT:
|
| + if (selected_index_ >= window_list_.size() - 1)
|
| + out_of_bounds = true;
|
| + else
|
| + selected_index_++;
|
| + break;
|
| + case WindowSelector::LEFT:
|
| + if (selected_index_ == 0)
|
| + out_of_bounds = true;
|
| + else
|
| + selected_index_--;
|
| + break;
|
| + case WindowSelector::DOWN:
|
| + if (selected_index_ + columns_ >= window_list_.size())
|
| + selected_index_ = (selected_index_ + 1) % columns_;
|
| + else
|
| + selected_index_ += columns_;
|
| + if (selected_index_ == 0 || selected_index_ >= window_list_.size())
|
| + out_of_bounds = true;
|
| + break;
|
| + case WindowSelector::UP:
|
| + if (selected_index_ == 0) {
|
| + out_of_bounds = true;
|
| + } else {
|
| + if (selected_index_ < columns_) {
|
| + selected_index_--;
|
| + if (!(selected_index_ + columns_ >= window_list_.size())) {
|
| + selected_index_ += columns_ * (rows_ - 1);
|
| + if (selected_index_ >= window_list_.size())
|
| + selected_index_ -= columns_;
|
| + }
|
| + } else {
|
| + selected_index_ -= columns_;
|
| + }
|
| + }
|
| + break;
|
| + }
|
| + } else {
|
| + // If the selection widget is not active, position the index in the "first"
|
| + // window relative to the chosen direction.
|
| + if (direction == WindowSelector::LEFT)
|
| + selected_index_ = window_list_.size() - 1;
|
| + if (direction == WindowSelector::UP) {
|
| + selected_index_ = rows_ * columns_ - 1;
|
| + if (selected_index_ >= window_list_.size())
|
| + selected_index_ -= columns_;
|
| + }
|
| + }
|
| + return out_of_bounds;
|
| +}
|
| +
|
| +const gfx::Rect WindowGrid::GetSelectionBounds() const {
|
| + gfx::Rect bounds = SelectedWindow()->target_bounds();
|
| + bounds.Inset(-kWindowMargin, -kWindowMargin);
|
| + return bounds;
|
| +}
|
| +
|
| +} // namespace ash
|
|
|