Home | History | Annotate | Download | only in wm
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "ash/wm/window_selector.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "ash/screen_ash.h"
     10 #include "ash/shell.h"
     11 #include "ash/shell_window_ids.h"
     12 #include "ash/wm/window_selector_delegate.h"
     13 #include "ash/wm/window_util.h"
     14 #include "base/memory/scoped_ptr.h"
     15 #include "third_party/skia/include/core/SkColor.h"
     16 #include "ui/aura/client/aura_constants.h"
     17 #include "ui/aura/client/screen_position_client.h"
     18 #include "ui/aura/root_window.h"
     19 #include "ui/aura/window.h"
     20 #include "ui/base/events/event.h"
     21 #include "ui/compositor/layer_animation_observer.h"
     22 #include "ui/compositor/scoped_layer_animation_settings.h"
     23 #include "ui/gfx/display.h"
     24 #include "ui/gfx/interpolated_transform.h"
     25 #include "ui/gfx/transform_util.h"
     26 #include "ui/views/corewm/shadow_types.h"
     27 #include "ui/views/corewm/window_animations.h"
     28 #include "ui/views/corewm/window_util.h"
     29 #include "ui/views/widget/widget.h"
     30 
     31 namespace ash {
     32 
     33 namespace {
     34 
     35 const float kCardAspectRatio = 4.0f / 3.0f;
     36 const int kWindowMargin = 30;
     37 const int kMinCardsMajor = 3;
     38 const int kOverviewTransitionMilliseconds = 100;
     39 const SkColor kWindowSelectorSelectionColor = SK_ColorBLACK;
     40 const float kWindowSelectorSelectionOpacity = 0.5f;
     41 const int kWindowSelectorSelectionPadding = 15;
     42 
     43 // Creates a copy of |window| with |recreated_layer| in the |target_root|.
     44 views::Widget* CreateCopyOfWindow(aura::RootWindow* target_root,
     45                                   aura::Window* src_window,
     46                                   ui::Layer* recreated_layer) {
     47   views::Widget* widget = new views::Widget;
     48   views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
     49   params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
     50   params.parent = src_window->parent();
     51   params.can_activate = false;
     52   params.keep_on_top = true;
     53   widget->set_focus_on_creation(false);
     54   widget->Init(params);
     55   widget->SetVisibilityChangedAnimationsEnabled(false);
     56   std::string name = src_window->name() + " (Copy)";
     57   widget->GetNativeWindow()->SetName(name);
     58   views::corewm::SetShadowType(widget->GetNativeWindow(),
     59                                views::corewm::SHADOW_TYPE_RECTANGULAR);
     60 
     61   // Set the bounds in the target root window.
     62   gfx::Display target_display =
     63       Shell::GetScreen()->GetDisplayNearestWindow(target_root);
     64   aura::client::ScreenPositionClient* screen_position_client =
     65       aura::client::GetScreenPositionClient(src_window->GetRootWindow());
     66   if (screen_position_client && target_display.is_valid()) {
     67     screen_position_client->SetBounds(widget->GetNativeWindow(),
     68         src_window->GetBoundsInScreen(), target_display);
     69   } else {
     70     widget->SetBounds(src_window->GetBoundsInScreen());
     71   }
     72   widget->StackAbove(src_window);
     73 
     74   // Move the |recreated_layer| to the newly created window.
     75   recreated_layer->set_delegate(src_window->layer()->delegate());
     76   gfx::Rect layer_bounds = recreated_layer->bounds();
     77   layer_bounds.set_origin(gfx::Point(0, 0));
     78   recreated_layer->SetBounds(layer_bounds);
     79   recreated_layer->SetVisible(false);
     80   recreated_layer->parent()->Remove(recreated_layer);
     81 
     82   aura::Window* window = widget->GetNativeWindow();
     83   recreated_layer->SetVisible(true);
     84   window->layer()->Add(recreated_layer);
     85   window->layer()->StackAtTop(recreated_layer);
     86   window->layer()->SetOpacity(1);
     87   window->Show();
     88   return widget;
     89 }
     90 
     91 // An observer which closes the widget and deletes the layer after an
     92 // animation finishes.
     93 class CleanupWidgetAfterAnimationObserver : public ui::LayerAnimationObserver {
     94  public:
     95   CleanupWidgetAfterAnimationObserver(views::Widget* widget, ui::Layer* layer);
     96 
     97   virtual void OnLayerAnimationEnded(
     98       ui::LayerAnimationSequence* sequence) OVERRIDE;
     99   virtual void OnLayerAnimationAborted(
    100       ui::LayerAnimationSequence* sequence) OVERRIDE;
    101   virtual void OnLayerAnimationScheduled(
    102       ui::LayerAnimationSequence* sequence) OVERRIDE;
    103 
    104  protected:
    105   virtual ~CleanupWidgetAfterAnimationObserver();
    106 
    107  private:
    108   views::Widget* widget_;
    109   ui::Layer* layer_;
    110 
    111   DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver);
    112 };
    113 
    114 CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
    115         views::Widget* widget,
    116         ui::Layer* layer)
    117     : widget_(widget),
    118       layer_(layer) {
    119   widget_->GetNativeWindow()->layer()->GetAnimator()->AddObserver(this);
    120 }
    121 
    122 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationEnded(
    123     ui::LayerAnimationSequence* sequence) {
    124   delete this;
    125 }
    126 
    127 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationAborted(
    128     ui::LayerAnimationSequence* sequence) {
    129   delete this;
    130 }
    131 
    132 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationScheduled(
    133     ui::LayerAnimationSequence* sequence) {
    134 }
    135 
    136 CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
    137   widget_->GetNativeWindow()->layer()->GetAnimator()->RemoveObserver(this);
    138   widget_->Close();
    139   widget_ = NULL;
    140   if (layer_) {
    141     views::corewm::DeepDeleteLayers(layer_);
    142     layer_ = NULL;
    143   }
    144 }
    145 
    146 // The animation settings used for window selector animations.
    147 class WindowSelectorAnimationSettings
    148     : public ui::ScopedLayerAnimationSettings {
    149  public:
    150   WindowSelectorAnimationSettings(aura::Window* window) :
    151       ui::ScopedLayerAnimationSettings(window->layer()->GetAnimator()) {
    152     SetPreemptionStrategy(
    153         ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
    154     SetTransitionDuration(
    155         base::TimeDelta::FromMilliseconds(kOverviewTransitionMilliseconds));
    156   }
    157 
    158   virtual ~WindowSelectorAnimationSettings() {
    159   }
    160 };
    161 
    162 }  // namespace
    163 
    164 // TODO(flackr): Split up into separate file under subdirectory in ash/wm.
    165 class WindowSelectorWindow {
    166  public:
    167   explicit WindowSelectorWindow(aura::Window* window);
    168   virtual ~WindowSelectorWindow();
    169 
    170   aura::Window* window() { return window_; }
    171   const aura::Window* window() const { return window_; }
    172 
    173   // Returns true if this window selector window contains the |target|. This is
    174   // used to determine if an event targetted this window.
    175   bool Contains(const aura::Window* target) const;
    176 
    177   // Restores this window on exit rather than returning it to a minimized state
    178   // if it was minimized on entering overview mode.
    179   void RestoreWindowOnExit();
    180 
    181   // Informs the WindowSelectorWindow that the window being watched was
    182   // destroyed. This resets the internal window pointer to avoid calling
    183   // anything on the window at destruction time.
    184   void OnWindowDestroyed();
    185 
    186   // Applies a transform to the window to fit within |target_bounds| while
    187   // maintaining its aspect ratio.
    188   void TransformToFitBounds(aura::RootWindow* root_window,
    189                             const gfx::Rect& target_bounds);
    190 
    191   gfx::Rect bounds() { return fit_bounds_; }
    192 
    193  private:
    194   // A weak pointer to the real window in the overview.
    195   aura::Window* window_;
    196 
    197   // A copy of the window used to transition the window to another root.
    198   views::Widget* window_copy_;
    199 
    200   // A weak pointer to a deep copy of the window's layers.
    201   ui::Layer* layer_;
    202 
    203   // If true, the window was minimized and should be restored if the window
    204   // was not selected.
    205   bool minimized_;
    206 
    207   // The original transform of the window before entering overview mode.
    208   gfx::Transform original_transform_;
    209 
    210   // The bounds this window is fit to.
    211   gfx::Rect fit_bounds_;
    212 
    213   DISALLOW_COPY_AND_ASSIGN(WindowSelectorWindow);
    214 };
    215 
    216 WindowSelectorWindow::WindowSelectorWindow(aura::Window* window)
    217     : window_(window),
    218       window_copy_(NULL),
    219       layer_(NULL),
    220       minimized_(window->GetProperty(aura::client::kShowStateKey) ==
    221                  ui::SHOW_STATE_MINIMIZED),
    222       original_transform_(window->layer()->transform()) {
    223   if (minimized_)
    224     window_->Show();
    225 }
    226 
    227 WindowSelectorWindow::~WindowSelectorWindow() {
    228   if (window_) {
    229     WindowSelectorAnimationSettings animation_settings(window_);
    230     gfx::Transform transform;
    231     window_->SetTransform(original_transform_);
    232     if (minimized_) {
    233       // Setting opacity 0 and visible false ensures that the property change
    234       // to SHOW_STATE_MINIMIZED will not animate the window from its original
    235       // bounds to the minimized position.
    236       window_->layer()->SetOpacity(0);
    237       window_->layer()->SetVisible(false);
    238       window_->SetProperty(aura::client::kShowStateKey,
    239                            ui::SHOW_STATE_MINIMIZED);
    240     }
    241   }
    242   // If a copy of the window was created, clean it up.
    243   if (window_copy_) {
    244     if (window_) {
    245       // If the initial window wasn't destroyed, the copy needs to be animated
    246       // out. CleanupWidgetAfterAnimationObserver will destroy the widget and
    247       // layer after the animation is complete.
    248       new CleanupWidgetAfterAnimationObserver(window_copy_, layer_);
    249       WindowSelectorAnimationSettings animation_settings(
    250           window_copy_->GetNativeWindow());
    251       window_copy_->GetNativeWindow()->SetTransform(original_transform_);
    252     } else {
    253       window_copy_->Close();
    254       if (layer_)
    255         views::corewm::DeepDeleteLayers(layer_);
    256     }
    257     window_copy_ = NULL;
    258     layer_ = NULL;
    259   }
    260 }
    261 
    262 bool WindowSelectorWindow::Contains(const aura::Window* window) const {
    263   if (window_copy_ && window_copy_->GetNativeWindow()->Contains(window))
    264     return true;
    265   return window_->Contains(window);
    266 }
    267 
    268 void WindowSelectorWindow::RestoreWindowOnExit() {
    269   minimized_ = false;
    270   original_transform_ = gfx::Transform();
    271 }
    272 
    273 void WindowSelectorWindow::OnWindowDestroyed() {
    274   window_ = NULL;
    275 }
    276 
    277 void WindowSelectorWindow::TransformToFitBounds(
    278     aura::RootWindow* root_window,
    279     const gfx::Rect& target_bounds) {
    280   fit_bounds_ = target_bounds;
    281   const gfx::Rect bounds = window_->GetBoundsInScreen();
    282   float scale = std::min(1.0f,
    283       std::min(static_cast<float>(target_bounds.width()) / bounds.width(),
    284                static_cast<float>(target_bounds.height()) / bounds.height()));
    285   gfx::Transform transform;
    286   gfx::Vector2d offset(
    287       0.5 * (target_bounds.width() - scale * bounds.width()),
    288       0.5 * (target_bounds.height() - scale * bounds.height()));
    289   transform.Translate(target_bounds.x() - bounds.x() + offset.x(),
    290                       target_bounds.y() - bounds.y() + offset.y());
    291   transform.Scale(scale, scale);
    292   if (root_window != window_->GetRootWindow()) {
    293     if (!window_copy_) {
    294       DCHECK(!layer_);
    295       layer_ = views::corewm::RecreateWindowLayers(window_, true);
    296       window_copy_ = CreateCopyOfWindow(root_window, window_, layer_);
    297     }
    298     WindowSelectorAnimationSettings animation_settings(
    299         window_copy_->GetNativeWindow());
    300     window_copy_->GetNativeWindow()->SetTransform(transform);
    301   }
    302   WindowSelectorAnimationSettings animation_settings(window_);
    303   window_->SetTransform(transform);
    304 }
    305 
    306 // A comparator for locating a given target window.
    307 struct WindowSelectorWindowComparator
    308     : public std::unary_function<WindowSelectorWindow*, bool> {
    309   explicit WindowSelectorWindowComparator(const aura::Window* target_window)
    310       : target(target_window) {
    311   }
    312 
    313   bool operator()(const WindowSelectorWindow* window) const {
    314     return target == window->window();
    315   }
    316 
    317   const aura::Window* target;
    318 };
    319 
    320 WindowSelector::WindowSelector(const WindowList& windows,
    321                                WindowSelector::Mode mode,
    322                                WindowSelectorDelegate* delegate)
    323     : mode_(mode),
    324       delegate_(delegate),
    325       selected_window_(0),
    326       selection_root_(NULL) {
    327   DCHECK(delegate_);
    328   for (size_t i = 0; i < windows.size(); ++i) {
    329     windows[i]->AddObserver(this);
    330     windows_.push_back(new WindowSelectorWindow(windows[i]));
    331   }
    332   if (mode == WindowSelector::CYCLE)
    333     selection_root_ = ash::Shell::GetActiveRootWindow();
    334   PositionWindows();
    335   ash::Shell::GetInstance()->AddPreTargetHandler(this);
    336 }
    337 
    338 WindowSelector::~WindowSelector() {
    339   for (size_t i = 0; i < windows_.size(); i++) {
    340     windows_[i]->window()->RemoveObserver(this);
    341   }
    342   ash::Shell::GetInstance()->RemovePreTargetHandler(this);
    343 }
    344 
    345 void WindowSelector::Step(WindowSelector::Direction direction) {
    346   DCHECK(windows_.size() > 0);
    347   if (!selection_widget_)
    348     InitializeSelectionWidget();
    349   selected_window_ = (selected_window_ + windows_.size() +
    350       (direction == WindowSelector::FORWARD ? 1 : -1)) % windows_.size();
    351   UpdateSelectionLocation(true);
    352 }
    353 
    354 void WindowSelector::SelectWindow() {
    355   delegate_->OnWindowSelected(windows_[selected_window_]->window());
    356 }
    357 
    358 void WindowSelector::OnEvent(ui::Event* event) {
    359   // If the event is targetted at any of the windows in the overview, then
    360   // prevent it from propagating.
    361   aura::Window* target = static_cast<aura::Window*>(event->target());
    362   for (size_t i = 0; i < windows_.size(); ++i) {
    363     if (windows_[i]->Contains(target)) {
    364       event->StopPropagation();
    365       break;
    366     }
    367   }
    368 
    369   // This object may not be valid after this call as a selection event can
    370   // trigger deletion of the window selector.
    371   ui::EventHandler::OnEvent(event);
    372 }
    373 
    374 void WindowSelector::OnMouseEvent(ui::MouseEvent* event) {
    375   if (event->type() != ui::ET_MOUSE_RELEASED)
    376     return;
    377   WindowSelectorWindow* target = GetEventTarget(event);
    378   if (!target)
    379     return;
    380 
    381   HandleSelectionEvent(target);
    382 }
    383 
    384 void WindowSelector::OnGestureEvent(ui::GestureEvent* event) {
    385   if (event->type() != ui::ET_GESTURE_TAP)
    386     return;
    387   WindowSelectorWindow* target = GetEventTarget(event);
    388   if (!target)
    389     return;
    390 
    391   HandleSelectionEvent(target);
    392 }
    393 
    394 void WindowSelector::OnWindowDestroyed(aura::Window* window) {
    395   ScopedVector<WindowSelectorWindow>::iterator iter =
    396       std::find_if(windows_.begin(), windows_.end(),
    397                    WindowSelectorWindowComparator(window));
    398   DCHECK(iter != windows_.end());
    399   size_t deleted_index = iter - windows_.begin();
    400   (*iter)->OnWindowDestroyed();
    401   windows_.erase(iter);
    402   if (windows_.empty()) {
    403     delegate_->OnSelectionCanceled();
    404     return;
    405   }
    406   if (selected_window_ >= deleted_index) {
    407     if (selected_window_ > deleted_index)
    408       selected_window_--;
    409     selected_window_ = selected_window_ % windows_.size();
    410     UpdateSelectionLocation(true);
    411   }
    412 
    413   PositionWindows();
    414 }
    415 
    416 WindowSelectorWindow* WindowSelector::GetEventTarget(ui::LocatedEvent* event) {
    417   aura::Window* target = static_cast<aura::Window*>(event->target());
    418   // If the target window doesn't actually contain the event location (i.e.
    419   // mouse down over the window and mouse up elsewhere) then do not select the
    420   // window.
    421   if (!target->HitTest(event->location()))
    422     return NULL;
    423 
    424   for (size_t i = 0; i < windows_.size(); i++) {
    425     if (windows_[i]->Contains(target))
    426       return windows_[i];
    427   }
    428   return NULL;
    429 }
    430 
    431 void WindowSelector::HandleSelectionEvent(WindowSelectorWindow* target) {
    432   // The selected window should not be minimized when window selection is
    433   // ended.
    434   target->RestoreWindowOnExit();
    435   delegate_->OnWindowSelected(target->window());
    436 }
    437 
    438 void WindowSelector::PositionWindows() {
    439   if (selection_root_) {
    440     DCHECK_EQ(mode_, CYCLE);
    441     std::vector<WindowSelectorWindow*> windows;
    442     for (size_t i = 0; i < windows_.size(); ++i)
    443       windows.push_back(windows_[i]);
    444     PositionWindowsOnRoot(selection_root_, windows);
    445   } else {
    446     DCHECK_EQ(mode_, OVERVIEW);
    447     Shell::RootWindowList root_window_list = Shell::GetAllRootWindows();
    448     for (size_t i = 0; i < root_window_list.size(); ++i)
    449       PositionWindowsFromRoot(root_window_list[i]);
    450   }
    451 }
    452 
    453 void WindowSelector::PositionWindowsFromRoot(aura::RootWindow* root_window) {
    454   std::vector<WindowSelectorWindow*> windows;
    455   for (size_t i = 0; i < windows_.size(); ++i) {
    456     if (windows_[i]->window()->GetRootWindow() == root_window)
    457       windows.push_back(windows_[i]);
    458   }
    459   PositionWindowsOnRoot(root_window, windows);
    460 }
    461 
    462 void WindowSelector::PositionWindowsOnRoot(
    463     aura::RootWindow* root_window,
    464     const std::vector<WindowSelectorWindow*>& windows) {
    465   if (windows.empty())
    466     return;
    467 
    468   gfx::Size window_size;
    469   gfx::Rect total_bounds = ScreenAsh::ConvertRectToScreen(root_window,
    470       ScreenAsh::GetDisplayWorkAreaBoundsInParent(
    471       Shell::GetContainer(root_window,
    472                           internal::kShellWindowId_DefaultContainer)));
    473 
    474   // Find the minimum number of windows per row that will fit all of the
    475   // windows on screen.
    476   size_t columns = std::max(
    477       total_bounds.width() > total_bounds.height() ? kMinCardsMajor : 1,
    478       static_cast<int>(ceil(sqrt(total_bounds.width() * windows.size() /
    479                                  (kCardAspectRatio * total_bounds.height())))));
    480   size_t rows = ((windows.size() + columns - 1) / columns);
    481   window_size.set_width(std::min(
    482       static_cast<int>(total_bounds.width() / columns),
    483       static_cast<int>(total_bounds.height() * kCardAspectRatio / rows)));
    484   window_size.set_height(window_size.width() / kCardAspectRatio);
    485 
    486   // Calculate the X and Y offsets necessary to center the grid.
    487   int x_offset = total_bounds.x() + ((windows.size() >= columns ? 0 :
    488       (columns - windows.size()) * window_size.width()) +
    489       (total_bounds.width() - columns * window_size.width())) / 2;
    490   int y_offset = total_bounds.y() + (total_bounds.height() -
    491       rows * window_size.height()) / 2;
    492   for (size_t i = 0; i < windows.size(); ++i) {
    493     gfx::Transform transform;
    494     int column = i % columns;
    495     int row = i / columns;
    496     gfx::Rect target_bounds(window_size.width() * column + x_offset,
    497                             window_size.height() * row + y_offset,
    498                             window_size.width(),
    499                             window_size.height());
    500     target_bounds.Inset(kWindowMargin, kWindowMargin);
    501     windows[i]->TransformToFitBounds(root_window, target_bounds);
    502   }
    503 }
    504 
    505 void WindowSelector::InitializeSelectionWidget() {
    506   selection_widget_.reset(new views::Widget);
    507   views::Widget::InitParams params;
    508   params.type = views::Widget::InitParams::TYPE_POPUP;
    509   params.can_activate = false;
    510   params.keep_on_top = false;
    511   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    512   params.opacity = views::Widget::InitParams::OPAQUE_WINDOW;
    513   params.parent = Shell::GetContainer(
    514       selection_root_,
    515       internal::kShellWindowId_DefaultContainer);
    516   params.accept_events = false;
    517   selection_widget_->set_focus_on_creation(false);
    518   selection_widget_->Init(params);
    519   views::View* content_view = new views::View;
    520   content_view->set_background(
    521       views::Background::CreateSolidBackground(kWindowSelectorSelectionColor));
    522   selection_widget_->SetContentsView(content_view);
    523   UpdateSelectionLocation(false);
    524   selection_widget_->GetNativeWindow()->parent()->StackChildAtBottom(
    525       selection_widget_->GetNativeWindow());
    526   selection_widget_->Show();
    527   selection_widget_->GetNativeWindow()->layer()->SetOpacity(
    528       kWindowSelectorSelectionOpacity);
    529 }
    530 
    531 void WindowSelector::UpdateSelectionLocation(bool animate) {
    532   if (!selection_widget_)
    533     return;
    534   gfx::Rect target_bounds = windows_[selected_window_]->bounds();
    535   target_bounds.Inset(-kWindowSelectorSelectionPadding,
    536                       -kWindowSelectorSelectionPadding);
    537   if (animate) {
    538     WindowSelectorAnimationSettings animation_settings(
    539         selection_widget_->GetNativeWindow());
    540     selection_widget_->SetBounds(target_bounds);
    541   } else {
    542     selection_widget_->SetBounds(target_bounds);
    543   }
    544 }
    545 
    546 }  // namespace ash
    547