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. 4 5#include "content/browser/android/overscroll_glow.h" 6 7#include "cc/layers/layer.h" 8#include "content/browser/android/edge_effect_base.h" 9 10using std::max; 11using std::min; 12 13namespace content { 14 15namespace { 16 17const float kEpsilon = 1e-3f; 18 19bool IsApproxZero(float value) { 20 return std::abs(value) < kEpsilon; 21} 22 23gfx::Vector2dF ZeroSmallComponents(gfx::Vector2dF vector) { 24 if (IsApproxZero(vector.x())) 25 vector.set_x(0); 26 if (IsApproxZero(vector.y())) 27 vector.set_y(0); 28 return vector; 29} 30 31gfx::Transform ComputeTransform(OverscrollGlow::Edge edge, 32 const gfx::SizeF& window_size, 33 float offset) { 34 // Transforms assume the edge layers are anchored to their *top center point*. 35 switch (edge) { 36 case OverscrollGlow::EDGE_TOP: 37 return gfx::Transform(1, 0, 0, 1, 0, offset); 38 case OverscrollGlow::EDGE_LEFT: 39 return gfx::Transform(0, 40 1, 41 -1, 42 0, 43 -window_size.height() / 2.f + offset, 44 window_size.height() / 2.f); 45 case OverscrollGlow::EDGE_BOTTOM: 46 return gfx::Transform(-1, 0, 0, -1, 0, window_size.height() + offset); 47 case OverscrollGlow::EDGE_RIGHT: 48 return gfx::Transform( 49 0, 50 -1, 51 1, 52 0, 53 -window_size.height() / 2.f + window_size.width() + offset, 54 window_size.height() / 2.f); 55 default: 56 NOTREACHED() << "Invalid edge: " << edge; 57 return gfx::Transform(); 58 }; 59} 60 61gfx::SizeF ComputeSize(OverscrollGlow::Edge edge, 62 const gfx::SizeF& window_size) { 63 switch (edge) { 64 case OverscrollGlow::EDGE_TOP: 65 case OverscrollGlow::EDGE_BOTTOM: 66 return window_size; 67 case OverscrollGlow::EDGE_LEFT: 68 case OverscrollGlow::EDGE_RIGHT: 69 return gfx::SizeF(window_size.height(), window_size.width()); 70 default: 71 NOTREACHED() << "Invalid edge: " << edge; 72 return gfx::SizeF(); 73 }; 74} 75 76} // namespace 77 78OverscrollGlow::OverscrollGlow(const EdgeEffectProvider& edge_effect_provider) 79 : edge_effect_provider_(edge_effect_provider), 80 enabled_(true), 81 initialized_(false) { 82 DCHECK(!edge_effect_provider_.is_null()); 83} 84 85OverscrollGlow::~OverscrollGlow() { 86 Detach(); 87} 88 89void OverscrollGlow::Enable() { 90 enabled_ = true; 91} 92 93void OverscrollGlow::Disable() { 94 if (!enabled_) 95 return; 96 enabled_ = false; 97 if (!enabled_ && initialized_) { 98 Detach(); 99 for (size_t i = 0; i < EDGE_COUNT; ++i) 100 edge_effects_[i]->Finish(); 101 } 102} 103 104bool OverscrollGlow::OnOverscrolled(cc::Layer* overscrolling_layer, 105 base::TimeTicks current_time, 106 gfx::Vector2dF accumulated_overscroll, 107 gfx::Vector2dF overscroll_delta, 108 gfx::Vector2dF velocity, 109 gfx::Vector2dF displacement) { 110 DCHECK(overscrolling_layer); 111 112 if (!enabled_) 113 return false; 114 115 // The size of the glow determines the relative effect of the inputs; an 116 // empty-sized effect is effectively disabled. 117 if (display_params_.size.IsEmpty()) 118 return false; 119 120 // Ignore sufficiently small values that won't meaningfuly affect animation. 121 overscroll_delta = ZeroSmallComponents(overscroll_delta); 122 if (overscroll_delta.IsZero()) { 123 if (initialized_) { 124 Release(current_time); 125 UpdateLayerAttachment(overscrolling_layer); 126 } 127 return NeedsAnimate(); 128 } 129 130 if (!InitializeIfNecessary()) 131 return false; 132 133 gfx::Vector2dF old_overscroll = accumulated_overscroll - overscroll_delta; 134 bool x_overscroll_started = 135 !IsApproxZero(overscroll_delta.x()) && IsApproxZero(old_overscroll.x()); 136 bool y_overscroll_started = 137 !IsApproxZero(overscroll_delta.y()) && IsApproxZero(old_overscroll.y()); 138 139 velocity = ZeroSmallComponents(velocity); 140 if (!velocity.IsZero()) 141 Absorb(current_time, velocity, x_overscroll_started, y_overscroll_started); 142 else 143 Pull(current_time, overscroll_delta, displacement); 144 145 UpdateLayerAttachment(overscrolling_layer); 146 return NeedsAnimate(); 147} 148 149bool OverscrollGlow::Animate(base::TimeTicks current_time) { 150 if (!NeedsAnimate()) { 151 Detach(); 152 return false; 153 } 154 155 for (size_t i = 0; i < EDGE_COUNT; ++i) { 156 if (edge_effects_[i]->Update(current_time)) { 157 Edge edge = static_cast<Edge>(i); 158 edge_effects_[i]->ApplyToLayers( 159 ComputeSize(edge, display_params_.size), 160 ComputeTransform( 161 edge, display_params_.size, display_params_.edge_offsets[i])); 162 } 163 } 164 165 if (!NeedsAnimate()) { 166 Detach(); 167 return false; 168 } 169 170 return true; 171} 172 173void OverscrollGlow::UpdateDisplayParameters(const DisplayParameters& params) { 174 display_params_ = params; 175} 176 177bool OverscrollGlow::NeedsAnimate() const { 178 if (!enabled_ || !initialized_) 179 return false; 180 for (size_t i = 0; i < EDGE_COUNT; ++i) { 181 if (!edge_effects_[i]->IsFinished()) 182 return true; 183 } 184 return false; 185} 186 187void OverscrollGlow::UpdateLayerAttachment(cc::Layer* parent) { 188 DCHECK(parent); 189 if (!root_layer_) 190 return; 191 192 if (!NeedsAnimate()) { 193 Detach(); 194 return; 195 } 196 197 if (root_layer_->parent() != parent) 198 parent->AddChild(root_layer_); 199 200 for (size_t i = 0; i < EDGE_COUNT; ++i) 201 edge_effects_[i]->SetParent(root_layer_); 202} 203 204void OverscrollGlow::Detach() { 205 if (root_layer_) 206 root_layer_->RemoveFromParent(); 207} 208 209bool OverscrollGlow::InitializeIfNecessary() { 210 DCHECK(enabled_); 211 if (initialized_) 212 return true; 213 214 DCHECK(!root_layer_); 215 root_layer_ = cc::Layer::Create(); 216 for (size_t i = 0; i < EDGE_COUNT; ++i) { 217 edge_effects_[i] = edge_effect_provider_.Run(); 218 DCHECK(edge_effects_[i]); 219 } 220 221 initialized_ = true; 222 return true; 223} 224 225void OverscrollGlow::Pull(base::TimeTicks current_time, 226 const gfx::Vector2dF& overscroll_delta, 227 const gfx::Vector2dF& overscroll_location) { 228 DCHECK(enabled_ && initialized_); 229 DCHECK(!overscroll_delta.IsZero()); 230 const float inv_width = 1.f / display_params_.size.width(); 231 const float inv_height = 1.f / display_params_.size.height(); 232 233 gfx::Vector2dF overscroll_pull = 234 gfx::ScaleVector2d(overscroll_delta, inv_width, inv_height); 235 const float edge_pull[EDGE_COUNT] = { 236 min(overscroll_pull.y(), 0.f), // Top 237 min(overscroll_pull.x(), 0.f), // Left 238 max(overscroll_pull.y(), 0.f), // Bottom 239 max(overscroll_pull.x(), 0.f) // Right 240 }; 241 242 gfx::Vector2dF displacement = 243 gfx::ScaleVector2d(overscroll_location, inv_width, inv_height); 244 displacement.set_x(max(0.f, min(1.f, displacement.x()))); 245 displacement.set_y(max(0.f, min(1.f, displacement.y()))); 246 const float edge_displacement[EDGE_COUNT] = { 247 1.f - displacement.x(), // Top 248 displacement.y(), // Left 249 displacement.x(), // Bottom 250 1.f - displacement.y() // Right 251 }; 252 253 for (size_t i = 0; i < EDGE_COUNT; ++i) { 254 if (!edge_pull[i]) 255 continue; 256 257 edge_effects_[i]->Pull( 258 current_time, std::abs(edge_pull[i]), edge_displacement[i]); 259 GetOppositeEdge(i)->Release(current_time); 260 } 261} 262 263void OverscrollGlow::Absorb(base::TimeTicks current_time, 264 const gfx::Vector2dF& velocity, 265 bool x_overscroll_started, 266 bool y_overscroll_started) { 267 DCHECK(enabled_ && initialized_); 268 DCHECK(!velocity.IsZero()); 269 270 // Only trigger on initial overscroll at a non-zero velocity 271 const float overscroll_velocities[EDGE_COUNT] = { 272 y_overscroll_started ? min(velocity.y(), 0.f) : 0, // Top 273 x_overscroll_started ? min(velocity.x(), 0.f) : 0, // Left 274 y_overscroll_started ? max(velocity.y(), 0.f) : 0, // Bottom 275 x_overscroll_started ? max(velocity.x(), 0.f) : 0 // Right 276 }; 277 278 for (size_t i = 0; i < EDGE_COUNT; ++i) { 279 if (!overscroll_velocities[i]) 280 continue; 281 282 edge_effects_[i]->Absorb(current_time, std::abs(overscroll_velocities[i])); 283 GetOppositeEdge(i)->Release(current_time); 284 } 285} 286 287void OverscrollGlow::Release(base::TimeTicks current_time) { 288 DCHECK(initialized_); 289 for (size_t i = 0; i < EDGE_COUNT; ++i) 290 edge_effects_[i]->Release(current_time); 291} 292 293EdgeEffectBase* OverscrollGlow::GetOppositeEdge(int edge_index) { 294 DCHECK(initialized_); 295 return edge_effects_[(edge_index + 2) % EDGE_COUNT].get(); 296} 297 298OverscrollGlow::DisplayParameters::DisplayParameters() { 299 edge_offsets[0] = edge_offsets[1] = edge_offsets[2] = edge_offsets[3] = 0.f; 300} 301 302} // namespace content 303