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 // 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