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/title_drag_controller.h"
6
7#include "base/bind.h"
8#include "ui/aura/window.h"
9#include "ui/aura/window_delegate.h"
10#include "ui/base/hit_test.h"
11#include "ui/compositor/closure_animation_observer.h"
12#include "ui/compositor/layer.h"
13#include "ui/compositor/scoped_layer_animation_settings.h"
14#include "ui/wm/core/shadow.h"
15#include "ui/wm/core/window_util.h"
16
17namespace {
18
19// The minimum amount to drag to confirm a window switch at the end of the
20// non-fling gesture.
21const int kMinDragDistanceForSwitch = 300;
22// The minimum velocity to confirm a window switch for a fling (only applicable
23// if the amount dragged was not sufficient, i.e. smaller than
24// kMinDragDistanceForSwitch).
25const int kMinDragVelocityForSwitch = 5000;
26
27}
28
29namespace athena {
30
31TitleDragController::TitleDragController(aura::Window* container,
32                                         TitleDragControllerDelegate* delegate)
33    : container_(container),
34      delegate_(delegate),
35      weak_ptr_(this) {
36  CHECK(container_);
37  CHECK(delegate_);
38  container_->AddPreTargetHandler(this);
39}
40
41TitleDragController::~TitleDragController() {
42  container_->RemovePreTargetHandler(this);
43}
44
45void TitleDragController::EndTransition(aura::Window* window, bool complete) {
46  ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
47  settings.SetPreemptionStrategy(
48      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
49  settings.AddObserver(new ui::ClosureAnimationObserver(
50      base::Bind(&TitleDragController::OnTransitionEnd,
51                 weak_ptr_.GetWeakPtr(),
52                 window,
53                 complete)));
54  gfx::Transform transform;
55  transform.Translate(0, complete ? window->bounds().height() : 0);
56  window->SetTransform(transform);
57}
58
59void TitleDragController::OnTransitionEnd(aura::Window* window, bool complete) {
60  weak_ptr_.InvalidateWeakPtrs();
61  if (!tracker_.Contains(window))
62    window = NULL;
63  shadow_.reset();
64  if (window) {
65    window->SetTransform(gfx::Transform());
66    tracker_.Remove(window);
67  }
68  if (complete && window && wm::IsActiveWindow(window))
69    delegate_->OnTitleDragCompleted(window);
70  else
71    delegate_->OnTitleDragCanceled(window);
72}
73
74void TitleDragController::OnGestureEvent(ui::GestureEvent* gesture) {
75  // Do not process any gesture events if an animation is still in progress from
76  // a previous drag.
77  if (weak_ptr_.HasWeakPtrs())
78    return;
79
80  if (gesture->type() == ui::ET_GESTURE_TAP_DOWN) {
81    // It is possible to start a gesture sequence on a second window while a
82    // drag is already in progress (e.g. the user starts interacting with the
83    // window that is being revealed by the title-drag). Ignore these gesture
84    // sequences.
85    if (!tracker_.windows().empty())
86      return;
87    aura::Window* window = static_cast<aura::Window*>(gesture->target());
88    if (!window || !window->delegate())
89      return;
90    int component =
91        window->delegate()->GetNonClientComponent(gesture->location());
92    if (component != HTCAPTION)
93      return;
94    if (!delegate_->GetWindowBehind(window))
95      return;
96    tracker_.Add(window);
97    drag_start_location_ = gesture->root_location();
98    return;
99  }
100
101  // If this gesture is for a different window, then ignore.
102  aura::Window* window = static_cast<aura::Window*>(gesture->target());
103  if (!tracker_.Contains(window))
104    return;
105
106  if (gesture->type() == ui::ET_GESTURE_SCROLL_BEGIN) {
107    delegate_->OnTitleDragStarted(window);
108    shadow_.reset(new wm::Shadow());
109    shadow_->Init(wm::Shadow::STYLE_ACTIVE);
110    shadow_->SetContentBounds(gfx::Rect(window->bounds().size()));
111    window->layer()->Add(shadow_->layer());
112    gesture->SetHandled();
113    return;
114  }
115
116  if (gesture->type() == ui::ET_GESTURE_SCROLL_UPDATE) {
117    gfx::Vector2dF distance = gesture->root_location() - drag_start_location_;
118    gfx::Transform transform;
119    transform.Translate(0, std::max(0.f, distance.y()));
120    window->SetTransform(transform);
121    gesture->SetHandled();
122    return;
123  }
124
125  if (gesture->type() == ui::ET_GESTURE_SCROLL_END) {
126    gfx::Vector2dF distance = gesture->root_location() - drag_start_location_;
127    EndTransition(window, distance.y() >= kMinDragDistanceForSwitch);
128    gesture->SetHandled();
129    return;
130  }
131
132  if (gesture->type() == ui::ET_SCROLL_FLING_START) {
133    gfx::Vector2dF distance = gesture->root_location() - drag_start_location_;
134    bool swipe_downwards = gesture->details().velocity_y() > 0;
135    EndTransition(
136        window,
137        swipe_downwards &&
138            (distance.y() >= kMinDragDistanceForSwitch ||
139             gesture->details().velocity_y() >= kMinDragVelocityForSwitch));
140    gesture->SetHandled();
141  }
142}
143
144}  // namespace athena
145