Skip to content

Commit

Permalink
Include Adorners in matrix calculations in VisualExtensions
Browse files Browse the repository at this point in the history
  • Loading branch information
BAndysc committed May 7, 2024
1 parent 5271f80 commit e54d25f
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 32 deletions.
23 changes: 23 additions & 0 deletions src/Avalonia.Base/Controls/AdornerLayerBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Avalonia.Metadata;

namespace Avalonia.Controls;

[PrivateApi]
public class AdornerLayerBase
{
/// <summary>
/// Allows for getting and setting of the adorned element.
/// </summary>
public static readonly AttachedProperty<Visual?> AdornedElementProperty =
AvaloniaProperty.RegisterAttached<AdornerLayerBase, Visual, Visual?>("AdornedElement");

public static Visual? GetAdornedElement(Visual adorner)
{
return adorner.GetValue(AdornedElementProperty);
}

public static void SetAdornedElement(Visual adorner, Visual? adorned)
{
adorner.SetValue(AdornedElementProperty, adorned);
}
}
9 changes: 9 additions & 0 deletions src/Avalonia.Base/Controls/IAdornerLayer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Avalonia.Metadata;

namespace Avalonia.Controls;

[PrivateApi]
[NotClientImplementable]
public interface IAdornerLayer
{
}
41 changes: 39 additions & 2 deletions src/Avalonia.Base/VisualExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Avalonia.Controls;
using Avalonia.VisualTree;

namespace Avalonia
Expand Down Expand Up @@ -52,8 +53,21 @@ public static PixelPoint PointToScreen(this Visual visual, Point point)

if (common != null)
{
var thisOffset = GetOffsetFrom(common, from);
var thatOffset = GetOffsetFrom(common, to);
Matrix thisOffset;
if (TryGetAdorner(from, common, out var adornedFrom, out var adornerLayerFrom))
{
thisOffset = GetOffsetFrom(common, adornedFrom!) * GetOffsetFrom(adornerLayerFrom!, from);
}
else
thisOffset = GetOffsetFrom(common, from);

Matrix thatOffset;
if (TryGetAdorner(to, common, out var adornedTo, out var adornerLayerTo))
{
thatOffset = GetOffsetFrom(common, adornedTo!) * GetOffsetFrom(adornerLayerTo!, to);
}
else
thatOffset = GetOffsetFrom(common, to);

if (!thatOffset.TryInvert(out var thatOffsetInverted))
{
Expand All @@ -66,6 +80,29 @@ public static PixelPoint PointToScreen(this Visual visual, Point point)
return null;
}

private static bool TryGetAdorner(Visual target, Visual? stopAtVisual, out Visual? adorned, out Visual? adornerLayer)
{
var element = target;
while (element != null && element != stopAtVisual)
{
if (AdornerLayerBase.GetAdornedElement(element) is { } adornedElement)
{
adorned = adornedElement;
adornerLayer = element;
while (adornerLayer != null && adornerLayer is not IAdornerLayer)
{
adornerLayer = adornerLayer.VisualParent;
}
return adornerLayer != null;
}
element = element.VisualParent;
}

adorned = null;
adornerLayer = null;
return false;
}

/// <summary>
/// Translates a point relative to this visual to coordinates that are relative to the specified visual.
/// </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Avalonia.Controls/Primitives/AdornerLayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ namespace Avalonia.Controls.Primitives
/// <remarks>
/// TODO: Need to track position of adorned elements and move the adorner if they move.
/// </remarks>
public class AdornerLayer : Canvas
public class AdornerLayer : Canvas, IAdornerLayer
{
/// <summary>
/// Allows for getting and setting of the adorned element.
/// </summary>
public static readonly AttachedProperty<Visual?> AdornedElementProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, Visual?>("AdornedElement");
AdornerLayerBase.AdornedElementProperty.AddOwner<AdornerLayer>();

/// <summary>
/// Allows for controlling clipping of the adorner.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -468,15 +468,7 @@ static class PopupPositionerExtensions
{
if (target == null)
throw new InvalidOperationException("Placement mode is not Pointer and PlacementTarget is null");
Matrix? matrix;
if (TryGetAdorner(target, out var adorned, out var adornerLayer))
{
matrix = adorned!.TransformToVisual(topLevel) * target.TransformToVisual(adornerLayer!);
}
else
{
matrix = target.TransformToVisual(topLevel);
}
Matrix? matrix = target.TransformToVisual(topLevel);

if (matrix == null)
{
Expand Down Expand Up @@ -538,25 +530,6 @@ static class PopupPositionerExtensions
}
}
}

private static bool TryGetAdorner(Visual target, out Visual? adorned, out Visual? adornerLayer)
{
var element = target;
while (element != null)
{
if (AdornerLayer.GetAdornedElement(element) is { } adornedElement)
{
adorned = adornedElement;
adornerLayer = AdornerLayer.GetAdornerLayer(adorned);
return true;
}
element = element.VisualParent;
}

adorned = null;
adornerLayer = null;
return false;
}
}

}
34 changes: 34 additions & 0 deletions tests/Avalonia.Controls.UnitTests/AdornerLayerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Avalonia.Controls.Primitives;
using Avalonia.UnitTests;
using Xunit;

namespace Avalonia.Controls.UnitTests;

public class AdornerLayerTests : ScopedTestBase
{
[Fact]
public void Adorners_Include_Adorned_Elements_In_Transform_Visual()
{
var button = new Button()
{
Margin = new Thickness(100, 100)
};
var root = new TestRoot()
{
Child = new VisualLayerManager()
{
Child = button
}
};
var adorner = new Border();

var adornerLayer = AdornerLayer.GetAdornerLayer(button);
adornerLayer.Children.Add(adorner);
AdornerLayer.SetAdornedElement(adorner, button);

root.LayoutManager.ExecuteInitialLayoutPass();

var translatedPoint = root.TranslatePoint(new Point(100, 100), adorner);
Assert.Equal(new Point(0, 0), translatedPoint);
}
}

0 comments on commit e54d25f

Please sign in to comment.