Skip to content

Commit

Permalink
customize templates
Browse files Browse the repository at this point in the history
  • Loading branch information
beto-rodriguez committed Feb 6, 2024
1 parent 74d2b72 commit a585a95
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 11 deletions.
21 changes: 21 additions & 0 deletions samples/MauiApp1/Input/CustomDisplayLabel.cs
Original file line number Diff line number Diff line change
@@ -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; }
}

21 changes: 21 additions & 0 deletions samples/MauiApp1/Input/CustomTextInput.cs
Original file line number Diff line number Diff line change
@@ -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; }
}

21 changes: 21 additions & 0 deletions samples/MauiApp1/Input/CustomValidationLabel.cs
Original file line number Diff line number Diff line change
@@ -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; }
}

27 changes: 27 additions & 0 deletions src/EntityViews.Attributes/Input/InputAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace EntityViews.Attributes.Input;

/// <summary>
/// Indicates that the marked class is the display control, the display control is used to display the property name.
/// </summary>
public class DisplayControlAttribute : Attribute
{ }

/// <summary>
/// Indicates that the marked class is the text control, the text control is where the user enters the property value.
/// </summary>
public class TextControlAttribute : Attribute
{ }

/// <summary>
/// Indicates that the marked class is the validation control, the validation control is where validation errors are shown.
/// </summary>
public class ValidationControlAttribute : Attribute
{ }

/// <summary>
/// Indicates that the marked property is the actual control to use. The containing class must be marked with
/// <see cref="DisplayControlAttribute"/>, <see cref="TextControlAttribute"/> or <see cref="ValidationControlAttribute"/>,
/// then the marked property will behave as the main control of the class.
/// </summary>
public class ControlPropertyAttribute : TextControlAttribute
{ }
30 changes: 27 additions & 3 deletions src/EntityViews.SourceGenerators/EntityViewsGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public class EntityViewsGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
Controls.Clear();

var classDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: IsSyntaxTargetForGeneration,
Expand Down Expand Up @@ -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")
{
Expand Down
13 changes: 13 additions & 0 deletions src/EntityViews.SourceGenerators/SyntaxNodeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@ private static IEnumerable<ITypeSymbol> 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<IPropertySymbol>()
.FirstOrDefault(p =>
p.GetAttributes()
.Any(x => x.AttributeClass?.ToDisplayString() == control))?
.Name;
}

public static string AsValidableProperty(this IPropertySymbol property)
{
var annotations = property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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""),
Expand All @@ -38,18 +40,19 @@ public class {property.Name}Input : StackLayout
}});
async Task<bool> 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);
Expand Down
53 changes: 53 additions & 0 deletions src/EntityViews.SourceGenerators/Templates/MauiControls.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit a585a95

Please sign in to comment.