diff --git a/Lattice.Core/Engine/LayoutManager.cs b/Lattice.Core/Engine/LayoutManager.cs index 7637d4c..5c87cc0 100644 --- a/Lattice.Core/Engine/LayoutManager.cs +++ b/Lattice.Core/Engine/LayoutManager.cs @@ -1,7 +1,10 @@ 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.Engine; @@ -158,8 +161,68 @@ public class LayoutManager : ILayoutService } /// - public string SaveLayout() { /* Реализация через JsonConverter */ return string.Empty; } + 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; + } + } /// - public void LoadLayout(string jsonData) { /* Реализация через JsonConverter */ } + public void LoadLayout(string jsonData) + { + if (string.IsNullOrWhiteSpace(jsonData)) return; + + var options = GetJsonOptions(); + try + { + var importedRoot = JsonSerializer.Deserialize(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); + } + } + } } diff --git a/Lattice.Core/Models/ActionDefinition.cs b/Lattice.Core/Models/ActionDefinition.cs index 7e11bab..91ec363 100644 --- a/Lattice.Core/Models/ActionDefinition.cs +++ b/Lattice.Core/Models/ActionDefinition.cs @@ -1,16 +1,32 @@ namespace Lattice.Core.Models; /// -/// Определение команды для панели инструментов или меню. +/// Определение действия (команды), которое может быть отображено в интерфейсе. /// -public class ActionDefinition +public record ActionDefinition { - public string Id { get; init; } = string.Empty; - public string Label { get; init; } = string.Empty; - public string IconKey { get; init; } = string.Empty; + /// + /// Уникальный идентификатор команды. + /// + public string Id { get; init; } = Guid.NewGuid().ToString(); /// - /// Контекст, в котором эта кнопка должна быть доступна (например, "C#", "XAML", "Common"). + /// Текст кнопки. + /// + public string Label { get; init; } = "Action"; + + /// + /// Группа контекста, к которой привязана кнопка (например, "CodeEditor"). /// public string TargetContext { get; init; } = "Common"; + + /// + /// Указывает, активна ли кнопка в данный момент. + /// + public bool IsEnabled { get; set; } = true; + + /// + /// Подсказка (Tooltip). + /// + public string Tooltip { get; init; } = string.Empty; } diff --git a/Lattice.Core/Persistence/LayoutJsonConverter.cs b/Lattice.Core/Persistence/LayoutJsonConverter.cs new file mode 100644 index 0000000..5b50f0a --- /dev/null +++ b/Lattice.Core/Persistence/LayoutJsonConverter.cs @@ -0,0 +1,31 @@ +using Lattice.Core.Models; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Lattice.Core.Persistence; + +/// +/// Конвертер для полиморфной сериализации и десериализации узлов дерева Lattice. +/// +public class LayoutJsonConverter : JsonConverter +{ + public override LayoutNode? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using var jsonDoc = JsonDocument.ParseValue(ref reader); + var rootElement = jsonDoc.RootElement; + + // Определяем тип узла по наличию специфических свойств + if (rootElement.TryGetProperty("Orientation", out _)) + { + return JsonSerializer.Deserialize(rootElement.GetRawText(), options); + } + + return JsonSerializer.Deserialize(rootElement.GetRawText(), options); + } + + public override void Write(Utf8JsonWriter writer, LayoutNode value, JsonSerializerOptions options) + { + // Используем стандартную сериализацию для конкретных типов + JsonSerializer.Serialize(writer, (object)value, value.GetType(), options); + } +} diff --git a/Lattice.Core/README.md b/Lattice.Core/README.md new file mode 100644 index 0000000..1d16b23 --- /dev/null +++ b/Lattice.Core/README.md @@ -0,0 +1,52 @@ +# Lattice.Core + +[![Framework](img.shields.io)](#) +[![Author](img.shields.io)](git.frigat.duckdns.org) +[![Platform](img.shields.io)](#) + +**Lattice.Core** — это платформонезависимое ядро (Layout Engine) для построения сложных интерфейсов с системой докинга в стиле Visual Studio 2026. + +Библиотека является частью экосистемы **Lattice** и отвечает исключительно за математику макета, управление деревом узлов и контекстное состояние, не имея зависимостей от конкретных UI-фреймворков. + +## 🚀 Особенности + +- **Агностическая архитектура**: Полная совместимость с .NET 8+, WinUI 3 и Uno Platform. +- **Древовидная компоновка**: Управление интерфейсом через узлы (`Split` и `Content`). +- **Context-Aware System**: Встроенный сервис отслеживания контекста для динамического переключения панелей инструментов. +- **Smart Docking**: Алгоритмы автоматического разделения зон и схлопывания пустых контейнеров. +- **JSON Persistence**: Полиморфная сериализация макетов для сохранения и загрузки состояний пользователя. + +## 📁 Структура проекта + +* `Abstractions/` — Интерфейсы для расширения системы. +* `Models/` — Базовые сущности дерева (узлы, направления, ориентация). +* `Engine/` — `LayoutManager`, реализующий логику трансформации дерева. +* `Context/` — Сервисы управления активными состояниями и командами. +* `Persistence/` — Логика сохранения макета в JSON. + +## 🛠 Использование + +### Создание базового макета + +```csharp +var layoutManager = new LayoutManager(); + +// Создаем контентные узлы +var explorer = new ContentNode(new MyToolComponent("Solution Explorer", "Explorer")); +var editor = new ContentNode(new MyDocumentComponent("Main.cs", "CodeEditor")); + +// Устанавливаем редактор как корень +layoutManager.SetRoot(editor); + +// Прикрепляем проводник слева от редактора +layoutManager.Dock(explorer, editor, DockDirection.Left); + +//Переключение контекста +var contextService = new ContextManager(); + +// Вызывается при активации вкладки в UI +contextService.SetContext("CodeEditor"); + +// Проверка видимости команд в текущем контексте +bool isDebugVisible = contextService.IsCommandVisible("btnDebug", "CodeEditor"); +``` \ No newline at end of file