Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DO NOT MERGE] Include Adorners in matrix calculations in VisualExtensions #15484

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
{
}
36 changes: 30 additions & 6 deletions src/Avalonia.Base/Data/Core/TargetTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,32 @@ public override bool TryConvert(object? value, Type type, CultureInfo culture, o

if (toTypeConverter.CanConvertFrom(from))
{
result = toTypeConverter.ConvertFrom(null, culture, value);
return true;
try
{
result = toTypeConverter.ConvertFrom(null, culture, value);
return true;
}
catch
{
result = null;
return false;
}
}

var fromTypeConverter = TypeDescriptor.GetConverter(from);

if (fromTypeConverter.CanConvertTo(t))
{
result = fromTypeConverter.ConvertTo(null, culture, value, t);
return true;
try
{
result = fromTypeConverter.ConvertTo(null, culture, value, t);
return true;
}
catch
{
result = null;
return false;
}
}

// TODO: This requires reflection: we probably need to make compiled bindings emit
Expand All @@ -95,8 +111,16 @@ public override bool TryConvert(object? value, Type type, CultureInfo culture, o
t,
OperatorType.Implicit | OperatorType.Explicit) is { } cast)
{
result = cast.Invoke(null, new[] { value });
return true;
try
{
result = cast.Invoke(null, new[] { value });
return true;
}
catch
{
result = null;
return false;
}
}
#pragma warning restore IL2067
#pragma warning restore IL2026
Expand Down
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);
}
}
37 changes: 37 additions & 0 deletions tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Headless;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Moq;
Expand Down Expand Up @@ -110,6 +113,40 @@ public void Setter_Exceptions_Should_Set_DataValidationErrors_HasErrors()
}
}

[Fact]
public void CompiledBindings_TypeConverter_Exceptions_Should_Set_DataValidationErrors_HasErrors()
{
var path = new CompiledBindingPathBuilder()
.Property(
new ClrPropertyInfo(
nameof(ExceptionTest.LessThan10),
target => ((ExceptionTest)target).LessThan10,
(target, value) => ((ExceptionTest)target).LessThan10 = (int)value,
typeof(int)),
PropertyInfoAccessorFactory.CreateInpcPropertyAccessor)
.Build();

using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
DataContext = new ExceptionTest(),
[!TextBox.TextProperty] = new CompiledBindingExtension
{
Source = new ExceptionTest(),
Path = path,
Mode = BindingMode.TwoWay
},
Template = CreateTemplate(),
};

target.ApplyTemplate();

target.Text = "a";
Assert.True(DataValidationErrors.GetHasErrors(target));
}
}

private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of<ICursorFactory>(),
textShaperImpl: new HeadlessTextShaperStub(),
Expand Down