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_context.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 gfx::Canvas
* canvas
= context
.canvas();
96 gfx::Point(left_arrow_
? 0 : kArrowWidth
, kArrowHeight
/ 2),
99 canvas
->DrawImageInt(*image_
.ToImageSkia(),
100 left_arrow_
? 0 : kArrowWidth
- image_
.Width(),
101 (kArrowHeight
- image_
.Height()) / 2);
104 void OnDelegatedFrameDamage(const gfx::Rect
& damage_rect_in_dip
) override
{}
106 void OnDeviceScaleFactorChanged(float device_scale_factor
) override
{}
108 base::Closure
PrepareForLayerBoundsChange() override
{
109 return base::Closure();
112 const gfx::Image
& image_
;
113 const bool left_arrow_
;
115 DISALLOW_COPY_AND_ASSIGN(ArrowLayerDelegate
);
118 GestureNavSimple::GestureNavSimple(WebContentsImpl
* web_contents
)
119 : web_contents_(web_contents
),
120 completion_threshold_(0.f
) {}
122 GestureNavSimple::~GestureNavSimple() {}
124 void GestureNavSimple::ApplyEffectsAndDestroy(const gfx::Transform
& transform
,
126 ui::Layer
* layer
= arrow_
.get();
127 ui::ScopedLayerAnimationSettings
settings(arrow_
->GetAnimator());
128 settings
.AddObserver(
129 new DeleteAfterAnimation
<ArrowLayerDelegate
>(arrow_delegate_
.Pass()));
130 settings
.AddObserver(new DeleteAfterAnimation
<ui::Layer
>(arrow_
.Pass()));
131 settings
.AddObserver(new DeleteAfterAnimation
<ui::Layer
>(clip_layer_
.Pass()));
132 layer
->SetTransform(transform
);
133 layer
->SetOpacity(opacity
);
136 void GestureNavSimple::AbortGestureAnimation() {
139 gfx::Transform transform
;
140 transform
.Translate(arrow_delegate_
->left() ? -kArrowWidth
: kArrowWidth
, 0);
141 ApplyEffectsAndDestroy(transform
, kMinOpacity
);
144 void GestureNavSimple::CompleteGestureAnimation() {
147 // Make sure the fade-out starts from the complete state.
148 ApplyEffectsForDelta(completion_threshold_
);
149 ApplyEffectsAndDestroy(arrow_
->transform(), 0.f
);
152 bool GestureNavSimple::ApplyEffectsForDelta(float delta_x
) {
155 CHECK_GT(completion_threshold_
, 0.f
);
156 CHECK_GE(delta_x
, 0.f
);
157 double complete
= std::min(1.f
, delta_x
/ completion_threshold_
);
158 float translate_x
= gfx::Tween::FloatValueBetween(complete
, -kArrowWidth
, 0);
159 gfx::Transform transform
;
160 transform
.Translate(arrow_delegate_
->left() ? translate_x
: -translate_x
,
162 arrow_
->SetTransform(transform
);
163 arrow_
->SetOpacity(gfx::Tween::FloatValueBetween(complete
, kMinOpacity
, 1.f
));
167 gfx::Rect
GestureNavSimple::GetVisibleBounds() const {
168 return web_contents_
->GetNativeView()->bounds();
171 bool GestureNavSimple::OnOverscrollUpdate(float delta_x
, float delta_y
) {
172 return ApplyEffectsForDelta(std::abs(delta_x
) + 50.f
);
175 void GestureNavSimple::OnOverscrollComplete(OverscrollMode overscroll_mode
) {
176 CompleteGestureAnimation();
178 NavigationControllerImpl
& controller
= web_contents_
->GetController();
179 if (ShouldNavigateForward(controller
, overscroll_mode
))
180 controller
.GoForward();
181 else if (ShouldNavigateBack(controller
, overscroll_mode
))
185 void GestureNavSimple::OnOverscrollModeChange(OverscrollMode old_mode
,
186 OverscrollMode new_mode
) {
187 NavigationControllerImpl
& controller
= web_contents_
->GetController();
188 if (!ShouldNavigateForward(controller
, new_mode
) &&
189 !ShouldNavigateBack(controller
, new_mode
)) {
190 AbortGestureAnimation();
194 arrow_
.reset(new ui::Layer(ui::LAYER_TEXTURED
));
195 // Note that RTL doesn't affect the arrow that should be displayed.
197 if (new_mode
== OVERSCROLL_WEST
)
198 resource_id
= IDR_FORWARD_ARROW
;
199 else if (new_mode
== OVERSCROLL_EAST
)
200 resource_id
= IDR_BACK_ARROW
;
204 arrow_delegate_
.reset(new ArrowLayerDelegate(resource_id
));
205 arrow_
->set_delegate(arrow_delegate_
.get());
206 arrow_
->SetFillsBoundsOpaquely(false);
208 aura::Window
* window
= web_contents_
->GetNativeView();
209 const gfx::Rect
& window_bounds
= window
->bounds();
210 completion_threshold_
= window_bounds
.width() *
211 GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE
);
213 // Align on the left or right edge.
214 int x
= (resource_id
== IDR_BACK_ARROW
) ? 0 :
215 (window_bounds
.width() - kArrowWidth
);
216 // Align in the center vertically.
217 int y
= std::max(0, (window_bounds
.height() - kArrowHeight
) / 2);
218 arrow_
->SetBounds(gfx::Rect(x
, y
, kArrowWidth
, kArrowHeight
));
219 ApplyEffectsForDelta(0.f
);
221 // Adding the arrow as a child of the content window is not sufficient,
222 // because it is possible for a new layer to be parented on top of the arrow
223 // layer (e.g. when the navigated-to page is displayed while the completion
224 // animation is in progress). So instead, a clip layer (that doesn't paint) is
225 // installed on top of the content window as its sibling, and the arrow layer
226 // is added to that clip layer.
227 clip_layer_
.reset(new ui::Layer(ui::LAYER_NOT_DRAWN
));
228 clip_layer_
->SetBounds(window
->layer()->bounds());
229 clip_layer_
->SetMasksToBounds(true);
230 clip_layer_
->Add(arrow_
.get());
232 ui::Layer
* parent
= window
->layer()->parent();
233 parent
->Add(clip_layer_
.get());
234 parent
->StackAtTop(clip_layer_
.get());
237 } // namespace content