Skip to content

Commit

Permalink
Merge pull request #6576 from AvaloniaUI/feature/1279-combining-geome…
Browse files Browse the repository at this point in the history
…tries

Add GeometryGroup and CombinedGeometry
  • Loading branch information
grokys committed Sep 23, 2021
2 parents 2c6a820 + 26bc124 commit f51efd3
Show file tree
Hide file tree
Showing 35 changed files with 750 additions and 4 deletions.
2 changes: 2 additions & 0 deletions src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public IGeometryImpl CreateRectangleGeometry(Rect rect)
}

public IStreamGeometryImpl CreateStreamGeometry() => new HeadlessStreamingGeometryStub();
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<Geometry> children) => throw new NotImplementedException();
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => throw new NotImplementedException();

public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => new HeadlessRenderTarget();

Expand Down
4 changes: 3 additions & 1 deletion src/Avalonia.Visuals/ApiCompatBaseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avaloni
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAndTangentAtDistance(System.Double, Avalonia.Point, Avalonia.Point)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAtDistance(System.Double, Avalonia.Point)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetSegment(System.Double, System.Double, System.Boolean, Avalonia.Platform.IGeometryImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGeometryImpl Avalonia.Platform.IPlatformRenderInterface.CreateCombinedGeometry(Avalonia.Media.GeometryCombineMode, Avalonia.Media.Geometry, Avalonia.Media.Geometry)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGeometryImpl Avalonia.Platform.IPlatformRenderInterface.CreateGeometryGroup(Avalonia.Media.FillRule, System.Collections.Generic.IReadOnlyList<Avalonia.Media.Geometry>)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.IO.Stream)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
Total Issues: 75
Total Issues: 77
170 changes: 170 additions & 0 deletions src/Avalonia.Visuals/Media/CombinedGeometry.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
37 changes: 37 additions & 0 deletions src/Avalonia.Visuals/Media/GeometryCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Collections;
using System.Collections.Generic;
using Avalonia.Animation;

#nullable enable

namespace Avalonia.Media
{
public class GeometryCollection : Animatable, IList<Geometry>, IReadOnlyList<Geometry>
{
private List<Geometry> _inner;

public GeometryCollection() => _inner = new List<Geometry>();
public GeometryCollection(IEnumerable<Geometry> collection) => _inner = new List<Geometry>(collection);
public GeometryCollection(int capacity) => _inner = new List<Geometry>(capacity);

public Geometry this[int index]
{
get => _inner[index];
set => _inner[index] = value;
}

public int Count => _inner.Count;
public bool IsReadOnly => false;

public void Add(Geometry item) => _inner.Add(item);
public void Clear() => _inner.Clear();
public bool Contains(Geometry item) => _inner.Contains(item);
public void CopyTo(Geometry[] array, int arrayIndex) => _inner.CopyTo(array, arrayIndex);
public IEnumerator<Geometry> GetEnumerator() => _inner.GetEnumerator();
public int IndexOf(Geometry item) => _inner.IndexOf(item);
public void Insert(int index, Geometry item) => _inner.Insert(index, item);
public bool Remove(Geometry item) => _inner.Remove(item);
public void RemoveAt(int index) => _inner.RemoveAt(index);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
80 changes: 80 additions & 0 deletions src/Avalonia.Visuals/Media/GeometryGroup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Metadata;
using Avalonia.Platform;

#nullable enable

namespace Avalonia.Media
{
/// <summary>
/// Represents a composite geometry, composed of other <see cref="Geometry"/> objects.
/// </summary>
public class GeometryGroup : Geometry
{
public static readonly DirectProperty<GeometryGroup, GeometryCollection?> ChildrenProperty =
AvaloniaProperty.RegisterDirect<GeometryGroup, GeometryCollection?> (
nameof(Children),
o => o.Children,
(o, v) => o.Children = v);

public static readonly StyledProperty<FillRule> FillRuleProperty =
AvaloniaProperty.Register<GeometryGroup, FillRule>(nameof(FillRule));

private GeometryCollection? _children;
private bool _childrenSet;

/// <summary>
/// Gets or sets the collection that contains the child geometries.
/// </summary>
[Content]
public GeometryCollection? Children
{
get => _children ??= (!_childrenSet ? new GeometryCollection() : null);
set
{
SetAndRaise(ChildrenProperty, ref _children, value);
_childrenSet = true;
}
}

/// <summary>
/// Gets or sets how the intersecting areas of the objects contained in this
/// <see cref="GeometryGroup"/> are combined. The default is <see cref="FillRule.EvenOdd"/>.
/// </summary>
public FillRule FillRule
{
get => GetValue(FillRuleProperty);
set => SetValue(FillRuleProperty, value);
}

public override Geometry Clone()
{
var result = new GeometryGroup { FillRule = FillRule, Transform = Transform };
if (_children?.Count > 0)
result.Children = new GeometryCollection(_children);
return result;
}

protected override IGeometryImpl? CreateDefiningGeometry()
{
if (_children?.Count > 0)
{
var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return factory.CreateGeometryGroup(FillRule, _children);
}

return null;
}

protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);

if (change.Property == ChildrenProperty || change.Property == FillRuleProperty)
{
InvalidateGeometry();
}
}
}
}
52 changes: 49 additions & 3 deletions src/Avalonia.Visuals/Media/RotateTransform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ public class RotateTransform : Transform
public static readonly StyledProperty<double> AngleProperty =
AvaloniaProperty.Register<RotateTransform, double>(nameof(Angle));

/// <summary>
/// Defines the <see cref="CenterX"/> property.
/// </summary>
public static readonly StyledProperty<double> CenterXProperty =
AvaloniaProperty.Register<RotateTransform, double>(nameof(CenterX));

/// <summary>
/// Defines the <see cref="CenterY"/> property.
/// </summary>
public static readonly StyledProperty<double> CenterYProperty =
AvaloniaProperty.Register<RotateTransform, double>(nameof(CenterY));

/// <summary>
/// Initializes a new instance of the <see cref="RotateTransform"/> class.
/// </summary>
Expand All @@ -32,18 +44,52 @@ public RotateTransform(double angle)
Angle = angle;
}

/// <summary>
/// Initializes a new instance of the <see cref="RotateTransform"/> class.
/// </summary>
/// <param name="angle">The angle, in degrees.</param>
/// <param name="centerX">The x-coordinate of the center point for the rotation.</param>
/// <param name="centerY">The y-coordinate of the center point for the rotation.</param>
public RotateTransform(double angle, double centerX, double centerY)
: this()
{
Angle = angle;
CenterX = centerX;
CenterY = centerY;
}

/// <summary>
/// Gets or sets the angle of rotation, in degrees.
/// </summary>
public double Angle
{
get { return GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
get => GetValue(AngleProperty);
set => SetValue(AngleProperty, value);
}

/// <summary>
/// Gets or sets the x-coordinate of the rotation center point. The default is 0.
/// </summary>
public double CenterX
{
get => GetValue(CenterXProperty);
set => SetValue(CenterXProperty, value);
}

/// <summary>
/// Gets or sets the y-coordinate of the rotation center point. The default is 0.
/// </summary>
public double CenterY
{
get => GetValue(CenterYProperty);
set => SetValue(CenterYProperty, value);
}

/// <summary>
/// Gets the transform's <see cref="Matrix"/>.
/// </summary>
public override Matrix Value => Matrix.CreateRotation(Matrix.ToRadians(Angle));
public override Matrix Value => Matrix.CreateTranslation(-CenterX, -CenterY) *
Matrix.CreateRotation(Matrix.ToRadians(Angle)) *
Matrix.CreateTranslation(CenterX, CenterY);
}
}

0 comments on commit f51efd3

Please sign in to comment.