Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Adds FontImage Markup Extension for FontImageSource #6398

Merged
merged 12 commits into from
Jun 28, 2019

Conversation

SkyeHoefling
Copy link
Contributor

Description of Change

Adds Markup Extension for FontImageSource to simplify usage from verbose XAML

<ImageButton>
    <ImageButton.Source>
        <FontImageSource
            FontFamily="{StaticResource MyFontFamily}"
            Glyph="{StaticResource SmileFace}"
            Color="{StaticResource PrimaryColor}" />
    </Image.Source>
</ImageButton>
<ImageButton Source="{FontImage FontFamily={StaticResource MyFontFamily}, Glyph={StaticResource SmileFace}, Color={StaticResource White}" />

Issues Resolved

API Changes

Added MarkupExtension: FontImageExtension to Xamarin.Forms.Xaml

Added:

string FontFamily { get; set; }
string Glyph { get; set; }
Color Color { get; set; }

Platforms Affected

  • Core/XAML (all platforms)
  • Any platform that implements FontImageSource

Behavioral/Visual Changes

None

Before/After Screenshots

Not applicable

Testing Procedure

This markup extension has been tested in the Sandbox project and a confidential project I am working on.

I added positive and negative unit tests in the Xamarin.Forms.Xaml.UnitTests project

PR Checklist

  • Has automated tests
  • Rebased on top of the target branch at time of PR
  • Changes adhere to coding standard

@@ -24,6 +24,8 @@ public object Parse(string match, ref string remaining, IServiceProvider service
markupExtension = new OnIdiomExtension();
else if (match == "DataTemplate")
markupExtension = new DataTemplateExtension();
else if (match == "FontImage")
Copy link
Member

Choose a reason for hiding this comment

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

not required. I'm not even sure we save any measurable time doing this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can remove this, no problem


namespace Xamarin.Forms.Xaml
{
[AcceptEmptyServiceProvider]
Copy link
Member

Choose a reason for hiding this comment

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

❤️

Copy link
Member

Choose a reason for hiding this comment

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

I'd add the Glyph as ContentProperty attribute

Suggested change
[AcceptEmptyServiceProvider]
[AcceptEmptyServiceProvider]
[ContentProperty("Glyph")]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not my first MarkupExtension and I noticed the other platform ones have [ContentProperty] but I am not sure exactly what it is for. How is it used?

Adding this change is not a problem at all

@SkyeHoefling
Copy link
Contributor Author

@StephaneDelcroix I have the Xamarin.Forms.Sandbox project working with this change but didn't commit it. Should I commit that change as well to make it easier for you to see this change in action?

@ChaseFlorell
Copy link

ContentProperty makes it so that you don't have to define Glyph=.

@SkyeHoefling
Copy link
Contributor Author

@samhouts @StephaneDelcroix
Is there a tipping point on adding too many properties on the markup extension and how much we really gain? I am using this in one of my projects and came to a point where I really wanted to use Size.

I am thinking about adding the property (Size) in because it is really simple but want to check with everyone before I do that.

@ChaseFlorell
Copy link

@samhouts @StephaneDelcroix
Is there a tipping point on adding too many properties on the markup extension and how much we really gain? I am using this in one of my projects and came to a point where I really wanted to use Size.

I am thinking about adding the property (Size) in because it is really simple but want to check with everyone before I do that.

I think all options need to be there, or the extension isn't fully useful. (side note: default 30 for size is absurd).

I also put together a proposal whereby we could set some defaults in order to keep verbosity down.
#6421

@SkyeHoefling
Copy link
Contributor Author

I left it out originally because my target use case was for the Shell TabBar but now I am using it in another part of my app and found it really needs Size.

@StephaneDelcroix
Copy link
Member

@ahoefling having all properties exposed adds versatility. that's what we want (see Binding). If all the options were mandatory, that would be verbose, but that's not the case here

@ChaseFlorell
Copy link

I gave this a try and got a strange result.

<!-- works good -->
<ImageButton Source="{FontImage FontFamily={StaticResource MyFontFamily}, Glyph={x:Static xml:Images.SmileFace}, Color={StaticResource White}" />

but

<!-- doesn't properly set Glyph -->
<ImageButton Source="{FontImage {x:Static xml:Images.SmileFace}, FontFamily={StaticResource MyFontFamily}, Color={StaticResource White}" />

In other words, the ContentProperty doesn't pick up the path the same way

@SkyeHoefling
Copy link
Contributor Author

SkyeHoefling commented Jun 6, 2019

Could it be because we are using

[ContentProperty(nameof(Glyph))]

instead of

[ContentProperty("Glyph")]

I don't think this would cause that, but I don't know much about how the ContentProperty works except what we discussed earlier

@SkyeHoefling
Copy link
Contributor Author

I just added more unit tests to cover this and did some manually testing as well. It appears that [ContentProperty] is not properly picking up the markup extension of x:Static in the usage you demonstrated (@ChaseFlorell). When I attached the debugger to the unit test is shows the string literal of

{x:Static xml:Images.SmileFace

Yes, that is correct and it is missing the trailing } (curly brace). I then updated the code to pass in a hardcoded Glyph and it all appears to work.

@ChaseFlorell I think you discovered a bug with how the [ContentProperty] works with nesting usage of markup extensions.

@ChaseFlorell
Copy link

I'm going to be submitting a proposal today that will require that the [ContentProperty(nameof(Glyph))] not be included on this markup extension.

Once the proposal is in, I will link back to this for further discussion.

@SkyeHoefling
Copy link
Contributor Author

After running several tests today I don't see a lot of value in [ContentProperty(nameof(Glyph)] since you can't use a markup extension with it to access your static reference. #6398 (comment).

I'm recommending that we remove the [ContentProperty(nameof(Glyph))] attribute so we can add a more useful [ContentProperty] in the future that won't be a breaking change.

public string FontFamily { get; set; }
public string Glyph { get; set; }
public Color Color { get; set; }
public double Size { get; set; } = 30d;
Copy link
Member

Choose a reason for hiding this comment

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

not sure about setting a default. does FontImageSource requires a Size ?

Choose a reason for hiding this comment

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

Size already defaults to 30 on FontImageSource, but if you don't set the default here, it'll revert to 0.

Perhaps you can use FontImageSource.SizeProperty.DefaultValue

Choose a reason for hiding this comment

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

alternatively, I don't know if FontImageSource has a concept of a magic value -1 to roll over to default.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My thought process behind this is the default size for FontImageSource is 30d. If someone uses this markup extension it will default to 0 unless they explicitly provide a size.

Perhaps we add a static default in FontImageSource and that way both the markup extension and FontImageSource can reference the default size.

FontImageSource

public class FontImageSource : ImageSource
{
    // ...
    public static double DefaultSize = 30d;
    // ...
}

FontImageExtension

public class FontImageExtension : IMarkupExtension<ImageSource>
{
    // ...
    public double Size { get; set; } = FontImageSource.DefaultSize;
    // ...
}

Choose a reason for hiding this comment

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

You can access it directly off of the SizeProperty

public double Size { get; set; } = (double)FontImageSource.SizeProperty.DefaultValue;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is a great suggestion, I'll go an update that to use the FontImageSource.SizeProperty.DefaultValue

…alue. This will keep it in sync if that value ever changes
@StephaneDelcroix
Copy link
Member

The ContentProperty has to be a literal, not another markup extension. I don't think it's bug

@SkyeHoefling
Copy link
Contributor Author

Is there a reason why ContentProperty doesn't support MarkupExtensions? Is this something that should be included in a proposal and feature request?

@StephaneDelcroix
Copy link
Member

Is there a reason why ContentProperty doesn't support MarkupExtensions? Is this something that should be included in a proposal and feature request?

Here are the reasons I can think of:

  1. the need never showed up
  2. I have no idea if it's supported in other XAML variants,
  3. It would be very-very odd to write {Binding {StaticResource path}}

Now, if 2. is false and the syntax is supported in WPF or UWP (and it's supported by the XLS (/cc @mgoertz-msft)) we could consider it, but we're not going to make a precedent because of 1.

I suggested adding a ContentProperty not to support StaticResource for the Glyph, but for the cases when you prefer to use the glyph code instead of a resource.

@SkyeHoefling
Copy link
Contributor Author

@StephaneDelcroix thanks for the detailed explanation, I am not as familiar with the reasoning behind it and wanted to understand better.

At this point I am in agreement with adding [ContentProperty(nameof(Glyph))] because it requires a string literal and we can't use a markup extension. Considering the proposal that @ChaseFlorell is working on I don't think ContentProperty would create any breaking changes as we would have to use the declarative syntax of

 <ImageButton Source="{FontImage Glyph={x:Static xml:Images.SmileFace}}" />

The ContentProperty is added here as a convenience utility for people that want to use a string literal

@samhouts samhouts added the API-change Heads-up to reviewers that this PR may contain an API change label Jun 25, 2019
@samhouts samhouts added the approved Has two approvals, no pending reviews, and no changes requested label Jun 25, 2019
@samhouts samhouts merged commit efa9cf4 into xamarin:master Jun 28, 2019
v4.2.0 automation moved this from In Progress to Done Jun 28, 2019
@samhouts samhouts removed this from Done in v4.2.0 Jul 4, 2019
@samhouts samhouts added this to the 4.2.0 milestone Jul 16, 2019
@samhouts samhouts added this to Done in v4.2.0 Jul 16, 2019
@samhouts samhouts added the F100 label Jul 25, 2019
@jamesmontemagno
Copy link
Contributor

jamesmontemagno commented Aug 22, 2019

Question...
It looks like this doesn't work with DynamicResources in the FontFamily?

Source="{FontImage FontFamily={DynamicResource MaterialFontFamily},
                                    Glyph={StaticResource IconUpNext}, 
                                    Color={StaticResource PrimaryColor},
                                    Size=15}"
            <OnPlatform x:Key="MaterialFontFamily" x:TypeArguments="x:String">
                <On Platform="iOS" Value="Material Design Icons" />
                <On Platform="Android" Value="materialdesignicons-webfont.ttf#Material Design Icons" />
                <On Platform="UWP" Value="Assets/Fonts/materialdesignicons-webfont.ttf#Material Design Icons" />
            </OnPlatform>

Compiler gives error about FontFamily:
. No property, bindable property, or event found for 'FontFamily', or mismatching type between value and property.
1>Done building project "Hanselman.csproj" -- FAILED.

if I changeto StaticResource compiles, but throws an exception at runtime.

@StephaneDelcroix
Copy link
Member

the extension is fully resolved at the time it's parsed, that's why DynamicResource won't work

@jamesmontemagno
Copy link
Contributor

Gotcha... any way to solve this to clean it up? Suggestions?

@jamesmontemagno
Copy link
Contributor

Something like:

FontFamily={OnPlatform 
                        iOS={StaticResource MaterialFontFamilyiOS},
                        Android={StaticResource MaterialFontFamilyAndroid}}

?

@StephaneDelcroix
Copy link
Member

@jamesmontemagno don't use the markup extension, it doesn't provide a lot of benefits as it doesn't save a lot of precious keystrokes

@jamesmontemagno
Copy link
Contributor

Yeah, but it looks better! Can i define a StaticResource and update it in the App.xaml.cs?

@jamesmontemagno
Copy link
Contributor

NM it all works with StaticResource in place :) yay... for some reason Xamarin.Forms didn't update when I updated Xamarin.Forms.Visual.Material... weird.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
a/Xaml </> API-change Heads-up to reviewers that this PR may contain an API change approved Has two approvals, no pending reviews, and no changes requested F100 proposal-accepted t/enhancement ➕
Projects
No open projects
v4.2.0
  
Done
Development

Successfully merging this pull request may close these issues.

[Enhancement] Add Markup Extension for FontImageSource
5 participants