1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
|
// 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 UI_VIEWS_LAYOUT_ANIMATING_LAYOUT_MANAGER_H_
#define UI_VIEWS_LAYOUT_ANIMATING_LAYOUT_MANAGER_H_
#include <memory>
#include <utility>
#include <vector>
#include "base/memory/raw_ptr.h"
#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).
//
// 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->SetBoundsAnimationMode(
// AnimatingLayoutManager::BoundsAnimationMode::kAnimateMainAxis);
// 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 if and how the bounds of the host view can be animated as part of
// layout animations, if the preferred size of the layout changes.
enum class BoundsAnimationMode {
// Default behavior: the host view will always take the space given to it by
// its parent view and child views will animate within those bounds. Useful
// for cases where the layout is in a fixed-size container or dialog, but
// we want child views to be able to animate.
kUseHostBounds,
// The host view will request more or less space within the available space
// offered by its parent view, allowing its main axis size to animate, but
// will use exactly the cross-axis space provided, as it would with
// kUseHostBounds. Useful if the host view is in a toolbar or a dialog with
// fixed width but variable height or vice-versa.
kAnimateMainAxis,
// The host view will request more space or less space in both axes within
// the available space offered by its parent view. Useful if the host view
// is in e.g. a dialog that can vary in size.
kAnimateBothAxes
};
// 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,
// A view fading in will slide out from the trailing edge and fade in. If
// the view does not paint to a layer (which is necessary to perform an
// opacity animation) we fall back to |kSlideFromTrailingEdge|.
kFadeAndSlideFromTrailingEdge,
};
AnimatingLayoutManager();
AnimatingLayoutManager(const AnimatingLayoutManager&) = delete;
AnimatingLayoutManager& operator=(const AnimatingLayoutManager&) = delete;
~AnimatingLayoutManager() override;
BoundsAnimationMode bounds_animation_mode() const {
return bounds_animation_mode_;
}
AnimatingLayoutManager& SetBoundsAnimationMode(
BoundsAnimationMode bounds_animation_mode);
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);
base::TimeDelta opacity_animation_duration() const {
return opacity_animation_duration_;
}
// Note this is only needed if using kFadeAndSlideFromTrailingEdge. The
// duration will not run longer than |animation_duration_| and if shorter than
// |animation_duration_| the opacity animation will run during the latter part
// of the fade in the the start of the fade out.
AnimatingLayoutManager& SetOpacityAnimationDuration(
base::TimeDelta animation_duration);
gfx::Tween::Type opacity_tween_type() const { return opacity_tween_type_; }
AnimatingLayoutManager& SetOpacityTweenType(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 GetPreferredSize(const View* host,
const SizeBounds& available_size) const override;
gfx::Size GetMinimumSize(const View* host) const override;
int GetPreferredHeightForWidth(const View* host, int width) const override;
std::vector<raw_ptr<View, VectorExperimental>> 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();
const ProposedLayout& starting_layout_for_testing() const {
return starting_layout_;
}
const ProposedLayout& target_layout() const { return target_layout_; }
protected:
// LayoutManagerBase:
ProposedLayout CalculateProposedLayout(
const SizeBounds& size_bounds) const override;
void OnInstalled(View* host) override;
bool OnViewAdded(View* host, View* view) override;
void OnLayoutChanged() override;
void LayoutImpl() override;
private:
struct LayoutFadeInfo;
class AnimationDelegate;
friend class AnimationDelegate;
// Cleans up after an animation and readies actions to be posted.
void EndAnimation();
// 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, double fade_in_opacity, double fade_out_opacity);
// 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,
double fade_in_opacity,
double fade_out_opacity);
// 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;
ChildLayout CalculateFadeAndSlideFade(const LayoutFadeInfo& fade_info,
double scale_percent,
double opacity_value,
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);
// How to animate bounds of the host view when the preferred size of the
// layout changes.
BoundsAnimationMode bounds_animation_mode_ =
BoundsAnimationMode::kUseHostBounds;
// 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::Milliseconds(250);
// The motion curve of the animation to perform.
gfx::Tween::Type tween_type_ = gfx::Tween::EASE_IN_OUT;
// How long each opacity animation takes. Note this is only used if using the
// kFadeAndSlideFromTrailingEdge FadeInOutMode. And is capped at the
// |animation_duraction_|.
base::TimeDelta opacity_animation_duration_ = base::Milliseconds(0);
// The motion curve of the opacity animation to perform.
gfx::Tween::Type opacity_tween_type_ = gfx::Tween::LINEAR;
// 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_;
// Signal that we want to post queued actions at the end of the next layout
// cycle.
bool hold_queued_actions_for_layout_ = false;
// 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};
};
} // namespace views
#endif // UI_VIEWS_LAYOUT_ANIMATING_LAYOUT_MANAGER_H_
|