Skip to content

Commit

Permalink
Add Page.ToolbarItems support
Browse files Browse the repository at this point in the history
  • Loading branch information
Dreamescaper authored and Eilon committed May 27, 2022
1 parent e5b2a6d commit 8334372
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,35 +1,48 @@
<ContentPage Title="Shell Properties">
<ShellProperties NavBarIsVisible="navBarVisible"
TabBarIsVisible="tabBarVisible"
TitleColor="titleColor" />
<ContentPage @ref="page">
<ToolbarItems>
<ToolbarItem Text="Help" OnClick="ShowHelp" />
</ToolbarItems>

<StackLayout>
<StackLayout Orientation="StackOrientation.Horizontal">
<Label Text="Enable NavBar: " />
<CheckBox @bind-IsChecked="navBarVisible" />
</StackLayout>
<StackLayout Orientation="StackOrientation.Horizontal">
<Label Text="Enable TabBar: " />
<CheckBox @bind-IsChecked="tabBarVisible" />
<ChildContent>
<ShellProperties NavBarIsVisible="navBarVisible"
TabBarIsVisible="tabBarVisible"
TitleColor="titleColor" />

<StackLayout>
<StackLayout Orientation="StackOrientation.Horizontal">
<Label Text="Enable NavBar: " />
<CheckBox @bind-IsChecked="navBarVisible" />
</StackLayout>
<StackLayout Orientation="StackOrientation.Horizontal">
<Label Text="Enable TabBar: " />
<CheckBox @bind-IsChecked="tabBarVisible" />
</StackLayout>
<Button Text="Change Title Color" OnClick="ChangeTitleColor" />
</StackLayout>
<Button Text="Change Title Color" OnClick="ChangeTitleColor" />
</StackLayout>
</ChildContent>
</ContentPage>

@code{
Microsoft.MobileBlazorBindings.Elements.Page page;

bool navBarVisible = true;
bool tabBarVisible = true;
Color? titleColor;

List<Color> colors = new List<Color> {
Color.Red,
Color.Green,
Color.Blue
Color.Red,
Color.Green,
Color.Blue
};

void ChangeTitleColor()
{
var index = (titleColor.HasValue ? colors.IndexOf(titleColor.Value) + 1 : 0) % colors.Count;
titleColor = colors[index];
}

async Task ShowHelp()
{
await page.NativeControl.DisplayAlert("Help", "Some help message", "OK");
}
}
1 change: 1 addition & 0 deletions src/ComponentWrapperGenerator/TypesToGenerate.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ TabbedPage
TemplatedPage
TemplatedView
TimePicker
ToolbarItem
View
VisualElement

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ public static class ElementHandlerRegistry
ElementHandlers.Add(typeof(TComponent).FullName, new ElementHandlerFactory((renderer, _) => factory(renderer)));
}

public static void RegisterPropertyContentHandler<TComponent>(string propertyName,
Func<NativeComponentRenderer, IElementHandler> factory) where TComponent : NativeControlComponentBase
{
var key = $"p-{typeof(TComponent).FullName}.{propertyName}";
ElementHandlers.Add(key, new ElementHandlerFactory((renderer, _) => factory(renderer)));
}

public static void RegisterElementHandler<TComponent, TControlHandler>() where TComponent : NativeControlComponentBase where TControlHandler : class, IElementHandler, new()
{
RegisterElementHandler<TComponent>((_, __) => new TControlHandler());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,20 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
builder.AddContent(2, childContent);
}

int sequence = 3;
RenderAdditionalElementContent(builder, ref sequence);

builder.CloseElement();
}

protected virtual void RenderAttributes(AttributesBuilder builder)
{
}

protected virtual void RenderAdditionalElementContent(RenderTreeBuilder builder, ref int sequence)
{
}

protected virtual RenderFragment GetChildContent() => null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using Microsoft.MobileBlazorBindings.Core;
using System;
using System.Collections.Generic;
using XF = Xamarin.Forms;

namespace Microsoft.MobileBlazorBindings.Elements.Handlers
{
public class ListContentPropertyHandler<TElementType, TItemType> : IXamarinFormsContainerElementHandler, INonChildContainerElement where TItemType : XF.Element
{
private readonly Func<TElementType, IList<TItemType>> _listPropertyAccessor;
private IList<TItemType> _propertyItems;

public ListContentPropertyHandler(Func<TElementType, IList<TItemType>> listPropertyAccessor)
{
_listPropertyAccessor = listPropertyAccessor;
}

public void SetParent(object parentElement)
{
_propertyItems = _listPropertyAccessor((TElementType)parentElement);
}

void IXamarinFormsContainerElementHandler.AddChild(XF.Element child, int physicalSiblingIndex)
{
if (!(child is TItemType typedChild))
{
throw new NotSupportedException($"Cannot add item of type {child?.GetType().Name} to a {typeof(TItemType)} collection.");
}

_propertyItems.Insert(physicalSiblingIndex, typedChild);
}

int IXamarinFormsContainerElementHandler.GetChildIndex(XF.Element child)
{
return _propertyItems.IndexOf(child as TItemType);
}

void IXamarinFormsContainerElementHandler.RemoveChild(XF.Element child)
{
_propertyItems.Remove(child as TItemType);
}

// Because this is a 'fake' element, all matters related to physical trees
// should be no-ops.

object IElementHandler.TargetElement => null;
void IElementHandler.ApplyAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName) { }

XF.Element IXamarinFormsElementHandler.ElementControl => null;
bool IXamarinFormsElementHandler.IsParented() => false;
bool IXamarinFormsElementHandler.IsParentedTo(XF.Element parent) => false;

void IXamarinFormsElementHandler.SetParent(XF.Element parent)
{
// This should never get called. Instead, INonChildContainerElement.SetParent() implemented
// in this class should get called.
throw new NotSupportedException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using Microsoft.MobileBlazorBindings.Core;
using System;
using XF = Xamarin.Forms;

namespace Microsoft.MobileBlazorBindings.Elements.Handlers
{
public partial class ToolbarItemHandler : MenuItemHandler
{

public ToolbarItemHandler(NativeComponentRenderer renderer, XF.ToolbarItem toolbarItemControl) : base(renderer, toolbarItemControl)
{
ToolbarItemControl = toolbarItemControl ?? throw new ArgumentNullException(nameof(toolbarItemControl));

Initialize(renderer);
}

partial void Initialize(NativeComponentRenderer renderer);

public XF.ToolbarItem ToolbarItemControl { get; }

public override void ApplyAttribute(ulong attributeEventHandlerId, string attributeName, object attributeValue, string attributeEventUpdatesAttributeName)
{
switch (attributeName)
{
case nameof(XF.ToolbarItem.Order):
ToolbarItemControl.Order = (XF.ToolbarItemOrder)AttributeHelper.GetInt(attributeValue);
break;
case nameof(XF.ToolbarItem.Priority):
ToolbarItemControl.Priority = AttributeHelper.GetInt(attributeValue);
break;
default:
base.ApplyAttribute(attributeEventHandlerId, attributeName, attributeValue, attributeEventUpdatesAttributeName);
break;
}
}
}
}
16 changes: 16 additions & 0 deletions src/Microsoft.MobileBlazorBindings/Elements/Page.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@
// Licensed under the MIT license.

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.MobileBlazorBindings.Core;
using Microsoft.MobileBlazorBindings.Elements.Handlers;
using XF = Xamarin.Forms;

namespace Microsoft.MobileBlazorBindings.Elements
{
public partial class Page : VisualElement
{
static partial void RegisterAdditionalHandlers()
{
ElementHandlerRegistry.RegisterPropertyContentHandler<Page>(nameof(ToolbarItems),
_ => new ListContentPropertyHandler<XF.Page, XF.ToolbarItem>(page => page.ToolbarItems));
}

/// <summary>
/// Indicates that the <see cref="Xamarin.Forms.Page" /> is about to appear.
/// </summary>
Expand All @@ -18,12 +27,19 @@ public partial class Page : VisualElement
/// </summary>
[Parameter] public EventCallback OnDisappearing { get; set; }

[Parameter] public RenderFragment ToolbarItems { get; set; }

#pragma warning disable CA1721 // Property names should not match get methods
[Parameter] public RenderFragment ChildContent { get; set; }
#pragma warning restore CA1721 // Property names should not match get methods

protected override RenderFragment GetChildContent() => ChildContent;

protected override void RenderAdditionalElementContent(RenderTreeBuilder builder, ref int sequence)
{
RenderTreeBuilderHelper.AddContentProperty(builder, sequence++, typeof(Page), nameof(ToolbarItems), ToolbarItems);
}

partial void RenderAdditionalAttributes(AttributesBuilder builder)
{
builder.AddAttribute("onappearing", OnAppearing);
Expand Down
4 changes: 4 additions & 0 deletions src/Microsoft.MobileBlazorBindings/Elements/Page.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ static Page()
{
ElementHandlerRegistry.RegisterElementHandler<Page>(
renderer => new PageHandler(renderer, new XF.Page()));

RegisterAdditionalHandlers();
}

[Parameter] public XF.ImageSource BackgroundImageSource { get; set; }
Expand Down Expand Up @@ -66,5 +68,7 @@ protected override void RenderAttributes(AttributesBuilder builder)
}

partial void RenderAdditionalAttributes(AttributesBuilder builder);

static partial void RegisterAdditionalHandlers();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.MobileBlazorBindings.Core;

namespace Microsoft.MobileBlazorBindings.Elements
{
/// <summary>
/// The only purpose of this type is to wrap content property handler, since currently renderer does not allow
/// handlers without corresponding component.
/// </summary>
#pragma warning disable CA1812 // Avoid uninstantiated internal classes. Class is used generically.
internal class PropertyWrapperComponent : NativeControlComponentBase
#pragma warning restore CA1812 // Avoid uninstantiated internal classes
{
[Parameter] public RenderFragment ChildContent { get; set; }

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
ChildContent(builder);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using Microsoft.AspNetCore.Components;
using Microsoft.MobileBlazorBindings.Core;
using Microsoft.MobileBlazorBindings.Elements.Handlers;
using System.Threading.Tasks;
using XF = Xamarin.Forms;

namespace Microsoft.MobileBlazorBindings.Elements
{
public partial class ToolbarItem : MenuItem
{
static ToolbarItem()
{
ElementHandlerRegistry.RegisterElementHandler<ToolbarItem>(
renderer => new ToolbarItemHandler(renderer, new XF.ToolbarItem()));
}

/// <summary>
/// Gets or sets a value that indicates on which of the primary, secondary, or default toolbar surfaces to display this <see cref="T:Xamarin.Forms.ToolbarItem" /> element.
/// </summary>
[Parameter] public XF.ToolbarItemOrder? Order { get; set; }
/// <summary>
/// Gets or sets the priority of this <see cref="T:Xamarin.Forms.ToolbarItem" /> element.
/// </summary>
[Parameter] public int? Priority { get; set; }

public new XF.ToolbarItem NativeControl => ((ToolbarItemHandler)ElementHandler).ToolbarItemControl;

protected override void RenderAttributes(AttributesBuilder builder)
{
base.RenderAttributes(builder);

if (Order != null)
{
builder.AddAttribute(nameof(Order), (int)Order.Value);
}
if (Priority != null)
{
builder.AddAttribute(nameof(Priority), Priority.Value);
}

RenderAdditionalAttributes(builder);
}

partial void RenderAdditionalAttributes(AttributesBuilder builder);
}
}
40 changes: 40 additions & 0 deletions src/Microsoft.MobileBlazorBindings/RenderTreeBuilderHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.MobileBlazorBindings.Elements;
using System;

namespace Microsoft.MobileBlazorBindings
{
public static class RenderTreeBuilderHelper
{
public static void AddContentProperty(RenderTreeBuilder builder, int sequence, Type containingType, string propertyName, RenderFragment content)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}

if (content != null)
{
builder.OpenRegion(sequence);

// Content properties are handled by separate handlers, there rendered as separate child elements.
// Renderer does not support elements without parent components as for now,
// therefore adding empty parent component to workaround that.
builder.OpenComponent<PropertyWrapperComponent>(0);
builder.AddAttribute(1, "ChildContent", (RenderFragment)(builder =>
{
builder.OpenElement(2, $"p-{containingType.FullName}.{propertyName}");
builder.AddContent(3, content);
builder.CloseElement();
}));
builder.CloseComponent();

builder.CloseRegion();
}
}
}
}

0 comments on commit 8334372

Please sign in to comment.