Browse Source

Some work on Paths.

Make paths with Stretch != None correctly fill the Path control's
available size. Added a Path_Tick_Scaled test with a path from
MahApps.Metro to test this.
pull/13/head
Steven Kirk 12 years ago
parent
commit
7fe1f7e814
  1. 5
      Cairo/Perspex.Cairo/Media/StreamGeometryContextImpl.cs
  2. 13
      Cairo/Perspex.Cairo/Media/StreamGeometryImpl.cs
  3. 45
      Perspex.Controls/Shapes/Shape.cs
  4. 10
      Perspex.SceneGraph/Matrix.cs
  5. 22
      Perspex.SceneGraph/Media/Geometry.cs
  6. 34
      Perspex.SceneGraph/Media/MatrixTransform.cs
  7. 66
      Perspex.SceneGraph/Media/PathMarkupParser.cs
  8. 6
      Perspex.SceneGraph/Media/RectangleGeometry.cs
  9. 11
      Perspex.SceneGraph/Media/StreamGeometry.cs
  10. 5
      Perspex.SceneGraph/Media/StreamGeometryContext.cs
  11. 1
      Perspex.SceneGraph/Perspex.SceneGraph.csproj
  12. 2
      Perspex.SceneGraph/Platform/IGeometryImpl.cs
  13. 2
      Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs
  14. 2
      Perspex.SceneGraph/Platform/IStreamGeometryImpl.cs
  15. 10
      Perspex.SceneGraph/Point.cs
  16. BIN
      TestFiles/Direct2D1/Shapes/Path/Path_Tick_Scaled.expected.png
  17. 24
      Windows/Perspex.Direct2D1.RenderTests/Shapes/PathTests.cs
  18. 44
      Windows/Perspex.Direct2D1/Media/GeometryImpl.cs
  19. 10
      Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs
  20. 33
      Windows/Perspex.Direct2D1/Media/StreamGeometryImpl.cs

5
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

13
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

45
Perspex.Controls/Shapes/Shape.cs

@ -24,6 +24,8 @@ namespace Perspex.Controls.Shapes
public static readonly PerspexProperty<double> StrokeThicknessProperty =
PerspexProperty.Register<Shape, double>("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);
}
}

10
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);

22
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<ITransform> TransformProperty =
PerspexProperty.Register<Geometry, ITransform>("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);

34
Perspex.SceneGraph/Media/MatrixTransform.cs

@ -0,0 +1,34 @@
// -----------------------------------------------------------------------
// <copyright file="RotateTransform.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Media
{
public class MatrixTransform : Transform
{
public static readonly PerspexProperty<Matrix> MatrixProperty =
PerspexProperty.Register<MatrixTransform, Matrix>("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; }
}
}
}

66
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);

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

11
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;
}
}
}

5
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);

1
Perspex.SceneGraph/Perspex.SceneGraph.csproj

@ -59,6 +59,7 @@
<Compile Include="Media\PathMarkupParser.cs" />
<Compile Include="Media\Pen.cs" />
<Compile Include="Media\RectangleGeometry.cs" />
<Compile Include="Media\MatrixTransform.cs" />
<Compile Include="Media\RotateTransform.cs" />
<Compile Include="Media\SolidColorBrush.cs" />
<Compile Include="Media\StreamGeometry.cs" />

2
Perspex.SceneGraph/Platform/IGeometryImpl.cs

@ -10,6 +10,8 @@ namespace Perspex.Platform
{
Rect Bounds { get; }
Matrix Transform { get; set; }
Rect GetRenderBounds(double strokeThickness);
}
}

2
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);

2
Perspex.SceneGraph/Platform/IStreamGeometryImpl.cs

@ -8,6 +8,8 @@ namespace Perspex.Platform
{
public interface IStreamGeometryImpl : IGeometryImpl
{
IStreamGeometryImpl Clone();
IStreamGeometryContextImpl Open();
}
}

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

BIN
TestFiles/Direct2D1/Shapes/Path/Path_Tick_Scaled.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

24
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();
}
}
}

44
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<Factory>();
this.transformed = new TransformedGeometry(
factory,
this.DefiningGeometry,
value.ToDirect2D());
}
}
}
}
public abstract Rect GetRenderBounds(double strokeThickness);

10
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());

33
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<Factory>();
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<Factory>();
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;
}
}
}

Loading…
Cancel
Save