1 // Copyright (c) 2013 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/android/overscroll_glow.h"
7 #include "base/debug/trace_event.h"
8 #include "base/lazy_instance.h"
9 #include "base/threading/worker_pool.h"
10 #include "cc/layers/image_layer.h"
11 #include "content/browser/android/edge_effect.h"
12 #include "skia/ext/image_operations.h"
13 #include "ui/gfx/android/java_bitmap.h"
22 const float kEpsilon
= 1e-3f
;
23 const int kScaledEdgeHeight
= 12;
24 const int kScaledGlowHeight
= 64;
25 const float kEdgeHeightAtMdpi
= 12.f
;
26 const float kGlowHeightAtMdpi
= 128.f
;
28 SkBitmap
CreateSkBitmapFromAndroidResource(const char* name
, gfx::Size size
) {
29 base::android::ScopedJavaLocalRef
<jobject
> jobj
=
30 gfx::CreateJavaBitmapFromAndroidResource(name
, size
);
34 SkBitmap bitmap
= CreateSkBitmapFromJavaBitmap(gfx::JavaBitmap(jobj
.obj()));
38 return skia::ImageOperations::Resize(
39 bitmap
, skia::ImageOperations::RESIZE_BOX
, size
.width(), size
.height());
42 class OverscrollResources
{
44 OverscrollResources() {
45 TRACE_EVENT0("browser", "OverscrollResources::Create");
47 CreateSkBitmapFromAndroidResource("android:drawable/overscroll_edge",
48 gfx::Size(128, kScaledEdgeHeight
));
50 CreateSkBitmapFromAndroidResource("android:drawable/overscroll_glow",
51 gfx::Size(128, kScaledGlowHeight
));
54 const SkBitmap
& edge_bitmap() const { return edge_bitmap_
; }
55 const SkBitmap
& glow_bitmap() const { return glow_bitmap_
; }
58 SkBitmap edge_bitmap_
;
59 SkBitmap glow_bitmap_
;
61 DISALLOW_COPY_AND_ASSIGN(OverscrollResources
);
64 // Leaky to allow access from a worker thread.
65 base::LazyInstance
<OverscrollResources
>::Leaky g_overscroll_resources
=
66 LAZY_INSTANCE_INITIALIZER
;
68 scoped_refptr
<cc::Layer
> CreateImageLayer(const SkBitmap
& bitmap
) {
69 scoped_refptr
<cc::ImageLayer
> layer
= cc::ImageLayer::Create();
70 layer
->SetBitmap(bitmap
);
74 bool IsApproxZero(float value
) {
75 return std::abs(value
) < kEpsilon
;
78 gfx::Vector2dF
ZeroSmallComponents(gfx::Vector2dF vector
) {
79 if (IsApproxZero(vector
.x()))
81 if (IsApproxZero(vector
.y()))
86 // Force loading of any necessary resources. This function is thread-safe.
87 void EnsureResources() {
88 g_overscroll_resources
.Get();
93 scoped_ptr
<OverscrollGlow
> OverscrollGlow::Create(bool enabled
) {
94 // Don't block the main thread with effect resource loading during creation.
95 // Effect instantiation is deferred until the effect overscrolls, in which
96 // case the main thread may block until the resource has loaded.
97 if (enabled
&& g_overscroll_resources
== NULL
)
98 base::WorkerPool::PostTask(FROM_HERE
, base::Bind(EnsureResources
), true);
100 return make_scoped_ptr(new OverscrollGlow(enabled
));
103 OverscrollGlow::OverscrollGlow(bool enabled
)
104 : enabled_(enabled
), initialized_(false) {}
106 OverscrollGlow::~OverscrollGlow() {
110 void OverscrollGlow::Enable() {
114 void OverscrollGlow::Disable() {
118 if (!enabled_
&& initialized_
) {
120 for (size_t i
= 0; i
< EdgeEffect::EDGE_COUNT
; ++i
)
121 edge_effects_
[i
]->Finish();
125 bool OverscrollGlow::OnOverscrolled(cc::Layer
* overscrolling_layer
,
126 base::TimeTicks current_time
,
127 gfx::Vector2dF accumulated_overscroll
,
128 gfx::Vector2dF overscroll_delta
,
129 gfx::Vector2dF velocity
) {
130 DCHECK(overscrolling_layer
);
135 // The size of the glow determines the relative effect of the inputs; an
136 // empty-sized effect is effectively disabled.
137 if (display_params_
.size
.IsEmpty())
140 // Ignore sufficiently small values that won't meaningfuly affect animation.
141 overscroll_delta
= ZeroSmallComponents(overscroll_delta
);
142 if (overscroll_delta
.IsZero()) {
144 Release(current_time
);
145 UpdateLayerAttachment(overscrolling_layer
);
147 return NeedsAnimate();
150 if (!InitializeIfNecessary())
153 gfx::Vector2dF old_overscroll
= accumulated_overscroll
- overscroll_delta
;
154 bool x_overscroll_started
=
155 !IsApproxZero(overscroll_delta
.x()) && IsApproxZero(old_overscroll
.x());
156 bool y_overscroll_started
=
157 !IsApproxZero(overscroll_delta
.y()) && IsApproxZero(old_overscroll
.y());
159 if (x_overscroll_started
)
160 ReleaseAxis(AXIS_X
, current_time
);
161 if (y_overscroll_started
)
162 ReleaseAxis(AXIS_Y
, current_time
);
164 velocity
= ZeroSmallComponents(velocity
);
165 if (!velocity
.IsZero())
166 Absorb(current_time
, velocity
, x_overscroll_started
, y_overscroll_started
);
168 Pull(current_time
, overscroll_delta
);
170 UpdateLayerAttachment(overscrolling_layer
);
171 return NeedsAnimate();
174 bool OverscrollGlow::Animate(base::TimeTicks current_time
) {
175 if (!NeedsAnimate()) {
180 for (size_t i
= 0; i
< EdgeEffect::EDGE_COUNT
; ++i
) {
181 if (edge_effects_
[i
]->Update(current_time
)) {
182 edge_effects_
[i
]->ApplyToLayers(
183 display_params_
.size
,
184 static_cast<EdgeEffect::Edge
>(i
),
185 kEdgeHeightAtMdpi
* display_params_
.device_scale_factor
,
186 kGlowHeightAtMdpi
* display_params_
.device_scale_factor
,
187 display_params_
.edge_offsets
[i
]);
191 if (!NeedsAnimate()) {
199 void OverscrollGlow::UpdateDisplayParameters(const DisplayParameters
& params
) {
200 display_params_
= params
;
203 bool OverscrollGlow::NeedsAnimate() const {
204 if (!enabled_
|| !initialized_
)
206 for (size_t i
= 0; i
< EdgeEffect::EDGE_COUNT
; ++i
) {
207 if (!edge_effects_
[i
]->IsFinished())
213 void OverscrollGlow::UpdateLayerAttachment(cc::Layer
* parent
) {
218 if (!NeedsAnimate()) {
223 if (root_layer_
->parent() != parent
)
224 parent
->AddChild(root_layer_
);
227 void OverscrollGlow::Detach() {
229 root_layer_
->RemoveFromParent();
232 bool OverscrollGlow::InitializeIfNecessary() {
237 const SkBitmap
& edge
= g_overscroll_resources
.Get().edge_bitmap();
238 const SkBitmap
& glow
= g_overscroll_resources
.Get().glow_bitmap();
239 if (edge
.isNull() || glow
.isNull()) {
244 DCHECK(!root_layer_
);
245 root_layer_
= cc::Layer::Create();
246 for (size_t i
= 0; i
< EdgeEffect::EDGE_COUNT
; ++i
) {
247 scoped_refptr
<cc::Layer
> edge_layer
= CreateImageLayer(edge
);
248 scoped_refptr
<cc::Layer
> glow_layer
= CreateImageLayer(glow
);
249 root_layer_
->AddChild(edge_layer
);
250 root_layer_
->AddChild(glow_layer
);
251 edge_effects_
[i
] = make_scoped_ptr(new EdgeEffect(edge_layer
, glow_layer
));
258 void OverscrollGlow::Pull(base::TimeTicks current_time
,
259 gfx::Vector2dF overscroll_delta
) {
260 DCHECK(enabled_
&& initialized_
);
261 overscroll_delta
= ZeroSmallComponents(overscroll_delta
);
262 if (overscroll_delta
.IsZero())
265 gfx::Vector2dF overscroll_pull
=
266 gfx::ScaleVector2d(overscroll_delta
,
267 1.f
/ display_params_
.size
.width(),
268 1.f
/ display_params_
.size
.height());
269 float edge_overscroll_pull
[EdgeEffect::EDGE_COUNT
] = {
270 min(overscroll_pull
.y(), 0.f
), // Top
271 min(overscroll_pull
.x(), 0.f
), // Left
272 max(overscroll_pull
.y(), 0.f
), // Bottom
273 max(overscroll_pull
.x(), 0.f
) // Right
276 for (size_t i
= 0; i
< EdgeEffect::EDGE_COUNT
; ++i
) {
277 if (!edge_overscroll_pull
[i
])
280 edge_effects_
[i
]->Pull(current_time
, std::abs(edge_overscroll_pull
[i
]));
281 GetOppositeEdge(i
)->Release(current_time
);
285 void OverscrollGlow::Absorb(base::TimeTicks current_time
,
286 gfx::Vector2dF velocity
,
287 bool x_overscroll_started
,
288 bool y_overscroll_started
) {
289 DCHECK(enabled_
&& initialized_
);
290 if (velocity
.IsZero())
293 // Only trigger on initial overscroll at a non-zero velocity
294 const float overscroll_velocities
[EdgeEffect::EDGE_COUNT
] = {
295 y_overscroll_started
? min(velocity
.y(), 0.f
) : 0, // Top
296 x_overscroll_started
? min(velocity
.x(), 0.f
) : 0, // Left
297 y_overscroll_started
? max(velocity
.y(), 0.f
) : 0, // Bottom
298 x_overscroll_started
? max(velocity
.x(), 0.f
) : 0 // Right
301 for (size_t i
= 0; i
< EdgeEffect::EDGE_COUNT
; ++i
) {
302 if (!overscroll_velocities
[i
])
305 edge_effects_
[i
]->Absorb(current_time
, std::abs(overscroll_velocities
[i
]));
306 GetOppositeEdge(i
)->Release(current_time
);
310 void OverscrollGlow::Release(base::TimeTicks current_time
) {
311 DCHECK(initialized_
);
312 for (size_t i
= 0; i
< EdgeEffect::EDGE_COUNT
; ++i
)
313 edge_effects_
[i
]->Release(current_time
);
316 void OverscrollGlow::ReleaseAxis(Axis axis
, base::TimeTicks current_time
) {
317 DCHECK(initialized_
);
320 edge_effects_
[EdgeEffect::EDGE_LEFT
]->Release(current_time
);
321 edge_effects_
[EdgeEffect::EDGE_RIGHT
]->Release(current_time
);
324 edge_effects_
[EdgeEffect::EDGE_TOP
]->Release(current_time
);
325 edge_effects_
[EdgeEffect::EDGE_BOTTOM
]->Release(current_time
);
330 EdgeEffect
* OverscrollGlow::GetOppositeEdge(int edge_index
) {
331 DCHECK(initialized_
);
332 return edge_effects_
[(edge_index
+ 2) % EdgeEffect::EDGE_COUNT
].get();
335 OverscrollGlow::DisplayParameters::DisplayParameters()
336 : device_scale_factor(1) {
337 edge_offsets
[0] = edge_offsets
[1] = edge_offsets
[2] = edge_offsets
[3] = 0.f
;
340 } // namespace content