diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml
index 2731240a8a..ffb609b7a6 100644
--- a/.github/workflows/dotnet-build.yml
+++ b/.github/workflows/dotnet-build.yml
@@ -42,6 +42,8 @@ env:
PathToCommunityToolkitCameraAnalyzersCodeFixCsproj: 'src/CommunityToolkit.Maui.Camera.Analyzers.CodeFixes/CommunityToolkit.Maui.Camera.Analyzers.CodeFixes.csproj'
PathToCommunityToolkitMediaElementAnalyzersCodeFixCsproj: 'src/CommunityToolkit.Maui.MediaElement.Analyzers.CodeFixes/CommunityToolkit.Maui.MediaElement.Analyzers.CodeFixes.csproj'
PathToCommunityToolkitAnalyzersUnitTestProjectDirectory: 'src/CommunityToolkit.Maui.Analyzers.UnitTests'
+ PathToCommunityToolkitSourceGeneratorsInternalUnitTestDirectory: 'src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests'
+ PathToCommunityToolkitSourceGeneratorsInternalUnitTestCsproj: 'src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.csproj'
PathToCommunityToolkitAnalyzersBenchmarkCsproj: 'src/CommunityToolkit.Maui.Analyzers.Benchmarks/CommunityToolkit.Maui.Analyzers.Benchmarks.csproj'
CommunityToolkitLibrary_Xcode_Version: '26.1'
@@ -209,6 +211,12 @@ jobs:
run: |
cd ${{ env.PathToCommunityToolkitAnalyzersUnitTestProjectDirectory }}
dotnet run -c Release --results-directory "${{ runner.temp }}" --coverage --coverage-output "${{ runner.temp }}/ut-analyzers.cobertura.xml" --coverage-output-format cobertura --report-xunit
+
+ - name: Run CommunityToolkit Source Generators Internal UnitTests
+ if: runner.os == 'Windows'
+ run: |
+ cd ${{ env.PathToCommunityToolkitSourceGeneratorsInternalUnitTestDirectory }}
+ dotnet run -c Release --results-directory "${{ runner.temp }}" --coverage --coverage-output "${{ runner.temp }}/ut-sourcegenerators-internal.cobertura.xml" --coverage-output-format cobertura --report-xunit
- name: Run CommunityToolkit UnitTests
run: |
diff --git a/Directory.Build.props b/Directory.Build.props
index 33e658a5af..73569d08ad 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -235,6 +235,7 @@
+
diff --git a/samples/CommunityToolkit.Maui.Sample.slnx b/samples/CommunityToolkit.Maui.Sample.slnx
index eaa637530e..10daac0d10 100644
--- a/samples/CommunityToolkit.Maui.Sample.slnx
+++ b/samples/CommunityToolkit.Maui.Sample.slnx
@@ -27,6 +27,7 @@
+
diff --git a/src/CommunityToolkit.Maui.Analyzers.UnitTests/CommunityToolkit.Maui.Analyzers.UnitTests.csproj b/src/CommunityToolkit.Maui.Analyzers.UnitTests/CommunityToolkit.Maui.Analyzers.UnitTests.csproj
index 6872375c9b..140af48816 100644
--- a/src/CommunityToolkit.Maui.Analyzers.UnitTests/CommunityToolkit.Maui.Analyzers.UnitTests.csproj
+++ b/src/CommunityToolkit.Maui.Analyzers.UnitTests/CommunityToolkit.Maui.Analyzers.UnitTests.csproj
@@ -6,7 +6,8 @@
$(BaseIntermediateOutputPath)\GF
true
true
-
+ false
+ true
Exe
CommunityToolkit.Maui.Analyzers.UnitTests
diff --git a/src/CommunityToolkit.Maui.Core/Primitives/Defaults/ProgressBarAnimationBehaviorDefaults.cs b/src/CommunityToolkit.Maui.Core/Primitives/Defaults/ProgressBarAnimationBehaviorDefaults.cs
new file mode 100644
index 0000000000..f105fabb8b
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Core/Primitives/Defaults/ProgressBarAnimationBehaviorDefaults.cs
@@ -0,0 +1,8 @@
+namespace CommunityToolkit.Maui.Core;
+
+static class ProgressBarAnimationBehaviorDefaults
+{
+ public const double Progress = 0.0;
+ public const uint Length = 500;
+ public static Easing Easing { get; } = Easing.Linear;
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.Core/Primitives/Defaults/UserStoppedTypingBehaviorDefaults.cs b/src/CommunityToolkit.Maui.Core/Primitives/Defaults/UserStoppedTypingBehaviorDefaults.cs
new file mode 100644
index 0000000000..3fd0bf7024
--- /dev/null
+++ b/src/CommunityToolkit.Maui.Core/Primitives/Defaults/UserStoppedTypingBehaviorDefaults.cs
@@ -0,0 +1,8 @@
+namespace CommunityToolkit.Maui.Core;
+
+static class UserStoppedTypingBehaviorDefaults
+{
+ public const int StoppedTypingTimeThreshold = 1000;
+ public const int MinimumLengthThreshold = 0;
+ public const bool ShouldDismissKeyboardAutomatically = false;
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BaseTest.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BaseTest.cs
new file mode 100644
index 0000000000..7c2515f991
--- /dev/null
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BaseTest.cs
@@ -0,0 +1,49 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Testing;
+using Xunit;
+
+namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests;
+
+public abstract class BaseTest
+{
+ protected static async Task VerifySourceGeneratorAsync(string source, string expectedAttribute, params List<(string FileName, string GeneratedFile)> expectedGenerated)
+ {
+ const string sourceGeneratorNamespace = "CommunityToolkit.Maui.SourceGenerators.Internal";
+ const string bindablePropertyAttributeGeneratedFileName = "BindablePropertyAttribute.g.cs";
+ var sourceGeneratorFullName = typeof(BindablePropertyAttributeSourceGenerator).FullName ?? throw new InvalidOperationException("Source Generator Type Path cannot be null");
+
+ var test = new CSharpSourceGeneratorTest
+ {
+#if NET10_0
+ ReferenceAssemblies = Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Net.Net100,
+#else
+#error ReferenceAssemblies must be updated to current version of .NET
+#endif
+ TestState =
+ {
+ Sources = { source },
+
+ AdditionalReferences =
+ {
+ MetadataReference.CreateFromFile(typeof(Microsoft.Maui.Controls.BindableObject).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(Microsoft.Maui.Controls.BindableProperty).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(Microsoft.Maui.Controls.BindingMode).Assembly.Location)
+ }
+ }
+ };
+
+ var expectedAttributeText = Microsoft.CodeAnalysis.Text.SourceText.From(expectedAttribute, System.Text.Encoding.UTF8);
+ var bindablePropertyAttributeFilePath = Path.Combine(sourceGeneratorNamespace, sourceGeneratorFullName, bindablePropertyAttributeGeneratedFileName);
+ test.TestState.GeneratedSources.Add((bindablePropertyAttributeFilePath, expectedAttributeText));
+
+ foreach (var generatedFile in expectedGenerated.Where(static x => !string.IsNullOrEmpty(x.GeneratedFile)))
+ {
+ var expectedGeneratedText = Microsoft.CodeAnalysis.Text.SourceText.From(generatedFile.GeneratedFile, System.Text.Encoding.UTF8);
+ var generatedFilePath = Path.Combine(sourceGeneratorNamespace, sourceGeneratorFullName, generatedFile.FileName);
+ test.TestState.GeneratedSources.Add((generatedFilePath, expectedGeneratedText));
+ }
+
+ await test.RunAsync(TestContext.Current.CancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BaseBindablePropertyAttributeSourceGeneratorTest.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BaseBindablePropertyAttributeSourceGeneratorTest.cs
new file mode 100644
index 0000000000..a03cc4af07
--- /dev/null
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BaseBindablePropertyAttributeSourceGeneratorTest.cs
@@ -0,0 +1,37 @@
+namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.BindablePropertyAttributeSourceGeneratorTests;
+
+public class BaseBindablePropertyAttributeSourceGeneratorTest : BaseTest
+{
+ protected const string defaultTestClassName = "TestView";
+ protected const string defaultTestNamespace = "TestNamespace";
+
+ protected const string expectedAttribute =
+ /* language=C#-test */
+ //lang=csharp
+ """
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+
+ #pragma warning disable
+ #nullable enable
+ namespace CommunityToolkit.Maui;
+
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ [global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
+ sealed partial class BindablePropertyAttribute : global::System.Attribute
+ {
+ public string? PropertyName { get; }
+ public global::System.Type? DeclaringType { get; set; }
+ public object? DefaultValue { get; set; }
+ public global::Microsoft.Maui.Controls.BindingMode DefaultBindingMode { get; set; }
+ public string ValidateValueMethodName { get; set; } = string.Empty;
+ public string PropertyChangedMethodName { get; set; } = string.Empty;
+ public string PropertyChangingMethodName { get; set; } = string.Empty;
+ public string CoerceValueMethodName { get; set; } = string.Empty;
+ public string DefaultValueCreatorMethodName { get; set; } = string.Empty;
+ }
+ """;
+
+ protected static Task VerifySourceGeneratorAsync(string source, string expectedGenerated) =>
+ VerifySourceGeneratorAsync(source, expectedAttribute, ($"{defaultTestClassName}.g.cs", expectedGenerated));
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGeneratorTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGeneratorTests.cs
new file mode 100644
index 0000000000..502892253a
--- /dev/null
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/BindablePropertyAttributeSourceGeneratorTests.cs
@@ -0,0 +1,354 @@
+using Xunit;
+
+namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.BindablePropertyAttributeSourceGeneratorTests;
+
+public class BindablePropertyAttributeSourceGeneratorTests : BaseBindablePropertyAttributeSourceGeneratorTest
+{
+ [Fact]
+ public async Task GenerateBindableProperty_SimpleExample_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindablePropertyAttribute]
+ public partial string Text { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_WithDefaultValue_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindablePropertyAttribute(DefaultValue = "Hello")]
+ public partial string Text { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (string)"Hello", Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_WithNewKeyword_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindablePropertyAttribute]
+ public new partial string Text { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public new static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public new partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_NullableReferenceType_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindablePropertyAttribute]
+ public partial string? Text { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string? Text { get => (string? )GetValue(TextProperty); set => SetValue(TextProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_MultipleProperties_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindablePropertyAttribute]
+ public partial string Text { get; set; }
+
+ [BindablePropertyAttribute]
+ public partial int Number { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
+
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty NumberProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Number", typeof(int), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial int Number { get => (int)GetValue(NumberProperty); set => SetValue(NumberProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_WithAllParameters_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindablePropertyAttribute(
+ DefaultValue = 42,
+ DefaultBindingMode = BindingMode.TwoWay,
+ ValidateValueMethodName = "ValidateValue",
+ PropertyChangedMethodName = "OnPropertyChanged",
+ PropertyChangingMethodName = "OnPropertyChanging",
+ CoerceValueMethodName = "CoerceValue",
+ DefaultValueCreatorMethodName = "CreateDefaultValue")]
+ public partial int Value { get; set; }
+
+ static bool ValidateValue(BindableObject bindable, object value) => true;
+ static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue) { }
+ static void OnPropertyChanging(BindableObject bindable, object oldValue, object newValue) { }
+ static object CoerceValue(BindableObject bindable, object value) => value;
+ static object CreateDefaultValue(BindableObject bindable) => 0;
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty ValueProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Value", typeof(int), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (int)42, (Microsoft.Maui.Controls.BindingMode)Microsoft.Maui.Controls.BindingMode.TwoWay, ValidateValue, OnPropertyChanged, OnPropertyChanging, CoerceValue, CreateDefaultValue);
+ public partial int Value { get => (int)GetValue(ValueProperty); set => SetValue(ValueProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_InternalClass_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ internal partial class TestView : View
+ {
+ [BindablePropertyAttribute]
+ public partial string Text { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ internal partial class TestView
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_NoAttribute_GeneratesNoCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ public string Text { get; set; }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, string.Empty);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_EmptyClass_GeneratesAttributeOnly()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, string.Empty);
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/EdgeCaseTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/EdgeCaseTests.cs
new file mode 100644
index 0000000000..8391d60fc2
--- /dev/null
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/EdgeCaseTests.cs
@@ -0,0 +1,363 @@
+using Xunit;
+
+namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.BindablePropertyAttributeSourceGeneratorTests;
+
+public class EdgeCaseTests : BaseBindablePropertyAttributeSourceGeneratorTest
+{
+ [Fact]
+ public async Task GenerateBindableProperty_PropertyWithReservedKeywords_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindableProperty]
+ public partial string @class { get; set; }
+
+ [BindableProperty]
+ public partial string @namespace { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty classProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("@class", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string @class { get => (string)GetValue(classProperty); set => SetValue(classProperty, value); }
+
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty namespaceProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("@namespace", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string @namespace { get => (string)GetValue(namespaceProperty); set => SetValue(namespaceProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_NullableValueTypes_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using System;
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindableProperty]
+ public partial int? NullableInt { get; set; }
+
+ [BindableProperty]
+ public partial DateTime? NullableDateTime { get; set; }
+
+ [BindableProperty]
+ public partial bool? NullableBool { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty NullableIntProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("NullableInt", typeof(int? ), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial int? NullableInt { get => (int? )GetValue(NullableIntProperty); set => SetValue(NullableIntProperty, value); }
+
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty NullableDateTimeProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("NullableDateTime", typeof(System.DateTime? ), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial System.DateTime? NullableDateTime { get => (System.DateTime? )GetValue(NullableDateTimeProperty); set => SetValue(NullableDateTimeProperty, value); }
+
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty NullableBoolProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("NullableBool", typeof(bool? ), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial bool? NullableBool { get => (bool? )GetValue(NullableBoolProperty); set => SetValue(NullableBoolProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_ArrayTypes_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindableProperty]
+ public partial string[] StringArray { get; set; }
+
+ [BindableProperty]
+ public partial int[,] MultiDimensionalArray { get; set; }
+
+ [BindableProperty]
+ public partial byte[][] JaggedArray { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty StringArrayProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("StringArray", typeof(string[]), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string[] StringArray { get => (string[])GetValue(StringArrayProperty); set => SetValue(StringArrayProperty, value); }
+
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty MultiDimensionalArrayProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("MultiDimensionalArray", typeof(int[, ]), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial int[, ] MultiDimensionalArray { get => (int[, ])GetValue(MultiDimensionalArrayProperty); set => SetValue(MultiDimensionalArrayProperty, value); }
+
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty JaggedArrayProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("JaggedArray", typeof(byte[][]), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial byte[][] JaggedArray { get => (byte[][])GetValue(JaggedArrayProperty); set => SetValue(JaggedArrayProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_LongNamespaces_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace Very.Long.Namespace.With.Many.Segments.TestNamespace;
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindableProperty]
+ public partial string Text { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace Very.Long.Namespace.With.Many.Segments.TestNamespace;
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof(Very.Long.Namespace.With.Many.Segments.TestNamespace.TestView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_GlobalNamespace_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindableProperty]
+ public partial string Text { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof(TestView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_SpecialCharactersInPropertyName_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindableProperty()]
+ public partial string Property_With_Underscores { get; set; }
+
+ [BindableProperty()]
+ public partial string Property123WithNumbers { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty Property_With_UnderscoresProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Property_With_Underscores", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string Property_With_Underscores { get => (string)GetValue(Property_With_UnderscoresProperty); set => SetValue(Property_With_UnderscoresProperty, value); }
+
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty Property123WithNumbersProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Property123WithNumbers", typeof(string), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string Property123WithNumbers { get => (string)GetValue(Property123WithNumbersProperty); set => SetValue(Property123WithNumbersProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_WithComplexDefaultValues_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindableProperty(DefaultValue = true)]
+ public partial bool IsEnabled { get; set; }
+
+ [BindableProperty(DefaultValue = 3.14)]
+ public partial double Pi { get; set; }
+
+ [BindableProperty(DefaultValue = 'A')]
+ public partial char Letter { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty IsEnabledProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("IsEnabled", typeof(bool), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (bool)true, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial bool IsEnabled { get => (bool)GetValue(IsEnabledProperty); set => SetValue(IsEnabledProperty, value); }
+
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty PiProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Pi", typeof(double), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (double)3.14, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial double Pi { get => (double)GetValue(PiProperty); set => SetValue(PiProperty, value); }
+
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty LetterProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Letter", typeof(char), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), (char)'A', Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial char Letter { get => (char)GetValue(LetterProperty); set => SetValue(LetterProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/IntegrationTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/IntegrationTests.cs
new file mode 100644
index 0000000000..3df8bd95cc
--- /dev/null
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyAttributeSourceGeneratorTests/IntegrationTests.cs
@@ -0,0 +1,317 @@
+using CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Testing;
+using Xunit;
+
+namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.BindablePropertyAttributeSourceGeneratorTests;
+
+public class IntegrationTests : BaseBindablePropertyAttributeSourceGeneratorTest
+{
+ [Fact]
+ public async Task GenerateBindableProperty_ComplexInheritanceScenario_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public abstract partial class BaseView : View
+ {
+ [BindableProperty]
+ public partial string BaseText { get; set; }
+ }
+
+ public partial class DerivedView : BaseView
+ {
+ [BindableProperty]
+ public partial string DerivedText { get; set; }
+
+ [BindableProperty]
+ public new partial string BaseText { get; set; }
+ }
+ """;
+
+ const string expectedBaseGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class BaseView
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty BaseTextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("BaseText", typeof(string), typeof(TestNamespace.BaseView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string BaseText { get => (string)GetValue(BaseTextProperty); set => SetValue(BaseTextProperty, value); }
+ }
+ """;
+
+ const string expectedDerivedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class DerivedView
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty DerivedTextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("DerivedText", typeof(string), typeof(TestNamespace.DerivedView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string DerivedText { get => (string)GetValue(DerivedTextProperty); set => SetValue(DerivedTextProperty, value); }
+
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public new static readonly global::Microsoft.Maui.Controls.BindableProperty BaseTextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("BaseText", typeof(string), typeof(TestNamespace.DerivedView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public new partial string BaseText { get => (string)GetValue(BaseTextProperty); set => SetValue(BaseTextProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedAttribute, ("BaseView.g.cs", expectedBaseGenerated), ("DerivedView.g.cs", expectedDerivedGenerated));
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_GenericClass_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{defaultTestClassName}} : View where T : class
+ {
+ [BindableProperty]
+ public partial T? Value { get; set; }
+
+ [BindableProperty]
+ public partial U? Name { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty ValueProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Value", typeof(T), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial T? Value { get => (T? )GetValue(ValueProperty); set => SetValue(ValueProperty, value); }
+
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty NameProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Name", typeof(U), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial U? Name { get => (U? )GetValue(NameProperty); set => SetValue(NameProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_NestedClass_GeneratesCorrectCode()
+ {
+ const string outerClassName = "Outerclass";
+
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class {{outerClassName}}
+ {
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindableProperty]
+ public partial string Text { get; set; }
+ }
+ }
+ """;
+
+ const string expectedGenerated =
+/* language=C#-test */
+//lang=csharp
+$$"""
+//
+// See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+#pragma warning disable
+#nullable enable
+namespace {{defaultTestNamespace}};
+public partial class {{outerClassName}}
+{
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty TextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Text", typeof(string), typeof({{defaultTestNamespace}}.{{outerClassName}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); }
+ }
+}
+""";
+
+ await VerifySourceGeneratorAsync(source, expectedAttribute, ($"{outerClassName}.{defaultTestClassName}.g.cs", expectedGenerated));
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_WithCustomTypes_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+ using System.Collections.Generic;
+
+ namespace {{defaultTestNamespace}};
+
+ public class CustomModel
+ {
+ public string Name { get; set; }
+ }
+
+ public partial class {{defaultTestClassName}} : View
+ {
+ [BindableProperty]
+ public partial CustomModel Model { get; set; }
+
+ [BindableProperty]
+ public partial List Items { get; set; }
+
+ [BindableProperty]
+ public partial Dictionary Properties { get; set; }
+ }
+ """;
+
+ const string expectedGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class {{defaultTestClassName}}
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty ModelProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Model", typeof(TestNamespace.CustomModel), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial {{defaultTestNamespace}}.CustomModel Model { get => ({{defaultTestNamespace}}.CustomModel)GetValue(ModelProperty); set => SetValue(ModelProperty, value); }
+
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty ItemsProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Items", typeof(System.Collections.Generic.List), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial System.Collections.Generic.List Items { get => (System.Collections.Generic.List)GetValue(ItemsProperty); set => SetValue(ItemsProperty, value); }
+
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty PropertiesProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("Properties", typeof(System.Collections.Generic.Dictionary), typeof({{defaultTestNamespace}}.{{defaultTestClassName}}), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial System.Collections.Generic.Dictionary Properties { get => (System.Collections.Generic.Dictionary)GetValue(PropertiesProperty); set => SetValue(PropertiesProperty, value); }
+ }
+ """;
+
+ await VerifySourceGeneratorAsync(source, expectedGenerated);
+ }
+
+ [Fact]
+ public async Task GenerateBindableProperty_MultipleClassesInSameFile_GeneratesCorrectCode()
+ {
+ const string source =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ using CommunityToolkit.Maui;
+ using Microsoft.Maui.Controls;
+
+ namespace {{defaultTestNamespace}};
+
+ public partial class FirstView : View
+ {
+ [BindableProperty]
+ public partial string FirstText { get; set; }
+ }
+
+ public partial class SecondView : View
+ {
+ [BindableProperty]
+ public partial string SecondText { get; set; }
+ }
+ """;
+
+ const string expectedFirstGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class FirstView
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty FirstTextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("FirstText", typeof(string), typeof(TestNamespace.FirstView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string FirstText { get => (string)GetValue(FirstTextProperty); set => SetValue(FirstTextProperty, value); }
+ }
+ """;
+
+ const string expectedSecondGenerated =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+ #pragma warning disable
+ #nullable enable
+ namespace {{defaultTestNamespace}};
+ public partial class SecondView
+ {
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly global::Microsoft.Maui.Controls.BindableProperty SecondTextProperty = global::Microsoft.Maui.Controls.BindableProperty.Create("SecondText", typeof(string), typeof(TestNamespace.SecondView), null, Microsoft.Maui.Controls.BindingMode.OneWay, null, null, null, null, null);
+ public partial string SecondText { get => (string)GetValue(SecondTextProperty); set => SetValue(SecondTextProperty, value); }
+ }
+ """;
+
+
+ await VerifySourceGeneratorAsync(source, expectedAttribute, ("FirstView.g.cs", expectedFirstGenerated), ("SecondView.g.cs", expectedSecondGenerated));
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyModelTests.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyModelTests.cs
new file mode 100644
index 0000000000..2f0946f120
--- /dev/null
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/BindablePropertyModelTests.cs
@@ -0,0 +1,150 @@
+using CommunityToolkit.Maui.SourceGenerators.Internal.Models;
+using CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Xunit;
+
+namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests;
+
+public class BindablePropertyModelTests : BaseTest
+{
+ [Fact]
+ public void BindablePropertyName_ReturnsCorrectPropertyName()
+ {
+ // Arrange
+ var compilation = CreateCompilation("public class TestClass { public string TestProperty { get; set; } }");
+ var typeSymbol = compilation.GetTypeByMetadataName("TestClass")!;
+ var propertySymbol = typeSymbol.GetMembers("TestProperty").OfType().First();
+
+ var model = new BindablePropertyModel(
+ "TestProperty",
+ propertySymbol.Type,
+ typeSymbol,
+ "null",
+ "Microsoft.Maui.Controls.BindingMode.OneWay",
+ "null",
+ "null",
+ "null",
+ "null",
+ "null",
+ string.Empty);
+
+ // Act
+ var bindablePropertyName = model.BindablePropertyName;
+
+ // Assert
+ Assert.Equal("TestPropertyProperty", bindablePropertyName);
+ }
+
+ [Fact]
+ public void BindablePropertyModel_WithAllParameters_StoresCorrectValues()
+ {
+ // Arrange
+ var compilation = CreateCompilation("public class TestClass { public string TestProperty { get; set; } }");
+ var typeSymbol = compilation.GetTypeByMetadataName("TestClass")!;
+ var propertySymbol = typeSymbol.GetMembers("TestProperty").OfType().First();
+
+ const string propertyName = "TestProperty";
+ const string defaultValue = "\"Hello\"";
+ const string defaultBindingMode = "Microsoft.Maui.Controls.BindingMode.TwoWay";
+ const string validateValueMethodName = "ValidateValue";
+ const string propertyChangedMethodName = "OnPropertyChanged";
+ const string propertyChangingMethodName = "OnPropertyChanging";
+ const string coerceValueMethodName = "CoerceValue";
+ const string defaultValueCreatorMethodName = "CreateDefaultValue";
+ const string newKeywordText = "new ";
+
+ // Act
+ var model = new BindablePropertyModel(
+ propertyName,
+ propertySymbol.Type,
+ typeSymbol,
+ defaultValue,
+ defaultBindingMode,
+ validateValueMethodName,
+ propertyChangedMethodName,
+ propertyChangingMethodName,
+ coerceValueMethodName,
+ defaultValueCreatorMethodName,
+ newKeywordText);
+
+ // Assert
+ Assert.Equal(propertyName, model.PropertyName);
+ Assert.Equal(propertySymbol.Type, model.ReturnType);
+ Assert.Equal(typeSymbol, model.DeclaringType);
+ Assert.Equal(defaultValue, model.DefaultValue);
+ Assert.Equal(defaultBindingMode, model.DefaultBindingMode);
+ Assert.Equal(validateValueMethodName, model.ValidateValueMethodName);
+ Assert.Equal(propertyChangedMethodName, model.PropertyChangedMethodName);
+ Assert.Equal(propertyChangingMethodName, model.PropertyChangingMethodName);
+ Assert.Equal(coerceValueMethodName, model.CoerceValueMethodName);
+ Assert.Equal(defaultValueCreatorMethodName, model.DefaultValueCreatorMethodName);
+ Assert.Equal(newKeywordText, model.NewKeywordText);
+ Assert.Equal("TestPropertyProperty", model.BindablePropertyName);
+ }
+
+ [Fact]
+ public void ClassInformation_WithAllParameters_StoresCorrectValues()
+ {
+ // Arrange
+ const string className = "TestClass";
+ const string declaredAccessibility = "public";
+ const string containingNamespace = "TestNamespace";
+
+ // Act
+ var classInfo = new ClassInformation(className, declaredAccessibility, containingNamespace);
+
+ // Assert
+ Assert.Equal(className, classInfo.ClassName);
+ Assert.Equal(declaredAccessibility, classInfo.DeclaredAccessibility);
+ Assert.Equal(containingNamespace, classInfo.ContainingNamespace);
+ }
+
+ [Fact]
+ public void SemanticValues_WithClassInfoAndProperties_StoresCorrectValues()
+ {
+ // Arrange
+ var compilation = CreateCompilation("public class TestClass { public string TestProperty { get; set; } }");
+ var typeSymbol = compilation.GetTypeByMetadataName("TestClass")!;
+ var propertySymbol = typeSymbol.GetMembers("TestProperty").OfType().First();
+
+ var classInfo = new ClassInformation("TestClass", "public", "TestNamespace");
+ var bindableProperty = new BindablePropertyModel(
+ "TestProperty",
+ propertySymbol.Type,
+ typeSymbol,
+ "null",
+ "Microsoft.Maui.Controls.BindingMode.OneWay",
+ "null",
+ "null",
+ "null",
+ "null",
+ "null",
+ string.Empty);
+
+ var bindableProperties = new[] { bindableProperty }.ToImmutableArray();
+
+ // Act
+ var semanticValues = new SemanticValues(classInfo, bindableProperties);
+
+ // Assert
+ Assert.Equal(classInfo, semanticValues.ClassInformation);
+ Assert.Equal(bindableProperties, semanticValues.BindableProperties);
+ Assert.Single(semanticValues.BindableProperties);
+ }
+
+ static Compilation CreateCompilation(string source)
+ {
+ var syntaxTree = CSharpSyntaxTree.ParseText(source);
+ var references = new[]
+ {
+ MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
+ };
+
+ return CSharpCompilation.Create(
+ "TestAssembly",
+ [syntaxTree],
+ references,
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.csproj b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.csproj
new file mode 100644
index 0000000000..ba06befedb
--- /dev/null
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests.csproj
@@ -0,0 +1,40 @@
+
+
+
+ $(NetVersion)
+ true
+ $(BaseIntermediateOutputPath)\GF
+ true
+ true
+ false
+ true
+ Exe
+ CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/GlobalUsings.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/GlobalUsings.cs
new file mode 100644
index 0000000000..324928b6a0
--- /dev/null
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/GlobalUsings.cs
@@ -0,0 +1,4 @@
+global using System;
+global using System.Collections.Immutable;
+global using System.Linq;
+global using System.Threading.Tasks;
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/ReferenceAssembliesExtensions.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/ReferenceAssembliesExtensions.cs
new file mode 100644
index 0000000000..93f06db774
--- /dev/null
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/ReferenceAssembliesExtensions.cs
@@ -0,0 +1,16 @@
+using Microsoft.CodeAnalysis.Testing;
+
+namespace CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests;
+
+static class ReferenceAssembliesExtensions
+{
+ static readonly Lazy lazyNet100 = new(() =>
+ new(targetFramework: "net10.0",
+ referenceAssemblyPackage: new PackageIdentity("Microsoft.NETCore.App.Ref", "10.0.0-rc.2.25502.107"),
+ referenceAssemblyPath: Path.Combine("ref", "net10.0")));
+
+ extension(ReferenceAssemblies.Net)
+ {
+ public static ReferenceAssemblies Net100 => lazyNet100.Value;
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/xunit.runner.json b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/xunit.runner.json
new file mode 100644
index 0000000000..86e7fbb26f
--- /dev/null
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal.UnitTests/xunit.runner.json
@@ -0,0 +1,3 @@
+{
+ "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json"
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal/BindablePropertyAttributeSourceGenerator.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal/BindablePropertyAttributeSourceGenerator.cs
deleted file mode 100644
index a79318f291..0000000000
--- a/src/CommunityToolkit.Maui.SourceGenerators.Internal/BindablePropertyAttributeSourceGenerator.cs
+++ /dev/null
@@ -1,240 +0,0 @@
-using System.Collections.Immutable;
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-using System.Text;
-using CommunityToolkit.Maui.SourceGenerators.Helpers;
-using CommunityToolkit.Maui.SourceGenerators.Internal.Helpers;
-using CommunityToolkit.Maui.SourceGenerators.Internal.Models;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Text;
-
-namespace CommunityToolkit.Maui.SourceGenerators.Internal;
-
-[Generator]
-public class BindablePropertyAttributeSourceGenerator : IIncrementalGenerator
-{
- static readonly SemanticValues emptySemanticValues = new(default, []);
-
- const string bpFullName = "global::Microsoft.Maui.Controls.BindableProperty";
- const string bindingModeFullName = "global::Microsoft.Maui.Controls.";
-
- const string bpAttribute =
- /* language=C#-test */
- //lang=csharp
- $$"""
- //
- // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
-
- #pragma warning disable
- #nullable enable
- namespace CommunityToolkit.Maui;
-
- [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
- [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
- sealed partial class BindablePropertyAttribute : Attribute
- {
- public string? PropertyName { get; }
- public Type? DeclaringType { get; set; }
- public object? DefaultValue { get; set; }
- public string DefaultBindingMode { get; set; } = string.Empty;
- public string ValidateValueMethodName { get; set; } = string.Empty;
- public string PropertyChangedMethodName { get; set; } = string.Empty;
- public string PropertyChangingMethodName { get; set; } = string.Empty;
- public string CoerceValueMethodName { get; set; } = string.Empty;
- public string DefaultValueCreatorMethodName { get; set; } = string.Empty;
-
- public BindablePropertyAttribute(string propertyName)
- {
- PropertyName = propertyName;
- }
-
- public BindablePropertyAttribute()
- {
- }
- }
- """;
-
- public void Initialize(IncrementalGeneratorInitializationContext context)
- {
-#if DEBUG
-
- if (!Debugger.IsAttached)
- {
- // To debug this SG, uncomment the line below and rebuild the SourceGenerator project.
-
- //Debugger.Launch();
- }
-#endif
-
- context.RegisterPostInitializationOutput(static ctx => ctx.AddSource("BindablePropertyAttribute.g.cs", SourceText.From(bpAttribute, Encoding.UTF8)));
-
- var provider = context.SyntaxProvider.ForAttributeWithMetadataName("CommunityToolkit.Maui.BindablePropertyAttribute",
- SyntaxPredicate, SemanticTransform)
- .Where(static x => x.ClassInformation != default || !x.BindableProperties.IsEmpty)
- .Collect();
-
-
- context.RegisterSourceOutput(provider, ExecuteAllValues);
- }
-
- static void ExecuteAllValues(SourceProductionContext context, ImmutableArray semanticValues)
- {
- var groupedValues = semanticValues
- .GroupBy(static sv => (sv.ClassInformation.ClassName, sv.ClassInformation.ContainingNamespace))
- .ToDictionary(static d => d.Key, static d => d.ToArray());
-
- foreach (var keyValuePair in groupedValues)
- {
- var (className, containingNamespace) = keyValuePair.Key;
- var values = keyValuePair.Value;
-
- if (values.Length is 0 || string.IsNullOrEmpty(className) || string.IsNullOrEmpty(containingNamespace))
- {
- continue;
- }
-
- var bindableProperties = values.SelectMany(static x => x.BindableProperties).ToImmutableArray();
-
- var classAccessibility = values[0].ClassInformation.DeclaredAccessibility;
-
- var combinedClassInfo = new ClassInformation(className, classAccessibility, containingNamespace);
- var combinedValues = new SemanticValues(combinedClassInfo, bindableProperties);
-
- var source = GenerateSource(combinedValues);
- SourceStringService.FormatText(ref source);
- context.AddSource($"{className}.g.cs", SourceText.From(source, Encoding.UTF8));
- }
- }
-
-
- static string GenerateSource(SemanticValues value)
- {
- var sb = new StringBuilder(
- /* language=C#-test */
- //lang=csharp
- $$"""
-
- //
- // Test2 : {{DateTime.Now}}
-
- namespace {{value.ClassInformation.ContainingNamespace}};
-
- {{value.ClassInformation.DeclaredAccessibility}} partial class {{value.ClassInformation.ClassName}}
- {
-
- """);
-
- foreach (var info in value.BindableProperties)
- {
- GenerateBindableProperty(sb, info);
- GenerateProperty(sb, info);
- }
-
- sb.AppendLine().Append('}');
- return sb.ToString();
-
- static void GenerateBindableProperty(StringBuilder sb, BindablePropertyModel info)
- {
- /*
- ///
- /// Backing BindableProperty for the property.
- ///
- */
- sb.AppendLine("/// ")
- .AppendLine($"/// Backing BindableProperty for the property.")
- .AppendLine("/// ");
-
- // public static readonly BindableProperty TextProperty = BindableProperty.Create(...);
- sb.AppendLine($"public {info.NewKeywordText} static readonly {bpFullName} {info.PropertyName}Property = ")
- .Append($"{bpFullName}.Create(")
- .Append($"\"{info.PropertyName}\", ")
- .Append($"typeof({info.ReturnType}), ")
- .Append($"typeof({info.DeclaringType}), ")
- .Append($"{info.DefaultValue}, ")
- .Append($"{bindingModeFullName}{info.DefaultBindingMode}, ")
- .Append($"{info.ValidateValueMethodName}, ")
- .Append($"{info.PropertyChangedMethodName}, ")
- .Append($"{info.PropertyChangingMethodName}, ")
- .Append($"{info.CoerceValueMethodName}, ")
- .Append($"{info.DefaultValueCreatorMethodName}")
- .Append(");");
-
- sb.AppendLine().AppendLine();
- }
-
- static void GenerateProperty(StringBuilder sb, BindablePropertyModel info)
- {
- // The code below creates the following Property:
- //
- // public partial string Text
- // {
- // get => (string)GetValue(TextProperty);
- // set => SetValue(TextProperty, value);
- // }
- //
- sb.AppendLine($"public {info.NewKeywordText}partial {info.ReturnType} {info.PropertyName}")
- .AppendLine("{")
- .Append("get => (")
- .Append(info.ReturnType)
- .Append(")GetValue(")
- .AppendLine($"{info.PropertyName}Property);")
- .Append("set => SetValue(")
- .AppendLine($"{info.PropertyName}Property, value);")
- .AppendLine("}");
- }
- }
-
- static SemanticValues SemanticTransform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken)
- {
- var propertyDeclarationSyntax = Unsafe.As(context.TargetNode);
- var semanticModel = context.SemanticModel;
- var propertySymbol = (IPropertySymbol?)ModelExtensions.GetDeclaredSymbol(semanticModel, propertyDeclarationSyntax, cancellationToken);
-
- if (propertySymbol is null)
- {
- return emptySemanticValues;
- }
-
- var @namespace = propertySymbol.ContainingNamespace.ToDisplayString();
- var className = propertySymbol.ContainingType.Name;
- var classAccessibility = propertySymbol.ContainingSymbol.DeclaredAccessibility.ToString().ToLower();
- var returnType = propertySymbol.Type;
-
- var propertyInfo = new ClassInformation(className, classAccessibility, @namespace);
-
- var bindablePropertyModels = new List(context.Attributes.Length);
-
- var doesContainNewKeyword = propertyDeclarationSyntax.Modifiers.Any(static x => x.IsKind(SyntaxKind.NewKeyword));
-
- var attributeData = context.Attributes[0];
- bindablePropertyModels.Add(CreateBindablePropertyModel(attributeData, propertySymbol.Type.ToDisplayString(), propertySymbol.Name, returnType, doesContainNewKeyword));
-
- return new(propertyInfo, bindablePropertyModels.ToImmutableArray());
- }
-
- static BindablePropertyModel CreateBindablePropertyModel(in AttributeData attributeData, in string declaringTypeString, in string defaultName, in ITypeSymbol returnType, in bool doesContainNewKeyword)
- {
- if (attributeData.AttributeClass is null)
- {
- throw new ArgumentException($"{nameof(attributeData.AttributeClass)} Cannot Be Null", nameof(attributeData.AttributeClass));
- }
-
- var defaultValue = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.DefaultValue));
- var coerceValueMethodName = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.CoerceValueMethodName));
- var defaultBindingMode = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.DefaultBindingMode), "BindingMode.OneWay");
- var defaultValueCreatorMethodName = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.DefaultValueCreatorMethodName));
- var declaringType = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.DeclaringType), declaringTypeString);
- var propertyChangedMethodName = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.PropertyChangedMethodName));
- var propertyChangingMethodName = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.PropertyChangingMethodName));
- var propertyName = attributeData.GetConstructorArgumentsAttributeValueByNameAsString(defaultName);
- var validateValueMethodName = attributeData.GetNamedArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.ValidateValueMethodName));
- var newKeywordText = doesContainNewKeyword ? "new " : string.Empty;
-
- return new BindablePropertyModel(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValueMethodName, propertyChangedMethodName, propertyChangingMethodName, coerceValueMethodName, defaultValueCreatorMethodName, newKeywordText);
- }
-
- static bool SyntaxPredicate(SyntaxNode node, CancellationToken cancellationToken) =>
- node is PropertyDeclarationSyntax { AttributeLists.Count: > 0 };
-}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal/CommunityToolkit.Maui.SourceGenerators.Internal.csproj b/src/CommunityToolkit.Maui.SourceGenerators.Internal/CommunityToolkit.Maui.SourceGenerators.Internal.csproj
index 7f246746b1..996822d31e 100644
--- a/src/CommunityToolkit.Maui.SourceGenerators.Internal/CommunityToolkit.Maui.SourceGenerators.Internal.csproj
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal/CommunityToolkit.Maui.SourceGenerators.Internal.csproj
@@ -16,6 +16,10 @@
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal/Generators/BindablePropertyAttributeSourceGenerator.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Generators/BindablePropertyAttributeSourceGenerator.cs
new file mode 100644
index 0000000000..b9be7baaf4
--- /dev/null
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Generators/BindablePropertyAttributeSourceGenerator.cs
@@ -0,0 +1,361 @@
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Text;
+using CommunityToolkit.Maui.SourceGenerators.Helpers;
+using CommunityToolkit.Maui.SourceGenerators.Internal.Helpers;
+using CommunityToolkit.Maui.SourceGenerators.Internal.Models;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace CommunityToolkit.Maui.SourceGenerators.Internal;
+
+[Generator]
+public class BindablePropertyAttributeSourceGenerator : IIncrementalGenerator
+{
+ static readonly SemanticValues emptySemanticValues = new(default, []);
+
+ const string bpFullName = "global::Microsoft.Maui.Controls.BindableProperty";
+
+ const string bpAttribute =
+ /* language=C#-test */
+ //lang=csharp
+ $$"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+
+ #pragma warning disable
+ #nullable enable
+ namespace CommunityToolkit.Maui;
+
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ [global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
+ sealed partial class BindablePropertyAttribute : global::System.Attribute
+ {
+ public string? PropertyName { get; }
+ public global::System.Type? DeclaringType { get; set; }
+ public object? DefaultValue { get; set; }
+ public global::Microsoft.Maui.Controls.BindingMode DefaultBindingMode { get; set; }
+ public string ValidateValueMethodName { get; set; } = string.Empty;
+ public string PropertyChangedMethodName { get; set; } = string.Empty;
+ public string PropertyChangingMethodName { get; set; } = string.Empty;
+ public string CoerceValueMethodName { get; set; } = string.Empty;
+ public string DefaultValueCreatorMethodName { get; set; } = string.Empty;
+ }
+ """;
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+#if DEBUG
+
+ if (!Debugger.IsAttached)
+ {
+ // To debug this SG, uncomment the line below and rebuild the SourceGenerator project.
+
+ //Debugger.Launch();
+ }
+#endif
+
+ context.RegisterPostInitializationOutput(static ctx => ctx.AddSource("BindablePropertyAttribute.g.cs", SourceText.From(bpAttribute, Encoding.UTF8)));
+
+ var provider = context.SyntaxProvider.ForAttributeWithMetadataName("CommunityToolkit.Maui.BindablePropertyAttribute",
+ IsNonEmptyPropertyDeclarationSyntax, SemanticTransform)
+ .Where(static x => x.ClassInformation != default || !x.BindableProperties.IsEmpty)
+ .Collect();
+
+
+ context.RegisterSourceOutput(provider, ExecuteAllValues);
+ }
+
+ static void ExecuteAllValues(SourceProductionContext context, ImmutableArray semanticValues)
+ {
+ var groupedValues = semanticValues
+ .GroupBy(static sv => (sv.ClassInformation.ClassName, sv.ClassInformation.ContainingNamespace, sv.ClassInformation.ContainingTypes, sv.ClassInformation.GenericTypeParameters))
+ .ToDictionary(static d => d.Key, static d => d.ToArray());
+
+ foreach (var keyValuePair in groupedValues)
+ {
+ var (className, containingNamespace, containingTypes, genericTypeParameters) = keyValuePair.Key;
+ var values = keyValuePair.Value;
+
+ if (values.Length is 0 || string.IsNullOrEmpty(className) || string.IsNullOrEmpty(containingNamespace))
+ {
+ continue;
+ }
+
+ var bindableProperties = values.SelectMany(static x => x.BindableProperties).ToImmutableArray();
+
+ var classAccessibility = values[0].ClassInformation.DeclaredAccessibility;
+
+ var combinedClassInfo = new ClassInformation(className, classAccessibility, containingNamespace, containingTypes, genericTypeParameters);
+ var combinedValues = new SemanticValues(combinedClassInfo, bindableProperties);
+
+ var fileNameSuffix = string.IsNullOrEmpty(containingTypes) ? className : $"{containingTypes}.{className}";
+ var source = GenerateSource(combinedValues);
+ SourceStringService.FormatText(ref source);
+ context.AddSource($"{fileNameSuffix}.g.cs", SourceText.From(source, Encoding.UTF8));
+ }
+ }
+
+
+ static string GenerateSource(SemanticValues value)
+ {
+ var namespaceLine = IsGlobalNamespace(value.ClassInformation)
+ ? string.Empty
+ : $"namespace {value.ClassInformation.ContainingNamespace};";
+
+ var sb = new StringBuilder(
+ /* language=C#-test */
+ //lang=csharp
+ $"""
+ //
+ // See: CommunityToolkit.Maui.SourceGenerators.Internal.BindablePropertyAttributeSourceGenerator
+
+ #pragma warning disable
+ #nullable enable
+
+ {namespaceLine}
+
+ """);
+
+ // Generate nested class hierarchy
+ if (!string.IsNullOrEmpty(value.ClassInformation.ContainingTypes))
+ {
+ var containingTypeNames = value.ClassInformation.ContainingTypes.Split('.');
+ foreach (var typeName in containingTypeNames)
+ {
+ sb.AppendLine($"{value.ClassInformation.DeclaredAccessibility} partial class {typeName}")
+ .AppendLine("{")
+ .AppendLine();
+ }
+ }
+
+ // Get the class name with generic parameters
+ var classNameWithGenerics = value.ClassInformation.ClassName;
+ if (!string.IsNullOrEmpty(value.ClassInformation.GenericTypeParameters))
+ {
+ classNameWithGenerics = $"{value.ClassInformation.ClassName}<{value.ClassInformation.GenericTypeParameters}>";
+ }
+
+ sb.AppendLine($"{value.ClassInformation.DeclaredAccessibility} partial class {classNameWithGenerics}")
+ .AppendLine("{")
+ .AppendLine();
+
+ foreach (var info in value.BindableProperties)
+ {
+ GenerateBindableProperty(ref sb, info);
+ GenerateProperty(ref sb, info);
+ }
+
+ sb.Append('}');
+
+ // Close nested class hierarchy
+ if (!string.IsNullOrEmpty(value.ClassInformation.ContainingTypes))
+ {
+ var containingTypeNames = value.ClassInformation.ContainingTypes.Split('.');
+ for (int i = 0; i < containingTypeNames.Length; i++)
+ {
+ sb.AppendLine().Append('}');
+ }
+ }
+
+ return sb.ToString();
+
+ static void GenerateBindableProperty(ref readonly StringBuilder sb, in BindablePropertyModel info)
+ {
+ // Sanitize the Return Type because Nullable Reference Types cannot be used in the `typeof()` operator
+ var nonNullableReturnType = ConvertToNonNullableTypeSymbol(info.ReturnType);
+ var sanitizedPropertyName = IsDotnetKeyword(info.PropertyName) ? "@" + info.PropertyName : info.PropertyName;
+
+ /*
+ // The code below creates the following XML Tag:
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ */
+ sb.AppendLine("/// ")
+ .AppendLine($"/// Backing BindableProperty for the property.")
+ .AppendLine("/// ");
+
+ /*
+ // The code below creates the following BindableProperty:
+ //
+ // public static readonly BindableProperty TextProperty = BindableProperty.Create(...);
+ */
+ sb.AppendLine($"public {info.NewKeywordText}static readonly {bpFullName} {info.BindablePropertyName} = ")
+ .Append($"{bpFullName}.Create(")
+ .Append($"\"{sanitizedPropertyName}\", ")
+ .Append($"typeof({GetFormattedReturnType(nonNullableReturnType)}), ")
+ .Append($"typeof({info.DeclaringType}), ")
+ .Append($"{info.DefaultValue}, ")
+ .Append($"{info.DefaultBindingMode}, ")
+ .Append($"{info.ValidateValueMethodName}, ")
+ .Append($"{info.PropertyChangedMethodName}, ")
+ .Append($"{info.PropertyChangingMethodName}, ")
+ .Append($"{info.CoerceValueMethodName}, ")
+ .Append($"{info.DefaultValueCreatorMethodName}")
+ .Append(");");
+
+ sb.AppendLine().AppendLine();
+ }
+
+ static void GenerateProperty(ref readonly StringBuilder sb, in BindablePropertyModel info)
+ {
+ // The code below creates the following Property:
+ //
+ // public partial string Text
+ // {
+ // get => (string)GetValue(TextProperty);
+ // set => SetValue(TextProperty, value);
+ // }
+ //
+ var sanitizedPropertyName = IsDotnetKeyword(info.PropertyName) ? "@" + info.PropertyName : info.PropertyName;
+
+ sb.AppendLine($"public {info.NewKeywordText}partial {GetFormattedReturnType(info.ReturnType)} {sanitizedPropertyName}")
+ .AppendLine("{")
+ .Append("get => (")
+ .Append(GetFormattedReturnType(info.ReturnType))
+ .Append(")GetValue(")
+ .AppendLine($"{info.BindablePropertyName});")
+ .Append("set => SetValue(")
+ .AppendLine($"{info.BindablePropertyName}, value);")
+ .AppendLine("}");
+ }
+ }
+
+ static SemanticValues SemanticTransform(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken)
+ {
+ var propertyDeclarationSyntax = Unsafe.As(context.TargetNode);
+ var semanticModel = context.SemanticModel;
+ var propertySymbol = (IPropertySymbol?)ModelExtensions.GetDeclaredSymbol(semanticModel, propertyDeclarationSyntax, cancellationToken);
+
+ if (propertySymbol is null)
+ {
+ return emptySemanticValues;
+ }
+
+ var @namespace = propertySymbol.ContainingNamespace.ToDisplayString();
+ var className = propertySymbol.ContainingType.Name;
+ var classAccessibility = propertySymbol.ContainingSymbol.DeclaredAccessibility.ToString().ToLower();
+ var returnType = propertySymbol.Type;
+
+ // Build containing types hierarchy
+ var containingTypes = GetContainingTypes(propertySymbol.ContainingType);
+
+ // Extract generic type parameters
+ var genericTypeParameters = GetGenericTypeParameters(propertySymbol.ContainingType);
+
+ var propertyInfo = new ClassInformation(className, classAccessibility, @namespace, containingTypes, genericTypeParameters);
+
+ var bindablePropertyModels = new List(context.Attributes.Length);
+
+ var doesContainNewKeyword = propertyDeclarationSyntax.Modifiers.Any(static x => x.IsKind(SyntaxKind.NewKeyword));
+
+ var attributeData = context.Attributes[0];
+ bindablePropertyModels.Add(CreateBindablePropertyModel(attributeData, propertySymbol.ContainingType, propertySymbol.Name, returnType, doesContainNewKeyword));
+
+ return new(propertyInfo, bindablePropertyModels.ToImmutableArray());
+ }
+
+ static string GetContainingTypes(INamedTypeSymbol typeSymbol)
+ {
+ var types = new List();
+ var current = typeSymbol.ContainingType;
+
+ while (current is not null)
+ {
+ types.Insert(0, current.Name);
+ current = current.ContainingType;
+ }
+
+ return string.Join(".", types);
+ }
+
+ static string GetGenericTypeParameters(INamedTypeSymbol typeSymbol)
+ {
+ if (!typeSymbol.IsGenericType || typeSymbol.TypeParameters.IsEmpty)
+ {
+ return string.Empty;
+ }
+
+ var typeParams = typeSymbol.TypeParameters.Select(tp => tp.Name);
+ return string.Join(", ", typeParams);
+ }
+
+ static BindablePropertyModel CreateBindablePropertyModel(in AttributeData attributeData, in INamedTypeSymbol declaringType, in string propertyName, in ITypeSymbol returnType, in bool doesContainNewKeyword)
+ {
+ if (attributeData.AttributeClass is null)
+ {
+ throw new ArgumentException($"{nameof(attributeData.AttributeClass)} Cannot Be Null", nameof(attributeData.AttributeClass));
+ }
+
+ var defaultValue = attributeData.GetNamedTypeArgumentsAttributeValueByNameAsCastedString(nameof(BindablePropertyModel.DefaultValue));
+ var coerceValueMethodName = attributeData.GetNamedMethodGroupArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.CoerceValueMethodName));
+ var defaultBindingMode = attributeData.GetNamedTypeArgumentsAttributeValueByNameAsCastedString(nameof(BindablePropertyModel.DefaultBindingMode), "Microsoft.Maui.Controls.BindingMode.OneWay");
+ var defaultValueCreatorMethodName = attributeData.GetNamedMethodGroupArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.DefaultValueCreatorMethodName));
+ var propertyChangedMethodName = attributeData.GetNamedMethodGroupArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.PropertyChangedMethodName));
+ var propertyChangingMethodName = attributeData.GetNamedMethodGroupArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.PropertyChangingMethodName));
+ var validateValueMethodName = attributeData.GetNamedMethodGroupArgumentsAttributeValueByNameAsString(nameof(BindablePropertyModel.ValidateValueMethodName));
+ var newKeywordText = doesContainNewKeyword ? "new " : string.Empty;
+
+ return new BindablePropertyModel(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValueMethodName, propertyChangedMethodName, propertyChangingMethodName, coerceValueMethodName, defaultValueCreatorMethodName, newKeywordText);
+ }
+
+ static ITypeSymbol ConvertToNonNullableTypeSymbol(in ITypeSymbol typeSymbol)
+ {
+ // Check for Nullable
+ if (typeSymbol is INamedTypeSymbol { IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T })
+ {
+ return typeSymbol;
+ }
+
+ // Check for Nullable Reference Type
+ if (typeSymbol.NullableAnnotation is NullableAnnotation.Annotated)
+ {
+ // For reference types, NullableAnnotation.None indicates non-nullable.
+ return typeSymbol.WithNullableAnnotation(NullableAnnotation.None);
+ }
+
+ return typeSymbol;
+ }
+
+ static bool IsNonEmptyPropertyDeclarationSyntax(SyntaxNode node, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ return node is PropertyDeclarationSyntax { AttributeLists.Count: > 0 };
+ }
+
+ static bool IsDotnetKeyword(in string name) => SyntaxFacts.GetKeywordKind(name) is not SyntaxKind.None;
+
+ static bool IsGlobalNamespace(in ClassInformation classInformation)
+ {
+ if (classInformation.ContainingNamespace is "")
+ {
+ return true;
+ }
+ return false;
+ }
+
+ static string GetFormattedReturnType(ITypeSymbol typeSymbol)
+ {
+ if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol)
+ {
+ // Get the element type name (e.g., "int")
+ string elementType = GetFormattedReturnType(arrayTypeSymbol.ElementType);
+
+ // Construct the correct rank syntax with commas (e.g., "[,]")
+ string rank = new(',', arrayTypeSymbol.Rank - 1);
+
+ return $"{elementType}[{rank}]";
+ }
+ else
+ {
+ // Use ToDisplayString with the correct format for the base type (e.g., "int")
+ // SymbolDisplayFormat.CSharpErrorMessageFormat often works well for standard C# names
+ return typeSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal/Helpers/AttributeExtensions.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Helpers/AttributeExtensions.cs
index e9d0c66167..da2259a77e 100644
--- a/src/CommunityToolkit.Maui.SourceGenerators.Internal/Helpers/AttributeExtensions.cs
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Helpers/AttributeExtensions.cs
@@ -1,4 +1,6 @@
-using Microsoft.CodeAnalysis;
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
namespace CommunityToolkit.Maui.SourceGenerators.Internal.Helpers;
@@ -10,23 +12,40 @@ public static TypedConstant GetAttributeValueByName(this AttributeData attribute
return x;
}
- public static string GetNamedArgumentsAttributeValueByNameAsString(this AttributeData attribute, string name, string placeholder = "null")
+ public static string GetNamedTypeArgumentsAttributeValueByNameAsCastedString(this AttributeData attribute, string name, string placeholder = "null")
{
var data = attribute.NamedArguments.SingleOrDefault(kvp => kvp.Key == name).Value;
- return data.Value is null ? placeholder : data.Value.ToString();
- }
+ // true.ToString() => "True" and false.ToString() => "False", but we want "true" and "false"
+ if (data.Kind is TypedConstantKind.Primitive && data.Type?.SpecialType is SpecialType.System_Boolean)
+ {
+ return data.Value is null ? placeholder : $"({data.Type}){data.Value.ToString().ToLowerInvariant()}";
+ }
- public static string GetConstructorArgumentsAttributeValueByNameAsString(this AttributeData attribute, string placeholder)
- {
- if (attribute.ConstructorArguments.Length is 0)
+ if (data.Kind is TypedConstantKind.Enum && data.Type is not null && data.Value is not null)
{
- return placeholder;
+ var members = data.Type.GetMembers();
+
+ return $"({data.Type}){members[(int)data.Value]}";
}
- var data = attribute.ConstructorArguments[0];
+ if(data.Type?.SpecialType is SpecialType.System_String)
+ {
+ return data.Value is null ? $"\"{placeholder}\"": $"({data.Type})\"{data.Value}\"";
+ }
- return data.Value is null ? placeholder : data.Value.ToString();
+ if (data.Type?.SpecialType is SpecialType.System_Char)
+ {
+ return data.Value is null ? $"\"{placeholder}\"" : $"({data.Type})\'{data.Value}\'";
+ }
+
+ return data.Value is null ? placeholder : $"({data.Type}){data.Value}";
}
+ public static string GetNamedMethodGroupArgumentsAttributeValueByNameAsString(this AttributeData attribute, string name, string placeholder = "null")
+ {
+ var data = attribute.NamedArguments.SingleOrDefault(kvp => kvp.Key == name).Value;
+
+ return data.Value is null ? placeholder : data.Value.ToString();
+ }
}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal/IsExternalInit.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal/IsExternalInit.cs
deleted file mode 100644
index 50b4f95c6c..0000000000
--- a/src/CommunityToolkit.Maui.SourceGenerators.Internal/IsExternalInit.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-//
-// This code file has automatically been added by the "IsExternalInit" NuGet package (https://www.nuget.org/packages/IsExternalInit).
-// Please see https://github.com/manuelroemer/IsExternalInit for more information.
-//
-// IMPORTANT:
-// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references.
-// Consider migrating to PackageReferences instead:
-// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference
-// Migrating brings the following benefits:
-// * The "IsExternalInit" folder and the "IsExternalInit.cs" file don't appear in your project.
-// * The added file is immutable and can therefore not be modified by coincidence.
-// * Updating/Uninstalling the package will work flawlessly.
-//
-
-#region License
-// MIT License
-//
-// Copyright (c) Manuel Römer
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-#endregion
-
-#if !ISEXTERNALINIT_DISABLE
-#nullable enable
-#pragma warning disable
-
-namespace System.Runtime.CompilerServices
-{
- using global::System.Diagnostics;
- using global::System.Diagnostics.CodeAnalysis;
-
- ///
- /// Reserved to be used by the compiler for tracking metadata.
- /// This class should not be used by developers in source code.
- ///
- ///
- /// This definition is provided by the IsExternalInit NuGet package (https://www.nuget.org/packages/IsExternalInit).
- /// Please see https://github.com/manuelroemer/IsExternalInit for more information.
- ///
-#if !ISEXTERNALINIT_INCLUDE_IN_CODE_COVERAGE
- [ExcludeFromCodeCoverage, DebuggerNonUserCode]
-#endif
- internal static class IsExternalInit
- {
- }
-}
-
-#pragma warning restore
-#nullable restore
-#endif // ISEXTERNALINIT_DISABLE
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal/Models/Records.cs b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Models/Records.cs
index fd9cb5133f..55b4b162a1 100644
--- a/src/CommunityToolkit.Maui.SourceGenerators.Internal/Models/Records.cs
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Models/Records.cs
@@ -3,8 +3,11 @@
namespace CommunityToolkit.Maui.SourceGenerators.Internal.Models;
-record BindablePropertyModel(string PropertyName, ITypeSymbol? ReturnType, string DeclaringType, string DefaultValue, string DefaultBindingMode, string ValidateValueMethodName, string PropertyChangedMethodName, string PropertyChangingMethodName, string CoerceValueMethodName, string DefaultValueCreatorMethodName, string NewKeywordText);
+record BindablePropertyModel(string PropertyName, ITypeSymbol ReturnType, ITypeSymbol DeclaringType, string DefaultValue, string DefaultBindingMode, string ValidateValueMethodName, string PropertyChangedMethodName, string PropertyChangingMethodName, string CoerceValueMethodName, string DefaultValueCreatorMethodName, string NewKeywordText)
+{
+ public string BindablePropertyName => $"{PropertyName}Property";
+}
record SemanticValues(ClassInformation ClassInformation, EquatableArray BindableProperties);
-readonly record struct ClassInformation(string ClassName, string DeclaredAccessibility, string ContainingNamespace);
\ No newline at end of file
+readonly record struct ClassInformation(string ClassName, string DeclaredAccessibility, string ContainingNamespace, string ContainingTypes = "", string GenericTypeParameters = "");
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.SourceGenerators.Internal/Properties/launchSettings.json b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Properties/launchSettings.json
new file mode 100644
index 0000000000..904d4b884a
--- /dev/null
+++ b/src/CommunityToolkit.Maui.SourceGenerators.Internal/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Profile 1": {
+ "commandName": "DebugRoslynComponent",
+ "targetProject": "..\\CommunityToolkit.Maui\\CommunityToolkit.Maui.csproj"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui.UnitTests/Behaviors/ProgressBarAnimationBehaviorTests.cs b/src/CommunityToolkit.Maui.UnitTests/Behaviors/ProgressBarAnimationBehaviorTests.cs
index 4da68694d1..7db7e75ab3 100644
--- a/src/CommunityToolkit.Maui.UnitTests/Behaviors/ProgressBarAnimationBehaviorTests.cs
+++ b/src/CommunityToolkit.Maui.UnitTests/Behaviors/ProgressBarAnimationBehaviorTests.cs
@@ -33,7 +33,7 @@ public async Task ValidPropertiesTests(double progress, uint length, Easing easi
Assert.Equal(0.0d, progressBar.Progress);
Assert.Equal(0.0d, ProgressBarAnimationBehavior.ProgressProperty.DefaultValue);
Assert.Equal((uint)500, ProgressBarAnimationBehavior.LengthProperty.DefaultValue);
- Assert.Equal(Easing.Linear, ProgressBarAnimationBehavior.EasingProperty.DefaultValue);
+ Assert.Equal(Easing.Linear, progressBarAnimationBehavior.Easing);
progressBarAnimationBehavior.Length = length;
progressBarAnimationBehavior.Easing = easing;
@@ -59,19 +59,20 @@ void HandleAnimationCompleted(object? sender, EventArgs e)
}
[Theory]
- [InlineData(double.MinValue, 0)]
- [InlineData(-1, 0)]
- [InlineData(-0.0000000000001, 0)]
- [InlineData(1.0000000000001, 1)]
- [InlineData(double.MaxValue, 1)]
- public void InvalidProgressValuesTest(double inputProgressValue, double expectedProgressValue)
+ [InlineData(double.MinValue)]
+ [InlineData(-1)]
+ [InlineData(-0.0000000000001)]
+ [InlineData(1.0000000000001)]
+ [InlineData(double.MaxValue)]
+ public void InvalidProgressValuesTest(double inputProgressValue)
{
- var progressBarAnimationBehavior = new ProgressBarAnimationBehavior
+ Assert.Throws(() =>
{
- Progress = inputProgressValue
- };
-
- Assert.Equal(expectedProgressValue, progressBarAnimationBehavior.Progress);
+ new ProgressBarAnimationBehavior
+ {
+ Progress = inputProgressValue
+ };
+ });
}
[Fact]
diff --git a/src/CommunityToolkit.Maui.UnitTests/Behaviors/TouchBehaviorTests.cs b/src/CommunityToolkit.Maui.UnitTests/Behaviors/TouchBehaviorTests.cs
index 6ab65e4fff..8a3af263e3 100644
--- a/src/CommunityToolkit.Maui.UnitTests/Behaviors/TouchBehaviorTests.cs
+++ b/src/CommunityToolkit.Maui.UnitTests/Behaviors/TouchBehaviorTests.cs
@@ -859,8 +859,6 @@ public void SetHoveredOpacityTest()
touchBehavior.SetBinding(TouchBehavior.HoveredOpacityProperty, nameof(TouchBehaviorViewModel.HoveredOpacity), mode: BindingMode.TwoWay);
- Assert.Throws(() => touchBehavior.HoveredOpacity = 1.01);
- Assert.Throws(() => touchBehavior.HoveredOpacity = -0.01);
Assert.Equal(default, viewModel.HoveredOpacity);
touchBehavior.HoveredOpacity = hoveredOpacity;
@@ -868,6 +866,30 @@ public void SetHoveredOpacityTest()
Assert.Equal(hoveredOpacity, viewModel.HoveredOpacity);
}
+ [Fact]
+ public void SetHoveredOpacityAbove1Test()
+ {
+ var viewModel = new TouchBehaviorViewModel();
+ touchBehavior.BindingContext = viewModel;
+
+ touchBehavior.SetBinding(TouchBehavior.HoveredOpacityProperty, nameof(TouchBehaviorViewModel.HoveredOpacity), mode: BindingMode.TwoWay);
+
+ Assert.Throws(() => touchBehavior.HoveredOpacity = 1.01);
+ Assert.Equal(default, viewModel.HoveredOpacity);
+ }
+
+ [Fact]
+ public void SetHoveredOpacityBelow0Test()
+ {
+ var viewModel = new TouchBehaviorViewModel();
+ touchBehavior.BindingContext = viewModel;
+
+ touchBehavior.SetBinding(TouchBehavior.HoveredOpacityProperty, nameof(TouchBehaviorViewModel.HoveredOpacity), mode: BindingMode.TwoWay);
+
+ Assert.Throws(() => touchBehavior.HoveredOpacity = -0.01);
+ Assert.Equal(default, viewModel.HoveredOpacity);
+ }
+
[Fact]
public void SetPressedOpacityTest()
{
@@ -876,9 +898,6 @@ public void SetPressedOpacityTest()
touchBehavior.BindingContext = viewModel;
touchBehavior.SetBinding(TouchBehavior.PressedOpacityProperty, nameof(TouchBehaviorViewModel.PressedOpacity), mode: BindingMode.TwoWay);
-
- Assert.Throws(() => touchBehavior.PressedOpacity = 1.01);
- Assert.Throws(() => touchBehavior.PressedOpacity = -0.01);
Assert.Equal(default, viewModel.PressedOpacity);
touchBehavior.PressedOpacity = pressedOpacity;
@@ -886,6 +905,30 @@ public void SetPressedOpacityTest()
Assert.Equal(pressedOpacity, viewModel.PressedOpacity);
}
+ [Fact]
+ public void SetPressedOpacityAbove1Test()
+ {
+ var viewModel = new TouchBehaviorViewModel();
+ touchBehavior.BindingContext = viewModel;
+
+ touchBehavior.SetBinding(TouchBehavior.PressedOpacityProperty, nameof(TouchBehaviorViewModel.PressedOpacity), mode: BindingMode.TwoWay);
+
+ Assert.Throws(() => touchBehavior.PressedOpacity = 1.01);
+ Assert.Equal(default, viewModel.PressedOpacity);
+ }
+
+ [Fact]
+ public void SetPressedOpacityBelow0Test()
+ {
+ var viewModel = new TouchBehaviorViewModel();
+ touchBehavior.BindingContext = viewModel;
+
+ touchBehavior.SetBinding(TouchBehavior.PressedOpacityProperty, nameof(TouchBehaviorViewModel.PressedOpacity), mode: BindingMode.TwoWay);
+
+ Assert.Throws(() => touchBehavior.PressedOpacity = -0.01);
+ Assert.Equal(default, viewModel.PressedOpacity);
+ }
+
[Fact]
public void SetDefaultOpacityTest()
{
@@ -895,13 +938,33 @@ public void SetDefaultOpacityTest()
touchBehavior.SetBinding(TouchBehavior.DefaultOpacityProperty, nameof(TouchBehaviorViewModel.DefaultOpacity), mode: BindingMode.TwoWay);
+ touchBehavior.DefaultOpacity = defaultOpacity;
+
+ Assert.Equal(defaultOpacity, viewModel.DefaultOpacity);
+ }
+
+ [Fact]
+ public void SetDefaultOpacityAbove1Test()
+ {
+ var viewModel = new TouchBehaviorViewModel();
+ touchBehavior.BindingContext = viewModel;
+
+ touchBehavior.SetBinding(TouchBehavior.DefaultOpacityProperty, nameof(TouchBehaviorViewModel.DefaultOpacity), mode: BindingMode.TwoWay);
+
Assert.Throws(() => touchBehavior.DefaultOpacity = 1.01);
- Assert.Throws(() => touchBehavior.DefaultOpacity = -0.01);
Assert.Equal(default, viewModel.DefaultOpacity);
+ }
- touchBehavior.DefaultOpacity = defaultOpacity;
+ [Fact]
+ public void SetDefaultOpacityBelow0Test()
+ {
+ var viewModel = new TouchBehaviorViewModel();
+ touchBehavior.BindingContext = viewModel;
- Assert.Equal(defaultOpacity, viewModel.DefaultOpacity);
+ touchBehavior.SetBinding(TouchBehavior.DefaultOpacityProperty, nameof(TouchBehaviorViewModel.DefaultOpacity), mode: BindingMode.TwoWay);
+
+ Assert.Throws(() => touchBehavior.DefaultOpacity = -0.01);
+ Assert.Equal(default, viewModel.DefaultOpacity);
}
[Fact]
diff --git a/src/CommunityToolkit.Maui.slnx b/src/CommunityToolkit.Maui.slnx
index bba33d4380..cd52b727af 100644
--- a/src/CommunityToolkit.Maui.slnx
+++ b/src/CommunityToolkit.Maui.slnx
@@ -18,6 +18,7 @@
+
diff --git a/src/CommunityToolkit.Maui/Behaviors/AnimationBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/AnimationBehavior.shared.cs
index afa9446b19..de62dff003 100644
--- a/src/CommunityToolkit.Maui/Behaviors/AnimationBehavior.shared.cs
+++ b/src/CommunityToolkit.Maui/Behaviors/AnimationBehavior.shared.cs
@@ -18,24 +18,6 @@ public partial class AnimationBehavior : EventToCommandBehavior
behavior.SetBinding(AnimationBehavior.AnimateCommandProperty, nameof(ViewModel.TriggerAnimationCommand));
""";
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty AnimationTypeProperty =
- BindableProperty.Create(nameof(AnimationType), typeof(BaseAnimation), typeof(AnimationBehavior));
-
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty AnimateCommandProperty =
- BindableProperty.CreateReadOnly(nameof(AnimateCommand), typeof(Command), typeof(AnimationBehavior), default, BindingMode.OneWayToSource, propertyChanging: OnAnimateCommandChanging, defaultValueCreator: CreateAnimateCommand).BindableProperty;
-
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty AnimateOnTapProperty =
- BindableProperty.Create(nameof(AnimateOnTap), typeof(bool), typeof(AnimationBehavior), propertyChanged: OnAnimateOnTapPropertyChanged);
-
TapGestureRecognizer? tapGestureRecognizer;
///
@@ -49,35 +31,25 @@ public partial class AnimationBehavior : EventToCommandBehavior
///
/// has a of Command<CancellationToken> which requires a as a CommandParameter. See and for more information on passing a into as a CommandParameter"
///
- public Command AnimateCommand
+ [BindableProperty(DefaultValue = null, DefaultBindingMode = BindingMode.OneWayToSource, PropertyChangingMethodName = nameof(OnAnimateCommandChanging), DefaultValueCreatorMethodName = nameof(CreateAnimateCommand))]
+ public partial Command AnimateCommand
{
- get => (Command)GetValue(AnimateCommandProperty);
-
+ get;
[Obsolete(animateCommandSetterWarning), EditorBrowsable(EditorBrowsableState.Never)]
- set
- {
- Trace.WriteLine(animateCommandSetterWarning);
- SetValue(AnimateCommandProperty, value);
- }
+ set;
}
///
/// The type of animation to perform.
///
- public BaseAnimation? AnimationType
- {
- get => (BaseAnimation?)GetValue(AnimationTypeProperty);
- set => SetValue(AnimationTypeProperty, value);
- }
+ [BindableProperty]
+ public partial BaseAnimation? AnimationType { get; set; }
///
/// Whether a TapGestureRecognizer is added to the control or not
///
- public bool AnimateOnTap
- {
- get => (bool)GetValue(AnimateOnTapProperty);
- set => SetValue(AnimateOnTapProperty, value);
- }
+ [BindableProperty(PropertyChangedMethodName = nameof(OnAnimateOnTapPropertyChanged))]
+ public partial bool AnimateOnTap { get; set; }
///
protected override void OnAttachedTo(VisualElement bindable)
diff --git a/src/CommunityToolkit.Maui/Behaviors/EventToCommandBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/EventToCommandBehavior.shared.cs
index 928b9c14fe..ca57df8704 100644
--- a/src/CommunityToolkit.Maui/Behaviors/EventToCommandBehavior.shared.cs
+++ b/src/CommunityToolkit.Maui/Behaviors/EventToCommandBehavior.shared.cs
@@ -1,4 +1,5 @@
-using System.Globalization;
+using System.ComponentModel;
+using System.Globalization;
using System.Reflection;
using System.Windows.Input;
using CommunityToolkit.Maui.Converters;
@@ -10,30 +11,6 @@ namespace CommunityToolkit.Maui.Behaviors;
///
public partial class EventToCommandBehavior : BaseBehavior
{
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty EventNameProperty =
- BindableProperty.Create(nameof(EventName), typeof(string), typeof(EventToCommandBehavior), propertyChanged: OnEventNamePropertyChanged);
-
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty CommandProperty =
- BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(EventToCommandBehavior));
-
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty CommandParameterProperty =
- BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(EventToCommandBehavior));
-
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty EventArgsConverterProperty =
- BindableProperty.Create(nameof(EventArgsConverter), typeof(IValueConverter), typeof(EventToCommandBehavior));
-
readonly MethodInfo eventHandlerMethodInfo = typeof(EventToCommandBehavior).GetTypeInfo().GetDeclaredMethod(nameof(OnTriggerHandled)) ?? throw new InvalidOperationException($"Cannot find method {nameof(OnTriggerHandled)}");
Delegate? eventHandler;
@@ -43,38 +20,26 @@ public partial class EventToCommandBehavior : BaseBehavior
///
/// The name of the event that should be associated with . This is bindable property.
///
- public string? EventName
- {
- get => (string?)GetValue(EventNameProperty);
- set => SetValue(EventNameProperty, value);
- }
+ [BindableProperty(PropertyChangedMethodName = nameof(OnEventNamePropertyChanged))]
+ public partial string? EventName { get; set; }
///
/// The Command that should be executed when the event configured with is triggered. This is a bindable property.
///
- public ICommand? Command
- {
- get => (ICommand?)GetValue(CommandProperty);
- set => SetValue(CommandProperty, value);
- }
+ [BindableProperty]
+ public partial ICommand? Command { get; set; }
///
/// An optional parameter to forward to the . This is a bindable property.
///
- public object? CommandParameter
- {
- get => GetValue(CommandParameterProperty);
- set => SetValue(CommandParameterProperty, value);
- }
+ [BindableProperty]
+ public partial object? CommandParameter { get; set; }
///
/// An optional that can be used to convert values, associated with the event configured with , to values passed into the . This is a bindable property.
///
- public IValueConverter? EventArgsConverter
- {
- get => (IValueConverter?)GetValue(EventArgsConverterProperty);
- set => SetValue(EventArgsConverterProperty, value);
- }
+ [BindableProperty]
+ public partial IValueConverter? EventArgsConverter { get; set; }
///
protected override void OnAttachedTo(VisualElement bindable)
diff --git a/src/CommunityToolkit.Maui/Behaviors/MaskedBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/MaskedBehavior.shared.cs
index a9a9bde807..d2e7b33002 100644
--- a/src/CommunityToolkit.Maui/Behaviors/MaskedBehavior.shared.cs
+++ b/src/CommunityToolkit.Maui/Behaviors/MaskedBehavior.shared.cs
@@ -8,18 +8,6 @@ namespace CommunityToolkit.Maui.Behaviors;
///
public partial class MaskedBehavior : BaseBehavior, IDisposable
{
- ///
- /// BindableProperty for the property.
- ///
- public static readonly BindableProperty MaskProperty =
- BindableProperty.Create(nameof(Mask), typeof(string), typeof(MaskedBehavior), propertyChanged: OnMaskPropertyChanged);
-
- ///
- /// BindableProperty for the property.
- ///
- public static readonly BindableProperty UnmaskedCharacterProperty =
- BindableProperty.Create(nameof(UnmaskedCharacter), typeof(char), typeof(MaskedBehavior), 'X', propertyChanged: OnUnmaskedCharacterPropertyChanged);
-
readonly SemaphoreSlim applyMaskSemaphoreSlim = new(1, 1);
bool isDisposed;
@@ -34,24 +22,18 @@ public partial class MaskedBehavior : BaseBehavior, IDisposable
///
/// The mask that the input value needs to match. This is a bindable property.
///
- public string? Mask
- {
- get => (string?)GetValue(MaskProperty);
- set => SetValue(MaskProperty, value);
- }
+ [BindableProperty(PropertyChangedMethodName = nameof(OnMaskPropertyChanged))]
+ public partial string? Mask { get; set; }
///
/// Gets or sets which character in the property that will be visible and entered by a user. Defaults to 'X'. This is a bindable property.
///
- /// By default the 'X' character will be unmasked therefore a of "XX XX XX" would display "12 34 56".
+ /// By default, the 'X' character will be unmasked therefore a of "XX XX XX" would display "12 34 56".
/// If you wish to include 'X' in your then you could set this to something else
/// e.g. '0' and then use a of "00X00X00" which would then display "12X34X56".
///
- public char UnmaskedCharacter
- {
- get => (char)GetValue(UnmaskedCharacterProperty);
- set => SetValue(UnmaskedCharacterProperty, value);
- }
+ [BindableProperty(DefaultValueCreatorMethodName = nameof(UnmaskedCharacterValueCreator), PropertyChangedMethodName = nameof(OnUnmaskedCharacterPropertyChanged))]
+ public partial char UnmaskedCharacter { get; set; }
///
public void Dispose()
@@ -87,6 +69,8 @@ protected override async void OnViewPropertyChanged(InputView sender, PropertyCh
}
}
+ static object UnmaskedCharacterValueCreator(BindableObject bindable) => 'X';
+
static void OnMaskPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var mask = (string?)newValue;
@@ -104,7 +88,7 @@ static async void OnUnmaskedCharacterPropertyChanged(BindableObject bindable, ob
Task OnTextPropertyChanged(CancellationToken token)
{
// Android does not play well when we update the Text inside the TextChanged event.
- // Therefore if we dispatch the mechanism of updating the Text property it solves the issue of the caret position being updated incorrectly.
+ // Therefore, if we dispatch the mechanism of updating the Text property it solves the issue of the caret position being updated incorrectly.
// https://github.com/CommunityToolkit/Maui/issues/460
return View?.Dispatcher.DispatchAsync(() => ApplyMask(View?.Text, token)) ?? Task.CompletedTask;
}
diff --git a/src/CommunityToolkit.Maui/Behaviors/MaxLengthReachedBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/MaxLengthReachedBehavior.shared.cs
index 2445d152bf..c2465a28f0 100644
--- a/src/CommunityToolkit.Maui/Behaviors/MaxLengthReachedBehavior.shared.cs
+++ b/src/CommunityToolkit.Maui/Behaviors/MaxLengthReachedBehavior.shared.cs
@@ -10,35 +10,17 @@ public partial class MaxLengthReachedBehavior : BaseBehavior
{
readonly WeakEventManager maxLengthReachedEventManager = new();
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty CommandProperty
- = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(MaxLengthReachedBehavior));
-
///
/// Command that is triggered when the value configured in is reached. Both the event and this command are triggered. This is a bindable property.
///
- public ICommand? Command
- {
- get => (ICommand?)GetValue(CommandProperty);
- set => SetValue(CommandProperty, value);
- }
-
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty ShouldDismissKeyboardAutomaticallyProperty
- = BindableProperty.Create(nameof(ShouldDismissKeyboardAutomatically), typeof(bool), typeof(MaxLengthReachedBehavior), false);
+ [BindableProperty]
+ public partial ICommand? Command { get; set; }
///
- /// Indicates whether or not the keyboard should be dismissed automatically after the maximum length is reached. This is a bindable property.
+ /// Indicates whether the keyboard should be dismissed automatically after the maximum length is reached. This is a bindable property.
///
- public bool ShouldDismissKeyboardAutomatically
- {
- get => (bool)GetValue(ShouldDismissKeyboardAutomaticallyProperty);
- set => SetValue(ShouldDismissKeyboardAutomaticallyProperty, value);
- }
+ [BindableProperty(DefaultValue = false)]
+ public partial bool ShouldDismissKeyboardAutomatically { get; set; }
///
/// Event that is triggered when the value configured in is reached. Both the and this event are triggered. This is a bindable property.
diff --git a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/IconTintColor/IconTintColorBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/IconTintColor/IconTintColorBehavior.shared.cs
index bef0c66c33..0dea419356 100644
--- a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/IconTintColor/IconTintColorBehavior.shared.cs
+++ b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/IconTintColor/IconTintColorBehavior.shared.cs
@@ -5,18 +5,9 @@
///
public partial class IconTintColorBehavior : BasePlatformBehavior
{
- ///
- /// Attached Bindable Property for the .
- ///
- public static readonly BindableProperty TintColorProperty =
- BindableProperty.Create(nameof(TintColor), typeof(Color), typeof(IconTintColorBehavior), default);
-
///
/// Property that represents the that Icon will be tinted.
///
- public Color? TintColor
- {
- get => (Color?)GetValue(TintColorProperty);
- set => SetValue(TintColorProperty, value);
- }
+ [BindableProperty]
+ public partial Color? TintColor { get; set; }
}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/ImageTouch/ImageTouchBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/ImageTouch/ImageTouchBehavior.shared.cs
index f0d81bb450..d2e5e2100a 100644
--- a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/ImageTouch/ImageTouchBehavior.shared.cs
+++ b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/ImageTouch/ImageTouchBehavior.shared.cs
@@ -1,3 +1,4 @@
+using System.ComponentModel;
using CommunityToolkit.Maui.Core;
namespace CommunityToolkit.Maui.Behaviors;
@@ -7,129 +8,45 @@ namespace CommunityToolkit.Maui.Behaviors;
///
public partial class ImageTouchBehavior : TouchBehavior
{
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty DefaultImageSourceProperty = BindableProperty.Create(
- nameof(DefaultImageSource),
- typeof(ImageSource),
- typeof(TouchBehavior),
- ImageTouchBehaviorDefaults.DefaultBackgroundImageSource);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty HoveredBackgroundImageSourceProperty = BindableProperty.Create(
- nameof(HoveredImageSource),
- typeof(ImageSource),
- typeof(TouchBehavior),
- ImageTouchBehaviorDefaults.HoveredBackgroundImageSource);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty PressedBackgroundImageSourceProperty = BindableProperty.Create(
- nameof(PressedImageSource),
- typeof(ImageSource),
- typeof(TouchBehavior),
- ImageTouchBehaviorDefaults.PressedBackgroundImageSource);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty DefaultImageAspectProperty = BindableProperty.Create(
- nameof(DefaultImageAspect),
- typeof(Aspect),
- typeof(TouchBehavior),
- ImageTouchBehaviorDefaults.DefaultBackgroundImageAspect);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty HoveredImageAspectProperty = BindableProperty.Create(
- nameof(HoveredImageAspect),
- typeof(Aspect),
- typeof(TouchBehavior),
- ImageTouchBehaviorDefaults.HoveredBackgroundImageAspect);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty PressedImageAspectProperty = BindableProperty.Create(
- nameof(PressedImageAspect),
- typeof(Aspect),
- typeof(TouchBehavior),
- ImageTouchBehaviorDefaults.PressedBackgroundImageAspect);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty ShouldSetImageOnAnimationEndProperty = BindableProperty.Create(
- nameof(ShouldSetImageOnAnimationEnd),
- typeof(bool),
- typeof(TouchBehavior),
- ImageTouchBehaviorDefaults.ShouldSetImageOnAnimationEnd);
-
///
/// Gets or sets the when is .
///
- public ImageSource? DefaultImageSource
- {
- get => (ImageSource?)GetValue(DefaultImageSourceProperty);
- set => SetValue(DefaultImageSourceProperty, value);
- }
+ [BindableProperty(DefaultValue = ImageTouchBehaviorDefaults.DefaultBackgroundImageSource)]
+ public partial ImageSource? DefaultImageSource { get; set; }
///
/// Gets or sets the when the is
///
- public ImageSource? HoveredImageSource
- {
- get => (ImageSource?)GetValue(HoveredBackgroundImageSourceProperty);
- set => SetValue(HoveredBackgroundImageSourceProperty, value);
- }
+ [BindableProperty(DefaultValue = ImageTouchBehaviorDefaults.HoveredBackgroundImageSource)]
+ public partial ImageSource? HoveredImageSource { get; set; }
///
/// Gets or sets the when the is
///
- public ImageSource? PressedImageSource
- {
- get => (ImageSource?)GetValue(PressedBackgroundImageSourceProperty);
- set => SetValue(PressedBackgroundImageSourceProperty, value);
- }
+ [BindableProperty(DefaultValue = ImageTouchBehaviorDefaults.PressedBackgroundImageSource)]
+ public partial ImageSource? PressedImageSource { get; set; }
///
/// Gets or sets the when is .
///
- public Aspect DefaultImageAspect
- {
- get => (Aspect)GetValue(DefaultImageAspectProperty);
- set => SetValue(DefaultImageAspectProperty, value);
- }
+ [BindableProperty(DefaultValue = ImageTouchBehaviorDefaults.DefaultBackgroundImageAspect)]
+ public partial Aspect DefaultImageAspect { get; set; }
///
/// Gets or sets the when is .
///
- public Aspect HoveredImageAspect
- {
- get => (Aspect)GetValue(HoveredImageAspectProperty);
- set => SetValue(HoveredImageAspectProperty, value);
- }
+ [BindableProperty(DefaultValue = ImageTouchBehaviorDefaults.HoveredBackgroundImageAspect)]
+ public partial Aspect HoveredImageAspect { get; set; }
///
/// Gets or sets the when the is
///
- public Aspect PressedImageAspect
- {
- get => (Aspect)GetValue(PressedImageAspectProperty);
- set => SetValue(PressedImageAspectProperty, value);
- }
+ [BindableProperty(DefaultValue = ImageTouchBehaviorDefaults.PressedBackgroundImageAspect)]
+ public partial Aspect PressedImageAspect { get; set; }
///
/// Gets or sets a value indicating whether the image should be set when the animation ends.
///
- public bool ShouldSetImageOnAnimationEnd
- {
- get => (bool)GetValue(ShouldSetImageOnAnimationEndProperty);
- set => SetValue(ShouldSetImageOnAnimationEndProperty, value);
- }
+ [BindableProperty(DefaultValue = ImageTouchBehaviorDefaults.ShouldSetImageOnAnimationEnd)]
+ public partial bool ShouldSetImageOnAnimationEnd { get; set; }
}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/StatusBar/StatusBarBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/StatusBar/StatusBarBehavior.shared.cs
index 521342b6cd..c6ed7b7daf 100644
--- a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/StatusBar/StatusBarBehavior.shared.cs
+++ b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/StatusBar/StatusBarBehavior.shared.cs
@@ -28,50 +28,23 @@ public enum StatusBarApplyOn
[UnsupportedOSPlatform("Windows"), UnsupportedOSPlatform("MacCatalyst"), UnsupportedOSPlatform("MacOS"), UnsupportedOSPlatform("Tizen")]
public partial class StatusBarBehavior : BasePlatformBehavior
{
- ///
- /// that manages the StatusBarColor property.
- ///
- public static readonly BindableProperty StatusBarColorProperty =
- BindableProperty.Create(nameof(StatusBarColor), typeof(Color), typeof(StatusBarBehavior), Colors.Transparent);
-
- ///
- /// that manages the StatusBarColor property.
- ///
- public static readonly BindableProperty StatusBarStyleProperty =
- BindableProperty.Create(nameof(StatusBarStyle), typeof(StatusBarStyle), typeof(StatusBarBehavior), StatusBarStyle.Default);
-
- ///
- /// that manages the ApplyOn property.
- ///
- public static readonly BindableProperty ApplyOnProperty =
- BindableProperty.Create(nameof(ApplyOn), typeof(StatusBarApplyOn), typeof(StatusBarBehavior), StatusBarApplyOn.OnBehaviorAttachedTo);
-
///
/// Property that holds the value of the Status bar color.
///
- public Color StatusBarColor
- {
- get => (Color)GetValue(StatusBarColorProperty);
- set => SetValue(StatusBarColorProperty, value);
- }
+ [BindableProperty(DefaultValueCreatorMethodName = nameof(StatusBarColorValueCreator))]
+ public partial Color StatusBarColor { get; set; }
///
/// Property that holds the value of the Status bar color.
///
- public StatusBarStyle StatusBarStyle
- {
- get => (StatusBarStyle)GetValue(StatusBarStyleProperty);
- set => SetValue(StatusBarStyleProperty, value);
- }
+ [BindableProperty(DefaultValue = StatusBarStyle.Default)]
+ public partial StatusBarStyle StatusBarStyle { get; set; }
///
/// When the status bar color and style should be applied.
///
- public StatusBarApplyOn ApplyOn
- {
- get => (StatusBarApplyOn)GetValue(ApplyOnProperty);
- set => SetValue(ApplyOnProperty, value);
- }
+ [BindableProperty(DefaultValue = StatusBarApplyOn.OnBehaviorAttachedTo)]
+ public partial StatusBarApplyOn ApplyOn { get; set; }
#if !(WINDOWS || MACCATALYST || TIZEN)
@@ -164,4 +137,6 @@ protected override void OnPropertyChanged([CallerMemberName] string? propertyNam
}
}
#endif
+
+ static Color StatusBarColorValueCreator(BindableObject bindable) => Colors.Transparent;
}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/Touch/GestureManager.shared.cs b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/Touch/GestureManager.shared.cs
index cad7fc04f6..59d9155f78 100644
--- a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/Touch/GestureManager.shared.cs
+++ b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/Touch/GestureManager.shared.cs
@@ -286,7 +286,7 @@ static async Task SetImageSource(TouchBehavior touchBehavior, TouchState touchSt
bindable.SetValue(ImageElement.AspectProperty, imageTouchBehavior.PressedImageAspect);
}
- if (imageTouchBehavior.IsSet(ImageTouchBehavior.PressedBackgroundImageSourceProperty))
+ if (imageTouchBehavior.IsSet(ImageTouchBehavior.PressedImageSourceProperty))
{
bindable.SetValue(ImageElement.SourceProperty, imageTouchBehavior.PressedImageSource);
}
@@ -302,7 +302,7 @@ static async Task SetImageSource(TouchBehavior touchBehavior, TouchState touchSt
bindable.SetValue(ImageElement.AspectProperty, imageTouchBehavior.DefaultImageAspect);
}
- if (imageTouchBehavior.IsSet(ImageTouchBehavior.HoveredBackgroundImageSourceProperty))
+ if (imageTouchBehavior.IsSet(ImageTouchBehavior.HoveredImageSourceProperty))
{
bindable.SetValue(ImageElement.SourceProperty, imageTouchBehavior.HoveredImageSource);
}
diff --git a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/Touch/TouchBehavior.methods.shared.cs b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/Touch/TouchBehavior.methods.shared.cs
index dfec3980ef..711e18e5d5 100644
--- a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/Touch/TouchBehavior.methods.shared.cs
+++ b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/Touch/TouchBehavior.methods.shared.cs
@@ -1,5 +1,6 @@
using System.Diagnostics;
using CommunityToolkit.Maui.Core;
+
namespace CommunityToolkit.Maui.Behaviors;
public partial class TouchBehavior : IDisposable
@@ -108,6 +109,77 @@ protected virtual void Dispose(bool disposing)
isDisposed = true;
}
+ static async void RaiseCurrentTouchStateChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var touchBehavior = (TouchBehavior)bindable;
+
+ await Task.WhenAll(touchBehavior.ForceUpdateState(CancellationToken.None), touchBehavior.HandleLongPress(CancellationToken.None));
+ touchBehavior.weakEventManager.HandleEvent(touchBehavior, new TouchStateChangedEventArgs(touchBehavior.CurrentTouchState), nameof(CurrentTouchStateChanged));
+ }
+
+ static void RaiseInteractionStatusChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var touchBehavior = (TouchBehavior)bindable;
+ touchBehavior.weakEventManager.HandleEvent(touchBehavior, new TouchInteractionStatusChangedEventArgs(touchBehavior.CurrentInteractionStatus), nameof(InteractionStatusChanged));
+ }
+
+ static void RaiseCurrentTouchStatusChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var touchBehavior = (TouchBehavior)bindable;
+ touchBehavior.weakEventManager.HandleEvent(touchBehavior, new TouchStatusChangedEventArgs(touchBehavior.CurrentTouchStatus), nameof(CurrentTouchStatusChanged));
+ }
+
+ static async void RaiseHoverStateChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var touchBehavior = (TouchBehavior)bindable;
+
+ await touchBehavior.ForceUpdateState(CancellationToken.None);
+ touchBehavior.weakEventManager.HandleEvent(touchBehavior, new HoverStateChangedEventArgs(touchBehavior.CurrentHoverState), nameof(HoverStateChanged));
+ }
+
+ static void RaiseHoverStatusChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ var touchBehavior = (TouchBehavior)bindable;
+
+ touchBehavior.weakEventManager.HandleEvent(touchBehavior, new HoverStatusChangedEventArgs(touchBehavior.CurrentHoverStatus), nameof(HoverStatusChanged));
+ }
+
+ static void HandleDefaultOpacityChanging(BindableObject bindable, object oldValue, object newValue)
+ {
+ var defaultOpacity = (double)newValue;
+ switch (defaultOpacity)
+ {
+ case < 0:
+ throw new ArgumentOutOfRangeException(nameof(newValue), newValue, $"{nameof(DefaultOpacity)} must be greater than 0");
+ case > 1:
+ throw new ArgumentOutOfRangeException(nameof(newValue), newValue, $"{nameof(DefaultOpacity)} must be less than 1");
+ }
+ }
+
+ static void HandleHoveredOpacityChanging(BindableObject bindable, object oldValue, object newValue)
+ {
+ var hoveredOpacity = (double)newValue;
+ switch (hoveredOpacity)
+ {
+ case < 0:
+ throw new ArgumentOutOfRangeException(nameof(newValue), newValue, $"{nameof(HoveredOpacity)} must be greater than 0");
+ case > 1:
+ throw new ArgumentOutOfRangeException(nameof(newValue), newValue, $"{nameof(HoveredOpacity)} must be less than 1");
+ }
+ }
+
+ static void HandlePressedOpacityChanging(BindableObject bindable, object oldValue, object newValue)
+ {
+ var pressedOpacity = (double)newValue;
+ switch (pressedOpacity)
+ {
+ case < 0:
+ throw new ArgumentOutOfRangeException(nameof(newValue), newValue, $"{nameof(PressedOpacity)} must be greater than 0");
+ case > 1:
+ throw new ArgumentOutOfRangeException(nameof(newValue), newValue, $"{nameof(PressedOpacity)} must be less than 1");
+ }
+ }
+
async Task HandleLongPress(CancellationToken token)
{
if (Element is null)
@@ -165,26 +237,5 @@ void OnLayoutChildAdded(object? sender, ElementEventArgs e)
view.InputTransparent = IsEnabled;
}
- async Task RaiseCurrentTouchStateChanged(CancellationToken token)
- {
- await Task.WhenAll(ForceUpdateState(token), HandleLongPress(token));
- weakEventManager.HandleEvent(this, new TouchStateChangedEventArgs(CurrentTouchState), nameof(CurrentTouchStateChanged));
- }
-
- void RaiseInteractionStatusChanged()
- => weakEventManager.HandleEvent(this, new TouchInteractionStatusChangedEventArgs(CurrentInteractionStatus), nameof(InteractionStatusChanged));
-
- void RaiseCurrentTouchStatusChanged()
- => weakEventManager.HandleEvent(this, new TouchStatusChangedEventArgs(CurrentTouchStatus), nameof(CurrentTouchStatusChanged));
-
- async Task RaiseHoverStateChanged(CancellationToken token)
- {
- await ForceUpdateState(token);
- weakEventManager.HandleEvent(this, new HoverStateChangedEventArgs(CurrentHoverState), nameof(HoverStateChanged));
- }
-
- void RaiseHoverStatusChanged()
- => weakEventManager.HandleEvent(this, new HoverStatusChangedEventArgs(CurrentHoverStatus), nameof(HoverStatusChanged));
-
partial void PlatformDispose();
}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/Touch/TouchBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/Touch/TouchBehavior.shared.cs
index 9ba172d742..a68fb859d4 100644
--- a/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/Touch/TouchBehavior.shared.cs
+++ b/src/CommunityToolkit.Maui/Behaviors/PlatformBehaviors/Touch/TouchBehavior.shared.cs
@@ -1,3 +1,4 @@
+using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Input;
using CommunityToolkit.Maui.Core;
@@ -25,403 +26,6 @@ public partial class TouchBehavior : BasePlatformBehavior
///
public const string HoveredVisualState = "Hovered";
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(
- nameof(IsEnabled),
- typeof(bool),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.IsEnabled);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty CommandProperty = BindableProperty.Create(
- nameof(Command),
- typeof(ICommand),
- typeof(TouchBehavior),
- null);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty ShouldMakeChildrenInputTransparentProperty = BindableProperty.Create(
- nameof(ShouldMakeChildrenInputTransparent),
- typeof(bool),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.ShouldMakeChildrenInputTransparent);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty DisallowTouchThresholdProperty = BindableProperty.Create(
- nameof(DisallowTouchThreshold),
- typeof(int),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.DisallowTouchThreshold);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty LongPressCommandProperty = BindableProperty.Create(
- nameof(LongPressCommand),
- typeof(ICommand),
- typeof(TouchBehavior),
- null);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty CurrentTouchStatusProperty = BindableProperty.Create(
- nameof(CurrentTouchStatus),
- typeof(TouchStatus),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.CurrentTouchStatus,
- BindingMode.OneWayToSource,
- propertyChanged: static (bindable, _, _) => ((TouchBehavior)bindable).RaiseCurrentTouchStatusChanged());
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty LongPressDurationProperty = BindableProperty.Create(
- nameof(LongPressDuration),
- typeof(int),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.LongPressDuration);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty LongPressCommandParameterProperty = BindableProperty.Create(
- nameof(LongPressCommandParameter),
- typeof(object),
- typeof(TouchBehavior),
- default);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty CurrentHoverStatusProperty = BindableProperty.Create(
- nameof(CurrentHoverStatus),
- typeof(HoverStatus),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.CurrentHoverStatus,
- BindingMode.OneWayToSource,
- propertyChanged: static (bindable, _, _) => ((TouchBehavior)bindable).RaiseHoverStatusChanged());
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty CurrentInteractionStatusProperty = BindableProperty.Create(
- nameof(CurrentInteractionStatus),
- typeof(TouchInteractionStatus),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.CurrentInteractionStatus,
- BindingMode.OneWayToSource,
- propertyChanged: static (bindable, _, _) => ((TouchBehavior)bindable).RaiseInteractionStatusChanged());
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty CurrentTouchStateProperty = BindableProperty.Create(
- nameof(CurrentTouchState),
- typeof(TouchState),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.CurrentTouchState,
- BindingMode.OneWayToSource,
- propertyChanged: static async (bindable, _, _) => await ((TouchBehavior)bindable).RaiseCurrentTouchStateChanged(CancellationToken.None));
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty DefaultBackgroundColorProperty = BindableProperty.Create(
- nameof(DefaultBackgroundColor),
- typeof(Color),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.DefaultBackgroundColor);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty CurrentHoverStateProperty = BindableProperty.Create(
- nameof(CurrentHoverState),
- typeof(HoverState),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.CurrentHoverState,
- BindingMode.OneWayToSource,
- propertyChanged: static async (bindable, _, _) => await ((TouchBehavior)bindable).RaiseHoverStateChanged(CancellationToken.None));
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(
- nameof(CommandParameter),
- typeof(object),
- typeof(TouchBehavior),
- default);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty DefaultScaleProperty = BindableProperty.Create(
- nameof(DefaultScale),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.DefaultScale);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty PressedOpacityProperty = BindableProperty.Create(
- nameof(PressedOpacity),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.PressedOpacity);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty HoveredOpacityProperty = BindableProperty.Create(
- nameof(HoveredOpacity),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.HoveredOpacity);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty DefaultOpacityProperty = BindableProperty.Create(
- nameof(DefaultOpacity),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.DefaultOpacity);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty PressedScaleProperty = BindableProperty.Create(
- nameof(PressedScale),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.PressedScale);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty HoveredScaleProperty = BindableProperty.Create(
- nameof(HoveredScale),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.HoveredScale);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty PressedBackgroundColorProperty = BindableProperty.Create(
- nameof(PressedBackgroundColor),
- typeof(Color),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.PressedBackgroundColor);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty HoveredBackgroundColorProperty = BindableProperty.Create(
- nameof(HoveredBackgroundColor),
- typeof(Color),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.HoveredBackgroundColor);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty PressedTranslationXProperty = BindableProperty.Create(
- nameof(PressedTranslationX),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.PressedTranslationX);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty HoveredTranslationXProperty = BindableProperty.Create(
- nameof(HoveredTranslationX),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.HoveredTranslationX);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty HoveredTranslationYProperty = BindableProperty.Create(
- nameof(HoveredTranslationY),
- typeof(double),
- typeof(TouchBehavior),
- 0.0);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty DefaultTranslationXProperty = BindableProperty.Create(
- nameof(DefaultTranslationX),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.DefaultTranslationX);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty PressedRotationXProperty = BindableProperty.Create(
- nameof(PressedRotationX),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.PressedRotationX);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty HoveredRotationXProperty = BindableProperty.Create(
- nameof(HoveredRotationX),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.HoveredRotationX);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty DefaultRotationXProperty = BindableProperty.Create(
- nameof(DefaultRotationX),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.DefaultRotationX);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty PressedRotationProperty = BindableProperty.Create(
- nameof(PressedRotation),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.PressedRotation);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty PressedRotationYProperty = BindableProperty.Create(
- nameof(PressedRotationY),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.PressedRotationY);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty DefaultRotationYProperty = BindableProperty.Create(
- nameof(DefaultRotationY),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.DefaultRotationY);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty HoveredRotationProperty = BindableProperty.Create(
- nameof(HoveredRotation),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.HoveredRotation);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty DefaultRotationProperty = BindableProperty.Create(
- nameof(DefaultRotation),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.DefaultRotation);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty PressedTranslationYProperty = BindableProperty.Create(
- nameof(PressedTranslationY),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.PressedTranslationY);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty HoveredAnimationEasingProperty = BindableProperty.Create(
- nameof(HoveredAnimationEasing),
- typeof(Easing),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.HoveredAnimationEasing);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty HoveredRotationYProperty = BindableProperty.Create(
- nameof(HoveredRotationY),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.HoveredRotationY);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty DefaultTranslationYProperty = BindableProperty.Create(
- nameof(DefaultTranslationY),
- typeof(double),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.DefaultTranslationY);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty PressedAnimationDurationProperty = BindableProperty.Create(
- nameof(PressedAnimationDuration),
- typeof(int),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.PressedAnimationDuration);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty HoveredAnimationDurationProperty = BindableProperty.Create(
- nameof(HoveredAnimationDuration),
- typeof(int),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.HoveredAnimationDuration);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty DefaultAnimationEasingProperty = BindableProperty.Create(
- nameof(DefaultAnimationEasing),
- typeof(Easing),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.DefaultAnimationEasing);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty DefaultAnimationDurationProperty = BindableProperty.Create(
- nameof(DefaultAnimationDuration),
- typeof(int),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.DefaultAnimationDuration);
-
- ///
- /// Bindable property for
- ///
- public static readonly BindableProperty PressedAnimationEasingProperty = BindableProperty.Create(
- nameof(PressedAnimationEasing),
- typeof(Easing),
- typeof(TouchBehavior),
- TouchBehaviorDefaults.PressedAnimationEasing);
-
readonly WeakEventManager weakEventManager = new();
readonly GestureManager gestureManager = new();
@@ -491,426 +95,261 @@ public event EventHandler LongPressCompleted
///
/// Gets or sets a value indicating whether the behavior is enabled.
///
- public bool IsEnabled
- {
- get => (bool)GetValue(IsEnabledProperty);
- set => SetValue(IsEnabledProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.IsEnabled)]
+ public partial bool IsEnabled { get; set; }
///
/// Gets or sets a value indicating whether the children of the element should be made input transparent.
///
- public bool ShouldMakeChildrenInputTransparent
- {
- get => (bool)GetValue(ShouldMakeChildrenInputTransparentProperty);
- set => SetValue(ShouldMakeChildrenInputTransparentProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.ShouldMakeChildrenInputTransparent)]
+ public partial bool ShouldMakeChildrenInputTransparent { get; set; }
///
/// Gets or sets the to invoke when the user has completed a touch gesture.
///
- public ICommand? Command
- {
- get => (ICommand?)GetValue(CommandProperty);
- set => SetValue(CommandProperty, value);
- }
+ [BindableProperty(DefaultValue = null)]
+ public partial ICommand? Command { get; set; }
///
- /// Gets or sets the to invoke when the user has completed a long press.
+ /// Gets or sets the parameter to pass to the property.
///
- public ICommand? LongPressCommand
- {
- get => (ICommand?)GetValue(LongPressCommandProperty);
- set => SetValue(LongPressCommandProperty, value);
- }
+ [BindableProperty(DefaultValue = null)]
+ public partial object? CommandParameter { get; set; }
///
- /// Gets or sets the parameter to pass to the property.
+ /// Gets or sets the to invoke when the user has completed a long press.
///
- public object? CommandParameter
- {
- get => (object?)GetValue(CommandParameterProperty);
- set => SetValue(CommandParameterProperty, value);
- }
+ [BindableProperty(DefaultValue = null)]
+ public partial ICommand? LongPressCommand { get; set; }
///
/// Gets or sets the parameter to pass to the property.
///
- public object? LongPressCommandParameter
- {
- get => (object?)GetValue(LongPressCommandParameterProperty);
- set => SetValue(LongPressCommandParameterProperty, value);
- }
+ [BindableProperty(DefaultValue = null)]
+ public partial object? LongPressCommandParameter { get; set; }
///
/// Gets or sets the duration required to trigger the long press gesture.
///
- public int LongPressDuration
- {
- get => (int)GetValue(LongPressDurationProperty);
- set => SetValue(LongPressDurationProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.LongPressDuration)]
+ public partial int LongPressDuration { get; set; }
///
/// Gets the current of the behavior.
///
- public TouchStatus CurrentTouchStatus
- {
- get => (TouchStatus)GetValue(CurrentTouchStatusProperty);
- set => SetValue(CurrentTouchStatusProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.CurrentTouchStatus, PropertyChangedMethodName = nameof(RaiseCurrentTouchStatusChanged), DefaultBindingMode = BindingMode.OneWayToSource)]
+ public partial TouchStatus CurrentTouchStatus { get; set; }
///
/// Gets the current of the behavior.
///
- public TouchState CurrentTouchState
- {
- get => (TouchState)GetValue(CurrentTouchStateProperty);
- set => SetValue(CurrentTouchStateProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.CurrentTouchState, DefaultBindingMode = BindingMode.OneWayToSource, PropertyChangedMethodName = nameof(RaiseCurrentTouchStateChanged))]
+ public partial TouchState CurrentTouchState { get; set; }
///
/// Gets the current of the behavior.
///
- public TouchInteractionStatus CurrentInteractionStatus
- {
- get => (TouchInteractionStatus)GetValue(CurrentInteractionStatusProperty);
- set => SetValue(CurrentInteractionStatusProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.CurrentInteractionStatus, DefaultBindingMode = BindingMode.OneWayToSource, PropertyChangedMethodName = nameof(RaiseInteractionStatusChanged))]
+ public partial TouchInteractionStatus CurrentInteractionStatus { get; set; }
///
/// Gets the current of the behavior.
///
- public HoverStatus CurrentHoverStatus
- {
- get => (HoverStatus)GetValue(CurrentHoverStatusProperty);
- set => SetValue(CurrentHoverStatusProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.CurrentHoverStatus, DefaultBindingMode = BindingMode.OneWayToSource, PropertyChangedMethodName = nameof(RaiseHoverStatusChanged))]
+ public partial HoverStatus CurrentHoverStatus { get; set; }
///
/// Gets the current of the behavior.
///
- public HoverState CurrentHoverState
- {
- get => (HoverState)GetValue(CurrentHoverStateProperty);
- set => SetValue(CurrentHoverStateProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.CurrentHoverState, DefaultBindingMode = BindingMode.OneWayToSource, PropertyChangedMethodName = nameof(RaiseHoverStateChanged))]
+ public partial HoverState CurrentHoverState { get; set; }
///
/// Gets or sets the background color of the element when the is .
///
- public Color? DefaultBackgroundColor
- {
- get => (Color?)GetValue(DefaultBackgroundColorProperty);
- set => SetValue(DefaultBackgroundColorProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.DefaultBackgroundColor)]
+ public partial Color? DefaultBackgroundColor { get; set; }
///
/// Gets or sets the background color of the element when the is .
///
- public Color? HoveredBackgroundColor
- {
- get => (Color?)GetValue(HoveredBackgroundColorProperty);
- set => SetValue(HoveredBackgroundColorProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.HoveredBackgroundColor)]
+ public partial Color? HoveredBackgroundColor { get; set; }
///
/// Gets or sets the background color of the element when the is .
///
- public Color? PressedBackgroundColor
- {
- get => (Color?)GetValue(PressedBackgroundColorProperty);
- set => SetValue(PressedBackgroundColorProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.PressedBackgroundColor)]
+ public partial Color? PressedBackgroundColor { get; set; }
///
/// Gets or sets the opacity of the element when the is .
///
///
- public double DefaultOpacity
- {
- get => (double)GetValue(DefaultOpacityProperty);
- set
- {
- switch (value)
- {
- case < 0:
- throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(DefaultOpacity)} must be greater than 0");
- case > 1:
- throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(DefaultOpacity)} must be less than 1");
- default:
- SetValue(DefaultOpacityProperty, value);
- break;
- }
- }
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.DefaultOpacity, PropertyChangingMethodName = nameof(HandleDefaultOpacityChanging))]
+ public partial double DefaultOpacity { get; set; }
///
/// Gets or sets the opacity of the element when the is .
///
- public double HoveredOpacity
- {
- get => (double)GetValue(HoveredOpacityProperty);
- set
- {
- switch (value)
- {
- case < 0:
- throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(HoveredOpacity)} must be greater than 0");
- case > 1:
- throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(HoveredOpacity)} must be less than 1");
- default:
- SetValue(HoveredOpacityProperty, value);
- break;
- }
- }
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.HoveredOpacity, PropertyChangingMethodName = nameof(HandleHoveredOpacityChanging))]
+ public partial double HoveredOpacity { get; set; }
///
/// Gets or sets the opacity of the element when the is .
///
- public double PressedOpacity
- {
- get => (double)GetValue(PressedOpacityProperty);
- set
- {
- switch (value)
- {
- case < 0:
- throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(PressedOpacity)} must be greater than 0");
- case > 1:
- throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(PressedOpacity)} must be less than 1");
- default:
- SetValue(PressedOpacityProperty, value);
- break;
- }
- }
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.PressedOpacity, PropertyChangingMethodName = nameof(HandlePressedOpacityChanging))]
+ public partial double PressedOpacity { get; set; }
///
/// Gets or sets the scale of the element when the is .
///
- public double DefaultScale
- {
- get => (double)GetValue(DefaultScaleProperty);
- set => SetValue(DefaultScaleProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.DefaultScale)]
+ public partial double DefaultScale { get; set; }
///
/// Gets or sets the scale of the element when the is .
///
- public double HoveredScale
- {
- get => (double)GetValue(HoveredScaleProperty);
- set => SetValue(HoveredScaleProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.HoveredScale)]
+ public partial double HoveredScale { get; set; }
///
/// Gets or sets the scale of the element when the is .
///
- public double PressedScale
- {
- get => (double)GetValue(PressedScaleProperty);
- set => SetValue(PressedScaleProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.PressedScale)]
+ public partial double PressedScale { get; set; }
///
- /// Gets or sets the translation X of the element when when the is .
+ /// Gets or sets the translation X of the element when the is .
///
- public double DefaultTranslationX
- {
- get => (double)GetValue(DefaultTranslationXProperty);
- set => SetValue(DefaultTranslationXProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.DefaultTranslationX)]
+ public partial double DefaultTranslationX { get; set; }
///
/// Gets or sets the translation X of the element when the is .
///
- public double HoveredTranslationX
- {
- get => (double)GetValue(HoveredTranslationXProperty);
- set => SetValue(HoveredTranslationXProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.HoveredTranslationX)]
+ public partial double HoveredTranslationX { get; set; }
///
/// Gets or sets the translation X of the element when the is .
///
- public double PressedTranslationX
- {
- get => (double)GetValue(PressedTranslationXProperty);
- set => SetValue(PressedTranslationXProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.PressedTranslationX)]
+ public partial double PressedTranslationX { get; set; }
///
- /// Gets or sets the translation Y of the element when when the is .
+ /// Gets or sets the translation Y of the element when the is .
///
- public double DefaultTranslationY
- {
- get => (double)GetValue(DefaultTranslationYProperty);
- set => SetValue(DefaultTranslationYProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.DefaultTranslationY)]
+ public partial double DefaultTranslationY { get; set; }
///
/// Gets or sets the translation Y of the element when the is .
///
- public double HoveredTranslationY
- {
- get => (double)GetValue(HoveredTranslationYProperty);
- set => SetValue(HoveredTranslationYProperty, value);
- }
+ [BindableProperty(DefaultValue = 0.0)]
+ public partial double HoveredTranslationY { get; set; }
///
/// Gets or sets the translation Y of the element when the is .
///
- public double PressedTranslationY
- {
- get => (double)GetValue(PressedTranslationYProperty);
- set => SetValue(PressedTranslationYProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.PressedTranslationY)]
+ public partial double PressedTranslationY { get; set; }
///
- /// Gets or sets the rotation of the element when when the is .
+ /// Gets or sets the rotation of the element when the is .
///
- public double DefaultRotation
- {
- get => (double)GetValue(DefaultRotationProperty);
- set => SetValue(DefaultRotationProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.DefaultRotation)]
+ public partial double DefaultRotation { get; set; }
///
/// Gets or sets the rotation of the element when the is .
///
- public double HoveredRotation
- {
- get => (double)GetValue(HoveredRotationProperty);
- set => SetValue(HoveredRotationProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.HoveredRotation)]
+ public partial double HoveredRotation { get; set; }
///
/// Gets or sets the rotation of the element when the is .
///
- public double PressedRotation
- {
- get => (double)GetValue(PressedRotationProperty);
- set => SetValue(PressedRotationProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.PressedRotation)]
+ public partial double PressedRotation { get; set; }
///
- /// Gets or sets the rotation X of the element when when the is .
+ /// Gets or sets the rotation X of the element when the is .
///
- public double DefaultRotationX
- {
- get => (double)GetValue(DefaultRotationXProperty);
- set => SetValue(DefaultRotationXProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.DefaultRotationX)]
+ public partial double DefaultRotationX { get; set; }
///
/// Gets or sets the rotation X of the element when the is .
///
- public double HoveredRotationX
- {
- get => (double)GetValue(HoveredRotationXProperty);
- set => SetValue(HoveredRotationXProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.HoveredRotationX)]
+ public partial double HoveredRotationX { get; set; }
///
/// Gets or sets the rotation X of the element when the is .
///
- public double PressedRotationX
- {
- get => (double)GetValue(PressedRotationXProperty);
- set => SetValue(PressedRotationXProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.PressedRotationX)]
+ public partial double PressedRotationX { get; set; }
///
- /// Gets or sets the rotation Y of the element when when the is .
+ /// Gets or sets the rotation Y of the element when the is .
///
- public double DefaultRotationY
- {
- get => (double)GetValue(DefaultRotationYProperty);
- set => SetValue(DefaultRotationYProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.DefaultRotationY)]
+ public partial double DefaultRotationY { get; set; }
///
/// Gets or sets the rotation Y of the element when the is .
///
- public double HoveredRotationY
- {
- get => (double)GetValue(HoveredRotationYProperty);
- set => SetValue(HoveredRotationYProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.HoveredRotationY)]
+ public partial double HoveredRotationY { get; set; }
///
/// Gets or sets the rotation Y of the element when the is .
///
- public double PressedRotationY
- {
- get => (double)GetValue(PressedRotationYProperty);
- set => SetValue(PressedRotationYProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.PressedRotationY)]
+ public partial double PressedRotationY { get; set; }
///
/// Gets or sets the duration of the animation when the is .
///
- public int PressedAnimationDuration
- {
- get => (int)GetValue(PressedAnimationDurationProperty);
- set => SetValue(PressedAnimationDurationProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.PressedAnimationDuration)]
+ public partial int PressedAnimationDuration { get; set; }
///
/// Gets or sets the easing of the animation when the is .
///
- public Easing? PressedAnimationEasing
- {
- get => (Easing?)GetValue(PressedAnimationEasingProperty);
- set => SetValue(PressedAnimationEasingProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.PressedAnimationEasing)]
+ public partial Easing? PressedAnimationEasing { get; set; }
///
/// Gets or sets the duration of the animation when is .
///
- public int DefaultAnimationDuration
- {
- get => (int)GetValue(DefaultAnimationDurationProperty);
- set => SetValue(DefaultAnimationDurationProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.DefaultAnimationDuration)]
+ public partial int DefaultAnimationDuration { get; set; }
///
/// Gets or sets the of the animation when is .
///
- public Easing? DefaultAnimationEasing
- {
- get => (Easing?)GetValue(DefaultAnimationEasingProperty);
- set => SetValue(DefaultAnimationEasingProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.DefaultAnimationEasing)]
+ public partial Easing? DefaultAnimationEasing { get; set; }
///
/// Gets or sets the duration of the animation when the is .
///
- public int HoveredAnimationDuration
- {
- get => (int)GetValue(HoveredAnimationDurationProperty);
- set => SetValue(HoveredAnimationDurationProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.HoveredAnimationDuration)]
+ public partial int HoveredAnimationDuration { get; set; }
///
/// Gets or sets the easing of the animation when the is .
///
- public Easing? HoveredAnimationEasing
- {
- get => (Easing?)GetValue(HoveredAnimationEasingProperty);
- set => SetValue(HoveredAnimationEasingProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.HoveredAnimationEasing)]
+ public partial Easing? HoveredAnimationEasing { get; set; }
///
/// Gets or sets the threshold for disallowing touch.
///
- public int DisallowTouchThreshold
- {
- get => (int)GetValue(DisallowTouchThresholdProperty);
- set => SetValue(DisallowTouchThresholdProperty, value);
- }
+ [BindableProperty(DefaultValue = TouchBehaviorDefaults.DisallowTouchThreshold)]
+ public partial int DisallowTouchThreshold { get; set; }
internal bool CanExecute => IsEnabled
&& Element?.IsEnabled is true
diff --git a/src/CommunityToolkit.Maui/Behaviors/ProgressBarAnimationBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/ProgressBarAnimationBehavior.shared.cs
index fcdfbc6ccc..b28107491c 100644
--- a/src/CommunityToolkit.Maui/Behaviors/ProgressBarAnimationBehavior.shared.cs
+++ b/src/CommunityToolkit.Maui/Behaviors/ProgressBarAnimationBehavior.shared.cs
@@ -1,4 +1,5 @@
-using ProgressBar = Microsoft.Maui.Controls.ProgressBar;
+using CommunityToolkit.Maui.Core;
+using ProgressBar = Microsoft.Maui.Controls.ProgressBar;
namespace CommunityToolkit.Maui.Behaviors;
@@ -9,24 +10,6 @@ public partial class ProgressBarAnimationBehavior : BaseBehavior
{
readonly WeakEventManager animationCompletedEventManager = new();
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty ProgressProperty =
- BindableProperty.CreateAttached(nameof(Progress), typeof(double), typeof(ProgressBarAnimationBehavior), 0.0d, propertyChanged: OnAnimateProgressPropertyChanged);
-
- ///
- /// BindableProperty for the property
- ///
- public static readonly BindableProperty LengthProperty =
- BindableProperty.CreateAttached(nameof(Length), typeof(uint), typeof(ProgressBarAnimationBehavior), (uint)500);
-
- ///
- /// BindableProperty for the property
- ///
- public static readonly BindableProperty EasingProperty =
- BindableProperty.CreateAttached(nameof(Easing), typeof(Easing), typeof(ProgressBarAnimationBehavior), Easing.Linear);
-
///
/// Event that is triggered when the ProgressBar.ProgressTo() animation completes
///
@@ -39,29 +22,20 @@ public event EventHandler AnimationCompleted
///
/// Value of , clamped to a minimum value of 0 and a maximum value of 1
///
- public double Progress
- {
- get => (double)GetValue(ProgressProperty);
- set => SetValue(ProgressProperty, Math.Clamp(value, 0, 1));
- }
+ [BindableProperty(DefaultValue = ProgressBarAnimationBehaviorDefaults.Progress, PropertyChangingMethodName = nameof(OnAnimateProgressPropertyChanging), PropertyChangedMethodName = nameof(OnAnimateProgressPropertyChanged))]
+ public partial double Progress { get; set; }
///
/// Length in milliseconds of the progress bar animation
///
- public uint Length
- {
- get => (uint)GetValue(LengthProperty);
- set => SetValue(LengthProperty, value);
- }
+ [BindableProperty(DefaultValue = ProgressBarAnimationBehaviorDefaults.Length)]
+ public partial uint Length { get; set; }
///
/// Easing of the progress bar animation
///
- public Easing Easing
- {
- get => (Easing)GetValue(EasingProperty);
- set => SetValue(EasingProperty, value);
- }
+ [BindableProperty(DefaultValueCreatorMethodName = nameof(EasingValueCreator))]
+ public partial Easing Easing { get; set; }
static async void OnAnimateProgressPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
@@ -79,6 +53,21 @@ await AnimateProgress(progressBarAnimationBehavior.View,
}
}
+ static void OnAnimateProgressPropertyChanging(BindableObject bindable, object oldValue, object newValue)
+ {
+ var progress = (double)newValue;
+ switch (progress)
+ {
+ case < 0:
+ throw new ArgumentOutOfRangeException(nameof(newValue), newValue, $"{nameof(Progress)} must be greater than 0");
+ case > 1:
+ throw new ArgumentOutOfRangeException(nameof(newValue), newValue, $"{nameof(Progress)} must be less than 1");
+ }
+ }
+
+
+ static Easing EasingValueCreator(BindableObject bindable) => ProgressBarAnimationBehaviorDefaults.Easing;
+
static Task AnimateProgress(in ProgressBar progressBar, in double progress, in uint animationLength, in Easing animationEasing, in CancellationToken token)
{
return progressBar.ProgressTo(progress, animationLength, animationEasing).WaitAsync(token);
diff --git a/src/CommunityToolkit.Maui/Behaviors/UserStoppedTypingBehavior.shared.cs b/src/CommunityToolkit.Maui/Behaviors/UserStoppedTypingBehavior.shared.cs
index 470d8aea07..0c2075204f 100644
--- a/src/CommunityToolkit.Maui/Behaviors/UserStoppedTypingBehavior.shared.cs
+++ b/src/CommunityToolkit.Maui/Behaviors/UserStoppedTypingBehavior.shared.cs
@@ -1,6 +1,7 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Input;
+using CommunityToolkit.Maui.Core;
namespace CommunityToolkit.Maui.Behaviors;
@@ -9,36 +10,6 @@ namespace CommunityToolkit.Maui.Behaviors;
///
public partial class UserStoppedTypingBehavior : BaseBehavior, IDisposable
{
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty CommandProperty
- = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(UserStoppedTypingBehavior));
-
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty CommandParameterProperty
- = BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(UserStoppedTypingBehavior));
-
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty StoppedTypingTimeThresholdProperty
- = BindableProperty.Create(nameof(StoppedTypingTimeThreshold), typeof(int), typeof(UserStoppedTypingBehavior), 1000);
-
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty MinimumLengthThresholdProperty
- = BindableProperty.Create(nameof(MinimumLengthThreshold), typeof(int), typeof(UserStoppedTypingBehavior), 0);
-
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty ShouldDismissKeyboardAutomaticallyProperty
- = BindableProperty.Create(nameof(ShouldDismissKeyboardAutomatically), typeof(bool), typeof(UserStoppedTypingBehavior), false);
-
CancellationTokenSource? tokenSource;
bool isDisposed;
@@ -48,47 +19,32 @@ public static readonly BindableProperty ShouldDismissKeyboardAutomaticallyProper
///
/// Command that is triggered when the is reached. When is set, it's only triggered when both conditions are met. This is a bindable property.
///
- public ICommand? Command
- {
- get => (ICommand?)GetValue(CommandProperty);
- set => SetValue(CommandProperty, value);
- }
+ [BindableProperty]
+ public partial ICommand? Command { get; set; }
///
/// An optional parameter to forward to the . This is a bindable property.
///
- public object? CommandParameter
- {
- get => GetValue(CommandParameterProperty);
- set => SetValue(CommandParameterProperty, value);
- }
+ [BindableProperty]
+ public partial object? CommandParameter { get; set; }
///
/// The time of inactivity in milliseconds after which will be executed. If is also set, the condition there also needs to be met. This is a bindable property.
///
- public int StoppedTypingTimeThreshold
- {
- get => (int)GetValue(StoppedTypingTimeThresholdProperty);
- set => SetValue(StoppedTypingTimeThresholdProperty, value);
- }
+ [BindableProperty(DefaultValue = UserStoppedTypingBehaviorDefaults.StoppedTypingTimeThreshold)]
+ public partial int StoppedTypingTimeThreshold { get; set; }
///
/// The minimum length of the input value required before will be executed but only after has passed. This is a bindable property.
///
- public int MinimumLengthThreshold
- {
- get => (int)GetValue(MinimumLengthThresholdProperty);
- set => SetValue(MinimumLengthThresholdProperty, value);
- }
+ [BindableProperty(DefaultValue = UserStoppedTypingBehaviorDefaults.MinimumLengthThreshold)]
+ public partial int MinimumLengthThreshold { get; set; }
///
- /// Indicates whether or not the keyboard should be dismissed automatically after the user stopped typing. This is a bindable property.
+ /// Indicates whether the keyboard should be dismissed automatically after the user stopped typing. This is a bindable property.
///
- public bool ShouldDismissKeyboardAutomatically
- {
- get => (bool)GetValue(ShouldDismissKeyboardAutomaticallyProperty);
- set => SetValue(ShouldDismissKeyboardAutomaticallyProperty, value);
- }
+ [BindableProperty(DefaultValue = UserStoppedTypingBehaviorDefaults.ShouldDismissKeyboardAutomatically)]
+ public partial bool ShouldDismissKeyboardAutomatically { get; set; }
///
public void Dispose()
diff --git a/src/CommunityToolkit.Maui/Views/Expander/Expander.shared.cs b/src/CommunityToolkit.Maui/Views/Expander/Expander.shared.cs
index bb56ecf741..f532f7683b 100644
--- a/src/CommunityToolkit.Maui/Views/Expander/Expander.shared.cs
+++ b/src/CommunityToolkit.Maui/Views/Expander/Expander.shared.cs
@@ -10,6 +10,12 @@ namespace CommunityToolkit.Maui.Views;
[RequiresUnreferencedCode("Calls Microsoft.Maui.Controls.Binding.Binding(String, BindingMode, IValueConverter, Object, String, Object)")]
public partial class Expander : ContentView, IExpander
{
+ ///
+ /// Backing BindableProperty for the property.
+ ///
+ public static readonly BindableProperty DirectionProperty
+ = BindableProperty.Create(nameof(Direction), typeof(ExpandDirection), typeof(Expander), ExpandDirection.Down, propertyChanged: OnDirectionPropertyChanged);
+
///
/// Gets or sets the command to execute when the expander is expanded or collapsed.
///
@@ -40,12 +46,6 @@ public partial class Expander : ContentView, IExpander
[BindableProperty(PropertyChangedMethodName = nameof(OnHeaderPropertyChanged))]
public partial IView Header { get; set; }
- ///
- /// Backing BindableProperty for the property.
- ///
- public static readonly BindableProperty DirectionProperty
- = BindableProperty.Create(nameof(Direction), typeof(ExpandDirection), typeof(Expander), ExpandDirection.Down, propertyChanged: OnDirectionPropertyChanged);
-
readonly WeakEventManager tappedEventManager = new();
///