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