10 changed files with 730 additions and 0 deletions
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue