Преглед изворни кода

Merge pull request #6764 from AvaloniaUI/fixes/6359-clip-transform

Fix clips with transforms in deferred renderer.
Steven Kirk пре 4 година
родитељ
комит
ec482c0187

+ 16 - 4
src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs

@@ -11,19 +11,23 @@ namespace Avalonia.Rendering.SceneGraph
         /// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
         /// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
         /// clip push.
         /// clip push.
         /// </summary>
         /// </summary>
+        /// <param name="transform">The current transform.</param>
         /// <param name="clip">The clip to push.</param>
         /// <param name="clip">The clip to push.</param>
-        public ClipNode(Rect clip)
+        public ClipNode(Matrix transform, Rect clip)
         {
         {
+            Transform = transform;
             Clip = clip;
             Clip = clip;
         }
         }
-        
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
         /// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
         /// clip push.
         /// clip push.
         /// </summary>
         /// </summary>
+        /// <param name="transform">The current transform.</param>
         /// <param name="clip">The clip to push.</param>
         /// <param name="clip">The clip to push.</param>
-        public ClipNode(RoundedRect clip)
+        public ClipNode(Matrix transform, RoundedRect clip)
         {
         {
+            Transform = transform;
             Clip = clip;
             Clip = clip;
         }
         }
 
 
@@ -43,23 +47,31 @@ namespace Avalonia.Rendering.SceneGraph
         /// </summary>
         /// </summary>
         public RoundedRect? Clip { get; }
         public RoundedRect? Clip { get; }
 
 
+        /// <summary>
+        /// Gets the transform with which the node will be drawn.
+        /// </summary>
+        public Matrix Transform { get; }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         public bool HitTest(Point p) => false;
         public bool HitTest(Point p) => false;
 
 
         /// <summary>
         /// <summary>
         /// Determines if this draw operation equals another.
         /// Determines if this draw operation equals another.
         /// </summary>
         /// </summary>
+        /// <param name="transform">The transform of the other draw operation.</param>
         /// <param name="clip">The clip of the other draw operation.</param>
         /// <param name="clip">The clip of the other draw operation.</param>
         /// <returns>True if the draw operations are the same, otherwise false.</returns>
         /// <returns>True if the draw operations are the same, otherwise false.</returns>
         /// <remarks>
         /// <remarks>
         /// The properties of the other draw operation are passed in as arguments to prevent
         /// The properties of the other draw operation are passed in as arguments to prevent
         /// allocation of a not-yet-constructed draw operation object.
         /// allocation of a not-yet-constructed draw operation object.
         /// </remarks>
         /// </remarks>
-        public bool Equals(RoundedRect? clip) => Clip == clip;
+        public bool Equals(Matrix transform, RoundedRect? clip) => Transform == transform && Clip == clip;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         public void Render(IDrawingContextImpl context)
         public void Render(IDrawingContextImpl context)
         {
         {
+            context.Transform = Transform;
+
             if (Clip.HasValue)
             if (Clip.HasValue)
             {
             {
                 context.PushClip(Clip.Value);
                 context.PushClip(Clip.Value);

+ 6 - 6
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@@ -303,9 +303,9 @@ namespace Avalonia.Rendering.SceneGraph
         {
         {
             var next = NextDrawAs<ClipNode>();
             var next = NextDrawAs<ClipNode>();
 
 
-            if (next == null || !next.Item.Equals(clip))
+            if (next == null || !next.Item.Equals(Transform, clip))
             {
             {
-                Add(new ClipNode(clip));
+                Add(new ClipNode(Transform, clip));
             }
             }
             else
             else
             {
             {
@@ -318,9 +318,9 @@ namespace Avalonia.Rendering.SceneGraph
         {
         {
             var next = NextDrawAs<ClipNode>();
             var next = NextDrawAs<ClipNode>();
 
 
-            if (next == null || !next.Item.Equals(clip))
+            if (next == null || !next.Item.Equals(Transform, clip))
             {
             {
-                Add(new ClipNode(clip));
+                Add(new ClipNode(Transform, clip));
             }
             }
             else
             else
             {
             {
@@ -333,9 +333,9 @@ namespace Avalonia.Rendering.SceneGraph
         {
         {
             var next = NextDrawAs<GeometryClipNode>();
             var next = NextDrawAs<GeometryClipNode>();
 
 
-            if (next == null || !next.Item.Equals(clip))
+            if (next == null || !next.Item.Equals(Transform, clip))
             {
             {
-                Add(new GeometryClipNode(clip));
+                Add(new GeometryClipNode(Transform, clip));
             }
             }
             else
             else
             {
             {

+ 12 - 2
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs

@@ -11,9 +11,11 @@ namespace Avalonia.Rendering.SceneGraph
         /// Initializes a new instance of the <see cref="GeometryClipNode"/> class that represents a
         /// Initializes a new instance of the <see cref="GeometryClipNode"/> class that represents a
         /// geometry clip push.
         /// geometry clip push.
         /// </summary>
         /// </summary>
+        /// <param name="transform">The current transform.</param>
         /// <param name="clip">The clip to push.</param>
         /// <param name="clip">The clip to push.</param>
-        public GeometryClipNode(IGeometryImpl clip)
+        public GeometryClipNode(Matrix transform, IGeometryImpl clip)
         {
         {
+            Transform = transform;
             Clip = clip;
             Clip = clip;
         }
         }
 
 
@@ -33,23 +35,31 @@ namespace Avalonia.Rendering.SceneGraph
         /// </summary>
         /// </summary>
         public IGeometryImpl Clip { get; }
         public IGeometryImpl Clip { get; }
 
 
+        /// <summary>
+        /// Gets the transform with which the node will be drawn.
+        /// </summary>
+        public Matrix Transform { get; }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         public bool HitTest(Point p) => false;
         public bool HitTest(Point p) => false;
 
 
         /// <summary>
         /// <summary>
         /// Determines if this draw operation equals another.
         /// Determines if this draw operation equals another.
         /// </summary>
         /// </summary>
+        /// <param name="transform">The transform of the other draw operation.</param>
         /// <param name="clip">The clip of the other draw operation.</param>
         /// <param name="clip">The clip of the other draw operation.</param>
         /// <returns>True if the draw operations are the same, otherwise false.</returns>
         /// <returns>True if the draw operations are the same, otherwise false.</returns>
         /// <remarks>
         /// <remarks>
         /// The properties of the other draw operation are passed in as arguments to prevent
         /// The properties of the other draw operation are passed in as arguments to prevent
         /// allocation of a not-yet-constructed draw operation object.
         /// allocation of a not-yet-constructed draw operation object.
         /// </remarks>
         /// </remarks>
-        public bool Equals(IGeometryImpl clip) => Clip == clip;
+        public bool Equals(Matrix transform, IGeometryImpl clip) => Transform == transform && Clip == clip;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         public void Render(IDrawingContextImpl context)
         public void Render(IDrawingContextImpl context)
         {
         {
+            context.Transform = Transform;
+
             if (Clip != null)
             if (Clip != null)
             {
             {
                 context.PushGeometryClip(Clip);
                 context.PushGeometryClip(Clip);

+ 48 - 0
tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs

@@ -78,6 +78,54 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
             CompareImages();
             CompareImages();
         }
         }
 
 
+        [Fact]
+        public async Task GeometryClip_With_Transform()
+        {
+            var target = new Border
+            {
+                Background = Brushes.White,
+                Width = 200,
+                Height = 200,
+                Child = new CustomRenderer((control, context) =>
+                {
+                    using (var transform = context.PushPreTransform(Matrix.CreateTranslation(100, 100)))
+                    using (var clip = context.PushClip(new Rect(0, 0, 100, 100)))
+                    {
+                        context.FillRectangle(Brushes.Blue, new Rect(0, 0, 200, 200));
+                    }
+
+                    context.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100));
+                }),
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
+
+        [Fact]
+        public async Task Clip_With_Transform()
+        {
+            var target = new Border
+            {
+                Background = Brushes.White,
+                Width = 200,
+                Height = 200,
+                Child = new CustomRenderer((control, context) =>
+                {
+                    using (var transform = context.PushPreTransform(Matrix.CreateTranslation(100, 100)))
+                    using (var clip = context.PushClip(new Rect(0, 0, 100, 100)))
+                    {
+                        context.FillRectangle(Brushes.Blue, new Rect(0, 0, 200, 200));
+                    }
+
+                    context.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100));
+                }),
+            };
+
+            await RenderToFile(target);
+            CompareImages();
+        }
+
         [Fact]
         [Fact]
         public async Task Opacity()
         public async Task Opacity()
         {
         {

BIN
tests/TestFiles/Direct2D1/Controls/CustomRender/Clip_With_Transform.expected.png


BIN
tests/TestFiles/Direct2D1/Controls/CustomRender/GeometryClip_With_Transform.expected.png


BIN
tests/TestFiles/Skia/Controls/CustomRender/Clip_With_Transform.expected.png


BIN
tests/TestFiles/Skia/Controls/CustomRender/GeometryClip_With_Transform.expected.png