diff --git a/docs/build.md b/docs/build.md
index b3be7581c4..935341c0d5 100644
--- a/docs/build.md
+++ b/docs/build.md
@@ -7,8 +7,11 @@ Perspex requires Visual Studio 2015 to build on Windows.
### Install GTK Sharp
To compile the full project under windows, you must have [gtk-sharp](http://www.mono-project.com/download/#download-win) installed. However, if you're
-not interested in building the cross-platform bits you can simply unload the Perspex.Cairo and
-Perspex.Gtk project in Visual Studio.
+not interested in building the cross-platform bits you can simply unload these projects from Visual Studio:
+
+ - Perspex.Cairo
+ - Perspex.Cairo.RenderTests
+ - Perspex.Gtk
### Clone the Perspex repository
@@ -19,7 +22,7 @@ is linked as a submodule in the git repository, so run:
git submodule update --init
-The next step is to download the Skia native libraries. Run ```getnatives.ps1``` PowerShell script which can be found under the folder ```Perspex\src\Skia\```.
+The next step is to download the Skia native libraries. Run ```getnatives.ps1``` PowerShell script which can be found under the folder ```src\Skia\```.
## Linux
@@ -39,14 +42,14 @@ Then install the needed packages:
### Clone the Perspex repository
- git clone https://github.com/grokys/Perspex.git
+ git clone https://github.com/Perspex/Perspex.git
We currently need to build our own private version of ReactiveUI as it doesn't work on mono. This
is linked as a submodule in the git repository, so run:
git submodule update --init
-The next step is to download the Skia native libraries. Run ```getnatives.sh``` script which can be found under the folder ```Perspex\src\Skia\```.
+The next step is to download the Skia native libraries. Run ```getnatives.sh``` script which can be found under the folder ```src\Skia\```.
### Load the Project in MonoDevelop
@@ -57,4 +60,4 @@ Set the TestApplication project as the startup project and click Run.
There will be some compile errors in the tests, but ignore them for now.
-You can track the Linux version's progress in the [Linux issue](https://github.com/grokys/Perspex/issues/78).
+You can track the Linux version's progress in the [Linux issue](https://github.com/Perspex/Perspex/issues/78).
diff --git a/samples/ControlCatalog/Pages/CanvasPage.paml b/samples/ControlCatalog/Pages/CanvasPage.paml
index 4a3f682d17..8aaa8176bd 100644
--- a/samples/ControlCatalog/Pages/CanvasPage.paml
+++ b/samples/ControlCatalog/Pages/CanvasPage.paml
@@ -2,12 +2,13 @@
Canvas
A panel which lays out its children by explicit coordinates
-
-
\ No newline at end of file
diff --git a/samples/TestApplicationShared/MainWindow.cs b/samples/TestApplicationShared/MainWindow.cs
index 0a6ebeae2c..30490f774b 100644
--- a/samples/TestApplicationShared/MainWindow.cs
+++ b/samples/TestApplicationShared/MainWindow.cs
@@ -543,6 +543,17 @@ namespace TestApplication
private static TabItem LayoutTab()
{
+ var polylinePoints = new Point[] { new Point(0, 0), new Point(5, 0), new Point(6, -2), new Point(7, 3), new Point(8, -3),
+ new Point(9, 1), new Point(10, 0), new Point(15, 0) };
+ var polygonPoints = new Point[] { new Point(5, 0), new Point(8, 8), new Point(0, 3), new Point(10, 3), new Point(2, 8) };
+ for (int i = 0; i < polylinePoints.Length; i++)
+ {
+ polylinePoints[i] = polylinePoints[i] * 13;
+ }
+ for (int i = 0; i < polygonPoints.Length; i++)
+ {
+ polygonPoints[i] = polygonPoints[i] * 15;
+ }
return new TabItem
{
Header = "Layout",
@@ -691,13 +702,36 @@ namespace TestApplication
},
new Line
{
- Width = 90,
- Height = 70,
Stroke = Brushes.Red,
StrokeThickness = 2,
+ StartPoint = new Point(120, 185),
+ EndPoint = new Point(30, 115)
+ },
+ new Perspex.Controls.Shapes.Path
+ {
+ Fill = Brushes.Orange,
+ Data = StreamGeometry.Parse("M 30,250 c 50,0 50,-50 c 50,0 50,50 h -50 v 50 l -50,-50 Z"),
+ },
+ new Polygon
+ {
+ Stroke = Brushes.DarkBlue,
+ Fill = Brushes.Violet,
+ Points = polygonPoints,
+ StrokeThickness = 1,
+ [Canvas.LeftProperty] = 150,
+ [Canvas.TopProperty] = 180,
+ },
+ new Polyline
+ {
+ Stroke = Brushes.Brown,
+ Points = polylinePoints,
+ StrokeThickness = 5,
+ StrokeJoin = PenLineJoin.Round,
+ StrokeStartLineCap = PenLineCap.Triangle,
+ StrokeEndLineCap = PenLineCap.Triangle,
[Canvas.LeftProperty] = 30,
- [Canvas.TopProperty] = 120
- }
+ [Canvas.TopProperty] = 350,
+ },
}
},
}
diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
index e42df57d59..45e3e7285d 100644
--- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
+++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
@@ -21,6 +21,7 @@ using Perspex.Media.Imaging;
using Perspex.Metadata;
using Perspex.Platform;
using Perspex.Styling;
+using System.Collections.Generic;
namespace Perspex.Markup.Xaml.Context
{
@@ -101,6 +102,7 @@ namespace Perspex.Markup.Xaml.Context
new TypeConverterRegistration(typeof(PerspexList), new PerspexListTypeConverter()),
new TypeConverterRegistration(typeof(IMemberSelector), new MemberSelectorTypeConverter()),
new TypeConverterRegistration(typeof(Point), new PointTypeConverter()),
+ new TypeConverterRegistration(typeof(IList), new PointsListTypeConverter()),
new TypeConverterRegistration(typeof(PerspexProperty), new PerspexPropertyTypeConverter()),
new TypeConverterRegistration(typeof(RelativePoint), new RelativePointTypeConverter()),
new TypeConverterRegistration(typeof(RelativeRect), new RelativeRectTypeConverter()),
diff --git a/src/Markup/Perspex.Markup.Xaml/Converters/PointsListTypeConverter.cs b/src/Markup/Perspex.Markup.Xaml/Converters/PointsListTypeConverter.cs
new file mode 100644
index 0000000000..cff7443eff
--- /dev/null
+++ b/src/Markup/Perspex.Markup.Xaml/Converters/PointsListTypeConverter.cs
@@ -0,0 +1,37 @@
+using OmniXaml.TypeConversion;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace Perspex.Markup.Xaml.Converters
+{
+ public class PointsListTypeConverter : ITypeConverter
+ {
+ public bool CanConvertFrom(IXamlTypeConverterContext context, Type sourceType)
+ {
+ return sourceType == typeof(string);
+ }
+
+ public bool CanConvertTo(IXamlTypeConverterContext context, Type destinationType)
+ {
+ return false;
+ }
+
+ public object ConvertFrom(IXamlTypeConverterContext context, CultureInfo culture, object value)
+ {
+ string strValue = (string)value;
+ string[] pointStrs = strValue.Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
+ var result = new List(pointStrs.Length);
+ foreach (var pointStr in pointStrs)
+ {
+ result.Add(Point.Parse(pointStr, culture));
+ }
+ return result;
+ }
+
+ public object ConvertTo(IXamlTypeConverterContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
index d9cd4c6f63..74cf64f3ec 100644
--- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
+++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
@@ -41,6 +41,7 @@
+
diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj
index cf44ab2997..1c2d72190e 100644
--- a/src/Perspex.Controls/Perspex.Controls.csproj
+++ b/src/Perspex.Controls/Perspex.Controls.csproj
@@ -68,6 +68,8 @@
+
+
diff --git a/src/Perspex.Controls/Shapes/Line.cs b/src/Perspex.Controls/Shapes/Line.cs
index 785ea7bbd1..0d659b126f 100644
--- a/src/Perspex.Controls/Shapes/Line.cs
+++ b/src/Perspex.Controls/Shapes/Line.cs
@@ -9,28 +9,46 @@ namespace Perspex.Controls.Shapes
{
public class Line : Shape
{
- private Geometry _geometry;
+ public static readonly PerspexProperty StartPointProperty =
+ PerspexProperty.Register("StartPoint");
- private Size _geometrySize;
+ public static readonly PerspexProperty EndPointProperty =
+ PerspexProperty.Register("EndPoint");
+
+ private LineGeometry _geometry;
+ private Point _startPoint;
+ private Point _endPoint;
+
+ static Line()
+ {
+ StrokeThicknessProperty.OverrideDefaultValue(1);
+ }
+
+ public Point StartPoint
+ {
+ get { return GetValue(StartPointProperty); }
+ set { SetValue(StartPointProperty, value); }
+ }
+
+ public Point EndPoint
+ {
+ get { return GetValue(EndPointProperty); }
+ set { SetValue(EndPointProperty, value); }
+ }
public override Geometry DefiningGeometry
{
get
{
- if (_geometry == null || _geometrySize != Bounds.Size)
+ if (_geometry == null || StartPoint != _startPoint || EndPoint != _endPoint)
{
- var rect = new Rect(Bounds.Size).Deflate(StrokeThickness);
- _geometry = new LineGeometry(rect.TopLeft, rect.BottomRight);
- _geometrySize = Bounds.Size;
+ _startPoint = StartPoint;
+ _endPoint = EndPoint;
+ _geometry = new LineGeometry(_startPoint, _endPoint);
}
return _geometry;
}
}
-
- protected override Size MeasureOverride(Size availableSize)
- {
- return new Size(StrokeThickness, StrokeThickness);
- }
}
}
diff --git a/src/Perspex.Controls/Shapes/Polygon.cs b/src/Perspex.Controls/Shapes/Polygon.cs
new file mode 100644
index 0000000000..9536d710a5
--- /dev/null
+++ b/src/Perspex.Controls/Shapes/Polygon.cs
@@ -0,0 +1,23 @@
+using Perspex.Media;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Perspex.Controls.Shapes
+{
+ public class Polygon : Shape
+ {
+ public static readonly PerspexProperty> PointsProperty =
+ PerspexProperty.Register>("Points");
+
+ public IList Points
+ {
+ get { return GetValue(PointsProperty); }
+ set { SetValue(PointsProperty, value); }
+ }
+
+ public override Geometry DefiningGeometry => new PolylineGeometry(Points, true);
+ }
+}
diff --git a/src/Perspex.Controls/Shapes/Polyline.cs b/src/Perspex.Controls/Shapes/Polyline.cs
new file mode 100644
index 0000000000..75b1353f6f
--- /dev/null
+++ b/src/Perspex.Controls/Shapes/Polyline.cs
@@ -0,0 +1,28 @@
+using Perspex.Media;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Perspex.Controls.Shapes
+{
+ public class Polyline: Shape
+ {
+ public static readonly PerspexProperty> PointsProperty =
+ PerspexProperty.Register>("Points");
+
+ static Polyline()
+ {
+ StrokeThicknessProperty.OverrideDefaultValue(1);
+ }
+
+ public IList Points
+ {
+ get { return GetValue(PointsProperty); }
+ set { SetValue(PointsProperty, value); }
+ }
+
+ public override Geometry DefiningGeometry => new PolylineGeometry(Points, false);
+ }
+}
diff --git a/src/Perspex.Controls/Shapes/Shape.cs b/src/Perspex.Controls/Shapes/Shape.cs
index 3b11d816b9..bb8b99ae70 100644
--- a/src/Perspex.Controls/Shapes/Shape.cs
+++ b/src/Perspex.Controls/Shapes/Shape.cs
@@ -96,6 +96,8 @@ namespace Perspex.Controls.Shapes
public PenLineCap StrokeEndLineCap { get; set; } = PenLineCap.Flat;
+ public PenLineJoin StrokeJoin { get; set; } = PenLineJoin.Miter;
+
public override void Render(DrawingContext context)
{
var geometry = RenderedGeometry;
@@ -103,7 +105,7 @@ namespace Perspex.Controls.Shapes
if (geometry != null)
{
var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray),
- StrokeDashCap, StrokeStartLineCap, StrokeEndLineCap);
+ StrokeDashCap, StrokeStartLineCap, StrokeEndLineCap, StrokeJoin);
context.DrawGeometry(Fill, pen, geometry);
}
}
diff --git a/src/Perspex.SceneGraph/Media/LineGeometry.cs b/src/Perspex.SceneGraph/Media/LineGeometry.cs
index 196fdc40eb..d9f24422b1 100644
--- a/src/Perspex.SceneGraph/Media/LineGeometry.cs
+++ b/src/Perspex.SceneGraph/Media/LineGeometry.cs
@@ -27,8 +27,8 @@ namespace Perspex.Media
using (IStreamGeometryContextImpl context = impl.Open())
{
- context.BeginFigure(startPoint, false);
- context.LineTo(endPoint);
+ context.BeginFigure(_startPoint, false);
+ context.LineTo(_endPoint);
context.EndFigure(false);
}
@@ -36,12 +36,40 @@ namespace Perspex.Media
}
///
- public override Rect Bounds => new Rect(_startPoint, _endPoint);
+ public override Rect Bounds
+ {
+ get
+ {
+ double xMin, yMin, xMax, yMax;
+ if (_startPoint.X <= _endPoint.X)
+ {
+ xMin = _startPoint.X;
+ xMax = _endPoint.X;
+ }
+ else
+ {
+ xMin = _endPoint.X;
+ xMax = _startPoint.X;
+ }
+ if (_startPoint.Y <= _endPoint.Y)
+ {
+ yMin = _startPoint.Y;
+ yMax = _endPoint.Y;
+ }
+ else
+ {
+ yMin = _endPoint.Y;
+ yMax = _startPoint.Y;
+ }
+
+ return new Rect(xMin, yMin, xMax - xMin, yMax - yMin);
+ }
+ }
///
public override Geometry Clone()
{
- return new LineGeometry(Bounds.TopLeft, Bounds.BottomRight);
+ return new LineGeometry(_startPoint, _endPoint);
}
}
}
diff --git a/src/Perspex.SceneGraph/Media/PathMarkupParser.cs b/src/Perspex.SceneGraph/Media/PathMarkupParser.cs
index 17539c924b..4d08d4ecf6 100644
--- a/src/Perspex.SceneGraph/Media/PathMarkupParser.cs
+++ b/src/Perspex.SceneGraph/Media/PathMarkupParser.cs
@@ -147,6 +147,16 @@ namespace Perspex.Media
_context.CubicBezierTo(point1, point2, point);
break;
}
+
+ case Command.CubicBezierCurveRelative:
+ {
+ Point point1 = ReadRelativePoint(reader, point);
+ Point point2 = ReadRelativePoint(reader, point);
+ _context.CubicBezierTo(point, point1, point2);
+ point = point2;
+ break;
+ }
+
case Command.Arc:
{
//example: A10,10 0 0,0 10,20
@@ -216,8 +226,10 @@ namespace Perspex.Media
}
}
- private static double ReadDouble(TextReader reader)
+ private static double ReadDouble(StringReader reader)
{
+ ReadWhitespace(reader);
+
// TODO: Handle Infinity, NaN and scientific notation.
StringBuilder b = new StringBuilder();
bool readSign = false;
diff --git a/src/Perspex.SceneGraph/Media/PolylineGeometry.cs b/src/Perspex.SceneGraph/Media/PolylineGeometry.cs
new file mode 100644
index 0000000000..0b431d6ae2
--- /dev/null
+++ b/src/Perspex.SceneGraph/Media/PolylineGeometry.cs
@@ -0,0 +1,79 @@
+using Perspex.Platform;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Perspex.Media
+{
+ ///
+ /// Represents the geometry of an polyline or polygon.
+ ///
+ public class PolylineGeometry : Geometry
+ {
+ private IList _points;
+ private bool _isFilled;
+
+ public PolylineGeometry(IList points, bool isFilled)
+ {
+ _points = points;
+ _isFilled = isFilled;
+ IPlatformRenderInterface factory = PerspexLocator.Current.GetService();
+ IStreamGeometryImpl impl = factory.CreateStreamGeometry();
+
+ using (IStreamGeometryContextImpl context = impl.Open())
+ {
+ if (points.Count > 0)
+ {
+ context.BeginFigure(points[0], isFilled);
+ for (int i = 1; i < points.Count; i++)
+ {
+ context.LineTo(points[i]);
+ }
+ context.EndFigure(isFilled);
+ }
+ }
+
+ PlatformImpl = impl;
+ }
+
+ ///
+ public override Rect Bounds
+ {
+ get
+ {
+ double xMin = double.MaxValue, yMin = double.MaxValue;
+ double xMax = double.MinValue, yMax = double.MinValue;
+ foreach (var point in _points)
+ {
+ if (point.X < xMin)
+ {
+ xMin = point.X;
+ }
+ else if (point.X > xMax)
+ {
+ xMax = point.X;
+ }
+
+ if (point.Y < yMin)
+ {
+ yMin = point.Y;
+ }
+ else if (point.Y > yMax)
+ {
+ yMax = point.Y;
+ }
+ }
+
+ return new Rect(xMin, yMin, xMax - xMin, yMax - yMin);
+ }
+ }
+
+ ///
+ public override Geometry Clone()
+ {
+ return new PolylineGeometry(new List(_points), _isFilled);
+ }
+ }
+}
diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
index 05f22c4ccf..6578b9c7b5 100644
--- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
+++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
@@ -72,11 +72,12 @@
+
+
-
diff --git a/src/Skia/Perspex.Skia/DrawingContextImpl.cs b/src/Skia/Perspex.Skia/DrawingContextImpl.cs
index 405f021515..8e7d481433 100644
--- a/src/Skia/Perspex.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Perspex.Skia/DrawingContextImpl.cs
@@ -37,18 +37,13 @@ namespace Perspex.Skia
public void DrawGeometry(Brush brush, Pen pen, Geometry geometry)
{
var impl = ((StreamGeometryImpl) geometry.PlatformImpl);
- var oldTransform = Transform;
- if (!impl.Transform.IsIdentity)
- Transform = impl.Transform*Transform;
-
var size = geometry.Bounds.Size;
using(var fill = brush!=null?CreateBrush(brush, size):null)
using (var stroke = pen?.Brush != null ? CreateBrush(pen, size) : null)
{
- MethodTable.Instance.DrawGeometry(Handle, impl.Path.Handle, fill != null ? fill.Brush : null,
+ MethodTable.Instance.DrawGeometry(Handle, impl.EffectivePath, fill != null ? fill.Brush : null,
stroke != null ? stroke.Brush : null, impl.FillRule == FillRule.EvenOdd);
}
- Transform = oldTransform;
}
unsafe NativeBrushContainer CreateBrush(Brush brush, Size targetSize)
@@ -56,7 +51,6 @@ namespace Perspex.Skia
var rv = NativeBrushPool.Instance.Get();
rv.Brush->Opacity = brush.Opacity;
-
var solid = brush as SolidColorBrush;
if (solid != null)
{
@@ -108,8 +102,6 @@ namespace Perspex.Skia
rv.Brush->Bitmap = bitmap.Handle;
rv.Brush->BitmapTileMode = tileBrush.TileMode;
rv.Brush->BitmapTranslation = new SkiaPoint(-helper.DestinationRect.X, -helper.DestinationRect.Y);
-
-
}
return rv;
@@ -121,6 +113,7 @@ namespace Perspex.Skia
brush.Brush->Stroke = true;
brush.Brush->StrokeThickness = (float)pen.Thickness;
brush.Brush->StrokeLineCap = pen.StartLineCap;
+ brush.Brush->StrokeLineJoin = pen.LineJoin;
brush.Brush->StrokeMiterLimit = (float)pen.MiterLimit;
if (pen.DashStyle?.Dashes != null)
@@ -196,14 +189,7 @@ namespace Perspex.Skia
if(_currentTransform == value)
return;
_currentTransform = value;
- _fmatrix[0] = (float)value.M11;
- _fmatrix[1] = (float)value.M21;
- _fmatrix[2] = (float)value.M31;
-
- _fmatrix[3] = (float)value.M12;
- _fmatrix[4] = (float)value.M22;
- _fmatrix[5] = (float)value.M32;
- MethodTable.Instance.SetTransform(Handle, _fmatrix);
+ MethodTable.Instance.SetTransform(Handle, value);
}
}
}
diff --git a/src/Skia/Perspex.Skia/MethodTable.cs b/src/Skia/Perspex.Skia/MethodTable.cs
index ba21588333..d17ead8720 100644
--- a/src/Skia/Perspex.Skia/MethodTable.cs
+++ b/src/Skia/Perspex.Skia/MethodTable.cs
@@ -47,9 +47,9 @@ namespace Perspex.Skia
public _PopClip PopClip;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- public delegate void _SetTransform(IntPtr ctx, float[] matrix6);
+ public delegate void _SetTransform(IntPtr ctx, void* matrix6);
- public _SetTransform SetTransform;
+ public _SetTransform SetTransformNative;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _DrawLine(IntPtr ctx, void* brush, float x1, float y1, float x2, float y2);
@@ -66,6 +66,11 @@ namespace Perspex.Skia
public _DisposePath DisposePath;
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate IntPtr _TransformPath(IntPtr path, void* matrix6);
+
+ public _TransformPath TransformPathNative;
+
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _DrawGeometry(IntPtr ctx, IntPtr path, void* fill, void* stroke, bool useEvenOdd);
@@ -185,10 +190,34 @@ namespace Perspex.Skia
typeof (_RebuildFormattedText),
typeof (_DestroyFormattedText),
typeof (_DrawFormattedText),
- typeof (_SetOption)
+ typeof (_SetOption),
+ typeof (_TransformPath)
};
+ void ConvertMatrix(Matrix value, float* target)
+ {
+ target[0] = (float)value.M11;
+ target[1] = (float)value.M21;
+ target[2] = (float)value.M31;
+
+ target[3] = (float)value.M12;
+ target[4] = (float)value.M22;
+ target[5] = (float)value.M32;
+ }
+ public unsafe IntPtr TransformPath(IntPtr path, Matrix matrix)
+ {
+ var tmp = stackalloc float[6];
+ ConvertMatrix(matrix, tmp);
+ return TransformPathNative(path, tmp);
+ }
+
+ public unsafe void SetTransform(IntPtr ctx, Matrix matrix)
+ {
+ var tmp = stackalloc float[6];
+ ConvertMatrix(matrix, tmp);
+ SetTransformNative(ctx, tmp);
+ }
protected MethodTable(IntPtr methodTable)
{
diff --git a/src/Skia/Perspex.Skia/PerspexHandleHolder.cs b/src/Skia/Perspex.Skia/PerspexHandleHolder.cs
index d8f52e8f61..158a2f768c 100644
--- a/src/Skia/Perspex.Skia/PerspexHandleHolder.cs
+++ b/src/Skia/Perspex.Skia/PerspexHandleHolder.cs
@@ -44,4 +44,63 @@ namespace Perspex.Skia
Dispose();
}
}
+
+ class RefCountable : IDisposable where T : PerspexHandleHolder
+ {
+ class Shared
+ {
+ public readonly T Target;
+ private int _refCount = 1;
+
+ public Shared(T target)
+ {
+ Target = target;
+ }
+
+ public void AddRef() => _refCount++;
+ public void Release()
+ {
+ _refCount--;
+ if (_refCount <= 0)
+ Target.Dispose();
+ }
+ }
+
+ public bool IsDisposed => _shared == null;
+ private Shared _shared;
+ public void CheckDisposed()
+ {
+ if (IsDisposed)
+ throw new ObjectDisposedException(GetType().FullName);
+ }
+
+ public IntPtr Handle
+ {
+ get
+ {
+ CheckDisposed();
+ return _shared.Target.Handle;
+ }
+ }
+
+ public RefCountable(T handle)
+ {
+ _shared = new Shared(handle);
+ }
+
+ public RefCountable(RefCountable other)
+ {
+ other._shared.Target.CheckDisposed();
+ other._shared.AddRef();
+ _shared = other._shared;
+ }
+
+ public RefCountable Clone() => new RefCountable(this);
+
+ public void Dispose()
+ {
+ _shared?.Release();
+ _shared = null;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Skia/Perspex.Skia/StreamGeometryImpl.cs b/src/Skia/Perspex.Skia/StreamGeometryImpl.cs
index 3c62e65e7d..292af3d651 100644
--- a/src/Skia/Perspex.Skia/StreamGeometryImpl.cs
+++ b/src/Skia/Perspex.Skia/StreamGeometryImpl.cs
@@ -41,7 +41,11 @@ namespace Perspex.Skia
class StreamGeometryImpl : IStreamGeometryImpl
{
- public SkPath Path;
+ RefCountable _path;
+ RefCountable _transformedPath;
+ private Matrix _transform = Matrix.Identity;
+
+ public IntPtr EffectivePath => (_transformedPath ?? _path).Handle;
public Rect GetRenderBounds(double strokeThickness)
{
@@ -51,11 +55,35 @@ namespace Perspex.Skia
public Rect Bounds { get; private set; }
- public Matrix Transform { get; set; } = Matrix.Identity;
+ public Matrix Transform
+ {
+ get { return _transform; }
+ set
+ {
+ if(_transform == value)
+ return;
+ _transform = value;
+ ApplyTransform();
+ }
+ }
+
+ void ApplyTransform()
+ {
+ if(_path == null)
+ return;
+ if (_transformedPath != null)
+ {
+ _transformedPath.Dispose();
+ _transformedPath = null;
+ }
+ if (!_transform.IsIdentity)
+ _transformedPath =
+ new RefCountable(new SkPath(MethodTable.Instance.TransformPath(_path.Handle, Transform)));
+ }
public IStreamGeometryImpl Clone()
{
- return new StreamGeometryImpl() {Path = Path, Transform = Transform, Bounds = Bounds};
+ return new StreamGeometryImpl {_path = _path?.Clone(), _transformedPath = _transformedPath?.Clone(), _transform = Transform, Bounds = Bounds};
}
public IStreamGeometryContextImpl Open()
@@ -77,7 +105,11 @@ namespace Perspex.Skia
{
var arr = _elements.ToArray();
SkRect rc;
- _geometryImpl.Path = new SkPath(MethodTable.Instance.CreatePath(arr, arr.Length, out rc));
+ _geometryImpl._path?.Dispose();
+ _geometryImpl._path =
+ new RefCountable(new SkPath(MethodTable.Instance.CreatePath(arr, arr.Length, out rc)));
+ _geometryImpl.ApplyTransform();
+
_geometryImpl.Bounds = rc.ToRect();
}
diff --git a/src/Skia/getnatives.sh b/src/Skia/getnatives.sh
index 7ceace9db3..7486b35f80 100755
--- a/src/Skia/getnatives.sh
+++ b/src/Skia/getnatives.sh
@@ -1,7 +1,13 @@
#!/bin/sh
-rm -rf native
+rm -rf native native.zip
mkdir -p native
cd native
+if which curl
+then
+curl `cat ../native.url` -o native.zip
+else
wget `cat ../native.url` -O native.zip
-unzip native.zip
+fi
+unzip native.zip
+chmod -R +x .
diff --git a/tests/Perspex.RenderTests/Perspex.Cairo.RenderTests.csproj b/tests/Perspex.RenderTests/Perspex.Cairo.RenderTests.csproj
index dd6953d355..18d7aeffcf 100644
--- a/tests/Perspex.RenderTests/Perspex.Cairo.RenderTests.csproj
+++ b/tests/Perspex.RenderTests/Perspex.Cairo.RenderTests.csproj
@@ -74,6 +74,8 @@
+
+
diff --git a/tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj b/tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj
index 2abf92fdb5..a922029365 100644
--- a/tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj
+++ b/tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj
@@ -79,6 +79,8 @@
+
+
diff --git a/tests/Perspex.RenderTests/Perspex.Skia.RenderTests.csproj b/tests/Perspex.RenderTests/Perspex.Skia.RenderTests.csproj
index 08bba41fce..e82de9eff2 100644
--- a/tests/Perspex.RenderTests/Perspex.Skia.RenderTests.csproj
+++ b/tests/Perspex.RenderTests/Perspex.Skia.RenderTests.csproj
@@ -70,6 +70,8 @@
+
+
diff --git a/tests/Perspex.RenderTests/Shapes/LineTests.cs b/tests/Perspex.RenderTests/Shapes/LineTests.cs
index 80eb7c75ca..6cd40db424 100644
--- a/tests/Perspex.RenderTests/Shapes/LineTests.cs
+++ b/tests/Perspex.RenderTests/Shapes/LineTests.cs
@@ -26,13 +26,54 @@ namespace Perspex.Direct2D1.RenderTests.Shapes
{
Decorator target = new Decorator
{
- Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Line
{
Stroke = Brushes.Black,
StrokeThickness = 1,
+ StartPoint = new Point(0, 0),
+ EndPoint = new Point(200, 200)
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+ [Fact]
+ public void Line_1px_Stroke_Reversed()
+ {
+ Decorator target = new Decorator
+ {
+ Width = 200,
+ Height = 200,
+ Child = new Line
+ {
+ Stroke = Brushes.Black,
+ StrokeThickness = 1,
+ StartPoint = new Point(200, 0),
+ EndPoint = new Point(0, 200)
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+ [Fact]
+ public void Line_1px_Stroke_Vertical()
+ {
+ Decorator target = new Decorator
+ {
+ Width = 200,
+ Height = 200,
+ Child = new Line
+ {
+ Stroke = Brushes.Black,
+ StrokeThickness = 1,
+ StartPoint = new Point(100, 200),
+ EndPoint = new Point(100, 0)
}
};
diff --git a/tests/Perspex.RenderTests/Shapes/PathTests.cs b/tests/Perspex.RenderTests/Shapes/PathTests.cs
index 68b5956234..3d52b87ac2 100644
--- a/tests/Perspex.RenderTests/Shapes/PathTests.cs
+++ b/tests/Perspex.RenderTests/Shapes/PathTests.cs
@@ -46,11 +46,7 @@ namespace Perspex.Direct2D1.RenderTests.Shapes
CompareImages();
}
-#if PERSPEX_SKIA
- [Fact(Skip = "FIXME")]
-#else
[Fact]
-#endif
public void Path_Tick_Scaled()
{
Decorator target = new Decorator
@@ -73,11 +69,7 @@ namespace Perspex.Direct2D1.RenderTests.Shapes
CompareImages();
}
-#if PERSPEX_SKIA
- [Fact(Skip = "FIXME")]
-#else
[Fact]
-#endif
public void Path_Tick_Scaled_Stroke_8px()
{
Decorator target = new Decorator
@@ -100,11 +92,7 @@ namespace Perspex.Direct2D1.RenderTests.Shapes
CompareImages();
}
-#if PERSPEX_SKIA
- [Fact(Skip = "FIXME")]
-#else
[Fact]
-#endif
public void Path_Expander_With_Border()
{
Decorator target = new Decorator
diff --git a/tests/Perspex.RenderTests/Shapes/PolygonTests.cs b/tests/Perspex.RenderTests/Shapes/PolygonTests.cs
new file mode 100644
index 0000000000..94f6bf584a
--- /dev/null
+++ b/tests/Perspex.RenderTests/Shapes/PolygonTests.cs
@@ -0,0 +1,76 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Perspex.Controls;
+using Perspex.Controls.Shapes;
+using Perspex.Media;
+using Xunit;
+
+#if PERSPEX_CAIRO
+namespace Perspex.Cairo.RenderTests.Shapes
+#elif PERSPEX_SKIA
+namespace Perspex.Skia.RenderTests
+#else
+namespace Perspex.Direct2D1.RenderTests.Shapes
+#endif
+{
+ public class PolygonTests : TestBase
+ {
+ public PolygonTests()
+ : base(@"Shapes\Polygon")
+ {
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "Caused by cairo bug")]
+#else
+ [Fact]
+#endif
+ public void Polygon_1px_Stroke()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Polygon
+ {
+ Stroke = Brushes.DarkBlue,
+ Stretch = Stretch.Uniform,
+ Fill = Brushes.Violet,
+ Points = new [] { new Point(5, 0), new Point(8, 8), new Point(0, 3), new Point(10, 3), new Point(2, 8) },
+ StrokeThickness = 1
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "Caused by cairo bug")]
+#else
+ [Fact]
+#endif
+ public void Polygon_NonUniformFill()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 400,
+ Height = 200,
+ Child = new Polygon
+ {
+ Stroke = Brushes.DarkBlue,
+ Stretch = Stretch.Fill,
+ Fill = Brushes.Violet,
+ Points = new[] { new Point(5, 0), new Point(8, 8), new Point(0, 3), new Point(10, 3), new Point(2, 8) },
+ StrokeThickness = 5,
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+ }
+}
diff --git a/tests/Perspex.RenderTests/Shapes/PolylineTests.cs b/tests/Perspex.RenderTests/Shapes/PolylineTests.cs
new file mode 100644
index 0000000000..ca91be8e4b
--- /dev/null
+++ b/tests/Perspex.RenderTests/Shapes/PolylineTests.cs
@@ -0,0 +1,83 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Perspex.Controls;
+using Perspex.Controls.Shapes;
+using Perspex.Media;
+using Xunit;
+
+#if PERSPEX_CAIRO
+namespace Perspex.Cairo.RenderTests.Shapes
+#elif PERSPEX_SKIA
+namespace Perspex.Skia.RenderTests
+#else
+namespace Perspex.Direct2D1.RenderTests.Shapes
+#endif
+{
+ public class PolylineTests : TestBase
+ {
+ public PolylineTests()
+ : base(@"Shapes\Polyline")
+ {
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "Caused by cairo bug")]
+#else
+ [Fact]
+#endif
+ public void Polyline_1px_Stroke()
+ {
+ var polylinePoints = new Point[] { new Point(0, 0), new Point(5, 0), new Point(6, -2), new Point(7, 3), new Point(8, -3),
+ new Point(9, 1), new Point(10, 0), new Point(15, 0) };
+
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 400,
+ Height = 200,
+ Child = new Polyline
+ {
+ Stroke = Brushes.Brown,
+ Points = polylinePoints,
+ Stretch = Stretch.Uniform,
+ StrokeThickness = 1
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "Caused by cairo bug")]
+#else
+ [Fact]
+#endif
+ public void Polyline_10px_Stroke_PenLineJoin()
+ {
+ var polylinePoints = new Point[] { new Point(0, 0), new Point(5, 0), new Point(6, -2), new Point(7, 3), new Point(8, -3),
+ new Point(9, 1), new Point(10, 0), new Point(15, 0) };
+
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 400,
+ Height = 200,
+ Child = new Polyline
+ {
+ Stroke = Brushes.Brown,
+ Points = polylinePoints,
+ Stretch = Stretch.Uniform,
+ StrokeJoin = PenLineJoin.Round,
+ StrokeStartLineCap = PenLineCap.Round,
+ StrokeEndLineCap = PenLineCap.Round,
+ StrokeThickness = 10
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+ }
+}
diff --git a/tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke.expected.png b/tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke.expected.png
index 18ade2da0f..f0a6a9b109 100644
Binary files a/tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke.expected.png and b/tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke.expected.png differ
diff --git a/tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke_Reversed.expected.png b/tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke_Reversed.expected.png
new file mode 100644
index 0000000000..9ed7e713a9
Binary files /dev/null and b/tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke_Reversed.expected.png differ
diff --git a/tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke_Vertical.expected.png b/tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke_Vertical.expected.png
new file mode 100644
index 0000000000..845961351c
Binary files /dev/null and b/tests/TestFiles/Cairo/Shapes/Line/Line_1px_Stroke_Vertical.expected.png differ
diff --git a/tests/TestFiles/Cairo/Shapes/Polygon/Polygon_1px_Stroke.expected.png b/tests/TestFiles/Cairo/Shapes/Polygon/Polygon_1px_Stroke.expected.png
new file mode 100644
index 0000000000..f62be2d4bf
Binary files /dev/null and b/tests/TestFiles/Cairo/Shapes/Polygon/Polygon_1px_Stroke.expected.png differ
diff --git a/tests/TestFiles/Cairo/Shapes/Polygon/Polygon_NonUniformFill.expected.png b/tests/TestFiles/Cairo/Shapes/Polygon/Polygon_NonUniformFill.expected.png
new file mode 100644
index 0000000000..0888c74c30
Binary files /dev/null and b/tests/TestFiles/Cairo/Shapes/Polygon/Polygon_NonUniformFill.expected.png differ
diff --git a/tests/TestFiles/Cairo/Shapes/Polyline/Polyline_10px_Stroke_PenLineJoin.expected.png b/tests/TestFiles/Cairo/Shapes/Polyline/Polyline_10px_Stroke_PenLineJoin.expected.png
new file mode 100644
index 0000000000..d908a572cb
Binary files /dev/null and b/tests/TestFiles/Cairo/Shapes/Polyline/Polyline_10px_Stroke_PenLineJoin.expected.png differ
diff --git a/tests/TestFiles/Cairo/Shapes/Polyline/Polyline_1px_Stroke.expected.png b/tests/TestFiles/Cairo/Shapes/Polyline/Polyline_1px_Stroke.expected.png
new file mode 100644
index 0000000000..7cbe87fa6c
Binary files /dev/null and b/tests/TestFiles/Cairo/Shapes/Polyline/Polyline_1px_Stroke.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke.expected.png b/tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke.expected.png
index 18ade2da0f..f0a6a9b109 100644
Binary files a/tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke.expected.png and b/tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke_Reversed.expected.png b/tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke_Reversed.expected.png
new file mode 100644
index 0000000000..9ed7e713a9
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke_Reversed.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke_Vertical.expected.png b/tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke_Vertical.expected.png
new file mode 100644
index 0000000000..845961351c
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Shapes/Line/Line_1px_Stroke_Vertical.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Shapes/Polygon/Polygon_1px_Stroke.expected.png b/tests/TestFiles/Direct2D1/Shapes/Polygon/Polygon_1px_Stroke.expected.png
new file mode 100644
index 0000000000..f62be2d4bf
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Shapes/Polygon/Polygon_1px_Stroke.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Shapes/Polygon/Polygon_NonUniformFill.expected.png b/tests/TestFiles/Direct2D1/Shapes/Polygon/Polygon_NonUniformFill.expected.png
new file mode 100644
index 0000000000..0888c74c30
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Shapes/Polygon/Polygon_NonUniformFill.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Shapes/Polyline/Polyline_10px_Stroke_PenLineJoin.expected.png b/tests/TestFiles/Direct2D1/Shapes/Polyline/Polyline_10px_Stroke_PenLineJoin.expected.png
new file mode 100644
index 0000000000..d908a572cb
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Shapes/Polyline/Polyline_10px_Stroke_PenLineJoin.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Shapes/Polyline/Polyline_1px_Stroke.expected.png b/tests/TestFiles/Direct2D1/Shapes/Polyline/Polyline_1px_Stroke.expected.png
new file mode 100644
index 0000000000..544d9e2320
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Shapes/Polyline/Polyline_1px_Stroke.expected.png differ
diff --git a/tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke.expected.png b/tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke.expected.png
index 18ade2da0f..f0a6a9b109 100644
Binary files a/tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke.expected.png and b/tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke.expected.png differ
diff --git a/tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke_Reversed.expected.png b/tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke_Reversed.expected.png
new file mode 100644
index 0000000000..9ed7e713a9
Binary files /dev/null and b/tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke_Reversed.expected.png differ
diff --git a/tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke_Vertical.expected.png b/tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke_Vertical.expected.png
new file mode 100644
index 0000000000..845961351c
Binary files /dev/null and b/tests/TestFiles/Skia/Shapes/Line/Line_1px_Stroke_Vertical.expected.png differ
diff --git a/tests/TestFiles/Skia/Shapes/Polygon/Polygon_1px_Stroke.expected.png b/tests/TestFiles/Skia/Shapes/Polygon/Polygon_1px_Stroke.expected.png
new file mode 100644
index 0000000000..f62be2d4bf
Binary files /dev/null and b/tests/TestFiles/Skia/Shapes/Polygon/Polygon_1px_Stroke.expected.png differ
diff --git a/tests/TestFiles/Skia/Shapes/Polygon/Polygon_NonUniformFill.expected.png b/tests/TestFiles/Skia/Shapes/Polygon/Polygon_NonUniformFill.expected.png
new file mode 100644
index 0000000000..0888c74c30
Binary files /dev/null and b/tests/TestFiles/Skia/Shapes/Polygon/Polygon_NonUniformFill.expected.png differ
diff --git a/tests/TestFiles/Skia/Shapes/Polyline/Polyline_10px_Stroke_PenLineJoin.expected.png b/tests/TestFiles/Skia/Shapes/Polyline/Polyline_10px_Stroke_PenLineJoin.expected.png
new file mode 100644
index 0000000000..d908a572cb
Binary files /dev/null and b/tests/TestFiles/Skia/Shapes/Polyline/Polyline_10px_Stroke_PenLineJoin.expected.png differ
diff --git a/tests/TestFiles/Skia/Shapes/Polyline/Polyline_1px_Stroke.expected.png b/tests/TestFiles/Skia/Shapes/Polyline/Polyline_1px_Stroke.expected.png
new file mode 100644
index 0000000000..544d9e2320
Binary files /dev/null and b/tests/TestFiles/Skia/Shapes/Polyline/Polyline_1px_Stroke.expected.png differ