• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CleanupWidgetAfterAnimationObserver(scoped_ptr<views::Widget> widget)49 CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
50     scoped_ptr<views::Widget> widget)
51     : widget_(widget.Pass()) {
52 }
53 
~CleanupWidgetAfterAnimationObserver()54 CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
55 }
56 
OnImplicitAnimationsCompleted()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> {
WindowSelectorItemComparatorash::__anon8f460f240111::WindowSelectorItemComparator64   explicit WindowSelectorItemComparator(const aura::Window* target_window)
65       : target(target_window) {
66   }
67 
operator ()ash::__anon8f460f240111::WindowSelectorItemComparator68   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> {
WindowSelectorItemTargetComparatorash::__anon8f460f240111::WindowSelectorItemTargetComparator78   explicit WindowSelectorItemTargetComparator(const aura::Window* target_window)
79       : target(target_window) {
80   }
81 
operator ()ash::__anon8f460f240111::WindowSelectorItemTargetComparator82   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.
GetSlideVectorForFadeIn(WindowSelector::Direction direction,const gfx::Rect & bounds)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 
WindowGrid(aura::Window * root_window,const std::vector<aura::Window * > & windows,WindowSelector * window_selector)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 
~WindowGrid()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 
PrepareForOverview()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 
PositionWindows(bool animate)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 
Move(WindowSelector::Direction direction)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 
SelectedWindow() const279 WindowSelectorItem* WindowGrid::SelectedWindow() const {
280   CHECK(selected_index_ < window_list_.size());
281   return window_list_[selected_index_];
282 }
283 
Contains(const aura::Window * window) const284 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 
OnWindowDestroying(aura::Window * window)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 
OnWindowBoundsChanged(aura::Window * window,const gfx::Rect & old_bounds,const gfx::Rect & new_bounds)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 
InitSelectionWidget(WindowSelector::Direction direction)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 
MoveSelectionWidget(WindowSelector::Direction direction,bool recreate_selection_widget,bool out_of_bounds)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 
MoveSelectionWidgetToTarget(bool animate)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