Added static view locator gnerator
ADD
WalletWasabi.Fluent.Generators/StaticViewLocatorGenerator.cs
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace WalletWasabi.Fluent.Generators
+{
+ [Generator]
+ public class StaticViewLocatorGenerator : ISourceGenerator
+ {
+ private const string AttributeText = @"// <auto-generated />
+using System;
+
+namespace WalletWasabi.Fluent
+{
+ [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
+ public sealed class StaticViewLocatorAttribute : Attribute
+ {
+ }
+}";
+
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ // System.Diagnostics.Debugger.Launch();
+ context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ context.AddSource("StaticViewLocatorAttribute", SourceText.From(AttributeText, Encoding.UTF8));
+
+ if (!(context.SyntaxReceiver is SyntaxReceiver receiver))
+ {
+ return;
+ }
+
+ var options = (context.Compilation as CSharpCompilation)?.SyntaxTrees[0].Options as CSharpParseOptions;
+ var compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(AttributeText, Encoding.UTF8), options));
+
+ var attributeSymbol = compilation.GetTypeByMetadataName("WalletWasabi.Fluent.StaticViewLocatorAttribute");
+ if (attributeSymbol is null)
+ {
+ return;
+ }
+
+ List<INamedTypeSymbol> namedTypeSymbolLocators = new();
+ List<INamedTypeSymbol> namedTypeSymbolViewModels = new();
+
+ foreach (var candidateClass in receiver.CandidateClasses)
+ {
+ var semanticModel = compilation.GetSemanticModel(candidateClass.SyntaxTree);
+ var namedTypeSymbol = semanticModel.GetDeclaredSymbol(candidateClass);
+ if (namedTypeSymbol is null)
+ {
+ continue;
+ }
+
+ var attributes = namedTypeSymbol.GetAttributes();
+ if (attributes.Any(ad => ad?.AttributeClass?.Equals(attributeSymbol, SymbolEqualityComparer.Default) ?? false))
+ {
+ namedTypeSymbolLocators.Add(namedTypeSymbol);
+ }
+ else if (namedTypeSymbol.Name.EndsWith("ViewModel"))
+ {
+ namedTypeSymbolViewModels.Add(namedTypeSymbol);
+ }
+ }
+
+ foreach (var namedTypeSymbol in namedTypeSymbolLocators)
+ {
+ var classSource = ProcessClass(compilation, namedTypeSymbol, namedTypeSymbolViewModels);
+ if (classSource is not null)
+ {
+ context.AddSource($"{namedTypeSymbol.Name}_StaticViewLocator.cs", SourceText.From(classSource, Encoding.UTF8));
+ }
+ }
+ }
+
+ private static string? ProcessClass(Compilation compilation, INamedTypeSymbol namedTypeSymbolLocator, List<INamedTypeSymbol> namedTypeSymbolViewModels)
+ {
+ if (!namedTypeSymbolLocator.ContainingSymbol.Equals(namedTypeSymbolLocator.ContainingNamespace,
+ SymbolEqualityComparer.Default))
+ {
+ return null;
+ }
+
+ string namespaceNameLocator = namedTypeSymbolLocator.ContainingNamespace.ToDisplayString();
+
+ var format = new SymbolDisplayFormat(
+ typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes,
+ genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeTypeConstraints | SymbolDisplayGenericsOptions.IncludeVariance
+ );
+
+ string classNameLocator = namedTypeSymbolLocator.ToDisplayString(format);
+
+ var source = new StringBuilder($@"// <auto-generated />
+#nullable enable
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Controls.Templates;
+using WalletWasabi.Gui.ViewModels;
+
+namespace {namespaceNameLocator}
+{{
+ public partial class {classNameLocator}
+ {{
+");
+ source.Append($@"
+ private static Dictionary<Type, Func<Control>> s_views = new()
+ {{");
+
+ foreach (var namedTypeSymbolViewModel in namedTypeSymbolViewModels)
+ {
+ string namespaceNameViewModel = namedTypeSymbolViewModel.ContainingNamespace.ToDisplayString();
+ string classNameViewModel = $"{namespaceNameViewModel}.{namedTypeSymbolViewModel.ToDisplayString(format)}";
+ string classNameView = classNameViewModel.Replace("ViewModel", "View");
+
+ var classNameViewSymbol = compilation.GetTypeByMetadataName(classNameView);
+ if (classNameViewSymbol is null)
+ {
+ source.Append($@"
+ [typeof({classNameViewModel})] = () => new TextBlock() {{ Text = {("\"Not Found: " + classNameView + "\"")} }},");
+ }
+ else
+ {
+ source.Append($@"
+ [typeof({classNameViewModel})] = () => new {classNameView}(),");
+ }
+ }
+
+ source.Append($@"
+ }};
+ }}
+}}");
+
+ return source.ToString();
+ }
+
+ private class SyntaxReceiver : ISyntaxReceiver
+ {
+ public List<ClassDeclarationSyntax> CandidateClasses { get; } = new();
+
+ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+ {
+ if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax)
+ {
+ CandidateClasses.Add(classDeclarationSyntax);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file

MODIFY
WalletWasabi.Fluent/ViewLocator.cs
WalletWasabi.Fluent/ViewLocator.cs
@@ -8,13 +8,20 @@ using WalletWasabi.Gui.ViewModels;
namespace WalletWasabi.Fluent
{
- public class ViewLocator : IDataTemplate
+ [StaticViewLocator]
+ public partial class ViewLocator : IDataTemplate
{
public bool SupportsRecycling => false;
public IControl Build(object data)
{
- var name = data.GetType().FullName!.Replace("ViewModel", "View");
+ var type = data.GetType();
+ if (s_views.TryGetValue(type, out var func))
+ {
+ return func?.Invoke();
+ }
+ throw new Exception($"Unable to create view for type: {type}");
+ /*var name =.FullName!.Replace("ViewModel", "View");
var type = Type.GetType(name);
if (type != null)
@@ -31,7 +38,7 @@ namespace WalletWasabi.Fluent
else
{
return new TextBlock { Text = "Not Found: " + name };
- }
+ }*/
}
public bool Match(object data)

Added static view locator gnerator