Skip to content

Commit

Permalink
- add tests and fix mappers
Browse files Browse the repository at this point in the history
  • Loading branch information
PureWeen committed May 14, 2024
1 parent a40f2df commit a5ad0e6
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 31 deletions.
18 changes: 18 additions & 0 deletions src/Controls/src/Core/Border/Border.Mapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#nullable disable
using System;
using Microsoft.Maui.Controls.Compatibility;
using Microsoft.Maui.Handlers;

namespace Microsoft.Maui.Controls
{
public partial class Border
{
internal static new void RemapForControls()
{
// Ideally we wouldn't need these and we could create a cross cutting modify at the VisualElement level
// But we can't currently do that with mappers
BorderHandler.Mapper.ModifyMapping<IBorderView, IBorderHandler>(nameof(IView.Width), MapWidthOrHeight);
BorderHandler.Mapper.ModifyMapping<IBorderView, IBorderHandler>(nameof(IView.Height), MapWidthOrHeight);
}
}
}
2 changes: 1 addition & 1 deletion src/Controls/src/Core/Border/Border.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace Microsoft.Maui.Controls
{
[ContentProperty(nameof(Content))]
public class Border : View, IContentView, IBorderView, IPaddingElement
public partial class Border : View, IContentView, IBorderView, IPaddingElement
{
float[]? _strokeDashPattern;

Expand Down
19 changes: 7 additions & 12 deletions src/Controls/src/Core/VisualElement/VisualElement.Mapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,21 @@ internal static new void RemapForControls()
viewMapper.ReplaceMapping<IView, IViewHandler>(SemanticProperties.DescriptionProperty.PropertyName, MapSemanticPropertiesDescriptionProperty);
viewMapper.ReplaceMapping<IView, IViewHandler>(SemanticProperties.HintProperty.PropertyName, MapSemanticPropertiesHintProperty);
viewMapper.ReplaceMapping<IView, IViewHandler>(SemanticProperties.HeadingLevelProperty.PropertyName, MapSemanticPropertiesHeadingLevelProperty);
viewMapper.ModifyMapping<IView, IViewHandler>(nameof(IView.Height), MapHeight);
viewMapper.ModifyMapping<IView, IViewHandler>(nameof(IView.Width), MapWidth);
viewMapper.ModifyMapping<IView, IViewHandler>(nameof(IView.Height), MapWidthOrHeight);
viewMapper.ModifyMapping<IView, IViewHandler>(nameof(IView.Width), MapWidthOrHeight);

viewMapper.AppendToMapping<VisualElement, IViewHandler>(nameof(IViewHandler.ContainerView), MapContainerView);

commandMapper.ModifyMapping<VisualElement, IViewHandler>(nameof(IView.Focus), MapFocus);
}

static void MapWidth(IViewHandler handler, IView view, Action<IElementHandler, IElement> action)
internal static void MapWidthOrHeight(IViewHandler handler, IView view, Action<IElementHandler, IElement> action)
{
if (view is VisualElement ve && ve.Batched)
return;

action?.Invoke(handler, view);
}

static void MapHeight(IViewHandler handler, IView view, Action<IElementHandler, IElement> action)
{
if (view is VisualElement ve && ve.Batched)
// If batched is set to true this means that VisualElement.Frame is being set from the platform layout pass
if (view is VisualElement ve && ve.Batched && !ve.DontSuppressHeightWidthRequestChangesToHandlers)
{
return;
}

action?.Invoke(handler, view);
}
Expand Down
14 changes: 14 additions & 0 deletions src/Controls/src/Core/VisualElement/VisualElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1624,6 +1624,17 @@ static void OnIsFocusedPropertyChanged(BindableObject bindable, object oldvalue,
element.ChangeVisualState();
}

// If we're in the midst of updating the Frame property on VisualElement
// we suppress WidthProperty and HeightProperty from propagating to the handler
// Updates to the Frame is really just the platform informing
// the xplat code of it's actual dimensions, these aren't user controlled properties.
// But, if there's a scenario where a user modifies the WidthRequest or HeightRequest
// during the SizeAllocated events, then, we still want to propagate those changes to the handler
// in order to not break any previous scenarios where the user was relying on that behavior.
// We could also do this by wiring up mappers to the WidthRequestProperty and HeightRequestProperty but
// that will somewhat change the order, so, this is least invasive.
bool DontSuppressHeightWidthRequestChangesToHandlers {get; set;}

static void OnRequestChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var constraint = LayoutConstraint.None;
Expand All @@ -1641,8 +1652,11 @@ static void OnRequestChanged(BindableObject bindable, object oldvalue, object ne

if (element is IView fe)
{
element.DontSuppressHeightWidthRequestChangesToHandlers = true;
fe.Handler?.UpdateValue(nameof(IView.Width));
fe.Handler?.UpdateValue(nameof(IView.Height));
element.DontSuppressHeightWidthRequestChangesToHandlers = false;

fe.Handler?.UpdateValue(nameof(IView.MinimumHeight));
fe.Handler?.UpdateValue(nameof(IView.MinimumWidth));
fe.Handler?.UpdateValue(nameof(IView.MaximumHeight));
Expand Down
1 change: 1 addition & 0 deletions src/Controls/src/Xaml/Hosting/AppHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ internal static MauiAppBuilder RemapForControls(this MauiAppBuilder builder)
Shape.RemapForControls();
WebView.RemapForControls();
ContentPage.RemapForControls();
Border.RemapForControls();

return builder;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Threading.Tasks;
using Microsoft.Maui.Animations;

using Microsoft.Maui.Handlers;

namespace Microsoft.Maui.Controls.Core.UnitTests
{
public class BasicVisualElement : VisualElement
{
}

public class BasicVisualElementHandler : ViewHandler<BasicVisualElement, object>
{
public BasicVisualElementHandler(IPropertyMapper mapper, CommandMapper commandMapper = null) : base(mapper, commandMapper)
{
}

protected override object CreatePlatformView()
{
return new object();
}
}
}
61 changes: 61 additions & 0 deletions src/Controls/tests/Core.UnitTests/VisualElementTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
using System.Data.Common;
using System.Threading.Tasks;
using Microsoft.Maui.Controls.Shapes;

using Microsoft.Maui.Controls.Hosting;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform;

using Microsoft.Maui.Handlers;
using Microsoft.Maui.Primitives;
using Xunit;
Expand Down Expand Up @@ -237,5 +242,61 @@ public async Task ShadowDoesNotLeak()

Assert.False(reference.IsAlive, "VisualElement should not be alive!");
}

[Fact]
public void HandlerDoesntPropagateWidthChangesDuringBatchUpdates()
{
bool mapperCalled = false;

var mapper = new PropertyMapper<IView, ViewHandler>(ViewHandler.ViewMapper)
{
[nameof(IView.Height)] = (_,_) => mapperCalled = true,
[nameof(IView.Width)] = (_,_) => mapperCalled = true,
};

VisualElement.RemapForControls(mapper, new CommandMapper<IView, IViewHandler>(ViewHandler.ViewCommandMapper));

var mauiApp1 = MauiApp.CreateBuilder()
.UseMauiApp<ApplicationStub>()
.ConfigureMauiHandlers(handlers => handlers.AddHandler<BasicVisualElement>((services) => new BasicVisualElementHandler(mapper)))
.Build();

var element = new BasicVisualElement();
var platformView = element.ToPlatform(new MauiContext(mauiApp1.Services));

mapperCalled = false;
element.Frame = new Rect(0,0,100,100);
Assert.False(mapperCalled);
}

[Fact]
public void HandlerDoesPropagateWidthChangesWhenUpdatedDuringSizedChanged()
{
// read comments for DontSuppressHeightWidthRequestChangesToHandlers
// to understand why this test is here
bool mapperCalled = false;

var mapper = new PropertyMapper<IView, ViewHandler>(ViewHandler.ViewMapper)
{
[nameof(IView.Height)] = (_,_) => mapperCalled = true,
[nameof(IView.Width)] = (_,_) => mapperCalled = true,
};

VisualElement.RemapForControls(mapper, new CommandMapper<IView, IViewHandler>(ViewHandler.ViewCommandMapper));

var mauiApp1 = MauiApp.CreateBuilder()
.UseMauiApp<ApplicationStub>()
.ConfigureMauiHandlers(handlers => handlers.AddHandler<BasicVisualElement>((services) => new BasicVisualElementHandler(mapper)))
.Build();

var element = new BasicVisualElement();
var platformView = element.ToPlatform(new MauiContext(mauiApp1.Services));

element.SizeChanged += (_,_) => element.HeightRequest = 100;
mapperCalled = false;
element.Frame = new Rect(0,0,100,100);

Assert.True(mapperCalled);
}
}
}
21 changes: 19 additions & 2 deletions src/Core/src/Handlers/Border/BorderHandler.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,31 @@ public override void SetVirtualView(IView view)
handler.PlatformView.AddView(view.ToPlatform(handler.MauiContext));
}

public static partial void MapHeight(IBorderHandler handler, IBorderView border)
/// <summary>
/// Maps the abstract <see cref="IView.Height"/> property to the platform-specific implementations.
/// </summary>
/// <param name="handler">The associated handler.</param>
/// <param name="border">The associated <see cref="IBorderView"/> instance.</param>
public static void MapHeight(IBorderHandler handler, IBorderView border)
{
// These are no longer called from the Mapper the Mapper just uses
// MapHeightOrWidth
// TODO .NET9 obsolete this?
handler.PlatformView?.UpdateHeight(border);
handler.PlatformView?.InvalidateBorderStrokeBounds();
}

public static partial void MapWidth(IBorderHandler handler, IBorderView border)
/// <summary>
/// Maps the abstract <see cref="IView.Width"/> property to the platform-specific implementations.
/// </summary>
/// <param name="handler">The associated handler.</param>
/// <param name="border">The associated <see cref="IBorderView"/> instance.</param>
public static void MapWidth(IBorderHandler handler, IBorderView border)
{

// These are no longer called from the Mapper the Mapper just uses
// MapHeightOrWidth
// TODO .NET9 obsolete these?
handler.PlatformView?.UpdateWidth(border);
handler.PlatformView?.InvalidateBorderStrokeBounds();
}
Expand Down
16 changes: 0 additions & 16 deletions src/Core/src/Handlers/Border/BorderHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,21 +164,5 @@ public static void MapContent(IBorderHandler handler, IBorderView border)
}

static partial void UpdateContent(IBorderHandler handler);

#if __ANDROID__
/// <summary>
/// Maps the abstract <see cref="IView.Width"/> property to the platform-specific implementations.
/// </summary>
/// <param name="handler">The associated handler.</param>
/// <param name="border">The associated <see cref="IBorderView"/> instance.</param>
public static partial void MapWidth(IBorderHandler handler, IBorderView border);

/// <summary>
/// Maps the abstract <see cref="IView.Height"/> property to the platform-specific implementations.
/// </summary>
/// <param name="handler">The associated handler.</param>
/// <param name="border">The associated <see cref="IBorderView"/> instance.</param>
public static partial void MapHeight(IBorderHandler handler, IBorderView border);
#endif
}
}

0 comments on commit a5ad0e6

Please sign in to comment.