Home | History | Annotate | Download | only in overview
      1 // Copyright 2014 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/overview/window_grid.h"
      6 
      7 #include "ash/screen_util.h"
      8 #include "ash/shell.h"
      9 #include "ash/shell_window_ids.h"
     10 #include "ash/wm/overview/scoped_transform_overview_window.h"
     11 #include "ash/wm/overview/window_selector.h"
     12 #include "ash/wm/overview/window_selector_item.h"
     13 #include "ash/wm/overview/window_selector_panels.h"
     14 #include "ash/wm/overview/window_selector_window.h"
     15 #include "ash/wm/window_state.h"
     16 #include "base/memory/scoped_vector.h"
     17 #include "third_party/skia/include/core/SkColor.h"
     18 #include "ui/aura/window.h"
     19 #include "ui/compositor/layer_animation_observer.h"
     20 #include "ui/compositor/scoped_layer_animation_settings.h"
     21 #include "ui/gfx/animation/tween.h"
     22 #include "ui/gfx/vector2d.h"
     23 #include "ui/views/background.h"
     24 #include "ui/views/view.h"
     25 #include "ui/views/widget/widget.h"
     26 #include "ui/wm/core/window_animations.h"
     27 
     28 namespace ash {
     29 namespace {
     30 
     31 // An observer which holds onto the passed widget until the animation is
     32 // complete.
     33 class CleanupWidgetAfterAnimationObserver
     34     : public ui::ImplicitAnimationObserver {
     35  public:
     36   explicit CleanupWidgetAfterAnimationObserver(
     37       scoped_ptr<views::Widget> widget);
     38   virtual ~CleanupWidgetAfterAnimationObserver();
     39 
     40   // ui::ImplicitAnimationObserver:
     41   virtual void OnImplicitAnimationsCompleted() OVERRIDE;
     42 
     43  private:
     44   scoped_ptr<views::Widget> widget_;
     45 
     46   DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver);
     47 };
     48 
     49 CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
     50     scoped_ptr<views::Widget> widget)
     51     : widget_(widget.Pass()) {
     52 }
     53 
     54 CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
     55 }
     56 
     57 void CleanupWidgetAfterAnimationObserver::OnImplicitAnimationsCompleted() {
     58   delete this;
     59 }
     60 
     61 // A comparator for locating a given target window.
     62 struct WindowSelectorItemComparator
     63     : public std::unary_function<WindowSelectorItem*, bool> {
     64   explicit WindowSelectorItemComparator(const aura::Window* target_window)
     65       : target(target_window) {
     66   }
     67 
     68   bool operator()(WindowSelectorItem* window) const {
     69     return window->HasSelectableWindow(target);
     70   }
     71 
     72   const aura::Window* target;
     73 };
     74 
     75 // A comparator for locating a WindowSelectorItem given a targeted window.
     76 struct WindowSelectorItemTargetComparator
     77     : public std::unary_function<WindowSelectorItem*, bool> {
     78   explicit WindowSelectorItemTargetComparator(const aura::Window* target_window)
     79       : target(target_window) {
     80   }
     81 
     82   bool operator()(WindowSelectorItem* window) const {
     83     return window->Contains(target);
     84   }
     85 
     86   const aura::Window* target;
     87 };
     88 
     89 // Conceptually the window overview is a table or grid of cells having this
     90 // fixed aspect ratio. The number of columns is determined by maximizing the
     91 // area of them based on the number of window_list.
     92 const float kCardAspectRatio = 4.0f / 3.0f;
     93 
     94 // The minimum number of cards along the major axis (i.e. horizontally on a
     95 // landscape orientation).
     96 const int kMinCardsMajor = 3;
     97 
     98 const int kOverviewSelectorTransitionMilliseconds = 100;
     99 
    100 // The color and opacity of the overview selector.
    101 const SkColor kWindowOverviewSelectionColor = SK_ColorBLACK;
    102 const unsigned char kWindowOverviewSelectorOpacity = 128;
    103 
    104 // Returns the vector for the fade in animation.
    105 gfx::Vector2d GetSlideVectorForFadeIn(WindowSelector::Direction direction,
    106                                       const gfx::Rect& bounds) {
    107   gfx::Vector2d vector;
    108   switch (direction) {
    109     case WindowSelector::DOWN:
    110       vector.set_y(bounds.width());
    111       break;
    112     case WindowSelector::RIGHT:
    113       vector.set_x(bounds.height());
    114       break;
    115     case WindowSelector::UP:
    116       vector.set_y(-bounds.width());
    117       break;
    118     case WindowSelector::LEFT:
    119       vector.set_x(-bounds.height());
    120       break;
    121   }
    122   return vector;
    123 }
    124 
    125 }  // namespace
    126 
    127 WindowGrid::WindowGrid(aura::Window* root_window,
    128                        const std::vector<aura::Window*>& windows,
    129                        WindowSelector* window_selector)
    130     : root_window_(root_window),
    131       window_selector_(window_selector) {
    132   WindowSelectorPanels* panels_item = NULL;
    133   for (aura::Window::Windows::const_iterator iter = windows.begin();
    134        iter != windows.end(); ++iter) {
    135     if ((*iter)->GetRootWindow() != root_window)
    136       continue;
    137     (*iter)->AddObserver(this);
    138     observed_windows_.insert(*iter);
    139 
    140     if ((*iter)->type() == ui::wm::WINDOW_TYPE_PANEL &&
    141         wm::GetWindowState(*iter)->panel_attached()) {
    142       // Attached panel windows are grouped into a single overview item per
    143       // grid.
    144       if (!panels_item) {
    145         panels_item = new WindowSelectorPanels(root_window_);
    146         window_list_.push_back(panels_item);
    147       }
    148       panels_item->AddWindow(*iter);
    149     } else {
    150       window_list_.push_back(new WindowSelectorWindow(*iter));
    151     }
    152   }
    153   if (window_list_.empty())
    154     return;
    155 }
    156 
    157 WindowGrid::~WindowGrid() {
    158   for (std::set<aura::Window*>::iterator iter = observed_windows_.begin();
    159        iter != observed_windows_.end(); iter++) {
    160     (*iter)->RemoveObserver(this);
    161   }
    162 }
    163 
    164 void WindowGrid::PrepareForOverview() {
    165   for (ScopedVector<WindowSelectorItem>::iterator iter = window_list_.begin();
    166        iter != window_list_.end(); ++iter) {
    167     (*iter)->PrepareForOverview();
    168   }
    169 }
    170 
    171 void WindowGrid::PositionWindows(bool animate) {
    172   CHECK(!window_list_.empty());
    173 
    174   gfx::Size window_size;
    175   gfx::Rect total_bounds = ScreenUtil::ConvertRectToScreen(
    176       root_window_,
    177       ScreenUtil::GetDisplayWorkAreaBoundsInParent(
    178           Shell::GetContainer(root_window_, kShellWindowId_DefaultContainer)));
    179 
    180   // Find the minimum number of windows per row that will fit all of the
    181   // windows on screen.
    182   num_columns_ = std::max(
    183       total_bounds.width() > total_bounds.height() ? kMinCardsMajor : 1,
    184       static_cast<int>(ceil(sqrt(total_bounds.width() * window_list_.size() /
    185                                  (kCardAspectRatio * total_bounds.height())))));
    186   int num_rows = ((window_list_.size() + num_columns_ - 1) / num_columns_);
    187   window_size.set_width(std::min(
    188       static_cast<int>(total_bounds.width() / num_columns_),
    189       static_cast<int>(total_bounds.height() * kCardAspectRatio / num_rows)));
    190   window_size.set_height(window_size.width() / kCardAspectRatio);
    191 
    192   // Calculate the X and Y offsets necessary to center the grid.
    193   int x_offset = total_bounds.x() + ((window_list_.size() >= num_columns_ ? 0 :
    194       (num_columns_ - window_list_.size()) * window_size.width()) +
    195       (total_bounds.width() - num_columns_ * window_size.width())) / 2;
    196   int y_offset = total_bounds.y() + (total_bounds.height() -
    197       num_rows * window_size.height()) / 2;
    198   for (size_t i = 0; i < window_list_.size(); ++i) {
    199     gfx::Transform transform;
    200     int column = i % num_columns_;
    201     int row = i / num_columns_;
    202     gfx::Rect target_bounds(window_size.width() * column + x_offset,
    203                             window_size.height() * row + y_offset,
    204                             window_size.width(),
    205                             window_size.height());
    206     window_list_[i]->SetBounds(root_window_, target_bounds, animate);
    207   }
    208 
    209   // If we have less than |kMinCardsMajor| windows, adjust the column_ value to
    210   // reflect how many "real" columns we have.
    211   if (num_columns_ > window_list_.size())
    212     num_columns_ = window_list_.size();
    213 
    214   // If the selection widget is active, reposition it without any animation.
    215   if (selection_widget_)
    216     MoveSelectionWidgetToTarget(animate);
    217 }
    218 
    219 bool WindowGrid::Move(WindowSelector::Direction direction) {
    220   bool recreate_selection_widget = false;
    221   bool out_of_bounds = false;
    222   if (!selection_widget_) {
    223     switch (direction) {
    224      case WindowSelector::LEFT:
    225        selected_index_ = window_list_.size() - 1;
    226        break;
    227      case WindowSelector::UP:
    228        selected_index_ =
    229            (window_list_.size() / num_columns_) * num_columns_ - 1;
    230        break;
    231      case WindowSelector::RIGHT:
    232      case WindowSelector::DOWN:
    233        selected_index_ = 0;
    234        break;
    235      }
    236   } else {
    237     switch (direction) {
    238       case WindowSelector::RIGHT:
    239         if (selected_index_ >= window_list_.size() - 1)
    240           out_of_bounds = true;
    241         selected_index_++;
    242         if (selected_index_ % num_columns_ == 0)
    243           recreate_selection_widget = true;
    244         break;
    245       case WindowSelector::LEFT:
    246         if (selected_index_ == 0)
    247           out_of_bounds = true;
    248         selected_index_--;
    249         if ((selected_index_ + 1) % num_columns_ == 0)
    250           recreate_selection_widget = true;
    251         break;
    252       case WindowSelector::DOWN:
    253         selected_index_ += num_columns_;
    254         if (selected_index_ >= window_list_.size()) {
    255           selected_index_ = (selected_index_ + 1) % num_columns_;
    256           if (selected_index_ == 0)
    257             out_of_bounds = true;
    258           recreate_selection_widget = true;
    259         }
    260         break;
    261       case WindowSelector::UP:
    262         if (selected_index_ == 0)
    263           out_of_bounds = true;
    264         if (selected_index_ < num_columns_) {
    265           selected_index_ += num_columns_ *
    266               ((window_list_.size() - selected_index_) / num_columns_) - 1;
    267           recreate_selection_widget = true;
    268         } else {
    269           selected_index_ -= num_columns_;
    270         }
    271         break;
    272     }
    273   }
    274 
    275   MoveSelectionWidget(direction, recreate_selection_widget, out_of_bounds);
    276   return out_of_bounds;
    277 }
    278 
    279 WindowSelectorItem* WindowGrid::SelectedWindow() const {
    280   CHECK(selected_index_ < window_list_.size());
    281   return window_list_[selected_index_];
    282 }
    283 
    284 bool WindowGrid::Contains(const aura::Window* window) const {
    285   return std::find_if(window_list_.begin(), window_list_.end(),
    286                       WindowSelectorItemTargetComparator(window)) !=
    287                           window_list_.end();
    288 }
    289 
    290 void WindowGrid::OnWindowDestroying(aura::Window* window) {
    291   window->RemoveObserver(this);
    292   observed_windows_.erase(window);
    293   ScopedVector<WindowSelectorItem>::iterator iter =
    294       std::find_if(window_list_.begin(), window_list_.end(),
    295                    WindowSelectorItemComparator(window));
    296 
    297   DCHECK(iter != window_list_.end());
    298 
    299   (*iter)->RemoveWindow(window);
    300 
    301   // If there are still windows in this selector entry then the overview is
    302   // still active and the active selection remains the same.
    303   if (!(*iter)->empty())
    304     return;
    305 
    306   size_t removed_index = iter - window_list_.begin();
    307   window_list_.erase(iter);
    308 
    309   if (empty()) {
    310     // If the grid is now empty, notify the window selector so that it erases us
    311     // from its grid list.
    312     window_selector_->OnGridEmpty(this);
    313     return;
    314   }
    315 
    316   // If selecting, update the selection index.
    317   if (selection_widget_) {
    318     bool send_focus_alert = selected_index_ == removed_index;
    319     if (selected_index_ >= removed_index && selected_index_ != 0)
    320       selected_index_--;
    321     if (send_focus_alert)
    322       SelectedWindow()->SendFocusAlert();
    323   }
    324 
    325   PositionWindows(true);
    326 }
    327 
    328 void WindowGrid::OnWindowBoundsChanged(aura::Window* window,
    329                                        const gfx::Rect& old_bounds,
    330                                        const gfx::Rect& new_bounds) {
    331   ScopedVector<WindowSelectorItem>::const_iterator iter =
    332       std::find_if(window_list_.begin(), window_list_.end(),
    333                    WindowSelectorItemTargetComparator(window));
    334   DCHECK(iter != window_list_.end());
    335 
    336   // Immediately finish any active bounds animation.
    337   window->layer()->GetAnimator()->StopAnimatingProperty(
    338       ui::LayerAnimationElement::BOUNDS);
    339 
    340   // Recompute the transform for the window.
    341   (*iter)->RecomputeWindowTransforms();
    342 }
    343 
    344 void WindowGrid::InitSelectionWidget(WindowSelector::Direction direction) {
    345   selection_widget_.reset(new views::Widget);
    346   views::Widget::InitParams params;
    347   params.type = views::Widget::InitParams::TYPE_POPUP;
    348   params.keep_on_top = false;
    349   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    350   params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
    351   params.parent = Shell::GetContainer(root_window_,
    352                                       kShellWindowId_DefaultContainer);
    353   params.accept_events = false;
    354   selection_widget_->set_focus_on_creation(false);
    355   selection_widget_->Init(params);
    356   // Disable the "bounce in" animation when showing the window.
    357   ::wm::SetWindowVisibilityAnimationTransition(
    358       selection_widget_->GetNativeWindow(), ::wm::ANIMATE_NONE);
    359   // The selection widget should not activate the shelf when passing under it.
    360   ash::wm::GetWindowState(selection_widget_->GetNativeWindow())->
    361       set_ignored_by_shelf(true);
    362 
    363   views::View* content_view = new views::View;
    364   content_view->set_background(
    365       views::Background::CreateSolidBackground(kWindowOverviewSelectionColor));
    366   selection_widget_->SetContentsView(content_view);
    367   selection_widget_->GetNativeWindow()->parent()->StackChildAtBottom(
    368       selection_widget_->GetNativeWindow());
    369   selection_widget_->Show();
    370   // New selection widget starts with 0 opacity and then fades in.
    371   selection_widget_->GetNativeWindow()->layer()->SetOpacity(0);
    372 
    373   const gfx::Rect target_bounds = SelectedWindow()->target_bounds();
    374   gfx::Vector2d fade_out_direction =
    375           GetSlideVectorForFadeIn(direction, target_bounds);
    376   gfx::Display dst_display = gfx::Screen::GetScreenFor(root_window_)->
    377       GetDisplayMatching(target_bounds);
    378   selection_widget_->GetNativeWindow()->SetBoundsInScreen(
    379       target_bounds - fade_out_direction, dst_display);
    380 }
    381 
    382 void WindowGrid::MoveSelectionWidget(WindowSelector::Direction direction,
    383                                      bool recreate_selection_widget,
    384                                      bool out_of_bounds) {
    385   // If the selection widget is already active, fade it out in the selection
    386   // direction.
    387   if (selection_widget_ && (recreate_selection_widget || out_of_bounds)) {
    388     // Animate the old selection widget and then destroy it.
    389     views::Widget* old_selection = selection_widget_.get();
    390     gfx::Vector2d fade_out_direction =
    391         GetSlideVectorForFadeIn(
    392             direction, old_selection->GetNativeWindow()->bounds());
    393 
    394     ui::ScopedLayerAnimationSettings animation_settings(
    395         old_selection->GetNativeWindow()->layer()->GetAnimator());
    396     animation_settings.SetTransitionDuration(
    397         base::TimeDelta::FromMilliseconds(
    398             kOverviewSelectorTransitionMilliseconds));
    399     animation_settings.SetPreemptionStrategy(
    400         ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
    401     animation_settings.SetTweenType(gfx::Tween::FAST_OUT_LINEAR_IN);
    402     // CleanupWidgetAfterAnimationObserver will delete itself (and the
    403     // widget) when the movement animation is complete.
    404     animation_settings.AddObserver(
    405         new CleanupWidgetAfterAnimationObserver(selection_widget_.Pass()));
    406     old_selection->SetOpacity(0);
    407     old_selection->GetNativeWindow()->SetBounds(
    408         old_selection->GetNativeWindow()->bounds() + fade_out_direction);
    409     old_selection->Hide();
    410   }
    411   if (out_of_bounds)
    412     return;
    413 
    414   if (!selection_widget_)
    415     InitSelectionWidget(direction);
    416   // Send an a11y alert so that if ChromeVox is enabled, the item label is
    417   // read.
    418   SelectedWindow()->SendFocusAlert();
    419   // The selection widget is moved to the newly selected item in the same
    420   // grid.
    421   MoveSelectionWidgetToTarget(true);
    422 }
    423 
    424 void WindowGrid::MoveSelectionWidgetToTarget(bool animate) {
    425   if (animate) {
    426     ui::ScopedLayerAnimationSettings animation_settings(
    427         selection_widget_->GetNativeWindow()->layer()->GetAnimator());
    428     animation_settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
    429         kOverviewSelectorTransitionMilliseconds));
    430     animation_settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
    431     animation_settings.SetPreemptionStrategy(
    432         ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
    433     selection_widget_->SetBounds(SelectedWindow()->target_bounds());
    434     selection_widget_->SetOpacity(kWindowOverviewSelectorOpacity);
    435     return;
    436   }
    437   selection_widget_->SetBounds(SelectedWindow()->target_bounds());
    438   selection_widget_->SetOpacity(kWindowOverviewSelectorOpacity);
    439 }
    440 
    441 }  // namespace ash
    442