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

Add Resources and Style to Popup #1351

Merged
merged 38 commits into from
Oct 22, 2023
Merged

Conversation

cat0363
Copy link
Contributor

@cat0363 cat0363 commented Aug 17, 2023

In this PR, we will add Resources and Style to the Popup so that Style can be applied to the Popup.

Description of Change

Since Popup is a class that inherits from Element and not from VisualElement, it does not have Resources and Style.
Modifications were made with reference to the following source code of .NET MAUI.

[src\Controls\src\Core\MergedStyle.cs]
[src\Controls\src\Core\Shell\NavigableElement.cs]
[src\Controls\src\Core\VisualElement\VisualElement.cs]

See above for the main implementation.

Inherited IResourcesProvider to be able to specify Resources for Popup.

[src\CommunityToolkit.Maui\Views\Popup\Popup.shared.cs]

public partial class Popup : Element, IPopup, IWindowController, IPropertyPropagationController, IResourcesProvider, IStyleSelectable

I also added the following implementation:

[src\CommunityToolkit.Maui\Views\Popup\Popup.shared.cs]

ResourceDictionary? resources;
bool IResourcesProvider.IsResourcesCreated => resources != null;

public ResourceDictionary Resources
{
    get
    {
        if (resources != null)
        {
            return resources;
        }

        resources = new ResourceDictionary();
        ((IResourceDictionary)resources).ValuesChanged += OnResourcesChanged;
        return resources;
    }
    set
    {
        if (resources == value)
        {
            return;
        }

        OnPropertyChanging();
        if (resources != null)
        {
            ((IResourceDictionary)resources).ValuesChanged -= OnResourcesChanged;
        }

        resources = value;
        OnResourcesChanged(value);
        if (resources != null)
        {
            ((IResourceDictionary)resources).ValuesChanged += OnResourcesChanged;
        }

        OnPropertyChanged();
    }
}

internal override void OnParentResourcesChanged(IEnumerable<KeyValuePair<string, object>> values)
{
    if (values == null)
    {
        return;
    }

    if (!((IResourcesProvider)this).IsResourcesCreated || Resources.Count == 0)
    {
        base.OnParentResourcesChanged(values);
        return;
    }

    var innerKeys = new HashSet<string>(StringComparer.Ordinal);
    var changedResources = new List<KeyValuePair<string, object>>();
    foreach (KeyValuePair<string, object> c in Resources)
    {
        innerKeys.Add(c.Key);
    }

    foreach (KeyValuePair<string, object> value in values)
    {
        if (innerKeys.Add(value.Key))
        {
            changedResources.Add(value);
        }
        else if (value.Key.StartsWith(Style.StyleClassPrefix, StringComparison.Ordinal))
        {
            var innerStyle = Resources[value.Key] as List<Style>;
            var parentStyle = value.Value as List<Style>;
            if (innerStyle is not null)
            {
                var mergedClassStyles = new List<Style>(innerStyle);
                if (parentStyle is not null)
                {
                    mergedClassStyles.AddRange(parentStyle);
                }
                changedResources.Add(new KeyValuePair<string, object>(value.Key, mergedClassStyles));
            }
        }
    }
    if (changedResources.Count != 0)
    {
        OnResourcesChanged(changedResources);
    }
}

The above is ported with reference to the VisualElement class of .NET MAUI.
[src\Controls\src\Core\VisualElement\VisualElement.cs]

This makes it possible to specify Resources in the Popup.

Next, I added the following to make it possible to specify global styles.Next, I added the following to make it possible to specify global styles.

[src\CommunityToolkit.Maui\Views\Popup\Popup.shared.cs]

readonly MergedStyle mergedStyle;

public Popup()
{
    platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<Popup>>(() => new(this));

    mergedStyle = new MergedStyle(GetType().BaseType, this);
}

GetType().BaseType is specified as the first argument of the constructor of MergedStyle, because the style search target within the implementation of the MergedStyle class is the following class.

static readonly IList<Type> s_stopAtTypes = new List<Type> { typeof(View), typeof(Compatibility.Layout<>), typeof(VisualElement), typeof(NavigableElement), typeof(Element) };

I referenced the implementation below.
[src\Controls\src\Core\MergedStyle.cs]
[src\Controls\src\Core\Shell\NavigableElement.cs]

Since Popup inherits from Element and Element is the first hit when searching for a class to apply the style, the type of Popup's Base class is specified.

This makes it possible to specify global styles in the Popup.

Next, Inherited IStyleSelectable to allow styles to be applied to the Popup.

[src\CommunityToolkit.Maui\Views\Popup\Popup.shared.cs]

public partial class Popup : Element, IPopup, IWindowController, IPropertyPropagationController, IResourcesProvider, IStyleSelectable

I also added the following implementation:

[src\CommunityToolkit.Maui\Views\Popup\Popup.shared.cs]

public static readonly BindableProperty StyleProperty = BindableProperty.Create(nameof(Style), typeof(Style), typeof(Popup), default(Style), propertyChanged: (bindable, oldValue, newValue) => ((Popup)bindable).mergedStyle.Style = (Style)newValue);

public Style Style
{
    get { return (Style)GetValue(StyleProperty); }
    set { SetValue(StyleProperty, value); }
}

[System.ComponentModel.TypeConverter(typeof(ListStringTypeConverter))]
public IList<string> StyleClass
{
    get { return mergedStyle.StyleClass; }
    set { mergedStyle.StyleClass = value; }
}

I ported it with reference to the following implementation of .NET MAUI.
[src\Controls\src\Core\VisualElement\VisualElement.cs]

The above implementation allows you to specify implicit styles, explicit styles, dynamic styles, and style classes.

Linked Issues

PR Checklist

Additional information

Windows results are with PR #1350 applied.
Below is the validation result when specifying the global style.

[App.xaml]

<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    xmlns:local="clr-namespace:MauiComm_VerifyPopupStyle"
    x:Class="MauiComm_VerifyPopupStyle.App">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources/Styles/Colors.xaml" />
                <ResourceDictionary Source="Resources/Styles/Styles.xaml" />
            </ResourceDictionary.MergedDictionaries>

            <Style TargetType="toolkit:Popup">
                <Setter Property="Size" Value="100, 100" />
                <Setter Property="Color" Value="Red" />
                <Setter Property="HorizontalOptions" Value="Start" />
                <Setter Property="VerticalOptions" Value="End" />
                <Setter Property="CanBeDismissedByTappingOutsideOfPopup" Value="False" />
            </Style>

        </ResourceDictionary>
    </Application.Resources>
</Application>

[Popup1.xaml]

<toolkit:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    x:Class="MauiComm_VerifyPopupStyle.Popup1">

    <Grid>
        <Grid.GestureRecognizers>
            <TapGestureRecognizer Tapped="gd_Tapped" />
        </Grid.GestureRecognizers>
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="Start" HorizontalOptions="Start" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="Start" HorizontalOptions="End" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="End" HorizontalOptions="Start" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="End" HorizontalOptions="End" BackgroundColor="Blue" />
    </Grid>

</toolkit:Popup>
[Android] [iOS] [Windows]
Android.Emulator.-.pixel_2_-_api_30_5554.2023-08-17.16-12-19.mp4
iPhone.14.iOS.16.4.2023-08-17.16-36-59.mp4
2023-08-17.16-23-55.mp4

Below is the validation result when specifying an explicit style.

<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    xmlns:local="clr-namespace:MauiComm_VerifyPopupStyle"
    x:Class="MauiComm_VerifyPopupStyle.App">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources/Styles/Colors.xaml" />
                <ResourceDictionary Source="Resources/Styles/Styles.xaml" />
            </ResourceDictionary.MergedDictionaries>

            <Style x:Key="stlExplicitStyle" TargetType="toolkit:Popup">
                <Setter Property="Size" Value="200, 100" />
                <Setter Property="Color" Value="Yellow" />
                <Setter Property="HorizontalOptions" Value="End" />
                <Setter Property="VerticalOptions" Value="Start" />
                <Setter Property="CanBeDismissedByTappingOutsideOfPopup" Value="False" />
            </Style>

        </ResourceDictionary>
    </Application.Resources>
</Application>

[Popup2.xaml]

<toolkit:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    x:Class="MauiComm_VerifyPopupStyle.Popup2" Style="{StaticResource stlExplicitStyle}">

    <Grid>
        <Grid.GestureRecognizers>
            <TapGestureRecognizer Tapped="gd_Tapped" />
        </Grid.GestureRecognizers>
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="Start" HorizontalOptions="Start" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="Start" HorizontalOptions="End" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="End" HorizontalOptions="Start" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="End" HorizontalOptions="End" BackgroundColor="Blue" />
    </Grid>

</toolkit:Popup>
[Android] [iOS] [Windows]
Android.Emulator.-.pixel_2_-_api_30_5554.2023-08-17.16-13-37.mp4
iPhone.14.iOS.16.4.2023-08-17.16-37-15.mp4
2023-08-17.16-24-35.mp4

Below is the validation result when specifying the implicit style.

[Popup3.xaml]

<toolkit:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    x:Class="MauiComm_VerifyPopupStyle.Popup3">
    <toolkit:Popup.Resources>
        <Style TargetType="toolkit:Popup">
            <Setter Property="Size" Value="100, 200" />
            <Setter Property="Color" Value="Green" />
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="VerticalOptions" Value="Center" />
            <Setter Property="CanBeDismissedByTappingOutsideOfPopup" Value="False" />
        </Style>
    </toolkit:Popup.Resources>

    <Grid>
        <Grid.GestureRecognizers>
            <TapGestureRecognizer Tapped="gd_Tapped" />
        </Grid.GestureRecognizers>
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="Start" HorizontalOptions="Start" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="Start" HorizontalOptions="End" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="End" HorizontalOptions="Start" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="End" HorizontalOptions="End" BackgroundColor="Blue" />
    </Grid>

</toolkit:Popup>
[Android] [iOS] [Windows]
Android.Emulator.-.pixel_2_-_api_30_5554.2023-08-17.16-15-52.mp4
iPhone.14.iOS.16.4.2023-08-17.16-37-27.mp4
2023-08-17.16-25-43.mp4

Below is the validation result when specifying the dynamic style.

<toolkit:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    x:Class="MauiComm_VerifyPopupStyle.Popup4" Style="{DynamicResource stlDynamicStyle}">
    <toolkit:Popup.Resources>
        <Style x:Key="stlDynamicStyle" TargetType="toolkit:Popup">
            <Setter Property="Size" Value="100, 100" />
            <Setter Property="Color" Value="Pink" />
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="VerticalOptions" Value="Center" />
            <Setter Property="CanBeDismissedByTappingOutsideOfPopup" Value="False" />
        </Style>
    </toolkit:Popup.Resources>

    <Grid>
        <Grid.GestureRecognizers>
            <TapGestureRecognizer Tapped="gd_Tapped" />
        </Grid.GestureRecognizers>
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="Start" HorizontalOptions="Start" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="Start" HorizontalOptions="End" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="End" HorizontalOptions="Start" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="End" HorizontalOptions="End" BackgroundColor="Blue" />
    </Grid>

</toolkit:Popup>
[Android] [iOS] [Windows]
Android.Emulator.-.pixel_2_-_api_30_5554.2023-08-17.16-17-04.mp4
iPhone.14.iOS.16.4.2023-08-17.16-37-39.mp4
2023-08-17.16-26-23.mp4

Below is the validation result when specifying the style class.

<toolkit:Popup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    x:Class="MauiComm_VerifyPopupStyle.Popup5" StyleClass="PopupStyle">
    <toolkit:Popup.Resources>
        <Style TargetType="toolkit:Popup" Class="PopupStyle">
            <Setter Property="Size" Value="150, 150" />
            <Setter Property="Color" Value="Orange" />
            <Setter Property="HorizontalOptions" Value="Center" />
            <Setter Property="VerticalOptions" Value="End" />
            <Setter Property="CanBeDismissedByTappingOutsideOfPopup" Value="False" />
        </Style>
    </toolkit:Popup.Resources>

    <Grid>
        <Grid.GestureRecognizers>
            <TapGestureRecognizer Tapped="gd_Tapped" />
        </Grid.GestureRecognizers>
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="Start" HorizontalOptions="Start" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="Start" HorizontalOptions="End" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="End" HorizontalOptions="Start" BackgroundColor="Blue" />
        <Grid WidthRequest="10" HeightRequest="10" VerticalOptions="End" HorizontalOptions="End" BackgroundColor="Blue" />
    </Grid>

</toolkit:Popup>
[Android] [iOS] [Windows]
Android.Emulator.-.pixel_2_-_api_30_5554.2023-08-17.16-18-23.mp4
iPhone.14.iOS.16.4.2023-08-17.16-37-51.mp4
2023-08-17.16-29-32.mp4

From the above, you can see that the style can be applied to the Popup as intended.
I would like to ask you to correct the document.

@cat0363
Copy link
Contributor Author

cat0363 commented Aug 17, 2023

The app created for verification of this PR has been uploaded to the following location.
https://github.com/cat0363/MauiComm-VerifyPopupStyle.git

Copy link
Collaborator

@brminnick brminnick left a comment

Choose a reason for hiding this comment

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

FYI - a unit test is failing https://dev.azure.com/dotnet/CommunityToolkit/_build/results?buildId=97272&view=logs&j=3f96dcf5-6e1e-5485-3200-c557d5216be3&t=885110bc-af55-5c2d-c8fd-8677fc4411e5&l=49

 Failed CommunityToolkit.Maui.UnitTests.Views.PopupTests.NullColorThrowsArgumentNullException [8 ms]
  Error Message:
   System.NullReferenceException : Object reference not set to an instance of an object.
  Stack Trace:
     at Microsoft.Maui.Controls.MergedStyle.RegisterImplicitStyles()
   at Microsoft.Maui.Controls.MergedStyle..ctor(Type targetType, BindableObject target)
   at CommunityToolkit.Maui.Views.Popup..ctor() in /_/src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs:line 68
   at CommunityToolkit.Maui.UnitTests.Views.PopupTests.NullColorThrowsArgumentNullException() in /Users/runner/work/1/s/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupTests.cs:line 190
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)

@cat0363
Copy link
Contributor Author

cat0363 commented Aug 17, 2023

@brminnick , Thanks for the unit test results.
I'll try to investigate why the unit test failed.
As far as I can see inside the MergedStyle class, it seems necessary to implement an equivalent class.

@cat0363
Copy link
Contributor Author

cat0363 commented Aug 21, 2023

I modified the TargetType passed to the constructor of the MergedStyle class as follows.

[When the result obtained by GetType() is Popup]

GetType()

[When the result obtained by GetType() is not Popup]

GetType().BaseType

[src\CommunityToolkit.Maui\Views\Popup\Popup.shared.cs]

public Popup()
{
    platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<Popup>>(() => new(this));

    var type = GetType();		
    mergedStyle = new MergedStyle(type == typeof(Popup) ? type : type.BaseType, this);
}

When using the Popup class without inheriting it, when searching for implicit styles with the RegisterImplicitStyles
method of the MergedStyle class, the Popup's BaseType was Element and the intended style could not be found,
causing a NullReferenceException. If you don't inherit the Popup class and use it as it is, I modified it so that the
TargetType is not the Popup's BaseType, but the Popup itself. This allows implicit styles to be explored as intended.

@cat0363 cat0363 requested a review from brminnick August 22, 2023 08:48
@brminnick brminnick added the needs discussion Discuss it on the next Monthly standup label Sep 7, 2023
@brminnick brminnick added approved This Proposal has been approved and is ready to be added to the Toolkit and removed needs discussion Discuss it on the next Monthly standup labels Sep 7, 2023
Copy link
Member

@pictos pictos left a comment

Choose a reason for hiding this comment

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

Thanks for this PR, looks good in general, I believe this one will be an easy one to merge. Can you add a sample page showing of this feature?

src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs Outdated Show resolved Hide resolved
src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs Outdated Show resolved Hide resolved
src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs Outdated Show resolved Hide resolved
src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs Outdated Show resolved Hide resolved
src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs Outdated Show resolved Hide resolved
@cat0363
Copy link
Contributor Author

cat0363 commented Sep 11, 2023

Thanks for this PR, looks good in general, I believe this one will be an easy one to merge. Can you add a sample page showing of this feature?

I would like to think about a sample using the creation of the sample project as a reference.
Defining implicit styles will affect other Popups in the sample, so I would like to create a sample that applies
styles to classes that inherit from Popup.

Copy link
Contributor

@bijington bijington left a comment

Choose a reason for hiding this comment

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

@cat0363 this is looking great! Just a small point on the casting. Otherwise I think we are good!

Thank you so much for this!

src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs Outdated Show resolved Hide resolved
bijington
bijington previously approved these changes Sep 29, 2023
Copy link
Contributor

@bijington bijington left a comment

Choose a reason for hiding this comment

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

I think we are good! Thanks so much @cat0363

@brminnick brminnick added pending documentation This feature requires documentation do not merge Do not merge this PR labels Sep 29, 2023
@brminnick
Copy link
Collaborator

brminnick commented Sep 29, 2023

Awesome!! Congrats and thank you @cat0363 for your always awesome work!!

The next step is to write the documentation for the new APIs! I've temporarily added the pending documentation This feature requires documentation and do not merge Do not merge this PR labels to ensure we don't accidentally merge this PR before the docs PR has been approved.

You can open a PR on the MicrosoftDocs repo here:
https://github.com/MicrosoftDocs/CommunityToolkit/pulls

I'll defer to @bijington to help with any questions you may have 🙌

@bijington
Copy link
Contributor

@cat0363 how do you feel about doing the docs? If you like I am more than happy to take on the effort

@cat0363
Copy link
Contributor Author

cat0363 commented Sep 30, 2023

@bijington , I wish I could write a document, but I'm embarrassed to say that I don't have the literary talent to write it in fluent English that anyone can understand. If possible, I would like to ask @bijington , who has published an excellent book, to create this document. I read your wonderful book.
Please let me know if there is anything I can do to help, such as providing screen captures or samples.

@cat0363 how do you feel about doing the docs? If you like I am more than happy to take on the effort

@brminnick , Thanks for explaining what the labels mean.

@bijington
Copy link
Contributor

@cat0363 poease don't be embarrassed, I will happily do it. And if you ever need assistance in this area I'm always willing to help.

And wow! Thank you so much for firstly buying the book and secondly for the kind words. I can't tell you how much I appreciate it!

@cat0363
Copy link
Contributor Author

cat0363 commented Oct 1, 2023

@bijington , Thank you for taking on the task of creating this document.
Your books contributed to improving my skills.

@brminnick brminnick enabled auto-merge (squash) October 21, 2023 13:42
Copy link
Collaborator

@brminnick brminnick left a comment

Choose a reason for hiding this comment

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

Thanks @cat0363 and @bijington!!

@brminnick brminnick merged commit 6d8065c into CommunityToolkit:main Oct 22, 2023
8 checks passed
@cat0363
Copy link
Contributor Author

cat0363 commented Oct 22, 2023

@bijington , Thank you for creating the document.
Thank you everyone for the code reviews.
I'm glad that this PR was featured on Standup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved This Proposal has been approved and is ready to be added to the Toolkit
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] Can't apply style to popup
4 participants