From 01a9197c268ce40467d5b7610654f563258923b6 Mon Sep 17 00:00:00 2001 From: Betta_Fish Date: Sat, 29 Nov 2025 21:54:05 +0800 Subject: [PATCH 01/17] feat: Composition brush --- .../Composition/Animations/Interpolators.cs | 27 ++ .../Brushes/ServerCompositionBrush.cs | 55 ++++ .../Rendering/Composition/CompositionBrush.cs | 69 +++++ .../Drawing/RenderDataDrawingContext.cs | 13 +- .../Drawing/ServerResourceHelperExtensions.cs | 4 +- .../Expressions/ExpressionVariant.cs | 267 +++++++++++------- .../Server/ServerRenderResource.cs | 106 ++++++- src/Avalonia.Base/composition-schema.xml | 39 ++- 8 files changed, 470 insertions(+), 110 deletions(-) create mode 100644 src/Avalonia.Base/Rendering/Composition/Brushes/ServerCompositionBrush.cs create mode 100644 src/Avalonia.Base/Rendering/Composition/CompositionBrush.cs diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs b/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs index 33caef5e68..f801bd1c66 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/Interpolators.cs @@ -98,4 +98,31 @@ namespace Avalonia.Rendering.Composition.Animations public static BooleanInterpolator Instance { get; } = new BooleanInterpolator(); } + + class RelativePointInterpolator : IInterpolator + { + public Avalonia.RelativePoint Interpolate(Avalonia.RelativePoint @from, Avalonia.RelativePoint to, float progress) + { + if (@from.Unit != to.Unit) + return progress >= 0.5f ? to : @from; + + var x = DoubleInterpolator.Instance.Interpolate(@from.Point.X, to.Point.X, progress); + var y = DoubleInterpolator.Instance.Interpolate(@from.Point.Y, to.Point.Y, progress); + return new Avalonia.RelativePoint(x, y, @from.Unit); + } + + public static RelativePointInterpolator Instance { get; } = new(); + } + + class RelativeScalarInterpolator : IInterpolator + { + public Avalonia.RelativeScalar Interpolate(Avalonia.RelativeScalar @from, Avalonia.RelativeScalar to, float progress) + { + if (@from.Unit != to.Unit) + return progress >= 0.5f ? to : @from; + return new RelativeScalar(@from.Scalar + (to.Scalar - @from.Scalar) * progress, from.Unit); + } + + public static RelativeScalarInterpolator Instance { get; } = new(); + } } diff --git a/src/Avalonia.Base/Rendering/Composition/Brushes/ServerCompositionBrush.cs b/src/Avalonia.Base/Rendering/Composition/Brushes/ServerCompositionBrush.cs new file mode 100644 index 0000000000..bcde024cd7 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Brushes/ServerCompositionBrush.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using Avalonia.Media; +using Avalonia.Media.Immutable; +using Avalonia.Rendering.Composition.Transport; + +namespace Avalonia.Rendering.Composition.Server; + +internal partial class ServerCompositionBrush : IBrush +{ + ITransform? IBrush.Transform => Transform; +} + +internal class ServerCompositionGradientBrush : ServerCompositionBrush, IGradientBrush +{ + + internal ServerCompositionGradientBrush(ServerCompositor compositor) : base(compositor) + { + + } + + private readonly List _gradientStops = new(); + public IReadOnlyList GradientStops => _gradientStops; + public GradientSpreadMethod SpreadMethod { get; private set; } + + protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) + { + base.DeserializeChangesCore(reader, committedAt); + SpreadMethod = reader.Read(); + _gradientStops.Clear(); + var count = reader.Read(); + for (var c = 0; c < count; c++) + _gradientStops.Add(reader.ReadObject()); + } +} + +partial class ServerCompositionConicGradientBrush : IConicGradientBrush +{ + +} + +partial class ServerCompositionLinearGradientBrush : ILinearGradientBrush +{ + +} + +partial class ServerCompositionRadialGradientBrush : IRadialGradientBrush +{ + public double Radius => RadiusX.Scalar; +} + +partial class ServerCompositionSolidColorBrush : ISolidColorBrush +{ + +} diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionBrush.cs b/src/Avalonia.Base/Rendering/Composition/CompositionBrush.cs new file mode 100644 index 0000000000..bb356bab81 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/CompositionBrush.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using Avalonia.Media; +using Avalonia.Rendering.Composition; +using Avalonia.Rendering.Composition.Server; +using Avalonia.Rendering.Composition.Transport; +using Avalonia.Styling; + +namespace Avalonia.Rendering.Composition; + +partial class CompositionBrush : IBrush +{ + partial void InitializeDefaultsExtra() + { + Server.Activate(); + } +} + +partial class CompositionSolidColorBrush : ISolidColorBrush +{ + internal CompositionSolidColorBrush(Compositor compositor, ServerCompositionSolidColorBrush server, Color color) : base(compositor, server) + { + Server = server; + Color = color; + InitializeDefaults(); + } +} + +partial class CompositionLinearGradientBrush : ILinearGradientBrush +{ +} + +partial class CompositionRadialGradientBrush : IRadialGradientBrush +{ + public double Radius => RadiusX.Scalar; +} + +partial class CompositionConicGradientBrush : IConicGradientBrush +{ + +} + + +public abstract partial class CompositionGradientBrush : CompositionBrush, IGradientBrush +{ + internal new ServerCompositionGradientBrush Server { get; } + public List GradientStops { get; set; } = []; + IReadOnlyList IGradientBrush.GradientStops => GradientStops; + public GradientSpreadMethod SpreadMethod { get; set; } + partial void OnRootChanged(); + partial void OnRootChanging(); + + internal CompositionGradientBrush(Compositor compositor, ServerCompositionGradientBrush server) : base(compositor, server) + { + Server = server; + } + private protected override void SerializeChangesCore(BatchStreamWriter writer) + { + base.SerializeChangesCore(writer); + writer.Write(SpreadMethod); + writer.Write(GradientStops.Count); + foreach (var stop in GradientStops) + { + if (stop is CompositionGradientStop comp) + writer.WriteObject(comp.Server); + else + writer.WriteObject(stop); + } + } +} diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs index 870a084d31..e3d472ac7f 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/RenderDataDrawingContext.cs @@ -27,13 +27,13 @@ internal class RenderDataDrawingContext : DrawingContext return _renderData ??= new(_compositor); } } - + struct ParentStackItem { public RenderDataPushNode? Node; public List Items; } - + private List? _currentItemList; private static readonly ThreadSafeObjectPool> s_listPool = new(); @@ -59,7 +59,7 @@ internal class RenderDataDrawingContext : DrawingContext { (_parentNodeStack ??= s_parentStackPool.Get()).Push(default); return; - } + } Add(node); (_parentNodeStack ??= s_parentStackPool.Get()).Push(new ParentStackItem { @@ -72,7 +72,7 @@ internal class RenderDataDrawingContext : DrawingContext void Pop() where T : IRenderDataItem { var parent = _parentNodeStack!.Pop(); - + // No-op node if (parent.Node == null) return; @@ -98,11 +98,12 @@ internal class RenderDataDrawingContext : DrawingContext { if (_compositor == null) return; - + if (resource == null || resource is IImmutableBrush || resource is ImmutablePen - || resource is ImmutableTransform) + || resource is ImmutableTransform + || resource is CompositionBrush) return; if (resource is ICompositionRenderResource renderResource) diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/ServerResourceHelperExtensions.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/ServerResourceHelperExtensions.cs index 2a6971e8ea..d4b7731776 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/ServerResourceHelperExtensions.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/ServerResourceHelperExtensions.cs @@ -18,6 +18,8 @@ static class ServerResourceHelperExtensions return immutable; if (brush is ICompositionRenderResource resource) return resource.GetForCompositor(compositor); + if (brush is CompositionBrush compositionBrush) + return compositionBrush.Server; ThrowNotCompatible(brush); return null; } @@ -52,4 +54,4 @@ static class ServerResourceHelperExtensions resource.GetForCompositor(compositor); return new ImmutableTransform(transform.Value); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs index 136ef69b55..a7c0db1553 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs @@ -21,7 +21,9 @@ namespace Avalonia.Rendering.Composition.Expressions Matrix3x2, Matrix4x4, Quaternion, - Color + Color, + RelativePoint, + RelativeScalar } /// @@ -45,7 +47,8 @@ namespace Avalonia.Rendering.Composition.Expressions [FieldOffset(4)] public Matrix4x4 Matrix4x4; [FieldOffset(4)] public Quaternion Quaternion; [FieldOffset(4)] public Color Color; - + [FieldOffset(4)] public RelativePoint RelativePoint; + [FieldOffset(4)] public RelativeScalar RelativeScalar; public ExpressionVariant GetProperty(string property) { @@ -57,7 +60,7 @@ namespace Avalonia.Rendering.Composition.Expressions return Vector2.Y; return default; } - + if (Type == VariantType.Vector) { if (ReferenceEquals(property, "X")) @@ -75,21 +78,21 @@ namespace Avalonia.Rendering.Composition.Expressions return Vector3.Y; if (ReferenceEquals(property, "Z")) return Vector3.Z; - if(ReferenceEquals(property, "XY")) + if (ReferenceEquals(property, "XY")) return new Vector2(Vector3.X, Vector3.Y); - if(ReferenceEquals(property, "YX")) + if (ReferenceEquals(property, "YX")) return new Vector2(Vector3.Y, Vector3.X); - if(ReferenceEquals(property, "XZ")) + if (ReferenceEquals(property, "XZ")) return new Vector2(Vector3.X, Vector3.Z); - if(ReferenceEquals(property, "ZX")) + if (ReferenceEquals(property, "ZX")) return new Vector2(Vector3.Z, Vector3.X); - if(ReferenceEquals(property, "YZ")) + if (ReferenceEquals(property, "YZ")) return new Vector2(Vector3.Y, Vector3.Z); - if(ReferenceEquals(property, "ZY")) + if (ReferenceEquals(property, "ZY")) return new Vector2(Vector3.Z, Vector3.Y); return default; } - + if (Type == VariantType.Vector3D) { if (ReferenceEquals(property, "X")) @@ -98,17 +101,17 @@ namespace Avalonia.Rendering.Composition.Expressions return Vector3D.Y; if (ReferenceEquals(property, "Z")) return Vector3D.Z; - if(ReferenceEquals(property, "XY")) + if (ReferenceEquals(property, "XY")) return new Vector(Vector3D.X, Vector3D.Y); - if(ReferenceEquals(property, "YX")) + if (ReferenceEquals(property, "YX")) return new Vector(Vector3D.Y, Vector3D.X); - if(ReferenceEquals(property, "XZ")) + if (ReferenceEquals(property, "XZ")) return new Vector(Vector3D.X, Vector3D.Z); - if(ReferenceEquals(property, "ZX")) + if (ReferenceEquals(property, "ZX")) return new Vector(Vector3D.Z, Vector3D.X); - if(ReferenceEquals(property, "YZ")) + if (ReferenceEquals(property, "YZ")) return new Vector(Vector3D.Y, Vector3D.Z); - if(ReferenceEquals(property, "ZY")) + if (ReferenceEquals(property, "ZY")) return new Vector(Vector3D.Z, Vector3D.Y); return default; } @@ -142,7 +145,7 @@ namespace Avalonia.Rendering.Composition.Expressions return Matrix3x2.M32; return default; } - + if (Type == VariantType.AvaloniaMatrix) { if (ReferenceEquals(property, "M11")) @@ -215,7 +218,7 @@ namespace Avalonia.Rendering.Composition.Expressions return Quaternion.W; return default; } - + if (Type == VariantType.Color) { if (ReferenceEquals(property, "A")) @@ -229,6 +232,15 @@ namespace Avalonia.Rendering.Composition.Expressions return default; } + if (Type == VariantType.RelativePoint) + { + if (ReferenceEquals(property, "X")) + return (float)RelativePoint.Point.X; + if (ReferenceEquals(property, "Y")) + return (float)RelativePoint.Point.Y; + return default; + } + return default; } @@ -245,7 +257,7 @@ namespace Avalonia.Rendering.Composition.Expressions Type = VariantType.Scalar, Scalar = scalar }; - + public static implicit operator ExpressionVariant(double d) => new ExpressionVariant { @@ -260,7 +272,7 @@ namespace Avalonia.Rendering.Composition.Expressions Type = VariantType.Vector2, Vector2 = value }; - + public static implicit operator ExpressionVariant(Vector value) => new ExpressionVariant { @@ -274,7 +286,7 @@ namespace Avalonia.Rendering.Composition.Expressions Type = VariantType.Vector3, Vector3 = value }; - + public static implicit operator ExpressionVariant(Vector3D value) => new ExpressionVariant { @@ -296,7 +308,7 @@ namespace Avalonia.Rendering.Composition.Expressions Type = VariantType.Matrix3x2, Matrix3x2 = value }; - + public static implicit operator ExpressionVariant(Matrix value) => new ExpressionVariant { @@ -317,7 +329,7 @@ namespace Avalonia.Rendering.Composition.Expressions Type = VariantType.Quaternion, Quaternion = value }; - + public static implicit operator ExpressionVariant(Avalonia.Media.Color value) => new ExpressionVariant { @@ -325,6 +337,21 @@ namespace Avalonia.Rendering.Composition.Expressions Color = value }; + public static implicit operator ExpressionVariant(RelativePoint value) => + new ExpressionVariant + { + Type = VariantType.RelativePoint, + RelativePoint = value + }; + + public static implicit operator ExpressionVariant(RelativeScalar value) => + new ExpressionVariant + { + Type = VariantType.RelativeScalar, + RelativeScalar = value + }; + + //TODO: RelativePoint/Scalar operators public static ExpressionVariant operator +(ExpressionVariant left, ExpressionVariant right) { if (left.Type != right.Type || left.Type == VariantType.Invalid) @@ -332,13 +359,13 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Scalar) return left.Scalar + right.Scalar; - + if (left.Type == VariantType.Double) return left.Double + right.Double; if (left.Type == VariantType.Vector2) return left.Vector2 + right.Vector2; - + if (left.Type == VariantType.Vector) return left.Vector + right.Vector; @@ -350,16 +377,16 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Vector4) return left.Vector4 + right.Vector4; - + if (left.Type == VariantType.Matrix3x2) return left.Matrix3x2 + right.Matrix3x2; - + if (left.Type == VariantType.Matrix4x4) return left.Matrix4x4 + right.Matrix4x4; - + if (left.Type == VariantType.Quaternion) return left.Quaternion + right.Quaternion; - + return default; } @@ -370,13 +397,13 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Scalar) return left.Scalar - right.Scalar; - + if (left.Type == VariantType.Double) return left.Double - right.Double; if (left.Type == VariantType.Vector2) return left.Vector2 - right.Vector2; - + if (left.Type == VariantType.Vector) return left.Vector - right.Vector; @@ -388,13 +415,13 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Vector4) return left.Vector4 - right.Vector4; - + if (left.Type == VariantType.Matrix3x2) return left.Matrix3x2 - right.Matrix3x2; - + if (left.Type == VariantType.Matrix4x4) return left.Matrix4x4 - right.Matrix4x4; - + if (left.Type == VariantType.Quaternion) return left.Quaternion - right.Quaternion; @@ -406,31 +433,31 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Scalar) return -left.Scalar; - + if (left.Type == VariantType.Double) return -left.Double; if (left.Type == VariantType.Vector2) return -left.Vector2; - + if (left.Type == VariantType.Vector) return -left.Vector; if (left.Type == VariantType.Vector3) return -left.Vector3; - + if (left.Type == VariantType.Vector3D) return -left.Vector3D; if (left.Type == VariantType.Vector4) return -left.Vector4; - + if (left.Type == VariantType.Matrix3x2) return -left.Matrix3x2; - + if (left.Type == VariantType.AvaloniaMatrix) return -left.AvaloniaMatrix; - + if (left.Type == VariantType.Matrix4x4) return -left.Matrix4x4; @@ -447,7 +474,7 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar) return left.Scalar * right.Scalar; - + if (left.Type == VariantType.Double && right.Type == VariantType.Double) return left.Double * right.Double; @@ -459,22 +486,22 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Vector2 && right.Type == VariantType.Scalar) return left.Vector2 * right.Scalar; - + if (left.Type == VariantType.Vector && right.Type == VariantType.Scalar) return left.Vector * right.Scalar; - + if (left.Type == VariantType.Vector && right.Type == VariantType.Double) return left.Vector * right.Double; if (left.Type == VariantType.Vector3 && right.Type == VariantType.Vector3) return left.Vector3 * right.Vector3; - + if (left.Type == VariantType.Vector3D && right.Type == VariantType.Vector3D) return Vector3D.Multiply(left.Vector3D, right.Vector3D); if (left.Type == VariantType.Vector3 && right.Type == VariantType.Scalar) return left.Vector3 * right.Scalar; - + if (left.Type == VariantType.Vector3D && right.Type == VariantType.Scalar) return Vector3D.Multiply(left.Vector3D, right.Scalar); @@ -483,22 +510,22 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Vector4 && right.Type == VariantType.Scalar) return left.Vector4 * right.Scalar; - + if (left.Type == VariantType.Matrix3x2 && right.Type == VariantType.Matrix3x2) return left.Matrix3x2 * right.Matrix3x2; if (left.Type == VariantType.Matrix3x2 && right.Type == VariantType.Scalar) return left.Matrix3x2 * right.Scalar; - + if (left.Type == VariantType.AvaloniaMatrix && right.Type == VariantType.AvaloniaMatrix) return left.AvaloniaMatrix * right.AvaloniaMatrix; - + if (left.Type == VariantType.Matrix4x4 && right.Type == VariantType.Matrix4x4) return left.Matrix4x4 * right.Matrix4x4; if (left.Type == VariantType.Matrix4x4 && right.Type == VariantType.Scalar) return left.Matrix4x4 * right.Scalar; - + if (left.Type == VariantType.Quaternion && right.Type == VariantType.Quaternion) return left.Quaternion * right.Quaternion; @@ -515,7 +542,7 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar) return left.Scalar / right.Scalar; - + if (left.Type == VariantType.Double && right.Type == VariantType.Double) return left.Double / right.Double; @@ -527,10 +554,10 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Vector2 && right.Type == VariantType.Scalar) return left.Vector2 / right.Scalar; - + if (left.Type == VariantType.Vector && right.Type == VariantType.Scalar) return left.Vector / right.Scalar; - + if (left.Type == VariantType.Vector && right.Type == VariantType.Double) return left.Vector / right.Scalar; @@ -545,7 +572,7 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Vector3D && right.Type == VariantType.Scalar) return Avalonia.Vector3D.Divide(left.Vector3D, right.Scalar); - + if (left.Type == VariantType.Vector3D && right.Type == VariantType.Double) return Avalonia.Vector3D.Divide(left.Vector3D, right.Double); @@ -554,7 +581,7 @@ namespace Avalonia.Rendering.Composition.Expressions if (left.Type == VariantType.Vector4 && right.Type == VariantType.Scalar) return left.Vector4 / right.Scalar; - + if (left.Type == VariantType.Quaternion && right.Type == VariantType.Quaternion) return left.Quaternion / right.Quaternion; @@ -568,20 +595,20 @@ namespace Avalonia.Rendering.Composition.Expressions if (Type == VariantType.Scalar) return Scalar == right.Scalar; - - + + if (Type == VariantType.Double) return Double == right.Double; if (Type == VariantType.Vector2) return Vector2 == right.Vector2; - + if (Type == VariantType.Vector) return Vector == right.Vector; - + if (Type == VariantType.Vector3) return Vector3 == right.Vector3; - + if (Type == VariantType.Vector3D) return Vector3D == right.Vector3D; @@ -593,7 +620,7 @@ namespace Avalonia.Rendering.Composition.Expressions if (Type == VariantType.Matrix3x2) return Matrix3x2 == right.Matrix3x2; - + if (Type == VariantType.AvaloniaMatrix) return AvaloniaMatrix == right.AvaloniaMatrix; @@ -602,7 +629,13 @@ namespace Avalonia.Rendering.Composition.Expressions if (Type == VariantType.Quaternion) return Quaternion == right.Quaternion; - + + if (Type == VariantType.RelativePoint) + return RelativePoint == right.RelativePoint; + + if (Type == VariantType.RelativeScalar) + return RelativeScalar == right.RelativeScalar; + return default; } @@ -643,7 +676,7 @@ namespace Avalonia.Rendering.Composition.Expressions { if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar) return left.Scalar > right.Scalar; - + if (left.Type == VariantType.Double && right.Type == VariantType.Double) return left.Double > right.Double; return default; @@ -669,7 +702,7 @@ namespace Avalonia.Rendering.Composition.Expressions { if (Type == VariantType.Boolean) { - res = (T) (object) Boolean; + res = (T)(object)Boolean; return true; } } @@ -678,27 +711,27 @@ namespace Avalonia.Rendering.Composition.Expressions { if (Type == VariantType.Scalar) { - res = (T) (object) Scalar; + res = (T)(object)Scalar; return true; } if (Type == VariantType.Double) { - res = (T)(object)Scalar; + res = (T)(object)(float)Double; return true; } } - + if (typeof(T) == typeof(double)) { if (Type == VariantType.Double) { - res = (T) (object) Double; + res = (T)(object)Double; return true; } if (Type == VariantType.Scalar) { - res = (T)(object)(float)Double; + res = (T)(object)(double)Scalar; return true; } } @@ -707,22 +740,22 @@ namespace Avalonia.Rendering.Composition.Expressions { if (Type == VariantType.Vector2) { - res = (T) (object) Vector2; + res = (T)(object)Vector2; return true; } if (Type == VariantType.Vector) { - res = (T) (object) Vector.ToVector2(); + res = (T)(object)Vector.ToVector2(); return true; } } - + if (typeof(T) == typeof(Vector)) { if (Type == VariantType.Vector) { - res = (T) (object) Vector; + res = (T)(object)Vector; return true; } @@ -737,24 +770,24 @@ namespace Avalonia.Rendering.Composition.Expressions { if (Type == VariantType.Vector3) { - res = (T) (object) Vector3; + res = (T)(object)Vector3; return true; } if (Type == VariantType.Vector3D) { - res = (T) (object) Vector3D.ToVector3(); + res = (T)(object)Vector3D.ToVector3(); return true; } } - + if (typeof(T) == typeof(Vector3D)) { if (Type == VariantType.Vector3D) { - res = (T) (object) Vector3D; + res = (T)(object)Vector3D; return true; } - + if (Type == VariantType.Vector3) { res = (T)(object)new Vector3D(Vector3); @@ -766,7 +799,7 @@ namespace Avalonia.Rendering.Composition.Expressions { if (Type == VariantType.Vector4) { - res = (T) (object) Vector4; + res = (T)(object)Vector4; return true; } } @@ -775,16 +808,16 @@ namespace Avalonia.Rendering.Composition.Expressions { if (Type == VariantType.Matrix3x2) { - res = (T) (object) Matrix3x2; + res = (T)(object)Matrix3x2; return true; } } - + if (typeof(T) == typeof(Matrix)) { if (Type == VariantType.AvaloniaMatrix) { - res = (T) (object) Matrix3x2; + res = (T)(object)Matrix3x2; return true; } } @@ -793,7 +826,7 @@ namespace Avalonia.Rendering.Composition.Expressions { if (Type == VariantType.Matrix4x4) { - res = (T) (object) Matrix4x4; + res = (T)(object)Matrix4x4; return true; } } @@ -802,16 +835,35 @@ namespace Avalonia.Rendering.Composition.Expressions { if (Type == VariantType.Quaternion) { - res = (T) (object) Quaternion; + res = (T)(object)Quaternion; return true; } } - + if (typeof(T) == typeof(Avalonia.Media.Color)) { if (Type == VariantType.Color) { - res = (T) (object) Color; + res = (T)(object)Color; + return true; + } + } + + if (typeof(T) == typeof(RelativePoint)) + { + if (Type == VariantType.RelativePoint) + { + res = (T)(object)RelativePoint; + return true; + } + } + + + if (typeof(T) == typeof(RelativeScalar)) + { + if (Type == VariantType.RelativeScalar) + { + res = (T)(object)RelativeScalar; return true; } } @@ -823,40 +875,49 @@ namespace Avalonia.Rendering.Composition.Expressions public static ExpressionVariant Create(T v) where T : struct { if (typeof(T) == typeof(bool)) - return (bool) (object) v; + return (bool)(object)v; if (typeof(T) == typeof(float)) - return (float) (object) v; + return (float)(object)v; + + if (typeof(T) == typeof(double)) + return (double)(object)v; if (typeof(T) == typeof(Vector2)) - return (Vector2) (object) v; - + return (Vector2)(object)v; + if (typeof(T) == typeof(Vector)) - return (Vector) (object) v; + return (Vector)(object)v; if (typeof(T) == typeof(Vector3)) - return (Vector3) (object) v; - + return (Vector3)(object)v; + if (typeof(T) == typeof(Vector3D)) - return (Vector3D) (object) v; + return (Vector3D)(object)v; if (typeof(T) == typeof(Vector4)) - return (Vector4) (object) v; + return (Vector4)(object)v; if (typeof(T) == typeof(Matrix3x2)) - return (Matrix3x2) (object) v; - + return (Matrix3x2)(object)v; + if (typeof(T) == typeof(Matrix)) - return (Matrix) (object) v; + return (Matrix)(object)v; if (typeof(T) == typeof(Matrix4x4)) - return (Matrix4x4) (object) v; + return (Matrix4x4)(object)v; if (typeof(T) == typeof(Quaternion)) - return (Quaternion) (object) v; - + return (Quaternion)(object)v; + if (typeof(T) == typeof(Avalonia.Media.Color)) - return (Avalonia.Media.Color) (object) v; + return (Avalonia.Media.Color)(object)v; + + if (typeof(T) == typeof(RelativePoint)) + return (RelativePoint)(object)v; + + if (typeof(T) == typeof(RelativeScalar)) + return (RelativeScalar)(object)v; throw new ArgumentException("Invalid variant type: " + typeof(T)); } @@ -895,6 +956,10 @@ namespace Avalonia.Rendering.Composition.Expressions return Matrix4x4.ToString(); if (Type == VariantType.Color) return Color.ToString(); + if (Type == VariantType.RelativePoint) + return RelativePoint.ToString(); + if (Type == VariantType.RelativeScalar) + return RelativeScalar.ToString(); if (Type == VariantType.Invalid) return "Invalid"; return "Unknown"; diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs index 105580e6ad..d0c27dafa7 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs @@ -120,4 +120,108 @@ internal class SimpleServerRenderResource : SimpleServerObject, IServerRenderRes observer.Key.DependencyQueuedInvalidate(this); } -} \ No newline at end of file +} + +internal class ServerRenderResource : ServerObject, IServerRenderResource, IDisposable +{ + private bool _pendingInvalidation; + private bool _disposed; + public bool IsDisposed => _disposed; + private RefCountingSmallDictionary _observers; + + public ServerRenderResource(ServerCompositor compositor) : base(compositor) + { + } + + protected new void SetValue(CompositionProperty prop, ref T field, T value) => SetValue(ref field, value); + + protected void SetValue(ref T field, T value) + { + if (EqualityComparer.Default.Equals(field, value)) + return; + + if (_disposed) + { + field = value; + return; + } + + if (field is IServerRenderResource oldChild) + oldChild.RemoveObserver(this); + else if (field is IServerRenderResource[] oldChildren) + { + foreach (var ch in oldChildren) + ch?.RemoveObserver(this); + } + field = value; + if (field is IServerRenderResource newChild) + newChild.AddObserver(this); + else if (field is IServerRenderResource[] newChildren) + { + foreach (var ch in newChildren) + ch.AddObserver(this); + } + Invalidated(); + } + + protected void Invalidated() + { + // This is needed to avoid triggering on multiple property changes + if (!_pendingInvalidation) + { + _pendingInvalidation = true; + Compositor.EnqueueRenderResourceForInvalidation(this); + PropertyChanged(); + } + } + + protected override void ValuesInvalidated() + { + Invalidated(); + base.ValuesInvalidated(); + } + + protected void RemoveObserversFromProperty(ref T field) + { + (field as IServerRenderResource)?.RemoveObserver(this); + } + + public virtual void Dispose() + { + _disposed = true; + // TODO: dispose once we implement pooling + _observers = default; + } + + public virtual void DependencyQueuedInvalidate(IServerRenderResource sender) => + Compositor.EnqueueRenderResourceForInvalidation(this); + + protected virtual void PropertyChanged() + { + + } + + public void AddObserver(IServerRenderResourceObserver observer) + { + Debug.Assert(!_disposed); + if (_disposed) + return; + _observers.Add(observer); + } + + public void RemoveObserver(IServerRenderResourceObserver observer) + { + if (_disposed) + return; + _observers.Remove(observer); + } + + public virtual void QueuedInvalidate() + { + _pendingInvalidation = false; + + foreach (var observer in _observers) + observer.Key.DependencyQueuedInvalidate(this); + + } +} diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml index ce989296b1..ed348e913e 100644 --- a/src/Avalonia.Base/composition-schema.xml +++ b/src/Avalonia.Base/composition-schema.xml @@ -67,6 +67,8 @@ + + @@ -109,4 +111,39 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 9552b35aa4ca8578ec4d72008e61d28ce228d5cf Mon Sep 17 00:00:00 2001 From: Betta_Fish Date: Sat, 29 Nov 2025 22:31:42 +0800 Subject: [PATCH 02/17] feat: Composition gradient brush --- .../Brushes/ServerCompositionBrush.cs | 41 ++++++++++++++++--- .../Composition/CompositionGradientStop.cs | 24 +++++++++++ .../Server/ServerCompositionGradientStop.cs | 8 ++++ .../Server/ServerRenderResource.cs | 8 ++-- 4 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 src/Avalonia.Base/Rendering/Composition/CompositionGradientStop.cs create mode 100644 src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionGradientStop.cs diff --git a/src/Avalonia.Base/Rendering/Composition/Brushes/ServerCompositionBrush.cs b/src/Avalonia.Base/Rendering/Composition/Brushes/ServerCompositionBrush.cs index bcde024cd7..e537db319a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Brushes/ServerCompositionBrush.cs +++ b/src/Avalonia.Base/Rendering/Composition/Brushes/ServerCompositionBrush.cs @@ -11,26 +11,55 @@ internal partial class ServerCompositionBrush : IBrush ITransform? IBrush.Transform => Transform; } -internal class ServerCompositionGradientBrush : ServerCompositionBrush, IGradientBrush +internal partial class ServerCompositionGradientBrush : ServerCompositionBrush, IGradientBrush { - internal ServerCompositionGradientBrush(ServerCompositor compositor) : base(compositor) { } - private readonly List _gradientStops = new(); - public IReadOnlyList GradientStops => _gradientStops; + internal static CompositionProperty> s_IdOfGradientStopsProperty = CompositionProperty.Register>("GradientStops", obj => ((ServerCompositionGradientBrush)obj)._gradientStops, (obj, v) => ((ServerCompositionGradientBrush)obj)._gradientStops = v, null); + private List _gradientStops = new(); + IReadOnlyList IGradientBrush.GradientStops => GradientStops; + public List GradientStops + { + get + { + return _gradientStops; + } + + set + { + var changed = false; + if (_gradientStops != value) + { + OnGradientStopsChanging(); + changed = true; + } + + SetValue(s_IdOfGradientStopsProperty, ref _gradientStops, value); + if (changed) + OnGradientStopsChanged(); + } + } + + partial void OnGradientStopsChanged(); + partial void OnGradientStopsChanging(); + public GradientSpreadMethod SpreadMethod { get; private set; } protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) { base.DeserializeChangesCore(reader, committedAt); SpreadMethod = reader.Read(); - _gradientStops.Clear(); + var stops = new List(); var count = reader.Read(); for (var c = 0; c < count; c++) - _gradientStops.Add(reader.ReadObject()); + { + var read = reader.ReadObject(); + stops.Add(read); + } + GradientStops = stops; } } diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionGradientStop.cs b/src/Avalonia.Base/Rendering/Composition/CompositionGradientStop.cs new file mode 100644 index 0000000000..cc14b6d176 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/CompositionGradientStop.cs @@ -0,0 +1,24 @@ +using Avalonia.Media; +using Avalonia.Rendering.Composition.Server; +using Avalonia.Utilities; + +namespace Avalonia.Rendering.Composition; + +public partial class CompositionGradientStop : IGradientStop +{ + internal CompositionGradientStop(Compositor compositor, ServerCompositionGradientStop server, double offset, Color color) : base(compositor, server) + { + Server = server; + if (MathUtilities.IsZero(offset)) + { + offset = 0; + } + Offset = (offset < 0) ? 0 : (offset > 1) ? 1 : offset; + Color = color; + InitializeDefaults(); + } + partial void InitializeDefaultsExtra() + { + Server.Activate(); + } +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionGradientStop.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionGradientStop.cs new file mode 100644 index 0000000000..6b7cdd20a1 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionGradientStop.cs @@ -0,0 +1,8 @@ +using Avalonia.Media; + +namespace Avalonia.Rendering.Composition.Server; + +partial class ServerCompositionGradientStop : IGradientStop +{ + +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs index d0c27dafa7..104fb71181 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs @@ -44,7 +44,7 @@ internal class SimpleServerRenderResource : SimpleServerObject, IServerRenderRes if (field is IServerRenderResource oldChild) oldChild.RemoveObserver(this); - else if (field is IServerRenderResource[] oldChildren) + else if (field is IEnumerable oldChildren) { foreach (var ch in oldChildren) ch?.RemoveObserver(this); @@ -52,7 +52,7 @@ internal class SimpleServerRenderResource : SimpleServerObject, IServerRenderRes field = value; if (field is IServerRenderResource newChild) newChild.AddObserver(this); - else if (field is IServerRenderResource[] newChildren) + else if (field is IEnumerable newChildren) { foreach (var ch in newChildren) ch.AddObserver(this); @@ -148,7 +148,7 @@ internal class ServerRenderResource : ServerObject, IServerRenderResource, IDisp if (field is IServerRenderResource oldChild) oldChild.RemoveObserver(this); - else if (field is IServerRenderResource[] oldChildren) + else if (field is IEnumerable oldChildren) { foreach (var ch in oldChildren) ch?.RemoveObserver(this); @@ -156,7 +156,7 @@ internal class ServerRenderResource : ServerObject, IServerRenderResource, IDisp field = value; if (field is IServerRenderResource newChild) newChild.AddObserver(this); - else if (field is IServerRenderResource[] newChildren) + else if (field is IEnumerable newChildren) { foreach (var ch in newChildren) ch.AddObserver(this); From 069a25c59a246938dfd80a006092e95581693850 Mon Sep 17 00:00:00 2001 From: Betta_Fish Date: Sat, 29 Nov 2025 22:32:25 +0800 Subject: [PATCH 03/17] feat: Add factory methods --- .../Rendering/Composition/Compositor.Factories.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs index 8a75681c68..f75f711aa3 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Avalonia.Media; using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Server; @@ -38,4 +39,12 @@ public partial class Compositor public CompositionSurfaceVisual CreateSurfaceVisual() => new(this, new ServerCompositionSurfaceVisual(_server)); public CompositionDrawingSurface CreateDrawingSurface() => new(this); + + public CompositionSolidColorBrush CreateSolidColorBrush() => new(this, new ServerCompositionSolidColorBrush(Server)); + public CompositionSolidColorBrush CreateSolidColorBrush(Color color) => new(this, new ServerCompositionSolidColorBrush(Server), color); + public CompositionLinearGradientBrush CreateLinearGradientBrush() => new(this, new ServerCompositionLinearGradientBrush(Server)); + public CompositionConicGradientBrush CreateConicGradientBrush() => new(this, new ServerCompositionConicGradientBrush(Server)); + public CompositionRadialGradientBrush CreateRadialGradientBrush() => new(this, new ServerCompositionRadialGradientBrush(Server)); + public CompositionGradientStop CreateCompositionGradientStop(double offset, Color color) => new(this, new ServerCompositionGradientStop(Server), offset, color); + public CompositionGradientStop CreateCompositionGradientStop() => new(this, new ServerCompositionGradientStop(Server)); } From 6fecae905636e7cca5c824c0e8a2cec7b9db7109 Mon Sep 17 00:00:00 2001 From: Betta_Fish Date: Sat, 29 Nov 2025 22:33:38 +0800 Subject: [PATCH 04/17] feat: Add composition brush samples --- .../Pages/CompositionPage.axaml | 267 ++++++++++++++---- .../Pages/CompositionPage.axaml.cs | 186 ++++++++++++ 2 files changed, 392 insertions(+), 61 deletions(-) diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml b/samples/ControlCatalog/Pages/CompositionPage.axaml index 4d9bb41781..da38afcdbb 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml +++ b/samples/ControlCatalog/Pages/CompositionPage.axaml @@ -1,62 +1,207 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - Resize me - - - - - - - - - - - - - - - - - - Precise dirty rects - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + Resize me + + + + + + + + + + + + + + + + + + + Precise dirty rects + + + + + + + + + + + + + + + + + + + + + +