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 virtual ~DeleteAfterAnimation() {}
59 // ui::ImplicitAnimationObserver:
60 virtual 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 ui::PaintRecorder
recorder(context
);
95 recorder
.canvas()->DrawCircle(
96 gfx::Point(left_arrow_
? 0 : kArrowWidth
, kArrowHeight
/ 2),
98 recorder
.canvas()->DrawImageInt(
99 *image_
.ToImageSkia(), left_arrow_
? 0 : kArrowWidth
- image_
.Width(),
100 (kArrowHeight
- image_
.Height()) / 2);
103 void OnDelegatedFrameDamage(const gfx::Rect
& damage_rect_in_dip
) override
{}
105 void OnDeviceScaleFactorChanged(float device_scale_factor
) override
{}
107 base::Closure
PrepareForLayerBoundsChange() override
{
108 return base::Closure();
111 const gfx::Image
& image_
;
112 const bool left_arrow_
;
114 DISALLOW_COPY_AND_ASSIGN(ArrowLayerDelegate
);
117 GestureNavSimple::GestureNavSimple(WebContentsImpl
* web_contents
)
118 : web_contents_(web_contents
),
119 completion_threshold_(0.f
) {}
121 GestureNavSimple::~GestureNavSimple() {}
123 void GestureNavSimple::ApplyEffectsAndDestroy(const gfx::Transform
& transform
,
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
);
135 void GestureNavSimple::AbortGestureAnimation() {
138 gfx::Transform transform
;
139 transform
.Translate(arrow_delegate_
->left() ? -kArrowWidth
: kArrowWidth
, 0);
140 ApplyEffectsAndDestroy(transform
, kMinOpacity
);
143 void GestureNavSimple::CompleteGestureAnimation() {
146 // Make sure the fade-out starts from the complete state.
147 ApplyEffectsForDelta(completion_threshold_
);
148 ApplyEffectsAndDestroy(arrow_
->transform(), 0.f
);
151 bool GestureNavSimple::ApplyEffectsForDelta(float delta_x
) {
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
,
161 arrow_
->SetTransform(transform
);
162 arrow_
->SetOpacity(gfx::Tween::FloatValueBetween(complete
, kMinOpacity
, 1.f
));
166 gfx::Rect
GestureNavSimple::GetVisibleBounds() const {
167 return web_contents_
->GetNativeView()->bounds();
170 bool GestureNavSimple::OnOverscrollUpdate(float delta_x
, float delta_y
) {
171 return ApplyEffectsForDelta(std::abs(delta_x
) + 50.f
);
174 void GestureNavSimple::OnOverscrollComplete(OverscrollMode overscroll_mode
) {
175 CompleteGestureAnimation();
177 NavigationControllerImpl
& controller
= web_contents_
->GetController();
178 if (ShouldNavigateForward(controller
, overscroll_mode
))
179 controller
.GoForward();
180 else if (ShouldNavigateBack(controller
, overscroll_mode
))
184 void 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();
193 arrow_
.reset(new ui::Layer(ui::LAYER_TEXTURED
));
194 // Note that RTL doesn't affect the arrow that should be displayed.
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
;
203 arrow_delegate_
.reset(new ArrowLayerDelegate(resource_id
));
204 arrow_
->set_delegate(arrow_delegate_
.get());
205 arrow_
->SetFillsBoundsOpaquely(false);
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
);
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
);
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());
231 ui::Layer
* parent
= window
->layer()->parent();
232 parent
->Add(clip_layer_
.get());
233 parent
->StackAtTop(clip_layer_
.get());
236 } // namespace content