Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / browser / web_contents / aura / gesture_nav_simple.cc
blobc8569b92dc37d3496a825677114370a2a6d390ea
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.
5 #include "content/browser/web_contents/aura/gesture_nav_simple.h"
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/paint_recorder.h"
20 #include "ui/compositor/scoped_layer_animation_settings.h"
21 #include "ui/gfx/animation/tween.h"
22 #include "ui/gfx/canvas.h"
23 #include "ui/gfx/image/image.h"
24 #include "ui/resources/grit/ui_resources.h"
26 namespace content {
28 namespace {
30 const int kArrowHeight = 280;
31 const int kArrowWidth = 140;
32 const float kMinOpacity = 0.25f;
34 bool ShouldNavigateForward(const NavigationController& controller,
35 OverscrollMode mode) {
36 return mode == (base::i18n::IsRTL() ? OVERSCROLL_EAST : OVERSCROLL_WEST) &&
37 controller.CanGoForward();
40 bool ShouldNavigateBack(const NavigationController& controller,
41 OverscrollMode mode) {
42 return mode == (base::i18n::IsRTL() ? OVERSCROLL_WEST : OVERSCROLL_EAST) &&
43 controller.CanGoBack();
46 // An animation observers that deletes itself and a pointer after the end of the
47 // animation.
48 template <class T>
49 class DeleteAfterAnimation : public ui::ImplicitAnimationObserver {
50 public:
51 explicit DeleteAfterAnimation(scoped_ptr<T> object)
52 : object_(object.Pass()) {}
54 private:
55 friend class base::DeleteHelper<DeleteAfterAnimation<T> >;
57 ~DeleteAfterAnimation() override {}
59 // ui::ImplicitAnimationObserver:
60 void OnImplicitAnimationsCompleted() override {
61 // Deleting an observer when a ScopedLayerAnimationSettings is iterating
62 // over them can cause a crash (which can happen during tests). So instead,
63 // schedule this observer to be deleted soon.
64 BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
67 scoped_ptr<T> object_;
68 DISALLOW_COPY_AND_ASSIGN(DeleteAfterAnimation);
71 } // namespace
73 // A layer delegate that paints the shield with the arrow in it.
74 class ArrowLayerDelegate : public ui::LayerDelegate {
75 public:
76 explicit ArrowLayerDelegate(int resource_id)
77 : image_(GetContentClient()->GetNativeImageNamed(resource_id)),
78 left_arrow_(resource_id == IDR_BACK_ARROW) {
79 CHECK(!image_.IsEmpty());
82 ~ArrowLayerDelegate() override {}
84 bool left() const { return left_arrow_; }
86 private:
87 // ui::LayerDelegate:
88 void OnPaintLayer(const ui::PaintContext& context) override {
89 SkPaint paint;
90 paint.setColor(SkColorSetARGB(0xa0, 0, 0, 0));
91 paint.setStyle(SkPaint::kFill_Style);
92 paint.setAntiAlias(true);
94 // Set the recording size to be the size of the |arrow_| layer, and draw a
95 // half circle (the other half will be clipped), then an arrow image inside
96 // it.
97 ui::PaintRecorder recorder(context, gfx::Size(kArrowWidth, kArrowHeight));
98 recorder.canvas()->DrawCircle(
99 gfx::Point(left_arrow_ ? 0 : kArrowWidth, kArrowHeight / 2),
100 kArrowWidth, paint);
101 recorder.canvas()->DrawImageInt(
102 *image_.ToImageSkia(), left_arrow_ ? 0 : kArrowWidth - image_.Width(),
103 (kArrowHeight - image_.Height()) / 2);
106 void OnDelegatedFrameDamage(const gfx::Rect& damage_rect_in_dip) override {}
108 void OnDeviceScaleFactorChanged(float device_scale_factor) override {}
110 base::Closure PrepareForLayerBoundsChange() override {
111 return base::Closure();
114 const gfx::Image& image_;
115 const bool left_arrow_;
117 DISALLOW_COPY_AND_ASSIGN(ArrowLayerDelegate);
120 GestureNavSimple::GestureNavSimple(WebContentsImpl* web_contents)
121 : web_contents_(web_contents),
122 completion_threshold_(0.f) {}
124 GestureNavSimple::~GestureNavSimple() {}
126 void GestureNavSimple::ApplyEffectsAndDestroy(const gfx::Transform& transform,
127 float opacity) {
128 ui::Layer* layer = arrow_.get();
129 ui::ScopedLayerAnimationSettings settings(arrow_->GetAnimator());
130 settings.AddObserver(
131 new DeleteAfterAnimation<ArrowLayerDelegate>(arrow_delegate_.Pass()));
132 settings.AddObserver(new DeleteAfterAnimation<ui::Layer>(arrow_.Pass()));
133 settings.AddObserver(new DeleteAfterAnimation<ui::Layer>(clip_layer_.Pass()));
134 layer->SetTransform(transform);
135 layer->SetOpacity(opacity);
138 void GestureNavSimple::AbortGestureAnimation() {
139 if (!arrow_)
140 return;
141 gfx::Transform transform;
142 transform.Translate(arrow_delegate_->left() ? -kArrowWidth : kArrowWidth, 0);
143 ApplyEffectsAndDestroy(transform, kMinOpacity);
146 void GestureNavSimple::CompleteGestureAnimation() {
147 if (!arrow_)
148 return;
149 // Make sure the fade-out starts from the complete state.
150 ApplyEffectsForDelta(completion_threshold_);
151 ApplyEffectsAndDestroy(arrow_->transform(), 0.f);
154 bool GestureNavSimple::ApplyEffectsForDelta(float delta_x) {
155 if (!arrow_)
156 return false;
157 CHECK_GT(completion_threshold_, 0.f);
158 CHECK_GE(delta_x, 0.f);
159 double complete = std::min(1.f, delta_x / completion_threshold_);
160 float translate_x = gfx::Tween::FloatValueBetween(complete, -kArrowWidth, 0);
161 gfx::Transform transform;
162 transform.Translate(arrow_delegate_->left() ? translate_x : -translate_x,
163 0.f);
164 arrow_->SetTransform(transform);
165 arrow_->SetOpacity(gfx::Tween::FloatValueBetween(complete, kMinOpacity, 1.f));
166 return true;
169 gfx::Rect GestureNavSimple::GetVisibleBounds() const {
170 return web_contents_->GetNativeView()->bounds();
173 bool GestureNavSimple::OnOverscrollUpdate(float delta_x, float delta_y) {
174 return ApplyEffectsForDelta(std::abs(delta_x) + 50.f);
177 void GestureNavSimple::OnOverscrollComplete(OverscrollMode overscroll_mode) {
178 CompleteGestureAnimation();
180 NavigationControllerImpl& controller = web_contents_->GetController();
181 if (ShouldNavigateForward(controller, overscroll_mode))
182 controller.GoForward();
183 else if (ShouldNavigateBack(controller, overscroll_mode))
184 controller.GoBack();
187 void GestureNavSimple::OnOverscrollModeChange(OverscrollMode old_mode,
188 OverscrollMode new_mode) {
189 NavigationControllerImpl& controller = web_contents_->GetController();
190 if (!ShouldNavigateForward(controller, new_mode) &&
191 !ShouldNavigateBack(controller, new_mode)) {
192 AbortGestureAnimation();
193 return;
196 arrow_.reset(new ui::Layer(ui::LAYER_TEXTURED));
197 // Note that RTL doesn't affect the arrow that should be displayed.
198 int resource_id = 0;
199 if (new_mode == OVERSCROLL_WEST)
200 resource_id = IDR_FORWARD_ARROW;
201 else if (new_mode == OVERSCROLL_EAST)
202 resource_id = IDR_BACK_ARROW;
203 else
204 NOTREACHED();
206 arrow_delegate_.reset(new ArrowLayerDelegate(resource_id));
207 arrow_->set_delegate(arrow_delegate_.get());
208 arrow_->SetFillsBoundsOpaquely(false);
210 aura::Window* window = web_contents_->GetNativeView();
211 const gfx::Rect& window_bounds = window->bounds();
212 completion_threshold_ = window_bounds.width() *
213 GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE);
215 // Align on the left or right edge.
216 int x = (resource_id == IDR_BACK_ARROW) ? 0 :
217 (window_bounds.width() - kArrowWidth);
218 // Align in the center vertically.
219 int y = std::max(0, (window_bounds.height() - kArrowHeight) / 2);
220 arrow_->SetBounds(gfx::Rect(x, y, kArrowWidth, kArrowHeight));
221 ApplyEffectsForDelta(0.f);
223 // Adding the arrow as a child of the content window is not sufficient,
224 // because it is possible for a new layer to be parented on top of the arrow
225 // layer (e.g. when the navigated-to page is displayed while the completion
226 // animation is in progress). So instead, a clip layer (that doesn't paint) is
227 // installed on top of the content window as its sibling, and the arrow layer
228 // is added to that clip layer.
229 clip_layer_.reset(new ui::Layer(ui::LAYER_NOT_DRAWN));
230 clip_layer_->SetBounds(window->layer()->bounds());
231 clip_layer_->SetMasksToBounds(true);
232 clip_layer_->Add(arrow_.get());
234 ui::Layer* parent = window->layer()->parent();
235 parent->Add(clip_layer_.get());
236 parent->StackAtTop(clip_layer_.get());
239 } // namespace content