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);
+ }
+ }
+}