|
|
|
@ -1,3 +1,4 @@ |
|
|
|
using System.Diagnostics.CodeAnalysis; |
|
|
|
using Avalonia.Media; |
|
|
|
using Avalonia.Platform; |
|
|
|
using SkiaSharp; |
|
|
|
@ -10,36 +11,39 @@ namespace Avalonia.Skia |
|
|
|
internal class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl |
|
|
|
{ |
|
|
|
private Rect _bounds; |
|
|
|
private readonly SKPath _effectivePath; |
|
|
|
private readonly SKPath _strokePath; |
|
|
|
private SKPath? _fillPath; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="path">An existing Skia <see cref="SKPath"/>.</param>
|
|
|
|
/// <param name="stroke">An existing Skia <see cref="SKPath"/> for the stroke.</param>
|
|
|
|
/// <param name="fill">An existing Skia <see cref="SKPath"/> for the fill, can also be null or the same as the stroke</param>
|
|
|
|
/// <param name="bounds">Precomputed path bounds.</param>
|
|
|
|
public StreamGeometryImpl(SKPath path, Rect bounds) |
|
|
|
public StreamGeometryImpl(SKPath stroke, SKPath? fill, Rect? bounds = null) |
|
|
|
{ |
|
|
|
_effectivePath = path; |
|
|
|
_bounds = bounds; |
|
|
|
_strokePath = stroke; |
|
|
|
_fillPath = fill; |
|
|
|
_bounds = bounds ?? stroke.TightBounds.ToAvaloniaRect(); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="path">An existing Skia <see cref="SKPath"/>.</param>
|
|
|
|
public StreamGeometryImpl(SKPath path) : this(path, path.TightBounds.ToAvaloniaRect()) |
|
|
|
private StreamGeometryImpl(SKPath path) : this(path, path, default(Rect)) |
|
|
|
{ |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
|
|
|
|
/// </summary>
|
|
|
|
public StreamGeometryImpl() : this(CreateEmptyPath(), default) |
|
|
|
public StreamGeometryImpl() : this(CreateEmptyPath()) |
|
|
|
{ |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public override SKPath EffectivePath => _effectivePath; |
|
|
|
public override SKPath? StrokePath => _strokePath; |
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public override SKPath? FillPath => _fillPath; |
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public override Rect Bounds => _bounds; |
|
|
|
@ -47,7 +51,9 @@ namespace Avalonia.Skia |
|
|
|
/// <inheritdoc />
|
|
|
|
public IStreamGeometryImpl Clone() |
|
|
|
{ |
|
|
|
return new StreamGeometryImpl(_effectivePath.Clone(), Bounds); |
|
|
|
var stroke = _strokePath.Clone(); |
|
|
|
var fill = _fillPath == _strokePath ? stroke : _fillPath.Clone(); |
|
|
|
return new StreamGeometryImpl(stroke, fill, Bounds); |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
@ -74,7 +80,10 @@ namespace Avalonia.Skia |
|
|
|
private class StreamContext : IStreamGeometryContextImpl |
|
|
|
{ |
|
|
|
private readonly StreamGeometryImpl _geometryImpl; |
|
|
|
private readonly SKPath _path; |
|
|
|
private SKPath Stroke => _geometryImpl._strokePath; |
|
|
|
private SKPath Fill => _geometryImpl._fillPath ??= new(); |
|
|
|
private bool _isFilled; |
|
|
|
private bool Duplicate => _isFilled && !ReferenceEquals(_geometryImpl._fillPath, Stroke); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="StreamContext"/> class.
|
|
|
|
@ -83,52 +92,79 @@ namespace Avalonia.Skia |
|
|
|
public StreamContext(StreamGeometryImpl geometryImpl) |
|
|
|
{ |
|
|
|
_geometryImpl = geometryImpl; |
|
|
|
_path = _geometryImpl._effectivePath; |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
/// <remarks>Will update bounds of passed geometry.</remarks>
|
|
|
|
public void Dispose() |
|
|
|
{ |
|
|
|
_geometryImpl._bounds = _path.TightBounds.ToAvaloniaRect(); |
|
|
|
_geometryImpl._bounds = Stroke.TightBounds.ToAvaloniaRect(); |
|
|
|
_geometryImpl.InvalidateCaches(); |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) |
|
|
|
{ |
|
|
|
_path.ArcTo( |
|
|
|
var arc = isLargeArc ? SKPathArcSize.Large : SKPathArcSize.Small; |
|
|
|
var sweep = sweepDirection == SweepDirection.Clockwise |
|
|
|
? SKPathDirection.Clockwise |
|
|
|
: SKPathDirection.CounterClockwise; |
|
|
|
Stroke.ArcTo( |
|
|
|
(float)size.Width, |
|
|
|
(float)size.Height, |
|
|
|
(float)rotationAngle, |
|
|
|
isLargeArc ? SKPathArcSize.Large : SKPathArcSize.Small, |
|
|
|
sweepDirection == SweepDirection.Clockwise ? SKPathDirection.Clockwise : SKPathDirection.CounterClockwise, |
|
|
|
arc, |
|
|
|
sweep, |
|
|
|
(float)point.X, |
|
|
|
(float)point.Y); |
|
|
|
if(Duplicate) |
|
|
|
Fill.ArcTo( |
|
|
|
(float)size.Width, |
|
|
|
(float)size.Height, |
|
|
|
(float)rotationAngle, |
|
|
|
arc, |
|
|
|
sweep, |
|
|
|
(float)point.X, |
|
|
|
(float)point.Y); |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public void BeginFigure(Point startPoint, bool isFilled) |
|
|
|
{ |
|
|
|
_path.MoveTo((float)startPoint.X, (float)startPoint.Y); |
|
|
|
if (!isFilled) |
|
|
|
{ |
|
|
|
if (Stroke == Fill) |
|
|
|
_geometryImpl._fillPath = Stroke.Clone(); |
|
|
|
} |
|
|
|
|
|
|
|
_isFilled = isFilled; |
|
|
|
Stroke.MoveTo((float)startPoint.X, (float)startPoint.Y); |
|
|
|
if(Duplicate) |
|
|
|
Fill.MoveTo((float)startPoint.X, (float)startPoint.Y); |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public void CubicBezierTo(Point point1, Point point2, Point point3) |
|
|
|
{ |
|
|
|
_path.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y); |
|
|
|
Stroke.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y); |
|
|
|
if(Duplicate) |
|
|
|
Fill.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y); |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public void QuadraticBezierTo(Point point1, Point point2) |
|
|
|
{ |
|
|
|
_path.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y); |
|
|
|
Stroke.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y); |
|
|
|
if(Duplicate) |
|
|
|
Fill.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y); |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public void LineTo(Point point) |
|
|
|
{ |
|
|
|
_path.LineTo((float)point.X, (float)point.Y); |
|
|
|
Stroke.LineTo((float)point.X, (float)point.Y); |
|
|
|
if(Duplicate) |
|
|
|
Fill.LineTo((float)point.X, (float)point.Y); |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
@ -136,14 +172,16 @@ namespace Avalonia.Skia |
|
|
|
{ |
|
|
|
if (isClosed) |
|
|
|
{ |
|
|
|
_path.Close(); |
|
|
|
Stroke.Close(); |
|
|
|
if (Duplicate) |
|
|
|
Fill.Close(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public void SetFillRule(FillRule fillRule) |
|
|
|
{ |
|
|
|
_path.FillType = fillRule == FillRule.EvenOdd ? SKPathFillType.EvenOdd : SKPathFillType.Winding; |
|
|
|
Fill.FillType = fillRule == FillRule.EvenOdd ? SKPathFillType.EvenOdd : SKPathFillType.Winding; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|