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)
{
var compositor = ElementCompositionPreview.GetElementVisual(this)!.Compositor;
var compositor = ElementComposition.GetElementVisual(this)!.Compositor;
var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
offsetAnimation.Target = "Offset";
@ -143,11 +143,11 @@ public partial class CompositionPage : UserControl
return;
}
if (ElementCompositionPreview.GetElementVisual(page) == null)
if (ElementComposition.GetElementVisual(page) == null)
return;
page.EnsureImplicitAnimations();
ElementCompositionPreview.GetElementVisual((Visual)border.GetVisualParent()).ImplicitAnimations =
ElementComposition.GetElementVisual((Visual)border.GetVisualParent()).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
using System;
using Avalonia.Utilities;
// ReSharper disable CompareOfFloatsByEqualityOperator
// ReSharper disable CommentTypo
@ -10,8 +11,11 @@ using System;
// ReSharper disable UnusedMember.Global
#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
{
const int CUBIC_BEZIER_SPLINE_SAMPLES = 11;
@ -284,7 +288,7 @@ namespace Avalonia.Rendering.Composition.Utils
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 dx = SampleCurveDerivativeX(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.UnitTests, 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>
</Project>

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

@ -7,10 +7,15 @@ using Avalonia.Utilities;
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
{
public bool IsValid;
public RefTrackingDictionary<IAnimationInstance> Subscribers;
public RefTrackingDictionary<IAnimationInstance>? Subscribers;
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)]
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)]
internal struct ServerAnimatedValueStore<T> where T : struct
{
@ -53,8 +67,6 @@ namespace Avalonia.Rendering.Composition.Animations
private T _direct;
private T? _lastAnimated;
public T Direct => _direct;
public T GetAnimated(ServerCompositor compositor)
{
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;
/// <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
{
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
{
public abstract class CompositionAnimation : CompositionObject, ICompositionAnimationBase
{
private readonly CompositionPropertySet _propertySet;
internal CompositionAnimation(Compositor compositor) : base(compositor, null!)
{
_propertySet = new CompositionPropertySet(compositor);
}
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() => _propertySet.Snapshot();
void ICompositionAnimationBase.InternalOnly()
{
}
}
/// <summary>
/// This is the base class for ExpressionAnimation and KeyFrameAnimation.
/// </summary>
/// <remarks>
/// Use the <see cref="CompositionObject.StartAnimation"/> method to start the animation.
/// 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
/// the value of the ExpressionAnimation.
/// See the remarks section of ExpressionAnimation for additional information.
/// </remarks>
public abstract class CompositionAnimation : CompositionObject, ICompositionAnimationBase
{
private readonly CompositionPropertySet _propertySet;
internal CompositionAnimation(Compositor compositor) : base(compositor, null!)
{
_propertySet = new CompositionPropertySet(compositor);
}
/// <summary>
/// Clears all of the parameters of the animation.
/// </summary>
public void ClearAllParameters() => _propertySet.ClearAll();
/// <summary>
/// Clears a parameter from the animation.
/// </summary>
public void ClearParameter(string key) => _propertySet.Clear(key);
void SetVariant(string key, ExpressionVariant value) => _propertySet.Set(key, value);
public void SetColorParameter(string key, 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);
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
{
/// <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
{
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
{
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
{
/// <summary>
/// Server-side counterpart of <see cref="ExpressionAnimation"/> with values baked-in.
/// </summary>
internal class ExpressionAnimationInstance : AnimationInstanceBase, IAnimationInstance
{
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
{
/// <summary>
/// Base class for composition animations.
/// </summary>
public interface ICompositionAnimationBase
{
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
{
/// <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>
{
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
{
/// <summary>
/// An interface to define interpolation logic for a particular type
/// </summary>
internal interface IInterpolator<T>
{
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
{
/// <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
{
private TimeSpan _duration = TimeSpan.FromMilliseconds(1);
internal KeyFrameAnimation(Compositor compositor) : base(compositor)
{
}
/// <summary>
/// The delay behavior of the key frame animation.
/// </summary>
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 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; }
/// <summary>
/// The number of times to repeat the key frame animation.
/// </summary>
public int IterationCount { get; set; } = 1;
/// <summary>
/// Specifies how to set the property value when animation is stopped
/// </summary>
public AnimationStopBehavior StopBehavior { get; set; }
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,
CompositionEasingFunction easingFunction) =>
KeyFrames.InsertExpressionKeyFrame(normalizedProgressKey, value, easingFunction);
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value)
=> KeyFrames.InsertExpressionKeyFrame(normalizedProgressKey, value, new LinearEasingFunction(Compositor));
Easing? easingFunction = null) =>
KeyFrames.InsertExpressionKeyFrame(normalizedProgressKey, value, easingFunction ?? Compositor.DefaultEasing);
}
/// <summary>
/// Specifies the animation delay behavior.
/// </summary>
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,
/// <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
}
public enum AnimationDirection
{
Normal,
Reverse,
Alternate,
AlternateReverse
}
/// <summary>
/// Specifies if the animation should loop.
/// </summary>
public enum AnimationIterationBehavior
{
/// <summary>
/// The animation should loop the specified number of times.
/// </summary>
Count,
/// <summary>
/// The animation should loop forever.
/// </summary>
Forever
}
/// <summary>
/// Specifies the behavior of an animation when it stops.
/// </summary>
public enum AnimationStopBehavior
{
/// <summary>
/// Leave the animation at its current value.
/// </summary>
LeaveCurrentValue,
/// <summary>
/// Reset the animation to its initial value.
/// </summary>
SetToInitialValue,
/// <summary>
/// Set the animation to its final value.
/// </summary>
SetToFinalValue
}
}

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

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

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

@ -1,9 +1,15 @@
using System;
using System.Collections.Generic;
using Avalonia.Animation.Easings;
using Avalonia.Rendering.Composition.Expressions;
namespace Avalonia.Rendering.Composition.Animations
{
/// <summary>
/// Collection of composition animation key frames
/// </summary>
/// <typeparam name="T"></typeparam>
class KeyFrames<T> : List<KeyFrame<T>>, IKeyFrames
{
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");
}
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value,
CompositionEasingFunction easingFunction)
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value, IEasing easingFunction)
{
Validate(normalizedProgressKey);
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);
Add(new KeyFrame<T>
@ -47,7 +52,7 @@ namespace Avalonia.Rendering.Composition.Animations
{
Expression = f.Expression,
Value = f.Value,
EasingFunction = f.EasingFunction.Snapshot(),
EasingFunction = f.EasingFunction,
Key = f.NormalizedProgressKey
};
}
@ -55,26 +60,30 @@ namespace Avalonia.Rendering.Composition.Animations
}
}
/// <summary>
/// Composition animation key frame
/// </summary>
struct KeyFrame<T>
{
public float NormalizedProgressKey;
public T Value;
public Expression Expression;
public CompositionEasingFunction EasingFunction;
public IEasing EasingFunction;
}
/// <summary>
/// Server-side composition animation key frame
/// </summary>
struct ServerKeyFrame<T>
{
public T Value;
public Expression? Expression;
public IEasingFunction EasingFunction;
public IEasing EasingFunction;
public float Key;
}
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
{
/// <summary>
/// A snapshot of properties used by an animation
/// </summary>
internal class PropertySetSnapshot : IExpressionParameterCollection, IExpressionObject
{
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;
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 Compositor _compositor;
@ -24,9 +27,10 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
private readonly CompositionTarget _target;
private bool _queuedUpdate;
private Action _update;
private Action _invalidateScene;
/// <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>
public bool RenderOnlyOnRenderThread { get; set; } = true;
@ -39,20 +43,24 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
_target = compositor.CreateCompositionTarget(root.CreateRenderTarget);
_target.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor);
_update = Update;
_invalidateScene = InvalidateScene;
}
/// <inheritdoc/>
public bool DrawFps
{
get => _target.DrawFps;
set => _target.DrawFps = value;
}
/// <inheritdoc/>
public bool DrawDirtyRects
{
get => _target.DrawDirtyRects;
set => _target.DrawDirtyRects = value;
}
/// <inheritdoc/>
public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated;
void QueueUpdate()
@ -62,12 +70,15 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
_queuedUpdate = true;
Dispatcher.UIThread.Post(_update, DispatcherPriority.Composition);
}
/// <inheritdoc/>
public void AddDirty(IVisual visual)
{
_dirty.Add((Visual)visual);
QueueUpdate();
}
/// <inheritdoc/>
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool>? 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)
{
// TODO: Optimize
return HitTest(p, root, filter).FirstOrDefault();
}
/// <inheritdoc/>
public void RecalculateChildren(IVisual visual)
{
_recalculateChildren.Add((Visual)visual);
@ -172,7 +185,10 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
compositionChildren.Add(compositionChild);
}
}
private void InvalidateScene() =>
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));
private void Update()
{
_queuedUpdate = false;
@ -223,6 +239,7 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
_recalculateChildren.Clear();
_target.Size = _root.ClientSize;
_target.Scaling = _root.RenderScaling;
Compositor.InvokeOnNextCommit(_invalidateScene);
}
public void Resized(Size size)
@ -257,6 +274,8 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor
_compositor.RequestCommitAsync().Wait();
}
/// <summary>
/// The associated <see cref="Avalonia.Rendering.Composition.Compositor"/> object
/// </summary>
public Compositor Compositor => _compositor;
}

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

@ -7,12 +7,23 @@ using Avalonia.VisualTree;
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
{
/// <summary>
/// The associated <see cref="Avalonia.Visual"/>
/// </summary>
public Visual Visual { get; }
private bool _drawListChanged;
private CompositionDrawList? _drawList;
/// <summary>
/// The list of drawing commands
/// </summary>
public CompositionDrawList? 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
{
/// <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
{
/// <summary>
/// The collection of implicit animations attached to this object.
/// </summary>
public ImplicitAnimationCollection? ImplicitAnimations { get; set; }
internal CompositionObject(Compositor compositor, ServerObject server)
{
@ -15,6 +23,9 @@ namespace Avalonia.Rendering.Composition
Server = server;
}
/// <summary>
/// The associated Compositor
/// </summary>
public Compositor Compositor { get; }
internal ServerObject Server { get; }
public bool IsDisposed { get; private set; }
@ -29,14 +40,22 @@ namespace Avalonia.Rendering.Composition
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)
=> 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);
}
/// <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)
{
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
{
/// <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
{
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
{
/// <summary>
/// Represents the composition output (e. g. a window, embedded control, entire screen)
/// </summary>
public partial class CompositionTarget
{
partial void OnRootChanged()
@ -20,6 +23,12 @@ namespace Avalonia.Rendering.Composition
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)
{
Server.Readback.NextRead();
@ -30,6 +39,10 @@ namespace Avalonia.Rendering.Composition
return res;
}
/// <summary>
/// Attempts to transform a point to a particular CompositionVisual coordinate space
/// </summary>
/// <returns></returns>
public Point? TryTransformToVisual(CompositionVisual visual, Point point)
{
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();
/// <summary>
/// Performs composition directly on the UI thread
/// </summary>
internal void ImmediateUIThreadRender()
{
Compositor.RequestCommitAsync();

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

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Threading.Tasks;
using Avalonia.Animation.Easings;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Animations;
@ -13,6 +14,10 @@ using Avalonia.Threading;
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
{
internal IRenderLoop Loop { get; }
@ -23,22 +28,38 @@ namespace Avalonia.Rendering.Composition
private BatchStreamMemoryPool _batchMemoryPool = new();
private List<CompositionObject> _objectsForSerialization = new();
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)
{
Loop = loop;
_server = new ServerCompositor(loop, gpu, _batchObjectPool, _batchMemoryPool);
_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)
{
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()
{
var batch = new Batch();
@ -59,64 +80,25 @@ namespace Avalonia.Rendering.Composition
batch.CommitedAt = Server.Clock.Elapsed;
_server.EnqueueBatch(batch);
if (_invokeOnNextCommit != null)
ScheduleCommitCallbacks(batch.Completed);
return batch.Completed;
}
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)
async void ScheduleCommitCallbacks(Task task)
{
var bmp = _server.Backend.LoadCpuMemoryBitmap(stream);
return new CompositionBitmapSurface(this, bmp);
var list = _invokeOnNextCommit;
_invokeOnNextCommit = null;
await task;
foreach (var i in list!)
i();
list.Clear();
_invokeListPool.Push(list);
}
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 CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server));
public ExpressionAnimation CreateExpressionAnimation() => new ExpressionAnimation(this);
public ExpressionAnimation CreateExpressionAnimation(string expression) => new ExpressionAnimation(this)
@ -147,5 +129,11 @@ namespace Avalonia.Rendering.Composition
_objectsForSerialization.Add(compositionObject);
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
{
/// <summary>
/// A node in the visual tree that can have children.
/// </summary>
public partial class CompositionContainerVisual : CompositionVisual
{
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;
/// <summary>
/// A list of serialized drawing commands
/// </summary>
internal class CompositionDrawList : PooledList<IRef<IDrawOperation>>
{
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
{
private CompositionDrawList? _operations;

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

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

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

@ -1,6 +1,14 @@
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;
}

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

@ -2,10 +2,13 @@ using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Utils;
using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Expressions
{
/// <summary>
/// Built-in functions for Foreign Function Interface available from composition animation expressions
/// </summary>
internal class BuiltInExpressionFfi : IExpressionForeignFunctionInterface
{
private readonly DelegateExpressionFfi _registry;
@ -26,7 +29,7 @@ namespace Avalonia.Rendering.Composition.Expressions
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);
}
@ -72,7 +75,7 @@ namespace Avalonia.Rendering.Composition.Expressions
{"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", (float a1, float a2, float a3) => MathUtilities.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)},
@ -96,10 +99,10 @@ namespace Avalonia.Rendering.Composition.Expressions
},
{
"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)
(byte) MathUtilities.Clamp(a, 0, 255),
(byte) MathUtilities.Clamp(r, 0, 255),
(byte) MathUtilities.Clamp(g, 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
{
/// <summary>
/// Foreign function interface for composition animations based on calling delegates
/// </summary>
internal class DelegateExpressionFfi : IExpressionForeignFunctionInterface, IEnumerable
{
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
{
/// <summary>
/// A parsed composition expression
/// </summary>
internal abstract class Expression
{
public abstract ExpressionType Type { get; }

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

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

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

@ -3,6 +3,9 @@ using System.Globalization;
namespace Avalonia.Rendering.Composition.Expressions
{
/// <summary>
/// Helper class for composition expression parser
/// </summary>
internal ref struct TokenParser
{
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;
/// <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
{
private IDrawingContextImpl _impl;

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

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

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

@ -1,5 +1,10 @@
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
{
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;
/// <summary>
/// Server-side counterpart of <see cref="CompositionDrawListVisual"/>
/// </summary>
internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisual
{
#if DEBUG
// This is needed for debugging purposes so we could see inspect the associated visual from debugger
public readonly Visual UiVisual;
#endif
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
{
/// <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
{
private readonly ServerCompositor _compositor;
@ -172,6 +176,7 @@ namespace Avalonia.Rendering.Composition.Server
_renderTarget?.Dispose();
_renderTarget = null;
}
_compositor.RemoveCompositionTarget(this);
}
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
{
/// <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
{
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
{
/// <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
{
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
{
/// <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
{
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
{
/// <summary>
/// Server-side <see cref="CompositionObject" /> counterpart.
/// Is responsible for animation activation and invalidation
/// </summary>
internal abstract class ServerObject : IExpressionObject
{
public ServerCompositor Compositor { get; }
@ -92,13 +96,15 @@ namespace Avalonia.Rendering.Composition.Server
public void SubscribeToInvalidation(int member, IAnimationInstance animation)
{
ref var store = ref GetStoreFromOffset(member);
if (store.Subscribers == null)
store.Subscribers = new();
store.Subscribers.AddRef(animation);
}
public void UnsubscribeFromInvalidation(int member, IAnimationInstance animation)
{
ref var store = ref GetStoreFromOffset(member);
store.Subscribers.ReleaseRef(animation);
store.Subscribers?.ReleaseRef(animation);
}
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
{
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 _isBackface;
@ -51,7 +57,10 @@ namespace Avalonia.Rendering.Composition.Server
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)
{
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 Matrix4x4 Matrix;
@ -126,7 +138,6 @@ namespace Avalonia.Rendering.Composition.Server
public long TargetId;
public bool Visible;
}
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
{
/// <summary>
/// Represents a group of serialized changes from the UI thread to be atomically applied at the render thread
/// </summary>
internal class Batch
{
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;
/// <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
{
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
{
/// <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>
where TServer : ServerObject
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
{
/// <summary>
/// The base visual object in the composition visual hierarchy.
/// </summary>
public abstract partial class CompositionVisual
{
private IBrush? _opacityMask;

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

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

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

@ -251,6 +251,20 @@ namespace Avalonia.Utilities
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>
/// 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;
/// <summary>
/// Maintains a set of objects with reference counts
/// </summary>
internal class RefTrackingDictionary<TKey> : Dictionary<TKey, int> where TKey : class
{
/// <summary>

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

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

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

@ -8,6 +8,9 @@ using Avalonia.Controls;
namespace Avalonia.Markup.Parsers
{
/// <summary>
/// Parser for composition expressions
/// </summary>
internal class ExpressionParser
{
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 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);
}}

Loading…
Cancel
Save