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