Добавлен Studio
This commit is contained in:
228
Lattice.Core/Services/LayoutService.cs
Normal file
228
Lattice.Core/Services/LayoutService.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
using Lattice.Core.Abstractions;
|
||||
using Lattice.Core.Models;
|
||||
using Lattice.Core.Models.Enums;
|
||||
using Lattice.Core.Persistence;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Lattice.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Реализация сервиса управления макетом.
|
||||
/// </summary>
|
||||
public class LayoutService : ILayoutService
|
||||
{
|
||||
private readonly ILogger? _logger;
|
||||
private LayoutNode? _root;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LayoutNode? Root => _root;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler? LayoutUpdated;
|
||||
|
||||
public LayoutService(ILogger<LayoutService>? logger = null)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dock(LayoutNode source, LayoutNode target, DockDirection direction)
|
||||
{
|
||||
if (source == target) return;
|
||||
|
||||
_logger?.LogDebug("Начало трансформации дерева: {Source} -> {Target} ({Direction})", source.Name, target.Name, direction);
|
||||
|
||||
// 1. Извлекаем источник из его текущего места в дереве
|
||||
Remove(source);
|
||||
|
||||
// 2. Если докинг в центр — это логика объединения (например, в TabView)
|
||||
// В рамках Core это может означать добавление в тот же контейнер
|
||||
if (direction == DockDirection.Center)
|
||||
{
|
||||
HandleCenterDock(source, target);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 3. Создаем разделение (Split)
|
||||
HandleSideDock(source, target, direction);
|
||||
}
|
||||
|
||||
LayoutUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Логика добавления элемента в центральную часть (вкладки).
|
||||
/// </summary>
|
||||
private void HandleCenterDock(LayoutNode source, LayoutNode target)
|
||||
{
|
||||
if (target.Parent is SplitContainerNode parent)
|
||||
{
|
||||
parent.AddChild(source);
|
||||
}
|
||||
else if (target == _root)
|
||||
{
|
||||
// Если таргет - корень, и мы докаем в центр, создаем контейнер по умолчанию
|
||||
var container = new SplitContainerNode(SplitOrientation.Horizontal);
|
||||
_root = container;
|
||||
container.AddChild(target);
|
||||
container.AddChild(source);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Логика разделения существующей области на две (Side Dock).
|
||||
/// </summary>
|
||||
private void HandleSideDock(LayoutNode source, LayoutNode target, DockDirection direction)
|
||||
{
|
||||
var orientation = (direction == DockDirection.Left || direction == DockDirection.Right)
|
||||
? SplitOrientation.Horizontal
|
||||
: SplitOrientation.Vertical;
|
||||
|
||||
var parent = target.Parent as SplitContainerNode;
|
||||
|
||||
// Создаем новый сплиттер, который заменит target
|
||||
var newContainer = new SplitContainerNode(orientation);
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
// Заменяем target на новый контейнер в списке детей родителя
|
||||
int index = parent.Children.IndexOf(target);
|
||||
parent.Children[index] = newContainer;
|
||||
newContainer.Parent = parent;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Если родителя нет, значит target был корнем
|
||||
_root = newContainer;
|
||||
}
|
||||
|
||||
// Настраиваем порядок в новом сплиттере
|
||||
if (direction == DockDirection.Left || direction == DockDirection.Top)
|
||||
{
|
||||
newContainer.AddChild(source);
|
||||
newContainer.AddChild(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
newContainer.AddChild(target);
|
||||
newContainer.AddChild(source);
|
||||
}
|
||||
|
||||
// Корректируем размеры (например, делим пополам)
|
||||
source.WidthValue = 0.5;
|
||||
target.WidthValue = 0.5;
|
||||
source.IsWidthStar = true;
|
||||
target.IsWidthStar = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Remove(LayoutNode node)
|
||||
{
|
||||
if (node.Parent is SplitContainerNode parent)
|
||||
{
|
||||
parent.Children.Remove(node);
|
||||
node.Parent = null;
|
||||
|
||||
// Если в контейнере остался один элемент — убираем лишнюю вложенность
|
||||
if (parent.Children.Count == 1)
|
||||
{
|
||||
CollapseContainer(parent);
|
||||
}
|
||||
}
|
||||
else if (node == _root)
|
||||
{
|
||||
_root = null;
|
||||
}
|
||||
|
||||
LayoutUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Убирает ненужные контейнеры, если в них остался только один элемент.
|
||||
/// </summary>
|
||||
private void CollapseContainer(SplitContainerNode container)
|
||||
{
|
||||
var lastChild = container.Children[0];
|
||||
var parent = container.Parent as SplitContainerNode;
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
int index = parent.Children.IndexOf(container);
|
||||
parent.Children[index] = lastChild;
|
||||
lastChild.Parent = parent;
|
||||
}
|
||||
else
|
||||
{
|
||||
_root = lastChild;
|
||||
lastChild.Parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string SaveLayout()
|
||||
{
|
||||
if (_root == null) return string.Empty;
|
||||
|
||||
var options = GetJsonOptions();
|
||||
try
|
||||
{
|
||||
string json = JsonSerializer.Serialize(_root, options);
|
||||
_logger?.LogInformation("Макет успешно экспортирован в JSON. Длина: {Length}", json.Length);
|
||||
return json;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "Ошибка при сохранении макета Lattice");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void LoadLayout(string jsonData)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(jsonData)) return;
|
||||
|
||||
var options = GetJsonOptions();
|
||||
try
|
||||
{
|
||||
var importedRoot = JsonSerializer.Deserialize<LayoutNode>(jsonData, options);
|
||||
if (importedRoot != null)
|
||||
{
|
||||
// При загрузке нужно восстановить связи Parent, так как они не сериализуются (циклические ссылки)
|
||||
RestoreParentLinks(importedRoot, null);
|
||||
_root = importedRoot;
|
||||
_logger?.LogInformation("Макет успешно загружен. Корневой узел: {Id}", _root.Id);
|
||||
LayoutUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "Ошибка при десериализации макета Lattice");
|
||||
}
|
||||
}
|
||||
|
||||
private JsonSerializerOptions GetJsonOptions()
|
||||
{
|
||||
return new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
Converters = { new LayoutJsonConverter() },
|
||||
// Игнорируем циклы, так как мы восстановим Parent вручную
|
||||
ReferenceHandler = ReferenceHandler.IgnoreCycles
|
||||
};
|
||||
}
|
||||
|
||||
private void RestoreParentLinks(LayoutNode node, LayoutNode? parent)
|
||||
{
|
||||
node.Parent = parent;
|
||||
if (node is SplitContainerNode container)
|
||||
{
|
||||
foreach (var child in container.Children)
|
||||
{
|
||||
RestoreParentLinks(child, container);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user