using Lattice.Core.Docking.Abstractions; using Lattice.Core.Docking.Engine; using Lattice.Core.Docking.Models; using System.Reflection; namespace Lattice.Serialization.Docking; /// /// Предоставляет методы для преобразования между объектной моделью док-системы /// и Data Transfer Objects (DTO) для сериализации. /// /// /// Этот класс является центральным местом для преобразования между различными представлениями /// данных макета. Он обеспечивает независимость формата сериализации от внутренней структуры /// данных и позволяет легко добавлять новые форматы без изменения логики преобразования. /// public static class LayoutConverter { /// /// Преобразует менеджер макета в DTO для сериализации. /// /// Менеджер макета для преобразования. /// /// DTO, содержащий все данные, необходимые для восстановления состояния макета. /// /// Выбрасывается, если равен null. 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; } /// /// Восстанавливает состояние менеджера макета из DTO. /// /// Менеджер макета для восстановления состояния. /// DTO, содержащий данные состояния макета. /// /// Функция разрешения контента по идентификатору. /// /// /// Выбрасывается, если или равен null. /// public static void RestoreFromDto(LayoutManager manager, LayoutDto dto, Func 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(panelDto.Side, out var side)) { var panel = manager.AddAutoHidePanel(content, side); panel.Size = panelDto.Size; panel.IsVisible = panelDto.IsVisible; panel.SlideOffset = panelDto.SlideOffset; } } } } /// /// Преобразует элемент дерева компоновки в DTO. /// 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"); } /// /// Преобразует контент в DTO ссылки. /// 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; } /// /// Преобразует окно в DTO. /// 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 }; } /// /// Преобразует автоскрываемую панель в DTO. /// 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 }; } /// /// Преобразует DTO обратно в элемент дерева. /// private static IDockElement ConvertDtoToElement(ElementDto dto, Func contentResolver) { return dto switch { GroupDto groupDto => ConvertGroupDtoToElement(groupDto, contentResolver), LeafDto leafDto => ConvertLeafDtoToElement(leafDto, contentResolver), _ => throw new NotSupportedException($"Unsupported DTO type: {dto.Type}") }; } /// /// Преобразует DTO группы в элемент. /// private static DockGroup ConvertGroupDtoToElement(GroupDto dto, Func contentResolver) { var group = new DockGroup( ConvertDtoToElement(dto.First, contentResolver), ConvertDtoToElement(dto.Second, contentResolver), Enum.Parse(dto.Orientation)) { Width = dto.Width, Height = dto.Height, SplitRatio = dto.SplitRatio }; // Восстанавливаем ID SetElementId(group, dto.Id); return group; } /// /// Преобразует DTO листа в элемент. /// private static DockLeaf ConvertLeafDtoToElement(LeafDto dto, Func contentResolver) { var leaf = new DockLeaf { Width = dto.Width, Height = dto.Height, MinWidth = dto.MinWidth, MinHeight = dto.MinHeight, TabPlacement = Enum.Parse(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; } /// /// Получает идентификатор типа контента. /// private static string GetContentTypeId(IDockContent content) { // По умолчанию используем имя типа return content.GetType().Name; } /// /// Устанавливает ID элемента через рефлексию. /// 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); } } /// /// Получает идентификатор приложения. /// private static string GetApplicationIdentifier() { return Assembly.GetEntryAssembly()?.GetName().Name ?? "UnknownApp"; } }