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 "content/browser/web_contents/aura/gesture_nav_simple.h"
6
7#include "cc/layers/layer.h"
8#include "content/browser/frame_host/navigation_controller_impl.h"
9#include "content/browser/renderer_host/overscroll_controller.h"
10#include "content/browser/web_contents/web_contents_impl.h"
11#include "content/browser/web_contents/web_contents_view.h"
12#include "content/public/browser/browser_thread.h"
13#include "content/public/browser/overscroll_configuration.h"
14#include "content/public/common/content_client.h"
15#include "ui/aura/window.h"
16#include "ui/compositor/layer.h"
17#include "ui/compositor/layer_animation_observer.h"
18#include "ui/compositor/layer_delegate.h"
19#include "ui/compositor/scoped_layer_animation_settings.h"
20#include "ui/gfx/animation/tween.h"
21#include "ui/gfx/canvas.h"
22#include "ui/gfx/image/image.h"
23#include "ui/resources/grit/ui_resources.h"
24
25namespace content {
26
27namespace {
28
29const int kArrowHeight = 280;
30const int kArrowWidth = 140;
31const float kMinOpacity = 0.25f;
32
33bool ShouldNavigateForward(const NavigationController& controller,
34                           OverscrollMode mode) {
35  return mode == (base::i18n::IsRTL() ? OVERSCROLL_EAST : OVERSCROLL_WEST) &&
36         controller.CanGoForward();
37}
38
39bool ShouldNavigateBack(const NavigationController& controller,
40                        OverscrollMode mode) {
41  return mode == (base::i18n::IsRTL() ? OVERSCROLL_WEST : OVERSCROLL_EAST) &&
42         controller.CanGoBack();
43}
44
45// An animation observers that deletes itself and a pointer after the end of the
46// animation.
47template <class T>
48class DeleteAfterAnimation : public ui::ImplicitAnimationObserver {
49 public:
50  explicit DeleteAfterAnimation(scoped_ptr<T> object)
51      : object_(object.Pass()) {}
52
53 private:
54  friend class base::DeleteHelper<DeleteAfterAnimation<T> >;
55
56  virtual ~DeleteAfterAnimation() {}
57
58  // ui::ImplicitAnimationObserver:
59  virtual void OnImplicitAnimationsCompleted() OVERRIDE {
60    // Deleting an observer when a ScopedLayerAnimationSettings is iterating
61    // over them can cause a crash (which can happen during tests). So instead,
62    // schedule this observer to be deleted soon.
63    BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
64  }
65
66  scoped_ptr<T> object_;
67  DISALLOW_COPY_AND_ASSIGN(DeleteAfterAnimation);
68};
69
70}  // namespace
71
72// A layer delegate that paints the shield with the arrow in it.
73class ArrowLayerDelegate : public ui::LayerDelegate {
74 public:
75  explicit ArrowLayerDelegate(int resource_id)
76      : image_(GetContentClient()->GetNativeImageNamed(resource_id)),
77        left_arrow_(resource_id == IDR_BACK_ARROW) {
78    CHECK(!image_.IsEmpty());
79  }
80
81  virtual ~ArrowLayerDelegate() {}
82
83  bool left() const { return left_arrow_; }
84
85 private:
86  // ui::LayerDelegate:
87  virtual void OnPaintLayer(gfx::Canvas* canvas) OVERRIDE {
88    SkPaint paint;
89    paint.setColor(SkColorSetARGB(0xa0, 0, 0, 0));
90    paint.setStyle(SkPaint::kFill_Style);
91    paint.setAntiAlias(true);
92
93    canvas->DrawCircle(
94        gfx::Point(left_arrow_ ? 0 : kArrowWidth, kArrowHeight / 2),
95        kArrowWidth,
96        paint);
97    canvas->DrawImageInt(*image_.ToImageSkia(),
98                         left_arrow_ ? 0 : kArrowWidth - image_.Width(),
99                         (kArrowHeight - image_.Height()) / 2);
100  }
101
102  virtual void OnDelegatedFrameDamage(
103      const gfx::Rect& damage_rect_in_dip) OVERRIDE {}
104
105  virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE {}
106
107  virtual base::Closure PrepareForLayerBoundsChange() OVERRIDE {
108    return base::Closure();
109  }
110
111  const gfx::Image& image_;
112  const bool left_arrow_;
113
114  DISALLOW_COPY_AND_ASSIGN(ArrowLayerDelegate);
115};
116
117GestureNavSimple::GestureNavSimple(WebContentsImpl* web_contents)
118    : web_contents_(web_contents),
119      completion_threshold_(0.f) {}
120
121GestureNavSimple::~GestureNavSimple() {}
122
123void GestureNavSimple::ApplyEffectsAndDestroy(const gfx::Transform& transform,
124                                              float opacity) {
125  ui::Layer* layer = arrow_.get();
126  ui::ScopedLayerAnimationSettings settings(arrow_->GetAnimator());
127  settings.AddObserver(
128      new DeleteAfterAnimation<ArrowLayerDelegate>(arrow_delegate_.Pass()));
129  settings.AddObserver(new DeleteAfterAnimation<ui::Layer>(arrow_.Pass()));
130  settings.AddObserver(new DeleteAfterAnimation<ui::Layer>(clip_layer_.Pass()));
131  layer->SetTransform(transform);
132  layer->SetOpacity(opacity);
133}
134
135void GestureNavSimple::AbortGestureAnimation() {
136  if (!arrow_)
137    return;
138  gfx::Transform transform;
139  transform.Translate(arrow_delegate_->left() ? -kArrowWidth : kArrowWidth, 0);
140  ApplyEffectsAndDestroy(transform, kMinOpacity);
141}
142
143void GestureNavSimple::CompleteGestureAnimation() {
144  if (!arrow_)
145    return;
146  // Make sure the fade-out starts from the complete state.
147  ApplyEffectsForDelta(completion_threshold_);
148  ApplyEffectsAndDestroy(arrow_->transform(), 0.f);
149}
150
151bool GestureNavSimple::ApplyEffectsForDelta(float delta_x) {
152  if (!arrow_)
153    return false;
154  CHECK_GT(completion_threshold_, 0.f);
155  CHECK_GE(delta_x, 0.f);
156  double complete = std::min(1.f, delta_x / completion_threshold_);
157  float translate_x = gfx::Tween::FloatValueBetween(complete, -kArrowWidth, 0);
158  gfx::Transform transform;
159  transform.Translate(arrow_delegate_->left() ? translate_x : -translate_x,
160                      0.f);
161  arrow_->SetTransform(transform);
162  arrow_->SetOpacity(gfx::Tween::FloatValueBetween(complete, kMinOpacity, 1.f));
163  return true;
164}
165
166gfx::Rect GestureNavSimple::GetVisibleBounds() const {
167  return web_contents_->GetNativeView()->bounds();
168}
169
170bool GestureNavSimple::OnOverscrollUpdate(float delta_x, float delta_y) {
171  return ApplyEffectsForDelta(std::abs(delta_x) + 50.f);
172}
173
174void GestureNavSimple::OnOverscrollComplete(OverscrollMode overscroll_mode) {
175  CompleteGestureAnimation();
176
177  NavigationControllerImpl& controller = web_contents_->GetController();
178  if (ShouldNavigateForward(controller, overscroll_mode))
179    controller.GoForward();
180  else if (ShouldNavigateBack(controller, overscroll_mode))
181    controller.GoBack();
182}
183
184void GestureNavSimple::OnOverscrollModeChange(OverscrollMode old_mode,
185                                              OverscrollMode new_mode) {
186  NavigationControllerImpl& controller = web_contents_->GetController();
187  if (!ShouldNavigateForward(controller, new_mode) &&
188      !ShouldNavigateBack(controller, new_mode)) {
189    AbortGestureAnimation();
190    return;
191  }
192
193  arrow_.reset(new ui::Layer(ui::LAYER_TEXTURED));
194  // Note that RTL doesn't affect the arrow that should be displayed.
195  int resource_id = 0;
196  if (new_mode == OVERSCROLL_WEST)
197    resource_id = IDR_FORWARD_ARROW;
198  else if (new_mode == OVERSCROLL_EAST)
199    resource_id = IDR_BACK_ARROW;
200  else
201    NOTREACHED();
202
203  arrow_delegate_.reset(new ArrowLayerDelegate(resource_id));
204  arrow_->set_delegate(arrow_delegate_.get());
205  arrow_->SetFillsBoundsOpaquely(false);
206
207  aura::Window* window = web_contents_->GetNativeView();
208  const gfx::Rect& window_bounds = window->bounds();
209  completion_threshold_ = window_bounds.width() *
210      GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE);
211
212  // Align on the left or right edge.
213  int x = (resource_id == IDR_BACK_ARROW) ? 0 :
214      (window_bounds.width() - kArrowWidth);
215  // Align in the center vertically.
216  int y = std::max(0, (window_bounds.height() - kArrowHeight) / 2);
217  arrow_->SetBounds(gfx::Rect(x, y, kArrowWidth, kArrowHeight));
218  ApplyEffectsForDelta(0.f);
219
220  // Adding the arrow as a child of the content window is not sufficient,
221  // because it is possible for a new layer to be parented on top of the arrow
222  // layer (e.g. when the navigated-to page is displayed while the completion
223  // animation is in progress). So instead, a clip layer (that doesn't paint) is
224  // installed on top of the content window as its sibling, and the arrow layer
225  // is added to that clip layer.
226  clip_layer_.reset(new ui::Layer(ui::LAYER_NOT_DRAWN));
227  clip_layer_->SetBounds(window->layer()->bounds());
228  clip_layer_->SetMasksToBounds(true);
229  clip_layer_->Add(arrow_.get());
230
231  ui::Layer* parent = window->layer()->parent();
232  parent->Add(clip_layer_.get());
233  parent->StackAtTop(clip_layer_.get());
234}
235
236}  // namespace content
237