diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators.Tests/CommunityToolkit.Labs.Core.SourceGenerators.Tests/CommunityToolkit.Labs.Core.SourceGenerators.Tests.csproj b/Common/CommunityToolkit.Labs.Core.SourceGenerators.Tests/CommunityToolkit.Labs.Core.SourceGenerators.Tests/CommunityToolkit.Labs.Core.SourceGenerators.Tests.csproj
new file mode 100644
index 000000000..a28d7077c
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators.Tests/CommunityToolkit.Labs.Core.SourceGenerators.Tests/CommunityToolkit.Labs.Core.SourceGenerators.Tests.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net6.0
+ enable
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators.Tests/CommunityToolkit.Labs.Core.SourceGenerators.Tests/ToolkitSampleMetadataTests.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators.Tests/CommunityToolkit.Labs.Core.SourceGenerators.Tests/ToolkitSampleMetadataTests.cs
new file mode 100644
index 000000000..863123c37
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators.Tests/CommunityToolkit.Labs.Core.SourceGenerators.Tests/ToolkitSampleMetadataTests.cs
@@ -0,0 +1,356 @@
+using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+using CommunityToolkit.Labs.Core.SourceGenerators.Diagnostics;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators.Tests
+{
+ [TestClass]
+ public class ToolkitSampleMetadataTests
+ {
+ [TestMethod]
+ public void PaneOptionOnNonSample()
+ {
+ string source = @"
+ using System.ComponentModel;
+ using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+ namespace MyApp
+ {
+ [ToolkitSampleBoolOption(""BindToMe"", ""Toggle visibility"", false)]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {
+ }
+ }
+
+ namespace Windows.UI.Xaml.Controls
+ {
+ public class UserControl { }
+ }";
+
+ VerifyGeneratedDiagnostics(source, DiagnosticDescriptors.SamplePaneOptionAttributeOnNonSample.Id);
+ }
+
+ [DataRow("", DisplayName = "Empty string"), DataRow(" ", DisplayName = "Only whitespace"), DataRow("Test ", DisplayName = "Text with whitespace")]
+ [DataRow("_", DisplayName = "Underscore"), DataRow("$", DisplayName = "Dollar sign"), DataRow("%", DisplayName = "Percent symbol")]
+ [DataRow("class", DisplayName = "Reserved keyword 'class'"), DataRow("string", DisplayName = "Reserved keyword 'string'"), DataRow("sealed", DisplayName = "Reserved keyword 'sealed'"), DataRow("ref", DisplayName = "Reserved keyword 'ref'")]
+ [TestMethod]
+ public void PaneOptionWithBadName(string name)
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Labs.Core.SourceGenerators;
+ using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: """")]
+ [ToolkitSampleBoolOption(""{name}"", ""Toggle visibility"", false)]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {{
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ VerifyGeneratedDiagnostics(source, DiagnosticDescriptors.SamplePaneOptionWithBadName.Id);
+ }
+
+ [TestMethod]
+ public void PaneOptionWithConflictingPropertyName()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Labs.Core.SourceGenerators;
+ using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleBoolOption(""IsVisible"", ""Toggle x"", false)]
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: """")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {{
+ public string IsVisible {{ get; set; }}
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ VerifyGeneratedDiagnostics(source, DiagnosticDescriptors.SamplePaneOptionWithConflictingName.Id);
+ }
+
+ [TestMethod]
+ public void PaneOptionWithConflictingInheritedPropertyName()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Labs.Core.SourceGenerators;
+ using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleBoolOption(""IsVisible"", ""Toggle x"", false)]
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: """")]
+ public partial class Sample : Base
+ {{
+ }}
+
+ public class Base : Windows.UI.Xaml.Controls.UserControl
+ {{
+ public string IsVisible {{ get; set; }}
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ VerifyGeneratedDiagnostics(source, DiagnosticDescriptors.SamplePaneOptionWithConflictingName.Id);
+ }
+
+ [TestMethod]
+ public void PaneOptionWithDuplicateName()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Labs.Core.SourceGenerators;
+ using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleBoolOption(""test"", ""Toggle x"", false)]
+ [ToolkitSampleBoolOption(""test"", ""Toggle y"", false)]
+
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: """")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {{
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ VerifyGeneratedDiagnostics(source, DiagnosticDescriptors.SamplePaneOptionWithDuplicateName.Id);
+ }
+
+ [TestMethod]
+ public void PaneOptionWithDuplicateName_AllowedForMultiChoice()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Labs.Core.SourceGenerators;
+ using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleMultiChoiceOption(""TextFontFamily"", label: ""Segoe UI"", value: ""Segoe UI"", title: ""Font"")]
+ [ToolkitSampleMultiChoiceOption(""TextFontFamily"", label: ""Arial"", value: ""Arial"")]
+
+ [ToolkitSampleBoolOption(""test"", ""Toggle y"", false)]
+
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: """")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {{
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ VerifyGeneratedDiagnostics(source);
+ }
+
+ [TestMethod]
+ public void PaneOptionWithDuplicateName_AllowedBetweenSamples()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Labs.Core.SourceGenerators;
+ using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleBoolOption(""test"", ""Toggle y"", false)]
+
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: """")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {{
+ }}
+
+ [ToolkitSampleBoolOption(""test"", ""Toggle y"", false)]
+
+ [ToolkitSample(id: nameof(Sample2), ""Test Sample"", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: """")]
+ public partial class Sample2 : Windows.UI.Xaml.Controls.UserControl
+ {{
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ VerifyGeneratedDiagnostics(source);
+ }
+
+ [TestMethod]
+ public void PaneMultipleChoiceOptionWithMultipleTitles()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Labs.Core.SourceGenerators;
+ using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleMultiChoiceOption(""TextFontFamily"", label: ""Segoe UI"", value: ""Segoe UI"", title: ""Font"")]
+ [ToolkitSampleMultiChoiceOption(""TextFontFamily"", label: ""Arial"", value: ""Arial"", title: ""Other font"")]
+
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: """")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {{
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ VerifyGeneratedDiagnostics(source, DiagnosticDescriptors.SamplePaneMultiChoiceOptionWithMultipleTitles.Id);
+ }
+
+ [TestMethod]
+ public void SampleGeneratedOptionAttributeOnUnsupportedType()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Labs.Core.SourceGenerators;
+ using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleMultiChoiceOption(""TextFontFamily"", label: ""Segoe UI"", value: ""Segoe UI"", title: ""Font"")]
+ [ToolkitSampleMultiChoiceOption(""TextFontFamily"", label: ""Arial"", value: ""Arial"")]
+ [ToolkitSampleBoolOption(""Test"", ""Toggle visibility"", false)]
+ public partial class Sample
+ {{
+ }}
+ }}";
+
+ VerifyGeneratedDiagnostics(source, DiagnosticDescriptors.SampleGeneratedOptionAttributeOnUnsupportedType.Id, DiagnosticDescriptors.SamplePaneOptionAttributeOnNonSample.Id);
+ }
+
+ [TestMethod]
+ public void SampleAttributeOnUnsupportedType()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Labs.Core.SourceGenerators;
+ using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: """")]
+ public partial class Sample
+ {{
+ }}
+ }}";
+
+ VerifyGeneratedDiagnostics(source, DiagnosticDescriptors.SampleAttributeOnUnsupportedType.Id);
+ }
+
+ [TestMethod]
+ public void SampleOptionPaneAttributeOnUnsupportedType()
+ {
+ var source = $@"
+ using System.ComponentModel;
+ using CommunityToolkit.Labs.Core.SourceGenerators;
+ using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+ namespace MyApp
+ {{
+ [ToolkitSampleOptionsPane(sampleId: nameof(Sample))]
+ public partial class SampleOptionsPane
+ {{
+ }}
+
+ [ToolkitSample(id: nameof(Sample), ""Test Sample"", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: """")]
+ public partial class Sample : Windows.UI.Xaml.Controls.UserControl
+ {{
+ }}
+ }}
+
+ namespace Windows.UI.Xaml.Controls
+ {{
+ public class UserControl {{ }}
+ }}";
+
+ VerifyGeneratedDiagnostics(source, DiagnosticDescriptors.SampleOptionPaneAttributeOnUnsupportedType.Id);
+ }
+
+ ///
+ /// Verifies the output of a source generator.
+ ///
+ /// The generator type to use.
+ /// The input source to process.
+ /// The diagnostic ids to expect for the input source code.
+ private static void VerifyGeneratedDiagnostics(string source, params string[] diagnosticsIds)
+ where TGenerator : class, IIncrementalGenerator, new()
+ {
+ VerifyGeneratedDiagnostics(CSharpSyntaxTree.ParseText(source), diagnosticsIds);
+ }
+
+ ///
+ /// Verifies the output of a source generator.
+ ///
+ /// The generator type to use.
+ /// The input source tree to process.
+ /// The diagnostic ids to expect for the input source code.
+ private static void VerifyGeneratedDiagnostics(SyntaxTree syntaxTree, params string[] diagnosticsIds)
+ where TGenerator : class, IIncrementalGenerator, new()
+ {
+ var sampleAttributeType = typeof(ToolkitSampleAttribute);
+
+ var references =
+ from assembly in AppDomain.CurrentDomain.GetAssemblies()
+ where !assembly.IsDynamic
+ let reference = MetadataReference.CreateFromFile(assembly.Location)
+ select reference;
+
+ var compilation = CSharpCompilation.Create(
+ "original",
+ new[] { syntaxTree },
+ references,
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+
+ IIncrementalGenerator generator = new TGenerator();
+
+ GeneratorDriver driver = CSharpGeneratorDriver.Create(generator).WithUpdatedParseOptions((CSharpParseOptions)syntaxTree.Options);
+
+ _ = driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation outputCompilation, out ImmutableArray diagnostics);
+
+ HashSet resultingIds = diagnostics.Select(diagnostic => diagnostic.Id).ToHashSet();
+
+ Assert.IsTrue(resultingIds.SetEquals(diagnosticsIds), $"Expected one of [{string.Join(", ", diagnosticsIds)}] diagnostic Ids. Got [{string.Join(", ", resultingIds)}]");
+
+ GC.KeepAlive(sampleAttributeType);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators.XamlNamedPropertyRelay/CommunityToolkit.Labs.Core.SourceGenerators.XamlNamedPropertyRelay.csproj b/Common/CommunityToolkit.Labs.Core.SourceGenerators.XamlNamedPropertyRelay/CommunityToolkit.Labs.Core.SourceGenerators.XamlNamedPropertyRelay.csproj
new file mode 100644
index 000000000..9b3c8d237
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators.XamlNamedPropertyRelay/CommunityToolkit.Labs.Core.SourceGenerators.XamlNamedPropertyRelay.csproj
@@ -0,0 +1,14 @@
+
+
+
+ netstandard2.0
+ enable
+ nullable
+ 10.0
+
+
+
+
+
+
+
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators.XamlNamedPropertyRelay/XamlNamedPropertyRelayGenerator.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators.XamlNamedPropertyRelay/XamlNamedPropertyRelayGenerator.cs
new file mode 100644
index 000000000..1eddeb81c
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators.XamlNamedPropertyRelay/XamlNamedPropertyRelayGenerator.cs
@@ -0,0 +1,158 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators.XamlNamedPropertyRelay;
+
+///
+/// Generates code that provides access to XAML elements with x:Name from code-behind by wrapping an instance of a control, without the need to use x:FieldProvider="public" directly in markup.
+///
+[Generator]
+public class XamlNamedPropertyRelayGenerator : IIncrementalGenerator
+{
+ private readonly HashSet _handledConstructors = new(SymbolEqualityComparer.Default);
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ // UWP generates fields for x:Name.
+ var fieldSymbols = context.SyntaxProvider
+ .CreateSyntaxProvider(
+ static (node, _) => node is FieldDeclarationSyntax,
+ static (context, _) => ((FieldDeclarationSyntax)context.Node).Declaration.Variables.Select(v => (IFieldSymbol?)context.SemanticModel.GetDeclaredSymbol(v)))
+ .SelectMany(static (item, _) => item)
+ .Where(IsDeclaredXamlXNameProperty);
+
+ // Uno generates private properties for x:Name.
+ var propertySymbols = context.SyntaxProvider
+ .CreateSyntaxProvider(
+ static (node, _) => node is MemberDeclarationSyntax,
+ static (context, _) => context.SemanticModel.GetDeclaredSymbol((MemberDeclarationSyntax)context.Node) as IPropertySymbol)
+ .Where(IsDeclaredXamlXNameProperty);
+
+ context.RegisterSourceOutput(fieldSymbols, (ctx, sym) => GenerateNamedPropertyRelay(ctx, sym, sym?.Type, _handledConstructors));
+ context.RegisterSourceOutput(propertySymbols, (ctx, sym) => GenerateNamedPropertyRelay(ctx, sym, sym?.Type, _handledConstructors));
+ }
+
+ private static void GenerateNamedPropertyRelay(SourceProductionContext context, ISymbol? symbol, ITypeSymbol? type, HashSet handledConstructors)
+ {
+ if (symbol?.ContainingType is null || type is null)
+ return;
+
+ GenerateConstructor(context, symbol, handledConstructors);
+
+ var source = $@"namespace {symbol.ContainingType.ContainingNamespace}
+{{
+ partial class {symbol.ContainingType.Name}
+ {{
+ ///
+ /// Provides the same functionality as using <SomeElement x:FieldProvider=""public"" x:Name=""someName"">
+ /// on an element in XAML, without the need for the extra x:FieldProvider markup.
+ ///
+ public partial record XamlNamedPropertyRelay
+ {{
+ public {type} {symbol.Name} => _{symbol.ContainingType.Name}.{symbol.Name};
+ }}
+ }}
+}}
+";
+
+ context.AddSource($"{symbol}.g", source);
+ }
+
+ private static void GenerateConstructor(SourceProductionContext context, ISymbol? symbol, HashSet handledConstructors)
+ {
+ if (symbol?.ContainingType is null)
+ return;
+
+ if (!handledConstructors.Add(symbol.ContainingType))
+ return;
+
+ var source = $@"#nullable enable
+namespace {symbol.ContainingType.ContainingNamespace}
+{{
+ partial class {symbol.ContainingType.Name}
+ {{
+ ///
+ /// Provides the same functionality as using <SomeElement x:FieldProvider=""public"" x:Name=""someName"">
+ /// on an element in XAML, without the need for the extra x:FieldProvider markup.
+ ///
+ public partial record XamlNamedPropertyRelay
+ {{
+ private readonly {symbol.ContainingType} _{symbol.ContainingType.Name};
+
+ public XamlNamedPropertyRelay({symbol.ContainingType} {symbol.ContainingType.Name.ToLower()})
+ {{
+ _{symbol.ContainingType.Name} = {symbol.ContainingType.Name.ToLower()};
+ }}
+ }}
+ }}
+}}
+";
+
+ context.AddSource($"{symbol.ContainingType}.ctor.g", source);
+ }
+
+ ///
+ /// Checks if a symbol's is or inherits from a type representing a XAML framework, and that the is a generated x:Name.
+ ///
+ /// if the is or inherits from a type representing a XAML framework.
+ public static bool IsDeclaredXamlXNameProperty(T? symbol)
+ where T : ISymbol
+ {
+ if (symbol is null)
+ return false;
+
+ // In UWP, Page inherits UserControl
+ // In Uno, Page doesn't appear to inherit UserControl.
+ var validSimpleTypeNames = new[] { "UserControl", "Page" };
+
+ // UWP / Uno / WinUI 3 namespaces.
+ var validNamespaceRoots = new[] { "Microsoft", "Windows" };
+
+ // Recursively crawl the base types until either UserControl or Page is found.
+ var validInheritedSymbol = CrawlBy(
+ symbol.ContainingType,
+ x => x?.BaseType,
+ baseType => validNamespaceRoots.Any(x => $"{baseType}".StartsWith(x)) &&
+ $"{baseType}".Contains(".UI.Xaml.Controls.") &&
+ validSimpleTypeNames.Any(x => $"{baseType}".EndsWith(x)));
+
+ var containerIsPublic = symbol.ContainingType?.DeclaredAccessibility == Accessibility.Public;
+ var isPrivate = symbol.DeclaredAccessibility == Accessibility.Private;
+ var typeIsAccessible = symbol is IFieldSymbol field && field.Type.DeclaredAccessibility == Accessibility.Public || symbol is IPropertySymbol prop && prop.Type.DeclaredAccessibility == Accessibility.Public;
+
+ return validInheritedSymbol != default && isPrivate && containerIsPublic && typeIsAccessible && !symbol.IsStatic;
+ }
+
+ ///
+ /// Crawls an object tree for nested properties of the same type and returns the first instance that matches the .
+ ///
+ ///
+ /// Does not filter against or return the object.
+ ///
+ public static T? CrawlBy(T? root, Func selectPredicate, Func filterPredicate)
+ {
+ crawl:
+ var current = selectPredicate(root);
+
+ if (filterPredicate(current))
+ {
+ return current;
+ }
+
+ if (current is null)
+ {
+ return default;
+ }
+
+ root = current;
+ goto crawl;
+ }
+}
+
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/MultiChoiceOption.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/MultiChoiceOption.cs
new file mode 100644
index 000000000..c706ad261
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/MultiChoiceOption.cs
@@ -0,0 +1,21 @@
+using CommunityToolkit.Labs.Core.SourceGenerators.Metadata;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+///
+/// Holds data for a multiple choice option.
+/// Primarily used by .
+///
+/// A label shown to the user for this option.
+/// The value passed to XAML when this option is selected.
+public record MultiChoiceOption(string Label, string Value)
+{
+ ///
+ /// The string has been overriden to display the label only,
+ /// especially so the data can be easily displayed in XAML without a custom template, converter or code behind.
+ ///
+ public override string ToString()
+ {
+ return Label;
+ }
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleAttribute.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleAttribute.cs
new file mode 100644
index 000000000..91e9853a8
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleAttribute.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Diagnostics;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+///
+/// Registers a control as a toolkit sample using the provided data.
+///
+[Conditional("COMMUNITYTOOLKIT_KEEP_SAMPLE_ATTRIBUTES")]
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+public sealed class ToolkitSampleAttribute : Attribute
+{
+ ///
+ /// Creates a new instance of .
+ ///
+ /// A unique identifier for this sample, used by the sample system.
+ /// The display name for this sample page.
+ /// The category that this sample belongs to.
+ /// A more specific category within the provided .
+ /// A short description of this sample.
+ public ToolkitSampleAttribute(string id, string displayName, ToolkitSampleCategory category, ToolkitSampleSubcategory subcategory, string description)
+ {
+ Id = id;
+ DisplayName = displayName;
+ Description = description;
+ Category = category;
+ Subcategory = subcategory;
+ }
+
+ ///
+ /// A unique identifier for this sample, used by the sample system.
+ ///
+ public string Id { get; }
+
+ ///
+ /// The display name for this sample page.
+ ///
+ public string DisplayName { get; }
+
+ ///
+ /// The category that this sample belongs to.
+ ///
+ public ToolkitSampleCategory Category { get; }
+
+ ///
+ /// A more specific category within the provided .
+ ///
+ public ToolkitSampleSubcategory Subcategory { get; }
+
+ ///
+ /// A short description of this sample.
+ ///
+ public string Description { get; }
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleBoolOptionAttribute.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleBoolOptionAttribute.cs
new file mode 100644
index 000000000..3eea97edd
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleBoolOptionAttribute.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Diagnostics;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+///
+/// Represents a boolean sample option that the user can manipulate and the XAML can bind to.
+///
+///
+/// Using this attribute will automatically generate an -enabled property
+/// that you can bind to in XAML, and displays an options pane alonside your sample which allows the user to manipulate the property.
+///
+[Conditional("COMMUNITYTOOLKIT_KEEP_SAMPLE_ATTRIBUTES")]
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+public sealed class ToolkitSampleBoolOptionAttribute : ToolkitSampleOptionBaseAttribute
+{
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The name of the generated property, which you can bind to in XAML.
+ /// The initial value for the bound property.
+ /// A title to display on top of this option.
+ public ToolkitSampleBoolOptionAttribute(string bindingName, string label, bool defaultState, string? title = null)
+ : base(bindingName, defaultState, title)
+ {
+ Label = label;
+ }
+
+ ///
+ /// The source generator-friendly type name used for casting.
+ ///
+ internal override string TypeName { get; } = "bool";
+
+ ///
+ /// A label to display along the boolean option.
+ ///
+ public string Label { get; }
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleMultiChoiceOptionAttribute.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleMultiChoiceOptionAttribute.cs
new file mode 100644
index 000000000..9bc32037f
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleMultiChoiceOptionAttribute.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Diagnostics;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+///
+/// Represents a boolean sample option.
+///
+///
+/// Using this attribute will automatically generate an -enabled property
+/// that you can bind to in XAML, and displays an options pane alonside your sample which allows the user to manipulate the property.
+///
+///
+[Conditional("COMMUNITYTOOLKIT_KEEP_SAMPLE_ATTRIBUTES")]
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+public sealed class ToolkitSampleMultiChoiceOptionAttribute : ToolkitSampleOptionBaseAttribute
+{
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The name of the generated property, which you can bind to in XAML.
+ /// The displayed text shown beside this option.
+ /// The value to provide in XAML when this item is selected.
+ /// A title to display on top of this option.
+ public ToolkitSampleMultiChoiceOptionAttribute(string bindingName, string label, string value, string? title = null)
+ : base(bindingName, null, title)
+ {
+ Label = label;
+ Value = value;
+ }
+
+ ///
+ /// The source generator-friendly type name used for casting.
+ ///
+ internal override string TypeName { get; } = "string";
+
+ ///
+ /// The displayed text shown beside this option.
+ ///
+ public string Label { get; private set; }
+
+ ///
+ /// The value to provide in XAML when this item is selected.
+ ///
+ public string Value { get; private set; }
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleOptionBaseAttribute.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleOptionBaseAttribute.cs
new file mode 100644
index 000000000..ab93643f3
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleOptionBaseAttribute.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Diagnostics;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+///
+/// Represents an abstraction of a sample option that the user can manipulate and the XAML can bind to.
+///
+[Conditional("COMMUNITYTOOLKIT_KEEP_SAMPLE_ATTRIBUTES")]
+public abstract class ToolkitSampleOptionBaseAttribute : Attribute
+{
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The name of the generated property, which you can bind to in XAML.
+ /// The initial value for the bound property.
+ /// A title to display on top of this option.
+ public ToolkitSampleOptionBaseAttribute(string bindingName, object? defaultState, string? title = null)
+ {
+ Title = title;
+ Name = bindingName;
+ DefaultState = defaultState;
+ }
+
+ ///
+ /// A name that you can bind to in your XAML.
+ ///
+ public string Name { get; }
+
+ ///
+ /// The default state.
+ ///
+ public object? DefaultState { get; }
+
+ ///
+ /// A title to display on top of the option.
+ ///
+ public string? Title { get; }
+
+ ///
+ /// The source generator-friendly type name used for casting.
+ ///
+ internal abstract string TypeName { get; }
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleOptionsPaneAttribute.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleOptionsPaneAttribute.cs
new file mode 100644
index 000000000..f37164642
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Attributes/ToolkitSampleOptionsPaneAttribute.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Diagnostics;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+///
+/// Registers a control as the options panel for a toolkit sample.
+///
+[AttributeUsage(AttributeTargets.Class)]
+[Conditional("COMMUNITYTOOLKIT_KEEP_SAMPLE_ATTRIBUTES")]
+public sealed class ToolkitSampleOptionsPaneAttribute : Attribute
+{
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The unique identifier of a toolkit sample, provided via .
+ public ToolkitSampleOptionsPaneAttribute(string sampleId)
+ {
+ SampleId = sampleId;
+ }
+
+ ///
+ /// The unique identifier of a toolkit sample, provided via .
+ ///
+ public string SampleId { get; }
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/CommunityToolkit.Labs.Core.SourceGenerators.csproj b/Common/CommunityToolkit.Labs.Core.SourceGenerators/CommunityToolkit.Labs.Core.SourceGenerators.csproj
new file mode 100644
index 000000000..9b3c8d237
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/CommunityToolkit.Labs.Core.SourceGenerators.csproj
@@ -0,0 +1,14 @@
+
+
+
+ netstandard2.0
+ enable
+ nullable
+ 10.0
+
+
+
+
+
+
+
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs
new file mode 100644
index 000000000..a0e240b8f
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs
@@ -0,0 +1,149 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators.Diagnostics
+{
+ ///
+ /// A container for all instances for errors reported by analyzers in this project.
+ ///
+ public static class DiagnosticDescriptors
+ {
+ ///
+ /// Gets a indicating a derived used on a member that isn't a toolkit sample.
+ ///
+ /// Format: "Cannot generate sample pane options for type {0} as it does not use ToolkitSampleAttribute".
+ ///
+ ///
+ public static readonly DiagnosticDescriptor SamplePaneOptionAttributeOnNonSample = new(
+ id: "TKSMPL0001",
+ title: $"Invalid sample option delaration",
+ messageFormat: $"Cannot generate sample pane options for type {{0}} as it does not use {nameof(Attributes.ToolkitSampleAttribute)}",
+ category: typeof(ToolkitSampleMetadataGenerator).FullName,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: $"Cannot generate sample pane options for a type which does not use {nameof(Attributes.ToolkitSampleAttribute)}.");
+
+ ///
+ /// Gets a indicating a derived with an empty or invalid name.
+ ///
+ /// Format: "Cannot generate sample pane options for type {0} as it contains an empty or invalid name.".
+ ///
+ ///
+ public static readonly DiagnosticDescriptor SamplePaneOptionWithBadName = new(
+ id: "TKSMPL0002",
+ title: $"Invalid sample option delaration",
+ messageFormat: $"Cannot generate sample pane options for type {{0}} as the provided name is empty or invalid",
+ category: typeof(ToolkitSampleMetadataGenerator).FullName,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: $"Cannot generate sample pane options when the provided name is empty or invalid.");
+
+ ///
+ /// Gets a indicating a with a that doesn't have a corresponding .
+ ///
+ /// Format: "Cannot link sample options pane to type {0} as the provided sample ID does not match any known {nameof(Attributes.ToolkitSampleAttribute)}".
+ ///
+ ///
+ public static readonly DiagnosticDescriptor OptionsPaneAttributeWithMissingOrInvalidSampleId = new(
+ id: "TKSMPL0003",
+ title: $"Missing or invalid sample Id",
+ messageFormat: $"Cannot link sample options pane to type {{0}} as the provided sample ID does not match any known {nameof(Attributes.ToolkitSampleAttribute)}",
+ category: typeof(ToolkitSampleMetadataGenerator).FullName,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: $"Cannot link sample options pane to a provided sample ID that does not match any known {nameof(Attributes.ToolkitSampleAttribute)}.");
+
+ ///
+ /// Gets a indicating a derived that contains a name which is already in use by another sample option.
+ ///
+ /// Format: "Cannot generate sample pane options with name {0} as the provided name is already in use by another sample option".
+ ///
+ ///
+ public static readonly DiagnosticDescriptor SamplePaneOptionWithDuplicateName = new(
+ id: "TKSMPL0004",
+ title: $"Duplicate sample option name",
+ messageFormat: $"Cannot generate sample pane option with name {{0}} as the provided name is already in use by another sample option",
+ category: typeof(ToolkitSampleMetadataGenerator).FullName,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: $"Cannot generate sample pane option when the provided name is used by another sample option.");
+
+ ///
+ /// Gets a indicating a derived that contains a name which is already defined as a member in the attached class.
+ ///
+ /// Format: "Cannot generate sample pane options with name {0} the provided name is already defined as a member in the attached class".
+ ///
+ ///
+ public static readonly DiagnosticDescriptor SamplePaneOptionWithConflictingName = new(
+ id: "TKSMPL0005",
+ title: $"Conflicting sample option name",
+ messageFormat: $"Cannot generate sample pane option with name {{0}} as the provided name is already defined as a member in the attached class",
+ category: typeof(ToolkitSampleMetadataGenerator).FullName,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: $"Cannot generate sample pane option when the provided name is already defined as a member in the attached class.");
+
+ ///
+ /// Gets a indicating a that contains a title which is already defined in another .
+ ///
+ /// Format: "Cannot generate multiple choice sample pane option with title {{0}} as the title was defined multiple times".
+ ///
+ ///
+ public static readonly DiagnosticDescriptor SamplePaneMultiChoiceOptionWithMultipleTitles = new(
+ id: "TKSMPL0006",
+ title: $"Conflicting sample option name",
+ messageFormat: $"Cannot generate multiple choice sample pane option with title {{0}} as the title was defined multiple times",
+ category: typeof(ToolkitSampleMetadataGenerator).FullName,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: $"Cannot generate multiple choice sample pane option as the title was defined multiple times.");
+
+ ///
+ /// Gets a indicating a that was used on an unsupported type.
+ ///
+ /// Format: "Cannot generate sample metadata as the attribute was used on an unsupported type.".
+ ///
+ ///
+ public static readonly DiagnosticDescriptor SampleAttributeOnUnsupportedType = new(
+ id: "TKSMPL0007",
+ title: $"ToolkitSampleAttribute declared on an invalid type",
+ messageFormat: $"Cannot generate sample metadata as the attribute was used on an unsupported type",
+ category: typeof(ToolkitSampleMetadataGenerator).FullName,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: $"Cannot generate sample metadata as the attribute was used on an unsupported type.");
+
+ ///
+ /// Gets a indicating a that was used on an unsupported type.
+ ///
+ /// Format: "Cannot generate options pane metadata as the attribute was used on an unsupported type.".
+ ///
+ ///
+ public static readonly DiagnosticDescriptor SampleOptionPaneAttributeOnUnsupportedType = new(
+ id: "TKSMPL0008",
+ title: $"Toolkit sample options pane declared on an invalid type",
+ messageFormat: $"Cannot generate options pane metadata as the attribute was used on an unsupported type",
+ category: typeof(ToolkitSampleMetadataGenerator).FullName,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: $"Cannot generate options pane metadata as the attribute was used on an unsupported type.");
+
+ ///
+ /// Gets a indicating a derived that was used on an unsupported type.
+ ///
+ /// Format: "Cannot generate sample option metadata as the attribute was used on an unsupported type.".
+ ///
+ ///
+ public static readonly DiagnosticDescriptor SampleGeneratedOptionAttributeOnUnsupportedType = new(
+ id: "TKSMPL0009",
+ title: $"Toolkit sample option declared on an invalid type",
+ messageFormat: $"Cannot generate sample option metadata as the attribute was used on an unsupported type",
+ category: typeof(ToolkitSampleMetadataGenerator).FullName,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: $"Cannot generate sample option metadata as the attribute was used on an unsupported type.");
+ }
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/GeneratorExtensions.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/GeneratorExtensions.cs
new file mode 100644
index 000000000..efb705c4f
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/GeneratorExtensions.cs
@@ -0,0 +1,122 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis;
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators
+{
+ public static class GeneratorExtensions
+ {
+ ///
+ /// Crawls a namespace and all child namespaces for all contained types.
+ ///
+ /// A flattened enumerable of s.
+ public static IEnumerable CrawlForAllNamedTypes(this INamespaceSymbol namespaceSymbol)
+ {
+ foreach (var member in namespaceSymbol.GetMembers())
+ {
+ if (member is INamespaceSymbol nestedNamespace)
+ {
+ foreach (var item in CrawlForAllNamedTypes(nestedNamespace))
+ yield return item;
+ }
+
+ if (member is INamedTypeSymbol typeSymbol)
+ yield return typeSymbol;
+ }
+ }
+
+ ///
+ /// Crawls an object tree for nested properties of the same type and returns the first instance that matches the .
+ ///
+ ///
+ /// Does not filter against or return the object.
+ ///
+ public static T? CrawlBy(this T? root, Func selectPredicate, Func filterPredicate)
+ {
+ crawl:
+ var current = selectPredicate(root);
+
+ if (filterPredicate(current))
+ {
+ return current;
+ }
+
+ if (current is null)
+ {
+ return default;
+ }
+
+ root = current;
+ goto crawl;
+ }
+
+ ///
+ /// Reconstructs an attribute instance as the given type.
+ ///
+ /// The attribute type to create.
+ /// The attribute data used to construct the instance of
+ public static T ReconstructAs(this AttributeData attributeData)
+ {
+ // Reconstructing the attribute instance provides some safety against changes to the attribute's constructor signature.
+ var attributeArgs = attributeData.ConstructorArguments.Select(PrepareParameterTypeForActivator).ToArray();
+ return (T)Activator.CreateInstance(typeof(T), attributeArgs);
+ }
+
+
+ ///
+ /// Attempts to reconstruct an attribute instance as the given type, returning null if and are mismatched.
+ ///
+ /// The attribute type to create.
+ /// The attribute data used to construct the instance of
+ public static T? TryReconstructAs(this AttributeData attributeData)
+ where T : Attribute
+ {
+ var attributeMatchesType = attributeData.AttributeClass?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == $"global::{typeof(T).FullName}";
+
+ if (attributeMatchesType)
+ return attributeData.ReconstructAs();
+
+ return null;
+ }
+
+ ///
+ /// Checks whether or not a given type symbol has a specified full name.
+ ///
+ /// The input instance to check.
+ /// The full name to check.
+ /// Whether has a full name equals to .
+ public static bool HasFullyQualifiedName(this ISymbol symbol, string name)
+ {
+ return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == name;
+ }
+
+ ///
+ /// Performs any data transforms needed for using as a parameter in .
+ ///
+ /// The 's was null.
+ public static object? PrepareParameterTypeForActivator(this TypedConstant parameterTypedConstant)
+ {
+ if (parameterTypedConstant.Type is null)
+ throw new ArgumentNullException(nameof(parameterTypedConstant.Type));
+
+ // Types prefixed with global:: do not work with Type.GetType and must be stripped away.
+ var assemblyQualifiedName = parameterTypedConstant.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat).Replace("global::", "");
+
+ var argType = Type.GetType(assemblyQualifiedName);
+
+ // Enums arrive as the underlying integer type, which doesn't work as a param for Activator.CreateInstance()
+ if (argType != null && parameterTypedConstant.Kind == TypedConstantKind.Enum)
+ return Enum.Parse(argType, parameterTypedConstant.Value?.ToString());
+
+ return parameterTypedConstant.Value;
+ }
+ }
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/IGeneratedToolkitSampleOptionViewModel.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/IGeneratedToolkitSampleOptionViewModel.cs
new file mode 100644
index 000000000..9212f958a
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/IGeneratedToolkitSampleOptionViewModel.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.ComponentModel;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators.Metadata
+{
+ ///
+ /// A common interface for all generated toolkit sample options.
+ /// Implementations of this interface are updated from the sample pane UI, and referenced by the generated property.
+ ///
+ ///
+ /// Must implement to notify when the user changes a value in the sample pane UI.
+ ///
+ /// However, the must be emitted as the changed property name when updates, so the
+ /// propogated IPNC event can notify the sample control of the change.
+ ///
+ public interface IGeneratedToolkitSampleOptionViewModel : INotifyPropertyChanged
+ {
+ ///
+ /// The current value. Can be updated by the user via the sample pane UI.
+ ///
+ /// A generated property's getter and setter directly references this value, making it available to bind to.
+ ///
+ public object? Value { get; set; }
+
+ ///
+ /// A unique identifier name for this option.
+ ///
+ ///
+ /// Used by the sample system to match up to the original and the control that declared it.
+ ///
+ public string Name { get; }
+ }
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/IToolkitSampleGeneratedOptionPropertyContainer.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/IToolkitSampleGeneratedOptionPropertyContainer.cs
new file mode 100644
index 000000000..ac7c651ff
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/IToolkitSampleGeneratedOptionPropertyContainer.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators.Metadata
+{
+ ///
+ /// Implementors of this class contain one or more source-generated properties
+ /// which are bound to in the XAML of a toolkit sample
+ /// and manipulated from a data-generated options pane.
+ ///
+ public interface IToolkitSampleGeneratedOptionPropertyContainer
+ {
+ ///
+ /// Holds a reference to all generated ViewModels that act
+ /// as a proxy between the current actual value and the
+ /// generated properties which consume them.
+ ///
+ public IEnumerable? GeneratedPropertyMetadata { get; set; }
+ }
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/ToolkitSampleBoolOptionMetadataViewModel.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/ToolkitSampleBoolOptionMetadataViewModel.cs
new file mode 100644
index 000000000..a2117fe6d
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/ToolkitSampleBoolOptionMetadataViewModel.cs
@@ -0,0 +1,98 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+using System.ComponentModel;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators.Metadata
+{
+ ///
+ /// An INPC-enabled metadata container for data defined in an .
+ ///
+ ///
+ /// Instances of these are generated by the and
+ /// provided to the app alongside the sample registration.
+ ///
+ public class ToolkitSampleBoolOptionMetadataViewModel : IGeneratedToolkitSampleOptionViewModel
+ {
+ private string _label;
+ private string? _title;
+ private object _value;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ public ToolkitSampleBoolOptionMetadataViewModel(string id, string label, bool defaultState, string? title = null)
+ {
+ Name = id;
+ _title = title;
+ _label = label;
+ _value = defaultState;
+ }
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ ///
+ /// A unique identifier for this option.
+ ///
+ ///
+ /// Used by the sample system to match up to the original and the control that declared it.
+ ///
+ public string Name { get; }
+
+ ///
+ /// The current boolean value.
+ ///
+ ///
+ /// Provided to accomodate binding to a property that is a non-nullable .
+ ///
+ public bool BoolValue
+ {
+ get => (bool)_value;
+ set
+ {
+ _value = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Name));
+ }
+ }
+
+ ///
+ public object? Value
+ {
+ get => BoolValue;
+ set
+ {
+ BoolValue = (bool)(value ?? false);
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Name));
+ }
+ }
+
+ ///
+ /// A label to display along the boolean option.
+ ///
+ public string Label
+ {
+ get => _label;
+ set
+ {
+ _label = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Label)));
+ }
+ }
+
+ ///
+ /// A title to display on top of the boolean option.
+ ///
+ public string? Title
+ {
+ get => _title;
+ set
+ {
+ _title = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title)));
+ }
+ }
+ }
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/ToolkitSampleMetadata.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/ToolkitSampleMetadata.cs
new file mode 100644
index 000000000..c1d787a5a
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/ToolkitSampleMetadata.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators.Metadata;
+
+///
+/// Contains the metadata needed to identify and display a toolkit sample.
+///
+/// The category that this sample belongs to.
+/// A more specific category within the provided .
+/// The display name for this sample page.
+/// The description for this sample page.
+/// A type that can be used to construct an instance of the sample control.
+///
+/// The control type for the sample page's options pane.
+/// Constructor should have exactly one parameter that can be assigned to the control type ().
+///
+/// The generated sample options that were declared alongside this sample, if any.
+public sealed record ToolkitSampleMetadata(
+ ToolkitSampleCategory Category,
+ ToolkitSampleSubcategory Subcategory,
+ string DisplayName,
+ string Description,
+ Type SampleControlType,
+ Type? SampleOptionsPaneType = null,
+ IEnumerable? GeneratedSampleOptions = null);
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/ToolkitSampleMultiChoiceOptionMetadataViewModel.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/ToolkitSampleMultiChoiceOptionMetadataViewModel.cs
new file mode 100644
index 000000000..fc9f4d1e1
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/Metadata/ToolkitSampleMultiChoiceOptionMetadataViewModel.cs
@@ -0,0 +1,77 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+using System.ComponentModel;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators.Metadata
+{
+ ///
+ /// An INPC-enabled metadata container for data defined in an .
+ ///
+ public class ToolkitSampleMultiChoiceOptionMetadataViewModel : IGeneratedToolkitSampleOptionViewModel
+ {
+ private string? _title;
+ private object? _value;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ public ToolkitSampleMultiChoiceOptionMetadataViewModel(string name, MultiChoiceOption[] options, string? title = null)
+ {
+ Name = name;
+ Options = options;
+ _title = title;
+ _value = options[0].Value;
+ }
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ ///
+ /// A unique identifier for this option.
+ ///
+ ///
+ /// Used by the sample system to match up to the original and the control that declared it.
+ ///
+ public string Name { get; }
+
+ ///
+ /// The available options presented to the user.
+ ///
+ public MultiChoiceOption[] Options { get; }
+
+ ///
+ /// The current boolean value.
+ ///
+ public object? Value
+ {
+ get => _value;
+ set
+ {
+ // XAML converting a null value isn't supported for all types.
+ if (value is null)
+ return;
+
+ if (value is MultiChoiceOption op)
+ _value = op.Value;
+
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Name));
+ }
+ }
+
+ ///
+ /// A label to display along the boolean option.
+ ///
+ public string? Title
+ {
+ get => _title;
+ set
+ {
+ _title = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title)));
+ }
+ }
+ }
+}
diff --git a/Common/CommunityToolkit.Labs.Core/System.Runtime.CompilerServices/IsExternalInit.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/System.Runtime.CompilerServices/IsExternalInit.cs
similarity index 100%
rename from Common/CommunityToolkit.Labs.Core/System.Runtime.CompilerServices/IsExternalInit.cs
rename to Common/CommunityToolkit.Labs.Core.SourceGenerators/System.Runtime.CompilerServices/IsExternalInit.cs
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleCategory.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleCategory.cs
new file mode 100644
index 000000000..072f99041
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleCategory.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators;
+
+///
+/// The various categories each sample is organized into.
+///
+public enum ToolkitSampleCategory : byte
+{
+ ///
+ /// Various UI controls that the user sees and interacts with.
+ ///
+ Controls,
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleMetadataGenerator.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleMetadataGenerator.cs
new file mode 100644
index 000000000..22339b286
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleMetadataGenerator.cs
@@ -0,0 +1,298 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+using CommunityToolkit.Labs.Core.SourceGenerators.Diagnostics;
+using CommunityToolkit.Labs.Core.SourceGenerators.Metadata;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators;
+
+///
+/// Crawls all referenced projects for s and generates a static method that returns metadata for each one found.
+///
+[Generator]
+public partial class ToolkitSampleMetadataGenerator : IIncrementalGenerator
+{
+ ///
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var classes = context.SyntaxProvider
+ .CreateSyntaxProvider(
+ static (s, _) => s is ClassDeclarationSyntax c && c.AttributeLists.Count > 0,
+ static (ctx, _) => ctx.SemanticModel.GetDeclaredSymbol(ctx.Node))
+ .Where(static m => m is not null)
+ .Select(static (x, _) => x!);
+
+ var referencedTypes = context.CompilationProvider
+ .SelectMany((x, _) => x.SourceModule.ReferencedAssemblySymbols)
+ .SelectMany((asm, _) => asm.GlobalNamespace.CrawlForAllNamedTypes())
+ .Where(x => x.TypeKind == TypeKind.Class && x.CanBeReferencedByName)
+ .Select((x, _) => (ISymbol)x);
+
+ Execute(classes);
+ Execute(referencedTypes);
+
+ void Execute(IncrementalValuesProvider types)
+ {
+ // Get all attributes + the original type symbol.
+ var allAttributeData = types.SelectMany(static (sym, _) => sym.GetAttributes().Select(x => (sym, x)));
+
+ // Find and reconstruct generated pane option attributes + the original type symbol.
+ var generatedPaneOptions = allAttributeData.Select(static (x, _) =>
+ {
+ if (x.Item2.TryReconstructAs() is ToolkitSampleBoolOptionAttribute boolOptionAttribute)
+ return (x.Item1, (ToolkitSampleOptionBaseAttribute)boolOptionAttribute);
+
+ if (x.Item2.TryReconstructAs() is ToolkitSampleMultiChoiceOptionAttribute multiChoiceOptionAttribute)
+ return (x.Item1, (ToolkitSampleOptionBaseAttribute)multiChoiceOptionAttribute);
+
+ return default;
+ }).Collect();
+
+ // Find and reconstruct sample attributes
+ var toolkitSampleAttributeData = allAttributeData.Select(static (data, _) =>
+ {
+ if (data.Item2.TryReconstructAs() is ToolkitSampleAttribute sampleAttribute)
+ return (Attribute: sampleAttribute, AttachedQualifiedTypeName: data.Item1.ToString(), Symbol: data.Item1);
+
+ return default;
+ }).Collect();
+
+ var optionsPaneAttributes = allAttributeData
+ .Select(static (x, _) => (x.Item2.TryReconstructAs(), x.Item1))
+ .Where(static x => x.Item1 is not null)
+ .Collect();
+
+ var all = optionsPaneAttributes
+ .Combine(toolkitSampleAttributeData)
+ .Combine(generatedPaneOptions);
+
+ context.RegisterSourceOutput(all, (ctx, data) =>
+ {
+ var toolkitSampleAttributeData = data.Left.Right.Where(x => x != default).Distinct();
+ var optionsPaneAttribute = data.Left.Left.Where(x => x != default).Distinct();
+ var generatedOptionPropertyData = data.Right.Where(x => x != default).Distinct();
+
+ ReportDiagnostics(ctx, toolkitSampleAttributeData, optionsPaneAttribute, generatedOptionPropertyData);
+
+ // Reconstruct sample metadata from attributes
+ var sampleMetadata = toolkitSampleAttributeData
+ .Select(sample =>
+ new ToolkitSampleRecord(
+ sample.Attribute.Category,
+ sample.Attribute.Subcategory,
+ sample.Attribute.DisplayName,
+ sample.Attribute.Description,
+ sample.AttachedQualifiedTypeName,
+ optionsPaneAttribute.FirstOrDefault(x => x.Item1?.SampleId == sample.Attribute.Id).Item2?.ToString(),
+ generatedOptionPropertyData.Where(x => ReferenceEquals(x.Item1, sample.Symbol)).Select(x => x.Item2)
+ )
+ );
+
+ if (!sampleMetadata.Any())
+ return;
+
+ // Build source string
+ var source = BuildRegistrationCallsFromMetadata(sampleMetadata);
+ ctx.AddSource($"ToolkitSampleRegistry.g.cs", source);
+ });
+ }
+ }
+
+ private static void ReportDiagnostics(SourceProductionContext ctx,
+ IEnumerable<(ToolkitSampleAttribute Attribute, string AttachedQualifiedTypeName, ISymbol Symbol)> toolkitSampleAttributeData,
+ IEnumerable<(ToolkitSampleOptionsPaneAttribute?, ISymbol)> optionsPaneAttribute,
+ IEnumerable<(ISymbol, ToolkitSampleOptionBaseAttribute)> generatedOptionPropertyData)
+ {
+ ReportDiagnosticsForInvalidAttributeUsage(ctx, toolkitSampleAttributeData, optionsPaneAttribute, generatedOptionPropertyData);
+ ReportDiagnosticsForLinkedOptionsPane(ctx, toolkitSampleAttributeData, optionsPaneAttribute);
+ ReportDiagnosticsGeneratedOptionsPane(ctx, toolkitSampleAttributeData, generatedOptionPropertyData);
+ }
+
+ private static void ReportDiagnosticsForInvalidAttributeUsage(SourceProductionContext ctx,
+ IEnumerable<(ToolkitSampleAttribute Attribute, string AttachedQualifiedTypeName, ISymbol Symbol)> toolkitSampleAttributeData,
+ IEnumerable<(ToolkitSampleOptionsPaneAttribute?, ISymbol)> optionsPaneAttribute,
+ IEnumerable<(ISymbol, ToolkitSampleOptionBaseAttribute)> generatedOptionPropertyData)
+ {
+ var toolkitAttributesOnUnsupportedType = toolkitSampleAttributeData.Where(x => x.Symbol is not INamedTypeSymbol namedSym || !IsValidXamlControl(namedSym));
+ var optionsAttributeOnUnsupportedType = optionsPaneAttribute.Where(x => x.Item2 is not INamedTypeSymbol namedSym || !IsValidXamlControl(namedSym));
+ var generatedOptionAttributeOnUnsupportedType = generatedOptionPropertyData.Where(x => x.Item1 is not INamedTypeSymbol namedSym || !IsValidXamlControl(namedSym));
+
+
+ foreach (var item in toolkitAttributesOnUnsupportedType)
+ ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SampleAttributeOnUnsupportedType, item.Symbol.Locations.FirstOrDefault()));
+
+
+ foreach (var item in optionsAttributeOnUnsupportedType)
+ ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SampleOptionPaneAttributeOnUnsupportedType, item.Item2.Locations.FirstOrDefault()));
+
+
+ foreach (var item in generatedOptionAttributeOnUnsupportedType)
+ ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SampleGeneratedOptionAttributeOnUnsupportedType, item.Item1.Locations.FirstOrDefault()));
+
+ }
+
+ private static void ReportDiagnosticsForLinkedOptionsPane(SourceProductionContext ctx,
+ IEnumerable<(ToolkitSampleAttribute Attribute, string AttachedQualifiedTypeName, ISymbol Symbol)> toolkitSampleAttributeData,
+ IEnumerable<(ToolkitSampleOptionsPaneAttribute?, ISymbol)> optionsPaneAttribute)
+ {
+ // Check for options pane attributes with no matching sample ID
+ var optionsPaneAttributeWithMissingOrInvalidSampleId = optionsPaneAttribute.Where(x => !toolkitSampleAttributeData.Any(sample => sample.Attribute.Id == x.Item1?.SampleId));
+
+ foreach (var item in optionsPaneAttributeWithMissingOrInvalidSampleId)
+ ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.OptionsPaneAttributeWithMissingOrInvalidSampleId, item.Item2.Locations.FirstOrDefault()));
+ }
+
+ private static void ReportDiagnosticsGeneratedOptionsPane(SourceProductionContext ctx,
+ IEnumerable<(ToolkitSampleAttribute Attribute, string AttachedQualifiedTypeName, ISymbol Symbol)> toolkitSampleAttributeData,
+ IEnumerable<(ISymbol, ToolkitSampleOptionBaseAttribute)> generatedOptionPropertyData)
+ {
+ ReportGeneratedMultiChoiceOptionsPaneDiagnostics(ctx, generatedOptionPropertyData);
+
+ // Check for generated options which don't have a valid sample attribute
+ var generatedOptionsWithMissingSampleAttribute = generatedOptionPropertyData.Where(x => !toolkitSampleAttributeData.Any(sample => ReferenceEquals(sample.Symbol, x.Item1)));
+
+ foreach (var item in generatedOptionsWithMissingSampleAttribute)
+ ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SamplePaneOptionAttributeOnNonSample, item.Item1.Locations.FirstOrDefault()));
+
+ // Check for generated options with an empty or invalid name.
+ var generatedOptionsWithBadName = generatedOptionPropertyData.Where(x => string.IsNullOrWhiteSpace(x.Item2.Name) || // Must not be null or empty
+ !x.Item2.Name.Any(char.IsLetterOrDigit) || // Must be alphanumeric
+ x.Item2.Name.Any(char.IsWhiteSpace) || // Must not have whitespace
+ SyntaxFacts.GetKeywordKind(x.Item2.Name) != SyntaxKind.None); // Must not be a reserved keyword
+
+ foreach (var item in generatedOptionsWithBadName)
+ ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SamplePaneOptionWithBadName, item.Item1.Locations.FirstOrDefault(), item.Item1.ToString()));
+
+ // Check for generated options with duplicate names.
+ var generatedOptionsWithDuplicateName = generatedOptionPropertyData.GroupBy(x => x.Item1, SymbolEqualityComparer.Default) // Group by containing symbol (allow reuse across samples)
+ .SelectMany(y => y.GroupBy(x => x.Item2.Name) // In this symbol, group options by name.
+ .Where(x => x.Any(x => x.Item2 is not ToolkitSampleMultiChoiceOptionAttribute)) // Exclude Multichoice.
+ .Where(x => x.Count() > 1)); // Options grouped by name should only contain 1 item.
+
+ foreach (var item in generatedOptionsWithDuplicateName)
+ ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SamplePaneOptionWithDuplicateName, item.SelectMany(x => x.Item1.Locations).FirstOrDefault(), item.Key));
+
+ // Check for generated options that conflict with an existing property name
+ var generatedOptionsWithConflictingPropertyNames = generatedOptionPropertyData.Where(x => GetAllMembers((INamedTypeSymbol)x.Item1).Any(y => x.Item2.Name == y.Name));
+
+ foreach (var item in generatedOptionsWithConflictingPropertyNames)
+ ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SamplePaneOptionWithConflictingName, item.Item1.Locations.FirstOrDefault(), item.Item2.Name));
+ }
+
+ private static void ReportGeneratedMultiChoiceOptionsPaneDiagnostics(SourceProductionContext ctx, IEnumerable<(ISymbol, ToolkitSampleOptionBaseAttribute)> generatedOptionPropertyData)
+ {
+ var generatedMultipleChoiceOptionWithMultipleTitles = new List<(ISymbol, ToolkitSampleOptionBaseAttribute)>();
+
+ var multiChoiceOptionsGroupedBySymbol = generatedOptionPropertyData.GroupBy(x => x.Item1, SymbolEqualityComparer.Default)
+ .Where(x => x.Any(x => x.Item2 is ToolkitSampleMultiChoiceOptionAttribute));
+
+ foreach (var symbolGroup in multiChoiceOptionsGroupedBySymbol)
+ {
+ var optionsGroupedByName = symbolGroup.GroupBy(x => x.Item2.Name);
+
+ foreach (var nameGroup in optionsGroupedByName)
+ {
+ var optionsGroupedByTitle = nameGroup.Where(x => !string.IsNullOrWhiteSpace(x.Item2?.Title))
+ .GroupBy(x => x.Item2.Title)
+ .SelectMany(x => x);
+
+ if (optionsGroupedByTitle.Count() > 1)
+ generatedMultipleChoiceOptionWithMultipleTitles.Add(optionsGroupedByTitle.First());
+ }
+ }
+
+ foreach (var item in generatedMultipleChoiceOptionWithMultipleTitles)
+ ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SamplePaneMultiChoiceOptionWithMultipleTitles, item.Item1.Locations.FirstOrDefault(), item.Item2.Title));
+ }
+
+ private static string BuildRegistrationCallsFromMetadata(IEnumerable sampleMetadata)
+ {
+ return $@"#nullable enable
+namespace CommunityToolkit.Labs.Core.SourceGenerators;
+
+public static class ToolkitSampleRegistry
+{{
+ public static System.Collections.Generic.IEnumerable<{typeof(ToolkitSampleMetadata).FullName}> Execute()
+ {{
+ {
+ string.Join("\n ", sampleMetadata.Select(MetadataToRegistryCall).ToArray())
+ }
+ }}
+}}";
+ }
+
+ private static string MetadataToRegistryCall(ToolkitSampleRecord metadata)
+ {
+ var sampleOptionsParam = metadata.SampleOptionsAssemblyQualifiedName is null ? "null" : $"typeof({metadata.SampleOptionsAssemblyQualifiedName})";
+ var categoryParam = $"{nameof(ToolkitSampleCategory)}.{metadata.Category}";
+ var subcategoryParam = $"{nameof(ToolkitSampleSubcategory)}.{metadata.Subcategory}";
+ var containingClassTypeParam = $"typeof({metadata.SampleAssemblyQualifiedName})";
+ var generatedSampleOptionsParam = $"new {typeof(IGeneratedToolkitSampleOptionViewModel).FullName}[] {{ {string.Join(", ", BuildNewGeneratedSampleOptionMetadataSource(metadata).ToArray())} }}";
+
+ return @$"yield return new {typeof(ToolkitSampleMetadata).FullName}({categoryParam}, {subcategoryParam}, ""{metadata.DisplayName}"", ""{metadata.Description}"", {containingClassTypeParam}, {sampleOptionsParam}, {generatedSampleOptionsParam});";
+ }
+
+ private static IEnumerable BuildNewGeneratedSampleOptionMetadataSource(ToolkitSampleRecord sample)
+ {
+ // Handle group-able items
+ var multiChoice = sample.GeneratedSampleOptions.Where(x => x is ToolkitSampleMultiChoiceOptionAttribute)
+ .Cast()
+ .GroupBy(x => x.Name);
+
+ foreach (var item in multiChoice)
+ yield return $@"new {typeof(ToolkitSampleMultiChoiceOptionMetadataViewModel).FullName}(name: ""{item.Key}"", options: new[] {{ {string.Join(",", item.Select(x => $@"new {typeof(MultiChoiceOption).FullName}(""{x.Label}"", ""{x.Value}"")").ToArray())} }}, title: ""{item.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.Title))?.Title}"")";
+
+ // Handle non-grouped items
+ var remainingItems = sample.GeneratedSampleOptions?.Except(multiChoice.SelectMany(x => x));
+
+ foreach (var item in remainingItems ?? Enumerable.Empty())
+ {
+ if (item is ToolkitSampleBoolOptionAttribute boolAttribute)
+ yield return $@"new {typeof(ToolkitSampleBoolOptionMetadataViewModel).FullName}(id: ""{boolAttribute.Name}"", label: ""{boolAttribute.Label}"", defaultState: {boolAttribute.DefaultState?.ToString().ToLower()}, title: ""{boolAttribute.Title}"")";
+ else
+ throw new NotSupportedException($"Unsupported or unhandled type {item.GetType()}.");
+ }
+ }
+
+ ///
+ /// Checks if a symbol's is or inherits from a type representing a XAML framework.
+ ///
+ /// if the is or inherits from a type representing a XAML framework.
+ private static bool IsValidXamlControl(INamedTypeSymbol symbol)
+ {
+ // In UWP, Page inherits UserControl
+ // In Uno, Page doesn't appear to inherit UserControl.
+ var validSimpleTypeNames = new[] { "UserControl", "Page" };
+
+ // UWP / Uno / WinUI 3 namespaces.
+ var validNamespaceRoots = new[] { "Microsoft", "Windows" };
+
+ // Recursively crawl the base types until either UserControl or Page is found.
+ var validInheritedSymbol = symbol.CrawlBy(x => x?.BaseType, baseType => validNamespaceRoots.Any(x => $"{baseType}".StartsWith(x)) &&
+ $"{baseType}".Contains(".UI.Xaml.Controls.") &&
+ validSimpleTypeNames.Any(x => $"{baseType}".EndsWith(x)));
+
+ var typeIsAccessible = symbol.DeclaredAccessibility == Accessibility.Public;
+
+ return validInheritedSymbol != default && typeIsAccessible && !symbol.IsStatic;
+ }
+
+ private static IEnumerable GetAllMembers(INamedTypeSymbol symbol)
+ {
+ foreach (var item in symbol.GetMembers())
+ yield return item;
+
+ if (symbol.BaseType is not null)
+ foreach (var item in GetAllMembers(symbol.BaseType))
+ yield return item;
+ }
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleOptionGenerator.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleOptionGenerator.cs
new file mode 100644
index 000000000..f75579723
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleOptionGenerator.cs
@@ -0,0 +1,159 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+using CommunityToolkit.Labs.Core.SourceGenerators.Metadata;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators
+{
+ ///
+ /// For the generated sample pane options, this generator creates the backing properties needed for binding in the UI,
+ /// as well as implementing the for relaying data between the options pane and the generated property.
+ ///
+ [Generator]
+ public class ToolkitSampleOptionGenerator : IIncrementalGenerator
+ {
+ private readonly HashSet _handledPropertyNames = new();
+ private readonly HashSet _handledAttributes = new();
+ private readonly HashSet _handledContainingClasses = new(SymbolEqualityComparer.Default);
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var classes = context.SyntaxProvider
+ .CreateSyntaxProvider(
+ static (s, _) => s is ClassDeclarationSyntax c && c.AttributeLists.Count > 0,
+ static (ctx, _) => ctx.SemanticModel.GetDeclaredSymbol(ctx.Node))
+ .Where(static m => m is not null)
+ .Select(static (x, _) => x!);
+
+ // Get all attributes + the original type symbol.
+ var allAttributeData = classes.SelectMany((sym, _) => sym.GetAttributes().Select(x => (sym, x)));
+
+ // Find and reconstruct attributes.
+ var sampleAttributeOptions = allAttributeData
+ .Select((x, _) =>
+ {
+ if (x.Item2.TryReconstructAs() is ToolkitSampleBoolOptionAttribute boolOptionAttribute)
+ return (Attribute: (ToolkitSampleOptionBaseAttribute)boolOptionAttribute, ContainingClassSymbol: x.Item1, Type: typeof(ToolkitSampleBoolOptionMetadataViewModel));
+
+ if (x.Item2.TryReconstructAs() is ToolkitSampleMultiChoiceOptionAttribute multiChoiceOptionAttribute)
+ return (Attribute: (ToolkitSampleOptionBaseAttribute)multiChoiceOptionAttribute, ContainingClassSymbol: x.Item1, Type: typeof(ToolkitSampleMultiChoiceOptionMetadataViewModel));
+
+ return default;
+ })
+ .Where(x => x != default);
+
+ context.RegisterSourceOutput(sampleAttributeOptions, (ctx, data) =>
+ {
+ if (_handledContainingClasses.Add(data.ContainingClassSymbol))
+ {
+ if (data.ContainingClassSymbol is ITypeSymbol typeSym && !typeSym.AllInterfaces.Any(x => x.HasFullyQualifiedName("global::System.ComponentModel.INotifyPropertyChanged")))
+ {
+ var inpcImpl = BuildINotifyPropertyChangedImplementation(data.ContainingClassSymbol);
+ ctx.AddSource($"{data.ContainingClassSymbol}.NotifyPropertyChanged.g", inpcImpl);
+ }
+
+ var propertyContainerSource = BuildGeneratedPropertyMetadataContainer(data.ContainingClassSymbol);
+ ctx.AddSource($"{data.ContainingClassSymbol}.GeneratedPropertyContainer.g", propertyContainerSource);
+ }
+
+ if (!_handledAttributes.Add(data.Attribute))
+ return;
+
+ var dependencyPropertySource = BuildProperty(data.ContainingClassSymbol, data.Attribute.Name, data.Attribute.TypeName, data.Type);
+
+ if (_handledPropertyNames.Add(data.Attribute.Name))
+ ctx.AddSource($"{data.ContainingClassSymbol}.Property.{data.Attribute.Name}.g", dependencyPropertySource);
+ });
+
+ }
+
+ private static string BuildINotifyPropertyChangedImplementation(ISymbol containingClassSymbol)
+ {
+ return $@"#nullable enable
+using System.ComponentModel;
+
+namespace {containingClassSymbol.ContainingNamespace}
+{{
+ public partial class {containingClassSymbol.Name} : {nameof(System.ComponentModel.INotifyPropertyChanged)}
+ {{
+ public event PropertyChangedEventHandler PropertyChanged;
+ }}
+}}
+";
+ }
+
+ private static string BuildGeneratedPropertyMetadataContainer(ISymbol containingClassSymbol)
+ {
+ return $@"#nullable enable
+using System.ComponentModel;
+using System.Collections.Generic;
+
+namespace {containingClassSymbol.ContainingNamespace}
+{{
+ public partial class {containingClassSymbol.Name} : {typeof(IToolkitSampleGeneratedOptionPropertyContainer).Namespace}.{nameof(IToolkitSampleGeneratedOptionPropertyContainer)}
+ {{
+ private IEnumerable<{typeof(IGeneratedToolkitSampleOptionViewModel).FullName}>? _generatedPropertyMetadata;
+
+ public IEnumerable<{typeof(IGeneratedToolkitSampleOptionViewModel).FullName}>? GeneratedPropertyMetadata
+ {{
+ get => _generatedPropertyMetadata;
+ set
+ {{
+ if (!(_generatedPropertyMetadata is null))
+ {{
+ foreach (var item in _generatedPropertyMetadata)
+ item.PropertyChanged -= OnPropertyChanged;
+ }}
+
+
+ if (!(value is null))
+ {{
+ foreach (var item in value)
+ item.PropertyChanged += OnPropertyChanged;
+ }}
+
+ _generatedPropertyMetadata = value;
+ }}
+ }}
+
+ private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
+ }}
+}}
+";
+ }
+
+ private static string BuildProperty(ISymbol containingClassSymbol, string propertyName, string typeName, Type viewModelType)
+ {
+ return $@"#nullable enable
+using System.ComponentModel;
+using System.Linq;
+
+namespace {containingClassSymbol.ContainingNamespace}
+{{
+ public partial class {containingClassSymbol.Name}
+ {{
+ public {typeName} {propertyName}
+ {{
+ get => (({typeName})(({viewModelType.FullName})GeneratedPropertyMetadata!.First(x => x.Name == ""{propertyName}""))!.Value);
+ set
+ {{
+ if (GeneratedPropertyMetadata?.FirstOrDefault(x => x.Name == nameof({propertyName})) is {viewModelType.FullName} metadata)
+ {{
+ metadata.Value = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof({propertyName})));
+ }}
+ }}
+ }}
+ }}
+}}
+";
+ }
+ }
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleRecord.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleRecord.cs
new file mode 100644
index 000000000..d90f12a4d
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleRecord.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators;
+
+public partial class ToolkitSampleMetadataGenerator
+{
+ ///
+ /// Used to hold interim data during the source generation process by .
+ ///
+ ///
+ /// A new record must be used instead of using directly
+ /// because we cannot Type.GetType using the ,
+ /// but we can safely generate a type reference in the final output using typeof(AssemblyQualifiedName).
+ ///
+ private sealed record ToolkitSampleRecord(
+ ToolkitSampleCategory Category,
+ ToolkitSampleSubcategory Subcategory,
+ string DisplayName,
+ string Description,
+ string SampleAssemblyQualifiedName,
+ string? SampleOptionsAssemblyQualifiedName,
+ IEnumerable? GeneratedSampleOptions = null);
+}
diff --git a/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleSubcategory.cs b/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleSubcategory.cs
new file mode 100644
index 000000000..c4191159b
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Core.SourceGenerators/ToolkitSampleSubcategory.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.Labs.Core.SourceGenerators;
+
+///
+/// The various subcategories used by samples.
+///
+///
+/// Subcategories can be used by samples across multiple categories.
+///
+public enum ToolkitSampleSubcategory : byte
+{
+ ///
+ /// No subcategory specified.
+ ///
+ None,
+
+ ///
+ /// A sample that focuses on control layout.
+ ///
+ Layout,
+}
+
diff --git a/Common/CommunityToolkit.Labs.Core/Attributes/ToolkitSampleAttribute.cs b/Common/CommunityToolkit.Labs.Core/Attributes/ToolkitSampleAttribute.cs
deleted file mode 100644
index 97f37a1c8..000000000
--- a/Common/CommunityToolkit.Labs.Core/Attributes/ToolkitSampleAttribute.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System;
-
-namespace CommunityToolkit.Labs.Core.Attributes
-{
- ///
- /// When used on a class that derives from Page, that page is registered as a toolkit sample using the provided data.
- ///
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
- public sealed class ToolkitSampleAttribute : Attribute
- {
- ///
- /// Creates a new instance of .
- ///
- public ToolkitSampleAttribute(string displayName, ToolkitSampleCategory category, ToolkitSampleSubcategory subcategory, string description)
- {
- Category = category;
- Subcategory = subcategory;
- DisplayName = displayName;
- Description = description;
- }
-
- ///
- /// The display name for this sample page.
- ///
- public string DisplayName { get; }
-
- ///
- /// The category that this sample belongs to.
- ///
- public ToolkitSampleCategory Category { get; }
-
- ///
- /// A more specific category within the provided .
- ///
- public ToolkitSampleSubcategory Subcategory { get; }
-
- ///
- /// The description for this sample page.
- ///
- public string Description { get; }
- }
-}
diff --git a/Common/CommunityToolkit.Labs.Core/ToolkitSampleCategory.cs b/Common/CommunityToolkit.Labs.Core/ToolkitSampleCategory.cs
deleted file mode 100644
index 622da5ea7..000000000
--- a/Common/CommunityToolkit.Labs.Core/ToolkitSampleCategory.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace CommunityToolkit.Labs.Core
-{
- ///
- /// The various categories each sample is organized into.
- ///
- public enum ToolkitSampleCategory : byte
- {
- ///
- /// Various UI controls that the user sees and interacts with.
- ///
- Controls,
- }
-}
diff --git a/Common/CommunityToolkit.Labs.Core/ToolkitSampleMetadata.cs b/Common/CommunityToolkit.Labs.Core/ToolkitSampleMetadata.cs
deleted file mode 100644
index 12d7a1a86..000000000
--- a/Common/CommunityToolkit.Labs.Core/ToolkitSampleMetadata.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-
-namespace CommunityToolkit.Labs.Core
-{
- ///
- /// Contains the metadata needed to identify and display a toolkit sample.
- ///
- public sealed record ToolkitSampleMetadata
- {
- ///
- /// Creates a new instance of .
- ///
- public ToolkitSampleMetadata(ToolkitSampleCategory category, ToolkitSampleSubcategory subcategory, string displayName, string description, Type sampleControlType)
- {
- DisplayName = displayName;
- Description = description;
- SampleControlType = sampleControlType;
- Category = category;
- Subcategory = subcategory;
- }
-
- ///
- /// The category that this sample belongs to.
- ///
- public ToolkitSampleCategory Category { get; }
-
- ///
- /// A more specific category within the provided .
- ///
- public ToolkitSampleSubcategory Subcategory { get; }
-
- ///
- /// The display name for this sample page.
- ///
- public string DisplayName { get; }
-
- ///
- /// The description for this sample page.
- ///
- public string Description { get; }
-
- ///
- /// A type that can be used to construct an instance of the sample control.
- ///
- public Type SampleControlType { get; }
- }
-}
diff --git a/Common/CommunityToolkit.Labs.Core/ToolkitSampleMetadataGenerator.cs b/Common/CommunityToolkit.Labs.Core/ToolkitSampleMetadataGenerator.cs
deleted file mode 100644
index 968501026..000000000
--- a/Common/CommunityToolkit.Labs.Core/ToolkitSampleMetadataGenerator.cs
+++ /dev/null
@@ -1,130 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using CommunityToolkit.Labs.Core.Attributes;
-using Microsoft.CodeAnalysis;
-
-namespace CommunityToolkit.Labs.Core
-{
- ///
- /// Crawls all referenced projects for s and generates a static method that returns metadata for each one found.
- ///
- [Generator]
- public partial class ToolkitSampleMetadataGenerator : ISourceGenerator
- {
- ///
- public void Initialize(GeneratorInitializationContext context)
- {
- // not needed
- }
-
- ///
- public void Execute(GeneratorExecutionContext context)
- {
- // Find all types in all assemblies.
- var assemblies = context.Compilation.SourceModule.ReferencedAssemblySymbols;
-
- var types = assemblies.SelectMany(asm => CrawlForAllNamedTypes(asm.GlobalNamespace))
- .Where(x => x is not null && x.TypeKind == TypeKind.Class && x.CanBeReferencedByName) // remove null and invalid values.
- .Cast(); // strip nullability from type.
-
- if (types is null)
- return;
-
- // Get all attributes + the original type symbol.
- var allAttributeData = types.SelectMany(type => type.GetAttributes(), (Type, Attribute) => (Type, Attribute));
- var toolkitSampleAttributeData = allAttributeData.Where(x => IsToolkitSampleAttribute(x.Attribute));
-
- // Reconstruct sample metadata from attributes
- var sampleMetadata = toolkitSampleAttributeData.Select(x => ReconstructSampleMetadata(x.Type, x.Attribute));
-
- // Build source string
- var source = BuildRegistrationCallsFromMetadata(sampleMetadata);
- context.AddSource($"ToolkitSampleRegistry.g.cs", source);
- }
-
- static private string BuildRegistrationCallsFromMetadata(IEnumerable sampleMetadata)
- {
- return $@"//
-namespace CommunityToolkit.Labs.Core;
-
-internal static class ToolkitSampleRegistry
-{{
- public static System.Collections.Generic.IEnumerable<{nameof(ToolkitSampleMetadata)}> Execute()
- {{
- {
- string.Join("\n ", sampleMetadata.Select(MetadataToRegistryCall).ToArray())
- }
- }}
-}}";
-
- static string MetadataToRegistryCall(ToolkitSampleRecord metadata)
- {
- return @$"yield return new {nameof(ToolkitSampleMetadata)}({nameof(ToolkitSampleCategory)}.{metadata.Category}, {nameof(ToolkitSampleSubcategory)}.{metadata.Subcategory}, ""{metadata.DisplayName}"", ""{metadata.Description}"", typeof({metadata.AssemblyQualifiedName}));";
- }
- }
-
- private static IEnumerable CrawlForAllNamedTypes(INamespaceSymbol namespaceSymbol)
- {
- foreach (var member in namespaceSymbol.GetMembers())
- {
- if (member is INamespaceSymbol nestedNamespace)
- {
- foreach (var item in CrawlForAllNamedTypes(nestedNamespace))
- yield return item;
- }
-
- if (member is INamedTypeSymbol typeSymbol)
- yield return typeSymbol;
- }
- }
-
- private static bool IsToolkitSampleAttribute(AttributeData attr)
- => attr.AttributeClass?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == $"global::{typeof(ToolkitSampleAttribute).FullName}";
-
- private static ToolkitSampleRecord ReconstructSampleMetadata(INamedTypeSymbol typeSymbol, AttributeData attributeData)
- {
- // Fully reconstructing the attribute as it was received
- // gives us safety against changes to the attribute constructor signature.
- var args = attributeData.ConstructorArguments.Select(PrepareTypeForActivator).ToArray();
-
- var reconstructedAttribute = (ToolkitSampleAttribute)Activator.CreateInstance(typeof(ToolkitSampleAttribute), args);
-
- var attachedTypeFullyQualifiedName = typeSymbol.ToString();
-
- return new ToolkitSampleRecord(reconstructedAttribute.Category,
- reconstructedAttribute.Subcategory,
- reconstructedAttribute.DisplayName,
- reconstructedAttribute.Description,
- attachedTypeFullyQualifiedName);
- }
-
- private static object? PrepareTypeForActivator(TypedConstant typedConstant)
- {
- if (typedConstant.Type is null)
- throw new ArgumentNullException(nameof(typedConstant.Type));
-
- // Types prefixed with global:: do not work with Type.GetType and must be stripped away.
- var assemblyQualifiedName = typedConstant.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat).Replace("global::", "");
-
- var argType = Type.GetType(assemblyQualifiedName);
-
- // Enums arrive as the underlying integer type, which doesn't work as a param for Activator.CreateInstance()
- if (argType != null && typedConstant.Kind == TypedConstantKind.Enum)
- return Enum.Parse(argType, typedConstant.Value?.ToString());
-
- return typedConstant.Value;
- }
-
- ///
- /// A new record must be used instead of using directly
- /// because we cannot Type.GetType using the ,
- /// but we can safely generate a type reference in the final output using typeof(AssemblyQualifiedName).
- ///
- private sealed record ToolkitSampleRecord(ToolkitSampleCategory Category, ToolkitSampleSubcategory Subcategory, string DisplayName, string Description, string AssemblyQualifiedName);
- }
-}
diff --git a/Common/CommunityToolkit.Labs.Core/ToolkitSampleSubcategory.cs b/Common/CommunityToolkit.Labs.Core/ToolkitSampleSubcategory.cs
deleted file mode 100644
index af9afd8e2..000000000
--- a/Common/CommunityToolkit.Labs.Core/ToolkitSampleSubcategory.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace CommunityToolkit.Labs.Core
-{
- ///
- /// All the different subcategories used by samples.
- ///
-
- // Subcategory is a flat enum so we can use a subcategory in multiple categories,
- // and so we can freely move samples or whole sections in the future.
- public enum ToolkitSampleSubcategory : byte
- {
- ///
- /// No subcategory specified.
- ///
- None,
-
- ///
- /// A sample that focuses on control layout.
- ///
- Layout,
- }
-}
diff --git a/Common/CommunityToolkit.Labs.Shared/AppLoadingView.xaml.cs b/Common/CommunityToolkit.Labs.Shared/AppLoadingView.xaml.cs
index f27cb2093..a84bc0b56 100644
--- a/Common/CommunityToolkit.Labs.Shared/AppLoadingView.xaml.cs
+++ b/Common/CommunityToolkit.Labs.Shared/AppLoadingView.xaml.cs
@@ -1,10 +1,8 @@
-using CommunityToolkit.Labs.Core;
-using CommunityToolkit.Labs.Core.Attributes;
+using CommunityToolkit.Labs.Core.SourceGenerators.Metadata;
+using CommunityToolkit.Labs.Core.SourceGenerators;
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
-using System.Reflection;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
@@ -14,10 +12,6 @@
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
-using Windows.UI.Xaml.Controls.Primitives;
-using Windows.UI.Xaml.Data;
-using Windows.UI.Xaml.Input;
-using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
#else
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
diff --git a/Common/CommunityToolkit.Labs.Shared/CommunityToolkit.Labs.Shared.projitems b/Common/CommunityToolkit.Labs.Shared/CommunityToolkit.Labs.Shared.projitems
index c98a657af..ddab2e8f7 100644
--- a/Common/CommunityToolkit.Labs.Shared/CommunityToolkit.Labs.Shared.projitems
+++ b/Common/CommunityToolkit.Labs.Shared/CommunityToolkit.Labs.Shared.projitems
@@ -21,19 +21,34 @@
AppLoadingView.xaml
+
+ GeneratedSampleOptionsRenderer.xaml
+
MainPage.xaml
+
+
+ ToolkitSampleRenderer.xaml
+
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
diff --git a/Common/CommunityToolkit.Labs.Shared/MainPage.xaml b/Common/CommunityToolkit.Labs.Shared/MainPage.xaml
index 42d9c6ec8..769452080 100644
--- a/Common/CommunityToolkit.Labs.Shared/MainPage.xaml
+++ b/Common/CommunityToolkit.Labs.Shared/MainPage.xaml
@@ -10,7 +10,7 @@
-
+
diff --git a/Common/CommunityToolkit.Labs.Shared/MainPage.xaml.cs b/Common/CommunityToolkit.Labs.Shared/MainPage.xaml.cs
index c55a86c74..135401084 100644
--- a/Common/CommunityToolkit.Labs.Shared/MainPage.xaml.cs
+++ b/Common/CommunityToolkit.Labs.Shared/MainPage.xaml.cs
@@ -29,6 +29,8 @@
using NavigationViewItem = Microsoft.UI.Xaml.Controls.NavigationViewItem;
using NavigationView = Microsoft.UI.Xaml.Controls.NavigationView;
using NavigationViewSelectionChangedEventArgs = Microsoft.UI.Xaml.Controls.NavigationViewSelectionChangedEventArgs;
+using CommunityToolkit.Labs.Shared.Renderers;
+using CommunityToolkit.Labs.Core.SourceGenerators.Metadata;
namespace CommunityToolkit.Labs.Shared
{
@@ -42,26 +44,11 @@ public MainPage()
this.InitializeComponent();
}
- ///
- /// Gets the backing dependency property for .
- ///
- public static readonly DependencyProperty MainContentProperty =
- DependencyProperty.Register(nameof(MainContent), typeof(object), typeof(MainPage), new PropertyMetadata(null));
-
///
/// Gets the items used for navigating.
///
public ObservableCollection NavigationViewItems { get; } = new ObservableCollection();
- ///
- /// Gets or sets the primary content displayed to the user.
- ///
- public object MainContent
- {
- get => (object)GetValue(MainContentProperty);
- set => SetValue(MainContentProperty, value);
- }
-
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var samplePages = e.Parameter as IEnumerable;
@@ -85,10 +72,7 @@ private void OnSelectionChanged(NavigationView sender, NavigationViewSelectionCh
if (selectedMetadata is null)
return;
- // TODO: Switch to Frame / Frame.Navigate when grouped-sample page is added.
- var controlInstance = Activator.CreateInstance(selectedMetadata.SampleControlType);
-
- MainContent = controlInstance;
+ NavFrame.Navigate(typeof(ToolkitSampleRenderer), selectedMetadata);
}
private IEnumerable GenerateSampleNavItemTree(IEnumerable sampleMetadata)
diff --git a/Common/CommunityToolkit.Labs.Shared/Renderers/GeneratedSampleOptionTemplateSelector.cs b/Common/CommunityToolkit.Labs.Shared/Renderers/GeneratedSampleOptionTemplateSelector.cs
new file mode 100644
index 000000000..7058f0d44
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Shared/Renderers/GeneratedSampleOptionTemplateSelector.cs
@@ -0,0 +1,35 @@
+using CommunityToolkit.Labs.Core.SourceGenerators.Metadata;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+#if WINAPPSDK
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+#else
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+#endif
+
+namespace CommunityToolkit.Labs.Shared.Renderers
+{
+ ///
+ /// Selects a sample option template for the provided .
+ ///
+ internal class GeneratedSampleOptionTemplateSelector : DataTemplateSelector
+ {
+ public DataTemplate? BoolOptionTemplate { get; set; }
+
+ public DataTemplate? MultiChoiceOptionTemplate { get; set; }
+
+ protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
+ {
+ return item switch
+ {
+ ToolkitSampleBoolOptionMetadataViewModel => BoolOptionTemplate ?? base.SelectTemplateCore(item, container),
+ ToolkitSampleMultiChoiceOptionMetadataViewModel => MultiChoiceOptionTemplate ?? base.SelectTemplateCore(item, container),
+ _ => base.SelectTemplateCore(item, container),
+ };
+ }
+ }
+}
diff --git a/Common/CommunityToolkit.Labs.Shared/Renderers/GeneratedSampleOptionsRenderer.xaml b/Common/CommunityToolkit.Labs.Shared/Renderers/GeneratedSampleOptionsRenderer.xaml
new file mode 100644
index 000000000..dca478dd0
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Shared/Renderers/GeneratedSampleOptionsRenderer.xaml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Common/CommunityToolkit.Labs.Shared/Renderers/GeneratedSampleOptionsRenderer.xaml.cs b/Common/CommunityToolkit.Labs.Shared/Renderers/GeneratedSampleOptionsRenderer.xaml.cs
new file mode 100644
index 000000000..e59a0feef
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Shared/Renderers/GeneratedSampleOptionsRenderer.xaml.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+using CommunityToolkit.Labs.Core.SourceGenerators.Metadata;
+
+#if WINAPPSDK
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Navigation;
+#else
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+#endif
+
+namespace CommunityToolkit.Labs.Shared.Renderers
+{
+ ///
+ /// Displays the provided for manipulation by the user.
+ ///
+ ///
+ /// Sample pages implement via source generators,
+ /// and are provided a reference to the same given to this control.
+ ///
+ /// When the user updates the ,
+ /// a PropertyChanged event with the should be emitted.
+ ///
+ /// The sample page sees this property change event via the generated ,
+ /// causing it to re-get the proxied .
+ ///
+ public sealed partial class GeneratedSampleOptionsRenderer : UserControl
+ {
+ public GeneratedSampleOptionsRenderer()
+ {
+ this.InitializeComponent();
+ }
+
+ ///
+ /// The backing for .
+ ///
+ public static readonly DependencyProperty SampleOptionsProperty =
+ DependencyProperty.Register(nameof(SampleOptions), typeof(IEnumerable), typeof(GeneratedSampleOptionsRenderer), new PropertyMetadata(null));
+
+ ///
+ /// The generated sample options that should be displayed to the user.
+ ///
+ public IEnumerable? SampleOptions
+ {
+ get => (IEnumerable?)GetValue(SampleOptionsProperty);
+ set => SetValue(SampleOptionsProperty, value);
+ }
+
+ public static Visibility NullOrWhiteSpaceToVisibility(string? str) => string.IsNullOrWhiteSpace(str) ? Visibility.Collapsed : Visibility.Visible;
+ }
+}
diff --git a/Common/CommunityToolkit.Labs.Shared/Renderers/ToolkitSampleRenderer.xaml b/Common/CommunityToolkit.Labs.Shared/Renderers/ToolkitSampleRenderer.xaml
new file mode 100644
index 000000000..191e7c970
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Shared/Renderers/ToolkitSampleRenderer.xaml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 14
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Common/CommunityToolkit.Labs.Shared/Renderers/ToolkitSampleRenderer.xaml.cs b/Common/CommunityToolkit.Labs.Shared/Renderers/ToolkitSampleRenderer.xaml.cs
new file mode 100644
index 000000000..8b0cf38ee
--- /dev/null
+++ b/Common/CommunityToolkit.Labs.Shared/Renderers/ToolkitSampleRenderer.xaml.cs
@@ -0,0 +1,186 @@
+using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+using CommunityToolkit.Labs.Core.SourceGenerators.Metadata;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.Storage;
+
+#if WINAPPSDK
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Navigation;
+#else
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+#endif
+
+namespace CommunityToolkit.Labs.Shared.Renderers
+{
+ ///
+ /// Handles the display of a single toolkit sample, its source code, and the options that control it.
+ ///
+ public sealed partial class ToolkitSampleRenderer : Page
+ {
+ ///
+ /// Creates a new instance of .
+ ///
+ public ToolkitSampleRenderer()
+ {
+ this.InitializeComponent();
+ }
+
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty MetadataProperty =
+ DependencyProperty.Register(nameof(Metadata), typeof(ToolkitSampleMetadata), typeof(ToolkitSampleRenderer), new PropertyMetadata(null));
+
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty SampleControlInstanceProperty =
+ DependencyProperty.Register(nameof(SampleControlInstance), typeof(UIElement), typeof(ToolkitSampleRenderer), new PropertyMetadata(null));
+
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty SampleOptionsPaneInstanceProperty =
+ DependencyProperty.Register(nameof(SampleOptionsPaneInstance), typeof(UIElement), typeof(ToolkitSampleRenderer), new PropertyMetadata(null));
+
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty XamlCodeProperty =
+ DependencyProperty.Register(nameof(XamlCode), typeof(string), typeof(ToolkitSampleRenderer), new PropertyMetadata(null));
+
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty CSharpCodeProperty =
+ DependencyProperty.Register(nameof(CSharpCode), typeof(string), typeof(ToolkitSampleRenderer), new PropertyMetadata(null));
+
+ public ToolkitSampleMetadata? Metadata
+ {
+ get { return (ToolkitSampleMetadata?)GetValue(MetadataProperty); }
+ set { SetValue(MetadataProperty, value); }
+ }
+
+ ///
+ /// The sample control instance being displayed.
+ ///
+ public UIElement? SampleControlInstance
+ {
+ get => (UIElement?)GetValue(SampleControlInstanceProperty);
+ set => SetValue(SampleControlInstanceProperty, value);
+ }
+
+
+ ///
+ /// The options pane for the sample being displayed.
+ ///
+ public UIElement? SampleOptionsPaneInstance
+ {
+ get => (UIElement?)GetValue(SampleOptionsPaneInstanceProperty);
+ set => SetValue(SampleOptionsPaneInstanceProperty, value);
+ }
+
+ ///
+ /// The XAML code being rendered.
+ ///
+ public string? XamlCode
+ {
+ get => (string?)GetValue(XamlCodeProperty);
+ set => SetValue(XamlCodeProperty, value);
+ }
+
+ ///
+ /// The backing C# for the being rendered.
+ ///
+ public string? CSharpCode
+ {
+ get => (string?)GetValue(CSharpCodeProperty);
+ set => SetValue(CSharpCodeProperty, value);
+ }
+
+ protected override async void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ Metadata = (ToolkitSampleMetadata)e.Parameter;
+
+ XamlCode = await GetMetadataFileContents(Metadata, "xaml");
+ CSharpCode = await GetMetadataFileContents(Metadata, "xaml.cs");
+
+ SampleControlInstance = (UIElement)Activator.CreateInstance(Metadata.SampleControlType);
+
+ // Custom control-based sample options.
+ if (Metadata.SampleOptionsPaneType is not null)
+ {
+ SampleOptionsPaneInstance = (UIElement)Activator.CreateInstance(Metadata.SampleOptionsPaneType, SampleControlInstance);
+ }
+
+ // Source generater-based sample options
+ else if (SampleControlInstance is IToolkitSampleGeneratedOptionPropertyContainer propertyContainer)
+ {
+ // Pass the generated sample options to the displayed Control instance.
+ // Generated properties reference these in getters and setters.
+ propertyContainer.GeneratedPropertyMetadata = Metadata.GeneratedSampleOptions;
+
+ SampleOptionsPaneInstance = new GeneratedSampleOptionsRenderer
+ {
+ SampleOptions = propertyContainer.GeneratedPropertyMetadata
+ };
+ }
+ }
+
+ public static async Task GetMetadataFileContents(ToolkitSampleMetadata metadata, string fileExtension)
+ {
+ var filePath = GetPathToFileWithoutExtension(metadata.SampleControlType);
+
+ try
+ {
+ var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri($"{filePath}.{fileExtension.Trim('.')}"));
+ var textContents = await FileIO.ReadTextAsync(file);
+
+ // Remove toolkit attributes
+ textContents = Regex.Replace(textContents, @$"\s+?\[{nameof(ToolkitSampleAttribute).Replace("Attribute", "")}.+\]", "");
+ textContents = Regex.Replace(textContents, @$"\s+?\[{nameof(ToolkitSampleOptionsPaneAttribute).Replace("Attribute", "")}.+\]", "");
+
+ return textContents;
+ }
+ catch (Exception ex)
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Compute path to a code file bundled in the app using type information.
+ /// Assumes file path in project matches namespace.
+ ///
+ public static string GetPathToFileWithoutExtension(Type type)
+ {
+ var simpleAssemblyName = type.Assembly.GetName().Name;
+ var typeNamespace = type.Namespace;
+
+ var folderPath = typeNamespace.Replace(simpleAssemblyName, "").Trim('.').Replace('.', '/');
+
+ return $"ms-appx:///{simpleAssemblyName}/{folderPath}/{type.Name}";
+ }
+ }
+}
diff --git a/Common/Labs.MultiTarget.props b/Common/Labs.MultiTarget.props
index 55e17b9f9..a5e5c7bfb 100644
--- a/Common/Labs.MultiTarget.props
+++ b/Common/Labs.MultiTarget.props
@@ -12,11 +12,14 @@
-
-
+
+
+
+
+
@@ -26,4 +29,13 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Common/Labs.Uwp.props b/Common/Labs.Uwp.props
index e0cec3606..1a227f5f6 100644
--- a/Common/Labs.Uwp.props
+++ b/Common/Labs.Uwp.props
@@ -148,9 +148,9 @@
-
+
{210476d6-42cc-4d01-b027-478145bea8fe}
- CommunityToolkit.Labs.Core
+ CommunityToolkit.Labs.Core.SourceGenerators
diff --git a/Common/Labs.Wasm.props b/Common/Labs.Wasm.props
index 165cf1f3a..af858a5db 100644
--- a/Common/Labs.Wasm.props
+++ b/Common/Labs.Wasm.props
@@ -45,7 +45,7 @@
-
diff --git a/Common/Labs.WinAppSdk.props b/Common/Labs.WinAppSdk.props
index e9b012f7e..d0e12e126 100644
--- a/Common/Labs.WinAppSdk.props
+++ b/Common/Labs.WinAppSdk.props
@@ -13,11 +13,17 @@
true
WINAPPSDK
-
+
+
+
-
+
+
+
+
\ No newline at end of file
diff --git a/CommunityToolkit.Labs.Uwp/CommunityToolkit.Labs.Uwp.csproj b/CommunityToolkit.Labs.Uwp/CommunityToolkit.Labs.Uwp.csproj
index 27545b7cd..fc4e9fff7 100644
--- a/CommunityToolkit.Labs.Uwp/CommunityToolkit.Labs.Uwp.csproj
+++ b/CommunityToolkit.Labs.Uwp/CommunityToolkit.Labs.Uwp.csproj
@@ -16,13 +16,15 @@
Designer
-
+
+ {5cb6662f-590f-4250-a19d-e27fee9c2876}
+ CommunityToolkit.Labs.Core.SourceGenerators
+
{a14189c0-39a8-4fbe-bf86-a78a94654c48}
CanvasLayout.Sample
-
\ No newline at end of file
diff --git a/CommunityToolkit.Labs.Uwp/Package.appxmanifest b/CommunityToolkit.Labs.Uwp/Package.appxmanifest
index 07ed0faab..7cfaaa0cb 100644
--- a/CommunityToolkit.Labs.Uwp/Package.appxmanifest
+++ b/CommunityToolkit.Labs.Uwp/Package.appxmanifest
@@ -7,7 +7,7 @@
IgnorableNamespaces="uap mp">
diff --git a/CommunityToolkit.Labs.WinAppSdk/CommunityToolkit.Labs.WinAppSdk.csproj b/CommunityToolkit.Labs.WinAppSdk/CommunityToolkit.Labs.WinAppSdk.csproj
index 9a134624e..bd459aa34 100644
--- a/CommunityToolkit.Labs.WinAppSdk/CommunityToolkit.Labs.WinAppSdk.csproj
+++ b/CommunityToolkit.Labs.WinAppSdk/CommunityToolkit.Labs.WinAppSdk.csproj
@@ -20,9 +20,9 @@
-
+
{210476d6-42cc-4d01-b027-478145bea8fe}
- CommunityToolkit.Labs.Core
+ CommunityToolkit.Labs.Core.SourceGenerators
diff --git a/Labs/CanvasLayout/CanvasLayout.sln b/Labs/CanvasLayout/CanvasLayout.sln
index 1fa4e01e3..febbe0bd9 100644
--- a/Labs/CanvasLayout/CanvasLayout.sln
+++ b/Labs/CanvasLayout/CanvasLayout.sln
@@ -15,7 +15,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Labs.Core"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platforms", "Platforms", "{E6AFDCD6-F59A-4EC4-BCA7-1E47A5A15751}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CanvasLayout.WinAppSdk", "samples\CanvasLayout.WinAppSdk\CanvasLayout.WinAppSdk.csproj", "{D6A5ACBA-582D-4A52-9452-1D143CA4C51F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CanvasLayout.WinAppSdk", "samples\CanvasLayout.WinAppSdk\CanvasLayout.WinAppSdk.csproj", "{70DF1194-D158-473E-B350-F630231FB328}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
@@ -144,36 +144,36 @@ Global
{21128E83-CA73-49E2-B8DA-552B9CE81A25}.Release|x64.Build.0 = Release|Any CPU
{21128E83-CA73-49E2-B8DA-552B9CE81A25}.Release|x86.ActiveCfg = Release|Any CPU
{21128E83-CA73-49E2-B8DA-552B9CE81A25}.Release|x86.Build.0 = Release|Any CPU
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|Any CPU.ActiveCfg = Debug|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|Any CPU.Build.0 = Debug|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|Any CPU.Deploy.0 = Debug|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|ARM.ActiveCfg = Debug|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|ARM.Build.0 = Debug|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|ARM.Deploy.0 = Debug|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|ARM64.ActiveCfg = Debug|arm64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|ARM64.Build.0 = Debug|arm64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|ARM64.Deploy.0 = Debug|arm64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|x64.ActiveCfg = Debug|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|x64.Build.0 = Debug|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|x64.Deploy.0 = Debug|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|x86.ActiveCfg = Debug|x86
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|x86.Build.0 = Debug|x86
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Debug|x86.Deploy.0 = Debug|x86
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|Any CPU.ActiveCfg = Release|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|Any CPU.Build.0 = Release|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|Any CPU.Deploy.0 = Release|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|ARM.ActiveCfg = Release|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|ARM.Build.0 = Release|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|ARM.Deploy.0 = Release|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|ARM64.ActiveCfg = Release|arm64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|ARM64.Build.0 = Release|arm64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|ARM64.Deploy.0 = Release|arm64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|x64.ActiveCfg = Release|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|x64.Build.0 = Release|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|x64.Deploy.0 = Release|x64
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|x86.ActiveCfg = Release|x86
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|x86.Build.0 = Release|x86
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F}.Release|x86.Deploy.0 = Release|x86
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|Any CPU.Build.0 = Debug|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|Any CPU.Deploy.0 = Debug|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|ARM.ActiveCfg = Debug|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|ARM.Build.0 = Debug|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|ARM.Deploy.0 = Debug|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|ARM64.ActiveCfg = Debug|arm64
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|ARM64.Build.0 = Debug|arm64
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|ARM64.Deploy.0 = Debug|arm64
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|x64.ActiveCfg = Debug|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|x64.Build.0 = Debug|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|x64.Deploy.0 = Debug|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|x86.ActiveCfg = Debug|x86
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|x86.Build.0 = Debug|x86
+ {70DF1194-D158-473E-B350-F630231FB328}.Debug|x86.Deploy.0 = Debug|x86
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|Any CPU.ActiveCfg = Release|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|Any CPU.Build.0 = Release|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|Any CPU.Deploy.0 = Release|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|ARM.ActiveCfg = Release|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|ARM.Build.0 = Release|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|ARM.Deploy.0 = Release|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|ARM64.ActiveCfg = Release|arm64
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|ARM64.Build.0 = Release|arm64
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|ARM64.Deploy.0 = Release|arm64
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|x64.ActiveCfg = Release|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|x64.Build.0 = Release|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|x64.Deploy.0 = Release|x64
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|x86.ActiveCfg = Release|x86
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|x86.Build.0 = Release|x86
+ {70DF1194-D158-473E-B350-F630231FB328}.Release|x86.Deploy.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -182,7 +182,7 @@ Global
{3BAAC2DA-7124-460E-A9A9-13138843CD57} = {E6AFDCD6-F59A-4EC4-BCA7-1E47A5A15751}
{6DC6B31C-D03C-4E53-A1A5-CAF51227440B} = {E6AFDCD6-F59A-4EC4-BCA7-1E47A5A15751}
{21128E83-CA73-49E2-B8DA-552B9CE81A25} = {E6AFDCD6-F59A-4EC4-BCA7-1E47A5A15751}
- {D6A5ACBA-582D-4A52-9452-1D143CA4C51F} = {E6AFDCD6-F59A-4EC4-BCA7-1E47A5A15751}
+ {70DF1194-D158-473E-B350-F630231FB328} = {E6AFDCD6-F59A-4EC4-BCA7-1E47A5A15751}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6D11723C-7575-40DF-9BC9-300A09554B5D}
diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/CanvasLayout.Sample.csproj b/Labs/CanvasLayout/samples/CanvasLayout.Sample/CanvasLayout.Sample.csproj
index 1fe429042..935bb389d 100644
--- a/Labs/CanvasLayout/samples/CanvasLayout.Sample/CanvasLayout.Sample.csproj
+++ b/Labs/CanvasLayout/samples/CanvasLayout.Sample/CanvasLayout.Sample.csproj
@@ -10,16 +10,24 @@
+
+
+
+
+
+
+
+
-
+
WinUITarget=$(WinUITarget)
diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleOne/SamplePage.xaml b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleOne/SamplePage.xaml
new file mode 100644
index 000000000..3124bb558
--- /dev/null
+++ b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleOne/SamplePage.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleOne/SamplePage.xaml.cs b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleOne/SamplePage.xaml.cs
new file mode 100644
index 000000000..b9e80ba2b
--- /dev/null
+++ b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleOne/SamplePage.xaml.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using CommunityToolkit.Labs.Core.SourceGenerators;
+using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+using System.Runtime.InteropServices.WindowsRuntime;
+
+#if !WINAPPSDK
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+#else
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Navigation;
+#endif
+
+// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
+
+namespace CanvasLayout.Sample.SampleOne
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ [ToolkitSampleBoolOption("IsTextVisible", "IsVisible", true)]
+
+ [ToolkitSampleMultiChoiceOption("TextForeground", label: "Teal", value: "#0ddc8c", title: "Text foreground")]
+ [ToolkitSampleMultiChoiceOption("TextForeground", label: "Sand", value: "#e7a676")]
+ [ToolkitSampleMultiChoiceOption("TextForeground", label: "Dull green", value: "#5d7577")]
+
+ [ToolkitSampleMultiChoiceOption("TextSize", label: "Small", value: "12", title: "Text size")]
+ [ToolkitSampleMultiChoiceOption("TextSize", label: "Normal", value: "16")]
+ [ToolkitSampleMultiChoiceOption("TextSize", label: "Big", value: "32")]
+
+ [ToolkitSampleMultiChoiceOption("TextFontFamily", label: "Segoe UI", value: "Segoe UI")]
+ [ToolkitSampleMultiChoiceOption("TextFontFamily", label: "Arial", value: "Arial")]
+ [ToolkitSampleMultiChoiceOption("TextFontFamily", label: "Consolas", value: "Consolas")]
+
+ [ToolkitSample(id: nameof(SamplePage), "Canvas Layout", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: "A canvas-like VirtualizingPanel for use in an ItemsControl")]
+ public sealed partial class SamplePage : Page
+ {
+ public SamplePage()
+ {
+ this.InitializeComponent();
+ }
+ }
+}
diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SamplePage.xaml b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SamplePage.xaml
deleted file mode 100644
index 89fc7ce05..000000000
--- a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SamplePage.xaml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SamplePage.xaml.cs b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SamplePage.xaml.cs
deleted file mode 100644
index 0204b7f0a..000000000
--- a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SamplePage.xaml.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using CommunityToolkit.Labs.Core;
-using CommunityToolkit.Labs.Core.Attributes;
-
-#if !WINAPPSDK
-using Windows.UI.Xaml;
-using Windows.UI.Xaml.Controls;
-using Windows.UI.Xaml.Controls.Primitives;
-using Windows.UI.Xaml.Data;
-using Windows.UI.Xaml.Input;
-using Windows.UI.Xaml.Media;
-using Windows.UI.Xaml.Navigation;
-#else
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Controls.Primitives;
-using Microsoft.UI.Xaml.Data;
-using Microsoft.UI.Xaml.Input;
-using Microsoft.UI.Xaml.Media;
-using Microsoft.UI.Xaml.Navigation;
-#endif
-
-// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
-
-namespace CanvasLayout.Sample
-{
- ///
- /// An empty page that can be used on its own or navigated to within a Frame.
- ///
- [ToolkitSample("Canvas Layout", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: "A canvas-like VirtualizingPanel for use in an ItemsControl")]
- public sealed partial class SamplePage : Page
- {
- public SamplePage()
- {
- this.InitializeComponent();
- }
- }
-}
diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SamplePage2.xaml.cs b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SamplePage2.xaml.cs
deleted file mode 100644
index e2c602da0..000000000
--- a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SamplePage2.xaml.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using CommunityToolkit.Labs.Core;
-using CommunityToolkit.Labs.Core.Attributes;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices.WindowsRuntime;
-using Windows.Foundation;
-using Windows.Foundation.Collections;
-
-#if WINAPPSDK
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Controls.Primitives;
-using Microsoft.UI.Xaml.Data;
-using Microsoft.UI.Xaml.Input;
-using Microsoft.UI.Xaml.Media;
-using Microsoft.UI.Xaml.Navigation;
-#else
-using Windows.UI.Xaml;
-using Windows.UI.Xaml.Controls;
-using Windows.UI.Xaml.Controls.Primitives;
-using Windows.UI.Xaml.Data;
-using Windows.UI.Xaml.Input;
-using Windows.UI.Xaml.Media;
-using Windows.UI.Xaml.Navigation;
-#endif
-
-namespace CanvasLayout.Sample
-{
- [ToolkitSample("Example sample", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: "An empty sample used to demonstrate the sample system.")]
- public sealed partial class SamplePage2 : UserControl
- {
- public SamplePage2()
- {
- this.InitializeComponent();
- }
- }
-}
diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SamplePage2.xaml b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePage2.xaml
similarity index 73%
rename from Labs/CanvasLayout/samples/CanvasLayout.Sample/SamplePage2.xaml
rename to Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePage2.xaml
index ebe9bba8d..793463c30 100644
--- a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SamplePage2.xaml
+++ b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePage2.xaml
@@ -1,5 +1,5 @@
-
+
diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePage2.xaml.cs b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePage2.xaml.cs
new file mode 100644
index 000000000..c6ff1026d
--- /dev/null
+++ b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePage2.xaml.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Labs.Core.SourceGenerators;
+using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+
+#if WINAPPSDK
+using Microsoft.UI.Xaml.Controls;
+#else
+using Windows.UI.Xaml.Controls;
+#endif
+
+namespace CanvasLayout.Sample.SampleTwo
+{
+ [ToolkitSample(id: nameof(SamplePage2), "Example sample", ToolkitSampleCategory.Controls, ToolkitSampleSubcategory.Layout, description: "An empty sample used to demonstrate the sample system.")]
+ public sealed partial class SamplePage2 : UserControl
+ {
+ public SamplePage2()
+ {
+ this.InitializeComponent();
+ }
+ }
+}
diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePageOptions.xaml b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePageOptions.xaml
new file mode 100644
index 000000000..12672f189
--- /dev/null
+++ b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePageOptions.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+ White
+ Green
+ Yellow
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePageOptions.xaml.cs b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePageOptions.xaml.cs
new file mode 100644
index 000000000..89cbcb591
--- /dev/null
+++ b/Labs/CanvasLayout/samples/CanvasLayout.Sample/SampleTwo/SamplePageOptions.xaml.cs
@@ -0,0 +1,73 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Labs.Core.SourceGenerators.Attributes;
+using Microsoft.UI.Xaml.Controls;
+
+#if WINAPPSDK
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Markup;
+using Microsoft.UI.Xaml.Media;
+#else
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Markup;
+using Windows.UI.Xaml.Media;
+#endif
+
+// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
+
+namespace CanvasLayout.Sample.SampleTwo
+{
+ [ToolkitSampleOptionsPane(sampleId: nameof(SamplePage2))]
+ public sealed partial class SamplePageOptions : UserControl
+ {
+ private readonly SamplePage2 _samplePage;
+ private SamplePage2.XamlNamedPropertyRelay _xamlProperties;
+
+ public SamplePageOptions(SamplePage2 samplePage)
+ {
+ Loaded += SamplePageOptions_Loaded;
+
+ _samplePage = samplePage;
+ _xamlProperties = new SamplePage2.XamlNamedPropertyRelay(_samplePage);
+
+ this.InitializeComponent();
+ }
+
+ private void SamplePageOptions_Loaded(object sender, RoutedEventArgs e)
+ {
+ Loaded -= SamplePageOptions_Loaded;
+
+ CustomText.Text = _xamlProperties.PrimaryText.Text;
+ FontSizeSlider.Value = _xamlProperties.PrimaryText.FontSize;
+ }
+
+ private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ _xamlProperties.PrimaryText.Text = ((TextBox)sender).Text;
+ }
+
+ private void OnRadioButtonSelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (sender is RadioButtons radioButtons)
+ {
+ if (radioButtons.SelectedItem is null)
+ return;
+
+ var selectedColor = (string)radioButtons.SelectedItem;
+
+ _xamlProperties.PrimaryText.Foreground = (SolidColorBrush)XamlBindingHelper.ConvertValue(typeof(SolidColorBrush), selectedColor);
+ }
+ }
+
+ private void Slider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
+ {
+ if (_xamlProperties.PrimaryText is not null && IsLoaded && _samplePage.IsLoaded)
+ _xamlProperties.PrimaryText.FontSize = ((Slider)sender).Value;
+ }
+ }
+}
diff --git a/Labs/CanvasLayout/samples/CanvasLayout.Uwp/CanvasLayout.Uwp.csproj b/Labs/CanvasLayout/samples/CanvasLayout.Uwp/CanvasLayout.Uwp.csproj
index bcf7c0f23..1271ecbac 100644
--- a/Labs/CanvasLayout/samples/CanvasLayout.Uwp/CanvasLayout.Uwp.csproj
+++ b/Labs/CanvasLayout/samples/CanvasLayout.Uwp/CanvasLayout.Uwp.csproj
@@ -21,7 +21,6 @@
{1c029cc0-bb45-45ff-ad34-086ec4840db0}
CommunityToolkit.Labs.Uwp.UI.CanvasLayout
-
{a14189c0-39a8-4fbe-bf86-a78a94654c48}
CanvasLayout.Sample
diff --git a/Toolkit.Labs.All.sln b/Toolkit.Labs.All.sln
index b73f78ac8..6db94c94e 100644
--- a/Toolkit.Labs.All.sln
+++ b/Toolkit.Labs.All.sln
@@ -32,11 +32,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CanvasLayout.Wasm", "Labs\C
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{09003B35-7A35-4BD1-9A26-5CFD02AB88DD}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Labs.Core", "Common\CommunityToolkit.Labs.Core\CommunityToolkit.Labs.Core.csproj", "{210476D6-42CC-4D01-B027-478145BEA8FE}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Labs.Core.SourceGenerators", "Common\CommunityToolkit.Labs.Core.SourceGenerators\CommunityToolkit.Labs.Core.SourceGenerators.csproj", "{5CB6662F-590F-4250-A19D-E27FEE9C2876}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Labs.WinAppSdk", "CommunityToolkit.Labs.WinAppSdk\CommunityToolkit.Labs.WinAppSdk.csproj", "{05AE3090-DAEE-4629-856B-120034CC4B1A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Labs.Core.SourceGenerators.XamlNamedPropertyRelay", "Common\CommunityToolkit.Labs.Core.SourceGenerators.XamlNamedPropertyRelay\CommunityToolkit.Labs.Core.SourceGenerators.XamlNamedPropertyRelay.csproj", "{1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CanvasLayout.WinAppSdk", "Labs\CanvasLayout\samples\CanvasLayout.WinAppSdk\CanvasLayout.WinAppSdk.csproj", "{3150A3C5-0DC3-4D6D-9598-1F767A7F323B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Labs.Core.SourceGenerators.Tests", "Common\CommunityToolkit.Labs.Core.SourceGenerators.Tests\CommunityToolkit.Labs.Core.SourceGenerators.Tests\CommunityToolkit.Labs.Core.SourceGenerators.Tests.csproj", "{5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Labs.WinAppSdk", "CommunityToolkit.Labs.WinAppSdk\CommunityToolkit.Labs.WinAppSdk.csproj", "{53E592FC-E73C-474B-89C3-0570854CB160}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CanvasLayout.WinAppSdk", "Labs\CanvasLayout\samples\CanvasLayout.WinAppSdk\CanvasLayout.WinAppSdk.csproj", "{25341845-BFEE-47E0-A6A9-16CADDF829C3}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
@@ -195,85 +199,126 @@ Global
{E8E5AAAA-B15B-4B35-8673-118F6417B6F2}.Release|x64.Build.0 = Release|Any CPU
{E8E5AAAA-B15B-4B35-8673-118F6417B6F2}.Release|x86.ActiveCfg = Release|Any CPU
{E8E5AAAA-B15B-4B35-8673-118F6417B6F2}.Release|x86.Build.0 = Release|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Debug|ARM.ActiveCfg = Debug|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Debug|ARM.Build.0 = Debug|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Debug|ARM64.ActiveCfg = Debug|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Debug|ARM64.Build.0 = Debug|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Debug|x64.ActiveCfg = Debug|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Debug|x64.Build.0 = Debug|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Debug|x86.ActiveCfg = Debug|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Debug|x86.Build.0 = Debug|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Release|Any CPU.Build.0 = Release|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Release|ARM.ActiveCfg = Release|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Release|ARM.Build.0 = Release|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Release|ARM64.ActiveCfg = Release|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Release|ARM64.Build.0 = Release|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Release|x64.ActiveCfg = Release|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Release|x64.Build.0 = Release|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Release|x86.ActiveCfg = Release|Any CPU
- {210476D6-42CC-4D01-B027-478145BEA8FE}.Release|x86.Build.0 = Release|Any CPU
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|Any CPU.ActiveCfg = Debug|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|Any CPU.Build.0 = Debug|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|Any CPU.Deploy.0 = Debug|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|ARM.ActiveCfg = Debug|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|ARM.Build.0 = Debug|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|ARM.Deploy.0 = Debug|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|ARM64.ActiveCfg = Debug|arm64
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|ARM64.Build.0 = Debug|arm64
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|ARM64.Deploy.0 = Debug|arm64
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|x64.ActiveCfg = Debug|x64
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|x64.Build.0 = Debug|x64
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|x64.Deploy.0 = Debug|x64
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|x86.ActiveCfg = Debug|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|x86.Build.0 = Debug|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Debug|x86.Deploy.0 = Debug|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Release|Any CPU.ActiveCfg = Release|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Release|Any CPU.Build.0 = Release|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Release|ARM.ActiveCfg = Release|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Release|ARM.Build.0 = Release|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Release|ARM.Deploy.0 = Release|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Release|ARM64.ActiveCfg = Release|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Release|ARM64.Build.0 = Release|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Release|ARM64.Deploy.0 = Release|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Release|x64.ActiveCfg = Release|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Release|x64.Build.0 = Release|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Release|x64.Deploy.0 = Release|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Release|x86.ActiveCfg = Release|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Release|x86.Build.0 = Release|x86
- {05AE3090-DAEE-4629-856B-120034CC4B1A}.Release|x86.Deploy.0 = Release|x86
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|Any CPU.ActiveCfg = Debug|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|Any CPU.Build.0 = Debug|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|Any CPU.Deploy.0 = Debug|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|ARM.ActiveCfg = Debug|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|ARM.Build.0 = Debug|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|ARM.Deploy.0 = Debug|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|ARM64.ActiveCfg = Debug|arm64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|ARM64.Build.0 = Debug|arm64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|ARM64.Deploy.0 = Debug|arm64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|x64.ActiveCfg = Debug|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|x64.Build.0 = Debug|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|x64.Deploy.0 = Debug|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|x86.ActiveCfg = Debug|x86
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|x86.Build.0 = Debug|x86
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Debug|x86.Deploy.0 = Debug|x86
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|Any CPU.ActiveCfg = Release|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|Any CPU.Build.0 = Release|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|Any CPU.Deploy.0 = Release|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|ARM.ActiveCfg = Release|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|ARM.Build.0 = Release|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|ARM.Deploy.0 = Release|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|ARM64.ActiveCfg = Release|arm64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|ARM64.Build.0 = Release|arm64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|ARM64.Deploy.0 = Release|arm64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|x64.ActiveCfg = Release|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|x64.Build.0 = Release|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|x64.Deploy.0 = Release|x64
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|x86.ActiveCfg = Release|x86
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|x86.Build.0 = Release|x86
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B}.Release|x86.Deploy.0 = Release|x86
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Debug|ARM.Build.0 = Debug|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Debug|x64.Build.0 = Debug|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Debug|x86.Build.0 = Debug|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Release|ARM.ActiveCfg = Release|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Release|ARM.Build.0 = Release|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Release|ARM64.Build.0 = Release|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Release|x64.ActiveCfg = Release|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Release|x64.Build.0 = Release|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Release|x86.ActiveCfg = Release|Any CPU
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876}.Release|x86.Build.0 = Release|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Debug|ARM.Build.0 = Debug|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Debug|x64.Build.0 = Debug|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Debug|x86.Build.0 = Debug|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Release|ARM.ActiveCfg = Release|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Release|ARM.Build.0 = Release|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Release|ARM64.Build.0 = Release|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Release|x64.ActiveCfg = Release|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Release|x64.Build.0 = Release|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Release|x86.ActiveCfg = Release|Any CPU
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D}.Release|x86.Build.0 = Release|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Debug|ARM.Build.0 = Debug|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Debug|x64.Build.0 = Debug|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Debug|x86.Build.0 = Debug|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Release|ARM.ActiveCfg = Release|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Release|ARM.Build.0 = Release|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Release|ARM64.Build.0 = Release|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Release|x64.ActiveCfg = Release|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Release|x64.Build.0 = Release|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Release|x86.ActiveCfg = Release|Any CPU
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68}.Release|x86.Build.0 = Release|Any CPU
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|Any CPU.Build.0 = Debug|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|Any CPU.Deploy.0 = Debug|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|ARM.ActiveCfg = Debug|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|ARM.Build.0 = Debug|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|ARM.Deploy.0 = Debug|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|ARM64.ActiveCfg = Debug|arm64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|ARM64.Build.0 = Debug|arm64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|ARM64.Deploy.0 = Debug|arm64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|x64.ActiveCfg = Debug|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|x64.Build.0 = Debug|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|x64.Deploy.0 = Debug|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|x86.ActiveCfg = Debug|x86
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|x86.Build.0 = Debug|x86
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Debug|x86.Deploy.0 = Debug|x86
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|Any CPU.ActiveCfg = Release|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|Any CPU.Build.0 = Release|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|Any CPU.Deploy.0 = Release|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|ARM.ActiveCfg = Release|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|ARM.Build.0 = Release|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|ARM.Deploy.0 = Release|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|ARM64.ActiveCfg = Release|arm64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|ARM64.Build.0 = Release|arm64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|ARM64.Deploy.0 = Release|arm64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|x64.ActiveCfg = Release|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|x64.Build.0 = Release|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|x64.Deploy.0 = Release|x64
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|x86.ActiveCfg = Release|x86
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|x86.Build.0 = Release|x86
+ {53E592FC-E73C-474B-89C3-0570854CB160}.Release|x86.Deploy.0 = Release|x86
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|Any CPU.Build.0 = Debug|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|Any CPU.Deploy.0 = Debug|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|ARM.ActiveCfg = Debug|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|ARM.Build.0 = Debug|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|ARM.Deploy.0 = Debug|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|ARM64.ActiveCfg = Debug|arm64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|ARM64.Build.0 = Debug|arm64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|ARM64.Deploy.0 = Debug|arm64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|x64.ActiveCfg = Debug|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|x64.Build.0 = Debug|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|x64.Deploy.0 = Debug|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|x86.ActiveCfg = Debug|x86
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|x86.Build.0 = Debug|x86
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Debug|x86.Deploy.0 = Debug|x86
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|Any CPU.ActiveCfg = Release|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|Any CPU.Build.0 = Release|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|Any CPU.Deploy.0 = Release|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|ARM.ActiveCfg = Release|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|ARM.Build.0 = Release|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|ARM.Deploy.0 = Release|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|ARM64.ActiveCfg = Release|arm64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|ARM64.Build.0 = Release|arm64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|ARM64.Deploy.0 = Release|arm64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|x64.ActiveCfg = Release|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|x64.Build.0 = Release|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|x64.Deploy.0 = Release|x64
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|x86.ActiveCfg = Release|x86
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|x86.Build.0 = Release|x86
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3}.Release|x86.Deploy.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -285,8 +330,10 @@ Global
{FE19FFF0-6AB6-4FC7-BFDF-B6499153DCD5} = {EDD2FCF0-74FE-4AB9-B40A-7B2A4E89D59C}
{A14189C0-39A8-4FBE-BF86-A78A94654C48} = {86E3CC4D-1359-4249-9C5B-C193BF65633D}
{E8E5AAAA-B15B-4B35-8673-118F6417B6F2} = {86E3CC4D-1359-4249-9C5B-C193BF65633D}
- {210476D6-42CC-4D01-B027-478145BEA8FE} = {09003B35-7A35-4BD1-9A26-5CFD02AB88DD}
- {3150A3C5-0DC3-4D6D-9598-1F767A7F323B} = {86E3CC4D-1359-4249-9C5B-C193BF65633D}
+ {5CB6662F-590F-4250-A19D-E27FEE9C2876} = {09003B35-7A35-4BD1-9A26-5CFD02AB88DD}
+ {1683BA1A-5D66-4488-B7CA-6DF3FDE2701D} = {09003B35-7A35-4BD1-9A26-5CFD02AB88DD}
+ {5BD4E79C-3744-4E89-A6F2-17FBAB7E3F68} = {09003B35-7A35-4BD1-9A26-5CFD02AB88DD}
+ {25341845-BFEE-47E0-A6A9-16CADDF829C3} = {86E3CC4D-1359-4249-9C5B-C193BF65633D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1F0A4823-84EF-41AA-BBF9-A07B38DDC555}