window_slider.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
1// Copyright (c) 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.
4
5#include "content/browser/web_contents/aura/window_slider.h"
6
7#include <algorithm>
8
9#include "base/bind.h"
10#include "base/callback.h"
11#include "content/browser/web_contents/aura/shadow_layer_delegate.h"
12#include "content/public/browser/overscroll_configuration.h"
13#include "ui/aura/window.h"
14#include "ui/compositor/layer_animation_observer.h"
15#include "ui/compositor/scoped_layer_animation_settings.h"
16#include "ui/events/event.h"
17
18namespace content {
19
20namespace {
21
22// An animation observer that runs a callback at the end of the animation, and
23// destroys itself.
24class CallbackAnimationObserver : public ui::ImplicitAnimationObserver {
25 public:
26  CallbackAnimationObserver(const base::Closure& closure)
27      : closure_(closure) {
28  }
29
30  virtual ~CallbackAnimationObserver() {}
31
32 private:
33  // Overridden from ui::ImplicitAnimationObserver:
34  virtual void OnImplicitAnimationsCompleted() OVERRIDE {
35    if (!closure_.is_null())
36      closure_.Run();
37    base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
38  }
39
40  const base::Closure closure_;
41
42  DISALLOW_COPY_AND_ASSIGN(CallbackAnimationObserver);
43};
44
45}  // namespace
46
47WindowSlider::WindowSlider(Delegate* delegate,
48                           aura::Window* event_window,
49                           aura::Window* owner)
50    : delegate_(delegate),
51      event_window_(event_window),
52      owner_(owner),
53      active_animator_(NULL),
54      delta_x_(0.f),
55      weak_factory_(this),
56      active_start_threshold_(0.f),
57      start_threshold_touchscreen_(content::GetOverscrollConfig(
58          content::OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHSCREEN)),
59      start_threshold_touchpad_(content::GetOverscrollConfig(
60          content::OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHPAD)),
61      complete_threshold_(content::GetOverscrollConfig(
62          content::OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE)) {
63  event_window_->AddPreTargetHandler(this);
64
65  event_window_->AddObserver(this);
66  owner_->AddObserver(this);
67}
68
69WindowSlider::~WindowSlider() {
70  if (event_window_) {
71    event_window_->RemovePreTargetHandler(this);
72    event_window_->RemoveObserver(this);
73  }
74  if (owner_)
75    owner_->RemoveObserver(this);
76  delegate_->OnWindowSliderDestroyed();
77}
78
79void WindowSlider::ChangeOwner(aura::Window* new_owner) {
80  if (owner_)
81    owner_->RemoveObserver(this);
82  owner_ = new_owner;
83  if (owner_) {
84    owner_->AddObserver(this);
85    UpdateForScroll(0.f, 0.f);
86  }
87}
88
89bool WindowSlider::IsSlideInProgress() const {
90  // if active_start_threshold_ is 0, it means that sliding hasn't been started
91  return active_start_threshold_ != 0 && (slider_.get() || active_animator_);
92}
93
94void WindowSlider::SetupSliderLayer() {
95  ui::Layer* parent = owner_->layer()->parent();
96  parent->Add(slider_.get());
97  if (delta_x_ < 0)
98    parent->StackAbove(slider_.get(), owner_->layer());
99  else
100    parent->StackBelow(slider_.get(), owner_->layer());
101  slider_->SetBounds(owner_->layer()->bounds());
102  slider_->SetVisible(true);
103}
104
105void WindowSlider::UpdateForScroll(float x_offset, float y_offset) {
106  if (active_animator_) {
107    // If there is an active animation, complete it before processing the scroll
108    // so that the callbacks that are invoked on the Delegate are consistent.
109    // Completing the animation may destroy WindowSlider through the animation
110    // callback, so we can't continue processing the scroll event here.
111    delta_x_ += x_offset;
112    CompleteActiveAnimations();
113    return;
114  }
115
116  float old_delta = delta_x_;
117  delta_x_ += x_offset;
118  if (fabs(delta_x_) < active_start_threshold_ && !slider_.get())
119    return;
120
121  if ((old_delta < 0 && delta_x_ > 0) ||
122      (old_delta > 0 && delta_x_ < 0)) {
123    slider_.reset();
124    shadow_.reset();
125  }
126
127  float translate = 0.f;
128  ui::Layer* translate_layer = NULL;
129
130  if (!slider_.get()) {
131    slider_.reset(delta_x_ < 0 ? delegate_->CreateFrontLayer() :
132                                 delegate_->CreateBackLayer());
133    if (!slider_.get())
134      return;
135    SetupSliderLayer();
136  }
137
138  if (delta_x_ <= -active_start_threshold_) {
139    translate = owner_->bounds().width() +
140        std::max(delta_x_ + active_start_threshold_,
141                 static_cast<float>(-owner_->bounds().width()));
142    translate_layer = slider_.get();
143  } else if (delta_x_ >= active_start_threshold_) {
144    translate = std::min(delta_x_ - active_start_threshold_,
145                         static_cast<float>(owner_->bounds().width()));
146    translate_layer = owner_->layer();
147  } else {
148    return;
149  }
150
151  if (!shadow_.get())
152    shadow_.reset(new ShadowLayerDelegate(translate_layer));
153
154  gfx::Transform transform;
155  transform.Translate(translate, 0);
156  translate_layer->SetTransform(transform);
157}
158
159void WindowSlider::CompleteOrResetSlide() {
160  if (!slider_.get())
161    return;
162
163  int width = owner_->bounds().width();
164  float ratio = (fabs(delta_x_) - active_start_threshold_) / width;
165  if (ratio < complete_threshold_) {
166    ResetSlide();
167    return;
168  }
169
170  ui::Layer* sliding = delta_x_ < 0 ? slider_.get() : owner_->layer();
171  active_animator_ = sliding->GetAnimator();
172
173  ui::ScopedLayerAnimationSettings settings(active_animator_);
174  settings.SetPreemptionStrategy(
175      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
176  settings.SetTweenType(gfx::Tween::EASE_OUT);
177  settings.AddObserver(new CallbackAnimationObserver(
178      base::Bind(&WindowSlider::SlideAnimationCompleted,
179                 weak_factory_.GetWeakPtr(),
180                 base::Passed(&slider_),
181                 base::Passed(&shadow_))));
182
183  gfx::Transform transform;
184  transform.Translate(delta_x_ < 0 ? 0 : width, 0);
185  delta_x_ = 0;
186  delegate_->OnWindowSlideCompleting();
187  sliding->SetTransform(transform);
188}
189
190void WindowSlider::CompleteActiveAnimations() {
191  if (active_animator_)
192    active_animator_->StopAnimating();
193}
194
195void WindowSlider::ResetSlide() {
196  if (!slider_.get())
197    return;
198
199  // Reset the state of the sliding layer.
200  if (slider_.get()) {
201    ui::Layer* translate_layer;
202    gfx::Transform transform;
203    if (delta_x_ < 0) {
204      translate_layer = slider_.get();
205      transform.Translate(translate_layer->bounds().width(), 0);
206    } else {
207      translate_layer = owner_->layer();
208    }
209
210    active_animator_ = translate_layer->GetAnimator();
211    ui::ScopedLayerAnimationSettings settings(active_animator_);
212    settings.SetPreemptionStrategy(
213        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
214    settings.SetTweenType(gfx::Tween::EASE_OUT);
215    settings.AddObserver(new CallbackAnimationObserver(
216        base::Bind(&WindowSlider::ResetSlideAnimationCompleted,
217                   weak_factory_.GetWeakPtr(),
218                   base::Passed(&slider_),
219                   base::Passed(&shadow_))));
220    translate_layer->SetTransform(transform);
221  }
222
223  delta_x_ = 0.f;
224}
225
226void WindowSlider::SlideAnimationCompleted(
227    scoped_ptr<ui::Layer> layer, scoped_ptr<ShadowLayerDelegate> shadow) {
228  active_animator_ = NULL;
229  shadow.reset();
230  delegate_->OnWindowSlideCompleted(layer.Pass());
231}
232
233void WindowSlider::ResetSlideAnimationCompleted(
234    scoped_ptr<ui::Layer> layer, scoped_ptr<ShadowLayerDelegate> shadow) {
235  active_animator_ = NULL;
236  shadow.reset();
237  layer.reset();
238  delegate_->OnWindowSlideAborted();
239}
240
241void WindowSlider::OnKeyEvent(ui::KeyEvent* event) {
242  ResetSlide();
243}
244
245void WindowSlider::OnMouseEvent(ui::MouseEvent* event) {
246  if (!(event->flags() & ui::EF_IS_SYNTHESIZED))
247    ResetSlide();
248}
249
250void WindowSlider::OnScrollEvent(ui::ScrollEvent* event) {
251  active_start_threshold_ = start_threshold_touchpad_;
252  if (event->type() == ui::ET_SCROLL)
253    UpdateForScroll(event->x_offset_ordinal(), event->y_offset_ordinal());
254  else if (event->type() == ui::ET_SCROLL_FLING_START)
255    CompleteOrResetSlide();
256  else
257    ResetSlide();
258  event->SetHandled();
259}
260
261void WindowSlider::OnGestureEvent(ui::GestureEvent* event) {
262  active_start_threshold_ = start_threshold_touchscreen_;
263  const ui::GestureEventDetails& details = event->details();
264  switch (event->type()) {
265    case ui::ET_GESTURE_SCROLL_BEGIN:
266      CompleteActiveAnimations();
267      break;
268
269    case ui::ET_GESTURE_SCROLL_UPDATE:
270      UpdateForScroll(details.scroll_x(), details.scroll_y());
271      break;
272
273    case ui::ET_GESTURE_SCROLL_END:
274      CompleteOrResetSlide();
275      break;
276
277    case ui::ET_SCROLL_FLING_START:
278      CompleteOrResetSlide();
279      break;
280
281    case ui::ET_GESTURE_PINCH_BEGIN:
282    case ui::ET_GESTURE_PINCH_UPDATE:
283    case ui::ET_GESTURE_PINCH_END:
284      ResetSlide();
285      break;
286
287    default:
288      break;
289  }
290
291  event->SetHandled();
292}
293
294void WindowSlider::OnWindowRemovingFromRootWindow(aura::Window* window,
295                                                  aura::Window* new_root) {
296  if (window == event_window_) {
297    window->RemoveObserver(this);
298    window->RemovePreTargetHandler(this);
299    event_window_ = NULL;
300  } else if (window == owner_) {
301    window->RemoveObserver(this);
302    owner_ = NULL;
303    delete this;
304  } else {
305    NOTREACHED();
306  }
307}
308
309}  // namespace content
310