From a2f679f0f93039d3f544d78e5e31bd5ce0ea0701 Mon Sep 17 00:00:00 2001 From: Filipe GP <4021025+Eastrall@users.noreply.github.com> Date: Tue, 15 Aug 2023 19:13:52 +0200 Subject: [PATCH] Add custom component binding generation support --- .../Helpers/RosalinaStatementSyntaxFactory.cs | 67 +++++++++++++------ Editor/Scripts/Generator/UIProperty.cs | 15 +++-- Editor/Scripts/Generator/UIPropertyTypes.cs | 8 ++- Editor/Scripts/Parsing/RosalinaUXMLParser.cs | 4 +- Editor/Scripts/Parsing/UxmlNode.cs | 9 ++- 5 files changed, 73 insertions(+), 30 deletions(-) diff --git a/Editor/Scripts/Generator/Helpers/RosalinaStatementSyntaxFactory.cs b/Editor/Scripts/Generator/Helpers/RosalinaStatementSyntaxFactory.cs index da499a9..9ad5685 100644 --- a/Editor/Scripts/Generator/Helpers/RosalinaStatementSyntaxFactory.cs +++ b/Editor/Scripts/Generator/Helpers/RosalinaStatementSyntaxFactory.cs @@ -3,17 +3,18 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; using System; +using System.Collections.Generic; using System.Linq; using UnityEngine; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; internal static class RosalinaStatementSyntaxFactory { public static InitializationStatement[] GenerateInitializeStatements(UxmlDocument uxmlDocument, MemberAccessExpressionSyntax documentQueryMethodAccess) { var statements = new List(); - IEnumerable properties = uxmlDocument.GetChildren().Select(x => new UIProperty(x.Type, x.Name)).ToList(); + IEnumerable properties = uxmlDocument.GetChildren().Select(x => new UIProperty(x)).ToList(); if (CheckForDuplicateProperties(properties)) { @@ -30,35 +31,57 @@ public static InitializationStatement[] GenerateInitializeStatements(UxmlDocumen PropertyDeclarationSyntax @property = RosalinaSyntaxFactory.CreateProperty(uiProperty.Type.Name, uiProperty.Name, SyntaxKind.PublicKeyword) .AddAccessorListAccessors( - SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) + AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) ) .AddAccessorListAccessors( - SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) - .AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) + AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) + .AddModifiers(Token(SyntaxKind.PrivateKeyword)) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) ); - var argumentList = SyntaxFactory.SeparatedList(new[] + SeparatedSyntaxList argumentList = SeparatedList(new[] { - SyntaxFactory.Argument( - SyntaxFactory.LiteralExpression( + Argument( + LiteralExpression( SyntaxKind.StringLiteralExpression, - SyntaxFactory.Literal(uiProperty.OriginalName) + Literal(uiProperty.OriginalName) ) ) }); - var cast = SyntaxFactory.CastExpression( - SyntaxFactory.ParseTypeName(uiProperty.Type.Name), - SyntaxFactory.InvocationExpression(documentQueryMethodAccess, SyntaxFactory.ArgumentList(argumentList)) - ); - var statement = SyntaxFactory.ExpressionStatement( - SyntaxFactory.AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - SyntaxFactory.IdentifierName(uiProperty.Name), - cast - ) - ); + InvocationExpressionSyntax queryElementMethodInvocation = InvocationExpression(documentQueryMethodAccess, ArgumentList(argumentList)); + ExpressionStatementSyntax statement; + + if (!uiProperty.IsCustomComponent) + { + statement = ExpressionStatement( + AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + IdentifierName(uiProperty.Name), + CastExpression( + ParseTypeName(uiProperty.Type.Name), + queryElementMethodInvocation + ) + ) + ); + } + else + { + statement = ExpressionStatement( + AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + IdentifierName(uiProperty.Name), + ObjectCreationExpression(IdentifierName(uiProperty.Type.Name)) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument(queryElementMethodInvocation) + ) + ) + ) + ) + ); + } statements.Add(new InitializationStatement(statement, property)); } diff --git a/Editor/Scripts/Generator/UIProperty.cs b/Editor/Scripts/Generator/UIProperty.cs index f4e16fa..9f6bb42 100644 --- a/Editor/Scripts/Generator/UIProperty.cs +++ b/Editor/Scripts/Generator/UIProperty.cs @@ -31,12 +31,17 @@ internal readonly struct UIProperty /// public string OriginalName { get; } - public UIProperty(string type, string name) + /// + /// Gets a boolean value that indicates if the UI property represents a custom component. + /// + public bool IsCustomComponent => TypeName == "Instance"; + + public UIProperty(UxmlNode uxmlNode) { - TypeName = type; - Type = UIPropertyTypes.GetUIElementType(type); - OriginalName = name; - Name = name.Contains('-') ? name.ToPascalCase() : OriginalName; + TypeName = uxmlNode.Type; + Type = UIPropertyTypes.GetUIElementType(uxmlNode.Type) ?? UIPropertyTypes.GetCustomUIElementType(uxmlNode.Template); + OriginalName = uxmlNode.Name; + Name = uxmlNode.Name.Contains('-') ? uxmlNode.Name.ToPascalCase() : OriginalName; } } diff --git a/Editor/Scripts/Generator/UIPropertyTypes.cs b/Editor/Scripts/Generator/UIPropertyTypes.cs index 627367e..cf6be89 100644 --- a/Editor/Scripts/Generator/UIPropertyTypes.cs +++ b/Editor/Scripts/Generator/UIPropertyTypes.cs @@ -1,6 +1,7 @@ #if UNITY_EDITOR using System; using System.Collections.Generic; +using System.Linq; internal static class UIPropertyTypes { @@ -26,13 +27,18 @@ internal static class UIPropertyTypes { "DropdownField", typeof(UnityEngine.UIElements.DropdownField) }, { "RadioButton", typeof(UnityEngine.UIElements.RadioButton) }, { "RadioButtonGroup", typeof(UnityEngine.UIElements.RadioButtonGroup) }, - { "Image", typeof(UnityEngine.UIElements.Image) }, + { "Image", typeof(UnityEngine.UIElements.Image) } }; public static Type GetUIElementType(string uiElementName) { return _nativeUITypes.TryGetValue(uiElementName, out Type type) ? type : null; } + + public static Type GetCustomUIElementType(string templateName) + { + return AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes()).FirstOrDefault(x => x.Name == templateName); + } } #endif \ No newline at end of file diff --git a/Editor/Scripts/Parsing/RosalinaUXMLParser.cs b/Editor/Scripts/Parsing/RosalinaUXMLParser.cs index 6553a9b..b3f0053 100644 --- a/Editor/Scripts/Parsing/RosalinaUXMLParser.cs +++ b/Editor/Scripts/Parsing/RosalinaUXMLParser.cs @@ -6,6 +6,7 @@ internal class RosalinaUXMLParser { private const string ElementAttributeName = "name"; + private const string ElementAttributeTemplateName = "template"; private const string EditorExtensionAttributeName = "editor-extension-mode"; /// @@ -27,7 +28,8 @@ private static UxmlNode ParseUxmlNode(XElement xmlNode) { string type = xmlNode.Name.LocalName; string name = xmlNode.Attribute(ElementAttributeName)?.Value ?? string.Empty; - var node = new UxmlNode(type, name, xmlNode.Parent is null); + string template = xmlNode.Attribute(ElementAttributeTemplateName)?.Value ?? string.Empty; + var node = new UxmlNode(type, name, xmlNode.Parent is null, template); if (xmlNode.HasElements) { diff --git a/Editor/Scripts/Parsing/UxmlNode.cs b/Editor/Scripts/Parsing/UxmlNode.cs index 89a9a94..269cff7 100644 --- a/Editor/Scripts/Parsing/UxmlNode.cs +++ b/Editor/Scripts/Parsing/UxmlNode.cs @@ -20,6 +20,11 @@ internal class UxmlNode /// public bool IsRoot { get; } + /// + /// Gets the UXML template name in case of custom component; null otherwise. + /// + public string Template { get; } + /// /// Gets the UXML child nodes. /// @@ -36,11 +41,13 @@ internal class UxmlNode /// Node type. /// Node name. /// Is root node. - public UxmlNode(string type, string name, bool isRoot = false) + /// Node template in case of custom component. + public UxmlNode(string type, string name, bool isRoot = false, string template = null) { Type = type; Name = name; IsRoot = isRoot; + Template = template; } } #endif \ No newline at end of file