Browse Source

Some docs and refactorings

pull/8105/head
Nikita Tsukanov 4 years ago
parent
commit
27e87857af
  1. 6
      samples/ControlCatalog/Pages/CompositionPage.axaml.cs
  2. 8
      src/Avalonia.Base/Animation/Easings/CubicBezier.cs
  3. 29
      src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs
  4. 6
      src/Avalonia.Base/Avalonia.Base.csproj
  5. 20
      src/Avalonia.Base/Rendering/Composition/Animations/AnimatedValueStore.cs
  6. 5
      src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs
  7. 109
      src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
  8. 19
      src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs
  9. 4
      src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs
  10. 3
      src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs
  11. 11
      src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs
  12. 3
      src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs
  13. 111
      src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs
  14. 16
      src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs
  15. 27
      src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs
  16. 3
      src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs
  17. 29
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  18. 11
      src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
  19. 95
      src/Avalonia.Base/Rendering/Composition/CompositionEasingFunction.cs
  20. 16
      src/Avalonia.Base/Rendering/Composition/CompositionGradientBrush.cs
  21. 21
      src/Avalonia.Base/Rendering/Composition/CompositionObject.cs
  22. 9
      src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs
  23. 19
      src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
  24. 100
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  25. 20
      src/Avalonia.Base/Rendering/Composition/CompositorRenderLoopTask.cs
  26. 3
      src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs
  27. 6
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs
  28. 3
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
  29. 10
      src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
  30. 17
      src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs
  31. 3
      src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs
  32. 3
      src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs
  33. 3
      src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs
  34. 3
      src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs
  35. 9
      src/Avalonia.Base/Rendering/Composition/ICompositionSurface.cs
  36. 7
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  37. 4
      src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
  38. 5
      src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs
  39. 7
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionBrush.cs
  40. 4
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
  41. 15
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionGradientBrush.cs
  42. 5
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  43. 6
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
  44. 5
      src/Avalonia.Base/Rendering/Composition/Server/ServerContainerVisual.cs
  45. 4
      src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs
  46. 8
      src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs
  47. 15
      src/Avalonia.Base/Rendering/Composition/Server/ServerSolidColorVisual.cs
  48. 20
      src/Avalonia.Base/Rendering/Composition/Server/ServerSpriteVisual.cs
  49. 17
      src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs
  50. 3
      src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs
  51. 6
      src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs
  52. 5
      src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs
  53. 23
      src/Avalonia.Base/Rendering/Composition/Utils/MathExt.cs
  54. 3
      src/Avalonia.Base/Rendering/Composition/Visual.cs
  55. 3
      src/Avalonia.Base/Rendering/Composition/VisualCollection.cs
  56. 14
      src/Avalonia.Base/Utilities/MathUtilities.cs
  57. 3
      src/Avalonia.Base/Utilities/RefTrackingDictionary.cs
  58. 35
      src/Avalonia.Base/composition-schema.xml
  59. 3
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs
  60. 2
      src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs

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

@ -111,7 +111,7 @@ public partial class CompositionPage : UserControl
{ {
if (_implicitAnimations == null) if (_implicitAnimations == null)
{ {
var compositor = ElementCompositionPreview.GetElementVisual(this)!.Compositor; var compositor = ElementComposition.GetElementVisual(this)!.Compositor;
var offsetAnimation = compositor.CreateVector3KeyFrameAnimation(); var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
offsetAnimation.Target = "Offset"; offsetAnimation.Target = "Offset";
@ -143,11 +143,11 @@ public partial class CompositionPage : UserControl
return; return;
} }
if (ElementCompositionPreview.GetElementVisual(page) == null) if (ElementComposition.GetElementVisual(page) == null)
return; return;
page.EnsureImplicitAnimations(); page.EnsureImplicitAnimations();
ElementCompositionPreview.GetElementVisual((Visual)border.GetVisualParent()).ImplicitAnimations = ElementComposition.GetElementVisual((Visual)border.GetVisualParent()).ImplicitAnimations =
page._implicitAnimations; page._implicitAnimations;
} }

8
src/Avalonia.Base/Rendering/Composition/Utils/CubicBezier.cs → src/Avalonia.Base/Animation/Easings/CubicBezier.cs

@ -2,6 +2,7 @@
// Ported from Chromium project https://github.com/chromium/chromium/blob/374d31b7704475fa59f7b2cb836b3b68afdc3d79/ui/gfx/geometry/cubic_bezier.cc // Ported from Chromium project https://github.com/chromium/chromium/blob/374d31b7704475fa59f7b2cb836b3b68afdc3d79/ui/gfx/geometry/cubic_bezier.cc
using System; using System;
using Avalonia.Utilities;
// ReSharper disable CompareOfFloatsByEqualityOperator // ReSharper disable CompareOfFloatsByEqualityOperator
// ReSharper disable CommentTypo // ReSharper disable CommentTypo
@ -10,8 +11,11 @@ using System;
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
#pragma warning disable 649 #pragma warning disable 649
namespace Avalonia.Rendering.Composition.Utils namespace Avalonia.Animation.Easings
{ {
/// <summary>
/// Represents a cubic bezier curve and can compute Y coordinate for a given X
/// </summary>
internal unsafe struct CubicBezier internal unsafe struct CubicBezier
{ {
const int CUBIC_BEZIER_SPLINE_SAMPLES = 11; const int CUBIC_BEZIER_SPLINE_SAMPLES = 11;
@ -284,7 +288,7 @@ namespace Avalonia.Rendering.Composition.Utils
public readonly double SlopeWithEpsilon(double x, double epsilon) public readonly double SlopeWithEpsilon(double x, double epsilon)
{ {
x = MathExt.Clamp(x, 0.0, 1.0); x = MathUtilities.Clamp(x, 0.0, 1.0);
double t = SolveCurveX(x, epsilon); double t = SolveCurveX(x, epsilon);
double dx = SampleCurveDerivativeX(t); double dx = SampleCurveDerivativeX(t);
double dy = SampleCurveDerivativeY(t); double dy = SampleCurveDerivativeY(t);

29
src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs

@ -0,0 +1,29 @@
using System;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Utils;
namespace Avalonia.Animation.Easings;
public class CubicBezierEasing : IEasing
{
private CubicBezier _bezier;
//cubic-bezier(0.25, 0.1, 0.25, 1.0)
internal CubicBezierEasing(Point controlPoint1, Point controlPoint2)
{
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);
}
public Point ControlPoint2 { get; set; }
public Point ControlPoint1 { get; set; }
internal static IEasing Ease { get; } = new CubicBezierEasing(new Point(0.25, 0.1), new Point(0.25, 1));
double IEasing.Ease(double progress)
{
return _bezier.Solve(progress);
}
}

6
src/Avalonia.Base/Avalonia.Base.csproj

@ -35,6 +35,10 @@
<InternalsVisibleTo Include="Avalonia.Skia.UnitTests, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Skia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.UnitTests, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Web.Blazor, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.Web.Blazor, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"/> <InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
</ItemGroup>
<ItemGroup>
<Folder Include="Rendering\Composition\Utils" />
</ItemGroup> </ItemGroup>
</Project> </Project>

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

@ -7,10 +7,15 @@ using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary>
/// This is the first element of both animated and non-animated value stores.
/// It's used to propagate property invalidation to subscribers
/// </summary>
internal struct ServerObjectSubscriptionStore internal struct ServerObjectSubscriptionStore
{ {
public bool IsValid; public bool IsValid;
public RefTrackingDictionary<IAnimationInstance> Subscribers; public RefTrackingDictionary<IAnimationInstance>? Subscribers;
public void Invalidate() public void Invalidate()
{ {
@ -23,6 +28,10 @@ namespace Avalonia.Rendering.Composition.Animations
} }
} }
/// <summary>
/// The value store for non-animated values that can still be referenced by animations.
/// Simply stores the value and notifies subscribers
/// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal struct ServerValueStore<T> internal struct ServerValueStore<T>
{ {
@ -43,7 +52,12 @@ namespace Avalonia.Rendering.Composition.Animations
} }
} }
} }
/// <summary>
/// Value store for potentially animated values. Can hold both direct value and animation instance.
/// Is also responsible for activating/deactivating the animation when container object is activated/deactivated
/// </summary>
/// <typeparam name="T"></typeparam>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal struct ServerAnimatedValueStore<T> where T : struct internal struct ServerAnimatedValueStore<T> where T : struct
{ {
@ -53,8 +67,6 @@ namespace Avalonia.Rendering.Composition.Animations
private T _direct; private T _direct;
private T? _lastAnimated; private T? _lastAnimated;
public T Direct => _direct;
public T GetAnimated(ServerCompositor compositor) public T GetAnimated(ServerCompositor compositor)
{ {
Subscriptions.IsValid = true; Subscriptions.IsValid = true;

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

@ -5,6 +5,11 @@ using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Animations; namespace Avalonia.Rendering.Composition.Animations;
/// <summary>
/// The base class for both key-frame and expression animation instances
/// Is responsible for activation tracking and for subscribing to properties used in dependencies
/// </summary>
internal abstract class AnimationInstanceBase : IAnimationInstance internal abstract class AnimationInstanceBase : IAnimationInstance
{ {
private List<(ServerObject obj, int member)>? _trackedObjects; private List<(ServerObject obj, int member)>? _trackedObjects;

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

@ -10,51 +10,66 @@ using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
public abstract class CompositionAnimation : CompositionObject, ICompositionAnimationBase /// <summary>
{ /// This is the base class for ExpressionAnimation and KeyFrameAnimation.
private readonly CompositionPropertySet _propertySet; /// </summary>
internal CompositionAnimation(Compositor compositor) : base(compositor, null!) /// <remarks>
{ /// Use the <see cref="CompositionObject.StartAnimation"/> method to start the animation.
_propertySet = new CompositionPropertySet(compositor); /// Value parameters (as opposed to reference parameters which are set using <see cref="SetReferenceParameter"/>)
} /// are copied and "embedded" into an expression at the time CompositionObject.StartAnimation is called.
/// Changing the value of the variable after <see cref="CompositionObject.StartAnimation"/> is called will not affect
public void ClearAllParameters() => _propertySet.ClearAll(); /// the value of the ExpressionAnimation.
/// See the remarks section of ExpressionAnimation for additional information.
public void ClearParameter(string key) => _propertySet.Clear(key); /// </remarks>
public abstract class CompositionAnimation : CompositionObject, ICompositionAnimationBase
void SetVariant(string key, ExpressionVariant value) => _propertySet.Set(key, value); {
private readonly CompositionPropertySet _propertySet;
public void SetColorParameter(string key, Avalonia.Media.Color value) => SetVariant(key, value); internal CompositionAnimation(Compositor compositor) : base(compositor, null!)
{
public void SetMatrix3x2Parameter(string key, Matrix3x2 value) => SetVariant(key, value); _propertySet = new CompositionPropertySet(compositor);
}
public void SetMatrix4x4Parameter(string key, Matrix4x4 value) => SetVariant(key, value);
/// <summary>
public void SetQuaternionParameter(string key, Quaternion value) => SetVariant(key, value); /// Clears all of the parameters of the animation.
/// </summary>
public void SetReferenceParameter(string key, CompositionObject compositionObject) => public void ClearAllParameters() => _propertySet.ClearAll();
_propertySet.Set(key, compositionObject);
/// <summary>
public void SetScalarParameter(string key, float value) => SetVariant(key, value); /// Clears a parameter from the animation.
/// </summary>
public void SetVector2Parameter(string key, Vector2 value) => SetVariant(key, value); public void ClearParameter(string key) => _propertySet.Clear(key);
public void SetVector3Parameter(string key, Vector3 value) => SetVariant(key, value); void SetVariant(string key, ExpressionVariant value) => _propertySet.Set(key, value);
public void SetVector4Parameter(string key, Vector4 value) => SetVariant(key, value); public void SetColorParameter(string key, Media.Color value) => SetVariant(key, value);
// TODO: void SetExpressionReferenceParameter(string parameterName, IAnimationObject source) public void SetMatrix3x2Parameter(string key, Matrix3x2 value) => SetVariant(key, value);
public string? Target { get; set; } public void SetMatrix4x4Parameter(string key, Matrix4x4 value) => SetVariant(key, value);
internal abstract IAnimationInstance CreateInstance(ServerObject targetObject, public void SetQuaternionParameter(string key, Quaternion value) => SetVariant(key, value);
ExpressionVariant? finalValue);
public void SetReferenceParameter(string key, CompositionObject compositionObject) =>
internal PropertySetSnapshot CreateSnapshot() => _propertySet.Snapshot(); _propertySet.Set(key, compositionObject);
void ICompositionAnimationBase.InternalOnly() 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);
public string? Target { get; set; }
internal abstract IAnimationInstance CreateInstance(ServerObject targetObject,
ExpressionVariant? finalValue);
internal PropertySetSnapshot CreateSnapshot() => _propertySet.Snapshot();
void ICompositionAnimationBase.InternalOnly()
{
}
}
} }

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

@ -5,6 +5,17 @@ using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary>
/// A Composition Animation that uses a mathematical equation to calculate the value for an animating property every frame.
/// </summary>
/// <remarks>
/// The core of ExpressionAnimations allows a developer to define a mathematical equation that can be used to calculate the value
/// of a targeted animating property each frame.
/// This contrasts <see cref="KeyFrameAnimation"/>s, which use an interpolator to define how the animating
/// property changes over time. The mathematical equation can be defined using references to properties
/// of Composition objects, mathematical functions and operators and Input.
/// Use the <see cref="CompositionObject.StartAnimation"/> method to start the animation.
/// </remarks>
public class ExpressionAnimation : CompositionAnimation public class ExpressionAnimation : CompositionAnimation
{ {
private string? _expression; private string? _expression;
@ -14,6 +25,14 @@ namespace Avalonia.Rendering.Composition.Animations
{ {
} }
/// <summary>
/// The mathematical equation specifying how the animated value is calculated each frame.
/// The Expression is the core of an <see cref="ExpressionAnimation"/> and represents the equation
/// the system will use to calculate the value of the animation property each frame.
/// The equation is set on this property in the form of a string.
/// Although expressions can be defined by simple mathematical equations such as "2+2",
/// the real power lies in creating mathematical relationships where the input values can change frame over frame.
/// </summary>
public string? Expression public string? Expression
{ {
get => _expression; get => _expression;

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

@ -5,6 +5,10 @@ using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary>
/// Server-side counterpart of <see cref="ExpressionAnimation"/> with values baked-in.
/// </summary>
internal class ExpressionAnimationInstance : AnimationInstanceBase, IAnimationInstance internal class ExpressionAnimationInstance : AnimationInstanceBase, IAnimationInstance
{ {
private readonly Expression _expression; private readonly Expression _expression;

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

@ -4,6 +4,9 @@ using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary>
/// Base class for composition animations.
/// </summary>
public interface ICompositionAnimationBase public interface ICompositionAnimationBase
{ {
internal void InternalOnly(); internal void InternalOnly();

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

@ -6,6 +6,17 @@ using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary>
/// A collection of animations triggered when a condition is met.
/// </summary>
/// <remarks>
/// Implicit animations let you drive animations by specifying trigger conditions rather than requiring the manual definition of animation behavior.
/// They help decouple animation start logic from core app logic. You define animations and the events that should trigger these animations.
/// Currently the only available trigger is animated property change.
///
/// When expression is used in ImplicitAnimationCollection a special keyword `this.FinalValue` will represent
/// the final value of the animated property that was changed
/// </remarks>
public class ImplicitAnimationCollection : CompositionObject, IDictionary<string, ICompositionAnimationBase> public class ImplicitAnimationCollection : CompositionObject, IDictionary<string, ICompositionAnimationBase>
{ {
private Dictionary<string, ICompositionAnimationBase> _inner = new Dictionary<string, ICompositionAnimationBase>(); private Dictionary<string, ICompositionAnimationBase> _inner = new Dictionary<string, ICompositionAnimationBase>();

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

@ -3,6 +3,9 @@ using System.Numerics;
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary>
/// An interface to define interpolation logic for a particular type
/// </summary>
internal interface IInterpolator<T> internal interface IInterpolator<T>
{ {
T Interpolate(T from, T to, float progress); T Interpolate(T from, T to, float progress);

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

@ -1,53 +1,134 @@
using System;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary>
/// A time-based animation with one or more key frames.
/// These frames are markers, allowing developers to specify values at specific times for the animating property.
/// KeyFrame animations can be further customized by specifying how the animation interpolates between keyframes.
/// </summary>
public abstract class KeyFrameAnimation : CompositionAnimation public abstract class KeyFrameAnimation : CompositionAnimation
{ {
private TimeSpan _duration = TimeSpan.FromMilliseconds(1);
internal KeyFrameAnimation(Compositor compositor) : base(compositor) internal KeyFrameAnimation(Compositor compositor) : base(compositor)
{ {
} }
/// <summary>
/// The delay behavior of the key frame animation.
/// </summary>
public AnimationDelayBehavior DelayBehavior { get; set; } public AnimationDelayBehavior DelayBehavior { get; set; }
/// <summary>
/// Delay before the animation starts after <see cref="CompositionObject.StartAnimation"/> is called.
/// </summary>
public System.TimeSpan DelayTime { get; set; } public System.TimeSpan DelayTime { get; set; }
public AnimationDirection Direction { get; set; }
public System.TimeSpan Duration { get; set; } /// <summary>
/// The direction the animation is playing.
/// The Direction property allows you to drive your animation from start to end or end to start or alternate
/// between start and end or end to start if animation has an <see cref="IterationCount"/> greater than one.
/// This gives an easy way for customizing animation definitions.
/// </summary>
public PlaybackDirection Direction { get; set; }
/// <summary>
/// The duration of the animation.
/// Minimum allowed value is 1ms and maximum allowed value is 24 days.
/// </summary>
public TimeSpan Duration
{
get => _duration;
set
{
if (_duration < TimeSpan.FromMilliseconds(1) || _duration > TimeSpan.FromDays(1))
throw new ArgumentException("Minimum allowed value is 1ms and maximum allowed value is 24 days.");
_duration = value;
}
}
/// <summary>
/// The iteration behavior for the key frame animation.
/// </summary>
public AnimationIterationBehavior IterationBehavior { get; set; } public AnimationIterationBehavior IterationBehavior { get; set; }
/// <summary>
/// The number of times to repeat the key frame animation.
/// </summary>
public int IterationCount { get; set; } = 1; public int IterationCount { get; set; } = 1;
/// <summary>
/// Specifies how to set the property value when animation is stopped
/// </summary>
public AnimationStopBehavior StopBehavior { get; set; } public AnimationStopBehavior StopBehavior { get; set; }
private protected abstract IKeyFrames KeyFrames { get; } private protected abstract IKeyFrames KeyFrames { get; }
/// <summary>
/// Inserts an expression keyframe.
/// </summary>
/// <param name="normalizedProgressKey">
/// The time the key frame should occur at, expressed as a percentage of the animation Duration. Allowed value is from 0.0 to 1.0.
/// </param>
/// <param name="value">The expression used to calculate the value of the key frame.</param>
/// <param name="easingFunction">The easing function to use when interpolating between frames.</param>
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value, public void InsertExpressionKeyFrame(float normalizedProgressKey, string value,
CompositionEasingFunction easingFunction) => Easing? easingFunction = null) =>
KeyFrames.InsertExpressionKeyFrame(normalizedProgressKey, value, easingFunction); KeyFrames.InsertExpressionKeyFrame(normalizedProgressKey, value, easingFunction ?? Compositor.DefaultEasing);
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value)
=> KeyFrames.InsertExpressionKeyFrame(normalizedProgressKey, value, new LinearEasingFunction(Compositor));
} }
/// <summary>
/// Specifies the animation delay behavior.
/// </summary>
public enum AnimationDelayBehavior public enum AnimationDelayBehavior
{ {
/// <summary>
/// If a DelayTime is specified, it delays starting the animation according to delay time and after delay
/// has expired it applies animation to the object property.
/// </summary>
SetInitialValueAfterDelay, SetInitialValueAfterDelay,
/// <summary>
/// Applies the initial value of the animation (i.e. the value at Keyframe 0) to the object before the delay time
/// is elapsed (when there is a DelayTime specified), it then delays starting the animation according to the DelayTime.
/// </summary>
SetInitialValueBeforeDelay SetInitialValueBeforeDelay
} }
public enum AnimationDirection /// <summary>
{ /// Specifies if the animation should loop.
Normal, /// </summary>
Reverse,
Alternate,
AlternateReverse
}
public enum AnimationIterationBehavior public enum AnimationIterationBehavior
{ {
/// <summary>
/// The animation should loop the specified number of times.
/// </summary>
Count, Count,
/// <summary>
/// The animation should loop forever.
/// </summary>
Forever Forever
} }
/// <summary>
/// Specifies the behavior of an animation when it stops.
/// </summary>
public enum AnimationStopBehavior public enum AnimationStopBehavior
{ {
/// <summary>
/// Leave the animation at its current value.
/// </summary>
LeaveCurrentValue, LeaveCurrentValue,
/// <summary>
/// Reset the animation to its initial value.
/// </summary>
SetToInitialValue, SetToInitialValue,
/// <summary>
/// Set the animation to its final value.
/// </summary>
SetToFinalValue SetToFinalValue
} }
} }

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

@ -1,10 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Animation;
using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server; using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary>
/// Server-side counterpart of KeyFrameAnimation with values baked-in
/// </summary>
class KeyFrameAnimationInstance<T> : AnimationInstanceBase, IAnimationInstance where T : struct class KeyFrameAnimationInstance<T> : AnimationInstanceBase, IAnimationInstance where T : struct
{ {
private readonly IInterpolator<T> _interpolator; private readonly IInterpolator<T> _interpolator;
@ -12,7 +16,7 @@ namespace Avalonia.Rendering.Composition.Animations
private readonly ExpressionVariant? _finalValue; private readonly ExpressionVariant? _finalValue;
private readonly AnimationDelayBehavior _delayBehavior; private readonly AnimationDelayBehavior _delayBehavior;
private readonly TimeSpan _delayTime; private readonly TimeSpan _delayTime;
private readonly AnimationDirection _direction; private readonly PlaybackDirection _direction;
private readonly TimeSpan _duration; private readonly TimeSpan _duration;
private readonly AnimationIterationBehavior _iterationBehavior; private readonly AnimationIterationBehavior _iterationBehavior;
private readonly int _iterationCount; private readonly int _iterationCount;
@ -27,7 +31,7 @@ namespace Avalonia.Rendering.Composition.Animations
PropertySetSnapshot snapshot, ExpressionVariant? finalValue, PropertySetSnapshot snapshot, ExpressionVariant? finalValue,
ServerObject target, ServerObject target,
AnimationDelayBehavior delayBehavior, TimeSpan delayTime, AnimationDelayBehavior delayBehavior, TimeSpan delayTime,
AnimationDirection direction, TimeSpan duration, PlaybackDirection direction, TimeSpan duration,
AnimationIterationBehavior iterationBehavior, AnimationIterationBehavior iterationBehavior,
int iterationCount, AnimationStopBehavior stopBehavior) : base(target, snapshot) int iterationCount, AnimationStopBehavior stopBehavior) : base(target, snapshot)
{ {
@ -96,11 +100,11 @@ namespace Avalonia.Rendering.Composition.Animations
elapsed = TimeSpan.FromTicks(elapsed.Ticks % _duration.Ticks); elapsed = TimeSpan.FromTicks(elapsed.Ticks % _duration.Ticks);
var reverse = var reverse =
_direction == AnimationDirection.Alternate _direction == PlaybackDirection.Alternate
? !evenIterationNumber ? !evenIterationNumber
: _direction == AnimationDirection.AlternateReverse : _direction == PlaybackDirection.AlternateReverse
? evenIterationNumber ? evenIterationNumber
: _direction == AnimationDirection.Reverse; : _direction == PlaybackDirection.Reverse;
var iterationProgress = elapsed.TotalSeconds / _duration.TotalSeconds; var iterationProgress = elapsed.TotalSeconds / _duration.TotalSeconds;
if (reverse) if (reverse)
@ -128,7 +132,7 @@ namespace Avalonia.Rendering.Composition.Animations
var keyProgress = Math.Max(0, Math.Min(1, (iterationProgress - left.Key) / (right.Key - left.Key))); var keyProgress = Math.Max(0, Math.Min(1, (iterationProgress - left.Key) / (right.Key - left.Key)));
var easedKeyProgress = right.EasingFunction.Ease((float) keyProgress); var easedKeyProgress = (float)right.EasingFunction.Ease(keyProgress);
if (float.IsNaN(easedKeyProgress) || float.IsInfinity(easedKeyProgress)) if (float.IsNaN(easedKeyProgress) || float.IsInfinity(easedKeyProgress))
return currentValue; return currentValue;

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

@ -1,9 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Animation.Easings;
using Avalonia.Rendering.Composition.Expressions; using Avalonia.Rendering.Composition.Expressions;
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary>
/// Collection of composition animation key frames
/// </summary>
/// <typeparam name="T"></typeparam>
class KeyFrames<T> : List<KeyFrame<T>>, IKeyFrames class KeyFrames<T> : List<KeyFrame<T>>, IKeyFrames
{ {
void Validate(float key) void Validate(float key)
@ -14,8 +20,7 @@ namespace Avalonia.Rendering.Composition.Animations
throw new ArgumentException("Key frame key " + key + " is less than the previous one"); throw new ArgumentException("Key frame key " + key + " is less than the previous one");
} }
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value, public void InsertExpressionKeyFrame(float normalizedProgressKey, string value, IEasing easingFunction)
CompositionEasingFunction easingFunction)
{ {
Validate(normalizedProgressKey); Validate(normalizedProgressKey);
Add(new KeyFrame<T> Add(new KeyFrame<T>
@ -26,7 +31,7 @@ namespace Avalonia.Rendering.Composition.Animations
}); });
} }
public void Insert(float normalizedProgressKey, T value, CompositionEasingFunction easingFunction) public void Insert(float normalizedProgressKey, T value, IEasing easingFunction)
{ {
Validate(normalizedProgressKey); Validate(normalizedProgressKey);
Add(new KeyFrame<T> Add(new KeyFrame<T>
@ -47,7 +52,7 @@ namespace Avalonia.Rendering.Composition.Animations
{ {
Expression = f.Expression, Expression = f.Expression,
Value = f.Value, Value = f.Value,
EasingFunction = f.EasingFunction.Snapshot(), EasingFunction = f.EasingFunction,
Key = f.NormalizedProgressKey Key = f.NormalizedProgressKey
}; };
} }
@ -55,26 +60,30 @@ namespace Avalonia.Rendering.Composition.Animations
} }
} }
/// <summary>
/// Composition animation key frame
/// </summary>
struct KeyFrame<T> struct KeyFrame<T>
{ {
public float NormalizedProgressKey; public float NormalizedProgressKey;
public T Value; public T Value;
public Expression Expression; public Expression Expression;
public CompositionEasingFunction EasingFunction; public IEasing EasingFunction;
} }
/// <summary>
/// Server-side composition animation key frame
/// </summary>
struct ServerKeyFrame<T> struct ServerKeyFrame<T>
{ {
public T Value; public T Value;
public Expression? Expression; public Expression? Expression;
public IEasingFunction EasingFunction; public IEasing EasingFunction;
public float Key; public float Key;
} }
interface IKeyFrames interface IKeyFrames
{ {
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value, CompositionEasingFunction easingFunction); public void InsertExpressionKeyFrame(float normalizedProgressKey, string value, IEasing easingFunction);
} }
} }

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

@ -3,6 +3,9 @@ using Avalonia.Rendering.Composition.Expressions;
namespace Avalonia.Rendering.Composition.Animations namespace Avalonia.Rendering.Composition.Animations
{ {
/// <summary>
/// A snapshot of properties used by an animation
/// </summary>
internal class PropertySetSnapshot : IExpressionParameterCollection, IExpressionObject internal class PropertySetSnapshot : IExpressionParameterCollection, IExpressionObject
{ {
private readonly Dictionary<string, Value> _dic; private readonly Dictionary<string, Value> _dic;

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

@ -13,7 +13,10 @@ using Avalonia.VisualTree;
namespace Avalonia.Rendering.Composition; namespace Avalonia.Rendering.Composition;
public class CompositingRenderer : RendererBase, IRendererWithCompositor /// <summary>
/// A renderer that utilizes <see cref="Avalonia.Rendering.Composition.Compositor"/> to render the visual tree
/// </summary>
public class CompositingRenderer : IRendererWithCompositor
{ {
private readonly IRenderRoot _root; private readonly IRenderRoot _root;
private readonly Compositor _compositor; private readonly Compositor _compositor;
@ -24,9 +27,10 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
private readonly CompositionTarget _target; private readonly CompositionTarget _target;
private bool _queuedUpdate; private bool _queuedUpdate;
private Action _update; private Action _update;
private Action _invalidateScene;
/// <summary> /// <summary>
/// Forces the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered /// Asks the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered.
/// </summary> /// </summary>
public bool RenderOnlyOnRenderThread { get; set; } = true; public bool RenderOnlyOnRenderThread { get; set; } = true;
@ -39,20 +43,24 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
_target = compositor.CreateCompositionTarget(root.CreateRenderTarget); _target = compositor.CreateCompositionTarget(root.CreateRenderTarget);
_target.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor); _target.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor);
_update = Update; _update = Update;
_invalidateScene = InvalidateScene;
} }
/// <inheritdoc/>
public bool DrawFps public bool DrawFps
{ {
get => _target.DrawFps; get => _target.DrawFps;
set => _target.DrawFps = value; set => _target.DrawFps = value;
} }
/// <inheritdoc/>
public bool DrawDirtyRects public bool DrawDirtyRects
{ {
get => _target.DrawDirtyRects; get => _target.DrawDirtyRects;
set => _target.DrawDirtyRects = value; set => _target.DrawDirtyRects = value;
} }
/// <inheritdoc/>
public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated; public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated;
void QueueUpdate() void QueueUpdate()
@ -62,12 +70,15 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
_queuedUpdate = true; _queuedUpdate = true;
Dispatcher.UIThread.Post(_update, DispatcherPriority.Composition); Dispatcher.UIThread.Post(_update, DispatcherPriority.Composition);
} }
/// <inheritdoc/>
public void AddDirty(IVisual visual) public void AddDirty(IVisual visual)
{ {
_dirty.Add((Visual)visual); _dirty.Add((Visual)visual);
QueueUpdate(); QueueUpdate();
} }
/// <inheritdoc/>
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool>? filter) public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool>? filter)
{ {
var res = _target.TryHitTest(p, filter); var res = _target.TryHitTest(p, filter);
@ -84,12 +95,14 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
} }
} }
/// <inheritdoc/>
public IVisual? HitTestFirst(Point p, IVisual root, Func<IVisual, bool>? filter) public IVisual? HitTestFirst(Point p, IVisual root, Func<IVisual, bool>? filter)
{ {
// TODO: Optimize // TODO: Optimize
return HitTest(p, root, filter).FirstOrDefault(); return HitTest(p, root, filter).FirstOrDefault();
} }
/// <inheritdoc/>
public void RecalculateChildren(IVisual visual) public void RecalculateChildren(IVisual visual)
{ {
_recalculateChildren.Add((Visual)visual); _recalculateChildren.Add((Visual)visual);
@ -172,7 +185,10 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
compositionChildren.Add(compositionChild); compositionChildren.Add(compositionChild);
} }
} }
private void InvalidateScene() =>
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));
private void Update() private void Update()
{ {
_queuedUpdate = false; _queuedUpdate = false;
@ -223,6 +239,7 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
_recalculateChildren.Clear(); _recalculateChildren.Clear();
_target.Size = _root.ClientSize; _target.Size = _root.ClientSize;
_target.Scaling = _root.RenderScaling; _target.Scaling = _root.RenderScaling;
Compositor.InvokeOnNextCommit(_invalidateScene);
} }
public void Resized(Size size) public void Resized(Size size)
@ -257,6 +274,8 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
_compositor.RequestCommitAsync().Wait(); _compositor.RequestCommitAsync().Wait();
} }
/// <summary>
/// The associated <see cref="Avalonia.Rendering.Composition.Compositor"/> object
/// </summary>
public Compositor Compositor => _compositor; public Compositor Compositor => _compositor;
} }

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

@ -7,12 +7,23 @@ using Avalonia.VisualTree;
namespace Avalonia.Rendering.Composition; namespace Avalonia.Rendering.Composition;
/// <summary>
/// A composition visual that holds a list of drawing commands issued by <see cref="Avalonia.Visual"/>
/// </summary>
internal class CompositionDrawListVisual : CompositionContainerVisual internal class CompositionDrawListVisual : CompositionContainerVisual
{ {
/// <summary>
/// The associated <see cref="Avalonia.Visual"/>
/// </summary>
public Visual Visual { get; } public Visual Visual { get; }
private bool _drawListChanged; private bool _drawListChanged;
private CompositionDrawList? _drawList; private CompositionDrawList? _drawList;
/// <summary>
/// The list of drawing commands
/// </summary>
public CompositionDrawList? DrawList public CompositionDrawList? DrawList
{ {
get => _drawList; get => _drawList;

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

@ -1,95 +0,0 @@
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!)
{
}
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

@ -1,16 +0,0 @@
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; }
}
}

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

@ -6,8 +6,16 @@ using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition namespace Avalonia.Rendering.Composition
{ {
/// <summary>
/// Base class of the composition API representing a node in the visual tree structure.
/// Composition objects are the visual tree structure on which all other features of the composition API use and build on.
/// The API allows developers to define and create one or many <see cref="CompositionVisual" /> objects each representing a single node in a Visual tree.
/// </summary>
public abstract class CompositionObject : IDisposable public abstract class CompositionObject : IDisposable
{ {
/// <summary>
/// The collection of implicit animations attached to this object.
/// </summary>
public ImplicitAnimationCollection? ImplicitAnimations { get; set; } public ImplicitAnimationCollection? ImplicitAnimations { get; set; }
internal CompositionObject(Compositor compositor, ServerObject server) internal CompositionObject(Compositor compositor, ServerObject server)
{ {
@ -15,6 +23,9 @@ namespace Avalonia.Rendering.Composition
Server = server; Server = server;
} }
/// <summary>
/// The associated Compositor
/// </summary>
public Compositor Compositor { get; } public Compositor Compositor { get; }
internal ServerObject Server { get; } internal ServerObject Server { get; }
public bool IsDisposed { get; private set; } public bool IsDisposed { get; private set; }
@ -29,14 +40,22 @@ namespace Avalonia.Rendering.Composition
IsDisposed = true; IsDisposed = true;
} }
/// <summary>
/// Connects an animation with the specified property of the object and starts the animation.
/// </summary>
public void StartAnimation(string propertyName, CompositionAnimation animation) public void StartAnimation(string propertyName, CompositionAnimation animation)
=> StartAnimation(propertyName, animation, null); => StartAnimation(propertyName, animation, null);
internal virtual void StartAnimation(string propertyName, CompositionAnimation animation, ExpressionVariant? finalValue = null) internal virtual void StartAnimation(string propertyName, CompositionAnimation animation, ExpressionVariant? finalValue)
{ {
throw new ArgumentException("Unknown property " + propertyName); throw new ArgumentException("Unknown property " + propertyName);
} }
/// <summary>
/// Starts an animation group.
/// The StartAnimationGroup method on CompositionObject lets you start CompositionAnimationGroup.
/// All the animations in the group will be started at the same time on the object.
/// </summary>
public void StartAnimationGroup(ICompositionAnimationBase grp) public void StartAnimationGroup(ICompositionAnimationBase grp)
{ {
if (grp is CompositionAnimation animation) if (grp is CompositionAnimation animation)

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

@ -7,6 +7,15 @@ using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition namespace Avalonia.Rendering.Composition
{ {
/// <summary>
/// <see cref="CompositionPropertySet"/>s are <see cref="CompositionObject"/>s that allow storage of key values pairs
/// that can be shared across the application and are not tied to the lifetime of another composition object.
/// <see cref="CompositionPropertySet"/>s are most commonly used with animations, where they maintain key-value pairs
/// that are referenced to drive portions of composition animations. <see cref="CompositionPropertySet"/>s
/// provide the ability to insert key-value pairs or retrieve a value for a given key.
/// <see cref="CompositionPropertySet"/> does not support a delete function – ensure you use <see cref="CompositionPropertySet"/>
/// to store values that will be shared across the application.
/// </summary>
public class CompositionPropertySet : CompositionObject public class CompositionPropertySet : CompositionObject
{ {
private readonly Dictionary<string, ExpressionVariant> _variants = new Dictionary<string, ExpressionVariant>(); private readonly Dictionary<string, ExpressionVariant> _variants = new Dictionary<string, ExpressionVariant>();

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

@ -6,6 +6,9 @@ using Avalonia.VisualTree;
namespace Avalonia.Rendering.Composition namespace Avalonia.Rendering.Composition
{ {
/// <summary>
/// Represents the composition output (e. g. a window, embedded control, entire screen)
/// </summary>
public partial class CompositionTarget public partial class CompositionTarget
{ {
partial void OnRootChanged() partial void OnRootChanged()
@ -20,6 +23,12 @@ namespace Avalonia.Rendering.Composition
Root.Root = null; Root.Root = null;
} }
/// <summary>
/// Attempts to perform a hit-tst
/// </summary>
/// <param name="point"></param>
/// <param name="filter"></param>
/// <returns></returns>
public PooledList<CompositionVisual>? TryHitTest(Point point, Func<IVisual, bool>? filter) public PooledList<CompositionVisual>? TryHitTest(Point point, Func<IVisual, bool>? filter)
{ {
Server.Readback.NextRead(); Server.Readback.NextRead();
@ -30,6 +39,10 @@ namespace Avalonia.Rendering.Composition
return res; return res;
} }
/// <summary>
/// Attempts to transform a point to a particular CompositionVisual coordinate space
/// </summary>
/// <returns></returns>
public Point? TryTransformToVisual(CompositionVisual visual, Point point) public Point? TryTransformToVisual(CompositionVisual visual, Point point)
{ {
if (visual.Root != this) if (visual.Root != this)
@ -108,8 +121,14 @@ namespace Avalonia.Rendering.Composition
} }
/// <summary>
/// Registers the composition target for explicit redraw
/// </summary>
public void RequestRedraw() => RegisterForSerialization(); public void RequestRedraw() => RegisterForSerialization();
/// <summary>
/// Performs composition directly on the UI thread
/// </summary>
internal void ImmediateUIThreadRender() internal void ImmediateUIThreadRender()
{ {
Compositor.RequestCommitAsync(); Compositor.RequestCommitAsync();

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

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Numerics; using System.Numerics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Animation.Easings;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Animations;
@ -13,6 +14,10 @@ using Avalonia.Threading;
namespace Avalonia.Rendering.Composition namespace Avalonia.Rendering.Composition
{ {
/// <summary>
/// The Compositor class manages communication between UI-thread and render-thread parts of the composition engine.
/// It also serves as a factory to create UI-thread parts of various composition objects
/// </summary>
public partial class Compositor public partial class Compositor
{ {
internal IRenderLoop Loop { get; } internal IRenderLoop Loop { get; }
@ -23,22 +28,38 @@ namespace Avalonia.Rendering.Composition
private BatchStreamMemoryPool _batchMemoryPool = new(); private BatchStreamMemoryPool _batchMemoryPool = new();
private List<CompositionObject> _objectsForSerialization = new(); private List<CompositionObject> _objectsForSerialization = new();
internal ServerCompositor Server => _server; internal ServerCompositor Server => _server;
internal CompositionEasingFunction DefaultEasing { get; } internal IEasing DefaultEasing { get; }
private List<Action>? _invokeOnNextCommit;
private readonly Stack<List<Action>> _invokeListPool = new();
/// <summary>
/// Creates a new compositor on a specified render loop that would use a particular GPU
/// </summary>
/// <param name="loop"></param>
/// <param name="gpu"></param>
public Compositor(IRenderLoop loop, IPlatformGpu? gpu) public Compositor(IRenderLoop loop, IPlatformGpu? gpu)
{ {
Loop = loop; Loop = loop;
_server = new ServerCompositor(loop, gpu, _batchObjectPool, _batchMemoryPool); _server = new ServerCompositor(loop, gpu, _batchObjectPool, _batchMemoryPool);
_implicitBatchCommit = ImplicitBatchCommit; _implicitBatchCommit = ImplicitBatchCommit;
DefaultEasing = new CubicBezierEasingFunction(this,
new Vector2(0.25f, 0.1f), new Vector2(0.25f, 1f)); DefaultEasing = new CubicBezierEasing(new Point(0.25f, 0.1f), new Point(0.25f, 1f));
} }
/// <summary>
/// Creates a new CompositionTarget
/// </summary>
/// <param name="renderTargetFactory">A factory method to create IRenderTarget to be called from the render thread</param>
/// <returns></returns>
public CompositionTarget CreateCompositionTarget(Func<IRenderTarget> renderTargetFactory) public CompositionTarget CreateCompositionTarget(Func<IRenderTarget> renderTargetFactory)
{ {
return new CompositionTarget(this, new ServerCompositionTarget(_server, renderTargetFactory)); return new CompositionTarget(this, new ServerCompositionTarget(_server, renderTargetFactory));
} }
/// <summary>
/// Requests pending changes in the composition objects to be serialized and sent to the render thread
/// </summary>
/// <returns>A task that completes when sent changes are applied and rendered on the render thread</returns>
public Task RequestCommitAsync() public Task RequestCommitAsync()
{ {
var batch = new Batch(); var batch = new Batch();
@ -59,64 +80,25 @@ namespace Avalonia.Rendering.Composition
batch.CommitedAt = Server.Clock.Elapsed; batch.CommitedAt = Server.Clock.Elapsed;
_server.EnqueueBatch(batch); _server.EnqueueBatch(batch);
if (_invokeOnNextCommit != null)
ScheduleCommitCallbacks(batch.Completed);
return batch.Completed; return batch.Completed;
} }
public CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server)); async void ScheduleCommitCallbacks(Task task)
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); var list = _invokeOnNextCommit;
return new CompositionBitmapSurface(this, bmp); _invokeOnNextCommit = null;
await task;
foreach (var i in list!)
i();
list.Clear();
_invokeListPool.Push(list);
} }
public async Task<CompositionBitmapSurface> LoadBitmapSurfaceAsync(Stream stream) public CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server));
{
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() => new ExpressionAnimation(this);
public ExpressionAnimation CreateExpressionAnimation(string expression) => new ExpressionAnimation(this) public ExpressionAnimation CreateExpressionAnimation(string expression) => new ExpressionAnimation(this)
@ -147,5 +129,11 @@ namespace Avalonia.Rendering.Composition
_objectsForSerialization.Add(compositionObject); _objectsForSerialization.Add(compositionObject);
QueueImplicitBatchCommit(); QueueImplicitBatchCommit();
} }
internal void InvokeOnNextCommit(Action action)
{
_invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new();
_invokeOnNextCommit.Add(action);
}
} }
} }

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

@ -1,20 +0,0 @@
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();
}
}
}

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

@ -2,6 +2,9 @@ using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition namespace Avalonia.Rendering.Composition
{ {
/// <summary>
/// A node in the visual tree that can have children.
/// </summary>
public partial class CompositionContainerVisual : CompositionVisual public partial class CompositionContainerVisual : CompositionVisual
{ {
public CompositionVisualCollection Children { get; private set; } = null!; public CompositionVisualCollection Children { get; private set; } = null!;

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

@ -6,6 +6,9 @@ using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Drawing; namespace Avalonia.Rendering.Composition.Drawing;
/// <summary>
/// A list of serialized drawing commands
/// </summary>
internal class CompositionDrawList : PooledList<IRef<IDrawOperation>> internal class CompositionDrawList : PooledList<IRef<IDrawOperation>>
{ {
public Size? Size { get; set; } public Size? Size { get; set; }
@ -47,6 +50,9 @@ internal class CompositionDrawList : PooledList<IRef<IDrawOperation>>
} }
} }
/// <summary>
/// An helper class for building <see cref="CompositionDrawList"/>
/// </summary>
internal class CompositionDrawListBuilder internal class CompositionDrawListBuilder
{ {
private CompositionDrawList? _operations; private CompositionDrawList? _operations;

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

@ -10,6 +10,9 @@ using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Rendering.Composition; namespace Avalonia.Rendering.Composition;
/// <summary>
/// An IDrawingContextImpl implementation that builds <see cref="CompositionDrawList"/>
/// </summary>
internal class CompositionDrawingContext : IDrawingContextImpl internal class CompositionDrawingContext : IDrawingContextImpl
{ {
private CompositionDrawListBuilder _builder = new(); private CompositionDrawListBuilder _builder = new();

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

@ -1,6 +1,14 @@
namespace Avalonia.Rendering.Composition; namespace Avalonia.Rendering.Composition;
public static class ElementCompositionPreview /// <summary>
/// Enables access to composition visual objects that back XAML elements in the XAML composition tree.
/// </summary>
public static class ElementComposition
{ {
/// <summary>
/// Gets CompositionVisual that backs a Visual
/// </summary>
/// <param name="visual"></param>
/// <returns></returns>
public static CompositionVisual? GetElementVisual(Visual visual) => visual.CompositionVisual; public static CompositionVisual? GetElementVisual(Visual visual) => visual.CompositionVisual;
} }

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

@ -2,10 +2,13 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Utils; using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Expressions namespace Avalonia.Rendering.Composition.Expressions
{ {
/// <summary>
/// Built-in functions for Foreign Function Interface available from composition animation expressions
/// </summary>
internal class BuiltInExpressionFfi : IExpressionForeignFunctionInterface internal class BuiltInExpressionFfi : IExpressionForeignFunctionInterface
{ {
private readonly DelegateExpressionFfi _registry; private readonly DelegateExpressionFfi _registry;
@ -26,7 +29,7 @@ namespace Avalonia.Rendering.Composition.Expressions
static float SmoothStep(float edge0, float edge1, float x) static float SmoothStep(float edge0, float edge1, float x)
{ {
var t = MathExt.Clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); var t = MathUtilities.Clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
return t * t * (3.0f - 2.0f * t); return t * t * (3.0f - 2.0f * t);
} }
@ -72,7 +75,7 @@ namespace Avalonia.Rendering.Composition.Expressions
{"ATan", (float f) => (float) Math.Atan(f)}, {"ATan", (float f) => (float) Math.Atan(f)},
{"Ceil", (float f) => (float) Math.Ceiling(f)}, {"Ceil", (float f) => (float) Math.Ceiling(f)},
{"Clamp", (float a1, float a2, float a3) => MathExt.Clamp(a1, a2, a3)}, {"Clamp", (float a1, float a2, float a3) => MathUtilities.Clamp(a1, a2, a3)},
{"Clamp", (Vector2 a1, Vector2 a2, Vector2 a3) => Vector2.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", (Vector3 a1, Vector3 a2, Vector3 a3) => Vector3.Clamp(a1, a2, a3)},
{"Clamp", (Vector4 a1, Vector4 a2, Vector4 a3) => Vector4.Clamp(a1, a2, a3)}, {"Clamp", (Vector4 a1, Vector4 a2, Vector4 a3) => Vector4.Clamp(a1, a2, a3)},
@ -96,10 +99,10 @@ namespace Avalonia.Rendering.Composition.Expressions
}, },
{ {
"ColorRGB", (float a, float r, float g, float b) => Avalonia.Media.Color.FromArgb( "ColorRGB", (float a, float r, float g, float b) => Avalonia.Media.Color.FromArgb(
(byte) MathExt.Clamp(a, 0, 255), (byte) MathUtilities.Clamp(a, 0, 255),
(byte) MathExt.Clamp(r, 0, 255), (byte) MathUtilities.Clamp(r, 0, 255),
(byte) MathExt.Clamp(g, 0, 255), (byte) MathUtilities.Clamp(g, 0, 255),
(byte) MathExt.Clamp(b, 0, 255) (byte) MathUtilities.Clamp(b, 0, 255)
) )
}, },

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

@ -7,6 +7,9 @@ using Avalonia.Media;
namespace Avalonia.Rendering.Composition.Expressions namespace Avalonia.Rendering.Composition.Expressions
{ {
/// <summary>
/// Foreign function interface for composition animations based on calling delegates
/// </summary>
internal class DelegateExpressionFfi : IExpressionForeignFunctionInterface, IEnumerable internal class DelegateExpressionFfi : IExpressionForeignFunctionInterface, IEnumerable
{ {
struct FfiRecord struct FfiRecord

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

@ -6,6 +6,9 @@ using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Expressions namespace Avalonia.Rendering.Composition.Expressions
{ {
/// <summary>
/// A parsed composition expression
/// </summary>
internal abstract class Expression internal abstract class Expression
{ {
public abstract ExpressionType Type { get; } public abstract ExpressionType Type { get; }

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

@ -22,6 +22,9 @@ namespace Avalonia.Rendering.Composition.Expressions
Color Color
} }
/// <summary>
/// A VARIANT type used in expression animations. Can represent multiple value types
/// </summary>
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
internal struct ExpressionVariant internal struct ExpressionVariant
{ {

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

@ -3,6 +3,9 @@ using System.Globalization;
namespace Avalonia.Rendering.Composition.Expressions namespace Avalonia.Rendering.Composition.Expressions
{ {
/// <summary>
/// Helper class for composition expression parser
/// </summary>
internal ref struct TokenParser internal ref struct TokenParser
{ {
private ReadOnlySpan<char> _s; private ReadOnlySpan<char> _s;

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

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

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

@ -8,6 +8,13 @@ using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Server;
/// <summary>
/// A bunch of hacks to make the existing rendering operations and IDrawingContext
/// to work with composition rendering infrastructure.
/// 1) Keeps and applies the transform of the current visual since drawing operations think that
/// they have information about the full render transform (they are not)
/// 2) Keeps the draw list for the VisualBrush contents of the current drawing operation.
/// </summary>
internal class CompositorDrawingContextProxy : IDrawingContextImpl internal class CompositorDrawingContextProxy : IDrawingContextImpl
{ {
private IDrawingContextImpl _impl; private IDrawingContextImpl _impl;

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

@ -8,6 +8,9 @@ using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Server;
/// <summary>
/// An FPS counter helper that can draw itself on the render thread
/// </summary>
internal class FpsCounter internal class FpsCounter
{ {
private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
@ -17,6 +20,7 @@ internal class FpsCounter
private TimeSpan _lastFpsUpdate; private TimeSpan _lastFpsUpdate;
const int FirstChar = 32; const int FirstChar = 32;
const int LastChar = 126; const int LastChar = 126;
// ASCII chars
private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1]; private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1];
public FpsCounter(GlyphTypeface typeface) public FpsCounter(GlyphTypeface typeface)

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

@ -1,5 +1,10 @@
namespace Avalonia.Rendering.Composition.Server namespace Avalonia.Rendering.Composition.Server
{ {
/// <summary>
/// A helper class used to manage the current slots for writing data from the render thread
/// and reading it from the UI thread.
/// Used mostly by hit-testing which needs to know the last transform of the visual
/// </summary>
internal class ReadbackIndices internal class ReadbackIndices
{ {
private readonly object _lock = new object(); private readonly object _lock = new object();

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

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

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

@ -9,9 +9,13 @@ using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Server;
/// <summary>
/// Server-side counterpart of <see cref="CompositionDrawListVisual"/>
/// </summary>
internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisual internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisual
{ {
#if DEBUG #if DEBUG
// This is needed for debugging purposes so we could see inspect the associated visual from debugger
public readonly Visual UiVisual; public readonly Visual UiVisual;
#endif #endif
private CompositionDrawList? _renderCommands; private CompositionDrawList? _renderCommands;

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

@ -1,15 +0,0 @@
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);
}
}

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

@ -10,6 +10,10 @@ using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Server namespace Avalonia.Rendering.Composition.Server
{ {
/// <summary>
/// Server-side counterpart of the <see cref="CompositionTarget"/>
/// That's the place where we update visual transforms, track dirty rects and actually do rendering
/// </summary>
internal partial class ServerCompositionTarget : IDisposable internal partial class ServerCompositionTarget : IDisposable
{ {
private readonly ServerCompositor _compositor; private readonly ServerCompositor _compositor;
@ -172,6 +176,7 @@ namespace Avalonia.Rendering.Composition.Server
_renderTarget?.Dispose(); _renderTarget?.Dispose();
_renderTarget = null; _renderTarget = null;
} }
_compositor.RemoveCompositionTarget(this);
} }
public void AddVisual(ServerCompositionVisual visual) public void AddVisual(ServerCompositionVisual visual)

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

@ -8,6 +8,12 @@ using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Server namespace Avalonia.Rendering.Composition.Server
{ {
/// <summary>
/// Server-side counterpart of the <see cref="Compositor"/>.
/// 1) manages deserialization of changes received from the UI thread
/// 2) triggers animation ticks
/// 3) asks composition targets to render themselves
/// </summary>
internal class ServerCompositor : IRenderLoopTask internal class ServerCompositor : IRenderLoopTask
{ {
private readonly IRenderLoop _renderLoop; private readonly IRenderLoop _renderLoop;

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

@ -3,6 +3,11 @@ using Avalonia.Platform;
namespace Avalonia.Rendering.Composition.Server namespace Avalonia.Rendering.Composition.Server
{ {
/// <summary>
/// Server-side counterpart of <see cref="CompositionContainerVisual"/>.
/// Mostly propagates update and render calls, but is also responsible
/// for updating adorners in deferred manner
/// </summary>
internal partial class ServerCompositionContainerVisual : ServerCompositionVisual internal partial class ServerCompositionContainerVisual : ServerCompositionVisual
{ {
public ServerCompositionVisualCollection Children { get; private set; } = null!; public ServerCompositionVisualCollection Children { get; private set; } = null!;

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

@ -4,6 +4,10 @@ using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Server namespace Avalonia.Rendering.Composition.Server
{ {
/// <summary>
/// A server-side list container capable of receiving changes from the UI thread
/// Right now it's quite dumb since it always receives the full list
/// </summary>
class ServerList<T> : ServerObject where T : ServerObject class ServerList<T> : ServerObject where T : ServerObject
{ {
public List<T> List { get; } = new List<T>(); public List<T> List { get; } = new List<T>();

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

@ -9,6 +9,10 @@ using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Server namespace Avalonia.Rendering.Composition.Server
{ {
/// <summary>
/// Server-side <see cref="CompositionObject" /> counterpart.
/// Is responsible for animation activation and invalidation
/// </summary>
internal abstract class ServerObject : IExpressionObject internal abstract class ServerObject : IExpressionObject
{ {
public ServerCompositor Compositor { get; } public ServerCompositor Compositor { get; }
@ -92,13 +96,15 @@ namespace Avalonia.Rendering.Composition.Server
public void SubscribeToInvalidation(int member, IAnimationInstance animation) public void SubscribeToInvalidation(int member, IAnimationInstance animation)
{ {
ref var store = ref GetStoreFromOffset(member); ref var store = ref GetStoreFromOffset(member);
if (store.Subscribers == null)
store.Subscribers = new();
store.Subscribers.AddRef(animation); store.Subscribers.AddRef(animation);
} }
public void UnsubscribeFromInvalidation(int member, IAnimationInstance animation) public void UnsubscribeFromInvalidation(int member, IAnimationInstance animation)
{ {
ref var store = ref GetStoreFromOffset(member); ref var store = ref GetStoreFromOffset(member);
store.Subscribers.ReleaseRef(animation); store.Subscribers?.ReleaseRef(animation);
} }
public virtual int? GetFieldOffset(string fieldName) => null; public virtual int? GetFieldOffset(string fieldName) => null;

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

@ -1,15 +0,0 @@
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)
{
canvas.DrawRectangle(new ImmutableSolidColorBrush(Color), null, new RoundedRect(new Rect(new Size(Size))));
base.RenderCore(canvas);
}
}
}

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

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

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

@ -4,7 +4,13 @@ using Avalonia.Rendering.Composition.Transport;
namespace Avalonia.Rendering.Composition.Server namespace Avalonia.Rendering.Composition.Server
{ {
unsafe partial class ServerCompositionVisual : ServerObject /// <summary>
/// Server-side <see cref="CompositionVisual"/> counterpart.
/// Is responsible for computing the transformation matrix, for applying various visual
/// properties before calling visual-specific drawing code and for notifying the
/// <see cref="ServerCompositionTarget"/> for new dirty rects
/// </summary>
partial class ServerCompositionVisual : ServerObject
{ {
private bool _isDirty; private bool _isDirty;
private bool _isBackface; private bool _isBackface;
@ -51,7 +57,10 @@ namespace Avalonia.Rendering.Composition.Server
private ReadbackData _readback0, _readback1, _readback2; private ReadbackData _readback0, _readback1, _readback2;
/// <summary>
/// Obtains "readback" data - the data that is sent from the render thread to the UI thread
/// in non-blocking manner. Used mostly by hit-testing
/// </summary>
public ref ReadbackData GetReadback(int idx) public ref ReadbackData GetReadback(int idx)
{ {
if (idx == 0) if (idx == 0)
@ -119,6 +128,9 @@ namespace Avalonia.Rendering.Composition.Server
} }
/// <summary>
/// Data that can be read from the UI thread
/// </summary>
public struct ReadbackData public struct ReadbackData
{ {
public Matrix4x4 Matrix; public Matrix4x4 Matrix;
@ -126,7 +138,6 @@ namespace Avalonia.Rendering.Composition.Server
public long TargetId; public long TargetId;
public bool Visible; public bool Visible;
} }
partial void DeserializeChangesExtra(BatchStreamReader c) partial void DeserializeChangesExtra(BatchStreamReader c)
{ {

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

@ -6,6 +6,9 @@ using System.Threading.Tasks;
namespace Avalonia.Rendering.Composition.Transport namespace Avalonia.Rendering.Composition.Transport
{ {
/// <summary>
/// Represents a group of serialized changes from the UI thread to be atomically applied at the render thread
/// </summary>
internal class Batch internal class Batch
{ {
private static long _nextSequenceId = 1; private static long _nextSequenceId = 1;

6
src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs

@ -7,6 +7,12 @@ using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Transport; namespace Avalonia.Rendering.Composition.Transport;
/// <summary>
/// The batch data is separated into 2 "streams":
/// - objects: CLR reference types that are references to either server-side or common objects
/// - structs: blittable types like int, Matrix, Color
/// Each "stream" consists of memory segments that are pooled
/// </summary>
internal class BatchStreamData internal class BatchStreamData
{ {
public Queue<BatchStreamSegment<object?[]>> Objects { get; } = new(); public Queue<BatchStreamSegment<object?[]>> Objects { get; } = new();

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

@ -4,6 +4,11 @@ using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition.Transport namespace Avalonia.Rendering.Composition.Transport
{ {
/// <summary>
/// A helper class used from generated UI-thread-side collections of composition objects.
/// </summary>
// NOTE: This should probably be a base class since TServer isn't used anymore and it was the reason why
// it couldn't be exposed as a base class
class ServerListProxyHelper<TClient, TServer> : IList<TClient> class ServerListProxyHelper<TClient, TServer> : IList<TClient>
where TServer : ServerObject where TServer : ServerObject
where TClient : CompositionObject where TClient : CompositionObject

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

@ -1,23 +0,0 @@
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);
}
}
}

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

@ -5,6 +5,9 @@ using Avalonia.VisualTree;
namespace Avalonia.Rendering.Composition namespace Avalonia.Rendering.Composition
{ {
/// <summary>
/// The base visual object in the composition visual hierarchy.
/// </summary>
public abstract partial class CompositionVisual public abstract partial class CompositionVisual
{ {
private IBrush? _opacityMask; private IBrush? _opacityMask;

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

@ -3,6 +3,9 @@ using Avalonia.Rendering.Composition.Server;
namespace Avalonia.Rendering.Composition namespace Avalonia.Rendering.Composition
{ {
/// <summary>
/// A collection of CompositionVisual objects
/// </summary>
public partial class CompositionVisualCollection : CompositionObject public partial class CompositionVisualCollection : CompositionObject
{ {
private CompositionVisual _owner; private CompositionVisual _owner;

14
src/Avalonia.Base/Utilities/MathUtilities.cs

@ -251,6 +251,20 @@ namespace Avalonia.Utilities
return val; return val;
} }
} }
/// <summary>
/// Clamps a value between a minimum and maximum value.
/// </summary>
/// <param name="val">The value.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The clamped value.</returns>
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);
}
/// <summary> /// <summary>
/// Clamps a value between a minimum and maximum value. /// Clamps a value between a minimum and maximum value.

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

@ -5,6 +5,9 @@ using System.Runtime.InteropServices;
namespace Avalonia.Utilities; namespace Avalonia.Utilities;
/// <summary>
/// Maintains a set of objects with reference counts
/// </summary>
internal class RefTrackingDictionary<TKey> : Dictionary<TKey, int> where TKey : class internal class RefTrackingDictionary<TKey> : Dictionary<TKey, int> where TKey : class
{ {
/// <summary> /// <summary>

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

@ -7,7 +7,6 @@
<Manual Name="Avalonia.Platform.IGeometryImpl" Passthrough="true"/> <Manual Name="Avalonia.Platform.IGeometryImpl" Passthrough="true"/>
<Manual Name="Avalonia.Media.IBrush" Passthrough="true"/> <Manual Name="Avalonia.Media.IBrush" Passthrough="true"/>
<Manual Name="ICompositionSurface" ServerName="ServerCompositionSurface" />
<Object Name="CompositionVisual" Abstract="true"> <Object Name="CompositionVisual" Abstract="true">
<Property Name="Root" Type="CompositionTarget?" InternalSet="true" /> <Property Name="Root" Type="CompositionTarget?" InternalSet="true" />
<Property Name="Parent" Type="CompositionVisual?" InternalSet="true" /> <Property Name="Parent" Type="CompositionVisual?" InternalSet="true" />
@ -37,40 +36,6 @@
<Property Name="Scaling" Type="double"/> <Property Name="Scaling" Type="double"/>
<Property Name="Size" Type="Size" /> <Property Name="Size" Type="Size" />
</Object> </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="Scalar" Type="float"/>
<KeyFrameAnimation Name="Boolean" Type="bool"/> <KeyFrameAnimation Name="Boolean" Type="bool"/>
<KeyFrameAnimation Name="Color" Type="Avalonia.Media.Color"/> <KeyFrameAnimation Name="Color" Type="Avalonia.Media.Color"/>

3
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs

@ -8,6 +8,9 @@ using Avalonia.Controls;
namespace Avalonia.Markup.Parsers namespace Avalonia.Markup.Parsers
{ {
/// <summary>
/// Parser for composition expressions
/// </summary>
internal class ExpressionParser internal class ExpressionParser
{ {
private readonly bool _enableValidation; private readonly bool _enableValidation;

2
src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs

@ -34,7 +34,7 @@ namespace Avalonia.Rendering.Composition
private KeyFrames<{a.Type}> _keyFrames = new KeyFrames<{a.Type}>(); private KeyFrames<{a.Type}> _keyFrames = new KeyFrames<{a.Type}>();
private protected override IKeyFrames KeyFrames => _keyFrames; private protected override IKeyFrames KeyFrames => _keyFrames;
public void InsertKeyFrame(float normalizedProgressKey, {a.Type} value, CompositionEasingFunction easingFunction) public void InsertKeyFrame(float normalizedProgressKey, {a.Type} value, Avalonia.Animation.Easings.IEasing easingFunction)
{{ {{
_keyFrames.Insert(normalizedProgressKey, value, easingFunction); _keyFrames.Insert(normalizedProgressKey, value, easingFunction);
}} }}

Loading…
Cancel
Save