diff --git a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml index 7a7eec3078..21255161d6 100644 --- a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml +++ b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml @@ -6,8 +6,40 @@ d:DesignHeight="800" d:DesignWidth="400" mc:Ignorable="d"> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 9e2962baca..d279c0904f 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -57,6 +57,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer() ); + InsertBefore( + new OnFormFactorTransformer()); InsertBefore( new OnPlatformTransformer()); InsertAfter( diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/OnFormFactorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/OnFormFactorTransformer.cs new file mode 100644 index 0000000000..e2eaaa3a9f --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/OnFormFactorTransformer.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; +using XamlX.Ast; +using XamlX.Transform; +using XamlX.Transform.Transformers; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; + +internal class OnFormFactorTransformer : IXamlAstTransformer +{ + public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) + { + if (node is XamlAstObjectNode xmlobj + && xmlobj.Type is XamlAstXmlTypeReference xmlref + && xmlref.Name.StartsWith("OnFormFactor") + && !xmlref.GenericArguments.Any()) + { + IXamlType propertyType = null; + + if (context.ParentNodes().FirstOrDefault() is XamlAstXamlPropertyValueNode parentPropertyValueNode) + { + var property = (XamlAstNamePropertyReference)parentPropertyValueNode.Property; + var declaringType = TypeReferenceResolver.ResolveType(context, (XamlAstXmlTypeReference)property.DeclaringType, context.StrictMode); + propertyType = declaringType.Type.GetAllProperties().First(p => p.Name == property.Name).PropertyType; + } + else if (context.ParentNodes().FirstOrDefault() is XamlAstObjectNode parentNode) + { + var parentType = parentNode.Type is XamlAstClrTypeReference clrType + ? clrType.Type + : TypeReferenceResolver.ResolveType(context, (XamlAstXmlTypeReference)parentNode.Type, context.StrictMode).Type; + var contentProperty = context.Configuration.FindContentProperty(parentType); + propertyType = contentProperty.PropertyType; + } + + if (propertyType is null) + { + throw new InvalidOperationException("Unable to find OnFormFactor property type"); + } + + xmlobj.Type = TypeReferenceResolver.ResolveType(context, xmlref.XmlNamespace, xmlref.Name, + xmlref.IsMarkupExtension, new[] { new XamlAstClrTypeReference(xmlref, propertyType, false) }, + xmlref, context.StrictMode); + } + + if (node is XamlAstNamePropertyReference xmlprop + && xmlprop.DeclaringType is XamlAstXmlTypeReference propxmlref + && propxmlref.Name.StartsWith("OnFormFactor") + && !propxmlref.GenericArguments.Any()) + { + var expectedType = context.ParentNodes().OfType() + .First(n => n.Type is XamlAstClrTypeReference clrRef && clrRef.Type.Name.StartsWith("OnFormFactor") + || n.Type is XamlAstXmlTypeReference xmlRef && xmlRef.Name.StartsWith("OnFormFactor")) + .Type; + xmlprop.DeclaringType = expectedType; + xmlprop.TargetType = expectedType; + } + + // if (node is XamlAstObjectNode onobj + // && onobj.Type is XamlAstXmlTypeReference onref + // && onref.Name == "On") + // { + // var platformStr = (onobj.Children.OfType() + // .FirstOrDefault(v => ((XamlAstNamePropertyReference)v.Property).Name == "Platform")?.Values.Single() as XamlAstTextNode)? + // .Text; + // if (string.IsNullOrWhiteSpace(platformStr)) + // { + // throw new InvalidOperationException("On.Platform string must be set"); + // } + // var content = onobj.Children.OfType().FirstOrDefault(); + // if (content is null) + // { + // throw new InvalidOperationException("On content object must be set"); + // } + // + // var parentOnFormFactorObject = context.ParentNodes().OfType() + // .First(n => n.Type is XamlAstClrTypeReference clrRef && clrRef.Type.Name.StartsWith("OnFormFactor") + // || n.Type is XamlAstXmlTypeReference xmlRef && xmlRef.Name.StartsWith("OnFormFactor")); + // parentOnFormFactorObject.Children.Remove(onobj); + // foreach (var platform in platformStr.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + // { + // var propertyNode = new XamlAstXamlPropertyValueNode(onobj, + // new XamlAstNamePropertyReference(onobj, parentOnFormFactorObject.Type, platform.Trim(), + // parentOnFormFactorObject.Type), content); + // parentOnFormFactorObject.Children.Add(propertyNode); + // } + // + // return parentOnFormFactorObject.Children.Last(); + // } + // if (node is XamlAstXamlPropertyValueNode propNode) + // { + // var type = (propNode.Property as XamlAstNamePropertyReference).TargetType as XamlAstXmlTypeReference; + // + // propNode.VisitChildren(new OnFormFactorGenericTypeVisitor(type)); + // + // return node; + // } + + return node; + } + + private class OnFormFactorGenericTypeVisitor : IXamlAstVisitor + { + private readonly XamlAstXmlTypeReference _type; + public OnFormFactorGenericTypeVisitor(IXamlAstTypeReference type) + { + + } + + public IXamlAstNode Visit(IXamlAstNode node) + { + if (node is XamlAstXmlTypeReference { Name: "OnFormFactor" } xmlref) + { + xmlref.GenericArguments.Add(_type); + } + + return node; + } + + public void Push(IXamlAstNode node) + { + } + + public void Pop() + { + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 7d6e237ebe..fd1375f82b 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 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs new file mode 100644 index 0000000000..0d1d350978 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs @@ -0,0 +1,78 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Platform; + +namespace Avalonia.Markup.Xaml.MarkupExtensions; + +public class OnFormFactorExtension +{ + private readonly Dictionary _values = new(); + + public OnFormFactorExtension(TReturn defaultValue) + { + Default = defaultValue; + } + + public TReturn? Default + { + get => _values.TryGetValue(nameof(Default), out var value) ? value : default; + set { _values[nameof(Default)] = value; } + } + + public TReturn? Desktop + { + get => _values.TryGetValue(nameof(Desktop), out var value) ? value : default; + set { _values[nameof(Desktop)] = value; } + } + + public TReturn? Mobile + { + get => _values.TryGetValue(nameof(Mobile), out var value) ? value : default; + set { _values[nameof(Mobile)] = value; } + } + + + public object? ProvideValue() + { + if (!_values.Any()) + { + throw new InvalidOperationException( + "OnPlatformExtension requires a value to be specified for at least one platform or Default."); + } + + var (value, hasValue) = TryGetValueForPlatform(); + return !hasValue ? AvaloniaProperty.UnsetValue : value; + } + + private (object? value, bool hasValue) TryGetValueForPlatform() + { + var runtimeInfo = AvaloniaLocator.Current.GetRequiredService().GetRuntimeInfo(); + + TReturn val; + + if (runtimeInfo.IsDesktop) + { + if (_values.TryGetValue(nameof(Desktop), out val)) + { + return (val, true); + } + } + + if (runtimeInfo.IsMobile) + { + if (_values.TryGetValue(nameof(Mobile), out val)) + { + return (val, true); + } + } + + if (_values.TryGetValue(nameof(Default), out val)) + { + return (val, true); + } + + return default; + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs index 1c984f93e0..fadbf9ad21 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs @@ -24,6 +24,11 @@ public class OnPlatformExtension : IAddChild Default = defaultValue; } + public OnPlatformExtension() + { + + } + public TReturn? Default { get => _values.TryGetValue(nameof(Default), out var value) ? value : default; set { _values[nameof(Default)] = value; } } public TReturn? Windows { get => _values.TryGetValue(nameof(Windows), out var value) ? value : default; set { _values[nameof(Windows)] = value; } } public TReturn? macOS { get => _values.TryGetValue(nameof(macOS), out var value) ? value : default; set { _values[nameof(macOS)] = value; } } @@ -31,11 +36,6 @@ public class OnPlatformExtension : IAddChild public TReturn? Android { get => _values.TryGetValue(nameof(Android), out var value) ? value : default; set { _values[nameof(Android)] = value; } } public TReturn? iOS { get => _values.TryGetValue(nameof(iOS), out var value) ? value : default; set { _values[nameof(iOS)] = value; } } public TReturn? Browser { get => _values.TryGetValue(nameof(Browser), out var value) ? value : default; set { _values[nameof(Browser)] = value; } } - - public TReturn? Desktop { get => _values.TryGetValue(nameof(Desktop), out var value) ? value : default; set { _values[nameof(Desktop)] = value; } } - public TReturn? Mobile { get => _values.TryGetValue(nameof(Mobile), out var value) ? value : default; set { _values[nameof(Mobile)] = value; } } - - public object? ProvideValue() { if (!_values.Any()) @@ -47,53 +47,63 @@ public class OnPlatformExtension : IAddChild return !hasValue ? AvaloniaProperty.UnsetValue : value; } - private (TReturn? value, bool hasValue) TryGetFormFactorValues(RuntimePlatformInfo runtimeInfo) + private (TReturn? value, bool hasValue) TryGetValueForPlatform() { - if (runtimeInfo.IsDesktop) - { - if (_values.TryGetValue(nameof(Desktop), out var val1)) - { - return (val1, true); - } - } + var runtimeInfo = AvaloniaLocator.Current.GetRequiredService().GetRuntimeInfo(); - if (runtimeInfo.IsMobile) - { - if (_values.TryGetValue(nameof(Mobile), out var val1)) - { - return (val1, true); - } - } + TReturn val; - return default; - } + switch (runtimeInfo.OperatingSystem) + { + case OperatingSystemType.WinNT: + if (_values.TryGetValue(nameof(Windows), out val)) + { + return (val, true); + } + break; - private (TReturn? value, bool hasValue) TryGetValueForPlatform() - { - var runtimeInfo = AvaloniaLocator.Current.GetRequiredService().GetRuntimeInfo(); + case OperatingSystemType.OSX: + if (_values.TryGetValue(nameof(macOS), out val)) + { + return (val, true); + } + break; - return runtimeInfo.OperatingSystem switch + case OperatingSystemType.Linux: + if (_values.TryGetValue(nameof(macOS), out val)) + { + return (val, true); + } + break; + + case OperatingSystemType.Android: + if (_values.TryGetValue(nameof(Android), out val)) + { + return (val, true); + } + break; + + case OperatingSystemType.iOS: + if (_values.TryGetValue(nameof(iOS), out val)) + { + return (val, true); + } + break; + + case OperatingSystemType.Browser: + if (_values.TryGetValue(nameof(Browser), out val)) + { + return (val, true); + } + break; + } + + if (_values.TryGetValue(nameof(Default), out val)) { - OperatingSystemType.WinNT => _values.TryGetValue(nameof(Windows), out var val) ? - (val, true) : - TryGetFormFactorValues(runtimeInfo), - OperatingSystemType.OSX => _values.TryGetValue(nameof(macOS), out var val) ? - (val, true) : - TryGetFormFactorValues(runtimeInfo), - OperatingSystemType.Linux => _values.TryGetValue(nameof(Linux), out var val) ? - (val, true) : - TryGetFormFactorValues(runtimeInfo), - OperatingSystemType.Android => _values.TryGetValue(nameof(Android), out var val) ? - (val, true) : - TryGetFormFactorValues(runtimeInfo), - OperatingSystemType.iOS => _values.TryGetValue(nameof(iOS), out var val) ? - (val, true) : - TryGetFormFactorValues(runtimeInfo), - OperatingSystemType.Browser => _values.TryGetValue(nameof(Browser), out var val) ? - (val, true) : - TryGetFormFactorValues(runtimeInfo), - _ => _values.TryGetValue(nameof(Default), out var val) ? (val, true) : TryGetFormFactorValues(runtimeInfo) + return (val, true); }; + + return default; } public void AddChild(On child)