| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/gfx/interpolated_transform.h" |
| |
| #include <cmath> |
| |
| #include "base/check.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "ui/gfx/animation/tween.h" |
| |
| namespace { |
| |
| static const double EPSILON = 1e-6; |
| |
| bool IsMultipleOfNinetyDegrees(double degrees) { |
| double remainder = fabs(fmod(degrees, 90.0)); |
| return remainder < EPSILON || 90.0 - remainder < EPSILON; |
| } |
| |
| // Returns false if |degrees| is not a multiple of ninety degrees or if |
| // |rotation| is NULL. It does not affect |rotation| in this case. Otherwise |
| // *rotation is set to be the appropriate sanitized rotation matrix. That is, |
| // the rotation matrix corresponding to |degrees| which has entries that are all |
| // either 0, 1 or -1. |
| bool MassageRotationIfMultipleOfNinetyDegrees(gfx::Transform* rotation, |
| float degrees) { |
| if (!IsMultipleOfNinetyDegrees(degrees) || !rotation) |
| return false; |
| |
| gfx::Transform transform; |
| skia::Matrix44& m = transform.matrix(); |
| float degrees_by_ninety = degrees / 90.0f; |
| |
| int n = base::ClampRound(degrees_by_ninety); |
| |
| n %= 4; |
| if (n < 0) |
| n += 4; |
| |
| // n should now be in the range [0, 3] |
| if (n == 1) { |
| m.set3x3( 0, 1, 0, |
| -1, 0, 0, |
| 0, 0, 1); |
| } else if (n == 2) { |
| m.set3x3(-1, 0, 0, |
| 0, -1, 0, |
| 0, 0, 1); |
| } else if (n == 3) { |
| m.set3x3( 0, -1, 0, |
| 1, 0, 0, |
| 0, 0, 1); |
| } |
| |
| *rotation = transform; |
| return true; |
| } |
| |
| } // namespace |
| |
| namespace ui { |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // InterpolatedTransform |
| // |
| |
| InterpolatedTransform::InterpolatedTransform() |
| : start_time_(0.0f), |
| end_time_(1.0f), |
| reversed_(false) { |
| } |
| |
| InterpolatedTransform::InterpolatedTransform(float start_time, |
| float end_time) |
| : start_time_(start_time), |
| end_time_(end_time), |
| reversed_(false) { |
| } |
| |
| InterpolatedTransform::~InterpolatedTransform() {} |
| |
| gfx::Transform InterpolatedTransform::Interpolate(float t) const { |
| if (reversed_) |
| t = 1.0f - t; |
| gfx::Transform result = InterpolateButDoNotCompose(t); |
| if (child_.get()) { |
| result.ConcatTransform(child_->Interpolate(t)); |
| } |
| return result; |
| } |
| |
| void InterpolatedTransform::SetChild( |
| std::unique_ptr<InterpolatedTransform> child) { |
| child_ = std::move(child); |
| } |
| |
| inline float InterpolatedTransform::ValueBetween(float time, |
| float start_value, |
| float end_value) const { |
| // can't handle NaN |
| DCHECK(time == time && start_time_ == start_time_ && end_time_ == end_time_); |
| if (time != time || start_time_ != start_time_ || end_time_ != end_time_) |
| return start_value; |
| |
| // Ok if equal -- we'll get a step function. Note: if end_time_ == |
| // start_time_ == x, then if none of the numbers are NaN, then it |
| // must be true that time < x or time >= x, so we will return early |
| // due to one of the following if statements. |
| DCHECK(end_time_ >= start_time_); |
| |
| if (time < start_time_) |
| return start_value; |
| |
| if (time >= end_time_) |
| return end_value; |
| |
| float t = (time - start_time_) / (end_time_ - start_time_); |
| return static_cast<float>( |
| gfx::Tween::DoubleValueBetween(t, start_value, end_value)); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // InterpolatedRotation |
| // |
| |
| InterpolatedRotation::InterpolatedRotation(float start_degrees, |
| float end_degrees) |
| : InterpolatedTransform(), |
| start_degrees_(start_degrees), |
| end_degrees_(end_degrees) { |
| } |
| |
| InterpolatedRotation::InterpolatedRotation(float start_degrees, |
| float end_degrees, |
| float start_time, |
| float end_time) |
| : InterpolatedTransform(start_time, end_time), |
| start_degrees_(start_degrees), |
| end_degrees_(end_degrees) { |
| } |
| |
| InterpolatedRotation::~InterpolatedRotation() {} |
| |
| gfx::Transform InterpolatedRotation::InterpolateButDoNotCompose(float t) const { |
| gfx::Transform result; |
| float interpolated_degrees = ValueBetween(t, start_degrees_, end_degrees_); |
| result.Rotate(interpolated_degrees); |
| if (t == 0.0f || t == 1.0f) |
| MassageRotationIfMultipleOfNinetyDegrees(&result, interpolated_degrees); |
| return result; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // InterpolatedAxisAngleRotation |
| // |
| |
| InterpolatedAxisAngleRotation::InterpolatedAxisAngleRotation( |
| const gfx::Vector3dF& axis, |
| float start_degrees, |
| float end_degrees) |
| : InterpolatedTransform(), |
| axis_(axis), |
| start_degrees_(start_degrees), |
| end_degrees_(end_degrees) { |
| } |
| |
| InterpolatedAxisAngleRotation::InterpolatedAxisAngleRotation( |
| const gfx::Vector3dF& axis, |
| float start_degrees, |
| float end_degrees, |
| float start_time, |
| float end_time) |
| : InterpolatedTransform(start_time, end_time), |
| axis_(axis), |
| start_degrees_(start_degrees), |
| end_degrees_(end_degrees) { |
| } |
| |
| InterpolatedAxisAngleRotation::~InterpolatedAxisAngleRotation() {} |
| |
| gfx::Transform |
| InterpolatedAxisAngleRotation::InterpolateButDoNotCompose(float t) const { |
| gfx::Transform result; |
| result.RotateAbout(axis_, ValueBetween(t, start_degrees_, end_degrees_)); |
| return result; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // InterpolatedScale |
| // |
| |
| InterpolatedScale::InterpolatedScale(float start_scale, float end_scale) |
| : InterpolatedTransform(), |
| start_scale_(gfx::Point3F(start_scale, start_scale, start_scale)), |
| end_scale_(gfx::Point3F(end_scale, end_scale, end_scale)) { |
| } |
| |
| InterpolatedScale::InterpolatedScale(float start_scale, float end_scale, |
| float start_time, float end_time) |
| : InterpolatedTransform(start_time, end_time), |
| start_scale_(gfx::Point3F(start_scale, start_scale, start_scale)), |
| end_scale_(gfx::Point3F(end_scale, end_scale, end_scale)) { |
| } |
| |
| InterpolatedScale::InterpolatedScale(const gfx::Point3F& start_scale, |
| const gfx::Point3F& end_scale) |
| : InterpolatedTransform(), |
| start_scale_(start_scale), |
| end_scale_(end_scale) { |
| } |
| |
| InterpolatedScale::InterpolatedScale(const gfx::Point3F& start_scale, |
| const gfx::Point3F& end_scale, |
| float start_time, |
| float end_time) |
| : InterpolatedTransform(start_time, end_time), |
| start_scale_(start_scale), |
| end_scale_(end_scale) { |
| } |
| |
| InterpolatedScale::~InterpolatedScale() {} |
| |
| gfx::Transform InterpolatedScale::InterpolateButDoNotCompose(float t) const { |
| gfx::Transform result; |
| float scale_x = ValueBetween(t, start_scale_.x(), end_scale_.x()); |
| float scale_y = ValueBetween(t, start_scale_.y(), end_scale_.y()); |
| float scale_z = ValueBetween(t, start_scale_.z(), end_scale_.z()); |
| result.Scale3d(scale_x, scale_y, scale_z); |
| return result; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // InterpolatedTranslation |
| // |
| |
| InterpolatedTranslation::InterpolatedTranslation(const gfx::PointF& start_pos, |
| const gfx::PointF& end_pos) |
| : InterpolatedTransform(), start_pos_(start_pos), end_pos_(end_pos) {} |
| |
| InterpolatedTranslation::InterpolatedTranslation(const gfx::PointF& start_pos, |
| const gfx::PointF& end_pos, |
| float start_time, |
| float end_time) |
| : InterpolatedTransform(start_time, end_time), |
| start_pos_(start_pos), |
| end_pos_(end_pos) {} |
| |
| InterpolatedTranslation::InterpolatedTranslation(const gfx::Point3F& start_pos, |
| const gfx::Point3F& end_pos) |
| : InterpolatedTransform(), start_pos_(start_pos), end_pos_(end_pos) { |
| } |
| |
| InterpolatedTranslation::InterpolatedTranslation(const gfx::Point3F& start_pos, |
| const gfx::Point3F& end_pos, |
| float start_time, |
| float end_time) |
| : InterpolatedTransform(start_time, end_time), |
| start_pos_(start_pos), |
| end_pos_(end_pos) { |
| } |
| |
| InterpolatedTranslation::~InterpolatedTranslation() {} |
| |
| gfx::Transform |
| InterpolatedTranslation::InterpolateButDoNotCompose(float t) const { |
| gfx::Transform result; |
| result.Translate3d(ValueBetween(t, start_pos_.x(), end_pos_.x()), |
| ValueBetween(t, start_pos_.y(), end_pos_.y()), |
| ValueBetween(t, start_pos_.z(), end_pos_.z())); |
| return result; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // InterpolatedConstantTransform |
| // |
| |
| InterpolatedConstantTransform::InterpolatedConstantTransform( |
| const gfx::Transform& transform) |
| : InterpolatedTransform(), |
| transform_(transform) { |
| } |
| |
| gfx::Transform |
| InterpolatedConstantTransform::InterpolateButDoNotCompose(float t) const { |
| return transform_; |
| } |
| |
| InterpolatedConstantTransform::~InterpolatedConstantTransform() {} |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // InterpolatedTransformAboutPivot |
| // |
| |
| InterpolatedTransformAboutPivot::InterpolatedTransformAboutPivot( |
| const gfx::Point& pivot, |
| std::unique_ptr<InterpolatedTransform> transform) |
| : InterpolatedTransform() { |
| Init(pivot, std::move(transform)); |
| } |
| |
| InterpolatedTransformAboutPivot::InterpolatedTransformAboutPivot( |
| const gfx::Point& pivot, |
| std::unique_ptr<InterpolatedTransform> transform, |
| float start_time, |
| float end_time) |
| : InterpolatedTransform() { |
| Init(pivot, std::move(transform)); |
| } |
| |
| InterpolatedTransformAboutPivot::~InterpolatedTransformAboutPivot() {} |
| |
| gfx::Transform |
| InterpolatedTransformAboutPivot::InterpolateButDoNotCompose(float t) const { |
| if (transform_.get()) { |
| return transform_->Interpolate(t); |
| } |
| return gfx::Transform(); |
| } |
| |
| void InterpolatedTransformAboutPivot::Init( |
| const gfx::Point& pivot, |
| std::unique_ptr<InterpolatedTransform> xform) { |
| gfx::Transform to_pivot; |
| gfx::Transform from_pivot; |
| to_pivot.Translate(SkIntToScalar(-pivot.x()), SkIntToScalar(-pivot.y())); |
| from_pivot.Translate(SkIntToScalar(pivot.x()), SkIntToScalar(pivot.y())); |
| |
| std::unique_ptr<InterpolatedTransform> pre_transform = |
| std::make_unique<InterpolatedConstantTransform>(to_pivot); |
| std::unique_ptr<InterpolatedTransform> post_transform = |
| std::make_unique<InterpolatedConstantTransform>(from_pivot); |
| |
| xform->SetChild(std::move(post_transform)); |
| pre_transform->SetChild(std::move(xform)); |
| transform_ = std::move(pre_transform); |
| } |
| |
| InterpolatedMatrixTransform::InterpolatedMatrixTransform( |
| const gfx::Transform& start_transform, |
| const gfx::Transform& end_transform) |
| : InterpolatedTransform() { |
| Init(start_transform, end_transform); |
| } |
| |
| InterpolatedMatrixTransform::InterpolatedMatrixTransform( |
| const gfx::Transform& start_transform, |
| const gfx::Transform& end_transform, |
| float start_time, |
| float end_time) |
| : InterpolatedTransform() { |
| Init(start_transform, end_transform); |
| } |
| |
| InterpolatedMatrixTransform::~InterpolatedMatrixTransform() {} |
| |
| gfx::Transform |
| InterpolatedMatrixTransform::InterpolateButDoNotCompose(float t) const { |
| gfx::DecomposedTransform blended = |
| gfx::BlendDecomposedTransforms(end_decomp_, start_decomp_, t); |
| return gfx::ComposeTransform(blended); |
| } |
| |
| void InterpolatedMatrixTransform::Init(const gfx::Transform& start_transform, |
| const gfx::Transform& end_transform) { |
| bool success = gfx::DecomposeTransform(&start_decomp_, start_transform); |
| DCHECK(success); |
| success = gfx::DecomposeTransform(&end_decomp_, end_transform); |
| DCHECK(success); |
| } |
| |
| } // namespace ui |