1 // Copyright 2012 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 "cc/input/page_scale_animation.h" 6 7 #include <math.h> 8 9 #include "base/logging.h" 10 #include "cc/animation/timing_function.h" 11 #include "ui/gfx/point_f.h" 12 #include "ui/gfx/rect_f.h" 13 #include "ui/gfx/vector2d_conversions.h" 14 15 namespace { 16 17 // This takes a viewport-relative vector and returns a vector whose values are 18 // between 0 and 1, representing the percentage position within the viewport. 19 gfx::Vector2dF NormalizeFromViewport(const gfx::Vector2dF& denormalized, 20 const gfx::SizeF& viewport_size) { 21 return gfx::ScaleVector2d(denormalized, 22 1.f / viewport_size.width(), 23 1.f / viewport_size.height()); 24 } 25 26 gfx::Vector2dF DenormalizeToViewport(const gfx::Vector2dF& normalized, 27 const gfx::SizeF& viewport_size) { 28 return gfx::ScaleVector2d(normalized, 29 viewport_size.width(), 30 viewport_size.height()); 31 } 32 33 gfx::Vector2dF InterpolateBetween(const gfx::Vector2dF& start, 34 const gfx::Vector2dF& end, 35 float interp) { 36 return start + gfx::ScaleVector2d(end - start, interp); 37 } 38 39 } // namespace 40 41 namespace cc { 42 43 using base::TimeTicks; 44 using base::TimeDelta; 45 46 scoped_ptr<PageScaleAnimation> PageScaleAnimation::Create( 47 const gfx::Vector2dF& start_scroll_offset, 48 float start_page_scale_factor, 49 const gfx::SizeF& viewport_size, 50 const gfx::SizeF& root_layer_size, 51 scoped_ptr<TimingFunction> timing_function) { 52 return make_scoped_ptr(new PageScaleAnimation(start_scroll_offset, 53 start_page_scale_factor, 54 viewport_size, 55 root_layer_size, 56 timing_function.Pass())); 57 } 58 59 PageScaleAnimation::PageScaleAnimation( 60 const gfx::Vector2dF& start_scroll_offset, 61 float start_page_scale_factor, 62 const gfx::SizeF& viewport_size, 63 const gfx::SizeF& root_layer_size, 64 scoped_ptr<TimingFunction> timing_function) 65 : start_page_scale_factor_(start_page_scale_factor), 66 target_page_scale_factor_(0.f), 67 start_scroll_offset_(start_scroll_offset), 68 start_anchor_(), 69 target_anchor_(), 70 viewport_size_(viewport_size), 71 root_layer_size_(root_layer_size), 72 timing_function_(timing_function.Pass()) { 73 } 74 75 PageScaleAnimation::~PageScaleAnimation() {} 76 77 void PageScaleAnimation::ZoomTo(const gfx::Vector2dF& target_scroll_offset, 78 float target_page_scale_factor, 79 double duration) { 80 target_page_scale_factor_ = target_page_scale_factor; 81 target_scroll_offset_ = target_scroll_offset; 82 ClampTargetScrollOffset(); 83 duration_ = TimeDelta::FromSecondsD(duration); 84 85 if (start_page_scale_factor_ == target_page_scale_factor) { 86 start_anchor_ = start_scroll_offset_; 87 target_anchor_ = target_scroll_offset; 88 return; 89 } 90 91 // For uniform-looking zooming, infer an anchor from the start and target 92 // viewport rects. 93 InferTargetAnchorFromScrollOffsets(); 94 start_anchor_ = target_anchor_; 95 } 96 97 void PageScaleAnimation::ZoomWithAnchor(const gfx::Vector2dF& anchor, 98 float target_page_scale_factor, 99 double duration) { 100 start_anchor_ = anchor; 101 target_page_scale_factor_ = target_page_scale_factor; 102 duration_ = TimeDelta::FromSecondsD(duration); 103 104 // We start zooming out from the anchor tapped by the user. But if 105 // the target scale is impossible to attain without hitting the root layer 106 // edges, then infer an anchor that doesn't collide with the edges. 107 // We will interpolate between the two anchors during the animation. 108 InferTargetScrollOffsetFromStartAnchor(); 109 ClampTargetScrollOffset(); 110 111 if (start_page_scale_factor_ == target_page_scale_factor_) { 112 target_anchor_ = start_anchor_; 113 return; 114 } 115 InferTargetAnchorFromScrollOffsets(); 116 } 117 118 void PageScaleAnimation::InferTargetScrollOffsetFromStartAnchor() { 119 gfx::Vector2dF normalized = NormalizeFromViewport( 120 start_anchor_ - start_scroll_offset_, StartViewportSize()); 121 target_scroll_offset_ = 122 start_anchor_ - DenormalizeToViewport(normalized, TargetViewportSize()); 123 } 124 125 void PageScaleAnimation::InferTargetAnchorFromScrollOffsets() { 126 // The anchor is the point which is at the same normalized relative position 127 // within both start viewport rect and target viewport rect. For example, a 128 // zoom-in double-tap to a perfectly centered rect will have normalized 129 // anchor (0.5, 0.5), while one to a rect touching the bottom-right of the 130 // screen will have normalized anchor (1.0, 1.0). In other words, it obeys 131 // the equations: 132 // anchor = start_size * normalized + start_offset 133 // anchor = target_size * normalized + target_offset 134 // where both anchor and normalized begin as unknowns. Solving 135 // for the normalized, we get the following: 136 float width_scale = 137 1.f / (TargetViewportSize().width() - StartViewportSize().width()); 138 float height_scale = 139 1.f / (TargetViewportSize().height() - StartViewportSize().height()); 140 gfx::Vector2dF normalized = gfx::ScaleVector2d( 141 start_scroll_offset_ - target_scroll_offset_, width_scale, height_scale); 142 target_anchor_ = 143 target_scroll_offset_ + DenormalizeToViewport(normalized, 144 TargetViewportSize()); 145 } 146 147 void PageScaleAnimation::ClampTargetScrollOffset() { 148 gfx::Vector2dF max_scroll_offset = 149 gfx::RectF(root_layer_size_).bottom_right() - 150 gfx::RectF(TargetViewportSize()).bottom_right(); 151 target_scroll_offset_.SetToMax(gfx::Vector2dF()); 152 target_scroll_offset_.SetToMin(max_scroll_offset); 153 } 154 155 gfx::SizeF PageScaleAnimation::StartViewportSize() const { 156 return gfx::ScaleSize(viewport_size_, 1.f / start_page_scale_factor_); 157 } 158 159 gfx::SizeF PageScaleAnimation::TargetViewportSize() const { 160 return gfx::ScaleSize(viewport_size_, 1.f / target_page_scale_factor_); 161 } 162 163 gfx::SizeF PageScaleAnimation::ViewportSizeAt(float interp) const { 164 return gfx::ScaleSize(viewport_size_, 1.f / PageScaleFactorAt(interp)); 165 } 166 167 bool PageScaleAnimation::IsAnimationStarted() const { 168 return start_time_ > base::TimeTicks(); 169 } 170 171 void PageScaleAnimation::StartAnimation(base::TimeTicks time) { 172 DCHECK(start_time_.is_null()); 173 start_time_ = time; 174 } 175 176 gfx::Vector2dF PageScaleAnimation::ScrollOffsetAtTime( 177 base::TimeTicks time) const { 178 DCHECK(!start_time_.is_null()); 179 return ScrollOffsetAt(InterpAtTime(time)); 180 } 181 182 float PageScaleAnimation::PageScaleFactorAtTime(base::TimeTicks time) const { 183 DCHECK(!start_time_.is_null()); 184 return PageScaleFactorAt(InterpAtTime(time)); 185 } 186 187 bool PageScaleAnimation::IsAnimationCompleteAtTime(base::TimeTicks time) const { 188 DCHECK(!start_time_.is_null()); 189 return time >= end_time(); 190 } 191 192 float PageScaleAnimation::InterpAtTime(base::TimeTicks monotonic_time) const { 193 DCHECK(!start_time_.is_null()); 194 DCHECK(monotonic_time >= start_time_); 195 if (IsAnimationCompleteAtTime(monotonic_time)) 196 return 1.f; 197 const double normalized_time = 198 (monotonic_time - start_time_).InSecondsF() / duration_.InSecondsF(); 199 return timing_function_->GetValue(normalized_time); 200 } 201 202 gfx::Vector2dF PageScaleAnimation::ScrollOffsetAt(float interp) const { 203 if (interp <= 0.f) 204 return start_scroll_offset_; 205 if (interp >= 1.f) 206 return target_scroll_offset_; 207 208 return AnchorAt(interp) - ViewportRelativeAnchorAt(interp); 209 } 210 211 gfx::Vector2dF PageScaleAnimation::AnchorAt(float interp) const { 212 // Interpolate from start to target anchor in absolute space. 213 return InterpolateBetween(start_anchor_, target_anchor_, interp); 214 } 215 216 gfx::Vector2dF PageScaleAnimation::ViewportRelativeAnchorAt( 217 float interp) const { 218 // Interpolate from start to target anchor in normalized space. 219 gfx::Vector2dF start_normalized = 220 NormalizeFromViewport(start_anchor_ - start_scroll_offset_, 221 StartViewportSize()); 222 gfx::Vector2dF target_normalized = 223 NormalizeFromViewport(target_anchor_ - target_scroll_offset_, 224 TargetViewportSize()); 225 gfx::Vector2dF interp_normalized = 226 InterpolateBetween(start_normalized, target_normalized, interp); 227 228 return DenormalizeToViewport(interp_normalized, ViewportSizeAt(interp)); 229 } 230 231 float PageScaleAnimation::PageScaleFactorAt(float interp) const { 232 if (interp <= 0.f) 233 return start_page_scale_factor_; 234 if (interp >= 1.f) 235 return target_page_scale_factor_; 236 237 // Linearly interpolate the magnitude in log scale. 238 float diff = target_page_scale_factor_ / start_page_scale_factor_; 239 float log_diff = log(diff); 240 log_diff *= interp; 241 diff = exp(log_diff); 242 return start_page_scale_factor_ * diff; 243 } 244 245 } // namespace cc 246