diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index a2dedd7340..e241f953e7 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -35,9 +35,9 @@ namespace Avalonia.Controls public Action WindowingSubsystemInitializer { get; private set; } /// - /// Gets or sets the name of the windowing subsystem to use. + /// Gets the name of the currently selected windowing subsystem. /// - public string WindowingSubsystemName { get; set; } + public string WindowingSubsystemName { get; private set; } /// /// Gets or sets a method to call the initialize the windowing subsystem. @@ -45,9 +45,9 @@ namespace Avalonia.Controls public Action RenderingSubsystemInitializer { get; private set; } /// - /// Gets or sets the name of the rendering subsystem to use. + /// Gets the name of the currently selected rendering subsystem. /// - public string RenderingSubsystemName { get; set; } + public string RenderingSubsystemName { get; private set; } /// /// Gets or sets a method to call after the is setup. @@ -141,9 +141,10 @@ namespace Avalonia.Controls /// /// The method to call to initialize the windowing subsystem. /// An instance. - public TAppBuilder UseWindowingSubsystem(Action initializer) + public TAppBuilder UseWindowingSubsystem(Action initializer, string name = "") { WindowingSubsystemInitializer = initializer; + WindowingSubsystemName = name; return Self; } @@ -152,16 +153,17 @@ namespace Avalonia.Controls /// /// The dll in which to look for subsystem. /// An instance. - public TAppBuilder UseWindowingSubsystem(string dll) => UseWindowingSubsystem(GetInitializer(dll)); + public TAppBuilder UseWindowingSubsystem(string dll) => UseWindowingSubsystem(GetInitializer(dll), dll.Replace("Avalonia.", string.Empty)); /// /// Specifies a rendering subsystem to use. /// /// The method to call to initialize the rendering subsystem. /// An instance. - public TAppBuilder UseRenderingSubsystem(Action initializer) + public TAppBuilder UseRenderingSubsystem(Action initializer, string name = "") { RenderingSubsystemInitializer = initializer; + RenderingSubsystemName = name; return Self; } @@ -188,14 +190,14 @@ namespace Avalonia.Controls { var moduleInitializers = from assembly in AvaloniaLocator.Current.GetService().GetLoadedAssemblies() from attribute in assembly.GetCustomAttributes() - where attribute.RequiredWindowingSubsystem == "" - || attribute.RequiredWindowingSubsystem == WindowingSubsystemName - where attribute.RequiredRenderingSubsystem == "" - || attribute.RequiredRenderingSubsystem == RenderingSubsystemName + where attribute.ForWindowingSubsystem == "" + || attribute.ForWindowingSubsystem == WindowingSubsystemName + where attribute.ForRenderingSubsystem == "" + || attribute.ForRenderingSubsystem == RenderingSubsystemName group attribute by attribute.Name into exports select (from export in exports - orderby export.RequiredWindowingSubsystem.Length descending - orderby export.RequiredRenderingSubsystem.Length descending + orderby export.ForWindowingSubsystem.Length descending + orderby export.ForRenderingSubsystem.Length descending select export).First().ModuleType into moduleType select (from constructor in moduleType.GetTypeInfo().DeclaredConstructors where constructor.GetParameters().Length == 0 && !constructor.IsStatic diff --git a/src/Avalonia.Controls/Platform/ExportAvaloniaModuleAttribute.cs b/src/Avalonia.Controls/Platform/ExportAvaloniaModuleAttribute.cs index 50e39252e4..5a34c5c0e1 100644 --- a/src/Avalonia.Controls/Platform/ExportAvaloniaModuleAttribute.cs +++ b/src/Avalonia.Controls/Platform/ExportAvaloniaModuleAttribute.cs @@ -2,6 +2,44 @@ namespace Avalonia.Platform { + /// + /// Defines an "Avalonia Module", a 3rd party extension to Avalonia that can be automatically initialized by an AppBuilder instance. + /// + /// + /// Avalonia Modules can either be platform independent (ex default control styles provider) or dependent on a + /// specific windowing or rendering subsystem being used (ex native rendering speedup, subsystem-specific interop backends). + /// In the case of a subsystem-specific module, you can specify multiple module implementations, and also a fallback + /// platform-independent module if you so choose. Additionally, these different implementations can be in different assemblies. + /// They just need to all share the same module name. + /// + /// For example, if I had a module Foo that has a special back-end for Skia and a less performant/less user friendly back-end for + /// any other rendering subsystem, I would do the following: + /// + /// // In assembly FooModuleSkia.dll + /// [assembly:ExportAvaloniaModule("Foo", typeof(FooModuleSkia), ForRenderingSubsystem="Skia")] + /// + /// class FooModuleSkia + /// { + /// public FooModuleSkia() + /// { + /// InitializeModule(); + /// } + /// } + /// + /// // In assembly FooModuleFallback.dll + /// [assembly:ExportAvaloniaModule("Foo", typeof(FooModuleFallback))] + /// + /// class FooModuleFallback + /// { + /// public FooModuleFallback() + /// { + /// InitializeModule(); + /// } + /// } + /// + /// + /// The fallback module will only be initialized if the Skia-specific module is not applicable. + /// [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public class ExportAvaloniaModuleAttribute : Attribute { @@ -14,7 +52,7 @@ namespace Avalonia.Platform public string Name { get; private set; } public Type ModuleType { get; private set; } - public string RequiredWindowingSubsystem { get; set; } = ""; - public string RequiredRenderingSubsystem { get; set; } = ""; + public string ForWindowingSubsystem { get; set; } = ""; + public string ForRenderingSubsystem { get; set; } = ""; } } diff --git a/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs b/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs index 157fb7acd9..3510539bc7 100644 --- a/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs +++ b/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs @@ -43,12 +43,12 @@ namespace Avalonia select attribute).First(); UseWindowingSubsystem(() => windowingSubsystemAttribute.InitializationType - .GetRuntimeMethod(windowingSubsystemAttribute.InitializationMethod, Type.EmptyTypes).Invoke(null, null)); - WindowingSubsystemName = windowingSubsystemAttribute.Name; + .GetRuntimeMethod(windowingSubsystemAttribute.InitializationMethod, Type.EmptyTypes).Invoke(null, null), + windowingSubsystemAttribute.Name); UseRenderingSubsystem(() => renderingSubsystemAttribute.InitializationType - .GetRuntimeMethod(renderingSubsystemAttribute.InitializationMethod, Type.EmptyTypes).Invoke(null, null)); - RenderingSubsystemName = renderingSubsystemAttribute.Name; + .GetRuntimeMethod(renderingSubsystemAttribute.InitializationMethod, Type.EmptyTypes).Invoke(null, null), + renderingSubsystemAttribute.Name); return this; } diff --git a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs index c97a95a1ed..1e6eef103b 100644 --- a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs +++ b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs @@ -14,8 +14,7 @@ namespace Avalonia { public static T UseCairo(this T builder) where T : AppBuilderBase, new() { - builder.UseRenderingSubsystem(Cairo.CairoPlatform.Initialize); - builder.RenderingSubsystemName = "Cairo"; + builder.UseRenderingSubsystem(Cairo.CairoPlatform.Initialize, "Cairo"); return builder; } } diff --git a/src/Gtk/Avalonia.Gtk/GtkPlatform.cs b/src/Gtk/Avalonia.Gtk/GtkPlatform.cs index 15c16b3c2c..e79c51915d 100644 --- a/src/Gtk/Avalonia.Gtk/GtkPlatform.cs +++ b/src/Gtk/Avalonia.Gtk/GtkPlatform.cs @@ -16,8 +16,7 @@ namespace Avalonia { public static T UseGtk(this T builder) where T : AppBuilderBase, new() { - builder.UseWindowingSubsystem(Gtk.GtkPlatform.Initialize); - builder.WindowingSubsystemName = "Gtk"; + builder.UseWindowingSubsystem(Gtk.GtkPlatform.Initialize, "Gtk"); return builder; } } diff --git a/src/Skia/Avalonia.Skia/SkiaPlatform.cs b/src/Skia/Avalonia.Skia/SkiaPlatform.cs index a49bb8bb13..001970fa36 100644 --- a/src/Skia/Avalonia.Skia/SkiaPlatform.cs +++ b/src/Skia/Avalonia.Skia/SkiaPlatform.cs @@ -10,8 +10,7 @@ namespace Avalonia { public static T UseSkia(this T builder) where T : AppBuilderBase, new() { - builder.UseRenderingSubsystem(Skia.SkiaPlatform.Initialize); - builder.RenderingSubsystemName = "Skia"; + builder.UseRenderingSubsystem(Skia.SkiaPlatform.Initialize, "Skia"); return builder; } } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 22d4a498cc..7222976a33 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -14,8 +14,7 @@ namespace Avalonia { public static T UseDirect2D1(this T builder) where T : AppBuilderBase, new() { - builder.UseRenderingSubsystem(Direct2D1.Direct2D1Platform.Initialize); - builder.RenderingSubsystemName = "Direct2D1"; + builder.UseRenderingSubsystem(Direct2D1.Direct2D1Platform.Initialize, "Direct2D1"); return builder; } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 6b5f55cb47..efd5c7a184 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -23,8 +23,7 @@ namespace Avalonia { public static T UseWin32(this T builder) where T : AppBuilderBase, new() { - builder.UseWindowingSubsystem(Win32.Win32Platform.Initialize); - builder.WindowingSubsystemName = "Win32"; + builder.UseWindowingSubsystem(Win32.Win32Platform.Initialize, "Win32"); return builder; } } diff --git a/src/iOS/Avalonia.iOS/iOSPlatform.cs b/src/iOS/Avalonia.iOS/iOSPlatform.cs index 4a25b7d374..1b69735221 100644 --- a/src/iOS/Avalonia.iOS/iOSPlatform.cs +++ b/src/iOS/Avalonia.iOS/iOSPlatform.cs @@ -15,8 +15,7 @@ namespace Avalonia { public static T UseiOS(this T builder) where T : AppBuilderBase, new() { - builder.UseWindowingSubsystem(iOSPlatform.Initialize); - builder.WindowingSubsystemName = "iOS"; + builder.UseWindowingSubsystem(iOSPlatform.Initialize, "iOS"); return builder; } diff --git a/tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs b/tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs index d426a7edb3..9de97b41cc 100644 --- a/tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs @@ -8,8 +8,8 @@ using Avalonia.Controls.UnitTests; using Avalonia.Platform; [assembly: ExportAvaloniaModule("DefaultModule", typeof(AppBuilderTests.DefaultModule))] -[assembly: ExportAvaloniaModule("RenderingModule", typeof(AppBuilderTests.Direct2DModule), RequiredRenderingSubsystem = "Direct2D1")] -[assembly: ExportAvaloniaModule("RenderingModule", typeof(AppBuilderTests.SkiaModule), RequiredRenderingSubsystem = "Skia")] +[assembly: ExportAvaloniaModule("RenderingModule", typeof(AppBuilderTests.Direct2DModule), ForRenderingSubsystem = "Direct2D1")] +[assembly: ExportAvaloniaModule("RenderingModule", typeof(AppBuilderTests.SkiaModule), ForRenderingSubsystem = "Skia")] [assembly: ExportAvaloniaModule("RenderingModule", typeof(AppBuilderTests.DefaultRenderingModule))] @@ -82,8 +82,7 @@ namespace Avalonia.Controls.UnitTests ResetModuleLoadStates(); var builder = AppBuilder.Configure() .UseWindowingSubsystem(() => { }) - .UseRenderingSubsystem(() => { }); - builder.RenderingSubsystemName = "Direct2D1"; + .UseRenderingSubsystem(() => { }, "Direct2D1"); builder.UseAvaloniaModules().SetupWithoutStarting(); Assert.False(DefaultRenderingModule.IsLoaded); Assert.True(Direct2DModule.IsLoaded); @@ -92,8 +91,7 @@ namespace Avalonia.Controls.UnitTests ResetModuleLoadStates(); builder = AppBuilder.Configure() .UseWindowingSubsystem(() => { }) - .UseRenderingSubsystem(() => { }); - builder.RenderingSubsystemName = "Skia"; + .UseRenderingSubsystem(() => { }, "Skia"); builder.UseAvaloniaModules().SetupWithoutStarting(); Assert.False(DefaultRenderingModule.IsLoaded); Assert.False(Direct2DModule.IsLoaded); @@ -109,8 +107,7 @@ namespace Avalonia.Controls.UnitTests ResetModuleLoadStates(); var builder = AppBuilder.Configure() .UseWindowingSubsystem(() => { }) - .UseRenderingSubsystem(() => { }); - builder.RenderingSubsystemName = "Cairo"; + .UseRenderingSubsystem(() => { }, "Cairo"); builder.UseAvaloniaModules().SetupWithoutStarting(); Assert.True(DefaultRenderingModule.IsLoaded); Assert.False(Direct2DModule.IsLoaded);