Browse Source

Compositor works with X11. Somewhat

pull/8105/head
Nikita Tsukanov 4 years ago
parent
commit
b094699f76
  1. 1
      samples/ControlCatalog.NetCore/Program.cs
  2. 5
      src/Avalonia.Base/Avalonia.Base.csproj
  3. 1
      src/Avalonia.Base/Collections/IAvaloniaReadOnlyList.cs
  4. 2
      src/Avalonia.Base/Collections/Pooled/PooledList.cs
  5. 39
      src/Avalonia.Base/Matrix.cs
  6. 40
      src/Avalonia.Base/Rendering/Composition/Animations/AnimatedValueStore.cs
  7. 63
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
  8. 26
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs
  9. 34
      src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs
  10. 44
      src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs
  11. 11
      src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs
  12. 12
      src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs
  13. 73
      src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
  14. 73
      src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs
  15. 53
      src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs
  16. 139
      src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs
  17. 80
      src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs
  18. 46
      src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs
  19. 194
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  20. 44
      src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
  21. 97
      src/Avalonia.Base/Rendering/Composition/CompositionEasingFunction.cs
  22. 16
      src/Avalonia.Base/Rendering/Composition/CompositionGradientBrush.cs
  23. 125
      src/Avalonia.Base/Rendering/Composition/CompositionObject.cs
  24. 132
      src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
  25. 107
      src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
  26. 143
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  27. 20
      src/Avalonia.Base/Rendering/Composition/CompositorRenderLoopTask.cs
  28. 20
      src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs
  29. 56
      src/Avalonia.Base/Rendering/Composition/CustomDrawVisual.cs
  30. 81
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs
  31. 383
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
  32. 120
      src/Avalonia.Base/Rendering/Composition/Enums.cs
  33. 234
      src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs
  34. 181
      src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs
  35. 331
      src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
  36. 31
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs
  37. 14
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParseException.cs
  38. 298
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs
  39. 739
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs
  40. 256
      src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs
  41. 9
      src/Avalonia.Base/Rendering/Composition/ICompositionSurface.cs
  42. 46
      src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs
  43. 142
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  44. 46
      src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs
  45. 7
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionBrush.cs
  46. 41
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
  47. 15
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionGradientBrush.cs
  48. 22
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionLinearGradientBrush.cs
  49. 9
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs
  50. 53
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  51. 90
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
  52. 29
      src/Avalonia.Base/Rendering/Composition/Server/ServerContainerVisual.cs
  53. 31
      src/Avalonia.Base/Rendering/Composition/Server/ServerCustomDrawVisual.cs
  54. 46
      src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs
  55. 36
      src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs
  56. 16
      src/Avalonia.Base/Rendering/Composition/Server/ServerSolidColorVisual.cs
  57. 20
      src/Avalonia.Base/Rendering/Composition/Server/ServerSpriteVisual.cs
  58. 92
      src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs
  59. 37
      src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs
  60. 82
      src/Avalonia.Base/Rendering/Composition/Transport/Change.cs
  61. 36
      src/Avalonia.Base/Rendering/Composition/Transport/ChangeSet.cs
  62. 42
      src/Avalonia.Base/Rendering/Composition/Transport/ChangeSetPool.cs
  63. 20
      src/Avalonia.Base/Rendering/Composition/Transport/CustomDrawVisualChanges.cs
  64. 48
      src/Avalonia.Base/Rendering/Composition/Transport/DrawListVisualChanges.cs
  65. 19
      src/Avalonia.Base/Rendering/Composition/Transport/ListChange.cs
  66. 25
      src/Avalonia.Base/Rendering/Composition/Transport/ListChangeSet.cs
  67. 92
      src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs
  68. 16
      src/Avalonia.Base/Rendering/Composition/Transport/VisualChanges.cs
  69. 302
      src/Avalonia.Base/Rendering/Composition/Utils/CubicBezier.cs
  70. 23
      src/Avalonia.Base/Rendering/Composition/Utils/MathExt.cs
  71. 64
      src/Avalonia.Base/Rendering/Composition/Visual.cs
  72. 64
      src/Avalonia.Base/Rendering/Composition/VisualCollection.cs
  73. 6
      src/Avalonia.Base/Rendering/IRenderer.cs
  74. 1
      src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  75. 11
      src/Avalonia.Base/Size.cs
  76. 14
      src/Avalonia.Base/Threading/DispatcherPriority.cs
  77. 25
      src/Avalonia.Base/Visual.cs
  78. 70
      src/Avalonia.Base/composition-schema.xml
  79. 2
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  80. 21
      src/Avalonia.SourceGenerator/CompositionGenerator/CompositionRoslynGenerator.cs
  81. 116
      src/Avalonia.SourceGenerator/CompositionGenerator/Config.cs
  82. 90
      src/Avalonia.SourceGenerator/CompositionGenerator/Extensions.cs
  83. 59
      src/Avalonia.SourceGenerator/CompositionGenerator/Generator.KeyFrameAnimation.cs
  84. 121
      src/Avalonia.SourceGenerator/CompositionGenerator/Generator.ListProxy.cs
  85. 66
      src/Avalonia.SourceGenerator/CompositionGenerator/Generator.Utils.cs
  86. 504
      src/Avalonia.SourceGenerator/CompositionGenerator/Generator.cs
  87. 8
      src/Avalonia.X11/X11Platform.cs
  88. 17
      src/Avalonia.X11/X11Window.cs
  89. 7
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

1
samples/ControlCatalog.NetCore/Program.cs

@ -111,6 +111,7 @@ namespace ControlCatalog.NetCore
EnableMultiTouch = true,
UseDBusMenu = true,
EnableIme = true,
UseCompositor = true
})
.With(new Win32PlatformOptions
{

5
src/Avalonia.Base/Avalonia.Base.csproj

@ -3,10 +3,13 @@
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<AssemblyName>Avalonia.Base</AssemblyName>
<RootNamespace>Avalonia</RootNamespace>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\*.trie" />
<AdditionalFiles Include="composition-schema.xml" />
</ItemGroup>
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\Binding.props" />

1
src/Avalonia.Base/Collections/IAvaloniaReadOnlyList.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;

2
src/Avalonia.Base/Collections/Pooled/PooledList.cs

@ -1434,7 +1434,7 @@ namespace Avalonia.Collections.Pooled
/// <summary>
/// Returns the internal buffers to the ArrayPool.
/// </summary>
public void Dispose()
public virtual void Dispose()
{
ReturnArray();
_size = 0;

39
src/Avalonia.Base/Matrix.cs

@ -1,5 +1,6 @@
using System;
using System.Globalization;
using System.Numerics;
using Avalonia.Utilities;
namespace Avalonia
@ -106,6 +107,36 @@ namespace Avalonia
(value1._m31 * value2.M12) + (value1._m32 * value2.M22) + value2._m32);
}
public static Matrix operator +(Matrix value1, Matrix value2)
{
return new Matrix(value1.M11 + value2.M11,
value1.M12 + value2.M12,
value1.M21 + value2.M21,
value1.M22 + value2.M22,
value1.M31 + value2.M31,
value1.M32 + value2.M32);
}
public static Matrix operator -(Matrix value1, Matrix value2)
{
return new Matrix(value1.M11 - value2.M11,
value1.M12 - value2.M12,
value1.M21 - value2.M21,
value1.M22 - value2.M22,
value1.M31 - value2.M31,
value1.M32 - value2.M32);
}
public static Matrix operator *(Matrix value1, double value2)
{
return new Matrix(value1.M11 * value2,
value1.M12 * value2,
value1.M21 * value2,
value1.M22 * value2,
value1.M31 * value2,
value1.M32 * value2);
}
/// <summary>
/// Negates the given matrix by multiplying all values by -1.
/// </summary>
@ -427,6 +458,14 @@ namespace Avalonia
return true;
}
#if !BUILDTASK
public static implicit operator Matrix4x4(Matrix m)
{
return new Matrix4x4(new Matrix3x2((float)m._m11, (float)m._m12, (float)m._m21, (float)m._m22,
(float)m._m31, (float)m._m32));
}
#endif
public struct Decomposed
{
public Vector Translate;

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

@ -0,0 +1,40 @@
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Animations
{
internal struct AnimatedValueStore<T> where T : struct
{
private T _direct;
private IAnimationInstance _animation;
private T? _lastAnimated;
public T Direct => _direct;
public T GetAnimated(ServerCompositor compositor)
{
if (_animation == null)
return _direct;
var v = _animation.Evaluate(compositor.ServerNow, ExpressionVariant.Create(_direct))
.CastOrDefault<T>();
_lastAnimated = v;
return v;
}
private T LastAnimated => _animation != null ? _lastAnimated ?? _direct : _direct;
public bool IsAnimation => _animation != null;
public void SetAnimation(ChangeSet cs, IAnimationInstance animation)
{
_animation = animation;
_animation.Start(cs.Batch.CommitedAt, ExpressionVariant.Create(LastAnimated));
}
public static implicit operator AnimatedValueStore<T>(T value) => new AnimatedValueStore<T>()
{
_direct = value
};
}
}

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

@ -0,0 +1,63 @@
// ReSharper disable InconsistentNaming
// ReSharper disable CheckNamespace
using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Animations
{
public abstract class CompositionAnimation : CompositionObject, ICompositionAnimationBase
{
private readonly CompositionPropertySet _propertySet;
internal CompositionAnimation(Compositor compositor) : base(compositor, null!)
{
_propertySet = new CompositionPropertySet(compositor);
}
private protected override IChangeSetPool ChangeSetPool => throw new InvalidOperationException();
public void ClearAllParameters() => _propertySet.ClearAll();
public void ClearParameter(string key) => _propertySet.Clear(key);
void SetVariant(string key, ExpressionVariant value) => _propertySet.Set(key, value);
public void SetColorParameter(string key, Avalonia.Media.Color value) => SetVariant(key, value);
public void SetMatrix3x2Parameter(string key, Matrix3x2 value) => SetVariant(key, value);
public void SetMatrix4x4Parameter(string key, Matrix4x4 value) => SetVariant(key, value);
public void SetQuaternionParameter(string key, Quaternion value) => SetVariant(key, value);
public void SetReferenceParameter(string key, CompositionObject compositionObject) =>
_propertySet.Set(key, compositionObject);
public void SetScalarParameter(string key, float value) => SetVariant(key, value);
public void SetVector2Parameter(string key, Vector2 value) => SetVariant(key, value);
public void SetVector3Parameter(string key, Vector3 value) => SetVariant(key, value);
public void SetVector4Parameter(string key, Vector4 value) => SetVariant(key, value);
// TODO: void SetExpressionReferenceParameter(string parameterName, IAnimationObject source)
public string? Target { get; set; }
internal abstract IAnimationInstance CreateInstance(ServerObject targetObject,
ExpressionVariant? finalValue);
internal PropertySetSnapshot CreateSnapshot(bool server)
=> _propertySet.Snapshot(server, 1);
void ICompositionAnimationBase.InternalOnly()
{
}
}
}

26
src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Animations
{
public class CompositionAnimationGroup : CompositionObject, ICompositionAnimationBase
{
internal List<CompositionAnimation> Animations { get; } = new List<CompositionAnimation>();
void ICompositionAnimationBase.InternalOnly()
{
}
public void Add(CompositionAnimation value) => Animations.Add(value);
public void Remove(CompositionAnimation value) => Animations.Remove(value);
public void RemoveAll() => Animations.Clear();
public CompositionAnimationGroup(Compositor compositor) : base(compositor, null!)
{
}
private protected override IChangeSetPool ChangeSetPool => throw new InvalidOperationException();
}
}

34
src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs

@ -0,0 +1,34 @@
// ReSharper disable CheckNamespace
using System;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Animations
{
public class ExpressionAnimation : CompositionAnimation
{
private string? _expression;
private Expression? _parsedExpression;
internal ExpressionAnimation(Compositor compositor) : base(compositor)
{
}
public string? Expression
{
get => _expression;
set
{
_expression = value;
_parsedExpression = null;
}
}
private Expression ParsedExpression => _parsedExpression ??= ExpressionParser.Parse(_expression.AsSpan());
internal override IAnimationInstance CreateInstance(
ServerObject targetObject, ExpressionVariant? finalValue)
=> new ExpressionAnimationInstance(ParsedExpression,
targetObject, finalValue, CreateSnapshot(true));
}
}

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

@ -0,0 +1,44 @@
using System;
using Avalonia.Rendering.Composition.Expressions;
namespace Avalonia.Rendering.Composition.Animations
{
internal class ExpressionAnimationInstance : 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)
{
var ctx = new ExpressionEvaluationContext
{
Parameters = _parameters,
Target = _target,
ForeignFunctionInterface = BuiltInExpressionFfi.Instance,
StartingValue = _startingValue,
FinalValue = _finalValue ?? _startingValue,
CurrentValue = currentValue
};
return _expression.Evaluate(ref ctx);
}
public void Start(TimeSpan startedAt, ExpressionVariant startingValue)
{
_startingValue = startingValue;
}
public ExpressionAnimationInstance(Expression expression,
IExpressionObject target,
ExpressionVariant? finalValue,
PropertySetSnapshot parameters)
{
_expression = expression;
_target = target;
_finalValue = finalValue;
_parameters = parameters;
}
}
}

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

@ -0,0 +1,11 @@
using System;
using Avalonia.Rendering.Composition.Expressions;
namespace Avalonia.Rendering.Composition.Animations
{
internal interface IAnimationInstance
{
ExpressionVariant Evaluate(TimeSpan now, ExpressionVariant currentValue);
void Start(TimeSpan startedAt, ExpressionVariant startingValue);
}
}

12
src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs

@ -0,0 +1,12 @@
// ReSharper disable CheckNamespace
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Animations
{
public interface ICompositionAnimationBase
{
internal void InternalOnly();
}
}

73
src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs

@ -0,0 +1,73 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Animations
{
public class ImplicitAnimationCollection : CompositionObject, IDictionary<string, ICompositionAnimationBase>
{
private Dictionary<string, ICompositionAnimationBase> _inner = new Dictionary<string, ICompositionAnimationBase>();
private IDictionary<string, ICompositionAnimationBase> _innerface;
internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null!)
{
_innerface = _inner;
}
private protected override IChangeSetPool ChangeSetPool => throw new InvalidOperationException();
public IEnumerator<KeyValuePair<string, ICompositionAnimationBase>> GetEnumerator() => _inner.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable) _inner).GetEnumerator();
void ICollection<KeyValuePair<string, ICompositionAnimationBase>>.Add(KeyValuePair<string, ICompositionAnimationBase> item) => _innerface.Add(item);
public void Clear() => _inner.Clear();
bool ICollection<KeyValuePair<string, ICompositionAnimationBase>>.Contains(KeyValuePair<string, ICompositionAnimationBase> item) => _innerface.Contains(item);
void ICollection<KeyValuePair<string, ICompositionAnimationBase>>.CopyTo(KeyValuePair<string, ICompositionAnimationBase>[] array, int arrayIndex) => _innerface.CopyTo(array, arrayIndex);
bool ICollection<KeyValuePair<string, ICompositionAnimationBase>>.Remove(KeyValuePair<string, ICompositionAnimationBase> item) => _innerface.Remove(item);
public int Count => _inner.Count;
bool ICollection<KeyValuePair<string, ICompositionAnimationBase>>.IsReadOnly => _innerface.IsReadOnly;
public void Add(string key, ICompositionAnimationBase value) => _inner.Add(key, value);
public bool ContainsKey(string key) => _inner.ContainsKey(key);
public bool Remove(string key) => _inner.Remove(key);
public bool TryGetValue(string key, [MaybeNullWhen(false)] out ICompositionAnimationBase value) =>
_inner.TryGetValue(key, out value);
public ICompositionAnimationBase this[string key]
{
get => _inner[key];
set => _inner[key] = value;
}
ICollection<string> IDictionary<string, ICompositionAnimationBase>.Keys => _innerface.Keys;
ICollection<ICompositionAnimationBase> IDictionary<string, ICompositionAnimationBase>.Values =>
_innerface.Values;
// UWP compat
public uint Size => (uint) Count;
public IReadOnlyDictionary<string, ICompositionAnimationBase> GetView() =>
new Dictionary<string, ICompositionAnimationBase>(this);
public bool HasKey(string key) => ContainsKey(key);
public void Insert(string key, ICompositionAnimationBase animation) => Add(key, animation);
public ICompositionAnimationBase? Lookup(string key)
{
_inner.TryGetValue(key, out var rv);
return rv;
}
}
}

73
src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs

@ -0,0 +1,73 @@
using System;
using System.Numerics;
namespace Avalonia.Rendering.Composition.Animations
{
internal interface IInterpolator<T>
{
T Interpolate(T from, T to, float progress);
}
class ScalarInterpolator : IInterpolator<float>
{
public float Interpolate(float @from, float to, float progress) => @from + (to - @from) * progress;
public static ScalarInterpolator Instance { get; } = new ScalarInterpolator();
}
class Vector2Interpolator : IInterpolator<Vector2>
{
public Vector2 Interpolate(Vector2 @from, Vector2 to, float progress)
=> Vector2.Lerp(@from, to, progress);
public static Vector2Interpolator Instance { get; } = new Vector2Interpolator();
}
class Vector3Interpolator : IInterpolator<Vector3>
{
public Vector3 Interpolate(Vector3 @from, Vector3 to, float progress)
=> Vector3.Lerp(@from, to, progress);
public static Vector3Interpolator Instance { get; } = new Vector3Interpolator();
}
class Vector4Interpolator : IInterpolator<Vector4>
{
public Vector4 Interpolate(Vector4 @from, Vector4 to, float progress)
=> Vector4.Lerp(@from, to, progress);
public static Vector4Interpolator Instance { get; } = new Vector4Interpolator();
}
class QuaternionInterpolator : IInterpolator<Quaternion>
{
public Quaternion Interpolate(Quaternion @from, Quaternion to, float progress)
=> Quaternion.Lerp(@from, to, progress);
public static QuaternionInterpolator Instance { get; } = new QuaternionInterpolator();
}
class ColorInterpolator : IInterpolator<Avalonia.Media.Color>
{
static byte Lerp(float a, float b, float p) => (byte) Math.Max(0, Math.Min(255, (p * (b - a) + a)));
public static Avalonia.Media.Color
LerpRGB(Avalonia.Media.Color to, Avalonia.Media.Color from, float progress) =>
new Avalonia.Media.Color(Lerp(to.A, @from.A, progress),
Lerp(to.R, @from.R, progress),
Lerp(to.G, @from.G, progress),
Lerp(to.B, @from.B, progress));
public Avalonia.Media.Color Interpolate(Avalonia.Media.Color @from, Avalonia.Media.Color to, float progress)
=> LerpRGB(@from, to, progress);
public static ColorInterpolator Instance { get; } = new ColorInterpolator();
}
class BooleanInterpolator : IInterpolator<bool>
{
public bool Interpolate(bool @from, bool to, float progress) => progress >= 1 ? to : @from;
public static BooleanInterpolator Instance { get; } = new BooleanInterpolator();
}
}

53
src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs

@ -0,0 +1,53 @@
namespace Avalonia.Rendering.Composition.Animations
{
public abstract class KeyFrameAnimation : CompositionAnimation
{
internal KeyFrameAnimation(Compositor compositor) : base(compositor)
{
}
public AnimationDelayBehavior DelayBehavior { get; set; }
public System.TimeSpan DelayTime { get; set; }
public AnimationDirection Direction { get; set; }
public System.TimeSpan Duration { get; set; }
public AnimationIterationBehavior IterationBehavior { get; set; }
public int IterationCount { get; set; } = 1;
public AnimationStopBehavior StopBehavior { get; set; }
private protected abstract IKeyFrames KeyFrames { get; }
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value,
CompositionEasingFunction easingFunction) =>
KeyFrames.InsertExpressionKeyFrame(normalizedProgressKey, value, easingFunction);
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value)
=> KeyFrames.InsertExpressionKeyFrame(normalizedProgressKey, value, new LinearEasingFunction(Compositor));
}
public enum AnimationDelayBehavior
{
SetInitialValueAfterDelay,
SetInitialValueBeforeDelay
}
public enum AnimationDirection
{
Normal,
Reverse,
Alternate,
AlternateReverse
}
public enum AnimationIterationBehavior
{
Count,
Forever
}
public enum AnimationStopBehavior
{
LeaveCurrentValue,
SetToInitialValue,
SetToFinalValue
}
}

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

@ -0,0 +1,139 @@
using System;
using Avalonia.Rendering.Composition.Expressions;
namespace Avalonia.Rendering.Composition.Animations
{
class KeyFrameAnimationInstance<T> : 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;
private readonly TimeSpan _duration;
private readonly AnimationIterationBehavior _iterationBehavior;
private readonly int _iterationCount;
private readonly AnimationStopBehavior _stopBehavior;
private TimeSpan _startedAt;
private T _startingValue;
public KeyFrameAnimationInstance(
IInterpolator<T> interpolator, ServerKeyFrame<T>[] keyFrames,
PropertySetSnapshot snapshot, ExpressionVariant? finalValue,
IExpressionObject target,
AnimationDelayBehavior delayBehavior, TimeSpan delayTime,
AnimationDirection direction, TimeSpan duration,
AnimationIterationBehavior iterationBehavior,
int iterationCount, AnimationStopBehavior stopBehavior)
{
_interpolator = interpolator;
_keyFrames = keyFrames;
_snapshot = snapshot;
_finalValue = finalValue;
_target = target;
_delayBehavior = delayBehavior;
_delayTime = delayTime;
_direction = direction;
_duration = duration;
_iterationBehavior = iterationBehavior;
_iterationCount = iterationCount;
_stopBehavior = stopBehavior;
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)
{
var elapsed = now - _startedAt;
var starting = ExpressionVariant.Create(_startingValue);
var ctx = new ExpressionEvaluationContext
{
Parameters = _snapshot,
Target = _target,
CurrentValue = currentValue,
FinalValue = _finalValue ?? starting,
StartingValue = starting,
ForeignFunctionInterface = BuiltInExpressionFfi.Instance
};
if (elapsed < _delayTime)
{
if (_delayBehavior == AnimationDelayBehavior.SetInitialValueBeforeDelay)
return ExpressionVariant.Create(GetKeyFrame(ref ctx, _keyFrames[0]));
return currentValue;
}
elapsed -= _delayTime;
var iterationNumber = elapsed.Ticks / _duration.Ticks;
if (_iterationBehavior == AnimationIterationBehavior.Count
&& iterationNumber >= _iterationCount)
return ExpressionVariant.Create(GetKeyFrame(ref ctx, _keyFrames[_keyFrames.Length - 1]));
var evenIterationNumber = iterationNumber % 2 == 0;
elapsed = TimeSpan.FromTicks(elapsed.Ticks % _duration.Ticks);
var reverse =
_direction == AnimationDirection.Alternate
? !evenIterationNumber
: _direction == AnimationDirection.AlternateReverse
? evenIterationNumber
: _direction == AnimationDirection.Reverse;
var iterationProgress = elapsed.TotalSeconds / _duration.TotalSeconds;
if (reverse)
iterationProgress = 1 - iterationProgress;
var left = new ServerKeyFrame<T>
{
Value = _startingValue
};
var right = _keyFrames[_keyFrames.Length - 1];
for (var c = 0; c < _keyFrames.Length; c++)
{
var kf = _keyFrames[c];
if (kf.Key < iterationProgress)
{
// this is the last frame
if (c == _keyFrames.Length - 1)
return ExpressionVariant.Create(GetKeyFrame(ref ctx, kf));
left = kf;
right = _keyFrames[c + 1];
break;
}
}
var keyProgress = Math.Max(0, Math.Min(1, (iterationProgress - left.Key) / (right.Key - left.Key)));
var easedKeyProgress = right.EasingFunction.Ease((float) keyProgress);
if (float.IsNaN(easedKeyProgress) || float.IsInfinity(easedKeyProgress))
return currentValue;
return ExpressionVariant.Create(_interpolator.Interpolate(
GetKeyFrame(ref ctx, left),
GetKeyFrame(ref ctx, right),
easedKeyProgress
));
}
T GetKeyFrame(ref ExpressionEvaluationContext ctx, ServerKeyFrame<T> f)
{
if (f.Expression != null)
return f.Expression.Evaluate(ref ctx).CastOrDefault<T>();
else
return f.Value;
}
public void Start(TimeSpan startedAt, ExpressionVariant startingValue)
{
_startedAt = startedAt;
_startingValue = startingValue.CastOrDefault<T>();
}
}
}

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

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Expressions;
namespace Avalonia.Rendering.Composition.Animations
{
class KeyFrames<T> : List<KeyFrame<T>>, IKeyFrames
{
void Validate(float key)
{
if (key < 0 || key > 1)
throw new ArgumentException("Key frame key");
if (Count > 0 && this[Count - 1].NormalizedProgressKey > key)
throw new ArgumentException("Key frame key " + key + " is less than the previous one");
}
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value,
CompositionEasingFunction easingFunction)
{
Validate(normalizedProgressKey);
Add(new KeyFrame<T>
{
NormalizedProgressKey = normalizedProgressKey,
Expression = Expression.Parse(value),
EasingFunction = easingFunction
});
}
public void Insert(float normalizedProgressKey, T value, CompositionEasingFunction easingFunction)
{
Validate(normalizedProgressKey);
Add(new KeyFrame<T>
{
NormalizedProgressKey = normalizedProgressKey,
Value = value,
EasingFunction = easingFunction
});
}
public ServerKeyFrame<T>[] Snapshot()
{
var frames = new ServerKeyFrame<T>[Count];
for (var c = 0; c < Count; c++)
{
var f = this[c];
frames[c] = new ServerKeyFrame<T>
{
Expression = f.Expression,
Value = f.Value,
EasingFunction = f.EasingFunction.Snapshot(),
Key = f.NormalizedProgressKey
};
}
return frames;
}
}
struct KeyFrame<T>
{
public float NormalizedProgressKey;
public T Value;
public Expression Expression;
public CompositionEasingFunction EasingFunction;
}
struct ServerKeyFrame<T>
{
public T Value;
public Expression Expression;
public IEasingFunction EasingFunction;
public float Key;
}
interface IKeyFrames
{
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value, CompositionEasingFunction easingFunction);
}
}

46
src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs

@ -0,0 +1,46 @@
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Expressions;
namespace Avalonia.Rendering.Composition.Animations
{
internal class PropertySetSnapshot : IExpressionParameterCollection, IExpressionObject
{
private readonly Dictionary<string, Value> _dic;
public struct Value
{
public ExpressionVariant Variant;
public IExpressionObject Object;
public Value(IExpressionObject o)
{
Object = o;
Variant = default;
}
public static implicit operator Value(ExpressionVariant v) => new Value
{
Variant = v
};
}
public PropertySetSnapshot(Dictionary<string, Value> dic)
{
_dic = dic;
}
public ExpressionVariant GetParameter(string name)
{
_dic.TryGetValue(name, out var v);
return v.Variant;
}
public IExpressionObject GetObjectParameter(string name)
{
_dic.TryGetValue(name, out var v);
return v.Object;
}
public ExpressionVariant GetProperty(string name) => GetParameter(name);
}
}

194
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using Avalonia.Collections;
using Avalonia.Media;
using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.Composition;
public class CompositingRenderer : RendererBase, IRendererWithCompositor
{
private readonly IRenderRoot _root;
private readonly Compositor _compositor;
private readonly IDeferredRendererLock? _rendererLock;
CompositionDrawingContext _recorder = new();
DrawingContext _recordingContext;
private HashSet<Visual> _dirty = new();
private HashSet<Visual> _recalculateChildren = new();
private readonly CompositionTarget _target;
private bool _queuedUpdate;
private Action _update;
public CompositingRenderer(IRenderRoot root,
Compositor compositor,
IDeferredRendererLock? rendererLock = null)
{
_root = root;
_compositor = compositor;
_recordingContext = new DrawingContext(_recorder);
_rendererLock = rendererLock ?? new ManagedDeferredRendererLock();
_target = compositor.CreateCompositionTarget(root.CreateRenderTarget);
_target.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor);
_update = Update;
}
public bool DrawFps { get; set; }
public bool DrawDirtyRects { get; set; }
public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated;
void QueueUpdate()
{
if(_queuedUpdate)
return;
_queuedUpdate = true;
Dispatcher.UIThread.Post(_update, DispatcherPriority.Composition);
}
public void AddDirty(IVisual visual)
{
_dirty.Add((Visual)visual);
QueueUpdate();
}
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter)
{
var res = _target.TryHitTest(new Vector2((float)p.X, (float)p.Y));
if(res == null)
yield break;
for (var index = res.Count - 1; index >= 0; index--)
{
var v = res[index];
if (v is CompositionDrawListVisual dv)
{
if (filter == null || filter(dv.Visual))
yield return dv.Visual;
}
}
}
public IVisual? HitTestFirst(Point p, IVisual root, Func<IVisual, bool> filter)
{
// TODO: Optimize
return HitTest(p, root, filter).FirstOrDefault();
}
public void RecalculateChildren(IVisual visual)
{
_recalculateChildren.Add((Visual)visual);
QueueUpdate();
}
private void SyncChildren(Visual v)
{
//TODO: Optimize by moving that logic to Visual itself
if(v.CompositionVisual == null)
return;
var compositionChildren = v.CompositionVisual.Children;
var visualChildren = (AvaloniaList<IVisual>)v.GetVisualChildren();
if (compositionChildren.Count == visualChildren.Count)
{
bool mismatch = false;
for(var c=0; c<visualChildren.Count; c++)
if(!object.ReferenceEquals(compositionChildren[c], ((Visual)visualChildren[c]).CompositionVisual))
{
mismatch = true;
break;
}
if(!mismatch)
return;
}
compositionChildren.Clear();
foreach (var ch in v.GetVisualChildren())
{
var compositionChild = ((Visual)ch).CompositionVisual;
if (compositionChild != null)
compositionChildren.Add(compositionChild);
}
}
private void Update()
{
_queuedUpdate = false;
foreach (var visual in _dirty)
{
var comp = visual.CompositionVisual;
if(comp == null)
continue;
// TODO: Optimize all of that by moving to the Visual itself, so we won't have to recalculate every time
comp.Offset = new Vector3((float)visual.Bounds.Left, (float)visual.Bounds.Top, 0);
comp.Size = new Vector2((float)visual.Bounds.Width, (float)visual.Bounds.Height);
comp.Visible = visual.IsVisible;
comp.Opacity = (float)visual.Opacity;
comp.ClipToBounds = visual.ClipToBounds;
comp.Clip = visual.Clip?.PlatformImpl;
var renderTransform = Matrix.Identity;
if (visual.RenderTransform != null)
{
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(origin);
renderTransform = (-offset) * visual.RenderTransform.Value * (offset);
}
if (visual.HasMirrorTransform)
{
var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0);
renderTransform *= mirrorMatrix;
}
comp.TransformMatrix = renderTransform;
_recorder.BeginUpdate(comp.DrawList ?? new CompositionDrawList());
visual.Render(_recordingContext);
comp.DrawList = _recorder.EndUpdate();
SyncChildren(visual);
}
foreach(var v in _recalculateChildren)
if (!_dirty.Contains(v))
SyncChildren(v);
_dirty.Clear();
_recalculateChildren.Clear();
}
public void Resized(Size size)
{
}
public void Paint(Rect rect)
{
// We render only on the render thread for now
Update();
Compositor.RequestCommitAsync().Wait();
}
public void Start() => _target.IsEnabled = true;
public void Stop()
{
_target.IsEnabled = true;
}
public void Dispose()
{
Stop();
_target.Dispose();
// Wait for the composition batch to be applied and rendered to guarantee that
// render target is not used anymore and can be safely disposed
_compositor.RequestCommitAsync().Wait();
}
public Compositor Compositor => _compositor;
}

44
src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs

@ -0,0 +1,44 @@
using System.Numerics;
using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition;
internal class CompositionDrawListVisual : CompositionContainerVisual
{
public Visual Visual { get; }
private new DrawListVisualChanges Changes => (DrawListVisualChanges)base.Changes;
private CompositionDrawList? _drawList;
public CompositionDrawList? DrawList
{
get => _drawList;
set
{
_drawList?.Dispose();
_drawList = value;
Changes.DrawCommands = value?.Clone();
}
}
private protected override IChangeSetPool ChangeSetPool => DrawListVisualChanges.Pool;
internal CompositionDrawListVisual(Compositor compositor, ServerCompositionContainerVisual server, Visual visual) : base(compositor, server)
{
Visual = visual;
}
internal override bool HitTest(Vector2 point)
{
if (DrawList == null)
return false;
var pt = new Point(point.X, point.Y);
if (Visual is ICustomHitTest custom)
return custom.HitTest(pt);
foreach (var op in DrawList)
if (op.Item.HitTest(pt))
return true;
return false;
}
}

97
src/Avalonia.Base/Rendering/Composition/CompositionEasingFunction.cs

@ -0,0 +1,97 @@
using System;
using System.Numerics;
using Avalonia.Rendering.Composition.Transport;
using Avalonia.Rendering.Composition.Utils;
namespace Avalonia.Rendering.Composition
{
public abstract class CompositionEasingFunction : CompositionObject
{
internal CompositionEasingFunction(Compositor compositor) : base(compositor, null!)
{
}
private protected override IChangeSetPool ChangeSetPool => throw new InvalidOperationException();
internal abstract IEasingFunction Snapshot();
}
internal interface IEasingFunction
{
float Ease(float progress);
}
public sealed class DelegateCompositionEasingFunction : CompositionEasingFunction
{
private readonly Easing _func;
public delegate float EasingDelegate(float progress);
internal DelegateCompositionEasingFunction(Compositor compositor, EasingDelegate func) : base(compositor)
{
_func = new Easing(func);
}
class Easing : IEasingFunction
{
private readonly EasingDelegate _func;
public Easing(EasingDelegate func)
{
_func = func;
}
public float Ease(float progress) => _func(progress);
}
internal override IEasingFunction Snapshot() => _func;
}
public class LinearEasingFunction : CompositionEasingFunction
{
public LinearEasingFunction(Compositor compositor) : base(compositor)
{
}
class Linear : IEasingFunction
{
public float Ease(float progress) => progress;
}
private static readonly Linear Instance = new Linear();
internal override IEasingFunction Snapshot() => Instance;
}
public class CubicBezierEasingFunction : CompositionEasingFunction
{
private CubicBezier _bezier;
public Vector2 ControlPoint1 { get; }
public Vector2 ControlPoint2 { get; }
//cubic-bezier(0.25, 0.1, 0.25, 1.0)
internal CubicBezierEasingFunction(Compositor compositor, Vector2 controlPoint1, Vector2 controlPoint2) : base(compositor)
{
ControlPoint1 = controlPoint1;
ControlPoint2 = controlPoint2;
if (controlPoint1.X < 0 || controlPoint1.X > 1 || controlPoint2.X < 0 || controlPoint2.X > 1)
throw new ArgumentException();
_bezier = new CubicBezier(controlPoint1.X, controlPoint1.Y, controlPoint2.X, controlPoint2.Y);
}
class EasingFunction : IEasingFunction
{
private readonly CubicBezier _bezier;
public EasingFunction(CubicBezier bezier)
{
_bezier = bezier;
}
public float Ease(float progress) => (float)_bezier.Solve(progress);
}
internal static IEasingFunction Ease { get; } = new EasingFunction(new CubicBezier(0.25, 0.1, 0.25, 1));
internal override IEasingFunction Snapshot() => new EasingFunction(_bezier);
}
}

16
src/Avalonia.Base/Rendering/Composition/CompositionGradientBrush.cs

@ -0,0 +1,16 @@
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition
{
public partial class CompositionGradientBrush : CompositionBrush
{
internal CompositionGradientBrush(Compositor compositor, ServerCompositionGradientBrush server) : base(compositor, server)
{
ColorStops = new CompositionGradientStopCollection(compositor, server.Stops);
}
public CompositionGradientStopCollection ColorStops { get; }
}
}

125
src/Avalonia.Base/Rendering/Composition/CompositionObject.cs

@ -0,0 +1,125 @@
using System;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition
{
public abstract class CompositionObject : IDisposable, IExpressionObject
{
public ImplicitAnimationCollection? ImplicitAnimations { get; set; }
internal CompositionObject(Compositor compositor, ServerObject server)
{
Compositor = compositor;
Server = server;
}
public Compositor Compositor { get; }
internal ServerObject Server { get; }
public bool IsDisposed { get; private set; }
private ChangeSet? _changes;
private static void ThrowInvalidOperation() =>
throw new InvalidOperationException("There is no server-side counterpart for this object");
private protected ChangeSet Changes
{
get
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (Server == null) ThrowInvalidOperation();
var currentBatch = Compositor.CurrentBatch;
if (_changes != null && _changes.Batch != currentBatch)
_changes = null;
if (_changes == null)
{
_changes = ChangeSetPool.Get(Server!, currentBatch);
currentBatch.Changes!.Add(_changes);
Compositor.QueueImplicitBatchCommit();
}
return _changes;
}
}
private protected abstract IChangeSetPool ChangeSetPool { get; }
public void Dispose()
{
Changes.Dispose = true;
IsDisposed = true;
}
internal virtual ExpressionVariant GetPropertyForAnimation(string name)
{
return default;
}
ExpressionVariant IExpressionObject.GetProperty(string name) => GetPropertyForAnimation(name);
public void StartAnimation(string propertyName, CompositionAnimation animation)
=> StartAnimation(propertyName, animation, null);
internal virtual void StartAnimation(string propertyName, CompositionAnimation animation, ExpressionVariant? finalValue = null)
{
throw new ArgumentException("Unknown property " + propertyName);
}
public void StartAnimationGroup(ICompositionAnimationBase grp)
{
if (grp is CompositionAnimation animation)
{
if(animation.Target == null)
throw new ArgumentException("Animation Target can't be null");
StartAnimation(animation.Target, animation);
}
else if (grp is CompositionAnimationGroup group)
{
foreach (var a in group.Animations)
{
if (a.Target == null)
throw new ArgumentException("Animation Target can't be null");
StartAnimation(a.Target, a);
}
}
}
bool StartAnimationGroupPart(CompositionAnimation animation, string target, ExpressionVariant finalValue)
{
if(animation.Target == null)
throw new ArgumentException("Animation Target can't be null");
if (animation.Target == target)
{
StartAnimation(animation.Target, animation, finalValue);
return true;
}
else
{
StartAnimation(animation.Target, animation);
return false;
}
}
internal bool StartAnimationGroup(ICompositionAnimationBase grp, string target, ExpressionVariant finalValue)
{
if (grp is CompositionAnimation animation)
return StartAnimationGroupPart(animation, target, finalValue);
if (grp is CompositionAnimationGroup group)
{
var matched = false;
foreach (var a in group.Animations)
{
if (a.Target == null)
throw new ArgumentException("Animation Target can't be null");
if (StartAnimationGroupPart(a, target, finalValue))
matched = true;
}
return matched;
}
throw new ArgumentException();
}
}
}

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

@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition
{
public class CompositionPropertySet : CompositionObject
{
private readonly Dictionary<string, ExpressionVariant> _variants = new Dictionary<string, ExpressionVariant>();
private readonly Dictionary<string, CompositionObject> _objects = new Dictionary<string, CompositionObject>();
internal CompositionPropertySet(Compositor compositor) : base(compositor, null!)
{
}
private protected override IChangeSetPool ChangeSetPool => throw new NotSupportedException();
internal void Set(string key, ExpressionVariant value)
{
_objects.Remove(key);
_variants[key] = value;
}
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);
public void InsertMatrix4x4(string propertyName, Matrix4x4 value) => Set(propertyName, value);
public void InsertQuaternion(string propertyName, Quaternion value) => Set(propertyName, value);
public void InsertScalar(string propertyName, float value) => Set(propertyName, value);
public void InsertVector2(string propertyName, Vector2 value) => Set(propertyName, value);
public void InsertVector3(string propertyName, Vector3 value) => Set(propertyName, value);
public void InsertVector4(string propertyName, Vector4 value) => Set(propertyName, value);
CompositionGetValueStatus TryGetVariant<T>(string key, out T value) where T : struct
{
value = default;
if (!_variants.TryGetValue(key, out var v))
return _objects.ContainsKey(key)
? CompositionGetValueStatus.TypeMismatch
: CompositionGetValueStatus.NotFound;
return v.TryCast(out value) ? CompositionGetValueStatus.Succeeded : CompositionGetValueStatus.TypeMismatch;
}
public CompositionGetValueStatus TryGetColor(string propertyName, out Avalonia.Media.Color value)
=> TryGetVariant(propertyName, out value);
public CompositionGetValueStatus TryGetMatrix3x2(string propertyName, out Matrix3x2 value)
=> TryGetVariant(propertyName, out value);
public CompositionGetValueStatus TryGetMatrix4x4(string propertyName, out Matrix4x4 value)
=> TryGetVariant(propertyName, out value);
public CompositionGetValueStatus TryGetQuaternion(string propertyName, out Quaternion value)
=> TryGetVariant(propertyName, out value);
public CompositionGetValueStatus TryGetScalar(string propertyName, out float value)
=> TryGetVariant(propertyName, out value);
public CompositionGetValueStatus TryGetVector2(string propertyName, out Vector2 value)
=> TryGetVariant(propertyName, out value);
public CompositionGetValueStatus TryGetVector3(string propertyName, out Vector3 value)
=> TryGetVariant(propertyName, out value);
public CompositionGetValueStatus TryGetVector4(string propertyName, out Vector4 value)
=> TryGetVariant(propertyName, out value);
public void InsertBoolean(string propertyName, bool value) => Set(propertyName, value);
public CompositionGetValueStatus TryGetBoolean(string propertyName, out bool value)
=> TryGetVariant(propertyName, out value);
internal void ClearAll()
{
_objects.Clear();
_variants.Clear();
}
internal void Clear(string key)
{
_objects.Remove(key);
_variants.Remove(key);
}
internal PropertySetSnapshot Snapshot(bool server, int allowedNestingLevel)
{
var dic = new Dictionary<string, PropertySetSnapshot.Value>(_objects.Count + _variants.Count);
foreach (var o in _objects)
{
if (o.Value is CompositionPropertySet ps)
{
if (allowedNestingLevel <= 0)
throw new InvalidOperationException("PropertySet depth limit reached");
dic[o.Key] = new PropertySetSnapshot.Value(ps.Snapshot(server, allowedNestingLevel - 1));
}
else if (o.Value.Server == null)
throw new InvalidOperationException($"Object of type {o.Value.GetType()} is not allowed");
else
dic[o.Key] = new PropertySetSnapshot.Value(server ? (IExpressionObject) o.Value.Server : o.Value);
}
foreach (var v in _variants)
dic[v.Key] = v.Value;
return new PropertySetSnapshot(dic);
}
}
public enum CompositionGetValueStatus
{
Succeeded,
TypeMismatch,
NotFound
}
}

107
src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs

@ -0,0 +1,107 @@
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Collections.Pooled;
namespace Avalonia.Rendering.Composition
{
public partial class CompositionTarget
{
partial void OnRootChanged()
{
if (Root != null)
Root.Root = this;
}
partial void OnRootChanging()
{
if (Root != null)
Root.Root = null;
}
public PooledList<CompositionVisual>? TryHitTest(Vector2 point)
{
Server.Readback.NextRead();
if (Root == null)
return null;
var res = new PooledList<CompositionVisual>();
HitTestCore(Root, point, res);
return res;
}
public Vector2? TryTransformToVisual(CompositionVisual visual, Vector2 point)
{
if (visual.Root != this)
return null;
var v = visual;
var m = Matrix3x2.Identity;
while (v != null)
{
if (!TryGetInvertedTransform(v, out var cm))
return null;
m = m * cm;
v = v.Parent;
}
return Vector2.Transform(point, m);
}
bool TryGetInvertedTransform(CompositionVisual visual, out Matrix3x2 matrix)
{
var m = visual.TryGetServerTransform();
if (m == null)
{
matrix = default;
return false;
}
// TODO: Use Matrix3x3
var m32 = new Matrix3x2(m.Value.M11, m.Value.M12, m.Value.M21, m.Value.M22, m.Value.M41, m.Value.M42);
return Matrix3x2.Invert(m32, out matrix);
}
bool TryTransformTo(CompositionVisual visual, ref Vector2 v)
{
if (TryGetInvertedTransform(visual, out var m))
{
v = Vector2.Transform(v, m);
return true;
}
return false;
}
bool HitTestCore(CompositionVisual visual, Vector2 point, PooledList<CompositionVisual> result)
{
//TODO: Check readback too
if (visual.Visible == false)
return false;
if (!TryTransformTo(visual, ref point))
return false;
if (point.X >= 0 && point.Y >= 0 && point.X <= visual.Size.X && point.Y <= visual.Size.Y)
{
bool success = false;
// Hit-test the current node
if (visual.HitTest(point))
{
result.Add(visual);
success = true;
}
// Inspect children too
if(visual is CompositionContainerVisual cv)
for (var c = cv.Children.Count - 1; c >= 0; c--)
{
var ch = cv.Children[c];
var hit = HitTestCore(ch, point, result);
if (hit)
return true;
}
return success;
}
return false;
}
}
}

143
src/Avalonia.Base/Rendering/Composition/Compositor.cs

@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Threading.Tasks;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
using Avalonia.Threading;
namespace Avalonia.Rendering.Composition
{
public partial class Compositor
{
private ServerCompositor _server;
private Batch _currentBatch;
private bool _implicitBatchCommitQueued;
private Action _implicitBatchCommit;
internal Batch CurrentBatch => _currentBatch;
internal ServerCompositor Server => _server;
internal CompositionEasingFunction DefaultEasing { get; }
private Compositor(ServerCompositor server)
{
_server = server;
_currentBatch = new Batch();
_implicitBatchCommit = ImplicitBatchCommit;
DefaultEasing = new CubicBezierEasingFunction(this,
new Vector2(0.25f, 0.1f), new Vector2(0.25f, 1f));
}
public CompositionTarget CreateCompositionTarget(Func<IRenderTarget> renderTargetFactory)
{
return new CompositionTarget(this, new ServerCompositionTarget(_server, renderTargetFactory));
}
public Task RequestCommitAsync()
{
var batch = CurrentBatch;
_currentBatch = new Batch();
batch.CommitedAt = Server.Clock.Elapsed;
_server.EnqueueBatch(batch);
return batch.Completed;
}
public static Compositor Create(IRenderLoop timer)
{
return new Compositor(new ServerCompositor(timer));
}
public void Dispose()
{
}
public CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server));
public CompositionSolidColorVisual CreateSolidColorVisual() => new CompositionSolidColorVisual(this,
new ServerCompositionSolidColorVisual(_server));
public CompositionSolidColorVisual CreateSolidColorVisual(Avalonia.Media.Color color)
{
var v = new CompositionSolidColorVisual(this, new ServerCompositionSolidColorVisual(_server));
v.Color = color;
return v;
}
public CompositionSpriteVisual CreateSpriteVisual() => new CompositionSpriteVisual(this, new ServerCompositionSpriteVisual(_server));
public CompositionLinearGradientBrush CreateLinearGradientBrush()
=> new CompositionLinearGradientBrush(this, new ServerCompositionLinearGradientBrush(_server));
public CompositionColorGradientStop CreateColorGradientStop()
=> new CompositionColorGradientStop(this, new ServerCompositionColorGradientStop(_server));
public CompositionColorGradientStop CreateColorGradientStop(float offset, Avalonia.Media.Color color)
{
var stop = CreateColorGradientStop();
stop.Offset = offset;
stop.Color = color;
return stop;
}
// We want to make it 100% async later
/*
public CompositionBitmapSurface LoadBitmapSurface(Stream stream)
{
var bmp = _server.Backend.LoadCpuMemoryBitmap(stream);
return new CompositionBitmapSurface(this, bmp);
}
public async Task<CompositionBitmapSurface> LoadBitmapSurfaceAsync(Stream stream)
{
var bmp = await Task.Run(() => _server.Backend.LoadCpuMemoryBitmap(stream));
return new CompositionBitmapSurface(this, bmp);
}
*/
public CompositionColorBrush CreateColorBrush(Avalonia.Media.Color color) =>
new CompositionColorBrush(this, new ServerCompositionColorBrush(_server)) {Color = color};
public CompositionSurfaceBrush CreateSurfaceBrush() =>
new CompositionSurfaceBrush(this, new ServerCompositionSurfaceBrush(_server));
/*
public CompositionGaussianBlurEffectBrush CreateGaussianBlurEffectBrush() =>
new CompositionGaussianBlurEffectBrush(this, new ServerCompositionGaussianBlurEffectBrush(_server));
public CompositionBackdropBrush CreateBackdropBrush() =>
new CompositionBackdropBrush(this, new ServerCompositionBackdropBrush(Server));*/
public ExpressionAnimation CreateExpressionAnimation() => new ExpressionAnimation(this);
public ExpressionAnimation CreateExpressionAnimation(string expression) => new ExpressionAnimation(this)
{
Expression = expression
};
public ImplicitAnimationCollection CreateImplicitAnimationCollection() => new ImplicitAnimationCollection(this);
public CompositionAnimationGroup CreateAnimationGroup() => new CompositionAnimationGroup(this);
internal CustomDrawVisual<T> CreateCustomDrawVisual<T>(ICustomDrawVisualRenderer<T> renderer,
ICustomDrawVisualHitTest<T>? hitTest = null) where T : IEquatable<T> =>
new CustomDrawVisual<T>(this, renderer, hitTest);
public void QueueImplicitBatchCommit()
{
if(_implicitBatchCommitQueued)
return;
_implicitBatchCommitQueued = true;
Dispatcher.UIThread.Post(_implicitBatchCommit, DispatcherPriority.CompositionBatch);
}
private void ImplicitBatchCommit()
{
_implicitBatchCommitQueued = false;
RequestCommitAsync();
}
}
}

20
src/Avalonia.Base/Rendering/Composition/CompositorRenderLoopTask.cs

@ -0,0 +1,20 @@
using System;
namespace Avalonia.Rendering.Composition;
partial class Compositor
{
class CompositorRenderLoopTask : IRenderLoopTask
{
public bool NeedsUpdate { get; }
public void Update(TimeSpan time)
{
throw new NotImplementedException();
}
public void Render()
{
throw new NotImplementedException();
}
}
}

20
src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs

@ -0,0 +1,20 @@
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition
{
public class CompositionContainerVisual : CompositionVisual
{
public CompositionVisualCollection Children { get; }
internal CompositionContainerVisual(Compositor compositor, ServerCompositionContainerVisual server) : base(compositor, server)
{
Children = new CompositionVisualCollection(this, server.Children);
}
private protected override void OnRootChanged()
{
foreach (var ch in Children)
ch.Root = Root;
base.OnRootChanged();
}
}
}

56
src/Avalonia.Base/Rendering/Composition/CustomDrawVisual.cs

@ -0,0 +1,56 @@
using System;
using System.Numerics;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition
{
internal class CustomDrawVisual<TData> : CompositionContainerVisual where TData : IEquatable<TData>
{
private readonly ICustomDrawVisualHitTest<TData>? _hitTest;
internal CustomDrawVisual(Compositor compositor, ICustomDrawVisualRenderer<TData> renderer,
ICustomDrawVisualHitTest<TData>? hitTest) : base(compositor,
new ServerCustomDrawVisual<TData>(compositor.Server, renderer))
{
_hitTest = hitTest;
}
private TData? _data;
static bool Eq(TData? left, TData? right)
{
if (left == null && right == null)
return true;
if (left == null)
return false;
return left.Equals(right);
}
public TData? Data
{
get => _data;
set
{
if (!Eq(_data, value))
{
((CustomDrawVisualChanges<TData?>) Changes).Data.Value = value;
_data = value;
}
}
}
private protected override IChangeSetPool ChangeSetPool => CustomDrawVisualChanges<TData>.Pool;
}
public interface ICustomDrawVisualRenderer<TData>
{
void Render(IDrawingContextImpl canvas, TData? data);
}
public interface ICustomDrawVisualHitTest<TData>
{
bool HitTest(TData data, Vector2 vector2);
}
}

81
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs

@ -0,0 +1,81 @@
using System;
using Avalonia.Collections.Pooled;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Drawing;
internal class CompositionDrawList : PooledList<IRef<IDrawOperation>>
{
public CompositionDrawList()
{
}
public CompositionDrawList(int capacity) : base(capacity)
{
}
public override void Dispose()
{
foreach(var item in this)
item.Dispose();
base.Dispose();
}
public CompositionDrawList Clone()
{
var clone = new CompositionDrawList(Count);
foreach (var r in this)
clone.Add(r.Clone());
return clone;
}
}
internal class CompositionDrawListBuilder
{
private CompositionDrawList? _operations;
private bool _owns;
public void Reset(CompositionDrawList? previousOperations)
{
_operations = previousOperations;
_owns = false;
}
public CompositionDrawList DrawOperations => _operations ?? new CompositionDrawList();
void MakeWritable(int atIndex)
{
if(_owns)
return;
_owns = true;
var newOps = new CompositionDrawList(_operations?.Count ?? Math.Max(1, atIndex));
if (_operations != null)
{
for (var c = 0; c < atIndex; c++)
newOps.Add(_operations[c].Clone());
}
_operations = newOps;
}
public void ReplaceDrawOperation(int index, IDrawOperation node)
{
MakeWritable(index);
DrawOperations.Add(RefCountable.Create(node));
}
public void AddDrawOperation(IDrawOperation node)
{
MakeWritable(DrawOperations.Count);
DrawOperations.Add(RefCountable.Create(node));
}
public void TrimTo(int count)
{
if (count < DrawOperations.Count)
DrawOperations.RemoveRange(count, DrawOperations.Count - count);
}
}

383
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs

@ -0,0 +1,383 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.Composition;
internal class CompositionDrawingContext : IDrawingContextImpl
{
private CompositionDrawListBuilder _builder = new();
private int _drawOperationIndex;
/// <inheritdoc/>
public Matrix Transform { get; set; } = Matrix.Identity;
/// <inheritdoc/>
public void Clear(Color color)
{
// Cannot clear a deferred scene.
}
/// <inheritdoc/>
public void Dispose()
{
// Nothing to do here since we allocate no unmanaged resources.
}
public void BeginUpdate(CompositionDrawList list)
{
_builder.Reset(list);
_drawOperationIndex = 0;
}
public CompositionDrawList EndUpdate()
{
_builder.TrimTo(_drawOperationIndex);
return _builder.DrawOperations!;
}
/// <inheritdoc/>
public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
{
var next = NextDrawAs<GeometryNode>();
if (next == null || !next.Item.Equals(Transform, brush, pen, geometry))
{
Add(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush)));
}
else
{
++_drawOperationIndex;
}
}
/// <inheritdoc/>
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode)
{
var next = NextDrawAs<ImageNode>();
if (next == null ||
!next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode))
{
Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode));
}
else
{
++_drawOperationIndex;
}
}
/// <inheritdoc/>
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect sourceRect)
{
// This method is currently only used to composite layers so shouldn't be called here.
throw new NotSupportedException();
}
/// <inheritdoc/>
public void DrawLine(IPen pen, Point p1, Point p2)
{
var next = NextDrawAs<LineNode>();
if (next == null || !next.Item.Equals(Transform, pen, p1, p2))
{
Add(new LineNode(Transform, pen, p1, p2, CreateChildScene(pen.Brush)));
}
else
{
++_drawOperationIndex;
}
}
/// <inheritdoc/>
public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect,
BoxShadows boxShadows = default)
{
var next = NextDrawAs<RectangleNode>();
if (next == null || !next.Item.Equals(Transform, brush, pen, rect, boxShadows))
{
Add(new RectangleNode(Transform, brush, pen, rect, boxShadows, CreateChildScene(brush)));
}
else
{
++_drawOperationIndex;
}
}
/// <inheritdoc/>
public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect)
{
var next = NextDrawAs<ExperimentalAcrylicNode>();
if (next == null || !next.Item.Equals(Transform, material, rect))
{
Add(new ExperimentalAcrylicNode(Transform, material, rect));
}
else
{
++_drawOperationIndex;
}
}
public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
{
var next = NextDrawAs<EllipseNode>();
if (next == null || !next.Item.Equals(Transform, brush, pen, rect))
{
Add(new EllipseNode(Transform, brush, pen, rect, CreateChildScene(brush)));
}
else
{
++_drawOperationIndex;
}
}
public void Custom(ICustomDrawOperation custom)
{
var next = NextDrawAs<CustomDrawOperation>();
if (next == null || !next.Item.Equals(Transform, custom))
Add(new CustomDrawOperation(custom, Transform));
else
++_drawOperationIndex;
}
/// <inheritdoc/>
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
var next = NextDrawAs<GlyphRunNode>();
if (next == null || !next.Item.Equals(Transform, foreground, glyphRun))
{
Add(new GlyphRunNode(Transform, foreground, glyphRun, CreateChildScene(foreground)));
}
else
{
++_drawOperationIndex;
}
}
public IDrawingContextLayerImpl CreateLayer(Size size)
{
throw new NotSupportedException("Creating layers on a deferred drawing context not supported");
}
/// <inheritdoc/>
public void PopClip()
{
var next = NextDrawAs<ClipNode>();
if (next == null || !next.Item.Equals(null))
{
Add(new ClipNode());
}
else
{
++_drawOperationIndex;
}
}
/// <inheritdoc/>
public void PopGeometryClip()
{
var next = NextDrawAs<GeometryClipNode>();
if (next == null || !next.Item.Equals(null))
{
Add(new GeometryClipNode());
}
else
{
++_drawOperationIndex;
}
}
/// <inheritdoc/>
public void PopBitmapBlendMode()
{
var next = NextDrawAs<BitmapBlendModeNode>();
if (next == null || !next.Item.Equals(null))
{
Add(new BitmapBlendModeNode());
}
else
{
++_drawOperationIndex;
}
}
/// <inheritdoc/>
public void PopOpacity()
{
var next = NextDrawAs<OpacityNode>();
if (next == null || !next.Item.Equals(null))
{
Add(new OpacityNode());
}
else
{
++_drawOperationIndex;
}
}
/// <inheritdoc/>
public void PopOpacityMask()
{
var next = NextDrawAs<OpacityMaskNode>();
if (next == null || !next.Item.Equals(null, null))
{
Add(new OpacityMaskNode());
}
else
{
++_drawOperationIndex;
}
}
/// <inheritdoc/>
public void PushClip(Rect clip)
{
var next = NextDrawAs<ClipNode>();
if (next == null || !next.Item.Equals(Transform, clip))
{
Add(new ClipNode(Transform, clip));
}
else
{
++_drawOperationIndex;
}
}
/// <inheritdoc />
public void PushClip(RoundedRect clip)
{
var next = NextDrawAs<ClipNode>();
if (next == null || !next.Item.Equals(Transform, clip))
{
Add(new ClipNode(Transform, clip));
}
else
{
++_drawOperationIndex;
}
}
/// <inheritdoc/>
public void PushGeometryClip(IGeometryImpl? clip)
{
if (clip is null)
return;
var next = NextDrawAs<GeometryClipNode>();
if (next == null || !next.Item.Equals(Transform, clip))
{
Add(new GeometryClipNode(Transform, clip));
}
else
{
++_drawOperationIndex;
}
}
/// <inheritdoc/>
public void PushOpacity(double opacity)
{
var next = NextDrawAs<OpacityNode>();
if (next == null || !next.Item.Equals(opacity))
{
Add(new OpacityNode(opacity));
}
else
{
++_drawOperationIndex;
}
}
/// <inheritdoc/>
public void PushOpacityMask(IBrush mask, Rect bounds)
{
var next = NextDrawAs<OpacityMaskNode>();
if (next == null || !next.Item.Equals(mask, bounds))
{
Add(new OpacityMaskNode(mask, bounds, CreateChildScene(mask)));
}
else
{
++_drawOperationIndex;
}
}
/// <inheritdoc/>
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
var next = NextDrawAs<BitmapBlendModeNode>();
if (next == null || !next.Item.Equals(blendingMode))
{
Add(new BitmapBlendModeNode(blendingMode));
}
else
{
++_drawOperationIndex;
}
}
private void Add<T>(T node) where T : class, IDrawOperation
{
if (_drawOperationIndex < _builder!.DrawOperations.Count)
{
_builder.ReplaceDrawOperation(_drawOperationIndex, node);
}
else
{
_builder.AddDrawOperation(node);
}
++_drawOperationIndex;
}
private IRef<T>? NextDrawAs<T>() where T : class, IDrawOperation
{
return _drawOperationIndex < _builder!.DrawOperations.Count
? _builder.DrawOperations[_drawOperationIndex] as IRef<T>
: null;
}
private IDictionary<IVisual, Scene>? CreateChildScene(IBrush? brush)
{
/*
var visualBrush = brush as VisualBrush;
if (visualBrush != null)
{
var visual = visualBrush.Visual;
if (visual != null)
{
(visual as IVisualBrushInitialize)?.EnsureInitialized();
var scene = new Scene(visual);
_sceneBuilder.UpdateAll(scene);
return new Dictionary<IVisual, Scene> { { visualBrush.Visual, scene } };
}
}*/
return null;
}
}

120
src/Avalonia.Base/Rendering/Composition/Enums.cs

@ -0,0 +1,120 @@
using System;
namespace Avalonia.Rendering.Composition
{
public enum CompositionBlendMode
{
/// <summary>No regions are enabled. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_clr.svg)</summary>
Clear,
/// <summary>Only the source will be present. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src.svg)</summary>
Src,
/// <summary>Only the destination will be present. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst.svg)</summary>
Dst,
/// <summary>Source is placed over the destination. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src-over.svg)</summary>
SrcOver,
/// <summary>Destination is placed over the source. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst-over.svg)</summary>
DstOver,
/// <summary>The source that overlaps the destination, replaces the destination. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src-in.svg)</summary>
SrcIn,
/// <summary>Destination which overlaps the source, replaces the source. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst-in.svg)</summary>
DstIn,
/// <summary>Source is placed, where it falls outside of the destination. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src-out.svg)</summary>
SrcOut,
/// <summary>Destination is placed, where it falls outside of the source. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst-out.svg)</summary>
DstOut,
/// <summary>Source which overlaps the destination, replaces the destination. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src-atop.svg)</summary>
SrcATop,
/// <summary>Destination which overlaps the source replaces the source. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst-atop.svg)</summary>
DstATop,
/// <summary>The non-overlapping regions of source and destination are combined. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_xor.svg)</summary>
Xor,
/// <summary>Display the sum of the source image and destination image. [Porter Duff Compositing Operators]</summary>
Plus,
/// <summary>Multiplies all components (= alpha and color). [Separable Blend Modes]</summary>
Modulate,
/// <summary>Multiplies the complements of the backdrop and source CompositionColorvalues, then complements the result. [Separable Blend Modes]</summary>
Screen,
/// <summary>Multiplies or screens the colors, depending on the backdrop CompositionColorvalue. [Separable Blend Modes]</summary>
Overlay,
/// <summary>Selects the darker of the backdrop and source colors. [Separable Blend Modes]</summary>
Darken,
/// <summary>Selects the lighter of the backdrop and source colors. [Separable Blend Modes]</summary>
Lighten,
/// <summary>Brightens the backdrop CompositionColorto reflect the source color. [Separable Blend Modes]</summary>
ColorDodge,
/// <summary>Darkens the backdrop CompositionColorto reflect the source color. [Separable Blend Modes]</summary>
ColorBurn,
/// <summary>Multiplies or screens the colors, depending on the source CompositionColorvalue. [Separable Blend Modes]</summary>
HardLight,
/// <summary>Darkens or lightens the colors, depending on the source CompositionColorvalue. [Separable Blend Modes]</summary>
SoftLight,
/// <summary>Subtracts the darker of the two constituent colors from the lighter color. [Separable Blend Modes]</summary>
Difference,
/// <summary>Produces an effect similar to that of the Difference mode but lower in contrast. [Separable Blend Modes]</summary>
Exclusion,
/// <summary>The source CompositionColoris multiplied by the destination CompositionColorand replaces the destination [Separable Blend Modes]</summary>
Multiply,
/// <summary>Creates a CompositionColorwith the hue of the source CompositionColorand the saturation and luminosity of the backdrop color. [Non-Separable Blend Modes]</summary>
Hue,
/// <summary>Creates a CompositionColorwith the saturation of the source CompositionColorand the hue and luminosity of the backdrop color. [Non-Separable Blend Modes]</summary>
Saturation,
/// <summary>Creates a CompositionColorwith the hue and saturation of the source CompositionColorand the luminosity of the backdrop color. [Non-Separable Blend Modes]</summary>
Color,
/// <summary>Creates a CompositionColorwith the luminosity of the source CompositionColorand the hue and saturation of the backdrop color. [Non-Separable Blend Modes]</summary>
Luminosity,
}
public enum CompositionGradientExtendMode
{
Clamp,
Wrap,
Mirror
}
[Flags]
public enum CompositionTileMode
{
None = 0,
TileX = 1,
TileY = 2,
FlipX = 4,
FlipY = 8,
Tile = TileX | TileY,
Flip = FlipX | FlipY
}
public enum CompositionStretch
{
None = 0,
Fill = 1,
//TODO: Uniform, UniformToFill
}
}

234
src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs

@ -0,0 +1,234 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Utils;
namespace Avalonia.Rendering.Composition.Expressions
{
internal class BuiltInExpressionFfi : IExpressionForeignFunctionInterface
{
private readonly DelegateExpressionFfi _registry;
static float Lerp(float a, float b, float p) => p * (b - a) + a;
static Matrix3x2 Inverse(Matrix3x2 m)
{
Matrix3x2.Invert(m, out var r);
return r;
}
static Matrix4x4 Inverse(Matrix4x4 m)
{
Matrix4x4.Invert(m, out var r);
return r;
}
static float SmoothStep(float edge0, float edge1, float x)
{
var t = MathExt.Clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
return t * t * (3.0f - 2.0f * t);
}
static Vector2 SmoothStep(Vector2 edge0, Vector2 edge1, Vector2 x)
{
return new Vector2(
SmoothStep(edge0.X, edge1.X, x.X),
SmoothStep(edge0.Y, edge1.Y, x.Y)
);
}
static Vector3 SmoothStep(Vector3 edge0, Vector3 edge1, Vector3 x)
{
return new Vector3(
SmoothStep(edge0.X, edge1.X, x.X),
SmoothStep(edge0.Y, edge1.Y, x.Y),
SmoothStep(edge0.Z, edge1.Z, x.Z)
);
}
static Vector4 SmoothStep(Vector4 edge0, Vector4 edge1, Vector4 x)
{
return new Vector4(
SmoothStep(edge0.X, edge1.X, x.X),
SmoothStep(edge0.Y, edge1.Y, x.Y),
SmoothStep(edge0.Z, edge1.Z, x.Z),
SmoothStep(edge0.W, edge1.W, x.W)
);
}
private BuiltInExpressionFfi()
{
_registry = new DelegateExpressionFfi
{
{"Abs", (float f) => Math.Abs(f)},
{"Abs", (Vector2 v) => Vector2.Abs(v)},
{"Abs", (Vector3 v) => Vector3.Abs(v)},
{"Abs", (Vector4 v) => Vector4.Abs(v)},
{"ACos", (float f) => (float) Math.Acos(f)},
{"ASin", (float f) => (float) Math.Asin(f)},
{"ATan", (float f) => (float) Math.Atan(f)},
{"Ceil", (float f) => (float) Math.Ceiling(f)},
{"Clamp", (float a1, float a2, float a3) => MathExt.Clamp(a1, a2, a3)},
{"Clamp", (Vector2 a1, Vector2 a2, Vector2 a3) => Vector2.Clamp(a1, a2, a3)},
{"Clamp", (Vector3 a1, Vector3 a2, Vector3 a3) => Vector3.Clamp(a1, a2, a3)},
{"Clamp", (Vector4 a1, Vector4 a2, Vector4 a3) => Vector4.Clamp(a1, a2, a3)},
{"Concatenate", (Quaternion a1, Quaternion a2) => Quaternion.Concatenate(a1, a2)},
{"Cos", (float a) => (float) Math.Cos(a)},
/*
TODO:
ColorHsl(Float h, Float s, Float l)
ColorLerpHSL(Color colorTo, CompositionColorcolorFrom, Float progress)
*/
{
"ColorLerp", (Avalonia.Media.Color to, Avalonia.Media.Color from, float progress) =>
ColorInterpolator.LerpRGB(to, from, progress)
},
{
"ColorLerpRGB", (Avalonia.Media.Color to, Avalonia.Media.Color from, float progress) =>
ColorInterpolator.LerpRGB(to, from, progress)
},
{
"ColorRGB", (float a, float r, float g, float b) => Avalonia.Media.Color.FromArgb(
(byte) MathExt.Clamp(a, 0, 255),
(byte) MathExt.Clamp(r, 0, 255),
(byte) MathExt.Clamp(g, 0, 255),
(byte) MathExt.Clamp(b, 0, 255)
)
},
{"Distance", (Vector2 a1, Vector2 a2) => Vector2.Distance(a1, a2)},
{"Distance", (Vector3 a1, Vector3 a2) => Vector3.Distance(a1, a2)},
{"Distance", (Vector4 a1, Vector4 a2) => Vector4.Distance(a1, a2)},
{"DistanceSquared", (Vector2 a1, Vector2 a2) => Vector2.DistanceSquared(a1, a2)},
{"DistanceSquared", (Vector3 a1, Vector3 a2) => Vector3.DistanceSquared(a1, a2)},
{"DistanceSquared", (Vector4 a1, Vector4 a2) => Vector4.DistanceSquared(a1, a2)},
{"Floor", (float v) => (float) Math.Floor(v)},
{"Inverse", (Matrix3x2 v) => Inverse(v)},
{"Inverse", (Matrix4x4 v) => Inverse(v)},
{"Length", (Vector2 a1) => a1.Length()},
{"Length", (Vector3 a1) => a1.Length()},
{"Length", (Vector4 a1) => a1.Length()},
{"Length", (Quaternion a1) => a1.Length()},
{"LengthSquared", (Vector2 a1) => a1.LengthSquared()},
{"LengthSquared", (Vector3 a1) => a1.LengthSquared()},
{"LengthSquared", (Vector4 a1) => a1.LengthSquared()},
{"LengthSquared", (Quaternion a1) => a1.LengthSquared()},
{"Lerp", (float a1, float a2, float a3) => Lerp(a1, a2, a3)},
{"Lerp", (Vector2 a1, Vector2 a2, float a3) => Vector2.Lerp(a1, a2, a3)},
{"Lerp", (Vector3 a1, Vector3 a2, float a3) => Vector3.Lerp(a1, a2, a3)},
{"Lerp", (Vector4 a1, Vector4 a2, float a3) => Vector4.Lerp(a1, a2, a3)},
{"Ln", (float f) => (float) Math.Log(f)},
{"Log10", (float f) => (float) Math.Log10(f)},
{"Matrix3x2.CreateFromScale", (Vector2 v) => Matrix3x2.CreateScale(v)},
{"Matrix3x2.CreateFromTranslation", (Vector2 v) => Matrix3x2.CreateTranslation(v)},
{"Matrix3x2.CreateRotation", (float v) => Matrix3x2.CreateRotation(v)},
{"Matrix3x2.CreateScale", (Vector2 v) => Matrix3x2.CreateScale(v)},
{"Matrix3x2.CreateSkew", (float a1, float a2, Vector2 a3) => Matrix3x2.CreateSkew(a1, a2, a3)},
{"Matrix3x2.CreateTranslation", (Vector2 v) => Matrix3x2.CreateScale(v)},
{
"Matrix3x2", (float m11, float m12, float m21, float m22, float m31, float m32) =>
new Matrix3x2(m11, m12, m21, m22, m31, m32)
},
{"Matrix4x4.CreateFromAxisAngle", (Vector3 v, float angle) => Matrix4x4.CreateFromAxisAngle(v, angle)},
{"Matrix4x4.CreateFromScale", (Vector3 v) => Matrix4x4.CreateScale(v)},
{"Matrix4x4.CreateFromTranslation", (Vector3 v) => Matrix4x4.CreateTranslation(v)},
{"Matrix4x4.CreateScale", (Vector3 v) => Matrix4x4.CreateScale(v)},
{"Matrix4x4.CreateTranslation", (Vector3 v) => Matrix4x4.CreateScale(v)},
{"Matrix4x4", (Matrix3x2 m) => new Matrix4x4(m)},
{
"Matrix4x4",
(float m11, float m12, float m13, float m14,
float m21, float m22, float m23, float m24,
float m31, float m32, float m33, float m34,
float m41, float m42, float m43, float m44) =>
new Matrix4x4(
m11, m12, m13, m14,
m21, m22, m23, m24,
m31, m32, m33, m34,
m41, m42, m43, m44)
},
{"Max", (float a1, float a2) => Math.Max(a1, a2)},
{"Max", (Vector2 a1, Vector2 a2) => Vector2.Max(a1, a2)},
{"Max", (Vector3 a1, Vector3 a2) => Vector3.Max(a1, a2)},
{"Max", (Vector4 a1, Vector4 a2) => Vector4.Max(a1, a2)},
{"Min", (float a1, float a2) => Math.Min(a1, a2)},
{"Min", (Vector2 a1, Vector2 a2) => Vector2.Min(a1, a2)},
{"Min", (Vector3 a1, Vector3 a2) => Vector3.Min(a1, a2)},
{"Min", (Vector4 a1, Vector4 a2) => Vector4.Min(a1, a2)},
{"Mod", (float a, float b) => a % b},
{"Normalize", (Quaternion a) => Quaternion.Normalize(a)},
{"Normalize", (Vector2 a) => Vector2.Normalize(a)},
{"Normalize", (Vector3 a) => Vector3.Normalize(a)},
{"Normalize", (Vector4 a) => Vector4.Normalize(a)},
{"Pow", (float a, float b) => (float) Math.Pow(a, b)},
{"Quaternion.CreateFromAxisAngle", (Vector3 a, float b) => Quaternion.CreateFromAxisAngle(a, b)},
{"Quaternion", (float a, float b, float c, float d) => new Quaternion(a, b, c, d)},
{"Round", (float a) => (float) Math.Round(a)},
{"Scale", (Matrix3x2 a, float b) => a * b},
{"Scale", (Matrix4x4 a, float b) => a * b},
{"Scale", (Vector2 a, float b) => a * b},
{"Scale", (Vector3 a, float b) => a * b},
{"Scale", (Vector4 a, float b) => a * b},
{"Sin", (float a) => (float) Math.Sin(a)},
{"SmoothStep", (float a1, float a2, float a3) => SmoothStep(a1, a2, a3)},
{"SmoothStep", (Vector2 a1, Vector2 a2, Vector2 a3) => SmoothStep(a1, a2, a3)},
{"SmoothStep", (Vector3 a1, Vector3 a2, Vector3 a3) => SmoothStep(a1, a2, a3)},
{"SmoothStep", (Vector4 a1, Vector4 a2, Vector4 a3) => SmoothStep(a1, a2, a3)},
// I have no idea how to do a spherical interpolation for a scalar value, so we are doing a linear one
{"Slerp", (float a1, float a2, float a3) => Lerp(a1, a2, a3)},
{"Slerp", (Quaternion a1, Quaternion a2, float a3) => Quaternion.Slerp(a1, a2, a3)},
{"Sqrt", (float a) => (float) Math.Sqrt(a)},
{"Square", (float a) => a * a},
{"Tan", (float a) => (float) Math.Tan(a)},
{"ToRadians", (float a) => (float) (a * Math.PI / 180)},
{"ToDegrees", (float a) => (float) (a * 180d / Math.PI)},
{"Transform", (Vector2 a, Matrix3x2 b) => Vector2.Transform(a, b)},
{"Transform", (Vector3 a, Matrix4x4 b) => Vector3.Transform(a, b)},
{"Vector2", (float a, float b) => new Vector2(a, b)},
{"Vector3", (float a, float b, float c) => new Vector3(a, b, c)},
{"Vector3", (Vector2 v2, float z) => new Vector3(v2, z)},
{"Vector4", (float a, float b, float c, float d) => new Vector4(a, b, c, d)},
{"Vector4", (Vector2 v2, float z, float w) => new Vector4(v2, z, w)},
{"Vector4", (Vector3 v3, float w) => new Vector4(v3, w)},
};
}
public bool Call(string name, IReadOnlyList<ExpressionVariant> arguments, out ExpressionVariant result) =>
_registry.Call(name, arguments, out result);
public static BuiltInExpressionFfi Instance { get; } = new BuiltInExpressionFfi();
}
}

181
src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs

@ -0,0 +1,181 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Avalonia.Media;
namespace Avalonia.Rendering.Composition.Expressions
{
internal class DelegateExpressionFfi : IExpressionForeignFunctionInterface, IEnumerable
{
struct FfiRecord
{
public VariantType[] Types;
public Func<IReadOnlyList<ExpressionVariant>, ExpressionVariant> Delegate;
}
private readonly Dictionary<string, Dictionary<int, List<FfiRecord>>>
_registry = new Dictionary<string, Dictionary<int, List<FfiRecord>>>();
public bool Call(string name, IReadOnlyList<ExpressionVariant> arguments, out ExpressionVariant result)
{
result = default;
if (!_registry.TryGetValue(name, out var nameGroup))
return false;
if (!nameGroup.TryGetValue(arguments.Count, out var countGroup))
return false;
foreach (var record in countGroup)
{
var match = true;
for (var c = 0; c < arguments.Count; c++)
{
if (record.Types[c] != arguments[c].Type)
{
match = false;
break;
}
}
if (match)
{
result = record.Delegate(arguments);
return true;
}
}
return false;
}
// Stub for collection initializer
IEnumerator IEnumerable.GetEnumerator() => Array.Empty<object>().GetEnumerator();
void Add(string name, Func<IReadOnlyList<ExpressionVariant>, ExpressionVariant> cb,
params Type[] types)
{
if (!_registry.TryGetValue(name, out var nameGroup))
_registry[name] = nameGroup =
new Dictionary<int, List<FfiRecord>>();
if (!nameGroup.TryGetValue(types.Length, out var countGroup))
nameGroup[types.Length] = countGroup = new List<FfiRecord>();
countGroup.Add(new FfiRecord
{
Types = types.Select(t => TypeMap[t]).ToArray(),
Delegate = cb
});
}
static readonly Dictionary<Type, VariantType> TypeMap = new Dictionary<Type, VariantType>
{
[typeof(bool)] = VariantType.Boolean,
[typeof(float)] = VariantType.Scalar,
[typeof(Vector2)] = VariantType.Vector2,
[typeof(Vector3)] = VariantType.Vector3,
[typeof(Vector4)] = VariantType.Vector4,
[typeof(Matrix3x2)] = VariantType.Matrix3x2,
[typeof(Matrix4x4)] = VariantType.Matrix4x4,
[typeof(Quaternion)] = VariantType.Quaternion,
[typeof(Color)] = VariantType.Color
};
public void Add<T1>(string name, Func<T1, ExpressionVariant> cb) where T1 : struct
{
Add(name, args => cb(args[0].CastOrDefault<T1>()), typeof(T1));
}
public void Add<T1, T2>(string name, Func<T1, T2, ExpressionVariant> cb) where T1 : struct where T2 : struct
{
Add(name, args => cb(args[0].CastOrDefault<T1>(), args[1].CastOrDefault<T2>()), typeof(T1), typeof(T2));
}
public void Add<T1, T2, T3>(string name, Func<T1, T2, T3, ExpressionVariant> cb)
where T1 : struct where T2 : struct where T3 : struct
{
Add(name, args => cb(args[0].CastOrDefault<T1>(), args[1].CastOrDefault<T2>(), args[2].CastOrDefault<T3>()), typeof(T1), typeof(T2),
typeof(T3));
}
public void Add<T1, T2, T3, T4>(string name, Func<T1, T2, T3, T4, ExpressionVariant> cb)
where T1 : struct where T2 : struct where T3 : struct where T4 : struct
{
Add(name, args => cb(
args[0].CastOrDefault<T1>(),
args[1].CastOrDefault<T2>(),
args[2].CastOrDefault<T3>(),
args[3].CastOrDefault<T4>()),
typeof(T1), typeof(T2), typeof(T3), typeof(T4));
}
public void Add<T1, T2, T3, T4, T5>(string name, Func<T1, T2, T3, T4, T5, ExpressionVariant> cb)
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct
{
Add(name, args => cb(
args[0].CastOrDefault<T1>(),
args[1].CastOrDefault<T2>(),
args[2].CastOrDefault<T3>(),
args[3].CastOrDefault<T4>(),
args[4].CastOrDefault<T5>()),
typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5));
}
public void Add<T1, T2, T3, T4, T5, T6>(string name, Func<T1, T2, T3, T4, T5, T6, ExpressionVariant> cb)
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct where T6 : struct
{
Add(name, args => cb(
args[0].CastOrDefault<T1>(),
args[1].CastOrDefault<T2>(),
args[2].CastOrDefault<T3>(),
args[3].CastOrDefault<T4>(),
args[4].CastOrDefault<T5>(),
args[4].CastOrDefault<T6>()),
typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6));
}
public void Add<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>(string name,
Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, ExpressionVariant> cb)
where T1 : struct
where T2 : struct
where T3 : struct
where T4 : struct
where T5 : struct
where T6 : struct
where T7 : struct
where T8 : struct
where T9 : struct
where T10 : struct
where T11 : struct
where T12 : struct
where T13 : struct
where T14 : struct
where T15 : struct
where T16 : struct
{
Add(name, args => cb(
args[0].CastOrDefault<T1>(),
args[1].CastOrDefault<T2>(),
args[2].CastOrDefault<T3>(),
args[3].CastOrDefault<T4>(),
args[4].CastOrDefault<T5>(),
args[4].CastOrDefault<T6>(),
args[4].CastOrDefault<T7>(),
args[4].CastOrDefault<T8>(),
args[4].CastOrDefault<T9>(),
args[4].CastOrDefault<T10>(),
args[4].CastOrDefault<T11>(),
args[4].CastOrDefault<T12>(),
args[4].CastOrDefault<T13>(),
args[4].CastOrDefault<T14>(),
args[4].CastOrDefault<T15>(),
args[4].CastOrDefault<T16>()
),
typeof(T1), typeof(T2), typeof(T3), typeof(T4),
typeof(T5), typeof(T6), typeof(T7), typeof(T8),
typeof(T9), typeof(T10), typeof(T11), typeof(T12),
typeof(T13), typeof(T14), typeof(T15), typeof(T16)
);
}
}
}

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

@ -0,0 +1,331 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
namespace Avalonia.Rendering.Composition.Expressions
{
internal abstract class Expression
{
public abstract ExpressionType Type { get; }
public static Expression Parse(string expression)
{
return ExpressionParser.Parse(expression.AsSpan());
}
public abstract ExpressionVariant Evaluate(ref ExpressionEvaluationContext context);
protected abstract string Print();
public override string ToString() => Print();
internal static string OperatorName(ExpressionType t)
{
var attr = typeof(ExpressionType).GetMember(t.ToString())[0]
.GetCustomAttribute<PrettyPrintStringAttribute>();
if (attr != null)
return attr.Name;
return t.ToString();
}
}
internal class PrettyPrintStringAttribute : Attribute
{
public string Name { get; }
public PrettyPrintStringAttribute(string name)
{
Name = name;
}
}
internal enum ExpressionType
{
// Binary operators
[PrettyPrintString("+")]
Add,
[PrettyPrintString("-")]
Subtract,
[PrettyPrintString("/")]
Divide,
[PrettyPrintString("*")]
Multiply,
[PrettyPrintString(">")]
MoreThan,
[PrettyPrintString("<")]
LessThan,
[PrettyPrintString(">=")]
MoreThanOrEqual,
[PrettyPrintString("<=")]
LessThanOrEqual,
[PrettyPrintString("&&")]
LogicalAnd,
[PrettyPrintString("||")]
LogicalOr,
[PrettyPrintString("%")]
Remainder,
[PrettyPrintString("==")]
Equals,
[PrettyPrintString("!=")]
NotEquals,
// Unary operators
[PrettyPrintString("!")]
Not,
[PrettyPrintString("-")]
UnaryMinus,
// The rest
MemberAccess,
Parameter,
FunctionCall,
Keyword,
Constant,
ConditionalExpression
}
internal enum ExpressionKeyword
{
StartingValue,
CurrentValue,
FinalValue,
Target,
Pi,
True,
False
}
internal class ConditionalExpression : Expression
{
public Expression Condition { get; }
public Expression TruePart { get; }
public Expression FalsePart { get; }
public override ExpressionType Type => ExpressionType.ConditionalExpression;
public ConditionalExpression(Expression condition, Expression truePart, Expression falsePart)
{
Condition = condition;
TruePart = truePart;
FalsePart = falsePart;
}
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context)
{
var cond = Condition.Evaluate(ref context);
if (cond.Type == VariantType.Boolean && cond.Boolean)
return TruePart.Evaluate(ref context);
return FalsePart.Evaluate(ref context);
}
protected override string Print() => $"({Condition}) ? ({TruePart}) : ({FalsePart})";
}
internal class ConstantExpression : Expression
{
public float Constant { get; }
public override ExpressionType Type => ExpressionType.Constant;
public ConstantExpression(float constant)
{
Constant = constant;
}
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) => Constant;
protected override string Print() => Constant.ToString(CultureInfo.InvariantCulture);
}
internal class FunctionCallExpression : Expression
{
public string Name { get; }
public List<Expression> Parameters { get; }
public override ExpressionType Type => ExpressionType.FunctionCall;
public FunctionCallExpression(string name, List<Expression> parameters)
{
Name = name;
Parameters = parameters;
}
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context)
{
if (context.ForeignFunctionInterface == null)
return default;
var args = new List<ExpressionVariant>();
foreach (var expr in Parameters)
args.Add(expr.Evaluate(ref context));
if (!context.ForeignFunctionInterface.Call(Name, args, out var res))
return default;
return res;
}
protected override string Print()
{
return Name + "( (" + string.Join("), (", Parameters) + ") )";
}
}
internal class MemberAccessExpression : Expression
{
public override ExpressionType Type => ExpressionType.MemberAccess;
public Expression Target { get; }
public string Member { get; }
public MemberAccessExpression(Expression target, string member)
{
Target = target;
Member = string.Intern(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);
}
return Target.Evaluate(ref context).GetProperty(Member);
}
protected override string Print()
{
return "(" + Target.ToString() + ")." + Member;
}
}
internal class ParameterExpression : Expression
{
public string Name { get; }
public override ExpressionType Type => ExpressionType.Parameter;
public ParameterExpression(string name)
{
Name = name;
}
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context)
{
return context.Parameters?.GetParameter(Name) ?? default;
}
protected override string Print()
{
return "{" + Name + "}";
}
}
internal class KeywordExpression : Expression
{
public override ExpressionType Type => ExpressionType.Keyword;
public ExpressionKeyword Keyword { get; }
public KeywordExpression(ExpressionKeyword keyword)
{
Keyword = keyword;
}
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context)
{
if (Keyword == ExpressionKeyword.StartingValue)
return context.StartingValue;
if (Keyword == ExpressionKeyword.CurrentValue)
return context.CurrentValue;
if (Keyword == ExpressionKeyword.FinalValue)
return context.FinalValue;
if (Keyword == ExpressionKeyword.Target)
// should be handled by MemberAccess
return default;
if (Keyword == ExpressionKeyword.True)
return true;
if (Keyword == ExpressionKeyword.False)
return false;
if (Keyword == ExpressionKeyword.Pi)
return (float) Math.PI;
return default;
}
protected override string Print()
{
return "[" + Keyword + "]";
}
}
internal class UnaryExpression : Expression
{
public Expression Parameter { get; }
public override ExpressionType Type { get; }
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context)
{
if (Type == ExpressionType.Not)
return !Parameter.Evaluate(ref context);
if (Type == ExpressionType.UnaryMinus)
return -Parameter.Evaluate(ref context);
return default;
}
protected override string Print()
{
return OperatorName(Type) + Parameter;
}
public UnaryExpression(Expression parameter, ExpressionType type)
{
Parameter = parameter;
Type = type;
}
}
internal class BinaryExpression : Expression
{
public Expression Left { get; }
public Expression Right { get; }
public override ExpressionType Type { get; }
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context)
{
var left = Left.Evaluate(ref context);
var right = Right.Evaluate(ref context);
if (Type == ExpressionType.Add)
return left + right;
if (Type == ExpressionType.Subtract)
return left - right;
if (Type == ExpressionType.Multiply)
return left * right;
if (Type == ExpressionType.Divide)
return left / right;
if (Type == ExpressionType.Remainder)
return left % right;
if (Type == ExpressionType.MoreThan)
return left > right;
if (Type == ExpressionType.LessThan)
return left < right;
if (Type == ExpressionType.MoreThanOrEqual)
return left > right;
if (Type == ExpressionType.LessThanOrEqual)
return left < right;
if (Type == ExpressionType.LogicalAnd)
return left.And(right);
if (Type == ExpressionType.LogicalOr)
return left.Or(right);
if (Type == ExpressionType.Equals)
return left.EqualsTo(right);
if (Type == ExpressionType.NotEquals)
return left.NotEqualsTo(right);
return default;
}
protected override string Print()
{
return "(" + Left + OperatorName(Type) + Right + ")";
}
public BinaryExpression(Expression left, Expression right, ExpressionType type)
{
Left = left;
Right = right;
Type = type;
}
}
}

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

@ -0,0 +1,31 @@
using System.Collections.Generic;
namespace Avalonia.Rendering.Composition.Expressions
{
internal struct ExpressionEvaluationContext
{
public ExpressionVariant StartingValue { get; set; }
public ExpressionVariant CurrentValue { get; set; }
public ExpressionVariant FinalValue { get; set; }
public IExpressionObject Target { get; set; }
public IExpressionParameterCollection Parameters { get; set; }
public IExpressionForeignFunctionInterface ForeignFunctionInterface { get; set; }
}
internal interface IExpressionObject
{
ExpressionVariant GetProperty(string name);
}
internal interface IExpressionParameterCollection
{
public ExpressionVariant GetParameter(string name);
public IExpressionObject GetObjectParameter(string name);
}
internal interface IExpressionForeignFunctionInterface
{
bool Call(string name, IReadOnlyList<ExpressionVariant> arguments, out ExpressionVariant result);
}
}

14
src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParseException.cs

@ -0,0 +1,14 @@
using System;
namespace Avalonia.Rendering.Composition.Expressions
{
internal class ExpressionParseException : Exception
{
public int Position { get; }
public ExpressionParseException(string message, int position) : base(message)
{
Position = position;
}
}
}

298
src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionParser.cs

@ -0,0 +1,298 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
// ReSharper disable StringLiteralTypo
namespace Avalonia.Rendering.Composition.Expressions
{
internal class ExpressionParser
{
public static Expression Parse(ReadOnlySpan<char> s)
{
var p = new TokenParser(s);
var parsed = ParseTillTerminator(ref p, "", false, false, out _);
p.SkipWhitespace();
if (p.Length != 0)
throw new ExpressionParseException("Unexpected data ", p.Position);
return parsed;
}
private static ReadOnlySpan<char> Dot => ".".AsSpan();
static bool TryParseAtomic(ref TokenParser parser,
[MaybeNullWhen(returnValue: false)] out Expression expr)
{
// We can parse keywords, parameter names and constants
expr = null;
if (parser.TryParseKeywordLowerCase("this.startingvalue"))
expr = new KeywordExpression(ExpressionKeyword.StartingValue);
else if(parser.TryParseKeywordLowerCase("this.currentvalue"))
expr = new KeywordExpression(ExpressionKeyword.CurrentValue);
else if(parser.TryParseKeywordLowerCase("this.finalvalue"))
expr = new KeywordExpression(ExpressionKeyword.FinalValue);
else if(parser.TryParseKeywordLowerCase("pi"))
expr = new KeywordExpression(ExpressionKeyword.Pi);
else if(parser.TryParseKeywordLowerCase("true"))
expr = new KeywordExpression(ExpressionKeyword.True);
else if(parser.TryParseKeywordLowerCase("false"))
expr = new KeywordExpression(ExpressionKeyword.False);
else if (parser.TryParseKeywordLowerCase("this.target"))
expr = new KeywordExpression(ExpressionKeyword.Target);
if (expr != null)
return true;
if (parser.TryParseIdentifier(out var identifier))
{
expr = new ParameterExpression(identifier.ToString());
return true;
}
if(parser.TryParseFloat(out var scalar))
{
expr = new ConstantExpression(scalar);
return true;
}
return false;
}
static bool TryParseOperator(ref TokenParser parser, out ExpressionType op)
{
op = (ExpressionType) (-1);
if (parser.TryConsume("||"))
op = ExpressionType.LogicalOr;
else if (parser.TryConsume("&&"))
op = ExpressionType.LogicalAnd;
else if (parser.TryConsume(">="))
op = ExpressionType.MoreThanOrEqual;
else if (parser.TryConsume("<="))
op = ExpressionType.LessThanOrEqual;
else if (parser.TryConsume("=="))
op = ExpressionType.Equals;
else if (parser.TryConsume("!="))
op = ExpressionType.NotEquals;
else if (parser.TryConsumeAny("+-/*><%".AsSpan(), out var sop))
{
#pragma warning disable CS8509
op = sop switch
#pragma warning restore CS8509
{
'+' => ExpressionType.Add,
'-' => ExpressionType.Subtract,
'/' => ExpressionType.Divide,
'*' => ExpressionType.Multiply,
'<' => ExpressionType.LessThan,
'>' => ExpressionType.MoreThan,
'%' => ExpressionType.Remainder
};
}
else
return false;
return true;
}
struct ExpressionOperatorGroup
{
private List<Expression> _expressions;
private List<ExpressionType> _operators;
private Expression? _first;
public bool NotEmpty => !Empty;
public bool Empty => _expressions == null && _first == null;
public void AppendFirst(Expression expr)
{
if (NotEmpty)
throw new InvalidOperationException();
_first = expr;
}
public void AppendWithOperator(Expression expr, ExpressionType op)
{
if (_expressions == null)
{
if (_first == null)
throw new InvalidOperationException();
_expressions = new List<Expression>();
_expressions.Add(_first);
_first = null;
_operators = new List<ExpressionType>();
}
_expressions.Add(expr);
_operators.Add(op);
}
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/
private static readonly ExpressionType[][] OperatorPrecedenceGroups = new[]
{
// multiplicative
new[] {ExpressionType.Multiply, ExpressionType.Divide, ExpressionType.Remainder},
// additive
new[] {ExpressionType.Add, ExpressionType.Subtract},
// relational
new[] {ExpressionType.MoreThan, ExpressionType.MoreThanOrEqual, ExpressionType.LessThan, ExpressionType.LessThanOrEqual},
// equality
new[] {ExpressionType.Equals, ExpressionType.NotEquals},
// conditional AND
new[] {ExpressionType.LogicalAnd},
// conditional OR
new[]{ ExpressionType.LogicalOr},
};
private static readonly ExpressionType[][] OperatorPrecedenceGroupsReversed =
OperatorPrecedenceGroups.Reverse().ToArray();
// a*b+c [a,b,c] [*,+], call with (0, 2)
// ToExpression(a*b) + ToExpression(c)
// a+b*c -> ToExpression(a) + ToExpression(b*c)
Expression ToExpression(int from, int to)
{
if (to - from == 0)
return _expressions[from];
if (to - from == 1)
return new BinaryExpression(_expressions[from], _expressions[to], _operators[from]);
foreach (var grp in OperatorPrecedenceGroupsReversed)
{
for (var c = from; c < to; c++)
{
var currentOperator = _operators[c];
foreach(var operatorFromGroup in grp)
if (currentOperator == operatorFromGroup)
{
// We are dividing the expression right here
var left = ToExpression(from, c);
var right = ToExpression(c + 1, to);
return new BinaryExpression(left, right, currentOperator);
}
}
}
// We shouldn't ever get here, if we are, there is something wrong in the code
throw new ExpressionParseException("Expression parsing algorithm bug in ToExpression", 0);
}
public Expression ToExpression()
{
if (_expressions == null)
return _first ?? throw new InvalidOperationException();
return ToExpression(0, _expressions.Count - 1);
}
}
static Expression ParseTillTerminator(ref TokenParser parser, string terminatorChars,
bool throwOnTerminator,
bool throwOnEnd,
out char? token)
{
ExpressionOperatorGroup left = default;
token = null;
while (true)
{
if (parser.TryConsumeAny(terminatorChars.AsSpan(), out var consumedToken))
{
if (throwOnTerminator || left.Empty)
throw new ExpressionParseException($"Unexpected '{token}'", parser.Position - 1);
token = consumedToken;
return left.ToExpression();
}
parser.SkipWhitespace();
if (parser.Length == 0)
{
if (throwOnEnd || left.Empty)
throw new ExpressionParseException("Unexpected end of expression", parser.Position);
return left.ToExpression();
}
ExpressionType? op = null;
if (left.NotEmpty)
{
if (parser.TryConsume('?'))
{
var truePart = ParseTillTerminator(ref parser, ":",
false, true, out _);
// pass through the current parsing rules to consume the rest
var falsePart = ParseTillTerminator(ref parser, terminatorChars, throwOnTerminator, throwOnEnd,
out token);
return new ConditionalExpression(left.ToExpression(), truePart, falsePart);
}
// We expect a binary operator here
if (!TryParseOperator(ref parser, out var sop))
throw new ExpressionParseException("Unexpected token", parser.Position);
op = sop;
}
// We expect an expression to be parsed (either due to expecting a binary operator or parsing the first part
var applyNegation = false;
while (parser.TryConsume('!'))
applyNegation = !applyNegation;
var applyUnaryMinus = false;
while (parser.TryConsume('-'))
applyUnaryMinus = !applyUnaryMinus;
Expression? parsed;
if (parser.TryConsume('('))
parsed = ParseTillTerminator(ref parser, ")", false, true, out _);
else if (parser.TryParseCall(out var functionName))
{
var parameterList = new List<Expression>();
while (true)
{
parameterList.Add(ParseTillTerminator(ref parser, ",)", false, true, out var closingToken));
if (closingToken == ')')
break;
if (closingToken != ',')
throw new ExpressionParseException("Unexpected end of the expression", parser.Position);
}
parsed = new FunctionCallExpression(functionName.ToString(), parameterList);
}
else if (TryParseAtomic(ref parser, out parsed))
{
// do nothing
}
else
throw new ExpressionParseException("Unexpected token", parser.Position);
// Parse any following member accesses
while (parser.TryConsume('.'))
{
if(!parser.TryParseIdentifier(out var memberName))
throw new ExpressionParseException("Unexpected token", parser.Position);
parsed = new MemberAccessExpression(parsed, memberName.ToString());
}
// Apply ! operator
if (applyNegation)
parsed = new UnaryExpression(parsed, ExpressionType.Not);
if (applyUnaryMinus)
{
if(parsed is ConstantExpression constexpr)
parsed = new ConstantExpression(-constexpr.Constant);
else parsed = new UnaryExpression(parsed, ExpressionType.UnaryMinus);
}
if (left.Empty)
left.AppendFirst(parsed);
else
left.AppendWithOperator(parsed, op!.Value);
}
}
}
}

739
src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs

@ -0,0 +1,739 @@
using System;
using System.Globalization;
using System.Numerics;
using System.Runtime.InteropServices;
using Avalonia.Media;
namespace Avalonia.Rendering.Composition.Expressions
{
internal enum VariantType
{
Invalid,
Boolean,
Scalar,
Double,
Vector2,
Vector3,
Vector4,
AvaloniaMatrix,
Matrix3x2,
Matrix4x4,
Quaternion,
Color
}
[StructLayout(LayoutKind.Explicit)]
internal struct ExpressionVariant
{
[FieldOffset(0)] public VariantType Type;
[FieldOffset(4)] public bool Boolean;
[FieldOffset(4)] public float Scalar;
[FieldOffset(4)] public double Double;
[FieldOffset(4)] public Vector2 Vector2;
[FieldOffset(4)] public Vector3 Vector3;
[FieldOffset(4)] public Vector4 Vector4;
[FieldOffset(4)] public Matrix AvaloniaMatrix;
[FieldOffset(4)] public Matrix3x2 Matrix3x2;
[FieldOffset(4)] public Matrix4x4 Matrix4x4;
[FieldOffset(4)] public Quaternion Quaternion;
[FieldOffset(4)] public Color Color;
public ExpressionVariant GetProperty(string property)
{
if (Type == VariantType.Vector2)
{
if (ReferenceEquals(property, "X"))
return Vector2.X;
if (ReferenceEquals(property, "Y"))
return Vector2.Y;
return default;
}
if (Type == VariantType.Vector3)
{
if (ReferenceEquals(property, "X"))
return Vector3.X;
if (ReferenceEquals(property, "Y"))
return Vector3.Y;
if (ReferenceEquals(property, "Z"))
return Vector3.Z;
if(ReferenceEquals(property, "XY"))
return new Vector2(Vector3.X, Vector3.Y);
if(ReferenceEquals(property, "YX"))
return new Vector2(Vector3.Y, Vector3.X);
if(ReferenceEquals(property, "XZ"))
return new Vector2(Vector3.X, Vector3.Z);
if(ReferenceEquals(property, "ZX"))
return new Vector2(Vector3.Z, Vector3.X);
if(ReferenceEquals(property, "YZ"))
return new Vector2(Vector3.Y, Vector3.Z);
if(ReferenceEquals(property, "ZY"))
return new Vector2(Vector3.Z, Vector3.Y);
return default;
}
if (Type == VariantType.Vector4)
{
if (ReferenceEquals(property, "X"))
return Vector4.X;
if (ReferenceEquals(property, "Y"))
return Vector4.Y;
if (ReferenceEquals(property, "Z"))
return Vector4.Z;
if (ReferenceEquals(property, "W"))
return Vector4.W;
return default;
}
if (Type == VariantType.Matrix3x2)
{
if (ReferenceEquals(property, "M11"))
return Matrix3x2.M11;
if (ReferenceEquals(property, "M12"))
return Matrix3x2.M12;
if (ReferenceEquals(property, "M21"))
return Matrix3x2.M21;
if (ReferenceEquals(property, "M22"))
return Matrix3x2.M22;
if (ReferenceEquals(property, "M31"))
return Matrix3x2.M31;
if (ReferenceEquals(property, "M32"))
return Matrix3x2.M32;
return default;
}
if (Type == VariantType.AvaloniaMatrix)
{
if (ReferenceEquals(property, "M11"))
return AvaloniaMatrix.M11;
if (ReferenceEquals(property, "M12"))
return AvaloniaMatrix.M12;
if (ReferenceEquals(property, "M21"))
return AvaloniaMatrix.M21;
if (ReferenceEquals(property, "M22"))
return AvaloniaMatrix.M22;
if (ReferenceEquals(property, "M31"))
return AvaloniaMatrix.M31;
if (ReferenceEquals(property, "M32"))
return AvaloniaMatrix.M32;
return default;
}
if (Type == VariantType.Matrix4x4)
{
if (ReferenceEquals(property, "M11"))
return Matrix4x4.M11;
if (ReferenceEquals(property, "M12"))
return Matrix4x4.M12;
if (ReferenceEquals(property, "M13"))
return Matrix4x4.M13;
if (ReferenceEquals(property, "M14"))
return Matrix4x4.M14;
if (ReferenceEquals(property, "M21"))
return Matrix4x4.M21;
if (ReferenceEquals(property, "M22"))
return Matrix4x4.M22;
if (ReferenceEquals(property, "M23"))
return Matrix4x4.M23;
if (ReferenceEquals(property, "M24"))
return Matrix4x4.M24;
if (ReferenceEquals(property, "M31"))
return Matrix4x4.M31;
if (ReferenceEquals(property, "M32"))
return Matrix4x4.M32;
if (ReferenceEquals(property, "M33"))
return Matrix4x4.M33;
if (ReferenceEquals(property, "M34"))
return Matrix4x4.M34;
if (ReferenceEquals(property, "M41"))
return Matrix4x4.M41;
if (ReferenceEquals(property, "M42"))
return Matrix4x4.M42;
if (ReferenceEquals(property, "M43"))
return Matrix4x4.M43;
if (ReferenceEquals(property, "M44"))
return Matrix4x4.M44;
return default;
}
if (Type == VariantType.Quaternion)
{
if (ReferenceEquals(property, "X"))
return Quaternion.X;
if (ReferenceEquals(property, "Y"))
return Quaternion.Y;
if (ReferenceEquals(property, "Z"))
return Quaternion.Z;
if (ReferenceEquals(property, "W"))
return Quaternion.W;
return default;
}
if (Type == VariantType.Color)
{
if (ReferenceEquals(property, "A"))
return Color.A;
if (ReferenceEquals(property, "R"))
return Color.R;
if (ReferenceEquals(property, "G"))
return Color.G;
if (ReferenceEquals(property, "B"))
return Color.B;
return default;
}
return default;
}
public static implicit operator ExpressionVariant(bool value) =>
new ExpressionVariant
{
Type = VariantType.Boolean,
Boolean = value
};
public static implicit operator ExpressionVariant(float scalar) =>
new ExpressionVariant
{
Type = VariantType.Scalar,
Scalar = scalar
};
public static implicit operator ExpressionVariant(double d) =>
new ExpressionVariant
{
Type = VariantType.Double,
Double = d
};
public static implicit operator ExpressionVariant(Vector2 value) =>
new ExpressionVariant
{
Type = VariantType.Vector2,
Vector2 = value
};
public static implicit operator ExpressionVariant(Vector3 value) =>
new ExpressionVariant
{
Type = VariantType.Vector3,
Vector3 = value
};
public static implicit operator ExpressionVariant(Vector4 value) =>
new ExpressionVariant
{
Type = VariantType.Vector4,
Vector4 = value
};
public static implicit operator ExpressionVariant(Matrix3x2 value) =>
new ExpressionVariant
{
Type = VariantType.Matrix3x2,
Matrix3x2 = value
};
public static implicit operator ExpressionVariant(Matrix value) =>
new ExpressionVariant
{
Type = VariantType.Matrix3x2,
AvaloniaMatrix = value
};
public static implicit operator ExpressionVariant(Matrix4x4 value) =>
new ExpressionVariant
{
Type = VariantType.Matrix4x4,
Matrix4x4 = value
};
public static implicit operator ExpressionVariant(Quaternion value) =>
new ExpressionVariant
{
Type = VariantType.Quaternion,
Quaternion = value
};
public static implicit operator ExpressionVariant(Avalonia.Media.Color value) =>
new ExpressionVariant
{
Type = VariantType.Color,
Color = value
};
public static ExpressionVariant operator +(ExpressionVariant left, ExpressionVariant right)
{
if (left.Type != right.Type || left.Type == VariantType.Invalid)
return default;
if (left.Type == VariantType.Scalar)
return left.Scalar + right.Scalar;
if (left.Type == VariantType.Double)
return left.Double + right.Double;
if (left.Type == VariantType.Vector2)
return left.Vector2 + right.Vector2;
if (left.Type == VariantType.Vector3)
return left.Vector3 + right.Vector3;
if (left.Type == VariantType.Vector4)
return left.Vector4 + right.Vector4;
if (left.Type == VariantType.Matrix3x2)
return left.Matrix3x2 + right.Matrix3x2;
if (left.Type == VariantType.AvaloniaMatrix)
return left.AvaloniaMatrix + right.AvaloniaMatrix;
if (left.Type == VariantType.Matrix4x4)
return left.Matrix4x4 + right.Matrix4x4;
if (left.Type == VariantType.Quaternion)
return left.Quaternion + right.Quaternion;
return default;
}
public static ExpressionVariant operator -(ExpressionVariant left, ExpressionVariant right)
{
if (left.Type != right.Type || left.Type == VariantType.Invalid)
return default;
if (left.Type == VariantType.Scalar)
return left.Scalar - right.Scalar;
if (left.Type == VariantType.Double)
return left.Double - right.Double;
if (left.Type == VariantType.Vector2)
return left.Vector2 - right.Vector2;
if (left.Type == VariantType.Vector3)
return left.Vector3 - right.Vector3;
if (left.Type == VariantType.Vector4)
return left.Vector4 - right.Vector4;
if (left.Type == VariantType.Matrix3x2)
return left.Matrix3x2 - right.Matrix3x2;
if (left.Type == VariantType.AvaloniaMatrix)
return left.AvaloniaMatrix - right.AvaloniaMatrix;
if (left.Type == VariantType.Matrix4x4)
return left.Matrix4x4 - right.Matrix4x4;
if (left.Type == VariantType.Quaternion)
return left.Quaternion - right.Quaternion;
return default;
}
public static ExpressionVariant operator -(ExpressionVariant left)
{
if (left.Type == VariantType.Scalar)
return -left.Scalar;
if (left.Type == VariantType.Double)
return -left.Double;
if (left.Type == VariantType.Vector2)
return -left.Vector2;
if (left.Type == VariantType.Vector3)
return -left.Vector3;
if (left.Type == VariantType.Vector4)
return -left.Vector4;
if (left.Type == VariantType.Matrix3x2)
return -left.Matrix3x2;
if (left.Type == VariantType.AvaloniaMatrix)
return -left.AvaloniaMatrix;
if (left.Type == VariantType.Matrix4x4)
return -left.Matrix4x4;
if (left.Type == VariantType.Quaternion)
return -left.Quaternion;
return default;
}
public static ExpressionVariant operator *(ExpressionVariant left, ExpressionVariant right)
{
if (left.Type == VariantType.Invalid || right.Type == VariantType.Invalid)
return default;
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar)
return left.Scalar * right.Scalar;
if (left.Type == VariantType.Double && right.Type == VariantType.Double)
return left.Double * right.Double;
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Vector2)
return left.Vector2 * right.Vector2;
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Scalar)
return left.Vector2 * right.Scalar;
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Vector3)
return left.Vector3 * right.Vector3;
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Scalar)
return left.Vector3 * right.Scalar;
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Vector4)
return left.Vector4 * right.Vector4;
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Scalar)
return left.Vector4 * right.Scalar;
if (left.Type == VariantType.Matrix3x2 && right.Type == VariantType.Matrix3x2)
return left.Matrix3x2 * right.Matrix3x2;
if (left.Type == VariantType.Matrix3x2 && right.Type == VariantType.Scalar)
return left.Matrix3x2 * right.Scalar;
if (left.Type == VariantType.AvaloniaMatrix && right.Type == VariantType.AvaloniaMatrix)
return left.AvaloniaMatrix * right.AvaloniaMatrix;
if (left.Type == VariantType.AvaloniaMatrix && right.Type == VariantType.Scalar)
return left.AvaloniaMatrix * (double)right.Scalar;
if (left.Type == VariantType.AvaloniaMatrix && right.Type == VariantType.Double)
return left.AvaloniaMatrix * right.Double;
if (left.Type == VariantType.Matrix4x4 && right.Type == VariantType.Matrix4x4)
return left.Matrix4x4 * right.Matrix4x4;
if (left.Type == VariantType.Matrix4x4 && right.Type == VariantType.Scalar)
return left.Matrix4x4 * right.Scalar;
if (left.Type == VariantType.Quaternion && right.Type == VariantType.Quaternion)
return left.Quaternion * right.Quaternion;
if (left.Type == VariantType.Quaternion && right.Type == VariantType.Scalar)
return left.Quaternion * right.Scalar;
return default;
}
public static ExpressionVariant operator /(ExpressionVariant left, ExpressionVariant right)
{
if (left.Type == VariantType.Invalid || right.Type == VariantType.Invalid)
return default;
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar)
return left.Scalar / right.Scalar;
if (left.Type == VariantType.Double && right.Type == VariantType.Double)
return left.Double / right.Double;
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Vector2)
return left.Vector2 / right.Vector2;
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Scalar)
return left.Vector2 / right.Scalar;
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Vector3)
return left.Vector3 / right.Vector3;
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Scalar)
return left.Vector3 / right.Scalar;
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Vector4)
return left.Vector4 / right.Vector4;
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Scalar)
return left.Vector4 / right.Scalar;
if (left.Type == VariantType.Quaternion && right.Type == VariantType.Quaternion)
return left.Quaternion / right.Quaternion;
return default;
}
public ExpressionVariant EqualsTo(ExpressionVariant right)
{
if (Type != right.Type || Type == VariantType.Invalid)
return default;
if (Type == VariantType.Scalar)
return Scalar == right.Scalar;
if (Type == VariantType.Double)
return Double == right.Double;
if (Type == VariantType.Vector2)
return Vector2 == right.Vector2;
if (Type == VariantType.Vector3)
return Vector3 == right.Vector3;
if (Type == VariantType.Vector4)
return Vector4 == right.Vector4;
if (Type == VariantType.Boolean)
return Boolean == right.Boolean;
if (Type == VariantType.Matrix3x2)
return Matrix3x2 == right.Matrix3x2;
if (Type == VariantType.AvaloniaMatrix)
return AvaloniaMatrix == right.AvaloniaMatrix;
if (Type == VariantType.Matrix4x4)
return Matrix4x4 == right.Matrix4x4;
if (Type == VariantType.Quaternion)
return Quaternion == right.Quaternion;
return default;
}
public ExpressionVariant NotEqualsTo(ExpressionVariant right)
{
var r = EqualsTo(right);
if (r.Type == VariantType.Boolean)
return !r.Boolean;
return default;
}
public static ExpressionVariant operator !(ExpressionVariant v)
{
if (v.Type == VariantType.Boolean)
return !v.Boolean;
return default;
}
public static ExpressionVariant operator %(ExpressionVariant left, ExpressionVariant right)
{
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar)
return left.Scalar % right.Scalar;
if (left.Type == VariantType.Double && right.Type == VariantType.Double)
return left.Double % right.Double;
return default;
}
public static ExpressionVariant operator <(ExpressionVariant left, ExpressionVariant right)
{
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar)
return left.Scalar < right.Scalar;
if (left.Type == VariantType.Double && right.Type == VariantType.Double)
return left.Double < right.Double;
return default;
}
public static ExpressionVariant operator >(ExpressionVariant left, ExpressionVariant right)
{
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar)
return left.Scalar > right.Scalar;
if (left.Type == VariantType.Double && right.Type == VariantType.Double)
return left.Double > right.Double;
return default;
}
public ExpressionVariant And(ExpressionVariant right)
{
if (Type == VariantType.Boolean && right.Type == VariantType.Boolean)
return Boolean && right.Boolean;
return default;
}
public ExpressionVariant Or(ExpressionVariant right)
{
if (Type == VariantType.Boolean && right.Type == VariantType.Boolean)
return Boolean && right.Boolean;
return default;
}
public bool TryCast<T>(out T res) where T : struct
{
if (typeof(T) == typeof(bool))
{
if (Type == VariantType.Boolean)
{
res = (T) (object) Boolean;
return true;
}
}
if (typeof(T) == typeof(float))
{
if (Type == VariantType.Scalar)
{
res = (T) (object) Scalar;
return true;
}
}
if (typeof(T) == typeof(double))
{
if (Type == VariantType.Double)
{
res = (T) (object) Double;
return true;
}
}
if (typeof(T) == typeof(Vector2))
{
if (Type == VariantType.Vector2)
{
res = (T) (object) Vector2;
return true;
}
}
if (typeof(T) == typeof(Vector3))
{
if (Type == VariantType.Vector3)
{
res = (T) (object) Vector3;
return true;
}
}
if (typeof(T) == typeof(Vector4))
{
if (Type == VariantType.Vector4)
{
res = (T) (object) Vector4;
return true;
}
}
if (typeof(T) == typeof(Matrix3x2))
{
if (Type == VariantType.Matrix3x2)
{
res = (T) (object) Matrix3x2;
return true;
}
}
if (typeof(T) == typeof(Matrix))
{
if (Type == VariantType.AvaloniaMatrix)
{
res = (T) (object) Matrix3x2;
return true;
}
}
if (typeof(T) == typeof(Matrix4x4))
{
if (Type == VariantType.Matrix4x4)
{
res = (T) (object) Matrix4x4;
return true;
}
}
if (typeof(T) == typeof(Quaternion))
{
if (Type == VariantType.Quaternion)
{
res = (T) (object) Quaternion;
return true;
}
}
if (typeof(T) == typeof(Avalonia.Media.Color))
{
if (Type == VariantType.Color)
{
res = (T) (object) Color;
return true;
}
}
res = default(T);
return false;
}
public static ExpressionVariant Create<T>(T v) where T : struct
{
if (typeof(T) == typeof(bool))
return (bool) (object) v;
if (typeof(T) == typeof(float))
return (float) (object) v;
if (typeof(T) == typeof(Vector2))
return (Vector2) (object) v;
if (typeof(T) == typeof(Vector3))
return (Vector3) (object) v;
if (typeof(T) == typeof(Vector4))
return (Vector4) (object) v;
if (typeof(T) == typeof(Matrix3x2))
return (Matrix3x2) (object) v;
if (typeof(T) == typeof(Matrix))
return (Matrix) (object) v;
if (typeof(T) == typeof(Matrix4x4))
return (Matrix4x4) (object) v;
if (typeof(T) == typeof(Quaternion))
return (Quaternion) (object) v;
if (typeof(T) == typeof(Avalonia.Media.Color))
return (Avalonia.Media.Color) (object) v;
throw new ArgumentException("Invalid variant type: " + typeof(T));
}
public T CastOrDefault<T>() where T : struct
{
TryCast<T>(out var r);
return r;
}
public override string ToString()
{
if (Type == VariantType.Boolean)
return Boolean.ToString();
if (Type == VariantType.Scalar)
return Scalar.ToString(CultureInfo.InvariantCulture);
if (Type == VariantType.Double)
return Double.ToString(CultureInfo.InvariantCulture);
if (Type == VariantType.Vector2)
return Vector2.ToString();
if (Type == VariantType.Vector3)
return Vector3.ToString();
if (Type == VariantType.Vector4)
return Vector4.ToString();
if (Type == VariantType.Quaternion)
return Quaternion.ToString();
if (Type == VariantType.Matrix3x2)
return Matrix3x2.ToString();
if (Type == VariantType.AvaloniaMatrix)
return AvaloniaMatrix.ToString();
if (Type == VariantType.Matrix4x4)
return Matrix4x4.ToString();
if (Type == VariantType.Color)
return Color.ToString();
if (Type == VariantType.Invalid)
return "Invalid";
return "Unknown";
}
}
}

256
src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs

@ -0,0 +1,256 @@
using System;
using System.Globalization;
namespace Avalonia.Rendering.Composition.Expressions
{
internal ref struct TokenParser
{
private ReadOnlySpan<char> _s;
public int Position { get; private set; }
public TokenParser(ReadOnlySpan<char> s)
{
_s = s;
Position = 0;
}
public void SkipWhitespace()
{
while (true)
{
if (_s.Length > 0 && char.IsWhiteSpace(_s[0]))
Advance(1);
else
return;
}
}
static bool IsAlphaNumeric(char ch) => (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z');
public bool TryConsume(char c)
{
SkipWhitespace();
if (_s.Length == 0 || _s[0] != c)
return false;
Advance(1);
return true;
}
public bool TryConsume(string s)
{
SkipWhitespace();
if (_s.Length < s.Length)
return false;
for (var c = 0; c < s.Length; c++)
{
if (_s[c] != s[c])
return false;
}
Advance(s.Length);
return true;
}
public bool TryConsumeAny(ReadOnlySpan<char> chars, out char token)
{
SkipWhitespace();
token = default;
if (_s.Length == 0)
return false;
foreach (var c in chars)
{
if (c == _s[0])
{
token = c;
Advance(1);
return true;
}
}
return false;
}
public bool TryParseKeyword(string keyword)
{
SkipWhitespace();
if (keyword.Length > _s.Length)
return false;
for(var c=0; c<keyword.Length;c++)
if (keyword[c] != _s[c])
return false;
if (_s.Length > keyword.Length && IsAlphaNumeric(_s[keyword.Length]))
return false;
Advance(keyword.Length);
return true;
}
public bool TryParseKeywordLowerCase(string keywordInLowerCase)
{
SkipWhitespace();
if (keywordInLowerCase.Length > _s.Length)
return false;
for(var c=0; c<keywordInLowerCase.Length;c++)
if (keywordInLowerCase[c] != char.ToLowerInvariant(_s[c]))
return false;
if (_s.Length > keywordInLowerCase.Length && IsAlphaNumeric(_s[keywordInLowerCase.Length]))
return false;
Advance(keywordInLowerCase.Length);
return true;
}
public void Advance(int c)
{
_s = _s.Slice(c);
Position += c;
}
public int Length => _s.Length;
public bool TryParseIdentifier(ReadOnlySpan<char> extraValidChars, out ReadOnlySpan<char> res)
{
res = ReadOnlySpan<char>.Empty;
SkipWhitespace();
if (_s.Length == 0)
return false;
var first = _s[0];
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z')))
return false;
int len = 1;
for (var c = 1; c < _s.Length; c++)
{
var ch = _s[c];
if (IsAlphaNumeric(ch))
len++;
else
{
var found = false;
foreach(var vc in extraValidChars)
if (vc == ch)
{
found = true;
break;
}
if (found)
len++;
else
break;
}
}
res = _s.Slice(0, len);
Advance(len);
return true;
}
public bool TryParseIdentifier(out ReadOnlySpan<char> res)
{
res = ReadOnlySpan<char>.Empty;
SkipWhitespace();
if (_s.Length == 0)
return false;
var first = _s[0];
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z')))
return false;
int len = 1;
for (var c = 1; c < _s.Length; c++)
{
var ch = _s[c];
if (IsAlphaNumeric(ch))
len++;
else
break;
}
res = _s.Slice(0, len);
Advance(len);
return true;
}
public bool TryParseCall(out ReadOnlySpan<char> res)
{
res = ReadOnlySpan<char>.Empty;
SkipWhitespace();
if (_s.Length == 0)
return false;
var first = _s[0];
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z')))
return false;
int len = 1;
for (var c = 1; c < _s.Length; c++)
{
var ch = _s[c];
if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch<= 'Z') || ch == '.')
len++;
else
break;
}
res = _s.Slice(0, len);
// Find '('
for (var c = len; c < _s.Length; c++)
{
if(char.IsWhiteSpace(_s[c]))
continue;
if(_s[c]=='(')
{
Advance(c + 1);
return true;
}
return false;
}
return false;
}
public bool TryParseFloat(out float res)
{
res = 0;
SkipWhitespace();
if (_s.Length == 0)
return false;
var len = 0;
var dotCount = 0;
for (var c = 0; c < _s.Length; c++)
{
var ch = _s[c];
if (ch >= '0' && ch <= '9')
len = c + 1;
else if (ch == '.' && dotCount == 0)
{
len = c + 1;
dotCount++;
}
else
break;
}
var span = _s.Slice(0, len);
#if NETSTANDARD2_0
if (!float.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, out res))
return false;
#else
if (!float.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, out res))
return false;
#endif
Advance(len);
return true;
}
public override string ToString() => _s.ToString();
}
}

9
src/Avalonia.Base/Rendering/Composition/ICompositionSurface.cs

@ -0,0 +1,9 @@
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition
{
public interface ICompositionSurface
{
internal ServerCompositionSurface Server { get; }
}
}

46
src/Avalonia.Base/Rendering/Composition/MatrixUtils.cs

@ -0,0 +1,46 @@
using System.Numerics;
namespace Avalonia.Rendering.Composition
{
static class MatrixUtils
{
public static Matrix4x4 ComputeTransform(Vector2 size, Vector2 anchorPoint, Vector3 centerPoint,
Matrix4x4 transformMatrix, Vector3 scale, float rotationAngle, Quaternion orientation, Vector3 offset)
{
// The math here follows the *observed* UWP behavior since there are no docs on how it's supposed to work
var anchor = size * anchorPoint;
var mat = Matrix4x4.CreateTranslation(-anchor.X, -anchor.Y, 0);
var center = new Vector3(centerPoint.X, centerPoint.Y, centerPoint.Z);
if (!transformMatrix.IsIdentity)
mat = transformMatrix * mat;
if (scale != new Vector3(1, 1, 1))
mat *= Matrix4x4.CreateScale(scale, center);
//TODO: RotationAxis support
if (rotationAngle != 0)
mat *= Matrix4x4.CreateRotationZ(rotationAngle, center);
if (orientation != Quaternion.Identity)
{
if (centerPoint != default)
{
mat *= Matrix4x4.CreateTranslation(-center)
* Matrix4x4.CreateFromQuaternion(orientation)
* Matrix4x4.CreateTranslation(center);
}
else
mat *= Matrix4x4.CreateFromQuaternion(orientation);
}
if (offset != default)
mat *= Matrix4x4.CreateTranslation(offset);
return mat;
}
}
}

142
src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs

@ -0,0 +1,142 @@
using System.Numerics;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Server;
internal class CompositorDrawingContextProxy : IDrawingContextImpl
{
private IDrawingContextImpl _impl;
public CompositorDrawingContextProxy(IDrawingContextImpl impl)
{
_impl = impl;
}
public Matrix PreTransform { get; set; } = Matrix.Identity;
public void Dispose()
{
_impl.Dispose();
}
Matrix _transform;
public Matrix Transform
{
get => _transform;
set => _impl.Transform = PreTransform * (_transform = value);
}
public void Clear(Color color)
{
_impl.Clear(color);
}
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
{
_impl.DrawBitmap(source, opacity, sourceRect, destRect, bitmapInterpolationMode);
}
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{
_impl.DrawBitmap(source, opacityMask, opacityMaskRect, destRect);
}
public void DrawLine(IPen pen, Point p1, Point p2)
{
_impl.DrawLine(pen, p1, p2);
}
public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
{
_impl.DrawGeometry(brush, pen, geometry);
}
public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, BoxShadows boxShadows = default)
{
_impl.DrawRectangle(brush, pen, rect, boxShadows);
}
public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
{
_impl.DrawEllipse(brush, pen, rect);
}
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
_impl.DrawGlyphRun(foreground, glyphRun);
}
public IDrawingContextLayerImpl CreateLayer(Size size)
{
return _impl.CreateLayer(size);
}
public void PushClip(Rect clip)
{
_impl.PushClip(clip);
}
public void PushClip(RoundedRect clip)
{
_impl.PushClip(clip);
}
public void PopClip()
{
_impl.PopClip();
}
public void PushOpacity(double opacity)
{
_impl.PushOpacity(opacity);
}
public void PopOpacity()
{
_impl.PopOpacity();
}
public void PushOpacityMask(IBrush mask, Rect bounds)
{
_impl.PushOpacityMask(mask, bounds);
}
public void PopOpacityMask()
{
_impl.PopOpacityMask();
}
public void PushGeometryClip(IGeometryImpl clip)
{
_impl.PushGeometryClip(clip);
}
public void PopGeometryClip()
{
_impl.PopGeometryClip();
}
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
_impl.PushBitmapBlendMode(blendingMode);
}
public void PopBitmapBlendMode()
{
_impl.PopBitmapBlendMode();
}
public void Custom(ICustomDrawOperation custom)
{
_impl.Custom(custom);
}
public Matrix CutTransform(Matrix4x4 transform) => new Matrix(transform.M11, transform.M12, transform.M21,
transform.M22, transform.M41,
transform.M42);
}

46
src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs

@ -0,0 +1,46 @@
namespace Avalonia.Rendering.Composition.Server
{
internal class ReadbackIndices
{
private readonly object _lock = new object();
public int ReadIndex { get; private set; } = 0;
public int WriteIndex { get; private set; } = -1;
public ulong ReadRevision { get; private set; }
public ulong WriteRevision { get; private set; }
private ulong[] _revisions = new ulong[3];
public void NextRead()
{
lock (_lock)
{
for (var c = 0; c < 3; c++)
{
if (c != WriteIndex && c != ReadIndex && _revisions[c] > ReadRevision)
{
ReadIndex = c;
ReadRevision = _revisions[c];
return;
}
}
}
}
public void NextWrite(ulong revision)
{
lock (_lock)
{
for (var c = 0; c < 3; c++)
{
if (c != WriteIndex && c != ReadIndex)
{
WriteIndex = c;
WriteRevision = revision;
_revisions[c] = revision;
return;
}
}
}
}
}
}

7
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionBrush.cs

@ -0,0 +1,7 @@
namespace Avalonia.Rendering.Composition.Server
{
internal abstract partial class ServerCompositionBrush : ServerObject
{
}
}

41
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs

@ -0,0 +1,41 @@
using System.Numerics;
using Avalonia.Collections.Pooled;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Rendering.Composition.Transport;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Server;
internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisual
{
private CompositionDrawList? _renderCommands;
public ServerCompositionDrawListVisual(ServerCompositor compositor) : base(compositor)
{
}
protected override void ApplyCore(ChangeSet changes)
{
var ch = (DrawListVisualChanges)changes;
if (ch.DrawCommandsIsSet)
{
_renderCommands?.Dispose();
_renderCommands = ch.AcquireDrawCommands();
}
base.ApplyCore(changes);
}
protected override void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform)
{
if (_renderCommands != null)
{
foreach (var cmd in _renderCommands)
{
cmd.Item.Render(canvas);
}
}
base.RenderCore(canvas, transform);
}
}

15
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionGradientBrush.cs

@ -0,0 +1,15 @@
using System;
namespace Avalonia.Rendering.Composition.Server
{
internal abstract partial class ServerCompositionGradientBrush : ServerCompositionBrush
{
public ServerCompositionGradientStopCollection Stops { get; }
public ServerCompositionGradientBrush(ServerCompositor compositor) : base(compositor)
{
Stops = new ServerCompositionGradientStopCollection(compositor);
}
public override long LastChangedBy => Math.Max(base.LastChangedBy, (long)Stops.LastChangedBy);
}
}

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

@ -0,0 +1,22 @@
using Avalonia.Media;
namespace Avalonia.Rendering.Composition.Server
{
internal partial class ServerCompositionLinearGradientBrush
{
/*
protected override void UpdateBackendBrush(ICbBrush brush)
{
var stopColors = new Color[Stops.List.Count];
var offsets = new float[Stops.List.Count];
for (var c = 0; c < Stops.List.Count; c++)
{
stopColors[c] = Stops.List[c].Color;
offsets[c] = Stops.List[c].Offset;
}
((ICbLinearGradientBrush) brush).Update(StartPoint, EndPoint, stopColors, offsets, ExtendMode);
}*/
}
}

9
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs

@ -0,0 +1,9 @@
namespace Avalonia.Rendering.Composition.Server
{
internal abstract class ServerCompositionSurface : ServerObject
{
protected ServerCompositionSurface(ServerCompositor compositor) : base(compositor)
{
}
}
}

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

@ -0,0 +1,53 @@
using System;
using System.Numerics;
using System.Threading;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Server
{
internal partial class ServerCompositionTarget
{
private readonly ServerCompositor _compositor;
private readonly Func<IRenderTarget> _renderTargetFactory;
private static long s_nextId = 1;
public long Id { get; }
private ulong _frame = 1;
private IRenderTarget? _renderTarget;
public ReadbackIndices Readback { get; } = new();
public ServerCompositionTarget(ServerCompositor compositor, Func<IRenderTarget> renderTargetFactory) :
base(compositor)
{
_compositor = compositor;
_renderTargetFactory = renderTargetFactory;
Id = Interlocked.Increment(ref s_nextId);
}
partial void OnIsEnabledChanged()
{
if (IsEnabled)
_compositor.AddCompositionTarget(this);
else
_compositor.RemoveCompositionTarget(this);
}
public void Render()
{
if (Root == null)
return;
_renderTarget ??= _renderTargetFactory();
Compositor.UpdateServerTime();
using (var context = _renderTarget.CreateDrawingContext(null))
{
context.Clear(Colors.Transparent);
Root.Render(new CompositorDrawingContextProxy(context), Root.CombinedTransformMatrix);
}
Readback.NextWrite(_frame);
_frame++;
}
}
}

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

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Server
{
internal class ServerCompositor : IRenderLoopTask
{
private readonly IRenderLoop _renderLoop;
private readonly Queue<Batch> _batches = new Queue<Batch>();
public long LastBatchId { get; private set; }
public Stopwatch Clock { get; } = Stopwatch.StartNew();
public TimeSpan ServerNow { get; private set; }
private List<ServerCompositionTarget> _activeTargets = new();
public ServerCompositor(IRenderLoop renderLoop)
{
_renderLoop = renderLoop;
_renderLoop.Add(this);
}
public void EnqueueBatch(Batch batch)
{
lock (_batches)
_batches.Enqueue(batch);
}
internal void UpdateServerTime() => ServerNow = Clock.Elapsed;
List<Batch> _reusableToCompleteList = new();
void ApplyPendingBatches()
{
while (true)
{
Batch batch;
lock (_batches)
{
if(_batches.Count == 0)
break;
batch = _batches.Dequeue();
}
foreach (var change in batch.Changes)
{
if (change.Dispose)
{
//TODO
}
change.Target!.Apply(change);
change.Reset();
}
_reusableToCompleteList.Add(batch);
LastBatchId = batch.SequenceId;
}
}
void CompletePendingBatches()
{
foreach(var batch in _reusableToCompleteList)
batch.Complete();
_reusableToCompleteList.Clear();
}
bool IRenderLoopTask.NeedsUpdate => false;
void IRenderLoopTask.Update(TimeSpan time)
{
}
void IRenderLoopTask.Render()
{
ApplyPendingBatches();
foreach (var t in _activeTargets)
t.Render();
CompletePendingBatches();
}
public void AddCompositionTarget(ServerCompositionTarget target)
{
_activeTargets.Add(target);
}
public void RemoveCompositionTarget(ServerCompositionTarget target)
{
_activeTargets.Remove(target);
}
}
}

29
src/Avalonia.Base/Rendering/Composition/Server/ServerContainerVisual.cs

@ -0,0 +1,29 @@
using System.Numerics;
using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Server
{
internal class ServerCompositionContainerVisual : ServerCompositionVisual
{
public ServerCompositionVisualCollection Children { get; }
protected override void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform)
{
base.RenderCore(canvas, transform);
foreach (var ch in Children)
{
var t = transform;
t = ch.CombinedTransformMatrix * t;
ch.Render(canvas, t);
}
}
public ServerCompositionContainerVisual(ServerCompositor compositor) : base(compositor)
{
Children = new ServerCompositionVisualCollection(compositor);
}
}
}

31
src/Avalonia.Base/Rendering/Composition/Server/ServerCustomDrawVisual.cs

@ -0,0 +1,31 @@
using System.Numerics;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Server
{
class ServerCustomDrawVisual<TData> : ServerCompositionContainerVisual
{
private readonly ICustomDrawVisualRenderer<TData> _renderer;
private TData? _data;
public ServerCustomDrawVisual(ServerCompositor compositor, ICustomDrawVisualRenderer<TData> renderer) : base(compositor)
{
_renderer = renderer;
}
protected override void ApplyCore(ChangeSet changes)
{
var c = (CustomDrawVisualChanges<TData>) changes;
if (c.Data.IsSet)
_data = c.Data.Value;
base.ApplyCore(changes);
}
protected override void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform)
{
_renderer.Render(canvas, _data);
base.RenderCore(canvas, transform);
}
}
}

46
src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Server
{
class ServerList<T> : ServerObject where T : ServerObject
{
public List<T> List { get; } = new List<T>();
protected override void ApplyCore(ChangeSet changes)
{
var c = (ListChangeSet<T>) changes;
if (c.HasListChanges)
{
foreach (var lc in c.ListChanges)
{
if(lc.Action == ListChangeAction.Clear)
List.Clear();
if(lc.Action == ListChangeAction.RemoveAt)
List.RemoveAt(lc.Index);
if(lc.Action == ListChangeAction.InsertAt)
List.Insert(lc.Index, lc.Added!);
if (lc.Action == ListChangeAction.ReplaceAt)
List[lc.Index] = lc.Added!;
}
}
}
public override long LastChangedBy
{
get
{
var seq = base.LastChangedBy;
foreach (var i in List)
seq = Math.Max(i.LastChangedBy, seq);
return seq;
}
}
public List<T>.Enumerator GetEnumerator() => List.GetEnumerator();
public ServerList(ServerCompositor compositor) : base(compositor)
{
}
}
}

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

@ -0,0 +1,36 @@
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Server
{
internal abstract class ServerObject : IExpressionObject
{
public ServerCompositor Compositor { get; }
public virtual long LastChangedBy => ItselfLastChangedBy;
public long ItselfLastChangedBy { get; private set; }
public ServerObject(ServerCompositor compositor)
{
Compositor = compositor;
}
protected virtual void ApplyCore(ChangeSet changes)
{
}
public void Apply(ChangeSet changes)
{
ApplyCore(changes);
ItselfLastChangedBy = changes.Batch!.SequenceId;
}
public virtual ExpressionVariant GetPropertyForAnimation(string name)
{
return default;
}
ExpressionVariant IExpressionObject.GetProperty(string name) => GetPropertyForAnimation(name);
}
}

16
src/Avalonia.Base/Rendering/Composition/Server/ServerSolidColorVisual.cs

@ -0,0 +1,16 @@
using System.Numerics;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Server
{
internal partial class ServerCompositionSolidColorVisual
{
protected override void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform)
{
canvas.Transform = canvas.CutTransform(transform);
canvas.DrawRectangle(new ImmutableSolidColorBrush(Color), null, new RoundedRect(new Rect(new Size(Size))));
base.RenderCore(canvas, transform);
}
}
}

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

@ -0,0 +1,20 @@
using System.Numerics;
using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Server
{
internal partial class ServerCompositionSpriteVisual
{
protected override void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform)
{
if (Brush != null)
{
//SetTransform(canvas, transform);
//canvas.FillRect((Vector2)Size, (ICbBrush)Brush.Brush!);
}
base.RenderCore(canvas, transform);
}
}
}

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

@ -0,0 +1,92 @@
using System.Numerics;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Server
{
unsafe partial class ServerCompositionVisual : ServerObject
{
protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform)
{
}
public void Render(CompositorDrawingContextProxy canvas, Matrix4x4 transform)
{
if(Visible == false)
return;
if(Opacity == 0)
return;
canvas.PreTransform = canvas.CutTransform(transform);
canvas.Transform = Matrix.Identity;
if (Opacity != 1)
canvas.PushOpacity(Opacity);
if(ClipToBounds)
canvas.PushClip(new Rect(new Size(Size.X, Size.Y)));
if (Clip != null)
canvas.PushGeometryClip(Clip);
RenderCore(canvas, transform);
if (Clip != null)
canvas.PopGeometryClip();
if (ClipToBounds)
canvas.PopClip();
if(Opacity != 1)
canvas.PopOpacity();
}
private ReadbackData _readback0, _readback1, _readback2;
public ref ReadbackData GetReadback(int idx)
{
if (idx == 0)
return ref _readback0;
if (idx == 1)
return ref _readback1;
return ref _readback2;
}
public Matrix4x4 CombinedTransformMatrix
{
get
{
if (Root == null)
return default;
var res = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint, TransformMatrix,
Scale, RotationAngle, Orientation, Offset);
var i = Root.Readback;
ref var readback = ref GetReadback(i.WriteIndex);
readback.Revision = i.WriteRevision;
readback.Matrix = res;
readback.TargetId = Root.Id;
return res;
}
}
public struct ReadbackData
{
public Matrix4x4 Matrix;
public bool Visible;
public ulong Revision;
public long TargetId;
}
partial void ApplyChangesExtra(CompositionVisualChanges c)
{
if (c.Parent.IsSet)
Parent = c.Parent.Value;
if (c.Root.IsSet)
Root = c.Root.Value;
}
public ServerCompositionTarget? Root { get; private set; }
public ServerCompositionVisual? Parent { get; private set; }
}
}

37
src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs

@ -0,0 +1,37 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Avalonia.Rendering.Composition.Transport
{
internal class Batch
{
private static long _nextSequenceId = 1;
private static ConcurrentBag<List<ChangeSet>> _pool = new ConcurrentBag<List<ChangeSet>>();
public long SequenceId { get; }
public Batch()
{
SequenceId = Interlocked.Increment(ref _nextSequenceId);
if (!_pool.TryTake(out var lst))
lst = new List<ChangeSet>();
Changes = lst;
}
private TaskCompletionSource<int> _tcs = new TaskCompletionSource<int>();
public List<ChangeSet> Changes { get; private set; }
public TimeSpan CommitedAt { get; set; }
public void Complete()
{
Changes.Clear();
_pool.Add(Changes);
Changes = null!;
_tcs.TrySetResult(0);
}
public Task Completed => _tcs.Task;
}
}

82
src/Avalonia.Base/Rendering/Composition/Transport/Change.cs

@ -0,0 +1,82 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Rendering.Composition.Animations;
namespace Avalonia.Rendering.Composition.Transport
{
struct Change<T>
{
private T? _value;
public bool IsSet { get; private set; }
public T? Value
{
get
{
if(!IsSet)
throw new InvalidOperationException();
return _value;
}
set
{
IsSet = true;
_value = value;
}
}
public void Reset()
{
_value = default;
IsSet = false;
}
}
struct AnimatedChange<T>
{
private T? _value;
private IAnimationInstance? _animation;
public bool IsValue { get; private set; }
public bool IsAnimation { get; private set; }
public T Value
{
get
{
if(!IsValue)
throw new InvalidOperationException();
return _value!;
}
set
{
IsAnimation = false;
_animation = null;
IsValue = true;
_value = value;
}
}
public IAnimationInstance Animation
{
get
{
if(!IsAnimation)
throw new InvalidOperationException();
return _animation!;
}
set
{
IsValue = false;
_value = default;
IsAnimation = true;
_animation = value;
}
}
public void Reset()
{
this = default;
}
}
}

36
src/Avalonia.Base/Rendering/Composition/Transport/ChangeSet.cs

@ -0,0 +1,36 @@
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Transport
{
internal abstract class ChangeSet
{
private readonly IChangeSetPool _pool;
public Batch Batch = null!;
public ServerObject? Target;
public bool Dispose;
public ChangeSet(IChangeSetPool pool)
{
_pool = pool;
}
public virtual void Reset()
{
Batch = null!;
Target = null;
Dispose = false;
}
public void Return()
{
_pool.Return(this);
}
}
internal class CompositionObjectChanges : ChangeSet
{
public CompositionObjectChanges(IChangeSetPool pool) : base(pool)
{
}
}
}

42
src/Avalonia.Base/Rendering/Composition/Transport/ChangeSetPool.cs

@ -0,0 +1,42 @@
using System;
using System.Collections.Concurrent;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Transport
{
interface IChangeSetPool
{
void Return(ChangeSet changes);
ChangeSet Get(ServerObject target, Batch batch);
}
class ChangeSetPool<T> : IChangeSetPool where T : ChangeSet
{
private readonly Func<IChangeSetPool, T> _factory;
private readonly ConcurrentBag<T> _pool = new ConcurrentBag<T>();
public ChangeSetPool(Func<IChangeSetPool, T> factory)
{
_factory = factory;
}
public void Return(T changes)
{
changes.Reset();
_pool.Add(changes);
}
void IChangeSetPool.Return(ChangeSet changes) => Return((T) changes);
ChangeSet IChangeSetPool.Get(ServerObject target, Batch batch) => Get(target, batch);
public T Get(ServerObject target, Batch batch)
{
if (!_pool.TryTake(out var res))
res = _factory(this);
res.Target = target;
res.Batch = batch;
res.Dispose = false;
return res;
}
}
}

20
src/Avalonia.Base/Rendering/Composition/Transport/CustomDrawVisualChanges.cs

@ -0,0 +1,20 @@
namespace Avalonia.Rendering.Composition.Transport
{
class CustomDrawVisualChanges<TData> : CompositionVisualChanges
{
public CustomDrawVisualChanges(IChangeSetPool pool) : base(pool)
{
}
public Change<TData> Data;
public override void Reset()
{
Data.Reset();
base.Reset();
}
public new static ChangeSetPool<CustomDrawVisualChanges<TData>> Pool { get; } =
new ChangeSetPool<CustomDrawVisualChanges<TData>>(pool => new CustomDrawVisualChanges<TData>(pool));
}
}

48
src/Avalonia.Base/Rendering/Composition/Transport/DrawListVisualChanges.cs

@ -0,0 +1,48 @@
using System.Collections.Generic;
using Avalonia.Collections.Pooled;
using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Transport;
internal class DrawListVisualChanges : CompositionVisualChanges
{
private CompositionDrawList? _drawCommands;
public DrawListVisualChanges(IChangeSetPool pool) : base(pool)
{
}
public CompositionDrawList? DrawCommands
{
get => _drawCommands;
set
{
_drawCommands?.Dispose();
_drawCommands = value;
DrawCommandsIsSet = true;
}
}
public bool DrawCommandsIsSet { get; private set; }
public CompositionDrawList? AcquireDrawCommands()
{
var rv = _drawCommands;
_drawCommands = null;
DrawCommandsIsSet = false;
return rv;
}
public override void Reset()
{
_drawCommands?.Dispose();
_drawCommands = null;
DrawCommandsIsSet = false;
base.Reset();
}
public new static ChangeSetPool<DrawListVisualChanges> Pool { get; } =
new ChangeSetPool<DrawListVisualChanges>(pool => new(pool));
}

19
src/Avalonia.Base/Rendering/Composition/Transport/ListChange.cs

@ -0,0 +1,19 @@
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Transport
{
internal class ListChange<T> where T : ServerObject
{
public int Index;
public ListChangeAction Action;
public T? Added;
}
internal enum ListChangeAction
{
InsertAt,
RemoveAt,
Clear,
ReplaceAt
}
}

25
src/Avalonia.Base/Rendering/Composition/Transport/ListChangeSet.cs

@ -0,0 +1,25 @@
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Transport
{
class ListChangeSet<T> : ChangeSet where T : ServerObject
{
private List<ListChange<T>>? _listChanges;
public List<ListChange<T>> ListChanges => _listChanges ??= new List<ListChange<T>>();
public bool HasListChanges => _listChanges != null;
public override void Reset()
{
_listChanges?.Clear();
base.Reset();
}
public ListChangeSet(IChangeSetPool pool) : base(pool)
{
}
public static readonly ChangeSetPool<ListChangeSet<T>> Pool =
new ChangeSetPool<ListChangeSet<T>>(pool => new ListChangeSet<T>(pool));
}
}

92
src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs

@ -0,0 +1,92 @@
using System.Collections;
using System.Collections.Generic;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Transport
{
class ServerListProxyHelper<TClient, TServer> : IList<TClient>
where TServer : ServerObject
where TClient : CompositionObject
{
private readonly IGetChanges _parent;
private readonly List<TClient> _list = new List<TClient>();
public interface IGetChanges
{
ListChangeSet<TServer> GetChanges();
}
public ServerListProxyHelper(IGetChanges parent)
{
_parent = parent;
}
IEnumerator<TClient> IEnumerable<TClient>.GetEnumerator() => GetEnumerator();
public List<TClient>.Enumerator GetEnumerator() => _list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Add(TClient item) => Insert(_list.Count, item);
public void Clear()
{
_list.Clear();
_parent.GetChanges().ListChanges.Add(new ListChange<TServer>
{
Action = ListChangeAction.Clear
});
}
public bool Contains(TClient item) => _list.Contains(item);
public void CopyTo(TClient[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
public bool Remove(TClient item)
{
var idx = _list.IndexOf(item);
if (idx == -1)
return false;
RemoveAt(idx);
return true;
}
public int Count => _list.Count;
public bool IsReadOnly => false;
public int IndexOf(TClient item) => _list.IndexOf(item);
public void Insert(int index, TClient item)
{
_list.Insert(index, item);
_parent.GetChanges().ListChanges.Add(new ListChange<TServer>
{
Action = ListChangeAction.InsertAt,
Index = index,
Added = (TServer) item.Server
});
}
public void RemoveAt(int index)
{
_list.RemoveAt(index);
_parent.GetChanges().ListChanges.Add(new ListChange<TServer>
{
Action = ListChangeAction.RemoveAt,
Index = index
});
}
public TClient this[int index]
{
get => _list[index];
set
{
_list[index] = value;
_parent.GetChanges().ListChanges.Add(new ListChange<TServer>
{
Action = ListChangeAction.ReplaceAt,
Index = index,
Added = (TServer) value.Server
});
}
}
}
}

16
src/Avalonia.Base/Rendering/Composition/Transport/VisualChanges.cs

@ -0,0 +1,16 @@
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Transport
{
partial class CompositionVisualChanges
{
public Change<ServerCompositionVisual> Parent;
public Change<ServerCompositionTarget> Root;
partial void ResetExtra()
{
Parent.Reset();
Root.Reset();
}
}
}

302
src/Avalonia.Base/Rendering/Composition/Utils/CubicBezier.cs

@ -0,0 +1,302 @@
// ReSharper disable InconsistentNaming
// Ported from Chromium project https://github.com/chromium/chromium/blob/374d31b7704475fa59f7b2cb836b3b68afdc3d79/ui/gfx/geometry/cubic_bezier.cc
using System;
// ReSharper disable CompareOfFloatsByEqualityOperator
// ReSharper disable CommentTypo
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable TooWideLocalVariableScope
// ReSharper disable UnusedMember.Global
#pragma warning disable 649
namespace Avalonia.Rendering.Composition.Utils
{
internal unsafe struct CubicBezier
{
const int CUBIC_BEZIER_SPLINE_SAMPLES = 11;
double ax_;
double bx_;
double cx_;
double ay_;
double by_;
double cy_;
double start_gradient_;
double end_gradient_;
double range_min_;
double range_max_;
private bool monotonically_increasing_;
fixed double spline_samples_[CUBIC_BEZIER_SPLINE_SAMPLES];
public CubicBezier(double p1x, double p1y, double p2x, double p2y) : this()
{
InitCoefficients(p1x, p1y, p2x, p2y);
InitGradients(p1x, p1y, p2x, p2y);
InitRange(p1y, p2y);
InitSpline();
}
public readonly double SampleCurveX(double t)
{
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
return ((ax_ * t + bx_) * t + cx_) * t;
}
readonly double SampleCurveY(double t)
{
return ((ay_ * t + by_) * t + cy_) * t;
}
readonly double SampleCurveDerivativeX(double t)
{
return (3.0 * ax_ * t + 2.0 * bx_) * t + cx_;
}
readonly double SampleCurveDerivativeY(double t)
{
return (3.0 * ay_ * t + 2.0 * by_) * t + cy_;
}
public readonly double SolveWithEpsilon(double x, double epsilon)
{
if (x < 0.0)
return 0.0 + start_gradient_ * x;
if (x > 1.0)
return 1.0 + end_gradient_ * (x - 1.0);
return SampleCurveY(SolveCurveX(x, epsilon));
}
void InitCoefficients(double p1x,
double p1y,
double p2x,
double p2y)
{
// Calculate the polynomial coefficients, implicit first and last control
// points are (0,0) and (1,1).
cx_ = 3.0 * p1x;
bx_ = 3.0 * (p2x - p1x) - cx_;
ax_ = 1.0 - cx_ - bx_;
cy_ = 3.0 * p1y;
by_ = 3.0 * (p2y - p1y) - cy_;
ay_ = 1.0 - cy_ - by_;
#if DEBUG
// Bezier curves with x-coordinates outside the range [0,1] for internal
// control points may have multiple values for t for a given value of x.
// In this case, calls to SolveCurveX may produce ambiguous results.
monotonically_increasing_ = p1x >= 0 && p1x <= 1 && p2x >= 0 && p2x <= 1;
#endif
}
void InitGradients(double p1x,
double p1y,
double p2x,
double p2y)
{
// End-point gradients are used to calculate timing function results
// outside the range [0, 1].
//
// There are four possibilities for the gradient at each end:
// (1) the closest control point is not horizontally coincident with regard to
// (0, 0) or (1, 1). In this case the line between the end point and
// the control point is tangent to the bezier at the end point.
// (2) the closest control point is coincident with the end point. In
// this case the line between the end point and the far control
// point is tangent to the bezier at the end point.
// (3) both internal control points are coincident with an endpoint. There
// are two special case that fall into this category:
// CubicBezier(0, 0, 0, 0) and CubicBezier(1, 1, 1, 1). Both are
// equivalent to linear.
// (4) the closest control point is horizontally coincident with the end
// point, but vertically distinct. In this case the gradient at the
// end point is Infinite. However, this causes issues when
// interpolating. As a result, we break down to a simple case of
// 0 gradient under these conditions.
if (p1x > 0)
start_gradient_ = p1y / p1x;
else if (p1y == 0 && p2x > 0)
start_gradient_ = p2y / p2x;
else if (p1y == 0 && p2y == 0)
start_gradient_ = 1;
else
start_gradient_ = 0;
if (p2x < 1)
end_gradient_ = (p2y - 1) / (p2x - 1);
else if (p2y == 1 && p1x < 1)
end_gradient_ = (p1y - 1) / (p1x - 1);
else if (p2y == 1 && p1y == 1)
end_gradient_ = 1;
else
end_gradient_ = 0;
}
const double kBezierEpsilon = 1e-7;
void InitRange(double p1y, double p2y)
{
range_min_ = 0;
range_max_ = 1;
if (0 <= p1y && p1y < 1 && 0 <= p2y && p2y <= 1)
return;
double epsilon = kBezierEpsilon;
// Represent the function's derivative in the form at^2 + bt + c
// as in sampleCurveDerivativeY.
// (Technically this is (dy/dt)*(1/3), which is suitable for finding zeros
// but does not actually give the slope of the curve.)
double a = 3.0 * ay_;
double b = 2.0 * by_;
double c = cy_;
// Check if the derivative is constant.
if (Math.Abs(a) < epsilon && Math.Abs(b) < epsilon)
return;
// Zeros of the function's derivative.
double t1;
double t2 = 0;
if (Math.Abs(a) < epsilon)
{
// The function's derivative is linear.
t1 = -c / b;
}
else
{
// The function's derivative is a quadratic. We find the zeros of this
// quadratic using the quadratic formula.
double discriminant = b * b - 4 * a * c;
if (discriminant < 0)
return;
double discriminant_sqrt = Math.Sqrt(discriminant);
t1 = (-b + discriminant_sqrt) / (2 * a);
t2 = (-b - discriminant_sqrt) / (2 * a);
}
double sol1 = 0;
double sol2 = 0;
// If the solution is in the range [0,1] then we include it, otherwise we
// ignore it.
// An interesting fact about these beziers is that they are only
// actually evaluated in [0,1]. After that we take the tangent at that point
// and linearly project it out.
if (0 < t1 && t1 < 1)
sol1 = SampleCurveY(t1);
if (0 < t2 && t2 < 1)
sol2 = SampleCurveY(t2);
range_min_ = Math.Min(Math.Min(range_min_, sol1), sol2);
range_max_ = Math.Max(Math.Max(range_max_, sol1), sol2);
}
void InitSpline()
{
double delta_t = 1.0 / (CUBIC_BEZIER_SPLINE_SAMPLES - 1);
for (int i = 0; i < CUBIC_BEZIER_SPLINE_SAMPLES; i++)
{
spline_samples_[i] = SampleCurveX(i * delta_t);
}
}
const int kMaxNewtonIterations = 4;
public readonly double SolveCurveX(double x, double epsilon)
{
if (x < 0 || x > 1)
throw new ArgumentException();
double t0 = 0;
double t1 = 0;
double t2 = x;
double x2 = 0;
double d2;
int i;
#if DEBUG
if (!monotonically_increasing_)
throw new InvalidOperationException();
#endif
// Linear interpolation of spline curve for initial guess.
double delta_t = 1.0 / (CUBIC_BEZIER_SPLINE_SAMPLES - 1);
for (i = 1; i < CUBIC_BEZIER_SPLINE_SAMPLES; i++)
{
if (x <= spline_samples_[i])
{
t1 = delta_t * i;
t0 = t1 - delta_t;
t2 = t0 + (t1 - t0) * (x - spline_samples_[i - 1]) /
(spline_samples_[i] - spline_samples_[i - 1]);
break;
}
}
// Perform a few iterations of Newton's method -- normally very fast.
// See https://en.wikipedia.org/wiki/Newton%27s_method.
double newton_epsilon = Math.Min(kBezierEpsilon, epsilon);
for (i = 0; i < kMaxNewtonIterations; i++)
{
x2 = SampleCurveX(t2) - x;
if (Math.Abs(x2) < newton_epsilon)
return t2;
d2 = SampleCurveDerivativeX(t2);
if (Math.Abs(d2) < kBezierEpsilon)
break;
t2 = t2 - x2 / d2;
}
if (Math.Abs(x2) < epsilon)
return t2;
// Fall back to the bisection method for reliability.
while (t0 < t1)
{
x2 = SampleCurveX(t2);
if (Math.Abs(x2 - x) < epsilon)
return t2;
if (x > x2)
t0 = t2;
else
t1 = t2;
t2 = (t1 + t0) * .5;
}
// Failure.
return t2;
}
public readonly double Solve(double x)
{
return SolveWithEpsilon(x, kBezierEpsilon);
}
public readonly double SlopeWithEpsilon(double x, double epsilon)
{
x = MathExt.Clamp(x, 0.0, 1.0);
double t = SolveCurveX(x, epsilon);
double dx = SampleCurveDerivativeX(t);
double dy = SampleCurveDerivativeY(t);
return dy / dx;
}
public readonly double Slope(double x)
{
return SlopeWithEpsilon(x, kBezierEpsilon);
}
public readonly double RangeMin => range_min_;
public readonly double RangeMax => range_max_;
}
}

23
src/Avalonia.Base/Rendering/Composition/Utils/MathExt.cs

@ -0,0 +1,23 @@
using System;
namespace Avalonia.Rendering.Composition.Utils
{
static class MathExt
{
public static float Clamp(float value, float min, float max)
{
var amax = Math.Max(min, max);
var amin = Math.Min(min, max);
return Math.Min(Math.Max(value, amin), amax);
}
public static double Clamp(double value, double min, double max)
{
var amax = Math.Max(min, max);
var amin = Math.Min(min, max);
return Math.Min(Math.Max(value, amin), amax);
}
}
}

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

@ -0,0 +1,64 @@
using System.Numerics;
namespace Avalonia.Rendering.Composition
{
public abstract partial class CompositionVisual
{
private CompositionVisual? _parent;
private CompositionTarget? _root;
public CompositionVisual? Parent
{
get => _parent;
internal set
{
if (_parent == value)
return;
_parent = value;
Changes.Parent.Value = value?.Server;
Root = _parent?.Root;
}
}
// TODO: hide behind private-ish API
public CompositionTarget? Root
{
get => _root;
internal set
{
var changed = _root != value;
_root = value;
Changes.Root.Value = value?.Server;
if (changed)
OnRootChanged();
}
}
private protected virtual void OnRootChanged()
{
}
internal Matrix4x4? TryGetServerTransform()
{
if (Root == null)
return null;
var i = Root.Server.Readback;
ref var readback = ref Server.GetReadback(i.ReadIndex);
// CompositionVisual wasn't visible
if (readback.Revision < i.ReadRevision)
return null;
// CompositionVisual was reparented (potential race here)
if (readback.TargetId != Root.Server.Id)
return null;
return readback.Matrix;
}
internal object? Tag { get; set; }
internal virtual bool HitTest(Vector2 point) => true;
}
}

64
src/Avalonia.Base/Rendering/Composition/VisualCollection.cs

@ -0,0 +1,64 @@
using System;
using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition
{
public partial class CompositionVisualCollection : CompositionObject
{
private CompositionVisual _owner;
internal CompositionVisualCollection(CompositionVisual parent, ServerCompositionVisualCollection server) : base(parent.Compositor, server)
{
_owner = parent;
InitializeDefaults();
}
public void InsertAbove(CompositionVisual newChild, CompositionVisual sibling)
{
var idx = _list.IndexOf(sibling);
if (idx == -1)
throw new InvalidOperationException();
Insert(idx + 1, newChild);
}
public void InsertBelow(CompositionVisual newChild, CompositionVisual sibling)
{
var idx = _list.IndexOf(sibling);
if (idx == -1)
throw new InvalidOperationException();
Insert(idx, newChild);
}
public void InsertAtTop(CompositionVisual newChild) => Insert(_list.Count, newChild);
public void InsertAtBottom(CompositionVisual newChild) => Insert(0, newChild);
public void RemoveAll() => Clear();
partial void OnAdded(CompositionVisual item) => item.Parent = _owner;
partial void OnBeforeReplace(CompositionVisual oldItem, CompositionVisual newItem)
{
if (oldItem != newItem)
OnBeforeAdded(newItem);
}
partial void OnReplace(CompositionVisual oldItem, CompositionVisual newItem)
{
if (oldItem != newItem)
{
OnRemoved(oldItem);
OnAdded(newItem);
}
}
partial void OnRemoved(CompositionVisual item) => item.Parent = null;
partial void OnBeforeAdded(CompositionVisual item)
{
if (item.Parent != null)
throw new InvalidOperationException("Visual already has a parent");
}
}
}

6
src/Avalonia.Base/Rendering/IRenderer.cs

@ -1,6 +1,7 @@
using System;
using Avalonia.VisualTree;
using System.Collections.Generic;
using Avalonia.Rendering.Composition;
namespace Avalonia.Rendering
{
@ -87,4 +88,9 @@ namespace Avalonia.Rendering
/// </summary>
void Stop();
}
public interface IRendererWithCompositor : IRenderer
{
Compositor Compositor { get; }
}
}

1
src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Utilities;

11
src/Avalonia.Base/Size.cs

@ -52,6 +52,17 @@ namespace Avalonia
_width = width;
_height = height;
}
#if !BUILDTASK
/// <summary>
/// Initializes a new instance of the <see cref="Size"/> structure.
/// </summary>
/// <param name="vector2">The vector to take values from.</param>
public Size(System.Numerics.Vector2 vector2) : this(vector2.X, vector2.Y)
{
}
#endif
/// <summary>
/// Gets the aspect ratio of the size.

14
src/Avalonia.Base/Threading/DispatcherPriority.cs

@ -62,10 +62,20 @@ namespace Avalonia.Threading
/// </summary>
public static readonly DispatcherPriority Render = new(5);
/// <summary>
/// The job will be processed with the same priority as composition batch commit.
/// </summary>
public static readonly DispatcherPriority CompositionBatch = new(6);
/// <summary>
/// The job will be processed with the same priority as composition updates.
/// </summary>
public static readonly DispatcherPriority Composition = new(7);
/// <summary>
/// The job will be processed with the same priority as render.
/// </summary>
public static readonly DispatcherPriority Layout = new(6);
public static readonly DispatcherPriority Layout = new(8);
/// <summary>
/// The job will be processed with the same priority as data binding.
@ -75,7 +85,7 @@ namespace Avalonia.Threading
/// <summary>
/// The job will be processed before other asynchronous operations.
/// </summary>
public static readonly DispatcherPriority Send = new(7);
public static readonly DispatcherPriority Send = new(9);
/// <summary>
/// Maximum possible priority

25
src/Avalonia.Base/Visual.cs

@ -1,3 +1,7 @@
#nullable enable
using System;
using System.Collections;
using System.Collections.Specialized;
@ -8,11 +12,10 @@ using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Server;
using Avalonia.Utilities;
using Avalonia.VisualTree;
#nullable enable
namespace Avalonia
{
/// <summary>
@ -288,6 +291,8 @@ namespace Avalonia
/// </summary>
protected IRenderRoot? VisualRoot => _visualRoot ?? (this as IRenderRoot);
internal CompositionDrawListVisual? CompositionVisual { get; private set; }
/// <summary>
/// Gets a value indicating whether this control is attached to a visual root.
/// </summary>
@ -432,6 +437,10 @@ namespace Avalonia
}
EnableTransitions();
if (_visualRoot.Renderer is IRendererWithCompositor compositingRenderer)
{
AttachToCompositor(compositingRenderer.Compositor);
}
OnAttachedToVisualTree(e);
AttachedToVisualTree?.Invoke(this, e);
InvalidateVisual();
@ -452,6 +461,14 @@ namespace Avalonia
}
}
internal CompositionVisual AttachToCompositor(Compositor compositor)
{
if (CompositionVisual == null || CompositionVisual.Compositor != compositor)
CompositionVisual = new CompositionDrawListVisual(compositor,
new ServerCompositionDrawListVisual(compositor.Server), this);
return CompositionVisual;
}
/// <summary>
/// Calls the <see cref="OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs)"/> method
/// for this control and all of its visual descendants.
@ -564,7 +581,7 @@ namespace Avalonia
{
newValue.Changed += sender.RenderTransformChanged;
}
sender.InvalidateVisual();
}
}

70
src/Avalonia.Base/composition-schema.xml

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<NComposition>
<Using>System.Numerics</Using>
<Using>Avalonia.Rendering.Composition.Server</Using>
<Using>Avalonia.Rendering.Composition.Transport</Using>
<Using>Avalonia.Rendering.Composition.Animations</Using>
<Manual Name="CompositionContainerVisual" />
<Manual Name="ICompositionSurface" ServerName="ServerCompositionSurface" />
<Object Name="CompositionVisual" Abstract="true">
<Property Name="Visible" Type="bool" Animated="true" DefaultValue="true"/>
<Property Name="Opacity" Type="float" Animated="true" DefaultValue="1"/>
<Property Name="Clip" Type="Avalonia.Platform.IGeometryImpl?" />
<Property Name="ClipToBounds" Type="bool" Animated="true" DefaultValue="true"/>
<Property Name="Offset" Type="Vector3" Animated="true"/>
<Property Name="Size" Type="Vector2" Animated="true"/>
<Property Name="AnchorPoint" Type="Vector2" Animated="true"/>
<Property Name="CenterPoint" Type="Vector3" Animated="true"/>
<Property Name="RotationAngle" Type="float" Animated="true"/>
<Property Name="Orientation" Type="Quaternion" DefaultValue="Quaternion.Identity" Animated="true"/>
<Property Name="Scale" Type="Vector3" DefaultValue="new Vector3(1, 1, 1)" Animated="true"/>
<Property Name="TransformMatrix" Type="Matrix4x4" DefaultValue="Matrix4x4.Identity" Animated="true"/>
</Object>
<List Name="CompositionVisualCollection" ItemType="CompositionVisual" CustomCtor="true"/>
<Object Name="CompositionTarget" CustomServerCtor="true">
<Property Name="Root" Type="CompositionVisual?"/>
<Property Name="IsEnabled" Type="bool"/>
</Object>
<Object Name="CompositionSolidColorVisual" Inherits="CompositionContainerVisual" ChangesBase="CompositionVisual">
<Property Name="Color" Type="Avalonia.Media.Color" Animated="true"/>
</Object>
<Object Name="CompositionSpriteVisual" Inherits="CompositionContainerVisual" ChangesBase="CompositionVisual">
<Property Name="Brush" Type="CompositionBrush?"/>
</Object>
<Object Name="CompositionBrush" Abstract="true">
</Object>
<Object Name="CompositionColorGradientStop">
<Property Name="Color" Type="Avalonia.Media.Color" Animated="true"/>
<Property Name="Offset" Type="float" Animated="true"/>
</Object>
<List Name="CompositionGradientStopCollection" ItemType="CompositionColorGradientStop" />
<Brush Name="CompositionGradientBrush" Abstract="true" CustomCtor="true" CustomServerCtor="true">
<Property Name="ExtendMode" Type="CompositionGradientExtendMode"/>
</Brush>
<Brush Name="CompositionLinearGradientBrush" Inherits="CompositionGradientBrush" CustomUpdate="true">
<Property Name="StartPoint" Type="Vector2" Animated="true"/>
<Property Name="EndPoint" Type="Vector2" Animated="true"/>
</Brush>
<Brush Name="CompositionColorBrush">
<Property Name="Color" Type="Avalonia.Media.Color" Animated="true"/>
</Brush>
<Brush Name="CompositionSurfaceBrush">
<Property Name="Surface" Type="ICompositionSurface?"/>
<Property Name="Stretch" Type="CompositionStretch" DefaultValue="CompositionStretch.Fill" />
<Property Name="TileMode" Type="CompositionTileMode" />
</Brush>
<KeyFrameAnimation Name="Scalar" Type="float"/>
<KeyFrameAnimation Name="Boolean" Type="bool"/>
<KeyFrameAnimation Name="Color" Type="Avalonia.Media.Color"/>
<KeyFrameAnimation Type="Vector2"/>
<KeyFrameAnimation Type="Vector3"/>
<KeyFrameAnimation Type="Vector4"/>
<KeyFrameAnimation Type="Quaternion"/>
</NComposition>

2
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Runtime.InteropServices;
using Avalonia.Media;
using Avalonia.Platform;
@ -344,6 +345,7 @@ namespace Avalonia.Headless
}
public Matrix Transform { get; set; }
public void Clear(Color color)
{

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

@ -0,0 +1,21 @@
using System.IO;
using System.Xml.Serialization;
using Microsoft.CodeAnalysis;
namespace Avalonia.SourceGenerator.CompositionGenerator;
[Generator(LanguageNames.CSharp)]
public class CompositionRoslynGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
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(spc, config);
generator.Generate();
});
}
}

116
src/Avalonia.SourceGenerator/CompositionGenerator/Config.cs

@ -0,0 +1,116 @@
#nullable disable
using System.Collections.Generic;
using System.Xml.Serialization;
namespace Avalonia.SourceGenerator.CompositionGenerator
{
[XmlRoot("NComposition")]
public class GConfig
{
[XmlElement("Using")]
public List<GUsing> Usings { get; set; } = new List<GUsing>();
[XmlElement(typeof(GManualClass), ElementName = "Manual")]
public List<GManualClass> ManualClasses { get; set; } = new List<GManualClass>();
[XmlElement(typeof(GClass), ElementName = "Object")]
[XmlElement(typeof(GBrush), ElementName = "Brush")]
[XmlElement(typeof(GList), ElementName = "List")]
public List<GClass> Classes { get; set; } = new List<GClass>();
[XmlElement(typeof(GAnimationType), ElementName = "KeyFrameAnimation")]
public List<GAnimationType> KeyFrameAnimations { get; set; } = new List<GAnimationType>();
}
public class GUsing
{
[XmlText]
public string Name { get; set; }
}
public class GManualClass
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string ServerName { get; set; }
}
public class GImplements
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string ServerName { get; set; }
}
public class GClass
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Inherits { get; set; }
[XmlAttribute]
public string ChangesBase { get; set; }
[XmlAttribute]
public string ServerBase { get; set; }
[XmlAttribute]
public bool CustomCtor { get; set; }
[XmlAttribute]
public bool CustomServerCtor { get; set; }
[XmlElement(typeof(GImplements), ElementName = "Implements")]
public List<GImplements> Implements { get; set; } = new List<GImplements>();
[XmlAttribute]
public bool Abstract { get; set; }
[XmlElement(typeof(GProperty), ElementName = "Property")]
public List<GProperty> Properties { get; set; } = new List<GProperty>();
}
public class GBrush : GClass
{
[XmlAttribute]
public bool CustomUpdate { get; set; }
public GBrush()
{
Inherits = "CompositionBrush";
}
}
public class GList : GClass
{
[XmlAttribute]
public string ItemType { get; set; }
}
public class GProperty
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Type { get; set; }
[XmlAttribute]
public string DefaultValue { get; set; }
[XmlAttribute]
public bool Animated { get; set; }
}
public class GAnimationType
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Type { get; set; }
}
}

90
src/Avalonia.SourceGenerator/CompositionGenerator/Extensions.cs

@ -0,0 +1,90 @@
using System;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Avalonia.SourceGenerator.CompositionGenerator
{
public static class Extensions
{
public static ClassDeclarationSyntax AddModifiers(this ClassDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static MethodDeclarationSyntax AddModifiers(this MethodDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static PropertyDeclarationSyntax AddModifiers(this PropertyDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static ConstructorDeclarationSyntax AddModifiers(this ConstructorDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static AccessorDeclarationSyntax AddModifiers(this AccessorDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static string WithLowerFirst(this string s)
{
if (string.IsNullOrEmpty(s))
return s;
return char.ToLowerInvariant(s[0]) + s.Substring(1);
}
public static ExpressionSyntax MemberAccess(params string[] identifiers)
{
if (identifiers == null || identifiers.Length == 0)
throw new ArgumentException();
var expr = (ExpressionSyntax)IdentifierName(identifiers[0]);
for (var c = 1; c < identifiers.Length; c++)
expr = MemberAccess(expr, identifiers[c]);
return expr;
}
public static ExpressionSyntax MemberAccess(ExpressionSyntax expr, params string[] identifiers)
{
foreach (var i in identifiers)
expr = MemberAccess(expr, i);
return expr;
}
public static MemberAccessExpressionSyntax MemberAccess(ExpressionSyntax expr, string identifier) =>
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expr, IdentifierName(identifier));
public static ExpressionSyntax ConditionalMemberAccess(ExpressionSyntax expr, string member, bool checkNull)
{
if (checkNull)
return ConditionalAccessExpression(expr, MemberBindingExpression(IdentifierName(member)));
return MemberAccess(expr, member);
}
public static ClassDeclarationSyntax WithBaseType(this ClassDeclarationSyntax cl, string bt)
{
return cl.AddBaseListTypes(SimpleBaseType(SyntaxFactory.ParseTypeName(bt)));
}
public static string StripPrefix(this string s, string prefix) => string.IsNullOrEmpty(s)
? s
: s.StartsWith(prefix)
? s.Substring(prefix.Length)
: s;
}
}

59
src/Avalonia.SourceGenerator/CompositionGenerator/Generator.KeyFrameAnimation.cs

@ -0,0 +1,59 @@
namespace Avalonia.SourceGenerator.CompositionGenerator
{
public partial class Generator
{
void GenerateAnimations()
{
var code = $@"using System.Numerics;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Expressions;
namespace Avalonia.Rendering.Composition
{{
";
foreach (var a in _config.KeyFrameAnimations)
{
var name = a.Name ?? a.Type;
code += $@"
public class {name}KeyFrameAnimation : KeyFrameAnimation
{{
public {name}KeyFrameAnimation(Compositor compositor) : base(compositor)
{{
}}
internal override IAnimationInstance CreateInstance(Avalonia.Rendering.Composition.Server.ServerObject targetObject, ExpressionVariant? finalValue)
{{
return new KeyFrameAnimationInstance<{a.Type}>({name}Interpolator.Instance, _keyFrames.Snapshot(), CreateSnapshot(true),
finalValue?.CastOrDefault<{a.Type}>(), targetObject,
DelayBehavior, DelayTime, Direction, Duration, IterationBehavior,
IterationCount, StopBehavior);
}}
private KeyFrames<{a.Type}> _keyFrames = new KeyFrames<{a.Type}>();
private protected override IKeyFrames KeyFrames => _keyFrames;
public void InsertKeyFrame(float normalizedProgressKey, {a.Type} value, CompositionEasingFunction easingFunction)
{{
_keyFrames.Insert(normalizedProgressKey, value, easingFunction);
}}
public void InsertKeyFrame(float normalizedProgressKey, {a.Type} value)
{{
_keyFrames.Insert(normalizedProgressKey, value, Compositor.DefaultEasing);
}}
}}
public partial class Compositor
{{
public {name}KeyFrameAnimation Create{name}KeyFrameAnimation() => new {name}KeyFrameAnimation(this);
}}
";
}
code += "}";
_output.AddSource("CompositionAnimations.cs", code);
}
}
}

121
src/Avalonia.SourceGenerator/CompositionGenerator/Generator.ListProxy.cs

@ -0,0 +1,121 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Avalonia.SourceGenerator.CompositionGenerator
{
public partial class Generator
{
private const string ListProxyTemplate = @"
class Template
{
private ServerListProxyHelper<ItemTypeName, ServerItemTypeName> _list = null!;
ListChangeSet<ServerItemTypeName>
ServerListProxyHelper<ItemTypeName, ServerItemTypeName>.IGetChanges.
GetChanges() => Changes;
public List<ItemTypeName>.Enumerator GetEnumerator() => _list.GetEnumerator();
IEnumerator<ItemTypeName> IEnumerable<ItemTypeName>.GetEnumerator() =>
GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable) _list).GetEnumerator();
public void Add(ItemTypeName item)
{
OnBeforeAdded(item);
_list.Add(item);
OnAdded(item);
}
public void Clear()
{
OnBeforeClear();
_list.Clear();
OnClear();
}
public bool Contains(ItemTypeName item) => _list.Contains(item);
public void CopyTo(ItemTypeName[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
public bool Remove(ItemTypeName item)
{
var removed = _list.Remove(item);
if(removed)
OnRemoved(item);
return removed;
}
public int Count => _list.Count;
public bool IsReadOnly => _list.IsReadOnly;
public int IndexOf(ItemTypeName item) => _list.IndexOf(item);
public void Insert(int index, ItemTypeName item)
{
OnBeforeAdded(item);
_list.Insert(index, item);
OnAdded(item);
}
public void RemoveAt(int index)
{
var item = _list[index];
_list.RemoveAt(index);
OnRemoved(item);
}
public ItemTypeName this[int index]
{
get => _list[index];
set
{
var old = _list[index];
OnBeforeReplace(old, value);
_list[index] = value;
OnReplace(old, value);
}
}
partial void OnBeforeAdded(ItemTypeName item);
partial void OnAdded(ItemTypeName item);
partial void OnRemoved(ItemTypeName item);
partial void OnBeforeClear();
partial void OnBeforeReplace(ItemTypeName oldItem, ItemTypeName newItem);
partial void OnReplace(ItemTypeName oldItem, ItemTypeName newItem);
partial void OnClear();
}
";
private ClassDeclarationSyntax AppendListProxy(GList list, ClassDeclarationSyntax cl)
{
var itemType = list.ItemType;
var serverItemType = ServerName(itemType);
cl = cl.AddBaseListTypes(SimpleBaseType(
ParseTypeName("ServerListProxyHelper<" + itemType + ", " + serverItemType + ">.IGetChanges")),
SimpleBaseType(ParseTypeName("IList<" + itemType + ">"))
);
var code = ListProxyTemplate.Replace("ListTypeName", list.Name)
.Replace("ItemTypeName", itemType);
var parsed = ParseCompilationUnit(code);
var parsedClass = (ClassDeclarationSyntax)parsed.Members.First();
cl = cl.AddMembers(parsedClass.Members.ToArray());
var defs = cl.Members.OfType<MethodDeclarationSyntax>().First(m => m.Identifier.Text == "InitializeDefaults");
cl = cl.ReplaceNode(defs.Body, defs.Body.AddStatements(
ParseStatement($"_list = new ServerListProxyHelper<{itemType}, {serverItemType}>(this);")));
return cl;
}
}
}

66
src/Avalonia.SourceGenerator/CompositionGenerator/Generator.Utils.cs

@ -0,0 +1,66 @@
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Avalonia.SourceGenerator.CompositionGenerator
{
public partial class Generator
{
static void CleanDirectory(string path)
{
Directory.CreateDirectory(path);
Directory.Delete(path, true);
Directory.CreateDirectory(path);
}
CompilationUnitSyntax Unit()
=> CompilationUnit().WithUsings(List(new[]
{
"System",
"System.Text",
"System.Collections",
"System.Collections.Generic"
}
.Concat(_config.Usings
.Select(x => x.Name)).Select(u => UsingDirective(IdentifierName(u)))));
void SaveTo(CompilationUnitSyntax unit, params string[] path)
{
var text = @"
#nullable enable
#pragma warning disable CS0108, CS0114
" +
unit.NormalizeWhitespace().ToFullString();
_output.AddSource(string.Join("_", path), text);
}
SyntaxToken Semicolon() => Token(SyntaxKind.SemicolonToken);
FieldDeclarationSyntax DeclareConstant(string type, string name, LiteralExpressionSyntax value)
=> FieldDeclaration(
VariableDeclaration(ParseTypeName(type),
SingletonSeparatedList(
VariableDeclarator(name).WithInitializer(EqualsValueClause(value))
))
).WithSemicolonToken(Semicolon())
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.ConstKeyword)));
FieldDeclarationSyntax DeclareField(string type, string name, params SyntaxKind[] modifiers) =>
DeclareField(type, name, null, modifiers);
FieldDeclarationSyntax DeclareField(string type, string name, EqualsValueClauseSyntax initializer,
params SyntaxKind[] modifiers) =>
FieldDeclaration(
VariableDeclaration(ParseTypeName(type),
SingletonSeparatedList(
VariableDeclarator(name).WithInitializer(initializer))))
.WithSemicolonToken(Semicolon())
.WithModifiers(TokenList(modifiers.Select(x => Token(x))));
}
}

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

@ -0,0 +1,504 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Avalonia.SourceGenerator.CompositionGenerator.Extensions;
namespace Avalonia.SourceGenerator.CompositionGenerator
{
partial class Generator
{
private readonly SourceProductionContext _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)
{
_output = output;
_config = config;
_manuals = _config.ManualClasses.ToDictionary(x => x.Name);
_objects = new HashSet<string>(_config.ManualClasses.Select(x => x.Name)
.Concat(_config.Classes.Select(x => x.Name)));
_brushes = new HashSet<string>(_config.Classes.OfType<GBrush>().Select(x => x.Name)) {"CompositionBrush"};
}
public void Generate()
{
foreach (var cl in _config.Classes)
GenerateClass(cl);
GenerateAnimations();
}
string ServerName(string c) => c != null ? ("Server" + c) : "ServerObject";
string ChangesName(string c) => c != null ? (c + "Changes") : "ChangeSet";
void GenerateClass(GClass cl)
{
var list = cl as GList;
var unit = Unit();
var clientNs = NamespaceDeclaration(IdentifierName("Avalonia.Rendering.Composition"));
var serverNs = NamespaceDeclaration(IdentifierName("Avalonia.Rendering.Composition.Server"));
var transportNs = NamespaceDeclaration(IdentifierName("Avalonia.Rendering.Composition.Transport"));
var inherits = cl.Inherits ?? "CompositionObject";
var abstractModifier = cl.Abstract ? new[] {SyntaxKind.AbstractKeyword} : null;
var client = ClassDeclaration(cl.Name)
.AddModifiers(abstractModifier)
.AddModifiers(SyntaxKind.PublicKeyword, SyntaxKind.UnsafeKeyword, SyntaxKind.PartialKeyword)
.WithBaseType(inherits);
var serverName = ServerName(cl.Name);
var serverBase = cl.ServerBase ?? ServerName(cl.Inherits);
if (list != null)
serverBase = "ServerList<" + ServerName(list.ItemType) + ">";
var server = ClassDeclaration(serverName)
.AddModifiers(abstractModifier)
.AddModifiers(SyntaxKind.UnsafeKeyword, SyntaxKind.PartialKeyword)
.WithBaseType(serverBase);
string changesName = ChangesName(cl.Name);
var changesBase = ChangesName(cl.ChangesBase ?? cl.Inherits);
if (list != null)
changesBase = "ListChangeSet<" + ServerName(list.ItemType) + ">";
var changeSetPoolType = "ChangeSetPool<" + changesName + ">";
var transport = ClassDeclaration(changesName)
.AddModifiers(SyntaxKind.UnsafeKeyword, SyntaxKind.PartialKeyword)
.WithBaseType(changesBase)
.AddMembers(DeclareField(changeSetPoolType, "Pool",
EqualsValueClause(
ParseExpression($"new {changeSetPoolType}(pool => new {changesName}(pool))")
),
SyntaxKind.PublicKeyword,
SyntaxKind.StaticKeyword, SyntaxKind.ReadOnlyKeyword))
.AddMembers(ParseMemberDeclaration($"public {changesName}(IChangeSetPool pool) : base(pool){{}}"));
client = client
.AddMembers(
PropertyDeclaration(ParseTypeName("IChangeSetPool"), "ChangeSetPool")
.AddModifiers(SyntaxKind.PrivateKeyword, SyntaxKind.ProtectedKeyword,
SyntaxKind.OverrideKeyword)
.WithExpressionBody(
ArrowExpressionClause(MemberAccess(changesName, "Pool")))
.WithSemicolonToken(Semicolon()))
.AddMembers(PropertyDeclaration(ParseTypeName(changesName), "Changes")
.AddModifiers(SyntaxKind.PrivateKeyword, SyntaxKind.NewKeyword)
.WithExpressionBody(ArrowExpressionClause(CastExpression(ParseTypeName(changesName),
MemberAccess(BaseExpression(), "Changes"))))
.WithSemicolonToken(Semicolon()));
if (!cl.CustomCtor)
{
client = client.AddMembers(PropertyDeclaration(ParseTypeName(serverName), "Server")
.AddModifiers(SyntaxKind.InternalKeyword, SyntaxKind.NewKeyword)
.AddAccessorListAccessors(AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithSemicolonToken(Semicolon())));
client = client.AddMembers(
ConstructorDeclaration(cl.Name)
.AddModifiers(SyntaxKind.InternalKeyword)
.WithParameterList(ParameterList(SeparatedList(new[]
{
Parameter(Identifier("compositor")).WithType(ParseTypeName("Compositor")),
Parameter(Identifier("server")).WithType(ParseTypeName(serverName)),
})))
.WithInitializer(ConstructorInitializer(SyntaxKind.BaseConstructorInitializer,
ArgumentList(SeparatedList(new[]
{
Argument(IdentifierName("compositor")),
Argument(IdentifierName("server")),
})))).WithBody(Block(
ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
IdentifierName("Server"),
CastExpression(ParseTypeName(serverName), IdentifierName("server")))),
ExpressionStatement(InvocationExpression(IdentifierName("InitializeDefaults")))
)));
}
if (!cl.CustomServerCtor)
{
server = server.AddMembers(
ConstructorDeclaration(serverName)
.AddModifiers(SyntaxKind.InternalKeyword)
.WithParameterList(ParameterList(SeparatedList(new[]
{
Parameter(Identifier("compositor")).WithType(ParseTypeName("ServerCompositor")),
})))
.WithInitializer(ConstructorInitializer(SyntaxKind.BaseConstructorInitializer,
ArgumentList(SeparatedList(new[]
{
Argument(IdentifierName("compositor")),
})))).WithBody(Block()));
}
var changesVarName = "c";
var changesVar = IdentifierName(changesVarName);
server = server.AddMembers(
MethodDeclaration(ParseTypeName("void"), "ApplyChangesExtra")
.AddParameterListParameters(Parameter(Identifier("c")).WithType(ParseTypeName(changesName)))
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()));
transport = transport.AddMembers(
MethodDeclaration(ParseTypeName("void"), "ResetExtra")
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()));
var applyMethodBody = Block(
ExpressionStatement(InvocationExpression(MemberAccess(IdentifierName("base"), "ApplyCore"),
ArgumentList(SeparatedList(new[] {Argument(IdentifierName("changes"))})))),
LocalDeclarationStatement(VariableDeclaration(ParseTypeName("var"))
.WithVariables(SingletonSeparatedList(
VariableDeclarator(changesVarName)
.WithInitializer(EqualsValueClause(CastExpression(ParseTypeName(changesName),
IdentifierName("changes"))))))),
ExpressionStatement(InvocationExpression(IdentifierName("ApplyChangesExtra"))
.AddArgumentListArguments(Argument(IdentifierName("c"))))
);
var resetBody = Block();
var startAnimationBody = Block();
var getPropertyBody = Block();
var serverGetPropertyBody = Block();
var defaultsMethodBody = Block();
foreach (var prop in cl.Properties)
{
var fieldName = "_" + prop.Name.WithLowerFirst();
var propType = ParseTypeName(prop.Type);
var filteredPropertyType = prop.Type.TrimEnd('?');
var isObject = _objects.Contains(filteredPropertyType);
var isNullable = prop.Type.EndsWith("?");
client = client
.AddMembers(DeclareField(prop.Type, fieldName))
.AddMembers(PropertyDeclaration(propType, prop.Name)
.AddModifiers(SyntaxKind.PublicKeyword)
.AddAccessorListAccessors(
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration,
Block(ReturnStatement(IdentifierName(fieldName)))),
AccessorDeclaration(SyntaxKind.SetAccessorDeclaration,
Block(
ParseStatement("var changed = false;"),
IfStatement(BinaryExpression(SyntaxKind.NotEqualsExpression,
IdentifierName(fieldName),
IdentifierName("value")),
Block(
ParseStatement("On" + prop.Name + "Changing();"),
ParseStatement("changed = true;"),
GeneratePropertySetterAssignment(prop, fieldName, isObject, isNullable))
),
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
IdentifierName(fieldName), IdentifierName("value"))),
ParseStatement($"if(changed) On" + prop.Name + "Changed();")
))
))
.AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changed")
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()))
.AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changing")
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()));
var animatedServer = prop.Animated;
var serverPropertyType = ((isObject ? "Server" : "") + prop.Type);
if (_manuals.TryGetValue(filteredPropertyType, out var manual) && manual.ServerName != null)
serverPropertyType = manual.ServerName + (isNullable ? "?" : "");
transport = transport
.AddMembers(DeclareField((animatedServer ? "Animated" : "") + "Change<" + serverPropertyType + ">",
prop.Name, SyntaxKind.PublicKeyword));
if (animatedServer)
server = server.AddMembers(
DeclareField("AnimatedValueStore<" + serverPropertyType + ">", fieldName),
PropertyDeclaration(ParseTypeName(serverPropertyType), prop.Name)
.AddModifiers(SyntaxKind.PublicKeyword)
.WithExpressionBody(ArrowExpressionClause(
InvocationExpression(MemberAccess(fieldName, "GetAnimated"),
ArgumentList(SingletonSeparatedList(Argument(IdentifierName("Compositor")))))))
.WithSemicolonToken(Semicolon())
);
else
{
server = server
.AddMembers(DeclareField(serverPropertyType, fieldName))
.AddMembers(PropertyDeclaration(ParseTypeName(serverPropertyType), prop.Name)
.AddModifiers(SyntaxKind.PublicKeyword)
.AddAccessorListAccessors(
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration,
Block(ReturnStatement(IdentifierName(fieldName)))),
AccessorDeclaration(SyntaxKind.SetAccessorDeclaration,
Block(
ParseStatement("var changed = false;"),
IfStatement(BinaryExpression(SyntaxKind.NotEqualsExpression,
IdentifierName(fieldName),
IdentifierName("value")),
Block(
ParseStatement("On" + prop.Name + "Changing();"),
ParseStatement($"changed = true;"))
),
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
IdentifierName(fieldName), IdentifierName("value"))),
ParseStatement($"if(changed) On" + prop.Name + "Changed();")
))
))
.AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changed")
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()))
.AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changing")
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()));
}
if (animatedServer)
applyMethodBody = applyMethodBody.AddStatements(
IfStatement(MemberAccess(changesVar, prop.Name, "IsValue"),
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
IdentifierName(fieldName), MemberAccess(changesVar, prop.Name, "Value")))),
IfStatement(MemberAccess(changesVar, prop.Name, "IsAnimation"),
ExpressionStatement(
InvocationExpression(MemberAccess(fieldName, "SetAnimation"),
ArgumentList(SeparatedList(new[]
{
Argument(changesVar),
Argument(MemberAccess(changesVar, prop.Name, "Animation"))
})))))
);
else
applyMethodBody = applyMethodBody.AddStatements(
IfStatement(MemberAccess(changesVar, prop.Name, "IsSet"),
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
IdentifierName(prop.Name), MemberAccess(changesVar, prop.Name, "Value"))))
);
resetBody = resetBody.AddStatements(
ExpressionStatement(InvocationExpression(MemberAccess(prop.Name, "Reset"))));
if (animatedServer)
startAnimationBody = ApplyStartAnimation(startAnimationBody, prop, fieldName);
getPropertyBody = ApplyGetProperty(getPropertyBody, prop);
serverGetPropertyBody = ApplyGetProperty(getPropertyBody, prop);
if (prop.DefaultValue != null)
{
defaultsMethodBody = defaultsMethodBody.AddStatements(
ExpressionStatement(
AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
IdentifierName(prop.Name), ParseExpression(prop.DefaultValue))));
}
}
if (cl is GBrush brush && !cl.Abstract)
{
var brushName = brush.Name.StripPrefix("Composition");
/*
server = server.AddMembers(
MethodDeclaration(ParseTypeName("ICbBrush"), "CreateBackendBrush")
.AddModifiers(SyntaxKind.ProtectedKeyword, SyntaxKind.OverrideKeyword)
.WithExpressionBody(ArrowExpressionClause(
InvocationExpression(MemberAccess("Compositor", "Backend", "Create" + brushName))
)).WithSemicolonToken(Semicolon())
);
if (!brush.CustomUpdate)
server = server.AddMembers(
MethodDeclaration(ParseTypeName("void"), "UpdateBackendBrush")
.AddModifiers(SyntaxKind.ProtectedKeyword, SyntaxKind.OverrideKeyword)
.AddParameterListParameters(Parameter(Identifier("brush"))
.WithType(ParseTypeName("ICbBrush")))
.AddBodyStatements(
ExpressionStatement(
InvocationExpression(
MemberAccess(
ParenthesizedExpression(
CastExpression(ParseTypeName("ICb" + brushName), IdentifierName("brush"))), "Update"),
ArgumentList(SeparatedList(cl.Properties.Select(x =>
{
if(x.Type.TrimEnd('?') == "ICompositionSurface")
return Argument(
ConditionalAccessExpression(IdentifierName(x.Name),
MemberBindingExpression(IdentifierName("BackendSurface")))
);
if (_brushes.Contains(x.Type))
return Argument(
ConditionalAccessExpression(IdentifierName(x.Name),
MemberBindingExpression(IdentifierName("Brush")))
);
return Argument(IdentifierName(x.Name));
}))))
)));
*/
}
server = server.AddMembers(
MethodDeclaration(ParseTypeName("void"), "ApplyCore")
.AddModifiers(SyntaxKind.ProtectedKeyword, SyntaxKind.OverrideKeyword)
.AddParameterListParameters(
Parameter(Identifier("changes")).WithType(ParseTypeName("ChangeSet")))
.WithBody(applyMethodBody));
client = client.AddMembers(
MethodDeclaration(ParseTypeName("void"), "InitializeDefaults").WithBody(defaultsMethodBody));
transport = transport.AddMembers(MethodDeclaration(ParseTypeName("void"), "Reset")
.AddModifiers(SyntaxKind.PublicKeyword, SyntaxKind.OverrideKeyword)
.WithBody(resetBody.AddStatements(
ExpressionStatement(InvocationExpression(IdentifierName("ResetExtra"))),
ExpressionStatement(InvocationExpression(MemberAccess("base", "Reset"))))));
if (list != null)
client = AppendListProxy(list, client);
if (startAnimationBody.Statements.Count != 0)
client = WithStartAnimation(client, startAnimationBody);
client = WithGetProperty(client, getPropertyBody, false);
server = WithGetProperty(server, serverGetPropertyBody, true);
if(cl.Implements.Count > 0)
foreach (var impl in cl.Implements)
{
client = client.WithBaseList(client.BaseList.AddTypes(SimpleBaseType(ParseTypeName(impl.Name))));
if (impl.ServerName != null)
server = server.WithBaseList(
server.BaseList.AddTypes(SimpleBaseType(ParseTypeName(impl.ServerName))));
client = client.AddMembers(
ParseMemberDeclaration($"{impl.ServerName} {impl.Name}.Server => Server;"));
}
SaveTo(unit.AddMembers(clientNs.AddMembers(client)),
cl.Name + ".generated.cs");
SaveTo(unit.AddMembers(serverNs.AddMembers(server)),
"Server", "Server" + cl.Name + ".generated.cs");
SaveTo(unit.AddMembers(transportNs.AddMembers(transport)),
"Transport", cl.Name + "Changes.generated.cs");
}
StatementSyntax GeneratePropertySetterAssignment(GProperty prop, string fieldName, bool isObject, bool isNullable)
{
var normalChangesAssignment = (StatementSyntax)ExpressionStatement(AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
MemberAccess((ExpressionSyntax) IdentifierName("Changes"), prop.Name,
"Value"),
isObject
?
ConditionalMemberAccess(IdentifierName("value"), "Server", isNullable)
: IdentifierName("value")));
if (!prop.Animated)
return normalChangesAssignment;
var code = $@"
{{
if(animation is CompositionAnimation a)
Changes.{prop.Name}.Animation = a.CreateInstance(this.Server, value);
else
{{
var saved = Changes.{prop.Name};
if(!StartAnimationGroup(animation, ""{prop.Name}"", value))
Changes.{prop.Name}.Value = value;
}}
}}
";
return IfStatement(
ParseExpression(
$"ImplicitAnimations != null && ImplicitAnimations.TryGetValue(\"{prop.Name}\", out var animation) == true"),
ParseStatement(code),
ElseClause(normalChangesAssignment)
);
}
BlockSyntax ApplyStartAnimation(BlockSyntax body, GProperty prop, string fieldName)
{
var code = $@"
if (propertyName == ""{prop.Name}"")
{{
var current = {fieldName};
var server = animation.CreateInstance(this.Server, finalValue);
Changes.{prop.Name}.Animation = server;
return;
}}
";
return body.AddStatements(ParseStatement(code));
}
private static HashSet<string> VariantPropertyTypes = new HashSet<string>
{
"bool",
"float",
"Vector2",
"Vector3",
"Vector4",
"Matrix3x2",
"Matrix4x4",
"Quaternion",
"CompositionColor"
};
BlockSyntax ApplyGetProperty(BlockSyntax body, GProperty prop)
{
if (VariantPropertyTypes.Contains(prop.Type))
return body.AddStatements(
ParseStatement($"if(name == \"{prop.Name}\")\n return {prop.Name};\n")
);
return body;
}
ClassDeclarationSyntax WithGetProperty(ClassDeclarationSyntax cl, BlockSyntax body, bool server)
{
if (body.Statements.Count == 0)
return cl;
body = body.AddStatements(
ParseStatement("return base.GetPropertyForAnimation(name);"));
var method = ((MethodDeclarationSyntax) ParseMemberDeclaration(
$"{(server ? "public" : "internal")} override Avalonia.Rendering.Composition.Expressions.ExpressionVariant GetPropertyForAnimation(string name){{}}"))
.WithBody(body);
return cl.AddMembers(method);
}
ClassDeclarationSyntax WithStartAnimation(ClassDeclarationSyntax cl, BlockSyntax body)
{
body = body.AddStatements(
ExpressionStatement(InvocationExpression(MemberAccess("base", "StartAnimation"),
ArgumentList(SeparatedList(new[]
{
Argument(IdentifierName("propertyName")),
Argument(IdentifierName("animation")),
Argument(IdentifierName("finalValue")),
}))))
);
return cl.AddMembers(
((MethodDeclarationSyntax) ParseMemberDeclaration(
"internal override void StartAnimation(string propertyName, CompositionAnimation animation, Avalonia.Rendering.Composition.Expressions.ExpressionVariant? finalValue){}"))
.WithBody(body));
}
}
}

8
src/Avalonia.X11/X11Platform.cs

@ -13,6 +13,7 @@ using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.X11;
using Avalonia.X11.Glx;
using Avalonia.X11.NativeDialogs;
@ -29,6 +30,7 @@ namespace Avalonia.X11
public XI2Manager XI2;
public X11Info Info { get; private set; }
public IX11Screens X11Screens { get; private set; }
public Compositor Compositor { get; private set; }
public IScreenImpl Screens { get; private set; }
public X11PlatformOptions Options { get; private set; }
public IntPtr OrphanedWindow { get; private set; }
@ -101,7 +103,9 @@ namespace Avalonia.X11
GlxPlatformOpenGlInterface.TryInitialize(Info, Options.GlProfiles);
}
if (options.UseCompositor)
Compositor = Compositor.Create(AvaloniaLocator.Current.GetService<IRenderLoop>()!);
}
public IntPtr DeferredDisplay { get; set; }
@ -222,6 +226,8 @@ namespace Avalonia
/// Immediate re-renders the whole scene when some element is changed on the scene. Deferred re-renders only changed elements.
/// </remarks>
public bool UseDeferredRendering { get; set; } = true;
public bool UseCompositor { get; set; }
/// <summary>
/// Determines whether to use IME.

17
src/Avalonia.X11/X11Window.cs

@ -18,6 +18,7 @@ using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using Avalonia.X11.Glx;
using static Avalonia.X11.XLib;
@ -360,13 +361,15 @@ namespace Avalonia.X11
if (customRendererFactory != null)
return customRendererFactory.Create(root, loop);
return _platform.Options.UseDeferredRendering ?
new DeferredRenderer(root, loop)
{
RenderOnlyOnRenderThread = true
} :
(IRenderer)new X11ImmediateRendererProxy(root, loop);
return _platform.Options.UseDeferredRendering
? _platform.Options.UseCompositor
? new CompositingRenderer(root, this._platform.Compositor)
: new DeferredRenderer(root, loop)
{
RenderOnlyOnRenderThread = true
}
: (IRenderer)new X11ImmediateRendererProxy(root, loop);
}
void OnEvent(ref XEvent ev)

7
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
@ -73,6 +74,12 @@ namespace Avalonia.Direct2D1.Media
set { _deviceContext.Transform = value.ToDirect2D(); }
}
public Matrix4x4 Transform4x4
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
/// <inheritdoc/>
public void Clear(Color color)
{

Loading…
Cancel
Save