Chromium Code Reviews| Index: chrome/browser/ui/touch/animation/screen_rotation.cc |
| diff --git a/chrome/browser/ui/touch/animation/screen_rotation.cc b/chrome/browser/ui/touch/animation/screen_rotation.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..1caed877c04eecd7d1ff6c201fc7bd37f21ddc6f |
| --- /dev/null |
| +++ b/chrome/browser/ui/touch/animation/screen_rotation.cc |
| @@ -0,0 +1,224 @@ |
| +// 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 "chrome/browser/ui/touch/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/animation/paint_lock.h" |
| +#include "views/view.h" |
| + |
| +namespace { |
| +const int kDefaultTransitionDurationMs = 350; |
| + |
| +} // namespace |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// ScreenRotationListenerInterface public: |
| +// |
| + |
| +ScreenRotationListenerInterface::~ScreenRotationListenerInterface() { |
| +} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// ScreenRotation public: |
| +// |
| + |
| +ScreenRotation::ScreenRotation(views::View* view, |
| + ScreenRotationListenerInterface* listener, |
| + float old_degrees, |
| + float new_degrees) |
| + : view_(view), |
| + listener_(listener), |
| + old_degrees_(old_degrees), |
| + new_degrees_(new_degrees), |
| + last_t_(0.0), |
| + duration_(kDefaultTransitionDurationMs), |
| + ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { |
| + DCHECK(view); |
| + DCHECK(listener); |
| + |
| + // A screen rotation is initiated via a call to set transform, which |
| + // initiates a paint. This paint is scheduled, but has not been processed. |
| + // If we post a delayed task, it will be in the queue after the paint, and |
|
sky
2011/08/25 21:10:12
This is going to be so racy because the paint come
|
| + // we'll be guaranteed to start animating after the paint is finished. |
| + // |
| + // TODO(vollick) Find a cleaner way of posting work to be done after painting |
| + // completes. |
| + MessageLoop::current()->PostDelayedTask( |
| + FROM_HERE, |
| + method_factory_.NewRunnableMethod(&ScreenRotation::Start), |
| + 1); |
| +} |
| + |
| +ScreenRotation::~ScreenRotation() { |
| +} |
| + |
| +void ScreenRotation::Finalize() { |
| + ui::Transform final_transform = view_->GetTransform(); |
| + gfx::Rect final_bounds(0, 0, new_size_.width(), new_size_.height()); |
| + listener_->OnScreenRotationCompleted(final_transform, final_bounds); |
| +} |
| + |
| +void ScreenRotation::SetTarget(float degrees) { |
| + if (new_degrees_ == degrees) |
| + return; |
| + |
| + new_degrees_ = degrees; |
| + Init(); |
| +} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// ScreenRotation private: |
| +// |
| + |
| +void ScreenRotation::AnimationProgressed(const ui::Animation* anim) { |
| + TRACE_EVENT0("ScreenRotation", "step"); |
| + if (!interpolated_transform_.get() || !view_->layer()) |
| + return; |
| + |
| + last_t_ = static_cast<float>(anim->GetCurrentValue()); |
| + view_->layer()->SetTransform(interpolated_transform_->Interpolate(last_t_)); |
| + view_->layer()->compositor()->SchedulePaint(); |
| +} |
| + |
| +void ScreenRotation::AnimationEnded(const ui::Animation* anim) { |
| + TRACE_EVENT_END0("ScreenRotation", "ScreenRotation"); |
| + // 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(); |
| + } |
| + |
| + // 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. |
| + // |
| + // TODO(vollick) Find a cleaner way of posting work to be done after painting |
| + // completes. |
| + MessageLoop::current()->PostDelayedTask( |
| + FROM_HERE, |
| + method_factory_.NewRunnableMethod(&ScreenRotation::Finalize), |
| + 1); |
| +} |
| + |
| +void ScreenRotation::Init() { |
| + TRACE_EVENT0("ScreenRotation", "init"); |
| + if (!view_->layer()) { |
| + view_->SetPaintToLayer(true); |
| + } |
| + |
| + // can't proceed without a layer. |
| + if (!view_->layer()) |
| + return; |
| + |
| + ui::Transform current_transform = view_->layer()->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_ = 550; |
| + 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. |
| + current_transform.TransformPoint(old_pivot); |
| + current_transform.TransformPoint(new_pivot); |
| + current_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(current_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 target = transition->Interpolate(1.0); |
| + interpolated_transform_.reset( |
| + new ui::InterpolatedTRSTransform( |
| + current_transform, target, last_t_, 1.0)); |
| + } else { |
| + interpolated_transform_.reset(transition.release()); |
| + } |
| +} |
| + |
| +void ScreenRotation::Start() { |
| + TRACE_EVENT_BEGIN0("ScreenRotation", "ScreenRotation"); |
| + Init(); |
| + if (interpolated_transform_.get()) { |
| + paint_lock_.reset(new views::PaintLock(view_)); |
| + animation_.reset(new ui::SlideAnimation(this)); |
| + animation_->SetTweenType(ui::Tween::LINEAR); |
| + animation_->SetSlideDuration(duration_); |
| + animation_->Show(); |
| + } else { |
| + Finalize(); |
| + } |
| +} |
| + |
| +int ScreenRotation::NormalizeAngle(int degrees) { |
| + while (degrees <= -180) degrees += 360; |
| + while (degrees > 180) degrees -= 360; |
| + return degrees; |
| +} |