Skip to content

Skip initial assignments in property mapper (take 4) #27042

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
2 changes: 1 addition & 1 deletion src/Controls/src/Core/Label/Label.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.Maui.Controls
{
/// <include file="../../docs/Microsoft.Maui.Controls/Label.xml" path="Type[@FullName='Microsoft.Maui.Controls.Label']/Docs/*" />
[ContentProperty(nameof(Text))]
public partial class Label : View, IFontElement, ITextElement, ITextAlignmentElement, ILineHeightElement, IElementConfiguration<Label>, IDecorableTextElement, IPaddingElement, ILabel
public partial class Label : View, IFontElement, ITextElement, ITextAlignmentElement, ILineHeightElement, IElementConfiguration<Label>, IDecorableTextElement, IPaddingElement, ILabel, ILabelCompat
{
/// <summary>Bindable property for <see cref="HorizontalTextAlignment"/>.</summary>
public static readonly BindableProperty HorizontalTextAlignmentProperty = TextAlignmentElement.HorizontalTextAlignmentProperty;
Expand Down
7 changes: 7 additions & 0 deletions src/Core/src/Core/ILabelCompat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Microsoft.Maui;

internal interface ILabelCompat
{
TextType TextType { get; }
TextTransform TextTransform { get; }
}
7 changes: 7 additions & 0 deletions src/Core/src/Core/IPropertyInitializerMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Microsoft.Maui;

// TODO: Potentially make public in NET10
internal interface IPropertyInitializerMapper
{
void InitializeProperties(IElementHandler viewHandler, IElement virtualView);
}
24 changes: 22 additions & 2 deletions src/Core/src/Handlers/Element/ElementHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Microsoft.Maui.Handlers
{
public abstract partial class ElementHandler : IElementHandler
public abstract partial class ElementHandler : IElementHandler, IPlatformPropertyDefaultsProvider
{
public static IPropertyMapper<IElement, IElementHandler> ElementMapper = new PropertyMapper<IElement, IElementHandler>()
{
Expand All @@ -16,6 +16,10 @@ public abstract partial class ElementHandler : IElementHandler
internal readonly CommandMapper? _commandMapper;
internal IPropertyMapper _mapper;


private protected IPlatformPropertyDefaults? PlatformPropertyDefaults { get; init; }
IPlatformPropertyDefaults? IPlatformPropertyDefaultsProvider.PlatformPropertyDefaults => PlatformPropertyDefaults;

protected ElementHandler(IPropertyMapper mapper, CommandMapper? commandMapper = null)
{
_ = mapper ?? throw new ArgumentNullException(nameof(mapper));
Expand Down Expand Up @@ -45,19 +49,29 @@ public virtual void SetVirtualView(IElement view)
var oldVirtualView = VirtualView;

bool setupPlatformView = oldVirtualView == null;
bool isNewPlatformView = false;

VirtualView = view;
PlatformView ??= CreatePlatformElement();

if (PlatformView is null)
{
PlatformView = CreatePlatformElement();
isNewPlatformView = true;
}

if (VirtualView.Handler != this)
{
VirtualView.Handler = this;
}

// We set the previous virtual view to null after setting it on the incoming virtual view.
// This makes it easier for the incoming virtual view to have influence
// on how the exchange of handlers happens.
// We will just set the handler to null ourselves as a last resort cleanup
if (oldVirtualView?.Handler != null)
{
oldVirtualView.Handler = null;
}

if (setupPlatformView)
{
Expand All @@ -76,6 +90,12 @@ public virtual void SetVirtualView(IElement view)
}
}

if (isNewPlatformView && _mapper is IPropertyInitializerMapper initializerMapper)
{
initializerMapper.InitializeProperties(this, VirtualView);
return;
}

_mapper.UpdateProperties(this, VirtualView);
}

Expand Down
7 changes: 7 additions & 0 deletions src/Core/src/Handlers/IPlatformPropertyDefaultsProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Microsoft.Maui;

// TODO: Potentially make public in NET10
internal interface IPlatformPropertyDefaultsProvider
{
IPlatformPropertyDefaults? PlatformPropertyDefaults { get; }
}
39 changes: 39 additions & 0 deletions src/Core/src/Handlers/Label/LabelHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@ namespace Microsoft.Maui.Handlers
{
public partial class LabelHandler : ILabelHandler
{
internal static readonly IPlatformPropertyDefaults<ILabel> LabelPropertyDefaults = new PlatformPropertyDefaults<ILabel>(ViewHandler.ViewPropertyDefaults)
{
[nameof(ILabel.CharacterSpacing)] = HasDefaultCharacterSpacing,

#if !IOS && !MACATALYST
// TODO: It is not clear if this can be enabled on iOS-like platforms.
[nameof(ILabel.HorizontalTextAlignment)] = HasDefaultHorizontalTextAlignment,

// Test Issue21325 fails on Mac Catalyst if this is on as a label is not placed on the top but in the center.
[nameof(ILabel.VerticalTextAlignment)] = HasDefaultVerticalTextAlignment,
#endif

[nameof(ILabel.TextDecorations)] = HasDefaultTextDecorations,

[nameof(ILabelCompat.TextType)] = HasDefaultTextType,
[nameof(ILabelCompat.TextTransform)] = HasDefaultTextTransform,
};

public static IPropertyMapper<ILabel, ILabelHandler> Mapper = new PropertyMapper<ILabel, ILabelHandler>(ViewHandler.ViewMapper)
{
#if IOS || TIZEN
Expand Down Expand Up @@ -48,20 +66,41 @@ public partial class LabelHandler : ILabelHandler

public LabelHandler() : base(Mapper, CommandMapper)
{
PlatformPropertyDefaults = LabelPropertyDefaults;
}

public LabelHandler(IPropertyMapper? mapper)
: base(mapper ?? Mapper, CommandMapper)
{
PlatformPropertyDefaults = LabelPropertyDefaults;
}

public LabelHandler(IPropertyMapper? mapper, CommandMapper? commandMapper)
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
{
PlatformPropertyDefaults = LabelPropertyDefaults;
}

ILabel ILabelHandler.VirtualView => VirtualView;

PlatformView ILabelHandler.PlatformView => PlatformView;

/// <summary>This is a legacy property automatically remapped to `Text`, so it can always be considered as a default value.</summary>
internal static bool HasDefaultTextTransform(ILabel arg) => true;

/// <summary>This is a legacy property automatically remapped to `Text`, so it can always be considered as a default value.</summary>
internal static bool HasDefaultTextType(ILabel arg) => true;

internal static bool HasDefaultTextDecorations(ILabel label)
=> label.TextDecorations == TextDecorations.None;

internal static bool HasDefaultCharacterSpacing(ILabel label)
=> label.CharacterSpacing == 0;

internal static bool HasDefaultHorizontalTextAlignment(ILabel label)
=> label.HorizontalTextAlignment == TextAlignment.Start;

internal static bool HasDefaultVerticalTextAlignment(ILabel label)
=> label.VerticalTextAlignment == TextAlignment.Start;
}
}
127 changes: 126 additions & 1 deletion src/Core/src/Handlers/View/ViewHandler.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using Microsoft.Maui.Graphics;
#if __IOS__ || MACCATALYST
using PlatformView = UIKit.UIView;
Expand All @@ -20,6 +22,52 @@ namespace Microsoft.Maui.Handlers
/// Handlers are also responsible for instantiating the underlying platform view, and mapping the cross-platform control API to the platform view API. </remarks>
public abstract partial class ViewHandler : ElementHandler, IViewHandler
{
internal static readonly IPlatformPropertyDefaults<IView> ViewPropertyDefaults = new PlatformPropertyDefaults<IView>
{
[nameof(IView.AutomationId)] = HasDefaultAutomationId,
[nameof(IView.Clip)] = HasDefaultClip,

// Uncommenting this leads to failing of the test Issue25887 (ContentPresenter just rendering component string in .Net9).
// [nameof(IPadding.Padding)] = HasDefaultPadding,

[nameof(IView.Shadow)] = HasDefaultShadow,
[nameof(IView.Visibility)] = HasDefaultVisibility,
[nameof(IView.FlowDirection)] = HasDefaultFlowDirection,

// TODO: Can this be enabled?
// [nameof(IView.Width)] = HasDefaultWidth,
// [nameof(IView.Height)] = HasDefaultHeight,

[nameof(IView.MinimumWidth)] = HasDefaultMinimumWidth,
[nameof(IView.MaximumWidth)] = HasDefaultMaximumWidth,

[nameof(IView.MinimumHeight)] = HasDefaultMinimumHeight,
[nameof(IView.MaximumHeight)] = HasDefaultMaximumHeight,

[nameof(IView.Scale)] = HasDefaultScale,
[nameof(IView.ScaleX)] = HasDefaultScaleX,
[nameof(IView.ScaleY)] = HasDefaultScaleY,

[nameof(IView.Rotation)] = HasDefaultRotation,
[nameof(IView.RotationX)] = HasDefaultRotationX,
[nameof(IView.RotationY)] = HasDefaultRotationY,

[nameof(IView.AnchorX)] = HasDefaultAnchorX,
[nameof(IView.AnchorY)] = HasDefaultAnchorY,

[nameof(IView.Opacity)] = HasDefaultOpacity,
[nameof(IView.TranslationX)] = HasDefaultTranslationX,
[nameof(IView.TranslationY)] = HasDefaultTranslationY,

[nameof(IContextFlyoutElement.ContextFlyout)] = HasDefaultContextFlyout,

// Input transparency must always be mapped to make views user-interactable and allow gesture recognizers to work properly
// [nameof(IView.InputTransparent)] = HasDefaultInputTransparent,

[nameof(IToolbarElement.Toolbar)] = HasDefaultToolbar,
[nameof(IToolTipElement.ToolTip)] = HasDefaultToolTip,
};

/// <summary>
/// A dictionary that maps the virtual view properties to their platform view counterparts.
/// </summary>
Expand All @@ -31,6 +79,9 @@ public abstract partial class ViewHandler : ElementHandler, IViewHandler
new PropertyMapper<IView, IViewHandler>(ElementHandler.ElementMapper)
#endif
{
// This property is a special one and needs to be set before other properties.
[nameof(IViewHandler.ContainerView)] = MapContainerView,

[nameof(IView.AutomationId)] = MapAutomationId,
[nameof(IView.Clip)] = MapClip,
[nameof(IView.Shadow)] = MapShadow,
Expand All @@ -56,7 +107,6 @@ public abstract partial class ViewHandler : ElementHandler, IViewHandler
[nameof(IView.RotationY)] = MapRotationY,
[nameof(IView.AnchorX)] = MapAnchorX,
[nameof(IView.AnchorY)] = MapAnchorY,
[nameof(IViewHandler.ContainerView)] = MapContainerView,
#pragma warning disable CS0618 // Type or member is obsolete
[nameof(IBorder.Border)] = MapBorderView,
#pragma warning restore CS0618 // Type or member is obsolete
Expand Down Expand Up @@ -100,6 +150,7 @@ public abstract partial class ViewHandler : ElementHandler, IViewHandler
protected ViewHandler(IPropertyMapper mapper, CommandMapper? commandMapper = null)
: base(mapper, commandMapper ?? ViewCommandMapper)
{
PlatformPropertyDefaults = ViewPropertyDefaults;
}

/// <summary>
Expand Down Expand Up @@ -535,5 +586,79 @@ public static void MapToolTip(IViewHandler handler, IView view)
handler.ToPlatform().UpdateToolTip(tooltipContainer.ToolTip);
#endif
}

internal static bool HasDefaultAutomationId(IView view)
=> string.IsNullOrEmpty(view.AutomationId);

internal static bool HasDefaultClip(IView view)
=> view.Clip is null;

internal static bool HasDefaultPadding(IView view)
=> view is IPadding padding && (padding.Padding.IsEmpty || padding.Padding.IsNaN);

internal static bool HasDefaultShadow(IView view)
=> view.Shadow is null;

internal static bool HasDefaultVisibility(IView view)
=> view.Visibility == Visibility.Visible;

internal static bool HasDefaultFlowDirection(IView view)
=> view.FlowDirection == FlowDirection.MatchParent;

internal static bool HasDefaultMinimumWidth(IView view)
=> double.IsNaN(view.MinimumWidth);

internal static bool HasDefaultMaximumWidth(IView view)
=> double.IsNaN(view.MaximumWidth);

internal static bool HasDefaultMinimumHeight(IView view)
=> double.IsNaN(view.MinimumHeight);

internal static bool HasDefaultMaximumHeight(IView view)
=> double.IsNaN(view.MaximumHeight);

internal static bool HasDefaultScale(IView view)
=> view.Scale == 1;

internal static bool HasDefaultScaleX(IView view)
=> view.Scale == 1 && view.ScaleX == 1;

internal static bool HasDefaultScaleY(IView view)
=> view.Scale == 1 && view.ScaleY == 1;

internal static bool HasDefaultRotation(IView view)
=> view.Rotation % 360 == 0;

internal static bool HasDefaultRotationX(IView view)
=> view.RotationX % 360 == 0;

internal static bool HasDefaultRotationY(IView view)
=> view.RotationY % 360 == 0;

internal static bool HasDefaultAnchorX(IView view)
=> view.AnchorX == 0.5;

internal static bool HasDefaultAnchorY(IView view)
=> view.AnchorY == 0.5;

internal static bool HasDefaultOpacity(IView view)
=> view.Opacity == 1;

internal static bool HasDefaultTranslationX(IView view)
=> view.TranslationX == 0;

internal static bool HasDefaultTranslationY(IView view)
=> view.TranslationY == 0;

internal static bool HasDefaultContextFlyout(IView view)
=> view is IContextFlyoutElement { ContextFlyout: null };
internal static bool HasDefaultInputTransparent(IView view)
=> !view.InputTransparent;

internal static bool HasDefaultToolbar(IView view)
=> view is IToolbarElement { Toolbar: null };

internal static bool HasDefaultToolTip(IView view)
=> view is IToolTipElement { ToolTip: null };
}
}
Loading
Loading