diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json
index 5bbc3d69158..875161d336e 100644
--- a/.nuke/build.schema.json
+++ b/.nuke/build.schema.json
@@ -84,11 +84,11 @@
"GenerateCppHeaders",
"Package",
"RunCoreLibsTests",
- "RunDesignerTests",
"RunHtmlPreviewerTests",
"RunLeakTests",
"RunRenderTests",
"RunTests",
+ "RunToolsTests",
"ZipFiles"
]
}
@@ -123,11 +123,11 @@
"GenerateCppHeaders",
"Package",
"RunCoreLibsTests",
- "RunDesignerTests",
"RunHtmlPreviewerTests",
"RunLeakTests",
"RunRenderTests",
"RunTests",
+ "RunToolsTests",
"ZipFiles"
]
}
diff --git a/Avalonia.sln b/Avalonia.sln
index e66b73de0ef..b21df076289 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -244,8 +244,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepe
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater.UnitTests", "tests\Avalonia.Controls.ItemsRepeater.UnitTests\Avalonia.Controls.ItemsRepeater.UnitTests.csproj", "{F4E36AA8-814E-4704-BC07-291F70F45193}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Generators", "src\tools\Avalonia.Generators\Avalonia.Generators.csproj", "{DDA28789-C21A-4654-86CE-D01E81F095C5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Generators.Tests", "tests\Avalonia.Generators.Tests\Avalonia.Generators.Tests.csproj", "{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Fonts.Inter", "src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj", "{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Generators.Sandbox", "samples\Generators.Sandbox\Generators.Sandbox.csproj", "{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -573,10 +579,22 @@ Global
{F4E36AA8-814E-4704-BC07-291F70F45193}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DDA28789-C21A-4654-86CE-D01E81F095C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DDA28789-C21A-4654-86CE-D01E81F095C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DDA28789-C21A-4654-86CE-D01E81F095C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DDA28789-C21A-4654-86CE-D01E81F095C5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F}.Release|Any CPU.Build.0 = Release|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -643,7 +661,10 @@ Global
{75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
+ {DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
+ {2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+ {A82AD1BC-EBE6-4FC3-A13B-D52A50297533} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/build/SourceGenerators.props b/build/SourceGenerators.props
index 4929578b604..a66bff49993 100644
--- a/build/SourceGenerators.props
+++ b/build/SourceGenerators.props
@@ -1,5 +1,10 @@
-
+
+ true
+ false
+
+
+
+
+
+
+
+
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index 46f267ae171..40232947d9f 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -220,16 +220,18 @@ void RunCoreTest(string projectName)
.Executes(() =>
{
RunCoreTest("Avalonia.Skia.RenderTests");
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ if (Parameters.IsRunningOnWindows)
RunCoreTest("Avalonia.Direct2D1.RenderTests");
});
- Target RunDesignerTests => _ => _
- .OnlyWhenStatic(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows)
+ Target RunToolsTests => _ => _
+ .OnlyWhenStatic(() => !Parameters.SkipTests)
.DependsOn(Compile)
.Executes(() =>
{
- RunCoreTest("Avalonia.DesignerSupport.Tests");
+ RunCoreTest("Avalonia.Generators.Tests");
+ if (Parameters.IsRunningOnWindows)
+ RunCoreTest("Avalonia.DesignerSupport.Tests");
});
Target RunLeakTests => _ => _
@@ -276,7 +278,7 @@ void DoMemoryTest()
Target RunTests => _ => _
.DependsOn(RunCoreLibsTests)
.DependsOn(RunRenderTests)
- .DependsOn(RunDesignerTests)
+ .DependsOn(RunToolsTests)
.DependsOn(RunHtmlPreviewerTests)
.DependsOn(RunLeakTests);
diff --git a/nukebuild/numerge.config b/nukebuild/numerge.config
index d1c0408241c..09f22ec527d 100644
--- a/nukebuild/numerge.config
+++ b/nukebuild/numerge.config
@@ -11,6 +11,11 @@
"Id": "Avalonia.Build.Tasks",
"IgnoreMissingFrameworkBinaries": true,
"DoNotMergeDependencies": true
+ },
+ {
+ "Id": "Avalonia.Generators",
+ "IgnoreMissingFrameworkBinaries": true,
+ "DoNotMergeDependencies": true
}
]
}
diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj
index 4d0ed866a36..1d210172f0e 100644
--- a/packages/Avalonia/Avalonia.csproj
+++ b/packages/Avalonia/Avalonia.csproj
@@ -6,11 +6,15 @@
-
+
all
true
TargetFramework=netstandard2.0
+
diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj
index 0e84b3d1826..5125b42426a 100644
--- a/samples/ControlCatalog/ControlCatalog.csproj
+++ b/samples/ControlCatalog/ControlCatalog.csproj
@@ -2,7 +2,8 @@
netstandard2.0;net6.0
true
- enable
+ enable
+ true
@@ -35,14 +36,5 @@
-
-
-
-
-
-
-
-
-
-
+
diff --git a/samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs b/samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs
index 7db6d9d3341..89441513856 100644
--- a/samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs
+++ b/samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs
@@ -1,11 +1,10 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
-using Avalonia.Markup.Xaml;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
- public class FlyoutsPage : UserControl
+ public partial class FlyoutsPage : UserControl
{
public FlyoutsPage()
{
@@ -28,11 +27,6 @@ private void Afp_DoubleTapped(object? sender, RoutedEventArgs e)
}
}
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
-
private void SetXamlTexts()
{
var bfxt = this.Get("ButtonFlyoutXamlText");
diff --git a/samples/ControlCatalog/Pages/LabelsPage.axaml.cs b/samples/ControlCatalog/Pages/LabelsPage.axaml.cs
index f05e5fd0333..f3a7647f8c8 100644
--- a/samples/ControlCatalog/Pages/LabelsPage.axaml.cs
+++ b/samples/ControlCatalog/Pages/LabelsPage.axaml.cs
@@ -1,11 +1,9 @@
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
+using Avalonia.Controls;
using ControlCatalog.Models;
namespace ControlCatalog.Pages
{
- public class LabelsPage : UserControl
+ public partial class LabelsPage : UserControl
{
private Person? _person;
@@ -25,11 +23,6 @@ private void CreateDefaultPerson()
};
}
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
-
public void DoSave()
{
diff --git a/samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs b/samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs
index f9d0328d9ae..a710cd7e5c7 100644
--- a/samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs
+++ b/samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs
@@ -1,18 +1,15 @@
-using System.Threading.Tasks;
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
+using Avalonia.Controls;
using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
- public class RefreshContainerPage : UserControl
+ public partial class RefreshContainerPage : UserControl
{
private RefreshContainerViewModel _viewModel;
public RefreshContainerPage()
{
- this.InitializeComponent();
+ InitializeComponent();
_viewModel = new RefreshContainerViewModel();
@@ -27,10 +24,5 @@ private async void RefreshContainerPage_RefreshRequested(object? sender, Refresh
deferral.Complete();
}
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
}
}
diff --git a/samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs b/samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs
index 11d0a5152e0..aec13a18e37 100644
--- a/samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs
+++ b/samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs
@@ -1,19 +1,12 @@
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
+using Avalonia.Controls;
namespace ControlCatalog.Pages
{
- public class RelativePanelPage : UserControl
+ public partial class RelativePanelPage : UserControl
{
public RelativePanelPage()
{
- this.InitializeComponent();
- }
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
+ InitializeComponent();
}
}
}
diff --git a/samples/ControlCatalog/Pages/ThemePage.axaml.cs b/samples/ControlCatalog/Pages/ThemePage.axaml.cs
index f0ae1a722da..5a0c4cba43d 100644
--- a/samples/ControlCatalog/Pages/ThemePage.axaml.cs
+++ b/samples/ControlCatalog/Pages/ThemePage.axaml.cs
@@ -1,35 +1,31 @@
-using Avalonia;
-using Avalonia.Controls;
+using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
namespace ControlCatalog.Pages
{
- public class ThemePage : UserControl
+ public partial class ThemePage : UserControl
{
public static ThemeVariant Pink { get; } = new("Pink", ThemeVariant.Light);
public ThemePage()
{
- AvaloniaXamlLoader.Load(this);
+ InitializeComponent();
- var selector = this.FindControl("Selector")!;
- var themeVariantScope = this.FindControl("ThemeVariantScope")!;
-
- selector.Items = new[]
+ Selector.Items = new[]
{
ThemeVariant.Default,
ThemeVariant.Dark,
ThemeVariant.Light,
Pink
};
- selector.SelectedIndex = 0;
+ Selector.SelectedIndex = 0;
- selector.SelectionChanged += (_, _) =>
+ Selector.SelectionChanged += (_, _) =>
{
- if (selector.SelectedItem is ThemeVariant theme)
+ if (Selector.SelectedItem is ThemeVariant theme)
{
- themeVariantScope.RequestedThemeVariant = theme;
+ ThemeVariantScope.RequestedThemeVariant = theme;
}
};
}
diff --git a/samples/Generators.Sandbox/App.xaml b/samples/Generators.Sandbox/App.xaml
new file mode 100644
index 00000000000..8064eac3f5c
--- /dev/null
+++ b/samples/Generators.Sandbox/App.xaml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/Generators.Sandbox/App.xaml.cs b/samples/Generators.Sandbox/App.xaml.cs
new file mode 100644
index 00000000000..6118b3f177b
--- /dev/null
+++ b/samples/Generators.Sandbox/App.xaml.cs
@@ -0,0 +1,20 @@
+using Avalonia;
+using Avalonia.Markup.Xaml;
+using Generators.Sandbox.ViewModels;
+
+namespace Generators.Sandbox;
+
+public class App : Application
+{
+ public override void Initialize() => AvaloniaXamlLoader.Load(this);
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ var view = new Views.SignUpView
+ {
+ ViewModel = new SignUpViewModel()
+ };
+ view.Show();
+ base.OnFrameworkInitializationCompleted();
+ }
+}
\ No newline at end of file
diff --git a/samples/Generators.Sandbox/Controls/CustomTextBox.cs b/samples/Generators.Sandbox/Controls/CustomTextBox.cs
new file mode 100644
index 00000000000..68ee9259867
--- /dev/null
+++ b/samples/Generators.Sandbox/Controls/CustomTextBox.cs
@@ -0,0 +1,10 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Styling;
+
+namespace Generators.Sandbox.Controls;
+
+public class CustomTextBox : TextBox, IStyleable
+{
+ Type IStyleable.StyleKey => typeof(TextBox);
+}
\ No newline at end of file
diff --git a/samples/Generators.Sandbox/Controls/SignUpView.xaml b/samples/Generators.Sandbox/Controls/SignUpView.xaml
new file mode 100644
index 00000000000..c126f36f534
--- /dev/null
+++ b/samples/Generators.Sandbox/Controls/SignUpView.xaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/Generators.Sandbox/Controls/SignUpView.xaml.cs b/samples/Generators.Sandbox/Controls/SignUpView.xaml.cs
new file mode 100644
index 00000000000..c4cd1cdc1aa
--- /dev/null
+++ b/samples/Generators.Sandbox/Controls/SignUpView.xaml.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Reactive.Disposables;
+using Avalonia.ReactiveUI;
+using Generators.Sandbox.ViewModels;
+using ReactiveUI;
+using ReactiveUI.Validation.Extensions;
+using ReactiveUI.Validation.Formatters;
+
+namespace Generators.Sandbox.Controls;
+
+///
+/// This is a sample view class with typed x:Name references generated using
+/// .NET 5 source generators. The class has to be partial because x:Name
+/// references are living in a separate partial class file. See also:
+/// https://devblogs.microsoft.com/dotnet/new-c-source-generator-samples/
+///
+public partial class SignUpView : ReactiveUserControl
+{
+ public SignUpView()
+ {
+ // The InitializeComponent method is also generated automatically
+ // and lives in the autogenerated part of the partial class.
+ InitializeComponent();
+ this.WhenActivated(disposables =>
+ {
+ this.Bind(ViewModel, x => x.UserName, x => x.UserNameTextBox.Text)
+ .DisposeWith(disposables);
+ this.Bind(ViewModel, x => x.Password, x => x.PasswordTextBox.Text)
+ .DisposeWith(disposables);
+ this.Bind(ViewModel, x => x.ConfirmPassword, x => x.ConfirmPasswordTextBox.Text)
+ .DisposeWith(disposables);
+ this.BindCommand(ViewModel, x => x.SignUp, x => x.SignUpButton)
+ .DisposeWith(disposables);
+
+ this.BindValidation(ViewModel, x => x.UserName, x => x.UserNameValidation.Text)
+ .DisposeWith(disposables);
+ this.BindValidation(ViewModel, x => x.Password, x => x.PasswordValidation.Text)
+ .DisposeWith(disposables);
+ this.BindValidation(ViewModel, x => x.ConfirmPassword, x => x.ConfirmPasswordValidation.Text)
+ .DisposeWith(disposables);
+
+ var newLineFormatter = new SingleLineFormatter(Environment.NewLine);
+ this.BindValidation(ViewModel, x => x.CompoundValidation.Text, newLineFormatter)
+ .DisposeWith(disposables);
+
+ // The references to text boxes below are also auto generated.
+ // Use Ctrl+Click in order to view the generated sources.
+ UserNameTextBox.Text = "Joseph!";
+ PasswordTextBox.Text = "1234";
+ ConfirmPasswordTextBox.Text = "1234";
+ SignUpButtonDescription.Text = "Press the button below to sign up.";
+ });
+ }
+}
diff --git a/samples/Generators.Sandbox/Generators.Sandbox.csproj b/samples/Generators.Sandbox/Generators.Sandbox.csproj
new file mode 100644
index 00000000000..885e71af4e1
--- /dev/null
+++ b/samples/Generators.Sandbox/Generators.Sandbox.csproj
@@ -0,0 +1,28 @@
+
+
+ Exe
+ net6.0
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/Generators.Sandbox/Program.cs b/samples/Generators.Sandbox/Program.cs
new file mode 100644
index 00000000000..7e533965df5
--- /dev/null
+++ b/samples/Generators.Sandbox/Program.cs
@@ -0,0 +1,15 @@
+using Avalonia;
+using Avalonia.ReactiveUI;
+
+namespace Generators.Sandbox;
+
+internal static class Program
+{
+ public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
+
+ private static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UseReactiveUI()
+ .UsePlatformDetect()
+ .LogToTrace();
+}
diff --git a/samples/Generators.Sandbox/ViewModels/SignUpViewModel.cs b/samples/Generators.Sandbox/ViewModels/SignUpViewModel.cs
new file mode 100644
index 00000000000..06b61220884
--- /dev/null
+++ b/samples/Generators.Sandbox/ViewModels/SignUpViewModel.cs
@@ -0,0 +1,70 @@
+using System.Reactive;
+using ReactiveUI;
+using ReactiveUI.Validation.Extensions;
+using ReactiveUI.Validation.Helpers;
+
+namespace Generators.Sandbox.ViewModels;
+
+public class SignUpViewModel : ReactiveValidationObject
+{
+ private string _userName = string.Empty;
+ private string _password = string.Empty;
+ private string _confirmPassword = string.Empty;
+
+ public SignUpViewModel()
+ {
+ this.ValidationRule(
+ vm => vm.UserName,
+ name => !string.IsNullOrWhiteSpace(name),
+ "UserName is required.");
+
+ this.ValidationRule(
+ vm => vm.Password,
+ password => !string.IsNullOrWhiteSpace(password),
+ "Password is required.");
+
+ this.ValidationRule(
+ vm => vm.Password,
+ password => password?.Length > 2,
+ password => $"Password should be longer, current length: {password.Length}");
+
+ this.ValidationRule(
+ vm => vm.ConfirmPassword,
+ confirmation => !string.IsNullOrWhiteSpace(confirmation),
+ "Confirm password field is required.");
+
+ var passwordsObservable =
+ this.WhenAnyValue(
+ x => x.Password,
+ x => x.ConfirmPassword,
+ (password, confirmation) =>
+ password == confirmation);
+
+ this.ValidationRule(
+ vm => vm.ConfirmPassword,
+ passwordsObservable,
+ "Passwords must match.");
+
+ SignUp = ReactiveCommand.Create(() => {}, this.IsValid());
+ }
+
+ public ReactiveCommand SignUp { get; }
+
+ public string UserName
+ {
+ get => _userName;
+ set => this.RaiseAndSetIfChanged(ref _userName, value);
+ }
+
+ public string Password
+ {
+ get => _password;
+ set => this.RaiseAndSetIfChanged(ref _password, value);
+ }
+
+ public string ConfirmPassword
+ {
+ get => _confirmPassword;
+ set => this.RaiseAndSetIfChanged(ref _confirmPassword, value);
+ }
+}
\ No newline at end of file
diff --git a/samples/Generators.Sandbox/Views/SignUpView.xaml b/samples/Generators.Sandbox/Views/SignUpView.xaml
new file mode 100644
index 00000000000..970b3a97104
--- /dev/null
+++ b/samples/Generators.Sandbox/Views/SignUpView.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/samples/Generators.Sandbox/Views/SignUpView.xaml.cs b/samples/Generators.Sandbox/Views/SignUpView.xaml.cs
new file mode 100644
index 00000000000..1f495b7dc62
--- /dev/null
+++ b/samples/Generators.Sandbox/Views/SignUpView.xaml.cs
@@ -0,0 +1,28 @@
+using System.Reactive.Disposables;
+using Avalonia.ReactiveUI;
+using Generators.Sandbox.ViewModels;
+using ReactiveUI;
+
+namespace Generators.Sandbox.Views;
+
+///
+/// This is a sample view class with typed x:Name references generated using
+/// .NET 5 source generators. The class has to be partial because x:Name
+/// references are living in a separate partial class file. See also:
+/// https://devblogs.microsoft.com/dotnet/new-c-source-generator-samples/
+///
+public partial class SignUpView : ReactiveWindow
+{
+ public SignUpView()
+ {
+ // The InitializeComponent method is also generated automatically
+ // and lives in the autogenerated part of the partial class.
+ InitializeComponent();
+ this.WhenActivated(disposables =>
+ {
+ this.WhenAnyValue(view => view.ViewModel)
+ .BindTo(this, view => view.SignUpControl.ViewModel)
+ .DisposeWith(disposables);
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/tools/Avalonia.Generators/Avalonia.Generators.csproj b/src/tools/Avalonia.Generators/Avalonia.Generators.csproj
new file mode 100644
index 00000000000..c6e32a3a4ff
--- /dev/null
+++ b/src/tools/Avalonia.Generators/Avalonia.Generators.csproj
@@ -0,0 +1,32 @@
+
+
+ netstandard2.0
+ false
+ Avalonia.Generators
+ $(DefineConstants);XAMLX_INTERNAL
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tools/Avalonia.Generators/Avalonia.Generators.props b/src/tools/Avalonia.Generators/Avalonia.Generators.props
new file mode 100644
index 00000000000..08cbeff1baf
--- /dev/null
+++ b/src/tools/Avalonia.Generators/Avalonia.Generators.props
@@ -0,0 +1,22 @@
+
+
+ true
+ InitializeComponent
+ internal
+ *
+ *
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tools/Avalonia.Generators/Common/Domain/ICodeGenerator.cs b/src/tools/Avalonia.Generators/Common/Domain/ICodeGenerator.cs
new file mode 100644
index 00000000000..4b426172f8e
--- /dev/null
+++ b/src/tools/Avalonia.Generators/Common/Domain/ICodeGenerator.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Generators.Common.Domain;
+
+internal interface ICodeGenerator
+{
+ string GenerateCode(string className, string nameSpace, IXamlType xamlType, IEnumerable names);
+}
diff --git a/src/tools/Avalonia.Generators/Common/Domain/IGlobPattern.cs b/src/tools/Avalonia.Generators/Common/Domain/IGlobPattern.cs
new file mode 100644
index 00000000000..04dbf9cbb95
--- /dev/null
+++ b/src/tools/Avalonia.Generators/Common/Domain/IGlobPattern.cs
@@ -0,0 +1,6 @@
+namespace Avalonia.Generators.Common.Domain;
+
+internal interface IGlobPattern
+{
+ bool Matches(string str);
+}
diff --git a/src/tools/Avalonia.Generators/Common/Domain/INameResolver.cs b/src/tools/Avalonia.Generators/Common/Domain/INameResolver.cs
new file mode 100644
index 00000000000..cb5488d8a39
--- /dev/null
+++ b/src/tools/Avalonia.Generators/Common/Domain/INameResolver.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using XamlX.Ast;
+
+namespace Avalonia.Generators.Common.Domain;
+
+internal enum NamedFieldModifier
+{
+ Public = 0,
+ Private = 1,
+ Internal = 2,
+ Protected = 3,
+}
+
+internal interface INameResolver
+{
+ IReadOnlyList ResolveNames(XamlDocument xaml);
+}
+
+internal record ResolvedName(string TypeName, string Name, string FieldModifier);
diff --git a/src/tools/Avalonia.Generators/Common/Domain/IViewResolver.cs b/src/tools/Avalonia.Generators/Common/Domain/IViewResolver.cs
new file mode 100644
index 00000000000..c3c219e3f01
--- /dev/null
+++ b/src/tools/Avalonia.Generators/Common/Domain/IViewResolver.cs
@@ -0,0 +1,11 @@
+using XamlX.Ast;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Generators.Common.Domain;
+
+internal interface IViewResolver
+{
+ ResolvedView ResolveView(string xaml);
+}
+
+internal record ResolvedView(string ClassName, IXamlType XamlType, string Namespace, XamlDocument Xaml);
diff --git a/src/tools/Avalonia.Generators/Common/GlobPattern.cs b/src/tools/Avalonia.Generators/Common/GlobPattern.cs
new file mode 100644
index 00000000000..484e17d7878
--- /dev/null
+++ b/src/tools/Avalonia.Generators/Common/GlobPattern.cs
@@ -0,0 +1,18 @@
+using System.Text.RegularExpressions;
+using Avalonia.Generators.Common.Domain;
+
+namespace Avalonia.Generators.Common;
+
+internal class GlobPattern : IGlobPattern
+{
+ private const RegexOptions GlobOptions = RegexOptions.IgnoreCase | RegexOptions.Singleline;
+ private readonly Regex _regex;
+
+ public GlobPattern(string pattern)
+ {
+ var expression = "^" + Regex.Escape(pattern).Replace(@"\*", ".*").Replace(@"\?", ".") + "$";
+ _regex = new Regex(expression, GlobOptions);
+ }
+
+ public bool Matches(string str) => _regex.IsMatch(str);
+}
diff --git a/src/tools/Avalonia.Generators/Common/GlobPatternGroup.cs b/src/tools/Avalonia.Generators/Common/GlobPatternGroup.cs
new file mode 100644
index 00000000000..1358ee79207
--- /dev/null
+++ b/src/tools/Avalonia.Generators/Common/GlobPatternGroup.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Generators.Common.Domain;
+
+namespace Avalonia.Generators.Common;
+
+internal class GlobPatternGroup : IGlobPattern
+{
+ private readonly GlobPattern[] _patterns;
+
+ public GlobPatternGroup(IEnumerable patterns) =>
+ _patterns = patterns
+ .Select(pattern => new GlobPattern(pattern))
+ .ToArray();
+
+ public bool Matches(string str) => _patterns.Any(pattern => pattern.Matches(str));
+}
diff --git a/src/tools/Avalonia.Generators/Common/ResolverExtensions.cs b/src/tools/Avalonia.Generators/Common/ResolverExtensions.cs
new file mode 100644
index 00000000000..04352298c85
--- /dev/null
+++ b/src/tools/Avalonia.Generators/Common/ResolverExtensions.cs
@@ -0,0 +1,25 @@
+using System.Linq;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Generators.Common;
+
+internal static class ResolverExtensions
+{
+ public static bool IsAvaloniaStyledElement(this IXamlType clrType) =>
+ clrType.HasStyledElementBaseType() ||
+ clrType.HasIStyledElementInterface();
+
+ private static bool HasStyledElementBaseType(this IXamlType clrType)
+ {
+ // Check for the base type since IStyledElement interface is removed.
+ // https://github.com/AvaloniaUI/Avalonia/pull/9553
+ if (clrType.FullName == "Avalonia.StyledElement")
+ return true;
+ return clrType.BaseType != null && IsAvaloniaStyledElement(clrType.BaseType);
+ }
+
+ private static bool HasIStyledElementInterface(this IXamlType clrType) =>
+ clrType.Interfaces.Any(abstraction =>
+ abstraction.IsInterface &&
+ abstraction.FullName == "Avalonia.IStyledElement");
+}
diff --git a/src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs b/src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs
new file mode 100644
index 00000000000..7ed19eb84c0
--- /dev/null
+++ b/src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs
@@ -0,0 +1,92 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using Avalonia.Generators.Common.Domain;
+using XamlX;
+using XamlX.Ast;
+
+namespace Avalonia.Generators.Common;
+
+internal class XamlXNameResolver : INameResolver, IXamlAstVisitor
+{
+ private readonly List _items = new();
+ private readonly string _defaultFieldModifier;
+
+ public XamlXNameResolver(NamedFieldModifier namedFieldModifier = NamedFieldModifier.Internal)
+ {
+ _defaultFieldModifier = namedFieldModifier.ToString().ToLowerInvariant();
+ }
+
+ public IReadOnlyList ResolveNames(XamlDocument xaml)
+ {
+ _items.Clear();
+ xaml.Root.Visit(this);
+ xaml.Root.VisitChildren(this);
+ return _items;
+ }
+
+ IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
+ {
+ if (node is not XamlAstObjectNode objectNode)
+ return node;
+
+ var clrType = objectNode.Type.GetClrType();
+ if (!clrType.IsAvaloniaStyledElement())
+ return node;
+
+ foreach (var child in objectNode.Children)
+ {
+ if (child is XamlAstXamlPropertyValueNode propertyValueNode &&
+ propertyValueNode.Property is XamlAstNamePropertyReference namedProperty &&
+ namedProperty.Name == "Name" &&
+ propertyValueNode.Values.Count > 0 &&
+ propertyValueNode.Values[0] is XamlAstTextNode text)
+ {
+ var fieldModifier = TryGetFieldModifier(objectNode);
+ var typeName = $@"{clrType.Namespace}.{clrType.Name}";
+ var typeAgs = clrType.GenericArguments.Select(arg => arg.FullName).ToImmutableList();
+ var genericTypeName = typeAgs.Count == 0
+ ? $"global::{typeName}"
+ : $@"global::{typeName}<{string.Join(", ", typeAgs.Select(arg => $"global::{arg}"))}>";
+
+ var resolvedName = new ResolvedName(genericTypeName, text.Text, fieldModifier);
+ if (_items.Contains(resolvedName))
+ continue;
+ _items.Add(resolvedName);
+ }
+ }
+
+ return node;
+ }
+
+ void IXamlAstVisitor.Push(IXamlAstNode node) { }
+
+ void IXamlAstVisitor.Pop() { }
+
+ private string TryGetFieldModifier(XamlAstObjectNode objectNode)
+ {
+ // We follow Xamarin.Forms API behavior in terms of x:FieldModifier here:
+ // https://docs.microsoft.com/en-us/xamarin/xamarin-forms/xaml/field-modifiers
+ // However, by default we use 'internal' field modifier here for generated
+ // x:Name references for historical purposes and WPF compatibility.
+ //
+ var fieldModifierType = objectNode
+ .Children
+ .OfType()
+ .Where(dir => dir.Name == "FieldModifier" && dir.Namespace == XamlNamespaces.Xaml2006)
+ .Select(dir => dir.Values[0])
+ .OfType()
+ .Select(txt => txt.Text)
+ .FirstOrDefault();
+
+ return fieldModifierType?.ToLowerInvariant() switch
+ {
+ "private" => "private",
+ "public" => "public",
+ "protected" => "protected",
+ "internal" => "internal",
+ "notpublic" => "internal",
+ _ => _defaultFieldModifier
+ };
+ }
+}
diff --git a/src/tools/Avalonia.Generators/Common/XamlXViewResolver.cs b/src/tools/Avalonia.Generators/Common/XamlXViewResolver.cs
new file mode 100644
index 00000000000..5bbe0c060d4
--- /dev/null
+++ b/src/tools/Avalonia.Generators/Common/XamlXViewResolver.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Generators.Common.Domain;
+using Avalonia.Generators.Compiler;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Parsers;
+
+namespace Avalonia.Generators.Common;
+
+internal class XamlXViewResolver : IViewResolver, IXamlAstVisitor
+{
+ private readonly RoslynTypeSystem _typeSystem;
+ private readonly MiniCompiler _compiler;
+ private readonly bool _checkTypeValidity;
+ private readonly Action _onTypeInvalid;
+ private readonly Action _onUnhandledError;
+
+ private ResolvedView _resolvedClass;
+ private XamlDocument _xaml;
+
+ public XamlXViewResolver(
+ RoslynTypeSystem typeSystem,
+ MiniCompiler compiler,
+ bool checkTypeValidity = false,
+ Action onTypeInvalid = null,
+ Action onUnhandledError = null)
+ {
+ _checkTypeValidity = checkTypeValidity;
+ _onTypeInvalid = onTypeInvalid;
+ _onUnhandledError = onUnhandledError;
+ _typeSystem = typeSystem;
+ _compiler = compiler;
+ }
+
+ public ResolvedView ResolveView(string xaml)
+ {
+ try
+ {
+ _resolvedClass = null;
+ _xaml = XDocumentXamlParser.Parse(xaml, new Dictionary
+ {
+ {XamlNamespaces.Blend2008, XamlNamespaces.Blend2008}
+ });
+
+ _compiler.Transform(_xaml);
+ _xaml.Root.Visit(this);
+ _xaml.Root.VisitChildren(this);
+ return _resolvedClass;
+ }
+ catch (Exception exception)
+ {
+ _onUnhandledError?.Invoke(exception);
+ return null;
+ }
+ }
+
+ IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
+ {
+ if (node is not XamlAstObjectNode objectNode)
+ return node;
+
+ var clrType = objectNode.Type.GetClrType();
+ if (!clrType.IsAvaloniaStyledElement())
+ return node;
+
+ foreach (var child in objectNode.Children)
+ {
+ if (child is XamlAstXmlDirective directive &&
+ directive.Name == "Class" &&
+ directive.Namespace == XamlNamespaces.Xaml2006 &&
+ directive.Values[0] is XamlAstTextNode text)
+ {
+ if (_checkTypeValidity)
+ {
+ var existingType = _typeSystem.FindType(text.Text);
+ if (existingType == null)
+ {
+ _onTypeInvalid?.Invoke(text.Text);
+ return node;
+ }
+ }
+
+ var split = text.Text.Split('.');
+ var nameSpace = string.Join(".", split.Take(split.Length - 1));
+ var className = split.Last();
+
+ _resolvedClass = new ResolvedView(className, clrType, nameSpace, _xaml);
+ return node;
+ }
+ }
+
+ return node;
+ }
+
+ void IXamlAstVisitor.Push(IXamlAstNode node) { }
+
+ void IXamlAstVisitor.Pop() { }
+}
diff --git a/src/tools/Avalonia.Generators/Compiler/DataTemplateTransformer.cs b/src/tools/Avalonia.Generators/Compiler/DataTemplateTransformer.cs
new file mode 100644
index 00000000000..e7c60c79ad2
--- /dev/null
+++ b/src/tools/Avalonia.Generators/Compiler/DataTemplateTransformer.cs
@@ -0,0 +1,17 @@
+using XamlX.Ast;
+using XamlX.Transform;
+
+namespace Avalonia.Generators.Compiler;
+
+internal class DataTemplateTransformer : IXamlAstTransformer
+{
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (node is XamlAstObjectNode objectNode &&
+ objectNode.Type is XamlAstXmlTypeReference typeReference &&
+ (typeReference.Name == "DataTemplate" ||
+ typeReference.Name == "ControlTemplate"))
+ objectNode.Children.Clear();
+ return node;
+ }
+}
\ No newline at end of file
diff --git a/src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs b/src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs
new file mode 100644
index 00000000000..71f34d173c1
--- /dev/null
+++ b/src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using XamlX.Compiler;
+using XamlX.Emit;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Generators.Compiler;
+
+internal sealed class MiniCompiler : XamlCompiler