1 // Copyright 2013 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/window_selector.h"
9 #include "ash/screen_ash.h"
10 #include "ash/shell.h"
11 #include "ash/shell_window_ids.h"
12 #include "ash/wm/window_selector_delegate.h"
13 #include "ash/wm/window_util.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "third_party/skia/include/core/SkColor.h"
16 #include "ui/aura/client/aura_constants.h"
17 #include "ui/aura/client/screen_position_client.h"
18 #include "ui/aura/root_window.h"
19 #include "ui/aura/window.h"
20 #include "ui/base/events/event.h"
21 #include "ui/compositor/layer_animation_observer.h"
22 #include "ui/compositor/scoped_layer_animation_settings.h"
23 #include "ui/gfx/display.h"
24 #include "ui/gfx/interpolated_transform.h"
25 #include "ui/gfx/transform_util.h"
26 #include "ui/views/corewm/shadow_types.h"
27 #include "ui/views/corewm/window_animations.h"
28 #include "ui/views/corewm/window_util.h"
29 #include "ui/views/widget/widget.h"
35 const float kCardAspectRatio
= 4.0f
/ 3.0f
;
36 const int kWindowMargin
= 30;
37 const int kMinCardsMajor
= 3;
38 const int kOverviewTransitionMilliseconds
= 100;
39 const SkColor kWindowSelectorSelectionColor
= SK_ColorBLACK
;
40 const float kWindowSelectorSelectionOpacity
= 0.5f
;
41 const int kWindowSelectorSelectionPadding
= 15;
43 // Creates a copy of |window| with |recreated_layer| in the |target_root|.
44 views::Widget
* CreateCopyOfWindow(aura::RootWindow
* target_root
,
45 aura::Window
* src_window
,
46 ui::Layer
* recreated_layer
) {
47 views::Widget
* widget
= new views::Widget
;
48 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_POPUP
);
49 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
50 params
.parent
= src_window
->parent();
51 params
.can_activate
= false;
52 params
.keep_on_top
= true;
53 widget
->set_focus_on_creation(false);
55 widget
->SetVisibilityChangedAnimationsEnabled(false);
56 std::string name
= src_window
->name() + " (Copy)";
57 widget
->GetNativeWindow()->SetName(name
);
58 views::corewm::SetShadowType(widget
->GetNativeWindow(),
59 views::corewm::SHADOW_TYPE_RECTANGULAR
);
61 // Set the bounds in the target root window.
62 gfx::Display target_display
=
63 Shell::GetScreen()->GetDisplayNearestWindow(target_root
);
64 aura::client::ScreenPositionClient
* screen_position_client
=
65 aura::client::GetScreenPositionClient(src_window
->GetRootWindow());
66 if (screen_position_client
&& target_display
.is_valid()) {
67 screen_position_client
->SetBounds(widget
->GetNativeWindow(),
68 src_window
->GetBoundsInScreen(), target_display
);
70 widget
->SetBounds(src_window
->GetBoundsInScreen());
72 widget
->StackAbove(src_window
);
74 // Move the |recreated_layer| to the newly created window.
75 recreated_layer
->set_delegate(src_window
->layer()->delegate());
76 gfx::Rect layer_bounds
= recreated_layer
->bounds();
77 layer_bounds
.set_origin(gfx::Point(0, 0));
78 recreated_layer
->SetBounds(layer_bounds
);
79 recreated_layer
->SetVisible(false);
80 recreated_layer
->parent()->Remove(recreated_layer
);
82 aura::Window
* window
= widget
->GetNativeWindow();
83 recreated_layer
->SetVisible(true);
84 window
->layer()->Add(recreated_layer
);
85 window
->layer()->StackAtTop(recreated_layer
);
86 window
->layer()->SetOpacity(1);
91 // An observer which closes the widget and deletes the layer after an
92 // animation finishes.
93 class CleanupWidgetAfterAnimationObserver
: public ui::LayerAnimationObserver
{
95 CleanupWidgetAfterAnimationObserver(views::Widget
* widget
, ui::Layer
* layer
);
97 virtual void OnLayerAnimationEnded(
98 ui::LayerAnimationSequence
* sequence
) OVERRIDE
;
99 virtual void OnLayerAnimationAborted(
100 ui::LayerAnimationSequence
* sequence
) OVERRIDE
;
101 virtual void OnLayerAnimationScheduled(
102 ui::LayerAnimationSequence
* sequence
) OVERRIDE
;
105 virtual ~CleanupWidgetAfterAnimationObserver();
108 views::Widget
* widget_
;
111 DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver
);
114 CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
115 views::Widget
* widget
,
119 widget_
->GetNativeWindow()->layer()->GetAnimator()->AddObserver(this);
122 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationEnded(
123 ui::LayerAnimationSequence
* sequence
) {
127 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationAborted(
128 ui::LayerAnimationSequence
* sequence
) {
132 void CleanupWidgetAfterAnimationObserver::OnLayerAnimationScheduled(
133 ui::LayerAnimationSequence
* sequence
) {
136 CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
137 widget_
->GetNativeWindow()->layer()->GetAnimator()->RemoveObserver(this);
141 views::corewm::DeepDeleteLayers(layer_
);
146 // The animation settings used for window selector animations.
147 class WindowSelectorAnimationSettings
148 : public ui::ScopedLayerAnimationSettings
{
150 WindowSelectorAnimationSettings(aura::Window
* window
) :
151 ui::ScopedLayerAnimationSettings(window
->layer()->GetAnimator()) {
152 SetPreemptionStrategy(
153 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
154 SetTransitionDuration(
155 base::TimeDelta::FromMilliseconds(kOverviewTransitionMilliseconds
));
158 virtual ~WindowSelectorAnimationSettings() {
164 // TODO(flackr): Split up into separate file under subdirectory in ash/wm.
165 class WindowSelectorWindow
{
167 explicit WindowSelectorWindow(aura::Window
* window
);
168 virtual ~WindowSelectorWindow();
170 aura::Window
* window() { return window_
; }
171 const aura::Window
* window() const { return window_
; }
173 // Returns true if this window selector window contains the |target|. This is
174 // used to determine if an event targetted this window.
175 bool Contains(const aura::Window
* target
) const;
177 // Restores this window on exit rather than returning it to a minimized state
178 // if it was minimized on entering overview mode.
179 void RestoreWindowOnExit();
181 // Informs the WindowSelectorWindow that the window being watched was
182 // destroyed. This resets the internal window pointer to avoid calling
183 // anything on the window at destruction time.
184 void OnWindowDestroyed();
186 // Applies a transform to the window to fit within |target_bounds| while
187 // maintaining its aspect ratio.
188 void TransformToFitBounds(aura::RootWindow
* root_window
,
189 const gfx::Rect
& target_bounds
);
191 gfx::Rect
bounds() { return fit_bounds_
; }
194 // A weak pointer to the real window in the overview.
195 aura::Window
* window_
;
197 // A copy of the window used to transition the window to another root.
198 views::Widget
* window_copy_
;
200 // A weak pointer to a deep copy of the window's layers.
203 // If true, the window was minimized and should be restored if the window
207 // The original transform of the window before entering overview mode.
208 gfx::Transform original_transform_
;
210 // The bounds this window is fit to.
211 gfx::Rect fit_bounds_
;
213 DISALLOW_COPY_AND_ASSIGN(WindowSelectorWindow
);
216 WindowSelectorWindow::WindowSelectorWindow(aura::Window
* window
)
220 minimized_(window
->GetProperty(aura::client::kShowStateKey
) ==
221 ui::SHOW_STATE_MINIMIZED
),
222 original_transform_(window
->layer()->transform()) {
227 WindowSelectorWindow::~WindowSelectorWindow() {
229 WindowSelectorAnimationSettings
animation_settings(window_
);
230 gfx::Transform transform
;
231 window_
->SetTransform(original_transform_
);
233 // Setting opacity 0 and visible false ensures that the property change
234 // to SHOW_STATE_MINIMIZED will not animate the window from its original
235 // bounds to the minimized position.
236 window_
->layer()->SetOpacity(0);
237 window_
->layer()->SetVisible(false);
238 window_
->SetProperty(aura::client::kShowStateKey
,
239 ui::SHOW_STATE_MINIMIZED
);
242 // If a copy of the window was created, clean it up.
245 // If the initial window wasn't destroyed, the copy needs to be animated
246 // out. CleanupWidgetAfterAnimationObserver will destroy the widget and
247 // layer after the animation is complete.
248 new CleanupWidgetAfterAnimationObserver(window_copy_
, layer_
);
249 WindowSelectorAnimationSettings
animation_settings(
250 window_copy_
->GetNativeWindow());
251 window_copy_
->GetNativeWindow()->SetTransform(original_transform_
);
253 window_copy_
->Close();
255 views::corewm::DeepDeleteLayers(layer_
);
262 bool WindowSelectorWindow::Contains(const aura::Window
* window
) const {
263 if (window_copy_
&& window_copy_
->GetNativeWindow()->Contains(window
))
265 return window_
->Contains(window
);
268 void WindowSelectorWindow::RestoreWindowOnExit() {
270 original_transform_
= gfx::Transform();
273 void WindowSelectorWindow::OnWindowDestroyed() {
277 void WindowSelectorWindow::TransformToFitBounds(
278 aura::RootWindow
* root_window
,
279 const gfx::Rect
& target_bounds
) {
280 fit_bounds_
= target_bounds
;
281 const gfx::Rect bounds
= window_
->GetBoundsInScreen();
282 float scale
= std::min(1.0f
,
283 std::min(static_cast<float>(target_bounds
.width()) / bounds
.width(),
284 static_cast<float>(target_bounds
.height()) / bounds
.height()));
285 gfx::Transform transform
;
286 gfx::Vector2d
offset(
287 0.5 * (target_bounds
.width() - scale
* bounds
.width()),
288 0.5 * (target_bounds
.height() - scale
* bounds
.height()));
289 transform
.Translate(target_bounds
.x() - bounds
.x() + offset
.x(),
290 target_bounds
.y() - bounds
.y() + offset
.y());
291 transform
.Scale(scale
, scale
);
292 if (root_window
!= window_
->GetRootWindow()) {
295 layer_
= views::corewm::RecreateWindowLayers(window_
, true);
296 window_copy_
= CreateCopyOfWindow(root_window
, window_
, layer_
);
298 WindowSelectorAnimationSettings
animation_settings(
299 window_copy_
->GetNativeWindow());
300 window_copy_
->GetNativeWindow()->SetTransform(transform
);
302 WindowSelectorAnimationSettings
animation_settings(window_
);
303 window_
->SetTransform(transform
);
306 // A comparator for locating a given target window.
307 struct WindowSelectorWindowComparator
308 : public std::unary_function
<WindowSelectorWindow
*, bool> {
309 explicit WindowSelectorWindowComparator(const aura::Window
* target_window
)
310 : target(target_window
) {
313 bool operator()(const WindowSelectorWindow
* window
) const {
314 return target
== window
->window();
317 const aura::Window
* target
;
320 WindowSelector::WindowSelector(const WindowList
& windows
,
321 WindowSelector::Mode mode
,
322 WindowSelectorDelegate
* delegate
)
326 selection_root_(NULL
) {
328 for (size_t i
= 0; i
< windows
.size(); ++i
) {
329 windows
[i
]->AddObserver(this);
330 windows_
.push_back(new WindowSelectorWindow(windows
[i
]));
332 if (mode
== WindowSelector::CYCLE
)
333 selection_root_
= ash::Shell::GetActiveRootWindow();
335 ash::Shell::GetInstance()->AddPreTargetHandler(this);
338 WindowSelector::~WindowSelector() {
339 for (size_t i
= 0; i
< windows_
.size(); i
++) {
340 windows_
[i
]->window()->RemoveObserver(this);
342 ash::Shell::GetInstance()->RemovePreTargetHandler(this);
345 void WindowSelector::Step(WindowSelector::Direction direction
) {
346 DCHECK(windows_
.size() > 0);
347 if (!selection_widget_
)
348 InitializeSelectionWidget();
349 selected_window_
= (selected_window_
+ windows_
.size() +
350 (direction
== WindowSelector::FORWARD
? 1 : -1)) % windows_
.size();
351 UpdateSelectionLocation(true);
354 void WindowSelector::SelectWindow() {
355 delegate_
->OnWindowSelected(windows_
[selected_window_
]->window());
358 void WindowSelector::OnEvent(ui::Event
* event
) {
359 // If the event is targetted at any of the windows in the overview, then
360 // prevent it from propagating.
361 aura::Window
* target
= static_cast<aura::Window
*>(event
->target());
362 for (size_t i
= 0; i
< windows_
.size(); ++i
) {
363 if (windows_
[i
]->Contains(target
)) {
364 event
->StopPropagation();
369 // This object may not be valid after this call as a selection event can
370 // trigger deletion of the window selector.
371 ui::EventHandler::OnEvent(event
);
374 void WindowSelector::OnMouseEvent(ui::MouseEvent
* event
) {
375 if (event
->type() != ui::ET_MOUSE_RELEASED
)
377 WindowSelectorWindow
* target
= GetEventTarget(event
);
381 HandleSelectionEvent(target
);
384 void WindowSelector::OnGestureEvent(ui::GestureEvent
* event
) {
385 if (event
->type() != ui::ET_GESTURE_TAP
)
387 WindowSelectorWindow
* target
= GetEventTarget(event
);
391 HandleSelectionEvent(target
);
394 void WindowSelector::OnWindowDestroyed(aura::Window
* window
) {
395 ScopedVector
<WindowSelectorWindow
>::iterator iter
=
396 std::find_if(windows_
.begin(), windows_
.end(),
397 WindowSelectorWindowComparator(window
));
398 DCHECK(iter
!= windows_
.end());
399 size_t deleted_index
= iter
- windows_
.begin();
400 (*iter
)->OnWindowDestroyed();
401 windows_
.erase(iter
);
402 if (windows_
.empty()) {
403 delegate_
->OnSelectionCanceled();
406 if (selected_window_
>= deleted_index
) {
407 if (selected_window_
> deleted_index
)
409 selected_window_
= selected_window_
% windows_
.size();
410 UpdateSelectionLocation(true);
416 WindowSelectorWindow
* WindowSelector::GetEventTarget(ui::LocatedEvent
* event
) {
417 aura::Window
* target
= static_cast<aura::Window
*>(event
->target());
418 // If the target window doesn't actually contain the event location (i.e.
419 // mouse down over the window and mouse up elsewhere) then do not select the
421 if (!target
->HitTest(event
->location()))
424 for (size_t i
= 0; i
< windows_
.size(); i
++) {
425 if (windows_
[i
]->Contains(target
))
431 void WindowSelector::HandleSelectionEvent(WindowSelectorWindow
* target
) {
432 // The selected window should not be minimized when window selection is
434 target
->RestoreWindowOnExit();
435 delegate_
->OnWindowSelected(target
->window());
438 void WindowSelector::PositionWindows() {
439 if (selection_root_
) {
440 DCHECK_EQ(mode_
, CYCLE
);
441 std::vector
<WindowSelectorWindow
*> windows
;
442 for (size_t i
= 0; i
< windows_
.size(); ++i
)
443 windows
.push_back(windows_
[i
]);
444 PositionWindowsOnRoot(selection_root_
, windows
);
446 DCHECK_EQ(mode_
, OVERVIEW
);
447 Shell::RootWindowList root_window_list
= Shell::GetAllRootWindows();
448 for (size_t i
= 0; i
< root_window_list
.size(); ++i
)
449 PositionWindowsFromRoot(root_window_list
[i
]);
453 void WindowSelector::PositionWindowsFromRoot(aura::RootWindow
* root_window
) {
454 std::vector
<WindowSelectorWindow
*> windows
;
455 for (size_t i
= 0; i
< windows_
.size(); ++i
) {
456 if (windows_
[i
]->window()->GetRootWindow() == root_window
)
457 windows
.push_back(windows_
[i
]);
459 PositionWindowsOnRoot(root_window
, windows
);
462 void WindowSelector::PositionWindowsOnRoot(
463 aura::RootWindow
* root_window
,
464 const std::vector
<WindowSelectorWindow
*>& windows
) {
468 gfx::Size window_size
;
469 gfx::Rect total_bounds
= ScreenAsh::ConvertRectToScreen(root_window
,
470 ScreenAsh::GetDisplayWorkAreaBoundsInParent(
471 Shell::GetContainer(root_window
,
472 internal::kShellWindowId_DefaultContainer
)));
474 // Find the minimum number of windows per row that will fit all of the
475 // windows on screen.
476 size_t columns
= std::max(
477 total_bounds
.width() > total_bounds
.height() ? kMinCardsMajor
: 1,
478 static_cast<int>(ceil(sqrt(total_bounds
.width() * windows
.size() /
479 (kCardAspectRatio
* total_bounds
.height())))));
480 size_t rows
= ((windows
.size() + columns
- 1) / columns
);
481 window_size
.set_width(std::min(
482 static_cast<int>(total_bounds
.width() / columns
),
483 static_cast<int>(total_bounds
.height() * kCardAspectRatio
/ rows
)));
484 window_size
.set_height(window_size
.width() / kCardAspectRatio
);
486 // Calculate the X and Y offsets necessary to center the grid.
487 int x_offset
= total_bounds
.x() + ((windows
.size() >= columns
? 0 :
488 (columns
- windows
.size()) * window_size
.width()) +
489 (total_bounds
.width() - columns
* window_size
.width())) / 2;
490 int y_offset
= total_bounds
.y() + (total_bounds
.height() -
491 rows
* window_size
.height()) / 2;
492 for (size_t i
= 0; i
< windows
.size(); ++i
) {
493 gfx::Transform transform
;
494 int column
= i
% columns
;
495 int row
= i
/ columns
;
496 gfx::Rect
target_bounds(window_size
.width() * column
+ x_offset
,
497 window_size
.height() * row
+ y_offset
,
499 window_size
.height());
500 target_bounds
.Inset(kWindowMargin
, kWindowMargin
);
501 windows
[i
]->TransformToFitBounds(root_window
, target_bounds
);
505 void WindowSelector::InitializeSelectionWidget() {
506 selection_widget_
.reset(new views::Widget
);
507 views::Widget::InitParams params
;
508 params
.type
= views::Widget::InitParams::TYPE_POPUP
;
509 params
.can_activate
= false;
510 params
.keep_on_top
= false;
511 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
512 params
.opacity
= views::Widget::InitParams::OPAQUE_WINDOW
;
513 params
.parent
= Shell::GetContainer(
515 internal::kShellWindowId_DefaultContainer
);
516 params
.accept_events
= false;
517 selection_widget_
->set_focus_on_creation(false);
518 selection_widget_
->Init(params
);
519 views::View
* content_view
= new views::View
;
520 content_view
->set_background(
521 views::Background::CreateSolidBackground(kWindowSelectorSelectionColor
));
522 selection_widget_
->SetContentsView(content_view
);
523 UpdateSelectionLocation(false);
524 selection_widget_
->GetNativeWindow()->parent()->StackChildAtBottom(
525 selection_widget_
->GetNativeWindow());
526 selection_widget_
->Show();
527 selection_widget_
->GetNativeWindow()->layer()->SetOpacity(
528 kWindowSelectorSelectionOpacity
);
531 void WindowSelector::UpdateSelectionLocation(bool animate
) {
532 if (!selection_widget_
)
534 gfx::Rect target_bounds
= windows_
[selected_window_
]->bounds();
535 target_bounds
.Inset(-kWindowSelectorSelectionPadding
,
536 -kWindowSelectorSelectionPadding
);
538 WindowSelectorAnimationSettings
animation_settings(
539 selection_widget_
->GetNativeWindow());
540 selection_widget_
->SetBounds(target_bounds
);
542 selection_widget_
->SetBounds(target_bounds
);