Skip to content

Commit fd76d5f

Browse files
authored
Feature IViewFor Support for Avalonia controls (#16)
1 parent 8410750 commit fd76d5f

File tree

3 files changed

+327
-188
lines changed

3 files changed

+327
-188
lines changed
Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using System.CodeDom.Compiler;
7+
using System.IO;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.CSharp;
10+
using Microsoft.CodeAnalysis.CSharp.Syntax;
11+
using ReactiveUI.SourceGenerators.Input.Models;
12+
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
13+
14+
namespace ReactiveUI.SourceGenerators;
15+
16+
/// <summary>
17+
/// IViewForGenerator.
18+
/// </summary>
19+
/// <seealso cref="Microsoft.CodeAnalysis.IIncrementalGenerator" />
20+
public partial class IViewForGenerator
21+
{
22+
internal static class Execute
23+
{
24+
internal static CompilationUnitSyntax GetIViewForWpfWinUiUno(IViewForInfo iViewForInfo)
25+
{
26+
UsingDirectiveSyntax[] usings = [];
27+
if (iViewForInfo.BaseType == IViewForBaseType.Wpf)
28+
{
29+
usings =
30+
[
31+
UsingDirective(ParseName("ReactiveUI")),
32+
UsingDirective(ParseName("System.Windows")),
33+
];
34+
}
35+
else if (iViewForInfo.BaseType == IViewForBaseType.WinUI)
36+
{
37+
usings =
38+
[
39+
UsingDirective(ParseName("ReactiveUI")),
40+
UsingDirective(ParseName("Microsoft.UI.Xaml")),
41+
];
42+
}
43+
else if (iViewForInfo.BaseType == IViewForBaseType.Uno)
44+
{
45+
usings =
46+
[
47+
UsingDirective(ParseName("ReactiveUI")),
48+
UsingDirective(ParseName("Windows.UI.Xaml")),
49+
];
50+
}
51+
52+
var code = CompilationUnit().AddMembers(
53+
NamespaceDeclaration(IdentifierName(iViewForInfo.ClassNamespace))
54+
.WithLeadingTrivia(TriviaList(
55+
Comment("// <auto-generated/>"),
56+
Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)),
57+
Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true))))
58+
.AddMembers(
59+
ClassDeclaration(iViewForInfo.ClassName)
60+
.AddBaseListTypes(
61+
SimpleBaseType(
62+
GenericName(Identifier("IViewFor"))
63+
.WithTypeArgumentList(
64+
TypeArgumentList(
65+
SingletonSeparatedList<TypeSyntax>(
66+
IdentifierName(iViewForInfo.ViewModelTypeName))))))
67+
.AddModifiers([.. iViewForInfo.DeclarationSyntax.Modifiers])
68+
.AddAttributeLists(AttributeList(SingletonSeparatedList(
69+
Attribute(IdentifierName(GeneratedCode))
70+
.AddArgumentListArguments(
71+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IViewForGenerator).FullName))),
72+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IViewForGenerator).Assembly.GetName().Version.ToString())))))))))
73+
.WithUsings(List(usings))
74+
.NormalizeWhitespace().ToFullString();
75+
76+
// Remove the last 4 characters to remove the closing brackets
77+
var baseCode = code.Remove(code.Length - 4);
78+
79+
// Prepare all necessary type names with type arguments
80+
using var stringStream = new StringWriter();
81+
using var writer = new IndentedTextWriter(stringStream, "\t");
82+
writer.WriteLine(baseCode);
83+
writer.Indent++;
84+
writer.Indent++;
85+
86+
// Add the necessary properties and methods for IViewFor.
87+
writer.WriteLine("/// <summary>");
88+
writer.WriteLine("/// The view model dependency property.");
89+
writer.WriteLine("/// </summary>");
90+
writer.WriteLine("public static readonly DependencyProperty ViewModelProperty =");
91+
writer.Indent++;
92+
writer.WriteLine("DependencyProperty.Register(");
93+
writer.WriteLine("nameof(ViewModel),");
94+
writer.WriteLine($"typeof({iViewForInfo.ViewModelTypeName}),");
95+
writer.WriteLine($"typeof(IViewFor<{iViewForInfo.ViewModelTypeName}>),");
96+
writer.WriteLine("new PropertyMetadata(null));");
97+
writer.WriteLine();
98+
99+
writer.Indent--;
100+
writer.WriteLine("/// <summary>");
101+
writer.WriteLine("/// Gets the binding root view model.");
102+
writer.WriteLine("/// </summary>");
103+
writer.WriteLine($"public {iViewForInfo.ViewModelTypeName}? BindingRoot => ViewModel;");
104+
writer.WriteLine();
105+
106+
writer.WriteLine("/// <inheritdoc/>");
107+
writer.WriteLine($"public {iViewForInfo.ViewModelTypeName}? ViewModel");
108+
writer.WriteLine(Token(SyntaxKind.OpenBraceToken));
109+
writer.Indent++;
110+
writer.WriteLine($"get => ({iViewForInfo.ViewModelTypeName}?)GetValue(ViewModelProperty);");
111+
writer.WriteLine("set => SetValue(ViewModelProperty, value);");
112+
writer.Indent--;
113+
writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
114+
writer.WriteLine();
115+
116+
writer.WriteLine("/// <inheritdoc/>");
117+
writer.WriteLine("object? IViewFor.ViewModel");
118+
writer.WriteLine(Token(SyntaxKind.OpenBraceToken));
119+
writer.Indent++;
120+
writer.WriteLine("get => ViewModel;");
121+
writer.WriteLine($"set => ViewModel = ({iViewForInfo.ViewModelTypeName}?)value;");
122+
writer.Indent--;
123+
writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
124+
writer.Indent--;
125+
writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
126+
writer.Indent--;
127+
writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
128+
writer.WriteLine(TriviaList(
129+
Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)),
130+
Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)))
131+
.NormalizeWhitespace());
132+
133+
var output = stringStream.ToString();
134+
return ParseCompilationUnit(output).NormalizeWhitespace();
135+
}
136+
137+
internal static CompilationUnitSyntax GetIViewForWinForms(IViewForInfo iViewForInfo)
138+
{
139+
UsingDirectiveSyntax[] usings =
140+
[
141+
UsingDirective(ParseName("ReactiveUI")),
142+
UsingDirective(ParseName("System.ComponentModel")),
143+
];
144+
145+
var code = CompilationUnit().AddMembers(
146+
NamespaceDeclaration(IdentifierName(iViewForInfo.ClassNamespace))
147+
.WithLeadingTrivia(TriviaList(
148+
Comment("// <auto-generated/>"),
149+
Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)),
150+
Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true))))
151+
.AddMembers(
152+
ClassDeclaration(iViewForInfo.ClassName)
153+
.AddBaseListTypes(
154+
SimpleBaseType(
155+
GenericName(Identifier("IViewFor"))
156+
.WithTypeArgumentList(
157+
TypeArgumentList(
158+
SingletonSeparatedList<TypeSyntax>(
159+
IdentifierName(iViewForInfo.ViewModelTypeName))))))
160+
.AddModifiers([.. iViewForInfo.DeclarationSyntax.Modifiers])
161+
.AddAttributeLists(AttributeList(SingletonSeparatedList(
162+
Attribute(IdentifierName(GeneratedCode))
163+
.AddArgumentListArguments(
164+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IViewForGenerator).FullName))),
165+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IViewForGenerator).Assembly.GetName().Version.ToString())))))))))
166+
.WithUsings(List(usings))
167+
.NormalizeWhitespace().ToFullString();
168+
169+
// Remove the last 4 characters to remove the closing brackets
170+
var baseCode = code.Remove(code.Length - 4);
171+
172+
// Prepare all necessary type names with type arguments
173+
using var stringStream = new StringWriter();
174+
using var writer = new IndentedTextWriter(stringStream, "\t");
175+
writer.WriteLine(baseCode);
176+
writer.Indent++;
177+
writer.Indent++;
178+
179+
// Add the necessary properties and methods for IViewFor.
180+
writer.WriteLine("/// <inheritdoc/>");
181+
writer.WriteLine("[Category(\"ReactiveUI\")]");
182+
writer.WriteLine("[Description(\"The ViewModel.\")]");
183+
writer.WriteLine("[Bindable(true)]");
184+
writer.WriteLine("[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]");
185+
writer.WriteLine($"public {iViewForInfo.ViewModelTypeName}? ViewModel " + "{ get; set; }");
186+
writer.WriteLine();
187+
188+
writer.WriteLine("/// <inheritdoc/>");
189+
writer.WriteLine("object? IViewFor.ViewModel");
190+
writer.WriteLine(Token(SyntaxKind.OpenBraceToken));
191+
writer.Indent++;
192+
writer.WriteLine("get => ViewModel;");
193+
writer.WriteLine($"set => ViewModel = ({iViewForInfo.ViewModelTypeName}?)value;");
194+
writer.Indent--;
195+
writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
196+
writer.Indent--;
197+
writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
198+
writer.Indent--;
199+
writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
200+
writer.WriteLine(TriviaList(
201+
Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)),
202+
Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)))
203+
.NormalizeWhitespace());
204+
205+
var output = stringStream.ToString();
206+
return ParseCompilationUnit(output).NormalizeWhitespace();
207+
}
208+
209+
internal static CompilationUnitSyntax GetIViewForAvalonia(IViewForInfo iViewForInfo)
210+
{
211+
UsingDirectiveSyntax[] usings =
212+
[
213+
UsingDirective(ParseName("System")),
214+
UsingDirective(ParseName("ReactiveUI")),
215+
UsingDirective(ParseName("Avalonia.Controls")),
216+
];
217+
218+
var code = CompilationUnit().AddMembers(
219+
NamespaceDeclaration(IdentifierName(iViewForInfo.ClassNamespace))
220+
.WithLeadingTrivia(TriviaList(
221+
Comment("// <auto-generated/>"),
222+
Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)),
223+
Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true))))
224+
.AddMembers(
225+
ClassDeclaration(iViewForInfo.ClassName)
226+
.AddBaseListTypes(
227+
SimpleBaseType(
228+
GenericName(Identifier("IViewFor"))
229+
.WithTypeArgumentList(
230+
TypeArgumentList(
231+
SingletonSeparatedList<TypeSyntax>(
232+
IdentifierName(iViewForInfo.ViewModelTypeName))))))
233+
.AddModifiers([.. iViewForInfo.DeclarationSyntax.Modifiers])
234+
.AddAttributeLists(AttributeList(SingletonSeparatedList(
235+
Attribute(IdentifierName(GeneratedCode))
236+
.AddArgumentListArguments(
237+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IViewForGenerator).FullName))),
238+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IViewForGenerator).Assembly.GetName().Version.ToString())))))))))
239+
.WithUsings(List(usings))
240+
.NormalizeWhitespace().ToFullString();
241+
242+
// Remove the last 4 characters to remove the closing brackets
243+
var baseCode = code.Remove(code.Length - 4);
244+
245+
// Prepare all necessary type names with type arguments
246+
using var stringStream = new StringWriter();
247+
using var writer = new IndentedTextWriter(stringStream, "\t");
248+
writer.WriteLine(baseCode);
249+
writer.Indent++;
250+
writer.Indent++;
251+
252+
// Add the necessary properties and methods for IViewFor.
253+
writer.WriteLine("/// <summary>");
254+
writer.WriteLine("/// The view model dependency property.");
255+
writer.WriteLine("/// </summary>");
256+
writer.WriteLine("[System.Diagnostics.CodeAnalysis.SuppressMessage(\"AvaloniaProperty\", \"AVP1002\", Justification = \"Generic avalonia property is expected here.\")]");
257+
writer.WriteLine($"public static readonly StyledProperty<{iViewForInfo.ViewModelTypeName}?> ViewModelProperty =");
258+
writer.Indent++;
259+
writer.WriteLine("AvaloniaProperty");
260+
writer.WriteLine($".Register<IViewFor<{iViewForInfo.ViewModelTypeName}>, {iViewForInfo.ViewModelTypeName}?>(nameof(ViewModel));");
261+
262+
writer.Indent--;
263+
writer.WriteLine("/// <summary>");
264+
writer.WriteLine("/// Gets the binding root view model.");
265+
writer.WriteLine("/// </summary>");
266+
writer.WriteLine($"public {iViewForInfo.ViewModelTypeName}? BindingRoot => ViewModel;");
267+
writer.WriteLine();
268+
269+
writer.WriteLine("/// <inheritdoc/>");
270+
writer.WriteLine($"public {iViewForInfo.ViewModelTypeName}? ViewModel");
271+
writer.WriteLine(Token(SyntaxKind.OpenBraceToken));
272+
writer.Indent++;
273+
writer.WriteLine($"get => ({iViewForInfo.ViewModelTypeName}?)GetValue(ViewModelProperty);");
274+
writer.WriteLine("set => SetValue(ViewModelProperty, value);");
275+
writer.Indent--;
276+
writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
277+
writer.WriteLine();
278+
279+
writer.WriteLine("/// <inheritdoc/>");
280+
writer.WriteLine("object? IViewFor.ViewModel");
281+
writer.WriteLine(Token(SyntaxKind.OpenBraceToken));
282+
writer.Indent++;
283+
writer.WriteLine("get => ViewModel;");
284+
writer.WriteLine($"set => ViewModel = ({iViewForInfo.ViewModelTypeName}?)value;");
285+
writer.Indent--;
286+
writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
287+
writer.WriteLine();
288+
writer.WriteLine(@"
289+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
290+
{
291+
base.OnPropertyChanged(change);
292+
293+
if (change.Property == DataContextProperty)
294+
{
295+
if (ReferenceEquals(change.OldValue, ViewModel)
296+
&& change.NewValue is null or TViewModel)
297+
{
298+
SetCurrentValue(ViewModelProperty, change.NewValue);
299+
}
300+
}
301+
else if (change.Property == ViewModelProperty)
302+
{
303+
if (ReferenceEquals(change.OldValue, DataContext))
304+
{
305+
SetCurrentValue(DataContextProperty, change.NewValue);
306+
}
307+
}
308+
}
309+
");
310+
writer.Indent--;
311+
writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
312+
writer.Indent--;
313+
writer.WriteLine(Token(SyntaxKind.CloseBraceToken));
314+
writer.WriteLine(TriviaList(
315+
Trivia(NullableDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)),
316+
Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), true)))
317+
.NormalizeWhitespace());
318+
319+
var output = stringStream.ToString();
320+
return ParseCompilationUnit(output).NormalizeWhitespace();
321+
}
322+
}
323+
}

0 commit comments

Comments
 (0)