jsmarcus / Xamarin.Plugins

Cross platform Xamarin and Windows controls and plugins
MIT License
195 stars 79 forks source link

FormsPlugin UWP IconImage and IconToolbarIcon not working #40

Open asneddon opened 7 years ago

asneddon commented 7 years ago

I seem to be able to get the text based controls to work (IconButton, IconLabel), but ones that use an ImageSource don't appear to work on UWP Forms projects. They work on iOS and Droid.

I've manually added the missing .tff files to the required .nugget packages folder as noted in another bug.

Looking at the code, there is a call in IconImageRenderer.cs to UpdateImageAsync(), but the body is commented out.

Also, I cant see the implementation of ToImageSourceAsync() anywhere (which is probably why its commented out).

Is there an ETA for this to be implemented? I'm loving this library as a way to get around building icons for both Light and Dark Themes in UWP apps.

surgien commented 7 years ago

I think the main problem is the empty implementation in IconNavigationRenderer (FormsPlugin.Iconize.UWP). You have no access to the Page and/or to the CommandBar/AppBarButtons: image

Neither with a ContentPageRenderer ​​nor a NavigationPageRenderer. With the ContentPageRenderer you can only override the whole page. The ToolbarItem itsself has no accompanying renderer.

Quite frustrating...

Tlaster commented 7 years ago

This is my custom implement of NavigationPageRenderer, maybe it still works on custom ContentPageRenderer

public class ExIconNavigationRenderer : NavigationPageRenderer
{
    private CommandBar _commandBar;

    public ExIconNavigationRenderer()
    {
        ElementChanged += ExIconNavigationRenderer_ElementChanged;
    }

    private void ContainerElement_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
    {
        ContainerElement.Loaded -= ContainerElement_Loaded;
        _commandBar = typeof(PageControl).GetTypeInfo().GetDeclaredField("_commandBar").GetValue(ContainerElement) as CommandBar;
        _commandBar.DataContextChanged += CommandBar_DataContextChanged;
    }

    private void CommandBar_DataContextChanged(Windows.UI.Xaml.FrameworkElement sender, Windows.UI.Xaml.DataContextChangedEventArgs args)
    {
        var toolbarItems = (args.NewValue as Xamarin.Forms.Page).ToolbarItems;
        foreach (IconToolbarItem item in toolbarItems.Where(item => item is IconToolbarItem && (item as IconToolbarItem).IsVisible))
        {
            var element = _commandBar.PrimaryCommands.Where(command => command is AppBarButton && (command as AppBarButton).DataContext == item).FirstOrDefault();
            if (element == null) continue;
            var appBarButton = element as AppBarButton;
            var icon = Plugin.Iconize.Iconize.FindIconForKey(item.Icon);
            if (icon == null) continue;
            appBarButton.Icon = new FontIcon()
            {
                FontFamily = Plugin.Iconize.Iconize.FindModuleOf(icon).ToFontFamily(),
                Glyph = $"{icon.Character}",
                Foreground = new SolidColorBrush(item.IconColor.ToWindowsColor()),
            };
        }
    }

    private void ExIconNavigationRenderer_ElementChanged(object sender, VisualElementChangedEventArgs e)
    {
        ElementChanged -= ExIconNavigationRenderer_ElementChanged;
        ContainerElement.Loaded += ContainerElement_Loaded;
    }
}

And this is my custom implement of IconImageRenderer, It uses Win2D

public class ExIconImageRenderer : IconImageRenderer
{
    protected override async void OnElementChanged(ElementChangedEventArgs<Image> e)
    {
        base.OnElementChanged(e);
        if (Control == null || Element == null)
            return;
        await UpdateImage();
    }
    protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
        if (Control == null || Element == null)
            return;

        switch (e.PropertyName)
        {
            case nameof(IconImage.Icon):
            case nameof(IconImage.IconColor):
            case nameof(IconImage.IconSize):
                await UpdateImage();
                break;
        }
    }

    private async Task UpdateImage()
    {
        var iconImage = Element as IconImage;
        var icon = Plugin.Iconize.Iconize.FindIconForKey(iconImage.Icon);
        CanvasDevice device = CanvasDevice.GetSharedDevice();
        var target = new CanvasRenderTarget(device, Convert.ToSingle(Element.HeightRequest), Convert.ToSingle(Element.HeightRequest), 96 * 4);
        using (var session = target.CreateDrawingSession())
        using (var format = new CanvasTextFormat { FontSize = Convert.ToSingle(Element.HeightRequest), FontFamily = Plugin.Iconize.Iconize.FindModuleOf(icon).ToFontFamily().Source })
        using (var textLayout = new CanvasTextLayout(device, $"{icon.Character}", format, Convert.ToSingle(Element.HeightRequest), Convert.ToSingle(Element.HeightRequest)))
            session.DrawTextLayout(textLayout, 0, 0, iconImage.IconColor.ToWindowsColor());
        using (var stream = new InMemoryRandomAccessStream())
        {
            await target.SaveAsync(stream, CanvasBitmapFileFormat.Png);
            stream.Seek(0);
            BitmapImage result = new BitmapImage();
            await result.SetSourceAsync(stream);
            Control.Source = result;
        }
    }
}

Hopes it can help

surgien commented 7 years ago

My _commandBar.PrimaryCommands are always empty. How do you fill the ContentPage.ToolbarItems? With the ContentPage you do not come on the commandBar.

Tlaster commented 7 years ago

Ok I notice that you still need a IconNavigationPage outside the ContentPage, just like MainPage = new ExIconNavigationPage(new YourCustomContentPage()); if you just set the MainPage to YourCustomContentPage, it's not gonna work

surgien commented 7 years ago

No change, PrimaryCommands are still empty:

var page = pages[viewModelType];
            var detailPage = new >**IconNavigationPage**<(page);
            masterPage.ListView.SelectedItem = null;
            masterPage.SettingsListView.SelectedItem = null;

            foreach (var item in page.ToolbarItems)
            {
                detailPage.ToolbarItems.Add(item);
            }
detailPage.ToolbarItems.Add(new FormsPlugin.Iconize.IconToolbarItem() { Priority = 10, Name = "Search", Command = new Command(() => IsPresented = true) });
[assembly: ExportRenderer(typeof(IconNavigationPage), typeof(AppNavigationPageRenderer))]
[assembly: ExportRenderer(typeof(IconImage), typeof(IconImageRenderer))]
namespace ...{
    public class AppNavigationPageRenderer : NavigationPageRenderer
    {
    ...
    }
}
pcdus commented 6 years ago

@surgien Did you found a solution?

surgien commented 6 years ago

puhh it was long ago...

iirc you have two different command collections.

Look here: https://github.com/surgien/GamesLibrary/blob/master/src/GamesLibrary.UWP/Renderer/IconNavigationPageRenderer.cs

pcdus commented 6 years ago

@surgien Thank you for your feedback. But you have finally removed Iconize, isn't it? Which font are using as glyph for the ToolbarIcon?

surgien commented 6 years ago

native platform fonts: var icon = new FontIcon() { FontFamily = new FontFamily("Segoe MDL2 Assets"), Glyph = toolBarItem.IconGlyph };

*Line 44