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