blob: 2b997a6bbb1ca237a495fcd7f635e77242441b3d [file] [log] [blame]
// 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.
#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 "base/callback.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. We take screenshots to make the
// visible state of the desks seem constant to the user (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.
// - `starting` desk: is the currently activated desk which will be deactivated
// shortly.
// - `ending` desk: is the desk desired to be activated with this animation.
// These can be changed if the enhanced desk animations feature is enabled using
// ReplaceAnimation() or UpdateSwipeAnimation().
//
// The animation goes through the following phases:
//
// - 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().
// * Once the screenshot is taken, it is placed in a sibling layer to the
// starting desk screenshot layer, with an offset of |kDesksSpacing| between
// the two layers.
// * 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.
// * 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:
// * 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).
//
// * 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 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, 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 and animated vertically by
// `kRemovedDeskWindowYTranslation`.
// - That old layer tree is then translated back down by the same amount while
// the desks screenshots are animating horizontally.
// This gives the effect that the removed desk windows are jumping from their
// desk to the target desk.
class ASH_EXPORT RootWindowDeskSwitchAnimator
: public ui::ImplicitAnimationObserver {
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;
};
// The space between the starting and ending desks screenshots in dips.
static constexpr int kDesksSpacing = 50;
// The animation layer has extra padding at its two edges. The width in dips
// is a ratio of the root window width. This padding is to notify users there
// are no more desks on that side by showing a black region as we swipe
// continuously.
static constexpr float kEdgePaddingRatio = 0.15f;
// In touchpad units, a touchpad swipe of this length will correspond to a
// full desk change.
static constexpr int kTouchpadSwipeLengthForDeskChange = 420;
RootWindowDeskSwitchAnimator(aura::Window* root,
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_; }
// 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 base::nullopt. The
// delegate is responsible for requesting the screenshot.
base::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.
int EndSwipeAnimation();
// 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;
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 be translated up vertically to simulate a jump from the removed desk
// to the target desk. |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.
aura::Window* const root_window_;
// 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_;
Delegate* const 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 is animated to simulate that the windows are jumping from the removed
// desk to the target desk.
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<ui::Layer*> 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 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;
// Callback that is run after the ending screenshot is taken for testing
// purposes.
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_