From a585a950ae77080f4f115374ff53247c88e266f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Rodr=C3=ADguez?= Date: Tue, 6 Feb 2024 11:59:12 -0600 Subject: [PATCH] customize templates --- samples/MauiApp1/Input/CustomDisplayLabel.cs | 21 ++++++++ samples/MauiApp1/Input/CustomTextInput.cs | 21 ++++++++ .../MauiApp1/Input/CustomValidationLabel.cs | 21 ++++++++ .../Input/InputAttributes.cs | 27 ++++++++++ .../EntityViewsGenerator.cs | 30 +++++++++-- .../SyntaxNodeHelper.cs | 13 +++++ .../Input/MauiEntryPropertyTemplate.cs | 19 ++++--- .../Templates/MauiControls.cs | 53 +++++++++++++++++++ 8 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 samples/MauiApp1/Input/CustomDisplayLabel.cs create mode 100644 samples/MauiApp1/Input/CustomTextInput.cs create mode 100644 samples/MauiApp1/Input/CustomValidationLabel.cs create mode 100644 src/EntityViews.Attributes/Input/InputAttributes.cs create mode 100644 src/EntityViews.SourceGenerators/Templates/MauiControls.cs diff --git a/samples/MauiApp1/Input/CustomDisplayLabel.cs b/samples/MauiApp1/Input/CustomDisplayLabel.cs new file mode 100644 index 0000000..40dc263 --- /dev/null +++ b/samples/MauiApp1/Input/CustomDisplayLabel.cs @@ -0,0 +1,21 @@ +using EntityViews.Attributes.Input; + +namespace MauiApp1.Input; + +[DisplayControl] +public class CustomDisplayLabel : Border +{ + public CustomDisplayLabel() + { + BackgroundColor = Colors.Red; + Padding = new Thickness(5); + Content = Label = new Label + { + TextColor = Colors.White + }; + } + + [ControlProperty] + public Label Label { get; set; } +} + diff --git a/samples/MauiApp1/Input/CustomTextInput.cs b/samples/MauiApp1/Input/CustomTextInput.cs new file mode 100644 index 0000000..a636716 --- /dev/null +++ b/samples/MauiApp1/Input/CustomTextInput.cs @@ -0,0 +1,21 @@ +using EntityViews.Attributes.Input; + +namespace MauiApp1.Input; + +[TextControl] +public class CustomTextInput : Border +{ + public CustomTextInput() + { + BackgroundColor = Colors.Gray; + Padding = new Thickness(5); + Content = TextInput = new Entry + { + TextColor = Colors.Black + }; + } + + [ControlProperty] + public Entry TextInput { get; set; } +} + diff --git a/samples/MauiApp1/Input/CustomValidationLabel.cs b/samples/MauiApp1/Input/CustomValidationLabel.cs new file mode 100644 index 0000000..aac1234 --- /dev/null +++ b/samples/MauiApp1/Input/CustomValidationLabel.cs @@ -0,0 +1,21 @@ +using EntityViews.Attributes.Input; + +namespace MauiApp1.Input; + +[ValidationControl] +public class CustomValidationLabel : Border +{ + public CustomValidationLabel() + { + BackgroundColor = Colors.Red; + Padding = new Thickness(5); + Content = Label = new Label + { + TextColor = Colors.White + }; + } + + [ControlProperty] + public Label Label { get; set; } +} + diff --git a/src/EntityViews.Attributes/Input/InputAttributes.cs b/src/EntityViews.Attributes/Input/InputAttributes.cs new file mode 100644 index 0000000..c9c69cf --- /dev/null +++ b/src/EntityViews.Attributes/Input/InputAttributes.cs @@ -0,0 +1,27 @@ +namespace EntityViews.Attributes.Input; + +/// +/// Indicates that the marked class is the display control, the display control is used to display the property name. +/// +public class DisplayControlAttribute : Attribute +{ } + +/// +/// Indicates that the marked class is the text control, the text control is where the user enters the property value. +/// +public class TextControlAttribute : Attribute +{ } + +/// +/// Indicates that the marked class is the validation control, the validation control is where validation errors are shown. +/// +public class ValidationControlAttribute : Attribute +{ } + +/// +/// Indicates that the marked property is the actual control to use. The containing class must be marked with +/// , or , +/// then the marked property will behave as the main control of the class. +/// +public class ControlPropertyAttribute : TextControlAttribute +{ } diff --git a/src/EntityViews.SourceGenerators/EntityViewsGenerator.cs b/src/EntityViews.SourceGenerators/EntityViewsGenerator.cs index d86591e..631ca0c 100644 --- a/src/EntityViews.SourceGenerators/EntityViewsGenerator.cs +++ b/src/EntityViews.SourceGenerators/EntityViewsGenerator.cs @@ -13,6 +13,8 @@ public class EntityViewsGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { + Controls.Clear(); + var classDeclarations = context.SyntaxProvider .CreateSyntaxProvider( predicate: IsSyntaxTargetForGeneration, @@ -42,9 +44,31 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationTok { if (attributeData.AttributeClass is null) continue; - var nameParts = attributeData.AttributeClass.ToDisplayParts(); - if (nameParts.Length < 5) continue; - var attributeName = $"{nameParts[0]}.{nameParts[2]}.{nameParts[4]}"; + var attributeName = attributeData.AttributeClass.ToDisplayString(); + + if (attributeName == "EntityViews.Attributes.Input.DisplayControlAttribute") + { + Controls.Display = new Controls.Control( + $"{classSymbol.ContainingNamespace.ToDisplayString()}.{classSymbol.Name}", + classSymbol.GetControlProperty()); + continue; + } + + if (attributeName == "EntityViews.Attributes.Input.TextControlAttribute") + { + Controls.TextInput = new Controls.Control( + $"{classSymbol.ContainingNamespace.ToDisplayString()}.{classSymbol.Name}", + classSymbol.GetControlProperty()); + continue; + } + + if (attributeName == "EntityViews.Attributes.Input.ValidationControlAttribute") + { + Controls.Validation = new Controls.Control( + $"{classSymbol.ContainingNamespace.ToDisplayString()}.{classSymbol.Name}", + classSymbol.GetControlProperty()); + continue; + } if (attributeName == "EntityViews.Attributes.ViewModelAttribute") { diff --git a/src/EntityViews.SourceGenerators/SyntaxNodeHelper.cs b/src/EntityViews.SourceGenerators/SyntaxNodeHelper.cs index a56b091..ec788f8 100644 --- a/src/EntityViews.SourceGenerators/SyntaxNodeHelper.cs +++ b/src/EntityViews.SourceGenerators/SyntaxNodeHelper.cs @@ -90,6 +90,19 @@ private static IEnumerable FindTypes(this INamespaceSymbol root) return null; } + public static string? GetControlProperty(this INamedTypeSymbol symbol) + { + const string control = "EntityViews.Attributes.Input.ControlPropertyAttribute"; + + return symbol.GetMembers() + .Where(m => m.Kind == SymbolKind.Property) + .Cast() + .FirstOrDefault(p => + p.GetAttributes() + .Any(x => x.AttributeClass?.ToDisplayString() == control))? + .Name; + } + public static string AsValidableProperty(this IPropertySymbol property) { var annotations = property diff --git a/src/EntityViews.SourceGenerators/Templates/Input/MauiEntryPropertyTemplate.cs b/src/EntityViews.SourceGenerators/Templates/Input/MauiEntryPropertyTemplate.cs index 023f124..73c773f 100644 --- a/src/EntityViews.SourceGenerators/Templates/Input/MauiEntryPropertyTemplate.cs +++ b/src/EntityViews.SourceGenerators/Templates/Input/MauiEntryPropertyTemplate.cs @@ -22,14 +22,16 @@ public class {property.Name}Input : StackLayout {{ public {property.Name}Input() {{ - var label = new Label().Text({propertyDisplaySource}); + var label = new {Controls.GetDisplayClassName()}(); + {Controls.GetDisplayRef("label")}.Text({propertyDisplaySource}); - var input = new Entry() + var input = new {Controls.GetTextInputClassName()}(); + {Controls.GetTextInputRef("input")} .Bind( Entry.TextProperty, getter: static ({viewModelName} vm) => vm.{property.Name}, setter: static ({viewModelName} vm, {property.Type.Name} value) => vm.{property.Name} = value); - input.Triggers.Add( + {Controls.GetTextInputRef("input")}.Triggers.Add( new DataTrigger(typeof(Entry)) {{ Binding = new Binding(""{property.Name}HasError""), @@ -38,18 +40,19 @@ public class {property.Name}Input : StackLayout }}); async Task UserKeepsTyping() {{ - var txt = input.Text; + var txt = {Controls.GetTextInputRef("input")}.Text; await Task.Delay(500); - return txt != input.Text; + return txt != {Controls.GetTextInputRef("input")}.Text; }} - input.TextChanged += async (_, _) => + {Controls.GetTextInputRef("input")}.TextChanged += async (_, _) => {{ if (await UserKeepsTyping()) return; (({viewModelName})BindingContext).ValidateDirtyProperty( - ""{property.Name}"", input.Text is not null && input.Text.Length > 0); + ""{property.Name}"", {Controls.GetTextInputRef("input")}.Text is not null && {Controls.GetTextInputRef("input")}.Text.Length > 0); }}; - var validationLabel = new Label() + var validationLabel = new {Controls.GetValidationClassName()}(); + {Controls.GetValidationRef("validationLabel")} .Bind( Label.TextProperty, getter: static ({viewModelName} vm) => vm.{property.Name}Error); diff --git a/src/EntityViews.SourceGenerators/Templates/MauiControls.cs b/src/EntityViews.SourceGenerators/Templates/MauiControls.cs new file mode 100644 index 0000000..1f6f2da --- /dev/null +++ b/src/EntityViews.SourceGenerators/Templates/MauiControls.cs @@ -0,0 +1,53 @@ +namespace EntityViews.SourceGenerators.Templates; + +public static class Controls +{ + public static Control? Display; + public static string GetDisplayClassName() + { + return Display?.ClassName ?? "Label"; + } + public static string GetDisplayRef(string defaultRefName) + { + return Display?.TargetProperty is null + ? defaultRefName + : $"{defaultRefName}.{Display.TargetProperty}"; + } + + public static Control? TextInput; + public static string GetTextInputClassName() + { + return TextInput?.ClassName ?? "Entry"; + } + public static string GetTextInputRef(string defaultRefName) + { + return TextInput?.TargetProperty is null + ? defaultRefName + : $"{defaultRefName}.{TextInput.TargetProperty}"; + } + + public static Control? Validation; + public static string GetValidationClassName() + { + return Validation?.ClassName ?? "Label"; + } + public static string GetValidationRef(string defaultRefName) + { + return Validation?.TargetProperty is null + ? defaultRefName + : $"{defaultRefName}.{Validation.TargetProperty}"; + } + + public class Control(string className, string? propertyName) + { + public string ClassName { get; } = className; + public string? TargetProperty { get; } = propertyName; + } + + public static void Clear() + { + Display = null; + TextInput = null; + Validation = null; + } +}