Back to Chromium

Browser Animation Controller

chrome/browser/ui/animation/README.md

150.0.7829.229.7 KB
Original Source

Browser Animation Controller

This is a browser-window-level service which coordinates multi-View and multi-part animations, such as the Vertical Tab Strip expand/collapse, side panel show/hide, etc.

It provides an interface for declaratively specifying animations, including using (but not limited to) syntax similar to CSS.

It also allows for smooth redirection of animations, even mid-animation.

Motivation

Consider the toolbar-height side panel. When it shows, several things happen:

  • The panel itself expands
  • The area including the toolbar and the contents shrinks and moves over
  • A shadow appears around the toolbar and contents

These motions don't use the same animation curves, and they don't happen at exactly the same time. This can be confusing to program and it often isn't easy to translate the UX spec (which often uses CSS animation concepts) to a Views animation.

Furthermore, if the panel is in the middle of closing and then decides to open again, you have to write special-case code to blend the two animations together.

Browser Animation Controller is designed to make it simple to define these animations, have them behave correctly, and to allow views to subscribe for update events, without having to write any special-case code.

Terminology

The primary entry point for this system is BrowserAnimationController. This is unowned user data attached to a BrowserWindowInterface, so it can be retrieved using:

cpp
  auto* const controller =
      BrowserAnimationController::From(browser_window_interface);

Animations are broken down into:

  1. Group - a set of related animations. For example:
    • Toolbar-height side panel
    • Contents-height side panel
    • Vertical tab strip
  2. Motion - a particular motion inside a group. For example, for the toolbar-height side panel, there are three motions:
    • Open
    • Open with content transition
    • Close
  3. Sequence - changes to a specific visual element that happen over the course of a motion.
    • For example, when the toolbar-height side panel opens, the following may change:
      • Side panel width
      • Padding around the main area (toolbar, bookmarks, web contents)
      • Opacity of the shadow around the main area
    • You can also specify if a sequence value persists after a motion completes, what the default value is, and if it returns automatically to that default value, etc.
  4. Individual parts of a sequence:
    • Keyframe - similar to CSS keyframes; these map a timestamp to an animation value
    • Segment - a subset of a motion during which an animation value changes to a new value (time between two keyframes)
    • Tween - an interpolation curve used by either the entire motion, a single segment, or between two keyframes
    • Snap - the animation value jumps between specified values at a specific timestamp
  5. Transition - how sequence values modified by one motion and persisting afterwards participate in the next motion, allowing animations to flow into each other.

Groups, Motions, and Sequences are named using unique identifiers: BrowserAnimationGroup, BrowserAnimationMotion, and BrowserAnimationSequence, defined in browser_animation_types.h. Specify these when playing an animation or reading an animation value.

A BrowserAnimationProvider defines animations using a straightforward, declarative syntax. For example, this (partial) example uses keyframes:

cpp
  Group(kToolbarHeightSidePanelAnimationGroup,
        Motion(kSidePanelOpenMotion,
               // Animate the side panel for 350ms using an EASE_IN_OUT tween.
               Sequence(kSidePanelWidth,
                        Keyframe(AtMs(0), Value(0)),
                        Keyframe(AtMs(350), Value(1.0), gfx::Tween::EASE_IN_OUT)),
               // Animate the padding around the main contents area from 100 to
               // 450ms using an EASE_IN tween.
               Sequence(kMainAreaPaddingSize,
                        Keyframe(AtMs(100), Value(0)),
                        Keyframe(AtMs(450), Value(1.0), gfx::Tween::EASE_IN)),
               // Animate in the shadow opacity from 350ms to the end of the
               // animation using a LINEAR tween (i.e. no animation curve).
               Sequence(kMainAreaShadowOpacity,
                        Keyframe(AtMs(350), Value(0)),
                        Keyframe(AtMs(650), Value(1.0), gfx::Tween::LINEAR))),
        Motion(kSidePanelCloseMotion,
               // ...

All of the different ways to define an animation will be described later.

Creating Animations

In order to create an animation, you will need to subclass either BrowserAnimationProvider or CachingBrowserAnimationProvider and override a single method.

  • CachingBrowserAnimationProvider (recommended): defines all of its animations once, in a single function.
  • BrowserAnimationProvider: computes animations on demand, whenever an animation is started.

The caching version is simpler, but some animations may need to change in response to user or system preferences, or the state of the browser, so plain BrowserAnimationProvider is provided for this case.

You register the animations using BrowserAnimationController::AddAnimationProvider(). You should register the provider once, before you need it:

cpp
  BrowserAnimationController::From(browser)->AddAnimationProvider(
      std::make_unique<MyAnimations>());

Animation providers should be placed in chrome/browser/ui/views/animations. There are several examples already there you can use as guides.

Animations should be registered in BrowserWindowFeatures::InitPostBrowserViewConstruction().

You'll need to define your group(s), motions, and segments - either in your provider class using DECLARE/DEFINE_CLASS_BROWSER_ANIMATION_*() macros, or in a separate file using DECLARE/DEFINE_BROWSER_ANIMATION_*() macros. Putting them in the class allows you to use much shorter identifier names and is preferred.

Defining Animations with CachingBrowserAnimationProvider

This is the easier (and recommended) of the two options.

Derive from CachingBrowserAnimationProvider and override GenerateAnimations():

cpp
// In chrome/browser/ui/views/animations/my_animation_provider.h:

class MyAnimations : public CachingBrowserAnimationProvider {
 public:
  // Required to support retrieval.
  DECLARE_FRAMEWORK_SPECIFIC_IMPLEMENTATION()

  DECLARE_CLASS_BROWSER_ANIMATION_GROUP(kPanelGroup);
  DECLARE_CLASS_BROWSER_ANIMATION_GROUP(kFlyoverGroup);
  DECLARE_CLASS_BROWSER_ANIMATION_MOTION(kExpandMotion);
  DECLARE_CLASS_BROWSER_ANIMATION_MOTION(kCollapseMotion);
  DECLARE_CLASS_BROWSER_ANIMATION_MOTION(kFadeInMotion);
  DECLARE_CLASS_BROWSER_ANIMATION_MOTION(kFadeOutMotion);
  DECLARE_CLASS_BROWSER_ANIMATION_SEQUENCE(kPanelWidth);
  DECLARE_CLASS_BROWSER_ANIMATION_SEQUENCE(kElementHeight);
  DECLARE_CLASS_BROWSER_ANIMATION_SEQUENCE(kOpacity);

  GroupInfos GenerateAnimations() override;
};

// In chrome/browser/ui/views/animations/my_animation_provider.cc:

DEFINE_FRAMEWORK_SPECIFIC_IMPLEMENTATION(MyAnimations)

DEFINE_CLASS_BROWSER_ANIMATION_GROUP(MyAnimations, kPanelGroup);
DEFINE_CLASS_BROWSER_ANIMATION_GROUP(MyAnimations, kFlyoverGroup);
DEFINE_CLASS_BROWSER_ANIMATION_MOTION(MyAnimations, kExpandMotion);
DEFINE_CLASS_BROWSER_ANIMATION_MOTION(MyAnimations, kCollapseMotion);
DEFINE_CLASS_BROWSER_ANIMATION_MOTION(MyAnimations, kFadeInMotion);
DEFINE_CLASS_BROWSER_ANIMATION_MOTION(MyAnimations, kFadeOutMotion);
DEFINE_CLASS_BROWSER_ANIMATION_SEQUENCE(MyAnimations, kPanelWidth);
DEFINE_CLASS_BROWSER_ANIMATION_SEQUENCE(MyAnimations, kElementHeight);
DEFINE_CLASS_BROWSER_ANIMATION_SEQUENCE(MyAnimations, kOpacity);

MyAnimations::GroupInfos
MyAnimations::GenerateAnimations() {
  return Groups(
      // Panel slides in and out while also growing vertically:
      Group(kPanelGroup,
            Motion(kExpandMotion, /* Insert sequences here. */),
            Motion(kCollapseMotion, /* Insert sequences here. */)),
      // Flyover expands/contracts and fades in/out:
      Group(kFlyoverGroup,
            Motion(kExpandMotion, /* Insert sequences here. */),
            Motion(kFadeOutMotion, /* Insert sequences here. */)));
}

See below for the syntax for sequences. Note that for CachingBrowserAnimationProvider, Motion() requires specifying the BrowserAnimationMotion as its first parameter.

Defining Animations with BrowserAnimationProvider

This is the more complex of the two options, but provides the ability to dynamically generate parameters as the state of the browser changes.

Derive from BrowserAnimationProvider and override GetMotionSpecificationImpl(). You must manually determine which animation sequences to build based on the group and motion parameters. Return std::nullopt if you do not provide that motion in that group.

Example:

cpp
// .h file:
class MyAnimations : public BrowserAnimationProvider {
 public:
  // Required to support retrieval.
  DECLARE_FRAMEWORK_SPECIFIC_IMPLEMENTATION()

  DECLARE_CLASS_BROWSER_ANIMATION_GROUP(kPanelGroup);
  DECLARE_CLASS_BROWSER_ANIMATION_GROUP(kFlyoverGroup);
  DECLARE_CLASS_BROWSER_ANIMATION_MOTION(kExpandMotion);
  DECLARE_CLASS_BROWSER_ANIMATION_MOTION(kCollapseMotion);
  DECLARE_CLASS_BROWSER_ANIMATION_MOTION(kFadeInMotion);
  DECLARE_CLASS_BROWSER_ANIMATION_MOTION(kFadeOutMotion);
  DECLARE_CLASS_BROWSER_ANIMATION_SEQUENCE(kPanelWidth);
  DECLARE_CLASS_BROWSER_ANIMATION_SEQUENCE(kElementHeight);
  DECLARE_CLASS_BROWSER_ANIMATION_SEQUENCE(kOpacity);

  std::optional<internal::MotionSpecification> GetMotionSpecificationImpl(
      BrowserAnimationGroup group,
      BrowserAnimationMotion motion) const override;
};

// .cc file:
DEFINE_FRAMEWORK_SPECIFIC_IMPLEMENTATION(MyAnimations)

DEFINE_CLASS_BROWSER_ANIMATION_GROUP(MyAnimations, kPanelGroup);
DEFINE_CLASS_BROWSER_ANIMATION_GROUP(MyAnimations, kFlyoverGroup);
DEFINE_CLASS_BROWSER_ANIMATION_MOTION(MyAnimations, kExpandMotion);
DEFINE_CLASS_BROWSER_ANIMATION_MOTION(MyAnimations, kCollapseMotion);
DEFINE_CLASS_BROWSER_ANIMATION_MOTION(MyAnimations, kFadeInMotion);
DEFINE_CLASS_BROWSER_ANIMATION_MOTION(MyAnimations, kFadeOutMotion);
DEFINE_CLASS_BROWSER_ANIMATION_SEQUENCE(MyAnimations, kPanelWidth);
DEFINE_CLASS_BROWSER_ANIMATION_SEQUENCE(MyAnimations, kElementHeight);
DEFINE_CLASS_BROWSER_ANIMATION_SEQUENCE(MyAnimations, kOpacity);

std::optional<internal::MotionSpecification>
MyAnimations::GetMotionSpecification(
    BrowserAnimationGroup group,
    BrowserAnimationMotion motion) {
  // Have to explicitly check for groups and motions I support.
  if (group == kPanelGroup) {
    if (motion == kExpandMotion) {
      return Motion(/* Insert sequences here. */);
    } else if (motion == kCollapseMotion) {
      return Motion(/* Insert sequences here. */);
    }
  } else if (group == kFlyoverGroup) {
    if (motion == kFadeInMotion) {
      return Motion(/* Insert sequences here. */);
    } else if (motion == kFadeOutMotion) {
      return Motion(/* Insert sequences here. */);
    }
  }
  // I don't handle any other animations or motions, so return null.
  return std::nullopt;
}

See below for the syntax for sequences. Note that for BrowserAnimationProvider, Motion() does not require specifying the BrowserAnimationMotion as its first parameter.

What Happens When There Is More Than One Provider

Providers are checked from most-recently-added to least-recently-added. The first provider that can provide an animation motion does so. This allows motions to be overridden for e.g. testing by simply adding another provider after all the others.

Normally, only one provider would provide a given motion.

Defining Animation Sequences

There are four different types of ways to specify sequences:

  • Snap: animates from starting to ending value at a particular time/timestamp in the motion.
  • Full-motion sequence: animates from starting to ending value throughout the entire motion.
  • Keyframe sequence: value will hit each keyframe as the motion progresses
  • Segment sequence: starts at a specified value, then animate over one or more segments (subsets of the overall motion).

When specifying timestamps for snaps, keyframes, and segments, you can use two different approaches:

  • Percentage of the overall animation.
  • Absolute time in milliseconds.

You cannot mix and match milliseconds and percentages within the same sequence (exception: zero ms and zero percent are equivalent).

Snap Sequences

Snap sequences provide a stepwise animation function.

Syntax for snap sequences is simple:

cpp
  Snap(sequence_name,
       FromValue(starting_value), ToValue(ending_value),
       AtPercent(progress_percent));

  Snap(sequence_name,
       FromValue(starting_value), ToValue(ending_value),
       AtMs(absolute_time));

For example:

cpp
  // Snap the top padding from 0% to 100% halfway through the motion.
  Snap(kTopPadding, FromValue(0.0), ToValue(1.0), AtPercent(0.5))

You may snap at the very start or end of a motion.

Full-Motion Sequences

A full-motion sequence uses the Animate() sequence declaration:

cpp
  Animate(sequence_name,
          FromValue(starting_value), ToValue(ending_value)
          [, tween])

For example:

cpp
  // Slide the panel out over the entire animation, using the global tween.
  Animate(kPanelWidth, FromValue(0.0), ToValue(1.0))

  // Slide the top of the panel down over the entire animation, applying an
  // ease in/out.
  Animate(kPanelTop, FromValue(0.0), ToValue(1.0), gfx::Tween::EASE_IN_OUT)

These will always play over the entire length of the animation. When using Animate(), you must specify the motion's total duration (see below).

Keyframe Sequences

A keyframe sequence is a series of Keyframe() declarations; similar to CSS:

cpp
  Sequence(sequence_name,
           Keyframe(AtMs(first_keyframe_time),
                   Value(first_keyframe_value)),
           Keyframe(AtMs(second_keyframe_time),
                   Value(second_keyframe_value)
                   [, tween]),
           [, Keyframe(...)]);

  Sequence(sequence_name,
           Keyframe(AtPercent(first_keyframe_percent),
                    Value(first_keyframe_value)),
           Keyframe(AtPercent(second_keyframe_percent),
                   Value(second_keyframe_value)
                   [, tween]),
           [, Keyframe(...)]);

The animation value will start at the value of the first keyframe, and end at the value of the last keyframe. You may specify keyframes at the start (time zero) and end of the animation.

Keyframes must be specified in order of time. If two subsequent keyframes are at the exact same time, the value of the sequence will jump from the first value to the second.

If two subsequent keyframes have the same value, no animation takes place between them.

If keyframes are specified in terms of percentage, you must specify the motion's total duration (see below). Keyframes in the same sequence cannot mix and match percentage and absolute time.

Keyframe Sequence Example

cpp
  Sequence(kMyAnimationElement,
           Keyframe(AtMs(100), Value(0)),
           Keyframe(AtMs(350), Value(0.7), gfx::Tween::EASE_IN_OUT),
           Keyframe(AtMs(500), Value(0.7)),
           Keyframe(AtMs(750), Value(1.0), gfx::Tween::LINEAR))

In this example:

  • The animation starts at 0.
  • From 100ms to 350ms it animates to 0.7 using ease-in-out.
  • From 350ms to 500ms it remains at 0.7.
  • From 500ms to 750ms it animates linearly to 1.0.
  • At 750ms this element is finished animating.

Segment Sequences

A segment sequence has a defined starting value, then only animates over specific segments, which are subsets of the motion.

Syntax for segment sequences is:

cpp
  Sequence(sequence_name,
           StartingAt(initial_value),
           Segment(StartPercent(first_segment_start),
                   EndPercent(first_segment_end),
                   AnimateTo(first_segment_end_value)
                   [, tween]),
           Segment(StartPercent(second_segment_start),
                   EndPercent(second_segment_end),
                   AnimateTo(second_segment_end_value)
                   [, tween])
           [, Segment(...)])

Segments must be in order and cannot overlap, though the end of one segment can be the beginning of the next.

You may specify a length instead of an end.

If segments are specified using percentages, you must specify the motion's total duration (see below). Segments in the same sequence cannot mix and match percent and absolute time.

Segment Sequence Example:

The exact same animation as in the keyframe example above can be expressed using segments:

cpp
  Element(kMyAnimationElement,
          StartingValue(0.0),
          Segment(StartMs(100), EndMs(350),
                  AnimateTo(0.7), gfx::Tween::EASE_IN_OUT),
          Segment(StartMs(500), LengthMs(250),
                  AnimateTo(1.0), gfx::Tween::LINEAR))

Keyframes and segments are functionally equivalent so use whichever one makes more sense for your situation.

Motion Length and Global Tween

You may optionally specify two additional values when defining a motion: a motion length, using TotalDurationMs(motion_length) and a global tween. These go immediately before the sequences.

If you do not specify a motion length and tween, then:

  • All segments must be in absolute time.
  • The length of the motion is defined by the largest timestamp in a sequence.

If you do specify a motion length and tween, then:

  • All percentages specified in segments are relative to that length and tween.
  • Tweens in percentage segments are applied on top of the global tween.
    • Make the global tween LINEAR to avoid layering tweens in this way.
  • Absolute time segments ignore the global tween.
  • Absolute time segments cannot be longer than the motion's total duration.

Percent and absolute time cannot be mixed in the same keyframe or segment sequence (with the caveat that zero ms and zero percent are equivalent), but you can mix and match sequences that use percentage and absolute time.

Motion Length and Global Tween Example

cpp
  Motion(// Note: motion id is only required for caching provider; omit this if
         // extending `BrowserAnimationProvider`:
         motion_id,
         // The motion lasts for exactly 1s.
         TotalDurationMs(1000),
         // All segments with percentages will follow an ease tween.
         gfx::Tween::EASE_IN_OUT,
         // This sequence is in percent and will use the global tween, animating
         // between 0.0 and 1.0 when the output of the global tween is between
         // 0.1 and 0.9.
         Sequence(kPanelWidth,
                  StartingAt(0.0),
                  Segment(StartPercent(0.1), EndPercent(0.9), AnimateTo(1.0))),
         // This sequence is in absolute time and will ignore the global tween.
         Sequence(kFloatingElement,
                  Keyframe(AtMs(0), Value(0.0)),
                  Keyframe(AtMs(250), Value(1.0), gfx::Tween::EASE_OUT)),
         // This sequence will play over the entire 1000 ms and will use the
         // global tween.
         Animate(kSomeOtherThing, FromValue(1.0), ToValue(0.0)))

Sequence Properties (Defaults, Persistence, and Transitions)

By default, once a motion has completed, GetCurrentValue() will return null. This means you will need to compute all the visual properties of your UI elements whenever an animation isn't playing.

If you want sequence values to persist between motions and even be factored into future motions, you can specify sequence properties. You do not need to specify properties for all sequences in your BrowserAnimationProvider; merely those that need to not have the default behavior described above.

For example, you might want to have the system remember your side panel width between animations, so that (a) you don't have to query the panel state when it's not animating and (b) if you start a close in the middle of an open or vice-versa, the animation starts from its current position (rather than fully open or closed).

Configuring Sequences

You'll want to create a SequenceParams in your animation provider for each such sequence. These have three properties:

  • persist_between_animations - if set to true, the value is not discarded at the end of a motion. Default is false.
  • default_value - specifies a value that will be returned if there is no known value, or the current value is explicitly default (see below). Default is none.
  • auto_return_to_default - whether a motion which does not specify an animation for this sequence should automatically return it to its default value. Default is false.

For BrowserAnimationProvider, override GetAllSequenceParams() and optionally GetSequenceParams(). These require directly using SequenceParams.

For CachingBrowserAnimationProvider (recommended), you should instead use SetSequenceParams(), typically in the constructor. This lets you use the much simpler Persist() and Default() to define params.

cpp

MyAnimations::MyAnimations() {
  SetSequenceParams(
    // Parameters for the panel group.
    kPanelGroup,
    // Panel width value persists between motions.
    Persist(kPanelWidth),
    // Element height persists and returns to a default value of 1 if not
    // specified in a motion.
    Default(kElementHeight, 1.0, /*auto_return=*/true));
}

Using the Default Value

In addition to a literal value (e.g. "1.0", "0.0", "0.5") you can specify that the default value should be used for a keyframe or animation endpoint:

cpp
    Motion(kExpandMotion,
           Animate(kOpacity, FromValue(0.0), ToValue(DefaultValue())))

This will animate to whatever the default opacity is (you must have already defined it). If you want to create a more complex animation curve but don't want to exceed the default value, you can do something like:

cpp
    Motion(kExpandMotion,
           Sequence(
              kOpacity,
              // Start at zero.
              StartingValue(0.0),
              // Pause at 0.7 or default value, whichever is less.
              Segment(StartMs(0), EndMs(200), ToValue(MinOfDefaultAnd(0.7))),
              // Animate to the default value.
              Segment(StartMs(800), EndMs(1000), ToValue(DefaultValue()))))

Updating the Default Value

Sequence properties including the default value may change, e.g. via CachingBrowserAnimationProvider::UpdateDefaultValue(). You might use this if, for example, the user changes a setting that affects the UI. Note that you can retrieve an animation provider from the animation controller by type:

cpp
void OnUsePanelTransparencyChanged(bool use_transparency) {
  auto* const controller = BrowserAnimationController::From(browser);
  MyAnimations* const animations = controller->GetAnimationProvider<MyAnimations>();
  animations->UpdateDefaultValue(
      MyAnimations::kPanelGroup,
      MyAnimations::kOpacity,
      use_transparency ? 0.8 : 1.0);
}

This won't only affect the target opacity for future animations - if the current value of kOpacity is the default, any calls to BrowserAnimationController::GetCurrentValue(kPanelGroup, kOpacity) will return the updated value.

Return and Auto-Return

You can specify that a sequence should explicitly return from the current value to some specific value using the Return() sequence specification. The value must use Persist() or Default() since otherwise there's no value to return from:

cpp
    Motion(kCollapseMotion,
           Return(kPanelWidth, ToValue(0.0)),
           ...)

Basically, if you don't care where the animation starts from, use Return().

Auto-return can happen when you specify something like Default(kElementHeight, 1.0, /*auto_return=*/true) and you don't specify what to do with kElementHeight in a particular motion. When that motion is played, the following specification will be implicitly added:

cpp
  Return(kElementHeight, ToValue(DefaultValue()))

Transition Types

When there is an existing value that has persisted, a transition plays. The default transition is "start at", but there are three options, which can be specified in most sequences:

  • Start at - scales the animation so that it starts at the previous value.
  • Cap at - plays the animation as normal, but bounds the value between the previous value and the final (target) value.
  • Ignore - just plays the motion, ignoring the current value. Current values still persist between motions.

Consider a segment which tweens linearly from 0 to 1, but the current value is 0.5. Here's the result with each transition type:

Transitionbefore0%25%50%75%100%
start at0.50.50.680.750.891
cap at0.50.50.50.50.751
ignore0.500.250.50.751

If start at or cap at is specified, but the starting value is outside the range of the animation, a crossfade is played between the current value and the animation over the course of the motion:

Transitionbefore0%25%50%75%100%
start at-1-1-0.500.51
cap at-1-1-0.500.51
ignore-100.250.50.751

You may explicitly specify a transition as the second parameter of a Segment() declaration. You never have to specify kStartAtOldValue as it is the default.

cpp
  Segment(kOpacity, Transition::kCapAtOldValue, Keyframe(...), ...)
  Segment(kOpacity, Transition::kIgnoreOldValue,
          StartingValue(0.0), Segment(...), ...)

Using BrowserAnimationController

Once your animation provider is registered, you can start an animation motion via Start(). Example:

cpp
  BrowserAnimationController::From(browser)->Start(
      MyAnimations::kPanelGroup, MyAnimations::kExpandMotion);

You can then get the value of an animation using GetCurrentValue(). Sometimes, it's sufficient to just call this during Layout() or OnPaint().

cpp
  const auto anim_value =
      BrowserAnimationController::From(browser)->GetCurrentValue(
          MyAnimations::kPanelGroup, MyAnimations::kPanelWidth);

  // Note: if we always call `Reset()` during system initialization, then
  // `kPanelWidth` - which is persisted - would always have a valid value, and
  // we wouldn't need `value_or()` here.
  const int side_panel_width =
      base::ClampRound(preferred_side_panel_width * anim_value.value_or(1.0));

Other times, you need to know when the animation has changed so you can e.g. invalidate a layout, or update a size or opacity. In this case, use Subscribe():

cpp
  MyClass() {
    // This will only trigger the callback for cases where the "panel group" is
    // animating; other animations won't trigger callbacks.
    animation_subscription_ =
        BrowserAnimationController::From(browser)->Subscribe(
            MyAnimations::kPanelGroup,
            base::BindRepeating(&MyClass::OnAnimationUpdated,
                                base::Unretained(this)));
  }

  void OnAnimationUpdated(const BrowserAnimationController* controller,
                          BrowserAnimationUpdate status) {
    // View grows or shrinks based on the current animation value.
    PreferredSizeChanged();

    // If I need to directly update a view property in this method, I can call
    // `controller->GetCurrentValue()` to see where the animation is.
  }

The status will be "started", "progressed", "ended", or "canceled". Calling GetCurrentValue() when the animation ends will always yield the final value for the sequence.

Resetting and Clearing Animations

The Clear() method clears all values and stops all motions, subsequently, GetCurrentValue() will return null (at least until the next call to Start()), or a default value if one has been set.

The Reset() method immediately stops all motions and snaps the state of all sequences to the end of a specified motion. Only sequences marked as persist will be saved; all others will be null/default as with Clear().

Reset() is useful when you want to snap directly to a state. If you do not specify a motion, then the values will snap to the end of the current motion (if there is no current motion this does nothing).

cpp
  // Immediately collapse the panel.
  auto* const controller = BrowserAnimationController::From(bwi);
  controller->Reset(kPanelGroup, kCollapseMotion);

Clear() generates a cancel event if a motion is playing. Reset() may generate a cancel event if a different motion is playing, and will generate an ended event if the target motion is valid. During the ended event, all sequence values will be the final values of the motion.

Best Practices

Persist all values that make sense to. This will make animations smoother. All things equal, also prefer to enable auto-return to make your motions simpler.

If your UI can be configured to start in one of several initial states (such as vertical tab strip expanded or collapsed mode), call Reset() to set up the current state on UI initialization. That way, each sequence will always have a valid value that reflects the current state.

When choosing which state to pick "default" values from, think of the state that the most things can transition to/from. For example, all animations in vertical tab strip start or end at the collapsed state.

Possible Future Work

  • Instead of one default state, have multiple states, with each animation ending in a state.
    • Keyframe values can reference one or more states rather than just the default.
    • Each state can be modified in response to e.g. user preference or window geometry changes.
  • Allow more complicated math when computing keyframe values.