Browse Source

Added support for auto-loading "Avalonia modules", which are classes that are auto-initialized via the instance constructor.

pull/707/head
Jeremy Koritzinsky 10 years ago
parent
commit
9a18ef0afa
  1. 56
      src/Avalonia.Controls/AppBuilderBase.cs
  2. 1
      src/Avalonia.Controls/Avalonia.Controls.csproj
  3. 20
      src/Avalonia.Controls/Platform/ExportAvaloniaModuleAttribute.cs
  4. 120
      tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs
  5. 5
      tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj

56
src/Avalonia.Controls/AppBuilderBase.cs

@ -3,6 +3,7 @@
using System;
using System.Reflection;
using System.Linq;
using Avalonia.Platform;
namespace Avalonia.Controls
@ -16,22 +17,22 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the <see cref="IRuntimePlatform"/> instance.
/// </summary>
public IRuntimePlatform RuntimePlatform { get; set; }
public IRuntimePlatform RuntimePlatform { get; private set; }
/// <summary>
/// Gets or sets a method to call the initialize the runtime platform services (e. g. AssetLoader)
/// </summary>
public Action RuntimePlatformServicesInitializer { get; set; }
public Action RuntimePlatformServicesInitializer { get; private set; }
/// <summary>
/// Gets or sets the <see cref="Application"/> instance being initialized.
/// </summary>
public Application Instance { get; set; }
public Application Instance { get; protected set; }
/// <summary>
/// Gets or sets a method to call the initialize the windowing subsystem.
/// </summary>
public Action WindowingSubsystemInitializer { get; set; }
public Action WindowingSubsystemInitializer { get; private set; }
/// <summary>
/// Gets or sets the name of the windowing subsystem to use.
@ -41,18 +42,23 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets a method to call the initialize the windowing subsystem.
/// </summary>
public Action RenderingSubsystemInitializer { get; set; }
public Action RenderingSubsystemInitializer { get; private set; }
/// <summary>
/// Gets or sets the name of the rendering subsystem to use.
/// </summary>
public string RenderingSubsystemName { get; set; }
/// <summary>
/// Gets or sets a method to call after the <see cref="Application"/> is setup.
/// </summary>
public Action<TAppBuilder> AfterSetupCallback { get; private set; } = builder => { };
/// <summary>
/// Gets or sets a method to call before <see cref="Start{TMainWindow}"/> is called on the
/// <see cref="Application"/>.
/// </summary>
public Action<TAppBuilder> BeforeStartCallback { get; set; }
public Action<TAppBuilder> BeforeStartCallback { get; private set; } = builder => { };
protected AppBuilderBase(IRuntimePlatform platform, Action platformSevices)
{
@ -95,12 +101,13 @@ namespace Avalonia.Controls
/// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns>
public TAppBuilder BeforeStarting(Action<TAppBuilder> callback)
{
var oldCallback = BeforeStartCallback;
BeforeStartCallback = builder =>
{
oldCallback?.Invoke(builder);
callback(builder);
};
BeforeStartCallback = (Action<TAppBuilder>)Delegate.Combine(BeforeStartCallback, callback);
return Self;
}
public AppBuilder AfterSetup(Action<TAppBuilder> callback)
{
AfterSetupCallback = (Action<TAppBuilder>)Delegate.Combine(AfterSetupCallback, callback);
return Self;
}
@ -112,7 +119,7 @@ namespace Avalonia.Controls
where TMainWindow : Window, new()
{
Setup();
BeforeStartCallback?.Invoke(Self);
BeforeStartCallback(Self);
var window = new TMainWindow();
window.Show();
@ -175,6 +182,28 @@ namespace Avalonia.Controls
init.Invoke(null, null);
};
public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules());
private void SetupAvaloniaModules()
{
var moduleInitializers = from assembly in AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetLoadedAssemblies()
from attribute in assembly.GetCustomAttributes<ExportAvaloniaModuleAttribute>()
where attribute.RequiredWindowingSubsystem == ""
|| attribute.RequiredWindowingSubsystem == WindowingSubsystemName
where attribute.RequiredRenderingSubsystem == ""
|| attribute.RequiredRenderingSubsystem == RenderingSubsystemName
group attribute by attribute.Name into exports
select (from export in exports
orderby export.RequiredWindowingSubsystem.Length descending
orderby export.RequiredRenderingSubsystem.Length descending
select export).First().ModuleType into moduleType
select (from constructor in moduleType.GetTypeInfo().DeclaredConstructors
where constructor.GetParameters().Length == 0 && !constructor.IsStatic
select constructor).Single() into constructor
select (Action)(() => constructor.Invoke(new object[0]));
Delegate.Combine(moduleInitializers.ToArray()).DynamicInvoke();
}
/// <summary>
/// Sets up the platform-speciic services for the <see cref="Application"/>.
/// </summary>
@ -205,6 +234,7 @@ namespace Avalonia.Controls
WindowingSubsystemInitializer();
RenderingSubsystemInitializer();
Instance.Initialize();
AfterSetupCallback(Self);
}
}
}

1
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -58,6 +58,7 @@
<Compile Include="IScrollable.cs" />
<Compile Include="Embedding\EmbeddableControlRoot.cs" />
<Compile Include="Platform\IEmbeddableWindowImpl.cs" />
<Compile Include="Platform\ExportAvaloniaModuleAttribute.cs" />
<Compile Include="WindowIcon.cs" />
<Compile Include="IPseudoClasses.cs" />
<Compile Include="DropDownItem.cs" />

20
src/Avalonia.Controls/Platform/ExportAvaloniaModuleAttribute.cs

@ -0,0 +1,20 @@
using System;
namespace Avalonia.Platform
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public class ExportAvaloniaModuleAttribute : Attribute
{
public ExportAvaloniaModuleAttribute(string name, Type moduleType)
{
Name = name;
ModuleType = moduleType;
}
public string Name { get; private set; }
public Type ModuleType { get; private set; }
public string RequiredWindowingSubsystem { get; set; } = "";
public string RequiredRenderingSubsystem { get; set; } = "";
}
}

120
tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
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.DefaultRenderingModule))]
namespace Avalonia.Controls.UnitTests
{
public class AppBuilderTests
{
class App : Application
{
}
public class DefaultModule
{
public static bool IsLoaded = false;
public DefaultModule()
{
IsLoaded = true;
}
}
public class DefaultRenderingModule
{
public static bool IsLoaded = false;
public DefaultRenderingModule()
{
IsLoaded = true;
}
}
public class Direct2DModule
{
public static bool IsLoaded = false;
public Direct2DModule()
{
IsLoaded = true;
}
}
public class SkiaModule
{
public static bool IsLoaded = false;
public SkiaModule()
{
IsLoaded = true;
}
}
[Fact]
public void LoadsDefaultModule()
{
ResetModuleLoadStates();
AppBuilder.Configure<App>()
.UseWindowingSubsystem(() => { })
.UseRenderingSubsystem(() => { })
.UseAvaloniaModules()
.SetupWithoutStarting();
Assert.True(DefaultModule.IsLoaded);
}
[Fact]
public void LoadsRenderingModuleWithMatchingRenderingSubsystem()
{
ResetModuleLoadStates();
var builder = AppBuilder.Configure<App>()
.UseWindowingSubsystem(() => { })
.UseRenderingSubsystem(() => { });
builder.RenderingSubsystemName = "Direct2D1";
builder.UseAvaloniaModules().SetupWithoutStarting();
Assert.False(DefaultRenderingModule.IsLoaded);
Assert.True(Direct2DModule.IsLoaded);
Assert.False(SkiaModule.IsLoaded);
ResetModuleLoadStates();
builder = AppBuilder.Configure<App>()
.UseWindowingSubsystem(() => { })
.UseRenderingSubsystem(() => { });
builder.RenderingSubsystemName = "Skia";
builder.UseAvaloniaModules().SetupWithoutStarting();
Assert.False(DefaultRenderingModule.IsLoaded);
Assert.False(Direct2DModule.IsLoaded);
Assert.True(SkiaModule.IsLoaded);
}
[Fact]
public void LoadsRenderingModuleWithoutDependenciesWhenNoModuleMatches()
{
ResetModuleLoadStates();
var builder = AppBuilder.Configure<App>()
.UseWindowingSubsystem(() => { })
.UseRenderingSubsystem(() => { });
builder.RenderingSubsystemName = "Cairo";
builder.UseAvaloniaModules().SetupWithoutStarting();
Assert.True(DefaultRenderingModule.IsLoaded);
Assert.False(Direct2DModule.IsLoaded);
Assert.False(SkiaModule.IsLoaded);
}
private static void ResetModuleLoadStates()
{
DefaultModule.IsLoaded = false;
DefaultRenderingModule.IsLoaded = false;
Direct2DModule.IsLoaded = false;
SkiaModule.IsLoaded = false;
}
}
}

5
tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj

@ -94,6 +94,7 @@
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="AppBuilderTests.cs" />
<Compile Include="ClassesTests.cs" />
<Compile Include="LayoutTransformControlTests.cs" />
<Compile Include="Presenters\ItemsPresenterTests_Virtualization_Simple.cs" />
@ -152,6 +153,10 @@
<Compile Include="WrapPanelTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj">
<Project>{4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}</Project>
<Name>Avalonia.DotNetFrameworkRuntime</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
<Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
<Name>Avalonia.Markup.Xaml</Name>

Loading…
Cancel
Save