diff --git a/Perspex.sln b/Perspex.sln index 9ddce7d51e..e3fa298f74 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -104,12 +104,17 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BindingTest", "samples\Bind EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamlTestApplicationPcl", "samples\XamlTestApplicationPcl\XamlTestApplicationPcl.csproj", "{EA113F1A-D8D7-4142-9948-353270E7EBAE}" EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "RenderHelpers", "src\Shared\RenderHelpers\RenderHelpers.shproj", "{3C4C0CB4-0C0F-4450-A37B-148C84FF905F}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution + src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{db070a10-bf39-4752-8456-86e9d5928478}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{54f237d5-a70a-4752-9656-0c70b1a7b047}*SharedItemsImports = 4 + src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4 + src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -289,5 +294,6 @@ Global {8EF392D5-1416-45AA-9956-7CBBC3229E8A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {08B3E6B9-1CD5-443C-9F61-6D49D1C5F162} = {9B9E3891-2366-4253-A952-D08BCEB71098} {EA113F1A-D8D7-4142-9948-353270E7EBAE} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {3C4C0CB4-0C0F-4450-A37B-148C84FF905F} = {A689DEF5-D50F-4975-8B72-124C9EB54066} EndGlobalSection EndGlobal diff --git a/appveyor.yml b/appveyor.yml index 25a2ba5b7b..1b10d99b4a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,6 +8,14 @@ environment: myget_key: secure: XOgD5bJUKNOS2kDDgb+affS4pDcslxALh+xvvnr1Koy0PjXlhILsBdNhxRe0KcNm +install: + - if not exist gtk-sharp-2.12.26.msi appveyor DownloadFile http://download.xamarin.com/GTKforWindows/Windows/gtk-sharp-2.12.26.msi + - msiexec /i gtk-sharp-2.12.26.msi /qn /norestart + - cmd: set PATH=%programfiles(x86)%\GtkSharp\2.12\bin\;%PATH% + +cache: + - gtk-sharp-2.12.26.msi + configuration: - Release diff --git a/samples/XamlTestApplicationPcl/Views/MainWindow.paml b/samples/XamlTestApplicationPcl/Views/MainWindow.paml index c3e2b458ec..64723719fc 100644 --- a/samples/XamlTestApplicationPcl/Views/MainWindow.paml +++ b/samples/XamlTestApplicationPcl/Views/MainWindow.paml @@ -245,6 +245,28 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Gtk/Perspex.Cairo/Media/ImageBrushImpl.cs b/src/Gtk/Perspex.Cairo/Media/ImageBrushImpl.cs index d6c781619c..0a7dedc1a7 100644 --- a/src/Gtk/Perspex.Cairo/Media/ImageBrushImpl.cs +++ b/src/Gtk/Perspex.Cairo/Media/ImageBrushImpl.cs @@ -7,7 +7,7 @@ namespace Perspex.Cairo.Media { public ImageBrushImpl(Perspex.Media.ImageBrush brush, Size destinationSize) { - this.PlatformBrush = TileBrushes.CreateImageBrush(brush, destinationSize); + this.PlatformBrush = TileBrushes.CreateTileBrush(brush, destinationSize); } } } diff --git a/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs b/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs index a49ac7ef90..5e3e279deb 100644 --- a/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs +++ b/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Perspex.Media; using Perspex.Platform; +using Perspex.RenderHelpers; namespace Perspex.Cairo.Media { @@ -12,6 +13,7 @@ namespace Perspex.Cairo.Media public class StreamGeometryContextImpl : IStreamGeometryContextImpl { + private Point _currentPoint; public StreamGeometryContextImpl(Cairo.Path path = null) { @@ -27,24 +29,44 @@ namespace Perspex.Cairo.Media public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) { + ArcToHelper.ArcTo(this, _currentPoint, point, size, rotationAngle, isLargeArc, sweepDirection); + _currentPoint = point; } public void BeginFigure(Point startPoint, bool isFilled) { - if (this.Path == null) - _context.MoveTo(startPoint.ToCairo()); + if (this.Path == null) + { + _context.MoveTo(startPoint.ToCairo()); + _currentPoint = startPoint; + } } public void BezierTo(Point point1, Point point2, Point point3) { - if (this.Path == null) - _context.CurveTo(point1.ToCairo(), point2.ToCairo(), point3.ToCairo()); + if (this.Path == null) + { + _context.CurveTo(point1.ToCairo(), point2.ToCairo(), point3.ToCairo()); + _currentPoint = point3; + } + } + + public void QuadTo(Point control, Point endPoint) + { + if (this.Path == null) + { + QuadBezierHelper.QuadTo(this, _currentPoint, control, endPoint); + _currentPoint = endPoint; + } } public void LineTo(Point point) { - if (this.Path == null) - _context.LineTo(point.ToCairo()); + if (this.Path == null) + { + _context.LineTo(point.ToCairo()); + _currentPoint = point; + } } private readonly Cairo.Context _context; diff --git a/src/Gtk/Perspex.Cairo/Media/TileBrushes.cs b/src/Gtk/Perspex.Cairo/Media/TileBrushes.cs index 98361427f0..91262ce714 100644 --- a/src/Gtk/Perspex.Cairo/Media/TileBrushes.cs +++ b/src/Gtk/Perspex.Cairo/Media/TileBrushes.cs @@ -8,115 +8,22 @@ using Perspex.Layout; using Perspex.Media; using Perspex.Platform; using Perspex.Rendering; +using Perspex.RenderHelpers; namespace Perspex.Cairo.Media { internal static class TileBrushes { - public static SurfacePattern CreateImageBrush(ImageBrush brush, Size targetSize) + public static SurfacePattern CreateTileBrush(TileBrush brush, Size targetSize) { - if (brush.Source == null) - { + var helper = new TileBrushImplHelper(brush, targetSize); + if (!helper.IsValid) return null; - } - - // TODO: This is directly ported from Direct2D and could probably be made more - // efficient on cairo by taking advantage of the fact that cairo has Extend.None. - var image = ((BitmapImpl)brush.Source.PlatformImpl).Surface; - var imageSize = new Size(brush.Source.PixelWidth, brush.Source.PixelHeight); - var tileMode = brush.TileMode; - var sourceRect = brush.SourceRect.ToPixels(imageSize); - var destinationRect = brush.DestinationRect.ToPixels(targetSize); - var scale = brush.Stretch.CalculateScaling(destinationRect.Size, sourceRect.Size); - var translate = CalculateTranslate(brush, sourceRect, destinationRect, scale); - var intermediateSize = CalculateIntermediateSize(tileMode, targetSize, destinationRect.Size); - - var intermediate = new ImageSurface (Format.ARGB32, (int)intermediateSize.Width, (int)intermediateSize.Height); - using (var context = new Context(intermediate)) - { - Rect drawRect; - var transform = CalculateIntermediateTransform( - tileMode, - sourceRect, - destinationRect, - scale, - translate, - out drawRect); - context.Rectangle(drawRect.ToCairo()); - context.Clip(); - context.Transform(transform.ToCairo()); - Gdk.CairoHelper.SetSourcePixbuf(context, image, 0, 0); - context.Rectangle(0, 0, imageSize.Width, imageSize.Height); - context.Fill(); - - var result = new SurfacePattern(intermediate); - - if ((brush.TileMode & TileMode.FlipXY) != 0) - { - // TODO: Currently always FlipXY as that's all cairo supports natively. - // Support separate FlipX and FlipY by drawing flipped images to intermediate - // surface. - result.Extend = Extend.Reflect; - } - else - { - result.Extend = Extend.Repeat; - } - - if (brush.TileMode != TileMode.None) - { - var matrix = result.Matrix; - matrix.InitTranslate(-destinationRect.X, -destinationRect.Y); - result.Matrix = matrix; - } - - return result; - } - } - - public static SurfacePattern CreateVisualBrush(VisualBrush brush, Size targetSize) - { - var visual = brush.Visual; - - if (visual == null) - { - return null; - } - - var layoutable = visual as ILayoutable; - - if (layoutable?.IsArrangeValid == false) - { - layoutable.Measure(Size.Infinity); - layoutable.Arrange(new Rect(layoutable.DesiredSize)); - } - - // TODO: This is directly ported from Direct2D and could probably be made more - // efficient on cairo by taking advantage of the fact that cairo has Extend.None. - var tileMode = brush.TileMode; - var sourceRect = brush.SourceRect.ToPixels(layoutable.Bounds.Size); - var destinationRect = brush.DestinationRect.ToPixels(targetSize); - var scale = brush.Stretch.CalculateScaling(destinationRect.Size, sourceRect.Size); - var translate = CalculateTranslate(brush, sourceRect, destinationRect, scale); - var intermediateSize = CalculateIntermediateSize(tileMode, targetSize, destinationRect.Size); - using (var intermediate = new ImageSurface(Format.ARGB32, (int)intermediateSize.Width, (int)intermediateSize.Height)) + using (var intermediate = new ImageSurface(Format.ARGB32, (int)helper.IntermediateSize.Width, (int)helper.IntermediateSize.Height)) using (var ctx = new RenderTarget(intermediate).CreateDrawingContext()) { - Rect drawRect; - var transform = CalculateIntermediateTransform( - tileMode, - sourceRect, - destinationRect, - scale, - translate, - out drawRect); - - using (ctx.PushClip(drawRect)) - using (ctx.PushPostTransform(transform)) - { - ctx.Render(visual); - } + helper.DrawIntermediate(ctx); var result = new SurfacePattern(intermediate); @@ -135,7 +42,7 @@ namespace Perspex.Cairo.Media if (brush.TileMode != TileMode.None) { var matrix = result.Matrix; - matrix.InitTranslate(-destinationRect.X, -destinationRect.Y); + matrix.InitTranslate(-helper.DestinationRect.X, -helper.DestinationRect.Y); result.Matrix = matrix; } @@ -143,83 +50,6 @@ namespace Perspex.Cairo.Media } } - /// - /// Calculates a translate based on a , a source and destination - /// rectangle and a scale. - /// - /// The brush. - /// The source rectangle. - /// The destination rectangle. - /// The scale factor. - /// A vector with the X and Y translate. - private static Vector CalculateTranslate( - TileBrush brush, - Rect sourceRect, - Rect destinationRect, - Vector scale) - { - var x = 0.0; - var y = 0.0; - var size = sourceRect.Size * scale; - - switch (brush.AlignmentX) - { - case AlignmentX.Center: - x += (destinationRect.Width - size.Width) / 2; - break; - case AlignmentX.Right: - x += destinationRect.Width - size.Width; - break; - } - - switch (brush.AlignmentY) - { - case AlignmentY.Center: - y += (destinationRect.Height - size.Height) / 2; - break; - case AlignmentY.Bottom: - y += destinationRect.Height - size.Height; - break; - } - - return new Vector(x, y); - } - - private static Size CalculateIntermediateSize( - TileMode tileMode, - Size targetSize, - Size destinationSize) - { - var result = tileMode == TileMode.None ? targetSize : destinationSize; - return result; - } - - private static Matrix CalculateIntermediateTransform( - TileMode tileMode, - Rect sourceRect, - Rect destinationRect, - Vector scale, - Vector translate, - out Rect drawRect) - { - var transform = Matrix.CreateTranslation(-sourceRect.Position) * - Matrix.CreateScale(scale) * - Matrix.CreateTranslation(translate); - Rect dr; - - if (tileMode == TileMode.None) - { - dr = destinationRect; - transform *= Matrix.CreateTranslation(destinationRect.Position); - } - else - { - dr = new Rect(destinationRect.Size); - } - - drawRect = dr; - - return transform; - } + } } diff --git a/src/Gtk/Perspex.Cairo/Media/VisualBrushImpl.cs b/src/Gtk/Perspex.Cairo/Media/VisualBrushImpl.cs index 634b2b38a5..39a3c9afde 100644 --- a/src/Gtk/Perspex.Cairo/Media/VisualBrushImpl.cs +++ b/src/Gtk/Perspex.Cairo/Media/VisualBrushImpl.cs @@ -7,7 +7,7 @@ namespace Perspex.Cairo.Media { public VisualBrushImpl(Perspex.Media.VisualBrush brush, Size destinationSize) { - this.PlatformBrush = TileBrushes.CreateVisualBrush(brush, destinationSize); + this.PlatformBrush = TileBrushes.CreateTileBrush(brush, destinationSize); } } } diff --git a/src/Gtk/Perspex.Cairo/Perspex.Cairo.csproj b/src/Gtk/Perspex.Cairo/Perspex.Cairo.csproj index 77deb8f6b3..a27683de49 100644 --- a/src/Gtk/Perspex.Cairo/Perspex.Cairo.csproj +++ b/src/Gtk/Perspex.Cairo/Perspex.Cairo.csproj @@ -93,6 +93,7 @@ + - \ No newline at end of file + diff --git a/tests/Perspex.SceneGraph.UnitTests/RelativeRectComparer.cs b/tests/Perspex.SceneGraph.UnitTests/RelativeRectComparer.cs new file mode 100644 index 0000000000..667e2b4cbb --- /dev/null +++ b/tests/Perspex.SceneGraph.UnitTests/RelativeRectComparer.cs @@ -0,0 +1,25 @@ +// 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 System; +using System.Collections.Generic; + +namespace Perspex.SceneGraph.UnitTests +{ + public class RelativeRectComparer : IEqualityComparer + { + public bool Equals(RelativeRect a, RelativeRect b) + { + return a.Unit == b.Unit && + Math.Round(a.Rect.X, 3) == Math.Round(b.Rect.X, 3) && + Math.Round(a.Rect.Y, 3) == Math.Round(b.Rect.Y, 3) && + Math.Round(a.Rect.Width, 3) == Math.Round(b.Rect.Width, 3) && + Math.Round(a.Rect.Height, 3) == Math.Round(b.Rect.Height, 3); + } + + public int GetHashCode(RelativeRect obj) + { + throw new NotImplementedException(); + } + } +} diff --git a/tests/Perspex.SceneGraph.UnitTests/RelativeRectTests.cs b/tests/Perspex.SceneGraph.UnitTests/RelativeRectTests.cs new file mode 100644 index 0000000000..625d73e95e --- /dev/null +++ b/tests/Perspex.SceneGraph.UnitTests/RelativeRectTests.cs @@ -0,0 +1,29 @@ +// 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 System.Globalization; +using Xunit; + +namespace Perspex.SceneGraph.UnitTests +{ + public class RelativeRectTests + { + private static readonly RelativeRectComparer Compare = new RelativeRectComparer(); + + [Fact] + public void Parse_Should_Accept_Absolute_Value() + { + var result = RelativeRect.Parse("4,5,50,60", CultureInfo.InvariantCulture); + + Assert.Equal(new RelativeRect(4, 5, 50, 60, RelativeUnit.Absolute), result, Compare); + } + + [Fact] + public void Parse_Should_Accept_Relative_Value() + { + var result = RelativeRect.Parse("10%, 20%, 40%, 70%", CultureInfo.InvariantCulture); + + Assert.Equal(new RelativeRect(0.1, 0.2, 0.4, 0.7, RelativeUnit.Relative), result, Compare); + } + } +}