Browse Source

Merge branch 'LayoutTransform' of https://github.com/donandren/Perspex into donandren-LayoutTransform

pull/482/head
Steven Kirk 11 years ago
parent
commit
ba4b3b0794
  1. 389
      src/Perspex.Controls/LayoutTransformControl.cs
  2. 1
      src/Perspex.Controls/Perspex.Controls.csproj
  3. 6
      src/Perspex.SceneGraph/Matrix.cs
  4. 69
      src/Perspex.SceneGraph/Media/ScaleTransform.cs
  5. 1
      src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
  6. 1
      src/Perspex.Themes.Default/DefaultTheme.xaml
  7. 12
      src/Perspex.Themes.Default/LayoutTransformControl.xaml
  8. 5
      src/Perspex.Themes.Default/Perspex.Themes.Default.csproj
  9. 245
      tests/Perspex.Controls.UnitTests/LayoutTransformControlTests.cs
  10. 1
      tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj

389
src/Perspex.Controls/LayoutTransformControl.cs

@ -0,0 +1,389 @@
// 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.
//
// Idea got from and adapted to work in perspex
// http://silverlight.codeplex.com/SourceControl/changeset/view/74775#Release/Silverlight4/Source/Controls.Layout.Toolkit/LayoutTransformer/LayoutTransformer.cs
//
using Perspex.Controls.Primitives;
using Perspex.Media;
using Perspex.VisualTree;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reactive.Linq;
namespace Perspex.Controls
{
public class LayoutTransformControl : ContentControl
{
public static readonly PerspexProperty<Transform> LayoutTransformProperty =
PerspexProperty.Register<LayoutTransformControl, Transform>(nameof(LayoutTransform));
static LayoutTransformControl()
{
LayoutTransformProperty.Changed
.AddClassHandler<LayoutTransformControl>(x => x.OnLayoutTransformChanged);
}
public Transform LayoutTransform
{
get { return GetValue(LayoutTransformProperty); }
set { SetValue(LayoutTransformProperty, value); }
}
public Control TransformRoot => _transformRoot ??
(_transformRoot = this.GetVisualChildren().OfType<Control>().FirstOrDefault());
/// <summary>
/// Provides the behavior for the "Arrange" pass of layout.
/// </summary>
/// <param name="finalSize">The final area within the parent that this element should use to arrange itself and its children.</param>
/// <returns>The actual size used.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
if (TransformRoot == null || LayoutTransform == null)
{
return base.ArrangeOverride(finalSize);
}
// Determine the largest available size after the transformation
Size finalSizeTransformed = ComputeLargestTransformedSize(finalSize);
if (IsSizeSmaller(finalSizeTransformed, TransformRoot.DesiredSize))
{
// Some elements do not like being given less space than they asked for (ex: TextBlock)
// Bump the working size up to do the right thing by them
finalSizeTransformed = TransformRoot.DesiredSize;
}
// Transform the working size to find its width/height
Rect transformedRect = new Rect(0, 0, finalSizeTransformed.Width, finalSizeTransformed.Height).TransformToAABB(_transformation);
// Create the Arrange rect to center the transformed content
Rect finalRect = new Rect(
-transformedRect.X + ((finalSize.Width - transformedRect.Width) / 2),
-transformedRect.Y + ((finalSize.Height - transformedRect.Height) / 2),
finalSizeTransformed.Width,
finalSizeTransformed.Height);
// Perform an Arrange on TransformRoot (containing Child)
Size arrangedsize;
TransformRoot.Arrange(finalRect);
arrangedsize = TransformRoot.Bounds.Size;
// This is the first opportunity under Silverlight to find out the Child's true DesiredSize
if (IsSizeSmaller(finalSizeTransformed, arrangedsize) && (Size.Empty == _childActualSize))
{
//// Unfortunately, all the work so far is invalid because the wrong DesiredSize was used
//// Make a note of the actual DesiredSize
//_childActualSize = arrangedsize;
//// Force a new measure/arrange pass
//InvalidateMeasure();
}
else
{
// Clear the "need to measure/arrange again" flag
_childActualSize = Size.Empty;
}
// Return result to perform the transformation
return finalSize;
}
/// <summary>
/// Provides the behavior for the "Measure" pass of layout.
/// </summary>
/// <param name="availableSize">The available size that this element can give to child elements.</param>
/// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns>
protected override Size MeasureOverride(Size availableSize)
{
if (TransformRoot == null || LayoutTransform == null)
{
return base.MeasureOverride(availableSize);
}
Size measureSize;
if (_childActualSize == Size.Empty)
{
// Determine the largest size after the transformation
measureSize = ComputeLargestTransformedSize(availableSize);
}
else
{
// Previous measure/arrange pass determined that Child.DesiredSize was larger than believed
measureSize = _childActualSize;
}
// Perform a measure on the TransformRoot (containing Child)
TransformRoot.Measure(measureSize);
var desiredSize = TransformRoot.DesiredSize;
// Transform DesiredSize to find its width/height
Rect transformedDesiredRect = new Rect(0, 0, desiredSize.Width, desiredSize.Height).TransformToAABB(_transformation);
Size transformedDesiredSize = new Size(transformedDesiredRect.Width, transformedDesiredRect.Height);
// Return result to allocate enough space for the transformation
return transformedDesiredSize;
}
/// <summary>
/// Builds the visual tree for the LayoutTransformerControl when a new
/// template is applied.
/// </summary>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
_matrixTransform = new MatrixTransform();
if (null != TransformRoot)
{
TransformRoot.RenderTransform = _matrixTransform;
TransformRoot.TransformOrigin = new RelativePoint(0, 0, RelativeUnit.Absolute);
}
ApplyLayoutTransform();
}
/// <summary>
/// Acceptable difference between two doubles.
/// </summary>
private const double AcceptableDelta = 0.0001;
/// <summary>
/// Number of decimals to round the Matrix to.
/// </summary>
private const int DecimalsAfterRound = 4;
/// <summary>
/// Actual DesiredSize of Child element (the value it returned from its MeasureOverride method).
/// </summary>
private Size _childActualSize = Size.Empty;
/// <summary>
/// RenderTransform/MatrixTransform applied to TransformRoot.
/// </summary>
private MatrixTransform _matrixTransform;
/// <summary>
/// Transformation matrix corresponding to _matrixTransform.
/// </summary>
private Matrix _transformation;
private IDisposable _transformChangedEvent = null;
private Control _transformRoot;
/// <summary>
/// Returns true if Size a is smaller than Size b in either dimension.
/// </summary>
/// <param name="a">Second Size.</param>
/// <param name="b">First Size.</param>
/// <returns>True if Size a is smaller than Size b in either dimension.</returns>
private static bool IsSizeSmaller(Size a, Size b)
{
return (a.Width + AcceptableDelta < b.Width) || (a.Height + AcceptableDelta < b.Height);
}
/// <summary>
/// Rounds the non-offset elements of a Matrix to avoid issues due to floating point imprecision.
/// </summary>
/// <param name="matrix">Matrix to round.</param>
/// <param name="decimals">Number of decimal places to round to.</param>
/// <returns>Rounded Matrix.</returns>
private static Matrix RoundMatrix(Matrix matrix, int decimals)
{
return new Matrix(
Math.Round(matrix.M11, decimals),
Math.Round(matrix.M12, decimals),
Math.Round(matrix.M21, decimals),
Math.Round(matrix.M22, decimals),
matrix.M31,
matrix.M32);
}
/// <summary>
/// Applies the layout transform on the LayoutTransformerControl content.
/// </summary>
/// <remarks>
/// Only used in advanced scenarios (like animating the LayoutTransform).
/// Should be used to notify the LayoutTransformer control that some aspect
/// of its Transform property has changed.
/// </remarks>
private void ApplyLayoutTransform()
{
if (LayoutTransform == null) return;
// Get the transform matrix and apply it
_transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound);
if (null != _matrixTransform)
{
_matrixTransform.Matrix = _transformation;
}
// New transform means re-layout is necessary
InvalidateMeasure();
}
/// <summary>
/// Compute the largest usable size (greatest area) after applying the transformation to the specified bounds.
/// </summary>
/// <param name="arrangeBounds">Arrange bounds.</param>
/// <returns>Largest Size possible.</returns>
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Closely corresponds to WPF's FrameworkElement.FindMaximalAreaLocalSpaceRect.")]
private Size ComputeLargestTransformedSize(Size arrangeBounds)
{
// Computed largest transformed size
Size computedSize = Size.Empty;
// Detect infinite bounds and constrain the scenario
bool infiniteWidth = double.IsInfinity(arrangeBounds.Width);
if (infiniteWidth)
{
// arrangeBounds.Width = arrangeBounds.Height;
arrangeBounds = arrangeBounds.WithWidth(arrangeBounds.Height);
}
bool infiniteHeight = double.IsInfinity(arrangeBounds.Height);
if (infiniteHeight)
{
//arrangeBounds.Height = arrangeBounds.Width;
arrangeBounds = arrangeBounds.WithHeight(arrangeBounds.Width);
}
// Capture the matrix parameters
double a = _transformation.M11;
double b = _transformation.M12;
double c = _transformation.M21;
double d = _transformation.M22;
// Compute maximum possible transformed width/height based on starting width/height
// These constraints define two lines in the positive x/y quadrant
double maxWidthFromWidth = Math.Abs(arrangeBounds.Width / a);
double maxHeightFromWidth = Math.Abs(arrangeBounds.Width / c);
double maxWidthFromHeight = Math.Abs(arrangeBounds.Height / b);
double maxHeightFromHeight = Math.Abs(arrangeBounds.Height / d);
// The transformed width/height that maximize the area under each segment is its midpoint
// At most one of the two midpoints will satisfy both constraints
double idealWidthFromWidth = maxWidthFromWidth / 2;
double idealHeightFromWidth = maxHeightFromWidth / 2;
double idealWidthFromHeight = maxWidthFromHeight / 2;
double idealHeightFromHeight = maxHeightFromHeight / 2;
// Compute slope of both constraint lines
double slopeFromWidth = -(maxHeightFromWidth / maxWidthFromWidth);
double slopeFromHeight = -(maxHeightFromHeight / maxWidthFromHeight);
if ((0 == arrangeBounds.Width) || (0 == arrangeBounds.Height))
{
// Check for empty bounds
computedSize = new Size(arrangeBounds.Width, arrangeBounds.Height);
}
else if (infiniteWidth && infiniteHeight)
{
// Check for completely unbound scenario
computedSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
}
else if (!_transformation.HasInverse)
{
// Check for singular matrix
computedSize = new Size(0, 0);
}
else if ((0 == b) || (0 == c))
{
// Check for 0/180 degree special cases
double maxHeight = (infiniteHeight ? double.PositiveInfinity : maxHeightFromHeight);
double maxWidth = (infiniteWidth ? double.PositiveInfinity : maxWidthFromWidth);
if ((0 == b) && (0 == c))
{
// No constraints
computedSize = new Size(maxWidth, maxHeight);
}
else if (0 == b)
{
// Constrained by width
double computedHeight = Math.Min(idealHeightFromWidth, maxHeight);
computedSize = new Size(
maxWidth - Math.Abs((c * computedHeight) / a),
computedHeight);
}
else if (0 == c)
{
// Constrained by height
double computedWidth = Math.Min(idealWidthFromHeight, maxWidth);
computedSize = new Size(
computedWidth,
maxHeight - Math.Abs((b * computedWidth) / d));
}
}
else if ((0 == a) || (0 == d))
{
// Check for 90/270 degree special cases
double maxWidth = (infiniteHeight ? double.PositiveInfinity : maxWidthFromHeight);
double maxHeight = (infiniteWidth ? double.PositiveInfinity : maxHeightFromWidth);
if ((0 == a) && (0 == d))
{
// No constraints
computedSize = new Size(maxWidth, maxHeight);
}
else if (0 == a)
{
// Constrained by width
double computedHeight = Math.Min(idealHeightFromHeight, maxHeight);
computedSize = new Size(
maxWidth - Math.Abs((d * computedHeight) / b),
computedHeight);
}
else if (0 == d)
{
// Constrained by height
double computedWidth = Math.Min(idealWidthFromWidth, maxWidth);
computedSize = new Size(
computedWidth,
maxHeight - Math.Abs((a * computedWidth) / c));
}
}
else if (idealHeightFromWidth <= ((slopeFromHeight * idealWidthFromWidth) + maxHeightFromHeight))
{
// Check the width midpoint for viability (by being below the height constraint line)
computedSize = new Size(idealWidthFromWidth, idealHeightFromWidth);
}
else if (idealHeightFromHeight <= ((slopeFromWidth * idealWidthFromHeight) + maxHeightFromWidth))
{
// Check the height midpoint for viability (by being below the width constraint line)
computedSize = new Size(idealWidthFromHeight, idealHeightFromHeight);
}
else
{
// Neither midpoint is viable; use the intersection of the two constraint lines instead
// Compute width by setting heights equal (m1*x+c1=m2*x+c2)
double computedWidth = (maxHeightFromHeight - maxHeightFromWidth) / (slopeFromWidth - slopeFromHeight);
// Compute height from width constraint line (y=m*x+c; using height would give same result)
computedSize = new Size(
computedWidth,
(slopeFromWidth * computedWidth) + maxHeightFromWidth);
}
// Return result
return computedSize;
}
private void OnLayoutTransformChanged(PerspexPropertyChangedEventArgs e)
{
var newTransform = e.NewValue as Transform;
if (_transformChangedEvent != null)
{
_transformChangedEvent.Dispose();
_transformChangedEvent = null;
}
if (newTransform != null)
{
_transformChangedEvent = Observable.FromEventPattern<EventHandler, EventArgs>(
v => newTransform.Changed += v, v => newTransform.Changed -= v)
.Subscribe(onNext: v => ApplyLayoutTransform());
}
ApplyLayoutTransform();
}
}
}

1
src/Perspex.Controls/Perspex.Controls.csproj

@ -54,6 +54,7 @@
<Compile Include="INameScope.cs" />
<Compile Include="IPseudoClasses.cs" />
<Compile Include="DropDownItem.cs" />
<Compile Include="LayoutTransformControl.cs" />
<Compile Include="Mixins\ContentControlMixin.cs" />
<Compile Include="NameScope.cs" />
<Compile Include="NameScopeEventArgs.cs" />

6
src/Perspex.SceneGraph/Matrix.cs

@ -53,6 +53,11 @@ namespace Perspex
/// </summary>
public bool IsIdentity => Equals(Identity);
/// <summary>
/// HasInverse Property - returns true if this matrix is invertable, false otherwise.
/// </summary>
public bool HasInverse => GetDeterminant() != 0;
/// <summary>
/// The first element of the first row
/// </summary>
@ -209,6 +214,7 @@ namespace Perspex
return (_m11 * _m22) - (_m12 * _m21);
}
/// <summary>
/// Returns a boolean indicating whether the matrix is equal to the other given matrix.
/// </summary>

69
src/Perspex.SceneGraph/Media/ScaleTransform.cs

@ -0,0 +1,69 @@
// 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;
namespace Perspex.Media
{
/// <summary>
/// Scale an <see cref="IVisual"/>.
/// </summary>
public class ScaleTransform : Transform
{
/// <summary>
/// Defines the <see cref="ScaleX"/> property.
/// </summary>
public static readonly StyledProperty<double> ScaleXProperty =
PerspexProperty.Register<ScaleTransform, double>(nameof(ScaleX), 1);
/// <summary>
/// Defines the <see cref="ScaleY"/> property.
/// </summary>
public static readonly StyledProperty<double> ScaleYProperty =
PerspexProperty.Register<ScaleTransform, double>(nameof(ScaleY), 1);
/// <summary>
/// Initializes a new instance of the <see cref="ScaleTransform"/> class.
/// </summary>
public ScaleTransform()
{
this.GetObservable(ScaleXProperty).Subscribe(_ => RaiseChanged());
this.GetObservable(ScaleYProperty).Subscribe(_ => RaiseChanged());
}
/// <summary>
/// Initializes a new instance of the <see cref="ScaleTransform"/> class.
/// </summary>
/// <param name="scaleX">ScaleX</param>
/// <param name="scaleY">ScaleY</param>
public ScaleTransform(double scaleX, double scaleY)
: this()
{
ScaleX = scaleX;
ScaleY = scaleY;
}
/// <summary>
/// Gets or sets the ScaleX property.
/// </summary>
public double ScaleX
{
get { return GetValue(ScaleXProperty); }
set { SetValue(ScaleXProperty, value); }
}
/// <summary>
/// Gets or sets the ScaleY property.
/// </summary>
public double ScaleY
{
get { return GetValue(ScaleYProperty); }
set { SetValue(ScaleYProperty, value); }
}
/// <summary>
/// Gets the tranform's <see cref="Matrix"/>.
/// </summary>
public override Matrix Value => Matrix.CreateScale(ScaleX, ScaleY);
}
}

1
src/Perspex.SceneGraph/Perspex.SceneGraph.csproj

@ -80,6 +80,7 @@
<Compile Include="Media\LinearGradientBrush.cs" />
<Compile Include="Media\MediaExtensions.cs" />
<Compile Include="Media\PenLineCap.cs" />
<Compile Include="Media\ScaleTransform.cs" />
<Compile Include="Media\TextAlignment.cs" />
<Compile Include="Media\FontWeight.cs" />
<Compile Include="Media\FontStyle.cs" />

1
src/Perspex.Themes.Default/DefaultTheme.xaml

@ -8,6 +8,7 @@
<StyleInclude Source="resm:Perspex.Themes.Default.DropDownItem.xaml?assembly=Perspex.Themes.Default"/>
<StyleInclude Source="resm:Perspex.Themes.Default.GridSplitter.xaml?assembly=Perspex.Themes.Default"/>
<StyleInclude Source="resm:Perspex.Themes.Default.ItemsControl.xaml?assembly=Perspex.Themes.Default"/>
<StyleInclude Source="resm:Perspex.Themes.Default.LayoutTransformControl.xaml?assembly=Perspex.Themes.Default"/>
<StyleInclude Source="resm:Perspex.Themes.Default.ListBox.xaml?assembly=Perspex.Themes.Default"/>
<StyleInclude Source="resm:Perspex.Themes.Default.ListBoxItem.xaml?assembly=Perspex.Themes.Default"/>
<StyleInclude Source="resm:Perspex.Themes.Default.Menu.xaml?assembly=Perspex.Themes.Default"/>

12
src/Perspex.Themes.Default/LayoutTransformControl.xaml

@ -0,0 +1,12 @@
<Style xmlns="https://github.com/perspex" Selector="LayoutTransformControl">
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>
</Style>

5
src/Perspex.Themes.Default/Perspex.Themes.Default.csproj

@ -200,6 +200,11 @@
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="LayoutTransformControl.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.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.

245
tests/Perspex.Controls.UnitTests/LayoutTransformControlTests.cs

@ -0,0 +1,245 @@
using Perspex.Controls.Presenters;
using Perspex.Controls.Shapes;
using Perspex.Controls.Templates;
using Perspex.Media;
using Xunit;
namespace Perspex.Controls.UnitTests
{
public class LayoutTransformControlTests
{
[Fact]
public void Measure_On_Scale_x2_Is_Correct()
{
double scale = 2;
TransformMeasureSizeTest(
new Size(100, 50),
new ScaleTransform() { ScaleX = scale, ScaleY = scale },
new Size(200, 100));
}
[Fact]
public void Measure_On_Scale_x0_5_Is_Correct()
{
double scale = 0.5;
TransformMeasureSizeTest(
new Size(100, 50),
new ScaleTransform() { ScaleX = scale, ScaleY = scale },
new Size(50, 25));
}
[Fact]
public void Measure_On_Rotate_90_degrees_Is_Correct()
{
TransformMeasureSizeTest(
new Size(100, 25),
new RotateTransform() { Angle = 90 },
new Size(25, 100));
}
[Fact]
public void Measure_On_Rotate_minus_90_degrees_Is_Correct()
{
TransformMeasureSizeTest(
new Size(100, 25),
new RotateTransform() { Angle = -90 },
new Size(25, 100));
}
[Fact]
public void Measure_On_Rotate_0_degrees_Is_Correct()
{
TransformMeasureSizeTest(
new Size(100, 25),
new RotateTransform() { Angle = 0 },
new Size(100, 25));
}
[Fact]
public void Measure_On_Rotate_180_degrees_Is_Correct()
{
TransformMeasureSizeTest(
new Size(100, 25),
new RotateTransform() { Angle = 180 },
new Size(100, 25));
}
[Fact]
public void Bounds_On_Scale_x2_Are_correct()
{
double scale = 2;
TransformRootBoundsTest(
new Size(100, 50),
new ScaleTransform() { ScaleX = scale, ScaleY = scale },
new Rect(0, 0, 100, 50));
}
[Fact]
public void Bounds_On_Scale_x0_5_Are_correct()
{
double scale = 0.5;
TransformRootBoundsTest(
new Size(100, 50),
new ScaleTransform() { ScaleX = scale, ScaleY = scale },
new Rect(0, 0, 100, 50));
}
[Fact]
public void Bounds_On_Rotate_180_degrees_Are_correct()
{
TransformRootBoundsTest(
new Size(100, 25),
new RotateTransform() { Angle = 180 },
new Rect(100, 25, 100, 25));
}
[Fact]
public void Bounds_On_Rotate_0_degrees_Are_correct()
{
TransformRootBoundsTest(
new Size(100, 25),
new RotateTransform() { Angle = 0 },
new Rect(0, 0, 100, 25));
}
[Fact]
public void Bounds_On_Rotate_90_degrees_Are_correct()
{
TransformRootBoundsTest(
new Size(100, 25),
new RotateTransform() { Angle = 90 },
new Rect(25, 0, 100, 25));
}
[Fact]
public void Bounds_On_Rotate_minus_90_degrees_Are_correct()
{
TransformRootBoundsTest(
new Size(100, 25),
new RotateTransform() { Angle = -90 },
new Rect(0, 100, 100, 25));
}
[Fact]
public void Should_Generate_RenderTransform_90_degrees()
{
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(
100,
25,
new RotateTransform() { Angle = 90 });
Assert.NotNull(lt.TransformRoot.RenderTransform);
Matrix m = lt.TransformRoot.RenderTransform.Value;
Matrix res = Matrix.CreateRotation(Matrix.ToRadians(90));
Assert.Equal(m.M11, res.M11, 3);
Assert.Equal(m.M12, res.M12, 3);
Assert.Equal(m.M21, res.M21, 3);
Assert.Equal(m.M22, res.M22, 3);
Assert.Equal(m.M31, res.M31, 3);
Assert.Equal(m.M32, res.M32, 3);
}
[Fact]
public void Should_Generate_RenderTransform_minus_90_degrees()
{
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(
100,
25,
new RotateTransform() { Angle = -90 });
Assert.NotNull(lt.TransformRoot.RenderTransform);
var m = lt.TransformRoot.RenderTransform.Value;
var res = Matrix.CreateRotation(Matrix.ToRadians(-90));
Assert.Equal(m.M11, res.M11, 3);
Assert.Equal(m.M12, res.M12, 3);
Assert.Equal(m.M21, res.M21, 3);
Assert.Equal(m.M22, res.M22, 3);
Assert.Equal(m.M31, res.M31, 3);
Assert.Equal(m.M32, res.M32, 3);
}
[Fact]
public void Should_Generate_ScaleTransform_x2()
{
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(
100,
50,
new ScaleTransform() { ScaleX = 2, ScaleY = 2 });
Assert.NotNull(lt.TransformRoot.RenderTransform);
Matrix m = lt.TransformRoot.RenderTransform.Value;
Matrix res = Matrix.CreateScale(2, 2);
Assert.Equal(m.M11, res.M11, 3);
Assert.Equal(m.M12, res.M12, 3);
Assert.Equal(m.M21, res.M21, 3);
Assert.Equal(m.M22, res.M22, 3);
Assert.Equal(m.M31, res.M31, 3);
Assert.Equal(m.M32, res.M32, 3);
}
private static void TransformMeasureSizeTest(Size size, Transform transform, Size expectedSize)
{
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(
size.Width,
size.Height,
transform);
Size outSize = lt.DesiredSize;
Assert.Equal(outSize.Width, expectedSize.Width);
Assert.Equal(outSize.Height, expectedSize.Height);
}
private static void TransformRootBoundsTest(Size size, Transform transform, Rect expectedBounds)
{
LayoutTransformControl lt = CreateWithChildAndMeasureAndTransform(size.Width, size.Height, transform);
Rect outBounds = lt.TransformRoot.Bounds;
Assert.Equal(outBounds.X, expectedBounds.X);
Assert.Equal(outBounds.Y, expectedBounds.Y);
Assert.Equal(outBounds.Width, expectedBounds.Width);
Assert.Equal(outBounds.Height, expectedBounds.Height);
}
private static LayoutTransformControl CreateWithChildAndMeasureAndTransform(
double width,
double height,
Transform transform)
{
var lt = new LayoutTransformControl()
{
LayoutTransform = transform,
Template = new FuncControlTemplate<LayoutTransformControl>(
p => new ContentPresenter() { Content = p.Content })
};
lt.Content = new Rectangle() { Width = width, Height = height };
lt.ApplyTemplate();
//we need to force create visual child
//so the measure after is correct
(lt.Presenter as ContentPresenter).UpdateChild();
Assert.NotNull(lt.Presenter?.Child);
lt.Measure(Size.Infinity);
lt.Arrange(new Rect(lt.DesiredSize));
return lt;
}
}
}

1
tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj

@ -90,6 +90,7 @@
</Choose>
<ItemGroup>
<Compile Include="ClassesTests.cs" />
<Compile Include="LayoutTransformControlTests.cs" />
<Compile Include="UserControlTests.cs" />
<Compile Include="DockPanelTests.cs" />
<Compile Include="EnumerableExtensions.cs" />

Loading…
Cancel
Save