blob: 8e87a95163cd0c7cd5eb7f8c399115cabf37d68f [file] [log] [blame]
// Copyright 2013 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/window_selector.h"
#include <algorithm>
#include "ash/screen_ash.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/window_selector_delegate.h"
#include "ash/wm/window_util.h"
#include "base/memory/scoped_ptr.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/root_window.h"
#include "ui/aura/window.h"
#include "ui/base/events/event.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/display.h"
#include "ui/gfx/interpolated_transform.h"
#include "ui/gfx/transform_util.h"
#include "ui/views/corewm/shadow_types.h"
#include "ui/views/corewm/window_animations.h"
#include "ui/views/corewm/window_util.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
const float kCardAspectRatio = 4.0f / 3.0f;
const int kWindowMargin = 30;
const int kMinCardsMajor = 3;
const int kOverviewTransitionMilliseconds = 100;
const SkColor kWindowSelectorSelectionColor = SK_ColorBLACK;
const float kWindowSelectorSelectionOpacity = 0.5f;
const int kWindowSelectorSelectionPadding = 15;
// Creates a copy of |window| with |recreated_layer| in the |target_root|.
views::Widget* CreateCopyOfWindow(aura::RootWindow* target_root,
aura::Window* src_window,
ui::Layer* recreated_layer) {
views::Widget* widget = new views::Widget;
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
params.parent = src_window->parent();
params.can_activate = false;
params.keep_on_top = true;
widget->set_focus_on_creation(false);
widget->Init(params);
widget->SetVisibilityChangedAnimationsEnabled(false);
std::string name = src_window->name() + " (Copy)";
widget->GetNativeWindow()->SetName(name);
views::corewm::SetShadowType(widget->GetNativeWindow(),
views::corewm::SHADOW_TYPE_RECTANGULAR);
// Set the bounds in the target root window.
gfx::Display target_display =
Shell::GetScreen()->GetDisplayNearestWindow(target_root);
aura::client::ScreenPositionClient* screen_position_client =
aura::client::GetScreenPositionClient(src_window->GetRootWindow());
if (screen_position_client && target_display.is_valid()) {
screen_position_client->SetBounds(widget->GetNativeWindow(),
src_window->GetBoundsInScreen(), target_display);
} else {
widget->SetBounds(src_window->GetBoundsInScreen());
}
widget->StackAbove(src_window);
// Move the |recreated_layer| to the newly created window.
recreated_layer->set_delegate(src_window->layer()->delegate());
gfx::Rect layer_bounds = recreated_layer->bounds();
layer_bounds.set_origin(gfx::Point(0, 0));
recreated_layer->SetBounds(layer_bounds);
recreated_layer->SetVisible(false);
recreated_layer->parent()->Remove(recreated_layer);
aura::Window* window = widget->GetNativeWindow();
recreated_layer->SetVisible(true);
window->layer()->Add(recreated_layer);
window->layer()->StackAtTop(recreated_layer);
window->layer()->SetOpacity(1);
window->Show();
return widget;
}
// An observer which closes the widget and deletes the layer after an
// animation finishes.
class CleanupWidgetAfterAnimationObserver : public ui::LayerAnimationObserver {
public:
CleanupWidgetAfterAnimationObserver(views::Widget* widget, ui::Layer* layer);
virtual void OnLayerAnimationEnded(
ui::LayerAnimationSequence* sequence) OVERRIDE;
virtual void OnLayerAnimationAborted(
ui::LayerAnimationSequence* sequence) OVERRIDE;
virtual void OnLayerAnimationScheduled(
ui::LayerAnimationSequence* sequence) OVERRIDE;
protected:
virtual ~CleanupWidgetAfterAnimationObserver();
private:
views::Widget* widget_;
ui::Layer* layer_;
DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver);
};
CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
views::Widget* widget,
ui::Layer* layer)
: widget_(widget),
layer_(layer) {
widget_->GetNativeWindow()->layer()->GetAnimator()->AddObserver(this);
}
void CleanupWidgetAfterAnimationObserver::OnLayerAnimationEnded(
ui::LayerAnimationSequence* sequence) {
delete this;
}
void CleanupWidgetAfterAnimationObserver::OnLayerAnimationAborted(
ui::LayerAnimationSequence* sequence) {
delete this;
}
void CleanupWidgetAfterAnimationObserver::OnLayerAnimationScheduled(
ui::LayerAnimationSequence* sequence) {
}
CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
widget_->GetNativeWindow()->layer()->GetAnimator()->RemoveObserver(this);
widget_->Close();
widget_ = NULL;
if (layer_) {
views::corewm::DeepDeleteLayers(layer_);
layer_ = NULL;
}
}
// The animation settings used for window selector animations.
class WindowSelectorAnimationSettings
: public ui::ScopedLayerAnimationSettings {
public:
WindowSelectorAnimationSettings(aura::Window* window) :
ui::ScopedLayerAnimationSettings(window->layer()->GetAnimator()) {
SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
SetTransitionDuration(
base::TimeDelta::FromMilliseconds(kOverviewTransitionMilliseconds));
}
virtual ~WindowSelectorAnimationSettings() {
}
};
} // namespace
// TODO(flackr): Split up into separate file under subdirectory in ash/wm.
class WindowSelectorWindow {
public:
explicit WindowSelectorWindow(aura::Window* window);
virtual ~WindowSelectorWindow();
aura::Window* window() { return window_; }
const aura::Window* window() const { return window_; }
// Returns true if this window selector window contains the |target|. This is
// used to determine if an event targetted this window.
bool Contains(const aura::Window* target) const;
// Restores this window on exit rather than returning it to a minimized state
// if it was minimized on entering overview mode.
void RestoreWindowOnExit();
// Informs the WindowSelectorWindow that the window being watched was
// destroyed. This resets the internal window pointer to avoid calling
// anything on the window at destruction time.
void OnWindowDestroyed();
// Applies a transform to the window to fit within |target_bounds| while
// maintaining its aspect ratio.
void TransformToFitBounds(aura::RootWindow* root_window,
const gfx::Rect& target_bounds);
gfx::Rect bounds() { return fit_bounds_; }
private:
// A weak pointer to the real window in the overview.
aura::Window* window_;
// A copy of the window used to transition the window to another root.
views::Widget* window_copy_;
// A weak pointer to a deep copy of the window's layers.
ui::Layer* layer_;
// If true, the window was minimized and should be restored if the window
// was not selected.
bool minimized_;
// The original transform of the window before entering overview mode.
gfx::Transform original_transform_;
// The bounds this window is fit to.
gfx::Rect fit_bounds_;
DISALLOW_COPY_AND_ASSIGN(WindowSelectorWindow);
};
WindowSelectorWindow::WindowSelectorWindow(aura::Window* window)
: window_(window),
window_copy_(NULL),
layer_(NULL),
minimized_(window->GetProperty(aura::client::kShowStateKey) ==
ui::SHOW_STATE_MINIMIZED),
original_transform_(window->layer()->transform()) {
if (minimized_)
window_->Show();
}
WindowSelectorWindow::~WindowSelectorWindow() {
if (window_) {
WindowSelectorAnimationSettings animation_settings(window_);
gfx::Transform transform;
window_->SetTransform(original_transform_);
if (minimized_) {
// Setting opacity 0 and visible false ensures that the property change
// to SHOW_STATE_MINIMIZED will not animate the window from its original
// bounds to the minimized position.
window_->layer()->SetOpacity(0);
window_->layer()->SetVisible(false);
window_->SetProperty(aura::client::kShowStateKey,
ui::SHOW_STATE_MINIMIZED);
}
}
// If a copy of the window was created, clean it up.
if (window_copy_) {
if (window_) {
// If the initial window wasn't destroyed, the copy needs to be animated
// out. CleanupWidgetAfterAnimationObserver will destroy the widget and
// layer after the animation is complete.
new CleanupWidgetAfterAnimationObserver(window_copy_, layer_);
WindowSelectorAnimationSettings animation_settings(
window_copy_->GetNativeWindow());
window_copy_->GetNativeWindow()->SetTransform(original_transform_);
} else {
window_copy_->Close();
if (layer_)
views::corewm::DeepDeleteLayers(layer_);
}
window_copy_ = NULL;
layer_ = NULL;
}
}
bool WindowSelectorWindow::Contains(const aura::Window* window) const {
if (window_copy_ && window_copy_->GetNativeWindow()->Contains(window))
return true;
return window_->Contains(window);
}
void WindowSelectorWindow::RestoreWindowOnExit() {
minimized_ = false;
original_transform_ = gfx::Transform();
}
void WindowSelectorWindow::OnWindowDestroyed() {
window_ = NULL;
}
void WindowSelectorWindow::TransformToFitBounds(
aura::RootWindow* root_window,
const gfx::Rect& target_bounds) {
fit_bounds_ = target_bounds;
const gfx::Rect bounds = window_->GetBoundsInScreen();
float scale = std::min(1.0f,
std::min(static_cast<float>(target_bounds.width()) / bounds.width(),
static_cast<float>(target_bounds.height()) / bounds.height()));
gfx::Transform transform;
gfx::Vector2d offset(
0.5 * (target_bounds.width() - scale * bounds.width()),
0.5 * (target_bounds.height() - scale * bounds.height()));
transform.Translate(target_bounds.x() - bounds.x() + offset.x(),
target_bounds.y() - bounds.y() + offset.y());
transform.Scale(scale, scale);
if (root_window != window_->GetRootWindow()) {
if (!window_copy_) {
DCHECK(!layer_);
layer_ = views::corewm::RecreateWindowLayers(window_, true);
window_copy_ = CreateCopyOfWindow(root_window, window_, layer_);
}
WindowSelectorAnimationSettings animation_settings(
window_copy_->GetNativeWindow());
window_copy_->GetNativeWindow()->SetTransform(transform);
}
WindowSelectorAnimationSettings animation_settings(window_);
window_->SetTransform(transform);
}
// A comparator for locating a given target window.
struct WindowSelectorWindowComparator
: public std::unary_function<WindowSelectorWindow*, bool> {
explicit WindowSelectorWindowComparator(const aura::Window* target_window)
: target(target_window) {
}
bool operator()(const WindowSelectorWindow* window) const {
return target == window->window();
}
const aura::Window* target;
};
WindowSelector::WindowSelector(const WindowList& windows,
WindowSelector::Mode mode,
WindowSelectorDelegate* delegate)
: mode_(mode),
delegate_(delegate),
selected_window_(0),
selection_root_(NULL) {
DCHECK(delegate_);
for (size_t i = 0; i < windows.size(); ++i) {
windows[i]->AddObserver(this);
windows_.push_back(new WindowSelectorWindow(windows[i]));
}
if (mode == WindowSelector::CYCLE)
selection_root_ = ash::Shell::GetActiveRootWindow();
PositionWindows();
ash::Shell::GetInstance()->AddPreTargetHandler(this);
}
WindowSelector::~WindowSelector() {
for (size_t i = 0; i < windows_.size(); i++) {
windows_[i]->window()->RemoveObserver(this);
}
ash::Shell::GetInstance()->RemovePreTargetHandler(this);
}
void WindowSelector::Step(WindowSelector::Direction direction) {
DCHECK(windows_.size() > 0);
if (!selection_widget_)
InitializeSelectionWidget();
selected_window_ = (selected_window_ + windows_.size() +
(direction == WindowSelector::FORWARD ? 1 : -1)) % windows_.size();
UpdateSelectionLocation(true);
}
void WindowSelector::SelectWindow() {
delegate_->OnWindowSelected(windows_[selected_window_]->window());
}
void WindowSelector::OnEvent(ui::Event* event) {
// If the event is targetted at any of the windows in the overview, then
// prevent it from propagating.
aura::Window* target = static_cast<aura::Window*>(event->target());
for (size_t i = 0; i < windows_.size(); ++i) {
if (windows_[i]->Contains(target)) {
event->StopPropagation();
break;
}
}
// This object may not be valid after this call as a selection event can
// trigger deletion of the window selector.
ui::EventHandler::OnEvent(event);
}
void WindowSelector::OnMouseEvent(ui::MouseEvent* event) {
if (event->type() != ui::ET_MOUSE_RELEASED)
return;
WindowSelectorWindow* target = GetEventTarget(event);
if (!target)
return;
HandleSelectionEvent(target);
}
void WindowSelector::OnGestureEvent(ui::GestureEvent* event) {
if (event->type() != ui::ET_GESTURE_TAP)
return;
WindowSelectorWindow* target = GetEventTarget(event);
if (!target)
return;
HandleSelectionEvent(target);
}
void WindowSelector::OnWindowDestroyed(aura::Window* window) {
ScopedVector<WindowSelectorWindow>::iterator iter =
std::find_if(windows_.begin(), windows_.end(),
WindowSelectorWindowComparator(window));
DCHECK(iter != windows_.end());
size_t deleted_index = iter - windows_.begin();
(*iter)->OnWindowDestroyed();
windows_.erase(iter);
if (windows_.empty()) {
delegate_->OnSelectionCanceled();
return;
}
if (selected_window_ >= deleted_index) {
if (selected_window_ > deleted_index)
selected_window_--;
selected_window_ = selected_window_ % windows_.size();
UpdateSelectionLocation(true);
}
PositionWindows();
}
WindowSelectorWindow* WindowSelector::GetEventTarget(ui::LocatedEvent* event) {
aura::Window* target = static_cast<aura::Window*>(event->target());
// If the target window doesn't actually contain the event location (i.e.
// mouse down over the window and mouse up elsewhere) then do not select the
// window.
if (!target->HitTest(event->location()))
return NULL;
for (size_t i = 0; i < windows_.size(); i++) {
if (windows_[i]->Contains(target))
return windows_[i];
}
return NULL;
}
void WindowSelector::HandleSelectionEvent(WindowSelectorWindow* target) {
// The selected window should not be minimized when window selection is
// ended.
target->RestoreWindowOnExit();
delegate_->OnWindowSelected(target->window());
}
void WindowSelector::PositionWindows() {
if (selection_root_) {
DCHECK_EQ(mode_, CYCLE);
std::vector<WindowSelectorWindow*> windows;
for (size_t i = 0; i < windows_.size(); ++i)
windows.push_back(windows_[i]);
PositionWindowsOnRoot(selection_root_, windows);
} else {
DCHECK_EQ(mode_, OVERVIEW);
Shell::RootWindowList root_window_list = Shell::GetAllRootWindows();
for (size_t i = 0; i < root_window_list.size(); ++i)
PositionWindowsFromRoot(root_window_list[i]);
}
}
void WindowSelector::PositionWindowsFromRoot(aura::RootWindow* root_window) {
std::vector<WindowSelectorWindow*> windows;
for (size_t i = 0; i < windows_.size(); ++i) {
if (windows_[i]->window()->GetRootWindow() == root_window)
windows.push_back(windows_[i]);
}
PositionWindowsOnRoot(root_window, windows);
}
void WindowSelector::PositionWindowsOnRoot(
aura::RootWindow* root_window,
const std::vector<WindowSelectorWindow*>& windows) {
if (windows.empty())
return;
gfx::Size window_size;
gfx::Rect total_bounds = ScreenAsh::ConvertRectToScreen(root_window,
ScreenAsh::GetDisplayWorkAreaBoundsInParent(
Shell::GetContainer(root_window,
internal::kShellWindowId_DefaultContainer)));
// Find the minimum number of windows per row that will fit all of the
// windows on screen.
size_t columns = std::max(
total_bounds.width() > total_bounds.height() ? kMinCardsMajor : 1,
static_cast<int>(ceil(sqrt(total_bounds.width() * windows.size() /
(kCardAspectRatio * total_bounds.height())))));
size_t rows = ((windows.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() + ((windows.size() >= columns ? 0 :
(columns - windows.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 < windows.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);
windows[i]->TransformToFitBounds(root_window, target_bounds);
}
}
void WindowSelector::InitializeSelectionWidget() {
selection_widget_.reset(new views::Widget);
views::Widget::InitParams params;
params.type = views::Widget::InitParams::TYPE_POPUP;
params.can_activate = false;
params.keep_on_top = false;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.opacity = views::Widget::InitParams::OPAQUE_WINDOW;
params.parent = Shell::GetContainer(
selection_root_,
internal::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(kWindowSelectorSelectionColor));
selection_widget_->SetContentsView(content_view);
UpdateSelectionLocation(false);
selection_widget_->GetNativeWindow()->parent()->StackChildAtBottom(
selection_widget_->GetNativeWindow());
selection_widget_->Show();
selection_widget_->GetNativeWindow()->layer()->SetOpacity(
kWindowSelectorSelectionOpacity);
}
void WindowSelector::UpdateSelectionLocation(bool animate) {
if (!selection_widget_)
return;
gfx::Rect target_bounds = windows_[selected_window_]->bounds();
target_bounds.Inset(-kWindowSelectorSelectionPadding,
-kWindowSelectorSelectionPadding);
if (animate) {
WindowSelectorAnimationSettings animation_settings(
selection_widget_->GetNativeWindow());
selection_widget_->SetBounds(target_bounds);
} else {
selection_widget_->SetBounds(target_bounds);
}
}
} // namespace ash