| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/shelf/window_scale_animation.h" |
| |
| #include <optional> |
| #include <vector> |
| |
| #include "ash/public/cpp/shelf_config.h" |
| #include "ash/public/cpp/window_backdrop.h" |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/screen_util.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shell.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_util.h" |
| #include "base/containers/unique_ptr_adapters.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/time/time.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_observer.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/layer_animation_observer.h" |
| #include "ui/compositor/layer_animator.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/gfx/animation/tween.h" |
| #include "ui/gfx/geometry/transform.h" |
| #include "ui/wm/core/scoped_animation_disabler.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // The time to do window transform to scale up to its original position or |
| // scale down to homescreen animation. |
| constexpr base::TimeDelta kWindowScaleUpOrDownTime = base::Milliseconds(350); |
| |
| // The delay to do window opacity fade out when scaling down the dragged window. |
| constexpr base::TimeDelta kWindowFadeOutDelay = base::Milliseconds(100); |
| |
| // The window scale down factor if we head to home screen after drag ends. |
| constexpr float kWindowScaleDownFactor = 0.001f; |
| |
| // The fast animation time to do window transform for transient child window. |
| // This will only be used in tests. |
| constexpr base::TimeDelta kFastAnimationTime = base::Milliseconds(100); |
| |
| // This will only be updated to true in tests via |
| // |EnableScopedFastAnimationForTransientChildForTest()|. |
| bool g_should_use_fast_animation_for_transient_child = false; |
| |
| // Returns the transform that should be applied to |window| if we should head to |
| // shelf after dragging. |
| gfx::Transform GetWindowTransformToShelf(aura::Window* window) { |
| // The origin of bounds returned by GetBoundsInScreen() is transformed using |
| // the window's transform. The transform that should be applied to the |
| // window is calculated relative to the window bounds with no transforms |
| // applied, and thus need the un-transformed window origin. |
| const gfx::RectF window_bounds(window->GetBoundsInScreen()); |
| gfx::PointF origin = window_bounds.origin(); |
| gfx::PointF origin_without_transform = |
| window->transform().InverseMapPoint(origin).value_or(origin); |
| |
| gfx::Transform transform; |
| Shelf* shelf = Shelf::ForWindow(window); |
| |
| const gfx::Rect shelf_item_bounds = |
| shelf->GetScreenBoundsOfItemIconForWindow(window); |
| |
| if (!shelf_item_bounds.IsEmpty()) { |
| const gfx::RectF shelf_item_bounds_f(shelf_item_bounds); |
| const gfx::PointF shelf_item_center = shelf_item_bounds_f.CenterPoint(); |
| transform.Translate(shelf_item_center.x() - origin_without_transform.x(), |
| shelf_item_center.y() - origin_without_transform.y()); |
| transform.Scale(shelf_item_bounds_f.width() / window_bounds.width(), |
| shelf_item_bounds_f.height() / window_bounds.height()); |
| } else { |
| const gfx::PointF shelf_center_point = |
| gfx::RectF(shelf->GetIdealBounds()).CenterPoint(); |
| transform.Translate(shelf_center_point.x() - origin_without_transform.x(), |
| shelf_center_point.y() - origin_without_transform.y()); |
| transform.Scale(kWindowScaleDownFactor, kWindowScaleDownFactor); |
| } |
| return transform; |
| } |
| |
| base::TimeDelta GetWindowAnimationTime(aura::Window* window) { |
| if (g_should_use_fast_animation_for_transient_child && |
| window != wm::GetTransientRoot(window)) { |
| return kFastAnimationTime; |
| } |
| return kWindowScaleUpOrDownTime; |
| } |
| |
| } // namespace |
| |
| // ----------------------------------------------------------------------------- |
| // WindowScaleAnimation::AnimationObserver: |
| |
| // It's owned by |WindowScaleAnimation| and will be destroyed when its |
| // |window_| animation is completed or its |window_| is being destroyed. |
| class WindowScaleAnimation::AnimationObserver |
| : public ui::ImplicitAnimationObserver, |
| public aura::WindowObserver { |
| public: |
| AnimationObserver(aura::Window* window, |
| WindowScaleAnimation* window_scale_animation) |
| : window_(window), window_scale_animation_(window_scale_animation) { |
| window_observation_.Observe(window_.get()); |
| } |
| |
| AnimationObserver(const AnimationObserver&) = delete; |
| AnimationObserver& operator=(const AnimationObserver&) = delete; |
| ~AnimationObserver() override { |
| // Explicitly stopping observing will prevent |
| // `OnImplicitAnimationsCompleted()` from being called. |
| StopObservingImplicitAnimations(); |
| } |
| |
| aura::Window* window() { return window_; } |
| |
| // ui::ImplicitAnimationObserver: |
| void OnImplicitAnimationsCompleted() override { |
| window_scale_animation_->DestroyWindowAnimationObserver(this); |
| // |this| is destroyed after the above line. |
| } |
| |
| // aura::WindowObserver: |
| void OnWindowDestroying(aura::Window* window) override { |
| window_scale_animation_->DestroyWindowAnimationObserver(this); |
| // |this| is destroyed after the above line. |
| } |
| |
| private: |
| // Pointers to the window and the parent scale animation. Guaranteed to |
| // outlive `this`. |
| const raw_ptr<aura::Window> window_; |
| |
| const raw_ptr<WindowScaleAnimation> window_scale_animation_; |
| |
| base::ScopedObservation<aura::Window, aura::WindowObserver> |
| window_observation_{this}; |
| }; |
| |
| WindowScaleAnimation::WindowScaleAnimation(WindowScaleType scale_type, |
| base::OnceClosure opt_callback) |
| : opt_callback_(std::move(opt_callback)), scale_type_(scale_type) {} |
| |
| WindowScaleAnimation::~WindowScaleAnimation() { |
| if (!opt_callback_.is_null()) |
| std::move(opt_callback_).Run(); |
| } |
| |
| void WindowScaleAnimation::Start(aura::Window* window) { |
| // In the destructor of `ScopedLayerAnimationSettings`, it will activate all |
| // of its observers. What we want is to activate the observer for each |
| // transient child window after the for loop is done, otherwise `this` can be |
| // early released via `WindowScaleAnimation::DestroyWindowAnimationObserver`. |
| // Hence creating this vector outside of the for loop. |
| std::vector<std::unique_ptr<ui::ScopedLayerAnimationSettings>> all_settings; |
| for (auto* transient_window : GetTransientTreeIterator(window)) { |
| window_animation_observers_.push_back( |
| std::make_unique<AnimationObserver>(transient_window, this)); |
| WindowBackdrop::Get(transient_window)->DisableBackdrop(); |
| all_settings.push_back(std::make_unique<ui::ScopedLayerAnimationSettings>( |
| transient_window->layer()->GetAnimator())); |
| auto* settings = all_settings.back().get(); |
| settings->SetTransitionDuration(GetWindowAnimationTime(transient_window)); |
| settings->SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
| settings->SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN); |
| settings->AddObserver(window_animation_observers_.back().get()); |
| if (scale_type_ == WindowScaleType::kScaleDownToShelf) { |
| transient_window->layer()->GetAnimator()->SchedulePauseForProperties( |
| kWindowFadeOutDelay, ui::LayerAnimationElement::OPACITY); |
| transient_window->layer()->SetTransform( |
| GetWindowTransformToShelf(transient_window)); |
| transient_window->layer()->SetOpacity(0.f); |
| } else { |
| transient_window->layer()->SetTransform(gfx::Transform()); |
| } |
| } |
| } |
| |
| // static |
| base::AutoReset<bool> |
| WindowScaleAnimation::EnableScopedFastAnimationForTransientChildForTest() { |
| return base::AutoReset<bool>(&g_should_use_fast_animation_for_transient_child, |
| true); |
| } |
| |
| void WindowScaleAnimation::DestroyWindowAnimationObserver( |
| WindowScaleAnimation::AnimationObserver* animation_observer) { |
| // `animation_observer` will get deleted on the next line. |
| auto* window = animation_observer->window(); |
| |
| std::erase_if(window_animation_observers_, |
| base::MatchesUniquePtr(animation_observer)); |
| |
| if (window_animation_observers_.empty()) { |
| // Do the scale transform for the entire transient tree. |
| OnScaleWindowsOnAnimationsCompleted(window); |
| // self-destructed when all windows' transform animation is done. |
| delete this; |
| } |
| } |
| |
| void WindowScaleAnimation::OnScaleWindowsOnAnimationsCompleted( |
| aura::Window* window) { |
| // Scale-down or scale-up window(s) with the windows' descending order |
| // in the transient tree. We need to use this fixed order to ensure the |
| // transient child window will be visible after returning back from home |
| // screen to the window. If the transient child window is minimized before its |
| // parent window, its visibility is not controlled by its parent anymore. |
| // Check |TransientWindowManager::UpdateTransientChildVisibility()| for more |
| // details. |
| const bool is_scaling_down = |
| scale_type_ == WindowScaleAnimation::WindowScaleType::kScaleDownToShelf; |
| for (auto* transient_window : GetTransientTreeIterator(window)) { |
| if (transient_window->is_destroying()) |
| continue; |
| |
| if (is_scaling_down) { |
| // Minimize the dragged window after transform animation is completed. |
| window_util::MinimizeAndHideWithoutAnimation({transient_window}); |
| // Reset its transform to identity transform and its original backdrop |
| // mode. |
| transient_window->layer()->SetTransform(gfx::Transform()); |
| transient_window->layer()->SetOpacity(1.f); |
| } |
| WindowBackdrop::Get(transient_window)->RestoreBackdrop(); |
| } |
| } |
| |
| } // namespace ash |