| 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..e59d078bf3accc0c87fd28249a6d8fe57e7d872c
|
| --- /dev/null
|
| +++ b/ash/wm/overview/window_grid.cc
|
| @@ -0,0 +1,439 @@
|
| +// 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);
|
| + WindowSelectorItem* item = NULL;
|
| +
|
| + 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();
|
| + window_list_.push_back(panels_item);
|
| + }
|
| + panels_item->AddWindow(*iter);
|
| + item = panels_item;
|
| + } else {
|
| + item = new WindowSelectorWindow(*iter);
|
| + item->PrepareForOverview();
|
| + window_list_.push_back(item);
|
| + }
|
| + }
|
| + if (window_list_.empty())
|
| + return;
|
| +
|
| + if (panels_item)
|
| + panels_item->PrepareForOverview();
|
| +
|
| + PositionWindows(true);
|
| +}
|
| +
|
| +WindowGrid::~WindowGrid() {
|
| + for (std::set<aura::Window*>::iterator iter = observed_windows_.begin();
|
| + iter != observed_windows_.end(); iter++) {
|
| + (*iter)->RemoveObserver(this);
|
| + }
|
| +}
|
| +
|
| +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);
|
| + 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
|
|
|