Добавлен Core
This commit is contained in:
@@ -1,7 +1,10 @@
|
|||||||
using Lattice.Core.Abstractions;
|
using Lattice.Core.Abstractions;
|
||||||
using Lattice.Core.Models;
|
using Lattice.Core.Models;
|
||||||
using Lattice.Core.Models.Enums;
|
using Lattice.Core.Models.Enums;
|
||||||
|
using Lattice.Core.Persistence;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Lattice.Core.Engine;
|
namespace Lattice.Core.Engine;
|
||||||
|
|
||||||
@@ -158,8 +161,68 @@ public class LayoutManager : ILayoutService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void LoadLayout(string jsonData) { /* Реализация через JsonConverter */ }
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,32 @@
|
|||||||
namespace Lattice.Core.Models;
|
namespace Lattice.Core.Models;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Определение команды для панели инструментов или меню.
|
/// Определение действия (команды), которое может быть отображено в интерфейсе.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ActionDefinition
|
public record ActionDefinition
|
||||||
{
|
{
|
||||||
public string Id { get; init; } = string.Empty;
|
/// <summary>
|
||||||
public string Label { get; init; } = string.Empty;
|
/// Уникальный идентификатор команды.
|
||||||
public string IconKey { get; init; } = string.Empty;
|
/// </summary>
|
||||||
|
public string Id { get; init; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Контекст, в котором эта кнопка должна быть доступна (например, "C#", "XAML", "Common").
|
/// Текст кнопки.
|
||||||
|
/// </summary>
|
||||||
|
public string Label { get; init; } = "Action";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Группа контекста, к которой привязана кнопка (например, "CodeEditor").
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string TargetContext { get; init; } = "Common";
|
public string TargetContext { get; init; } = "Common";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Указывает, активна ли кнопка в данный момент.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEnabled { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Подсказка (Tooltip).
|
||||||
|
/// </summary>
|
||||||
|
public string Tooltip { get; init; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
31
Lattice.Core/Persistence/LayoutJsonConverter.cs
Normal file
31
Lattice.Core/Persistence/LayoutJsonConverter.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using Lattice.Core.Models;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Lattice.Core.Persistence;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Конвертер для полиморфной сериализации и десериализации узлов дерева Lattice.
|
||||||
|
/// </summary>
|
||||||
|
public class LayoutJsonConverter : JsonConverter<LayoutNode>
|
||||||
|
{
|
||||||
|
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<SplitContainerNode>(rootElement.GetRawText(), options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<ContentNode>(rootElement.GetRawText(), options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, LayoutNode value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
// Используем стандартную сериализацию для конкретных типов
|
||||||
|
JsonSerializer.Serialize(writer, (object)value, value.GetType(), options);
|
||||||
|
}
|
||||||
|
}
|
||||||
52
Lattice.Core/README.md
Normal file
52
Lattice.Core/README.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Lattice.Core
|
||||||
|
|
||||||
|
[](#)
|
||||||
|
[](git.frigat.duckdns.org)
|
||||||
|
[](#)
|
||||||
|
|
||||||
|
**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");
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user