From a8f2115c89679dea905482d9c189c1df78cfd26f Mon Sep 17 00:00:00 2001 From: "vollick@chromium.org" Date: Thu, 8 Sep 2011 15:30:02 +0000 Subject: With this CL animated rotations can be triggered by visiting URLs like about:rotate?right. The about handler fires orientation events that the TouchBrowserFrameView is listening for. When notified, the TBFV either rotates itself, or the views desktop (if it is active). The animation code lives in views/animation and knows nothing about sensors. BUG=none TEST=none Review URL: http://codereview.chromium.org/7273073 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@100148 0039d316-1c4b-4281-b951-d872f2087c98 --- .../browser/ui/touch/animation/screen_rotation.cc | 234 +++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 chrome/browser/ui/touch/animation/screen_rotation.cc (limited to '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 0000000..7f3a053 --- /dev/null +++ b/chrome/browser/ui/touch/animation/screen_rotation.cc @@ -0,0 +1,234 @@ +// 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/paint_lock.h" +#include "views/view.h" +#include "views/widget/widget.h" + +namespace { +const int kDefaultTransitionDurationMs = 350; + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// ScreenRotationListener public: +// + +ScreenRotationListener::~ScreenRotationListener() { +} + +//////////////////////////////////////////////////////////////////////////////// +// ScreenRotation public: +// + +ScreenRotation::ScreenRotation(views::View* view, + ScreenRotationListener* listener, + float old_degrees, + float new_degrees) + : view_(view), + widget_(view->GetWidget()), + listener_(listener), + old_degrees_(old_degrees), + new_degrees_(new_degrees), + last_t_(0.0), + duration_(kDefaultTransitionDurationMs), + animation_started_(false), + animation_stopped_(false) { + DCHECK(view); + DCHECK(listener); + + if (!view->layer() || !widget_) { + Finalize(); + } else { + // Screen rotations are trigged as a result of a call to SetTransform which + // causes a paint to be scheduled to occur. At this point, the paint has + // been scheduled, but has not yet been started. We will listen for this + // paint to be completed before we start animating. + view->layer()->compositor()->AddObserver(this); + } +} + +ScreenRotation::~ScreenRotation() { + if (view_->layer()) + view_->layer()->compositor()->RemoveObserver(this); +} + +void ScreenRotation::SetTarget(float degrees) { + if (new_degrees_ == degrees) + return; + + new_degrees_ = degrees; + Init(); +} + +void ScreenRotation::Stop() { + animation_.reset(); + if (view_->layer()) { + if (!interpolated_transform_.get()) { + // attempt to initialize. + Init(); + } + if (interpolated_transform_.get()) { + view_->layer()->SetTransform(interpolated_transform_->Interpolate(1.0)); + view_->GetWidget()->SchedulePaintInRect( + view_->GetWidget()->GetClientAreaScreenBounds()); + } + } + Finalize(); +} + +//////////////////////////////////////////////////////////////////////////////// +// ScreenRotation private: +// + +void ScreenRotation::AnimationProgressed(const ui::Animation* anim) { + TRACE_EVENT0("ScreenRotation", "step"); + last_t_ = static_cast(anim->GetCurrentValue()); + view_->layer()->SetTransform(interpolated_transform_->Interpolate(last_t_)); + widget_->SchedulePaintInRect(widget_->GetClientAreaScreenBounds()); +} + +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. + 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); + widget_->SchedulePaintInRect(widget_->GetClientAreaScreenBounds()); + animation_stopped_ = true; +} + +void ScreenRotation::OnCompositingEnded() { + DoPendingWork(); +} + +void ScreenRotation::Init() { + TRACE_EVENT0("ScreenRotation", "init"); + + 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 rotation( + new ui::InterpolatedTransformAboutPivot( + old_pivot, + new ui::InterpolatedRotation(0, degrees))); + + scoped_ptr 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 scale_down( + new ui::InterpolatedScale(1.0f, scale_factor, 0.0f, 0.5f)); + + scoped_ptr scale_up( + new ui::InterpolatedScale(1.0f, 1.0f / scale_factor, 0.5f, 1.0f)); + + scoped_ptr 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(); + animation_started_ = true; + } else { + Finalize(); + } +} + +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); +} + +int ScreenRotation::NormalizeAngle(int degrees) { + while (degrees <= -180) degrees += 360; + while (degrees > 180) degrees -= 360; + return degrees; +} + +void ScreenRotation::DoPendingWork() { + if (!animation_started_) + Start(); + else if (animation_stopped_) { + view_->layer()->compositor()->RemoveObserver(this); + Finalize(); + } +} -- cgit v1.1