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 "athena/wm/window_overview_mode.h"
11 #include "athena/wm/overview_toolbar.h"
12 #include "athena/wm/public/window_list_provider.h"
13 #include "base/bind.h"
14 #include "base/macros.h"
15 #include "ui/aura/scoped_window_targeter.h"
16 #include "ui/aura/window.h"
17 #include "ui/aura/window_delegate.h"
18 #include "ui/aura/window_property.h"
19 #include "ui/aura/window_targeter.h"
20 #include "ui/aura/window_tree_host.h"
21 #include "ui/compositor/closure_animation_observer.h"
22 #include "ui/compositor/compositor.h"
23 #include "ui/compositor/compositor_animation_observer.h"
24 #include "ui/compositor/scoped_layer_animation_settings.h"
25 #include "ui/events/event_handler.h"
26 #include "ui/events/gestures/fling_curve.h"
27 #include "ui/gfx/frame_time.h"
28 #include "ui/gfx/transform.h"
29 #include "ui/wm/core/shadow_types.h"
33 struct WindowOverviewState
{
34 // The transform for when the window is at the topmost position.
37 // The transform for when the window is at the bottom-most position.
38 gfx::Transform bottom
;
40 // The current overview state of the window. 0.f means the window is at the
41 // topmost position. 1.f means the window is at the bottom-most position.
47 DECLARE_WINDOW_PROPERTY_TYPE(WindowOverviewState
*)
48 DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowOverviewState
,
55 // Gets the transform for the window in its current state.
56 gfx::Transform
GetTransformForState(WindowOverviewState
* state
) {
57 return gfx::Tween::TransformValueBetween(state
->progress
,
62 // Sets the progress-state for the window in the overview mode.
63 void SetWindowProgress(aura::Window
* window
, float progress
) {
64 WindowOverviewState
* state
= window
->GetProperty(kWindowOverviewState
);
65 state
->progress
= progress
;
66 window
->SetTransform(GetTransformForState(state
));
69 // Resets the overview-related state for |window|.
70 void RestoreWindowState(aura::Window
* window
) {
71 window
->ClearProperty(kWindowOverviewState
);
73 ui::ScopedLayerAnimationSettings
settings(window
->layer()->GetAnimator());
74 settings
.SetPreemptionStrategy(
75 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
76 settings
.SetTransitionDuration(base::TimeDelta::FromMilliseconds(250));
77 window
->SetTransform(gfx::Transform());
78 wm::SetShadowType(window
, wm::SHADOW_TYPE_NONE
);
81 // Always returns the same target.
82 class StaticWindowTargeter
: public aura::WindowTargeter
{
84 explicit StaticWindowTargeter(aura::Window
* target
) : target_(target
) {}
85 virtual ~StaticWindowTargeter() {}
88 // aura::WindowTargeter:
89 virtual ui::EventTarget
* FindTargetForEvent(ui::EventTarget
* root
,
90 ui::Event
* event
) OVERRIDE
{
94 virtual ui::EventTarget
* FindTargetForLocatedEvent(
95 ui::EventTarget
* root
,
96 ui::LocatedEvent
* event
) OVERRIDE
{
100 aura::Window
* target_
;
101 DISALLOW_COPY_AND_ASSIGN(StaticWindowTargeter
);
104 class WindowOverviewModeImpl
: public WindowOverviewMode
,
105 public ui::EventHandler
,
106 public ui::CompositorAnimationObserver
{
108 WindowOverviewModeImpl(aura::Window
* container
,
109 const WindowListProvider
* window_list_provider
,
110 WindowOverviewModeDelegate
* delegate
)
111 : container_(container
),
112 window_list_provider_(window_list_provider
),
114 scoped_targeter_(new aura::ScopedWindowTargeter(
116 scoped_ptr
<ui::EventTargeter
>(
117 new StaticWindowTargeter(container
)))),
118 dragged_window_(NULL
) {
119 container_
->set_target_handler(this);
121 // Prepare the desired transforms for all the windows, and set the initial
122 // state on the windows.
123 ComputeTerminalStatesForAllWindows();
124 SetInitialWindowStates();
127 virtual ~WindowOverviewModeImpl() {
128 container_
->set_target_handler(container_
->delegate());
129 RemoveAnimationObserver();
130 aura::Window::Windows windows
= window_list_provider_
->GetWindowList();
133 std::for_each(windows
.begin(), windows
.end(), &RestoreWindowState
);
137 // Computes the transforms for all windows in both the topmost and bottom-most
138 // positions. The transforms are set in the |kWindowOverviewState| property of
140 void ComputeTerminalStatesForAllWindows() {
141 aura::Window::Windows windows
= window_list_provider_
->GetWindowList();
144 for (aura::Window::Windows::const_reverse_iterator iter
= windows
.rbegin();
145 iter
!= windows
.rend();
147 aura::Window
* window
= (*iter
);
148 wm::SetShadowType(window
, wm::SHADOW_TYPE_RECTANGULAR_ALWAYS_ACTIVE
);
150 WindowOverviewState
* state
= new WindowOverviewState
;
151 window
->SetProperty(kWindowOverviewState
, state
);
152 UpdateTerminalStateForWindowAtIndex(window
, index
, windows
.size());
156 void UpdateTerminalStateForWindowAtIndex(aura::Window
* window
,
158 size_t window_count
) {
159 const int kGapBetweenWindowsBottom
= 10;
160 const int kGapBetweenWindowsTop
= 5;
162 const int container_width
= container_
->bounds().width();
163 const int window_width
= window
->bounds().width();
164 const int window_x
= window
->bounds().x();
165 gfx::Transform top_transform
;
166 int top
= (window_count
- index
- 1) * kGapBetweenWindowsTop
;
168 (container_width
- (window_width
* kMinScale
)) / 2 - window_x
;
169 top_transform
.Translate(x_translate
, top
);
170 top_transform
.Scale(kMinScale
, kMinScale
);
172 gfx::Transform bottom_transform
;
173 int bottom
= GetScrollableHeight() - (index
* kGapBetweenWindowsBottom
);
174 x_translate
= (container_width
- (window_width
* kMaxScale
)) / 2 - window_x
;
175 bottom_transform
.Translate(x_translate
, bottom
- window
->bounds().y());
176 bottom_transform
.Scale(kMaxScale
, kMaxScale
);
178 WindowOverviewState
* state
= window
->GetProperty(kWindowOverviewState
);
180 state
->top
= top_transform
;
181 state
->bottom
= bottom_transform
;
182 state
->progress
= 0.f
;
185 // Sets the initial position for the windows for the overview mode.
186 void SetInitialWindowStates() {
187 aura::Window::Windows windows
= window_list_provider_
->GetWindowList();
188 size_t window_count
= windows
.size();
189 // The initial overview state of the topmost three windows.
190 const float kInitialProgress
[] = { 0.5f
, 0.05f
, 0.01f
};
191 for (size_t i
= 0; i
< window_count
; ++i
) {
192 float progress
= 0.f
;
193 aura::Window
* window
= windows
[window_count
- 1 - i
];
194 if (i
< arraysize(kInitialProgress
))
195 progress
= kInitialProgress
[i
];
197 scoped_refptr
<ui::LayerAnimator
> animator
=
198 window
->layer()->GetAnimator();
200 // Unset any in-progress animation.
201 animator
->AbortAllAnimations();
203 window
->SetTransform(gfx::Transform());
204 // Setup the animation.
206 ui::ScopedLayerAnimationSettings
settings(animator
);
207 settings
.SetPreemptionStrategy(
208 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
209 settings
.SetTransitionDuration(base::TimeDelta::FromMilliseconds(250));
210 SetWindowProgress(window
, progress
);
215 aura::Window
* SelectWindowAt(ui::LocatedEvent
* event
) {
216 CHECK_EQ(container_
, event
->target());
217 // Find the old targeter to find the target of the event.
218 ui::EventTarget
* window
= container_
;
219 ui::EventTargeter
* targeter
= scoped_targeter_
->old_targeter();
220 while (!targeter
&& window
->GetParentTarget()) {
221 window
= window
->GetParentTarget();
222 targeter
= window
->GetEventTargeter();
226 aura::Window
* target
= static_cast<aura::Window
*>(
227 targeter
->FindTargetForLocatedEvent(container_
, event
));
228 while (target
&& target
->parent() != container_
)
229 target
= target
->parent();
233 // Scroll the window list by |delta_y| amount. |delta_y| is negative when
234 // scrolling up; and positive when scrolling down.
235 void DoScroll(float delta_y
) {
236 const float kEpsilon
= 1e-3f
;
237 float delta_y_p
= std::abs(delta_y
) / GetScrollableHeight();
238 aura::Window::Windows windows
= window_list_provider_
->GetWindowList();
240 // Scroll up. Start with the top-most (i.e. behind-most in terms of
241 // z-index) window, and try to scroll them up.
242 for (aura::Window::Windows::const_iterator iter
= windows
.begin();
243 delta_y_p
> kEpsilon
&& iter
!= windows
.end();
245 aura::Window
* window
= (*iter
);
246 WindowOverviewState
* state
= window
->GetProperty(kWindowOverviewState
);
247 if (state
->progress
> kEpsilon
) {
248 // It is possible to scroll |window| up. Scroll it up, and update
249 // |delta_y_p| for the next window.
250 float apply
= delta_y_p
* state
->progress
;
251 SetWindowProgress(window
, std::max(0.f
, state
->progress
- apply
* 3));
256 // Scroll down. Start with the bottom-most (i.e. front-most in terms of
257 // z-index) window, and try to scroll them down.
258 aura::Window::Windows::const_reverse_iterator iter
;
259 for (iter
= windows
.rbegin();
260 delta_y_p
> kEpsilon
&& iter
!= windows
.rend();
262 aura::Window
* window
= (*iter
);
263 WindowOverviewState
* state
= window
->GetProperty(kWindowOverviewState
);
264 if (1.f
- state
->progress
> kEpsilon
) {
265 // It is possible to scroll |window| down. Scroll it down, and update
266 // |delta_y_p| for the next window.
267 SetWindowProgress(window
, std::min(1.f
, state
->progress
+ delta_y_p
));
274 int GetScrollableHeight() const {
275 const float kScrollableFraction
= 0.65f
;
276 return container_
->bounds().height() * kScrollableFraction
;
279 void CreateFlingerFor(const ui::GestureEvent
& event
) {
280 gfx::Vector2dF
velocity(event
.details().velocity_x(),
281 event
.details().velocity_y());
282 fling_
.reset(new ui::FlingCurve(velocity
, gfx::FrameTime::Now()));
285 void AddAnimationObserver() {
286 ui::Compositor
* compositor
= container_
->GetHost()->compositor();
287 if (!compositor
->HasAnimationObserver(this))
288 compositor
->AddAnimationObserver(this);
291 void RemoveAnimationObserver() {
292 ui::Compositor
* compositor
= container_
->GetHost()->compositor();
293 if (compositor
->HasAnimationObserver(this))
294 compositor
->RemoveAnimationObserver(this);
297 void DragWindow(const ui::GestureEvent
& event
) {
298 CHECK(dragged_window_
);
299 CHECK_EQ(ui::ET_GESTURE_SCROLL_UPDATE
, event
.type());
300 CHECK(overview_toolbar_
);
301 gfx::Vector2dF dragged_distance
=
302 dragged_start_location_
- event
.location();
303 WindowOverviewState
* dragged_state
=
304 dragged_window_
->GetProperty(kWindowOverviewState
);
305 CHECK(dragged_state
);
306 gfx::Transform transform
= GetTransformForState(dragged_state
);
307 transform
.Translate(-dragged_distance
.x(), 0);
308 dragged_window_
->SetTransform(transform
);
310 // Update the toolbar.
311 const int kMinDistanceForActionButtons
= 20;
312 if (fabs(dragged_distance
.x()) > kMinDistanceForActionButtons
)
313 overview_toolbar_
->ShowActionButtons();
315 overview_toolbar_
->HideActionButtons();
317 // See if the touch-point is above one of the action-buttons.
318 OverviewToolbar::ActionType new_action
=
319 overview_toolbar_
->GetHighlightAction(event
);
321 // If the touch-point is not above any of the action buttons, then highlight
322 // the close-button by default, if the user has dragged enough to close the
324 if (new_action
== OverviewToolbar::ACTION_TYPE_NONE
) {
325 if (fabs(dragged_distance
.x()) > kMinDistanceForDismissal
)
326 new_action
= OverviewToolbar::ACTION_TYPE_CLOSE
;
328 new_action
= OverviewToolbar::ACTION_TYPE_NONE
;
330 OverviewToolbar::ActionType previous_action
=
331 overview_toolbar_
->current_action();
332 overview_toolbar_
->SetHighlightAction(new_action
);
334 // If the user has selected to get into split-view mode, then show the
335 // window with full opacity. Otherwise, fade it out as it closes. Animate
336 // the opacity if transitioning to/from the split-view button.
337 bool animate_opacity
=
338 (new_action
!= previous_action
) &&
339 ((new_action
== OverviewToolbar::ACTION_TYPE_SPLIT
) ||
340 (previous_action
== OverviewToolbar::ACTION_TYPE_SPLIT
));
341 float ratio
= std::min(
342 1.f
, std::abs(dragged_distance
.x()) / kMinDistanceForDismissal
);
344 (new_action
== OverviewToolbar::ACTION_TYPE_SPLIT
)
346 : gfx::Tween::FloatValueBetween(ratio
, kMaxOpacity
, kMinOpacity
);
347 if (animate_opacity
) {
348 ui::ScopedLayerAnimationSettings
settings(
349 dragged_window_
->layer()->GetAnimator());
350 dragged_window_
->layer()->SetOpacity(opacity
);
352 dragged_window_
->layer()->SetOpacity(opacity
);
356 bool ShouldCloseDragWindow(const ui::GestureEvent
& event
) const {
357 gfx::Vector2dF dragged_distance
=
358 dragged_start_location_
- event
.location();
359 if (event
.type() == ui::ET_GESTURE_SCROLL_END
)
360 return std::abs(dragged_distance
.x()) >= kMinDistanceForDismissal
;
361 CHECK_EQ(ui::ET_SCROLL_FLING_START
, event
.type());
362 const bool dragging_towards_right
= dragged_distance
.x() < 0;
363 const bool swipe_towards_right
= event
.details().velocity_x() > 0;
364 if (dragging_towards_right
!= swipe_towards_right
)
366 const float kMinVelocityForDismissal
= 500.f
;
367 return std::abs(event
.details().velocity_x()) > kMinVelocityForDismissal
;
370 void CloseDragWindow(const ui::GestureEvent
& gesture
) {
371 // Animate |dragged_window_| offscreen first, then destroy it.
372 ui::ScopedLayerAnimationSettings
settings(
373 dragged_window_
->layer()->GetAnimator());
374 settings
.SetPreemptionStrategy(
375 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
376 settings
.AddObserver(new ui::ClosureAnimationObserver(
377 base::Bind(&base::DeletePointer
<aura::Window
>, dragged_window_
)));
379 WindowOverviewState
* dragged_state
=
380 dragged_window_
->GetProperty(kWindowOverviewState
);
381 CHECK(dragged_state
);
382 gfx::Transform transform
= dragged_window_
->layer()->transform();
383 gfx::RectF transformed_bounds
= dragged_window_
->bounds();
384 transform
.TransformRect(&transformed_bounds
);
385 float transform_x
= 0.f
;
386 if (gesture
.location().x() > dragged_start_location_
.x())
387 transform_x
= container_
->bounds().right() - transformed_bounds
.x();
389 transform_x
= -(transformed_bounds
.x() + transformed_bounds
.width());
390 float scale
= gfx::Tween::FloatValueBetween(
391 dragged_state
->progress
, kMinScale
, kMaxScale
);
392 transform
.Translate(transform_x
/ scale
, 0);
393 dragged_window_
->SetTransform(transform
);
394 dragged_window_
->layer()->SetOpacity(kMinOpacity
);
396 // Move the windows behind |dragged_window_| in the stack forward one step.
397 const aura::Window::Windows
& list
= container_
->children();
398 for (aura::Window::Windows::const_iterator iter
= list
.begin();
399 iter
!= list
.end() && *iter
!= dragged_window_
;
401 aura::Window
* window
= *iter
;
402 ui::ScopedLayerAnimationSettings
settings(window
->layer()->GetAnimator());
403 settings
.SetPreemptionStrategy(
404 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
406 aura::Window
* next
= *(iter
+ 1);
407 WindowOverviewState
* next_state
= next
->GetProperty(kWindowOverviewState
);
408 UpdateTerminalStateForWindowAtIndex(window
, list
.end() - iter
,
410 SetWindowProgress(window
, next_state
->progress
);
413 dragged_window_
= NULL
;
416 void RestoreDragWindow() {
417 CHECK(dragged_window_
);
418 WindowOverviewState
* dragged_state
=
419 dragged_window_
->GetProperty(kWindowOverviewState
);
420 CHECK(dragged_state
);
422 ui::ScopedLayerAnimationSettings
settings(
423 dragged_window_
->layer()->GetAnimator());
424 settings
.SetPreemptionStrategy(
425 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET
);
426 dragged_window_
->SetTransform(GetTransformForState(dragged_state
));
427 dragged_window_
->layer()->SetOpacity(1.f
);
428 dragged_window_
= NULL
;
431 void EndDragWindow(const ui::GestureEvent
& gesture
) {
432 CHECK(dragged_window_
);
433 CHECK(overview_toolbar_
);
434 OverviewToolbar::ActionType action
= overview_toolbar_
->current_action();
435 overview_toolbar_
.reset();
436 if (action
== OverviewToolbar::ACTION_TYPE_SPLIT
)
437 delegate_
->OnSplitViewMode(NULL
, dragged_window_
);
438 else if (ShouldCloseDragWindow(gesture
))
439 CloseDragWindow(gesture
);
445 virtual void OnMouseEvent(ui::MouseEvent
* mouse
) OVERRIDE
{
446 if (mouse
->type() == ui::ET_MOUSE_PRESSED
) {
447 aura::Window
* select
= SelectWindowAt(mouse
);
450 delegate_
->OnSelectWindow(select
);
452 } else if (mouse
->type() == ui::ET_MOUSEWHEEL
) {
453 DoScroll(static_cast<ui::MouseWheelEvent
*>(mouse
)->y_offset());
457 virtual void OnScrollEvent(ui::ScrollEvent
* scroll
) OVERRIDE
{
458 if (scroll
->type() == ui::ET_SCROLL
)
459 DoScroll(scroll
->y_offset());
462 virtual void OnGestureEvent(ui::GestureEvent
* gesture
) OVERRIDE
{
463 if (gesture
->type() == ui::ET_GESTURE_TAP
) {
464 aura::Window
* select
= SelectWindowAt(gesture
);
466 gesture
->SetHandled();
467 delegate_
->OnSelectWindow(select
);
469 } else if (gesture
->type() == ui::ET_GESTURE_SCROLL_BEGIN
) {
470 if (std::abs(gesture
->details().scroll_x_hint()) >
471 std::abs(gesture
->details().scroll_y_hint()) * 2) {
472 dragged_start_location_
= gesture
->location();
473 dragged_window_
= SelectWindowAt(gesture
);
475 overview_toolbar_
.reset(new OverviewToolbar(container_
));
477 } else if (gesture
->type() == ui::ET_GESTURE_SCROLL_UPDATE
) {
479 DragWindow(*gesture
);
481 DoScroll(gesture
->details().scroll_y());
482 gesture
->SetHandled();
483 } else if (gesture
->type() == ui::ET_GESTURE_SCROLL_END
) {
485 EndDragWindow(*gesture
);
486 gesture
->SetHandled();
487 } else if (gesture
->type() == ui::ET_SCROLL_FLING_START
) {
488 if (dragged_window_
) {
489 EndDragWindow(*gesture
);
491 CreateFlingerFor(*gesture
);
492 AddAnimationObserver();
494 gesture
->SetHandled();
495 } else if (gesture
->type() == ui::ET_GESTURE_TAP_DOWN
) {
498 RemoveAnimationObserver();
499 gesture
->SetHandled();
501 dragged_window_
= NULL
;
505 // ui::CompositorAnimationObserver:
506 virtual void OnAnimationStep(base::TimeTicks timestamp
) OVERRIDE
{
508 if (fling_
->start_timestamp() > timestamp
)
510 gfx::Vector2dF scroll
= fling_
->GetScrollAmountAtTime(timestamp
);
511 if (scroll
.IsZero()) {
513 RemoveAnimationObserver();
515 DoScroll(scroll
.y());
519 const int kMinDistanceForDismissal
= 300;
520 const float kMinScale
= 0.6f
;
521 const float kMaxScale
= 0.95f
;
522 const float kMaxOpacity
= 1.0f
;
523 const float kMinOpacity
= 0.2f
;
525 aura::Window
* container_
;
526 // Provider of the stack of windows to show in the overview mode. Not owned.
527 const WindowListProvider
* window_list_provider_
;
528 WindowOverviewModeDelegate
* delegate_
;
529 scoped_ptr
<aura::ScopedWindowTargeter
> scoped_targeter_
;
530 scoped_ptr
<ui::FlingCurve
> fling_
;
532 aura::Window
* dragged_window_
;
533 gfx::Point dragged_start_location_
;
534 scoped_ptr
<OverviewToolbar
> overview_toolbar_
;
536 DISALLOW_COPY_AND_ASSIGN(WindowOverviewModeImpl
);
542 scoped_ptr
<WindowOverviewMode
> WindowOverviewMode::Create(
543 aura::Window
* container
,
544 const WindowListProvider
* window_list_provider
,
545 WindowOverviewModeDelegate
* delegate
) {
546 return scoped_ptr
<WindowOverviewMode
>(
547 new WindowOverviewModeImpl(container
, window_list_provider
, delegate
));
550 } // namespace athena