A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

349 lines
13 KiB

using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Data.Core.Plugins;
namespace Avalonia
{
/// <summary>
/// Initializes platform-specific services for an <see cref="Application"/>.
/// </summary>
public sealed class AppBuilder
{
private static bool s_setupWasAlreadyCalled;
private Action? _optionsInitializers;
private Func<Application>? _appFactory;
private IApplicationLifetime? _lifetime;
/// <summary>
/// Gets or sets a method to call the initialize the runtime platform services (e. g. AssetLoader)
/// </summary>
public Action? RuntimePlatformServicesInitializer { get; private set; }
/// <summary>
/// Gets the name of the currently selected windowing subsystem.
/// </summary>
public string? RuntimePlatformServicesName { get; private set; }
/// <summary>
/// Gets the <see cref="Application"/> instance being initialized.
/// </summary>
public Application? Instance { get; private set; }
/// <summary>
/// Gets the type of the Instance (even if it's not created yet)
/// </summary>
public Type? ApplicationType { get; private set; }
/// <summary>
/// Gets or sets a method to call the initialize the windowing subsystem.
/// </summary>
public Action? WindowingSubsystemInitializer { get; private set; }
/// <summary>
/// Gets the name of the currently selected windowing subsystem.
/// </summary>
public string? WindowingSubsystemName { get; private set; }
/// <summary>
/// Gets or sets a method to call the initialize the windowing subsystem.
/// </summary>
public Action? RenderingSubsystemInitializer { get; private set; }
/// <summary>
/// Gets the name of the currently selected rendering subsystem.
/// </summary>
public string? RenderingSubsystemName { get; private set; }
/// <summary>
/// Gets a method to call after the <see cref="Application"/> is setup.
/// </summary>
public Action<AppBuilder> AfterSetupCallback { get; private set; } = builder => { };
/// <summary>
/// Callbacks that are commonly used by backends to initialize avalonia views.
/// </summary>
private Action<AppBuilder> AfterApplicationSetupCallback { get; set; } = builder => { };
public Action<AppBuilder> AfterPlatformServicesSetupCallback { get; private set; } = builder => { };
/// <summary>
/// Initializes a new instance of the <see cref="AppBuilder"/> class.
/// </summary>
private AppBuilder()
{
}
/// <summary>
/// Begin configuring an <see cref="Application"/>.
/// </summary>
/// <typeparam name="TApp">The subclass of <see cref="Application"/> to configure.</typeparam>
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public static AppBuilder Configure<TApp>()
where TApp : Application, new()
{
return new AppBuilder()
{
ApplicationType = typeof(TApp),
_appFactory = () => new TApp()
};
}
/// <summary>
/// Begin configuring an <see cref="Application"/>.
/// </summary>
/// <param name="appFactory">Factory function for <typeparamref name="TApp"/>.</param>
/// <typeparam name="TApp">The subclass of <see cref="Application"/> to configure.</typeparam>
/// <remarks><paramref name="appFactory"/> is useful for passing of dependencies to <typeparamref name="TApp"/>.</remarks>
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public static AppBuilder Configure<TApp>(Func<TApp> appFactory)
where TApp : Application
{
return new AppBuilder()
{
ApplicationType = typeof(TApp),
_appFactory = appFactory
};
}
/// <summary>
/// Begin configuring an <see cref="Application"/>.
/// Should only be used for testing and design purposes, as it relies on dynamic code.
/// </summary>
/// <param name="entryPointType">
/// Parameter from which <see cref="AppBuilder"/> should be created.
/// It either needs to have BuildAvaloniaApp -> AppBuilder method or inherit Application.
/// </param>
/// <returns>An <see cref="AppBuilder"/> instance. If can't be created, thrown an exception.</returns>
internal static AppBuilder Configure(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type entryPointType)
{
var appBuilderObj = entryPointType
.GetMethod(
"BuildAvaloniaApp",
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy,
null,
Array.Empty<Type>(),
null)?
.Invoke(null, Array.Empty<object?>());
if (appBuilderObj is AppBuilder appBuilder)
{
return appBuilder;
}
if (typeof(Application).IsAssignableFrom(entryPointType))
{
return Configure(() => (Application)Activator.CreateInstance(entryPointType)!);
}
throw new InvalidOperationException(
$"Unable to create AppBuilder from type \"{entryPointType.FullName}\". " +
$"Input type either needs to have BuildAvaloniaApp -> AppBuilder method or inherit Application type.");
}
private AppBuilder Self => this;
public AppBuilder AfterSetup(Action<AppBuilder> callback)
{
AfterSetupCallback = (Action<AppBuilder>)Delegate.Combine(AfterSetupCallback, callback);
return Self;
}
[PrivateApi]
public AppBuilder AfterApplicationSetup(Action<AppBuilder> callback)
{
AfterApplicationSetupCallback = (Action<AppBuilder>)Delegate.Combine(AfterApplicationSetupCallback, callback);
return Self;
}
public AppBuilder AfterPlatformServicesSetup(Action<AppBuilder> callback)
{
AfterPlatformServicesSetupCallback = (Action<AppBuilder>)Delegate.Combine(AfterPlatformServicesSetupCallback, callback);
return Self;
}
public delegate void AppMainDelegate(Application app, string[] args);
public void Start(AppMainDelegate main, string[] args)
{
Setup();
main(Instance!, args);
}
/// <summary>
/// Sets up the platform-specific services for the application, but does not run it.
/// </summary>
/// <returns></returns>
public AppBuilder SetupWithoutStarting()
{
Setup();
return Self;
}
/// <summary>
/// Sets up the platform-specific services for the application and initialized it with a particular lifetime, but does not run it.
/// </summary>
/// <param name="lifetime"></param>
/// <returns></returns>
public AppBuilder SetupWithLifetime(IApplicationLifetime lifetime)
{
_lifetime = lifetime;
Setup();
return Self;
}
/// <summary>
/// Specifies a windowing subsystem to use.
/// </summary>
/// <param name="initializer">The method to call to initialize the windowing subsystem.</param>
/// <param name="name">The name of the windowing subsystem.</param>
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder UseWindowingSubsystem(Action initializer, string name = "")
{
WindowingSubsystemInitializer = initializer;
WindowingSubsystemName = name;
return Self;
}
/// <summary>
/// Specifies a rendering subsystem to use.
/// </summary>
/// <param name="initializer">The method to call to initialize the rendering subsystem.</param>
/// <param name="name">The name of the rendering subsystem.</param>
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder UseRenderingSubsystem(Action initializer, string name = "")
{
RenderingSubsystemInitializer = initializer;
RenderingSubsystemName = name;
return Self;
}
/// <summary>
/// Specifies a runtime platform subsystem to use.
/// </summary>
/// <param name="initializer">The method to call to initialize the runtime platform subsystem.</param>
/// <param name="name">The name of the runtime platform subsystem.</param>
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder UseRuntimePlatformSubsystem(Action initializer, string name = "")
{
RuntimePlatformServicesInitializer = initializer;
RuntimePlatformServicesName = name;
return Self;
}
/// <summary>
/// Specifies a standard runtime platform subsystem to use.
/// </summary>
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder UseStandardRuntimePlatformSubsystem()
{
RuntimePlatformServicesInitializer = () => StandardRuntimePlatformServices.Register(ApplicationType?.Assembly);
RuntimePlatformServicesName = nameof(StandardRuntimePlatform);
return Self;
}
/// <summary>
/// Configures platform-specific options
/// </summary>
public AppBuilder With<T>(T options)
{
_optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind<T>().ToConstant(options); };
return Self;
}
/// <summary>
/// Configures platform-specific options
/// </summary>
public AppBuilder With<T>(Func<T> options)
{
_optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind<T>().ToFunc(options); };
return Self;
}
/// <summary>
/// Adds support for validation using <c>System.ComponentModel.DataAnnotations</c>.
/// </summary>
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public AppBuilder WithDataAnnotationsValidation()
{
if (!BindingPlugins.DataValidators.Any(x => x is DataAnnotationsValidationPlugin))
BindingPlugins.DataValidators.Insert(0, new DataAnnotationsValidationPlugin());
return Self;
}
/// <summary>
/// Registers an action that is executed with the current font manager.
/// </summary>
/// <param name="action">The action.</param>
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder ConfigureFonts(Action<FontManager> action)
{
return AfterSetup(appBuilder =>
{
action?.Invoke(FontManager.Current);
});
}
/// <summary>
/// Sets up the platform-specific services for the <see cref="Application"/>.
/// </summary>
private void Setup()
{
if (RuntimePlatformServicesInitializer == null)
{
throw new InvalidOperationException("No runtime platform services configured.");
}
if (WindowingSubsystemInitializer == null)
{
throw new InvalidOperationException("No windowing system configured.");
}
if (RenderingSubsystemInitializer == null)
{
throw new InvalidOperationException("No rendering system configured.");
}
if (_appFactory == null)
{
throw new InvalidOperationException("No Application factory configured.");
}
if (s_setupWasAlreadyCalled)
{
throw new InvalidOperationException("Setup was already called on one of AppBuilder instances");
}
s_setupWasAlreadyCalled = true;
SetupUnsafe();
}
/// <summary>
/// Setup method that doesn't check for input initalizers being set.
/// Nor
/// </summary>
internal void SetupUnsafe()
{
_optionsInitializers?.Invoke();
RuntimePlatformServicesInitializer?.Invoke();
RenderingSubsystemInitializer?.Invoke();
WindowingSubsystemInitializer?.Invoke();
AfterPlatformServicesSetupCallback?.Invoke(Self);
Instance = _appFactory!();
Instance.ApplicationLifetime = _lifetime;
AvaloniaLocator.CurrentMutable.BindToSelf(Instance);
Instance.RegisterServices();
Instance.Initialize();
AfterApplicationSetupCallback?.Invoke(Self);
AfterSetupCallback?.Invoke(Self);
Instance.OnFrameworkInitializationCompleted();
}
}
}