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/edge_effect.h" 6 7 #include "cc/layers/layer.h" 8 #include "cc/layers/ui_resource_layer.h" 9 #include "ui/base/android/system_ui_resource_manager.h" 10 11 namespace content { 12 13 namespace { 14 15 // Time it will take the effect to fully recede in ms 16 const int kRecedeTimeMs = 1000; 17 18 // Time it will take before a pulled glow begins receding in ms 19 const int kPullTimeMs = 167; 20 21 // Time it will take in ms for a pulled glow to decay before release 22 const int kPullDecayTimeMs = 1000; 23 24 const float kMaxAlpha = 1.f; 25 const float kHeldEdgeScaleY = .5f; 26 27 const float kMaxGlowHeight = 4.f; 28 29 const float kPullGlowBegin = 1.f; 30 const float kPullEdgeBegin = 0.6f; 31 32 // Min/max velocity that will be absorbed 33 const float kMinVelocity = 100.f; 34 const float kMaxVelocity = 10000.f; 35 36 const float kEpsilon = 0.001f; 37 38 const float kGlowHeightWidthRatio = 0.25f; 39 40 // How much dragging should effect the height of the edge image. 41 // Number determined by user testing. 42 const int kPullDistanceEdgeFactor = 7; 43 44 // How much dragging should effect the height of the glow image. 45 // Number determined by user testing. 46 const int kPullDistanceGlowFactor = 7; 47 const float kPullDistanceAlphaGlowFactor = 1.1f; 48 49 const int kVelocityEdgeFactor = 8; 50 const int kVelocityGlowFactor = 12; 51 52 const float kEdgeHeightAtMdpi = 12.f; 53 const float kGlowHeightAtMdpi = 128.f; 54 55 template <typename T> 56 T Lerp(T a, T b, T t) { 57 return a + (b - a) * t; 58 } 59 60 template <typename T> 61 T Clamp(T value, T low, T high) { 62 return value < low ? low : (value > high ? high : value); 63 } 64 65 template <typename T> 66 T Damp(T input, T factor) { 67 T result; 68 if (factor == 1) { 69 result = 1 - (1 - input) * (1 - input); 70 } else { 71 result = 1 - std::pow(1 - input, 2 * factor); 72 } 73 return result; 74 } 75 76 } // namespace 77 78 class EdgeEffect::EffectLayer { 79 public: 80 EffectLayer(ui::SystemUIResourceManager::ResourceType resource_type, 81 ui::SystemUIResourceManager* resource_manager) 82 : ui_resource_layer_(cc::UIResourceLayer::Create()), 83 resource_type_(resource_type), 84 resource_manager_(resource_manager) {} 85 86 ~EffectLayer() { ui_resource_layer_->RemoveFromParent(); } 87 88 void SetParent(cc::Layer* parent) { 89 if (ui_resource_layer_->parent() != parent) 90 parent->AddChild(ui_resource_layer_); 91 ui_resource_layer_->SetUIResourceId( 92 resource_manager_->GetUIResourceId(resource_type_)); 93 } 94 95 void Disable() { ui_resource_layer_->SetIsDrawable(false); } 96 97 void Update(const gfx::Size& size, 98 const gfx::Transform& transform, 99 float opacity) { 100 ui_resource_layer_->SetUIResourceId( 101 resource_manager_->GetUIResourceId(resource_type_)); 102 ui_resource_layer_->SetIsDrawable(true); 103 ui_resource_layer_->SetTransformOrigin( 104 gfx::Point3F(size.width() * 0.5f, 0, 0)); 105 ui_resource_layer_->SetTransform(transform); 106 ui_resource_layer_->SetBounds(size); 107 ui_resource_layer_->SetOpacity(Clamp(opacity, 0.f, 1.f)); 108 } 109 110 scoped_refptr<cc::UIResourceLayer> ui_resource_layer_; 111 ui::SystemUIResourceManager::ResourceType resource_type_; 112 ui::SystemUIResourceManager* resource_manager_; 113 114 DISALLOW_COPY_AND_ASSIGN(EffectLayer); 115 }; 116 117 EdgeEffect::EdgeEffect(ui::SystemUIResourceManager* resource_manager, 118 float device_scale_factor) 119 : edge_(new EffectLayer(ui::SystemUIResourceManager::OVERSCROLL_EDGE, 120 resource_manager)), 121 glow_(new EffectLayer(ui::SystemUIResourceManager::OVERSCROLL_GLOW, 122 resource_manager)), 123 base_edge_height_(kEdgeHeightAtMdpi * device_scale_factor), 124 base_glow_height_(kGlowHeightAtMdpi * device_scale_factor), 125 edge_alpha_(0), 126 edge_scale_y_(0), 127 glow_alpha_(0), 128 glow_scale_y_(0), 129 edge_alpha_start_(0), 130 edge_alpha_finish_(0), 131 edge_scale_y_start_(0), 132 edge_scale_y_finish_(0), 133 glow_alpha_start_(0), 134 glow_alpha_finish_(0), 135 glow_scale_y_start_(0), 136 glow_scale_y_finish_(0), 137 state_(STATE_IDLE), 138 pull_distance_(0) { 139 } 140 141 EdgeEffect::~EdgeEffect() { 142 } 143 144 bool EdgeEffect::IsFinished() const { 145 return state_ == STATE_IDLE; 146 } 147 148 void EdgeEffect::Finish() { 149 edge_->Disable(); 150 glow_->Disable(); 151 pull_distance_ = 0; 152 state_ = STATE_IDLE; 153 } 154 155 void EdgeEffect::Pull(base::TimeTicks current_time, 156 float delta_distance, 157 float displacement) { 158 if (state_ == STATE_PULL_DECAY && current_time - start_time_ < duration_) { 159 return; 160 } 161 if (state_ != STATE_PULL) { 162 glow_scale_y_ = kPullGlowBegin; 163 } 164 state_ = STATE_PULL; 165 166 start_time_ = current_time; 167 duration_ = base::TimeDelta::FromMilliseconds(kPullTimeMs); 168 169 float abs_delta_distance = std::abs(delta_distance); 170 pull_distance_ += delta_distance; 171 float distance = std::abs(pull_distance_); 172 173 edge_alpha_ = edge_alpha_start_ = Clamp(distance, kPullEdgeBegin, kMaxAlpha); 174 edge_scale_y_ = edge_scale_y_start_ = 175 Clamp(distance * kPullDistanceEdgeFactor, kHeldEdgeScaleY, 1.f); 176 177 glow_alpha_ = glow_alpha_start_ = 178 std::min(kMaxAlpha, 179 glow_alpha_ + abs_delta_distance * kPullDistanceAlphaGlowFactor); 180 181 float glow_change = abs_delta_distance; 182 if (delta_distance > 0 && pull_distance_ < 0) 183 glow_change = -glow_change; 184 if (pull_distance_ == 0) 185 glow_scale_y_ = 0; 186 187 // Do not allow glow to get larger than kMaxGlowHeight. 188 glow_scale_y_ = glow_scale_y_start_ = 189 Clamp(glow_scale_y_ + glow_change * kPullDistanceGlowFactor, 190 0.f, 191 kMaxGlowHeight); 192 193 edge_alpha_finish_ = edge_alpha_; 194 edge_scale_y_finish_ = edge_scale_y_; 195 glow_alpha_finish_ = glow_alpha_; 196 glow_scale_y_finish_ = glow_scale_y_; 197 } 198 199 void EdgeEffect::Release(base::TimeTicks current_time) { 200 pull_distance_ = 0; 201 202 if (state_ != STATE_PULL && state_ != STATE_PULL_DECAY) 203 return; 204 205 state_ = STATE_RECEDE; 206 edge_alpha_start_ = edge_alpha_; 207 edge_scale_y_start_ = edge_scale_y_; 208 glow_alpha_start_ = glow_alpha_; 209 glow_scale_y_start_ = glow_scale_y_; 210 211 edge_alpha_finish_ = 0.f; 212 edge_scale_y_finish_ = 0.f; 213 glow_alpha_finish_ = 0.f; 214 glow_scale_y_finish_ = 0.f; 215 216 start_time_ = current_time; 217 duration_ = base::TimeDelta::FromMilliseconds(kRecedeTimeMs); 218 } 219 220 void EdgeEffect::Absorb(base::TimeTicks current_time, float velocity) { 221 state_ = STATE_ABSORB; 222 velocity = Clamp(std::abs(velocity), kMinVelocity, kMaxVelocity); 223 224 start_time_ = current_time; 225 // This should never be less than 1 millisecond. 226 duration_ = base::TimeDelta::FromMilliseconds(0.15f + (velocity * 0.02f)); 227 228 // The edge should always be at least partially visible, regardless 229 // of velocity. 230 edge_alpha_start_ = 0.f; 231 edge_scale_y_ = edge_scale_y_start_ = 0.f; 232 // The glow depends more on the velocity, and therefore starts out 233 // nearly invisible. 234 glow_alpha_start_ = 0.3f; 235 glow_scale_y_start_ = 0.f; 236 237 // Factor the velocity by 8. Testing on device shows this works best to 238 // reflect the strength of the user's scrolling. 239 edge_alpha_finish_ = Clamp(velocity * kVelocityEdgeFactor, 0.f, 1.f); 240 // Edge should never get larger than the size of its asset. 241 edge_scale_y_finish_ = 242 Clamp(velocity * kVelocityEdgeFactor, kHeldEdgeScaleY, 1.f); 243 244 // Growth for the size of the glow should be quadratic to properly 245 // respond 246 // to a user's scrolling speed. The faster the scrolling speed, the more 247 // intense the effect should be for both the size and the saturation. 248 glow_scale_y_finish_ = 249 std::min(0.025f + (velocity * (velocity / 100) * 0.00015f), 1.75f); 250 // Alpha should change for the glow as well as size. 251 glow_alpha_finish_ = Clamp( 252 glow_alpha_start_, velocity * kVelocityGlowFactor * .00001f, kMaxAlpha); 253 } 254 255 bool EdgeEffect::Update(base::TimeTicks current_time) { 256 if (IsFinished()) 257 return false; 258 259 const double dt = (current_time - start_time_).InMilliseconds(); 260 const double t = std::min(dt / duration_.InMilliseconds(), 1.); 261 const float interp = static_cast<float>(Damp(t, 1.)); 262 263 edge_alpha_ = Lerp(edge_alpha_start_, edge_alpha_finish_, interp); 264 edge_scale_y_ = Lerp(edge_scale_y_start_, edge_scale_y_finish_, interp); 265 glow_alpha_ = Lerp(glow_alpha_start_, glow_alpha_finish_, interp); 266 glow_scale_y_ = Lerp(glow_scale_y_start_, glow_scale_y_finish_, interp); 267 268 if (t >= 1.f - kEpsilon) { 269 switch (state_) { 270 case STATE_ABSORB: 271 state_ = STATE_RECEDE; 272 start_time_ = current_time; 273 duration_ = base::TimeDelta::FromMilliseconds(kRecedeTimeMs); 274 275 edge_alpha_start_ = edge_alpha_; 276 edge_scale_y_start_ = edge_scale_y_; 277 glow_alpha_start_ = glow_alpha_; 278 glow_scale_y_start_ = glow_scale_y_; 279 280 // After absorb, the glow and edge should fade to nothing. 281 edge_alpha_finish_ = 0.f; 282 edge_scale_y_finish_ = 0.f; 283 glow_alpha_finish_ = 0.f; 284 glow_scale_y_finish_ = 0.f; 285 break; 286 case STATE_PULL: 287 state_ = STATE_PULL_DECAY; 288 start_time_ = current_time; 289 duration_ = base::TimeDelta::FromMilliseconds(kPullDecayTimeMs); 290 291 edge_alpha_start_ = edge_alpha_; 292 edge_scale_y_start_ = edge_scale_y_; 293 glow_alpha_start_ = glow_alpha_; 294 glow_scale_y_start_ = glow_scale_y_; 295 296 // After pull, the glow and edge should fade to nothing. 297 edge_alpha_finish_ = 0.f; 298 edge_scale_y_finish_ = 0.f; 299 glow_alpha_finish_ = 0.f; 300 glow_scale_y_finish_ = 0.f; 301 break; 302 case STATE_PULL_DECAY: { 303 // When receding, we want edge to decrease more slowly 304 // than the glow. 305 const float factor = 306 glow_scale_y_finish_ 307 ? 1 / (glow_scale_y_finish_ * glow_scale_y_finish_) 308 : std::numeric_limits<float>::max(); 309 edge_scale_y_ = 310 edge_scale_y_start_ + 311 (edge_scale_y_finish_ - edge_scale_y_start_) * interp * factor; 312 state_ = STATE_RECEDE; 313 } break; 314 case STATE_RECEDE: 315 Finish(); 316 break; 317 default: 318 break; 319 } 320 } 321 322 if (state_ == STATE_RECEDE && glow_scale_y_ <= 0 && edge_scale_y_ <= 0) 323 Finish(); 324 325 return !IsFinished(); 326 } 327 328 void EdgeEffect::ApplyToLayers(const gfx::SizeF& size, 329 const gfx::Transform& transform) { 330 if (IsFinished()) 331 return; 332 333 // An empty window size, while meaningless, is also relatively harmless, and 334 // will simply prevent any drawing of the layers. 335 if (size.IsEmpty()) { 336 edge_->Disable(); 337 glow_->Disable(); 338 return; 339 } 340 341 // Glow 342 const int scaled_glow_height = static_cast<int>( 343 std::min(base_glow_height_ * glow_scale_y_ * kGlowHeightWidthRatio * 0.6f, 344 base_glow_height_ * kMaxGlowHeight) + 345 0.5f); 346 const gfx::Size glow_size(size.width(), scaled_glow_height); 347 glow_->Update(glow_size, transform, glow_alpha_); 348 349 // Edge 350 const int scaled_edge_height = 351 static_cast<int>(base_edge_height_ * edge_scale_y_); 352 const gfx::Size edge_size(size.width(), scaled_edge_height); 353 edge_->Update(edge_size, transform, edge_alpha_); 354 } 355 356 void EdgeEffect::SetParent(cc::Layer* parent) { 357 edge_->SetParent(parent); 358 glow_->SetParent(parent); 359 } 360 361 // static 362 void EdgeEffect::PreloadResources( 363 ui::SystemUIResourceManager* resource_manager) { 364 DCHECK(resource_manager); 365 resource_manager->PreloadResource( 366 ui::SystemUIResourceManager::OVERSCROLL_EDGE); 367 resource_manager->PreloadResource( 368 ui::SystemUIResourceManager::OVERSCROLL_GLOW); 369 } 370 371 } // namespace content 372