|
|
|
@ -9,7 +9,7 @@ using Perspex.Controls.Primitives; |
|
|
|
using Perspex.Media; |
|
|
|
using Perspex.VisualTree; |
|
|
|
using System; |
|
|
|
//using System.Diagnostics.CodeAnalysis;
|
|
|
|
using System.Diagnostics.CodeAnalysis; |
|
|
|
using System.Linq; |
|
|
|
using System.Reactive.Linq; |
|
|
|
|
|
|
|
@ -32,99 +32,61 @@ namespace Perspex.Controls |
|
|
|
set { SetValue(LayoutTransformProperty, value); } |
|
|
|
} |
|
|
|
|
|
|
|
/// <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>
|
|
|
|
/// RenderTransform/MatrixTransform applied to TransformRoot.
|
|
|
|
/// </summary>
|
|
|
|
private MatrixTransform _matrixTransform; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Transformation matrix corresponding to _matrixTransform.
|
|
|
|
/// </summary>
|
|
|
|
private Matrix _transformation; |
|
|
|
public Control TransformRoot => _transformRoot ?? |
|
|
|
(_transformRoot = this.GetVisualChildren().OfType<Control>().FirstOrDefault()); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Actual DesiredSize of Child element (the value it returned from its MeasureOverride method).
|
|
|
|
/// Provides the behavior for the "Arrange" pass of layout.
|
|
|
|
/// </summary>
|
|
|
|
private Size _childActualSize = Size.Empty; |
|
|
|
|
|
|
|
private Control _transformRoot; |
|
|
|
|
|
|
|
public Control TransformRoot => _transformRoot ?? |
|
|
|
(_transformRoot = this.GetVisualChildren().OfType<Control>().FirstOrDefault()); |
|
|
|
|
|
|
|
private IDisposable _transformChangedEvent = null; |
|
|
|
|
|
|
|
private void OnLayoutTransformChanged(PerspexPropertyChangedEventArgs e) |
|
|
|
/// <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) |
|
|
|
{ |
|
|
|
var newTransform = e.NewValue as Transform; |
|
|
|
|
|
|
|
if (_transformChangedEvent != null) |
|
|
|
if (TransformRoot == null || LayoutTransform == null) |
|
|
|
{ |
|
|
|
_transformChangedEvent.Dispose(); |
|
|
|
_transformChangedEvent = null; |
|
|
|
return base.ArrangeOverride(finalSize); |
|
|
|
} |
|
|
|
|
|
|
|
if (newTransform != null) |
|
|
|
// Determine the largest available size after the transformation
|
|
|
|
Size finalSizeTransformed = ComputeLargestTransformedSize(finalSize); |
|
|
|
if (IsSizeSmaller(finalSizeTransformed, TransformRoot.DesiredSize)) |
|
|
|
{ |
|
|
|
_transformChangedEvent = Observable.FromEventPattern<EventHandler, EventArgs>( |
|
|
|
v => newTransform.Changed += v, v => newTransform.Changed -= v) |
|
|
|
.Subscribe(onNext: v => ApplyLayoutTransform()); |
|
|
|
// 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; |
|
|
|
} |
|
|
|
|
|
|
|
ApplyLayoutTransform(); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Builds the visual tree for the LayoutTransformerControl when a new
|
|
|
|
/// template is applied.
|
|
|
|
/// </summary>
|
|
|
|
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) |
|
|
|
{ |
|
|
|
base.OnTemplateApplied(e); |
|
|
|
// 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); |
|
|
|
|
|
|
|
_matrixTransform = new MatrixTransform(); |
|
|
|
// Perform an Arrange on TransformRoot (containing Child)
|
|
|
|
Size arrangedsize; |
|
|
|
TransformRoot.Arrange(finalRect); |
|
|
|
arrangedsize = TransformRoot.Bounds.Size; |
|
|
|
|
|
|
|
if (null != TransformRoot) |
|
|
|
// This is the first opportunity under Silverlight to find out the Child's true DesiredSize
|
|
|
|
if (IsSizeSmaller(finalSizeTransformed, arrangedsize) && (Size.Empty == _childActualSize)) |
|
|
|
{ |
|
|
|
TransformRoot.RenderTransform = _matrixTransform; |
|
|
|
TransformRoot.TransformOrigin = new RelativePoint(0, 0, RelativeUnit.Absolute); |
|
|
|
//// 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();
|
|
|
|
} |
|
|
|
|
|
|
|
ApplyLayoutTransform(); |
|
|
|
} |
|
|
|
|
|
|
|
/// <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) |
|
|
|
else |
|
|
|
{ |
|
|
|
_matrixTransform.Matrix = _transformation; |
|
|
|
// Clear the "need to measure/arrange again" flag
|
|
|
|
_childActualSize = Size.Empty; |
|
|
|
} |
|
|
|
|
|
|
|
// New transform means re-layout is necessary
|
|
|
|
InvalidateMeasure(); |
|
|
|
// Return result to perform the transformation
|
|
|
|
return finalSize; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -165,57 +127,100 @@ namespace Perspex.Controls |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Provides the behavior for the "Arrange" pass of layout.
|
|
|
|
/// Builds the visual tree for the LayoutTransformerControl when a new
|
|
|
|
/// template is applied.
|
|
|
|
/// </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) |
|
|
|
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) |
|
|
|
{ |
|
|
|
if (TransformRoot == null || LayoutTransform == null) |
|
|
|
{ |
|
|
|
return base.ArrangeOverride(finalSize); |
|
|
|
} |
|
|
|
base.OnTemplateApplied(e); |
|
|
|
|
|
|
|
// Determine the largest available size after the transformation
|
|
|
|
Size finalSizeTransformed = ComputeLargestTransformedSize(finalSize); |
|
|
|
if (IsSizeSmaller(finalSizeTransformed, TransformRoot.DesiredSize)) |
|
|
|
_matrixTransform = new MatrixTransform(); |
|
|
|
|
|
|
|
if (null != TransformRoot) |
|
|
|
{ |
|
|
|
// 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; |
|
|
|
TransformRoot.RenderTransform = _matrixTransform; |
|
|
|
TransformRoot.TransformOrigin = new RelativePoint(0, 0, RelativeUnit.Absolute); |
|
|
|
} |
|
|
|
|
|
|
|
// 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); |
|
|
|
ApplyLayoutTransform(); |
|
|
|
} |
|
|
|
|
|
|
|
// Perform an Arrange on TransformRoot (containing Child)
|
|
|
|
Size arrangedsize; |
|
|
|
TransformRoot.Arrange(finalRect); |
|
|
|
arrangedsize = TransformRoot.Bounds.Size; |
|
|
|
/// <summary>
|
|
|
|
/// Acceptable difference between two doubles.
|
|
|
|
/// </summary>
|
|
|
|
private const double AcceptableDelta = 0.0001; |
|
|
|
|
|
|
|
// 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 |
|
|
|
/// <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) |
|
|
|
{ |
|
|
|
// Clear the "need to measure/arrange again" flag
|
|
|
|
_childActualSize = Size.Empty; |
|
|
|
_matrixTransform.Matrix = _transformation; |
|
|
|
} |
|
|
|
|
|
|
|
// Return result to perform the transformation
|
|
|
|
return finalSize; |
|
|
|
// New transform means re-layout is necessary
|
|
|
|
InvalidateMeasure(); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -361,32 +366,24 @@ namespace Perspex.Controls |
|
|
|
return computedSize; |
|
|
|
} |
|
|
|
|
|
|
|
/// <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) |
|
|
|
private void OnLayoutTransformChanged(PerspexPropertyChangedEventArgs e) |
|
|
|
{ |
|
|
|
return (a.Width + AcceptableDelta < b.Width) || (a.Height + AcceptableDelta < b.Height); |
|
|
|
} |
|
|
|
var newTransform = e.NewValue as Transform; |
|
|
|
|
|
|
|
/// <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); |
|
|
|
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(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |