Browse Source

Merge pull request #2450 from Gillibald/fixes/removeUnsupportedLineCaps

Remove unsupported features from Pen implementation
pull/2476/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
53dc895e7d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 72
      src/Avalonia.Controls/Shapes/Shape.cs
  2. 10
      src/Avalonia.Visuals/Media/BrushExtensions.cs
  3. 72
      src/Avalonia.Visuals/Media/Pen.cs
  4. 3
      src/Avalonia.Visuals/Media/PenLineCap.cs
  5. 19
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  6. 8
      src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
  7. 8
      tests/Avalonia.RenderTests/Shapes/PathTests.cs
  8. 3
      tests/Avalonia.RenderTests/Shapes/PolylineTests.cs
  9. BIN
      tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png
  10. BIN
      tests/TestFiles/Skia/Shapes/Path/Path_With_PenLineCap.expected.png

72
src/Avalonia.Controls/Shapes/Shape.cs

@ -21,13 +21,19 @@ namespace Avalonia.Controls.Shapes
public static readonly StyledProperty<AvaloniaList<double>> StrokeDashArrayProperty = public static readonly StyledProperty<AvaloniaList<double>> StrokeDashArrayProperty =
AvaloniaProperty.Register<Shape, AvaloniaList<double>>(nameof(StrokeDashArray)); AvaloniaProperty.Register<Shape, AvaloniaList<double>>(nameof(StrokeDashArray));
public static readonly StyledProperty<double> StrokeDashOffsetProperty = public static readonly StyledProperty<double> StrokeDashOffsetProperty =
AvaloniaProperty.Register<Shape, double>(nameof(StrokeDashOffset)); AvaloniaProperty.Register<Shape, double>(nameof(StrokeDashOffset));
public static readonly StyledProperty<double> StrokeThicknessProperty = public static readonly StyledProperty<double> StrokeThicknessProperty =
AvaloniaProperty.Register<Shape, double>(nameof(StrokeThickness)); AvaloniaProperty.Register<Shape, double>(nameof(StrokeThickness));
public static readonly StyledProperty<PenLineCap> StrokeLineCapProperty =
AvaloniaProperty.Register<Shape, PenLineCap>(nameof(StrokeLineCap), PenLineCap.Flat);
public static readonly StyledProperty<PenLineJoin> StrokeJoinProperty =
AvaloniaProperty.Register<Shape, PenLineJoin>(nameof(StrokeJoin), PenLineJoin.Miter);
private Matrix _transform = Matrix.Identity; private Matrix _transform = Matrix.Identity;
private Geometry _definingGeometry; private Geometry _definingGeometry;
private Geometry _renderedGeometry; private Geometry _renderedGeometry;
@ -36,7 +42,9 @@ namespace Avalonia.Controls.Shapes
static Shape() static Shape()
{ {
AffectsMeasure<Shape>(StretchProperty, StrokeThicknessProperty); AffectsMeasure<Shape>(StretchProperty, StrokeThicknessProperty);
AffectsRender<Shape>(FillProperty, StrokeProperty, StrokeDashArrayProperty);
AffectsRender<Shape>(FillProperty, StrokeProperty, StrokeDashArrayProperty, StrokeDashOffsetProperty,
StrokeThicknessProperty, StrokeLineCapProperty, StrokeJoinProperty);
} }
public Geometry DefiningGeometry public Geometry DefiningGeometry
@ -106,7 +114,7 @@ namespace Avalonia.Controls.Shapes
get { return GetValue(StrokeDashArrayProperty); } get { return GetValue(StrokeDashArrayProperty); }
set { SetValue(StrokeDashArrayProperty, value); } set { SetValue(StrokeDashArrayProperty, value); }
} }
public double StrokeDashOffset public double StrokeDashOffset
{ {
get { return GetValue(StrokeDashOffsetProperty); } get { return GetValue(StrokeDashOffsetProperty); }
@ -119,13 +127,17 @@ namespace Avalonia.Controls.Shapes
set { SetValue(StrokeThicknessProperty, value); } set { SetValue(StrokeThicknessProperty, value); }
} }
public PenLineCap StrokeDashCap { get; set; } = PenLineCap.Flat; public PenLineCap StrokeLineCap
{
public PenLineCap StrokeStartLineCap { get; set; } = PenLineCap.Flat; get { return GetValue(StrokeLineCapProperty); }
set { SetValue(StrokeLineCapProperty, value); }
public PenLineCap StrokeEndLineCap { get; set; } = PenLineCap.Flat; }
public PenLineJoin StrokeJoin { get; set; } = PenLineJoin.Miter; public PenLineJoin StrokeJoin
{
get { return GetValue(StrokeJoinProperty); }
set { SetValue(StrokeJoinProperty, value); }
}
public override void Render(DrawingContext context) public override void Render(DrawingContext context)
{ {
@ -133,8 +145,8 @@ namespace Avalonia.Controls.Shapes
if (geometry != null) if (geometry != null)
{ {
var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray, StrokeDashOffset), var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray, StrokeDashOffset),
StrokeDashCap, StrokeStartLineCap, StrokeEndLineCap, StrokeJoin); StrokeLineCap, StrokeJoin);
context.DrawGeometry(Fill, pen, geometry); context.DrawGeometry(Fill, pen, geometry);
} }
} }
@ -169,11 +181,11 @@ namespace Avalonia.Controls.Shapes
protected void InvalidateGeometry() protected void InvalidateGeometry()
{ {
this._renderedGeometry = null; _renderedGeometry = null;
this._definingGeometry = null; _definingGeometry = null;
InvalidateMeasure(); InvalidateMeasure();
} }
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
bool deferCalculateTransform; bool deferCalculateTransform;
@ -203,10 +215,10 @@ namespace Avalonia.Controls.Shapes
return CalculateShapeSizeAndSetTransform(availableSize); return CalculateShapeSizeAndSetTransform(availableSize);
} }
} }
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
if(_calculateTransformOnArrange) if (_calculateTransformOnArrange)
{ {
_calculateTransformOnArrange = false; _calculateTransformOnArrange = false;
CalculateShapeSizeAndSetTransform(finalSize); CalculateShapeSizeAndSetTransform(finalSize);
@ -312,25 +324,25 @@ namespace Avalonia.Controls.Shapes
private static void AffectsGeometryInvalidate(AvaloniaPropertyChangedEventArgs e) private static void AffectsGeometryInvalidate(AvaloniaPropertyChangedEventArgs e)
{ {
var control = e.Sender as Shape; if (!(e.Sender is Shape control))
{
return;
}
if (control != null) // If the geometry is invalidated when Bounds changes, only invalidate when the Size
// portion changes.
if (e.Property == BoundsProperty)
{ {
// If the geometry is invalidated when Bounds changes, only invalidate when the Size var oldBounds = (Rect)e.OldValue;
// portion changes. var newBounds = (Rect)e.NewValue;
if (e.Property == BoundsProperty)
{
var oldBounds = (Rect)e.OldValue;
var newBounds = (Rect)e.NewValue;
if (oldBounds.Size == newBounds.Size) if (oldBounds.Size == newBounds.Size)
{ {
return; return;
}
} }
control.InvalidateGeometry();
} }
control.InvalidateGeometry();
} }
} }
} }

10
src/Avalonia.Visuals/Media/BrushExtensions.cs

@ -34,16 +34,14 @@ namespace Avalonia.Media
{ {
Contract.Requires<ArgumentNullException>(pen != null); Contract.Requires<ArgumentNullException>(pen != null);
var brush = pen?.Brush?.ToImmutable(); var brush = pen.Brush?.ToImmutable();
return pen == null || ReferenceEquals(pen?.Brush, brush) ? return ReferenceEquals(pen.Brush, brush) ?
pen : pen :
new Pen( new Pen(
brush, brush,
thickness: pen.Thickness, thickness: pen.Thickness,
dashStyle: pen.DashStyle, dashStyle: pen.DashStyle,
dashCap: pen.DashCap, lineCap: pen.LineCap,
startLineCap: pen.StartLineCap,
endLineCap: pen.EndLineCap,
lineJoin: pen.LineJoin, lineJoin: pen.LineJoin,
miterLimit: pen.MiterLimit); miterLimit: pen.MiterLimit);
} }

72
src/Avalonia.Visuals/Media/Pen.cs

@ -11,63 +11,45 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class. /// Initializes a new instance of the <see cref="Pen"/> class.
/// </summary> /// </summary>
/// <param name="brush">The brush used to draw.</param> /// <param name="color">The stroke color.</param>
/// <param name="thickness">The stroke thickness.</param> /// <param name="thickness">The stroke thickness.</param>
/// <param name="dashStyle">The dash style.</param> /// <param name="dashStyle">The dash style.</param>
/// <param name="dashCap">The dash cap.</param> /// <param name="lineCap">Specifies the type of graphic shape to use on both ends of a line.</param>
/// <param name="startLineCap">The start line cap.</param>
/// <param name="endLineCap">The end line cap.</param>
/// <param name="lineJoin">The line join.</param> /// <param name="lineJoin">The line join.</param>
/// <param name="miterLimit">The miter limit.</param> /// <param name="miterLimit">The miter limit.</param>
public Pen( public Pen(
IBrush brush, uint color,
double thickness = 1.0, double thickness = 1.0,
DashStyle dashStyle = null, DashStyle dashStyle = null,
PenLineCap dashCap = PenLineCap.Flat, PenLineCap lineCap = PenLineCap.Flat,
PenLineCap startLineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter,
PenLineCap endLineCap = PenLineCap.Flat, double miterLimit = 10.0) : this(new SolidColorBrush(color), thickness, dashStyle, lineCap, lineJoin, miterLimit)
PenLineJoin lineJoin = PenLineJoin.Miter,
double miterLimit = 10.0)
{ {
Brush = brush;
Thickness = thickness;
DashCap = dashCap;
StartLineCap = startLineCap;
EndLineCap = endLineCap;
LineJoin = lineJoin;
MiterLimit = miterLimit;
DashStyle = dashStyle;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class. /// Initializes a new instance of the <see cref="Pen"/> class.
/// </summary> /// </summary>
/// <param name="color">The stroke color.</param> /// <param name="brush">The brush used to draw.</param>
/// <param name="thickness">The stroke thickness.</param> /// <param name="thickness">The stroke thickness.</param>
/// <param name="dashStyle">The dash style.</param> /// <param name="dashStyle">The dash style.</param>
/// <param name="dashCap">The dash cap.</param> /// <param name="lineCap">The line cap.</param>
/// <param name="startLineCap">The start line cap.</param>
/// <param name="endLineCap">The end line cap.</param>
/// <param name="lineJoin">The line join.</param> /// <param name="lineJoin">The line join.</param>
/// <param name="miterLimit">The miter limit.</param> /// <param name="miterLimit">The miter limit.</param>
public Pen( public Pen(
uint color, IBrush brush,
double thickness = 1.0, double thickness = 1.0,
DashStyle dashStyle = null, DashStyle dashStyle = null,
PenLineCap dashCap = PenLineCap.Flat, PenLineCap lineCap = PenLineCap.Flat,
PenLineCap startLineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter,
PenLineCap endLineCap = PenLineCap.Flat,
PenLineJoin lineJoin = PenLineJoin.Miter,
double miterLimit = 10.0) double miterLimit = 10.0)
{ {
Brush = new SolidColorBrush(color); Brush = brush;
Thickness = thickness; Thickness = thickness;
StartLineCap = startLineCap; LineCap = lineCap;
EndLineCap = endLineCap;
LineJoin = lineJoin; LineJoin = lineJoin;
MiterLimit = miterLimit; MiterLimit = miterLimit;
DashStyle = dashStyle; DashStyle = dashStyle;
DashCap = dashCap;
} }
/// <summary> /// <summary>
@ -78,18 +60,26 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Gets the stroke thickness. /// Gets the stroke thickness.
/// </summary> /// </summary>
public double Thickness { get; } = 1.0; public double Thickness { get; }
/// <summary>
/// Specifies the style of dashed lines drawn with a <see cref="Pen"/> object.
/// </summary>
public DashStyle DashStyle { get; } public DashStyle DashStyle { get; }
public PenLineCap DashCap { get; } /// <summary>
/// Specifies the type of graphic shape to use on both ends of a line.
public PenLineCap StartLineCap { get; } = PenLineCap.Flat; /// </summary>
public PenLineCap LineCap { get; }
public PenLineCap EndLineCap { get; } = PenLineCap.Flat;
public PenLineJoin LineJoin { get; } = PenLineJoin.Miter; /// <summary>
/// Specifies how to join consecutive line or curve segments in a <see cref="PathFigure"/> (subpath) contained in a <see cref="PathGeometry"/> object.
/// </summary>
public PenLineJoin LineJoin { get; }
public double MiterLimit { get; } = 10.0; /// <summary>
/// The limit on the ratio of the miter length to half this pen's Thickness.
/// </summary>
public double MiterLimit { get; }
} }
} }

3
src/Avalonia.Visuals/Media/PenLineCap.cs

@ -4,7 +4,6 @@ namespace Avalonia.Media
{ {
Flat, Flat,
Round, Round,
Square, Square
Triangle
} }
} }

19
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -578,25 +578,17 @@ namespace Avalonia.Skia
// Need to modify dashes due to Skia modifying their lengths // Need to modify dashes due to Skia modifying their lengths
// https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/paths/dots // https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/paths/dots
// TODO: Still something is off, dashes are now present, but don't look the same as D2D ones. // TODO: Still something is off, dashes are now present, but don't look the same as D2D ones.
float dashLengthModifier;
float gapLengthModifier;
switch (pen.StartLineCap) switch (pen.LineCap)
{ {
case PenLineCap.Round: case PenLineCap.Round:
paint.StrokeCap = SKStrokeCap.Round; paint.StrokeCap = SKStrokeCap.Round;
dashLengthModifier = -paint.StrokeWidth;
gapLengthModifier = paint.StrokeWidth;
break; break;
case PenLineCap.Square: case PenLineCap.Square:
paint.StrokeCap = SKStrokeCap.Square; paint.StrokeCap = SKStrokeCap.Square;
dashLengthModifier = -paint.StrokeWidth;
gapLengthModifier = paint.StrokeWidth;
break; break;
default: default:
paint.StrokeCap = SKStrokeCap.Butt; paint.StrokeCap = SKStrokeCap.Butt;
dashLengthModifier = 0.0f;
gapLengthModifier = 0.0f;
break; break;
} }
@ -622,13 +614,12 @@ namespace Avalonia.Skia
for (var i = 0; i < srcDashes.Count; ++i) for (var i = 0; i < srcDashes.Count; ++i)
{ {
var lengthModifier = i % 2 == 0 ? dashLengthModifier : gapLengthModifier; dashesArray[i] = (float) srcDashes[i] * paint.StrokeWidth;
// Avalonia dash lengths are relative, but Skia takes absolute sizes - need to scale
dashesArray[i] = (float) srcDashes[i] * paint.StrokeWidth + lengthModifier;
} }
var pe = SKPathEffect.CreateDash(dashesArray, (float) pen.DashStyle.Offset); var offset = (float)(pen.DashStyle.Offset * pen.Thickness);
var pe = SKPathEffect.CreateDash(dashesArray, offset);
paint.PathEffect = pe; paint.PathEffect = pe;
rv.AddDisposable(pe); rv.AddDisposable(pe);

8
src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs

@ -122,14 +122,16 @@ namespace Avalonia.Direct2D1
/// <returns>The Direct2D brush.</returns> /// <returns>The Direct2D brush.</returns>
public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.Pen pen, Factory factory) public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.Pen pen, Factory factory)
{ {
var d2dLineCap = pen.LineCap.ToDirect2D();
var properties = new StrokeStyleProperties var properties = new StrokeStyleProperties
{ {
DashStyle = DashStyle.Solid, DashStyle = DashStyle.Solid,
MiterLimit = (float)pen.MiterLimit, MiterLimit = (float)pen.MiterLimit,
LineJoin = pen.LineJoin.ToDirect2D(), LineJoin = pen.LineJoin.ToDirect2D(),
StartCap = pen.StartLineCap.ToDirect2D(), StartCap = d2dLineCap,
EndCap = pen.EndLineCap.ToDirect2D(), EndCap = d2dLineCap,
DashCap = pen.DashCap.ToDirect2D() DashCap = d2dLineCap
}; };
float[] dashes = null; float[] dashes = null;
if (pen.DashStyle?.Dashes != null && pen.DashStyle.Dashes.Count > 0) if (pen.DashStyle?.Dashes != null && pen.DashStyle.Dashes.Count > 0)

8
tests/Avalonia.RenderTests/Shapes/PathTests.cs

@ -334,11 +334,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
CompareImages(); CompareImages();
} }
#if AVALONIA_SKIA_SKIP_FAIL
[Fact(Skip = "FIXME")]
#else
[Fact] [Fact]
#endif
public async Task Path_With_PenLineCap() public async Task Path_With_PenLineCap()
{ {
Decorator target = new Decorator Decorator target = new Decorator
@ -351,10 +347,8 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
StrokeThickness = 10, StrokeThickness = 10,
HorizontalAlignment = HorizontalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
StrokeDashCap = PenLineCap.Triangle,
StrokeDashArray = new AvaloniaList<double>(3, 1), StrokeDashArray = new AvaloniaList<double>(3, 1),
StrokeStartLineCap = PenLineCap.Round, StrokeLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Square,
Data = StreamGeometry.Parse("M 20,20 L 180,180"), Data = StreamGeometry.Parse("M 20,20 L 180,180"),
} }
}; };

3
tests/Avalonia.RenderTests/Shapes/PolylineTests.cs

@ -61,8 +61,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
Points = polylinePoints, Points = polylinePoints,
Stretch = Stretch.Uniform, Stretch = Stretch.Uniform,
StrokeJoin = PenLineJoin.Round, StrokeJoin = PenLineJoin.Round,
StrokeStartLineCap = PenLineCap.Round, StrokeLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round,
StrokeThickness = 10 StrokeThickness = 10
} }
}; };

BIN
tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
tests/TestFiles/Skia/Shapes/Path/Path_With_PenLineCap.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Loading…
Cancel
Save