Skip to content
Open
16 changes: 14 additions & 2 deletions src/Avalonia.Base/Media/PolylineGeometry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class PolylineGeometry : Geometry

private IList<Point> _points;
private IDisposable? _pointsObserver;
private readonly FillRule _fillRule;

static PolylineGeometry()
{
Expand All @@ -40,15 +41,17 @@ static PolylineGeometry()
public PolylineGeometry()
{
_points = new Points();
_fillRule = FillRule.EvenOdd;
}

/// <summary>
/// Initializes a new instance of the <see cref="PolylineGeometry"/> class.
/// </summary>
public PolylineGeometry(IEnumerable<Point> points, bool isFilled)
public PolylineGeometry(IEnumerable<Point> points, bool isFilled, FillRule fillRule = FillRule.EvenOdd)
{
_points = new Points(points);
IsFilled = isFilled;
_fillRule = fillRule;
}

/// <summary>
Expand All @@ -70,10 +73,18 @@ public bool IsFilled
set => SetValue(IsFilledProperty, value);
}

/// <summary>
/// Gets how the intersecting areas of the polyline are combined.
/// </summary>
public FillRule FillRule => _fillRule;

/// <inheritdoc/>
public override Geometry Clone()
{
return new PolylineGeometry(Points, IsFilled);
return new PolylineGeometry(Points, IsFilled, _fillRule)
{
Transform = Transform
};
}

private protected sealed override IGeometryImpl? CreateDefiningGeometry()
Expand All @@ -83,6 +94,7 @@ public override Geometry Clone()

using (var context = geometry.Open())
{
context.SetFillRule(_fillRule);
var points = Points;
var isFilled = IsFilled;
if (points.Count > 0)
Expand Down
13 changes: 11 additions & 2 deletions src/Avalonia.Controls/Shapes/Polygon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ public class Polygon : Shape
public static readonly StyledProperty<IList<Point>> PointsProperty =
AvaloniaProperty.Register<Polygon, IList<Point>>("Points");

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

static Polygon()
{
AffectsGeometry<Polygon>(PointsProperty);
AffectsGeometry<Polygon>(PointsProperty, FillRuleProperty);
}

public Polygon()
Expand All @@ -25,9 +28,15 @@ public IList<Point> Points
set => SetValue(PointsProperty, value);
}

public FillRule FillRule
{
get => GetValue(FillRuleProperty);
set => SetValue(FillRuleProperty, value);
}

protected override Geometry CreateDefiningGeometry()
{
return new PolylineGeometry { Points = Points, IsFilled = true };
return new PolylineGeometry(Points, isFilled: true, fillRule: FillRule);
}
}
}
14 changes: 12 additions & 2 deletions src/Avalonia.Controls/Shapes/Polyline.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Avalonia;
using Avalonia.Media;
using Avalonia.Data;

Expand All @@ -10,10 +11,13 @@ public class Polyline : Shape
public static readonly StyledProperty<IList<Point>> PointsProperty =
AvaloniaProperty.Register<Polyline, IList<Point>>("Points");

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

static Polyline()
{
StrokeThicknessProperty.OverrideDefaultValue<Polyline>(1);
AffectsGeometry<Polyline>(PointsProperty);
AffectsGeometry<Polyline>(PointsProperty, FillRuleProperty);
}

public Polyline()
Expand All @@ -27,9 +31,15 @@ public IList<Point> Points
set => SetValue(PointsProperty, value);
}

public FillRule FillRule
{
get => GetValue(FillRuleProperty);
set => SetValue(FillRuleProperty, value);
}

protected override Geometry CreateDefiningGeometry()
{
return new PolylineGeometry { Points = Points, IsFilled = false };
return new PolylineGeometry(Points, isFilled: true, fillRule: FillRule);
}
}
}
18 changes: 18 additions & 0 deletions tests/Avalonia.Controls.UnitTests/Shapes/PolygonTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.ObjectModel;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.UnitTests;
using Xunit;

Expand All @@ -25,4 +26,21 @@ public void Polygon_Will_Update_Geometry_On_Shapes_Collection_Content_Change()

root.Child = null;
}

[Fact]
public void FillRule_On_Polygon_Is_Applied_To_DefiningGeometry()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Tested in the Gallery already but there is not a specific Shapes Page. Should include it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested with:

<TextBlock Classes="h3" Margin="0,12,0,4">Path FillRule</TextBlock>
  <TextBlock Text="EvenOdd creates holes for nested figures; NonZero keeps the interior filled when the contours wind in the same direction." />
  <StackPanel Orientation="Horizontal" Spacing="12">
    <StackPanel Spacing="4">
      <TextBlock Text="EvenOdd" />
      <Border Background="White" BorderBrush="Gainsboro" BorderThickness="1" Padding="8" CornerRadius="4">
        <Path Width="140"
              Height="140"
              Stretch="Fill"
              Fill="Orange"
              Stroke="Black"
              StrokeThickness="1"
              Data="F1 M 10,10 L 130,10 130,130 10,130 Z M 50,50 L 90,50 90,90 50,90 Z"/>
      </Border>
    </StackPanel>
    <StackPanel Spacing="4">
      <TextBlock Text="NonZero" />
      <Border Background="White" BorderBrush="Gainsboro" BorderThickness="1" Padding="8" CornerRadius="4">
        <Path Width="140"
              Height="140"
              Stretch="Fill"
              Fill="Orange"
              Stroke="Black"
              StrokeThickness="1"
              Data="F0 M 10,10 L 130,10 130,130 10,130 Z M 50,50 L 90,50 90,90 50,90 Z"/>
      </Border>
    </StackPanel>
  </StackPanel>
  <TextBlock Classes="h3" Margin="0,12,0,4">Polygon/Polyline FillRule</TextBlock>
  <TextBlock Text="Self-intersecting polygons show different winding behavior for EvenOdd and NonZero." />
  <StackPanel Orientation="Horizontal" Spacing="12">
    <StackPanel Spacing="4">
      <TextBlock Text="EvenOdd" />
      <Border Background="White" BorderBrush="Gainsboro" BorderThickness="1" Padding="8" CornerRadius="4">
        <Polygon Width="160"
                 Height="160"
                 Stretch="Fill"
                 Fill="Gold"
                 Stroke="Black"
                 StrokeThickness="1"
                 FillRule="EvenOdd"
                 Points="50,0  21,90  98,35  2,35  79,90" />
      </Border>
    </StackPanel>
    <StackPanel Spacing="4">
      <TextBlock Text="NonZero" />
      <Border Background="White" BorderBrush="Gainsboro" BorderThickness="1" Padding="8" CornerRadius="4">
        <Polygon Width="160"
                 Height="160"
                 Stretch="Fill"
                 Fill="Gold"
                 Stroke="Black"
                 StrokeThickness="1"
                 FillRule="NonZero"
                 Points="50,0  21,90  98,35  2,35  79,90" />
      </Border>
    </StackPanel>
  </StackPanel>
  <TextBlock Classes="h3" Margin="0,12,0,4">Polyline FillRule</TextBlock>
  <TextBlock Text="A jagged filled polyline using NonZero/EvenOdd winding (adapted from WPF FillRule sample)." />
  <StackPanel Orientation="Horizontal" Spacing="12">
    <StackPanel Spacing="4">
      <TextBlock Text="EvenOdd" />
      <Border Background="White" BorderBrush="Gainsboro" BorderThickness="1" Padding="8" CornerRadius="4">
        <Polyline Width="200"
                  Height="140"
                  Stretch="Fill"
                  Fill="Red"
                  Stroke="Black"
                  StrokeThickness="2"
                  FillRule="EvenOdd"
                  Points="10,140
                  60,20
                  110,140
                  20,60
                  180,60
                  90,140
                  140,20
                  10,140" />
      </Border>
    </StackPanel>
    <StackPanel Spacing="4">
      <TextBlock Text="NonZero" />
      <Border Background="White" BorderBrush="Gainsboro" BorderThickness="1" Padding="8" CornerRadius="4">
        <Polyline Width="200"
                  Height="140"
                  Stretch="Fill"
                  Fill="Red"
                  Stroke="Black"
                  StrokeThickness="2"
                  FillRule="NonZero"
                  Points="10,140
                  60,20
                  110,140
                  20,60
                  180,60
                  90,140
                  140,20
                  10,140" />
      </Border>
    </StackPanel>
  </StackPanel>

{
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);

var target = new Polygon
{
Points = new Points { new Point(0, 0), new Point(10, 10), new Point(20, 0) },
FillRule = FillRule.NonZero
};

target.Measure(Size.Infinity);

var geometry = Assert.IsType<PolylineGeometry>(target.DefiningGeometry);
Assert.Equal(FillRule.NonZero, geometry.FillRule);
}
}
18 changes: 18 additions & 0 deletions tests/Avalonia.Controls.UnitTests/Shapes/PolylineTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.ObjectModel;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.UnitTests;
using Xunit;

Expand All @@ -25,4 +26,21 @@ public void Polyline_Will_Update_Geometry_On_Shapes_Collection_Content_Change()

root.Child = null;
}

[Fact]
public void FillRule_On_Polyline_Is_Applied_To_DefiningGeometry()
{
using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);

var target = new Polyline
{
Points = new Points { new Point(0, 0), new Point(10, 10), new Point(20, 0) },
FillRule = FillRule.NonZero
};

target.Measure(Size.Infinity);

var geometry = Assert.IsType<PolylineGeometry>(target.DefiningGeometry);
Assert.Equal(FillRule.NonZero, geometry.FillRule);
}
}
Loading