File: animating_layout_manager.h

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (405 lines) | stat: -rw-r--r-- 17,333 bytes parent folder | download | duplicates (8)
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_