diff --git a/Avalonia.sln b/Avalonia.sln index c3e86734d8..104245118a 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -177,8 +177,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.OpenGL", "src\Aval EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Native", "src\Avalonia.Native\Avalonia.Native.csproj", "{12A91A62-C064-42CA-9A8C-A1272F354388}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesktopRuntime", "src\Avalonia.DesktopRuntime\Avalonia.DesktopRuntime.csproj", "{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Packages", "Packages", "{E870DCD7-F46A-498D-83FC-D0FD13E0A11C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia", "packages\Avalonia\Avalonia.csproj", "{D49233F8-F29C-47DD-9975-C4C9E4502720}" @@ -1559,30 +1557,6 @@ Global {12A91A62-C064-42CA-9A8C-A1272F354388}.Release|iPhone.Build.0 = Release|Any CPU {12A91A62-C064-42CA-9A8C-A1272F354388}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {12A91A62-C064-42CA-9A8C-A1272F354388}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.AppStore|iPhone.Build.0 = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Debug|Any CPU.Build.0 = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Debug|iPhone.Build.0 = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Release|Any CPU.ActiveCfg = Release|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Release|Any CPU.Build.0 = Release|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Release|iPhone.ActiveCfg = Release|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Release|iPhone.Build.0 = Release|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {D49233F8-F29C-47DD-9975-C4C9E4502720}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {D49233F8-F29C-47DD-9975-C4C9E4502720}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {D49233F8-F29C-47DD-9975-C4C9E4502720}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU diff --git a/build/CoreLibraries.props b/build/CoreLibraries.props index 3fccad2641..6bf69603c0 100644 --- a/build/CoreLibraries.props +++ b/build/CoreLibraries.props @@ -16,7 +16,6 @@ - diff --git a/nukebuild/numerge.config b/nukebuild/numerge.config index e4e15d693d..d1c0408241 100644 --- a/nukebuild/numerge.config +++ b/nukebuild/numerge.config @@ -11,11 +11,6 @@ "Id": "Avalonia.Build.Tasks", "IgnoreMissingFrameworkBinaries": true, "DoNotMergeDependencies": true - }, - { - "Id": "Avalonia.DesktopRuntime", - "IgnoreMissingFrameworkBinaries": true, - "IgnoreMissingFrameworkDependencies": true } ] } diff --git a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs index 160c7301f5..1ca70140ec 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs @@ -22,19 +22,9 @@ namespace Avalonia.Data.Core.Plugins var method = GetFirstMethodWithName(instance.GetType(), methodName); - if (method != null) + if (method is not null) { - var parameters = method.GetParameters(); - - if (parameters.Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8) - { - var exception = new ArgumentException( - "Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", - nameof(methodName)); - return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); - } - - return new Accessor(reference, method, parameters); + return new Accessor(reference, method); } else { @@ -82,18 +72,20 @@ namespace Avalonia.Data.Core.Plugins private sealed class Accessor : PropertyAccessorBase { - public Accessor(WeakReference reference, MethodInfo method, ParameterInfo[] parameters) + public Accessor(WeakReference reference, MethodInfo method) { _ = reference ?? throw new ArgumentNullException(nameof(reference)); _ = method ?? throw new ArgumentNullException(nameof(method)); var returnType = method.ReturnType; - bool hasReturn = returnType != typeof(void); - var signatureTypeCount = (hasReturn ? 1 : 0) + parameters.Length; + var parameters = method.GetParameters(); + + var signatureTypeCount = parameters.Length + 1; var paramTypes = new Type[signatureTypeCount]; + for (var i = 0; i < parameters.Length; i++) { ParameterInfo parameter = parameters[i]; @@ -101,16 +93,9 @@ namespace Avalonia.Data.Core.Plugins paramTypes[i] = parameter.ParameterType; } - if (hasReturn) - { - paramTypes[paramTypes.Length - 1] = returnType; + paramTypes[paramTypes.Length - 1] = returnType; - PropertyType = Expression.GetFuncType(paramTypes); - } - else - { - PropertyType = Expression.GetActionType(paramTypes); - } + PropertyType = Expression.GetDelegateType(paramTypes); if (method.IsStatic) { diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 593d79471e..fa437de186 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -124,6 +124,9 @@ namespace Avalonia.Build.Tasks var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure", TypeAttributes.Class, asm.MainModule.TypeSystem.Object); asm.MainModule.Types.Add(indexerAccessorClosure); + var trampolineBuilder = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlTrampolines", + TypeAttributes.Class, asm.MainModule.TypeSystem.Object); + asm.MainModule.Types.Add(trampolineBuilder); var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem); var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem, @@ -133,6 +136,7 @@ namespace Avalonia.Build.Tasks AvaloniaXamlIlLanguage.CustomValueConverter, new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)), new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)), + new XamlIlTrampolineBuilder(typeSystem.CreateTypeBuilder(trampolineBuilder)), new DeterministicIdGenerator()); @@ -255,6 +259,8 @@ namespace Avalonia.Build.Tasks true), (closureName, closureBaseType) => populateBuilder.DefineSubType(closureBaseType, closureName, false), + (closureName, returnType, parameterTypes) => + populateBuilder.DefineDelegateSubType(closureName, false, returnType, parameterTypes), res.Uri, res ); diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index 2c206b53f6..55372a7f1a 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -48,6 +48,7 @@ MembersMustExist : Member 'public Avalonia.Media.FormattedText Avalonia.Controls MembersMustExist : Member 'public System.Int32 Avalonia.Controls.Presenters.TextPresenter.GetCaretIndex(Avalonia.Point)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected void Avalonia.Controls.Presenters.TextPresenter.InvalidateFormattedText()' does not exist in the implementation but it does exist in the contract. CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Primitives.PopupRoot' does not implement interface 'Avalonia.Utilities.IWeakSubscriber' in the implementation but it does in the contract. +TypesMustExist : Type 'Avalonia.Platform.ExportWindowingSubsystemAttribute' does not exist in the implementation but it does exist in the contract. EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract. @@ -67,4 +68,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract. -Total Issues: 68 +Total Issues: 69 diff --git a/src/Avalonia.Controls/Platform/ExportWindowingSubsystemAttribute.cs b/src/Avalonia.Controls/Platform/ExportWindowingSubsystemAttribute.cs deleted file mode 100644 index 2a401ad912..0000000000 --- a/src/Avalonia.Controls/Platform/ExportWindowingSubsystemAttribute.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace Avalonia.Platform -{ - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public class ExportWindowingSubsystemAttribute : Attribute - { - public ExportWindowingSubsystemAttribute(OperatingSystemType requiredRuntimePlatform, int priority, string name, Type initializationType, - string initializationMethod, Type? environmentChecker = null) - { - Name = name; - InitializationType = initializationType; - InitializationMethod = initializationMethod; - EnvironmentChecker = environmentChecker; - RequiredOS = requiredRuntimePlatform; - Priority = priority; - } - - public string InitializationMethod { get; private set; } - public Type? EnvironmentChecker { get; } - public Type InitializationType { get; private set; } - public string Name { get; private set; } - public int Priority { get; private set; } - public OperatingSystemType RequiredOS { get; private set; } - } -} diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index a5f48bd4a5..2e31e1095d 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -255,6 +255,11 @@ namespace Avalonia.Controls /// public new IWindowImpl? PlatformImpl => (IWindowImpl?)base.PlatformImpl; + /// + /// Gets a collection of child windows owned by this window. + /// + public IReadOnlyList OwnedWindows => _children.Select(x => x.child).ToList(); + /// /// Gets or sets a value indicating how the window will size itself to fit its content. /// diff --git a/src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt b/src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt deleted file mode 100644 index 0493db9ab3..0000000000 --- a/src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt +++ /dev/null @@ -1,3 +0,0 @@ -Compat issues with assembly Avalonia.DesktopRuntime: -TypesMustExist : Type 'Avalonia.Shared.PlatformSupport.AssetLoader' does not exist in the implementation but it does exist in the contract. -Total Issues: 1 diff --git a/src/Avalonia.DesktopRuntime/AppBuilder.cs b/src/Avalonia.DesktopRuntime/AppBuilder.cs deleted file mode 100644 index 2946324c83..0000000000 --- a/src/Avalonia.DesktopRuntime/AppBuilder.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using Avalonia.Controls; -using Avalonia.Platform; -using Avalonia.PlatformSupport; - -namespace Avalonia -{ - /// - /// Initializes platform-specific services for an . - /// - public sealed class AppBuilder : AppBuilderBase - { - /// - /// Initializes a new instance of the class. - /// - public AppBuilder() - : base(new StandardRuntimePlatform(), - builder => StandardRuntimePlatformServices.Register(builder.ApplicationType.Assembly)) - { - } - - bool CheckEnvironment(Type checkerType) - { - if (checkerType == null) - return true; - try - { - return ((IModuleEnvironmentChecker) Activator.CreateInstance(checkerType)).IsCompatible; - } - catch - { - return false; - } - } - - /// - /// Instructs the to use the best settings for the platform. - /// - /// An instance. - public AppBuilder UseSubsystemsFromStartupDirectory() - { - var os = RuntimePlatform.GetRuntimeInfo().OperatingSystem; - - LoadAssembliesInDirectory(); - - var windowingSubsystemAttribute = (from assembly in AppDomain.CurrentDomain.GetAssemblies() - from attribute in assembly.GetCustomAttributes() - where attribute.RequiredOS == os && CheckEnvironment(attribute.EnvironmentChecker) - orderby attribute.Priority ascending - select attribute).FirstOrDefault(); - if (windowingSubsystemAttribute == null) - { - throw new InvalidOperationException("No windowing subsystem found. Are you missing assembly references?"); - } - - var renderingSubsystemAttribute = (from assembly in AppDomain.CurrentDomain.GetAssemblies() - from attribute in assembly.GetCustomAttributes() - where attribute.RequiredOS == os && CheckEnvironment(attribute.EnvironmentChecker) - where attribute.RequiresWindowingSubsystem == null - || attribute.RequiresWindowingSubsystem == windowingSubsystemAttribute.Name - orderby attribute.Priority ascending - select attribute).FirstOrDefault(); - - if (renderingSubsystemAttribute == null) - { - throw new InvalidOperationException("No rendering subsystem found. Are you missing assembly references?"); - } - - UseWindowingSubsystem(() => windowingSubsystemAttribute.InitializationType - .GetRuntimeMethod(windowingSubsystemAttribute.InitializationMethod, Type.EmptyTypes).Invoke(null, null), - windowingSubsystemAttribute.Name); - - UseRenderingSubsystem(() => renderingSubsystemAttribute.InitializationType - .GetRuntimeMethod(renderingSubsystemAttribute.InitializationMethod, Type.EmptyTypes).Invoke(null, null), - renderingSubsystemAttribute.Name); - - return this; - } - - private void LoadAssembliesInDirectory() - { - var location = Assembly.GetEntryAssembly().Location; - if (string.IsNullOrWhiteSpace(location)) - return; - var dir = new FileInfo(location).Directory; - if (dir == null) - return; - foreach (var file in dir.EnumerateFiles("*.dll")) - { - try - { - Assembly.LoadFile(file.FullName); - } - catch (Exception) - { - } - } - } - } -} diff --git a/src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj b/src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj deleted file mode 100644 index 25effae46e..0000000000 --- a/src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - net6.0;net461;netcoreapp2.0 - - - - - - - - - - - - - - - - - diff --git a/src/Avalonia.Native/Properties/AssemblyInfo.cs b/src/Avalonia.Native/Properties/AssemblyInfo.cs deleted file mode 100644 index d4766e45dc..0000000000 --- a/src/Avalonia.Native/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Avalonia.Native; -using Avalonia.Platform; - -[assembly: ExportWindowingSubsystem(OperatingSystemType.OSX, 1, "AvaloniaNative", typeof(AvaloniaNativePlatform), nameof(AvaloniaNativePlatform.Initialize))] diff --git a/src/Avalonia.PlatformSupport/AppBuilder.cs b/src/Avalonia.PlatformSupport/AppBuilder.cs new file mode 100644 index 0000000000..136f1f39b3 --- /dev/null +++ b/src/Avalonia.PlatformSupport/AppBuilder.cs @@ -0,0 +1,20 @@ +using Avalonia.Controls; +using Avalonia.PlatformSupport; + +namespace Avalonia +{ + /// + /// Initializes platform-specific services for an . + /// + public sealed class AppBuilder : AppBuilderBase + { + /// + /// Initializes a new instance of the class. + /// + public AppBuilder() + : base(new StandardRuntimePlatform(), + builder => StandardRuntimePlatformServices.Register(builder.ApplicationType?.Assembly)) + { + } + } +} diff --git a/src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj b/src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj index be73d87e2c..e08dc5e194 100644 --- a/src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj +++ b/src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 70fcb6bc00..2a2f4d13f9 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -115,6 +115,7 @@ CannotAddAbstractMembers : Member 'public Avalonia.Media.BaselineAlignment Avalo MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Media.TextFormatting.TextShaper.ShapeText(Avalonia.Utilities.ReadOnlySlice, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'Avalonia.Media.TextFormatting.Unicode.BiDiClass' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.TextFormatting.Unicode.BiDiClass Avalonia.Media.TextFormatting.Unicode.Codepoint.BiDiClass.get()' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'Avalonia.Platform.ExportRenderingSubsystemAttribute' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawEllipse(Avalonia.Media.IBrush, Avalonia.Media.IPen, Avalonia.Rect)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawText(Avalonia.Media.IBrush, Avalonia.Point, Avalonia.Platform.IFormattedTextImpl)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.IDrawingContextImpl.DrawText(Avalonia.Media.IBrush, Avalonia.Point, Avalonia.Platform.IFormattedTextImpl)' does not exist in the implementation but it does exist in the contract. diff --git a/src/Avalonia.Visuals/Platform/ExportRenderingSubsystemAttribute.cs b/src/Avalonia.Visuals/Platform/ExportRenderingSubsystemAttribute.cs deleted file mode 100644 index db157304f4..0000000000 --- a/src/Avalonia.Visuals/Platform/ExportRenderingSubsystemAttribute.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace Avalonia.Platform -{ - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public class ExportRenderingSubsystemAttribute : Attribute - { - public ExportRenderingSubsystemAttribute(OperatingSystemType requiredOS, int priority, string name, Type initializationType, string initializationMethod, - Type? environmentChecker = null) - { - Name = name; - InitializationType = initializationType; - InitializationMethod = initializationMethod; - EnvironmentChecker = environmentChecker; - RequiredOS = requiredOS; - Priority = priority; - } - - public string InitializationMethod { get; private set; } - public Type? EnvironmentChecker { get; } - public Type InitializationType { get; private set; } - public string Name { get; private set; } - public int Priority { get; private set; } - public OperatingSystemType RequiredOS { get; private set; } - public string? RequiresWindowingSubsystem { get; set; } - } -} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs index ece90762cb..1e2a77c34d 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs @@ -177,11 +177,13 @@ namespace Avalonia.Markup.Xaml.XamlIl var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri); var clrPropertyBuilder = tb.DefineNestedType("ClrProperties_" + Guid.NewGuid().ToString("N")); var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N")); + var trampolineBuilder = _sreBuilder.DefineType("Trampolines_" + Guid.NewGuid().ToString("N")); var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm, _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter, new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)), - new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType))), + new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)), + new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))), _sreEmitMappings, _sreContextType) { EnableIlVerification = true }; @@ -196,6 +198,7 @@ namespace Avalonia.Markup.Xaml.XamlIl compiler.ParseAndCompile(xaml, uri?.ToString(), null, _sreTypeSystem.CreateTypeBuilder(tb), overrideType); var created = tb.CreateTypeInfo(); clrPropertyBuilder.CreateTypeInfo(); + trampolineBuilder.CreateTypeInfo(); return LoadOrPopulate(created, rootInstance); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs index f6f47dce0d..9fc6b5d517 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs @@ -7,6 +7,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { public XamlIlClrPropertyInfoEmitter ClrPropertyEmitter { get; } public XamlIlPropertyInfoAccessorFactoryEmitter AccessorFactoryEmitter { get; } + public XamlIlTrampolineBuilder TrampolineBuilder { get; } public AvaloniaXamlIlCompilerConfiguration(IXamlTypeSystem typeSystem, IXamlAssembly defaultAssembly, @@ -15,13 +16,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions XamlValueConverter customValueConverter, XamlIlClrPropertyInfoEmitter clrPropertyEmitter, XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter, + XamlIlTrampolineBuilder trampolineBuilder, IXamlIdentifierGenerator identifierGenerator = null) : base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter, identifierGenerator) { ClrPropertyEmitter = clrPropertyEmitter; AccessorFactoryEmitter = accessorFactoryEmitter; + TrampolineBuilder = trampolineBuilder; AddExtra(ClrPropertyEmitter); AddExtra(AccessorFactoryEmitter); + AddExtra(TrampolineBuilder); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index 4592b9c8b4..b622971d38 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -201,7 +201,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions if (type.Equals(types.Classes)) { var classes = text.Split(' '); - var classNodes = classes.Select(c => new XamlAstTextNode(node, c, types.XamlIlTypes.String)).ToArray(); + var classNodes = classes.Select(c => new XamlAstTextNode(node, c, type: types.XamlIlTypes.String)).ToArray(); result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, types.Classes, types.XamlIlTypes.String, classNodes); return true; diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs index 79589a5a4f..4c4df1f53a 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs @@ -77,7 +77,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers throw new XamlParseException($"Cannot find '{property.Property}' on '{type}", node); if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, - new XamlAstTextNode(node, property.Value, context.Configuration.WellKnownTypes.String), + new XamlAstTextNode(node, property.Value, type: context.Configuration.WellKnownTypes.String), targetProperty.PropertyType, out var typedValue)) throw new XamlParseException( $"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}", @@ -118,7 +118,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers .GetAvaloniaPropertyType(targetPropertyField, context.GetAvaloniaTypes(), node); if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, - new XamlAstTextNode(node, attachedProperty.Value, context.Configuration.WellKnownTypes.String), + new XamlAstTextNode(node, attachedProperty.Value, type: context.Configuration.WellKnownTypes.String), targetPropertyType, out var typedValue)) throw new XamlParseException( $"Cannot convert '{attachedProperty.Value}' to '{targetPropertyType.GetFqn()}", diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 89b88790dc..90c3989238 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -20,10 +20,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlMethod AvaloniaObjectBindMethod { get; } public IXamlMethod AvaloniaObjectSetValueMethod { get; } public IXamlType IDisposable { get; } + public IXamlType ICommand { get; } public XamlTypeWellKnownTypes XamlIlTypes { get; } public XamlLanguageTypeMappings XamlIlMappings { get; } public IXamlType Transitions { get; } public IXamlType AssignBindingAttribute { get; } + public IXamlType DependsOnAttribute { get; } public IXamlType UnsetValueType { get; } public IXamlType StyledElement { get; } public IXamlType IStyledElement { get; } @@ -85,6 +87,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType IBrush { get; } public IXamlType ImmutableSolidColorBrush { get; } public IXamlConstructor ImmutableSolidColorBrushConstructorColor { get; } + public IXamlType TypeUtilities { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -98,8 +101,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers BindingPriority = cfg.TypeSystem.GetType("Avalonia.Data.BindingPriority"); IBinding = cfg.TypeSystem.GetType("Avalonia.Data.IBinding"); IDisposable = cfg.TypeSystem.GetType("System.IDisposable"); + ICommand = cfg.TypeSystem.GetType("System.Windows.Input.ICommand"); Transitions = cfg.TypeSystem.GetType("Avalonia.Animation.Transitions"); AssignBindingAttribute = cfg.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute"); + DependsOnAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DependsOnAttribute"); AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, IAvaloniaObject, AvaloniaProperty, IBinding, cfg.WellKnownTypes.Object); @@ -187,6 +192,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers IBrush = cfg.TypeSystem.GetType("Avalonia.Media.IBrush"); ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush"); ImmutableSolidColorBrushConstructorColor = ImmutableSolidColorBrush.GetConstructor(new List { UInt }); + TypeUtilities = cfg.TypeSystem.GetType("Avalonia.Utilities.TypeUtilities"); } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs index 7f7f60ed94..db4cee32b0 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -14,7 +14,7 @@ using XamlX.Emit; using XamlX.IL; using Avalonia.Utilities; -using XamlIlEmitContext = XamlX.Emit.XamlEmitContext; +using XamlIlEmitContext = XamlX.Emit.XamlEmitContextWithLocals; namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { @@ -32,6 +32,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions selfType, bindingPath.Path); + transformed = TransformForTargetTyping(transformed, context); + bindingResultType = transformed.BindingResultType; binding.Arguments[0] = transformed; } @@ -54,6 +56,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions selfType, bindingPathNode.Path); + transformed = TransformForTargetTyping(transformed, context); + bindingResultType = transformed.BindingResultType; bindingPathAssignment.Values[0] = transformed; } @@ -66,7 +70,32 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions return bindingResultType; } - private static IXamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, Func startTypeResolver, IXamlType selfType, IEnumerable bindingExpression) + private static XamlIlBindingPathNode TransformForTargetTyping(XamlIlBindingPathNode transformed, AstTransformationContext context) + { + if (context.ParentNodes().OfType().FirstOrDefault().Property.Getter.ReturnType == context.GetAvaloniaTypes().ICommand + && transformed.Elements[transformed.Elements.Count - 1] is XamlIlClrMethodPathElementNode method) + { + IXamlMethod executeMethod = method.Method; + IXamlMethod canExecuteMethod = executeMethod.DeclaringType.FindMethod(new FindMethodMethodSignature($"Can{executeMethod.Name}", context.Configuration.WellKnownTypes.Boolean, context.Configuration.WellKnownTypes.Object)); + List dependsOnProperties = new(); + if (canExecuteMethod is not null) + { + foreach (var attr in canExecuteMethod.CustomAttributes) + { + if (attr.Type == context.GetAvaloniaTypes().DependsOnAttribute) + { + dependsOnProperties.Add((string)attr.Parameters[0]); + } + } + } + transformed.Elements.RemoveAt(transformed.Elements.Count - 1); + transformed.Elements.Add(new XamlIlClrMethodAsCommandPathElementNode(context.GetAvaloniaTypes().ICommand, executeMethod, canExecuteMethod, dependsOnProperties)); + } + + return transformed; + } + + private static XamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, Func startTypeResolver, IXamlType selfType, IEnumerable bindingExpression) { List transformNodes = new List(); List nodes = new List(); @@ -126,16 +155,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyFieldMaybe, XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyFieldMaybe, context.GetAvaloniaTypes(), lineInfo))); } - else + else if (GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName) is IXamlProperty clrProperty) { - var clrProperty = GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName); - - if (clrProperty is null) - { - throw new XamlX.XamlParseException($"Unable to resolve property of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo); - } nodes.Add(new XamlIlClrPropertyPathElementNode(clrProperty)); } + else if (GetAllDefinedMethods(targetType).FirstOrDefault(m => m.Name == propName.PropertyName) is IXamlMethod method) + { + nodes.Add(new XamlIlClrMethodPathElementNode(method, context.Configuration.WellKnownTypes.Delegate)); + } + else + { + throw new XamlX.XamlParseException($"Unable to resolve property or method of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo); + } break; } case BindingExpressionGrammar.IndexerNode indexer: @@ -284,6 +315,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } } + static IEnumerable GetAllDefinedMethods(IXamlType type) + { + foreach (var t in TraverseTypeHierarchy(type)) + { + foreach (var m in t.Methods) + { + yield return m; + } + } + } + static IEnumerable TraverseTypeHierarchy(IXamlType type) { if (type.IsInterface) @@ -538,6 +580,131 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions public IXamlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0]; } + class XamlIlClrMethodPathElementNode : IXamlIlBindingPathElementNode + { + + public XamlIlClrMethodPathElementNode(IXamlMethod method, IXamlType systemDelegateType) + { + Method = method; + Type = systemDelegateType; + } + public IXamlMethod Method { get; } + + public IXamlType Type { get; } + + public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen) + { + IXamlTypeBuilder newDelegateTypeBuilder = null; + IXamlType specificDelegateType; + if (Method.ReturnType == context.Configuration.WellKnownTypes.Void && Method.Parameters.Count == 0) + { + specificDelegateType = context.Configuration.TypeSystem + .GetType("System.Action"); + } + else if (Method.ReturnType == context.Configuration.WellKnownTypes.Void && Method.Parameters.Count <= 16) + { + specificDelegateType = context.Configuration.TypeSystem + .GetType($"System.Action`{Method.Parameters.Count}") + .MakeGenericType(Method.Parameters); + } + else if (Method.Parameters.Count <= 16) + { + List genericParameters = new(); + genericParameters.AddRange(Method.Parameters); + genericParameters.Add(Method.ReturnType); + specificDelegateType = context.Configuration.TypeSystem + .GetType($"System.Func`{Method.Parameters.Count + 1}") + .MakeGenericType(genericParameters); + } + else + { + // In this case, we need to emit our own delegate type. + string delegateTypeName = context.Configuration.IdentifierGenerator.GenerateIdentifierPart(); + specificDelegateType = newDelegateTypeBuilder = context.DefineDelegateSubType(delegateTypeName, Method.ReturnType, Method.Parameters); + } + + codeGen + .Ldtoken(Method) + .Ldtoken(specificDelegateType) + .EmitCall(context.GetAvaloniaTypes() + .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Method")); + + newDelegateTypeBuilder?.CreateType(); + } + } + + class XamlIlClrMethodAsCommandPathElementNode : IXamlIlBindingPathElementNode + { + private readonly IXamlMethod _executeMethod; + private readonly IXamlMethod _canExecuteMethod; + private readonly IReadOnlyList _dependsOnProperties; + + public XamlIlClrMethodAsCommandPathElementNode(IXamlType iCommandType, IXamlMethod executeMethod, IXamlMethod canExecuteMethod, IReadOnlyList dependsOnProperties) + { + Type = iCommandType; + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + _dependsOnProperties = dependsOnProperties; + } + + + public IXamlType Type { get; } + + public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen) + { + var trampolineBuilder = context.Configuration.GetExtra(); + var objectType = context.Configuration.WellKnownTypes.Object; + codeGen + .Ldstr(_executeMethod.Name) + .Ldnull() + .Ldftn(trampolineBuilder.EmitCommandExecuteTrampoline(context, _executeMethod)) + .Newobj(context.Configuration.TypeSystem.GetType("System.Action`2") + .MakeGenericType(objectType, objectType) + .GetConstructor(new() { objectType, context.Configuration.TypeSystem.GetType("System.IntPtr") })); + + if (_canExecuteMethod is null) + { + codeGen.Ldnull(); + } + else + { + codeGen + .Ldnull() + .Ldftn(trampolineBuilder.EmitCommandCanExecuteTrampoline(context, _canExecuteMethod)) + .Newobj(context.Configuration.TypeSystem.GetType("System.Func`3") + .MakeGenericType(objectType, objectType, context.Configuration.WellKnownTypes.Boolean) + .GetConstructor(new() { objectType, context.Configuration.TypeSystem.GetType("System.IntPtr") })); + } + + if (_dependsOnProperties is { Count:> 1 }) + { + using var dependsOnPropertiesArray = context.GetLocalOfType(context.Configuration.WellKnownTypes.String.MakeArrayType(1)); + codeGen + .Ldc_I4(_dependsOnProperties.Count) + .Newarr(context.Configuration.WellKnownTypes.String) + .Stloc(dependsOnPropertiesArray.Local); + + for (int i = 0; i < _dependsOnProperties.Count; i++) + { + codeGen + .Ldloc(dependsOnPropertiesArray.Local) + .Ldc_I4(i) + .Ldstr(_dependsOnProperties[i]) + .Stelem_ref(); + } + codeGen.Ldloc(dependsOnPropertiesArray.Local); + } + else + { + codeGen.Ldnull(); + } + + codeGen + .EmitCall(context.GetAvaloniaTypes() + .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Command")); + } + } + class XamlIlClrIndexerPathElementNode : IXamlIlBindingPathElementNode { private readonly IXamlProperty _property; @@ -660,10 +827,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } } - class XamlIlBindingPathNode : XamlAstNode, IXamlIlBindingPathNode, IXamlAstEmitableNode + class XamlIlBindingPathNode : XamlAstNode, IXamlIlBindingPathNode, IXamlAstLocalsEmitableNode { private readonly List _transformElements; - private readonly List _elements; public XamlIlBindingPathNode(IXamlLineInfo lineInfo, IXamlType bindingPathType, @@ -672,16 +838,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { Type = new XamlAstClrTypeReference(lineInfo, bindingPathType, false); _transformElements = transformElements; - _elements = elements; + Elements = elements; } public IXamlType BindingResultType => _transformElements.Count > 0 ? _transformElements[0].Type - : _elements[_elements.Count - 1].Type; + : Elements[Elements.Count - 1].Type; public IXamlAstTypeReference Type { get; } + public List Elements { get; } + public XamlILNodeEmitResult Emit(XamlIlEmitContext context, IXamlILEmitter codeGen) { var types = context.GetAvaloniaTypes(); @@ -692,7 +860,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions transform.Emit(context, codeGen); } - foreach (var element in _elements) + foreach (var element in Elements) { element.Emit(context, codeGen); } @@ -710,11 +878,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions _transformElements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor); } } - for (int i = 0; i < _elements.Count; i++) + for (int i = 0; i < Elements.Count; i++) { - if (_elements[i] is IXamlAstNode ast) + if (Elements[i] is IXamlAstNode ast) { - _elements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor); + Elements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs new file mode 100644 index 0000000000..a28607f0f4 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using XamlX.Emit; +using XamlX.IL; +using XamlX.TypeSystem; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; +using System.Reflection.Emit; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions +{ + internal class XamlIlTrampolineBuilder + { + private IXamlTypeBuilder _builder; + private Dictionary _trampolines = new(); + + public XamlIlTrampolineBuilder(IXamlTypeBuilder builder) + { + _builder = builder; + } + + public IXamlMethod EmitCommandExecuteTrampoline(XamlEmitContext context, IXamlMethod executeMethod) + { + Debug.Assert(!executeMethod.IsStatic); + string methodName = $"{executeMethod.DeclaringType.GetFqn()}+{executeMethod.Name}_{executeMethod.Parameters.Count}!CommandExecuteTrampoline"; + if (_trampolines.TryGetValue(methodName, out var method)) + { + return method; + } + var trampoline = _builder.DefineMethod( + context.Configuration.WellKnownTypes.Void, + new[] { context.Configuration.WellKnownTypes.Object, context.Configuration.WellKnownTypes.Object }, + methodName, + true, + true, + false); + var gen = trampoline.Generator; + if (executeMethod.DeclaringType.IsValueType) + { + gen.Ldarg_0() + .Unbox(executeMethod.DeclaringType); + } + else + { + gen.Ldarg_0() + .Castclass(executeMethod.DeclaringType); + } + if (executeMethod.Parameters.Count != 0) + { + Debug.Assert(executeMethod.Parameters.Count == 1); + if (executeMethod.Parameters[0] != context.Configuration.WellKnownTypes.Object) + { + var convertedValue = gen.DefineLocal(context.Configuration.WellKnownTypes.Object); + gen.Ldtype(executeMethod.Parameters[0]) + .Ldarg(1) + .EmitCall(context.Configuration.WellKnownTypes.CultureInfo.FindMethod(m => m.Name == "get_CurrentCulture")) + .Ldloca(convertedValue) + .EmitCall( + context.GetAvaloniaTypes().TypeUtilities.FindMethod(m => m.Name == "TryConvert"), + swallowResult: true) + .Ldloc(convertedValue) + .Unbox_Any(executeMethod.Parameters[0]); + } + else + { + gen.Ldarg(1); + } + } + gen.EmitCall(executeMethod, swallowResult: true); + gen.Ret(); + + _trampolines.Add(methodName, trampoline); + return trampoline; + } + + public IXamlMethod EmitCommandCanExecuteTrampoline(XamlEmitContext context, IXamlMethod canExecuteMethod) + { + Debug.Assert(!canExecuteMethod.IsStatic); + Debug.Assert(canExecuteMethod.Parameters.Count == 1); + Debug.Assert(canExecuteMethod.ReturnType == context.Configuration.WellKnownTypes.Boolean); + string methodName = $"{canExecuteMethod.DeclaringType.GetFqn()}+{canExecuteMethod.Name}!CommandCanExecuteTrampoline"; + if (_trampolines.TryGetValue(methodName, out var method)) + { + return method; + } + var trampoline = _builder.DefineMethod( + context.Configuration.WellKnownTypes.Boolean, + new[] { context.Configuration.WellKnownTypes.Object, context.Configuration.WellKnownTypes.Object }, + methodName, + true, + true, + false); + if (canExecuteMethod.DeclaringType.IsValueType) + { + trampoline.Generator + .Ldarg_0() + .Unbox(canExecuteMethod.DeclaringType); + } + else + { + trampoline.Generator + .Ldarg_0() + .Castclass(canExecuteMethod.DeclaringType); + } + trampoline.Generator + .Ldarg(1) + .Emit(OpCodes.Tailcall) + .EmitCall(canExecuteMethod) + .Ret(); + + _trampolines.Add(methodName, trampoline); + return trampoline; + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github index 8e20d65eb5..daaac590e0 160000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github @@ -1 +1 @@ -Subproject commit 8e20d65eb5f1efbae08e49b18f39bfdce32df7b3 +Subproject commit daaac590e078967b78045f74c38ef046d00d8582 diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 86132c5d27..548aae31a8 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -18,8 +18,10 @@ + + diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs new file mode 100644 index 0000000000..970cc767f7 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Text; +using System.Windows.Input; +using Avalonia.Data; +using Avalonia.Data.Core.Plugins; +using Avalonia.Utilities; + +namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings +{ + internal class CommandAccessorPlugin : IPropertyAccessorPlugin + { + private readonly Action _execute; + private readonly Func _canExecute; + private readonly ISet _dependsOnProperties; + + public CommandAccessorPlugin(Action execute, Func canExecute, ISet dependsOnProperties) + { + _execute = execute; + _canExecute = canExecute; + _dependsOnProperties = dependsOnProperties; + } + + public bool Match(object obj, string propertyName) + { + throw new InvalidOperationException("The CommandAccessorPlugin does not support dynamic matching"); + } + + public IPropertyAccessor Start(WeakReference reference, string propertyName) + { + return new CommandAccessor(reference, _execute, _canExecute, _dependsOnProperties); + } + + private sealed class CommandAccessor : PropertyAccessorBase + { + private readonly WeakReference _reference; + private Command _command; + private readonly ISet _dependsOnProperties; + + public CommandAccessor(WeakReference reference, Action execute, Func canExecute, ISet dependsOnProperties) + { + Contract.Requires(reference != null); + + _reference = reference; + _dependsOnProperties = dependsOnProperties; + _command = new Command(reference, execute, canExecute); + + } + + public override object Value => _reference.TryGetTarget(out var _) ? _command : null; + + private void RaiseCanExecuteChanged() + { + _command.RaiseCanExecuteChanged(); + } + + private sealed class Command : ICommand + { + private readonly WeakReference _target; + private readonly Action _execute; + private readonly Func _canExecute; + + public event EventHandler CanExecuteChanged; + + public Command(WeakReference target, Action execute, Func canExecute) + { + _target = target; + _execute = execute; + _canExecute = canExecute; + } + + public void RaiseCanExecuteChanged() + { + Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty) + , Threading.DispatcherPriority.Input); + } + + public bool CanExecute(object parameter) + { + if (_target.TryGetTarget(out var target)) + { + if (_canExecute == null) + { + return true; + } + return _canExecute(target, parameter); + } + return false; + } + + public void Execute(object parameter) + { + if (_target.TryGetTarget(out var target)) + { + _execute(target, parameter); + } + } + } + + public override Type PropertyType => typeof(ICommand); + + public override bool SetValue(object value, BindingPriority priority) + { + return false; + } + + void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (string.IsNullOrEmpty(e.PropertyName) || _dependsOnProperties.Contains(e.PropertyName)) + { + RaiseCanExecuteChanged(); + } + } + + protected override void SubscribeCore() + { + SendCurrentValue(); + SubscribeToChanges(); + } + + protected override void UnsubscribeCore() + { + if (_dependsOnProperties is { Count: > 0 } && _reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc) + { + WeakEventHandlerManager.Unsubscribe( + inpc, + nameof(INotifyPropertyChanged.PropertyChanged), + OnNotifyPropertyChanged); + } + } + + private void SendCurrentValue() + { + try + { + var value = Value; + PublishValue(value); + } + catch { } + } + + private void SubscribeToChanges() + { + if (_dependsOnProperties is { Count:>0 } && _reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc) + { + WeakEventHandlerManager.Subscribe( + inpc, + nameof(INotifyPropertyChanged.PropertyChanged), + OnNotifyPropertyChanged); + } + } + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs index 11489c39aa..73a14fd437 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Avalonia.Controls; using Avalonia.Data.Core; using Avalonia.Data.Core.Plugins; @@ -36,6 +37,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings case PropertyElement prop: node = new PropertyAccessorNode(prop.Property.Name, enableValidation, new PropertyInfoAccessorPlugin(prop.Property, prop.AccessorFactory)); break; + case MethodAsCommandElement methodAsCommand: + node = new PropertyAccessorNode(methodAsCommand.MethodName, enableValidation, new CommandAccessorPlugin(methodAsCommand.ExecuteMethod, methodAsCommand.CanExecuteMethod, methodAsCommand.DependsOnProperties)); + break; + case MethodAsDelegateElement methodAsDelegate: + node = new PropertyAccessorNode(methodAsDelegate.Method.Name, enableValidation, new MethodAccessorPlugin(methodAsDelegate.Method, methodAsDelegate.DelegateType)); + break; case ArrayElementPathElement arr: node = new PropertyAccessorNode(CommonPropertyNames.IndexerName, enableValidation, new ArrayElementPlugin(arr.Indices, arr.ElementType)); break; @@ -92,6 +99,18 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings return this; } + public CompiledBindingPathBuilder Method(RuntimeMethodHandle handle, RuntimeTypeHandle delegateType) + { + _elements.Add(new MethodAsDelegateElement(handle, delegateType)); + return this; + } + + public CompiledBindingPathBuilder Command(string methodName, Action executeHelper, Func canExecuteHelper, string[] dependsOnProperties) + { + _elements.Add(new MethodAsCommandElement(methodName, executeHelper, canExecuteHelper, dependsOnProperties ?? Array.Empty())); + return this; + } + public CompiledBindingPathBuilder StreamTask() { _elements.Add(new TaskStreamPathElement()); @@ -178,6 +197,35 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings => _isFirstElement ? Property.Name : $".{Property.Name}"; } + internal class MethodAsDelegateElement : ICompiledBindingPathElement + { + public MethodAsDelegateElement(RuntimeMethodHandle method, RuntimeTypeHandle delegateType) + { + Method = (MethodInfo)MethodBase.GetMethodFromHandle(method); + DelegateType = Type.GetTypeFromHandle(delegateType); + } + + public MethodInfo Method { get; } + + public Type DelegateType { get; } + } + + internal class MethodAsCommandElement : ICompiledBindingPathElement + { + public MethodAsCommandElement(string methodName, Action executeHelper, Func canExecuteHelper, string[] dependsOnElements) + { + MethodName = methodName; + ExecuteMethod = executeHelper; + CanExecuteMethod = canExecuteHelper; + DependsOnProperties = new HashSet(dependsOnElements); + } + + public string MethodName { get; } + public Action ExecuteMethod { get; } + public Func CanExecuteMethod { get; } + public HashSet DependsOnProperties { get; } + } + internal interface IStronglyTypedStreamElement : ICompiledBindingPathElement { IStreamPlugin CreatePlugin(); diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs new file mode 100644 index 0000000000..45ad45e658 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Text; +using Avalonia.Data; +using Avalonia.Data.Core.Plugins; + +#nullable enable + +namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings +{ + internal class MethodAccessorPlugin : IPropertyAccessorPlugin + { + private MethodInfo _method; + private readonly Type _delegateType; + + public MethodAccessorPlugin(MethodInfo method, Type delegateType) + { + _method = method; + _delegateType = delegateType; + } + + public bool Match(object obj, string propertyName) + { + throw new InvalidOperationException("The MethodAccessorPlugin does not support dynamic matching"); + } + + public IPropertyAccessor Start(WeakReference reference, string propertyName) + { + Debug.Assert(_method.Name == propertyName); + return new Accessor(reference, _method, _delegateType); + } + + private sealed class Accessor : PropertyAccessorBase + { + public Accessor(WeakReference reference, MethodInfo method, Type delegateType) + { + _ = reference ?? throw new ArgumentNullException(nameof(reference)); + _ = method ?? throw new ArgumentNullException(nameof(method)); + + PropertyType = delegateType; + + if (method.IsStatic) + { + Value = method.CreateDelegate(PropertyType); + } + else if (reference.TryGetTarget(out var target)) + { + Value = method.CreateDelegate(PropertyType, target); + } + } + + public override Type? PropertyType { get; } + + public override object? Value { get; } + + public override bool SetValue(object? value, BindingPriority priority) => false; + + protected override void SubscribeCore() + { + try + { + PublishValue(Value); + } + catch { } + } + + protected override void UnsubscribeCore() + { + } + } + } +} diff --git a/src/Windows/Avalonia.Direct2D1/Properties/AssemblyInfo.cs b/src/Windows/Avalonia.Direct2D1/Properties/AssemblyInfo.cs index cdeb675bcd..89fd6c2dd8 100644 --- a/src/Windows/Avalonia.Direct2D1/Properties/AssemblyInfo.cs +++ b/src/Windows/Avalonia.Direct2D1/Properties/AssemblyInfo.cs @@ -1,9 +1,4 @@ using System.Runtime.CompilerServices; -using Avalonia.Platform; -using Avalonia.Direct2D1; - -[assembly: ExportRenderingSubsystem(OperatingSystemType.WinNT, 1, "Direct2D1", typeof(Direct2D1Platform), nameof(Direct2D1Platform.Initialize), - typeof(Direct2DChecker))] [assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: InternalsVisibleTo("Avalonia.Direct2D1.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] diff --git a/src/Windows/Avalonia.Win32/Properties/AssemblyInfo.cs b/src/Windows/Avalonia.Win32/Properties/AssemblyInfo.cs deleted file mode 100644 index 30a3f71cad..0000000000 --- a/src/Windows/Avalonia.Win32/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using Avalonia.Platform; -using Avalonia.Win32; - -[assembly: ExportWindowingSubsystem(OperatingSystemType.WinNT, 1, "Win32", typeof(Win32Platform), nameof(Win32Platform.Initialize))] diff --git a/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj b/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj index 5358b71571..f4c6434a68 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj +++ b/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj @@ -15,7 +15,6 @@ - diff --git a/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj b/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj index 4353b7b09c..cdadc3dcba 100644 --- a/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj +++ b/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj @@ -9,7 +9,6 @@ - diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Method.cs b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Method.cs index b0623aa456..72e0ac5e57 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Method.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Method.cs @@ -23,8 +23,8 @@ namespace Avalonia.Markup.UnitTests.Parsers public static void StaticMethod() { } - public static void TooManyParameters(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) { } - public static int TooManyParametersWithReturnType(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) => 1; + public static void ManyParameters(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) { } + public static int ManyParametersWithReturnType(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) => 1; } [Fact] @@ -44,6 +44,8 @@ namespace Avalonia.Markup.UnitTests.Parsers [InlineData(nameof(TestObject.MethodWithReturn), typeof(Func))] [InlineData(nameof(TestObject.MethodWithReturnAndParameters), typeof(Func))] [InlineData(nameof(TestObject.StaticMethod), typeof(Action))] + [InlineData(nameof(TestObject.ManyParameters), typeof(Action))] + [InlineData(nameof(TestObject.ManyParametersWithReturnType), typeof(Func))] public async Task Should_Get_Method_WithCorrectDelegateType(string methodName, Type expectedType) { var data = new TestObject(); @@ -68,21 +70,5 @@ namespace Avalonia.Markup.UnitTests.Parsers GC.KeepAlive(data); } - - [Theory] - [InlineData(nameof(TestObject.TooManyParameters))] - [InlineData(nameof(TestObject.TooManyParametersWithReturnType))] - public async Task Should_Return_Error_Notification_If_Too_Many_Parameters(string methodName) - { - var data = new TestObject(); - var observer = ExpressionObserverBuilder.Build(data, methodName); - var result = await observer.Take(1); - - Assert.IsType(result); - - Assert.Equal(BindingErrorType.Error, ((BindingNotification)result).ErrorType); - - GC.KeepAlive(data); - } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 15c6f5877f..ecfb54a624 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel; using System.Globalization; using System.Reactive.Subjects; using System.Text; @@ -9,6 +10,7 @@ using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Data.Converters; using Avalonia.Data.Core; +using Avalonia.Input; using Avalonia.Markup.Data; using Avalonia.Media; using Avalonia.UnitTests; @@ -1062,6 +1064,174 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void SupportsMethodBindingAsDelegate() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + window.DataContext = new MethodDataContext(); + + Assert.IsAssignableFrom(typeof(Action), window.FindControl("action").Content); + Assert.IsAssignableFrom(typeof(Func), window.FindControl("func").Content); + Assert.IsAssignableFrom(typeof(Action), window.FindControl("action16").Content); + Assert.IsAssignableFrom(typeof(Func), window.FindControl("func16").Content); + Assert.True(typeof(Delegate).IsAssignableFrom(window.FindControl("customvoid").Content.GetType())); + Assert.True(typeof(Delegate).IsAssignableFrom(window.FindControl("customint").Content.GetType())); + } + } + + [Fact] + public void Binding_Method_To_Command_Works() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + +