diff options
Diffstat (limited to 'chromium/ash/wm/window_selector.cc')
-rw-r--r-- | chromium/ash/wm/window_selector.cc | 550 |
1 files changed, 550 insertions, 0 deletions
diff --git a/chromium/ash/wm/window_selector.cc b/chromium/ash/wm/window_selector.cc new file mode 100644 index 00000000000..47e20159572 --- /dev/null +++ b/chromium/ash/wm/window_selector.cc @@ -0,0 +1,550 @@ +// 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)) { + // TODO(flackr): StopPropogation prevents generation of gesture events. + // We should find a better way to prevent events from being delivered to + // the window, perhaps a transparent window in front of the target window + // or using EventClientImpl::CanProcessEventsWithinSubtree. + 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::OnTouchEvent(ui::TouchEvent* event) { + if (event->type() != ui::ET_TOUCH_PRESSED) + 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 |