318 lines
11 KiB
C#
318 lines
11 KiB
C#
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";
|
|
}
|
|
}
|