Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / athena / wm / window_overview_mode.cc
blobb34229309a2c5326c51df2b892a8cd3cdf656cbe
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"
7 #include <algorithm>
8 #include <functional>
9 #include <vector>
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"
31 namespace {
33 struct WindowOverviewState {
34 // The transform for when the window is at the topmost position.
35 gfx::Transform top;
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.
42 float progress;
45 } // namespace
47 DECLARE_WINDOW_PROPERTY_TYPE(WindowOverviewState*)
48 DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowOverviewState,
49 kWindowOverviewState,
50 NULL)
51 namespace athena {
53 namespace {
55 // Gets the transform for the window in its current state.
56 gfx::Transform GetTransformForState(WindowOverviewState* state) {
57 return gfx::Tween::TransformValueBetween(state->progress,
58 state->top,
59 state->bottom);
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 {
83 public:
84 explicit StaticWindowTargeter(aura::Window* target) : target_(target) {}
85 virtual ~StaticWindowTargeter() {}
87 private:
88 // aura::WindowTargeter:
89 virtual ui::EventTarget* FindTargetForEvent(ui::EventTarget* root,
90 ui::Event* event) OVERRIDE {
91 return target_;
94 virtual ui::EventTarget* FindTargetForLocatedEvent(
95 ui::EventTarget* root,
96 ui::LocatedEvent* event) OVERRIDE {
97 return target_;
100 aura::Window* target_;
101 DISALLOW_COPY_AND_ASSIGN(StaticWindowTargeter);
104 class WindowOverviewModeImpl : public WindowOverviewMode,
105 public ui::EventHandler,
106 public ui::CompositorAnimationObserver {
107 public:
108 WindowOverviewModeImpl(aura::Window* container,
109 const WindowListProvider* window_list_provider,
110 WindowOverviewModeDelegate* delegate)
111 : container_(container),
112 window_list_provider_(window_list_provider),
113 delegate_(delegate),
114 scoped_targeter_(new aura::ScopedWindowTargeter(
115 container,
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();
131 if (windows.empty())
132 return;
133 std::for_each(windows.begin(), windows.end(), &RestoreWindowState);
136 private:
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
139 // the windows.
140 void ComputeTerminalStatesForAllWindows() {
141 aura::Window::Windows windows = window_list_provider_->GetWindowList();
142 size_t index = 0;
144 for (aura::Window::Windows::const_reverse_iterator iter = windows.rbegin();
145 iter != windows.rend();
146 ++iter, ++index) {
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,
157 size_t index,
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;
167 float x_translate =
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);
179 CHECK(state);
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();
202 window->Show();
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();
224 if (!targeter)
225 return NULL;
226 aura::Window* target = static_cast<aura::Window*>(
227 targeter->FindTargetForLocatedEvent(container_, event));
228 while (target && target->parent() != container_)
229 target = target->parent();
230 return target;
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();
239 if (delta_y < 0) {
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();
244 ++iter) {
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));
252 delta_y_p -= apply;
255 } else {
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();
261 ++iter) {
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));
268 delta_y_p /= 2.f;
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();
314 else
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
323 // window.
324 if (new_action == OverviewToolbar::ACTION_TYPE_NONE) {
325 if (fabs(dragged_distance.x()) > kMinDistanceForDismissal)
326 new_action = OverviewToolbar::ACTION_TYPE_CLOSE;
327 else
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);
343 float opacity =
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);
351 } else {
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)
365 return false;
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();
388 else
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_;
400 ++iter) {
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,
409 list.size());
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);
440 else
441 RestoreDragWindow();
444 // ui::EventHandler:
445 virtual void OnMouseEvent(ui::MouseEvent* mouse) OVERRIDE {
446 if (mouse->type() == ui::ET_MOUSE_PRESSED) {
447 aura::Window* select = SelectWindowAt(mouse);
448 if (select) {
449 mouse->SetHandled();
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);
465 if (select) {
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);
474 if (dragged_window_)
475 overview_toolbar_.reset(new OverviewToolbar(container_));
477 } else if (gesture->type() == ui::ET_GESTURE_SCROLL_UPDATE) {
478 if (dragged_window_)
479 DragWindow(*gesture);
480 else
481 DoScroll(gesture->details().scroll_y());
482 gesture->SetHandled();
483 } else if (gesture->type() == ui::ET_GESTURE_SCROLL_END) {
484 if (dragged_window_)
485 EndDragWindow(*gesture);
486 gesture->SetHandled();
487 } else if (gesture->type() == ui::ET_SCROLL_FLING_START) {
488 if (dragged_window_) {
489 EndDragWindow(*gesture);
490 } else {
491 CreateFlingerFor(*gesture);
492 AddAnimationObserver();
494 gesture->SetHandled();
495 } else if (gesture->type() == ui::ET_GESTURE_TAP_DOWN) {
496 if (fling_) {
497 fling_.reset();
498 RemoveAnimationObserver();
499 gesture->SetHandled();
501 dragged_window_ = NULL;
505 // ui::CompositorAnimationObserver:
506 virtual void OnAnimationStep(base::TimeTicks timestamp) OVERRIDE {
507 CHECK(fling_);
508 if (fling_->start_timestamp() > timestamp)
509 return;
510 gfx::Vector2dF scroll = fling_->GetScrollAmountAtTime(timestamp);
511 if (scroll.IsZero()) {
512 fling_.reset();
513 RemoveAnimationObserver();
514 } else {
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);
539 } // namespace
541 // static
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