From bd9c9783ab1b6110b26db8bf29ce9f044da71c64 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 18 Jan 2023 02:05:40 -0500 Subject: [PATCH] Parse ThemeVariant compile time and merge ThemeDictionaries --- .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 20 ++++ .../XamlMergeResourceGroupTransformer.cs | 100 ++++++++++++++++-- .../AvaloniaXamlIlWellKnownTypes.cs | 4 + 3 files changed, 118 insertions(+), 6 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index 925bf0a4fa..365a07a7f6 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -291,6 +291,26 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions return true; } + if (type.Equals(types.ThemeVariant)) + { + var variantText = text.Trim(); + var foundConstProperty = types.ThemeVariant.Properties.FirstOrDefault(p => + p.Name == variantText && p.PropertyType == types.ThemeVariant); + var themeVariantTypeRef = new XamlAstClrTypeReference(node, types.ThemeVariant, false); + if (foundConstProperty is not null) + { + result = new XamlStaticExtensionNode(new XamlAstObjectNode(node, node.Type), themeVariantTypeRef, foundConstProperty.Name); + return true; + } + + result = new XamlAstNewClrObjectNode(node, themeVariantTypeRef, types.ThemeVariantConstructor, + new List() + { + new XamlConstantNode(node, context.Configuration.WellKnownTypes.String, variantText) + }); + return true; + } + result = null; return false; } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs index 8c83c74248..db8d604154 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs @@ -4,6 +4,8 @@ using System.Linq; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; using XamlX.Ast; using XamlX.IL.Emitters; +using XamlX.Transform.Transformers; +using XamlX.TypeSystem; namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers; #nullable enable @@ -72,14 +74,20 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer } } - var manipulationGroup = new XamlManipulationGroupNode(node, new List()); + if (!mergeSourceNodes.Any()) + { + return node; + } + + var manipulationGroup = new List(); foreach (var sourceNode in mergeSourceNodes) { var (originalAssetPath, propertyNode) = AvaloniaXamlIncludeTransformer.ResolveSourceFromXamlInclude(context, "MergeResourceInclude", sourceNode, true); if (originalAssetPath is null) { - return node; + return context.ParseError( + $"Node MergeResourceInclude is unable to resolve \"{originalAssetPath}\" path.", propertyNode, node); } var targetDocument = context.Documents.FirstOrDefault(d => @@ -99,15 +107,95 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer $"MergeResourceInclude can only include another ResourceDictionary", propertyNode, node); } - manipulationGroup.Children.Add(singleRootObject.Manipulation); + manipulationGroup.Add(singleRootObject.Manipulation); } + + // Order of resources is defined by ResourceDictionary.TryGetResource. + // It is read by following priority: + // - own resources. + // - own theme dictionaries. + // - merged dictionaries. + // We need to maintain this order when we inject "compiled merged" resources. + // Doing this by injecting merged dictionaries in the beginning, so it can be overwritten by "own resources". + // MergedDictionaries are read first, so we need ot inject our merged values in the beginning. + var children = resourceDictionaryManipulation.Children; + children.InsertRange(0, manipulationGroup); - if (manipulationGroup.Children.Any()) + // Flatten resource assignments. + for (var i = 0; i < children.Count; i++) { - // MergedDictionaries are read first, so we need ot inject our merged values in the beginning. - resourceDictionaryManipulation.Children.Insert(0, manipulationGroup); + if (children[i] is XamlManipulationGroupNode group) + { + children.RemoveAt(i); + children.AddRange(group.Children); + i--; // step back, so new items can be reiterated. + } + } + + // Merge "ThemeDictionaries" as well. + for (var i = children.Count - 1; i >= 0; i--) + { + if (children[i] is XamlPropertyAssignmentNode assignmentNode + && assignmentNode.Property.Name == "ThemeDictionaries" + && assignmentNode.Values.Count == 2 + && assignmentNode.Values[0] is {} key + && assignmentNode.Values[1] is XamlValueWithManipulationNode + { + Manipulation: XamlObjectInitializationNode + { + Manipulation: XamlManipulationGroupNode valueGroup + } + }) + { + for (var j = i - 1; j >= 0; j--) + { + if (children[j] is XamlPropertyAssignmentNode sameKeyPrevAssignmentNode + && sameKeyPrevAssignmentNode.Property.Name == "ThemeDictionaries" + && sameKeyPrevAssignmentNode.Values.Count == 2 + && sameKeyPrevAssignmentNode.Values[1] is XamlValueWithManipulationNode + { + Manipulation: XamlObjectInitializationNode + { + Manipulation: XamlManipulationGroupNode sameKeyPrevValueGroup + } + } + && ThemeVariantNodeEquals(context, key, sameKeyPrevAssignmentNode.Values[0])) + { + sameKeyPrevValueGroup.Children.AddRange(valueGroup.Children); + children.RemoveAt(i); + break; + } + } + } } return node; } + + public static bool ThemeVariantNodeEquals(AstGroupTransformationContext context, IXamlAstValueNode left, IXamlAstValueNode right) + { + if (left is XamlConstantNode leftConst + && right is XamlConstantNode rightConst) + { + return leftConst.Constant == rightConst.Constant; + } + if (left is XamlStaticExtensionNode leftStaticExt + && right is XamlStaticExtensionNode rightStaticExt) + { + return leftStaticExt.Type.GetClrType().GetFullName() == rightStaticExt.Type.GetClrType().GetFullName() + && leftStaticExt.Member == rightStaticExt.Member; + } + if (left is XamlAstNewClrObjectNode leftClrObjectNode + && right is XamlAstNewClrObjectNode rightClrObjectNode) + { + var themeVariant = context.GetAvaloniaTypes().ThemeVariant; + return leftClrObjectNode.Type.GetClrType() == themeVariant + && leftClrObjectNode.Type == rightClrObjectNode.Type + && leftClrObjectNode.Constructor == rightClrObjectNode.Constructor + && ThemeVariantNodeEquals(context, leftClrObjectNode.Arguments.Single(), + leftClrObjectNode.Arguments.Single()); + } + + return false; + } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index aab6239a35..f9e14a7641 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -67,6 +67,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlConstructor FontFamilyConstructorUriName { get; } public IXamlType Thickness { get; } public IXamlConstructor ThicknessFullConstructor { get; } + public IXamlType ThemeVariant { get; } + public IXamlConstructor ThemeVariantConstructor { get; } public IXamlType Point { get; } public IXamlConstructor PointFullConstructor { get; } public IXamlType Vector { get; } @@ -188,6 +190,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers Uri = cfg.TypeSystem.GetType("System.Uri"); FontFamily = cfg.TypeSystem.GetType("Avalonia.Media.FontFamily"); FontFamilyConstructorUriName = FontFamily.GetConstructor(new List { Uri, XamlIlTypes.String }); + ThemeVariant = cfg.TypeSystem.GetType("Avalonia.Styling.ThemeVariant"); + ThemeVariantConstructor = ThemeVariant.GetConstructor(new List { XamlIlTypes.String }); (IXamlType, IXamlConstructor) GetNumericTypeInfo(string name, IXamlType componentType, int componentCount) {