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