diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs index b37231243d..74ee43ed92 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs +++ b/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; } diff --git a/src/Avalonia.Base/Rendering/Composition/Utils/CubicBezier.cs b/src/Avalonia.Base/Animation/Easings/CubicBezier.cs similarity index 97% rename from src/Avalonia.Base/Rendering/Composition/Utils/CubicBezier.cs rename to src/Avalonia.Base/Animation/Easings/CubicBezier.cs index 8c85d7978b..5c2487a516 100644 --- a/src/Avalonia.Base/Rendering/Composition/Utils/CubicBezier.cs +++ b/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 { + /// + /// Represents a cubic bezier curve and can compute Y coordinate for a given X + /// 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); diff --git a/src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs b/src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs new file mode 100644 index 0000000000..e51c35c4e6 --- /dev/null +++ b/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); + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 38d114eca2..4c988c8ae1 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -35,6 +35,10 @@ - + + + + + diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/AnimatedValueStore.cs b/src/Avalonia.Base/Rendering/Composition/Animations/AnimatedValueStore.cs index 95bc384743..f4735040d5 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/AnimatedValueStore.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/AnimatedValueStore.cs @@ -7,10 +7,15 @@ using Avalonia.Utilities; namespace Avalonia.Rendering.Composition.Animations { + /// + /// This is the first element of both animated and non-animated value stores. + /// It's used to propagate property invalidation to subscribers + /// + internal struct ServerObjectSubscriptionStore { public bool IsValid; - public RefTrackingDictionary Subscribers; + public RefTrackingDictionary? Subscribers; public void Invalidate() { @@ -23,6 +28,10 @@ namespace Avalonia.Rendering.Composition.Animations } } + /// + /// The value store for non-animated values that can still be referenced by animations. + /// Simply stores the value and notifies subscribers + /// [StructLayout(LayoutKind.Sequential)] internal struct ServerValueStore { @@ -43,7 +52,12 @@ namespace Avalonia.Rendering.Composition.Animations } } } - + + /// + /// 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 + /// + /// [StructLayout(LayoutKind.Sequential)] internal struct ServerAnimatedValueStore 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; diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs index 212237049f..35aa8de1bc 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs @@ -5,6 +5,11 @@ using Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Animations; + +/// +/// The base class for both key-frame and expression animation instances +/// Is responsible for activation tracking and for subscribing to properties used in dependencies +/// internal abstract class AnimationInstanceBase : IAnimationInstance { private List<(ServerObject obj, int member)>? _trackedObjects; diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs index cf81c6e656..c5102a2d7d 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs +++ b/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() - { - - } - } + /// + /// This is the base class for ExpressionAnimation and KeyFrameAnimation. + /// + /// + /// Use the method to start the animation. + /// Value parameters (as opposed to reference parameters which are set using ) + /// are copied and "embedded" into an expression at the time CompositionObject.StartAnimation is called. + /// Changing the value of the variable after is called will not affect + /// the value of the ExpressionAnimation. + /// See the remarks section of ExpressionAnimation for additional information. + /// + public abstract class CompositionAnimation : CompositionObject, ICompositionAnimationBase + { + private readonly CompositionPropertySet _propertySet; + internal CompositionAnimation(Compositor compositor) : base(compositor, null!) + { + _propertySet = new CompositionPropertySet(compositor); + } + + /// + /// Clears all of the parameters of the animation. + /// + public void ClearAllParameters() => _propertySet.ClearAll(); + + /// + /// Clears a parameter from the animation. + /// + 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() + { + + } + } } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs index 6a2c07e6ef..163f4e99ba 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs @@ -5,6 +5,17 @@ using Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Animations { + /// + /// A Composition Animation that uses a mathematical equation to calculate the value for an animating property every frame. + /// + /// + /// 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 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 method to start the animation. + /// public class ExpressionAnimation : CompositionAnimation { private string? _expression; @@ -14,6 +25,14 @@ namespace Avalonia.Rendering.Composition.Animations { } + /// + /// The mathematical equation specifying how the animated value is calculated each frame. + /// The Expression is the core of an 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. + /// public string? Expression { get => _expression; diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs index 7944fe7990..445cef9a08 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimationInstance.cs @@ -5,6 +5,10 @@ using Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Animations { + + /// + /// Server-side counterpart of with values baked-in. + /// internal class ExpressionAnimationInstance : AnimationInstanceBase, IAnimationInstance { private readonly Expression _expression; diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs index bf40fd3ad2..87e5ad757a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ICompositionAnimationBase.cs @@ -4,6 +4,9 @@ using Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Animations { + /// + /// Base class for composition animations. + /// public interface ICompositionAnimationBase { internal void InternalOnly(); diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs index fa5b69dae9..f4bcc6ff38 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs @@ -6,6 +6,17 @@ using Avalonia.Rendering.Composition.Transport; namespace Avalonia.Rendering.Composition.Animations { + /// + /// A collection of animations triggered when a condition is met. + /// + /// + /// 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 + /// public class ImplicitAnimationCollection : CompositionObject, IDictionary { private Dictionary _inner = new Dictionary(); diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs b/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs index 62b790701a..a4eeacef32 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs @@ -3,6 +3,9 @@ using System.Numerics; namespace Avalonia.Rendering.Composition.Animations { + /// + /// An interface to define interpolation logic for a particular type + /// internal interface IInterpolator { T Interpolate(T from, T to, float progress); diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs index 065dfd7a8e..49b3ab753a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs +++ b/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 { + + /// + /// 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. + /// public abstract class KeyFrameAnimation : CompositionAnimation { + private TimeSpan _duration = TimeSpan.FromMilliseconds(1); + internal KeyFrameAnimation(Compositor compositor) : base(compositor) { } + /// + /// The delay behavior of the key frame animation. + /// public AnimationDelayBehavior DelayBehavior { get; set; } + + /// + /// Delay before the animation starts after is called. + /// public System.TimeSpan DelayTime { get; set; } - public AnimationDirection Direction { get; set; } - public System.TimeSpan Duration { get; set; } + + /// + /// 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 greater than one. + /// This gives an easy way for customizing animation definitions. + /// + public PlaybackDirection Direction { get; set; } + + /// + /// The duration of the animation. + /// Minimum allowed value is 1ms and maximum allowed value is 24 days. + /// + 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; + } + } + + /// + /// The iteration behavior for the key frame animation. + /// public AnimationIterationBehavior IterationBehavior { get; set; } + + /// + /// The number of times to repeat the key frame animation. + /// public int IterationCount { get; set; } = 1; + + /// + /// Specifies how to set the property value when animation is stopped + /// public AnimationStopBehavior StopBehavior { get; set; } private protected abstract IKeyFrames KeyFrames { get; } + /// + /// Inserts an expression keyframe. + /// + /// + /// 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. + /// + /// The expression used to calculate the value of the key frame. + /// The easing function to use when interpolating between frames. 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); } + /// + /// Specifies the animation delay behavior. + /// public enum AnimationDelayBehavior { + /// + /// 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. + /// SetInitialValueAfterDelay, + /// + /// 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. + /// SetInitialValueBeforeDelay } - public enum AnimationDirection - { - Normal, - Reverse, - Alternate, - AlternateReverse - } - + /// + /// Specifies if the animation should loop. + /// public enum AnimationIterationBehavior { + /// + /// The animation should loop the specified number of times. + /// Count, + /// + /// The animation should loop forever. + /// Forever } + /// + /// Specifies the behavior of an animation when it stops. + /// public enum AnimationStopBehavior { + /// + /// Leave the animation at its current value. + /// LeaveCurrentValue, + /// + /// Reset the animation to its initial value. + /// SetToInitialValue, + /// + /// Set the animation to its final value. + /// SetToFinalValue } } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs index 9571cef0b4..7268780298 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs +++ b/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 { + /// + /// Server-side counterpart of KeyFrameAnimation with values baked-in + /// class KeyFrameAnimationInstance : AnimationInstanceBase, IAnimationInstance where T : struct { private readonly IInterpolator _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; diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs index 26ba35409d..369cc80b95 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrames.cs +++ b/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 { + + /// + /// Collection of composition animation key frames + /// + /// class KeyFrames : List>, 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 @@ -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 @@ -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 } } + /// + /// Composition animation key frame + /// struct KeyFrame { public float NormalizedProgressKey; public T Value; public Expression Expression; - public CompositionEasingFunction EasingFunction; + public IEasing EasingFunction; } + /// + /// Server-side composition animation key frame + /// struct ServerKeyFrame { 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); } } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs b/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs index ca703dfc6f..fc6cfc9f3d 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/PropertySetSnapshot.cs @@ -3,6 +3,9 @@ using Avalonia.Rendering.Composition.Expressions; namespace Avalonia.Rendering.Composition.Animations { + /// + /// A snapshot of properties used by an animation + /// internal class PropertySetSnapshot : IExpressionParameterCollection, IExpressionObject { private readonly Dictionary _dic; diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index 8cb7daca6b..5e8e9c24f9 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -13,7 +13,10 @@ using Avalonia.VisualTree; namespace Avalonia.Rendering.Composition; -public class CompositingRenderer : RendererBase, IRendererWithCompositor +/// +/// A renderer that utilizes to render the visual tree +/// +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; /// - /// 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. /// 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; } + /// public bool DrawFps { get => _target.DrawFps; set => _target.DrawFps = value; } - + + /// public bool DrawDirtyRects { get => _target.DrawDirtyRects; set => _target.DrawDirtyRects = value; } + /// public event EventHandler? SceneInvalidated; void QueueUpdate() @@ -62,12 +70,15 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor _queuedUpdate = true; Dispatcher.UIThread.Post(_update, DispatcherPriority.Composition); } + + /// public void AddDirty(IVisual visual) { _dirty.Add((Visual)visual); QueueUpdate(); } + /// public IEnumerable HitTest(Point p, IVisual root, Func? filter) { var res = _target.TryHitTest(p, filter); @@ -84,12 +95,14 @@ public class CompositingRenderer : RendererBase, IRendererWithCompositor } } + /// public IVisual? HitTestFirst(Point p, IVisual root, Func? filter) { // TODO: Optimize return HitTest(p, root, filter).FirstOrDefault(); } + /// 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(); } - + /// + /// The associated object + /// public Compositor Compositor => _compositor; } diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs index cc2b411822..47cfcd325b 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs @@ -7,12 +7,23 @@ using Avalonia.VisualTree; namespace Avalonia.Rendering.Composition; + +/// +/// A composition visual that holds a list of drawing commands issued by +/// internal class CompositionDrawListVisual : CompositionContainerVisual { + /// + /// The associated + /// public Visual Visual { get; } private bool _drawListChanged; private CompositionDrawList? _drawList; + + /// + /// The list of drawing commands + /// public CompositionDrawList? DrawList { get => _drawList; diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionEasingFunction.cs b/src/Avalonia.Base/Rendering/Composition/CompositionEasingFunction.cs deleted file mode 100644 index 90b2bec268..0000000000 --- a/src/Avalonia.Base/Rendering/Composition/CompositionEasingFunction.cs +++ /dev/null @@ -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); - } - -} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionGradientBrush.cs b/src/Avalonia.Base/Rendering/Composition/CompositionGradientBrush.cs deleted file mode 100644 index cf222550dd..0000000000 --- a/src/Avalonia.Base/Rendering/Composition/CompositionGradientBrush.cs +++ /dev/null @@ -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; } - } - - -} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs b/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs index baf1bfcddf..0ed9a3c75d 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs @@ -6,8 +6,16 @@ using Avalonia.Rendering.Composition.Transport; namespace Avalonia.Rendering.Composition { + /// + /// 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 objects each representing a single node in a Visual tree. + /// public abstract class CompositionObject : IDisposable { + /// + /// The collection of implicit animations attached to this object. + /// public ImplicitAnimationCollection? ImplicitAnimations { get; set; } internal CompositionObject(Compositor compositor, ServerObject server) { @@ -15,6 +23,9 @@ namespace Avalonia.Rendering.Composition Server = server; } + /// + /// The associated Compositor + /// public Compositor Compositor { get; } internal ServerObject Server { get; } public bool IsDisposed { get; private set; } @@ -29,14 +40,22 @@ namespace Avalonia.Rendering.Composition IsDisposed = true; } + /// + /// Connects an animation with the specified property of the object and starts the animation. + /// 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); } + /// + /// 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. + /// public void StartAnimationGroup(ICompositionAnimationBase grp) { if (grp is CompositionAnimation animation) diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs b/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs index 584969cbc0..ee4552d154 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs @@ -7,6 +7,15 @@ using Avalonia.Rendering.Composition.Transport; namespace Avalonia.Rendering.Composition { + /// + /// s are 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. + /// s are most commonly used with animations, where they maintain key-value pairs + /// that are referenced to drive portions of composition animations. s + /// provide the ability to insert key-value pairs or retrieve a value for a given key. + /// does not support a delete function – ensure you use + /// to store values that will be shared across the application. + /// public class CompositionPropertySet : CompositionObject { private readonly Dictionary _variants = new Dictionary(); diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs index 7e50c43ac1..25bbd4dc88 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs @@ -6,6 +6,9 @@ using Avalonia.VisualTree; namespace Avalonia.Rendering.Composition { + /// + /// Represents the composition output (e. g. a window, embedded control, entire screen) + /// public partial class CompositionTarget { partial void OnRootChanged() @@ -20,6 +23,12 @@ namespace Avalonia.Rendering.Composition Root.Root = null; } + /// + /// Attempts to perform a hit-tst + /// + /// + /// + /// public PooledList? TryHitTest(Point point, Func? filter) { Server.Readback.NextRead(); @@ -30,6 +39,10 @@ namespace Avalonia.Rendering.Composition return res; } + /// + /// Attempts to transform a point to a particular CompositionVisual coordinate space + /// + /// public Point? TryTransformToVisual(CompositionVisual visual, Point point) { if (visual.Root != this) @@ -108,8 +121,14 @@ namespace Avalonia.Rendering.Composition } + /// + /// Registers the composition target for explicit redraw + /// public void RequestRedraw() => RegisterForSerialization(); + /// + /// Performs composition directly on the UI thread + /// internal void ImmediateUIThreadRender() { Compositor.RequestCommitAsync(); diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index e92b69bd60..28c81dfb91 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/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 { + /// + /// 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 + /// public partial class Compositor { internal IRenderLoop Loop { get; } @@ -23,22 +28,38 @@ namespace Avalonia.Rendering.Composition private BatchStreamMemoryPool _batchMemoryPool = new(); private List _objectsForSerialization = new(); internal ServerCompositor Server => _server; - internal CompositionEasingFunction DefaultEasing { get; } - + internal IEasing DefaultEasing { get; } + private List? _invokeOnNextCommit; + private readonly Stack> _invokeListPool = new(); + + /// + /// Creates a new compositor on a specified render loop that would use a particular GPU + /// + /// + /// 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)); } + /// + /// Creates a new CompositionTarget + /// + /// A factory method to create IRenderTarget to be called from the render thread + /// public CompositionTarget CreateCompositionTarget(Func renderTargetFactory) { return new CompositionTarget(this, new ServerCompositionTarget(_server, renderTargetFactory)); } + /// + /// Requests pending changes in the composition objects to be serialized and sent to the render thread + /// + /// A task that completes when sent changes are applied and rendered on the render thread 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 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); + } } } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/CompositorRenderLoopTask.cs b/src/Avalonia.Base/Rendering/Composition/CompositorRenderLoopTask.cs deleted file mode 100644 index 074c0a9ccf..0000000000 --- a/src/Avalonia.Base/Rendering/Composition/CompositorRenderLoopTask.cs +++ /dev/null @@ -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(); - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs b/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs index 5b2a4be1bc..caf074dd6b 100644 --- a/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/ContainerVisual.cs @@ -2,6 +2,9 @@ using Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition { + /// + /// A node in the visual tree that can have children. + /// public partial class CompositionContainerVisual : CompositionVisual { public CompositionVisualCollection Children { get; private set; } = null!; diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs index 1d416f5a8a..315faf2c86 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawList.cs @@ -6,6 +6,9 @@ using Avalonia.Utilities; namespace Avalonia.Rendering.Composition.Drawing; +/// +/// A list of serialized drawing commands +/// internal class CompositionDrawList : PooledList> { public Size? Size { get; set; } @@ -47,6 +50,9 @@ internal class CompositionDrawList : PooledList> } } +/// +/// An helper class for building +/// internal class CompositionDrawListBuilder { private CompositionDrawList? _operations; diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs index 0c5a98b239..e678a85fcf 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs @@ -10,6 +10,9 @@ using Avalonia.Utilities; using Avalonia.VisualTree; namespace Avalonia.Rendering.Composition; +/// +/// An IDrawingContextImpl implementation that builds +/// internal class CompositionDrawingContext : IDrawingContextImpl { private CompositionDrawListBuilder _builder = new(); diff --git a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs index afda314276..1397a20fb6 100644 --- a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs +++ b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs @@ -1,6 +1,14 @@ namespace Avalonia.Rendering.Composition; -public static class ElementCompositionPreview +/// +/// Enables access to composition visual objects that back XAML elements in the XAML composition tree. +/// +public static class ElementComposition { + /// + /// Gets CompositionVisual that backs a Visual + /// + /// + /// public static CompositionVisual? GetElementVisual(Visual visual) => visual.CompositionVisual; } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs index db9a26e301..44347d2c7a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/BuiltInExpressionFfi.cs +++ b/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 { + /// + /// Built-in functions for Foreign Function Interface available from composition animation expressions + /// 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) ) }, diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs index 002cf37522..85c6141409 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/DelegateExpressionFfi.cs @@ -7,6 +7,9 @@ using Avalonia.Media; namespace Avalonia.Rendering.Composition.Expressions { + /// + /// Foreign function interface for composition animations based on calling delegates + /// internal class DelegateExpressionFfi : IExpressionForeignFunctionInterface, IEnumerable { struct FfiRecord diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs index 088771e1ba..5abba00365 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs @@ -6,6 +6,9 @@ using Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Expressions { + /// + /// A parsed composition expression + /// internal abstract class Expression { public abstract ExpressionType Type { get; } diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs index 086c8ce276..7b900534d8 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs @@ -22,6 +22,9 @@ namespace Avalonia.Rendering.Composition.Expressions Color } + /// + /// A VARIANT type used in expression animations. Can represent multiple value types + /// [StructLayout(LayoutKind.Explicit)] internal struct ExpressionVariant { diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs index 1050c7274c..27782c8c2c 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/TokenParser.cs @@ -3,6 +3,9 @@ using System.Globalization; namespace Avalonia.Rendering.Composition.Expressions { + /// + /// Helper class for composition expression parser + /// internal ref struct TokenParser { private ReadOnlySpan _s; diff --git a/src/Avalonia.Base/Rendering/Composition/ICompositionSurface.cs b/src/Avalonia.Base/Rendering/Composition/ICompositionSurface.cs deleted file mode 100644 index 9ef31c30e0..0000000000 --- a/src/Avalonia.Base/Rendering/Composition/ICompositionSurface.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Avalonia.Rendering.Composition.Server; - -namespace Avalonia.Rendering.Composition -{ - public interface ICompositionSurface - { - internal ServerCompositionSurface Server { get; } - } -} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index 8b6ac5b0c2..1b85159b02 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -8,6 +8,13 @@ using Avalonia.Utilities; namespace Avalonia.Rendering.Composition.Server; +/// +/// 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. +/// internal class CompositorDrawingContextProxy : IDrawingContextImpl { private IDrawingContextImpl _impl; diff --git a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs index a60084d8f3..a09de2c0ff 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs @@ -8,6 +8,9 @@ using Avalonia.Utilities; namespace Avalonia.Rendering.Composition.Server; +/// +/// An FPS counter helper that can draw itself on the render thread +/// 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) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs b/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs index 1971451811..c9592b70ab 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ReadbackIndices.cs @@ -1,5 +1,10 @@ namespace Avalonia.Rendering.Composition.Server { + /// + /// 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 + /// internal class ReadbackIndices { private readonly object _lock = new object(); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionBrush.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionBrush.cs deleted file mode 100644 index eb041aaf88..0000000000 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionBrush.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Avalonia.Rendering.Composition.Server -{ - internal abstract partial class ServerCompositionBrush : ServerObject - { - - } -} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs index 45062632c7..eff1af65e2 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs @@ -9,9 +9,13 @@ using Avalonia.Utilities; namespace Avalonia.Rendering.Composition.Server; +/// +/// Server-side counterpart of +/// 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; diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionGradientBrush.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionGradientBrush.cs deleted file mode 100644 index 0948b9692f..0000000000 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionGradientBrush.cs +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 24a5f0294a..8b903c4382 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -10,6 +10,10 @@ using Avalonia.Utilities; namespace Avalonia.Rendering.Composition.Server { + /// + /// Server-side counterpart of the + /// That's the place where we update visual transforms, track dirty rects and actually do rendering + /// 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) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs index 811fffd8eb..73792fdf98 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs @@ -8,6 +8,12 @@ using Avalonia.Rendering.Composition.Transport; namespace Avalonia.Rendering.Composition.Server { + /// + /// Server-side counterpart of the . + /// 1) manages deserialization of changes received from the UI thread + /// 2) triggers animation ticks + /// 3) asks composition targets to render themselves + /// internal class ServerCompositor : IRenderLoopTask { private readonly IRenderLoop _renderLoop; diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerContainerVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerContainerVisual.cs index bcfcfbe4f2..136ebc1d63 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerContainerVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerContainerVisual.cs @@ -3,6 +3,11 @@ using Avalonia.Platform; namespace Avalonia.Rendering.Composition.Server { + /// + /// Server-side counterpart of . + /// Mostly propagates update and render calls, but is also responsible + /// for updating adorners in deferred manner + /// internal partial class ServerCompositionContainerVisual : ServerCompositionVisual { public ServerCompositionVisualCollection Children { get; private set; } = null!; diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs index 4beea4715b..39d6a8dc70 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs @@ -4,6 +4,10 @@ using Avalonia.Rendering.Composition.Transport; namespace Avalonia.Rendering.Composition.Server { + /// + /// 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 + /// class ServerList : ServerObject where T : ServerObject { public List List { get; } = new List(); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs index f55c9439e4..e5ae7fa859 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs @@ -9,6 +9,10 @@ using Avalonia.Utilities; namespace Avalonia.Rendering.Composition.Server { + /// + /// Server-side counterpart. + /// Is responsible for animation activation and invalidation + /// 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; diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerSolidColorVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerSolidColorVisual.cs deleted file mode 100644 index 5720d80304..0000000000 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerSolidColorVisual.cs +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerSpriteVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerSpriteVisual.cs deleted file mode 100644 index 2f4c446cfa..0000000000 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerSpriteVisual.cs +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs index d42bcae7e7..0c58647319 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs +++ b/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 + /// + /// Server-side counterpart. + /// Is responsible for computing the transformation matrix, for applying various visual + /// properties before calling visual-specific drawing code and for notifying the + /// for new dirty rects + /// + partial class ServerCompositionVisual : ServerObject { private bool _isDirty; private bool _isBackface; @@ -51,7 +57,10 @@ namespace Avalonia.Rendering.Composition.Server private ReadbackData _readback0, _readback1, _readback2; - + /// + /// 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 + /// public ref ReadbackData GetReadback(int idx) { if (idx == 0) @@ -119,6 +128,9 @@ namespace Avalonia.Rendering.Composition.Server } + /// + /// Data that can be read from the UI thread + /// 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) { diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs b/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs index 0714db5781..e69768d3bf 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs @@ -6,6 +6,9 @@ using System.Threading.Tasks; namespace Avalonia.Rendering.Composition.Transport { + /// + /// Represents a group of serialized changes from the UI thread to be atomically applied at the render thread + /// internal class Batch { private static long _nextSequenceId = 1; diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs index 9e9ed739fb..65237473fb 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs @@ -7,6 +7,12 @@ using Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Transport; +/// +/// 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 +/// internal class BatchStreamData { public Queue> Objects { get; } = new(); diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs b/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs index 2399bd71d7..e295c3c2c8 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/ServerListProxyHelper.cs @@ -4,6 +4,11 @@ using Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Transport { + /// + /// A helper class used from generated UI-thread-side collections of composition objects. + /// + // 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 : IList where TServer : ServerObject where TClient : CompositionObject diff --git a/src/Avalonia.Base/Rendering/Composition/Utils/MathExt.cs b/src/Avalonia.Base/Rendering/Composition/Utils/MathExt.cs deleted file mode 100644 index 0be19a8e9d..0000000000 --- a/src/Avalonia.Base/Rendering/Composition/Utils/MathExt.cs +++ /dev/null @@ -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); - } - - - } -} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Visual.cs b/src/Avalonia.Base/Rendering/Composition/Visual.cs index f092029457..f9e1eae2ab 100644 --- a/src/Avalonia.Base/Rendering/Composition/Visual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Visual.cs @@ -5,6 +5,9 @@ using Avalonia.VisualTree; namespace Avalonia.Rendering.Composition { + /// + /// The base visual object in the composition visual hierarchy. + /// public abstract partial class CompositionVisual { private IBrush? _opacityMask; diff --git a/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs b/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs index 42226a8b4d..60ebd9271c 100644 --- a/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs +++ b/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs @@ -3,6 +3,9 @@ using Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition { + /// + /// A collection of CompositionVisual objects + /// public partial class CompositionVisualCollection : CompositionObject { private CompositionVisual _owner; diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index 596cbf1d7e..3d5be806e1 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -251,6 +251,20 @@ namespace Avalonia.Utilities return val; } } + + /// + /// Clamps a value between a minimum and maximum value. + /// + /// The value. + /// The minimum value. + /// The maximum value. + /// The clamped value. + 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); + } /// /// Clamps a value between a minimum and maximum value. diff --git a/src/Avalonia.Base/Utilities/RefTrackingDictionary.cs b/src/Avalonia.Base/Utilities/RefTrackingDictionary.cs index 71305a8305..9400e37f21 100644 --- a/src/Avalonia.Base/Utilities/RefTrackingDictionary.cs +++ b/src/Avalonia.Base/Utilities/RefTrackingDictionary.cs @@ -5,6 +5,9 @@ using System.Runtime.InteropServices; namespace Avalonia.Utilities; +/// +/// Maintains a set of objects with reference counts +/// internal class RefTrackingDictionary : Dictionary where TKey : class { /// diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml index 2b555359c6..e0e177da44 100644 --- a/src/Avalonia.Base/composition-schema.xml +++ b/src/Avalonia.Base/composition-schema.xml @@ -7,7 +7,6 @@ - @@ -37,40 +36,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs index 297eef9e80..a88bfc3651 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs @@ -8,6 +8,9 @@ using Avalonia.Controls; namespace Avalonia.Markup.Parsers { + /// + /// Parser for composition expressions + /// internal class ExpressionParser { private readonly bool _enableValidation; diff --git a/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs b/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs index 314ac1acbf..7ad40f68e4 100644 --- a/src/tools/DevGenerators/CompositionGenerator/Generator.KeyFrameAnimation.cs +++ b/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); }}