| // Copyright 2019 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 "ash/wm/desks/desk_mini_view_animations.h" |
| |
| #include <utility> |
| |
| #include "ash/shell.h" |
| #include "ash/wm/desks/desk_mini_view.h" |
| #include "ash/wm/desks/desks_bar_view.h" |
| #include "ash/wm/desks/expanded_desks_bar_button.h" |
| #include "ash/wm/overview/overview_controller.h" |
| #include "ash/wm/overview/overview_session.h" |
| #include "base/containers/contains.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/geometry/transform_util.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| constexpr gfx::Transform kEndTransform; |
| |
| constexpr base::TimeDelta kExistingMiniViewsAnimationDuration = |
| base::Milliseconds(250); |
| |
| constexpr base::TimeDelta kRemovedMiniViewsFadeOutDuration = |
| base::Milliseconds(200); |
| |
| constexpr base::TimeDelta kZeroStateAnimationDuration = base::Milliseconds(200); |
| |
| // Scale for entering/exiting zero state. |
| constexpr float kEnterOrExitZeroStateScale = 0.6f; |
| |
| // |settings| will be initialized with a fast-out-slow-in animation with the |
| // given |duration|. |
| void InitScopedAnimationSettings(ui::ScopedLayerAnimationSettings* settings, |
| base::TimeDelta duration) { |
| settings->SetTransitionDuration(duration); |
| settings->SetTweenType(gfx::Tween::ACCEL_20_DECEL_60); |
| settings->SetPreemptionStrategy( |
| ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
| } |
| |
| // Animates the transform of the layer of the given |view| from the supplied |
| // |begin_transform| to the identity transform. |
| void AnimateView(views::View* view, const gfx::Transform& begin_transform) { |
| ui::Layer* layer = view->layer(); |
| layer->SetTransform(begin_transform); |
| |
| ui::ScopedLayerAnimationSettings settings{layer->GetAnimator()}; |
| InitScopedAnimationSettings(&settings, kExistingMiniViewsAnimationDuration); |
| layer->SetTransform(kEndTransform); |
| } |
| |
| // See details at AnimateView. |
| void AnimateMiniViews(std::vector<DeskMiniView*> mini_views, |
| const gfx::Transform& begin_transform) { |
| for (auto* mini_view : mini_views) |
| AnimateView(mini_view, begin_transform); |
| } |
| |
| // Gets the scale transform for |view|, it can be scale up or scale down. The |
| // anchor of the scale animation will be a point whose |x| is the center of the |
| // desks bar while |y| is the top of the given |view|. GetMirroredX is used here |
| // to make sure the transform is correct while in RTL layout. |
| gfx::Transform GetScaleTransformForView(views::View* view, int bar_x_center) { |
| return gfx::GetScaleTransform( |
| gfx::Point(bar_x_center - view->GetMirroredX(), 0), |
| kEnterOrExitZeroStateScale); |
| } |
| |
| // Scales down the given |view| to |kEnterOrExitZeroStateScale| and fading out |
| // it at the same time. |
| void ScaleDownAndFadeOutView(views::View* view, int bar_x_center) { |
| ui::Layer* layer = view->layer(); |
| ui::ScopedLayerAnimationSettings settings{layer->GetAnimator()}; |
| InitScopedAnimationSettings(&settings, kZeroStateAnimationDuration); |
| |
| layer->SetTransform(GetScaleTransformForView(view, bar_x_center)); |
| layer->SetOpacity(0.f); |
| } |
| |
| // Scales up the given |view| from |kEnterOrExitZeroStateScale| to identity and |
| // fading in it at the same time. |
| void ScaleUpAndFadeInView(views::View* view, int bar_x_center) { |
| DCHECK(view); |
| ui::Layer* layer = view->layer(); |
| layer->SetTransform(GetScaleTransformForView(view, bar_x_center)); |
| layer->SetOpacity(0.f); |
| |
| ui::ScopedLayerAnimationSettings settings{layer->GetAnimator()}; |
| InitScopedAnimationSettings(&settings, kZeroStateAnimationDuration); |
| layer->SetTransform(kEndTransform); |
| layer->SetOpacity(1.f); |
| } |
| |
| void PositionWindowsInOverview() { |
| auto* controller = Shell::Get()->overview_controller(); |
| DCHECK(controller->InOverviewSession()); |
| controller->overview_session()->PositionWindows(true); |
| } |
| |
| // A self-deleting object that performs a fade out animation on |
| // |removed_mini_view|'s layer by changing its opacity from 1 to 0 and scales |
| // down it around the center of |bar_view| while switching back to zero state. |
| // |removed_mini_view_| and the object itserlf will be deleted when the |
| // animation is complete. |
| // TODO(afakhry): Consider generalizing HidingWindowAnimationObserverBase to be |
| // reusable for the mini_view removal animation. |
| class RemovedMiniViewAnimation : public ui::ImplicitAnimationObserver { |
| public: |
| RemovedMiniViewAnimation(DeskMiniView* removed_mini_view, |
| DesksBarView* bar_view, |
| const bool to_zero_state) |
| : removed_mini_view_(removed_mini_view) { |
| ui::Layer* layer = removed_mini_view_->layer(); |
| ui::ScopedLayerAnimationSettings settings{layer->GetAnimator()}; |
| InitScopedAnimationSettings(&settings, kRemovedMiniViewsFadeOutDuration); |
| settings.AddObserver(this); |
| |
| if (to_zero_state) { |
| DCHECK(bar_view); |
| layer->SetTransform(GetScaleTransformForView( |
| removed_mini_view, bar_view->bounds().CenterPoint().x())); |
| } else { |
| layer->SetTransform(kEndTransform); |
| } |
| layer->SetOpacity(0); |
| } |
| |
| RemovedMiniViewAnimation(const RemovedMiniViewAnimation&) = delete; |
| RemovedMiniViewAnimation& operator=(const RemovedMiniViewAnimation&) = delete; |
| |
| ~RemovedMiniViewAnimation() override { |
| DCHECK(removed_mini_view_->parent()); |
| removed_mini_view_->parent()->RemoveChildViewT(removed_mini_view_); |
| } |
| |
| // ui::ImplicitAnimationObserver: |
| void OnImplicitAnimationsCompleted() override { delete this; } |
| |
| private: |
| DeskMiniView* removed_mini_view_; |
| }; |
| |
| // A self-deleting object that performs bounds changes animation for the desks |
| // bar while it switches between zero state and expanded state. |
| // `is_bounds_animation_on_going_` will be used to help hold Layout calls during |
| // the animation. Since Layout is expensive and will be called lots of times |
| // during the bounds changes animation without doing this. The object itself |
| // will be deleted when the animation is complete. |
| class DesksBarBoundsAnimation : public ui::ImplicitAnimationObserver { |
| public: |
| DesksBarBoundsAnimation(DesksBarView* bar_view, bool to_zero_state) |
| : bar_view_(bar_view) { |
| auto* desks_widget = bar_view_->GetWidget(); |
| const gfx::Rect current_widget_bounds = |
| desks_widget->GetWindowBoundsInScreen(); |
| gfx::Rect target_widget_bounds = current_widget_bounds; |
| |
| // When `to_zero_state` is false, desks bar is switching from zero to |
| // expanded state. |
| if (to_zero_state) { |
| target_widget_bounds.set_height(DesksBarView::kZeroStateBarHeight); |
| bar_view_->set_is_bounds_animation_on_going(true); |
| } else { |
| // While switching desks bar from zero state to expanded state, setting |
| // its bounds to its bounds at expanded state directly without animation, |
| // which will trigger Layout and make sure the contents of |
| // desks bar(e.g, desk mini view, new desk button) are at the correct |
| // positions before the animation. And set `is_bounds_animation_on_going_` |
| // to be true, which will help hold Layout until the animation is done. |
| // Then set the bounds of the desks bar back to its bounds at zero state |
| // to start the bounds change animation. See more details at |
| // `is_bounds_animation_on_going_`. |
| target_widget_bounds.set_height(bar_view_->GetExpandedBarHeight( |
| desks_widget->GetNativeWindow()->GetRootWindow())); |
| desks_widget->SetBounds(target_widget_bounds); |
| bar_view_->set_is_bounds_animation_on_going(true); |
| desks_widget->SetBounds(current_widget_bounds); |
| } |
| |
| ui::ScopedLayerAnimationSettings settings{ |
| desks_widget->GetLayer()->GetAnimator()}; |
| InitScopedAnimationSettings(&settings, kZeroStateAnimationDuration); |
| settings.AddObserver(this); |
| desks_widget->SetBounds(target_widget_bounds); |
| } |
| |
| DesksBarBoundsAnimation(const DesksBarBoundsAnimation&) = delete; |
| DesksBarBoundsAnimation& operator=(const DesksBarBoundsAnimation&) = delete; |
| |
| ~DesksBarBoundsAnimation() override { |
| DCHECK(bar_view_); |
| bar_view_->set_is_bounds_animation_on_going(false); |
| // Layout the desks bar to make sure the buttons visibility will be updated |
| // on desks bar state changes. Also make sure the button's text will be |
| // updated correctly while going back to zero state. |
| bar_view_->Layout(); |
| } |
| |
| // ui::ImplicitAnimationObserver: |
| void OnImplicitAnimationsCompleted() override { delete this; } |
| |
| private: |
| DesksBarView* const bar_view_; |
| }; |
| |
| } // namespace |
| |
| void PerformNewDeskMiniViewAnimation( |
| DesksBarView* bar_view, |
| const std::vector<DeskMiniView*>& new_mini_views, |
| int shift_x) { |
| gfx::Transform begin_transform; |
| begin_transform.Translate(shift_x, 0); |
| |
| for (auto* mini_view : bar_view->mini_views()) { |
| const bool is_new = base::Contains(new_mini_views, mini_view); |
| |
| ui::Layer* layer = mini_view->layer(); |
| if (is_new) |
| layer->SetOpacity(0.f); |
| layer->SetTransform(begin_transform); |
| |
| ui::ScopedLayerAnimationSettings settings{layer->GetAnimator()}; |
| InitScopedAnimationSettings(&settings, kExistingMiniViewsAnimationDuration); |
| |
| // Fade in new desk mini_views and shift all of them (new & old) to the |
| // left. |
| if (is_new) |
| layer->SetOpacity(1); |
| layer->SetTransform(kEndTransform); |
| } |
| |
| // The new desk button in the expanded desks bar moves at the opposite |
| // direction of the existing mini views while creating a new mini view. The |
| // existing mini views will move from right to left while the new desk button |
| // will move from left to right. Since the newly added mini view will be added |
| // between the last mini view and the new desk button. |
| gfx::Transform new_desk_button_begin_transform; |
| new_desk_button_begin_transform.Translate(-shift_x, 0); |
| AnimateView(bar_view->expanded_state_new_desk_button(), |
| new_desk_button_begin_transform); |
| } |
| |
| void PerformRemoveDeskMiniViewAnimation( |
| DeskMiniView* removed_mini_view, |
| std::vector<DeskMiniView*> mini_views_left, |
| std::vector<DeskMiniView*> mini_views_right, |
| ExpandedDesksBarButton* expanded_state_new_desk_button, |
| int shift_x) { |
| gfx::Transform mini_views_left_begin_transform; |
| mini_views_left_begin_transform.Translate(shift_x, 0); |
| gfx::Transform mini_views_right_begin_transform; |
| mini_views_right_begin_transform.Translate(-shift_x, 0); |
| |
| new RemovedMiniViewAnimation(removed_mini_view, /*bar_view=*/nullptr, |
| /*to_zero_state=*/false); |
| |
| AnimateMiniViews(mini_views_left, mini_views_left_begin_transform); |
| AnimateMiniViews(mini_views_right, mini_views_right_begin_transform); |
| AnimateView(expanded_state_new_desk_button, mini_views_right_begin_transform); |
| } |
| |
| void PerformZeroStateToExpandedStateMiniViewAnimation(DesksBarView* bar_view) { |
| new DesksBarBoundsAnimation(bar_view, /*to_zero_state=*/false); |
| const int bar_x_center = bar_view->bounds().CenterPoint().x(); |
| for (auto* mini_view : bar_view->mini_views()) |
| ScaleUpAndFadeInView(mini_view, bar_x_center); |
| |
| ScaleUpAndFadeInView(bar_view->expanded_state_new_desk_button(), |
| bar_x_center); |
| PositionWindowsInOverview(); |
| } |
| |
| void PerformExpandedStateToZeroStateMiniViewAnimation( |
| DesksBarView* bar_view, |
| std::vector<DeskMiniView*> removed_mini_views) { |
| for (auto* mini_view : removed_mini_views) |
| new RemovedMiniViewAnimation(mini_view, bar_view, /*to_zero_state=*/true); |
| new DesksBarBoundsAnimation(bar_view, /*to_zero_state=*/true); |
| const gfx::Rect bounds = bar_view->bounds(); |
| ScaleDownAndFadeOutView(bar_view->expanded_state_new_desk_button(), |
| bounds.CenterPoint().x()); |
| |
| PositionWindowsInOverview(); |
| } |
| |
| void PerformReorderDeskMiniViewAnimation( |
| int old_index, |
| int new_index, |
| const std::vector<DeskMiniView*>& mini_views) { |
| const int views_size = static_cast<int>(mini_views.size()); |
| |
| DCHECK_GE(old_index, 0); |
| DCHECK_LT(old_index, views_size); |
| DCHECK_GE(new_index, 0); |
| DCHECK_LT(new_index, views_size); |
| |
| if (old_index == new_index) |
| return; |
| |
| // Reordering should be finished before calling this function. The source view |
| // and the target view has been exchanged. The range should be selected |
| // according to current mini views position. |
| const bool move_right = old_index < new_index; |
| const int start_index = move_right ? old_index : new_index + 1; |
| const int end_index = move_right ? new_index : old_index + 1; |
| |
| // Since |old_index| and |new_index| are unequal valid indices, there |
| // must be at least two desks. |
| int shift_x = mini_views[0]->GetMirroredBounds().x() - |
| mini_views[1]->GetMirroredBounds().x(); |
| shift_x = move_right ? -shift_x : shift_x; |
| gfx::Transform desks_transform; |
| desks_transform.Translate(shift_x, 0); |
| |
| auto start_iter = mini_views.begin(); |
| AnimateMiniViews(std::vector<DeskMiniView*>(start_iter + start_index, |
| start_iter + end_index), |
| desks_transform); |
| |
| // Animate the mini view being reordered if it is visible. |
| auto* reorder_view = mini_views[new_index]; |
| ui::Layer* layer = reorder_view->layer(); |
| if (layer->opacity() == 0.0f) |
| return; |
| |
| // Back to old position. |
| gfx::Transform reorder_desk_transform; |
| reorder_desk_transform.Translate( |
| mini_views[old_index]->GetMirroredBounds().x() - |
| reorder_view->GetMirroredBounds().x(), |
| 0); |
| layer->SetTransform(reorder_desk_transform); |
| |
| // Animate movement. |
| ui::ScopedLayerAnimationSettings settings{layer->GetAnimator()}; |
| InitScopedAnimationSettings(&settings, kExistingMiniViewsAnimationDuration); |
| layer->SetTransform(kEndTransform); |
| } |
| |
| } // namespace ash |