910e62b5创建于 1月15日历史提交
// 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.

#ifndef ASH_WM_DESKS_ROOT_WINDOW_DESK_SWITCH_ANIMATOR_H_
#define ASH_WM_DESKS_ROOT_WINDOW_DESK_SWITCH_ANIMATOR_H_

#include <memory>

#include "ash/ash_export.h"
#include "ash/shell_observer.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "ui/compositor/layer_animation_observer.h"

namespace aura {
class Window;
}  // namespace aura

namespace ui {
class LayerTreeOwner;
class Layer;
}  // namespace ui

namespace viz {
class CopyOutputResult;
}  // namespace viz

namespace ash {

// Performs the desk switch animation on a root window (i.e. display). Since a
// desk spans all displays, one instance of this object will be created for each
// display when a new desk is activated.
//
// Screenshots of the starting and ending desks are taken, and we animate
// between them such that the starting desk can appear sliding out of the
// screen, while the ending desk is sliding in.
// - `starting` desk: is the currently activated desk which will be deactivated
//    shortly.
// - `ending` desk: is the desk desired to be activated with this animation.
// Note: These can be changed when doing a continuous animation from the
// keyboard or touchpad.
//
// Currently there are 2 animations with similar style:
// 1. Quick Animation: This is triggered when user changes desk from clicking
// chevrons and thumbnails in desk bar and any non keyboard/touchpad input
// source. Similar to shuffling cards, starting desk is sliding out while ending
// desk sliding in, both have sliding distances equal to 25% of root window
// width. When sliding starts, the ending desk screenshot also fades in from 0
// opacity to 1 opacity.
// 2. Continuous Animation: We take screenshots to make the
// visible state of the desks seem constant to the user when using touchpad or
// keyboard shortcut (e.g. if the starting desk is in overview, it appears to
// remain in overview while sliding out). This approach makes it possible to
// show an empty black space separating both desks while we animate them (See
// `kDesksSpacing`). The ending desk may change after the animation has started.
// In this case, a new animation will replace the current one and animate to the
// new ending desk, requesting a new screenshot if necessary.
//
// Both of the animations goes through the following phases with some twists:
//
// - Phase (1) begins by calling TakeStartingDeskScreenshot(), which should be
//   called before the ending desk is activated.
//   * Once the screenshot is taken, it is placed in a layer that covers
//     everything on the screen, so that desk activation can happen without
//     being seen.
//   * Delegate::OnStartingDeskScreenshotTaken() is called, and the owner of
//     this object can check that all animators of all root windows have
//     finished taking the starting desk screenshots (through checking
//     starting_desk_screenshot_taken()), upon which the actual ending desk
//     activation can take place, and phase (2) of the animation can be
//     triggered.
//
// - Phase (2) should begin after the ending desk had been activated,
//   by calling TakeEndingDeskScreenshot().
//
// The way screenshots are placed is different based on the animation types:
//
//   * Once the screenshot is taken, it is placed in a sibling layer to the
//     starting desk screenshot layer:
//     1. Quick animation:
//     Ending desk screenshot is set to 0 opacity and placed on top of starting
//     desk, with a 25% root window width offset.
//
//                         Animation Layers
//                       +-----+-------------+
//                       |start|   end desk  |
//                       | desk| screenshot  |
//                       |layer|     layer   |
//                       +-----+-------------+
//                       25% width
//
//     2. Continuous animation:
//     Ending desk screenshot is placed side by side next to starting desk, with
//     an offset of |kDesksSpacing| between the two layers.
//                         Animation Layers
//                       +--------------+-+--------------+
//                       |  start desk  | |   end desk   |
//                       |  screenshot  | |  screenshot  |
//                       |    layer     | |    layer     |
//                       +--------------+-+--------------+
//
//   * Delegate::OnEndingDeskScreenshotTaken() will be called, upon which the
//     owner of this object can check if all ending desks screenshots on all
//     roots are taken by all animators (through checking
//     ending_desk_screenshot_taken()), so that it can start phase (3) on all of
//     them at the same time.
//   * In Continuous Animation, phase (2) can be rentered after starting phase
//   (3) by calling
//     ReplaceAnimation() or UpdateSwipeAnimation(). The new ending desk will
//     change, and if it does not have an associated screenshot layer, the
//     caller will be responsible for requesting one using
//     TakeEndingDeskScreenshot().
//     The screenshots are taken as needed since
//     their layers are fullscreen and require activating a desk which may be a
//     large operation for something that the user may not see. Once the
//     screenshot is taken, it is kept until |this| is destroyed. If an
//     associated screenshot layer exists already, ReplaceAnimation() and
//     UpdateSwipeAnimation() can proceed without returning to phase (2).
//
// - Phase (3) begins when StartAnimation() is called.
//   * The parent layer of both screenshot layers is animated, either:
//     - To the left (starting_desk_index_ < ending_desk_index_); when the
//       starting desk is on the left.
//
//              <<<<<-------------------------- move left.
//                       +-----------+
//                       | Animation |
//                       |  layer    |
//                       +-----------+
//                         /        \
//              +------------+      +------------+
//              | start desk |      | end desk   |
//              | screenshot |      | screenshot |
//              |  layer     |      |  layer     |
//              +------------+      +------------+
//                    ^
//                start here
//
//       Animation layer transforms:
//       Continuous Animation:
//       `x_translation_offset_` is root_window_size_.width() + kDesksSpacing
//       Quick animation:
//       `x_translation_offset_` is root_window_size_.width() * 0.25
//
//       * Begin transform: The transform that will make the starting desk
//         screenshot visible. In this case it is a transform with translation
//         (edge_padding_width_dp_, 0).
//       * End transform: The transform that will make the ending desk
//         screenshot visible. In this case it is a transform with translation
//         (-|edge_padding_width_dp_| - |x_translation_offset_| -
//         |kDesksSpacing|, 0).
//
//     - Or to the right (starting_desk_index_ > ending_desk_index_), when the
//       starting desk is on the right.
//
//          move right. -------------------------->>>>>
//                       +-----------+
//                       | Animation |
//                       |  layer    |
//                       +-----------+
//                         /        \
//              +------------+      +------------+
//              | end desk   |      | start desk |
//              | screenshot |      | screenshot |
//              |  layer     |      |  layer     |
//              +------------+      +------------+
//                                        ^
//                                    start here
//
//       Animation layer transforms:
//       * Begin transform: The transform that will make the starting desk
//         screenshot visible. In this case it is a transform with translation
//         (-|edge_padding_width_dp_| - |x_translation_offset_| -
//         |kDesksSpacing|, 0).
//       * End transform: The transform that will make the ending desk
//         screenshot visible. In this case it is a transform with translation
//         (edge_padding_width_dp_, 0).
//
//   * In case of quick animation, end desk screenshot will fade in from 0
//   opacity to 1 opacity.
//   * The animation always begins such that the starting desk screenshot layer
//     is the one visible on the screen, and the parent (animation layer) always
//     moves(fade in) in the direction such that the ending desk screenshot
//     becomes visible on the screen.
//   * The children (screenshot layers) are always placed left to right to match
//     desk order. For example, in continuous animation if there are three desks
//     and this class has been instructed to create a screenshot for all three
//     desks, desk 1's screenshot will be on the left, desk 2's screenshot will
//     be in the middle and desk 3's screenshot will be on the right.
//   * Once the animation finishes, Delegate::OnDeskSwitchAnimationFinished() is
//     triggered. The owner of this object can then check that all animators on
//     all roots have finished their animations (by checking
//     animation_finished()), upon which it can delete these animators which
//     will destroy all the screenshot layers and the real screen contents will
//     be visible again.
//
// This cooperative interaction between the animators and their owner
// (DesksController::AbstractDeskSwitchAnimation) is needed for the following
// reasons:
// 1- The new desk is only activated after all starting desk screenshots on all
//    roots have been taken and placed on top of everything (between phase (1)
//    and (2)), so that the effects of desk activation (windows hiding and
//    showing, overview exiting .. etc.) are not visible to the user.
// 2- The animation doesn't start until all ending desk screenshots on all
//    root windows are ready (between phase (2) and (3)). This is needed to
//    synchronize the animations on all displays together (otherwise the
//    animations will lag behind each other).
//
// When this animator is used to implement the remove-active-desk animation
// (which also involves switching desks; from the to-be-removed desk to another
// desk), `for_remove` is set to true in the constructor. The animation is
// slightly tweaked to do the following:
// - Instead of taking a screenshot of the starting desk, we replace it by a
//   black solid color layer, to indicate the desk is being removed.
// - The layer tree of the active-desk container is recreated, and the old
// layers are detached.
// - That old layer tree stays still on screen while the desks screenshots are
// animating horizontally.

// The types of animations, see detailed comments above for `Quick Animation`
// and `Continuous Animation` animation types.
enum class DeskSwitchAnimationType {
  kQuickAnimation,
  kContinuousAnimation,
};

class ASH_EXPORT RootWindowDeskSwitchAnimator
    : public ui::ImplicitAnimationObserver,
      public ShellObserver {
 public:
  class Delegate {
   public:
    // Called when phase (1) completes. The starting desk screenshot has been
    // taken and put on the screen. |ending_desk_index| is the index of the desk
    // that will be activated after all starting desk screenshots on all roots
    // are taken.
    virtual void OnStartingDeskScreenshotTaken(int ending_desk_index) = 0;

    // Called when phase (2) completes. The ending desk screenshot has been
    // taken and put on the screen. This can be called multiple times during the
    // lifetime of |this|.
    virtual void OnEndingDeskScreenshotTaken() = 0;

    // Called when phase (3) completes. The animation completes and the ending
    // desk screenshot is now showing on the screen.
    virtual void OnDeskSwitchAnimationFinished() = 0;

   protected:
    virtual ~Delegate() = default;
  };

  RootWindowDeskSwitchAnimator(aura::Window* root,
                               DeskSwitchAnimationType type,
                               int starting_desk_index,
                               int ending_desk_index,
                               Delegate* delegate,
                               bool for_remove);
  RootWindowDeskSwitchAnimator(const RootWindowDeskSwitchAnimator&) = delete;
  RootWindowDeskSwitchAnimator& operator=(const RootWindowDeskSwitchAnimator&) =
      delete;
  ~RootWindowDeskSwitchAnimator() override;

  int ending_desk_index() const { return ending_desk_index_; }

  bool starting_desk_screenshot_taken() const {
    return starting_desk_screenshot_taken_;
  }
  bool ending_desk_screenshot_taken() const {
    return ending_desk_screenshot_taken_;
  }
  bool animation_finished() const { return animation_finished_; }
  bool reached_edge() const { return reached_edge_; }
  void set_is_combine_desks_type(bool is_combine_desks_type) {
    is_combine_desks_type_ = is_combine_desks_type;
  }

  // When true, this indicates that the animator has failed and we cannot
  // proceed. Reasons for failure currently includes failures to take a
  // screenshot (either the first or the second), or that the root window has
  // gone away.
  bool animator_failed() const { return animator_failed_; }

  // Begins phase (1) of the animation by taking a screenshot of the starting
  // desk content. Delegate::OnStartingDeskScreenshotTaken() will be called once
  // the screenshot is taken and placed on top of everything on the screen.
  void TakeStartingDeskScreenshot();

  // Begins phase (2) of the animation, after the ending desk has already
  // been activated. Delegate::OnEndingDeskScreenshotTaken() will be called once
  // the screenshot is taken.
  void TakeEndingDeskScreenshot();

  // Begins phase (3) of the animation by actually animating the screenshot
  // layers such that we have a movement from the starting desk screenshot
  // towards the ending desk screenshot.
  // Delegate::OnDeskSwitchAnimationFinished() will be called once the animation
  // finishes.
  void StartAnimation();

  // Replace the current animation with one that goes to
  // |new_ending_desk_index|. Returns true if a screenshot of the new desk needs
  // to be taken.
  bool ReplaceAnimation(int new_ending_desk_index);

  // Called as a user is performing a touchpad swipe. Requests a new screenshot
  // if necessary based on the last direction as specified in |scroll_delta_x|.
  // |scroll_delta_x| is in touchpad units, it will be converted to display
  // units and then used to shift the animation layer. If the animation layer is
  // near its boundaries, this will return an index for the desk we should take
  // a screenshot for. If we are not near the boundaries, or if there is no next
  // adjacent desk in the direction we are heading, return std::nullopt. The
  // delegate is responsible for requesting the screenshot.
  std::optional<int> UpdateSwipeAnimation(float scroll_delta_x);

  // Maybe called after UpdateSwipeAnimation() if we need a new screenshot.
  // Updates |ending_desk_index_| and resets some other internal state related
  // to the ending desk screenshot.
  void PrepareForEndingDeskScreenshot(int new_ending_desk_index);

  // Called when a user ends a touchpad swipe. This will animate to the most
  // visible desk, whose index is also returned. If |is_fast_swipe| is true, we
  // will use a different logic to determine which ending desk index we want to
  // end at.
  int EndSwipeAnimation(bool is_fast_swipe);

  // Gets the index of the desk whose screenshot of the animation layer is most
  // visible to the user. That desk screenshot is the one which aligns the most
  // with the root window bounds.
  int GetIndexOfMostVisibleDeskScreenshot() const;

  // ui::ImplicitAnimationObserver:
  void OnImplicitAnimationsCompleted() override;

  // ShellObserver:
  void OnRootWindowWillShutdown(aura::Window* root_window) override;

  ui::Layer* GetAnimationLayerForTesting() const;

 private:
  friend class RootWindowDeskSwitchAnimatorTestApi;

  // Completes the first phase of the animation using the given |layer| as the
  // screenshot layer of the starting desk. This layer will be parented to the
  // animation layer, which will be setup with its initial transform according
  // to |starting_desk_index_| and |ending_desk_index_|. If |for_remove_| is
  // true, the detached old layer tree of the soon-to-be-removed-desk's windows
  // will stay still on screen until the target desk moves in.
  // `Delegate::OnStartingDeskScreenshotTaken()` will be called at the end.
  void CompleteAnimationPhase1WithLayer(std::unique_ptr<ui::Layer> layer);

  void OnStartingDeskScreenshotTaken(
      std::unique_ptr<viz::CopyOutputResult> copy_result);
  void OnEndingDeskScreenshotTaken(
      std::unique_ptr<viz::CopyOutputResult> copy_result);

  // Called when a screenshot layer is created and added to the animation layer.
  // Sets its bounds and transforms the animation layer to the correct starting
  // position.
  void OnScreenshotLayerCreated();

  // Gets the x position of the |screenshot_layer_| associated with |index| in
  // its parent layer's coordinates (|animation_layer_owner_->root()|).
  int GetXPositionOfScreenshot(int index);

  // The root window that this animator is associated with.
  raw_ptr<aura::Window, DanglingUntriaged> root_window_;

  // The type of animator, this will determine what type of animation is
  // created.
  const DeskSwitchAnimationType type_;

  // The index of the active desk at the start of the animation.
  int starting_desk_index_;

  // The index of the desk to activate and animate to with this animator.
  int ending_desk_index_;

  const raw_ptr<Delegate> delegate_;

  // The owner of the layer tree of the old detached layers of the removed
  // desk's windows. This is only valid if |for_remove_| is true. This layer
  // tree will stay still on screen during the period of desk switch animation.
  std::unique_ptr<ui::LayerTreeOwner> old_windows_layer_tree_owner_;

  // The owner of the layer tree that contains the parent "animation layer" and
  // both its child starting and ending desks "screenshot layers".
  std::unique_ptr<ui::LayerTreeOwner> animation_layer_owner_;

  // Stores the layers of taken screenshots. This vector is the same size as
  // desks_util::kMaxNumberOfDesks and the screenshot at index i will correspond
  // to desk i but the layers will be nullptr until they are needed. For
  // example, for a desk activation animation from desk index 0 -> 1 will have
  // screenshots of desk 0 and desk 1 stored at indices 0 and 1, but the
  // remaining indices will have nullptr. The layers, if not null are owned by
  // |animation_layer_owner_|.
  std::vector<raw_ptr<ui::Layer, VectorExperimental>> screenshot_layers_;

  // Stores the size of |root_window_| that takes into account all scale factors
  // by snapping to the edge of the display. This will prevent any 1px gaps we
  // may see while switching desks. Prefer to use this in all calculations over
  // |root_window_| get bounds functions.
  const gfx::Size root_window_size_;

  // The amount by which the animation layer will be translated horizontally
  // either startingly or at the end of the animation, depending on the value of
  // of the desk indices.
  const int x_translation_offset_;

  // The amount of padding in dips on the edges of the animation layer.
  const int edge_padding_width_dp_;

  // Number of retires for taking the starting and ending screenshots, if we
  // get an empty result.
  int starting_desk_screenshot_retries_ = 0;
  int ending_desk_screenshot_retries_ = 0;

  // True if this animator is handling the remove-active-desk animation.
  const bool for_remove_;

  // True when phase (1) finishes.
  bool starting_desk_screenshot_taken_ = false;

  // True when phase (2) finishes.
  bool ending_desk_screenshot_taken_ = false;

  // True when phase (3) finishes.
  bool animation_finished_ = false;

  // True if this animator has failed, for any reason. This currently includes
  // repeated failures to screenshot a desk, or the root window going away.
  bool animator_failed_ = false;

  // True if during a continuous swipe, the user went all the way left or right
  // and swiping in that direction will no longer update the UI.
  bool reached_edge_ = false;

  // True while setting a new transform for chaining. If a animation is active,
  // calling SetTransform will trigger OnImplicitAnimationsCompleted. In these
  // cases we do not want to notify our delegate that the animation is finished.
  bool setting_new_transform_ = false;

  // When desk close type is `kCombineDesks`, we animate windows moving to the new
  // desk, otherwise only show desk switching animation.
  bool is_combine_desks_type_ = false;

  // Callbacks that are run after the screenshots are taken for testing
  // purposes. Waiting for the ending screenshots means you will implicitly wait
  // for the starting screenshots too.
  base::OnceClosure on_starting_screenshot_taken_callback_for_testing_;
  base::OnceClosure on_ending_screenshot_taken_callback_for_testing_;

  base::WeakPtrFactory<RootWindowDeskSwitchAnimator> weak_ptr_factory_{this};
};

}  // namespace ash

#endif  // ASH_WM_DESKS_ROOT_WINDOW_DESK_SWITCH_ANIMATOR_H_