From b26573656a5fdd2b87b0096181b4a15cdacee5eb Mon Sep 17 00:00:00 2001 From: donandren Date: Wed, 9 Mar 2016 15:56:40 +0200 Subject: [PATCH 1/8] HasInverse Property property of Matrix --- src/Perspex.SceneGraph/Matrix.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Perspex.SceneGraph/Matrix.cs b/src/Perspex.SceneGraph/Matrix.cs index baf38fb7679..121ad1667d9 100644 --- a/src/Perspex.SceneGraph/Matrix.cs +++ b/src/Perspex.SceneGraph/Matrix.cs @@ -53,6 +53,11 @@ public Matrix( /// public bool IsIdentity => Equals(Identity); + /// + /// HasInverse Property - returns true if this matrix is invertable, false otherwise. + /// + public bool HasInverse => GetDeterminant() != 0; + /// /// The first element of the first row /// @@ -209,6 +214,7 @@ public double GetDeterminant() return (_m11 * _m22) - (_m12 * _m21); } + /// /// Returns a boolean indicating whether the matrix is equal to the other given matrix. /// From d36aae900cb565dba415510173a8bd85a2f3a4db Mon Sep 17 00:00:00 2001 From: donandren Date: Wed, 9 Mar 2016 16:00:20 +0200 Subject: [PATCH 2/8] LayoutTransformControll added --- .../LayoutTransformControl.cs | 407 ++++++++++++++++++ src/Perspex.Controls/Perspex.Controls.csproj | 1 + 2 files changed, 408 insertions(+) create mode 100644 src/Perspex.Controls/LayoutTransformControl.cs diff --git a/src/Perspex.Controls/LayoutTransformControl.cs b/src/Perspex.Controls/LayoutTransformControl.cs new file mode 100644 index 00000000000..f92437ad037 --- /dev/null +++ b/src/Perspex.Controls/LayoutTransformControl.cs @@ -0,0 +1,407 @@ +// 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.Presenters; +using Perspex.Controls.Primitives; +using Perspex.Controls.Templates; +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 LayoutTransformProperty = + PerspexProperty.Register(nameof(LayoutTransform)); + + static LayoutTransformControl() + { + LayoutTransformProperty.Changed + .AddClassHandler(x => x.OnLayoutTransformChanged); + TemplateProperty.OverrideDefaultValue(_defaultTemplate); + } + + public Transform LayoutTransform + { + get { return GetValue(LayoutTransformProperty); } + set { SetValue(LayoutTransformProperty, value); } + } + + /// + /// Acceptable difference between two doubles. + /// + private const double AcceptableDelta = 0.0001; + + /// + /// Number of decimals to round the Matrix to. + /// + private const int DecimalsAfterRound = 4; + + private static readonly FuncControlTemplate _defaultTemplate = + new FuncControlTemplate(control => + { + return new Decorator() + { + Child = new ContentPresenter() + { + [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty] + } + }; + }); + + /// + /// RenderTransform/MatrixTransform applied to TransformRoot. + /// + private MatrixTransform _matrixTransform; + + /// + /// Transformation matrix corresponding to _matrixTransform. + /// + private Matrix _transformation; + + /// + /// Actual DesiredSize of Child element (the value it returned from its MeasureOverride method). + /// + private Size _childActualSize = Size.Empty; + + private Control _transformRoot; + + public Control TransformRoot => _transformRoot ?? + (_transformRoot = this.GetVisualChildren().OfType().FirstOrDefault()); + + private IDisposable _transformChangedEvent = null; + + private void OnLayoutTransformChanged(PerspexPropertyChangedEventArgs e) + { + var newTransform = e.NewValue as Transform; + + if (_transformChangedEvent != null) + { + _transformChangedEvent.Dispose(); + _transformChangedEvent = null; + } + + if (newTransform != null) + { + _transformChangedEvent = Observable.FromEventPattern( + v => newTransform.Changed += v, v => newTransform.Changed -= v) + .Subscribe(onNext: v => ApplyLayoutTransform()); + } + + ApplyLayoutTransform(); + } + + /// + /// Builds the visual tree for the LayoutTransformerControl when a new + /// template is applied. + /// + 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(); + } + + /// + /// Applies the layout transform on the LayoutTransformerControl content. + /// + /// + /// 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. + /// + public 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(); + } + + /// + /// Provides the behavior for the "Measure" pass of layout. + /// + /// The available size that this element can give to child elements. + /// The size that this element determines it needs during layout, based on its calculations of child element sizes. + 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; + } + + /// + /// Provides the behavior for the "Arrange" pass of layout. + /// + /// The final area within the parent that this element should use to arrange itself and its children. + /// The actual size used. + 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; + } + + /// + /// Compute the largest usable size (greatest area) after applying the transformation to the specified bounds. + /// + /// Arrange bounds. + /// Largest Size possible. + [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; + } + + /// + /// Returns true if Size a is smaller than Size b in either dimension. + /// + /// Second Size. + /// First Size. + /// True if Size a is smaller than Size b in either dimension. + private static bool IsSizeSmaller(Size a, Size b) + { + return (a.Width + AcceptableDelta < b.Width) || (a.Height + AcceptableDelta < b.Height); + } + + /// + /// Rounds the non-offset elements of a Matrix to avoid issues due to floating point imprecision. + /// + /// Matrix to round. + /// Number of decimal places to round to. + /// Rounded Matrix. + 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); + } + } +} \ No newline at end of file diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index ee90633945a..c249a901f6a 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -54,6 +54,7 @@ + From 454d1b744cf6b468e9590ecf58ff88b9253353b3 Mon Sep 17 00:00:00 2001 From: donandren Date: Wed, 9 Mar 2016 16:12:39 +0200 Subject: [PATCH 3/8] ScaleTransform Added --- .../Media/ScaleTransform.cs | 69 +++++++++++++++++++ .../Perspex.SceneGraph.csproj | 1 + 2 files changed, 70 insertions(+) create mode 100644 src/Perspex.SceneGraph/Media/ScaleTransform.cs diff --git a/src/Perspex.SceneGraph/Media/ScaleTransform.cs b/src/Perspex.SceneGraph/Media/ScaleTransform.cs new file mode 100644 index 00000000000..9e0f1ecb3a6 --- /dev/null +++ b/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 +{ + /// + /// Scale an . + /// + public class ScaleTransform : Transform + { + /// + /// Defines the property. + /// + public static readonly StyledProperty ScaleXProperty = + PerspexProperty.Register(nameof(ScaleX), 1); + + /// + /// Defines the property. + /// + public static readonly StyledProperty ScaleYProperty = + PerspexProperty.Register(nameof(ScaleY), 1); + + /// + /// Initializes a new instance of the class. + /// + public ScaleTransform() + { + this.GetObservable(ScaleXProperty).Subscribe(_ => RaiseChanged()); + this.GetObservable(ScaleYProperty).Subscribe(_ => RaiseChanged()); + } + + /// + /// Initializes a new instance of the class. + /// + /// ScaleX + /// ScaleY + public ScaleTransform(double scaleX, double scaleY) + : this() + { + ScaleX = scaleX; + ScaleY = scaleY; + } + + /// + /// Gets or sets the ScaleX property. + /// + public double ScaleX + { + get { return GetValue(ScaleXProperty); } + set { SetValue(ScaleXProperty, value); } + } + + /// + /// Gets or sets the ScaleY property. + /// + public double ScaleY + { + get { return GetValue(ScaleYProperty); } + set { SetValue(ScaleYProperty, value); } + } + + /// + /// Gets the tranform's . + /// + public override Matrix Value => Matrix.CreateScale(ScaleX, ScaleY); + } +} \ No newline at end of file diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj index 17ee3e98dbc..be199ab423c 100644 --- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -80,6 +80,7 @@ + From 4e2513b3ffc9f2a3e8f8f242f938b86857cf8d6a Mon Sep 17 00:00:00 2001 From: donandren Date: Wed, 9 Mar 2016 16:16:50 +0200 Subject: [PATCH 4/8] LayoutTransformControl Unit tests --- .../LayoutTransformControlTests.cs | 248 ++++++++++++++++++ .../Perspex.Controls.UnitTests.csproj | 1 + 2 files changed, 249 insertions(+) create mode 100644 tests/Perspex.Controls.UnitTests/LayoutTransformControlTests.cs diff --git a/tests/Perspex.Controls.UnitTests/LayoutTransformControlTests.cs b/tests/Perspex.Controls.UnitTests/LayoutTransformControlTests.cs new file mode 100644 index 00000000000..946fb9e8b9e --- /dev/null +++ b/tests/Perspex.Controls.UnitTests/LayoutTransformControlTests.cs @@ -0,0 +1,248 @@ +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( + p => + { + var c = new ContentPresenter() { Content = p.Content }; + //we need to force create visual child + //so the measure after is correct + c.UpdateChild(); + return c; + }) + }; + + lt.Content = new Rectangle() { Width = width, Height = height }; + + lt.ApplyTemplate(); + + Assert.NotNull(lt.Presenter?.Child); + + lt.Measure(Size.Infinity); + lt.Arrange(new Rect(lt.DesiredSize)); + + return lt; + } + } +} \ No newline at end of file diff --git a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index 57a6b16868c..2888640b07b 100644 --- a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -90,6 +90,7 @@ + From e7363e5fd818d60ff8d3ec6fb6b37676419e3b46 Mon Sep 17 00:00:00 2001 From: donandren Date: Wed, 9 Mar 2016 21:12:03 +0200 Subject: [PATCH 5/8] ApplyLayoutTransform made private --- src/Perspex.Controls/LayoutTransformControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perspex.Controls/LayoutTransformControl.cs b/src/Perspex.Controls/LayoutTransformControl.cs index f92437ad037..cb865c4cbb4 100644 --- a/src/Perspex.Controls/LayoutTransformControl.cs +++ b/src/Perspex.Controls/LayoutTransformControl.cs @@ -126,7 +126,7 @@ protected override void OnTemplateApplied(TemplateAppliedEventArgs e) /// Should be used to notify the LayoutTransformer control that some aspect /// of its Transform property has changed. /// - public void ApplyLayoutTransform() + private void ApplyLayoutTransform() { if (LayoutTransform == null) return; From 1b8e0faa7f8338ba9a9de63a6e53fb51f0450b59 Mon Sep 17 00:00:00 2001 From: donandren Date: Wed, 9 Mar 2016 21:26:53 +0200 Subject: [PATCH 6/8] Default template in code of LayoutTransformControl removed and added default template in theme --- src/Perspex.Controls/LayoutTransformControl.cs | 13 ------------- src/Perspex.Themes.Default/DefaultTheme.xaml | 1 + .../LayoutTransformControl.xaml | 12 ++++++++++++ .../Perspex.Themes.Default.csproj | 5 +++++ 4 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 src/Perspex.Themes.Default/LayoutTransformControl.xaml diff --git a/src/Perspex.Controls/LayoutTransformControl.cs b/src/Perspex.Controls/LayoutTransformControl.cs index cb865c4cbb4..49169b58959 100644 --- a/src/Perspex.Controls/LayoutTransformControl.cs +++ b/src/Perspex.Controls/LayoutTransformControl.cs @@ -26,7 +26,6 @@ static LayoutTransformControl() { LayoutTransformProperty.Changed .AddClassHandler(x => x.OnLayoutTransformChanged); - TemplateProperty.OverrideDefaultValue(_defaultTemplate); } public Transform LayoutTransform @@ -45,18 +44,6 @@ public Transform LayoutTransform /// private const int DecimalsAfterRound = 4; - private static readonly FuncControlTemplate _defaultTemplate = - new FuncControlTemplate(control => - { - return new Decorator() - { - Child = new ContentPresenter() - { - [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty] - } - }; - }); - /// /// RenderTransform/MatrixTransform applied to TransformRoot. /// diff --git a/src/Perspex.Themes.Default/DefaultTheme.xaml b/src/Perspex.Themes.Default/DefaultTheme.xaml index b25c85bbd1b..f536a523f37 100644 --- a/src/Perspex.Themes.Default/DefaultTheme.xaml +++ b/src/Perspex.Themes.Default/DefaultTheme.xaml @@ -8,6 +8,7 @@ + diff --git a/src/Perspex.Themes.Default/LayoutTransformControl.xaml b/src/Perspex.Themes.Default/LayoutTransformControl.xaml new file mode 100644 index 00000000000..e393eedf837 --- /dev/null +++ b/src/Perspex.Themes.Default/LayoutTransformControl.xaml @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj index e954fd04194..c26bb74ef0e 100644 --- a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj +++ b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj @@ -200,6 +200,11 @@ Designer + + + Designer + +