Chromium Code Reviews| Index: views/animation/screen_rotation.cc |
| diff --git a/views/animation/screen_rotation.cc b/views/animation/screen_rotation.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..fe4af306be414e1bffb74eb1c3fa4abebe241ed3 |
| --- /dev/null |
| +++ b/views/animation/screen_rotation.cc |
| @@ -0,0 +1,207 @@ |
| +// Copyright (c) 2011 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 "views/animation/screen_rotation.h" |
| + |
| +#include "base/debug/trace_event.h" |
| +#include "base/message_loop.h" |
| +#include "base/task.h" |
| +#include "ui/base/animation/slide_animation.h" |
| +#include "ui/gfx/compositor/layer.h" |
| +#include "ui/gfx/interpolated_transform.h" |
| +#include "ui/gfx/rect.h" |
| +#include "ui/gfx/transform.h" |
| +#include "views/view.h" |
| + |
| +namespace { |
| +const int kDefaultTransitionDurationMs = 500; |
|
sky
2011/08/25 03:00:13
500ms is pretty long for an animation, are you sur
|
| + |
| +} // namespace |
| + |
| +namespace views { |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// ScreenRotation private: |
|
sky
2011/08/25 03:00:13
private -> public
|
| +// |
| + |
| +ScreenRotation::ScreenRotation(View* view, |
| + float old_degrees, |
| + float new_degrees) |
| + : view_(view), |
| + listener_(NULL), |
| + old_degrees_(old_degrees), |
| + new_degrees_(new_degrees), |
| + last_t_(0.0), |
| + done_(false), |
| + duration_(kDefaultTransitionDurationMs), |
| + ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { |
| + Start(); |
| +} |
| + |
| +ScreenRotation::~ScreenRotation() { |
| + animation_.reset(); |
|
sky
2011/08/25 03:00:13
Why do you need to explicitly stop the animation h
|
| +} |
| + |
| +void ScreenRotation::Finalize() { |
| + done_ = true; |
|
sky
2011/08/25 03:00:13
DCHECK(!done_) ?
|
| + view_->SetBounds(0, 0, new_size_.width(), new_size_.height()); |
| + view_->SchedulePaint(); |
| + if (listener_) |
| + listener_->OnScreenRotationCompleted(this); |
| +} |
| + |
| +void ScreenRotation::UpdateTarget(float degrees) { |
| + if (new_degrees_ == degrees) |
| + return; |
| + |
| + new_degrees_ = degrees; |
| + Init(); |
| +} |
| + |
| +void ScreenRotation::AnimationProgressed(const ui::Animation* anim) { |
| + if (!interpolated_transform_.get() || !view_->layer()) |
| + return; |
| + |
| + last_t_ = static_cast<float>(anim->GetCurrentValue()); |
| + if (view_->painting_enabled()) |
| + view_->set_painting_enabled(false); |
|
sky
2011/08/25 03:00:13
Why do you need to disable painting here? Assuming
|
| + |
| + view_->layer()->SetTransform(interpolated_transform_->Interpolate(last_t_)); |
| + view_->layer()->compositor()->SchedulePaint(); |
| +} |
| + |
| +void ScreenRotation::AnimationEnded(const ui::Animation* anim) { |
| + TRACE_EVENT_END0("ScreenRotation", "Start rotating"); |
| + // TODO(vollick) massage matrix so that entries sufficiently close |
| + // to 0, 1, or -1 are clamped to these values. The idea is to fight |
| + // accumulated numeric error due to successive rotations. |
| + if (view_->layer()) { |
| + ui::Transform xform = view_->layer()->transform(); |
| + gfx::Point origin; |
| + xform.TransformPoint(origin); |
| + ui::Transform translation; |
| + translation.SetTranslate(new_origin_.x() - origin.x(), |
| + new_origin_.y() - origin.y()); |
| + xform.ConcatTransform(translation); |
| + view_->layer()->SetTransform(xform); |
| + view_->layer()->compositor()->SchedulePaint(); |
| + } |
| + view_->set_painting_enabled(true); |
| + |
| + // the last call to SchedulePaint may not have been processed yet, so if we |
| + // set the bounds here, we may hang the ui in an 'almost-rotated' state for |
| + // a moment while we layout. If we post a task to execute immediately, it |
| + // will get processed after SchedulePaint, and we should be safe. |
| + MessageLoop::current()->PostDelayedTask( |
| + FROM_HERE, |
| + method_factory_.NewRunnableMethod(&ScreenRotation::Finalize), |
|
sky
2011/08/25 03:00:13
ick. This seems easy to mess up, but I don't curre
|
| + 1); |
| +} |
| + |
| +void ScreenRotation::Init() { |
| + if (!view_->layer()) { |
| + view_->SetPaintToLayer(true); |
| + view_->SchedulePaint(); |
| + } |
| + |
| + // can't proceed without a layer. |
| + if (!view_->layer()) |
| + return; |
|
sky
2011/08/25 03:00:13
If you early return here or at 121 should the anim
|
| + |
| + // cache the inital transformation matrix. |
| + if (!old_transform_.get()) |
| + old_transform_.reset(new ui::Transform(view_->layer()->transform())); |
|
sky
2011/08/25 03:00:13
How come you don't always reset old_transform_?
|
| + |
| + int degrees = new_degrees_ - old_degrees_; |
| + degrees = NormalizeAngle(degrees); |
| + |
| + // No rotation required. |
| + if (degrees == 0) |
| + return; |
| + |
| + gfx::Point old_pivot; |
| + gfx::Point new_pivot; |
| + int width = view_->layer()->bounds().width(); |
| + int height = view_->layer()->bounds().height(); |
| + |
| + switch (degrees) { |
| + case 90: |
| + new_origin_ = new_pivot = gfx::Point(width, 0); |
| + new_size_.SetSize(height, width); |
| + break; |
| + case -90: |
| + new_origin_ = new_pivot = gfx::Point(0, height); |
| + new_size_.SetSize(height, width); |
| + break; |
| + case 180: |
| + duration_ = 750; |
| + new_pivot = old_pivot = gfx::Point(width / 2, height / 2); |
| + new_origin_.SetPoint(width, height); |
| + new_size_.SetSize(width, height); |
| + break; |
| + } |
| + |
| + // Convert points to world space. |
| + old_transform_->TransformPoint(old_pivot); |
| + old_transform_->TransformPoint(new_pivot); |
| + old_transform_->TransformPoint(new_origin_); |
| + |
| + scoped_ptr<ui::InterpolatedTransform> rotation( |
| + new ui::InterpolatedTransformAboutPivot( |
| + old_pivot, |
| + new ui::InterpolatedRotation(0, degrees))); |
| + |
| + scoped_ptr<ui::InterpolatedTransform> translation( |
| + new ui::InterpolatedTranslation( |
| + gfx::Point(0, 0), |
| + gfx::Point(new_pivot.x() - old_pivot.x(), |
| + new_pivot.y() - old_pivot.y()))); |
| + |
| + float scale_factor = 0.9f; |
| + scoped_ptr<ui::InterpolatedTransform> scale_down( |
| + new ui::InterpolatedScale(1.0f, scale_factor, 0.0f, 0.5f)); |
| + |
| + scoped_ptr<ui::InterpolatedTransform> scale_up( |
| + new ui::InterpolatedScale(1.0f, 1.0f / scale_factor, 0.5f, 1.0f)); |
| + |
| + scoped_ptr<ui::InterpolatedTransform> transition( |
| + new ui::InterpolatedConstantTransform(*old_transform_)); |
| + |
| + scale_up->SetChild(scale_down.release()); |
| + translation->SetChild(scale_up.release()); |
| + rotation->SetChild(translation.release()); |
| + transition->SetChild(rotation.release()); |
| + |
| + if (interpolated_transform_.get()) { |
| + // We are in the middle of a transition. In this case, we need to create |
| + // an interpolated transform that gets us from where we are to the target |
| + // transform. |
| + ui::Transform current = view_->layer()->transform(); |
| + ui::Transform target = transition->Interpolate(1.0); |
| + interpolated_transform_.reset(new ui::InterpolatedTRSTransform(current, |
| + target, |
| + last_t_, |
| + 1.0)); |
| + } else { |
| + interpolated_transform_.reset(transition.release()); |
| + } |
| +} |
| + |
| +void ScreenRotation::Start() { |
| + TRACE_EVENT_BEGIN0("ScreenRotation", "Start rotating"); |
| + Init(); |
| + animation_.reset(new ui::SlideAnimation(this)); |
| + animation_->SetTweenType(ui::Tween::LINEAR); |
| + animation_->SetSlideDuration(duration_); |
| + animation_->Show(); |
| +} |
| + |
| +int ScreenRotation::NormalizeAngle(int degrees) { |
| + while (degrees <= -180) degrees += 360; |
| + while (degrees > 180) degrees -= 360; |
| + return degrees; |
| +} |
| + |
| + |
| +} // namespace views |