diff --git a/Cairo/Perspex.Cairo/Media/StreamGeometryContextImpl.cs b/Cairo/Perspex.Cairo/Media/StreamGeometryContextImpl.cs index 864fe7d41f..12e15ad503 100644 --- a/Cairo/Perspex.Cairo/Media/StreamGeometryContextImpl.cs +++ b/Cairo/Perspex.Cairo/Media/StreamGeometryContextImpl.cs @@ -20,6 +20,11 @@ namespace Perspex.Cairo.Media // TODO: Implement } + public void BezierTo(Point point1, Point point2, Point point3) + { + // TODO: Implement + } + public void LineTo(Point point) { // TODO: Implement diff --git a/Cairo/Perspex.Cairo/Media/StreamGeometryImpl.cs b/Cairo/Perspex.Cairo/Media/StreamGeometryImpl.cs index 89aef0d408..70150a17d9 100644 --- a/Cairo/Perspex.Cairo/Media/StreamGeometryImpl.cs +++ b/Cairo/Perspex.Cairo/Media/StreamGeometryImpl.cs @@ -23,6 +23,19 @@ namespace Perspex.Cairo.Media get { return new Rect(); } } + // TODO: Implement + public Matrix Transform + { + get { return Matrix.Identity; } + set { } + } + + public IStreamGeometryImpl Clone() + { + // TODO: Implement + return new StreamGeometryImpl(); + } + public Rect GetRenderBounds(double strokeThickness) { // TODO: Implement diff --git a/Perspex.Controls/Shapes/Shape.cs b/Perspex.Controls/Shapes/Shape.cs index 09c37facc0..271e575682 100644 --- a/Perspex.Controls/Shapes/Shape.cs +++ b/Perspex.Controls/Shapes/Shape.cs @@ -24,6 +24,8 @@ namespace Perspex.Controls.Shapes public static readonly PerspexProperty StrokeThicknessProperty = PerspexProperty.Register("StrokeThickness"); + private Matrix transform = Matrix.Identity; + public abstract Geometry DefiningGeometry { get; @@ -37,7 +39,18 @@ namespace Perspex.Controls.Shapes public Geometry RenderedGeometry { - get { return this.DefiningGeometry; } + get + { + var result = this.DefiningGeometry; + + if (result != null) + { + result = result.Clone(); + result.Transform = new MatrixTransform(this.transform); + } + + return result; + } } public Stretch Stretch @@ -60,15 +73,19 @@ namespace Perspex.Controls.Shapes public override void Render(IDrawingContext context) { - if (this.RenderedGeometry != null) + var geometry = this.RenderedGeometry; + + if (geometry != null) { - context.DrawGeometry(this.Fill, new Pen(this.Stroke, this.StrokeThickness), this.RenderedGeometry); + context.DrawGeometry(this.Fill, new Pen(this.Stroke, this.StrokeThickness), geometry); } } protected override Size MeasureOverride(Size availableSize) { - Rect shapeBounds = this.RenderedGeometry.GetRenderBounds(this.StrokeThickness); + Rect shapeBounds = this.DefiningGeometry.GetRenderBounds(this.StrokeThickness); + Size shapeSize = new Size(shapeBounds.Right, shapeBounds.Bottom); + Matrix translate = Matrix.Identity; double width = this.Width; double height = this.Height; double desiredX = availableSize.Width; @@ -76,24 +93,30 @@ namespace Perspex.Controls.Shapes double sx = 0.0; double sy = 0.0; + if (this.Stretch != Stretch.None) + { + shapeSize = shapeBounds.Size; + translate = Matrix.Translation(-(Vector)shapeBounds.Position); + } + if (double.IsInfinity(availableSize.Width)) { - desiredX = shapeBounds.Right; + desiredX = shapeSize.Width; } if (double.IsInfinity(availableSize.Height)) { - desiredY = shapeBounds.Bottom; + desiredY = shapeSize.Height; } if (shapeBounds.Width > 0) { - sx = desiredX / shapeBounds.Right; + sx = desiredX / shapeBounds.Width; } if (shapeBounds.Height > 0) { - sy = desiredY / shapeBounds.Bottom; + sy = desiredY / shapeBounds.Height; } if (double.IsInfinity(availableSize.Width)) @@ -131,8 +154,10 @@ namespace Perspex.Controls.Shapes break; } - double finalX = (width > 0) ? width : shapeBounds.Right * sx; - double finalY = (height > 0) ? height : shapeBounds.Bottom * sy; + this.transform = translate * Matrix.Scaling(sx, sy); + + double finalX = (width > 0) ? width : shapeSize.Width * sx; + double finalY = (height > 0) ? height : shapeSize.Width * sy; return new Size(finalX, finalY); } } diff --git a/Perspex.SceneGraph/Matrix.cs b/Perspex.SceneGraph/Matrix.cs index a262081e02..6d055918fa 100644 --- a/Perspex.SceneGraph/Matrix.cs +++ b/Perspex.SceneGraph/Matrix.cs @@ -106,6 +106,16 @@ namespace Perspex return new Matrix(cos, sin, -sin, cos, 0, 0); } + public static Matrix Scaling(double x, double y) + { + return Scaling(new Vector(x, y)); + } + + public static Matrix Scaling(Vector scale) + { + return new Matrix(scale.X, 0, 0, scale.Y, 0, 0); + } + public static Matrix Translation(Vector v) { return Translation(v.X, v.Y); diff --git a/Perspex.SceneGraph/Media/Geometry.cs b/Perspex.SceneGraph/Media/Geometry.cs index 192842aa28..b0c971576f 100644 --- a/Perspex.SceneGraph/Media/Geometry.cs +++ b/Perspex.SceneGraph/Media/Geometry.cs @@ -6,10 +6,22 @@ namespace Perspex.Media { + using System; using Perspex.Platform; - public abstract class Geometry + public abstract class Geometry : PerspexObject { + public static readonly PerspexProperty TransformProperty = + PerspexProperty.Register("Transform"); + + static Geometry() + { + TransformProperty.Changed.Subscribe(x => + { + ((Geometry)x.Sender).PlatformImpl.Transform = ((ITransform)x.NewValue).Value; + }); + } + public abstract Rect Bounds { get; @@ -21,6 +33,14 @@ namespace Perspex.Media protected set; } + public ITransform Transform + { + get { return this.GetValue(TransformProperty); } + set { this.SetValue(TransformProperty, value); } + } + + public abstract Geometry Clone(); + public Rect GetRenderBounds(double strokeThickness) { return this.PlatformImpl.GetRenderBounds(strokeThickness); diff --git a/Perspex.SceneGraph/Media/MatrixTransform.cs b/Perspex.SceneGraph/Media/MatrixTransform.cs new file mode 100644 index 0000000000..4db9b26ec0 --- /dev/null +++ b/Perspex.SceneGraph/Media/MatrixTransform.cs @@ -0,0 +1,34 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Media +{ + public class MatrixTransform : Transform + { + public static readonly PerspexProperty MatrixProperty = + PerspexProperty.Register("Matrix", Matrix.Identity); + + public MatrixTransform() + { + } + + public MatrixTransform(Matrix matrix) + { + this.Matrix = matrix; + } + + public Matrix Matrix + { + get { return this.GetValue(MatrixProperty); } + set { this.SetValue(MatrixProperty, value); } + } + + public override Matrix Value + { + get { return this.Matrix; } + } + } +} diff --git a/Perspex.SceneGraph/Media/PathMarkupParser.cs b/Perspex.SceneGraph/Media/PathMarkupParser.cs index 866160732d..3ec2741330 100644 --- a/Perspex.SceneGraph/Media/PathMarkupParser.cs +++ b/Perspex.SceneGraph/Media/PathMarkupParser.cs @@ -85,7 +85,15 @@ namespace Perspex.Media this.context.EndFigure(false); } - point = ReadPoint(reader); + if (command == Command.Move) + { + point = ReadPoint(reader); + } + else + { + point = ReadRelativePoint(reader, point); + } + this.context.BeginFigure(point, true); openFigure = true; break; @@ -100,34 +108,34 @@ namespace Perspex.Media this.context.LineTo(point); break; - ////case Command.HorizontalLine: - //// point.X = ReadDouble(reader); - //// this.context.LineTo(point, true, false); - //// break; - - ////case Command.HorizontalLineRelative: - //// point.X += ReadDouble(reader); - //// this.context.LineTo(point, true, false); - //// break; - - ////case Command.VerticalLine: - //// point.Y = ReadDouble(reader); - //// this.context.LineTo(point, true, false); - //// break; - - ////case Command.VerticalLineRelative: - //// point.Y += ReadDouble(reader); - //// this.context.LineTo(point, true, false); - //// break; - - ////case Command.CubicBezierCurve: - ////{ - //// Point point1 = ReadPoint(reader); - //// Point point2 = ReadPoint(reader); - //// point = ReadPoint(reader); - //// this.context.BezierTo(point1, point2, point, true, false); - //// break; - ////} + case Command.HorizontalLine: + point = point.WithX(ReadDouble(reader)); + this.context.LineTo(point); + break; + + case Command.HorizontalLineRelative: + point = new Point(point.X + ReadDouble(reader), point.Y); + this.context.LineTo(point); + break; + + case Command.VerticalLine: + point = point.WithY(ReadDouble(reader)); + this.context.LineTo(point); + break; + + case Command.VerticalLineRelative: + point = new Point(point.X, point.Y + ReadDouble(reader)); + this.context.LineTo(point); + break; + + case Command.CubicBezierCurve: + { + Point point1 = ReadPoint(reader); + Point point2 = ReadPoint(reader); + point = ReadPoint(reader); + this.context.BezierTo(point1, point2, point); + break; + } case Command.Close: this.context.EndFigure(true); diff --git a/Perspex.SceneGraph/Media/RectangleGeometry.cs b/Perspex.SceneGraph/Media/RectangleGeometry.cs index c47478489e..6d89919e26 100644 --- a/Perspex.SceneGraph/Media/RectangleGeometry.cs +++ b/Perspex.SceneGraph/Media/RectangleGeometry.cs @@ -6,6 +6,7 @@ namespace Perspex.Media { + using System; using Perspex.Platform; using Splat; @@ -32,5 +33,10 @@ namespace Perspex.Media { get { return this.PlatformImpl.Bounds; } } + + public override Geometry Clone() + { + return new RectangleGeometry(this.Bounds); + } } } diff --git a/Perspex.SceneGraph/Media/StreamGeometry.cs b/Perspex.SceneGraph/Media/StreamGeometry.cs index 79f0934ebb..be0abcc07e 100644 --- a/Perspex.SceneGraph/Media/StreamGeometry.cs +++ b/Perspex.SceneGraph/Media/StreamGeometry.cs @@ -6,6 +6,7 @@ namespace Perspex.Media { + using System; using Perspex.Platform; using Splat; @@ -34,9 +35,19 @@ namespace Perspex.Media } } + public override Geometry Clone() + { + return new StreamGeometry(((IStreamGeometryImpl)this.PlatformImpl).Clone()); + } + public StreamGeometryContext Open() { return new StreamGeometryContext(((IStreamGeometryImpl)this.PlatformImpl).Open()); } + + private StreamGeometry(IGeometryImpl impl) + { + this.PlatformImpl = impl; + } } } diff --git a/Perspex.SceneGraph/Media/StreamGeometryContext.cs b/Perspex.SceneGraph/Media/StreamGeometryContext.cs index c7641ee4e9..84677cb066 100644 --- a/Perspex.SceneGraph/Media/StreamGeometryContext.cs +++ b/Perspex.SceneGraph/Media/StreamGeometryContext.cs @@ -23,6 +23,11 @@ namespace Perspex.Media this.impl.BeginFigure(startPoint, isFilled); } + public void BezierTo(Point point1, Point point2, Point point3) + { + this.impl.BezierTo(point1, point2, point3); + } + public void LineTo(Point point) { this.impl.LineTo(point); diff --git a/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/Perspex.SceneGraph/Perspex.SceneGraph.csproj index b070c4bef2..d82ece6791 100644 --- a/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -59,6 +59,7 @@ + diff --git a/Perspex.SceneGraph/Platform/IGeometryImpl.cs b/Perspex.SceneGraph/Platform/IGeometryImpl.cs index c9aafd92f4..02f4bf8fbd 100644 --- a/Perspex.SceneGraph/Platform/IGeometryImpl.cs +++ b/Perspex.SceneGraph/Platform/IGeometryImpl.cs @@ -10,6 +10,8 @@ namespace Perspex.Platform { Rect Bounds { get; } + Matrix Transform { get; set; } + Rect GetRenderBounds(double strokeThickness); } } diff --git a/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs b/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs index cb58199f72..dc783491e1 100644 --- a/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs +++ b/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs @@ -12,6 +12,8 @@ namespace Perspex.Platform { void BeginFigure(Point startPoint, bool isFilled); + void BezierTo(Point point1, Point point2, Point point3); + void LineTo(Point point); void EndFigure(bool isClosed); diff --git a/Perspex.SceneGraph/Platform/IStreamGeometryImpl.cs b/Perspex.SceneGraph/Platform/IStreamGeometryImpl.cs index 0f7065ff22..8424306615 100644 --- a/Perspex.SceneGraph/Platform/IStreamGeometryImpl.cs +++ b/Perspex.SceneGraph/Platform/IStreamGeometryImpl.cs @@ -8,6 +8,8 @@ namespace Perspex.Platform { public interface IStreamGeometryImpl : IGeometryImpl { + IStreamGeometryImpl Clone(); + IStreamGeometryContextImpl Open(); } } diff --git a/Perspex.SceneGraph/Point.cs b/Perspex.SceneGraph/Point.cs index 08e665d5e7..1a60774440 100644 --- a/Perspex.SceneGraph/Point.cs +++ b/Perspex.SceneGraph/Point.cs @@ -124,5 +124,15 @@ namespace Perspex { return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", this.x, this.y); } + + public Point WithX(double x) + { + return new Point(x, this.y); + } + + public Point WithY(double y) + { + return new Point(this.x, y); + } } } diff --git a/TestFiles/Direct2D1/Shapes/Path/Path_Tick_Scaled.expected.png b/TestFiles/Direct2D1/Shapes/Path/Path_Tick_Scaled.expected.png new file mode 100644 index 0000000000..8a3ec1f4f1 Binary files /dev/null and b/TestFiles/Direct2D1/Shapes/Path/Path_Tick_Scaled.expected.png differ diff --git a/Windows/Perspex.Direct2D1.RenderTests/Shapes/PathTests.cs b/Windows/Perspex.Direct2D1.RenderTests/Shapes/PathTests.cs index 7488b249e3..0b1b69694e 100644 --- a/Windows/Perspex.Direct2D1.RenderTests/Shapes/PathTests.cs +++ b/Windows/Perspex.Direct2D1.RenderTests/Shapes/PathTests.cs @@ -41,5 +41,29 @@ namespace Perspex.Direct2D1.RenderTests.Shapes this.RenderToFile(target); this.CompareImages(); } + + + [TestMethod] + public void Path_Tick_Scaled() + { + Decorator target = new Decorator + { + Width = 200, + Height = 200, + Content = new Path + { + Fill = Brushes.Gray, + Stroke = Brushes.Red, + StrokeThickness = 2, + Stretch = Stretch.Uniform, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Data = StreamGeometry.Parse("M 1145.607177734375,430 C1145.607177734375,430 1141.449951171875,435.0772705078125 1141.449951171875,435.0772705078125 1141.449951171875,435.0772705078125 1139.232177734375,433.0999755859375 1139.232177734375,433.0999755859375 1139.232177734375,433.0999755859375 1138,434.5538330078125 1138,434.5538330078125 1138,434.5538330078125 1141.482177734375,438 1141.482177734375,438 1141.482177734375,438 1141.96875,437.9375 1141.96875,437.9375 1141.96875,437.9375 1147,431.34619140625 1147,431.34619140625 1147,431.34619140625 1145.607177734375,430 1145.607177734375,430 z"), + } + }; + + this.RenderToFile(target); + this.CompareImages(); + } } } diff --git a/Windows/Perspex.Direct2D1/Media/GeometryImpl.cs b/Windows/Perspex.Direct2D1/Media/GeometryImpl.cs index e80bcc88ac..d3f6263fdc 100644 --- a/Windows/Perspex.Direct2D1/Media/GeometryImpl.cs +++ b/Windows/Perspex.Direct2D1/Media/GeometryImpl.cs @@ -6,21 +6,57 @@ namespace Perspex.Direct2D1.Media { - using System; - using Perspex.Media; using Perspex.Platform; + using SharpDX.Direct2D1; + using Splat; public abstract class GeometryImpl : IGeometryImpl { + private TransformedGeometry transformed; + public abstract Rect Bounds { get; } - public SharpDX.Direct2D1.Geometry Geometry + public abstract Geometry DefiningGeometry { get; - protected set; + } + + public Geometry Geometry + { + get { return this.transformed ?? this.DefiningGeometry; } + } + + public Matrix Transform + { + get + { + return this.transformed != null ? + this.transformed.Transform.ToPerspex() : + Matrix.Identity; + } + + set + { + if (value != this.Transform) + { + if (this.transformed != null) + { + this.transformed.Dispose(); + } + + if (!value.IsIdentity) + { + Factory factory = Locator.Current.GetService(); + this.transformed = new TransformedGeometry( + factory, + this.DefiningGeometry, + value.ToDirect2D()); + } + } + } } public abstract Rect GetRenderBounds(double strokeThickness); diff --git a/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs b/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs index ddd903f1ec..53f98a24da 100644 --- a/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs +++ b/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs @@ -23,6 +23,16 @@ namespace Perspex.Direct2D1.Media this.sink.BeginFigure(startPoint.ToSharpDX(), isFilled ? FigureBegin.Filled : FigureBegin.Hollow); } + public void BezierTo(Point point1, Point point2, Point point3) + { + this.sink.AddBezier(new BezierSegment + { + Point1 = point1.ToSharpDX(), + Point2 = point2.ToSharpDX(), + Point3 = point3.ToSharpDX(), + }); + } + public void LineTo(Point point) { this.sink.AddLine(point.ToSharpDX()); diff --git a/Windows/Perspex.Direct2D1/Media/StreamGeometryImpl.cs b/Windows/Perspex.Direct2D1/Media/StreamGeometryImpl.cs index 7452a74024..41f0a91a68 100644 --- a/Windows/Perspex.Direct2D1/Media/StreamGeometryImpl.cs +++ b/Windows/Perspex.Direct2D1/Media/StreamGeometryImpl.cs @@ -9,33 +9,54 @@ namespace Perspex.Direct2D1.Media using System; using Perspex.Media; using Perspex.Platform; + using SharpDX; using SharpDX.Direct2D1; using Splat; + using D2DGeometry = SharpDX.Direct2D1.Geometry; public class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl { - private PathGeometry geometry; + private PathGeometry path; public StreamGeometryImpl() { Factory factory = Locator.Current.GetService(); - this.geometry = new PathGeometry(factory); - this.Geometry = this.geometry; + this.path = new PathGeometry(factory); } public override Rect Bounds { - get { return this.geometry.GetBounds().ToPerspex(); } + get { return this.path.GetBounds().ToPerspex(); } + } + + public override D2DGeometry DefiningGeometry + { + get { return this.path; } + } + + public IStreamGeometryImpl Clone() + { + Factory factory = Locator.Current.GetService(); + var result = new PathGeometry(factory); + var sink = result.Open(); + this.path.Stream(sink); + sink.Close(); + return new StreamGeometryImpl(result); } public override Rect GetRenderBounds(double strokeThickness) { - return this.geometry.GetWidenedBounds((float)strokeThickness).ToPerspex(); + return this.path.GetWidenedBounds((float)strokeThickness).ToPerspex(); } public IStreamGeometryContextImpl Open() { - return new StreamGeometryContextImpl(this.geometry.Open()); + return new StreamGeometryContextImpl(this.path.Open()); + } + + protected StreamGeometryImpl(PathGeometry geometry) + { + this.path = geometry; } } }