chromium/ash/app_list/apps_grid_row_change_animator.cc

// Copyright 2022 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/app_list/apps_grid_row_change_animator.h"

#include <utility>

#include "ash/app_list/views/apps_grid_view.h"
#include "base/auto_reset.h"
#include "base/i18n/rtl.h"
#include "base/time/time.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/transform_util.h"

namespace ash {

AppsGridRowChangeAnimator::AppsGridRowChangeAnimator(
    AppsGridView* apps_grid_view)
    : apps_grid_view_(apps_grid_view) {}

AppsGridRowChangeAnimator::~AppsGridRowChangeAnimator() = default;

void AppsGridRowChangeAnimator::AnimateBetweenRows(
    AppListItemView* view,
    const gfx::Rect& current,
    const gfx::Rect& target,
    views::AnimationSequenceBlock* animation_sequence) {
  base::AutoReset<bool> auto_reset(&setting_up_animation_, true);

  // The direction used to calculate the offset for animating the item view into
  // and the layer copy out of the grid. Reversed for RTL.
  int dir = current.y() < target.y() ? 1 : -1;
  if (base::i18n::IsRTL())
    dir *= -1;

  int offset =
      apps_grid_view_->GetTotalTileSize(apps_grid_view_->GetSelectedPage())
          .width();

  // Calculate where offscreen the layer copy will animate to.
  gfx::Rect current_out = current;
  current_out.Offset(dir * offset, 0);

  // The transform for moving the layer copy out and off screen.
  gfx::Transform layer_copy_transform;

  // The bounds where `view` will begin animating to `target`.
  gfx::RectF target_in;

  // The layer copy of the view which is animating between rows.
  std::unique_ptr<ui::Layer> row_change_layer;

  if (row_change_layers_.count(view)) {
    row_change_layer = std::move(row_change_layers_[view]);
    row_change_layers_.erase(view);

    // Reverse existing row change animation, by swapping the positions of the
    // mid-animation layer copy and `view`. Then animate each to the correct
    // target.

    // Calculate 'target_in' so that 'view' can start its animation in the place
    // of the layer copy.
    target_in = row_change_layer->transform().MapRect(
        gfx::RectF(row_change_layer->bounds()));

    // Calculate the current bounds of `view` including its layer transform.
    gfx::RectF current_bounds_in_animation =
        view->layer()->transform().MapRect(gfx::RectF(current));

    // Set the bounds of the layer copy to the current bounds of'view'.
    row_change_layer->SetBounds(
        gfx::ToRoundedRect(current_bounds_in_animation));
    row_change_layer->SetTransform(gfx::Transform());

    layer_copy_transform = gfx::TransformBetweenRects(
        current_bounds_in_animation, gfx::RectF(current_out));

    // Swap the opacity of the layer copy and the item view.
    const float layer_copy_opacity = row_change_layer->opacity();
    row_change_layer->SetOpacity(view->layer()->opacity());
    view->layer()->SetOpacity(layer_copy_opacity);
  } else {
    // Create the row change animation for this view. `view` will animate from
    // offscreen into the 'target' location, while a layer copy of 'view' will
    // animate from the original `view` bounds to offscreen.
    view->EnsureLayer();
    row_change_layer = view->RecreateLayer();

    layer_copy_transform = gfx::TransformBetweenRects(gfx::RectF(current),
                                                      gfx::RectF(current_out));

    view->layer()->SetOpacity(0.0f);

    // Calculate offscreen position to begin animating `view` from.
    target_in = gfx::RectF(target);
    const int target_in_direction = current.y() < target.y() ? -1 : 1;
    target_in.Offset(target_in_direction * offset, 0);
    target_in =
        gfx::RectF(apps_grid_view_->GetMirroredRect(ToRoundedRect(target_in)));
  }

  // Set the transform for the item view before animating it into the target
  // grid position.
  view->layer()->SetTransform(gfx::TransformBetweenRects(
      gfx::RectF(apps_grid_view_->GetMirroredRect(target)), target_in));
  view->SetBoundsRect(target);

  // Fade out and animate out the copied layer. Fade in the real item view.
  animation_sequence
      ->SetTransform(row_change_layer.get(), layer_copy_transform,
                     gfx::Tween::ACCEL_40_DECEL_100_3)
      .SetOpacity(row_change_layer.get(), 0.0f,
                  gfx::Tween::ACCEL_40_DECEL_100_3)
      .SetOpacity(view->layer(), 1.0f, gfx::Tween::ACCEL_40_DECEL_100_3)
      .SetTransform(view->layer(), gfx::Transform(),
                    gfx::Tween::ACCEL_40_DECEL_100_3);

  row_change_layers_[view] = std::move(row_change_layer);
}

void AppsGridRowChangeAnimator::CancelAnimation(views::View* view) {
  row_change_layers_.erase(view);
}

bool AppsGridRowChangeAnimator::IsAnimating() const {
  return !row_change_layers_.empty() || setting_up_animation_;
}

}  // namespace ash