Добавьте файлы проекта.
This commit is contained in:
27
Lattice.Core/Abstractions/IContextService.cs
Normal file
27
Lattice.Core/Abstractions/IContextService.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace Lattice.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Сервис управления контекстом приложения и связанными командами.
|
||||
/// </summary>
|
||||
public interface IContextService
|
||||
{
|
||||
/// <summary>
|
||||
/// Имя текущего активного контекста.
|
||||
/// </summary>
|
||||
string CurrentContext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Возникает при смене фокуса между вкладками с разными ContextGroup.
|
||||
/// </summary>
|
||||
event EventHandler<string>? ContextChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает активный контекст. Вызывается UI-слоем при активации вкладки.
|
||||
/// </summary>
|
||||
void SetContext(string contextGroup);
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, должна ли команда быть видимой в текущем контексте.
|
||||
/// </summary>
|
||||
bool IsCommandVisible(string commandId, string commandContext);
|
||||
}
|
||||
33
Lattice.Core/Abstractions/IDockableComponent.cs
Normal file
33
Lattice.Core/Abstractions/IDockableComponent.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace Lattice.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Описывает компонент, который может быть размещен внутри узла компоновки Lattice.
|
||||
/// </summary>
|
||||
public interface IDockableComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Уникальный строковый идентификатор компонента (например, "SolutionExplorer").
|
||||
/// </summary>
|
||||
string UniqueId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Заголовок, отображаемый на вкладке или в заголовке панели.
|
||||
/// </summary>
|
||||
string DisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Ключ иконки (для Segoe Fluent Icons или путей к ресурсам).
|
||||
/// </summary>
|
||||
string? IconKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Группа контекста (например, "CodeEditor", "Debugger").
|
||||
/// Определяет, какие панели инструментов будут активны.
|
||||
/// </summary>
|
||||
string ContextGroup { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Указывает, разрешено ли закрывать данный компонент пользователем.
|
||||
/// </summary>
|
||||
bool CanClose { get; }
|
||||
}
|
||||
42
Lattice.Core/Abstractions/ILayoutElement.cs
Normal file
42
Lattice.Core/Abstractions/ILayoutElement.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
namespace Lattice.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Представляет базовый элемент иерархии компоновки Lattice.
|
||||
/// </summary>
|
||||
public interface ILayoutElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Уникальный идентификатор элемента.
|
||||
/// </summary>
|
||||
Guid Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Имя элемента для отображения или идентификации в логах.
|
||||
/// </summary>
|
||||
string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Значение ширины (в пикселях или долях "star").
|
||||
/// </summary>
|
||||
double WidthValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Указывает, является ли ширина пропорциональной (star).
|
||||
/// </summary>
|
||||
bool IsWidthStar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Значение высоты (в пикселях или долях "star").
|
||||
/// </summary>
|
||||
double HeightValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Указывает, является ли высота пропорциональной (star).
|
||||
/// </summary>
|
||||
bool IsHeightStar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Родительский элемент в дереве компоновки.
|
||||
/// </summary>
|
||||
ILayoutElement? Parent { get; set; }
|
||||
}
|
||||
40
Lattice.Core/Abstractions/ILayoutService.cs
Normal file
40
Lattice.Core/Abstractions/ILayoutService.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Lattice.Core.Models;
|
||||
using Lattice.Core.Models.Enums;
|
||||
|
||||
namespace Lattice.Core.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Сервис управления жизненным циклом макета приложения.
|
||||
/// </summary>
|
||||
public interface ILayoutService
|
||||
{
|
||||
/// <summary>
|
||||
/// Текущий корневой узел всей структуры окон.
|
||||
/// </summary>
|
||||
LayoutNode? Root { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Событие, возникающее при любом изменении структуры (докинг, закрытие, изменение размеров).
|
||||
/// </summary>
|
||||
event EventHandler? LayoutUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Перемещает узел в указанную позицию относительно целевого узла.
|
||||
/// </summary>
|
||||
void Dock(LayoutNode source, LayoutNode target, DockDirection direction);
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет узел из макета (например, при закрытии вкладки).
|
||||
/// </summary>
|
||||
void Remove(LayoutNode node);
|
||||
|
||||
/// <summary>
|
||||
/// Импортирует структуру макета из снапшота.
|
||||
/// </summary>
|
||||
void LoadLayout(string jsonData);
|
||||
|
||||
/// <summary>
|
||||
/// Экспортирует текущую структуру в строку для сохранения.
|
||||
/// </summary>
|
||||
string SaveLayout();
|
||||
}
|
||||
39
Lattice.Core/Context/ContextManager.cs
Normal file
39
Lattice.Core/Context/ContextManager.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Lattice.Core.Abstractions;
|
||||
|
||||
namespace Lattice.Core.Context;
|
||||
|
||||
/// <summary>
|
||||
/// Реализация сервиса управления контекстом приложения.
|
||||
/// </summary>
|
||||
public class ContextManager : IContextService
|
||||
{
|
||||
private string _currentContext = "Common";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string CurrentContext => _currentContext;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<string>? ContextChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetContext(string contextGroup)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(contextGroup)) contextGroup = "Common";
|
||||
|
||||
if (_currentContext != contextGroup)
|
||||
{
|
||||
_currentContext = contextGroup;
|
||||
ContextChanged?.Invoke(this, contextGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsCommandVisible(string commandId, string commandContext)
|
||||
{
|
||||
// Базовая логика: команда видима, если её контекст совпадает с текущим
|
||||
// или если команда помечена как общая ("Common" или "Global").
|
||||
return commandContext == "Common" ||
|
||||
commandContext == "Global" ||
|
||||
commandContext == _currentContext;
|
||||
}
|
||||
}
|
||||
165
Lattice.Core/Engine/LayoutManager.cs
Normal file
165
Lattice.Core/Engine/LayoutManager.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using Lattice.Core.Abstractions;
|
||||
using Lattice.Core.Models;
|
||||
using Lattice.Core.Models.Enums;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Lattice.Core.Engine;
|
||||
|
||||
/// <summary>
|
||||
/// Реализация сервиса управления макетом.
|
||||
/// </summary>
|
||||
public class LayoutManager : ILayoutService
|
||||
{
|
||||
private readonly ILogger? _logger;
|
||||
private LayoutNode? _root;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LayoutNode? Root => _root;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler? LayoutUpdated;
|
||||
|
||||
public LayoutManager(ILogger<LayoutManager>? 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() { /* Реализация через JsonConverter */ return string.Empty; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void LoadLayout(string jsonData) { /* Реализация через JsonConverter */ }
|
||||
}
|
||||
28
Lattice.Core/Lattice.Core.csproj
Normal file
28
Lattice.Core/Lattice.Core.csproj
Normal file
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Поддержка LTS версий и актуальной на 2026 год .NET 10 -->
|
||||
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>Lattice.Core</AssemblyName>
|
||||
<RootNamespace>Lattice.Core</RootNamespace>
|
||||
|
||||
<!-- Метаданные разработчика -->
|
||||
<Authors>FrigaT</Authors>
|
||||
<Company>FrigaT</Company>
|
||||
<RepositoryUrl>https://git.frigat.duckdns.org/FrigaT/Lattice</RepositoryUrl>
|
||||
<PackageProjectUrl>https://git.frigat.duckdns.org/FrigaT/Lattice</PackageProjectUrl>
|
||||
<Description>Core docking and layout engine for Lattice UI (WinUI 3 / Uno Platform).</Description>
|
||||
|
||||
<!-- Совместимость с Uno Platform (Trimming и AOT) -->
|
||||
<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsTrimmable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
16
Lattice.Core/Models/ActionDefinition.cs
Normal file
16
Lattice.Core/Models/ActionDefinition.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Lattice.Core.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Определение команды для панели инструментов или меню.
|
||||
/// </summary>
|
||||
public class ActionDefinition
|
||||
{
|
||||
public string Id { get; init; } = string.Empty;
|
||||
public string Label { get; init; } = string.Empty;
|
||||
public string IconKey { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Контекст, в котором эта кнопка должна быть доступна (например, "C#", "XAML", "Common").
|
||||
/// </summary>
|
||||
public string TargetContext { get; init; } = "Common";
|
||||
}
|
||||
29
Lattice.Core/Models/ContentNode.cs
Normal file
29
Lattice.Core/Models/ContentNode.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Lattice.Core.Abstractions;
|
||||
|
||||
namespace Lattice.Core.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Узел, представляющий конечный контент (вкладку, панель инструментов или документ).
|
||||
/// </summary>
|
||||
public class ContentNode : LayoutNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Ссылка на визуальный или логический компонент, закрепленный в этом узле.
|
||||
/// </summary>
|
||||
public IDockableComponent? Component { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Указывает, является ли данный узел частью основной рабочей области документов.
|
||||
/// </summary>
|
||||
public bool IsDocumentArea { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует новый экземпляр <see cref="ContentNode"/> на основе компонента.
|
||||
/// </summary>
|
||||
/// <param name="component">Компонент содержимого.</param>
|
||||
public ContentNode(IDockableComponent component)
|
||||
{
|
||||
Component = component;
|
||||
Name = component.DisplayName;
|
||||
}
|
||||
}
|
||||
11
Lattice.Core/Models/Enums/DockDirection.cs
Normal file
11
Lattice.Core/Models/Enums/DockDirection.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Lattice.Core.Models.Enums;
|
||||
|
||||
public enum DockDirection
|
||||
{
|
||||
Center,
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
Floating,
|
||||
}
|
||||
13
Lattice.Core/Models/Enums/SplitOrientation.cs
Normal file
13
Lattice.Core/Models/Enums/SplitOrientation.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Lattice.Core.Models.Enums;
|
||||
|
||||
public enum SplitOrientation
|
||||
{
|
||||
/// <summary>
|
||||
/// Элементы располагаются друг за другом по горизонтали
|
||||
/// </summary>
|
||||
Horizontal,
|
||||
/// <summary>
|
||||
/// Элементы располагаются друг за другом по вертикали
|
||||
/// </summary>
|
||||
Vertical,
|
||||
}
|
||||
35
Lattice.Core/Models/LayoutNode.cs
Normal file
35
Lattice.Core/Models/LayoutNode.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Lattice.Core.Abstractions;
|
||||
|
||||
namespace Lattice.Core.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный базовый класс для всех узлов дерева компоновки.
|
||||
/// </summary>
|
||||
public abstract class LayoutNode : ILayoutElement
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public Guid Id { get; } = Guid.NewGuid();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double WidthValue { get; set; } = 1.0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsWidthStar { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public double HeightValue { get; set; } = 1.0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsHeightStar { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ILayoutElement? Parent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает строковое представление узла для отладки.
|
||||
/// </summary>
|
||||
public override string ToString() => $"{GetType().Name} [{Name}] ({Id.ToString()[..4]})";
|
||||
}
|
||||
38
Lattice.Core/Models/SplitContainerNode.cs
Normal file
38
Lattice.Core/Models/SplitContainerNode.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Lattice.Core.Models.Enums;
|
||||
|
||||
namespace Lattice.Core.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Узел-контейнер, разделяющий пространство между дочерними элементами в определенной ориентации.
|
||||
/// </summary>
|
||||
public class SplitContainerNode : LayoutNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Ориентация разделения (горизонтальная или вертикальная).
|
||||
/// </summary>
|
||||
public SplitOrientation Orientation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Список дочерних узлов, находящихся внутри данного контейнера.
|
||||
/// </summary>
|
||||
public List<LayoutNode> Children { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует новый экземпляр <see cref="SplitContainerNode"/>.
|
||||
/// </summary>
|
||||
/// <param name="orientation">Ориентация контейнера.</param>
|
||||
public SplitContainerNode(SplitOrientation orientation)
|
||||
{
|
||||
Orientation = orientation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет дочерний узел в контейнер и устанавливает связь с родителем.
|
||||
/// </summary>
|
||||
/// <param name="child">Узел для добавления.</param>
|
||||
public void AddChild(LayoutNode child)
|
||||
{
|
||||
child.Parent = this;
|
||||
Children.Add(child);
|
||||
}
|
||||
}
|
||||
12
Lattice.Core/Models/WorkspaceSnapshot.cs
Normal file
12
Lattice.Core/Models/WorkspaceSnapshot.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Lattice.Core.Models
|
||||
{
|
||||
internal class WorkspaceSnapshot
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user