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"
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
49 class DeleteAfterAnimation
: public ui::ImplicitAnimationObserver
{
51 explicit DeleteAfterAnimation(scoped_ptr
<T
> object
)
52 : object_(object
.Pass()) {}
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
);
73 // A layer delegate that paints the shield with the arrow in it.
74 class ArrowLayerDelegate
: public ui::LayerDelegate
{
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_
; }
88 void OnPaintLayer(const ui::PaintContext
& context
) override
{
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
97 ui::PaintRecorder
recorder(context
, gfx::Size(kArrowWidth
, kArrowHeight
));
98 recorder
.canvas()->DrawCircle(
99 gfx::Point(left_arrow_
? 0 : kArrowWidth
, kArrowHeight
/ 2),
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
,
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() {
141 gfx::Transform transform
;
142 transform
.Translate(arrow_delegate_
->left() ? -kArrowWidth
: kArrowWidth
, 0);
143 ApplyEffectsAndDestroy(transform
, kMinOpacity
);
146 void GestureNavSimple::CompleteGestureAnimation() {
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
) {
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
,
164 arrow_
->SetTransform(transform
);
165 arrow_
->SetOpacity(gfx::Tween::FloatValueBetween(complete
, kMinOpacity
, 1.f
));
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
))
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();
196 arrow_
.reset(new ui::Layer(ui::LAYER_TEXTURED
));
197 // Note that RTL doesn't affect the arrow that should be displayed.
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
;
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