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.
4
5#include "athena/wm/window_overview_mode.h"
6
7#include <algorithm>
8#include <functional>
9#include <vector>
10
11#include "athena/wm/overview_toolbar.h"
12#include "athena/wm/public/window_list_provider.h"
13#include "athena/wm/public/window_list_provider_observer.h"
14#include "athena/wm/split_view_controller.h"
15#include "base/bind.h"
16#include "base/macros.h"
17#include "ui/aura/scoped_window_targeter.h"
18#include "ui/aura/window.h"
19#include "ui/aura/window_delegate.h"
20#include "ui/aura/window_property.h"
21#include "ui/aura/window_targeter.h"
22#include "ui/aura/window_tree_host.h"
23#include "ui/compositor/closure_animation_observer.h"
24#include "ui/compositor/compositor.h"
25#include "ui/compositor/compositor_animation_observer.h"
26#include "ui/compositor/scoped_layer_animation_settings.h"
27#include "ui/events/event_handler.h"
28#include "ui/events/gestures/fling_curve.h"
29#include "ui/gfx/frame_time.h"
30#include "ui/gfx/transform.h"
31#include "ui/wm/core/shadow_types.h"
32#include "ui/wm/core/window_animations.h"
33#include "ui/wm/core/window_util.h"
34
35namespace {
36
37const float kOverviewDefaultScale = 0.75f;
38
39struct WindowOverviewState {
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;
43
44  // The top-most and bottom-most vertical position of the window in overview
45  // mode.
46  float max_y;
47  float min_y;
48
49  // |split| is set if this window is one of the two split windows in split-view
50  // mode.
51  bool split;
52};
53
54}  // namespace
55
56DECLARE_WINDOW_PROPERTY_TYPE(WindowOverviewState*)
57DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowOverviewState,
58                                 kWindowOverviewState,
59                                 NULL)
60namespace athena {
61
62namespace {
63
64gfx::Transform GetTransformForSplitWindow(aura::Window* window, float scale) {
65  const float kScrollWindowPositionInOverview = 0.65f;
66  int x_translate = window->bounds().width() * (1 - scale) / 2;
67  gfx::Transform transform;
68  transform.Translate(
69      x_translate, window->bounds().height() * kScrollWindowPositionInOverview);
70  transform.Scale(scale, scale);
71  return transform;
72}
73
74// Gets the transform for the window in its current state.
75gfx::Transform GetTransformForState(aura::Window* window,
76                                    WindowOverviewState* state) {
77  if (state->split)
78    return GetTransformForSplitWindow(window, kOverviewDefaultScale);
79
80  const float kProgressToStartShrinking = 0.07;
81  const float kOverviewScale = 0.75f;
82  float scale = kOverviewScale;
83  if (state->progress < kProgressToStartShrinking) {
84    const float kShrunkMinimumScale = 0.7f;
85    scale = gfx::Tween::FloatValueBetween(
86        state->progress / kProgressToStartShrinking,
87        kShrunkMinimumScale,
88        kOverviewScale);
89  }
90  int container_width = window->parent()->bounds().width();
91  int window_width = window->bounds().width();
92  int window_x = window->bounds().x();
93  float x_translate = (container_width - (window_width * scale)) / 2 - window_x;
94  float y_translate = gfx::Tween::FloatValueBetween(
95      state->progress, state->min_y, state->max_y);
96  gfx::Transform transform;
97  transform.Translate(x_translate, y_translate);
98  transform.Scale(scale, scale);
99  return transform;
100}
101
102// Sets the progress-state for the window in the overview mode.
103void SetWindowProgress(aura::Window* window, float progress) {
104  WindowOverviewState* state = window->GetProperty(kWindowOverviewState);
105  state->progress = progress;
106
107  gfx::Transform transform = GetTransformForState(window, state);
108  window->SetTransform(transform);
109}
110
111void HideWindowIfNotVisible(aura::Window* window,
112                            SplitViewController* split_view_controller) {
113  bool should_hide = true;
114  if (split_view_controller->IsSplitViewModeActive()) {
115    should_hide = window != split_view_controller->left_window() &&
116                  window != split_view_controller->right_window();
117  } else {
118    should_hide = !wm::IsActiveWindow(window);
119  }
120  if (should_hide)
121    window->Hide();
122}
123
124// Resets the overview-related state for |window|.
125void RestoreWindowState(aura::Window* window,
126                        SplitViewController* split_view_controller) {
127  window->ClearProperty(kWindowOverviewState);
128
129  ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
130  settings.SetPreemptionStrategy(
131      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
132  settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(250));
133
134  settings.AddObserver(new ui::ClosureAnimationObserver(
135      base::Bind(&HideWindowIfNotVisible, window, split_view_controller)));
136
137  window->SetTransform(gfx::Transform());
138
139  // Reset the window opacity in case the user is dragging a window.
140  window->layer()->SetOpacity(1.0f);
141
142  wm::SetShadowType(window, wm::SHADOW_TYPE_NONE);
143}
144
145gfx::RectF GetTransformedBounds(aura::Window* window) {
146  gfx::Transform transform;
147  gfx::RectF bounds = window->bounds();
148  transform.Translate(bounds.x(), bounds.y());
149  transform.PreconcatTransform(window->layer()->transform());
150  transform.Translate(-bounds.x(), -bounds.y());
151  transform.TransformRect(&bounds);
152  return bounds;
153}
154
155void TransformSplitWindowScale(aura::Window* window, float scale) {
156  gfx::Transform transform = window->layer()->GetTargetTransform();
157  if (transform.Scale2d() == gfx::Vector2dF(scale, scale))
158    return;
159  ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
160  window->SetTransform(GetTransformForSplitWindow(window, scale));
161}
162
163void AnimateWindowTo(aura::Window* animate_window,
164                     aura::Window* target_window) {
165  ui::ScopedLayerAnimationSettings settings(
166      animate_window->layer()->GetAnimator());
167  settings.SetPreemptionStrategy(
168      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
169  WindowOverviewState* target_state =
170      target_window->GetProperty(kWindowOverviewState);
171  SetWindowProgress(animate_window, target_state->progress);
172}
173
174// Always returns the same target.
175class StaticWindowTargeter : public aura::WindowTargeter {
176 public:
177  explicit StaticWindowTargeter(aura::Window* target) : target_(target) {}
178  virtual ~StaticWindowTargeter() {}
179
180 private:
181  // aura::WindowTargeter:
182  virtual ui::EventTarget* FindTargetForEvent(ui::EventTarget* root,
183                                              ui::Event* event) OVERRIDE {
184    return target_;
185  }
186
187  virtual ui::EventTarget* FindTargetForLocatedEvent(
188      ui::EventTarget* root,
189      ui::LocatedEvent* event) OVERRIDE {
190    return target_;
191  }
192
193  aura::Window* target_;
194  DISALLOW_COPY_AND_ASSIGN(StaticWindowTargeter);
195};
196
197class WindowOverviewModeImpl : public WindowOverviewMode,
198                               public ui::EventHandler,
199                               public ui::CompositorAnimationObserver,
200                               public WindowListProviderObserver {
201 public:
202  WindowOverviewModeImpl(aura::Window* container,
203                         WindowListProvider* window_list_provider,
204                         SplitViewController* split_view_controller,
205                         WindowOverviewModeDelegate* delegate)
206      : container_(container),
207        window_list_provider_(window_list_provider),
208        split_view_controller_(split_view_controller),
209        delegate_(delegate),
210        scoped_targeter_(new aura::ScopedWindowTargeter(
211            container,
212            scoped_ptr<ui::EventTargeter>(
213                new StaticWindowTargeter(container)))),
214        dragged_window_(NULL) {
215    CHECK(delegate_);
216    container_->set_target_handler(this);
217
218    // Prepare the desired transforms for all the windows, and set the initial
219    // state on the windows.
220    ComputeTerminalStatesForAllWindows();
221    SetInitialWindowStates();
222
223    window_list_provider_->AddObserver(this);
224  }
225
226  virtual ~WindowOverviewModeImpl() {
227    window_list_provider_->RemoveObserver(this);
228    container_->set_target_handler(container_->delegate());
229    RemoveAnimationObserver();
230    const aura::Window::Windows& windows =
231        window_list_provider_->GetWindowList();
232    if (windows.empty())
233      return;
234    std::for_each(windows.begin(),
235                  windows.end(),
236                  std::bind2nd(std::ptr_fun(&RestoreWindowState),
237                               split_view_controller_));
238  }
239
240 private:
241  // Computes the transforms for all windows in both the topmost and bottom-most
242  // positions. The transforms are set in the |kWindowOverviewState| property of
243  // the windows.
244  void ComputeTerminalStatesForAllWindows() {
245    size_t index = 0;
246
247    const aura::Window::Windows& windows =
248        window_list_provider_->GetWindowList();
249    for (aura::Window::Windows::const_reverse_iterator iter = windows.rbegin();
250         iter != windows.rend();
251         ++iter, ++index) {
252      aura::Window* window = (*iter);
253      wm::SetShadowType(window, wm::SHADOW_TYPE_RECTANGULAR_ALWAYS_ACTIVE);
254
255      WindowOverviewState* state = new WindowOverviewState;
256      window->SetProperty(kWindowOverviewState, state);
257      if (split_view_controller_->IsSplitViewModeActive() &&
258          (window == split_view_controller_->left_window() ||
259           window == split_view_controller_->right_window())) {
260        // Do not let the left/right windows be scrolled.
261        gfx::Transform transform =
262            GetTransformForSplitWindow(window, kOverviewDefaultScale);
263        state->max_y = state->min_y = transform.To2dTranslation().y();
264        state->split = true;
265        --index;
266        continue;
267      }
268      state->split = false;
269      UpdateTerminalStateForWindowAtIndex(window, index, windows.size());
270    }
271  }
272
273  // Computes the terminal states (i.e. the transforms for the top-most and
274  // bottom-most position in the stack) for |window|. |window_count| is the
275  // number of windows in the stack, and |index| is the position of the window
276  // in the stack (0 being the front-most window).
277  void UpdateTerminalStateForWindowAtIndex(aura::Window* window,
278                                           size_t index,
279                                           size_t window_count) {
280    const int kGapBetweenWindowsBottom = 10;
281    const int kGapBetweenWindowsTop = 5;
282
283    int top = (window_count - index - 1) * kGapBetweenWindowsTop;
284    int bottom = GetScrollableHeight() - (index * kGapBetweenWindowsBottom);
285
286    WindowOverviewState* state = window->GetProperty(kWindowOverviewState);
287    CHECK(state);
288    if (state->split)
289      return;
290    state->min_y = top;
291    state->max_y = bottom - window->bounds().y();
292    state->progress = 0.f;
293  }
294
295  // Sets the initial position for the windows for the overview mode.
296  void SetInitialWindowStates() {
297    // The initial overview state of the topmost three windows.
298    const float kInitialProgress[] = { 0.5f, 0.05f, 0.01f };
299    size_t index = 0;
300    const aura::Window::Windows& windows =
301        window_list_provider_->GetWindowList();
302    for (aura::Window::Windows::const_reverse_iterator iter = windows.rbegin();
303         iter != windows.rend();
304         ++iter) {
305      float progress = 0.f;
306      aura::Window* window = *iter;
307      if (split_view_controller_->IsSplitViewModeActive() &&
308          (window == split_view_controller_->left_window() ||
309           window == split_view_controller_->right_window())) {
310        progress = 1;
311      } else {
312        if (index < arraysize(kInitialProgress))
313          progress = kInitialProgress[index];
314        ++index;
315      }
316
317      scoped_refptr<ui::LayerAnimator> animator =
318          window->layer()->GetAnimator();
319
320      // Unset any in-progress animation.
321      animator->AbortAllAnimations();
322      window->Show();
323      window->SetTransform(gfx::Transform());
324      // Setup the animation.
325      {
326        ui::ScopedLayerAnimationSettings settings(animator);
327        settings.SetPreemptionStrategy(
328            ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
329        settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(250));
330        SetWindowProgress(window, progress);
331      }
332    }
333  }
334
335  aura::Window* SelectWindowAt(ui::LocatedEvent* event) {
336    CHECK_EQ(container_, event->target());
337    // Find the old targeter to find the target of the event.
338    ui::EventTarget* window = container_;
339    ui::EventTargeter* targeter = scoped_targeter_->old_targeter();
340    while (!targeter && window->GetParentTarget()) {
341      window = window->GetParentTarget();
342      targeter = window->GetEventTargeter();
343    }
344    if (!targeter)
345      return NULL;
346    aura::Window* target = static_cast<aura::Window*>(
347        targeter->FindTargetForLocatedEvent(container_, event));
348    while (target && target->parent() != container_)
349      target = target->parent();
350    return target;
351  }
352
353  // Scroll the window list by |delta_y| amount. |delta_y| is negative when
354  // scrolling up; and positive when scrolling down.
355  void DoScroll(float delta_y) {
356    const float kEpsilon = 1e-3f;
357    float delta_y_p = std::abs(delta_y) / GetScrollableHeight();
358    const aura::Window::Windows& windows =
359        window_list_provider_->GetWindowList();
360    if (delta_y < 0) {
361      // Scroll up. Start with the top-most (i.e. behind-most in terms of
362      // z-index) window, and try to scroll them up.
363      for (aura::Window::Windows::const_iterator iter = windows.begin();
364           delta_y_p > kEpsilon && iter != windows.end();
365           ++iter) {
366        aura::Window* window = (*iter);
367        WindowOverviewState* state = window->GetProperty(kWindowOverviewState);
368        if (state->progress > kEpsilon) {
369          // It is possible to scroll |window| up. Scroll it up, and update
370          // |delta_y_p| for the next window.
371          float apply = delta_y_p * state->progress;
372          SetWindowProgress(window, std::max(0.f, state->progress - apply * 3));
373          delta_y_p -= apply;
374        }
375      }
376    } else {
377      // Scroll down. Start with the bottom-most (i.e. front-most in terms of
378      // z-index) window, and try to scroll them down.
379      aura::Window::Windows::const_reverse_iterator iter;
380      for (iter = windows.rbegin();
381           delta_y_p > kEpsilon && iter != windows.rend();
382           ++iter) {
383        aura::Window* window = (*iter);
384        WindowOverviewState* state = window->GetProperty(kWindowOverviewState);
385        if (1.f - state->progress > kEpsilon) {
386          // It is possible to scroll |window| down. Scroll it down, and update
387          // |delta_y_p| for the next window.
388          SetWindowProgress(window, std::min(1.f, state->progress + delta_y_p));
389          delta_y_p /= 2.f;
390        }
391      }
392    }
393  }
394
395  int GetScrollableHeight() const {
396    const float kScrollableFraction = 0.85f;
397    const float kScrollableFractionInSplit = 0.5f;
398    const float fraction = split_view_controller_->IsSplitViewModeActive()
399                               ? kScrollableFractionInSplit
400                               : kScrollableFraction;
401    return container_->bounds().height() * fraction;
402  }
403
404  void CreateFlingerFor(const ui::GestureEvent& event) {
405    gfx::Vector2dF velocity(event.details().velocity_x(),
406                            event.details().velocity_y());
407    fling_.reset(new ui::FlingCurve(velocity, gfx::FrameTime::Now()));
408  }
409
410  void AddAnimationObserver() {
411    ui::Compositor* compositor = container_->GetHost()->compositor();
412    if (!compositor->HasAnimationObserver(this))
413      compositor->AddAnimationObserver(this);
414  }
415
416  void RemoveAnimationObserver() {
417    ui::Compositor* compositor = container_->GetHost()->compositor();
418    if (compositor->HasAnimationObserver(this))
419      compositor->RemoveAnimationObserver(this);
420  }
421
422  aura::Window* GetSplitWindowDropTarget(const ui::GestureEvent& event) const {
423    if (!split_view_controller_->IsSplitViewModeActive())
424      return NULL;
425    CHECK(dragged_window_);
426    CHECK_NE(split_view_controller_->left_window(), dragged_window_);
427    CHECK_NE(split_view_controller_->right_window(), dragged_window_);
428    aura::Window* window = split_view_controller_->left_window();
429    if (GetTransformedBounds(window).Contains(event.location()))
430      return window;
431    window = split_view_controller_->right_window();
432    if (GetTransformedBounds(window).Contains(event.location()))
433      return window;
434    return NULL;
435  }
436
437  void DragWindow(const ui::GestureEvent& event) {
438    CHECK(dragged_window_);
439    CHECK_EQ(ui::ET_GESTURE_SCROLL_UPDATE, event.type());
440    CHECK(overview_toolbar_);
441    gfx::Vector2dF dragged_distance =
442        dragged_start_location_ - event.location();
443    WindowOverviewState* dragged_state =
444        dragged_window_->GetProperty(kWindowOverviewState);
445    CHECK(dragged_state);
446    gfx::Transform transform =
447        GetTransformForState(dragged_window_, dragged_state);
448    transform.Translate(-dragged_distance.x(), 0);
449    dragged_window_->SetTransform(transform);
450
451    // Update the toolbar.
452    const int kMinDistanceForActionButtons = 20;
453    if (fabs(dragged_distance.x()) > kMinDistanceForActionButtons)
454      overview_toolbar_->ShowActionButtons();
455    else
456      overview_toolbar_->HideActionButtons();
457
458    // See if the touch-point is above one of the action-buttons.
459    OverviewToolbar::ActionType new_action =
460        overview_toolbar_->GetHighlightAction(event);
461
462    // If the touch-point is not above any of the action buttons, then highlight
463    // the close-button by default, if the user has dragged enough to close the
464    // window.
465    if (new_action == OverviewToolbar::ACTION_TYPE_NONE) {
466      if (fabs(dragged_distance.x()) > kMinDistanceForDismissal)
467        new_action = OverviewToolbar::ACTION_TYPE_CLOSE;
468      else
469        new_action = OverviewToolbar::ACTION_TYPE_NONE;
470    }
471    OverviewToolbar::ActionType previous_action =
472        overview_toolbar_->current_action();
473    overview_toolbar_->SetHighlightAction(new_action);
474
475    aura::Window* split_drop = GetSplitWindowDropTarget(event);
476
477    // If the user has selected to get into split-view mode, then show the
478    // window with full opacity. Otherwise, fade it out as it closes. Animate
479    // the opacity if transitioning to/from the split-view button.
480    bool animate_opacity =
481        (new_action != previous_action) &&
482        ((new_action == OverviewToolbar::ACTION_TYPE_SPLIT) ||
483         (previous_action == OverviewToolbar::ACTION_TYPE_SPLIT));
484    float ratio = std::min(
485        1.f, std::abs(dragged_distance.x()) / kMinDistanceForDismissal);
486    float opacity =
487        (new_action == OverviewToolbar::ACTION_TYPE_SPLIT || split_drop)
488            ? 1
489            : gfx::Tween::FloatValueBetween(ratio, kMaxOpacity, kMinOpacity);
490    if (animate_opacity) {
491      ui::ScopedLayerAnimationSettings settings(
492          dragged_window_->layer()->GetAnimator());
493      dragged_window_->layer()->SetOpacity(opacity);
494    } else {
495      dragged_window_->layer()->SetOpacity(opacity);
496    }
497
498    if (split_view_controller_->IsSplitViewModeActive()) {
499      float scale = kOverviewDefaultScale;
500      if (split_drop == split_view_controller_->left_window())
501        scale = kMaxScaleForSplitTarget;
502      TransformSplitWindowScale(split_view_controller_->left_window(), scale);
503
504      scale = kOverviewDefaultScale;
505      if (split_drop == split_view_controller_->right_window())
506        scale = kMaxScaleForSplitTarget;
507      TransformSplitWindowScale(split_view_controller_->right_window(), scale);
508    }
509  }
510
511  bool ShouldCloseDragWindow(const ui::GestureEvent& event) const {
512    gfx::Vector2dF dragged_distance =
513        dragged_start_location_ - event.location();
514    if (event.type() == ui::ET_GESTURE_SCROLL_END)
515      return std::abs(dragged_distance.x()) >= kMinDistanceForDismissal;
516    CHECK_EQ(ui::ET_SCROLL_FLING_START, event.type());
517    const bool dragging_towards_right = dragged_distance.x() < 0;
518    const bool swipe_towards_right = event.details().velocity_x() > 0;
519    if (dragging_towards_right != swipe_towards_right)
520      return false;
521    const float kMinVelocityForDismissal = 500.f;
522    return std::abs(event.details().velocity_x()) > kMinVelocityForDismissal;
523  }
524
525  void CloseDragWindow(const ui::GestureEvent& gesture) {
526    // Animate |dragged_window_| offscreen first, then destroy it.
527    {
528      wm::ScopedHidingAnimationSettings settings(dragged_window_);
529      settings.layer_animation_settings()->SetPreemptionStrategy(
530          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
531
532      WindowOverviewState* dragged_state =
533          dragged_window_->GetProperty(kWindowOverviewState);
534      CHECK(dragged_state);
535      gfx::Transform transform = dragged_window_->layer()->transform();
536      gfx::RectF transformed_bounds = dragged_window_->bounds();
537      transform.TransformRect(&transformed_bounds);
538      float transform_x = 0.f;
539      if (gesture.location().x() > dragged_start_location_.x())
540        transform_x = container_->bounds().right() - transformed_bounds.x();
541      else
542        transform_x = -(transformed_bounds.x() + transformed_bounds.width());
543      transform.Translate(transform_x / kOverviewDefaultScale, 0);
544      dragged_window_->SetTransform(transform);
545      dragged_window_->layer()->SetOpacity(kMinOpacity);
546    }
547    delete dragged_window_;
548    dragged_window_ = NULL;
549  }
550
551  void RestoreDragWindow() {
552    CHECK(dragged_window_);
553    WindowOverviewState* dragged_state =
554        dragged_window_->GetProperty(kWindowOverviewState);
555    CHECK(dragged_state);
556
557    ui::ScopedLayerAnimationSettings settings(
558        dragged_window_->layer()->GetAnimator());
559    settings.SetPreemptionStrategy(
560        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
561    dragged_window_->SetTransform(
562        GetTransformForState(dragged_window_, dragged_state));
563    dragged_window_->layer()->SetOpacity(1.f);
564    dragged_window_ = NULL;
565  }
566
567  void EndDragWindow(const ui::GestureEvent& gesture) {
568    CHECK(dragged_window_);
569    CHECK(overview_toolbar_);
570    OverviewToolbar::ActionType action = overview_toolbar_->current_action();
571    overview_toolbar_.reset();
572    if (action == OverviewToolbar::ACTION_TYPE_SPLIT) {
573      delegate_->OnSelectSplitViewWindow(NULL,
574                                         dragged_window_,
575                                         dragged_window_);
576      return;
577    }
578
579    // If the window is dropped on one of the left/right windows in split-mode,
580    // then switch that window.
581    aura::Window* split_drop = GetSplitWindowDropTarget(gesture);
582    if (split_drop) {
583      aura::Window* left = split_view_controller_->left_window();
584      aura::Window* right = split_view_controller_->right_window();
585      if (left == split_drop)
586        left = dragged_window_;
587      else
588        right = dragged_window_;
589      delegate_->OnSelectSplitViewWindow(left, right, dragged_window_);
590      return;
591    }
592
593    if (ShouldCloseDragWindow(gesture))
594      CloseDragWindow(gesture);
595    else
596      RestoreDragWindow();
597  }
598
599  void SelectWindow(aura::Window* window) {
600    if (!split_view_controller_->IsSplitViewModeActive()) {
601      delegate_->OnSelectWindow(window);
602    } else {
603      // If the selected window is one of the left/right windows, then keep the
604      // current state.
605      if (window == split_view_controller_->left_window() ||
606          window == split_view_controller_->right_window()) {
607        delegate_->OnSelectSplitViewWindow(
608            split_view_controller_->left_window(),
609            split_view_controller_->right_window(),
610            window);
611      } else {
612        delegate_->OnSelectWindow(window);
613      }
614    }
615  }
616
617  // ui::EventHandler:
618  virtual void OnMouseEvent(ui::MouseEvent* mouse) OVERRIDE {
619    if (mouse->type() == ui::ET_MOUSE_PRESSED) {
620      aura::Window* select = SelectWindowAt(mouse);
621      if (select) {
622        mouse->SetHandled();
623        SelectWindow(select);
624      }
625    } else if (mouse->type() == ui::ET_MOUSEWHEEL) {
626      DoScroll(static_cast<ui::MouseWheelEvent*>(mouse)->y_offset());
627    }
628  }
629
630  virtual void OnScrollEvent(ui::ScrollEvent* scroll) OVERRIDE {
631    if (scroll->type() == ui::ET_SCROLL)
632      DoScroll(scroll->y_offset());
633  }
634
635  virtual void OnGestureEvent(ui::GestureEvent* gesture) OVERRIDE {
636    if (gesture->type() == ui::ET_GESTURE_TAP) {
637      aura::Window* select = SelectWindowAt(gesture);
638      if (select) {
639        gesture->SetHandled();
640        SelectWindow(select);
641      }
642    } else if (gesture->type() == ui::ET_GESTURE_SCROLL_BEGIN) {
643      if (std::abs(gesture->details().scroll_x_hint()) >
644          std::abs(gesture->details().scroll_y_hint()) * 2) {
645        dragged_start_location_ = gesture->location();
646        dragged_window_ = SelectWindowAt(gesture);
647        if (split_view_controller_->IsSplitViewModeActive() &&
648            (dragged_window_ == split_view_controller_->left_window() ||
649             dragged_window_ == split_view_controller_->right_window())) {
650          // TODO(sad): Allow closing the left/right window. Closing one of
651          // these windows will terminate the split-view mode. Until then, do
652          // not allow closing these (since otherwise it gets into an undefined
653          // state).
654          dragged_window_ = NULL;
655        }
656
657        if (dragged_window_) {
658          // Show the toolbar (for closing a window, or going into split-view
659          // mode). If already in split-view mode, then do not show the 'Split'
660          // option.
661          overview_toolbar_.reset(new OverviewToolbar(container_));
662          if (!split_view_controller_->CanActivateSplitViewMode()) {
663            overview_toolbar_->DisableAction(
664                OverviewToolbar::ACTION_TYPE_SPLIT);
665          }
666        }
667      }
668    } else if (gesture->type() == ui::ET_GESTURE_SCROLL_UPDATE) {
669      if (dragged_window_)
670        DragWindow(*gesture);
671      else
672        DoScroll(gesture->details().scroll_y());
673      gesture->SetHandled();
674    } else if (gesture->type() == ui::ET_GESTURE_SCROLL_END) {
675      if (dragged_window_)
676        EndDragWindow(*gesture);
677      gesture->SetHandled();
678    } else if (gesture->type() == ui::ET_SCROLL_FLING_START) {
679      if (dragged_window_) {
680        EndDragWindow(*gesture);
681      } else {
682        CreateFlingerFor(*gesture);
683        AddAnimationObserver();
684      }
685      gesture->SetHandled();
686    } else if (gesture->type() == ui::ET_GESTURE_TAP_DOWN) {
687      if (fling_) {
688        fling_.reset();
689        RemoveAnimationObserver();
690        gesture->SetHandled();
691      }
692      dragged_window_ = NULL;
693    }
694  }
695
696  // ui::CompositorAnimationObserver:
697  virtual void OnAnimationStep(base::TimeTicks timestamp) OVERRIDE {
698    CHECK(fling_);
699    if (fling_->start_timestamp() > timestamp)
700      return;
701    gfx::Vector2dF scroll = fling_->GetScrollAmountAtTime(timestamp);
702    if (scroll.IsZero()) {
703      fling_.reset();
704      RemoveAnimationObserver();
705    } else {
706      DoScroll(scroll.y());
707    }
708  }
709
710  // WindowListProviderObserver:
711  virtual void OnWindowStackingChanged() OVERRIDE {
712    // Recompute the states of all windows. There isn't enough information at
713    // this point to do anything more clever.
714    ComputeTerminalStatesForAllWindows();
715    SetInitialWindowStates();
716  }
717
718  virtual void OnWindowRemoved(aura::Window* removed_window,
719                               int index) OVERRIDE {
720    const aura::Window::Windows& windows =
721        window_list_provider_->GetWindowList();
722    if (windows.empty())
723      return;
724    CHECK_LE(index, static_cast<int>(windows.size()));
725    if (index == 0) {
726      // The back-most window has been removed. Move all the remaining windows
727      // one step backwards.
728      for (int i = windows.size() - 1; i > 0; --i) {
729        UpdateTerminalStateForWindowAtIndex(
730            windows[i], windows.size() - 1 - i, windows.size());
731        AnimateWindowTo(windows[i], windows[i - 1]);
732      }
733      UpdateTerminalStateForWindowAtIndex(windows.front(),
734                                          windows.size() - 1,
735                                          windows.size());
736      AnimateWindowTo(windows.front(), removed_window);
737    } else {
738      // Move all windows behind the removed window one step forwards.
739      for (int i = 0; i < index - 1; ++i) {
740        UpdateTerminalStateForWindowAtIndex(windows[i], windows.size() - 1 - i,
741                                            windows.size());
742        AnimateWindowTo(windows[i], windows[i + 1]);
743      }
744      UpdateTerminalStateForWindowAtIndex(windows[index - 1],
745                                          windows.size() - index,
746                                          windows.size());
747      AnimateWindowTo(windows[index - 1], removed_window);
748    }
749  }
750
751  const int kMinDistanceForDismissal = 300;
752  const float kMaxOpacity = 1.0f;
753  const float kMinOpacity = 0.2f;
754  const float kMaxScaleForSplitTarget = 0.9f;
755
756  aura::Window* container_;
757  // Provider of the stack of windows to show in the overview mode. Not owned.
758  WindowListProvider* window_list_provider_;
759  SplitViewController* split_view_controller_;
760
761  WindowOverviewModeDelegate* delegate_;
762  scoped_ptr<aura::ScopedWindowTargeter> scoped_targeter_;
763  scoped_ptr<ui::FlingCurve> fling_;
764
765  aura::Window* dragged_window_;
766  gfx::Point dragged_start_location_;
767  scoped_ptr<OverviewToolbar> overview_toolbar_;
768
769  DISALLOW_COPY_AND_ASSIGN(WindowOverviewModeImpl);
770};
771
772}  // namespace
773
774// static
775scoped_ptr<WindowOverviewMode> WindowOverviewMode::Create(
776    aura::Window* container,
777    WindowListProvider* window_list_provider,
778    SplitViewController* split_view_controller,
779    WindowOverviewModeDelegate* delegate) {
780  return scoped_ptr<WindowOverviewMode>(
781      new WindowOverviewModeImpl(container, window_list_provider,
782                                 split_view_controller, delegate));
783}
784
785}  // namespace athena
786