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.
5 #include "ash/wm/overview/window_grid.h"
12 #include "ash/ash_switches.h"
13 #include "ash/screen_util.h"
14 #include "ash/shell.h"
15 #include "ash/shell_window_ids.h"
16 #include "ash/wm/overview/scoped_transform_overview_window.h"
17 #include "ash/wm/overview/window_selector.h"
18 #include "ash/wm/overview/window_selector_item.h"
19 #include "ash/wm/window_state.h"
20 #include "base/command_line.h"
21 #include "base/i18n/string_search.h"
22 #include "base/memory/scoped_vector.h"
23 #include "third_party/skia/include/core/SkColor.h"
24 #include "ui/aura/window.h"
25 #include "ui/compositor/layer_animation_observer.h"
26 #include "ui/compositor/scoped_layer_animation_settings.h"
27 #include "ui/gfx/animation/tween.h"
28 #include "ui/gfx/geometry/vector2d.h"
29 #include "ui/views/background.h"
30 #include "ui/views/view.h"
31 #include "ui/views/widget/widget.h"
32 #include "ui/wm/core/window_animations.h"
37 typedef std::vector
<aura::Window
*> Windows
;
39 // An observer which holds onto the passed widget until the animation is
41 class CleanupWidgetAfterAnimationObserver
42 : public ui::ImplicitAnimationObserver
{
44 explicit CleanupWidgetAfterAnimationObserver(
45 scoped_ptr
<views::Widget
> widget
);
46 ~CleanupWidgetAfterAnimationObserver() override
;
48 // ui::ImplicitAnimationObserver:
49 void OnImplicitAnimationsCompleted() override
;
52 scoped_ptr
<views::Widget
> widget_
;
54 DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver
);
57 CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
58 scoped_ptr
<views::Widget
> widget
)
59 : widget_(widget
.Pass()) {
62 CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
65 void CleanupWidgetAfterAnimationObserver::OnImplicitAnimationsCompleted() {
69 // A comparator for locating a given target window.
70 struct WindowSelectorItemComparator
71 : public std::unary_function
<WindowSelectorItem
*, bool> {
72 explicit WindowSelectorItemComparator(const aura::Window
* target_window
)
73 : target(target_window
) {
76 bool operator()(WindowSelectorItem
* window
) const {
77 return window
->GetWindow() == target
;
80 const aura::Window
* target
;
83 // Conceptually the window overview is a table or grid of cells having this
84 // fixed aspect ratio. The number of columns is determined by maximizing the
85 // area of them based on the number of window_list.
86 const float kCardAspectRatio
= 4.0f
/ 3.0f
;
88 // The minimum number of cards along the major axis (i.e. horizontally on a
89 // landscape orientation).
90 const int kMinCardsMajor
= 3;
92 const int kOverviewSelectorTransitionMilliseconds
= 100;
94 // The color and opacity of the overview selector.
95 const SkColor kWindowOverviewSelectionColor
= SK_ColorBLACK
;
96 const unsigned char kWindowOverviewSelectorOpacity
= 128;
98 // The minimum amount of spacing between the bottom of the text filtering
99 // text field and the top of the selection widget on the first row of items.
100 const int kTextFilterBottomMargin
= 5;
102 // Returns the vector for the fade in animation.
103 gfx::Vector2d
GetSlideVectorForFadeIn(WindowSelector::Direction direction
,
104 const gfx::Rect
& bounds
) {
105 gfx::Vector2d vector
;
107 case WindowSelector::DOWN
:
108 vector
.set_y(bounds
.width());
110 case WindowSelector::RIGHT
:
111 vector
.set_x(bounds
.height());
113 case WindowSelector::UP
:
114 vector
.set_y(-bounds
.width());
116 case WindowSelector::LEFT
:
117 vector
.set_x(-bounds
.height());
125 WindowGrid::WindowGrid(aura::Window
* root_window
,
126 const std::vector
<aura::Window
*>& windows
,
127 WindowSelector
* window_selector
)
128 : root_window_(root_window
),
129 window_selector_(window_selector
) {
131 for (aura::Window::Windows::const_iterator iter
= windows
.begin();
132 iter
!= windows
.end(); ++iter
) {
133 if ((*iter
)->GetRootWindow() != root_window
)
135 (*iter
)->AddObserver(this);
136 observed_windows_
.insert(*iter
);
138 window_list_
.push_back(new WindowSelectorItem(*iter
));
142 WindowGrid::~WindowGrid() {
143 for (std::set
<aura::Window
*>::iterator iter
= observed_windows_
.begin();
144 iter
!= observed_windows_
.end(); iter
++) {
145 (*iter
)->RemoveObserver(this);
149 void WindowGrid::PrepareForOverview() {
150 for (ScopedVector
<WindowSelectorItem
>::iterator iter
= window_list_
.begin();
151 iter
!= window_list_
.end(); ++iter
) {
152 (*iter
)->PrepareForOverview();
156 void WindowGrid::PositionWindows(bool animate
) {
157 CHECK(!window_list_
.empty());
159 gfx::Size window_size
;
160 gfx::Rect total_bounds
= ScreenUtil::ConvertRectToScreen(
162 ScreenUtil::GetDisplayWorkAreaBoundsInParent(
163 Shell::GetContainer(root_window_
, kShellWindowId_DefaultContainer
)));
165 // Reserve space at the top for the text filtering textbox to appear.
167 0, WindowSelector::kTextFilterBottomEdge
+ kTextFilterBottomMargin
, 0, 0);
169 // Find the minimum number of windows per row that will fit all of the
170 // windows on screen.
171 num_columns_
= std::max(
172 total_bounds
.width() > total_bounds
.height() ? kMinCardsMajor
: 1,
173 static_cast<int>(ceil(sqrt(total_bounds
.width() * window_list_
.size() /
174 (kCardAspectRatio
* total_bounds
.height())))));
175 int num_rows
= ((window_list_
.size() + num_columns_
- 1) / num_columns_
);
176 window_size
.set_width(std::min(
177 static_cast<int>(total_bounds
.width() / num_columns_
),
178 static_cast<int>(total_bounds
.height() * kCardAspectRatio
/ num_rows
)));
179 window_size
.set_height(window_size
.width() / kCardAspectRatio
);
181 // Calculate the X and Y offsets necessary to center the grid.
182 int x_offset
= total_bounds
.x() + ((window_list_
.size() >= num_columns_
? 0 :
183 (num_columns_
- window_list_
.size()) * window_size
.width()) +
184 (total_bounds
.width() - num_columns_
* window_size
.width())) / 2;
185 int y_offset
= total_bounds
.y() + (total_bounds
.height() -
186 num_rows
* window_size
.height()) / 2;
188 for (size_t i
= 0; i
< window_list_
.size(); ++i
) {
189 gfx::Transform transform
;
190 int column
= i
% num_columns_
;
191 int row
= i
/ num_columns_
;
192 gfx::Rect
target_bounds(window_size
.width() * column
+ x_offset
,
193 window_size
.height() * row
+ y_offset
,
195 window_size
.height());
196 window_list_
[i
]->SetBounds(target_bounds
, animate
?
197 OverviewAnimationType::OVERVIEW_ANIMATION_LAY_OUT_SELECTOR_ITEMS
:
198 OverviewAnimationType::OVERVIEW_ANIMATION_NONE
);
201 // If we have less than |kMinCardsMajor| windows, adjust the column_ value to
202 // reflect how many "real" columns we have.
203 if (num_columns_
> window_list_
.size())
204 num_columns_
= window_list_
.size();
206 // If the selection widget is active, reposition it without any animation.
207 if (selection_widget_
)
208 MoveSelectionWidgetToTarget(animate
);
211 bool WindowGrid::Move(WindowSelector::Direction direction
, bool animate
) {
212 bool recreate_selection_widget
= false;
213 bool out_of_bounds
= false;
214 if (!selection_widget_
) {
216 case WindowSelector::LEFT
:
217 selected_index_
= window_list_
.size() - 1;
219 case WindowSelector::UP
:
221 (window_list_
.size() / num_columns_
) * num_columns_
- 1;
223 case WindowSelector::RIGHT
:
224 case WindowSelector::DOWN
:
229 while (SelectedWindow()->dimmed() || selection_widget_
) {
231 case WindowSelector::RIGHT
:
232 if (selected_index_
>= window_list_
.size() - 1)
233 out_of_bounds
= true;
235 if (selected_index_
% num_columns_
== 0)
236 recreate_selection_widget
= true;
238 case WindowSelector::LEFT
:
239 if (selected_index_
== 0)
240 out_of_bounds
= true;
242 if ((selected_index_
+ 1) % num_columns_
== 0)
243 recreate_selection_widget
= true;
245 case WindowSelector::DOWN
:
246 selected_index_
+= num_columns_
;
247 if (selected_index_
>= window_list_
.size()) {
248 selected_index_
= (selected_index_
+ 1) % num_columns_
;
249 if (selected_index_
== 0)
250 out_of_bounds
= true;
251 recreate_selection_widget
= true;
254 case WindowSelector::UP
:
255 if (selected_index_
== 0)
256 out_of_bounds
= true;
257 if (selected_index_
< num_columns_
) {
258 selected_index_
+= num_columns_
*
259 ((window_list_
.size() - selected_index_
) / num_columns_
) - 1;
260 recreate_selection_widget
= true;
262 selected_index_
-= num_columns_
;
266 // Exit the loop if we broke free from the grid or found an active item.
267 if (out_of_bounds
|| !SelectedWindow()->dimmed())
271 MoveSelectionWidget(direction
, recreate_selection_widget
,
272 out_of_bounds
, animate
);
273 return out_of_bounds
;
276 WindowSelectorItem
* WindowGrid::SelectedWindow() const {
277 CHECK(selected_index_
< window_list_
.size());
278 return window_list_
[selected_index_
];
281 bool WindowGrid::Contains(const aura::Window
* window
) const {
282 for (const WindowSelectorItem
* window_item
: window_list_
) {
283 if (window_item
->Contains(window
))
289 void WindowGrid::FilterItems(const base::string16
& pattern
) {
290 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents
finder(pattern
);
291 for (ScopedVector
<WindowSelectorItem
>::iterator iter
= window_list_
.begin();
292 iter
!= window_list_
.end(); iter
++) {
293 if (finder
.Search((*iter
)->GetWindow()->title(), nullptr, nullptr)) {
294 (*iter
)->SetDimmed(false);
296 (*iter
)->SetDimmed(true);
297 if (selection_widget_
&& SelectedWindow() == *iter
)
298 selection_widget_
.reset();
303 void WindowGrid::OnWindowDestroying(aura::Window
* window
) {
304 window
->RemoveObserver(this);
305 observed_windows_
.erase(window
);
306 ScopedVector
<WindowSelectorItem
>::iterator iter
=
307 std::find_if(window_list_
.begin(), window_list_
.end(),
308 WindowSelectorItemComparator(window
));
310 DCHECK(iter
!= window_list_
.end());
312 size_t removed_index
= iter
- window_list_
.begin();
313 window_list_
.erase(iter
);
316 // If the grid is now empty, notify the window selector so that it erases us
317 // from its grid list.
318 window_selector_
->OnGridEmpty(this);
322 // If selecting, update the selection index.
323 if (selection_widget_
) {
324 bool send_focus_alert
= selected_index_
== removed_index
;
325 if (selected_index_
>= removed_index
&& selected_index_
!= 0)
327 if (send_focus_alert
)
328 SelectedWindow()->SendFocusAlert();
331 PositionWindows(true);
334 void WindowGrid::OnWindowBoundsChanged(aura::Window
* window
,
335 const gfx::Rect
& old_bounds
,
336 const gfx::Rect
& new_bounds
) {
337 ScopedVector
<WindowSelectorItem
>::const_iterator iter
=
338 std::find_if(window_list_
.begin(), window_list_
.end(),
339 WindowSelectorItemComparator(window
));
340 DCHECK(iter
!= window_list_
.end());
342 // Immediately finish any active bounds animation.
343 window
->layer()->GetAnimator()->StopAnimatingProperty(
344 ui::LayerAnimationElement::BOUNDS
);
346 // Recompute the transform for the window.
347 (*iter
)->RecomputeWindowTransforms();
350 void WindowGrid::InitSelectionWidget(WindowSelector::Direction direction
) {
351 selection_widget_
.reset(new views::Widget
);
352 views::Widget::InitParams params
;
353 params
.type
= views::Widget::InitParams::TYPE_POPUP
;
354 params
.keep_on_top
= false;
355 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
356 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
357 params
.parent
= Shell::GetContainer(root_window_
,
358 kShellWindowId_DefaultContainer
);
359 params
.accept_events
= false;
360 selection_widget_
->set_focus_on_creation(false);
361 selection_widget_
->Init(params
);
362 // Disable the "bounce in" animation when showing the window.
363 ::wm::SetWindowVisibilityAnimationTransition(
364 selection_widget_
->GetNativeWindow(), ::wm::ANIMATE_NONE
);
365 // The selection widget should not activate the shelf when passing under it.
366 wm::GetWindowState(selection_widget_
->GetNativeWindow())->
367 set_ignored_by_shelf(true);
369 views::View
* content_view
= new views::View
;
370 content_view
->set_background(
371 views::Background::CreateSolidBackground(kWindowOverviewSelectionColor
));
372 selection_widget_
->SetContentsView(content_view
);
373 selection_widget_
->GetNativeWindow()->parent()->StackChildAtBottom(
374 selection_widget_
->GetNativeWindow());
375 selection_widget_
->Show();
376 // New selection widget starts with 0 opacity and then fades in.
377 selection_widget_
->GetNativeWindow()->layer()->SetOpacity(0);
379 const gfx::Rect target_bounds
= SelectedWindow()->target_bounds();
380 gfx::Vector2d fade_out_direction
=
381 GetSlideVectorForFadeIn(direction
, target_bounds
);
382 gfx::Display dst_display
= gfx::Screen::GetScreenFor(root_window_
)->
383 GetDisplayMatching(target_bounds
);
384 selection_widget_
->GetNativeWindow()->SetBoundsInScreen(
385 target_bounds
- fade_out_direction
, dst_display
);
388 void WindowGrid::MoveSelectionWidget(WindowSelector::Direction direction
,
389 bool recreate_selection_widget
,
392 // If the selection widget is already active, fade it out in the selection
394 if (selection_widget_
&& (recreate_selection_widget
|| out_of_bounds
)) {
395 // Animate the old selection widget and then destroy it.
396 views::Widget
* old_selection
= selection_widget_
.get();
397 gfx::Vector2d fade_out_direction
=
398 GetSlideVectorForFadeIn(
399 direction
, old_selection
->GetNativeWindow()->bounds());
401 ui::ScopedLayerAnimationSettings
animation_settings(
402 old_selection
->GetNativeWindow()->layer()->GetAnimator());
403 animation_settings
.SetTransitionDuration(
404 base::TimeDelta::FromMilliseconds(
405 kOverviewSelectorTransitionMilliseconds
));
406 animation_settings
.SetPreemptionStrategy(
407 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
408 animation_settings
.SetTweenType(gfx::Tween::FAST_OUT_LINEAR_IN
);
409 // CleanupWidgetAfterAnimationObserver will delete itself (and the
410 // widget) when the movement animation is complete.
411 animation_settings
.AddObserver(
412 new CleanupWidgetAfterAnimationObserver(selection_widget_
.Pass()));
413 old_selection
->SetOpacity(0);
414 old_selection
->GetNativeWindow()->SetBounds(
415 old_selection
->GetNativeWindow()->bounds() + fade_out_direction
);
416 old_selection
->Hide();
421 if (!selection_widget_
)
422 InitSelectionWidget(direction
);
423 // Send an a11y alert so that if ChromeVox is enabled, the item label is
425 SelectedWindow()->SendFocusAlert();
426 // The selection widget is moved to the newly selected item in the same
428 MoveSelectionWidgetToTarget(animate
);
431 void WindowGrid::MoveSelectionWidgetToTarget(bool animate
) {
433 ui::ScopedLayerAnimationSettings
animation_settings(
434 selection_widget_
->GetNativeWindow()->layer()->GetAnimator());
435 animation_settings
.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
436 kOverviewSelectorTransitionMilliseconds
));
437 animation_settings
.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN
);
438 animation_settings
.SetPreemptionStrategy(
439 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
440 selection_widget_
->SetBounds(SelectedWindow()->target_bounds());
441 selection_widget_
->SetOpacity(kWindowOverviewSelectorOpacity
);
444 selection_widget_
->SetBounds(SelectedWindow()->target_bounds());
445 selection_widget_
->SetOpacity(kWindowOverviewSelectorOpacity
);