DragAndDrop core

This commit is contained in:
FrigaT
2026-01-18 16:33:35 +03:00
parent 9ea82af329
commit 79bdd8bc62
229 changed files with 21214 additions and 2494 deletions

View File

@@ -0,0 +1,158 @@
namespace Lattice.Core.Docking.Services;
/// <summary>
/// Реестр типов содержимого, который позволяет создавать экземпляры контента по типу.
/// Этот сервис является центральным для динамического создания панелей инструментов и документов в IDE.
/// </summary>
/// <remarks>
/// Реализует шаблон "Фабрика" для создания экземпляров <see cref="Abstractions.IDockContent"/>.
/// Позволяет регистрировать фабричные методы для различных типов контента, что обеспечивает
/// позднее связывание и возможность плагинной архитектуры.
/// </remarks>
public class ContentRegistry
{
private readonly Dictionary<string, ContentDescriptor> _contentTypes = new();
/// <summary>
/// Регистрирует фабричный метод для создания контента указанного типа.
/// </summary>
/// <typeparam name="T">Тип контента, реализующий <see cref="Abstractions.IDockContent"/>.</typeparam>
/// <param name="contentTypeId">Уникальный идентификатор типа контента.</param>
/// <param name="factory">Фабричный метод для создания экземпляров контента.</param>
/// <param name="metadata">Метаданные типа контента (опционально).</param>
/// <exception cref="ArgumentNullException">Выбрасывается, если contentTypeId или factory равны null.</exception>
/// <exception cref="ArgumentException">Выбрасывается, если contentTypeId уже зарегистрирован.</exception>
public void Register<T>(string contentTypeId, Func<T> factory, ContentMetadata? metadata = null)
where T : Abstractions.IDockContent
{
if (string.IsNullOrWhiteSpace(contentTypeId))
throw new ArgumentNullException(nameof(contentTypeId));
if (factory == null)
throw new ArgumentNullException(nameof(factory));
if (_contentTypes.ContainsKey(contentTypeId))
throw new ArgumentException($"Content type '{contentTypeId}' is already registered.");
_contentTypes[contentTypeId] = new ContentDescriptor(
typeof(T),
() => factory(),
metadata ?? new ContentMetadata(contentTypeId, typeof(T).Name)
);
}
/// <summary>
/// Создает новый экземпляр контента указанного типа с заданным идентификатором.
/// </summary>
/// <param name="contentTypeId">Идентификатор типа контента.</param>
/// <param name="id">Уникальный идентификатор для создаваемого экземпляра контента.</param>
/// <returns>Новый экземпляр контента.</returns>
/// <exception cref="KeyNotFoundException">Выбрасывается, если тип контента не зарегистрирован.</exception>
public Abstractions.IDockContent CreateContent(string contentTypeId, string id)
{
if (!_contentTypes.TryGetValue(contentTypeId, out var descriptor))
throw new KeyNotFoundException($"Content type '{contentTypeId}' is not registered.");
var content = descriptor.Factory();
// Устанавливаем ID через рефлексию, если есть свойство Id
var property = content.GetType().GetProperty("Id");
if (property != null && property.CanWrite)
{
property.SetValue(content, id);
}
return content;
}
/// <summary>
/// Получает метаданные для указанного типа контента.
/// </summary>
/// <param name="contentTypeId">Идентификатор типа контента.</param>
/// <returns>Метаданные типа контента или null, если тип не найден.</returns>
public ContentMetadata? GetMetadata(string contentTypeId)
{
return _contentTypes.TryGetValue(contentTypeId, out var descriptor)
? descriptor.Metadata
: null;
}
/// <summary>
/// Получает все зарегистрированные типы контента.
/// </summary>
/// <returns>Коллекция идентификаторов зарегистрированных типов контента.</returns>
public IEnumerable<string> GetRegisteredTypes() => _contentTypes.Keys;
/// <summary>
/// Проверяет, зарегистрирован ли указанный тип контента.
/// </summary>
public bool IsRegistered(string contentTypeId) => _contentTypes.ContainsKey(contentTypeId);
/// <summary>
/// Дескриптор типа контента, содержащий информацию о фабричном методе и метаданных.
/// </summary>
private class ContentDescriptor
{
public Type ContentType { get; }
public Func<Abstractions.IDockContent> Factory { get; }
public ContentMetadata Metadata { get; }
public ContentDescriptor(Type contentType, Func<Abstractions.IDockContent> factory, ContentMetadata metadata)
{
ContentType = contentType;
Factory = factory;
Metadata = metadata;
}
}
}
/// <summary>
/// Метаданные типа контента, предоставляющие дополнительную информацию для отображения в UI.
/// </summary>
public class ContentMetadata
{
/// <summary>
/// Идентификатор типа контента.
/// </summary>
public string ContentTypeId { get; }
/// <summary>
/// Отображаемое имя типа контента.
/// </summary>
public string DisplayName { get; set; }
/// <summary>
/// Описание типа контента.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Имя ресурса для иконки (опционально).
/// </summary>
public string? IconResource { get; set; }
/// <summary>
/// Признак того, что контент является документом (а не инструментальной панелью).
/// </summary>
public bool IsDocument { get; set; }
/// <summary>
/// Минимальная ширина контента в пикселях.
/// </summary>
public double DefaultWidth { get; set; } = 300;
/// <summary>
/// Минимальная высота контента в пикселях.
/// </summary>
public double DefaultHeight { get; set; } = 200;
/// <summary>
/// Инициализирует новый экземпляр метаданных контента.
/// </summary>
/// <param name="contentTypeId">Идентификатор типа контента.</param>
/// <param name="displayName">Отображаемое имя.</param>
public ContentMetadata(string contentTypeId, string displayName)
{
ContentTypeId = contentTypeId;
DisplayName = displayName;
Description = string.Empty;
}
}