Browse Source

Animations now work... more or less?

pull/8105/head
Nikita Tsukanov 4 years ago
parent
commit
9ac37065dc
  1. 3
      samples/ControlCatalog/MainView.xaml
  2. 45
      samples/ControlCatalog/Pages/CompositionPage.axaml
  3. 157
      samples/ControlCatalog/Pages/CompositionPage.axaml.cs
  4. 88
      src/Avalonia.Base/Rendering/Composition/Animations/AnimatedValueStore.cs
  5. 77
      src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs
  6. 2
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
  7. 25
      src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs
  8. 7
      src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs
  9. 59
      src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs
  10. 2
      src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs
  11. 12
      src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
  12. 6
      src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
  13. 45
      src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
  14. 1
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs
  15. 57
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionTrackedValues.cs
  16. 27
      src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
  17. 5
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  18. 20
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
  19. 82
      src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs
  20. 22
      src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs
  21. 1
      src/Avalonia.Base/Rendering/Composition/Visual.cs
  22. 67
      src/Avalonia.Base/Utilities/RefTrackingDictionary.cs
  23. 1
      src/Avalonia.SourceGenerator/Avalonia.SourceGenerator.csproj
  24. 26
      src/Avalonia.SourceGenerator/CompositionGenerator/CompositionRoslynGenerator.cs
  25. 102
      src/Avalonia.SourceGenerator/CompositionGenerator/Generator.cs
  26. 6
      src/Avalonia.SourceGenerator/CompositionGenerator/ICompositionGeneratorSink.cs
  27. 15
      src/Avalonia.SourceGenerator/CompositionGenerator/RoslynCompositionGeneratorSink.cs

3
samples/ControlCatalog/MainView.xaml

@ -13,6 +13,9 @@
</Style>
</Grid.Styles>
<controls:HamburgerMenu Name="Sidebar">
<TabItem Header="Composition">
<pages:CompositionPage/>
</TabItem>
<TabItem Header="Acrylic">
<pages:AcrylicPage />
</TabItem>

45
samples/ControlCatalog/Pages/CompositionPage.axaml

@ -0,0 +1,45 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pages="clr-namespace:ControlCatalog.Pages"
x:Class="ControlCatalog.Pages.CompositionPage">
<StackPanel>
<TextBlock Classes="h1">Implicit animations</TextBlock>
<Grid ColumnDefinitions="*,10,40" Margin="0 0 40 0">
<ItemsControl x:Name="Items">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.DataTemplates>
<DataTemplate>
<Border
pages:CompositionPage.EnableAnimations="True"
Padding="10" BorderBrush="Gray" BorderThickness="2"
Background="{Binding ColorBrush}" Width="100" Height="100" Margin="10">
<TextBlock Text="{Binding ColorHexValue}"/>
</Border>
</DataTemplate>
</ItemsControl.DataTemplates>
</ItemsControl>
<GridSplitter Margin="2" BorderThickness="1" BorderBrush="Gray"
Background="#e0e0e0" Grid.Column="1"
ResizeDirection="Columns" ResizeBehavior="PreviousAndNext"
/>
<Border Grid.Column="2">
<LayoutTransformControl
HorizontalAlignment="Center"
MinWidth="30">
<LayoutTransformControl.LayoutTransform>
<RotateTransform Angle="90"/>
</LayoutTransformControl.LayoutTransform>
<TextBlock>Resize me</TextBlock>
</LayoutTransformControl>
</Border>
</Grid>
</StackPanel>
</UserControl>

157
samples/ControlCatalog/Pages/CompositionPage.axaml.cs

@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.VisualTree;
namespace ControlCatalog.Pages;
public partial class CompositionPage : UserControl
{
private ImplicitAnimationCollection _implicitAnimations;
public CompositionPage()
{
AvaloniaXamlLoader.Load(this);
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
this.FindControl<ItemsControl>("Items").Items = CreateColorItems();
}
private List<ColorItem> CreateColorItems()
{
var list = new List<ColorItem>();
list.Add(new ColorItem(Color.FromArgb(255, 255, 185, 0)));
list.Add(new ColorItem(Color.FromArgb(255, 231, 72, 86)));
list.Add(new ColorItem(Color.FromArgb(255, 0, 120, 215)));
list.Add(new ColorItem(Color.FromArgb(255, 0, 153, 188)));
list.Add(new ColorItem(Color.FromArgb(255, 122, 117, 116)));
list.Add(new ColorItem(Color.FromArgb(255, 118, 118, 118)));
list.Add(new ColorItem(Color.FromArgb(255, 255, 141, 0)));
list.Add(new ColorItem(Color.FromArgb(255, 232, 17, 35)));
list.Add(new ColorItem(Color.FromArgb(255, 0, 99, 177)));
list.Add(new ColorItem(Color.FromArgb(255, 45, 125, 154)));
list.Add(new ColorItem(Color.FromArgb(255, 93, 90, 88)));
list.Add(new ColorItem(Color.FromArgb(255, 76, 74, 72)));
list.Add(new ColorItem(Color.FromArgb(255, 247, 99, 12)));
list.Add(new ColorItem(Color.FromArgb(255, 234, 0, 94)));
list.Add(new ColorItem(Color.FromArgb(255, 142, 140, 216)));
list.Add(new ColorItem(Color.FromArgb(255, 0, 183, 195)));
list.Add(new ColorItem(Color.FromArgb(255, 104, 118, 138)));
list.Add(new ColorItem(Color.FromArgb(255, 105, 121, 126)));
list.Add(new ColorItem(Color.FromArgb(255, 202, 80, 16)));
list.Add(new ColorItem(Color.FromArgb(255, 195, 0, 82)));
list.Add(new ColorItem(Color.FromArgb(255, 107, 105, 214)));
list.Add(new ColorItem(Color.FromArgb(255, 3, 131, 135)));
list.Add(new ColorItem(Color.FromArgb(255, 81, 92, 107)));
list.Add(new ColorItem(Color.FromArgb(255, 74, 84, 89)));
list.Add(new ColorItem(Color.FromArgb(255, 218, 59, 1)));
list.Add(new ColorItem(Color.FromArgb(255, 227, 0, 140)));
list.Add(new ColorItem(Color.FromArgb(255, 135, 100, 184)));
list.Add(new ColorItem(Color.FromArgb(255, 0, 178, 148)));
list.Add(new ColorItem(Color.FromArgb(255, 86, 124, 115)));
list.Add(new ColorItem(Color.FromArgb(255, 100, 124, 100)));
list.Add(new ColorItem(Color.FromArgb(255, 239, 105, 80)));
list.Add(new ColorItem(Color.FromArgb(255, 191, 0, 119)));
list.Add(new ColorItem(Color.FromArgb(255, 116, 77, 169)));
list.Add(new ColorItem(Color.FromArgb(255, 1, 133, 116)));
list.Add(new ColorItem(Color.FromArgb(255, 72, 104, 96)));
list.Add(new ColorItem(Color.FromArgb(255, 82, 94, 84)));
list.Add(new ColorItem(Color.FromArgb(255, 209, 52, 56)));
list.Add(new ColorItem(Color.FromArgb(255, 194, 57, 179)));
list.Add(new ColorItem(Color.FromArgb(255, 177, 70, 194)));
list.Add(new ColorItem(Color.FromArgb(255, 0, 204, 106)));
list.Add(new ColorItem(Color.FromArgb(255, 73, 130, 5)));
list.Add(new ColorItem(Color.FromArgb(255, 132, 117, 69)));
list.Add(new ColorItem(Color.FromArgb(255, 255, 67, 67)));
list.Add(new ColorItem(Color.FromArgb(255, 154, 0, 137)));
list.Add(new ColorItem(Color.FromArgb(255, 136, 23, 152)));
list.Add(new ColorItem(Color.FromArgb(255, 16, 137, 62)));
list.Add(new ColorItem(Color.FromArgb(255, 16, 124, 16)));
list.Add(new ColorItem(Color.FromArgb(255, 126, 115, 95)));
return list;
}
public class ColorItem
{
public Color Color { get; private set; }
public SolidColorBrush ColorBrush
{
get { return new SolidColorBrush(Color); }
}
public String ColorHexValue
{
get { return Color.ToString().Substring(3).ToUpperInvariant(); }
}
public ColorItem(Color color)
{
Color = color;
}
}
private void EnsureImplicitAnimations()
{
if (_implicitAnimations == null)
{
var compositor = ElementCompositionPreview.GetElementVisual(this)!.Compositor;
var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
offsetAnimation.Target = "Offset";
offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
offsetAnimation.Duration = TimeSpan.FromMilliseconds(400);
var rotationAnimation = compositor.CreateScalarKeyFrameAnimation();
rotationAnimation.Target = "RotationAngle";
rotationAnimation.InsertKeyFrame(.5f, 0.160f);
rotationAnimation.InsertKeyFrame(1f, 0f);
rotationAnimation.Duration = TimeSpan.FromMilliseconds(400);
var animationGroup = compositor.CreateAnimationGroup();
animationGroup.Add(offsetAnimation);
animationGroup.Add(rotationAnimation);
_implicitAnimations = compositor.CreateImplicitAnimationCollection();
_implicitAnimations["Offset"] = animationGroup;
}
}
public static void SetEnableAnimations(Border border, bool value)
{
var page = border.FindAncestorOfType<CompositionPage>();
if (page == null)
{
border.AttachedToVisualTree += delegate { SetEnableAnimations(border, true); };
return;
}
if (ElementCompositionPreview.GetElementVisual(page) == null)
return;
page.EnsureImplicitAnimations();
ElementCompositionPreview.GetElementVisual((Visual)border.GetVisualParent()).ImplicitAnimations =
page._implicitAnimations;
}
}

88
src/Avalonia.Base/Rendering/Composition/Animations/AnimatedValueStore.cs

@ -1,19 +1,62 @@
using System.Runtime.InteropServices;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Animations
{
internal struct AnimatedValueStore<T> where T : struct
internal struct ServerObjectSubscriptionStore
{
public bool IsValid;
public RefTrackingDictionary<IAnimationInstance> Subscribers;
public void Invalidate()
{
if (IsValid)
return;
IsValid = false;
if (Subscribers != null)
foreach (var sub in Subscribers)
sub.Key.Invalidate();
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct ServerValueStore<T>
{
// HAS TO BE THE FIRST FIELD, accessed by field offset from ServerObject
private ServerObjectSubscriptionStore Subscriptions;
private T _value;
public T Value
{
set
{
_value = value;
Subscriptions.Invalidate();
}
get
{
Subscriptions.IsValid = true;
return _value;
}
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct ServerAnimatedValueStore<T> where T : struct
{
// HAS TO BE THE FIRST FIELD, accessed by field offset from ServerObject
private ServerObjectSubscriptionStore Subscriptions;
private IAnimationInstance? _animation;
private T _direct;
private IAnimationInstance _animation;
private T? _lastAnimated;
public T Direct => _direct;
public T GetAnimated(ServerCompositor compositor)
{
Subscriptions.IsValid = true;
if (_animation == null)
return _direct;
var v = _animation.Evaluate(compositor.ServerNow, ExpressionVariant.Create(_direct))
@ -22,19 +65,50 @@ namespace Avalonia.Rendering.Composition.Animations
return v;
}
public void Activate(ServerObject parent)
{
if (_animation != null)
_animation.Activate();
}
public void Deactivate(ServerObject parent)
{
if (_animation != null)
_animation.Deactivate();
}
private T LastAnimated => _animation != null ? _lastAnimated ?? _direct : _direct;
public bool IsAnimation => _animation != null;
public void SetAnimation(ChangeSet cs, IAnimationInstance animation)
public void SetAnimation(ServerObject target, ChangeSet cs, IAnimationInstance animation, int storeOffset)
{
_direct = default;
if (_animation != null)
{
if (target.IsActive)
_animation.Deactivate();
}
_animation = animation;
_animation.Start(cs.Batch.CommitedAt, ExpressionVariant.Create(LastAnimated));
_animation.Initialize(cs.Batch.CommitedAt, ExpressionVariant.Create(LastAnimated), storeOffset);
if (target.IsActive)
_animation.Activate();
Subscriptions.Invalidate();
}
public static implicit operator AnimatedValueStore<T>(T value) => new AnimatedValueStore<T>()
public void SetValue(ServerObject target, T value)
{
_direct = value
};
if (_animation != null)
{
if (target.IsActive)
_animation.Deactivate();
}
_animation = null;
_direct = value;
Subscriptions.Invalidate();
}
}
}

77
src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Animations;
internal abstract class AnimationInstanceBase : IAnimationInstance
{
private List<(ServerObject obj, int member)>? _trackedObjects;
protected PropertySetSnapshot Parameters { get; }
public ServerObject TargetObject { get; }
protected int StoreOffset { get; private set; }
private bool _invalidated;
public AnimationInstanceBase(ServerObject target, PropertySetSnapshot parameters)
{
Parameters = parameters;
TargetObject = target;
}
protected void Initialize(int storeOffset, HashSet<(string name, string member)> trackedObjects)
{
if (trackedObjects.Count > 0)
{
_trackedObjects = new ();
foreach (var t in trackedObjects)
{
var obj = Parameters.GetObjectParameter(t.name);
if (obj is ServerObject tracked)
{
var off = tracked.GetFieldOffset(t.member);
if (off == null)
#if DEBUG
throw new InvalidCastException("Attempting to subscribe to unknown field");
#else
continue;
#endif
_trackedObjects.Add((tracked, off.Value));
}
}
}
StoreOffset = storeOffset;
}
public abstract void Initialize(TimeSpan startedAt, ExpressionVariant startingValue, int storeOffset);
protected abstract ExpressionVariant EvaluateCore(TimeSpan now, ExpressionVariant currentValue);
public ExpressionVariant Evaluate(TimeSpan now, ExpressionVariant currentValue)
{
_invalidated = false;
return EvaluateCore(now, currentValue);
}
public virtual void Activate()
{
if (_trackedObjects != null)
foreach (var tracked in _trackedObjects)
tracked.obj.SubscribeToInvalidation(tracked.member, this);
}
public virtual void Deactivate()
{
if (_trackedObjects != null)
foreach (var tracked in _trackedObjects)
tracked.obj.UnsubscribeFromInvalidation(tracked.member, this);
}
public void Invalidate()
{
if (_invalidated)
return;
_invalidated = true;
TargetObject.NotifyAnimatedValueChanged(StoreOffset);
}
}

2
src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs

@ -53,7 +53,7 @@ namespace Avalonia.Rendering.Composition.Animations
ExpressionVariant? finalValue);
internal PropertySetSnapshot CreateSnapshot(bool server)
=> _propertySet.Snapshot(server, 1);
=> _propertySet.Snapshot(server);
void ICompositionAnimationBase.InternalOnly()
{

25
src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs

@ -1,22 +1,22 @@
using System;
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Animations
{
internal class ExpressionAnimationInstance : IAnimationInstance
internal class ExpressionAnimationInstance : AnimationInstanceBase, IAnimationInstance
{
private readonly Expression _expression;
private readonly IExpressionObject _target;
private ExpressionVariant _startingValue;
private readonly ExpressionVariant? _finalValue;
private readonly PropertySetSnapshot _parameters;
public ExpressionVariant Evaluate(TimeSpan now, ExpressionVariant currentValue)
protected override ExpressionVariant EvaluateCore(TimeSpan now, ExpressionVariant currentValue)
{
var ctx = new ExpressionEvaluationContext
{
Parameters = _parameters,
Target = _target,
Parameters = Parameters,
Target = TargetObject,
ForeignFunctionInterface = BuiltInExpressionFfi.Instance,
StartingValue = _startingValue,
FinalValue = _finalValue ?? _startingValue,
@ -25,20 +25,21 @@ namespace Avalonia.Rendering.Composition.Animations
return _expression.Evaluate(ref ctx);
}
public void Start(TimeSpan startedAt, ExpressionVariant startingValue)
public override void Initialize(TimeSpan startedAt, ExpressionVariant startingValue, int storeOffset)
{
_startingValue = startingValue;
var hs = new HashSet<(string, string)>();
_expression.CollectReferences(hs);
base.Initialize(storeOffset, hs);
}
public ExpressionAnimationInstance(Expression expression,
IExpressionObject target,
ServerObject target,
ExpressionVariant? finalValue,
PropertySetSnapshot parameters)
PropertySetSnapshot parameters) : base(target, parameters)
{
_expression = expression;
_target = target;
_finalValue = finalValue;
_parameters = parameters;
}
}
}

7
src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs

@ -1,11 +1,16 @@
using System;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Animations
{
internal interface IAnimationInstance
{
ServerObject TargetObject { get; }
ExpressionVariant Evaluate(TimeSpan now, ExpressionVariant currentValue);
void Start(TimeSpan startedAt, ExpressionVariant startingValue);
void Initialize(TimeSpan startedAt, ExpressionVariant startingValue, int storeOffset);
void Activate();
void Deactivate();
void Invalidate();
}
}

59
src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs

@ -1,15 +1,15 @@
using System;
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Animations
{
class KeyFrameAnimationInstance<T> : IAnimationInstance where T : struct
class KeyFrameAnimationInstance<T> : AnimationInstanceBase, IAnimationInstance where T : struct
{
private readonly IInterpolator<T> _interpolator;
private readonly ServerKeyFrame<T>[] _keyFrames;
private readonly PropertySetSnapshot _snapshot;
private readonly ExpressionVariant? _finalValue;
private readonly IExpressionObject _target;
private readonly AnimationDelayBehavior _delayBehavior;
private readonly TimeSpan _delayTime;
private readonly AnimationDirection _direction;
@ -19,21 +19,21 @@ namespace Avalonia.Rendering.Composition.Animations
private readonly AnimationStopBehavior _stopBehavior;
private TimeSpan _startedAt;
private T _startingValue;
private readonly TimeSpan _totalDuration;
private bool _finished;
public KeyFrameAnimationInstance(
IInterpolator<T> interpolator, ServerKeyFrame<T>[] keyFrames,
PropertySetSnapshot snapshot, ExpressionVariant? finalValue,
IExpressionObject target,
ServerObject target,
AnimationDelayBehavior delayBehavior, TimeSpan delayTime,
AnimationDirection direction, TimeSpan duration,
AnimationIterationBehavior iterationBehavior,
int iterationCount, AnimationStopBehavior stopBehavior)
int iterationCount, AnimationStopBehavior stopBehavior) : base(target, snapshot)
{
_interpolator = interpolator;
_keyFrames = keyFrames;
_snapshot = snapshot;
_finalValue = finalValue;
_target = target;
_delayBehavior = delayBehavior;
_delayTime = delayTime;
_direction = direction;
@ -41,26 +41,43 @@ namespace Avalonia.Rendering.Composition.Animations
_iterationBehavior = iterationBehavior;
_iterationCount = iterationCount;
_stopBehavior = stopBehavior;
if (_iterationBehavior == AnimationIterationBehavior.Count)
_totalDuration = delayTime.Add(TimeSpan.FromTicks(iterationCount * _duration.Ticks));
if (_keyFrames.Length == 0)
throw new InvalidOperationException("Animation has no key frames");
if(_duration.Ticks <= 0)
throw new InvalidOperationException("Invalid animation duration");
}
public ExpressionVariant Evaluate(TimeSpan now, ExpressionVariant currentValue)
protected override ExpressionVariant EvaluateCore(TimeSpan now, ExpressionVariant currentValue)
{
var elapsed = now - _startedAt;
var starting = ExpressionVariant.Create(_startingValue);
var ctx = new ExpressionEvaluationContext
{
Parameters = _snapshot,
Target = _target,
Parameters = Parameters,
Target = TargetObject,
CurrentValue = currentValue,
FinalValue = _finalValue ?? starting,
StartingValue = starting,
ForeignFunctionInterface = BuiltInExpressionFfi.Instance
};
var elapsed = now - _startedAt;
var res = EvaluateImpl(elapsed, currentValue, ref ctx);
if (_iterationBehavior == AnimationIterationBehavior.Count
&& !_finished
&& elapsed > _totalDuration)
{
// Active check?
TargetObject.Compositor.RemoveFromClock(this);
_finished = true;
}
return res;
}
private ExpressionVariant EvaluateImpl(TimeSpan elapsed, ExpressionVariant currentValue, ref ExpressionEvaluationContext ctx)
{
if (elapsed < _delayTime)
{
if (_delayBehavior == AnimationDelayBehavior.SetInitialValueBeforeDelay)
@ -130,10 +147,28 @@ namespace Avalonia.Rendering.Composition.Animations
return f.Value;
}
public void Start(TimeSpan startedAt, ExpressionVariant startingValue)
public override void Initialize(TimeSpan startedAt, ExpressionVariant startingValue, int storeOffset)
{
_startedAt = startedAt;
_startingValue = startingValue.CastOrDefault<T>();
var hs = new HashSet<(string, string)>();
// TODO: Update subscriptions based on the current keyframe rather than keeping subscriptions to all of them
foreach (var frame in _keyFrames)
frame.Expression?.CollectReferences(hs);
Initialize(storeOffset, hs);
}
public override void Activate()
{
TargetObject.Compositor.AddToClock(this);
base.Activate();
}
public override void Deactivate()
{
TargetObject.Compositor.RemoveFromClock(this);
base.Deactivate();
}
}
}

2
src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs

@ -66,7 +66,7 @@ namespace Avalonia.Rendering.Composition.Animations
struct ServerKeyFrame<T>
{
public T Value;
public Expression Expression;
public Expression? Expression;
public IEasingFunction EasingFunction;
public float Key;
}

12
src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs

@ -24,11 +24,16 @@ namespace Avalonia.Rendering.Composition
_variants[key] = value;
}
/*
For INTERNAL USE by CompositionAnimation ONLY, we DON'T support expression
paths like SomeParam.SomePropertyObject.SomeValue
*/
internal void Set(string key, CompositionObject obj)
{
_objects[key] = obj ?? throw new ArgumentNullException(nameof(obj));
_variants.Remove(key);
}
public void InsertColor(string propertyName, Avalonia.Media.Color value) => Set(propertyName, value);
public void InsertMatrix3x2(string propertyName, Matrix3x2 value) => Set(propertyName, value);
@ -99,7 +104,10 @@ namespace Avalonia.Rendering.Composition
_variants.Remove(key);
}
internal PropertySetSnapshot Snapshot(bool server, int allowedNestingLevel)
internal PropertySetSnapshot Snapshot(bool server) =>
SnapshotCore(server, 1);
private PropertySetSnapshot SnapshotCore(bool server, int allowedNestingLevel)
{
var dic = new Dictionary<string, PropertySetSnapshot.Value>(_objects.Count + _variants.Count);
foreach (var o in _objects)
@ -108,7 +116,7 @@ namespace Avalonia.Rendering.Composition
{
if (allowedNestingLevel <= 0)
throw new InvalidOperationException("PropertySet depth limit reached");
dic[o.Key] = new PropertySetSnapshot.Value(ps.Snapshot(server, allowedNestingLevel - 1));
dic[o.Key] = new PropertySetSnapshot.Value(ps.SnapshotCore(server, allowedNestingLevel - 1));
}
else if (o.Value.Server == null)
throw new InvalidOperationException($"Object of type {o.Value.GetType()} is not allowed");

6
src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs

@ -0,0 +1,6 @@
namespace Avalonia.Rendering.Composition;
public static class ElementCompositionPreview
{
public static CompositionVisual? GetElementVisual(Visual visual) => visual.CompositionVisual;
}

45
src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Expressions
{
@ -15,6 +16,11 @@ namespace Avalonia.Rendering.Composition.Expressions
public abstract ExpressionVariant Evaluate(ref ExpressionEvaluationContext context);
public virtual void CollectReferences(HashSet<(string parameter, string property)> references)
{
}
protected abstract string Print();
public override string ToString() => Print();
@ -114,6 +120,13 @@ namespace Avalonia.Rendering.Composition.Expressions
return FalsePart.Evaluate(ref context);
}
public override void CollectReferences(HashSet<(string parameter, string property)> references)
{
Condition.CollectReferences(references);
TruePart.CollectReferences(references);
FalsePart.CollectReferences(references);
}
protected override string Print() => $"({Condition}) ? ({TruePart}) : ({FalsePart})";
}
@ -128,6 +141,7 @@ namespace Avalonia.Rendering.Composition.Expressions
}
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) => Constant;
protected override string Print() => Constant.ToString(CultureInfo.InvariantCulture);
}
@ -155,6 +169,12 @@ namespace Avalonia.Rendering.Composition.Expressions
return res;
}
public override void CollectReferences(HashSet<(string parameter, string property)> references)
{
foreach(var arg in Parameters)
arg.CollectReferences(references);
}
protected override string Print()
{
return Name + "( (" + string.Join("), (", Parameters) + ") )";
@ -173,18 +193,30 @@ namespace Avalonia.Rendering.Composition.Expressions
Member = string.Intern(member);
}
public override void CollectReferences(HashSet<(string parameter, string property)> references)
{
Target.CollectReferences(references);
if (Target is ParameterExpression pe)
references.Add((pe.Name, Member));
}
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context)
{
if (Target is KeywordExpression ke
&& ke.Keyword == ExpressionKeyword.Target)
{
return context.Target.GetProperty(Member);
}
if (Target is ParameterExpression pe)
{
var obj = context.Parameters?.GetObjectParameter(pe.Name);
if (obj != null)
{
return obj.GetProperty(Member);
}
}
// Those are considered immutable
return Target.Evaluate(ref context).GetProperty(Member);
}
@ -263,6 +295,11 @@ namespace Avalonia.Rendering.Composition.Expressions
return default;
}
public override void CollectReferences(HashSet<(string parameter, string property)> references)
{
Parameter.CollectReferences(references);
}
protected override string Print()
{
return OperatorName(Type) + Parameter;
@ -313,6 +350,12 @@ namespace Avalonia.Rendering.Composition.Expressions
return default;
}
public override void CollectReferences(HashSet<(string parameter, string property)> references)
{
Left.CollectReferences(references);
Right.CollectReferences(references);
}
protected override string Print()
{
return "(" + Left + OperatorName(Type) + Right + ")";

1
src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Expressions
{

57
src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionTrackedValues.cs

@ -0,0 +1,57 @@
using System.Collections;
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Expressions;
internal class ExpressionTrackedObjects : IEnumerable<IExpressionObject>
{
private List<IExpressionObject> _list = new();
private HashSet<IExpressionObject> _hashSet = new();
public void Add(IExpressionObject obj, string member)
{
if (_hashSet.Add(obj))
_list.Add(obj);
}
public void Clear()
{
_list.Clear();
_hashSet.Clear();
}
IEnumerator<IExpressionObject> IEnumerable<IExpressionObject>.GetEnumerator()
{
return _list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_list).GetEnumerator();
}
public List<IExpressionObject>.Enumerator GetEnumerator() => _list.GetEnumerator();
public struct Pool
{
private Stack<ExpressionTrackedObjects> _stack = new();
public Pool()
{
}
public ExpressionTrackedObjects Get()
{
if (_stack.Count > 0)
return _stack.Pop();
return new ExpressionTrackedObjects();
}
public void Return(ExpressionTrackedObjects obj)
{
_stack.Clear();
_stack.Push(obj);
}
}
}

27
src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs

@ -10,24 +10,23 @@ namespace Avalonia.Rendering.Composition.Server;
internal class FpsCounter
{
private readonly GlyphTypeface _typeface;
private readonly bool _useManualFpsCounting;
private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
private int _framesThisSecond;
private int _totalFrames;
private int _fps;
private TimeSpan _lastFpsUpdate;
private GlyphRun[] _runs = new GlyphRun[10];
const int FirstChar = 32;
const int LastChar = 126;
private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1];
public FpsCounter(GlyphTypeface typeface, bool useManualFpsCounting = false)
public FpsCounter(GlyphTypeface typeface)
{
for (var c = 0; c <= 9; c++)
for (var c = FirstChar; c <= LastChar; c++)
{
var s = c.ToString();
var s = new string((char)c, 1);
var glyph = typeface.GetGlyph((uint)(s[0]));
_runs[c] = new GlyphRun(typeface, 18, new ReadOnlySlice<char>(s.AsMemory()), new ushort[] { glyph });
_runs[c - FirstChar] = new GlyphRun(typeface, 18, new ReadOnlySlice<char>(s.AsMemory()), new ushort[] { glyph });
}
_typeface = typeface;
_useManualFpsCounting = useManualFpsCounting;
}
public void FpsTick() => _framesThisSecond++;
@ -37,8 +36,8 @@ internal class FpsCounter
var now = _stopwatch.Elapsed;
var elapsed = now - _lastFpsUpdate;
if (!_useManualFpsCounting)
++_framesThisSecond;
++_framesThisSecond;
++_totalFrames;
if (elapsed.TotalSeconds > 1)
{
@ -47,12 +46,12 @@ internal class FpsCounter
_lastFpsUpdate = now;
}
var fpsLine = _fps.ToString("000");
var fpsLine = $"Frame #{_totalFrames:00000000} FPS: {_fps:000}";
double width = 0;
double height = 0;
foreach (var ch in fpsLine)
{
var run = _runs[ch - '0'];
var run = _runs[ch - FirstChar];
width += run.Size.Width;
height = Math.Max(height, run.Size.Height);
}
@ -64,7 +63,7 @@ internal class FpsCounter
double offset = 0;
foreach (var ch in fpsLine)
{
var run = _runs[ch - '0'];
var run = _runs[ch - FirstChar];
context.Transform = Matrix.CreateTranslation(offset, 0);
context.DrawGlyphRun(Brushes.White, run);
offset += run.Size.Width;

5
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@ -56,10 +56,11 @@ namespace Avalonia.Rendering.Composition.Server
Compositor.UpdateServerTime();
Root.Update(this, Matrix4x4.Identity);
if(_dirtyRect.IsEmpty && !_redrawRequested)
return;
Root.Update(this, Matrix4x4.Identity);
_redrawRequested = false;
using (var targetContext = _renderTarget.CreateDrawingContext(null))
{

20
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Server
@ -13,6 +15,8 @@ namespace Avalonia.Rendering.Composition.Server
public Stopwatch Clock { get; } = Stopwatch.StartNew();
public TimeSpan ServerNow { get; private set; }
private List<ServerCompositionTarget> _activeTargets = new();
private HashSet<IAnimationInstance> _activeAnimations = new();
private List<IAnimationInstance> _animationsToUpdate = new();
public ServerCompositor(IRenderLoop renderLoop)
{
@ -64,6 +68,7 @@ namespace Avalonia.Rendering.Composition.Server
}
bool IRenderLoopTask.NeedsUpdate => false;
void IRenderLoopTask.Update(TimeSpan time)
{
}
@ -71,6 +76,15 @@ namespace Avalonia.Rendering.Composition.Server
void IRenderLoopTask.Render()
{
ApplyPendingBatches();
foreach(var animation in _activeAnimations)
_animationsToUpdate.Add(animation);
foreach(var animation in _animationsToUpdate)
animation.Invalidate();
_animationsToUpdate.Clear();
foreach (var t in _activeTargets)
t.Render();
@ -86,5 +100,11 @@ namespace Avalonia.Rendering.Composition.Server
{
_activeTargets.Remove(target);
}
public void AddToClock(IAnimationInstance animationInstance) =>
_activeAnimations.Add(animationInstance);
public void RemoveFromClock(IAnimationInstance animationInstance) =>
_activeAnimations.Remove(animationInstance);
}
}

82
src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs

@ -1,5 +1,11 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Transport;
using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Server
{
@ -9,7 +15,9 @@ namespace Avalonia.Rendering.Composition.Server
public virtual long LastChangedBy => ItselfLastChangedBy;
public long ItselfLastChangedBy { get; private set; }
private uint _activationCount;
public bool IsActive => _activationCount != 0;
public ServerObject(ServerCompositor compositor)
{
Compositor = compositor;
@ -23,6 +31,7 @@ namespace Avalonia.Rendering.Composition.Server
public void Apply(ChangeSet changes)
{
ApplyCore(changes);
ValuesInvalidated();
ItselfLastChangedBy = changes.Batch!.SequenceId;
}
@ -32,5 +41,76 @@ namespace Avalonia.Rendering.Composition.Server
}
ExpressionVariant IExpressionObject.GetProperty(string name) => GetPropertyForAnimation(name);
public void Activate()
{
_activationCount++;
if (_activationCount == 1)
Activated();
}
public void Deactivate()
{
#if DEBUG
if (_activationCount == 0)
throw new InvalidOperationException();
#endif
_activationCount--;
if (_activationCount == 0)
Deactivated();
}
protected virtual void Activated()
{
}
protected virtual void Deactivated()
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected int GetOffset<T>(ref T field) where T : struct
{
return Unsafe.ByteOffset(ref Unsafe.As<uint, object>(ref _activationCount),
ref Unsafe.As<T, object>(ref field))
.ToInt32();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ref ServerObjectSubscriptionStore GetStoreFromOffset(int offset)
{
return ref Unsafe.As<uint, ServerObjectSubscriptionStore>(ref Unsafe.AddByteOffset(ref _activationCount,
new IntPtr(offset)));
}
public void NotifyAnimatedValueChanged(int offset)
{
ref var store = ref GetStoreFromOffset(offset);
store.Invalidate();
ValuesInvalidated();
}
protected virtual void ValuesInvalidated()
{
}
public void SubscribeToInvalidation(int member, IAnimationInstance animation)
{
ref var store = ref GetStoreFromOffset(member);
if (store.Subscribers.AddRef(animation))
Activate();
}
public void UnsubscribeFromInvalidation(int member, IAnimationInstance animation)
{
ref var store = ref GetStoreFromOffset(member);
if (store.Subscribers.ReleaseRef(animation))
Deactivate();
}
public virtual int? GetFieldOffset(string fieldName) => null;
}
}

22
src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs

@ -7,6 +7,7 @@ namespace Avalonia.Rendering.Composition.Server
unsafe partial class ServerCompositionVisual : ServerObject
{
private bool _isDirty;
private ServerCompositionTarget? _root;
protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform)
{
@ -95,16 +96,31 @@ namespace Avalonia.Rendering.Composition.Server
Parent = c.Parent.Value;
if (c.Root.IsSet)
Root = c.Root.Value;
_isDirty = true;
ValuesInvalidated();
}
public ServerCompositionTarget? Root
{
get => _root;
private set
{
if(_root != null)
Deactivate();
_root = value;
if (_root != null)
Activate();
}
}
protected override void ValuesInvalidated()
{
_isDirty = true;
if (IsVisibleInFrame)
Root?.AddDirtyRect(TransformedBounds);
else
Root?.Invalidate();
}
public ServerCompositionTarget? Root { get; private set; }
public ServerCompositionVisual? Parent { get; private set; }
public bool IsVisibleInFrame { get; set; }
public Rect TransformedBounds { get; set; }

1
src/Avalonia.Base/Rendering/Composition/Visual.cs

@ -38,7 +38,6 @@ namespace Avalonia.Rendering.Composition
{
}
internal Matrix4x4? TryGetServerTransform()
{
if (Root == null)

67
src/Avalonia.Base/Utilities/RefTrackingDictionary.cs

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Avalonia.Utilities;
internal class RefTrackingDictionary<TKey> : Dictionary<TKey, int> where TKey : class
{
/// <summary>
/// Increase reference count for a key by 1.
/// </summary>
/// <returns>true if key was added to the dictionary, false otherwise</returns>
public bool AddRef(TKey key)
{
#if NET5_0_OR_GREATER
ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(this, key, out var _);
count++;
#else
TryGetValue(key, out var count);
count++;
this[key] = count;
#endif
return count == 1;
}
/// <summary>
/// Decrease reference count for a key by 1.
/// </summary>
/// <returns>true if key was removed to the dictionary, false otherwise</returns>
public bool ReleaseRef(TKey key)
{
#if NET5_0_OR_GREATER
ref var count = ref CollectionsMarshal.GetValueRefOrNullRef(this, key);
if (Unsafe.IsNullRef(ref count))
#if DEBUG
throw new InvalidOperationException("Attempting to release a non-referenced object");
#else
return false;
#endif // DEBUG
count--;
if (count == 0)
{
Remove(key);
return true;
}
return false;
#else
if (!TryGetValue(key, out var count))
#if DEBUG
throw new InvalidOperationException("Attempting to release a non-referenced object");
#else
return false;
#endif // DEBUG
count--;
if (count == 0)
{
Remove(key);
return true;
}
this[key] = count;
return false;
#endif
}
}

1
src/Avalonia.SourceGenerator/Avalonia.SourceGenerator.csproj

@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>

26
src/Avalonia.SourceGenerator/CompositionGenerator/CompositionRoslynGenerator.cs

@ -2,20 +2,22 @@ using System.IO;
using System.Xml.Serialization;
using Microsoft.CodeAnalysis;
namespace Avalonia.SourceGenerator.CompositionGenerator;
[Generator(LanguageNames.CSharp)]
public class CompositionRoslynGenerator : IIncrementalGenerator
namespace Avalonia.SourceGenerator.CompositionGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
[Generator(LanguageNames.CSharp)]
public class CompositionRoslynGenerator : IIncrementalGenerator
{
var schema = context.AdditionalTextsProvider.Where(static file => file.Path.EndsWith("composition-schema.xml"));
var configs = schema.Select((t, _) =>
(GConfig)new XmlSerializer(typeof(GConfig)).Deserialize(new StringReader(t.GetText().ToString())));
context.RegisterSourceOutput(configs, (spc, config) =>
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var generator = new Generator(spc, config);
generator.Generate();
});
var schema =
context.AdditionalTextsProvider.Where(static file => file.Path.EndsWith("composition-schema.xml"));
var configs = schema.Select((t, _) =>
(GConfig)new XmlSerializer(typeof(GConfig)).Deserialize(new StringReader(t.GetText().ToString())));
context.RegisterSourceOutput(configs, (spc, config) =>
{
var generator = new Generator(new RoslynCompositionGeneratorSink(spc), config);
generator.Generate();
});
}
}
}

102
src/Avalonia.SourceGenerator/CompositionGenerator/Generator.cs

@ -7,14 +7,14 @@ using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Avalonia.SourceGenerator.CompositionGenerator.Extensions;
namespace Avalonia.SourceGenerator.CompositionGenerator
{
partial class Generator
public partial class Generator
{
private readonly SourceProductionContext _output;
private readonly ICompositionGeneratorSink _output;
private readonly GConfig _config;
private readonly HashSet<string> _objects;
private readonly HashSet<string> _brushes;
private readonly Dictionary<string, GManualClass> _manuals;
public Generator(SourceProductionContext output, GConfig config)
public Generator(ICompositionGeneratorSink output, GConfig config)
{
_output = output;
_config = config;
@ -168,17 +168,35 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
ExpressionStatement(InvocationExpression(IdentifierName("ApplyChangesExtra"))
.AddArgumentListArguments(Argument(IdentifierName("c"))))
);
var uninitializedObjectName = "dummy";
var serverStaticCtorBody = cl.Abstract
? Block()
: Block(
ParseStatement(
$"var dummy = ({serverName})System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof({serverName}));"),
ParseStatement($"System.GC.SuppressFinalize(dummy);"),
ParseStatement("InitializeFieldOffsets(dummy);")
);
var initializeFieldOffsetsBody = cl.ServerBase == null
? Block()
: Block(ParseStatement($"{cl.ServerBase}.InitializeFieldOffsets(dummy);"));
var resetBody = Block();
var startAnimationBody = Block();
var getPropertyBody = Block();
var serverGetPropertyBody = Block();
var serverGetFieldOffsetBody = Block();
var activatedBody = Block(ParseStatement("base.Activated();"));
var deactivatedBody = Block(ParseStatement("base.Deactivated();"));
var defaultsMethodBody = Block();
foreach (var prop in cl.Properties)
{
var fieldName = "_" + prop.Name.WithLowerFirst();
var fieldOffsetName = "s_OffsetOf" + fieldName;
var propType = ParseTypeName(prop.Type);
var filteredPropertyType = prop.Type.TrimEnd('?');
var isObject = _objects.Contains(filteredPropertyType);
@ -229,7 +247,7 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
if (animatedServer)
server = server.AddMembers(
DeclareField("AnimatedValueStore<" + serverPropertyType + ">", fieldName),
DeclareField("ServerAnimatedValueStore<" + serverPropertyType + ">", fieldName),
PropertyDeclaration(ParseTypeName(serverPropertyType), prop.Name)
.AddModifiers(SyntaxKind.PublicKeyword)
.WithExpressionBody(ArrowExpressionClause(
@ -240,24 +258,24 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
else
{
server = server
.AddMembers(DeclareField(serverPropertyType, fieldName))
.AddMembers(DeclareField("ServerValueStore<" + serverPropertyType + ">", fieldName))
.AddMembers(PropertyDeclaration(ParseTypeName(serverPropertyType), prop.Name)
.AddModifiers(SyntaxKind.PublicKeyword)
.AddAccessorListAccessors(
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration,
Block(ReturnStatement(IdentifierName(fieldName)))),
Block(ReturnStatement(MemberAccess(IdentifierName(fieldName), "Value")))),
AccessorDeclaration(SyntaxKind.SetAccessorDeclaration,
Block(
ParseStatement("var changed = false;"),
IfStatement(BinaryExpression(SyntaxKind.NotEqualsExpression,
IdentifierName(fieldName),
MemberAccess(IdentifierName(fieldName), "Value"),
IdentifierName("value")),
Block(
ParseStatement("On" + prop.Name + "Changing();"),
ParseStatement($"changed = true;"))
),
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
IdentifierName(fieldName), IdentifierName("value"))),
MemberAccess(IdentifierName(fieldName), "Value"), IdentifierName("value"))),
ParseStatement($"if(changed) On" + prop.Name + "Changed();")
))
))
@ -270,15 +288,22 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
if (animatedServer)
applyMethodBody = applyMethodBody.AddStatements(
IfStatement(MemberAccess(changesVar, prop.Name, "IsValue"),
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
IdentifierName(fieldName), MemberAccess(changesVar, prop.Name, "Value")))),
ExpressionStatement(
InvocationExpression(MemberAccess(fieldName, "SetValue"),
ArgumentList(SeparatedList(new[]
{
Argument(IdentifierName("this")),
Argument(MemberAccess(changesVar, prop.Name, "Value")),
}))))),
IfStatement(MemberAccess(changesVar, prop.Name, "IsAnimation"),
ExpressionStatement(
InvocationExpression(MemberAccess(fieldName, "SetAnimation"),
ArgumentList(SeparatedList(new[]
{
Argument(IdentifierName("this")),
Argument(changesVar),
Argument(MemberAccess(changesVar, prop.Name, "Animation"))
Argument(MemberAccess(changesVar, prop.Name, "Animation")),
Argument(IdentifierName(fieldOffsetName))
})))))
);
else
@ -288,16 +313,28 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
IdentifierName(prop.Name), MemberAccess(changesVar, prop.Name, "Value"))))
);
resetBody = resetBody.AddStatements(
ExpressionStatement(InvocationExpression(MemberAccess(prop.Name, "Reset"))));
if (animatedServer)
{
startAnimationBody = ApplyStartAnimation(startAnimationBody, prop, fieldName);
activatedBody = activatedBody.AddStatements(ParseStatement($"{fieldName}.Activate(this);"));
deactivatedBody = deactivatedBody.AddStatements(ParseStatement($"{fieldName}.Deactivate(this);"));
}
getPropertyBody = ApplyGetProperty(getPropertyBody, prop);
serverGetPropertyBody = ApplyGetProperty(getPropertyBody, prop);
serverGetPropertyBody = ApplyGetProperty(serverGetPropertyBody, prop);
serverGetFieldOffsetBody = ApplyGetProperty(serverGetFieldOffsetBody, prop, fieldOffsetName);
server = server.AddMembers(DeclareField("int", fieldOffsetName, SyntaxKind.StaticKeyword));
initializeFieldOffsetsBody = initializeFieldOffsetsBody.AddStatements(ExpressionStatement(
AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName(fieldOffsetName),
InvocationExpression(MemberAccess(IdentifierName(uninitializedObjectName), "GetOffset"),
ArgumentList(SingletonSeparatedList(Argument(
RefExpression(MemberAccess(IdentifierName(uninitializedObjectName), fieldName)))))))));
if (prop.DefaultValue != null)
{
@ -357,6 +394,21 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
Parameter(Identifier("changes")).WithType(ParseTypeName("ChangeSet")))
.WithBody(applyMethodBody));
server = server.AddMembers(ConstructorDeclaration(serverName)
.WithModifiers(TokenList(Token(SyntaxKind.StaticKeyword)))
.WithBody(serverStaticCtorBody));
server = server.AddMembers(
((MethodDeclarationSyntax)ParseMemberDeclaration(
$"protected static void InitializeFieldOffsets({serverName} dummy){{}}")!)
.WithBody(initializeFieldOffsetsBody));
server = server
.AddMembers(((MethodDeclarationSyntax)ParseMemberDeclaration(
$"protected override void Activated(){{}}")!).WithBody(activatedBody))
.AddMembers(((MethodDeclarationSyntax)ParseMemberDeclaration(
$"protected override void Deactivated(){{}}")!).WithBody(deactivatedBody));
client = client.AddMembers(
MethodDeclaration(ParseTypeName("void"), "InitializeDefaults").WithBody(defaultsMethodBody));
@ -374,7 +426,8 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
client = WithGetProperty(client, getPropertyBody, false);
server = WithGetProperty(server, serverGetPropertyBody, true);
server = WithGetFieldOffset(server, serverGetFieldOffsetBody);
if(cl.Implements.Count > 0)
foreach (var impl in cl.Implements)
{
@ -452,17 +505,19 @@ return;
"Vector2",
"Vector3",
"Vector4",
"Matrix",
"Matrix3x2",
"Matrix4x4",
"Quaternion",
"CompositionColor"
"Color",
"Avalonia.Media.Color"
};
BlockSyntax ApplyGetProperty(BlockSyntax body, GProperty prop)
BlockSyntax ApplyGetProperty(BlockSyntax body, GProperty prop, string? expr = null)
{
if (VariantPropertyTypes.Contains(prop.Type))
return body.AddStatements(
ParseStatement($"if(name == \"{prop.Name}\")\n return {prop.Name};\n")
ParseStatement($"if(name == \"{prop.Name}\")\n return {expr ?? prop.Name};\n")
);
return body;
@ -480,6 +535,19 @@ return;
return cl.AddMembers(method);
}
ClassDeclarationSyntax WithGetFieldOffset(ClassDeclarationSyntax cl, BlockSyntax body)
{
if (body.Statements.Count == 0)
return cl;
body = body.AddStatements(
ParseStatement("return base.GetFieldOffset(name);"));
var method = ((MethodDeclarationSyntax)ParseMemberDeclaration(
$"public override int? GetFieldOffset(string name){{}}"))
.WithBody(body);
return cl.AddMembers(method);
}
ClassDeclarationSyntax WithStartAnimation(ClassDeclarationSyntax cl, BlockSyntax body)
{

6
src/Avalonia.SourceGenerator/CompositionGenerator/ICompositionGeneratorSink.cs

@ -0,0 +1,6 @@
namespace Avalonia.SourceGenerator.CompositionGenerator;
public interface ICompositionGeneratorSink
{
void AddSource(string name, string code);
}

15
src/Avalonia.SourceGenerator/CompositionGenerator/RoslynCompositionGeneratorSink.cs

@ -0,0 +1,15 @@
using Microsoft.CodeAnalysis;
namespace Avalonia.SourceGenerator.CompositionGenerator;
class RoslynCompositionGeneratorSink : ICompositionGeneratorSink
{
private readonly SourceProductionContext _ctx;
public RoslynCompositionGeneratorSink(SourceProductionContext ctx)
{
_ctx = ctx;
}
public void AddSource(string name, string code) => _ctx.AddSource(name, code);
}
Loading…
Cancel
Save