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/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
index 9597cf1128..30f2b5ca7c 100644
--- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
+++ b/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()),
diff --git a/src/Markup/Perspex.Markup.Xaml/Converters/RelativeRectTypeConverter.cs b/src/Markup/Perspex.Markup.Xaml/Converters/RelativeRectTypeConverter.cs
new file mode 100644
index 0000000000..e3f8af7a1d
--- /dev/null
+++ b/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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
index aea7b0b04e..cebb76826f 100644
--- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
+++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
@@ -39,6 +39,7 @@
Properties\SharedAssemblyInfo.cs
+
diff --git a/src/Perspex.SceneGraph/RelativeRect.cs b/src/Perspex.SceneGraph/RelativeRect.cs
index 5dedbdf00d..5ededd2e52 100644
--- a/src/Perspex.SceneGraph/RelativeRect.cs
+++ b/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
{
///
/// Defines a rectangle that may be defined relative to a containing element.
///
- public struct RelativeRect
+ public struct RelativeRect : IEquatable
{
///
/// A rectangle that represents 100% of an area.
@@ -83,6 +87,63 @@ namespace Perspex
///
public Rect Rect { get; }
+ ///
+ /// Checks for equality between two s.
+ ///
+ /// The first rectangle.
+ /// The second rectangle.
+ /// True if the rectangles are equal; otherwise false.
+ public static bool operator ==(RelativeRect left, RelativeRect right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Checks for unequality between two s.
+ ///
+ /// The first rectangle.
+ /// The second rectangle.
+ /// True if the rectangles are unequal; otherwise false.
+ public static bool operator !=(RelativeRect left, RelativeRect right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ /// Checks if the equals another object.
+ ///
+ /// The other object.
+ /// True if the objects are equal, otherwise false.
+ public override bool Equals(object obj)
+ {
+ return (obj is RelativeRect) ? Equals((RelativeRect)obj) : false;
+ }
+
+ ///
+ /// Checks if the equals another rectangle.
+ ///
+ /// The other rectangle.
+ /// True if the objects are equal, otherwise false.
+ public bool Equals(RelativeRect p)
+ {
+ return Unit == p.Unit && Rect == p.Rect;
+ }
+
+ ///
+ /// Gets a hashcode for a .
+ ///
+ /// A hash code.
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+ hash = (hash * 23) + Unit.GetHashCode();
+ hash = (hash * 23) + Rect.GetHashCode();
+ return hash;
+ }
+ }
+
///
/// Converts a into pixels.
///
@@ -98,5 +159,52 @@ namespace Perspex
Rect.Width * size.Width,
Rect.Height * size.Height);
}
+
+ ///
+ /// Parses a string.
+ ///
+ /// The string.
+ /// The current culture.
+ /// The parsed .
+ 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.");
+ }
+ }
}
}
diff --git a/tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj b/tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj
index e48d6ac73b..7c4daf0867 100644
--- a/tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj
+++ b/tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj
@@ -72,6 +72,7 @@
+
diff --git a/tests/Perspex.SceneGraph.UnitTests/RelativeRectTests.cs b/tests/Perspex.SceneGraph.UnitTests/RelativeRectTests.cs
new file mode 100644
index 0000000000..9ee4e37e3f
--- /dev/null
+++ b/tests/Perspex.SceneGraph.UnitTests/RelativeRectTests.cs
@@ -0,0 +1,27 @@
+// 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
+ {
+ [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);
+ }
+
+ [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);
+ }
+ }
+}