Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Binding SG] Dogfood binding source generator in the MAUI codebase #23393

Merged
1 change: 0 additions & 1 deletion eng/devices/catalyst.cake
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@ void ExecuteBuildUITestApp(string appProject, string binDir, string config, stri
Framework = tfm,
ToolPath = toolPath,
ArgumentCustomization = args => args
.Append("/t:Restore;Build")
.Append($"/bl:{binlog}")
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ Cell GetNewGroupHeaderCell(ITemplatedItemsList<Cell> group)
groupHeaderCell = new TextCell();
groupHeaderCell.SetBinding(
TextCell.TextProperty,
TypedBinding.ForSingleNestingLevel(nameof(group.Name), static (ITemplatedItemsList<Cell> g) => g.Name));
static (ITemplatedItemsList<Cell> g) => g.Name);
groupHeaderCell.BindingContext = group;
}

Expand Down
12 changes: 12 additions & 0 deletions src/Compatibility/Core/src/Compatibility.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@
<ItemGroup Condition="$(TargetFramework.Contains('-windows')) == true ">
</ItemGroup>

<PropertyGroup>
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Microsoft.Maui.Controls.Generated</InterceptorsPreviewNamespaces>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, if an interceptor is declared outside of a namespace listed here, it will get a C# compiler error.

It sounds like they will rename this to $(InterceptorsNamespaces) one day:

No problem here, just fyi for later.

<!-- https://github.com/dotnet/msbuild/issues/9785 -->
<CompilerResponseFile Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">$(CompilerResponseFile);WorkaroundXamlPreCompilePreviewFeatures.rsp</CompilerResponseFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference
Include="..\..\..\Controls\src\BindingSourceGen\Controls.BindingSourceGen.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetPlatformIdentifier)' == 'android' ">
<AndroidResource Include="$(AndroidRoot)Resources\layout\Tabbar.axml" />
<AndroidResource Include="$(AndroidRoot)Resources\layout\Toolbar.axml" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/features:"InterceptorsPreviewNamespaces=Microsoft.Maui.Controls.Generated"
44 changes: 22 additions & 22 deletions src/Controls/src/BindingSourceGen/BindingCodeWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,6 @@ public static string GenerateCommonCode() => $$"""
//------------------------------------------------------------------------------
#nullable enable

namespace System.Runtime.CompilerServices
{
using System;
using System.CodeDom.Compiler;

{{GeneratedCodeAttribute}}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
internal sealed class InterceptsLocationAttribute : Attribute
{
public InterceptsLocationAttribute(string filePath, int line, int column)
{
FilePath = filePath;
Line = line;
Column = column;
}

public string FilePath { get; }
public int Line { get; }
public int Column { get; }
}
}

namespace Microsoft.Maui.Controls.Generated
{
using System.CodeDom.Compiler;
Expand Down Expand Up @@ -73,6 +51,28 @@ private static string GenerateBindingCode(string bindingMethodBody) => $$"""
//------------------------------------------------------------------------------
#nullable enable

namespace System.Runtime.CompilerServices
{
using System;
using System.CodeDom.Compiler;

{{GeneratedCodeAttribute}}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
file sealed class InterceptsLocationAttribute : Attribute
{
public InterceptsLocationAttribute(string filePath, int line, int column)
{
FilePath = filePath;
Line = line;
Column = column;
}

public string FilePath { get; }
public int Line { get; }
public int Column { get; }
}
}

Comment on lines +54 to +75
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might seem like a weird change but we need to make the attribute file-scoped so that there aren't any collisions when there are assemblies with reference between them and they see each others internals (as in the case of Microsoft.Maui.Controls.Core.dll and Microsoft.Maui.Controls.Compatibility.dll

namespace Microsoft.Maui.Controls.Generated
{
using System;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -763,9 +763,7 @@ Cell GetNewGroupHeaderCell(ITemplatedItemsList<Cell> group)
else
{
groupHeaderCell = new TextCell();
groupHeaderCell.SetBinding(
TextCell.TextProperty,
TypedBinding.ForSingleNestingLevel(nameof(group.Name), static (ITemplatedItemsList<Cell> g) => g.Name));
groupHeaderCell.SetBinding(TextCell.TextProperty, static (ITemplatedItemsList<Cell> g) => g.Name);
groupHeaderCell.BindingContext = group;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ static View CreateContent(SectionCell sectionCell)
#pragma warning restore CS0612 // Type or member is obsolete
};

text.SetBinding(Label.TextProperty, TypedBinding.ForSingleNestingLevel("Text", static (SectionCell cell) => cell.Text, source: sectionCell));
text.SetBinding(Label.TextColorProperty, TypedBinding.ForSingleNestingLevel("TextColor", static (SectionCell cell) => cell.TextColor, source: sectionCell));
text.SetBinding(Label.TextProperty, static (SectionCell cell) => cell.Text, source: sectionCell);
text.SetBinding(Label.TextColorProperty, static (SectionCell cell) => cell.TextColor, source: sectionCell);

var layout = new Controls.StackLayout
{
Expand All @@ -93,15 +93,15 @@ static View CreateContent(SectionCell sectionCell)
static View CreateContent(TextCell textcell)
{
var text = new Label();
text.SetBinding(Label.TextProperty, TypedBinding.ForSingleNestingLevel("Text", static (TextCell cell) => cell.Text, source: textcell));
text.SetBinding(Label.TextColorProperty, TypedBinding.ForSingleNestingLevel("TextColor", static (TextCell cell) => cell.TextColor, source: textcell));
text.SetBinding(Label.TextProperty, static (TextCell cell) => cell.Text, source: textcell);
text.SetBinding(Label.TextColorProperty, static (TextCell cell) => cell.TextColor, source: textcell);
#pragma warning disable CS0612 // Type or member is obsolete
text.FontSize = Device.GetNamedSize(NamedSize.Default, typeof(Label));
#pragma warning restore CS0612 // Type or member is obsolete

var detail = new Label();
detail.SetBinding(Label.TextProperty, TypedBinding.ForSingleNestingLevel("Detail", static (TextCell cell) => cell.Detail, source: textcell));
detail.SetBinding(Label.TextColorProperty, TypedBinding.ForSingleNestingLevel("DetailColor", static (TextCell cell) => cell.DetailColor, source: textcell));
detail.SetBinding(Label.TextProperty, static (TextCell cell) => cell.Detail, source: textcell);
detail.SetBinding(Label.TextColorProperty, static (TextCell cell) => cell.DetailColor, source: textcell);
#pragma warning disable CS0612 // Type or member is obsolete
detail.FontSize = Device.GetNamedSize(NamedSize.Micro, typeof(Label));
#pragma warning restore CS0612 // Type or member is obsolete
Expand Down Expand Up @@ -139,7 +139,7 @@ static View CreateContent(ImageCell imageCell)
{
Aspect = Aspect.AspectFit,
};
img.SetBinding(Image.SourceProperty, TypedBinding.ForSingleNestingLevel("ImageSource", static (ImageCell cell) => cell.ImageSource, source: imageCell));
img.SetBinding(Image.SourceProperty, static (ImageCell cell) => cell.ImageSource, source: imageCell);
layout.Add(img, 0, 0);
layout.Add(textcell, 1, 0);
return layout;
Expand All @@ -148,20 +148,15 @@ static View CreateContent(ImageCell imageCell)
static View CreateContent(EntryCell entryCell)
{
var entry = new Entry();
entry.SetBinding(Entry.TextProperty,
TypedBinding.ForSingleNestingLevel("Text",
static (EntryCell cell) => cell.Text,
static (cell, value) => cell.Text = value,
mode: BindingMode.TwoWay,
source: entryCell));
entry.SetBinding(Entry.PlaceholderProperty, TypedBinding.ForSingleNestingLevel("Placeholder", static (EntryCell cell) => cell.Placeholder, source: entryCell));
entry.SetBinding(InputView.KeyboardProperty, TypedBinding.ForSingleNestingLevel("Keyboard", static (EntryCell cell) => cell.Keyboard, source: entryCell));
entry.SetBinding(Entry.HorizontalTextAlignmentProperty, TypedBinding.ForSingleNestingLevel("HorizontalTextAlignment", static (EntryCell cell) => cell.HorizontalTextAlignment, source: entryCell));
entry.SetBinding(Entry.VerticalTextAlignmentProperty, TypedBinding.ForSingleNestingLevel("VerticalTextAlignment", static (EntryCell cell) => cell.VerticalTextAlignment, source: entryCell));
entry.SetBinding(Entry.TextProperty, static (EntryCell cell) => cell.Text, mode: BindingMode.TwoWay, source: entryCell);
entry.SetBinding(Entry.PlaceholderProperty, static (EntryCell cell) => cell.Placeholder, source: entryCell);
entry.SetBinding(InputView.KeyboardProperty, static (EntryCell cell) => cell.Keyboard, source: entryCell);
entry.SetBinding(Entry.HorizontalTextAlignmentProperty, static (EntryCell cell) => cell.HorizontalTextAlignment, source: entryCell);
entry.SetBinding(Entry.VerticalTextAlignmentProperty, static (EntryCell cell) => cell.VerticalTextAlignment, source: entryCell);

var label = new Label();
label.SetBinding(Label.TextProperty, TypedBinding.ForSingleNestingLevel("Label", static (EntryCell cell) => cell.Label, source: entryCell));
label.SetBinding(Label.TextColorProperty, TypedBinding.ForSingleNestingLevel("LabelColor", static (EntryCell cell) => cell.LabelColor, source: entryCell));
label.SetBinding(Label.TextProperty, static (EntryCell cell) => cell.Label, source: entryCell);
label.SetBinding(Label.TextColorProperty, static (EntryCell cell) => cell.LabelColor, source: entryCell);
#pragma warning disable CS0612 // Type or member is obsolete
label.FontSize = Device.GetNamedSize(NamedSize.Micro, typeof(Label));
#pragma warning restore CS0612 // Type or member is obsolete
Expand All @@ -188,19 +183,14 @@ static View CreateContent(SwitchCell switchCell)
HorizontalOptions = LayoutOptions.StartAndExpand,
};
#pragma warning restore CS0618
text.SetBinding(Label.TextProperty, TypedBinding.ForSingleNestingLevel("Text", static (SwitchCell cell) => cell.Text, source: switchCell));
text.SetBinding(Label.TextProperty, static (SwitchCell cell) => cell.Text, source: switchCell);

var sw = new Switch
{
HorizontalOptions = LayoutOptions.End
};
sw.SetBinding(Switch.IsToggledProperty,
TypedBinding.ForSingleNestingLevel("On",
static (SwitchCell cell) => cell.On,
static (cell, value) => cell.On = value,
mode: BindingMode.TwoWay,
source: switchCell));
sw.SetBinding(Switch.OnColorProperty, TypedBinding.ForSingleNestingLevel("OnColor", static (SwitchCell cell) => cell.OnColor, source: switchCell));
sw.SetBinding(Switch.IsToggledProperty, static (SwitchCell cell) => cell.On, mode: BindingMode.TwoWay, source: switchCell);
sw.SetBinding(Switch.OnColorProperty, static (SwitchCell cell) => cell.OnColor, source: switchCell);

var layout = new Controls.StackLayout
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ protected void UpdateAdaptor(bool initialize)
template = new CellWrapperTemplate(new DataTemplate(() =>
{
var label = new TextCell();
label.SetBinding(TextCell.TextProperty, TypedBinding.ForSingleNestingLevel(string.Empty, static (object source) => source));
label.SetBinding(TextCell.TextProperty, static (object source) => source);
return label;
}), Element);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ DataTemplate DefaultTemplate
}
#pragma warning restore CS0618

label.SetBinding(Label.TextProperty, TypedBinding.ForSingleNestingLevel(string.Empty, static (object o) => o));
label.SetBinding(Label.TextProperty, static (object o) => o);
}

label.HorizontalTextAlignment = TextAlignment.Center;
Expand Down
48 changes: 31 additions & 17 deletions src/Controls/src/Core/ContentConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,31 +66,45 @@ static Label ConvertToLabel(string textContent, ContentPresenter presenter)

static void BindTextProperties(BindableObject content)
{
BindProperty(content, TextElement.TextColorProperty, static (ITextElement te) => te.TextColor);
BindProperty(content, TextElement.CharacterSpacingProperty, static (ITextElement te) => te.CharacterSpacing);
BindProperty(content, TextElement.TextTransformProperty, static (ITextElement te) => te.TextTransform);
var source = new RelativeBindingSource(RelativeBindingSourceMode.FindAncestor, typeof(ITextElement));
if (ShouldSetBinding(content, TextElement.TextColorProperty))
{
content.SetBinding(TextElement.TextColorProperty, static (ITextElement te) => te.TextColor, source: source);
}

if (ShouldSetBinding(content, TextElement.CharacterSpacingProperty))
{
content.SetBinding(TextElement.CharacterSpacingProperty, static (ITextElement te) => te.CharacterSpacing, source: source);
}

if (ShouldSetBinding(content, TextElement.TextTransformProperty))
{
content.SetBinding(TextElement.TextTransformProperty, static (ITextElement te) => te.TextTransform, source: source);
}
}

static void BindFontProperties(BindableObject content)
{
BindProperty(content, FontElement.FontAttributesProperty, static (IFontElement fe) => fe.FontAttributes);
BindProperty(content, FontElement.FontSizeProperty, static (IFontElement fe) => fe.FontSize);
BindProperty(content, FontElement.FontFamilyProperty, static (IFontElement fe) => fe.FontFamily);
}
var source = new RelativeBindingSource(RelativeBindingSourceMode.FindAncestor, typeof(IFontElement));
if (ShouldSetBinding(content, FontElement.FontAttributesProperty))
{
content.SetBinding(FontElement.FontAttributesProperty, static (IFontElement fe) => fe.FontAttributes, source: source);
}

static void BindProperty<TSource, TProperty>(
BindableObject content,
BindableProperty property,
Func<TSource, TProperty> getter)
{
if (content.IsSet(property) || content.GetIsBound(property))
if (ShouldSetBinding(content, FontElement.FontSizeProperty))
{
// Don't override the property if user has already set it
return;
content.SetBinding(FontElement.FontSizeProperty, static (IFontElement fe) => fe.FontSize, source: source);
}

content.SetBinding(property, TypedBinding.ForSingleNestingLevel(
property.PropertyName, getter, source: new RelativeBindingSource(RelativeBindingSourceMode.FindAncestor, typeof(TSource))));
if (ShouldSetBinding(content, FontElement.FontFamilyProperty))
{
content.SetBinding(FontElement.FontFamilyProperty, static (IFontElement fe) => fe.FontFamily, source: source);
}
}

static bool ShouldSetBinding(BindableObject content, BindableProperty property)
{
return !content.IsSet(property) && !content.GetIsBound(property);
}

static bool HasTemplateAncestor(ContentPresenter presenter, Type type)
Expand Down
12 changes: 5 additions & 7 deletions src/Controls/src/Core/ContentPresenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@ public class ContentPresenter : Compatibility.Layout, IContentView
/// <include file="../../docs/Microsoft.Maui.Controls/ContentPresenter.xml" path="//Member[@MemberName='.ctor']/Docs/*" />
public ContentPresenter()
{
SetBinding(
this.SetBinding(
ContentProperty,
TypedBinding.ForSingleNestingLevel(
nameof(IContentView.Content),
static (IContentView view) => view.Content,
source: RelativeBindingSource.TemplatedParent,
converter: new ContentConverter(),
converterParameter: this));
static (IContentView view) => view.Content,
source: RelativeBindingSource.TemplatedParent,
converter: new ContentConverter(),
converterParameter: this);
}

/// <include file="../../docs/Microsoft.Maui.Controls/ContentPresenter.xml" path="//Member[@MemberName='Content']/Docs/*" />
Expand Down
12 changes: 12 additions & 0 deletions src/Controls/src/Core/Controls.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@
<ProjectReference Include="..\..\..\Controls\src\Core.Design\Controls.Core.Design.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>

<PropertyGroup>
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Microsoft.Maui.Controls.Generated</InterceptorsPreviewNamespaces>
<!-- https://github.com/dotnet/msbuild/issues/9785 -->
<CompilerResponseFile Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">$(CompilerResponseFile);WorkaroundXamlPreCompilePreviewFeatures.rsp</CompilerResponseFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference
Include="..\..\..\Controls\src\BindingSourceGen\Controls.BindingSourceGen.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ static View CreateView()
{
TextColor = Colors.Black,
};
label.SetBinding(XLabel.TextProperty, TypedBinding.ForSingleNestingLevel(string.Empty, static (object source) => source, converter: new ToTextConverter()));
label.SetBinding(XLabel.TextProperty, static (object source) => source, converter: new ToTextConverter());

return new Controls.StackLayout
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ public override NView CreateNativeView(int index)
var nativeView = base.CreateNativeView(index);

var view = GetTemplatedView(nativeView);
view?.SetBinding(ShellContentItemView.SelectedTextColorProperty, TypedBinding.ForSingleNestingLevel("TitleColor", static (ItemAppearance appearance) => appearance.TitleColor, source: _itemAppearance));
view?.SetBinding(ShellContentItemView.SelectedBarColorProperty, TypedBinding.ForSingleNestingLevel("ForegroundColor", static (ItemAppearance appearance) => appearance.ForegroundColor, source: _itemAppearance));
view?.SetBinding(ShellContentItemView.UnselectedColorProperty, TypedBinding.ForSingleNestingLevel("UnselectedColor", static (ItemAppearance appearance) => appearance.UnselectedColor, source: _itemAppearance));
view?.SetBinding(ShellContentItemView.SelectedTextColorProperty, static (ItemAppearance appearance) => appearance.TitleColor, source: _itemAppearance);
view?.SetBinding(ShellContentItemView.SelectedBarColorProperty, static (ItemAppearance appearance) => appearance.ForegroundColor, source: _itemAppearance);
view?.SetBinding(ShellContentItemView.UnselectedColorProperty, static (ItemAppearance appearance) => appearance.UnselectedColor, source: _itemAppearance);

return nativeView;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ void InitializeComponent()
HorizontalTextAlignment = TextAlignment.Center,
VerticalTextAlignment = TextAlignment.Center,
};
_label.SetBinding(Label.TextProperty, TypedBinding.ForSingleNestingLevel("Title", static (BaseShellItem item) => item.Title));
_label.SetBinding(Label.TextProperty, static (BaseShellItem item) => item.Title);

_bar = new BoxView
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ void InitializeComponent()
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
};
icon.SetBinding(Image.SourceProperty, TypedBinding.ForSingleNestingLevel("Icon", static (BaseShellItem item) => item.Icon));
icon.SetBinding(Image.SourceProperty, static (BaseShellItem item) => item.Icon);

var label = new Label
{
Margin = new Thickness(15, 15),
FontSize = 16,
VerticalTextAlignment = TextAlignment.Center,
};
label.SetBinding(Label.TextProperty, TypedBinding.ForSingleNestingLevel("Title", static (BaseShellItem item) => item.Title));
label.SetBinding(Label.TextProperty, static (BaseShellItem item) => item.Title);

_grid = new Grid
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ static View CreateView()
{
TextColor = GColors.Black,
};
label.SetBinding(Label.TextProperty, TypedBinding.ForSingleNestingLevel(string.Empty, static (object source) => source));
label.SetBinding(Label.TextProperty, static (object source) => source);

return new Controls.StackLayout
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public override NView CreateNativeView(int index)
var nativeView = base.CreateNativeView(index);

var view = GetTemplatedView(nativeView);
view?.SetBinding(ShellSectionItemView.SelectedColorProperty, TypedBinding.ForSingleNestingLevel("TitleColor", static (ItemAppearance appearance) => appearance.TitleColor, source: _itemAppearance));
view?.SetBinding(ShellSectionItemView.UnselectedColorProperty, TypedBinding.ForSingleNestingLevel("UnselectedColor", static (ItemAppearance appearance) => appearance.UnselectedColor, source: _itemAppearance));
view?.SetBinding(ShellSectionItemView.SelectedColorProperty, static (ItemAppearance appearance) => appearance.TitleColor, source: _itemAppearance);
view?.SetBinding(ShellSectionItemView.UnselectedColorProperty, static (ItemAppearance appearance) => appearance.UnselectedColor, source: _itemAppearance);

return nativeView;
}
Expand Down
Loading
Loading