Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] New/Updated XAML/C# Animations Package #3638

Closed
michael-hawker opened this issue Dec 18, 2020 · 12 comments · Fixed by #3639 or #3680
Closed

[Feature] New/Updated XAML/C# Animations Package #3638

michael-hawker opened this issue Dec 18, 2020 · 12 comments · Fixed by #3639 or #3680
Labels
Milestone

Comments

@michael-hawker
Copy link
Member

michael-hawker commented Dec 18, 2020

See Technical Background below for more details.

Background (Summary)

As part of our refactoring work there are some Win2D based behaviors which don't fit in well in any particular package, see technical background below for more details. It's unclear if we would want to create a temporary home vs. utilizing existing work already in the toolkit and from the goal of #3108 to bootstrap a revitalization of our animations in general to support all the original scenarios.

This work item it to detail the new animation system and how everything can work together with these more independent package setups.

Describe the problem this feature would solve

The new animation system is focused primarily on extending the success we've seen with exposing the Implicit Composition Animation APIs to XAML as well as the flexibility we've seen from our Pipeline Brushes to leverage similar API surfaces with code-behind and XAML that are smaller in scope, but powerful to compose.

We have built this proposal with a variety of scenarios in mind to fill in gaps in our existing APIs, streamline the usage of other API dependencies like Behaviors and Win2D effects, and address new opportunities. We've done this as well with a focus on ease-of-use and composability to present this as a complete alternative to animations that are either composition or XAML based.

Describe the solution

Firstly, existing XAML Composition based Animation Support in the Toolkit should still be supported (with hopefully with no to minimal breaking changes). References:

This section is collapsed by scenario with embedded examples of a proposed XAML based syntax. Please expand each scenario to explore and see how it would be handled with the new system. Each example may build on previously defined examples, so it is best to review in order from top to bottom. This order does not necessarily imply any particular priority for each feature.

This section will be updated inline based on feedback.

Scenarios

Explicitly Defined Animations in XAML


This would expand upon our Implicit animation XAML API to create an independent animation that can be triggered or run in different cases outside of the normal show/hide cases of the composition API.

<!-- This animation causes the button to move left when clicked -->
<Button Content="Click Me"
              Click="Button_Click"> <!-- C# code would trigger animation: E.g. MoveAnimation.Start() -->
  <animations:Explicit.Animations>
    <animations:AnimationSet x:Name="MoveAnimation">
      <animations:TranslationAnimation From="0" To="0, -200, 0" Duration="0:0:1" Easing="Cubic"/>
    </animations:AnimationSet>
  </animations:Explicit.Animations>
</Button>
Key framed animations are still supported (existing+)


Existing complex crafted key frame animations should still be supported.

<!-- This animation causes the button to move up with a bump down first when clicked -->
<Button Content="Click Me"
              Click="Button_Click"> <!-- C# code would trigger animation: E.g. MoveAnimation.Start() -->
  <animations:Explicit.Animations>
    <animations:AnimationSet x:Name="MoveAnimation">
      <animations:ScalarAnimation Target="Translation.Y" Duration="0:0:1" From="0" To="-200">
        <animations:ScalarKeyFrame Key="0.1" Value="30"/>
        <animations:ScalarKeyFrame Key="0.5" Value="0.0"/>
      </animations:ScalarAnimation>
    </animations:AnimationSet>
  </animations:Explicit.Animations>
</Button>
Multiple copies of the same animation type can be used


As an alternative to key frames for a single animation, multiple animations can be used within the same collection without overlapping time windows:

<!-- This animation causes the button to move left and back fancily when clicked -->
<Button Content="Click Me"
              Click="Button_Click"> <!-- C# code would trigger animation: E.g. MoveAnimation.Start() -->
  <animations:Explicit.Animations>
    <animations:AnimationSet x:Name="MoveAnimation">
      <animations:TranslationAnimation From="0" To="0, -200, 0" Duration="0:0:1" Easing="Cubic"/>
      <animations:TranslationAnimation From="0, -200, 0" To="0" Delay="0:0:1" Duration="0:0:1" Easing="Bounce"/>
    </animations:AnimationSet>
  </animations:Explicit.Animations>
</Button>
Expression animations can still be used (existing+)


Expression animations allow for animations to interact with dynamic values within the context of it's parent control.

<!-- TODO: Need to define FinalValue (it's not well defined scenario in our current docs) -->
<Button Content="Click Me"
              Click="Button_Click"> <!-- C# code would trigger animation: E.g. MoveAnimation.Start() -->
  <animations:Explicit.Animations>
    <animations:AnimationSet x:Name="MoveAnimation">
      <animations:OffsetAnimation Duration="0:0:1">
        <animations:ExpressionKeyFrame Key="0.2" Value="This.FinalValue / 2"/>
      </animations:OffsetAnimation>    
    </animations:AnimationSet>
  </animations:Explicit.Animations>
</Button>

Existing example with Implicit Animation:

    <Page.Resources>
        <animations:AnimationSet x:Key="OffRotAnim">
            <animations:OffsetAnimation Duration="0:0:1"/>
            <animations:ScalarAnimation Target="RotationAngleInDegrees" ImplicitTarget="Offset"  Duration="0:0:1">
                <animations:ExpressionKeyFrame Key="1" Value="This.StartingValue + 90"/>
            </animations:ScalarAnimation>
        </animations:AnimationSet>
    </Page.Resources>

    <Border x:Name="Element" Height="100" Width="100" Background="Red"
        extensions:VisualEx.NormalizedCenterPoint="0.5,0.5,0" animations:Implicit.Animations="{StaticResource OffRotAnim}"/>
     </Border>
AnimationSets can be nested to share properties


This makes it easier to group similar timed and transitioned animations to build more complex timelines. These properties are shared with children and multiple collections can be nested together. The named parent collection can also define these properties.

<Button Content="Click Me"
              Click="Button_Click"> <!-- C# code would trigger animation: E.g. MoveAnimation.Start() -->
  <animations:Explicit.Animations>
    <animations:AnimationSet x:Name="MoveAnimation" Duration="0:0:1">
      <!-- This rotation gets its duration from the parent collection of 1 -->
      <animation:RotateAnimation From="0" To="90"/>
      <!-- Redefined properties will overwrite the default from the parent -->
      <animations:AnimationScope Delay="0:0:1" Duration="0:0:2" Easing="Elastic">
        <!-- Therefore, these two child animations both have a duration of 2, a delay of 1, and elastic easing -->
        <animations:TranslationAnimation From="0" To="0, -200, 0" />
        <animations:OpacityAnimation From="0" To="1.0" />
      </animations:AnimationScope>
    </animations:AnimationSet>
  </animations:Explicit.Animations>
</Button>
Ability to choose whether an Animation collection starts concurrently or sequentially


By default all animations start concurrently at the same time (this would remain the default). However, being able to create a timeline of animations that occur sequentially instead can make it easier to manage and visualize a sequence of complex animations.

<!-- The button will move left first wait a second and then move right -->
<Button Content="Click Me"
              Click="Button_Click"> <!-- C# code would trigger animation: E.g. MyAnimation.Start() -->
  <animations:Explicit.Animations>
    <animations:AnimationSet x:Key="MyAnimation" Duration="0:0:1" IsSequential="True">
      <animations:TranslationAnimation From="0" To="0, -200, 0"/>
      <animations:TranslationAnimation From="0, -200, 0" To="0" Delay="0:0:1"/>
    </animations:AnimationSet >
  </animations:Explicit.Animations>
</Button>

Complex example:

<Button Content="Click Me"
              Click="Button_Click"> <!-- C# code would trigger animation: E.g. MyAnimation.Start() -->
  <animations:Explicit.Animations>
    <animations:AnimationSet x:Key="MyAnimation" IsSequential="True">
      <!-- This set of Translation/Opacity Animations start first after a second delay for 2 seconds (the child collection does NOT inherit the IsSequential tag) -->
      <animations:AnimationSet Delay="0:0:1" Duration="0:0:2">
        <animations:TranslationAnimation From="0, -200, 0" To="0" />
        <animations:OpacityAnimation From="0" To="1.0" />
      </animations:AnimationSet >
      <!-- After the previous two animations have finished, there's now a new delay of 1 second before the next Rotation/Translation animations occur -->
      <animations:AnimationSet Delay="0:0:1" Duration="0:0:3">
        <animation:RotateAnimation Duration="0:0:1" From="0" To="90"/> <!-- This one is shorter than the other as it's overridden still -->
        <animations:TranslationAnimation From="0" To="0, -10, 0" />
      </animations:AnimationSet >
      <!-- Finally after the last set, this final translation waits another second and then plays -->
      <animations:TranslationAnimation From="0, -10, 0" To="0" Delay="0:0:1" Duration="0:0:1"/>
    </animations:AnimationSet>
    <!-- The whole animation timeline is 9 seconds long (1 + 2 + 1 + 3 + 1 + 1 = sum of duration + delays)-->
  </animations:Explicit.Animations>
</Button>
Can re-use the same animation in a resource with multiple elements


In some scenarios there may be a common animation that will want to be played on different elements. They should be able to be added to a resource dictionary and used where appropriate. For an example (with the Behaviors, see more in section below):

<Page>
  <Page.Resources>
    <animations:AnimationSet x:Name="FadeAnimation">
      <animations:OpacityAnimation From="1" To="0.3" Duration="0:0:2" Easing="Elastic"/>
    </animations:AnimationSet >
  </Page.Resources>

  <StackPanel>
    <Button>
        <Interactivity:Interaction.Behaviors>
            <Interactions:EventTriggerBehavior EventName="Click">
                <behaviors:StartAnimationAction Animation="{x:Bind FadeAnimation}" />
            </Interactions:EventTriggerBehavior>
        </Interactivity:Interaction.Behaviors>
    </Button>

    <Button>
        <Interactivity:Interaction.Behaviors>
            <Interactions:EventTriggerBehavior EventName="Click">
                <behaviors:StartAnimationAction Animation="{x:Bind FadeAnimation}" />
            </Interactions:EventTriggerBehavior>
        </Interactivity:Interaction.Behaviors>
    </Button>
  </StackPanel>
</Page>
Ability to trigger other animations within an animation


Sometimes an animation may be composed of multiple elements. These may want to occur together or one after another, in combination with different delays (or using sequential mode defined above), this ability would allow complex compositing of animations with ease.

  <!-- In this example when the button is clicked, the button will wiggle, wait a second, and then the image will fade-in -->
  <Grid>
    <Button Content="Click Me"
                  Click="Button_Click"> <!-- C# code would trigger animation: E.g. MyAnimation.Start() -->
       <animations:Explicit.Animations>
         <animations:AnimationSet x:Key="MyAnimation" Duration="0:0:0.25" IsSequential="True">
           <animations:TranslationAnimation From="0" To="0, -50, 0"/>
           <animations:TranslationAnimation From="0, -50, 0" To="0"/>
           <animations:StartAnimationActivity Animation="{x:Bind FadeInAnimation}" Delay="0:0:1"/>
        </animations:AnimationSet >
      </animations:Explicit.Animations>
    </Button>

    <Image Source="...">
       <animations:Explicit.Animations>
         <animations:AnimationSet x:Key="FadeInAnimation" Duration="0:0:1">
           <animations:OpacityAnimation From="0" To="1"/>
        </animations:AnimationSet >
      </animations:Explicit.Animations>
    </Image>
  </Grid>

If we had a general animation in a resource we could use a TargetObject="{x:Bind OurImage}" instead to play that generalized animation on that particular object:

  <Grid>
    <Grid.Resources>
         <animations:AnimationSet x:Key="FadeInAnimation" Duration="0:0:1">
           <animations:OpacityAnimation From="0" To="1"/>
        </animations:AnimationSet >
    </Grid.Resources>

    <Button Content="Click Me"
                  Click="Button_Click"> <!-- C# code would trigger animation: E.g. MyAnimation.Start() -->
       <animations:Explicit.Animations>
         <animations:AnimationSet x:Key="MyAnimation" Duration="0:0:0.25" IsSequential="True">
           <animations:TranslationAnimation From="0" To="0, -50, 0"/>
           <animations:TranslationAnimation From="0, -50, 0" To="0"/>
           <animations:StartAnimationActivity TargetObject="{x:Bind OurImage}" Animation="{x:Bind FadeInAnimation}" Delay="0:0:1"/>
        </animations:AnimationSet >
      </animations:Explicit.Animations>
    </Button>

    <Image x:Name="OurImage" Source="..." Opacity="0"/>
  </Grid>
Multiple animations can be triggered from within a single animation collection


To allow for complex chaining, triggered animations should be allowed in any number (with reason) to either daisy-chain animations across different elements or to set-off a coordinated animation across multiple elements.

<!-- In this example when the button is clicked, the left and right images will start fading in with the middle one lagging behind half-a-second, they'll then disappear in the same order after 3 seconds from being visible -->
<Page>
  <Page.Resources>
    <animations:AnimationSet x:Name="CoordinationedAnimation"> <!-- Note: If we made this sequential, then the entire sequence of the first animation would have to finish before the 2nd began. -->
      <animations:StartAnimationActivity TargetObject="{x:Bind Image1}" Animation="{x:Bind FadeInAnimation}"/>
      <animations:StartAnimationActivity TargetObject="{x:Bind Image2}" Animation="{x:Bind FadeInAnimation}" Delay="0:0:0.5"/>
      <animations:StartAnimationActivity TargetObject="{x:Bind Image3}" Animation="{x:Bind FadeInAnimation}"/>
    </animations:AnimationSet>
  
    <animations:AnimationSet x:Name="FadeInAnimation" IsSequential="True">
      <animations:OpacityAnimation From="0" To="1" Duration="0:0:2" Easing="Elastic"/>
      <animations:StartAnimationActivity Animation="{x:Bind FadeOutAnimation}" Delay="0:0:3"/>
    </animations:AnimationSet >
    
    <animations:AnimationSet x:Name="FadeOutAnimation">
      <animations:OpacityAnimation From="1" To="0" Duration="0:0:2"/>
    </animations:AnimationSet>
  </Page.Resources>

  <Grid>
    <Button Content="Click Me"
                  Click="Button_Click"/> <!-- C# code would trigger animation: E.g. CoordinatedAnimation.Start() -->

    <StackPanel Orientation="Horizontal">
       <Image x:Name="Image1" Source="..." Opacity="0"/>
       <Image x:Name="Image2" Source="..." Opacity="0"/>
       <Image x:Name="Image3" Source="..." Opacity="0"/>
    </StackPanel>
  </Grid>
</Page>
Support `RepeatBehaviors` on Collections and Animations


Animations alone are powerful, but there are times when you want them to repeat or loop for various reasons. We should support adding a RepeatBehavior element on both collections and animations. This would provide a greater amount of flexibility for animation definitions. P2: A stretch goal in the future would be to support a RepeatDirection enum for either Forward, Backward, ForwardThenBackward, or BackwardThenForward modes of controlling how an animation repeats itself.

<!-- The button will move left first wait a second and then move right and then immediately start over forever -->
<Button Content="Click Me"
              Click="Button_Click"> <!-- C# code would trigger animation: E.g. MyAnimation.Start() -->
  <animations:Explicit.Animations>
    <animations:AnimationSet x:Key="MyAnimation" Duration="0:0:1" IsSequential="True" RepeatBehavior="Forever">
      <animations:TranslationAnimation From="0" To="0, -200, 0"/>
      <animations:TranslationAnimation From="0, -200, 0" To="0" Delay="0:0:1"/>
    </animations:AnimationSet >
  </animations:Explicit.Animations>
</Button>

More complex future example:

<Button Content="Click Me"
              Click="Button_Click"> <!-- C# code would trigger animation: E.g. MyAnimation.Start() -->
  <animations:Explicit.Animations>
    <animations:AnimationSet x:Key="MyAnimation" IsSequential="True" Delay="0:0:1" Duration="0:0:2"
                                                           RepeatBehavior="0:0:9"> <!-- This entire animation will repeat until 9 seconds have passed (so it should play 3 times in full) -->
        <animations:TranslationAnimation From="0, -200, 0" To="0" />
        <!-- This would cause the button to fade out and then back in again (a single time but by repeating the animation in reverse) -->
        <animations:OpacityAnimation From="1.0" To="0" RepeatBehavior="2x" RepeatDirection="ForwardThenBackward"/>
    </animations:AnimationSet>
  </animations:Explicit.Animations>
</Button>

TODO: Choose which framework the animation is running on (Composition vs. XAML)
TODO: Have C# API for Animations in General that's easy to use and exposes duration, delays, easings, enableDependentAnimations, layer (composition or XAML), calling action after animation.

Integration with Microsoft.Toolkit.Uwp.UI.Behaviors (new package)

Can trigger animations with Behaviors


This would allow the new Explicit animations to be triggered from a variety of scenarios within XAML, for instance events caused by user interaction, data loading, progress completion, etc...

<Button Content="Click Me">
  <Interactivity:Interaction.Behaviors>
    <Interactions:EventTriggerBehavior EventName="Click">
      <behaviors:StartAnimationAction Animation="{x:Bind FadeInAnimation}" />
    </Interactions:EventTriggerBehavior>
  </Interactivity:Interaction.Behaviors>
  <animations:Explicit.Animations>
    <animations:AnimationSet x:Name="FadeInAnimation">
      <animations:OpacityAnimation From="0" To="1" Duration="0:0:1"/>
    </animations:AnimationSet>
  </animations:Explicit.Animations>
</Button>
Ability to trigger Behavior Actions


By including the behaviors package, we'd also have the ability to trigger other actions within this ecosystem.

<Button Content="Click Me">
   ...
  <animations:Implicit.ShowAnimations>
      <animations:OpacityAnimation From="0" To="1" Duration="0:0:1"/>
      <behaviors:AnimationAction>
        <Interactions:CallMethodAction TargetObject="{x:Bind MyPage}" MethodName="ShowAnimationCompleted"/>
      </behaviors:AnimationAction>
  </animations:Implicit.ShowAnimations>
</Button>

Integration with Microsoft.Toolkit.Uwp.UI.Media (Win2D based Effects)

Expose Pipeline Effects to General Framework Effects


This is base work outside of animations specifically to expose the work done in the Pipeline Brush API effects to be usable on any general framework element to provide similar effects..

    <TextBlock ...>
        <media:VisualExtensions.Effects>
          <media:PipelineVisual IsMasked="True" Placement="Background"> <!-- Default would be Foreground -->
            <effects:OpacityEffect Value="0.8"/>
            <effects:BlendEffect Mode="Multiply" Source="{effects:BackdropSource}"/>
            <effects:BlurEffect x:Name="BlurEffect" Amount="16"/>
          </media:PipelineVisual>
        </media:VisualExtensions.Effects>
    </TextBlock>
Can specify to add animations to an effect with `IsAnimatable`


By including the behaviors package, we'd also have the ability to trigger other actions within this ecosystem.

    <Button ...>
        <media:VisualExtensions.Effects>
          <media:PipelineVisual>
            <effects:OpacityEffect Value="0.4"/>
            <effects:BlendEffect Mode="Multiply" Source="{effects:BackdropSource}"/>
            <effects:BlurEffect x:Name="BlurEffect" Amount="0" IsAnimatable="True"/>
          </media:PipelineVisual>
        </media:VisualExtensions.Effects>
        <Interactivity:Interaction.Behaviors>
            <Interactions:EventTriggerBehavior EventName="Click">
                <Interactions:CallMethodAction TargetObject="{x:Bind TestAnimation}" MethodName="Start"/>
            </Interactions:EventTriggerBehavior>
        </Interactivity:Interaction.Behaviors>
        <animations:Explicit.Animations>
            <animations:AnimationSet x:Name="TestAnimation">
                <animations:OpacityAnimation From="0" To="1" Duration="0:0:1"/>
                <animations:TranslationAnimation Axis="X" From="-40" To="40" Duration="0:0:0.5" Layer="Xaml"/>
                <media:EffectAnimation Target="{x:Bind BlurEffect}" PropertyName="Amount" Value="16" Duration="0:0:4" Easing="Cubic"/>
            </animations:AnimationSet >
        </animations:Explicit.Animations>
    </Button>
Visual effect animations work with implicit animations as well


This is base work outside of animations specifically to expose the work done in the Pipeline Brush API effects to be usable on any general framework element to provide similar effects..

  <Button ...>
    <extensions:PipelineVisual>
      <effects:BlurEffect x:Name="BlurEffect" Amount="16" IsAnimatable="True">
      <effects:ShadeEffect Color="#FF22222" Intensity="0.2"/>
    </extensions:PipelineVisual>

    <!-- Button will slide and fade in while going from blurry to clear -->
    <animations:Implicit.ShowAnimations>
       <animations:TranslationAnimation Duration="0:0:1" From="0, -200, 0" To="0" />
       <animations:OpacityAnimation Duration="0:0:1" From="0" To="1.0" />
       <effects:EffectAnimation Target="{x:Bind BlurEffect}" Property="Amount" From="16" To="0" Duration="0:0:2"/>
    </animations:Implicit.ShowAnimations>
  </Button>

Currently Lower-Priority Scenarios/Goals

TODO: Expose our Implicit Animation API via C# (or do we do this already?)
TODO: Existing Implicit Animation Toolkit users can re-use the same code without any modification... (it's important to note that the base scenarios of attaching to implicit/show/hide animations are still a requirement as you can do today with the current animations package, this spec is more about detailing all the additions to the base system we had here and changing some of the underlying infrastructure.)

Describe alternatives you've considered

We know from experience that there are many scenarios that are difficult to accomplish with Storyboards, as we've seen the great power and flexibility that comes with our Implicit animation work. We also know that there are some scenarios more easily handled in code, but that exposing XAML helps better define workflow patterns between view and function. We want this to build upon the work we've already seen within the Toolkit and the community to move the platform forward in this space.

Additional context & Screenshots

Quick test example of an explicitly defined mixed animation triggered from code-behind:
Quick Test Example
(see above examples for proposed syntax)

Animating an Effect using a Behavior on a Button Click (all in XAML!):
EffectAnimation

Custom XAML animation combining new types (eg. clip) with also Win2D effect animations:
SZkeDoepFW

Example of the declarative C# APIs to create complex animations, combining composition and XAML:

hMFUNA88vr.mp4

Technical Background (Detailed)

This is a follow-on from work being done in #3594 and PR #3634 which is trying to sort out the dependencies of the toolkit and clean-up their complexities. Currently the Animations package pulls in both Win2D and the XAML Behaviors package, which then get also included in the Controls package.

The goal here is to have a more light-weight animations package which doesn't pull in these dependencies directly. Instead, both our Win2D based Media package and the new Behaviors package (in #3634) would add the animations library for supporting those scenarios if desired by the end developer.

Migrations:

  • AnimationExtensions -> AnimationBuilder extensions (Blur, Rotate, etc... extension methods on FrameworkElement in Code-behind in animations package or media package)
  • 'Effects' (in Animation) ->
    • Media PipelineBuilder Effects on Visuals AttachAsync (new expose in XAML)
    • Media +IsAnimatable to existing effects Allow Animations on them (in XAML)
  • Animation Behaviors -> Goes away - new system of using a regular Behavior to Trigger custom Animation (see above) or new visual effects attached to visuals for non-animation scenarios.

This will streamline the ability for developers to consume the toolkit and focus the purpose of each individual package.

@JustinXinLiu
Copy link
Contributor

Just... WOW!!! 👏👏👏

@ghost ghost added the In-PR 🚀 label Dec 18, 2020
@Sergio0694 Sergio0694 linked a pull request Dec 18, 2020 that will close this issue
8 tasks
@ghost ghost added the in progress 🚧 label Dec 18, 2020
@michael-hawker
Copy link
Member Author

michael-hawker commented Dec 18, 2020

Spec Change Note: Added RepeatBehaviors proposal above. 🙂

@Sergio0694
Copy link
Member

Sergio0694 commented Dec 19, 2020

Hi @JustinXinLiu, was thinking we could go all-in with default parameters both on the C# side and in XAML to allow developers to write less code when just dealing with simple animations, while still getting some nice default values that could work in a good number of cases, like you suggested. Would these be default values that could work, as a baseline?

  • From: null, so whatever the current value is
  • Delay: null, so none
  • Duration: 0.4s
  • Easing type: cubic
  • Easing mode: ease out

NOTE: those two easing options currently result in cubic-bezier(0.215, 0.61, 0.355, 1) in the Toolkit

Of course, we can tweak the duration or easing if you had something else in mind for the default values.
The idea is that doing this, it'd be possible to make simple animations much less verbose, like so:

AnimationBuilder.New()
    .Opacity(to: 0)
    .Translation(Axis.X, to: 20)
    .Start(MyButton);

Another option would be to make the to parameter the first one, so that you could even do just:

AnimationBuilder.New()
    .Opacity(0)
    .Translation(Axis.X, 20)
    .Start(MyButton);

This one would make simpler animations with just a target value particularly simple to define.
And of course, these same exact default values would apply to XAML as well, so eg. this would produce the same:

<Button x:Name="MyButton">
    <animations:Explicit.Animations>
        <xaml:AnimationCollection x:Name="MyAnimation">
            <xaml:OpacityAnimation To="0"/>
            <xaml:TranslationAnimation To="20,0,0" />
        </xaml:AnimationCollection>
    </animations:Explicit.Animations>
</Button>

And of course, more complex animations would be possible too, with additional parameters and methods (work in progress):

AnimationBuilder.New()
    .Opacity(from: 0, to: 1, duration: 600)
    .Scale(from: 1.4, to: 1)
    .NormalizedKeyFrames<double>(nameof(Visual.Rotation), b => b
        .KeyFrame(0.0, 0)
        .KeyFrame(0.4, 90, EasingType.Cubic)
        .KeyFrame(0.8, -45)
        .KeyFrame(1.0, 0, EasingType.Circle, EasingMode.EaseIn))
    .TimedKeyFrames<Vector3>(nameof(Visual.Offset), b => b
        .KeyFrame(200, Vector3.Zero)
        .KeyFrame(600, new(40, 0, 0))
        .KeyFrame(2000, new(20, 20, 0))
    .Start(this);

Any feedbacks are welcome! 😊

EDIT: fluent APIs to create custom animations with normalized keyframes now working 🚀

Custom keyframe composition animation (click to expand):
SdJrglmlR1.mp4
Custom keyframe XAML color animation (click to expand):
Q4d0Ed1v0b.mp4
Combining XAML + composition, both relative and timed keyframes (click to expand):
hMFUNA88vr.mp4

@robloo
Copy link
Contributor

robloo commented Dec 21, 2020

I really hope WinUI itself starts taking some of the great ideas from the toolkit like this one. I've been astounded more than once by the things you come up with here. Having this level of advanced feature development in the framework itself is sorely missed.

The whole animation system in WinUI XAML should be revised and this is a great start. I hope you can voice this internally once it all comes together. Using Lottie animations instead of expanding what XAML can do was one of my concerns:

microsoft/microsoft-ui-xaml#1858 (comment)
microsoft/microsoft-ui-xaml#1170 (comment)
microsoft/microsoft-ui-xaml#1968
microsoft/microsoft-ui-xaml#2802

@JustinXinLiu
Copy link
Contributor

@Sergio0694 looking good! For the easing, is the Toolkit default the same as the Composition default? Because Composition explicit animation already has a default easing that follows the Fluent design guideline, and let's just make sure they are the same. 😉

Also, I wonder if it's worth creating an enum to store all the animation properties so we don't need to write them like nameof(Visual.Scale) or "Scale.XY", and we keep them all in one place so it's easier to learn too.

@Sergio0694
Copy link
Member

Sergio0694 commented Dec 23, 2020

@JustinXinLiu Awesome, glad to hear that! 😄

For the easing, is the Toolkit default the same as the Composition default? Because Composition explicit animation already has a default easing that follows the Fluent design guideline, and let's just make sure they are the same.

So far I mostly focused on the backend work for the package, so I just reused the same beizer curve control points that were in the Toolkit before. We'll double check to ensure the default ones match those that are used in the default animations, absolutely! 🙂

I wonder if it's worth creating an enum to store all the animation properties so we don't need to write them like nameof(Visual.Scale) or "Scale.XY", and we keep them all in one place so it's easier to learn too.

I definitely agree that having to pass the property name as a string when doing animations with custom keyframes is not ideal, so I ended up reworking those APIs and exposing some additional overload that offer a fully type safe way to set those animations up, so that it won't be possible to get the property name wrong nor to accidentally try to use a keyframe type that's not supported or that doesn't match the target property.

Here's a before/after comparison, with the code I previously shared:

// BEFORE
AnimationBuilder.Create()
    .Opacity(from: 0, to: 1)
    .NormalizedKeyFrames<double>(nameof(Visual.RotationAngleInDegrees), b => b
        .KeyFrame(0.0, 0)
        .KeyFrame(0.4, 90, EasingType.Cubic)
        .KeyFrame(0.8, -45))
    .NormalizedKeyFrames<Vector3>(nameof(Visual.Offset), b => b
        .KeyFrame(0.4, Vector3.Zero)
        .KeyFrame(0.8, new(40, 0, 0))
        .KeyFrame(1, new(20, 20, 0)))
    .NormalizedKeyFrames<Vector3>(nameof(Visual.Scale), b => b
        .KeyFrame(0.5, new(1.2f))
        .KeyFrame(1, new(1)))
    .Start(this);

//AFTER
AnimationBuilder.Create()
    .Opacity(from: 0, to: 1)
    .RotationInDegrees().NormalizedKeyFrames(b => b
        .KeyFrame(0.0, 0)
        .KeyFrame(0.4, 90, EasingType.Cubic)
        .KeyFrame(0.8, -45))
    .Offset().NormalizedKeyFrames(b => b
        .KeyFrame(0.4, Vector3.Zero)
        .KeyFrame(0.8, new(40, 0, 0))
        .KeyFrame(1, new(20, 20, 0)))
    .Scale().NormalizedKeyFrames(b => b
        .KeyFrame(0.5, new(1.2f))
        .KeyFrame(1, new(1)))
    .Start(this);

Each supported animation type now has a dedicated method that allows you to add a keyframe animation to the schedule (still with the option to choose to use either normalized keyframes, or timed ones too), and the builder will take care of selecting the right animaiton type automatically, so the whole API is much more robust and way less error prone to use too.

Let us know what you think! 😊

EDIT: updated the default easing types in d963d11 as requested, we're now using the same default easing values as mentioned in the official docs page (here). The ones that were being used before were probably outdated now, so this should address that 😄

@JustinXinLiu
Copy link
Contributor

Awesome! I will also double check the easing when I get to play with this new lib. 😉

I love the change you made. Now the expression looks even clearer! One small question is, let's take properties (e.g. Scale and Offset) that support Vector3 for example, can I express an animation that only happens on a single axis, or both X and Y axes instead of X, Y and Z? This way I don't have to specify a Vector3 value like new(100, 0, 0), instead I write 40, when I just want to move an object horizontally. But this also means we will need a way to tell whether it's X or Y in the expression.

@Sergio0694
Copy link
Member

Sergio0694 commented Dec 27, 2020

Really happy to hear you like the new changes, awesome! 😊

One small question is, let's take properties (e.g. Scale and Offset) that support Vector3 for example, can I express an animation that only happens on a single axis, or both X and Y axes instead of X, Y and Z?

Absolutely! I've specifically added overloads for all animation properties that support multiple axes/dimensions to make it easy to only target one, which as you said is handy both to make the code more compact, but also to avoid overwriting values on other axes. Here's a few examples, but the same APIs are available for all APIs for eg. offset/translation/scale/etc.:

// Explicit, single-keyframe APIs
AnimationBuilder.Create()
    .Offset(Axis.X, 100)
    .Translation(Axis.Y, 200)
    .Scale(Axis.Z, 1.4)
    .AnchorPoint(Axis.X, 20)
    .CenterPoint(Axis.Y, 10)
    .Size(Axis.Y, 200)
    .Start(this);

// Works for clipping too!
AnimationBuilder.Create()
    .Clip(Side.Right, from: 240, to: 0)
    .Start(this);

// Explicit, multi-keyframe APIs
AnimationBuilder.Create()
    .Offset(Axis.X).NormalizedKeyFrames(b => b
        .KeyFrame(0.5, 100)
        .KeyFrame(1.0, 80))
    .Scale(Axis.Y).NormalizedKeyFrames(b => b
        .KeyFrame(0.5, 1.4)
        .KeyFrame(1.0, 1))
    // And more...
    .Start(this);

I've also added some special convenience overloads for 2D animations that can automatically target the .XY components within properties that are of type Vector3. Eg. they allow you to easily define a Vector2 animation for Scale or Translation, which will be mapped to for instance "Scale.XY" or "Translation.XY" behind the scenes:

AnimationBuilder.Create()
    .Translation(new Vector2(100, 200))
    .Scale(new Vector2(1.2f))
    .Start(this);

The same pattern applies to all these overloads - they have the default values we mentioned earlier but it's possible to specify the starting/final value for each of them, as well as the delay, duration, easing type and easing mode. I also applied the SetInitialValueBeforeDelay setting you mentioned when using composition animations, which is the one used by default.

Let me know what you think, and if there's anything else you'd like me to tweak or to investigate! 😄

EDIT: I'm making some tests comparing the default easings for composition animations and it actually looks like the default easing used by them doesn't match the one reported in the docs (as in, this one), and it's quite different. Investigating...

EDIT 2: fixed in 7262c7c, the easing should now be identical and the APIs are also a bit more efficient with this change! 🎉

@ghost ghost removed the in progress 🚧 label Jan 6, 2021
@michael-hawker
Copy link
Member Author

michael-hawker commented Jan 8, 2021

FYI @Sergio0694 updated some of the spec to use the new names for things.

Think we've got a lot of things covered, just need to do some more testing probably with implicit animations and that works for effects and things? (And add samples for each individual thing.)

Outside of that, I think the only thing we haven't done yet is have RepeatBehavior/Direction property stuff implemented yet.

@Sergio0694
Copy link
Member

I think the only thing we haven't done yet is have RepeatBehavior/Direction property stuff implemented yet.

Those options are available through the extensions in the CompositorExtensions class, which can be used to create custom Composition animations of all supported types. These can then also be inserted into an AnimationBuilder schedule through the "external animation" APIs (see document). We just don't have properties to set those parameters directly through the XAML mapping types for the animations, but I could certainly add those two if needed. I've just been a bit conservative on how many options to add on the XAML side to reduce the overhead there (especially now that we're using dependency properties for every available property), but if we think either of those options would be needed there I can integrate them as well 😄

@michael-hawker
Copy link
Member Author

@Sergio0694 yeah, think having those properties exposed would be powerful and help anyone migrating from Storyboards as well. Mind taking a quick look?

@Sergio0694
Copy link
Member

Alright, so this is the current plan regarding those missing APIs (posting here after conversation on Discord).

There are three main aspects of an animation that don't yet have a mapping in our XAML types:

  • Repeat count (including repeat forever)
  • Stop behavior
  • Direction (normal, alternating, etc.)

In general, the options in KeyFrameAnimation seem to be a superset of those available on Timeline objects. It makes sense to base our public options on the Composition animation types, since those are the default ones used by the APIs, and also the one that users should default to using unless necessary (due to better performance, etc.). This is the current idea:

  • Behavior/Count ---> Custom type incorporating either an explicit count, or "forever". The default is 1. This will map to a combination of IterationCount and AnimationIterationBehavior when using the Composition layer, or to a RepeatBehavior value when using a XAML animation. We can also use the [CreateFromString] attribute here to make the XAML syntax less verbose for consumers too. The behavior with a specific total duration wouldn't be supported (since that's not available in the Composition layer anyway), but the same could be achieved by using eg. the StopAnimationActivity.
  • StopBehavior ---> Use the Composition enum, and just map SetToFinalValue to HoldEnd for XAML too. This would mean that the same enum option would work regaardless of the target framework layer used by any given animation.
  • Direction ---> Use the Composition enum. If on the XAML layer, we can map Alternate to AutoReverse and half the number of iterations, or otherwise throw because those other combinations wouldn't be supported. When on the Composition layer instead, all options would naturally work as expected.

Will start working on these new features, will update again when all the new changes are available 🙂

@ghost ghost added Completed 🔥 and removed In-PR 🚀 labels Jan 21, 2021
@Rosuavio Rosuavio mentioned this issue Jan 21, 2021
8 tasks
@ghost ghost added In-PR 🚀 and removed Completed 🔥 labels Jan 21, 2021
@ghost ghost added Completed 🔥 and removed In-PR 🚀 labels Jan 21, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Mar 22, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
4 participants