From 6d3ce0a8a5c8c8bf38a0fed9c9b730bcd8c66e7c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 15 Mar 2022 19:42:18 +0000 Subject: [PATCH 01/33] Add service for runtime platform information. --- .../Avalonia.Markup.Xaml.csproj | 2 + .../XamlIl/Runtime/RuntimePlatformInfo.cs | 46 +++++++++++++++++++ .../XamlIl/Runtime/XamlIlRuntimeHelpers.cs | 3 ++ 3 files changed, 51 insertions(+) create mode 100644 src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/RuntimePlatformInfo.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 548aae31a8..961542fc14 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -28,6 +28,7 @@ + @@ -50,6 +51,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/RuntimePlatformInfo.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/RuntimePlatformInfo.cs new file mode 100644 index 0000000000..2440989d71 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/RuntimePlatformInfo.cs @@ -0,0 +1,46 @@ +using System; +using System.Runtime.InteropServices; + + +namespace Avalonia.Markup.Xaml.XamlIl.Runtime; + +public class RuntimePlatformInfo +{ + private static OSPlatform IOS { get; } = OSPlatform.Create("IOS"); + + private static OSPlatform Android { get; } = OSPlatform.Create("ANDROID"); + + private static OSPlatform Browser { get; } = OSPlatform.Create("BROWSER"); + + public static RuntimePlatformInfo Instance { get; } = new(); + + private RuntimePlatformInfo() + { + } + + public bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + public bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + + public bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + + public bool IsIOS => RuntimeInformation.IsOSPlatform(IOS); + + public bool IsAndroid => RuntimeInformation.IsOSPlatform(Android); + + public bool IsBrowser => RuntimeInformation.IsOSPlatform(Browser); + + public bool IsDesktop(Size primaryScreenSize) => primaryScreenSize.Width > 1280; + + public bool IsLaptopOrDesktop(Size primaryScreenSize) => + primaryScreenSize.Width > 1024 && primaryScreenSize.Width <= 1280; + + public bool IsTablet(Size primaryScreenSize) => + primaryScreenSize.Width > 768 && primaryScreenSize.Width <= 1024; + + public bool IsMobileLandscape(Size primaryScreenSize) => + primaryScreenSize.Width > 480 && primaryScreenSize.Width <= 768; + + public bool IsMobile(Size primaryScreenSize) => + primaryScreenSize.Width <= 480; +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs index c48f386ffd..7e5dd79602 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs @@ -182,6 +182,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime return _nameScope; if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider)) return this; + if (serviceType == typeof(RuntimePlatformInfo)) + return RuntimePlatformInfo.Instance; + return null; } From 9479baaa2b0f92922d638c70b6b13076c4d7a97f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 1 Jun 2022 01:12:20 -0400 Subject: [PATCH 02/33] Add object-based OnPlatformExtension impl --- .../Avalonia.Markup.Xaml.csproj | 1 - .../MarkupExtensions/OnPlatformExtension.cs | 97 +++++++++++++++++++ .../XamlIl/Runtime/RuntimePlatformInfo.cs | 46 --------- .../XamlIl/Runtime/XamlIlRuntimeHelpers.cs | 2 - 4 files changed, 97 insertions(+), 49 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs delete mode 100644 src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/RuntimePlatformInfo.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 3664070ca0..f4c5d70f7f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -51,7 +51,6 @@ - diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs new file mode 100644 index 0000000000..a9e8e0c994 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs @@ -0,0 +1,97 @@ +#nullable enable +using System; +using System.Globalization; +using System.Reflection; +using Avalonia.Data.Converters; +using Avalonia.Metadata; +using Avalonia.Platform; +using Avalonia.Styling; + +namespace Avalonia.Markup.Xaml.MarkupExtensions; + +public class OnPlatformExtension +{ + private static readonly object s_unset = new object(); + + public OnPlatformExtension() + { + + } + + public OnPlatformExtension(object defaultValue) + { + Default = defaultValue; + } + + [Content] + public object? Default { get; set; } = s_unset; + public object? Windows { get; set; } = s_unset; + public object? macOS { get; set; } = s_unset; + public object? Linux { get; set; } = s_unset; + public object? Android { get; set; } = s_unset; + public object? iOS { get; set; } = s_unset; + public object? Browser { get; set; } = s_unset; + + public IValueConverter? Converter { get; set; } + + public object? ConverterParameter { get; set; } + + public object? ProvideValue(IServiceProvider serviceProvider) + { + if (Default == s_unset + && Windows == s_unset + && macOS == s_unset + && Linux == s_unset + && Android == s_unset + && iOS == s_unset + && Browser == s_unset) + { + throw new InvalidOperationException("OnPlatformExtension requires a value to be specified for at least one platform or Default."); + } + + var provideTarget = serviceProvider.GetService(); + + var targetType = provideTarget.TargetProperty switch + { + AvaloniaProperty ap => ap.PropertyType, + PropertyInfo pi => pi.PropertyType, + _ => null, + }; + + if (provideTarget.TargetObject is Setter setter) + { + targetType = setter.Property?.PropertyType ?? targetType; + } + + if (!TryGetValueForPlatform(out var value)) + { + return AvaloniaProperty.UnsetValue; + } + + if (targetType is null) + { + return value; + } + + var converter = Converter ?? DefaultValueConverter.Instance; + return converter.Convert(value, targetType, ConverterParameter, CultureInfo.CurrentUICulture); + } + + private bool TryGetValueForPlatform(out object? value) + { + var runtimeInfo = AvaloniaLocator.Current.GetRequiredService().GetRuntimeInfo(); + + value = runtimeInfo.OperatingSystem switch + { + OperatingSystemType.WinNT when Windows != s_unset => Windows, + OperatingSystemType.Linux when Linux != s_unset => Linux, + OperatingSystemType.OSX when macOS != s_unset => macOS, + OperatingSystemType.Android when Android != s_unset => Android, + OperatingSystemType.iOS when iOS != s_unset => iOS, + OperatingSystemType.Browser when Browser != s_unset => Browser, + _ => Default + }; + + return value != s_unset; + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/RuntimePlatformInfo.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/RuntimePlatformInfo.cs deleted file mode 100644 index 2440989d71..0000000000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/RuntimePlatformInfo.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Runtime.InteropServices; - - -namespace Avalonia.Markup.Xaml.XamlIl.Runtime; - -public class RuntimePlatformInfo -{ - private static OSPlatform IOS { get; } = OSPlatform.Create("IOS"); - - private static OSPlatform Android { get; } = OSPlatform.Create("ANDROID"); - - private static OSPlatform Browser { get; } = OSPlatform.Create("BROWSER"); - - public static RuntimePlatformInfo Instance { get; } = new(); - - private RuntimePlatformInfo() - { - } - - public bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - - public bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); - - public bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - - public bool IsIOS => RuntimeInformation.IsOSPlatform(IOS); - - public bool IsAndroid => RuntimeInformation.IsOSPlatform(Android); - - public bool IsBrowser => RuntimeInformation.IsOSPlatform(Browser); - - public bool IsDesktop(Size primaryScreenSize) => primaryScreenSize.Width > 1280; - - public bool IsLaptopOrDesktop(Size primaryScreenSize) => - primaryScreenSize.Width > 1024 && primaryScreenSize.Width <= 1280; - - public bool IsTablet(Size primaryScreenSize) => - primaryScreenSize.Width > 768 && primaryScreenSize.Width <= 1024; - - public bool IsMobileLandscape(Size primaryScreenSize) => - primaryScreenSize.Width > 480 && primaryScreenSize.Width <= 768; - - public bool IsMobile(Size primaryScreenSize) => - primaryScreenSize.Width <= 480; -} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs index 7e5dd79602..1d7fffe195 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs @@ -182,8 +182,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime return _nameScope; if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider)) return this; - if (serviceType == typeof(RuntimePlatformInfo)) - return RuntimePlatformInfo.Instance; return null; } From 30447ac033de41d1494866caa96323591548b3f3 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 1 Jun 2022 01:18:35 -0400 Subject: [PATCH 03/33] Add tests for OnPlatform extensions --- .../OnPlatformExtensionTests.cs | 263 ++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs new file mode 100644 index 0000000000..0583de691f --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs @@ -0,0 +1,263 @@ +using System; +using System.Globalization; +using Avalonia.Controls; +using Avalonia.Data.Converters; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.PlatformSupport; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions; + +public class OnPlatformExtensionTests : XamlTestBase +{ + [Fact] + public void Should_Resolve_Default_Value() + { + using (AvaloniaLocator.EnterScope()) + { + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(new TestRuntimePlatform(OperatingSystemType.Unknown)); + + var xaml = @" + + +"; + + var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); + var textBlock = (TextBlock)userControl.Content!; + + Assert.Equal("Hello World", textBlock.Text); + } + } + + [Fact] + public void Should_Resolve_Default_Value_From_Ctor() + { + using (AvaloniaLocator.EnterScope()) + { + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(new TestRuntimePlatform(OperatingSystemType.Unknown)); + + var xaml = @" + + +"; + + var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); + var textBlock = (TextBlock)userControl.Content!; + + Assert.Equal("Hello World", textBlock.Text); + } + } + + [Theory] + [InlineData(OperatingSystemType.WinNT, "Im Windows")] + [InlineData(OperatingSystemType.OSX, "Im macOS")] + [InlineData(OperatingSystemType.Linux, "Im Linux")] + [InlineData(OperatingSystemType.Android, "Im Android")] + [InlineData(OperatingSystemType.iOS, "Im iOS")] + [InlineData(OperatingSystemType.Browser, "Im Browser")] + [InlineData(OperatingSystemType.Unknown, "Default value")] + public void Should_Resolve_Expected_Value_Per_Platform(OperatingSystemType currentPlatform, string expectedResult) + { + using (AvaloniaLocator.EnterScope()) + { + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(new TestRuntimePlatform(currentPlatform)); + + var xaml = @" + + +"; + + var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); + var textBlock = (TextBlock)userControl.Content!; + + Assert.Equal(expectedResult, textBlock.Text); + } + } + + [Fact] + public void Should_Convert_Bcl_Type() + { + using (AvaloniaLocator.EnterScope()) + { + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(new TestRuntimePlatform(OperatingSystemType.WinNT)); + + var xaml = @" + + +"; + + var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); + var border = (Border)userControl.Content!; + + Assert.Equal(50.1, border.Height); + } + } + + [Fact] + public void Should_Convert_Avalonia_Type() + { + using (AvaloniaLocator.EnterScope()) + { + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(new TestRuntimePlatform(OperatingSystemType.WinNT)); + + var xaml = @" + + +"; + + var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); + var border = (Border)userControl.Content!; + + Assert.Equal(new Thickness(10, 8, 10, 8), border.Padding); + } + } + + [Fact] + public void Should_Allow_Nester_Markup_Extensions() + { + using (AvaloniaLocator.EnterScope()) + { + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(new TestRuntimePlatform(OperatingSystemType.WinNT)); + + var xaml = @" + + + #ff506070 + + +"; + + var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); + var border = (Border)userControl.Content!; + + Assert.Equal(Color.Parse("#ff506070"), ((ISolidColorBrush)border.Background!).Color); + } + } + + [Fact] + public void Should_Use_Converter_If_Provided() + { + using (AvaloniaLocator.EnterScope()) + { + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(new TestRuntimePlatform(OperatingSystemType.WinNT)); + + var xaml = @" + + + + + +"; + + var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); + var border = (Border)userControl.Content!; + + Assert.Equal(new Thickness(4), border.Padding); + } + } + + [Fact] + public void Should_Support_Xml_Syntax() + { + using (AvaloniaLocator.EnterScope()) + { + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(new TestRuntimePlatform(OperatingSystemType.WinNT)); + + var xaml = @" + + + + + + + + + + +"; + + var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); + var border = (Border)userControl.Content!; + + Assert.Equal(Color.Parse("#ff506070"), ((ISolidColorBrush)border.Background!).Color); + } + } + + [Fact] + public void Should_Support_Control_Inside_Xml_Syntax() + { + using (AvaloniaLocator.EnterScope()) + { + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(new TestRuntimePlatform(OperatingSystemType.WinNT)); + + var xaml = @" + + + +