Home | History | Annotate | Download | only in android
      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 // Min/max velocity that will be absorbed
     40 const float kMinVelocity = 100.f;
     41 const float kMaxVelocity = 10000.f;
     42 
     43 const float kEpsilon = 0.001f;
     44 
     45 // How much dragging should effect the height of the edge image.
     46 // Number determined by user testing.
     47 const int kPullDistanceEdgeFactor = 7;
     48 
     49 // How much dragging should effect the height of the glow image.
     50 // Number determined by user testing.
     51 const int kPullDistanceGlowFactor = 7;
     52 const float kPullDistanceAlphaGlowFactor = 1.1f;
     53 
     54 const int kVelocityEdgeFactor = 8;
     55 const int kVelocityGlowFactor = 12;
     56 
     57 template <typename T>
     58 T Lerp(T a, T b, T t) {
     59   return a + (b - a) * t;
     60 }
     61 
     62 template <typename T>
     63 T Clamp(T value, T low, T high) {
     64    return value < low ? low : (value > high ? high : value);
     65 }
     66 
     67 template <typename T>
     68 T 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 
     78 gfx::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 
     97 void DisableLayer(cc::Layer* layer) {
     98   DCHECK(layer);
     99   layer->SetIsDrawable(false);
    100   layer->SetTransform(gfx::Transform());
    101   layer->SetOpacity(1.f);
    102 }
    103 
    104 void 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 
    118 EdgeEffect::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 
    145 EdgeEffect::~EdgeEffect() { }
    146 
    147 bool EdgeEffect::IsFinished() const {
    148   return state_ == STATE_IDLE;
    149 }
    150 
    151 void EdgeEffect::Finish() {
    152   DisableLayer(edge_.get());
    153   DisableLayer(glow_.get());
    154   pull_distance_ = 0;
    155   state_ = STATE_IDLE;
    156 }
    157 
    158 void 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 
    200 void 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 
    221 void 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 
    258 bool 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 
    331 void 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