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/border.h"
31 #include "ui/views/view.h"
32 #include "ui/views/widget/widget.h"
33 #include "ui/wm/core/window_animations.h"
38 typedef std::vector
<aura::Window
*> Windows
;
40 // An observer which holds onto the passed widget until the animation is
42 class CleanupWidgetAfterAnimationObserver
43 : public ui::ImplicitAnimationObserver
{
45 explicit CleanupWidgetAfterAnimationObserver(
46 scoped_ptr
<views::Widget
> widget
);
47 ~CleanupWidgetAfterAnimationObserver() override
;
49 // ui::ImplicitAnimationObserver:
50 void OnImplicitAnimationsCompleted() override
;
53 scoped_ptr
<views::Widget
> widget_
;
55 DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver
);
58 CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
59 scoped_ptr
<views::Widget
> widget
)
60 : widget_(widget
.Pass()) {
63 CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
66 void CleanupWidgetAfterAnimationObserver::OnImplicitAnimationsCompleted() {
70 // A comparator for locating a given target window.
71 struct WindowSelectorItemComparator
72 : public std::unary_function
<WindowSelectorItem
*, bool> {
73 explicit WindowSelectorItemComparator(const aura::Window
* target_window
)
74 : target(target_window
) {
77 bool operator()(WindowSelectorItem
* window
) const {
78 return window
->GetWindow() == target
;
81 const aura::Window
* target
;
84 // Conceptually the window overview is a table or grid of cells having this
85 // fixed aspect ratio. The number of columns is determined by maximizing the
86 // area of them based on the number of window_list.
87 const float kCardAspectRatio
= 4.0f
/ 3.0f
;
89 // The minimum number of cards along the major axis (i.e. horizontally on a
90 // landscape orientation).
91 const int kMinCardsMajor
= 3;
93 const int kOverviewSelectorTransitionMilliseconds
= 100;
95 // The color and opacity of the overview selector.
96 const SkColor kWindowSelectionColor
= SkColorSetARGB(128, 0, 0, 0);
97 const SkColor kWindowSelectionBorderColor
= SkColorSetARGB(38, 255, 255, 255);
98 const int kWindowSelectionBorderThickness
= 2;
100 // The minimum amount of spacing between the bottom of the text filtering
101 // text field and the top of the selection widget on the first row of items.
102 const int kTextFilterBottomMargin
= 5;
104 // Returns the vector for the fade in animation.
105 gfx::Vector2d
GetSlideVectorForFadeIn(WindowSelector::Direction direction
,
106 const gfx::Rect
& bounds
) {
107 gfx::Vector2d vector
;
109 case WindowSelector::DOWN
:
110 vector
.set_y(bounds
.width());
112 case WindowSelector::RIGHT
:
113 vector
.set_x(bounds
.height());
115 case WindowSelector::UP
:
116 vector
.set_y(-bounds
.width());
118 case WindowSelector::LEFT
:
119 vector
.set_x(-bounds
.height());
125 // Given |root_window|, calculates the item size necessary to fit |items|
126 // items in the window selection. |bounding_rect| is set to the centered
127 // rectangle containing the grid and |item_size| is set to the size of each
129 void CalculateOverviewSizes(aura::Window
* root_window
,
131 gfx::Rect
* bounding_rect
,
132 gfx::Size
* item_size
) {
133 gfx::Rect total_bounds
= ScreenUtil::ConvertRectToScreen(
135 ScreenUtil::GetDisplayWorkAreaBoundsInParent(
136 Shell::GetContainer(root_window
, kShellWindowId_DefaultContainer
)));
138 // Reserve space at the top for the text filtering textbox to appear.
140 0, WindowSelector::kTextFilterBottomEdge
+ kTextFilterBottomMargin
, 0, 0);
142 // Find the minimum number of windows per row that will fit all of the
143 // windows on screen.
144 int num_columns
= std::max(
145 total_bounds
.width() > total_bounds
.height() ? kMinCardsMajor
: 1,
146 static_cast<int>(ceil(sqrt(total_bounds
.width() * items
/
147 (kCardAspectRatio
* total_bounds
.height())))));
148 int num_rows
= ((items
+ num_columns
- 1) / num_columns
);
149 item_size
->set_width(std::min(
150 static_cast<int>(total_bounds
.width() / num_columns
),
151 static_cast<int>(total_bounds
.height() * kCardAspectRatio
/ num_rows
)));
152 item_size
->set_height(item_size
->width() / kCardAspectRatio
);
154 bounding_rect
->set_width(std::min(static_cast<int>(items
), num_columns
) *
156 bounding_rect
->set_height(num_rows
* item_size
->height());
157 // Calculate the X and Y offsets necessary to center the grid.
158 bounding_rect
->set_x(total_bounds
.x() +
159 (total_bounds
.width() - bounding_rect
->width()) / 2);
160 bounding_rect
->set_y(total_bounds
.y() +
161 (total_bounds
.height() - bounding_rect
->height()) / 2);
164 // Reorders the list of windows |items| in |root_window| in an attempt to
165 // minimize the distance each window will travel to enter overview. For
166 // equidistant windows preserves a stable order between overview sessions
167 // by comparing window pointers.
168 void ReorderItemsGreedyLeastMovement(std::vector
<aura::Window
*>* items
,
169 aura::Window
* root_window
) {
172 gfx::Rect bounding_rect
;
174 CalculateOverviewSizes(root_window
, items
->size(), &bounding_rect
,
176 int num_columns
= std::min(static_cast<int>(items
->size()),
177 bounding_rect
.width() / item_size
.width());
178 for (size_t i
= 0; i
< items
->size(); ++i
) {
179 int column
= i
% num_columns
;
180 int row
= i
/ num_columns
;
181 gfx::Point
overview_item_center(
182 bounding_rect
.x() + column
* item_size
.width() + item_size
.width() / 2,
183 bounding_rect
.y() + row
* item_size
.height() + item_size
.height() / 2);
184 // Find the nearest window for this position.
185 size_t swap_index
= i
;
186 int64 shortest_distance
= std::numeric_limits
<int64
>::max();
187 for (size_t j
= i
; j
< items
->size(); ++j
) {
188 aura::Window
* window
= (*items
)[j
];
189 int64 distance
= (ScreenUtil::ConvertRectToScreen(
190 window
, window
->GetTargetBounds()).CenterPoint() -
191 overview_item_center
).LengthSquared();
192 // We compare raw pointers to create a stable ordering given two windows
193 // with the same center point.
194 if (distance
< shortest_distance
||
195 (distance
== shortest_distance
&& window
< (*items
)[swap_index
])) {
196 shortest_distance
= distance
;
201 std::swap((*items
)[i
], (*items
)[swap_index
]);
207 WindowGrid::WindowGrid(aura::Window
* root_window
,
208 const std::vector
<aura::Window
*>& windows
,
209 WindowSelector
* window_selector
)
210 : root_window_(root_window
),
211 window_selector_(window_selector
) {
212 std::vector
<aura::Window
*> windows_in_root
;
213 for (auto window
: windows
) {
214 if (window
->GetRootWindow() == root_window
)
215 windows_in_root
.push_back(window
);
218 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
219 switches::kAshEnableStableOverviewOrder
)) {
220 // Reorder windows to try to minimize movement to target overview positions.
221 // This also creates a stable window ordering.
222 ReorderItemsGreedyLeastMovement(&windows_in_root
, root_window_
);
224 for (auto window
: windows_in_root
) {
225 window
->AddObserver(this);
226 observed_windows_
.insert(window
);
227 window_list_
.push_back(new WindowSelectorItem(window
, window_selector_
));
231 WindowGrid::~WindowGrid() {
232 for (std::set
<aura::Window
*>::iterator iter
= observed_windows_
.begin();
233 iter
!= observed_windows_
.end(); iter
++) {
234 (*iter
)->RemoveObserver(this);
238 void WindowGrid::PrepareForOverview() {
239 for (ScopedVector
<WindowSelectorItem
>::iterator iter
= window_list_
.begin();
240 iter
!= window_list_
.end(); ++iter
) {
241 (*iter
)->PrepareForOverview();
245 void WindowGrid::PositionWindows(bool animate
) {
246 CHECK(!window_list_
.empty());
247 gfx::Rect bounding_rect
;
249 CalculateOverviewSizes(root_window_
, window_list_
.size(), &bounding_rect
,
251 num_columns_
= std::min(static_cast<int>(window_list_
.size()),
252 bounding_rect
.width() / item_size
.width());
253 for (size_t i
= 0; i
< window_list_
.size(); ++i
) {
254 gfx::Transform transform
;
255 int column
= i
% num_columns_
;
256 int row
= i
/ num_columns_
;
257 gfx::Rect
target_bounds(item_size
.width() * column
+ bounding_rect
.x(),
258 item_size
.height() * row
+ bounding_rect
.y(),
259 item_size
.width(), item_size
.height());
260 window_list_
[i
]->SetBounds(target_bounds
, animate
?
261 OverviewAnimationType::OVERVIEW_ANIMATION_LAY_OUT_SELECTOR_ITEMS
:
262 OverviewAnimationType::OVERVIEW_ANIMATION_NONE
);
265 // If the selection widget is active, reposition it without any animation.
266 if (selection_widget_
)
267 MoveSelectionWidgetToTarget(animate
);
270 bool WindowGrid::Move(WindowSelector::Direction direction
, bool animate
) {
271 bool recreate_selection_widget
= false;
272 bool out_of_bounds
= false;
273 bool changed_selection_index
= false;
274 if (!selection_widget_
) {
276 case WindowSelector::LEFT
:
277 selected_index_
= window_list_
.size() - 1;
279 case WindowSelector::UP
:
281 (window_list_
.size() / num_columns_
) * num_columns_
- 1;
283 case WindowSelector::RIGHT
:
284 case WindowSelector::DOWN
:
288 changed_selection_index
= true;
290 while (!changed_selection_index
||
291 (!out_of_bounds
&& window_list_
[selected_index_
]->dimmed())) {
293 case WindowSelector::RIGHT
:
294 if (selected_index_
>= window_list_
.size() - 1)
295 out_of_bounds
= true;
297 if (selected_index_
% num_columns_
== 0)
298 recreate_selection_widget
= true;
300 case WindowSelector::LEFT
:
301 if (selected_index_
== 0)
302 out_of_bounds
= true;
304 if ((selected_index_
+ 1) % num_columns_
== 0)
305 recreate_selection_widget
= true;
307 case WindowSelector::DOWN
:
308 selected_index_
+= num_columns_
;
309 if (selected_index_
>= window_list_
.size()) {
310 selected_index_
= (selected_index_
+ 1) % num_columns_
;
311 if (selected_index_
== 0)
312 out_of_bounds
= true;
313 recreate_selection_widget
= true;
316 case WindowSelector::UP
:
317 if (selected_index_
== 0)
318 out_of_bounds
= true;
319 if (selected_index_
< num_columns_
) {
320 selected_index_
+= num_columns_
*
321 ((window_list_
.size() - selected_index_
) / num_columns_
) - 1;
322 recreate_selection_widget
= true;
324 selected_index_
-= num_columns_
;
328 changed_selection_index
= true;
331 MoveSelectionWidget(direction
, recreate_selection_widget
,
332 out_of_bounds
, animate
);
333 return out_of_bounds
;
336 WindowSelectorItem
* WindowGrid::SelectedWindow() const {
337 if (!selection_widget_
)
339 CHECK(selected_index_
< window_list_
.size());
340 return window_list_
[selected_index_
];
343 bool WindowGrid::Contains(const aura::Window
* window
) const {
344 for (const WindowSelectorItem
* window_item
: window_list_
) {
345 if (window_item
->Contains(window
))
351 void WindowGrid::FilterItems(const base::string16
& pattern
) {
352 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents
finder(pattern
);
353 for (ScopedVector
<WindowSelectorItem
>::iterator iter
= window_list_
.begin();
354 iter
!= window_list_
.end(); iter
++) {
355 if (finder
.Search((*iter
)->GetWindow()->title(), nullptr, nullptr)) {
356 (*iter
)->SetDimmed(false);
358 (*iter
)->SetDimmed(true);
359 if (selection_widget_
&& SelectedWindow() == *iter
)
360 selection_widget_
.reset();
365 void WindowGrid::OnWindowDestroying(aura::Window
* window
) {
366 window
->RemoveObserver(this);
367 observed_windows_
.erase(window
);
368 ScopedVector
<WindowSelectorItem
>::iterator iter
=
369 std::find_if(window_list_
.begin(), window_list_
.end(),
370 WindowSelectorItemComparator(window
));
372 DCHECK(iter
!= window_list_
.end());
374 size_t removed_index
= iter
- window_list_
.begin();
375 window_list_
.erase(iter
);
378 // If the grid is now empty, notify the window selector so that it erases us
379 // from its grid list.
380 window_selector_
->OnGridEmpty(this);
384 // If selecting, update the selection index.
385 if (selection_widget_
) {
386 bool send_focus_alert
= selected_index_
== removed_index
;
387 if (selected_index_
>= removed_index
&& selected_index_
!= 0)
389 if (send_focus_alert
)
390 SelectedWindow()->SendFocusAlert();
393 PositionWindows(true);
396 void WindowGrid::OnWindowBoundsChanged(aura::Window
* window
,
397 const gfx::Rect
& old_bounds
,
398 const gfx::Rect
& new_bounds
) {
399 ScopedVector
<WindowSelectorItem
>::const_iterator iter
=
400 std::find_if(window_list_
.begin(), window_list_
.end(),
401 WindowSelectorItemComparator(window
));
402 DCHECK(iter
!= window_list_
.end());
404 // Immediately finish any active bounds animation.
405 window
->layer()->GetAnimator()->StopAnimatingProperty(
406 ui::LayerAnimationElement::BOUNDS
);
408 // Recompute the transform for the window.
409 (*iter
)->RecomputeWindowTransforms();
412 void WindowGrid::InitSelectionWidget(WindowSelector::Direction direction
) {
413 selection_widget_
.reset(new views::Widget
);
414 views::Widget::InitParams params
;
415 params
.type
= views::Widget::InitParams::TYPE_POPUP
;
416 params
.keep_on_top
= false;
417 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
418 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
419 params
.parent
= Shell::GetContainer(root_window_
,
420 kShellWindowId_DefaultContainer
);
421 params
.accept_events
= false;
422 selection_widget_
->set_focus_on_creation(false);
423 selection_widget_
->Init(params
);
424 // Disable the "bounce in" animation when showing the window.
425 ::wm::SetWindowVisibilityAnimationTransition(
426 selection_widget_
->GetNativeWindow(), ::wm::ANIMATE_NONE
);
427 // The selection widget should not activate the shelf when passing under it.
428 wm::GetWindowState(selection_widget_
->GetNativeWindow())->
429 set_ignored_by_shelf(true);
431 views::View
* content_view
= new views::View
;
432 content_view
->set_background(
433 views::Background::CreateSolidBackground(kWindowSelectionColor
));
434 content_view
->SetBorder(views::Border::CreateSolidBorder(
435 kWindowSelectionBorderThickness
, kWindowSelectionBorderColor
));
436 selection_widget_
->SetContentsView(content_view
);
437 selection_widget_
->GetNativeWindow()->parent()->StackChildAtBottom(
438 selection_widget_
->GetNativeWindow());
439 selection_widget_
->Show();
440 // New selection widget starts with 0 opacity and then fades in.
441 selection_widget_
->GetNativeWindow()->layer()->SetOpacity(0);
443 const gfx::Rect target_bounds
= SelectedWindow()->target_bounds();
444 gfx::Vector2d fade_out_direction
=
445 GetSlideVectorForFadeIn(direction
, target_bounds
);
446 gfx::Display dst_display
= gfx::Screen::GetScreenFor(root_window_
)->
447 GetDisplayMatching(target_bounds
);
448 selection_widget_
->GetNativeWindow()->SetBoundsInScreen(
449 target_bounds
- fade_out_direction
, dst_display
);
452 void WindowGrid::MoveSelectionWidget(WindowSelector::Direction direction
,
453 bool recreate_selection_widget
,
456 // If the selection widget is already active, fade it out in the selection
458 if (selection_widget_
&& (recreate_selection_widget
|| out_of_bounds
)) {
459 // Animate the old selection widget and then destroy it.
460 views::Widget
* old_selection
= selection_widget_
.get();
461 gfx::Vector2d fade_out_direction
=
462 GetSlideVectorForFadeIn(
463 direction
, old_selection
->GetNativeWindow()->bounds());
465 ui::ScopedLayerAnimationSettings
animation_settings(
466 old_selection
->GetNativeWindow()->layer()->GetAnimator());
467 animation_settings
.SetTransitionDuration(
468 base::TimeDelta::FromMilliseconds(
469 kOverviewSelectorTransitionMilliseconds
));
470 animation_settings
.SetPreemptionStrategy(
471 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
472 animation_settings
.SetTweenType(gfx::Tween::FAST_OUT_LINEAR_IN
);
473 // CleanupWidgetAfterAnimationObserver will delete itself (and the
474 // widget) when the movement animation is complete.
475 animation_settings
.AddObserver(
476 new CleanupWidgetAfterAnimationObserver(selection_widget_
.Pass()));
477 old_selection
->SetOpacity(0);
478 old_selection
->GetNativeWindow()->SetBounds(
479 old_selection
->GetNativeWindow()->bounds() + fade_out_direction
);
480 old_selection
->Hide();
485 if (!selection_widget_
)
486 InitSelectionWidget(direction
);
487 // Send an a11y alert so that if ChromeVox is enabled, the item label is
489 SelectedWindow()->SendFocusAlert();
490 // The selection widget is moved to the newly selected item in the same
492 MoveSelectionWidgetToTarget(animate
);
495 void WindowGrid::MoveSelectionWidgetToTarget(bool animate
) {
497 ui::ScopedLayerAnimationSettings
animation_settings(
498 selection_widget_
->GetNativeWindow()->layer()->GetAnimator());
499 animation_settings
.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
500 kOverviewSelectorTransitionMilliseconds
));
501 animation_settings
.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN
);
502 animation_settings
.SetPreemptionStrategy(
503 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
504 selection_widget_
->SetBounds(SelectedWindow()->target_bounds());
505 selection_widget_
->SetOpacity(255);
508 selection_widget_
->SetBounds(SelectedWindow()->target_bounds());
509 selection_widget_
->SetOpacity(255);