|
@@ -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;
|
|
|
}
|
|
|
}
|
|
|
}
|