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,317 @@
using Lattice.Core.Docking.Abstractions;
using Lattice.Core.Docking.Engine;
using Lattice.Core.Docking.Models;
using System.Reflection;
namespace Lattice.Serialization.Docking;
/// <summary>
/// Предоставляет методы для преобразования между объектной моделью док-системы
/// и Data Transfer Objects (DTO) для сериализации.
/// </summary>
/// <remarks>
/// Этот класс является центральным местом для преобразования между различными представлениями
/// данных макета. Он обеспечивает независимость формата сериализации от внутренней структуры
/// данных и позволяет легко добавлять новые форматы без изменения логики преобразования.
/// </remarks>
public static class LayoutConverter
{
/// <summary>
/// Преобразует менеджер макета в DTO для сериализации.
/// </summary>
/// <param name="manager">Менеджер макета для преобразования.</param>
/// <returns>
/// DTO, содержащий все данные, необходимые для восстановления состояния макета.
/// </returns>
/// <exception cref="ArgumentNullException">Выбрасывается, если <paramref name="manager"/> равен null.</exception>
public static LayoutDto ConvertToDto(LayoutManager manager)
{
if (manager == null)
throw new ArgumentNullException(nameof(manager));
var dto = new LayoutDto
{
ApplicationId = GetApplicationIdentifier()
};
if (manager.Root != null)
{
dto.Root = ConvertElementToDto(manager.Root);
}
dto.FloatingWindows = manager.FloatingWindows
.Select(ConvertWindowToDto)
.ToList();
dto.AutoHidePanels = manager.AutoHidePanels
.Select(ConvertAutoHidePanelToDto)
.ToList();
return dto;
}
/// <summary>
/// Восстанавливает состояние менеджера макета из DTO.
/// </summary>
/// <param name="manager">Менеджер макета для восстановления состояния.</param>
/// <param name="dto">DTO, содержащий данные состояния макета.</param>
/// <param name="contentResolver">
/// Функция разрешения контента по идентификатору.
/// </param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="manager"/> или <paramref name="dto"/> равен null.
/// </exception>
public static void RestoreFromDto(LayoutManager manager, LayoutDto dto,
Func<string, IDockContent?> contentResolver)
{
if (manager == null)
throw new ArgumentNullException(nameof(manager));
if (dto == null)
throw new ArgumentNullException(nameof(dto));
// Сбрасываем текущее состояние
manager.Reset();
// Восстанавливаем корневой элемент
if (dto.Root != null)
{
manager.Root = ConvertDtoToElement(dto.Root, contentResolver);
}
// Восстанавливаем плавающие окна
foreach (var windowDto in dto.FloatingWindows)
{
var window = new DockWindow
{
X = windowDto.X,
Y = windowDto.Y,
Width = windowDto.Width,
Height = windowDto.Height,
Title = windowDto.Title
};
if (windowDto.Root != null)
{
window.Root = ConvertDtoToElement(windowDto.Root, contentResolver);
}
manager.FloatingWindows.Add(window);
}
// Восстанавливаем автоскрываемые панели
foreach (var panelDto in dto.AutoHidePanels)
{
var content = contentResolver(panelDto.Content.Id);
if (content != null)
{
if (Enum.TryParse<DockSide>(panelDto.Side, out var side))
{
var panel = manager.AddAutoHidePanel(content, side);
panel.Size = panelDto.Size;
panel.IsVisible = panelDto.IsVisible;
panel.SlideOffset = panelDto.SlideOffset;
}
}
}
}
/// <summary>
/// Преобразует элемент дерева компоновки в DTO.
/// </summary>
private static ElementDto ConvertElementToDto(IDockElement element)
{
if (element is DockGroup group)
{
return new GroupDto
{
Id = group.Id,
Type = "group",
Width = group.Width,
Height = group.Height,
MinWidth = group.MinWidth,
MinHeight = group.MinHeight,
First = ConvertElementToDto(group.First),
Second = ConvertElementToDto(group.Second),
Orientation = group.Orientation.ToString(),
SplitRatio = group.SplitRatio
};
}
else if (element is DockLeaf leaf)
{
return new LeafDto
{
Id = leaf.Id,
Type = "leaf",
Width = leaf.Width,
Height = leaf.Height,
MinWidth = leaf.MinWidth,
MinHeight = leaf.MinHeight,
Contents = leaf.Children.Select(ConvertContentToDto).ToList(),
ActiveContentId = leaf.ActiveContent?.Id,
TabPlacement = leaf.TabPlacement.ToString()
};
}
throw new NotSupportedException($"Element type {element.GetType().Name} is not supported for serialization");
}
/// <summary>
/// Преобразует контент в DTO ссылки.
/// </summary>
private static ContentReferenceDto ConvertContentToDto(IDockContent content)
{
var dto = new ContentReferenceDto
{
Id = content.Id,
TypeId = GetContentTypeId(content),
Title = content.Title,
CanClose = content.CanClose
};
// Сохраняем дополнительные свойства, если контент поддерживает сериализацию состояния
if (content is ISerializableContent serializable)
{
dto.Properties = serializable.GetSerializableState();
}
return dto;
}
/// <summary>
/// Преобразует окно в DTO.
/// </summary>
private static WindowDto ConvertWindowToDto(DockWindow window)
{
return new WindowDto
{
Id = window.Id,
X = window.X,
Y = window.Y,
Width = window.Width,
Height = window.Height,
Title = window.Title,
Root = window.Root != null ? ConvertElementToDto(window.Root) : null
};
}
/// <summary>
/// Преобразует автоскрываемую панель в DTO.
/// </summary>
private static AutoHidePanelDto ConvertAutoHidePanelToDto(AutoHidePanel panel)
{
return new AutoHidePanelDto
{
Id = panel.Id,
Content = ConvertContentToDto(panel.Content),
Side = panel.Side.ToString(),
Size = panel.Size,
IsVisible = panel.IsVisible,
SlideOffset = panel.SlideOffset
};
}
/// <summary>
/// Преобразует DTO обратно в элемент дерева.
/// </summary>
private static IDockElement ConvertDtoToElement(ElementDto dto, Func<string, IDockContent?> contentResolver)
{
return dto switch
{
GroupDto groupDto => ConvertGroupDtoToElement(groupDto, contentResolver),
LeafDto leafDto => ConvertLeafDtoToElement(leafDto, contentResolver),
_ => throw new NotSupportedException($"Unsupported DTO type: {dto.Type}")
};
}
/// <summary>
/// Преобразует DTO группы в элемент.
/// </summary>
private static DockGroup ConvertGroupDtoToElement(GroupDto dto, Func<string, IDockContent?> contentResolver)
{
var group = new DockGroup(
ConvertDtoToElement(dto.First, contentResolver),
ConvertDtoToElement(dto.Second, contentResolver),
Enum.Parse<SplitDirection>(dto.Orientation))
{
Width = dto.Width,
Height = dto.Height,
SplitRatio = dto.SplitRatio
};
// Восстанавливаем ID
SetElementId(group, dto.Id);
return group;
}
/// <summary>
/// Преобразует DTO листа в элемент.
/// </summary>
private static DockLeaf ConvertLeafDtoToElement(LeafDto dto, Func<string, IDockContent?> contentResolver)
{
var leaf = new DockLeaf
{
Width = dto.Width,
Height = dto.Height,
MinWidth = dto.MinWidth,
MinHeight = dto.MinHeight,
TabPlacement = Enum.Parse<TabPlacement>(dto.TabPlacement)
};
// Восстанавливаем ID
SetElementId(leaf, dto.Id);
// Восстанавливаем контент
foreach (var contentRef in dto.Contents)
{
var content = contentResolver(contentRef.Id);
if (content != null)
{
// Восстанавливаем состояние контента, если он поддерживает десериализацию
if (content is ISerializableContent serializable)
{
serializable.RestoreFromState(contentRef.Properties);
}
leaf.AddContent(content);
}
}
// Восстанавливаем активный контент
if (dto.ActiveContentId != null)
{
leaf.ActiveContent = leaf.Children.FirstOrDefault(c => c.Id == dto.ActiveContentId);
}
return leaf;
}
/// <summary>
/// Получает идентификатор типа контента.
/// </summary>
private static string GetContentTypeId(IDockContent content)
{
// По умолчанию используем имя типа
return content.GetType().Name;
}
/// <summary>
/// Устанавливает ID элемента через рефлексию.
/// </summary>
private static void SetElementId(object element, string id)
{
var property = element.GetType().GetProperty("Id");
if (property != null && property.CanWrite && property.PropertyType == typeof(string))
{
property.SetValue(element, id);
}
}
/// <summary>
/// Получает идентификатор приложения.
/// </summary>
private static string GetApplicationIdentifier()
{
return Assembly.GetEntryAssembly()?.GetName().Name ?? "UnknownApp";
}
}