diff --git a/src/Controls/src/Xaml/MarkupExtensions/OnPlatformExtension.cs b/src/Controls/src/Xaml/MarkupExtensions/OnPlatformExtension.cs index 5d8d080e6ca7..286a86ce704a 100644 --- a/src/Controls/src/Xaml/MarkupExtensions/OnPlatformExtension.cs +++ b/src/Controls/src/Xaml/MarkupExtensions/OnPlatformExtension.cs @@ -10,6 +10,32 @@ namespace Microsoft.Maui.Controls.Xaml /// /// Provides a XAML markup extension that returns different values depending on the platform the app is running on. /// + /// + /// The value is resolved by matching the identifier + /// (the struct's ToString() value) + /// against the per-platform values, so a custom backend whose platform string matches one of the named + /// properties below (for example GTK) resolves correctly at runtime. The match is + /// case-sensitive (ordinal, per equality), so the + /// identifier must match the property name's casing exactly — a backend reporting + /// DevicePlatform.Create("gtk") (lowercase) does not match the GTK property and falls through + /// to . Note that the compile-time + /// optimization (SimplifyOnPlatformVisitor) currently recognizes only the + /// -android/-ios/-macos/-maccatalyst target frameworks; every other target + /// framework (including -windows and -tizen) and any custom backend falls back to runtime + /// resolution. + /// + /// Because a XAML markup extension maps each named argument to a CLR property, the inline + /// {OnPlatform iOS=…, Android=…} form can only express the platforms exposed as properties here. + /// To target an arbitrary custom platform (e.g. a backend reporting + /// DevicePlatform.Create("Web")), use the element form, which accepts any platform string: + /// + /// <OnPlatform x:TypeArguments="x:String"> + /// <On Platform="Web" Value="…" /> + /// <On Platform="iOS, Android" Value="…" /> + /// </OnPlatform> + /// + /// + /// [ContentProperty(nameof(Default))] [RequireService( [typeof(IProvideValueTarget), @@ -31,14 +57,21 @@ public class OnPlatformExtension : IMarkupExtension /// public object Android { get; set; } = s_notset; - internal object GTK { get; set; } = s_notset; + /// + /// Gets or sets the value to use on GTK. + /// + public object GTK { get; set; } = s_notset; /// /// Gets or sets the value to use on iOS. /// public object iOS { get; set; } = s_notset; - internal object macOS { get; set; } = s_notset; + /// + /// Gets or sets the value to use on macOS. + /// + /// Note, this is different than . + public object macOS { get; set; } = s_notset; /// /// Gets or sets the value to use on Mac Catalyst. @@ -56,7 +89,10 @@ public class OnPlatformExtension : IMarkupExtension [Obsolete("Use WinUI instead.")] public object UWP { get; set; } = s_notset; - internal object WPF { get; set; } = s_notset; + /// + /// Gets or sets the value to use on WPF. + /// + public object WPF { get; set; } = s_notset; /// /// Gets or sets the value to use on Windows (WinUI). diff --git a/src/Controls/src/Xaml/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Controls/src/Xaml/PublicAPI/net-android/PublicAPI.Unshipped.txt index a5c1d1fa583f..91fc8305068a 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -1,6 +1,12 @@ #nullable enable ~Microsoft.Maui.Controls.Xaml.BindingExtension.ConverterCulture.get -> System.Globalization.CultureInfo ~Microsoft.Maui.Controls.Xaml.BindingExtension.ConverterCulture.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.GTK.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.GTK.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.WPF.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.WPF.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.macOS.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.macOS.set -> void Microsoft.Maui.Controls.Xaml.XamlComponentRegistry static Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.FindStaticResource(object! element, string! key) -> object? static Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.GetComponent(object! page, string! nodeId) -> object? diff --git a/src/Controls/src/Xaml/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Controls/src/Xaml/PublicAPI/net-ios/PublicAPI.Unshipped.txt index a5c1d1fa583f..91fc8305068a 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -1,6 +1,12 @@ #nullable enable ~Microsoft.Maui.Controls.Xaml.BindingExtension.ConverterCulture.get -> System.Globalization.CultureInfo ~Microsoft.Maui.Controls.Xaml.BindingExtension.ConverterCulture.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.GTK.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.GTK.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.WPF.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.WPF.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.macOS.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.macOS.set -> void Microsoft.Maui.Controls.Xaml.XamlComponentRegistry static Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.FindStaticResource(object! element, string! key) -> object? static Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.GetComponent(object! page, string! nodeId) -> object? diff --git a/src/Controls/src/Xaml/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Controls/src/Xaml/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index a5c1d1fa583f..91fc8305068a 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -1,6 +1,12 @@ #nullable enable ~Microsoft.Maui.Controls.Xaml.BindingExtension.ConverterCulture.get -> System.Globalization.CultureInfo ~Microsoft.Maui.Controls.Xaml.BindingExtension.ConverterCulture.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.GTK.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.GTK.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.WPF.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.WPF.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.macOS.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.macOS.set -> void Microsoft.Maui.Controls.Xaml.XamlComponentRegistry static Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.FindStaticResource(object! element, string! key) -> object? static Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.GetComponent(object! page, string! nodeId) -> object? diff --git a/src/Controls/src/Xaml/PublicAPI/net-tizen/PublicAPI.Unshipped.txt b/src/Controls/src/Xaml/PublicAPI/net-tizen/PublicAPI.Unshipped.txt index a5c1d1fa583f..91fc8305068a 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-tizen/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-tizen/PublicAPI.Unshipped.txt @@ -1,6 +1,12 @@ #nullable enable ~Microsoft.Maui.Controls.Xaml.BindingExtension.ConverterCulture.get -> System.Globalization.CultureInfo ~Microsoft.Maui.Controls.Xaml.BindingExtension.ConverterCulture.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.GTK.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.GTK.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.WPF.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.WPF.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.macOS.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.macOS.set -> void Microsoft.Maui.Controls.Xaml.XamlComponentRegistry static Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.FindStaticResource(object! element, string! key) -> object? static Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.GetComponent(object! page, string! nodeId) -> object? diff --git a/src/Controls/src/Xaml/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Controls/src/Xaml/PublicAPI/net-windows/PublicAPI.Unshipped.txt index a5c1d1fa583f..91fc8305068a 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -1,6 +1,12 @@ #nullable enable ~Microsoft.Maui.Controls.Xaml.BindingExtension.ConverterCulture.get -> System.Globalization.CultureInfo ~Microsoft.Maui.Controls.Xaml.BindingExtension.ConverterCulture.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.GTK.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.GTK.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.WPF.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.WPF.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.macOS.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.macOS.set -> void Microsoft.Maui.Controls.Xaml.XamlComponentRegistry static Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.FindStaticResource(object! element, string! key) -> object? static Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.GetComponent(object! page, string! nodeId) -> object? diff --git a/src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Unshipped.txt index 79c24c9732e0..7221188e52c3 100644 --- a/src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Unshipped.txt @@ -1,6 +1,12 @@ #nullable enable ~Microsoft.Maui.Controls.Xaml.BindingExtension.ConverterCulture.get -> System.Globalization.CultureInfo ~Microsoft.Maui.Controls.Xaml.BindingExtension.ConverterCulture.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.GTK.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.GTK.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.WPF.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.WPF.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.macOS.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.macOS.set -> void Microsoft.Maui.Controls.Xaml.XamlComponentRegistry Microsoft.Maui.Controls.Xaml.XamlIncrementalHotReloadHandler static Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.FindStaticResource(object! element, string! key) -> object? diff --git a/src/Controls/src/Xaml/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/Controls/src/Xaml/PublicAPI/netstandard/PublicAPI.Unshipped.txt index 267609aa29ae..f74b7d2db606 100644 --- a/src/Controls/src/Xaml/PublicAPI/netstandard/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/netstandard/PublicAPI.Unshipped.txt @@ -1,6 +1,12 @@ #nullable enable ~Microsoft.Maui.Controls.Xaml.BindingExtension.ConverterCulture.get -> System.Globalization.CultureInfo ~Microsoft.Maui.Controls.Xaml.BindingExtension.ConverterCulture.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.GTK.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.GTK.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.WPF.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.WPF.set -> void +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.macOS.get -> object +~Microsoft.Maui.Controls.Xaml.OnPlatformExtension.macOS.set -> void Microsoft.Maui.Controls.Xaml.XamlComponentRegistry static Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.FindStaticResource(object! element, string! key) -> object? static Microsoft.Maui.Controls.Xaml.XamlComponentRegistry.GetComponent(object! page, string! nodeId) -> object? diff --git a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs index b0de84267be5..1d3b636ea584 100644 --- a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs +++ b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs @@ -135,6 +135,47 @@ private partial void InitializeComponent() Assert.Equal(expected, generated, ignoreLineEndingDifferences: true); } + [Fact] + public void NonSimplifiedOnPlatformMarkupExtensionUsesPublicCustomPlatformProperties() + { + var xaml = +""" + + + +"""; + + var code = +""" +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Xaml; + +namespace Test; + +[XamlProcessing(XamlInflator.SourceGen)] +public partial class TestPage : ContentPage +{ + public TestPage() + { + InitializeComponent(); + } +} +"""; + + var (result, generated) = RunGenerator(xaml, code, targetFramework: "netstandard2.0"); + Assert.False(result.Diagnostics.Any()); + + Assert.Contains("global::Microsoft.Maui.Controls.Xaml.OnPlatformExtension", generated, StringComparison.Ordinal); + Assert.Matches(@"GTK\s*=\s*""gtk""", generated); + Assert.Matches(@"macOS\s*=\s*""macos""", generated); + Assert.Matches(@"WPF\s*=\s*""wpf""", generated); + Assert.Contains("ProvideValue(xamlServiceProvider)", generated, StringComparison.Ordinal); + } + [Fact] public void SimplifyOnPlatformElement() { diff --git a/src/Controls/tests/Xaml.UnitTests/MarkupExpressionParserTests.cs b/src/Controls/tests/Xaml.UnitTests/MarkupExpressionParserTests.cs index c9e950bfcdff..fab85ab9a407 100644 --- a/src/Controls/tests/Xaml.UnitTests/MarkupExpressionParserTests.cs +++ b/src/Controls/tests/Xaml.UnitTests/MarkupExpressionParserTests.cs @@ -288,6 +288,11 @@ public void BindingWithStaticConverter() [InlineData("{OnPlatform Android=20, UWP=25}", "WinUI", 25)] [InlineData("{OnPlatform Android=20, WinUI=25, UWP=20}", "WinUI", 25)] [InlineData("{OnPlatform Android=20, UWP=25}", "UWP", 25)] + // GTK, macOS and WPF are now public, so custom/community backends can express + // platform-conditional values for them through the {OnPlatform} markup extension. + [InlineData("{OnPlatform Android=20, GTK=25}", "GTK", 25)] + [InlineData("{OnPlatform iOS=20, macOS=25}", "macOS", 25)] + [InlineData("{OnPlatform Android=20, WPF=25}", "WPF", 25)] [InlineData("{OnPlatform 20}", "Android", 20)] [InlineData("{OnPlatform 20}", "iOS", 20)] [InlineData("{OnPlatform 20}", "Tizen", 20)] diff --git a/src/Controls/tests/Xaml.UnitTests/OnPlatformTests.cs b/src/Controls/tests/Xaml.UnitTests/OnPlatformTests.cs index 8db78cec8d95..bd2f4cbc8280 100644 --- a/src/Controls/tests/Xaml.UnitTests/OnPlatformTests.cs +++ b/src/Controls/tests/Xaml.UnitTests/OnPlatformTests.cs @@ -137,6 +137,24 @@ public void ChecksPreferWinUI() image = new Image().LoadFromXaml(xaml); Assert.Equal("Images/icon_twitter_preferred.png", (image.Source as FileImageSource).File); } + + [Theory] + [InlineData("GTK", "gtk")] + [InlineData("macOS", "macos")] + [InlineData("WPF", "wpf")] + // Issue 35695: custom/community backends can express platform-conditional + // values through the OnPlatform markup-extension form. + public void MarkupExtensionResolvesCustomPlatform(string platform, string expected) + { + var xaml = @" +