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