@ -0,0 +1,170 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Avalonia.Platform; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
public enum GeometryCombineMode |
|||
{ |
|||
/// <summary>
|
|||
/// The two regions are combined by taking the union of both. The resulting geometry is
|
|||
/// geometry A + geometry B.
|
|||
/// </summary>
|
|||
Union, |
|||
|
|||
/// <summary>
|
|||
/// The two regions are combined by taking their intersection. The new area consists of the
|
|||
/// overlapping region between the two geometries.
|
|||
/// </summary>
|
|||
Intersect, |
|||
|
|||
/// <summary>
|
|||
/// The two regions are combined by taking the area that exists in the first region but not
|
|||
/// the second and the area that exists in the second region but not the first. The new
|
|||
/// region consists of (A-B) + (B-A), where A and B are geometries.
|
|||
/// </summary>
|
|||
Xor, |
|||
|
|||
/// <summary>
|
|||
/// The second region is excluded from the first. Given two geometries, A and B, the area of
|
|||
/// geometry B is removed from the area of geometry A, producing a region that is A-B.
|
|||
/// </summary>
|
|||
Exclude, |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents a 2-D geometric shape defined by the combination of two Geometry objects.
|
|||
/// </summary>
|
|||
public class CombinedGeometry : Geometry |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Geometry1"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<Geometry?> Geometry1Property = |
|||
AvaloniaProperty.Register<CombinedGeometry, Geometry?>(nameof(Geometry1)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Geometry2"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<Geometry?> Geometry2Property = |
|||
AvaloniaProperty.Register<CombinedGeometry, Geometry?>(nameof(Geometry2)); |
|||
/// <summary>
|
|||
/// Defines the <see cref="GeometryCombineMode"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<GeometryCombineMode> GeometryCombineModeProperty = |
|||
AvaloniaProperty.Register<CombinedGeometry, GeometryCombineMode>(nameof(GeometryCombineMode)); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CombinedGeometry"/> class.
|
|||
/// </summary>
|
|||
public CombinedGeometry() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CombinedGeometry"/> class with the
|
|||
/// specified <see cref="Geometry"/> objects.
|
|||
/// </summary>
|
|||
/// <param name="geometry1">The first geometry to combine.</param>
|
|||
/// <param name="geometry2">The second geometry to combine.</param>
|
|||
public CombinedGeometry(Geometry geometry1, Geometry geometry2) |
|||
{ |
|||
Geometry1 = geometry1; |
|||
Geometry2 = geometry2; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CombinedGeometry"/> class with the
|
|||
/// specified <see cref="Geometry"/> objects and <see cref="GeometryCombineMode"/>.
|
|||
/// </summary>
|
|||
/// <param name="combineMode">The method by which geometry1 and geometry2 are combined.</param>
|
|||
/// <param name="geometry1">The first geometry to combine.</param>
|
|||
/// <param name="geometry2">The second geometry to combine.</param>
|
|||
public CombinedGeometry(GeometryCombineMode combineMode, Geometry? geometry1, Geometry? geometry2) |
|||
{ |
|||
Geometry1 = geometry1; |
|||
Geometry2 = geometry2; |
|||
GeometryCombineMode = combineMode; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CombinedGeometry"/> class with the
|
|||
/// specified <see cref="Geometry"/> objects, <see cref="GeometryCombineMode"/> and
|
|||
/// <see cref="Transform"/>.
|
|||
/// </summary>
|
|||
/// <param name="combineMode">The method by which geometry1 and geometry2 are combined.</param>
|
|||
/// <param name="geometry1">The first geometry to combine.</param>
|
|||
/// <param name="geometry2">The second geometry to combine.</param>
|
|||
/// <param name="transform">The transform applied to the geometry.</param>
|
|||
public CombinedGeometry( |
|||
GeometryCombineMode combineMode, |
|||
Geometry? geometry1, |
|||
Geometry? geometry2, |
|||
Transform? transform) |
|||
{ |
|||
Geometry1 = geometry1; |
|||
Geometry2 = geometry2; |
|||
GeometryCombineMode = combineMode; |
|||
Transform = transform; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the first <see cref="Geometry"/> object of this
|
|||
/// <see cref="CombinedGeometry"/> object.
|
|||
/// </summary>
|
|||
public Geometry? Geometry1 |
|||
{ |
|||
get => GetValue(Geometry1Property); |
|||
set => SetValue(Geometry1Property, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the second <see cref="Geometry"/> object of this
|
|||
/// <see cref="CombinedGeometry"/> object.
|
|||
/// </summary>
|
|||
public Geometry? Geometry2 |
|||
{ |
|||
get => GetValue(Geometry2Property); |
|||
set => SetValue(Geometry2Property, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the method by which the two geometries (specified by the
|
|||
/// <see cref="Geometry1"/> and <see cref="Geometry2"/> properties) are combined. The
|
|||
/// default value is <see cref="GeometryCombineMode.Union"/>.
|
|||
/// </summary>
|
|||
public GeometryCombineMode GeometryCombineMode |
|||
{ |
|||
get => GetValue(GeometryCombineModeProperty); |
|||
set => SetValue(GeometryCombineModeProperty, value); |
|||
} |
|||
|
|||
public override Geometry Clone() |
|||
{ |
|||
return new CombinedGeometry(GeometryCombineMode, Geometry1, Geometry2, Transform); |
|||
} |
|||
|
|||
protected override IGeometryImpl? CreateDefiningGeometry() |
|||
{ |
|||
var g1 = Geometry1; |
|||
var g2 = Geometry2; |
|||
|
|||
if (g1 is object && g2 is object) |
|||
{ |
|||
var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); |
|||
return factory.CreateCombinedGeometry(GeometryCombineMode, g1, g2); |
|||
} |
|||
else if (GeometryCombineMode == GeometryCombineMode.Intersect) |
|||
return null; |
|||
else if (g1 is object) |
|||
return g1.PlatformImpl; |
|||
else if (g2 is object) |
|||
return g2.PlatformImpl; |
|||
else |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System.Collections.Generic; |
|||
using Avalonia.Media; |
|||
using SkiaSharp; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Skia |
|||
{ |
|||
/// <summary>
|
|||
/// A Skia implementation of a <see cref="Avalonia.Media.GeometryGroup"/>.
|
|||
/// </summary>
|
|||
internal class CombinedGeometryImpl : GeometryImpl |
|||
{ |
|||
public CombinedGeometryImpl(GeometryCombineMode combineMode, Geometry g1, Geometry g2) |
|||
{ |
|||
var path1 = ((GeometryImpl)g1.PlatformImpl).EffectivePath; |
|||
var path2 = ((GeometryImpl)g2.PlatformImpl).EffectivePath; |
|||
var op = combineMode switch |
|||
{ |
|||
GeometryCombineMode.Intersect => SKPathOp.Intersect, |
|||
GeometryCombineMode.Xor => SKPathOp.Xor, |
|||
GeometryCombineMode.Exclude => SKPathOp.Difference, |
|||
_ => SKPathOp.Union, |
|||
}; |
|||
|
|||
var path = path1.Op(path2, op); |
|||
|
|||
EffectivePath = path; |
|||
Bounds = path.Bounds.ToAvaloniaRect(); |
|||
} |
|||
|
|||
public override Rect Bounds { get; } |
|||
public override SKPath EffectivePath { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
using SharpDX.Direct2D1; |
|||
using AM = Avalonia.Media; |
|||
|
|||
namespace Avalonia.Direct2D1.Media |
|||
{ |
|||
/// <summary>
|
|||
/// A Direct2D implementation of a <see cref="Avalonia.Media.CombinedGeometry"/>.
|
|||
/// </summary>
|
|||
internal class CombinedGeometryImpl : GeometryImpl |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
|
|||
/// </summary>
|
|||
public CombinedGeometryImpl( |
|||
AM.GeometryCombineMode combineMode, |
|||
AM.Geometry geometry1, |
|||
AM.Geometry geometry2) |
|||
: base(CreateGeometry(combineMode, geometry1, geometry2)) |
|||
{ |
|||
} |
|||
|
|||
private static Geometry CreateGeometry( |
|||
AM.GeometryCombineMode combineMode, |
|||
AM.Geometry geometry1, |
|||
AM.Geometry geometry2) |
|||
{ |
|||
var g1 = ((GeometryImpl)geometry1.PlatformImpl).Geometry; |
|||
var g2 = ((GeometryImpl)geometry2.PlatformImpl).Geometry; |
|||
var dest = new PathGeometry(Direct2D1Platform.Direct2D1Factory); |
|||
using var sink = dest.Open(); |
|||
g1.Combine(g2, (CombineMode)combineMode, sink); |
|||
sink.Close(); |
|||
return dest; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Shapes; |
|||
using Avalonia.Media; |
|||
using Xunit; |
|||
|
|||
#if AVALONIA_SKIA
|
|||
namespace Avalonia.Skia.RenderTests |
|||
#else
|
|||
namespace Avalonia.Direct2D1.RenderTests.Media |
|||
#endif
|
|||
{ |
|||
public class CombinedGeometryTests : TestBase |
|||
{ |
|||
public CombinedGeometryTests() |
|||
: base(@"Media\CombinedGeometry") |
|||
{ |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(Avalonia.Media.GeometryCombineMode.Union)] |
|||
[InlineData(Avalonia.Media.GeometryCombineMode.Intersect)] |
|||
[InlineData(Avalonia.Media.GeometryCombineMode.Xor)] |
|||
[InlineData(Avalonia.Media.GeometryCombineMode.Exclude)] |
|||
public async Task GeometryCombineMode(GeometryCombineMode mode) |
|||
{ |
|||
var target = new Border |
|||
{ |
|||
Width = 200, |
|||
Height = 200, |
|||
Background = Brushes.White, |
|||
Child = new Path |
|||
{ |
|||
Data = new CombinedGeometry |
|||
{ |
|||
GeometryCombineMode = mode, |
|||
Geometry1 = new RectangleGeometry(new Rect(25, 25, 100, 100)), |
|||
Geometry2 = new EllipseGeometry |
|||
{ |
|||
Center = new Point(125, 125), |
|||
RadiusX = 50, |
|||
RadiusY = 50, |
|||
} |
|||
}, |
|||
Fill = Brushes.Blue, |
|||
Stroke = Brushes.Red, |
|||
StrokeThickness = 1, |
|||
} |
|||
}; |
|||
|
|||
var testName = $"GeometryCombineMode_{mode}"; |
|||
await RenderToFile(target, testName); |
|||
CompareImages(testName); |
|||
} |
|||
} |
|||
} |
|||
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.0 KiB |