// 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/wm/core/window_animations.h" #include #include #include #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/logging.h" #include "base/macros.h" #include "base/message_loop/message_loop.h" #include "base/stl_util.h" #include "base/time/time.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" #include "ui/aura/window_delegate.h" #include "ui/aura/window_observer.h" #include "ui/aura/window_property.h" #include "ui/compositor/compositor_observer.h" #include "ui/compositor/layer.h" #include "ui/compositor/layer_animation_observer.h" #include "ui/compositor/layer_animation_sequence.h" #include "ui/compositor/layer_animator.h" #include "ui/compositor/layer_tree_owner.h" #include "ui/compositor/scoped_animation_duration_scale_mode.h" #include "ui/compositor/scoped_layer_animation_settings.h" #include "ui/gfx/animation/animation.h" #include "ui/gfx/geometry/rect_conversions.h" #include "ui/gfx/geometry/vector2d.h" #include "ui/gfx/geometry/vector3d_f.h" #include "ui/gfx/interpolated_transform.h" #include "ui/gfx/screen.h" #include "ui/wm/core/window_util.h" #include "ui/wm/core/wm_core_switches.h" #include "ui/wm/public/animation_host.h" DECLARE_WINDOW_PROPERTY_TYPE(wm::WindowVisibilityAnimationType) DECLARE_WINDOW_PROPERTY_TYPE(wm::WindowVisibilityAnimationTransition) DECLARE_WINDOW_PROPERTY_TYPE(float) namespace wm { namespace { const float kWindowAnimation_Vertical_TranslateY = 15.f; // A base class for hiding animation observer which has two roles: // 1) Notifies AnimationHost at the end of hiding animation. // 2) Detaches the window's layers for hiding animation and deletes // them upon completion of the animation. This is necessary to a) // ensure that the animation continues in the event of the window being // deleted, and b) to ensure that the animation is visible even if the // window gets restacked below other windows when focus or activation // changes. // The subclass will determine when the animation is completed. class HidingWindowAnimationObserverBase : public aura::WindowObserver { public: explicit HidingWindowAnimationObserverBase(aura::Window* window) : window_(window) { window_->AddObserver(this); } ~HidingWindowAnimationObserverBase() override { if (window_) window_->RemoveObserver(this); } // aura::WindowObserver: void OnWindowDestroying(aura::Window* window) override { DCHECK_EQ(window, window_); WindowInvalid(); } void OnWindowDestroyed(aura::Window* window) override { DCHECK_EQ(window, window_); WindowInvalid(); } // Detach the current layers and create new layers for |window_|. // Stack the original layers above |window_| and its transient // children. If the window has transient children, the original // layers will be moved above the top most transient child so that // activation change does not put the window above the animating // layer. void DetachAndRecreateLayers() { layer_owner_ = RecreateLayers(window_); if (window_->parent()) { const aura::Window::Windows& transient_children = GetTransientChildren(window_); aura::Window::Windows::const_iterator iter = std::find(window_->parent()->children().begin(), window_->parent()->children().end(), window_); DCHECK(iter != window_->parent()->children().end()); aura::Window* topmost_transient_child = NULL; for (++iter; iter != window_->parent()->children().end(); ++iter) { if (ContainsValue(transient_children, *iter)) topmost_transient_child = *iter; } if (topmost_transient_child) { window_->parent()->layer()->StackAbove( layer_owner_->root(), topmost_transient_child->layer()); } } } protected: // Invoked when the hiding animation is completed. It will delete // 'this', and no operation should be made on this object after this // point. void OnAnimationCompleted() { // Window may have been destroyed by this point. if (window_) { aura::client::AnimationHost* animation_host = aura::client::GetAnimationHost(window_); if (animation_host) animation_host->OnWindowHidingAnimationCompleted(); window_->RemoveObserver(this); } delete this; } private: // Invoked when the window is destroyed (or destroying). void WindowInvalid() { layer_owner_->root()->SuppressPaint(); window_->RemoveObserver(this); window_ = NULL; } aura::Window* window_; // The owner of detached layers. scoped_ptr layer_owner_; DISALLOW_COPY_AND_ASSIGN(HidingWindowAnimationObserverBase); }; } // namespace DEFINE_WINDOW_PROPERTY_KEY(int, kWindowVisibilityAnimationTypeKey, WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT); DEFINE_WINDOW_PROPERTY_KEY(int, kWindowVisibilityAnimationDurationKey, 0); DEFINE_WINDOW_PROPERTY_KEY(WindowVisibilityAnimationTransition, kWindowVisibilityAnimationTransitionKey, ANIMATE_BOTH); DEFINE_WINDOW_PROPERTY_KEY(float, kWindowVisibilityAnimationVerticalPositionKey, kWindowAnimation_Vertical_TranslateY); // A HidingWindowAnimationObserver that deletes observer and detached // layers upon the completion of the implicit animation. class ImplicitHidingWindowAnimationObserver : public HidingWindowAnimationObserverBase, public ui::ImplicitAnimationObserver { public: ImplicitHidingWindowAnimationObserver( aura::Window* window, ui::ScopedLayerAnimationSettings* settings); ~ImplicitHidingWindowAnimationObserver() override {} // ui::ImplicitAnimationObserver: void OnImplicitAnimationsCompleted() override; private: DISALLOW_COPY_AND_ASSIGN(ImplicitHidingWindowAnimationObserver); }; namespace { const int kDefaultAnimationDurationForMenuMS = 150; const float kWindowAnimation_HideOpacity = 0.f; const float kWindowAnimation_ShowOpacity = 1.f; const float kWindowAnimation_TranslateFactor = 0.5f; const float kWindowAnimation_ScaleFactor = .95f; const int kWindowAnimation_Rotate_DurationMS = 180; const int kWindowAnimation_Rotate_OpacityDurationPercent = 90; const float kWindowAnimation_Rotate_TranslateY = -20.f; const float kWindowAnimation_Rotate_PerspectiveDepth = 500.f; const float kWindowAnimation_Rotate_DegreesX = 5.f; const float kWindowAnimation_Rotate_ScaleFactor = .99f; const float kWindowAnimation_Bounce_Scale = 1.02f; const int kWindowAnimation_Bounce_DurationMS = 180; const int kWindowAnimation_Bounce_GrowShrinkDurationPercent = 40; base::TimeDelta GetWindowVisibilityAnimationDuration( const aura::Window& window) { int duration = window.GetProperty(kWindowVisibilityAnimationDurationKey); if (duration == 0 && window.type() == ui::wm::WINDOW_TYPE_MENU) { return base::TimeDelta::FromMilliseconds( kDefaultAnimationDurationForMenuMS); } return base::TimeDelta::FromInternalValue(duration); } // Gets/sets the WindowVisibilityAnimationType associated with a window. // TODO(beng): redundant/fold into method on public api? int GetWindowVisibilityAnimationType(aura::Window* window) { int type = window->GetProperty(kWindowVisibilityAnimationTypeKey); if (type == WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT) { return (window->type() == ui::wm::WINDOW_TYPE_MENU || window->type() == ui::wm::WINDOW_TYPE_TOOLTIP) ? WINDOW_VISIBILITY_ANIMATION_TYPE_FADE : WINDOW_VISIBILITY_ANIMATION_TYPE_DROP; } return type; } void GetTransformRelativeToRoot(ui::Layer* layer, gfx::Transform* transform) { const ui::Layer* root = layer; while (root->parent()) root = root->parent(); layer->GetTargetTransformRelativeTo(root, transform); } gfx::Rect GetLayerWorldBoundsAfterTransform(ui::Layer* layer, const gfx::Transform& transform) { gfx::Transform in_world = transform; GetTransformRelativeToRoot(layer, &in_world); gfx::RectF transformed = gfx::RectF(layer->bounds()); in_world.TransformRect(&transformed); return gfx::ToEnclosingRect(transformed); } // Augment the host window so that the enclosing bounds of the full // animation will fit inside of it. void AugmentWindowSize(aura::Window* window, const gfx::Transform& end_transform) { aura::client::AnimationHost* animation_host = aura::client::GetAnimationHost(window); if (!animation_host) return; const gfx::Rect& world_at_start = window->bounds(); gfx::Rect world_at_end = GetLayerWorldBoundsAfterTransform(window->layer(), end_transform); gfx::Rect union_in_window_space = gfx::UnionRects(world_at_start, world_at_end); // Calculate the top left and bottom right deltas to be added to the window // bounds. gfx::Vector2d top_left_delta(world_at_start.x() - union_in_window_space.x(), world_at_start.y() - union_in_window_space.y()); gfx::Vector2d bottom_right_delta( union_in_window_space.x() + union_in_window_space.width() - (world_at_start.x() + world_at_start.width()), union_in_window_space.y() + union_in_window_space.height() - (world_at_start.y() + world_at_start.height())); DCHECK(top_left_delta.x() >= 0 && top_left_delta.y() >= 0 && bottom_right_delta.x() >= 0 && bottom_right_delta.y() >= 0); animation_host->SetHostTransitionOffsets(top_left_delta, bottom_right_delta); } // Shows a window using an animation, animating its opacity from 0.f to 1.f, // its visibility to true, and its transform from |start_transform| to // |end_transform|. void AnimateShowWindowCommon(aura::Window* window, const gfx::Transform& start_transform, const gfx::Transform& end_transform) { AugmentWindowSize(window, end_transform); window->layer()->SetOpacity(kWindowAnimation_HideOpacity); window->layer()->SetTransform(start_transform); window->layer()->SetVisible(true); { // Property sets within this scope will be implicitly animated. ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); base::TimeDelta duration = GetWindowVisibilityAnimationDuration(*window); if (duration.ToInternalValue() > 0) settings.SetTransitionDuration(duration); window->layer()->SetTransform(end_transform); window->layer()->SetOpacity(kWindowAnimation_ShowOpacity); } } // Hides a window using an animation, animating its opacity from 1.f to 0.f, // its visibility to false, and its transform to |end_transform|. void AnimateHideWindowCommon(aura::Window* window, const gfx::Transform& end_transform) { AugmentWindowSize(window, end_transform); // Property sets within this scope will be implicitly animated. ScopedHidingAnimationSettings hiding_settings(window); base::TimeDelta duration = GetWindowVisibilityAnimationDuration(*window); if (duration.ToInternalValue() > 0) hiding_settings.layer_animation_settings()->SetTransitionDuration(duration); window->layer()->SetOpacity(kWindowAnimation_HideOpacity); window->layer()->SetTransform(end_transform); window->layer()->SetVisible(false); } static gfx::Transform GetScaleForWindow(aura::Window* window) { gfx::Rect bounds = window->bounds(); gfx::Transform scale = gfx::GetScaleTransform( gfx::Point(kWindowAnimation_TranslateFactor * bounds.width(), kWindowAnimation_TranslateFactor * bounds.height()), kWindowAnimation_ScaleFactor); return scale; } // Show/Hide windows using a shrink animation. void AnimateShowWindow_Drop(aura::Window* window) { AnimateShowWindowCommon(window, GetScaleForWindow(window), gfx::Transform()); } void AnimateHideWindow_Drop(aura::Window* window) { AnimateHideWindowCommon(window, GetScaleForWindow(window)); } // Show/Hide windows using a vertical Glenimation. void AnimateShowWindow_Vertical(aura::Window* window) { gfx::Transform transform; transform.Translate(0, window->GetProperty( kWindowVisibilityAnimationVerticalPositionKey)); AnimateShowWindowCommon(window, transform, gfx::Transform()); } void AnimateHideWindow_Vertical(aura::Window* window) { gfx::Transform transform; transform.Translate(0, window->GetProperty( kWindowVisibilityAnimationVerticalPositionKey)); AnimateHideWindowCommon(window, transform); } // Show/Hide windows using a fade. void AnimateShowWindow_Fade(aura::Window* window) { AnimateShowWindowCommon(window, gfx::Transform(), gfx::Transform()); } void AnimateHideWindow_Fade(aura::Window* window) { AnimateHideWindowCommon(window, gfx::Transform()); } ui::LayerAnimationElement* CreateGrowShrinkElement( aura::Window* window, bool grow) { scoped_ptr scale(new ui::InterpolatedScale( gfx::Point3F(kWindowAnimation_Bounce_Scale, kWindowAnimation_Bounce_Scale, 1), gfx::Point3F(1, 1, 1))); scoped_ptr scale_about_pivot( new ui::InterpolatedTransformAboutPivot( gfx::Point(window->bounds().width() * 0.5, window->bounds().height() * 0.5), scale.release())); scale_about_pivot->SetReversed(grow); scoped_ptr transition( ui::LayerAnimationElement::CreateInterpolatedTransformElement( scale_about_pivot.release(), base::TimeDelta::FromMilliseconds( kWindowAnimation_Bounce_DurationMS * kWindowAnimation_Bounce_GrowShrinkDurationPercent / 100))); transition->set_tween_type(grow ? gfx::Tween::EASE_OUT : gfx::Tween::EASE_IN); return transition.release(); } void AnimateBounce(aura::Window* window) { ui::ScopedLayerAnimationSettings scoped_settings( window->layer()->GetAnimator()); scoped_settings.SetPreemptionStrategy( ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS); scoped_ptr sequence( new ui::LayerAnimationSequence); sequence->AddElement(CreateGrowShrinkElement(window, true)); sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement( ui::LayerAnimationElement::BOUNDS, base::TimeDelta::FromMilliseconds( kWindowAnimation_Bounce_DurationMS * (100 - 2 * kWindowAnimation_Bounce_GrowShrinkDurationPercent) / 100))); sequence->AddElement(CreateGrowShrinkElement(window, false)); window->layer()->GetAnimator()->StartAnimation(sequence.release()); } // A HidingWindowAnimationObserver that deletes observer and detached // layers when the last_sequence has been completed or aborted. class RotateHidingWindowAnimationObserver : public HidingWindowAnimationObserverBase, public ui::LayerAnimationObserver { public: explicit RotateHidingWindowAnimationObserver(aura::Window* window) : HidingWindowAnimationObserverBase(window) {} ~RotateHidingWindowAnimationObserver() override {} // Destroys itself after |last_sequence| ends or is aborted. Does not take // ownership of |last_sequence|, which should not be NULL. void SetLastSequence(ui::LayerAnimationSequence* last_sequence) { last_sequence->AddObserver(this); } // ui::LayerAnimationObserver: void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override { OnAnimationCompleted(); } void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override { OnAnimationCompleted(); } void OnLayerAnimationScheduled( ui::LayerAnimationSequence* sequence) override {} private: DISALLOW_COPY_AND_ASSIGN(RotateHidingWindowAnimationObserver); }; void AddLayerAnimationsForRotate(aura::Window* window, bool show) { if (show) window->layer()->SetOpacity(kWindowAnimation_HideOpacity); base::TimeDelta duration = base::TimeDelta::FromMilliseconds( kWindowAnimation_Rotate_DurationMS); RotateHidingWindowAnimationObserver* observer = NULL; if (!show) { observer = new RotateHidingWindowAnimationObserver(window); window->layer()->GetAnimator()->SchedulePauseForProperties( duration * (100 - kWindowAnimation_Rotate_OpacityDurationPercent) / 100, ui::LayerAnimationElement::OPACITY); } scoped_ptr opacity( ui::LayerAnimationElement::CreateOpacityElement( show ? kWindowAnimation_ShowOpacity : kWindowAnimation_HideOpacity, duration * kWindowAnimation_Rotate_OpacityDurationPercent / 100)); opacity->set_tween_type(gfx::Tween::EASE_IN_OUT); window->layer()->GetAnimator()->ScheduleAnimation( new ui::LayerAnimationSequence(opacity.release())); float xcenter = window->bounds().width() * 0.5; gfx::Transform transform; transform.Translate(xcenter, 0); transform.ApplyPerspectiveDepth(kWindowAnimation_Rotate_PerspectiveDepth); transform.Translate(-xcenter, 0); scoped_ptr perspective( new ui::InterpolatedConstantTransform(transform)); scoped_ptr scale( new ui::InterpolatedScale(1, kWindowAnimation_Rotate_ScaleFactor)); scoped_ptr scale_about_pivot( new ui::InterpolatedTransformAboutPivot( gfx::Point(xcenter, kWindowAnimation_Rotate_TranslateY), scale.release())); scoped_ptr translation( new ui::InterpolatedTranslation( gfx::PointF(), gfx::PointF(0, kWindowAnimation_Rotate_TranslateY))); scoped_ptr rotation( new ui::InterpolatedAxisAngleRotation( gfx::Vector3dF(1, 0, 0), 0, kWindowAnimation_Rotate_DegreesX)); scale_about_pivot->SetChild(perspective.release()); translation->SetChild(scale_about_pivot.release()); rotation->SetChild(translation.release()); rotation->SetReversed(show); scoped_ptr transition( ui::LayerAnimationElement::CreateInterpolatedTransformElement( rotation.release(), duration)); ui::LayerAnimationSequence* last_sequence = new ui::LayerAnimationSequence(transition.release()); window->layer()->GetAnimator()->ScheduleAnimation(last_sequence); if (observer) { observer->SetLastSequence(last_sequence); observer->DetachAndRecreateLayers(); } } void AnimateShowWindow_Rotate(aura::Window* window) { AddLayerAnimationsForRotate(window, true); } void AnimateHideWindow_Rotate(aura::Window* window) { AddLayerAnimationsForRotate(window, false); } bool AnimateShowWindow(aura::Window* window) { if (!HasWindowVisibilityAnimationTransition(window, ANIMATE_SHOW)) { if (HasWindowVisibilityAnimationTransition(window, ANIMATE_HIDE)) { // Since hide animation may have changed opacity and transform, // reset them to show the window. window->layer()->SetOpacity(kWindowAnimation_ShowOpacity); window->layer()->SetTransform(gfx::Transform()); } return false; } switch (GetWindowVisibilityAnimationType(window)) { case WINDOW_VISIBILITY_ANIMATION_TYPE_DROP: AnimateShowWindow_Drop(window); return true; case WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL: AnimateShowWindow_Vertical(window); return true; case WINDOW_VISIBILITY_ANIMATION_TYPE_FADE: AnimateShowWindow_Fade(window); return true; case WINDOW_VISIBILITY_ANIMATION_TYPE_ROTATE: AnimateShowWindow_Rotate(window); return true; default: return false; } } bool AnimateHideWindow(aura::Window* window) { if (!HasWindowVisibilityAnimationTransition(window, ANIMATE_HIDE)) { if (HasWindowVisibilityAnimationTransition(window, ANIMATE_SHOW)) { // Since show animation may have changed opacity and transform, // reset them, though the change should be hidden. window->layer()->SetOpacity(kWindowAnimation_HideOpacity); window->layer()->SetTransform(gfx::Transform()); } return false; } switch (GetWindowVisibilityAnimationType(window)) { case WINDOW_VISIBILITY_ANIMATION_TYPE_DROP: AnimateHideWindow_Drop(window); return true; case WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL: AnimateHideWindow_Vertical(window); return true; case WINDOW_VISIBILITY_ANIMATION_TYPE_FADE: AnimateHideWindow_Fade(window); return true; case WINDOW_VISIBILITY_ANIMATION_TYPE_ROTATE: AnimateHideWindow_Rotate(window); return true; default: return false; } } } // namespace //////////////////////////////////////////////////////////////////////////////// // ImplicitHidingWindowAnimationObserver ImplicitHidingWindowAnimationObserver::ImplicitHidingWindowAnimationObserver( aura::Window* window, ui::ScopedLayerAnimationSettings* settings) : HidingWindowAnimationObserverBase(window) { settings->AddObserver(this); } void ImplicitHidingWindowAnimationObserver::OnImplicitAnimationsCompleted() { OnAnimationCompleted(); } //////////////////////////////////////////////////////////////////////////////// // ScopedHidingAnimationSettings ScopedHidingAnimationSettings::ScopedHidingAnimationSettings( aura::Window* window) : layer_animation_settings_(window->layer()->GetAnimator()), observer_(new ImplicitHidingWindowAnimationObserver( window, &layer_animation_settings_)) { } ScopedHidingAnimationSettings::~ScopedHidingAnimationSettings() { observer_->DetachAndRecreateLayers(); } //////////////////////////////////////////////////////////////////////////////// // External interface void SetWindowVisibilityAnimationType(aura::Window* window, int type) { window->SetProperty(kWindowVisibilityAnimationTypeKey, type); } int GetWindowVisibilityAnimationType(aura::Window* window) { return window->GetProperty(kWindowVisibilityAnimationTypeKey); } void SetWindowVisibilityAnimationTransition( aura::Window* window, WindowVisibilityAnimationTransition transition) { window->SetProperty(kWindowVisibilityAnimationTransitionKey, transition); } bool HasWindowVisibilityAnimationTransition( aura::Window* window, WindowVisibilityAnimationTransition transition) { WindowVisibilityAnimationTransition prop = window->GetProperty( kWindowVisibilityAnimationTransitionKey); return (prop & transition) != 0; } void SetWindowVisibilityAnimationDuration(aura::Window* window, const base::TimeDelta& duration) { window->SetProperty(kWindowVisibilityAnimationDurationKey, static_cast(duration.ToInternalValue())); } base::TimeDelta GetWindowVisibilityAnimationDuration( const aura::Window& window) { return base::TimeDelta::FromInternalValue( window.GetProperty(kWindowVisibilityAnimationDurationKey)); } void SetWindowVisibilityAnimationVerticalPosition(aura::Window* window, float position) { window->SetProperty(kWindowVisibilityAnimationVerticalPositionKey, position); } bool AnimateOnChildWindowVisibilityChanged(aura::Window* window, bool visible) { if (WindowAnimationsDisabled(window)) return false; if (visible) return AnimateShowWindow(window); // Don't start hiding the window again if it's already being hidden. return window->layer()->GetTargetOpacity() != 0.0f && AnimateHideWindow(window); } bool AnimateWindow(aura::Window* window, WindowAnimationType type) { switch (type) { case WINDOW_ANIMATION_TYPE_BOUNCE: AnimateBounce(window); return true; default: NOTREACHED(); return false; } } bool WindowAnimationsDisabled(aura::Window* window) { // Individual windows can choose to skip animations. if (window && window->GetProperty(aura::client::kAnimationsDisabledKey)) return true; // Animations can be disabled globally for testing. if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kWindowAnimationsDisabled)) return true; // Tests of animations themselves should still run even if the machine is // being accessed via Remote Desktop. if (ui::ScopedAnimationDurationScaleMode::duration_scale_mode() == ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION) return false; // Let the user decide whether or not to play the animation. return !gfx::Animation::ShouldRenderRichAnimation(); } } // namespace wm