Browse Source

A number of changes to make ProgressBar animation work.

xaml_integrated_comp_animations
Max Katz 1 week ago
parent
commit
b07e2937c3
  1. 112
      src/Avalonia.Base/Animation/CompositionAnimations/CompositionAnimation.cs
  2. 23
      src/Avalonia.Base/Animation/CompositionAnimations/CompositionAnimations.cs
  3. 67
      src/Avalonia.Base/Animation/CompositionAnimations/CompositionKeyFrame.cs
  4. 2
      src/Avalonia.Base/Animation/IAnimation.cs
  5. 2
      src/Avalonia.Base/Styling/StyleInstance.cs
  6. 124
      src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml

112
src/Avalonia.Base/Animation/CompositionAnimations/CompositionAnimation.cs

@ -75,21 +75,82 @@ namespace Avalonia.Animation
set => SetValue(StopBehaviorProperty, value);
}
IDisposable ICompositionAnimation.Apply(Visual parent)
private CompositionKeyFrameInstance[]? _keyFramesInstances;
IDisposable ICompositionAnimation.Apply(Visual parent, IObservable<bool> match)
{
var subscription = IsEnabledProperty.Changed
.Where(args => args.GetNewValue<bool>())
// Terrible terrible terrible code ahead
// Just a proof of concept, trying to make it work
var disposable = new CompositeDisposable();
var shouldContinue = false;
var subject = new LightweightSubject<bool>();
var count = Children.Count;
var instances = new CompositionKeyFrameInstance[count];
var hasInitialValue = new bool[count];
for (var i = 0; i < count; i++)
{
var index = i;
instances[index] = Children[index].Instance(parent);
disposable.Add(instances[index]);
hasInitialValue[index] = instances[index].IsSet(CompositionKeyFrameInstance.InstanceValueProperty);
instances[index].PropertyChanged += InstancePropChanged;
disposable.Add(Disposable.Create(() =>
{
instances[index].PropertyChanged -= InstancePropChanged;
}));
void InstancePropChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == CompositionKeyFrameInstance.InstanceValueProperty)
{
hasInitialValue[index] = true;
subject.OnNext(true);
}
}
}
_keyFramesInstances = instances;
disposable.Add(match.Subscribe(value =>
{
shouldContinue = value;
subject.OnNext(true);
}));
PropertyChanged += ThisOnPropertyChanged;
disposable.Add(Disposable.Create(() =>
{
PropertyChanged -= ThisOnPropertyChanged;
}));
void ThisOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == IsEnabledProperty)
{
subject.OnNext(true);
}
}
disposable.Add(subject
.Where(_ => shouldContinue && IsEnabled && hasInitialValue.All(b => b))
.Subscribe(_ =>
{
_keyFramesInstances = instances;
if (GetCompositionAnimation(parent) is KeyFrameAnimation { Target: not null } newAnimation)
{
Attach(parent, newAnimation);
}
});
}));
return Disposable.Create(() =>
{
subscription.Dispose();
if (_keyFramesInstances == instances)
{
_keyFramesInstances = null;
}
disposable.Dispose();
Detach();
});
}
@ -111,11 +172,20 @@ namespace Avalonia.Animation
keyFrameAnimation.IterationCount = IterationCount;
keyFrameAnimation.StopBehavior = StopBehavior;
if (Children.Any())
if (_keyFramesInstances is not null)
{
foreach (var instanceKeyFrame in _keyFramesInstances)
{
SetKeyFrame(keyFrameAnimation, instanceKeyFrame.KeyFrame, instanceKeyFrame.Value);
}
}
else if (Children.Any())
{
foreach (var frame in Children)
{
SetKeyFrame(keyFrameAnimation, frame);
SetKeyFrame(keyFrameAnimation, frame, frame.Value);
}
}
else
{
keyFrameAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
@ -123,47 +193,51 @@ namespace Avalonia.Animation
}
}
private static void SetKeyFrame(KeyFrameAnimation animation, CompositionKeyFrame frame)
private static void SetKeyFrame(KeyFrameAnimation animation, CompositionKeyFrame frame, object? value)
{
var easing = (frame.Easing ?? animation.Compositor.DefaultEasing) as Easing;
if(frame is ExpressionKeyFrame expressionKeyFrame && expressionKeyFrame.Value is string value)
if(frame is ExpressionKeyFrame && value is string str)
{
animation.InsertExpressionKeyFrame(frame.NormalizedProgressKey, value, easing);
animation.InsertExpressionKeyFrame(frame.NormalizedProgressKey, str, easing);
}
else if(animation is VectorKeyFrameAnimation vectorKeyFrameAnimation && frame.Value is Vector vector)
else if(animation is VectorKeyFrameAnimation vectorKeyFrameAnimation && value is Vector vector)
{
vectorKeyFrameAnimation.InsertKeyFrame(frame.NormalizedProgressKey, vector, easing!);
}
else if (animation is Vector2KeyFrameAnimation vector2KeyFrameAnimation && frame.Value is Vector2 vector2)
else if (animation is Vector2KeyFrameAnimation vector2KeyFrameAnimation && value is Vector2 vector2)
{
vector2KeyFrameAnimation.InsertKeyFrame(frame.NormalizedProgressKey, vector2, easing!);
}
else if (animation is Vector3KeyFrameAnimation vector3KeyFrameAnimation && frame.Value is Vector3 vector3)
else if (animation is Vector3KeyFrameAnimation vector3KeyFrameAnimation && value is Vector3 vector3)
{
vector3KeyFrameAnimation.InsertKeyFrame(frame.NormalizedProgressKey, vector3, easing!);
}
else if (animation is Vector4KeyFrameAnimation vector4KeyFrameAnimation && frame.Value is Vector4 vector4)
else if (animation is Vector3KeyFrameAnimation vector3KeyFrameAnimation1 && value is double vector3X)
{
vector3KeyFrameAnimation1.InsertKeyFrame(frame.NormalizedProgressKey, new Vector3((float)vector3X, 0, 0), easing!);
}
else if (animation is Vector4KeyFrameAnimation vector4KeyFrameAnimation && value is Vector4 vector4)
{
vector4KeyFrameAnimation.InsertKeyFrame(frame.NormalizedProgressKey, vector4, easing!);
}
else if (animation is ScalarKeyFrameAnimation scalarKeyFrameAnimation && frame.Value is float scalar)
else if (animation is ScalarKeyFrameAnimation scalarKeyFrameAnimation && value is float scalar)
{
scalarKeyFrameAnimation.InsertKeyFrame(frame.NormalizedProgressKey, scalar, easing!);
}
else if (animation is QuaternionKeyFrameAnimation quaternionKeyFrameAnimation && frame.Value is Quaternion quaternion)
else if (animation is QuaternionKeyFrameAnimation quaternionKeyFrameAnimation && value is Quaternion quaternion)
{
quaternionKeyFrameAnimation.InsertKeyFrame(frame.NormalizedProgressKey, quaternion, easing!);
}
else if (animation is BooleanKeyFrameAnimation booleanKeyFrameAnimation && frame.Value is bool boolean)
else if (animation is BooleanKeyFrameAnimation booleanKeyFrameAnimation && value is bool boolean)
{
booleanKeyFrameAnimation.InsertKeyFrame(frame.NormalizedProgressKey, boolean, easing!);
}
else if (animation is DoubleKeyFrameAnimation doubleKeyFrameAnimation && frame.Value is double val)
else if (animation is DoubleKeyFrameAnimation doubleKeyFrameAnimation && value is double val)
{
doubleKeyFrameAnimation.InsertKeyFrame(frame.NormalizedProgressKey, val, easing!);
}
else if (animation is ColorKeyFrameAnimation colorKeyFrameAnimation && frame.Value is Color color)
else if (animation is ColorKeyFrameAnimation colorKeyFrameAnimation && value is Color color)
{
colorKeyFrameAnimation.InsertKeyFrame(frame.NormalizedProgressKey, color, easing!);
}

23
src/Avalonia.Base/Animation/CompositionAnimations/CompositionAnimations.cs

@ -22,6 +22,29 @@ namespace Avalonia.Animation
}
}
/// <summary>
/// Shouldn't really exist.
/// </summary>
public class OffsetXCompositionAnimation : CompositionAnimation
{
/// <inheritdoc/>
protected override Rendering.Composition.Animations.CompositionAnimation? GetCompositionAnimation(Visual visual)
{
var compositor = ElementComposition.GetElementVisual(visual)?.Compositor;
if (compositor == null)
return null;
var animation = compositor.CreateVector3KeyFrameAnimation();
animation.Target = "Offset";
SetAnimationValues(animation);
return animation;
}
}
public class OpacityCompositionAnimation : CompositionAnimation
{
/// <inheritdoc/>

67
src/Avalonia.Base/Animation/CompositionAnimations/CompositionKeyFrame.cs

@ -1,7 +1,7 @@
using System.Numerics;
using Avalonia.Animation;
using System;
using Avalonia.Animation.Easings;
using Avalonia.Media;
using Avalonia.Data;
using Avalonia.Metadata;
namespace Avalonia.Animation
{
@ -11,15 +11,19 @@ namespace Avalonia.Animation
public class CompositionKeyFrame : AvaloniaObject
{
public static readonly StyledProperty<object?> ValueProperty = AvaloniaProperty.Register<CompositionKeyFrame, object?>(
nameof(Value));
public static readonly StyledProperty<object?> ValueProperty =
AvaloniaProperty.Register<CompositionKeyFrame, object?>(
nameof(Value));
public static readonly StyledProperty<Easing?> EasingProperty = AvaloniaProperty.Register<CompositionKeyFrame, Easing?>(
nameof(Easing));
public static readonly StyledProperty<Easing?> EasingProperty =
AvaloniaProperty.Register<CompositionKeyFrame, Easing?>(
nameof(Easing));
[Content]
[AssignBinding]
public object? Value
{
get => GetValue(ValueProperty);
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
@ -30,5 +34,52 @@ namespace Avalonia.Animation
}
public float NormalizedProgressKey { get; set; }
internal CompositionKeyFrameInstance Instance(Visual target)
{
var instance = new CompositionKeyFrameInstance(this);
if (Value is BindingBase bindingBase)
{
// var expression = bindingBase
// .CreateInstance(target, CompositionKeyFrameInstance.InstanceValueProperty, null);
instance.Expression = instance.Bind(
CompositionKeyFrameInstance.InstanceValueProperty, bindingBase, target);
//
// expression.Attach(
// instance.GetValueStore(), null, instance,
// CompositionKeyFrameInstance.InstanceValueProperty, BindingPriority.LocalValue);
// instance.Expression = expression;
}
else
{
instance.SetValue(CompositionKeyFrameInstance.InstanceValueProperty, Value);
}
instance.NormalizedProgressKey = NormalizedProgressKey;
instance.Easing = Easing;
return instance;
}
}
internal class CompositionKeyFrameInstance(CompositionKeyFrame keyFrame) : AvaloniaObject, IDisposable
{
public CompositionKeyFrame KeyFrame { get; } = keyFrame;
public static readonly StyledProperty<object?> InstanceValueProperty
= AvaloniaProperty.Register<CompositionKeyFrameInstance, object?>(
nameof(Value));
public object? Value => GetValue(InstanceValueProperty);
public float NormalizedProgressKey { get; set; }
public Easing? Easing { get; set; }
internal BindingExpressionBase? Expression { get; set; }
public void Dispose()
{
Expression?.Dispose();
}
}
}

2
src/Avalonia.Base/Animation/IAnimation.cs

@ -24,7 +24,7 @@ namespace Avalonia.Animation
/// <summary>
/// Apply the animation to the specified visual and return a disposable to remove it.
/// </summary>
IDisposable Apply(Visual parent);
IDisposable Apply(Visual parent, IObservable<bool> match);
}
/// <summary>

2
src/Avalonia.Base/Styling/StyleInstance.cs

@ -79,7 +79,7 @@ namespace Avalonia.Styling
}
else if (animation is ICompositionAnimation compositionAnimation && animatable is Visual visual)
{
_animationApplyDisposables.Add(compositionAnimation.Apply(visual));
_animationApplyDisposables.Add(compositionAnimation.Apply(visual, _animationTrigger));
}
}

124
src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml

@ -46,7 +46,7 @@
<Panel x:Name="DeterminateRoot" Opacity="1">
<Panel.Transitions>
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.197" />
<OpacityCompositionAnimation Duration="0:0:0.197" />
</Transitions>
</Panel.Transitions>
<Border
@ -58,7 +58,7 @@
<Panel x:Name="IndeterminateRoot" Opacity="0">
<Panel.Transitions>
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.197" />
<OpacityCompositionAnimation Duration="0:0:0.197" />
</Transitions>
</Panel.Transitions>
<Border
@ -124,62 +124,94 @@
</Style>
<Style Selector="^:horizontal:indeterminate /template/ Border#IndeterminateProgressBarIndicator">
<Style.Animations>
<Animation IterationCount="Infinite" Duration="0:0:2">
<KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:0">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationStartPosition}" />
</KeyFrame>
<KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:1.5">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationEndPosition}" />
</KeyFrame>
<KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:2">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationEndPosition}" />
</KeyFrame>
</Animation>
<OffsetXCompositionAnimation IterationBehavior="Forever" Duration="0:0:2">
<CompositionKeyFrame NormalizedProgressKey="0"
Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationStartPosition}" />
<CompositionKeyFrame NormalizedProgressKey="0.75"
Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationEndPosition}" />
<CompositionKeyFrame NormalizedProgressKey="1"
Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationEndPosition}" />
</OffsetXCompositionAnimation>
<!-- <Animation IterationCount="Infinite" Duration="0:0:2"> -->
<!-- <KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:0"> -->
<!-- <Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationStartPosition}" /> -->
<!-- </KeyFrame> -->
<!-- <KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:1.5"> -->
<!-- <Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationEndPosition}" /> -->
<!-- </KeyFrame> -->
<!-- <KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:2"> -->
<!-- <Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationEndPosition}" /> -->
<!-- </KeyFrame> -->
<!-- </Animation> -->
</Style.Animations>
</Style>
<Style Selector="^:horizontal:indeterminate /template/ Border#IndeterminateProgressBarIndicator2">
<Style.Animations>
<Animation IterationCount="Infinite" Duration="0:0:2">
<KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:0">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationStartPosition}" />
</KeyFrame>
<KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:0.75">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationStartPosition}" />
</KeyFrame>
<KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:2">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationEndPosition}" />
</KeyFrame>
</Animation>
<OffsetXCompositionAnimation IterationBehavior="Forever" Duration="0:0:2">
<CompositionKeyFrame NormalizedProgressKey="0"
Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationStartPosition}" />
<CompositionKeyFrame NormalizedProgressKey="0.75"
Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationStartPosition}" />
<CompositionKeyFrame NormalizedProgressKey="1"
Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationEndPosition}" />
</OffsetXCompositionAnimation>
<!-- <Animation IterationCount="Infinite" Duration="0:0:2"> -->
<!-- <KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:0"> -->
<!-- <Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationStartPosition}" /> -->
<!-- </KeyFrame> -->
<!-- <KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:0.75"> -->
<!-- <Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationStartPosition}" /> -->
<!-- </KeyFrame> -->
<!-- <KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:2"> -->
<!-- <Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationEndPosition}" /> -->
<!-- </KeyFrame> -->
<!-- </Animation> -->
</Style.Animations>
</Style>
<Style Selector="^:vertical:indeterminate /template/ Border#IndeterminateProgressBarIndicator">
<Style.Animations>
<Animation IterationCount="Infinite" Duration="0:0:2">
<KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:0">
<Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationStartPosition}" />
</KeyFrame>
<KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:1.5">
<Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationEndPosition}" />
</KeyFrame>
<KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:2">
<Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationEndPosition}" />
</KeyFrame>
</Animation>
<!-- <CompositionDoubleAnimation Target="Offset.Y" IterationBehavior="Forever" Duration="0:0:2"> -->
<!-- <CompositionKeyFrame NormalizedProgressKey="0" -->
<!-- Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationStartPosition}" /> -->
<!-- <CompositionKeyFrame NormalizedProgressKey="0.75" -->
<!-- Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationEndPosition}" /> -->
<!-- <CompositionKeyFrame NormalizedProgressKey="1" -->
<!-- Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationEndPosition}" /> -->
<!-- </CompositionDoubleAnimation> -->
<!-- <Animation IterationCount="Infinite" Duration="0:0:2"> -->
<!-- <KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:0"> -->
<!-- <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationStartPosition}" /> -->
<!-- </KeyFrame> -->
<!-- <KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:1.5"> -->
<!-- <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationEndPosition}" /> -->
<!-- </KeyFrame> -->
<!-- <KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:2"> -->
<!-- <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateSettings.ContainerAnimationEndPosition}" /> -->
<!-- </KeyFrame> -->
<!-- </Animation> -->
</Style.Animations>
</Style>
<Style Selector="^:vertical:indeterminate /template/ Border#IndeterminateProgressBarIndicator2">
<Style.Animations>
<Animation IterationCount="Infinite" Duration="0:0:2">
<KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:0">
<Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationStartPosition}" />
</KeyFrame>
<KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:0.75">
<Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationStartPosition}" />
</KeyFrame>
<KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:2">
<Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationEndPosition}" />
</KeyFrame>
</Animation>
<!-- <CompositionDoubleAnimation Target="Offset.Y" IterationBehavior="Forever" Duration="0:0:2"> -->
<!-- <CompositionKeyFrame NormalizedProgressKey="0" -->
<!-- Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationStartPosition}" /> -->
<!-- <CompositionKeyFrame NormalizedProgressKey="0.75" -->
<!-- Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationStartPosition}" /> -->
<!-- <CompositionKeyFrame NormalizedProgressKey="1" -->
<!-- Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationEndPosition}" /> -->
<!-- </CompositionDoubleAnimation> -->
<!-- <Animation IterationCount="Infinite" Duration="0:0:2"> -->
<!-- <KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:0"> -->
<!-- <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationStartPosition}" /> -->
<!-- </KeyFrame> -->
<!-- <KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:0.75"> -->
<!-- <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationStartPosition}" /> -->
<!-- </KeyFrame> -->
<!-- <KeyFrame KeySpline="0.4,0,0.6,1" KeyTime="0:0:2"> -->
<!-- <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateSettings.Container2AnimationEndPosition}" /> -->
<!-- </KeyFrame> -->
<!-- </Animation> -->
</Style.Animations>
</Style>
<Style Selector="^:horizontal /template/ Border#IndeterminateProgressBarIndicator">

Loading…
Cancel
Save