LANCommander/LANCommander.Launcher.Plugins/ViewRegistry.cs
2026-07-23 19:47:22 -05:00

58 lines
2.2 KiB
C#

using Avalonia.Controls;
using Avalonia.Controls.Templates;
namespace LANCommander.Launcher.Plugins;
/// <inheritdoc cref="IViewRegistry" />
public sealed class ViewRegistry : IViewRegistry
{
private readonly List<Registration> _registrations = new();
public void Register(Type viewModelType, Func<Control> factory)
{
ArgumentNullException.ThrowIfNull(viewModelType);
ArgumentNullException.ThrowIfNull(factory);
_registrations.Add(new Registration(viewModelType, factory));
}
public void Register<TViewModel>(Func<Control> factory) => Register(typeof(TViewModel), factory);
public IDataTemplate AsDataTemplate() => new RegistryDataTemplate(_registrations);
private readonly record struct Registration(Type ViewModelType, Func<Control> Factory);
/// <summary>
/// A live view over the registry's registrations. Because it holds the same list instance the
/// registry appends to, plugin registrations made after this template is attached are still
/// picked up.
/// </summary>
private sealed class RegistryDataTemplate(IReadOnlyList<Registration> registrations) : IDataTemplate
{
public bool Match(object? data) => data != null && FindFactory(data.GetType()) != null;
public Control? Build(object? data) => data == null ? null : FindFactory(data.GetType())?.Invoke();
private Func<Control>? FindFactory(Type dataType)
{
Type? bestType = null;
Func<Control>? bestFactory = null;
foreach (var registration in registrations)
{
if (!registration.ViewModelType.IsAssignableFrom(dataType))
continue;
// Prefer the most-derived match: a candidate wins if the current best is one of its
// base types (i.e. the candidate is more specific), or if there is no best yet.
if (bestType == null || bestType.IsAssignableFrom(registration.ViewModelType))
{
bestType = registration.ViewModelType;
bestFactory = registration.Factory;
}
}
return bestFactory;
}
}
}