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/public/browser/overscroll_configuration.h"
12 #include "content/public/browser/web_contents_view.h"
13 #include "content/public/common/content_client.h"
14 #include "grit/ui_resources.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"
28 const int kArrowHeight
= 280;
29 const int kArrowWidth
= 140;
30 const float kMinOpacity
= 0.25f
;
32 bool ShouldNavigateForward(const NavigationController
& controller
,
33 OverscrollMode mode
) {
34 return mode
== (base::i18n::IsRTL() ? OVERSCROLL_EAST
: OVERSCROLL_WEST
) &&
35 controller
.CanGoForward();
38 bool ShouldNavigateBack(const NavigationController
& controller
,
39 OverscrollMode mode
) {
40 return mode
== (base::i18n::IsRTL() ? OVERSCROLL_WEST
: OVERSCROLL_EAST
) &&
41 controller
.CanGoBack();
44 // An animation observers that deletes itself and a pointer after the end of the
47 class DeleteAfterAnimation
: public ui::ImplicitAnimationObserver
{
49 explicit DeleteAfterAnimation(scoped_ptr
<T
> object
)
50 : object_(object
.Pass()) {}
53 friend class base::DeleteHelper
<DeleteAfterAnimation
<T
> >;
55 virtual ~DeleteAfterAnimation() {}
57 // ui::ImplicitAnimationObserver:
58 virtual void OnImplicitAnimationsCompleted() OVERRIDE
{
59 // Deleting an observer when a ScopedLayerAnimationSettings is iterating
60 // over them can cause a crash (which can happen during tests). So instead,
61 // schedule this observer to be deleted soon.
62 BrowserThread::DeleteSoon(BrowserThread::UI
, FROM_HERE
, this);
65 scoped_ptr
<T
> object_
;
66 DISALLOW_COPY_AND_ASSIGN(DeleteAfterAnimation
);
71 // A layer delegate that paints the shield with the arrow in it.
72 class ArrowLayerDelegate
: public ui::LayerDelegate
{
74 explicit ArrowLayerDelegate(int resource_id
)
75 : image_(GetContentClient()->GetNativeImageNamed(resource_id
)),
76 left_arrow_(resource_id
== IDR_BACK_ARROW
) {
77 CHECK(!image_
.IsEmpty());
80 virtual ~ArrowLayerDelegate() {}
82 bool left() const { return left_arrow_
; }
86 virtual void OnPaintLayer(gfx::Canvas
* canvas
) OVERRIDE
{
88 paint
.setColor(SkColorSetARGB(0xa0, 0, 0, 0));
89 paint
.setStyle(SkPaint::kFill_Style
);
90 paint
.setAntiAlias(true);
93 gfx::Point(left_arrow_
? 0 : kArrowWidth
, kArrowHeight
/ 2),
96 canvas
->DrawImageInt(*image_
.ToImageSkia(),
97 left_arrow_
? 0 : kArrowWidth
- image_
.Width(),
98 (kArrowHeight
- image_
.Height()) / 2);
101 virtual void OnDeviceScaleFactorChanged(float device_scale_factor
) OVERRIDE
{}
103 virtual base::Closure
PrepareForLayerBoundsChange() OVERRIDE
{
104 return base::Closure();
107 const gfx::Image
& image_
;
108 const bool left_arrow_
;
110 DISALLOW_COPY_AND_ASSIGN(ArrowLayerDelegate
);
113 GestureNavSimple::GestureNavSimple(WebContentsImpl
* web_contents
)
114 : web_contents_(web_contents
),
115 completion_threshold_(0.f
) {}
117 GestureNavSimple::~GestureNavSimple() {}
119 void GestureNavSimple::ApplyEffectsAndDestroy(const gfx::Transform
& transform
,
121 ui::Layer
* layer
= arrow_
.get();
122 ui::ScopedLayerAnimationSettings
settings(arrow_
->GetAnimator());
123 settings
.AddObserver(
124 new DeleteAfterAnimation
<ArrowLayerDelegate
>(arrow_delegate_
.Pass()));
125 settings
.AddObserver(new DeleteAfterAnimation
<ui::Layer
>(arrow_
.Pass()));
126 settings
.AddObserver(new DeleteAfterAnimation
<ui::Layer
>(clip_layer_
.Pass()));
127 layer
->SetTransform(transform
);
128 layer
->SetOpacity(opacity
);
131 void GestureNavSimple::AbortGestureAnimation() {
134 gfx::Transform transform
;
135 transform
.Translate(arrow_delegate_
->left() ? -kArrowWidth
: kArrowWidth
, 0);
136 ApplyEffectsAndDestroy(transform
, kMinOpacity
);
139 void GestureNavSimple::CompleteGestureAnimation() {
142 // Make sure the fade-out starts from the complete state.
143 ApplyEffectsForDelta(completion_threshold_
);
144 ApplyEffectsAndDestroy(arrow_
->transform(), 0.f
);
147 void GestureNavSimple::ApplyEffectsForDelta(float delta_x
) {
150 CHECK_GT(completion_threshold_
, 0.f
);
151 CHECK_GE(delta_x
, 0.f
);
152 double complete
= std::min(1.f
, delta_x
/ completion_threshold_
);
153 float translate_x
= gfx::Tween::FloatValueBetween(complete
, -kArrowWidth
, 0);
154 gfx::Transform transform
;
155 transform
.Translate(arrow_delegate_
->left() ? translate_x
: -translate_x
,
157 arrow_
->SetTransform(transform
);
158 arrow_
->SetOpacity(gfx::Tween::FloatValueBetween(complete
, kMinOpacity
, 1.f
));
161 gfx::Rect
GestureNavSimple::GetVisibleBounds() const {
162 return web_contents_
->GetView()->GetNativeView()->bounds();
165 void GestureNavSimple::OnOverscrollUpdate(float delta_x
, float delta_y
) {
166 ApplyEffectsForDelta(std::abs(delta_x
) + 50.f
);
169 void GestureNavSimple::OnOverscrollComplete(OverscrollMode overscroll_mode
) {
170 CompleteGestureAnimation();
172 NavigationControllerImpl
& controller
= web_contents_
->GetController();
173 if (ShouldNavigateForward(controller
, overscroll_mode
))
174 controller
.GoForward();
175 else if (ShouldNavigateBack(controller
, overscroll_mode
))
179 void GestureNavSimple::OnOverscrollModeChange(OverscrollMode old_mode
,
180 OverscrollMode new_mode
) {
181 NavigationControllerImpl
& controller
= web_contents_
->GetController();
182 if (!ShouldNavigateForward(controller
, new_mode
) &&
183 !ShouldNavigateBack(controller
, new_mode
)) {
184 AbortGestureAnimation();
188 arrow_
.reset(new ui::Layer(ui::LAYER_TEXTURED
));
189 // Note that RTL doesn't affect the arrow that should be displayed.
191 if (new_mode
== OVERSCROLL_WEST
)
192 resource_id
= IDR_FORWARD_ARROW
;
193 else if (new_mode
== OVERSCROLL_EAST
)
194 resource_id
= IDR_BACK_ARROW
;
198 arrow_delegate_
.reset(new ArrowLayerDelegate(resource_id
));
199 arrow_
->set_delegate(arrow_delegate_
.get());
200 arrow_
->SetFillsBoundsOpaquely(false);
202 aura::Window
* window
= web_contents_
->GetView()->GetNativeView();
203 const gfx::Rect
& window_bounds
= window
->bounds();
204 completion_threshold_
= window_bounds
.width() *
205 GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE
);
207 // Align on the left or right edge.
208 int x
= (resource_id
== IDR_BACK_ARROW
) ? 0 :
209 (window_bounds
.width() - kArrowWidth
);
210 // Align in the center vertically.
211 int y
= std::max(0, (window_bounds
.height() - kArrowHeight
) / 2);
212 arrow_
->SetBounds(gfx::Rect(x
, y
, kArrowWidth
, kArrowHeight
));
213 ApplyEffectsForDelta(0.f
);
215 // Adding the arrow as a child of the content window is not sufficient,
216 // because it is possible for a new layer to be parented on top of the arrow
217 // layer (e.g. when the navigated-to page is displayed while the completion
218 // animation is in progress). So instead, a clip layer (that doesn't paint) is
219 // installed on top of the content window as its sibling, and the arrow layer
220 // is added to that clip layer.
221 clip_layer_
.reset(new ui::Layer(ui::LAYER_NOT_DRAWN
));
222 clip_layer_
->SetBounds(window
->layer()->bounds());
223 clip_layer_
->SetMasksToBounds(true);
224 clip_layer_
->Add(arrow_
.get());
226 ui::Layer
* parent
= window
->layer()->parent();
227 parent
->Add(clip_layer_
.get());
228 parent
->StackAtTop(clip_layer_
.get());
231 } // namespace content