Browse Source

Merge branch 'master' into name-scope

pull/325/head
Steven Kirk 11 years ago
parent
commit
20c43474f2
  1. 6
      Perspex.sln
  2. 8
      appveyor.yml
  3. 22
      samples/XamlTestApplicationPcl/Views/MainWindow.paml
  4. 2
      src/Gtk/Perspex.Cairo/Media/ImageBrushImpl.cs
  5. 34
      src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs
  6. 186
      src/Gtk/Perspex.Cairo/Media/TileBrushes.cs
  7. 2
      src/Gtk/Perspex.Cairo/Media/VisualBrushImpl.cs
  8. 1
      src/Gtk/Perspex.Cairo/Perspex.Cairo.csproj
  9. 1
      src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
  10. 32
      src/Markup/Perspex.Markup.Xaml/Converters/RelativeRectTypeConverter.cs
  11. 1
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  12. 10
      src/Perspex.SceneGraph/Media/StreamGeometryContext.cs
  13. 7
      src/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs
  14. 24
      src/Perspex.SceneGraph/Point.cs
  15. 3
      src/Perspex.SceneGraph/Rect.cs
  16. 110
      src/Perspex.SceneGraph/RelativeRect.cs
  17. 19
      src/Perspex.SceneGraph/Vector.cs
  18. 1146
      src/Shared/RenderHelpers/ArcToHelper.cs
  19. 13
      src/Shared/RenderHelpers/QuadBezierHelper.cs
  20. 16
      src/Shared/RenderHelpers/RenderHelpers.projitems
  21. 13
      src/Shared/RenderHelpers/RenderHelpers.shproj
  22. 174
      src/Shared/RenderHelpers/TileBrushImplHelper.cs
  23. 4
      src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs
  24. 94
      src/Windows/Perspex.Direct2D1/Media/ImageBrushImpl.cs
  25. 9
      src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs
  26. 92
      src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs
  27. 73
      src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs
  28. 3
      src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj
  29. 4
      tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj
  30. 25
      tests/Perspex.SceneGraph.UnitTests/RelativeRectComparer.cs
  31. 29
      tests/Perspex.SceneGraph.UnitTests/RelativeRectTests.cs

6
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

8
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

22
samples/XamlTestApplicationPcl/Views/MainWindow.paml

@ -245,6 +245,28 @@
<Button Grid.Row="1" Grid.Column="1" Content="Animate" HorizontalAlignment="Center"></Button>
</Grid>
</TabItem>
<TabItem Header="Brushes">
<Grid ColumnDefinitions="Auto" RowDefinitions="Auto">
<Border Grid.Row="0" Grid.Column="0" Width="200" Height="200" Margin="10">
<Border.Background>
<VisualBrush TileMode="Tile" Stretch="None" AlignmentX="Left" AlignmentY="Top" SourceRect="0%,0%,100%,100%" DestinationRect="0,0,20,20">
<VisualBrush.Visual>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<Rectangle Width="10" Height="10" Fill="DarkGray"/>
<Rectangle Width="10" Height="10" Fill="LightGray"/>
</StackPanel>
<StackPanel Orientation="Vertical">
<Rectangle Width="10" Height="10" Fill="LightGray"/>
<Rectangle Width="10" Height="10" Fill="DarkGray"/>
</StackPanel>
</StackPanel>
</VisualBrush.Visual>
</VisualBrush>
</Border.Background>
</Border>
</Grid>
</TabItem>
</TabControl>
</Grid>
</Window>

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

34
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;

186
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
}
}
/// <summary>
/// Calculates a translate based on a <see cref="TileBrush"/>, a source and destination
/// rectangle and a scale.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="sourceRect">The source rectangle.</param>
/// <param name="destinationRect">The destination rectangle.</param>
/// <param name="scale">The scale factor.</param>
/// <returns>A vector with the X and Y translate.</returns>
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;
}
}
}

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

1
src/Gtk/Perspex.Cairo/Perspex.Cairo.csproj

@ -93,6 +93,7 @@
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="..\..\Shared\RenderHelpers\RenderHelpers.projitems" Label="Shared" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

1
src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs

@ -106,6 +106,7 @@ namespace Perspex.Markup.Xaml.Context
new TypeConverterRegistration(typeof(Point), new PointTypeConverter()),
new TypeConverterRegistration(typeof(PerspexProperty), new PerspexPropertyTypeConverter()),
new TypeConverterRegistration(typeof(RelativePoint), new RelativePointTypeConverter()),
new TypeConverterRegistration(typeof(RelativeRect), new RelativeRectTypeConverter()),
new TypeConverterRegistration(typeof(RowDefinitions), new RowDefinitionsTypeConverter()),
new TypeConverterRegistration(typeof(Selector), new SelectorTypeConverter()),
new TypeConverterRegistration(typeof(Thickness), new ThicknessTypeConverter()),

32
src/Markup/Perspex.Markup.Xaml/Converters/RelativeRectTypeConverter.cs

@ -0,0 +1,32 @@
// 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.Globalization;
using OmniXaml.TypeConversion;
namespace Perspex.Markup.Xaml.Converters
{
public class RelativeRectTypeConverter : 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)
{
return RelativeRect.Parse((string)value, culture);
}
public object ConvertTo(IXamlTypeConverterContext context, CultureInfo culture, object value, Type destinationType)
{
throw new NotImplementedException();
}
}
}

1
src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj

@ -39,6 +39,7 @@
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Converters\PerspexListTypeConverter.cs" />
<Compile Include="Converters\RelativeRectTypeConverter.cs" />
<Compile Include="Data\IXamlBinding.cs" />
<Compile Include="Data\MultiBinding.cs" />
<Compile Include="Data\RelativeSource.cs" />

10
src/Perspex.SceneGraph/Media/StreamGeometryContext.cs

@ -64,6 +64,16 @@ namespace Perspex.Media
_impl.BezierTo(point1, point2, point3);
}
/// <summary>
/// Draws a quadratic Bezier curve to the specified point
/// </summary>
/// <param name="control">The control point used to specify the shape of the curve.</param>
/// <param name="endPoint">The destination point for the end of the curve.</param>
public void QuadTo(Point control, Point endPoint)
{
_impl.QuadTo(control, endPoint);
}
/// <summary>
/// Draws a line to the specified point.
/// </summary>

7
src/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs

@ -38,6 +38,13 @@ namespace Perspex.Platform
/// <param name="point3">The destination point for the end of the curve.</param>
void BezierTo(Point point1, Point point2, Point point3);
/// <summary>
/// Draws a quadratic Bezier curve to the specified point
/// </summary>
/// <param name="control">Control point</param>
/// <param name="endPoint">DestinationPoint</param>
void QuadTo(Point control, Point endPoint);
/// <summary>
/// Draws a line to the specified point.
/// </summary>

24
src/Perspex.SceneGraph/Point.cs

@ -128,6 +128,30 @@ namespace Perspex
return new Point(a._x - b.X, a._y - b.Y);
}
/// <summary>
/// Multiplies a point by a factor coordinate-wise
/// </summary>
/// <param name="p">Point to multiply</param>
/// <param name="k">Factor</param>
/// <returns>Points having its coordinates multiplied</returns>
public static Point operator *(Point p, double k) => new Point(p.X*k, p.Y*k);
/// <summary>
/// Multiplies a point by a factor coordinate-wise
/// </summary>
/// <param name="p">Point to multiply</param>
/// <param name="k">Factor</param>
/// <returns>Points having its coordinates multiplied</returns>
public static Point operator *(double k, Point p) => new Point(p.X*k, p.Y*k);
/// <summary>
/// Divides a point by a factor coordinate-wise
/// </summary>
/// <param name="p">Point to divide by</param>
/// <param name="k">Factor</param>
/// <returns>Points having its coordinates divided</returns>
public static Point operator /(Point p, double k) => new Point(p.X/k, p.Y/k);
/// <summary>
/// Applies a matrix to a point.
/// </summary>

3
src/Perspex.SceneGraph/Rect.cs

@ -297,7 +297,8 @@ namespace Perspex
{
if (obj is Rect)
{
return this == (Rect)obj;
var other = (Rect)obj;
return Position == other.Position && Size == other.Size;
}
return false;

110
src/Perspex.SceneGraph/RelativeRect.cs

@ -1,12 +1,16 @@
// 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.Globalization;
using System.Linq;
namespace Perspex
{
/// <summary>
/// Defines a rectangle that may be defined relative to a containing element.
/// </summary>
public struct RelativeRect
public struct RelativeRect : IEquatable<RelativeRect>
{
/// <summary>
/// A rectangle that represents 100% of an area.
@ -83,6 +87,63 @@ namespace Perspex
/// </summary>
public Rect Rect { get; }
/// <summary>
/// Checks for equality between two <see cref="RelativeRect"/>s.
/// </summary>
/// <param name="left">The first rectangle.</param>
/// <param name="right">The second rectangle.</param>
/// <returns>True if the rectangles are equal; otherwise false.</returns>
public static bool operator ==(RelativeRect left, RelativeRect right)
{
return left.Equals(right);
}
/// <summary>
/// Checks for unequality between two <see cref="RelativeRect"/>s.
/// </summary>
/// <param name="left">The first rectangle.</param>
/// <param name="right">The second rectangle.</param>
/// <returns>True if the rectangles are unequal; otherwise false.</returns>
public static bool operator !=(RelativeRect left, RelativeRect right)
{
return !left.Equals(right);
}
/// <summary>
/// Checks if the <see cref="RelativeRect"/> equals another object.
/// </summary>
/// <param name="obj">The other object.</param>
/// <returns>True if the objects are equal, otherwise false.</returns>
public override bool Equals(object obj)
{
return (obj is RelativeRect) ? Equals((RelativeRect)obj) : false;
}
/// <summary>
/// Checks if the <see cref="RelativeRect"/> equals another rectangle.
/// </summary>
/// <param name="p">The other rectangle.</param>
/// <returns>True if the objects are equal, otherwise false.</returns>
public bool Equals(RelativeRect p)
{
return Unit == p.Unit && Rect == p.Rect;
}
/// <summary>
/// Gets a hashcode for a <see cref="RelativeRect"/>.
/// </summary>
/// <returns>A hash code.</returns>
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = (hash * 23) + Unit.GetHashCode();
hash = (hash * 23) + Rect.GetHashCode();
return hash;
}
}
/// <summary>
/// Converts a <see cref="RelativeRect"/> into pixels.
/// </summary>
@ -98,5 +159,52 @@ namespace Perspex
Rect.Width * size.Width,
Rect.Height * size.Height);
}
/// <summary>
/// Parses a <see cref="RelativeRect"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The parsed <see cref="RelativeRect"/>.</returns>
public static RelativeRect Parse(string s, CultureInfo culture)
{
var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList();
if (parts.Count == 4)
{
var unit = RelativeUnit.Absolute;
var scale = 1.0;
if (parts[0].EndsWith("%"))
{
if (!parts[1].EndsWith("%")
|| !parts[2].EndsWith("%")
|| !parts[3].EndsWith("%"))
{
throw new FormatException("If one coordinate is relative, all other must be too.");
}
parts[0] = parts[0].TrimEnd('%');
parts[1] = parts[1].TrimEnd('%');
parts[2] = parts[2].TrimEnd('%');
parts[3] = parts[3].TrimEnd('%');
unit = RelativeUnit.Relative;
scale = 0.01;
}
return new RelativeRect(
double.Parse(parts[0], culture) * scale,
double.Parse(parts[1], culture) * scale,
double.Parse(parts[2], culture) * scale,
double.Parse(parts[3], culture) * scale,
unit);
}
else
{
throw new FormatException("Invalid Rect.");
}
}
}
}

19
src/Perspex.SceneGraph/Vector.cs

@ -3,6 +3,7 @@
using System;
using System.Globalization;
using System.Xml.Linq;
namespace Perspex
{
@ -51,6 +52,24 @@ namespace Perspex
return new Point(a._x, a._y);
}
/// <summary>
/// Calculates the dot product of two vectors
/// </summary>
/// <param name="a">First vector</param>
/// <param name="b">Second vector</param>
/// <returns>The dot product</returns>
public static double operator *(Vector a, Vector b)
{
return a.X*b.X + a.Y*b.Y;
}
/// <summary>
/// Length of the vector
/// </summary>
public double Length => Math.Sqrt(X*X + Y*Y);
/// <summary>
/// Negates a vector.
/// </summary>

1146
src/Shared/RenderHelpers/ArcToHelper.cs

File diff suppressed because it is too large

13
src/Shared/RenderHelpers/QuadBezierHelper.cs

@ -0,0 +1,13 @@
using Perspex.Platform;
namespace Perspex.RenderHelpers
{
static class QuadBezierHelper
{
public static void QuadTo(IStreamGeometryContextImpl context, Point current, Point controlPoint, Point endPoint)
{
//(s, (s + 2c)/ 3, (e + 2c)/ 3, e)
context.BezierTo((current + 2*controlPoint)/3, (endPoint + 2*controlPoint)/3, endPoint);
}
}
}

16
src/Shared/RenderHelpers/RenderHelpers.projitems

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>3c4c0cb4-0c0f-4450-a37b-148c84ff905f</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>Perspex.RenderHelpers</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)ArcToHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)QuadBezierHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TileBrushImplHelper.cs" />
</ItemGroup>
</Project>

13
src/Shared/RenderHelpers/RenderHelpers.shproj

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>3c4c0cb4-0c0f-4450-a37b-148c84ff905f</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="RenderHelpers.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>

174
src/Shared/RenderHelpers/TileBrushImplHelper.cs

@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Perspex;
using Perspex.Media;
using Perspex.Rendering;
using Perspex.Layout;
namespace Perspex.RenderHelpers
{
class TileBrushImplHelper
{
public Size IntermediateSize { get; }
public Rect DestinationRect { get; }
private readonly TileMode _tileMode;
private readonly Rect _sourceRect;
private readonly Vector _scale;
private readonly Vector _translate;
private readonly Size _imageSize;
private readonly VisualBrush _visualBrush;
private readonly ImageBrush _imageBrush;
public bool IsValid { get; }
public TileBrushImplHelper(TileBrush brush, Size targetSize)
{
_imageBrush = brush as ImageBrush;
_visualBrush = brush as VisualBrush;
if (_imageBrush != null)
{
if (_imageBrush.Source == null)
return;
_imageSize = new Size(_imageBrush.Source.PixelWidth, _imageBrush.Source.PixelHeight);
IsValid = true;
}
else if (_visualBrush != null)
{
var visual = _visualBrush.Visual;
if (visual == null)
return;
var layoutable = visual as ILayoutable;
if (layoutable?.IsArrangeValid == false)
{
layoutable.Measure(Size.Infinity);
layoutable.Arrange(new Rect(layoutable.DesiredSize));
}
//I have no idea why are we using layoutable after `as` cast, but it was in original VisualBrush code by @grokys
_imageSize = layoutable.Bounds.Size;
IsValid = true;
}
else
return;
_tileMode = brush.TileMode;
_sourceRect = brush.SourceRect.ToPixels(_imageSize);
DestinationRect = brush.DestinationRect.ToPixels(targetSize);
_scale = brush.Stretch.CalculateScaling(DestinationRect.Size, _sourceRect.Size);
_translate = CalculateTranslate(brush, _sourceRect, DestinationRect, _scale);
IntermediateSize = CalculateIntermediateSize(_tileMode, targetSize, DestinationRect.Size);
}
public void DrawIntermediate(DrawingContext ctx)
{
Rect drawRect;
var transform = CalculateIntermediateTransform(
_tileMode,
_sourceRect,
DestinationRect,
_scale,
_translate,
out drawRect);
using (ctx.PushClip(drawRect))
using (ctx.PushPostTransform(transform))
{
if (_imageBrush != null)
{
var bmpRc = new Rect(0, 0, _imageBrush.Source.PixelWidth, _imageBrush.Source.PixelHeight);
ctx.DrawImage(_imageBrush.Source, 1, bmpRc, bmpRc);
}
else if (_visualBrush != null)
{
ctx.FillRectangle(Brushes.Black, new Rect(new Point(0, 0), IntermediateSize));
ctx.Render(_visualBrush.Visual);
}
}
}
/// <summary>
/// Calculates a _translate based on a <see cref="TileBrush"/>, a source and destination
/// rectangle and a _scale.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="sourceRect">The source rectangle.</param>
/// <param name="destinationRect">The destination rectangle.</param>
/// <param name="scale">The _scale factor.</param>
/// <returns>A vector with the X and Y _translate.</returns>
public 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);
}
public 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;
}
static Size CalculateIntermediateSize(
TileMode tileMode,
Size targetSize,
Size destinationSize) => tileMode == TileMode.None ? targetSize : destinationSize;
}
}

4
src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs

@ -312,11 +312,11 @@ namespace Perspex.Direct2D1.Media
}
else if (imageBrush != null)
{
return new ImageBrushImpl(imageBrush, _renderTarget, destinationSize);
return new TileBrushImpl(imageBrush, _renderTarget, destinationSize);
}
else if (visualBrush != null)
{
return new VisualBrushImpl(visualBrush, _renderTarget, destinationSize);
return new TileBrushImpl(visualBrush, _renderTarget, destinationSize);
}
else
{

94
src/Windows/Perspex.Direct2D1/Media/ImageBrushImpl.cs

@ -1,94 +0,0 @@
// 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 Perspex.Media;
using SharpDX.Direct2D1;
namespace Perspex.Direct2D1.Media
{
public class ImageBrushImpl : TileBrushImpl
{
public ImageBrushImpl(
ImageBrush brush,
SharpDX.Direct2D1.RenderTarget target,
Size targetSize)
{
if (brush.Source == null)
{
return;
}
var image = ((BitmapImpl)brush.Source.PlatformImpl).GetDirect2DBitmap(target);
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 brtOpts = CompatibleRenderTargetOptions.None;
// TODO: There are times where we don't need to draw an intermediate bitmap. Identify
// them and directly use 'image' in those cases.
using (var intermediate = new BitmapRenderTarget(target, brtOpts, intermediateSize))
{
Rect drawRect;
var transform = CalculateIntermediateTransform(
tileMode,
sourceRect,
destinationRect,
scale,
translate,
out drawRect);
intermediate.BeginDraw();
intermediate.PushAxisAlignedClip(drawRect.ToDirect2D(), AntialiasMode.Aliased);
intermediate.Transform = transform.ToDirect2D();
intermediate.DrawBitmap(image, 1, BitmapInterpolationMode.Linear);
intermediate.PopAxisAlignedClip();
intermediate.EndDraw();
this.PlatformBrush = new BitmapBrush(
target,
intermediate.Bitmap,
GetBitmapBrushProperties(brush),
GetBrushProperties(brush, destinationRect));
}
}
private BitmapBrush CreateDirectBrush(
ImageBrush brush,
SharpDX.Direct2D1.RenderTarget target,
Bitmap image,
Rect sourceRect,
Rect destinationRect)
{
var tileMode = brush.TileMode;
var scale = brush.Stretch.CalculateScaling(destinationRect.Size, sourceRect.Size);
var translate = CalculateTranslate(brush, sourceRect, destinationRect, scale);
var transform = Matrix.CreateTranslation(-sourceRect.Position) *
Matrix.CreateScale(scale) *
Matrix.CreateTranslation(translate);
var opts = new BrushProperties
{
Transform = transform.ToDirect2D(),
Opacity = (float)brush.Opacity,
};
var bitmapOpts = new BitmapBrushProperties
{
ExtendModeX = GetExtendModeX(tileMode),
ExtendModeY = GetExtendModeY(tileMode),
};
return new BitmapBrush(target, image, bitmapOpts, opts);
}
private BitmapBrush CreateIndirectBrush()
{
throw new NotImplementedException();
}
}
}

9
src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs

@ -47,6 +47,15 @@ namespace Perspex.Direct2D1.Media
});
}
public void QuadTo(Point control, Point dest)
{
_sink.AddQuadraticBezier(new QuadraticBezierSegment
{
Point1 = control.ToSharpDX(),
Point2 = dest.ToSharpDX()
});
}
public void LineTo(Point point)
{
_sink.AddLine(point.ToSharpDX());

92
src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs

@ -6,94 +6,38 @@ using Perspex.Layout;
using Perspex.Media;
using SharpDX;
using SharpDX.Direct2D1;
using Perspex.RenderHelpers;
namespace Perspex.Direct2D1.Media
{
public class TileBrushImpl : BrushImpl
public sealed class TileBrushImpl : BrushImpl
{
/// <summary>
/// Calculates a translate based on a <see cref="TileBrush"/>, a source and destination
/// rectangle and a scale.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="sourceRect">The source rectangle.</param>
/// <param name="destinationRect">The destination rectangle.</param>
/// <param name="scale">The scale factor.</param>
/// <returns>A vector with the X and Y translate.</returns>
protected static Vector CalculateTranslate(
public TileBrushImpl(
TileBrush brush,
Rect sourceRect,
Rect destinationRect,
Vector scale)
SharpDX.Direct2D1.RenderTarget target,
Size targetSize)
{
var x = 0.0;
var y = 0.0;
var size = sourceRect.Size * scale;
var helper = new TileBrushImplHelper(brush, targetSize);
if (!helper.IsValid)
return;
switch (brush.AlignmentX)
using (var intermediate = new BitmapRenderTarget(target, CompatibleRenderTargetOptions.None, helper.IntermediateSize.ToSharpDX()))
{
case AlignmentX.Center:
x += (destinationRect.Width - size.Width) / 2;
break;
case AlignmentX.Right:
x += destinationRect.Width - size.Width;
break;
using (var ctx = new RenderTarget(intermediate).CreateDrawingContext())
helper.DrawIntermediate(ctx);
PlatformBrush = new BitmapBrush(
target,
intermediate.Bitmap,
GetBitmapBrushProperties(brush),
GetBrushProperties(brush, helper.DestinationRect));
}
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);
}
protected static Size2F CalculateIntermediateSize(
TileMode tileMode,
Size targetSize,
Size destinationSize)
{
var result = tileMode == TileMode.None ? targetSize : destinationSize;
return result.ToSharpDX();
}
protected 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;
}
protected static BrushProperties GetBrushProperties(TileBrush brush, Rect destinationRect)
{
var tileMode = brush.TileMode;
return new BrushProperties
{
Opacity = (float)brush.Opacity,

73
src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs

@ -1,73 +0,0 @@
// 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 Perspex.Layout;
using Perspex.Media;
using Perspex.Rendering;
using SharpDX;
using SharpDX.Direct2D1;
namespace Perspex.Direct2D1.Media
{
public class VisualBrushImpl : TileBrushImpl
{
public VisualBrushImpl(
VisualBrush brush,
SharpDX.Direct2D1.RenderTarget target,
Size targetSize)
{
var visual = brush.Visual;
if (visual == null)
{
return;
}
var layoutable = visual as ILayoutable;
if (layoutable?.IsArrangeValid == false)
{
layoutable.Measure(Size.Infinity);
layoutable.Arrange(new Rect(layoutable.DesiredSize));
}
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);
var brtOpts = CompatibleRenderTargetOptions.None;
// TODO: There are times where we don't need to draw an intermediate bitmap. Identify
// them and directly use 'image' in those cases.
using (var intermediate = new BitmapRenderTarget(target, brtOpts, intermediateSize))
{
Rect drawRect;
var transform = CalculateIntermediateTransform(
tileMode,
sourceRect,
destinationRect,
scale,
translate,
out drawRect);
var renderer = new RenderTarget(intermediate);
using (var ctx = renderer.CreateDrawingContext())
using (ctx.PushClip(drawRect))
using (ctx.PushPostTransform(transform))
{
intermediate.Clear(new Color4(0));
ctx.Render(visual);
}
this.PlatformBrush = new BitmapBrush(
target,
intermediate.Bitmap,
GetBitmapBrushProperties(brush),
GetBrushProperties(brush, destinationRect));
}
}
}
}

3
src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj

@ -79,9 +79,7 @@
<Compile Include="Media\RadialGradientBrushImpl.cs" />
<Compile Include="Media\LinearGradientBrushImpl.cs" />
<Compile Include="Media\PerspexTextRenderer.cs" />
<Compile Include="Media\ImageBrushImpl.cs" />
<Compile Include="Media\TileBrushImpl.cs" />
<Compile Include="Media\VisualBrushImpl.cs" />
<Compile Include="Media\SolidColorBrushImpl.cs" />
<Compile Include="Media\StreamGeometryContextImpl.cs" />
<Compile Include="Media\GeometryImpl.cs" />
@ -113,6 +111,7 @@
<Name>Perspex.SceneGraph</Name>
</ProjectReference>
</ItemGroup>
<Import Project="..\..\Shared\RenderHelpers\RenderHelpers.projitems" Label="Shared" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\..\packages\SharpDX.2.6.3\build\SharpDX.targets" Condition="Exists('..\..\..\packages\SharpDX.2.6.3\build\SharpDX.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

4
tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj

@ -73,6 +73,8 @@
</Choose>
<ItemGroup>
<Compile Include="NameScopeTests.cs" />
<Compile Include="RelativeRectComparer.cs" />
<Compile Include="RelativeRectTests.cs" />
<Compile Include="ThicknessTests.cs" />
<Compile Include="Media\BrushTests.cs" />
<Compile Include="Media\ColorTests.cs" />
@ -158,4 +160,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

25
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<RelativeRect>
{
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();
}
}
}

29
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);
}
}
}
Loading…
Cancel
Save