blob: 207c1edef77b8d4085329c100aacce7fe1eed188 [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 UI_VIEWS_LAYOUT_ANIMATING_LAYOUT_MANAGER_H_
#define UI_VIEWS_LAYOUT_ANIMATING_LAYOUT_MANAGER_H_
#include <memory>
#include <utility>
#include <vector>
#include "base/observer_list.h"
#include "base/time/time.h"
#include "ui/gfx/animation/tween.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/layout_manager_base.h"
#include "ui/views/views_export.h"
namespace gfx {
class AnimationContainer;
} // namespace gfx
namespace views {
// Layout manager which explicitly animates its child views and/or its preferred
// size when the target layout changes (the target layout being provided by a
// separate, non-animating layout manager; typically a FlexLayout or
// InterpolatingLayoutManager).
//
// For example, consider a view in which multiple buttons can be displayed
// depending on context, in a horizontal row. When we add a button, we want all
// the buttons to the left to slide over and the new button to appear in the
// gap:
// | [a] [b] [c] |
// | [a] [b] [c] |
// | [a] [b] . [c] |
// | [a] [b] .. [c] |
// | [a] [b] [x] [c] |
//
// Without AnimatingLayoutManager you would have to explicitly animate the
// bounds of the host view and the layout elements each frame, calculating which
// go where. With AnimatingLayout you create a single declarative layout for the
// whole thing and just insert the button where you want it. Here's the setup:
//
// auto* animating_layout = button_container->SetLayoutManager(
// std::make_unique<AnimatingLayoutManager>());
// animating_layout->SetShouldAnimateBounds(true);
// auto* flex_layout = animating_layout->SetTargetLayoutManager(
// std::make_unique<FlexLayout>());
// flex_layout->SetOrientation(LayoutOrientation::kHorizontal)
// .SetCollapseMargins(true)
// .SetDefault(kMarginsKey, gfx::Insets(5));
//
// Now, when you want to add (or remove) a button and animate, you just call:
//
// button_container->AddChildViewAt(button, position);
// button_container->RemoveChildView(button);
//
// The bounds of |button_container| will then animate appropriately and |button|
// will either appear or disappear in the appropriate location.
//
// Note that under normal operation, any changes made to the host view before
// being added to a widget will not result in animation. If initial setup of the
// host view happens after being added to a widget, you can call ResetLayout()
// to prevent changes made during setup from animating.
class VIEWS_EXPORT AnimatingLayoutManager : public LayoutManagerBase {
public:
class VIEWS_EXPORT Observer : public base::CheckedObserver {
public:
virtual void OnLayoutIsAnimatingChanged(AnimatingLayoutManager* source,
bool is_animating) = 0;
};
// Describes how a view which is appearing or disappearing during an animation
// behaves. Child views which are removed from the parent view always simply
// disappear; use one of the Fade methods below to cause a view to fade out.
//
// TODO(dfried): break this out into layout_types and make a view class
// property so that it can be set separately on each child view.
enum class FadeInOutMode {
// Default behavior: a view fading in or out is hidden during the animation.
kHide,
// A view fading in or out shrinks to or from nothing.
kScaleFromZero,
// A view fading in or out appears or disappears when it hits its minimum
// size, and scales the rest of the way in or out.
kScaleFromMinimum,
// A view fading in will slide out from under the view on its leading edge;
// if no view is present a suitable substitute fade is chosen.
kSlideFromLeadingEdge,
// A view fading in will slide out from under the view on its trailing edge;
// if no view is present a suitable substitute fade is chosen.
kSlideFromTrailingEdge,
};
AnimatingLayoutManager();
~AnimatingLayoutManager() override;
bool should_animate_bounds() const { return should_animate_bounds_; }
AnimatingLayoutManager& SetShouldAnimateBounds(bool should_animate_bounds);
base::TimeDelta animation_duration() const { return animation_duration_; }
AnimatingLayoutManager& SetAnimationDuration(
base::TimeDelta animation_duration);
gfx::Tween::Type tween_type() const { return tween_type_; }
AnimatingLayoutManager& SetTweenType(gfx::Tween::Type tween_type);
LayoutOrientation orientation() const { return orientation_; }
AnimatingLayoutManager& SetOrientation(LayoutOrientation orientation);
FadeInOutMode default_fade_mode() const { return default_fade_mode_; }
AnimatingLayoutManager& SetDefaultFadeMode(FadeInOutMode default_fade_mode);
bool is_animating() const { return is_animating_; }
// Sets the owned (non-animating) layout manager which defines the target
// layout that will be animated to when it changes. This layout manager can
// only be set once.
template <class T>
T* SetTargetLayoutManager(std::unique_ptr<T> layout_manager) {
DCHECK_EQ(0U, num_owned_layouts());
T* const result = AddOwnedLayout(std::move(layout_manager));
ResetLayout();
return result;
}
LayoutManagerBase* target_layout_manager() {
return num_owned_layouts() ? owned_layout(0) : nullptr;
}
const LayoutManagerBase* target_layout_manager() const {
return num_owned_layouts() ? owned_layout(0) : nullptr;
}
// Clears any previous layout, stops any animation, and re-loads the proposed
// layout from the embedded layout manager. Also invalidates the host view.
void ResetLayout();
// Causes the specified child view to fade out and become hidden. Alternative
// to directly hiding the view (which will have the same effect, but could
// cause visual flicker if the view paints before it can re-layout.
void FadeOut(View* child_view);
// Causes the specified child view to fade in and become visible. Alternative
// to directly showing the view (which will have the same effect, but could
// cause visual flicker if the view paints before it can re-layout.
void FadeIn(View* child_view);
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
bool HasObserver(Observer* observer) const;
// LayoutManagerBase:
gfx::Size GetPreferredSize(const View* host) const override;
gfx::Size GetMinimumSize(const View* host) const override;
int GetPreferredHeightForWidth(const View* host, int width) const override;
std::vector<View*> GetChildViewsInPaintOrder(const View* host) const override;
bool OnViewRemoved(View* host, View* view) override;
// Queues an action to take place after the current animation completes.
// If |action| needs access to external resources, views, etc. then it must
// check that those resources are still available and valid when it is run. If
// the layout is not animating the action is posted immediately.
// There is no guarantee that this action runs as the AnimatingLayoutManager
// may get torn down before the task runs.
void PostOrQueueAction(base::OnceClosure action);
// Returns a flex rule for the host view that will work in the vast majority
// of cases where the host view is embedded in a FlexLayout.
FlexRule GetDefaultFlexRule() const;
// Returns the animation container being used by the layout manager, creating
// one if one has not yet been created. Implicitly enables animation on this
// layout, so you do not need to also call EnableAnimationForTesting().
gfx::AnimationContainer* GetAnimationContainerForTesting();
// Enables animation of this layout even if the host view does not yet meet
// the normal requirements for being able to animate (e.g. being added to a
// widget).
void EnableAnimationForTesting();
protected:
// LayoutManagerBase:
ProposedLayout CalculateProposedLayout(
const SizeBounds& size_bounds) const override;
void OnInstalled(View* host) override;
void OnLayoutChanged() override;
void LayoutImpl() override;
private:
struct LayoutFadeInfo;
class AnimationDelegate;
friend class AnimationDelegate;
// Cleans up after an animation, runs delayed actions, and sends
// notifications.
void OnAnimationEnded();
// Equivalent to calling ResetLayoutToSize(GetAvailableTargetLayoutSize()).
// Convenience method.
void ResetLayoutToTargetSize();
// Does the work of ResetLayout(), with the resulting layout snapped to
// |target_size|.
void ResetLayoutToSize(const gfx::Size& target_size);
// Calculates the new target layout and returns true if it has changed.
bool RecalculateTarget();
// Called by the animation logic every time a new frame happens.
void AnimateTo(double value);
// Notifies all observers that the animation state has changed.
void NotifyIsAnimatingChanged();
// Runs actions from earlier PostTask() calls.
void RunQueuedActions();
// Moves actions from |queued_actions_| to |actions_to_run_| and posts to
// RunDelayedTasks.
void PostQueuedActions();
// Updates the current layout to |percent| interpolated between the starting
// and target layouts.
void UpdateCurrentLayout(double percent);
// Updates information about which views are fading in or out during the
// current animation.
void CalculateFadeInfos();
// Called when resetting the layout; resolves any in-progress fades so that a
// view that should be rendered invisible actually is.
void ResolveFades();
// Calculates a kScaleFrom[Minimum|Zero] fade and returns the resulting child
// layout info.
ChildLayout CalculateScaleFade(const LayoutFadeInfo& fade_info,
double scale_percent,
bool scale_from_zero) const;
// Calculates a kSlideFrom[Leading|Trailing]Edge fade and returns the
// resulting child layout info.
ChildLayout CalculateSlideFade(const LayoutFadeInfo& fade_info,
double scale_percent,
bool slide_from_leading) const;
// Returns the space in which to calculate the target layout.
gfx::Size GetAvailableTargetLayoutSize();
// Implementation of the default flex rule for animating layout manager.
// See GetDefaultFlexRule() above.
static gfx::Size DefaultFlexRuleImpl(
const AnimatingLayoutManager* animating_layout,
const View* view,
const SizeBounds& size_bounds);
// Whether or not to animate the bounds of the host view when the preferred
// size of the layout changes. If false, the size will have to be set
// explicitly by the host view's owner. Bounds animation is done by changing
// the preferred size and invalidating the layout.
bool should_animate_bounds_ = false;
// How long each animation takes. Depending on how far along an animation is,
// a new target layout will either cause the animation to restart or redirect.
base::TimeDelta animation_duration_ = base::TimeDelta::FromMilliseconds(250);
// The motion curve of the animation to perform.
gfx::Tween::Type tween_type_ = gfx::Tween::EASE_IN_OUT;
// The layout orientation, used for side and scale fades.
LayoutOrientation orientation_ = LayoutOrientation::kHorizontal;
// The default fade mode.
FadeInOutMode default_fade_mode_ = FadeInOutMode::kHide;
// Used to determine when to fire animation events.
bool is_animating_ = false;
// Where in the animation the last layout recalculation happened.
double starting_offset_ = 0.0;
// The current animation progress.
double current_offset_ = 1.0;
// The restrictions on the layout's size the last time we recalculated our
// target layout. If they have changed, we may need to recalculate the target
// of the current animation.
//
// Contrast with LayoutManagerBase::cached_available_size_, which tracks
// changes from one layout application to the next and affects re-layout of
// children; this value tracks changes from one layout *calculation* to
// the next and affects recalculation of *this* layout.
SizeBounds last_available_host_size_;
// The layout being animated away from.
ProposedLayout starting_layout_;
// The current state of the layout, possibly between |starting_layout_| and
// |target_layout_|.
ProposedLayout current_layout_;
// The desired layout being animated to. When the animation is complete,
// |current_layout_| will match |target_layout_|.
ProposedLayout target_layout_;
// Stores information about elements fading in or out of the layout.
std::vector<LayoutFadeInfo> fade_infos_;
std::unique_ptr<AnimationDelegate> animation_delegate_;
base::ObserverList<Observer, true> observers_;
// Actions to be run as animations finish. This is split between queued
// actions and queued actions to be run as a result of a pending PostTask().
// This prevents a race condition where PostTask() would pick up queued
// actions from future delayed actions during animations that were added after
// PostTask() ran, even if the layout is animating.
// For example: PostTask() due to finished layout -> start layout animation ->
// queue action -> the posted task runs while still animating.
// Without this division of actions + actions to run PostTask would pick up
// the queued task even though it belonged to a later animation that hasn't
// yet finished.
std::vector<base::OnceClosure> queued_actions_;
std::vector<base::OnceClosure> queued_actions_to_run_;
// True when there's a pending PostTask() to RunQueuedActions(). Used to avoid
// scheduling redundant tasks.
bool run_queued_actions_is_pending_ = false;
base::WeakPtrFactory<AnimatingLayoutManager> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(AnimatingLayoutManager);
};
} // namespace views
#endif // UI_VIEWS_LAYOUT_ANIMATING_LAYOUT_MANAGER_H_