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"
7 #include "ash/ash_switches.h"
8 #include "ash/screen_util.h"
10 #include "ash/shell_window_ids.h"
11 #include "ash/wm/overview/scoped_transform_overview_window.h"
12 #include "ash/wm/overview/window_selector.h"
13 #include "ash/wm/overview/window_selector_item.h"
14 #include "ash/wm/overview/window_selector_panels.h"
15 #include "ash/wm/overview/window_selector_window.h"
16 #include "ash/wm/window_state.h"
17 #include "base/command_line.h"
18 #include "base/i18n/string_search.h"
19 #include "base/memory/scoped_vector.h"
20 #include "third_party/skia/include/core/SkColor.h"
21 #include "ui/aura/window.h"
22 #include "ui/compositor/layer_animation_observer.h"
23 #include "ui/compositor/scoped_layer_animation_settings.h"
24 #include "ui/gfx/animation/tween.h"
25 #include "ui/gfx/vector2d.h"
26 #include "ui/views/background.h"
27 #include "ui/views/view.h"
28 #include "ui/views/widget/widget.h"
29 #include "ui/wm/core/window_animations.h"
34 // An observer which holds onto the passed widget until the animation is
36 class CleanupWidgetAfterAnimationObserver
37 : public ui::ImplicitAnimationObserver
{
39 explicit CleanupWidgetAfterAnimationObserver(
40 scoped_ptr
<views::Widget
> widget
);
41 virtual ~CleanupWidgetAfterAnimationObserver();
43 // ui::ImplicitAnimationObserver:
44 virtual void OnImplicitAnimationsCompleted() OVERRIDE
;
47 scoped_ptr
<views::Widget
> widget_
;
49 DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver
);
52 CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
53 scoped_ptr
<views::Widget
> widget
)
54 : widget_(widget
.Pass()) {
57 CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
60 void CleanupWidgetAfterAnimationObserver::OnImplicitAnimationsCompleted() {
64 // A comparator for locating a given target window.
65 struct WindowSelectorItemComparator
66 : public std::unary_function
<WindowSelectorItem
*, bool> {
67 explicit WindowSelectorItemComparator(const aura::Window
* target_window
)
68 : target(target_window
) {
71 bool operator()(WindowSelectorItem
* window
) const {
72 return window
->HasSelectableWindow(target
);
75 const aura::Window
* target
;
78 // A comparator for locating a WindowSelectorItem given a targeted window.
79 struct WindowSelectorItemTargetComparator
80 : public std::unary_function
<WindowSelectorItem
*, bool> {
81 explicit WindowSelectorItemTargetComparator(const aura::Window
* target_window
)
82 : target(target_window
) {
85 bool operator()(WindowSelectorItem
* window
) const {
86 return window
->Contains(target
);
89 const aura::Window
* target
;
92 // Conceptually the window overview is a table or grid of cells having this
93 // fixed aspect ratio. The number of columns is determined by maximizing the
94 // area of them based on the number of window_list.
95 const float kCardAspectRatio
= 4.0f
/ 3.0f
;
97 // The minimum number of cards along the major axis (i.e. horizontally on a
98 // landscape orientation).
99 const int kMinCardsMajor
= 3;
101 const int kOverviewSelectorTransitionMilliseconds
= 100;
103 // The color and opacity of the overview selector.
104 const SkColor kWindowOverviewSelectionColor
= SK_ColorBLACK
;
105 const unsigned char kWindowOverviewSelectorOpacity
= 128;
107 // The minimum amount of spacing between the bottom of the text filtering
108 // text field and the top of the selection widget on the first row of items.
109 const int kTextFilterBottomMargin
= 5;
111 // Returns the vector for the fade in animation.
112 gfx::Vector2d
GetSlideVectorForFadeIn(WindowSelector::Direction direction
,
113 const gfx::Rect
& bounds
) {
114 gfx::Vector2d vector
;
116 case WindowSelector::DOWN
:
117 vector
.set_y(bounds
.width());
119 case WindowSelector::RIGHT
:
120 vector
.set_x(bounds
.height());
122 case WindowSelector::UP
:
123 vector
.set_y(-bounds
.width());
125 case WindowSelector::LEFT
:
126 vector
.set_x(-bounds
.height());
134 WindowGrid::WindowGrid(aura::Window
* root_window
,
135 const std::vector
<aura::Window
*>& windows
,
136 WindowSelector
* window_selector
)
137 : root_window_(root_window
),
138 window_selector_(window_selector
) {
139 WindowSelectorPanels
* panels_item
= NULL
;
140 for (aura::Window::Windows::const_iterator iter
= windows
.begin();
141 iter
!= windows
.end(); ++iter
) {
142 if ((*iter
)->GetRootWindow() != root_window
)
144 (*iter
)->AddObserver(this);
145 observed_windows_
.insert(*iter
);
147 if ((*iter
)->type() == ui::wm::WINDOW_TYPE_PANEL
&&
148 wm::GetWindowState(*iter
)->panel_attached()) {
149 // Attached panel windows are grouped into a single overview item per
152 panels_item
= new WindowSelectorPanels(root_window_
);
153 window_list_
.push_back(panels_item
);
155 panels_item
->AddWindow(*iter
);
157 window_list_
.push_back(new WindowSelectorWindow(*iter
));
160 if (window_list_
.empty())
164 WindowGrid::~WindowGrid() {
165 for (std::set
<aura::Window
*>::iterator iter
= observed_windows_
.begin();
166 iter
!= observed_windows_
.end(); iter
++) {
167 (*iter
)->RemoveObserver(this);
171 void WindowGrid::PrepareForOverview() {
172 for (ScopedVector
<WindowSelectorItem
>::iterator iter
= window_list_
.begin();
173 iter
!= window_list_
.end(); ++iter
) {
174 (*iter
)->PrepareForOverview();
178 void WindowGrid::PositionWindows(bool animate
) {
179 CHECK(!window_list_
.empty());
181 gfx::Size window_size
;
182 gfx::Rect total_bounds
= ScreenUtil::ConvertRectToScreen(
184 ScreenUtil::GetDisplayWorkAreaBoundsInParent(
185 Shell::GetContainer(root_window_
, kShellWindowId_DefaultContainer
)));
187 // If the text filtering feature is enabled, reserve space at the top for the
188 // text filtering textbox to appear.
189 if (!CommandLine::ForCurrentProcess()->HasSwitch(
190 switches::kAshDisableTextFilteringInOverviewMode
)) {
193 WindowSelector::kTextFilterBottomEdge
+ kTextFilterBottomMargin
,
198 // Find the minimum number of windows per row that will fit all of the
199 // windows on screen.
200 num_columns_
= std::max(
201 total_bounds
.width() > total_bounds
.height() ? kMinCardsMajor
: 1,
202 static_cast<int>(ceil(sqrt(total_bounds
.width() * window_list_
.size() /
203 (kCardAspectRatio
* total_bounds
.height())))));
204 int num_rows
= ((window_list_
.size() + num_columns_
- 1) / num_columns_
);
205 window_size
.set_width(std::min(
206 static_cast<int>(total_bounds
.width() / num_columns_
),
207 static_cast<int>(total_bounds
.height() * kCardAspectRatio
/ num_rows
)));
208 window_size
.set_height(window_size
.width() / kCardAspectRatio
);
210 // Calculate the X and Y offsets necessary to center the grid.
211 int x_offset
= total_bounds
.x() + ((window_list_
.size() >= num_columns_
? 0 :
212 (num_columns_
- window_list_
.size()) * window_size
.width()) +
213 (total_bounds
.width() - num_columns_
* window_size
.width())) / 2;
214 int y_offset
= total_bounds
.y() + (total_bounds
.height() -
215 num_rows
* window_size
.height()) / 2;
217 for (size_t i
= 0; i
< window_list_
.size(); ++i
) {
218 gfx::Transform transform
;
219 int column
= i
% num_columns_
;
220 int row
= i
/ num_columns_
;
221 gfx::Rect
target_bounds(window_size
.width() * column
+ x_offset
,
222 window_size
.height() * row
+ y_offset
,
224 window_size
.height());
225 window_list_
[i
]->SetBounds(root_window_
, target_bounds
, animate
);
228 // If we have less than |kMinCardsMajor| windows, adjust the column_ value to
229 // reflect how many "real" columns we have.
230 if (num_columns_
> window_list_
.size())
231 num_columns_
= window_list_
.size();
233 // If the selection widget is active, reposition it without any animation.
234 if (selection_widget_
)
235 MoveSelectionWidgetToTarget(animate
);
238 bool WindowGrid::Move(WindowSelector::Direction direction
, bool animate
) {
239 bool recreate_selection_widget
= false;
240 bool out_of_bounds
= false;
241 if (!selection_widget_
) {
243 case WindowSelector::LEFT
:
244 selected_index_
= window_list_
.size() - 1;
246 case WindowSelector::UP
:
248 (window_list_
.size() / num_columns_
) * num_columns_
- 1;
250 case WindowSelector::RIGHT
:
251 case WindowSelector::DOWN
:
256 while (SelectedWindow()->dimmed() || selection_widget_
) {
258 case WindowSelector::RIGHT
:
259 if (selected_index_
>= window_list_
.size() - 1)
260 out_of_bounds
= true;
262 if (selected_index_
% num_columns_
== 0)
263 recreate_selection_widget
= true;
265 case WindowSelector::LEFT
:
266 if (selected_index_
== 0)
267 out_of_bounds
= true;
269 if ((selected_index_
+ 1) % num_columns_
== 0)
270 recreate_selection_widget
= true;
272 case WindowSelector::DOWN
:
273 selected_index_
+= num_columns_
;
274 if (selected_index_
>= window_list_
.size()) {
275 selected_index_
= (selected_index_
+ 1) % num_columns_
;
276 if (selected_index_
== 0)
277 out_of_bounds
= true;
278 recreate_selection_widget
= true;
281 case WindowSelector::UP
:
282 if (selected_index_
== 0)
283 out_of_bounds
= true;
284 if (selected_index_
< num_columns_
) {
285 selected_index_
+= num_columns_
*
286 ((window_list_
.size() - selected_index_
) / num_columns_
) - 1;
287 recreate_selection_widget
= true;
289 selected_index_
-= num_columns_
;
293 // Exit the loop if we broke free from the grid or found an active item.
294 if (out_of_bounds
|| !SelectedWindow()->dimmed())
298 MoveSelectionWidget(direction
, recreate_selection_widget
,
299 out_of_bounds
, animate
);
300 return out_of_bounds
;
303 WindowSelectorItem
* WindowGrid::SelectedWindow() const {
304 CHECK(selected_index_
< window_list_
.size());
305 return window_list_
[selected_index_
];
308 bool WindowGrid::Contains(const aura::Window
* window
) const {
309 return std::find_if(window_list_
.begin(), window_list_
.end(),
310 WindowSelectorItemTargetComparator(window
)) !=
314 void WindowGrid::FilterItems(const base::string16
& pattern
) {
315 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents
finder(pattern
);
316 for (ScopedVector
<WindowSelectorItem
>::iterator iter
= window_list_
.begin();
317 iter
!= window_list_
.end(); iter
++) {
318 if (finder
.Search((*iter
)->SelectionWindow()->title(), NULL
, NULL
)) {
319 (*iter
)->SetDimmed(false);
321 (*iter
)->SetDimmed(true);
322 if (selection_widget_
&& SelectedWindow() == *iter
)
323 selection_widget_
.reset();
328 void WindowGrid::OnWindowDestroying(aura::Window
* window
) {
329 window
->RemoveObserver(this);
330 observed_windows_
.erase(window
);
331 ScopedVector
<WindowSelectorItem
>::iterator iter
=
332 std::find_if(window_list_
.begin(), window_list_
.end(),
333 WindowSelectorItemComparator(window
));
335 DCHECK(iter
!= window_list_
.end());
337 (*iter
)->RemoveWindow(window
);
339 // If there are still windows in this selector entry then the overview is
340 // still active and the active selection remains the same.
341 if (!(*iter
)->empty())
344 size_t removed_index
= iter
- window_list_
.begin();
345 window_list_
.erase(iter
);
348 // If the grid is now empty, notify the window selector so that it erases us
349 // from its grid list.
350 window_selector_
->OnGridEmpty(this);
354 // If selecting, update the selection index.
355 if (selection_widget_
) {
356 bool send_focus_alert
= selected_index_
== removed_index
;
357 if (selected_index_
>= removed_index
&& selected_index_
!= 0)
359 if (send_focus_alert
)
360 SelectedWindow()->SendFocusAlert();
363 PositionWindows(true);
366 void WindowGrid::OnWindowBoundsChanged(aura::Window
* window
,
367 const gfx::Rect
& old_bounds
,
368 const gfx::Rect
& new_bounds
) {
369 ScopedVector
<WindowSelectorItem
>::const_iterator iter
=
370 std::find_if(window_list_
.begin(), window_list_
.end(),
371 WindowSelectorItemTargetComparator(window
));
372 DCHECK(iter
!= window_list_
.end());
374 // Immediately finish any active bounds animation.
375 window
->layer()->GetAnimator()->StopAnimatingProperty(
376 ui::LayerAnimationElement::BOUNDS
);
378 // Recompute the transform for the window.
379 (*iter
)->RecomputeWindowTransforms();
382 void WindowGrid::InitSelectionWidget(WindowSelector::Direction direction
) {
383 selection_widget_
.reset(new views::Widget
);
384 views::Widget::InitParams params
;
385 params
.type
= views::Widget::InitParams::TYPE_POPUP
;
386 params
.keep_on_top
= false;
387 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
388 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
389 params
.parent
= Shell::GetContainer(root_window_
,
390 kShellWindowId_DefaultContainer
);
391 params
.accept_events
= false;
392 selection_widget_
->set_focus_on_creation(false);
393 selection_widget_
->Init(params
);
394 // Disable the "bounce in" animation when showing the window.
395 ::wm::SetWindowVisibilityAnimationTransition(
396 selection_widget_
->GetNativeWindow(), ::wm::ANIMATE_NONE
);
397 // The selection widget should not activate the shelf when passing under it.
398 ash::wm::GetWindowState(selection_widget_
->GetNativeWindow())->
399 set_ignored_by_shelf(true);
401 views::View
* content_view
= new views::View
;
402 content_view
->set_background(
403 views::Background::CreateSolidBackground(kWindowOverviewSelectionColor
));
404 selection_widget_
->SetContentsView(content_view
);
405 selection_widget_
->GetNativeWindow()->parent()->StackChildAtBottom(
406 selection_widget_
->GetNativeWindow());
407 selection_widget_
->Show();
408 // New selection widget starts with 0 opacity and then fades in.
409 selection_widget_
->GetNativeWindow()->layer()->SetOpacity(0);
411 const gfx::Rect target_bounds
= SelectedWindow()->target_bounds();
412 gfx::Vector2d fade_out_direction
=
413 GetSlideVectorForFadeIn(direction
, target_bounds
);
414 gfx::Display dst_display
= gfx::Screen::GetScreenFor(root_window_
)->
415 GetDisplayMatching(target_bounds
);
416 selection_widget_
->GetNativeWindow()->SetBoundsInScreen(
417 target_bounds
- fade_out_direction
, dst_display
);
420 void WindowGrid::MoveSelectionWidget(WindowSelector::Direction direction
,
421 bool recreate_selection_widget
,
424 // If the selection widget is already active, fade it out in the selection
426 if (selection_widget_
&& (recreate_selection_widget
|| out_of_bounds
)) {
427 // Animate the old selection widget and then destroy it.
428 views::Widget
* old_selection
= selection_widget_
.get();
429 gfx::Vector2d fade_out_direction
=
430 GetSlideVectorForFadeIn(
431 direction
, old_selection
->GetNativeWindow()->bounds());
433 ui::ScopedLayerAnimationSettings
animation_settings(
434 old_selection
->GetNativeWindow()->layer()->GetAnimator());
435 animation_settings
.SetTransitionDuration(
436 base::TimeDelta::FromMilliseconds(
437 kOverviewSelectorTransitionMilliseconds
));
438 animation_settings
.SetPreemptionStrategy(
439 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
440 animation_settings
.SetTweenType(gfx::Tween::FAST_OUT_LINEAR_IN
);
441 // CleanupWidgetAfterAnimationObserver will delete itself (and the
442 // widget) when the movement animation is complete.
443 animation_settings
.AddObserver(
444 new CleanupWidgetAfterAnimationObserver(selection_widget_
.Pass()));
445 old_selection
->SetOpacity(0);
446 old_selection
->GetNativeWindow()->SetBounds(
447 old_selection
->GetNativeWindow()->bounds() + fade_out_direction
);
448 old_selection
->Hide();
453 if (!selection_widget_
)
454 InitSelectionWidget(direction
);
455 // Send an a11y alert so that if ChromeVox is enabled, the item label is
457 SelectedWindow()->SendFocusAlert();
458 // The selection widget is moved to the newly selected item in the same
460 MoveSelectionWidgetToTarget(animate
);
463 void WindowGrid::MoveSelectionWidgetToTarget(bool animate
) {
465 ui::ScopedLayerAnimationSettings
animation_settings(
466 selection_widget_
->GetNativeWindow()->layer()->GetAnimator());
467 animation_settings
.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
468 kOverviewSelectorTransitionMilliseconds
));
469 animation_settings
.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN
);
470 animation_settings
.SetPreemptionStrategy(
471 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
472 selection_widget_
->SetBounds(SelectedWindow()->target_bounds());
473 selection_widget_
->SetOpacity(kWindowOverviewSelectorOpacity
);
476 selection_widget_
->SetBounds(SelectedWindow()->target_bounds());
477 selection_widget_
->SetOpacity(kWindowOverviewSelectorOpacity
);