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/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"
23 #include "ui/resources/grit/ui_resources.h"
29 const int kArrowHeight
= 280;
30 const int kArrowWidth
= 140;
31 const float kMinOpacity
= 0.25f
;
33 bool ShouldNavigateForward(const NavigationController
& controller
,
34 OverscrollMode mode
) {
35 return mode
== (base::i18n::IsRTL() ? OVERSCROLL_EAST
: OVERSCROLL_WEST
) &&
36 controller
.CanGoForward();
39 bool ShouldNavigateBack(const NavigationController
& controller
,
40 OverscrollMode mode
) {
41 return mode
== (base::i18n::IsRTL() ? OVERSCROLL_WEST
: OVERSCROLL_EAST
) &&
42 controller
.CanGoBack();
45 // An animation observers that deletes itself and a pointer after the end of the
48 class DeleteAfterAnimation
: public ui::ImplicitAnimationObserver
{
50 explicit DeleteAfterAnimation(scoped_ptr
<T
> object
)
51 : object_(object
.Pass()) {}
54 friend class base::DeleteHelper
<DeleteAfterAnimation
<T
> >;
56 virtual ~DeleteAfterAnimation() {}
58 // ui::ImplicitAnimationObserver:
59 virtual void OnImplicitAnimationsCompleted() override
{
60 // Deleting an observer when a ScopedLayerAnimationSettings is iterating
61 // over them can cause a crash (which can happen during tests). So instead,
62 // schedule this observer to be deleted soon.
63 BrowserThread::DeleteSoon(BrowserThread::UI
, FROM_HERE
, this);
66 scoped_ptr
<T
> object_
;
67 DISALLOW_COPY_AND_ASSIGN(DeleteAfterAnimation
);
72 // A layer delegate that paints the shield with the arrow in it.
73 class ArrowLayerDelegate
: public ui::LayerDelegate
{
75 explicit ArrowLayerDelegate(int resource_id
)
76 : image_(GetContentClient()->GetNativeImageNamed(resource_id
)),
77 left_arrow_(resource_id
== IDR_BACK_ARROW
) {
78 CHECK(!image_
.IsEmpty());
81 ~ArrowLayerDelegate() override
{}
83 bool left() const { return left_arrow_
; }
87 void OnPaintLayer(gfx::Canvas
* canvas
) override
{
89 paint
.setColor(SkColorSetARGB(0xa0, 0, 0, 0));
90 paint
.setStyle(SkPaint::kFill_Style
);
91 paint
.setAntiAlias(true);
94 gfx::Point(left_arrow_
? 0 : kArrowWidth
, kArrowHeight
/ 2),
97 canvas
->DrawImageInt(*image_
.ToImageSkia(),
98 left_arrow_
? 0 : kArrowWidth
- image_
.Width(),
99 (kArrowHeight
- image_
.Height()) / 2);
102 void OnDelegatedFrameDamage(const gfx::Rect
& damage_rect_in_dip
) override
{}
104 void OnDeviceScaleFactorChanged(float device_scale_factor
) override
{}
106 base::Closure
PrepareForLayerBoundsChange() override
{
107 return base::Closure();
110 const gfx::Image
& image_
;
111 const bool left_arrow_
;
113 DISALLOW_COPY_AND_ASSIGN(ArrowLayerDelegate
);
116 GestureNavSimple::GestureNavSimple(WebContentsImpl
* web_contents
)
117 : web_contents_(web_contents
),
118 completion_threshold_(0.f
) {}
120 GestureNavSimple::~GestureNavSimple() {}
122 void GestureNavSimple::ApplyEffectsAndDestroy(const gfx::Transform
& transform
,
124 ui::Layer
* layer
= arrow_
.get();
125 ui::ScopedLayerAnimationSettings
settings(arrow_
->GetAnimator());
126 settings
.AddObserver(
127 new DeleteAfterAnimation
<ArrowLayerDelegate
>(arrow_delegate_
.Pass()));
128 settings
.AddObserver(new DeleteAfterAnimation
<ui::Layer
>(arrow_
.Pass()));
129 settings
.AddObserver(new DeleteAfterAnimation
<ui::Layer
>(clip_layer_
.Pass()));
130 layer
->SetTransform(transform
);
131 layer
->SetOpacity(opacity
);
134 void GestureNavSimple::AbortGestureAnimation() {
137 gfx::Transform transform
;
138 transform
.Translate(arrow_delegate_
->left() ? -kArrowWidth
: kArrowWidth
, 0);
139 ApplyEffectsAndDestroy(transform
, kMinOpacity
);
142 void GestureNavSimple::CompleteGestureAnimation() {
145 // Make sure the fade-out starts from the complete state.
146 ApplyEffectsForDelta(completion_threshold_
);
147 ApplyEffectsAndDestroy(arrow_
->transform(), 0.f
);
150 bool GestureNavSimple::ApplyEffectsForDelta(float delta_x
) {
153 CHECK_GT(completion_threshold_
, 0.f
);
154 CHECK_GE(delta_x
, 0.f
);
155 double complete
= std::min(1.f
, delta_x
/ completion_threshold_
);
156 float translate_x
= gfx::Tween::FloatValueBetween(complete
, -kArrowWidth
, 0);
157 gfx::Transform transform
;
158 transform
.Translate(arrow_delegate_
->left() ? translate_x
: -translate_x
,
160 arrow_
->SetTransform(transform
);
161 arrow_
->SetOpacity(gfx::Tween::FloatValueBetween(complete
, kMinOpacity
, 1.f
));
165 gfx::Rect
GestureNavSimple::GetVisibleBounds() const {
166 return web_contents_
->GetNativeView()->bounds();
169 bool GestureNavSimple::OnOverscrollUpdate(float delta_x
, float delta_y
) {
170 return ApplyEffectsForDelta(std::abs(delta_x
) + 50.f
);
173 void GestureNavSimple::OnOverscrollComplete(OverscrollMode overscroll_mode
) {
174 CompleteGestureAnimation();
176 NavigationControllerImpl
& controller
= web_contents_
->GetController();
177 if (ShouldNavigateForward(controller
, overscroll_mode
))
178 controller
.GoForward();
179 else if (ShouldNavigateBack(controller
, overscroll_mode
))
183 void GestureNavSimple::OnOverscrollModeChange(OverscrollMode old_mode
,
184 OverscrollMode new_mode
) {
185 NavigationControllerImpl
& controller
= web_contents_
->GetController();
186 if (!ShouldNavigateForward(controller
, new_mode
) &&
187 !ShouldNavigateBack(controller
, new_mode
)) {
188 AbortGestureAnimation();
192 arrow_
.reset(new ui::Layer(ui::LAYER_TEXTURED
));
193 // Note that RTL doesn't affect the arrow that should be displayed.
195 if (new_mode
== OVERSCROLL_WEST
)
196 resource_id
= IDR_FORWARD_ARROW
;
197 else if (new_mode
== OVERSCROLL_EAST
)
198 resource_id
= IDR_BACK_ARROW
;
202 arrow_delegate_
.reset(new ArrowLayerDelegate(resource_id
));
203 arrow_
->set_delegate(arrow_delegate_
.get());
204 arrow_
->SetFillsBoundsOpaquely(false);
206 aura::Window
* window
= web_contents_
->GetNativeView();
207 const gfx::Rect
& window_bounds
= window
->bounds();
208 completion_threshold_
= window_bounds
.width() *
209 GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE
);
211 // Align on the left or right edge.
212 int x
= (resource_id
== IDR_BACK_ARROW
) ? 0 :
213 (window_bounds
.width() - kArrowWidth
);
214 // Align in the center vertically.
215 int y
= std::max(0, (window_bounds
.height() - kArrowHeight
) / 2);
216 arrow_
->SetBounds(gfx::Rect(x
, y
, kArrowWidth
, kArrowHeight
));
217 ApplyEffectsForDelta(0.f
);
219 // Adding the arrow as a child of the content window is not sufficient,
220 // because it is possible for a new layer to be parented on top of the arrow
221 // layer (e.g. when the navigated-to page is displayed while the completion
222 // animation is in progress). So instead, a clip layer (that doesn't paint) is
223 // installed on top of the content window as its sibling, and the arrow layer
224 // is added to that clip layer.
225 clip_layer_
.reset(new ui::Layer(ui::LAYER_NOT_DRAWN
));
226 clip_layer_
->SetBounds(window
->layer()->bounds());
227 clip_layer_
->SetMasksToBounds(true);
228 clip_layer_
->Add(arrow_
.get());
230 ui::Layer
* parent
= window
->layer()->parent();
231 parent
->Add(clip_layer_
.get());
232 parent
->StackAtTop(clip_layer_
.get());
235 } // namespace content