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