Browse Source

Add Visual.GetTransformedBounds extension method.

Fixes #9569.
pull/10627/head
Steven Kirk 4 years ago
parent
commit
ed7ee5cf39
  1. 63
      src/Avalonia.Base/VisualTree/VisualExtensions.cs
  2. 153
      tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetTransformedBounds.cs

63
src/Avalonia.Base/VisualTree/VisualExtensions.cs

@ -204,6 +204,69 @@ namespace Avalonia.VisualTree
}
}
public static TransformedBounds? GetTransformedBounds(this Visual visual)
{
Rect clip = default;
var transform = Matrix.Identity;
bool Visit(Visual visual)
{
if (!visual.IsVisible)
return false;
// The visual's bounds in local coordinates.
var bounds = new Rect(visual.Bounds.Size);
// If the visual has no parent, we've reached the root. We start the clip
// rectangle with these bounds.
if (visual.GetVisualParent() is not { } parent)
{
clip = bounds;
return true;
}
// Otherwise recurse until the root visual is found, exiting early if one of the
// ancestors is invisible.
if (!Visit(parent))
return false;
// Calculate the transform for this control from its offset and render transform.
var renderTransform = Matrix.Identity;
if (visual.HasMirrorTransform)
{
var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0);
renderTransform *= mirrorMatrix;
}
if (visual.RenderTransform != null)
{
var origin = visual.RenderTransformOrigin.ToPixels(bounds.Size);
var offset = Matrix.CreateTranslation(origin);
var finalTransform = (-offset) * visual.RenderTransform.Value * offset;
renderTransform *= finalTransform;
}
transform = renderTransform *
Matrix.CreateTranslation(visual.Bounds.Position) *
transform;
// If the visual is clipped, update the clip bounds.
if (visual.ClipToBounds)
{
var globalBounds = bounds.TransformToAABB(transform);
var clipBounds = visual.ClipToBounds ?
globalBounds.Intersect(clip) :
clip;
clip = clip.Intersect(clipBounds);
}
return true;
}
return Visit(visual) ? new(new(visual.Bounds.Size), clip, transform) : null;
}
/// <summary>
/// Gets the first visual in the visual tree whose bounds contain a point.
/// </summary>

153
tests/Avalonia.Base.UnitTests/VisualTree/VisualExtensions_GetTransformedBounds.cs

@ -0,0 +1,153 @@
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.VisualTree;
using Xunit;
namespace Avalonia.Base.UnitTests.VisualTree
{
public class VisualExtensions_GetTransformedBounds
{
[Fact]
public void Root()
{
var root = new Border
{
Width = 100,
Height = 123,
};
Layout(root);
Assert.Equal(
new TransformedBounds(
new Rect(0, 0, 100, 123),
new Rect(0, 0, 100, 123),
Matrix.Identity),
root.GetTransformedBounds());
}
[Fact]
public void Depth_1_No_Transform_Or_Clip()
{
Border target;
var root = new Border
{
Width = 1000,
Height = 1000,
Child = target = new Border
{
Width = 500,
Height = 500,
}
};
Layout(root);
Assert.Equal(
new TransformedBounds(
new Rect(250, 250, 500, 500),
new Rect(0, 0, 1000, 1000),
Matrix.CreateTranslation(250, 250)),
target.GetTransformedBounds());
}
[Fact]
public void Depth_2_No_Transform_Or_Clip()
{
Border target;
var root = new Border
{
Width = 1000,
Height = 1000,
Child = new Border
{
Width = 800,
Height = 800,
Child = target = new Border
{
Width = 500,
Height = 500,
}
}
};
Layout(root);
Assert.Equal(
new TransformedBounds(
new Rect(150, 150, 500, 500),
new Rect(0, 0, 1000, 1000),
Matrix.CreateTranslation(250, 250)),
target.GetTransformedBounds());
}
[Fact]
public void Depth_2_No_Transform_With_Clip()
{
Border target;
var root = new Border
{
Width = 1000,
Height = 1000,
Child = new Border
{
Width = 800,
Height = 800,
ClipToBounds = true,
Child = target = new Border
{
Width = 500,
Height = 500,
}
}
};
Layout(root);
Assert.Equal(
new TransformedBounds(
new Rect(150, 150, 500, 500),
new Rect(100, 100, 800, 800),
Matrix.CreateTranslation(250, 250)),
target.GetTransformedBounds());
}
[Fact]
public void Depth_2_Transformed_Clip()
{
Border target;
var root = new Border
{
Width = 1000,
Height = 1000,
Child = new Border
{
Width = 800,
Height = 800,
ClipToBounds = true,
RenderTransform = new MatrixTransform(Matrix.CreateTranslation(10, 20)),
Child = target = new Border
{
Width = 500,
Height = 500,
}
}
};
Layout(root);
Assert.Equal(
new TransformedBounds(
new Rect(150, 150, 500, 500),
new Rect(110, 120, 800, 800),
Matrix.CreateTranslation(260, 270)),
target.GetTransformedBounds());
}
private void Layout(Control c)
{
c.Measure(Size.Infinity);
c.Arrange(new Rect(c.DesiredSize));
}
}
}
Loading…
Cancel
Save