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

Correctly handle ContainerView changes #13114

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,

public static void MapBackground(IActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Background));
handler.ToPlatform().UpdateBackground(activityIndicator);
}

Expand Down
8 changes: 8 additions & 0 deletions src/Core/src/Handlers/IViewHandler.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Microsoft.Maui.Graphics;

namespace Microsoft.Maui
Expand All @@ -14,4 +15,11 @@ public interface IViewHandler : IElementHandler

void PlatformArrange(Rect frame);
}

public interface INeedsContainerViewHandler : IViewHandler
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore this.

{
bool NeedsContainer { get; }

ICollection<string> ChangesContainer { get; }
}
}
2 changes: 1 addition & 1 deletion src/Core/src/Handlers/Image/ImageHandler.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ protected override void DisconnectHandler(ImageView platformView)

public static void MapBackground(IImageHandler handler, IImage image)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Background));

handler.ToPlatform().UpdateBackground(image);
handler.ToPlatform().UpdateOpacity(image);
Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/Handlers/Image/ImageHandler.Tizen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected override void DisconnectHandler(Image platformView)

public static void MapBackground(IImageHandler handler, IImage image)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Background));
handler.ToPlatform()?.UpdateBackground(image);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/Handlers/Image/ImageHandler.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ protected override void DisconnectHandler(Image platformView)

public static void MapBackground(IImageHandler handler, IImage image)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Background));
handler.ToPlatform().UpdateBackground(image);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/Handlers/Image/ImageHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ protected override void DisconnectHandler(UIImageView platformView)

public static void MapBackground(IImageHandler handler, IImage image)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Background));

handler.ToPlatform().UpdateBackground(image);
}
Expand Down
6 changes: 3 additions & 3 deletions src/Core/src/Handlers/Label/LabelHandler.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ protected override void RemoveContainer()

public static void MapBackground(ILabelHandler handler, ILabel label)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Background));

handler.ToPlatform().UpdateBackground(label);
}

public static void MapOpacity(ILabelHandler handler, ILabel label)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Opacity));
handler.PlatformView.UpdateOpacity(label);
handler.ToPlatform().UpdateOpacity(label);
}
Expand All @@ -69,7 +69,7 @@ public static void MapFont(ILabelHandler handler, ILabel label)

public static void MapVerticalTextAlignment(ILabelHandler handler, ILabel label)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(ILabel.VerticalTextAlignment));

handler.PlatformView?.UpdateVerticalTextAlignment(label);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/Handlers/Label/LabelHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public partial class LabelHandler : ViewHandler<ILabel, MauiLabel>

public static void MapBackground(ILabelHandler handler, ILabel label)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Background));

handler.ToPlatform().UpdateBackground(label);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ protected override MauiShapeView CreatePlatformView()

public static void MapBackground(IShapeViewHandler handler, IShapeView shapeView)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Background));
handler.ToPlatform().UpdateBackground(shapeView);

handler.PlatformView?.InvalidateShape(shapeView);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ protected override W2DGraphicsView CreatePlatformView()

public static void MapBackground(IShapeViewHandler handler, IShapeView shapeView)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Background));
handler.ToPlatform().UpdateBackground(shapeView);

handler.PlatformView?.InvalidateShape(shapeView);
Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/Handlers/ShapeView/ShapeViewHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ protected override MauiShapeView CreatePlatformView()

public static void MapBackground(IShapeViewHandler handler, IShapeView shapeView)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Background));
handler.ToPlatform().UpdateBackground(shapeView);

handler.PlatformView?.InvalidateShape(shapeView);
Expand Down
82 changes: 59 additions & 23 deletions src/Core/src/Handlers/View/ViewHandler.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Microsoft.Maui.Graphics;
#if __IOS__ || MACCATALYST
using PlatformView = UIKit.UIView;
Expand All @@ -13,7 +14,7 @@

namespace Microsoft.Maui.Handlers
{
public abstract partial class ViewHandler : ElementHandler, IViewHandler
public abstract partial class ViewHandler : ElementHandler, IViewHandler, INeedsContainerViewHandler
{
public static IPropertyMapper<IView, IViewHandler> ViewMapper =
#if ANDROID
Expand Down Expand Up @@ -48,6 +49,7 @@ public abstract partial class ViewHandler : ElementHandler, IViewHandler
[nameof(IView.RotationY)] = MapRotationY,
[nameof(IView.AnchorX)] = MapAnchorX,
[nameof(IView.AnchorY)] = MapAnchorY,
[nameof(INeedsContainerViewHandler.NeedsContainer)] = MapNeedsContainer,
[nameof(IViewHandler.ContainerView)] = MapContainerView,
[nameof(IBorder.Border)] = MapBorderView,
#if ANDROID || WINDOWS || TIZEN
Expand All @@ -67,6 +69,7 @@ public abstract partial class ViewHandler : ElementHandler, IViewHandler
[nameof(IView.ZIndex)] = MapZIndex,
[nameof(IView.Focus)] = MapFocus,
[nameof(IView.Unfocus)] = MapUnfocus,
[nameof(INeedsContainerViewHandler.NeedsContainer)] = MapNeedsContainer,
};

bool _hasContainer;
Expand All @@ -78,6 +81,8 @@ protected ViewHandler(IPropertyMapper mapper, CommandMapper? commandMapper = nul
{
}

public ICollection<string> ChangesContainer { get; } = new HashSet<string>();

public bool HasContainer
{
get => _hasContainer;
Expand All @@ -92,12 +97,33 @@ public bool HasContainer
SetupContainer();
else
RemoveContainer();

UpdateValue(nameof(IViewHandler.ContainerView));
Comment on lines +103 to +104
Copy link
Member Author

@mattleibow mattleibow Feb 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HasContainer property adds/removes the container and then calls the ContainerView property mapper.

If the value is the same as before, it is terminated here and we skip the rest.

}
}

public virtual bool NeedsContainer
{
get => VirtualView.NeedsContainer();
get
{
#if !TIZEN
if (VirtualView?.Clip != null || VirtualView?.Shadow != null)
return true;
#endif
#if ANDROID
if (VirtualView?.InputTransparent == true)
return true;
#endif
#if ANDROID || IOS
if (VirtualView is IBorder border)
return border.Border != null;
mattleibow marked this conversation as resolved.
Show resolved Hide resolved
#endif
#if WINDOWS || TIZEN
if (VirtualView is IBorderView border)
return border?.Shape != null || border?.Stroke != null;
mattleibow marked this conversation as resolved.
Show resolved Hide resolved
#endif
return false;
}
}

protected abstract void SetupContainer();
Expand Down Expand Up @@ -201,10 +227,17 @@ public static void MapIsEnabled(IViewHandler handler, IView view)

public static void MapVisibility(IViewHandler handler, IView view)
{
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Visibility));

if (handler.HasContainer)
{
((PlatformView?)handler.ContainerView)?.UpdateVisibility(view);
((PlatformView?)handler.PlatformView)?.UpdateVisibility(Visibility.Visible);
}
else
{
((PlatformView?)handler.PlatformView)?.UpdateVisibility(view);
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a property changes that needs to respond to ContainerView changes, we call the "special" invoke and pass the property that requires a re-fire. This updates a collection, and ALSO triggers the path to ensure the container is correct.

Then based on the HasContainer, the mapper needs to set the values of the container and/or the platform view.


public static void MapBackground(IViewHandler handler, IView view)
Expand Down Expand Up @@ -232,10 +265,8 @@ public static void MapFlowDirection(IViewHandler handler, IView view)

public static void MapOpacity(IViewHandler handler, IView view)
{
if (handler.HasContainer)
((PlatformView?)handler.ContainerView)?.UpdateOpacity(view);
else
((PlatformView?)handler.PlatformView)?.UpdateOpacity(view);
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Opacity));
handler.ToPlatform()?.UpdateOpacity(view);
}

public static void MapAutomationId(IViewHandler handler, IView view)
Expand All @@ -245,14 +276,14 @@ public static void MapAutomationId(IViewHandler handler, IView view)

public static void MapClip(IViewHandler handler, IView view)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Clip));

((PlatformView?)handler.ContainerView)?.UpdateClip(view);
}

public static void MapShadow(IViewHandler handler, IView view)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.Shadow));

((PlatformView?)handler.ContainerView)?.UpdateShadow(view);
}
Expand All @@ -270,27 +301,32 @@ public static void MapInvalidateMeasure(IViewHandler handler, IView view, object
(handler.PlatformView as PlatformView)?.InvalidateMeasure(view);
}

public static void MapContainerView(IViewHandler handler, IView view)
public static void MapNeedsContainer(IViewHandler handler, IView view, object? args)
{
if (handler is ViewHandler viewHandler)
handler.HasContainer = viewHandler.NeedsContainer;
else
handler.HasContainer = view.NeedsContainer();
if (handler is ViewHandler viewHandler && args is string mapper)
viewHandler.ChangesContainer.Add(mapper);

UpdateInputTransparentOnContainerView(handler, view);
handler.UpdateValue(nameof(INeedsContainerViewHandler.NeedsContainer));
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then the NeedsContainer command adds the incoming property to the list to update later. After that, it calls the NeedsContainer PROPERTY mapper.


static void UpdateInputTransparentOnContainerView(IViewHandler handler, IView view)
public static void MapNeedsContainer(IViewHandler handler, IView view)
{
#if ANDROID
if (handler.ContainerView is WrapperView wrapper)
wrapper.InputTransparent = view.InputTransparent;
#endif
if (handler is INeedsContainerViewHandler ncvh)
handler.HasContainer = ncvh.NeedsContainer;
}
Copy link
Member Author

@mattleibow mattleibow Feb 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NeedsContainer property mapper now updates the HasContainer property.


public static void MapContainerView(IViewHandler handler, IView view)
{
if (handler is INeedsContainerViewHandler ncvh)
{
foreach (var map in ncvh.ChangesContainer)
handler.UpdateValue(map);
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ContainerView then re-fires all the things that were added before.


public static void MapBorderView(IViewHandler handler, IView view)
{
handler.UpdateValue(nameof(IViewHandler.ContainerView));
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IBorder.Border));

((PlatformView?)handler.ContainerView)?.UpdateBorder(view);
}
Expand Down Expand Up @@ -326,10 +362,10 @@ public static void MapFocus(IViewHandler handler, IView view, object? args)
public static void MapInputTransparent(IViewHandler handler, IView view)
{
#if ANDROID
handler.UpdateValue(nameof(IViewHandler.ContainerView));
UpdateInputTransparentOnContainerView(handler, view);
handler.Invoke(nameof(INeedsContainerViewHandler.NeedsContainer), nameof(IView.InputTransparent));
((PlatformView?)handler.ContainerView)?.UpdateInputTransparent(view);
#else
((PlatformView?)handler.PlatformView)?.UpdateInputTransparent(handler, view);
((PlatformView?)handler.PlatformView)?.UpdateInputTransparent(view);
#endif
}

Expand Down
14 changes: 12 additions & 2 deletions src/Core/src/Platform/Android/ViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,12 @@ public static void Unfocus(this AView platformView, IView view)
platformView.ClearFocus();
}

public static void UpdateVisibility(this AView platformView, IView view)
public static void UpdateVisibility(this AView platformView, IView view) =>
ViewExtensions.UpdateVisibility(platformView, view.Visibility);

public static void UpdateVisibility(this AView platformView, Visibility visibility)
{
platformView.Visibility = view.Visibility.ToPlatformVisibility();
platformView.Visibility = visibility.ToPlatformVisibility();
}

public static void UpdateClip(this AView platformView, IView view)
Expand All @@ -107,6 +110,13 @@ public static void UpdateShadow(this AView platformView, IView view)
if (platformView is WrapperView wrapper)
wrapper.Shadow = view.Shadow;
}

public static void UpdateInputTransparent(this AView platformView, IView view)
{
if (platformView is WrapperView wrapper)
wrapper.InputTransparent = view.InputTransparent;
}

public static void UpdateBorder(this AView platformView, IView view)
{
if (platformView is WrapperView wrapper)
Expand Down
2 changes: 2 additions & 0 deletions src/Core/src/Platform/Standard/ViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,7 @@ public static Task UpdateBackgroundImageSourceAsync(this object platformView, II
=> null;

public static void UpdateInputTransparent(this object nativeView, IViewHandler handler, IView view) { }

public static void UpdateInputTransparent(this object nativeView, IView view) { }
}
}
13 changes: 4 additions & 9 deletions src/Core/src/Platform/Tizen/ViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,10 @@ public static void UpdateMaximumHeight(this NView platformView, IView view)
// NUI MaximumSize is not working properly
}

public static void UpdateInputTransparent(this NView platformView, IViewHandler handler, IView view)
public static void UpdateInputTransparent(this NView platformView, IViewHandler handler, IView view) =>
UpdateInputTransparent(platformView, view);

public static void UpdateInputTransparent(this NView platformView, IView view)
{
platformView.Sensitive = !view.InputTransparent;
}
Expand Down Expand Up @@ -333,13 +336,5 @@ internal static IDisposable OnUnloaded(this NView view, Action action)
view.RemovedFromWindow += routedEventHandler;
return disposable;
}

internal static bool NeedsContainer(this IView? view)
{
if (view is IBorderView border)
return border?.Shape != null || border?.Stroke != null;

return false;
}
}
}
Loading