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