csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
603 lines
23 KiB
603 lines
23 KiB
using Avalonia.Media;
|
|
using Avalonia.Platform;
|
|
using Avalonia.Rendering.SceneGraph;
|
|
using Avalonia.Utilities;
|
|
using Avalonia.Media.Imaging;
|
|
using Avalonia.Media.Immutable;
|
|
using Avalonia.Rendering;
|
|
using Avalonia.Rendering.Composition;
|
|
using Avalonia.Rendering.Composition.Drawing;
|
|
using Avalonia.Rendering.Composition.Drawing.Nodes;
|
|
using Avalonia.Threading;
|
|
using Avalonia.UnitTests;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
|
|
{
|
|
public class DrawOperationTests : ScopedTestBase
|
|
{
|
|
|
|
class TestContext
|
|
{
|
|
private readonly Compositor _compositor;
|
|
public RenderDataDrawingContext Context { get; }
|
|
|
|
public TestContext(CompositorTestServices services)
|
|
{
|
|
_compositor = services.Compositor;
|
|
Context = new RenderDataDrawingContext(_compositor);
|
|
}
|
|
|
|
public void ForceRender()
|
|
{
|
|
_compositor.Commit();
|
|
_compositor.Server.Render(false);
|
|
}
|
|
|
|
public Rect? GetBounds()
|
|
{
|
|
var renderData = Context.GetRenderResults();
|
|
if (renderData == null)
|
|
return null;
|
|
ForceRender();
|
|
return renderData.Server.Bounds?.ToRect();
|
|
}
|
|
}
|
|
|
|
private CompositorTestServices _services = new();
|
|
public override void Dispose()
|
|
{
|
|
_services.Dispose();
|
|
base.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public void Empty_Bounds_Remain_Empty()
|
|
{
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, default);
|
|
|
|
Assert.Null(ctx.GetBounds());
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(10, 10, 10, 10, 1, 1, 1, 9, 9, 12, 12)]
|
|
[InlineData(10, 10, 10, 10, 1, 1, 2, 9, 9, 12, 12)]
|
|
[InlineData(10, 10, 10, 10, 1.5, 1.5, 1, 14, 14, 17, 17)]
|
|
public void Rectangle_Bounds_Are_Snapped_To_Pixels(
|
|
double x,
|
|
double y,
|
|
double width,
|
|
double height,
|
|
double scaleX,
|
|
double scaleY,
|
|
double penThickness,
|
|
double expectedX,
|
|
double expectedY,
|
|
double expectedWidth,
|
|
double expectedHeight)
|
|
{
|
|
var ctx = new TestContext(_services);
|
|
using (ctx.Context.PushTransform(Matrix.CreateScale(scaleX, scaleY)))
|
|
ctx.Context.DrawRectangle(null, new ImmutablePen(Brushes.Black, penThickness), new Rect(x, y, width, height));
|
|
|
|
var bounds = Assert.NotNull(ctx.GetBounds());
|
|
Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), bounds);
|
|
}
|
|
|
|
[Theory, InlineData(false), InlineData(true)]
|
|
public void Image_Node_Releases_Reference_To_Bitmap_On_Dispose(bool disposeBeforeCommit)
|
|
{
|
|
var bitmap = RefCountable.Create(Mock.Of<IBitmapImpl>());
|
|
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.DrawBitmap(bitmap, 1, new Rect(1, 1, 1, 1), new Rect(1, 1, 1, 1));
|
|
var renderData = ctx.Context.GetRenderResults()!;
|
|
Assert.Equal(2, bitmap.RefCount);
|
|
if (disposeBeforeCommit)
|
|
{
|
|
renderData.Dispose();
|
|
Assert.Equal(1, bitmap.RefCount);
|
|
ctx.ForceRender();
|
|
Assert.Equal(1, bitmap.RefCount);
|
|
}
|
|
else
|
|
{
|
|
ctx.ForceRender();
|
|
Assert.Equal(2, bitmap.RefCount);
|
|
|
|
// Refs ownership is transferred to server-side render data
|
|
renderData.Dispose();
|
|
Assert.Equal(2, bitmap.RefCount);
|
|
|
|
ctx.ForceRender();
|
|
Assert.Equal(1, bitmap.RefCount);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void HitTest_On_Geometry_Node_With_Zero_Transform_Does_Not_Throw()
|
|
{
|
|
var ctx = new TestContext(_services);
|
|
using (ctx.Context.PushTransform(new Matrix()))
|
|
ctx.Context.DrawGeometry(Brushes.Black, null, Mock.Of<IGeometryImpl>());
|
|
Assert.False(ctx.Context.GetRenderResults()!.HitTest(default));
|
|
}
|
|
|
|
[Fact]
|
|
public void HitTest_RectangleNode_With_Transform_Hits()
|
|
{
|
|
var ctx = new TestContext(_services);
|
|
using (ctx.Context.PushTransform(Matrix.CreateTranslation(20, 20)))
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
|
|
Assert.True(ctx.Context.GetRenderResults()!.HitTest(new Point(25, 25)));
|
|
}
|
|
|
|
[Fact]
|
|
public void Empty_Push_Pop_Sequence_Produces_No_Results()
|
|
{
|
|
var ctx = new TestContext(_services);
|
|
using (ctx.Context.PushTransform(Matrix.CreateTranslation(20, 20)))
|
|
using (ctx.Context.PushOpacity(1))
|
|
{
|
|
|
|
}
|
|
|
|
Assert.Null(ctx.Context.GetRenderResults());
|
|
}
|
|
|
|
[Fact]
|
|
public void HitTest_Through_PushTransform_Maps_Coordinates()
|
|
{
|
|
var ctx = new TestContext(_services);
|
|
using (ctx.Context.PushTransform(Matrix.CreateTranslation(50, 50)))
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
Assert.True(rd.HitTest(new Point(55, 55)));
|
|
Assert.False(rd.HitTest(new Point(5, 5)));
|
|
}
|
|
|
|
[Fact]
|
|
public void HitTest_Through_PushClip_Restricts_To_Clip_Region()
|
|
{
|
|
var ctx = new TestContext(_services);
|
|
using (ctx.Context.PushClip(new Rect(0, 0, 10, 10)))
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, new RoundedRect(new Rect(0, 0, 100, 100)));
|
|
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
Assert.True(rd.HitTest(new Point(5, 5)));
|
|
Assert.False(rd.HitTest(new Point(50, 50)));
|
|
}
|
|
|
|
[Fact]
|
|
public void HitTest_Through_Nested_Transforms_Composes_Outer_To_Inner()
|
|
{
|
|
var ctx = new TestContext(_services);
|
|
using (ctx.Context.PushTransform(Matrix.CreateTranslation(20, 20)))
|
|
using (ctx.Context.PushTransform(Matrix.CreateScale(2, 2)))
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
Assert.True(rd.HitTest(new Point(30, 30)));
|
|
Assert.False(rd.HitTest(new Point(5, 5)));
|
|
Assert.False(rd.HitTest(new Point(45, 45)));
|
|
}
|
|
|
|
[Fact]
|
|
public void Bounds_Union_Across_Multiple_Top_Level_Draws()
|
|
{
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, new RoundedRect(new Rect(20, 20, 10, 10)));
|
|
|
|
Assert.Equal(new Rect(0, 0, 30, 30), ctx.GetBounds());
|
|
}
|
|
|
|
[Fact]
|
|
public void Bounds_Reflect_PushTransform_Translation()
|
|
{
|
|
var ctx = new TestContext(_services);
|
|
using (ctx.Context.PushTransform(Matrix.CreateTranslation(50, 50)))
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
|
|
Assert.Equal(new Rect(50, 50, 10, 10), ctx.GetBounds());
|
|
}
|
|
|
|
[Fact]
|
|
public void Identity_PushTransform_Does_Not_Wrap_Children()
|
|
{
|
|
var ctx = new TestContext(_services);
|
|
using (ctx.Context.PushTransform(Matrix.Identity))
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
ctx.ForceRender();
|
|
Assert.Equal(new Rect(0, 0, 10, 10), rd.Server.Bounds?.ToRect());
|
|
Assert.True(rd.HitTest(new Point(5, 5)));
|
|
}
|
|
|
|
[Fact]
|
|
public void Opacity_One_Push_Does_Not_Wrap_Children()
|
|
{
|
|
var ctx = new TestContext(_services);
|
|
using (ctx.Context.PushOpacity(1))
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
ctx.ForceRender();
|
|
Assert.Equal(new Rect(0, 0, 10, 10), rd.Server.Bounds?.ToRect());
|
|
Assert.True(rd.HitTest(new Point(5, 5)));
|
|
}
|
|
|
|
[Fact]
|
|
public void Empty_Push_Between_Real_Draws_Does_Not_Affect_Siblings()
|
|
{
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
using (ctx.Context.PushTransform(Matrix.CreateTranslation(100, 100)))
|
|
{
|
|
}
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, new RoundedRect(new Rect(40, 40, 10, 10)));
|
|
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
ctx.ForceRender();
|
|
Assert.Equal(new Rect(0, 0, 50, 50), rd.Server.Bounds?.ToRect());
|
|
Assert.True(rd.HitTest(new Point(5, 5)));
|
|
Assert.True(rd.HitTest(new Point(45, 45)));
|
|
Assert.False(rd.HitTest(new Point(25, 25)));
|
|
}
|
|
|
|
[Fact]
|
|
public void NoOp_Inner_Push_Inside_Real_Outer_Push_Is_Stripped()
|
|
{
|
|
var ctx = new TestContext(_services);
|
|
using (ctx.Context.PushTransform(Matrix.CreateTranslation(20, 20)))
|
|
{
|
|
using (ctx.Context.PushOpacity(1))
|
|
{
|
|
}
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
}
|
|
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
ctx.ForceRender();
|
|
Assert.Equal(new Rect(20, 20, 10, 10), rd.Server.Bounds?.ToRect());
|
|
Assert.True(rd.HitTest(new Point(25, 25)));
|
|
Assert.False(rd.HitTest(new Point(5, 5)));
|
|
}
|
|
|
|
[Fact]
|
|
public void Non_Immutable_Brush_Is_AddRefed_Once_And_Released_On_Dispose()
|
|
{
|
|
var brush = new TrackingBrush();
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.DrawRectangle(brush, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
|
|
Assert.Equal(1, brush.AddRefCount);
|
|
Assert.Equal(0, brush.ReleaseCount);
|
|
|
|
rd.Dispose();
|
|
Assert.Equal(1, brush.AddRefCount);
|
|
Assert.Equal(1, brush.ReleaseCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void Non_Immutable_Brush_Used_Multiple_Times_Is_AddRefed_Once()
|
|
{
|
|
var brush = new TrackingBrush();
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.DrawRectangle(brush, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
ctx.Context.DrawRectangle(brush, null, new RoundedRect(new Rect(20, 20, 10, 10)));
|
|
ctx.Context.DrawEllipse(brush, null, new Rect(40, 40, 10, 10));
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
|
|
Assert.Equal(1, brush.AddRefCount);
|
|
|
|
rd.Dispose();
|
|
Assert.Equal(1, brush.ReleaseCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void Non_Immutable_Pen_Is_AddRefed_Once_And_Released_On_Dispose()
|
|
{
|
|
var pen = new TrackingPen();
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.DrawLine(pen, new Point(0, 0), new Point(10, 10));
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
|
|
Assert.Equal(1, pen.AddRefCount);
|
|
Assert.Equal(0, pen.ReleaseCount);
|
|
|
|
rd.Dispose();
|
|
Assert.Equal(1, pen.ReleaseCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void Immutable_Brush_Is_Not_Registered_As_Server_Resource()
|
|
{
|
|
var brush = new ImmutableTrackingBrush();
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.DrawRectangle(brush, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
|
|
Assert.Equal(0, brush.AddRefCount);
|
|
|
|
rd.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public void Immutable_Pen_Is_Not_Registered_As_Server_Resource()
|
|
{
|
|
var pen = new ImmutableTrackingPen();
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.DrawLine(pen, new Point(0, 0), new Point(10, 10));
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
|
|
Assert.Equal(0, pen.AddRefCount);
|
|
|
|
rd.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public void Custom_Draw_Operation_Disposed_When_RenderData_Disposed_Before_Commit()
|
|
{
|
|
var op = new TrackingCustomOp();
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.Custom(op);
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
|
|
Assert.Equal(0, op.DisposeCount);
|
|
|
|
rd.Dispose();
|
|
Assert.Equal(1, op.DisposeCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void GlyphRun_Node_Releases_Reference_On_Direct_Dispose()
|
|
{
|
|
var glyphRunRef = RefCountable.Create(Mock.Of<IGlyphRunImpl>());
|
|
Assert.Equal(1, glyphRunRef.RefCount);
|
|
|
|
var node = new RenderDataGlyphRunNode { GlyphRun = glyphRunRef.Clone() };
|
|
Assert.Equal(2, glyphRunRef.RefCount);
|
|
|
|
node.Dispose();
|
|
Assert.Equal(1, glyphRunRef.RefCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void GlyphRun_Node_Disposed_When_Containing_Push_Node_Disposed()
|
|
{
|
|
var glyphRunRef = RefCountable.Create(Mock.Of<IGlyphRunImpl>());
|
|
var glyphNode = new RenderDataGlyphRunNode { GlyphRun = glyphRunRef.Clone() };
|
|
Assert.Equal(2, glyphRunRef.RefCount);
|
|
|
|
var pushNode = new RenderDataOpacityNode { Opacity = 0.5 };
|
|
pushNode.Children.Add(glyphNode);
|
|
|
|
pushNode.Dispose();
|
|
Assert.Equal(1, glyphRunRef.RefCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void PushOpacityMask_Brush_Is_AddRefed_Once_And_Released_On_Dispose()
|
|
{
|
|
var brush = new TrackingBrush();
|
|
var ctx = new TestContext(_services);
|
|
using (ctx.Context.PushOpacityMask(brush, new Rect(0, 0, 100, 100)))
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
|
|
Assert.Equal(1, brush.AddRefCount);
|
|
Assert.Equal(0, brush.ReleaseCount);
|
|
|
|
rd.Dispose();
|
|
Assert.Equal(1, brush.ReleaseCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void PushOpacityMask_HitTest_Recurses_Into_Children()
|
|
{
|
|
var brush = new TrackingBrush();
|
|
var ctx = new TestContext(_services);
|
|
using (ctx.Context.PushOpacityMask(brush, new Rect(0, 0, 100, 100)))
|
|
ctx.Context.DrawRectangle(Brushes.Black, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
|
|
Assert.True(rd.HitTest(new Point(5, 5)));
|
|
Assert.False(rd.HitTest(new Point(50, 50)));
|
|
}
|
|
|
|
[Fact]
|
|
public void PushGeometryClip_HitTest_Restricts_By_FillContains()
|
|
{
|
|
var geomMock = new Mock<IGeometryImpl>();
|
|
geomMock.Setup(g => g.FillContains(new Point(5, 5))).Returns(true);
|
|
geomMock.Setup(g => g.FillContains(new Point(50, 50))).Returns(false);
|
|
|
|
var node = new RenderDataGeometryClipNode { Geometry = geomMock.Object };
|
|
node.Children.Add(new RenderDataRectangleNode
|
|
{
|
|
ServerBrush = Brushes.Black,
|
|
Rect = new RoundedRect(new Rect(0, 0, 100, 100))
|
|
});
|
|
|
|
Assert.True(node.HitTest(new Point(5, 5)));
|
|
Assert.False(node.HitTest(new Point(50, 50)));
|
|
}
|
|
|
|
[Fact]
|
|
public void Geometry_Node_AddRefs_Brush_And_Pen()
|
|
{
|
|
var brush = new TrackingBrush();
|
|
var pen = new TrackingPen();
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.DrawGeometry(brush, pen, Mock.Of<IGeometryImpl>());
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
|
|
Assert.Equal(1, brush.AddRefCount);
|
|
Assert.Equal(1, pen.AddRefCount);
|
|
|
|
rd.Dispose();
|
|
Assert.Equal(1, brush.ReleaseCount);
|
|
Assert.Equal(1, pen.ReleaseCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void Geometry_Node_HitTest_Uses_FillContains_When_Brush_Set()
|
|
{
|
|
var geomMock = new Mock<IGeometryImpl>();
|
|
geomMock.Setup(g => g.FillContains(new Point(5, 5))).Returns(true);
|
|
geomMock.Setup(g => g.FillContains(new Point(50, 50))).Returns(false);
|
|
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.DrawGeometry(Brushes.Black, null, geomMock.Object);
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
|
|
Assert.True(rd.HitTest(new Point(5, 5)));
|
|
Assert.False(rd.HitTest(new Point(50, 50)));
|
|
}
|
|
|
|
[Fact]
|
|
public void Geometry_Node_HitTest_Uses_StrokeContains_When_Pen_Set()
|
|
{
|
|
var pen = new ImmutablePen(Brushes.Black, 1);
|
|
var geomMock = new Mock<IGeometryImpl>();
|
|
geomMock.Setup(g => g.StrokeContains(pen, new Point(5, 5))).Returns(true);
|
|
geomMock.Setup(g => g.StrokeContains(pen, new Point(50, 50))).Returns(false);
|
|
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.DrawGeometry(null, pen, geomMock.Object);
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
|
|
Assert.True(rd.HitTest(new Point(5, 5)));
|
|
Assert.False(rd.HitTest(new Point(50, 50)));
|
|
}
|
|
|
|
[Fact]
|
|
public void PushRenderOptions_Forwards_Push_And_Pop_To_Drawing_Context_Impl()
|
|
{
|
|
var mockImpl = new Mock<IDrawingContextImpl>();
|
|
var ctx = new RenderDataNodeRenderContext(mockImpl.Object);
|
|
var opts = new RenderOptions { EdgeMode = EdgeMode.Aliased };
|
|
var node = new RenderDataRenderOptionsNode { RenderOptions = opts };
|
|
|
|
node.Push(ref ctx);
|
|
node.Pop(ref ctx);
|
|
|
|
mockImpl.Verify(x => x.PushRenderOptions(opts), Times.Once);
|
|
mockImpl.Verify(x => x.PopRenderOptions(), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public void PushTextOptions_Forwards_Push_And_Pop_To_Drawing_Context_Impl()
|
|
{
|
|
var mockImpl = new Mock<IDrawingContextImpl>();
|
|
var ctx = new RenderDataNodeRenderContext(mockImpl.Object);
|
|
var opts = new TextOptions { TextRenderingMode = TextRenderingMode.Antialias };
|
|
var node = new RenderDataTextOptionsNode { TextOptions = opts };
|
|
|
|
node.Push(ref ctx);
|
|
node.Pop(ref ctx);
|
|
|
|
mockImpl.Verify(x => x.PushTextOptions(opts), Times.Once);
|
|
mockImpl.Verify(x => x.PopTextOptions(), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public void Two_Distinct_Brushes_Are_AddRefed_Separately()
|
|
{
|
|
var brush1 = new TrackingBrush();
|
|
var brush2 = new TrackingBrush();
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.DrawRectangle(brush1, null, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
ctx.Context.DrawRectangle(brush2, null, new RoundedRect(new Rect(20, 20, 10, 10)));
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
|
|
Assert.Equal(1, brush1.AddRefCount);
|
|
Assert.Equal(1, brush2.AddRefCount);
|
|
|
|
rd.Dispose();
|
|
Assert.Equal(1, brush1.ReleaseCount);
|
|
Assert.Equal(1, brush2.ReleaseCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void Brush_And_Pen_On_Same_Draw_Are_Both_AddRefed_Once()
|
|
{
|
|
var brush = new TrackingBrush();
|
|
var pen = new TrackingPen();
|
|
var ctx = new TestContext(_services);
|
|
ctx.Context.DrawRectangle(brush, pen, new RoundedRect(new Rect(0, 0, 10, 10)));
|
|
var rd = ctx.Context.GetRenderResults()!;
|
|
|
|
Assert.Equal(1, brush.AddRefCount);
|
|
Assert.Equal(1, pen.AddRefCount);
|
|
|
|
rd.Dispose();
|
|
Assert.Equal(1, brush.ReleaseCount);
|
|
Assert.Equal(1, pen.ReleaseCount);
|
|
}
|
|
|
|
sealed class TrackingBrush : IBrush, ICompositionRenderResource<IBrush>
|
|
{
|
|
public int AddRefCount;
|
|
public int ReleaseCount;
|
|
public double Opacity => 1;
|
|
public ITransform? Transform => null;
|
|
public RelativePoint TransformOrigin => default;
|
|
public void AddRefOnCompositor(Compositor c) => AddRefCount++;
|
|
public void ReleaseOnCompositor(Compositor c) => ReleaseCount++;
|
|
public IBrush GetForCompositor(Compositor c) => this;
|
|
}
|
|
|
|
sealed class TrackingPen : IPen, ICompositionRenderResource<IPen>
|
|
{
|
|
public int AddRefCount;
|
|
public int ReleaseCount;
|
|
public IBrush? Brush => Brushes.Black;
|
|
public IDashStyle? DashStyle => null;
|
|
public PenLineCap LineCap => PenLineCap.Flat;
|
|
public PenLineJoin LineJoin => PenLineJoin.Miter;
|
|
public double MiterLimit => 10;
|
|
public double Thickness => 1;
|
|
public void AddRefOnCompositor(Compositor c) => AddRefCount++;
|
|
public void ReleaseOnCompositor(Compositor c) => ReleaseCount++;
|
|
public IPen GetForCompositor(Compositor c) => this;
|
|
}
|
|
|
|
sealed class ImmutableTrackingBrush : IImmutableBrush, ICompositionRenderResource<IBrush>
|
|
{
|
|
public int AddRefCount;
|
|
public double Opacity => 1;
|
|
public ITransform? Transform => null;
|
|
public RelativePoint TransformOrigin => default;
|
|
public void AddRefOnCompositor(Compositor c) => AddRefCount++;
|
|
public void ReleaseOnCompositor(Compositor c) { }
|
|
public IBrush GetForCompositor(Compositor c) => this;
|
|
}
|
|
|
|
sealed class ImmutableTrackingPen : ImmutablePen, ICompositionRenderResource<IPen>
|
|
{
|
|
public int AddRefCount;
|
|
public ImmutableTrackingPen() : base(Brushes.Black, 1) { }
|
|
public void AddRefOnCompositor(Compositor c) => AddRefCount++;
|
|
public void ReleaseOnCompositor(Compositor c) { }
|
|
public IPen GetForCompositor(Compositor c) => this;
|
|
}
|
|
|
|
sealed class TrackingCustomOp : ICustomDrawOperation
|
|
{
|
|
public int DisposeCount;
|
|
public Rect Bounds => new Rect(0, 0, 10, 10);
|
|
public bool HitTest(Point p) => false;
|
|
public void Render(ImmediateDrawingContext context) { }
|
|
public bool Equals(ICustomDrawOperation? other) => ReferenceEquals(this, other);
|
|
public void Dispose() => DisposeCount++;
|
|
}
|
|
}
|
|
}
|
|
|