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.
200 lines
7.5 KiB
200 lines
7.5 KiB
using Avalonia.Media;
|
|
using Avalonia.Platform;
|
|
using Avalonia.Rendering.SceneGraph;
|
|
using Avalonia.UnitTests;
|
|
using Avalonia.Utilities;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace Avalonia.Base.UnitTests.Media;
|
|
|
|
public class DrawingGroupTests
|
|
{
|
|
[Fact]
|
|
public void PushEffect_Should_Store_Provided_Bounds()
|
|
{
|
|
using (UnitTestApplication.Start(new TestServices(renderInterface: Mock.Of<IPlatformRenderInterface>())))
|
|
{
|
|
var group = new DrawingGroup();
|
|
var effect = new BlurEffect { Radius = 10 };
|
|
var bounds = new Rect(10, 10, 100, 100);
|
|
|
|
using (var context = group.Open())
|
|
{
|
|
using (context.PushEffect(effect, bounds))
|
|
{
|
|
context.DrawRectangle(Brushes.Red, null, new Rect(20, 20, 50, 50));
|
|
}
|
|
}
|
|
|
|
// The Open() call adds a child DrawingGroup to the root group when PushEffect is called.
|
|
Assert.Single(group.Children);
|
|
var childGroup = Assert.IsType<DrawingGroup>(group.Children[0]);
|
|
Assert.Equal(effect, childGroup.Effect);
|
|
Assert.Equal(bounds, childGroup.EffectBounds);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Drawing_With_Effect_Should_Use_Stored_Bounds()
|
|
{
|
|
using (UnitTestApplication.Start(new TestServices(renderInterface: Mock.Of<IPlatformRenderInterface>())))
|
|
{
|
|
var effect = new BlurEffect { Radius = 10 };
|
|
var bounds = new Rect(10, 10, 100, 100);
|
|
var group = new DrawingGroup
|
|
{
|
|
Effect = effect,
|
|
EffectBounds = bounds
|
|
};
|
|
group.Children.Add(new GeometryDrawing { Brush = Brushes.Red, Geometry = new RectangleGeometry { Rect = new Rect(20, 20, 50, 50) } });
|
|
|
|
var mockContext = new MockDrawingContext();
|
|
group.Draw(mockContext);
|
|
|
|
Assert.Equal(effect, mockContext.Effect);
|
|
Assert.Equal(bounds, mockContext.Bounds);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Regression test: PlatformDrawingContext.PushEffectCore was passing the raw content bounds to
|
|
/// the platform effect API without inflating by the effect output padding, causing effects to be clipped.
|
|
/// </summary>
|
|
[Fact]
|
|
public void PlatformDrawingContext_PushEffect_Should_Inflate_Bounds_For_Platform_Api()
|
|
{
|
|
var effectImpl = new Mock<IDrawingContextImplWithEffects>();
|
|
effectImpl.Setup(x => x.Transform).Returns(Matrix.Identity);
|
|
using var context = new PlatformDrawingContext(effectImpl.Object, ownsImpl: false);
|
|
|
|
var effect = new BlurEffect { Radius = 10 };
|
|
var contentBounds = new Rect(10, 10, 100, 100);
|
|
var expectedInflatedBounds = contentBounds.Inflate(effect.GetEffectOutputPadding());
|
|
|
|
using (context.PushEffect(effect, contentBounds)) { }
|
|
|
|
// Verify the platform API received the inflated (output) bounds, not the raw content bounds
|
|
effectImpl.Verify(x => x.PushEffect(expectedInflatedBounds, It.IsAny<IEffect>()), Times.Once);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Regression test: DrawingGroup.DrawCore was passing uninflated localBounds to PushOpacityMask
|
|
/// when an Effect was also set, meaning the opacity mask didn't cover the effect output region.
|
|
/// </summary>
|
|
[Fact]
|
|
public void DrawCore_With_Effect_And_OpacityMask_Should_Use_EffectBounds_For_OpacityMask()
|
|
{
|
|
using (UnitTestApplication.Start(new TestServices(renderInterface: Mock.Of<IPlatformRenderInterface>())))
|
|
{
|
|
var effect = new BlurEffect { Radius = 10 };
|
|
var contentBounds = new Rect(10, 10, 100, 100);
|
|
var expectedEffectBounds = contentBounds.Inflate(effect.GetEffectOutputPadding());
|
|
|
|
var group = new DrawingGroup
|
|
{
|
|
Effect = effect,
|
|
EffectBounds = contentBounds,
|
|
OpacityMask = Brushes.Red
|
|
};
|
|
group.Children.Add(new GeometryDrawing
|
|
{
|
|
Brush = Brushes.Blue,
|
|
Geometry = new RectangleGeometry { Rect = new Rect(20, 20, 50, 50) }
|
|
});
|
|
|
|
var mockContext = new MockDrawingContext();
|
|
group.Draw(mockContext);
|
|
|
|
Assert.Equal(expectedEffectBounds, mockContext.OpacityMaskBounds);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Invalidated_Is_Raised_When_Child_Is_Added_Or_Removed()
|
|
{
|
|
var group = new DrawingGroup();
|
|
var child = new GeometryDrawing();
|
|
var count = 0;
|
|
group.Invalidated += (_, _) => count++;
|
|
|
|
group.Children.Add(child);
|
|
Assert.Equal(1, count);
|
|
|
|
group.Children.Remove(child);
|
|
Assert.Equal(2, count);
|
|
}
|
|
|
|
[Fact]
|
|
public void Invalidated_Is_Raised_When_Child_Changes()
|
|
{
|
|
var child = new GeometryDrawing();
|
|
var group = new DrawingGroup();
|
|
group.Children.Add(child);
|
|
|
|
var count = 0;
|
|
group.Invalidated += (_, _) => count++;
|
|
|
|
child.Brush = Brushes.Red;
|
|
Assert.True(count > 0);
|
|
}
|
|
|
|
[Fact]
|
|
public void Removed_Child_Is_No_Longer_Tracked()
|
|
{
|
|
var child = new GeometryDrawing();
|
|
var group = new DrawingGroup();
|
|
group.Children.Add(child);
|
|
group.Children.Remove(child);
|
|
|
|
var count = 0;
|
|
group.Invalidated += (_, _) => count++;
|
|
|
|
child.Brush = Brushes.Red;
|
|
Assert.Equal(0, count);
|
|
}
|
|
|
|
private class MockDrawingContext : DrawingContext
|
|
{
|
|
public IEffect? Effect { get; private set; }
|
|
public Rect Bounds { get; private set; }
|
|
public Rect OpacityMaskBounds { get; private set; }
|
|
|
|
protected override void PushEffectCore(IEffect effect, Rect bounds)
|
|
{
|
|
Effect = effect;
|
|
Bounds = bounds;
|
|
}
|
|
|
|
protected override void PopEffectCore() { }
|
|
|
|
// Implementing required abstract members
|
|
protected override void DrawEllipseCore(IBrush? brush, IPen? pen, Rect rect) { }
|
|
protected override void DrawGeometryCore(IBrush? brush, IPen? pen, IGeometryImpl geometry) { }
|
|
protected override void DrawLineCore(IPen pen, Point p1, Point p2) { }
|
|
protected override void DrawRectangleCore(IBrush? brush, IPen? pen, RoundedRect rrect, BoxShadows boxShadows = default) { }
|
|
protected override void PushClipCore(Rect rect) { }
|
|
protected override void PushClipCore(RoundedRect rect) { }
|
|
protected override void PushGeometryClipCore(Geometry clip) { }
|
|
protected override void PushOpacityCore(double opacity) { }
|
|
protected override void PushOpacityMaskCore(IBrush mask, Rect bounds)
|
|
{
|
|
OpacityMaskBounds = bounds;
|
|
}
|
|
protected override void PushTransformCore(Matrix matrix) { }
|
|
protected override void PopClipCore() { }
|
|
protected override void PopGeometryClipCore() { }
|
|
protected override void PopOpacityCore() { }
|
|
protected override void PopOpacityMaskCore() { }
|
|
protected override void PopTransformCore() { }
|
|
|
|
protected override void DisposeCore() { }
|
|
internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect) { }
|
|
public override void Custom(ICustomDrawOperation custom) { }
|
|
public override void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun) { }
|
|
protected override void PushRenderOptionsCore(RenderOptions renderOptions) { }
|
|
protected override void PushTextOptionsCore(TextOptions textOptions) { }
|
|
protected override void PopRenderOptionsCore() { }
|
|
protected override void PopTextOptionsCore() { }
|
|
}
|
|
}
|
|
|