Compare commits
14 Commits
b6de0543b7
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| e8b4cb9881 | |||
| 584df249f6 | |||
| 33abd94f6e | |||
| a902474345 | |||
| 0e050b452a | |||
| 6ad7b5dcdb | |||
| bbb20edb03 | |||
| 2bd7d3c474 | |||
| be12154262 | |||
| a6ee6fcb36 | |||
|
|
79bdd8bc62 | ||
| 9ea82af329 | |||
| c3770c789b | |||
| ca5d912c9c |
23
Lattice.Core.Docking/Abstractions/IDockCommand.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
namespace Lattice.Core.Docking.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет контракт для команды в системе докинга.
|
||||||
|
/// Команды представляют действия, которые могут быть выполнены над элементами док-системы.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDockCommand : System.Windows.Input.ICommand
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает отображаемое имя команды.
|
||||||
|
/// </summary>
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает идентификатор ресурса для иконки команды.
|
||||||
|
/// </summary>
|
||||||
|
string Icon { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает текстовое представление жеста (горячей клавиши) для команды.
|
||||||
|
/// </summary>
|
||||||
|
string GestureText { get; }
|
||||||
|
}
|
||||||
38
Lattice.Core.Docking/Abstractions/IDockContainer.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using Lattice.Core.Docking.Models;
|
||||||
|
|
||||||
|
namespace Lattice.Core.Docking.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет контракт для контейнеров, содержащих коллекцию вкладок.
|
||||||
|
/// Контейнеры являются листьями дерева компоновки и непосредственно отображают содержимое.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDockContainer : IDockElement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает список вкладок, находящихся в данном контейнере.
|
||||||
|
/// </summary>
|
||||||
|
IList<IDockContent> Children { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает текущую активную (выбранную) вкладку.
|
||||||
|
/// </summary>
|
||||||
|
IDockContent? ActiveContent { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавляет контент в контейнер и делает его активным.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content">Контент для добавления.</param>
|
||||||
|
void AddContent(IDockContent content);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Удаляет контент из контейнера. Если коллекция становится пустой,
|
||||||
|
/// контейнер может быть удален из дерева макета.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content">Контент для удаления.</param>
|
||||||
|
void RemoveContent(IDockContent content);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает положение панели вкладок в интерфейсе.
|
||||||
|
/// </summary>
|
||||||
|
TabPlacement TabPlacement { get; set; }
|
||||||
|
}
|
||||||
43
Lattice.Core.Docking/Abstractions/IDockContent.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
namespace Lattice.Core.Docking.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет контракт для содержимого (вкладки), которое может быть размещено внутри контейнера.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDockContent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает уникальный идентификатор контента.
|
||||||
|
/// Используется для идентификации вкладки в системе.
|
||||||
|
/// </summary>
|
||||||
|
string Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Устанавливает идентификатор контента.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Новый идентификатор.</param>
|
||||||
|
void SetId(string id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает заголовок, отображаемый пользователю на вкладке.
|
||||||
|
/// </summary>
|
||||||
|
string Title { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает визуальный элемент для отображения в теле вкладки.
|
||||||
|
/// </summary>
|
||||||
|
object View { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает значение, указывающее, можно ли закрыть вкладку.
|
||||||
|
/// </summary>
|
||||||
|
bool CanClose { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Вызывается системой при попытке закрытия контента.
|
||||||
|
/// Позволяет выполнить дополнительные проверки или сохранить состояние.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// true, если закрытие разрешено; в противном случае false.
|
||||||
|
/// </returns>
|
||||||
|
bool OnClosing();
|
||||||
|
}
|
||||||
91
Lattice.Core.Docking/Abstractions/IDockElement.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
namespace Lattice.Core.Docking.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Базовый интерфейс для любого элемента, являющегося частью дерева компоновки.
|
||||||
|
/// Определяет общие свойства и методы для всех элементов док-системы.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Элементы док-системы образуют древовидную структуру, где каждый элемент может иметь
|
||||||
|
/// родителя и дочерние элементы. Эта иерархия используется для организации пространства
|
||||||
|
/// главного окна и плавающих окон в IDE-подобных приложениях.
|
||||||
|
/// </remarks>
|
||||||
|
public interface IDockElement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает уникальный идентификатор элемента.
|
||||||
|
/// Используется для поиска элементов, сериализации состояния и отслеживания изменений.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Строковый идентификатор, гарантированно уникальный в пределах дерева компоновки.
|
||||||
|
/// Обычно представляет собой GUID в строковом формате.
|
||||||
|
/// </value>
|
||||||
|
string Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает родительский элемент в иерархии дерева компоновки.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Родительский элемент или null, если элемент является корневым.
|
||||||
|
/// Это свойство управляется системой компоновки при добавлении или удалении элементов.
|
||||||
|
/// </value>
|
||||||
|
/// <remarks>
|
||||||
|
/// Изменение этого свойства вручную может привести к нарушению целостности дерева.
|
||||||
|
/// Для манипуляции структурой дерева следует использовать методы <see cref="DockOperations"/>.
|
||||||
|
/// </remarks>
|
||||||
|
IDockElement? Parent { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает желаемую ширину элемента.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Ширина элемента в пикселях или относительных единицах.
|
||||||
|
/// Может быть выражена как абсолютное значение (в пикселях) или как пропорция
|
||||||
|
/// (например, 0.5 для 50% доступного пространства).
|
||||||
|
/// </value>
|
||||||
|
/// <remarks>
|
||||||
|
/// Фактическая ширина элемента определяется родительским контейнером с учетом
|
||||||
|
/// минимальных размеров и соотношений разделения.
|
||||||
|
/// </remarks>
|
||||||
|
double Width { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает желаемую высоту элемента.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Высота элемента в пикселях или относительных единицах.
|
||||||
|
/// Может быть выражена как абсолютное значение (в пикселях) или как пропорция.
|
||||||
|
/// </value>
|
||||||
|
/// <remarks>
|
||||||
|
/// Фактическая высота элемента определяется родительским контейнером с учетом
|
||||||
|
/// минимальных размеров и соотношений разделения.
|
||||||
|
/// </remarks>
|
||||||
|
double Height { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает минимально допустимую ширину элемента.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Минимальная ширина элемента в пикселях, при которой элемент сохраняет
|
||||||
|
/// базовую функциональность и читаемость содержимого.
|
||||||
|
/// </value>
|
||||||
|
/// <remarks>
|
||||||
|
/// Система компоновки не позволит уменьшить элемент ниже этого значения.
|
||||||
|
/// Для групп разделения минимальная ширина вычисляется рекурсивно на основе
|
||||||
|
/// минимальных размеров дочерних элементов.
|
||||||
|
/// </remarks>
|
||||||
|
double MinWidth { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает минимально допустимую высоту элемента.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Минимальная высота элемента в пикселях, при которой элемент сохраняет
|
||||||
|
/// базовую функциональность и читаемость содержимого.
|
||||||
|
/// </value>
|
||||||
|
/// <remarks>
|
||||||
|
/// Система компоновки не позволит уменьшить элемент ниже этого значения.
|
||||||
|
/// Для групп разделения минимальная высота вычисляется рекурсивно на основе
|
||||||
|
/// минимальных размеров дочерних элементов.
|
||||||
|
/// </remarks>
|
||||||
|
double MinHeight { get; }
|
||||||
|
}
|
||||||
129
Lattice.Core.Docking/Engine/DockOperations.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
using Lattice.Core.Docking.Abstractions;
|
||||||
|
using Lattice.Core.Docking.Models;
|
||||||
|
|
||||||
|
namespace Lattice.Core.Docking.Engine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Предоставляет статические методы для манипуляции иерархией дерева компоновки.
|
||||||
|
/// Содержит чистые алгоритмы трансформации графа без зависимости от UI.
|
||||||
|
/// </summary>
|
||||||
|
public static class DockOperations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Извлекает элемент из дерева компоновки.
|
||||||
|
/// Если родительская группа остается с одним ребенком, она удаляется,
|
||||||
|
/// а оставшийся ребенок занимает её место в иерархии.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="element">Элемент для удаления из дерева.</param>
|
||||||
|
/// <param name="root">Текущий корневой элемент дерева.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Новый корневой элемент дерева после удаления и оптимизации структуры.
|
||||||
|
/// Возвращает null, если дерево становится пустым.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, когда <paramref name="root"/> равен null.
|
||||||
|
/// </exception>
|
||||||
|
public static IDockElement? Remove(IDockElement element, IDockElement root)
|
||||||
|
{
|
||||||
|
if (element == null) throw new ArgumentNullException(nameof(element));
|
||||||
|
if (root == null) throw new ArgumentNullException(nameof(root));
|
||||||
|
|
||||||
|
if (element == root) return null;
|
||||||
|
|
||||||
|
var parent = element.Parent as DockGroup;
|
||||||
|
if (parent == null) return root;
|
||||||
|
|
||||||
|
// Определяем "выжившего" соседа
|
||||||
|
var sibling = (parent.First == element) ? parent.Second : parent.First;
|
||||||
|
var grandParent = parent.Parent as DockGroup;
|
||||||
|
|
||||||
|
if (grandParent != null)
|
||||||
|
{
|
||||||
|
// Переподключаем соседа напрямую к дедушке
|
||||||
|
if (grandParent.First == parent) grandParent.First = sibling;
|
||||||
|
else grandParent.Second = sibling;
|
||||||
|
|
||||||
|
sibling.Parent = grandParent;
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если дедушки нет, сосед становится новым корнем
|
||||||
|
sibling.Parent = null;
|
||||||
|
return sibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Вставляет элемент в дерево компоновки относительно целевого элемента.
|
||||||
|
/// Создает новую группу разделения или объединяет контент в зависимости от позиции.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">Целевой элемент, относительно которого выполняется вставка.</param>
|
||||||
|
/// <param name="source">Вставляемый элемент.</param>
|
||||||
|
/// <param name="pos">Позиция вставки относительно целевого элемента.</param>
|
||||||
|
/// <param name="root">Текущий корневой элемент дерева.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Новый корневой элемент дерева после вставки и оптимизации структуры.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, когда <paramref name="target"/>, <paramref name="source"/>
|
||||||
|
/// или <paramref name="root"/> равны null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// Выбрасывается, когда <paramref name="pos"/> имеет недопустимое значение.
|
||||||
|
/// </exception>
|
||||||
|
public static IDockElement Insert(IDockElement target, IDockElement source,
|
||||||
|
DockPosition pos, IDockElement root)
|
||||||
|
{
|
||||||
|
if (target == null) throw new ArgumentNullException(nameof(target));
|
||||||
|
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||||
|
if (root == null) throw new ArgumentNullException(nameof(root));
|
||||||
|
|
||||||
|
// Случай 1: Объединение вкладок в центре
|
||||||
|
if (pos == DockPosition.Center)
|
||||||
|
{
|
||||||
|
if (target is IDockContainer targetContainer && source is IDockContainer sourceContainer)
|
||||||
|
{
|
||||||
|
// Переносим все вкладки из источника в целевой контейнер
|
||||||
|
var items = new List<IDockContent>(sourceContainer.Children);
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
sourceContainer.RemoveContent(item);
|
||||||
|
targetContainer.AddContent(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Случай 2: Разделение (Split)
|
||||||
|
var direction = (pos == DockPosition.Left || pos == DockPosition.Right)
|
||||||
|
? SplitDirection.Horizontal : SplitDirection.Vertical;
|
||||||
|
|
||||||
|
bool sourceIsFirst = (pos == DockPosition.Left || pos == DockPosition.Top);
|
||||||
|
|
||||||
|
var oldParent = target.Parent;
|
||||||
|
|
||||||
|
// Создаем новую группу. Источник и цель делят пространство 50/50
|
||||||
|
var newGroup = sourceIsFirst
|
||||||
|
? new DockGroup(source, target, direction) { SplitRatio = 0.5 }
|
||||||
|
: new DockGroup(target, source, direction) { SplitRatio = 0.5 };
|
||||||
|
|
||||||
|
if (oldParent is DockGroup gp)
|
||||||
|
{
|
||||||
|
if (gp.First == target) gp.First = newGroup;
|
||||||
|
else gp.Second = newGroup;
|
||||||
|
newGroup.Parent = gp;
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если target был корнем, новая группа становится новым корнем
|
||||||
|
if (target == root)
|
||||||
|
{
|
||||||
|
newGroup.Parent = null;
|
||||||
|
return newGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Эта точка недостижима при правильном использовании,
|
||||||
|
// но добавляем для безопасности
|
||||||
|
newGroup.Parent = null;
|
||||||
|
return newGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
345
Lattice.Core.Docking/Engine/LayoutManager.cs
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
using Lattice.Core.Docking.Abstractions;
|
||||||
|
using Lattice.Core.Docking.Models;
|
||||||
|
using Lattice.Core.Docking.Services;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Lattice.UI.Docking.WinUI")]
|
||||||
|
|
||||||
|
namespace Lattice.Core.Docking.Engine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Центральный менеджер макета, управляющий всей структурой док-системы.
|
||||||
|
/// Координирует дерево компоновки, плавающие окна, автоскрываемые панели
|
||||||
|
/// и предоставляет API для манипуляции макетом. Использует кэширование
|
||||||
|
/// для оптимизации поиска элементов по идентификатору.
|
||||||
|
/// </summary>
|
||||||
|
public class LayoutManager
|
||||||
|
{
|
||||||
|
private readonly ObservableCollection<AutoHidePanel> _autoHidePanels = new();
|
||||||
|
private readonly Dictionary<string, IDockElement> _elementCache = new();
|
||||||
|
private IDockElement? _root;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает корневой элемент дерева компоновки главного окна.
|
||||||
|
/// При изменении значения генерируется событие <see cref="LayoutUpdated"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IDockElement? Root
|
||||||
|
{
|
||||||
|
get => _root;
|
||||||
|
internal set
|
||||||
|
{
|
||||||
|
if (_root != value)
|
||||||
|
{
|
||||||
|
_root = value;
|
||||||
|
LayoutUpdated?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает список активных плавающих окон.
|
||||||
|
/// </summary>
|
||||||
|
public List<DockWindow> FloatingWindows { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает коллекцию автоскрываемых панелей.
|
||||||
|
/// </summary>
|
||||||
|
public ReadOnlyObservableCollection<AutoHidePanel> AutoHidePanels { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает реестр типов контента.
|
||||||
|
/// </summary>
|
||||||
|
public ContentRegistry? ContentRegistry { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Происходит при изменении структуры дерева компоновки.
|
||||||
|
/// </summary>
|
||||||
|
public event Action? LayoutUpdated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Происходит при изменении коллекции автоскрываемых панелей.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler? AutoHidePanelsChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый экземпляр класса <see cref="LayoutManager"/>.
|
||||||
|
/// </summary>
|
||||||
|
public LayoutManager()
|
||||||
|
{
|
||||||
|
AutoHidePanels = new ReadOnlyObservableCollection<AutoHidePanel>(_autoHidePanels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавляет автоскрываемую панель с указанным содержимым к заданной стороне окна.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content">Содержимое панели.</param>
|
||||||
|
/// <param name="side">Сторона окна для прикрепления панели.</param>
|
||||||
|
/// <returns>Созданная автоскрываемая панель.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Выбрасывается, когда <paramref name="content"/> равен null.</exception>
|
||||||
|
public AutoHidePanel AddAutoHidePanel(IDockContent content, DockSide side)
|
||||||
|
{
|
||||||
|
if (content == null) throw new ArgumentNullException(nameof(content));
|
||||||
|
|
||||||
|
var panel = new AutoHidePanel(content, side);
|
||||||
|
_autoHidePanels.Add(panel);
|
||||||
|
AutoHidePanelsChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Удаляет автоскрываемую панель из коллекции.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="panel">Панель для удаления.</param>
|
||||||
|
/// <returns>true, если панель была успешно удалена; в противном случае false.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Выбрасывается, когда <paramref name="panel"/> равен null.</exception>
|
||||||
|
public bool RemoveAutoHidePanel(AutoHidePanel panel)
|
||||||
|
{
|
||||||
|
if (panel == null) throw new ArgumentNullException(nameof(panel));
|
||||||
|
|
||||||
|
if (_autoHidePanels.Remove(panel))
|
||||||
|
{
|
||||||
|
AutoHidePanelsChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создает документ указанного типа контента с заданным идентификатором.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentTypeId">Идентификатор типа контента.</param>
|
||||||
|
/// <param name="id">Уникальный идентификатор документа.</param>
|
||||||
|
/// <returns>Созданный контент или null, если ContentRegistry не установлен или тип контента не зарегистрирован.</returns>
|
||||||
|
public IDockContent? CreateDocument(string contentTypeId, string id)
|
||||||
|
{
|
||||||
|
if (ContentRegistry == null || !ContentRegistry.IsRegistered(contentTypeId))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return ContentRegistry.CreateContent(contentTypeId, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Выполняет перемещение элемента в макете относительно целевого элемента.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">Перемещаемый элемент.</param>
|
||||||
|
/// <param name="target">Целевой элемент, относительно которого выполняется перемещение.</param>
|
||||||
|
/// <param name="position">Позиция перемещения относительно цели.</param>
|
||||||
|
/// <param name="asDocument">Если true, контент будет добавлен как документ в центральную область.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Выбрасывается, когда <paramref name="source"/> равен null.</exception>
|
||||||
|
public void Move(IDockElement source, IDockElement? target,
|
||||||
|
DockPosition position, bool asDocument = false)
|
||||||
|
{
|
||||||
|
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||||
|
if (source == target) return;
|
||||||
|
|
||||||
|
// 1. Удаляем источник из текущего местоположения
|
||||||
|
bool sourceRemoved = false;
|
||||||
|
|
||||||
|
if (Root != null && IsDescendantOf(source, Root))
|
||||||
|
{
|
||||||
|
Root = DockOperations.Remove(source, Root);
|
||||||
|
sourceRemoved = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sourceRemoved = RemoveFromFloatingWindows(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sourceRemoved)
|
||||||
|
{
|
||||||
|
// Проверяем автоскрываемые панели
|
||||||
|
var autoHidePanel = _autoHidePanels.FirstOrDefault(p => p.Content == source);
|
||||||
|
if (autoHidePanel != null)
|
||||||
|
{
|
||||||
|
_autoHidePanels.Remove(autoHidePanel);
|
||||||
|
sourceRemoved = true;
|
||||||
|
AutoHidePanelsChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sourceRemoved) return;
|
||||||
|
|
||||||
|
// Обновляем кэш - удаляем перемещенный элемент
|
||||||
|
_elementCache.Remove(source.Id);
|
||||||
|
|
||||||
|
// 2. Вставляем в новое место
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
// Создаем новое плавающее окно
|
||||||
|
FloatingWindows.Add(new DockWindow { Root = source });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Root != null && IsDescendantOf(target, Root))
|
||||||
|
{
|
||||||
|
Root = DockOperations.Insert(target, source, position, Root);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InsertIntoFloatingWindow(target, source, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем кэш для вставленного элемента
|
||||||
|
_elementCache[source.Id] = source;
|
||||||
|
|
||||||
|
LayoutUpdated?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Удаляет элемент из всех плавающих окон.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="element">Элемент для удаления.</param>
|
||||||
|
/// <returns>true, если элемент был найден и удален; в противном случае false.</returns>
|
||||||
|
private bool RemoveFromFloatingWindows(IDockElement element)
|
||||||
|
{
|
||||||
|
foreach (var win in FloatingWindows.ToArray())
|
||||||
|
{
|
||||||
|
if (win.Root != null && IsDescendantOf(element, win.Root))
|
||||||
|
{
|
||||||
|
win.Root = DockOperations.Remove(element, win.Root);
|
||||||
|
if (win.Root == null)
|
||||||
|
FloatingWindows.Remove(win);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Вставляет элемент в плавающее окно, содержащее целевой элемент.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">Целевой элемент в плавающем окне.</param>
|
||||||
|
/// <param name="source">Вставляемый элемент.</param>
|
||||||
|
/// <param name="position">Позиция вставки.</param>
|
||||||
|
private void InsertIntoFloatingWindow(IDockElement target, IDockElement source,
|
||||||
|
DockPosition position)
|
||||||
|
{
|
||||||
|
foreach (var win in FloatingWindows)
|
||||||
|
{
|
||||||
|
if (win.Root != null && IsDescendantOf(target, win.Root))
|
||||||
|
{
|
||||||
|
win.Root = DockOperations.Insert(target, source, position, win.Root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет, является ли элемент потомком указанного предка.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="element">Проверяемый элемент.</param>
|
||||||
|
/// <param name="ancestor">Предполагаемый предок.</param>
|
||||||
|
/// <returns>true, если элемент является потомком предка; в противном случае false.</returns>
|
||||||
|
private bool IsDescendantOf(IDockElement element, IDockElement ancestor)
|
||||||
|
{
|
||||||
|
var current = element.Parent;
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
if (current == ancestor)
|
||||||
|
return true;
|
||||||
|
current = current.Parent;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Находит элемент по его идентификатору во всех окнах (главном и плавающих).
|
||||||
|
/// Использует кэширование для оптимизации повторных поисков.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Идентификатор элемента для поиска.</param>
|
||||||
|
/// <returns>Найденный элемент или null, если элемент с таким идентификатором не найден.</returns>
|
||||||
|
public IDockElement? FindById(string id)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(id)) return null;
|
||||||
|
|
||||||
|
// Проверка кэша
|
||||||
|
if (_elementCache.TryGetValue(id, out var cached))
|
||||||
|
return cached;
|
||||||
|
|
||||||
|
// Поиск в основном дереве
|
||||||
|
var found = FindRecursive(Root, id);
|
||||||
|
if (found != null)
|
||||||
|
{
|
||||||
|
_elementCache[id] = found;
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Поиск в плавающих окнах
|
||||||
|
foreach (var win in FloatingWindows)
|
||||||
|
{
|
||||||
|
found = FindRecursive(win.Root, id);
|
||||||
|
if (found != null)
|
||||||
|
{
|
||||||
|
_elementCache[id] = found;
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Рекурсивно ищет элемент по идентификатору в поддереве.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">Корневой узел поддерева для поиска.</param>
|
||||||
|
/// <param name="id">Идентификатор элемента для поиска.</param>
|
||||||
|
/// <returns>Найденный элемент или null, если элемент не найден.</returns>
|
||||||
|
private IDockElement? FindRecursive(IDockElement? node, string id)
|
||||||
|
{
|
||||||
|
if (node == null) return null;
|
||||||
|
if (node.Id == id) return node;
|
||||||
|
|
||||||
|
if (node is DockGroup g)
|
||||||
|
return FindRecursive(g.First, id) ?? FindRecursive(g.Second, id);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сбрасывает макет к состоянию по умолчанию.
|
||||||
|
/// Очищает корневой элемент, плавающие окна, автоскрываемые панели и кэш.
|
||||||
|
/// </summary>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
Root = null;
|
||||||
|
FloatingWindows.Clear();
|
||||||
|
_autoHidePanels.Clear();
|
||||||
|
_elementCache.Clear();
|
||||||
|
LayoutUpdated?.Invoke();
|
||||||
|
AutoHidePanelsChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Находит элемент по идентификатору в дереве компоновки.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Идентификатор элемента для поиска.</param>
|
||||||
|
/// <returns>Найденный элемент или null, если элемент с таким идентификатором не найден.</returns>
|
||||||
|
public IDockElement? FindElementById(string id)
|
||||||
|
{
|
||||||
|
return FindElementByIdRecursive(Root, id) ??
|
||||||
|
FloatingWindows.Select(w => FindElementByIdRecursive(w.Root, id))
|
||||||
|
.FirstOrDefault(result => result != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Рекурсивно ищет элемент по идентификатору в поддереве.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="element">Корневой элемент поддерева для поиска.</param>
|
||||||
|
/// <param name="id">Идентификатор элемента для поиска.</param>
|
||||||
|
/// <returns>Найденный элемент или null, если элемент не найден.</returns>
|
||||||
|
private IDockElement? FindElementByIdRecursive(IDockElement? element, string id)
|
||||||
|
{
|
||||||
|
if (element == null) return null;
|
||||||
|
if (element.Id == id) return element;
|
||||||
|
|
||||||
|
if (element is DockGroup group)
|
||||||
|
{
|
||||||
|
return FindElementByIdRecursive(group.First, id) ??
|
||||||
|
FindElementByIdRecursive(group.Second, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Lattice.Core.Docking/Lattice.Core.Docking.csproj
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Lattice.Core.Geometry\Lattice.Core.Geometry.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
83
Lattice.Core.Docking/Models/AutoHidePanel.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using Lattice.Core.Docking.Abstractions;
|
||||||
|
|
||||||
|
namespace Lattice.Core.Docking.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Представляет автоскрываемую панель, которая может быть прикреплена к одной из сторон окна.
|
||||||
|
/// Автоскрываемые панели скрываются, оставляя видимой только заголовок, и разворачиваются при наведении курсора или клике.
|
||||||
|
/// </summary>
|
||||||
|
public class AutoHidePanel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый экземпляр класса <see cref="AutoHidePanel"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content">Содержимое панели.</param>
|
||||||
|
/// <param name="side">Сторона окна для прикрепления панели.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Выбрасывается, когда <paramref name="content"/> равен null.</exception>
|
||||||
|
public AutoHidePanel(IDockContent content, DockSide side)
|
||||||
|
{
|
||||||
|
Content = content ?? throw new ArgumentNullException(nameof(content));
|
||||||
|
Side = side;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает уникальный идентификатор панели.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает содержимое панели.
|
||||||
|
/// </summary>
|
||||||
|
public IDockContent Content { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает сторону окна, к которой прикреплена панель.
|
||||||
|
/// </summary>
|
||||||
|
public DockSide Side { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает размер панели в пикселях.
|
||||||
|
/// Для левой/правой сторон - ширина, для верхней/нижней - высота.
|
||||||
|
/// </summary>
|
||||||
|
public double Size { get; set; } = 300;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает значение, указывающее, видима ли панель.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsVisible { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает смещение для анимации выезда/заезда панели.
|
||||||
|
/// Значение от 0.0 (полностью скрыта) до 1.0 (полностью развернута).
|
||||||
|
/// </summary>
|
||||||
|
public double SlideOffset { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает заголовок панели, взятый из содержимого.
|
||||||
|
/// </summary>
|
||||||
|
public string Title => Content?.Title ?? "Auto-hide Panel";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Переключает видимость панели.
|
||||||
|
/// </summary>
|
||||||
|
public void Toggle()
|
||||||
|
{
|
||||||
|
IsVisible = !IsVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Показывает панель.
|
||||||
|
/// </summary>
|
||||||
|
public void Show()
|
||||||
|
{
|
||||||
|
IsVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Скрывает панель.
|
||||||
|
/// </summary>
|
||||||
|
public void Hide()
|
||||||
|
{
|
||||||
|
IsVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
133
Lattice.Core.Docking/Models/DockGroup.cs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
using Lattice.Core.Docking.Abstractions;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Lattice.Core.Docking.Models;
|
||||||
|
|
||||||
|
public class DockGroup : IDockElement, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private IDockElement _first;
|
||||||
|
private IDockElement _second;
|
||||||
|
private SplitDirection _orientation;
|
||||||
|
private double _splitRatio = 0.5;
|
||||||
|
private IDockElement? _parent;
|
||||||
|
private double _width;
|
||||||
|
private double _height;
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
public DockGroup(IDockElement first, IDockElement second, SplitDirection orientation)
|
||||||
|
{
|
||||||
|
First = first ?? throw new ArgumentNullException(nameof(first));
|
||||||
|
Second = second ?? throw new ArgumentNullException(nameof(second));
|
||||||
|
Orientation = orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Id { get; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
public IDockElement? Parent
|
||||||
|
{
|
||||||
|
get => _parent;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_parent != value)
|
||||||
|
{
|
||||||
|
_parent = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDockElement First
|
||||||
|
{
|
||||||
|
get => _first;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_first != value)
|
||||||
|
{
|
||||||
|
_first = value ?? throw new ArgumentNullException(nameof(value));
|
||||||
|
_first.Parent = this;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDockElement Second
|
||||||
|
{
|
||||||
|
get => _second;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_second != value)
|
||||||
|
{
|
||||||
|
_second = value ?? throw new ArgumentNullException(nameof(value));
|
||||||
|
_second.Parent = this;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SplitDirection Orientation
|
||||||
|
{
|
||||||
|
get => _orientation;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_orientation != value)
|
||||||
|
{
|
||||||
|
_orientation = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double SplitRatio
|
||||||
|
{
|
||||||
|
get => _splitRatio;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Math.Abs(_splitRatio - value) > 0.001)
|
||||||
|
{
|
||||||
|
_splitRatio = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double Width
|
||||||
|
{
|
||||||
|
get => _width;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Math.Abs(_width - value) > 0.001)
|
||||||
|
{
|
||||||
|
_width = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double Height
|
||||||
|
{
|
||||||
|
get => _height;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Math.Abs(_height - value) > 0.001)
|
||||||
|
{
|
||||||
|
_height = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double MinWidth => Orientation == SplitDirection.Horizontal
|
||||||
|
? First.MinWidth + Second.MinWidth
|
||||||
|
: Math.Max(First.MinWidth, Second.MinWidth);
|
||||||
|
|
||||||
|
public double MinHeight => Orientation == SplitDirection.Vertical
|
||||||
|
? First.MinHeight + Second.MinHeight
|
||||||
|
: Math.Max(First.MinHeight, Second.MinHeight);
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
131
Lattice.Core.Docking/Models/DockLeaf.cs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
using Lattice.Core.Docking.Abstractions;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Lattice.Core.Docking.Models;
|
||||||
|
|
||||||
|
public class DockLeaf : IDockContainer, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private readonly ObservableCollection<IDockContent> _items = new();
|
||||||
|
private IDockContent? _activeContent;
|
||||||
|
private IDockElement? _parent;
|
||||||
|
private double _width;
|
||||||
|
private double _height;
|
||||||
|
private TabPlacement _tabPlacement = TabPlacement.Top;
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
public DockLeaf()
|
||||||
|
{
|
||||||
|
_items.CollectionChanged += (s, e) => OnPropertyChanged(nameof(Children));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Id { get; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
public IDockElement? Parent
|
||||||
|
{
|
||||||
|
get => _parent;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_parent != value)
|
||||||
|
{
|
||||||
|
_parent = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IList<IDockContent> Children => _items;
|
||||||
|
|
||||||
|
public IDockContent? ActiveContent
|
||||||
|
{
|
||||||
|
get => _activeContent;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_activeContent != value)
|
||||||
|
{
|
||||||
|
_activeContent = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double Width
|
||||||
|
{
|
||||||
|
get => _width;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Math.Abs(_width - value) > 0.001)
|
||||||
|
{
|
||||||
|
_width = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double Height
|
||||||
|
{
|
||||||
|
get => _height;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Math.Abs(_height - value) > 0.001)
|
||||||
|
{
|
||||||
|
_height = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double MinWidth { get; set; } = 100;
|
||||||
|
public double MinHeight { get; set; } = 100;
|
||||||
|
|
||||||
|
public TabPlacement TabPlacement
|
||||||
|
{
|
||||||
|
get => _tabPlacement;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_tabPlacement != value)
|
||||||
|
{
|
||||||
|
_tabPlacement = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddContent(IDockContent content)
|
||||||
|
{
|
||||||
|
if (content == null)
|
||||||
|
throw new ArgumentNullException(nameof(content));
|
||||||
|
|
||||||
|
if (!_items.Contains(content))
|
||||||
|
{
|
||||||
|
_items.Add(content);
|
||||||
|
}
|
||||||
|
ActiveContent = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveContent(IDockContent content)
|
||||||
|
{
|
||||||
|
if (content == null)
|
||||||
|
throw new ArgumentNullException(nameof(content));
|
||||||
|
|
||||||
|
int index = _items.IndexOf(content);
|
||||||
|
if (index == -1) return;
|
||||||
|
|
||||||
|
_items.RemoveAt(index);
|
||||||
|
|
||||||
|
if (ActiveContent == content)
|
||||||
|
{
|
||||||
|
if (_items.Count > 0)
|
||||||
|
ActiveContent = _items[Math.Min(index, _items.Count - 1)];
|
||||||
|
else
|
||||||
|
ActiveContent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Lattice.Core.Docking/Models/DockPosition.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
namespace Lattice.Core.Docking.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет позицию вставки элемента относительно целевого элемента.
|
||||||
|
/// Используется при операциях перемещения и вставки элементов в дерево компоновки.
|
||||||
|
/// </summary>
|
||||||
|
public enum DockPosition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Слева от целевого элемента.
|
||||||
|
/// </summary>
|
||||||
|
Left,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Справа от целевого элемента.
|
||||||
|
/// </summary>
|
||||||
|
Right,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сверху от целевого элемента.
|
||||||
|
/// </summary>
|
||||||
|
Top,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Снизу от целевого элемента.
|
||||||
|
/// </summary>
|
||||||
|
Bottom,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// В центре целевого элемента (для объединения вкладок).
|
||||||
|
/// </summary>
|
||||||
|
Center,
|
||||||
|
}
|
||||||
27
Lattice.Core.Docking/Models/DockSide.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
namespace Lattice.Core.Docking.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет стороны окна, к которым могут быть прикреплены автоскрываемые панели.
|
||||||
|
/// </summary>
|
||||||
|
public enum DockSide
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Левая сторона окна.
|
||||||
|
/// </summary>
|
||||||
|
Left,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Правая сторона окна.
|
||||||
|
/// </summary>
|
||||||
|
Right,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Верхняя сторона окна.
|
||||||
|
/// </summary>
|
||||||
|
Top,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Нижняя сторона окна.
|
||||||
|
/// </summary>
|
||||||
|
Bottom
|
||||||
|
}
|
||||||
68
Lattice.Core.Docking/Models/DockWindow.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using Lattice.Core.Docking.Abstractions;
|
||||||
|
|
||||||
|
namespace Lattice.Core.Docking.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Представляет плавающее окно в системе докинга.
|
||||||
|
/// Плавающие окна могут перемещаться по экрану независимо от главного окна.
|
||||||
|
/// </summary>
|
||||||
|
public class DockWindow
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает уникальный идентификатор окна.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Строковый идентификатор, сгенерированный с помощью GUID.
|
||||||
|
/// Используется для сохранения позиции и размера окна в конфигурации.
|
||||||
|
/// </value>
|
||||||
|
public string Id { get; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает корневой элемент макета внутри данного окна.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Корневой элемент дерева компоновки плавающего окна.
|
||||||
|
/// </value>
|
||||||
|
public IDockElement? Root { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает позицию X окна на экране.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Координата X левого верхнего угла окна в пикселях.
|
||||||
|
/// </value>
|
||||||
|
public double X { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает позицию Y окна на экране.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Координата Y левого верхнего угла окна в пикселях.
|
||||||
|
/// </value>
|
||||||
|
public double Y { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает ширину окна.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Ширина окна в пикселях. Значение по умолчанию: 800.
|
||||||
|
/// </value>
|
||||||
|
public double Width { get; set; } = 800;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает высоту окна.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Высота окна в пикселях. Значение по умолчанию: 600.
|
||||||
|
/// </value>
|
||||||
|
public double Height { get; set; } = 600;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает заголовок окна.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Текст заголовка окна. Обычно берется из активного контента.
|
||||||
|
/// Значение по умолчанию: "Lattice Tool Window".
|
||||||
|
/// </value>
|
||||||
|
public string Title { get; set; } = "Lattice Tool Window";
|
||||||
|
}
|
||||||
17
Lattice.Core.Docking/Models/SplitDirection.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Lattice.Core.Docking.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет направление разделения пространства внутри группы.
|
||||||
|
/// </summary>
|
||||||
|
public enum SplitDirection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Разделение по горизонтали (создает левую и правую области).
|
||||||
|
/// </summary>
|
||||||
|
Horizontal,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Разделение по вертикали (создает верхнюю и нижнюю области).
|
||||||
|
/// </summary>
|
||||||
|
Vertical
|
||||||
|
}
|
||||||
27
Lattice.Core.Docking/Models/TabPlacement.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
namespace Lattice.Core.Docking.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет положение полосы вкладок в контейнере.
|
||||||
|
/// </summary>
|
||||||
|
public enum TabPlacement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Вкладки располагаются сверху.
|
||||||
|
/// </summary>
|
||||||
|
Top,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Вкладки располагаются снизу.
|
||||||
|
/// </summary>
|
||||||
|
Bottom,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Вкладки располагаются слева.
|
||||||
|
/// </summary>
|
||||||
|
Left,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Вкладки располагаются справа.
|
||||||
|
/// </summary>
|
||||||
|
Right,
|
||||||
|
}
|
||||||
40
Lattice.Core.Docking/Serialization/ILayoutSerializer.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
namespace Lattice.Core.Docking.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет контракт для сериализации и десериализации состояния макета док-системы.
|
||||||
|
/// Позволяет сохранять и восстанавливать расположение панелей, окон и их состояние.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Эта абстракция позволяет реализовать различные форматы сериализации (JSON, XML, бинарный)
|
||||||
|
/// и различные хранилища (файлы, базы данных, облако) без изменения основной логики док-системы.
|
||||||
|
/// </remarks>
|
||||||
|
public interface ILayoutSerializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Сериализует состояние менеджера макета в строку.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manager">Менеджер макета для сериализации.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Строковое представление состояния макета.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, когда <paramref name="manager"/> равен null.
|
||||||
|
/// </exception>
|
||||||
|
string Serialize(Engine.LayoutManager manager);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Десериализует состояние макета из строки и восстанавливает его в менеджере.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manager">Менеджер макета для восстановления состояния.</param>
|
||||||
|
/// <param name="serializedLayout">Сериализованное состояние макета.</param>
|
||||||
|
/// <param name="contentResolver">
|
||||||
|
/// Функция разрешения контента по идентификатору, используемая для восстановления
|
||||||
|
/// ссылок на контент в десериализованном состоянии.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, когда <paramref name="manager"/> или <paramref name="serializedLayout"/>
|
||||||
|
/// равны null.
|
||||||
|
/// </exception>
|
||||||
|
void Deserialize(Engine.LayoutManager manager, string serializedLayout,
|
||||||
|
Func<string, Abstractions.IDockContent?> contentResolver);
|
||||||
|
}
|
||||||
24
Lattice.Core.Docking/Serialization/ISerializableLayout.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace Lattice.Core.Docking.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет контракт для объектов, которые могут предоставлять состояние для сериализации.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISerializableLayout
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает состояние объекта для сериализации.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// Объект состояния, готовый к сериализации.
|
||||||
|
/// </returns>
|
||||||
|
object GetSerializableState();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Восстанавливает состояние объекта из десериализованного объекта.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">Десериализованное состояние.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, когда <paramref name="state"/> равен null.
|
||||||
|
/// </exception>
|
||||||
|
void RestoreFromState(object state);
|
||||||
|
}
|
||||||
235
Lattice.Core.Docking/Services/ContentRegistry.cs
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
namespace Lattice.Core.Docking.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Реестр типов содержимого, который позволяет создавать экземпляры контента по типу.
|
||||||
|
/// Этот сервис является центральным для динамического создания панелей инструментов и документов.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Реализует шаблон "Фабрика" для создания экземпляров <see cref="Abstractions.IDockContent"/>.
|
||||||
|
/// Позволяет регистрировать фабричные методы для различных типов контента, что обеспечивает
|
||||||
|
/// позднее связывание и возможность плагинной архитектуры.
|
||||||
|
/// </remarks>
|
||||||
|
public class ContentRegistry
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, ContentDescriptor> _contentTypes = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Регистрирует фабричный метод для создания контента указанного типа.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">
|
||||||
|
/// Тип контента, реализующий <see cref="Abstractions.IDockContent"/>.
|
||||||
|
/// </typeparam>
|
||||||
|
/// <param name="contentTypeId">Уникальный идентификатор типа контента.</param>
|
||||||
|
/// <param name="factory">Фабричный метод для создания экземпляров контента.</param>
|
||||||
|
/// <param name="metadata">Метаданные типа контента (опционально).</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, если <paramref name="contentTypeId"/> или <paramref name="factory"/>
|
||||||
|
/// равны null.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="ArgumentException">
|
||||||
|
/// Выбрасывается, если <paramref name="contentTypeId"/> уже зарегистрирован.
|
||||||
|
/// </exception>
|
||||||
|
public void Register<T>(string contentTypeId, Func<T> factory, ContentMetadata? metadata = null)
|
||||||
|
where T : Abstractions.IDockContent
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(contentTypeId))
|
||||||
|
throw new ArgumentNullException(nameof(contentTypeId));
|
||||||
|
if (factory == null)
|
||||||
|
throw new ArgumentNullException(nameof(factory));
|
||||||
|
|
||||||
|
// Дополнительная проверка на пустую строку
|
||||||
|
if (string.IsNullOrEmpty(contentTypeId.Trim()))
|
||||||
|
throw new ArgumentException("Идентификатор типа контента не может быть пустой строкой.", nameof(contentTypeId));
|
||||||
|
|
||||||
|
if (_contentTypes.ContainsKey(contentTypeId))
|
||||||
|
throw new ArgumentException($"Тип контента '{contentTypeId}' уже зарегистрирован.");
|
||||||
|
|
||||||
|
_contentTypes[contentTypeId] = new ContentDescriptor(
|
||||||
|
typeof(T),
|
||||||
|
() => factory(),
|
||||||
|
metadata ?? new ContentMetadata(contentTypeId, typeof(T).Name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создает новый экземпляр контента указанного типа с заданным идентификатором.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentTypeId">Идентификатор типа контента.</param>
|
||||||
|
/// <param name="id">Уникальный идентификатор для создаваемого экземпляра контента.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Новый экземпляр контента.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, если <paramref name="contentTypeId"/> равен null или пустой строке.
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="KeyNotFoundException">
|
||||||
|
/// Выбрасывается, если тип контента не зарегистрирован.
|
||||||
|
/// </exception>
|
||||||
|
public Abstractions.IDockContent CreateContent(string contentTypeId, string id)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(contentTypeId))
|
||||||
|
throw new ArgumentNullException(nameof(contentTypeId));
|
||||||
|
|
||||||
|
if (!_contentTypes.TryGetValue(contentTypeId, out var descriptor))
|
||||||
|
throw new KeyNotFoundException($"Тип контента '{contentTypeId}' не зарегистрирован.");
|
||||||
|
|
||||||
|
var content = descriptor.Factory();
|
||||||
|
content.SetId(id);
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает метаданные для указанного типа контента.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentTypeId">Идентификатор типа контента.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// Метаданные типа контента или null, если тип не найден.
|
||||||
|
/// </returns>
|
||||||
|
public ContentMetadata? GetMetadata(string contentTypeId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(contentTypeId))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return _contentTypes.TryGetValue(contentTypeId, out var descriptor)
|
||||||
|
? descriptor.Metadata
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает все зарегистрированные типы контента.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// Коллекция идентификаторов зарегистрированных типов контента.
|
||||||
|
/// </returns>
|
||||||
|
public IEnumerable<string> GetRegisteredTypes() => _contentTypes.Keys;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проверяет, зарегистрирован ли указанный тип контента.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentTypeId">Идентификатор типа контента.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// true, если тип контента зарегистрирован; в противном случае false.
|
||||||
|
/// </returns>
|
||||||
|
public bool IsRegistered(string contentTypeId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(contentTypeId))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _contentTypes.ContainsKey(contentTypeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Представляет дескриптор типа контента, содержащий информацию о фабричном методе и метаданных.
|
||||||
|
/// </summary>
|
||||||
|
private class ContentDescriptor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает тип контента.
|
||||||
|
/// </summary>
|
||||||
|
public Type ContentType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает фабричный метод для создания экземпляров контента.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Abstractions.IDockContent> Factory { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает метаданные типа контента.
|
||||||
|
/// </summary>
|
||||||
|
public ContentMetadata Metadata { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый экземпляр класса <see cref="ContentDescriptor"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentType">Тип контента.</param>
|
||||||
|
/// <param name="factory">Фабричный метод.</param>
|
||||||
|
/// <param name="metadata">Метаданные.</param>
|
||||||
|
public ContentDescriptor(Type contentType, Func<Abstractions.IDockContent> factory,
|
||||||
|
ContentMetadata metadata)
|
||||||
|
{
|
||||||
|
ContentType = contentType;
|
||||||
|
Factory = factory;
|
||||||
|
Metadata = metadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Представляет метаданные типа контента, предоставляющие дополнительную информацию для отображения в UI.
|
||||||
|
/// </summary>
|
||||||
|
public class ContentMetadata
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает идентификатор типа контента.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Уникальный строковый идентификатор типа контента.
|
||||||
|
/// </value>
|
||||||
|
public string ContentTypeId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает отображаемое имя типа контента.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Имя типа контента, отображаемое пользователю.
|
||||||
|
/// </value>
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает описание типа контента.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Текстовое описание функциональности контента.
|
||||||
|
/// </value>
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает имя ресурса для иконки типа контента.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Имя ресурса иконки или null, если иконка не определена.
|
||||||
|
/// </value>
|
||||||
|
public string? IconResource { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает значение, указывающее, является ли контент документом
|
||||||
|
/// (а не инструментальной панелью).
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// true, если контент является документом; в противном случае false.
|
||||||
|
/// </value>
|
||||||
|
public bool IsDocument { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает ширину контента по умолчанию.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Ширина контента в пикселях. Значение по умолчанию: 300.
|
||||||
|
/// </value>
|
||||||
|
public double DefaultWidth { get; set; } = 300;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает или задает высоту контента по умолчанию.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// Высота контента в пикселях. Значение по умолчанию: 200.
|
||||||
|
/// </value>
|
||||||
|
public double DefaultHeight { get; set; } = 200;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый экземпляр класса <see cref="ContentMetadata"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentTypeId">Идентификатор типа контента.</param>
|
||||||
|
/// <param name="displayName">Отображаемое имя типа контента.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, если <paramref name="contentTypeId"/> или <paramref name="displayName"/>
|
||||||
|
/// равны null.
|
||||||
|
/// </exception>
|
||||||
|
public ContentMetadata(string contentTypeId, string displayName)
|
||||||
|
{
|
||||||
|
ContentTypeId = contentTypeId ?? throw new ArgumentNullException(nameof(contentTypeId));
|
||||||
|
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
|
||||||
|
Description = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Lattice.Core.Geometry/Lattice.Core.Geometry.csproj
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
79
Lattice.Core.Geometry/Point.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
namespace Lattice.Core.Geometry;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Представляет точку в двумерном пространстве с координатами X и Y.
|
||||||
|
/// Эта структура является платформонезависимой и может использоваться
|
||||||
|
/// во всех слоях системы Lattice.
|
||||||
|
/// </summary>
|
||||||
|
public struct Point : IEquatable<Point>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает точку с координатами (0, 0).
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Point Zero = new(0, 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Координата X (горизонтальная).
|
||||||
|
/// </summary>
|
||||||
|
public double X { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Координата Y (вертикальная).
|
||||||
|
/// </summary>
|
||||||
|
public double Y { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новую точку с указанными координатами.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">Координата X.</param>
|
||||||
|
/// <param name="y">Координата Y.</param>
|
||||||
|
public Point(double x, double y)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создает точку из System.Drawing.Point.
|
||||||
|
/// </summary>
|
||||||
|
public static Point FromDrawingPoint(System.Drawing.Point point) =>
|
||||||
|
new(point.X, point.Y);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Преобразует точку в System.Drawing.Point.
|
||||||
|
/// </summary>
|
||||||
|
public System.Drawing.Point ToDrawingPoint() =>
|
||||||
|
new((int)X, (int)Y);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет, равна ли эта точка другой точке.
|
||||||
|
/// </summary>
|
||||||
|
public bool Equals(Point other) =>
|
||||||
|
Math.Abs(X - other.X) < double.Epsilon &&
|
||||||
|
Math.Abs(Y - other.Y) < double.Epsilon;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object? obj) =>
|
||||||
|
obj is Point point && Equals(point);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode() =>
|
||||||
|
HashCode.Combine(X, Y);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет, равны ли две точки.
|
||||||
|
/// </summary>
|
||||||
|
public static bool operator ==(Point left, Point right) =>
|
||||||
|
left.Equals(right);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет, не равны ли две точки.
|
||||||
|
/// </summary>
|
||||||
|
public static bool operator !=(Point left, Point right) =>
|
||||||
|
!left.Equals(right);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Возвращает строковое представление точки.
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString() => $"{X}, {Y}";
|
||||||
|
}
|
||||||
153
Lattice.Core.Geometry/Rect.cs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
namespace Lattice.Core.Geometry;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Представляет прямоугольник в двумерном пространстве с позицией и размерами.
|
||||||
|
/// Эта структура является платформонезависимой и может использоваться
|
||||||
|
/// во всех слоях системы Lattice.
|
||||||
|
/// </summary>
|
||||||
|
public struct Rect : IEquatable<Rect>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает пустой прямоугольник (позиция (0, 0), размеры (0, 0)).
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Rect Empty = new(0, 0, 0, 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Координата X левого верхнего угла прямоугольника.
|
||||||
|
/// </summary>
|
||||||
|
public double X { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Координата Y левого верхнего угла прямоугольника.
|
||||||
|
/// </summary>
|
||||||
|
public double Y { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ширина прямоугольника.
|
||||||
|
/// </summary>
|
||||||
|
public double Width { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Высота прямоугольника.
|
||||||
|
/// </summary>
|
||||||
|
public double Height { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает координату X правого края прямоугольника.
|
||||||
|
/// </summary>
|
||||||
|
public double Right => X + Width;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает координату Y нижнего края прямоугольника.
|
||||||
|
/// </summary>
|
||||||
|
public double Bottom => Y + Height;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает левый верхний угол прямоугольника.
|
||||||
|
/// </summary>
|
||||||
|
public Point TopLeft => new(X, Y);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает правый нижний угол прямоугольника.
|
||||||
|
/// </summary>
|
||||||
|
public Point BottomRight => new(Right, Bottom);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает центр прямоугольника.
|
||||||
|
/// </summary>
|
||||||
|
public Point Center => new(X + Width / 2, Y + Height / 2);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает площадь прямоугольника.
|
||||||
|
/// </summary>
|
||||||
|
public double Area => Width * Height;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый прямоугольник с указанными параметрами.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">Координата X.</param>
|
||||||
|
/// <param name="y">Координата Y.</param>
|
||||||
|
/// <param name="width">Ширина.</param>
|
||||||
|
/// <param name="height">Высота.</param>
|
||||||
|
public Rect(double x, double y, double width, double height)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый прямоугольник с указанной позицией и размером.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="location">Позиция прямоугольника.</param>
|
||||||
|
/// <param name="size">Размер прямоугольника.</param>
|
||||||
|
public Rect(Point location, Size size)
|
||||||
|
{
|
||||||
|
X = location.X;
|
||||||
|
Y = location.Y;
|
||||||
|
Width = size.Width;
|
||||||
|
Height = size.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создает прямоугольник из System.Drawing.Rectangle.
|
||||||
|
/// </summary>
|
||||||
|
public static Rect FromDrawingRectangle(System.Drawing.Rectangle rect) =>
|
||||||
|
new(rect.X, rect.Y, rect.Width, rect.Height);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Преобразует прямоугольник в System.Drawing.Rectangle.
|
||||||
|
/// </summary>
|
||||||
|
public System.Drawing.Rectangle ToDrawingRectangle() =>
|
||||||
|
new((int)X, (int)Y, (int)Width, (int)Height);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проверяет, содержит ли прямоугольник указанную точку.
|
||||||
|
/// </summary>
|
||||||
|
public bool Contains(Point point) =>
|
||||||
|
point.X >= X && point.X <= Right &&
|
||||||
|
point.Y >= Y && point.Y <= Bottom;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проверяет, пересекается ли этот прямоугольник с другим.
|
||||||
|
/// </summary>
|
||||||
|
public bool Intersects(Rect other) =>
|
||||||
|
X < other.Right && Right > other.X &&
|
||||||
|
Y < other.Bottom && Bottom > other.Y;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет, равен ли этот прямоугольник другому прямоугольнику.
|
||||||
|
/// </summary>
|
||||||
|
public bool Equals(Rect other) =>
|
||||||
|
Math.Abs(X - other.X) < double.Epsilon &&
|
||||||
|
Math.Abs(Y - other.Y) < double.Epsilon &&
|
||||||
|
Math.Abs(Width - other.Width) < double.Epsilon &&
|
||||||
|
Math.Abs(Height - other.Height) < double.Epsilon;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object? obj) =>
|
||||||
|
obj is Rect rect && Equals(rect);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode() =>
|
||||||
|
HashCode.Combine(X, Y, Width, Height);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет, равны ли два прямоугольника.
|
||||||
|
/// </summary>
|
||||||
|
public static bool operator ==(Rect left, Rect right) =>
|
||||||
|
left.Equals(right);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет, не равны ли два прямоугольника.
|
||||||
|
/// </summary>
|
||||||
|
public static bool operator !=(Rect left, Rect right) =>
|
||||||
|
!left.Equals(right);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Возвращает строковое представление прямоугольника.
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString() =>
|
||||||
|
$"[X={X:F2}, Y={Y:F2}, Width={Width:F2}, Height={Height:F2}]";
|
||||||
|
}
|
||||||
84
Lattice.Core.Geometry/Size.cs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
namespace Lattice.Core.Geometry;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Представляет размеры в двумерном пространстве с шириной и высотой.
|
||||||
|
/// Эта структура является платформонезависимой и может использоваться
|
||||||
|
/// во всех слоях системы Lattice.
|
||||||
|
/// </summary>
|
||||||
|
public struct Size : IEquatable<Size>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает размер с нулевой шириной и высотой.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Size Zero = new(0, 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ширина.
|
||||||
|
/// </summary>
|
||||||
|
public double Width { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Высота.
|
||||||
|
/// </summary>
|
||||||
|
public double Height { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает признак того, что размер является пустым (нулевая ширина или высота).
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEmpty => Width <= 0 || Height <= 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый размер с указанными значениями.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="width">Ширина.</param>
|
||||||
|
/// <param name="height">Высота.</param>
|
||||||
|
public Size(double width, double height)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создает размер из System.Drawing.Size.
|
||||||
|
/// </summary>
|
||||||
|
public static Size FromDrawingSize(System.Drawing.Size size) =>
|
||||||
|
new(size.Width, size.Height);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Преобразует размер в System.Drawing.Size.
|
||||||
|
/// </summary>
|
||||||
|
public System.Drawing.Size ToDrawingSize() =>
|
||||||
|
new((int)Width, (int)Height);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет, равен ли этот размер другому размеру.
|
||||||
|
/// </summary>
|
||||||
|
public bool Equals(Size other) =>
|
||||||
|
Math.Abs(Width - other.Width) < double.Epsilon &&
|
||||||
|
Math.Abs(Height - other.Height) < double.Epsilon;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Equals(object? obj) =>
|
||||||
|
obj is Size size && Equals(size);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode() =>
|
||||||
|
HashCode.Combine(Width, Height);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет, равны ли два размера.
|
||||||
|
/// </summary>
|
||||||
|
public static bool operator ==(Size left, Size right) =>
|
||||||
|
left.Equals(right);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет, не равны ли два размера.
|
||||||
|
/// </summary>
|
||||||
|
public static bool operator !=(Size left, Size right) =>
|
||||||
|
!left.Equals(right);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Возвращает строковое представление размера.
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString() => $"{Width} × {Height}";
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
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; }
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
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; }
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
/// <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()
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
namespace Lattice.Core.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Определение действия (команды), которое может быть отображено в интерфейсе.
|
|
||||||
/// </summary>
|
|
||||||
public record ActionDefinition
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Уникальный идентификатор команды.
|
|
||||||
/// </summary>
|
|
||||||
public string Id { get; init; } = Guid.NewGuid().ToString();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Текст кнопки.
|
|
||||||
/// </summary>
|
|
||||||
public string Label { get; init; } = "Action";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Группа контекста, к которой привязана кнопка (например, "CodeEditor").
|
|
||||||
/// </summary>
|
|
||||||
public string TargetContext { get; init; } = "Common";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Указывает, активна ли кнопка в данный момент.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsEnabled { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Подсказка (Tooltip).
|
|
||||||
/// </summary>
|
|
||||||
public string Tooltip { get; init; } = string.Empty;
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
namespace Lattice.Core.Models.Enums;
|
|
||||||
|
|
||||||
public enum DockDirection
|
|
||||||
{
|
|
||||||
Center,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Top,
|
|
||||||
Bottom,
|
|
||||||
Floating,
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace Lattice.Core.Models.Enums;
|
|
||||||
|
|
||||||
public enum SplitOrientation
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Элементы располагаются друг за другом по горизонтали
|
|
||||||
/// </summary>
|
|
||||||
Horizontal,
|
|
||||||
/// <summary>
|
|
||||||
/// Элементы располагаются друг за другом по вертикали
|
|
||||||
/// </summary>
|
|
||||||
Vertical,
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
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]})";
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Lattice.Core.Models
|
|
||||||
{
|
|
||||||
internal class WorkspaceSnapshot
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
# 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");
|
|
||||||
```
|
|
||||||
15
Lattice.Example.DragDrop/App.xaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<Application
|
||||||
|
x:Class="Lattice.Example.DragDrop.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:Lattice.Example.DragDrop">
|
||||||
|
|
||||||
|
<Application.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<!-- Fluent Theme Resources -->
|
||||||
|
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
33
Lattice.Example.DragDrop/App.xaml.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using Lattice.Themes;
|
||||||
|
using Lattice.Themes.Fluent;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
|
||||||
|
namespace Lattice.Example.DragDrop;
|
||||||
|
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
private Window? _window;
|
||||||
|
|
||||||
|
public App()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||||
|
{
|
||||||
|
// Регистрируем Fluent тему
|
||||||
|
var themeManager = ThemeManager.Current;
|
||||||
|
themeManager.RegisterTheme(new FluentThemePack(false)); // Light тема
|
||||||
|
themeManager.RegisterTheme(new FluentThemePack(true)); // Dark тема
|
||||||
|
|
||||||
|
// Применяем тему по умолчанию
|
||||||
|
themeManager.ApplyTheme("Fluent Dark");
|
||||||
|
|
||||||
|
// Создаем главное окно
|
||||||
|
_window = new MainWindow();
|
||||||
|
_window.Activate();
|
||||||
|
|
||||||
|
// Регистрируем окно в трекере
|
||||||
|
WindowTracker.Register(_window);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Lattice.Example.DragDrop/Assets/LockScreenLogo.scale-200.png
Normal file
|
After Width: | Height: | Size: 432 B |
BIN
Lattice.Example.DragDrop/Assets/SplashScreen.scale-200.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
Lattice.Example.DragDrop/Assets/Square150x150Logo.scale-200.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Lattice.Example.DragDrop/Assets/Square44x44Logo.scale-200.png
Normal file
|
After Width: | Height: | Size: 637 B |
|
After Width: | Height: | Size: 283 B |
BIN
Lattice.Example.DragDrop/Assets/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 456 B |
BIN
Lattice.Example.DragDrop/Assets/Wide310x150Logo.scale-200.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
64
Lattice.Example.DragDrop/Lattice.Example.DragDrop.csproj
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||||
|
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||||
|
<RootNamespace>Lattice.Example.DragDrop</RootNamespace>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<Platforms>x86;x64;ARM64</Platforms>
|
||||||
|
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||||
|
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||||
|
<UseWinUI>true</UseWinUI>
|
||||||
|
<WinUISDKReferences>false</WinUISDKReferences>
|
||||||
|
<EnableMsixTooling>true</EnableMsixTooling>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||||
|
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||||
|
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||||
|
<Content Include="Assets\Square44x44Logo.scale-200.png" />
|
||||||
|
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||||
|
<Content Include="Assets\StoreLogo.png" />
|
||||||
|
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Manifest Include="$(ApplicationManifest)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||||
|
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||||
|
package has not yet been restored.
|
||||||
|
-->
|
||||||
|
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||||
|
<ProjectCapability Include="Msix" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7463" />
|
||||||
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.260101001" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Lattice.Themes.Core\Lattice.Themes.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\Lattice.Themes.Fluent\Lattice.Themes.Fluent.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
|
||||||
|
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
||||||
|
the Windows App SDK Nuget package has not yet been restored.
|
||||||
|
-->
|
||||||
|
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||||
|
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- Publish Properties -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||||
|
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||||
|
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||||
|
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
141
Lattice.Example.DragDrop/MainWindow.xaml
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
<Window
|
||||||
|
x:Class="Lattice.Example.DragDrop.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:lattice="using:Lattice.UI.DragDrop.WinUI"
|
||||||
|
Title="Drag Drop Demo"
|
||||||
|
>
|
||||||
|
|
||||||
|
<Grid Background="#F0F2F5">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Инструкция -->
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
Text="Просто перетащите элементы справа влево!"
|
||||||
|
FontSize="14" Margin="20" HorizontalAlignment="Center"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
|
||||||
|
<!-- Основное содержимое -->
|
||||||
|
<Grid Grid.Row="1" Margin="20">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- ЦЕЛЕВАЯ ЗОНА (куда бросаем) -->
|
||||||
|
<Border Grid.Column="0"
|
||||||
|
Background="White"
|
||||||
|
CornerRadius="10"
|
||||||
|
BorderThickness="2"
|
||||||
|
BorderBrush="#4CAF50"
|
||||||
|
Padding="20"
|
||||||
|
Margin="0,0,10,0"
|
||||||
|
lattice:DragDropProperties.IsDropTarget="True">
|
||||||
|
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="🟢 ЦЕЛЕВАЯ ЗОНА"
|
||||||
|
FontSize="16" FontWeight="Bold"
|
||||||
|
Foreground="#4CAF50"
|
||||||
|
Margin="0,0,0,15"/>
|
||||||
|
|
||||||
|
<TextBlock x:Name="DropInfoText"
|
||||||
|
Text="Бросьте сюда элементы"
|
||||||
|
FontSize="14"
|
||||||
|
Foreground="#666"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- ЗОНА С ЭЛЕМЕНТАМИ (откуда тянем) -->
|
||||||
|
<StackPanel Grid.Column="1"
|
||||||
|
Background="White"
|
||||||
|
CornerRadius="10"
|
||||||
|
Padding="20"
|
||||||
|
Margin="10,0,0,0">
|
||||||
|
|
||||||
|
<TextBlock Text="📦 ЭЛЕМЕНТЫ ДЛЯ ПЕРЕТАСКИВАНИЯ"
|
||||||
|
FontSize="16" FontWeight="Bold"
|
||||||
|
Foreground="#2196F3"
|
||||||
|
Margin="0,0,0,15"/>
|
||||||
|
|
||||||
|
<!-- 1. TextBlock элемент -->
|
||||||
|
<Border Padding="15"
|
||||||
|
Background="#E3F2FD"
|
||||||
|
CornerRadius="8"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="#90CAF9"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
lattice:DragDropProperties.IsDragSource="True"
|
||||||
|
lattice:DragDropProperties.DragData="TextBlock Element">
|
||||||
|
|
||||||
|
<TextBlock Text="📝 Это TextBlock"
|
||||||
|
FontSize="14"
|
||||||
|
Foreground="#1565C0"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 2. Border элемент -->
|
||||||
|
<Border Padding="15"
|
||||||
|
Background="#E8F5E9"
|
||||||
|
CornerRadius="8"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="#A5D6A7"
|
||||||
|
Margin="0,0,0,10"
|
||||||
|
lattice:DragDropProperties.IsDragSource="True"
|
||||||
|
lattice:DragDropProperties.DragData="Border Element">
|
||||||
|
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="🟩 Это Border"
|
||||||
|
FontSize="14"
|
||||||
|
Foreground="#2E7D32"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
<TextBlock Text="С рамкой и заливкой"
|
||||||
|
FontSize="12"
|
||||||
|
Foreground="#666"
|
||||||
|
Margin="0,5,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 3. Grid элемент -->
|
||||||
|
<Border Padding="15"
|
||||||
|
Background="#FFF3E0"
|
||||||
|
CornerRadius="8"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="#FFCC80"
|
||||||
|
lattice:DragDropProperties.IsDragSource="True"
|
||||||
|
lattice:DragDropProperties.DragData="Grid Element">
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Иконка -->
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Text="🔲"
|
||||||
|
FontSize="18"
|
||||||
|
Margin="0,0,10,0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
|
||||||
|
<!-- Контент -->
|
||||||
|
<StackPanel Grid.Column="1">
|
||||||
|
<TextBlock Text="Это Grid"
|
||||||
|
FontSize="14"
|
||||||
|
Foreground="#EF6C00"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
<TextBlock Text="С несколькими колонками"
|
||||||
|
FontSize="12"
|
||||||
|
Foreground="#666"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
17
Lattice.Example.DragDrop/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using Lattice.UI.DragDrop.WinUI;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
|
||||||
|
namespace Lattice.Example.DragDrop;
|
||||||
|
|
||||||
|
public sealed partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
private bool _isInitialized = false;
|
||||||
|
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
XamlInitializer.Initialize(this);
|
||||||
|
Title = "✅ Drag & Drop работает! Перетащите элементы →";
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Lattice.Example.DragDrop/Package.appxmanifest
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<Package
|
||||||
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||||
|
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||||
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||||
|
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||||
|
IgnorableNamespaces="uap rescap">
|
||||||
|
|
||||||
|
<Identity
|
||||||
|
Name="dcfd6640-86d9-4ce7-bc17-24685f01b577"
|
||||||
|
Publisher="CN=frost"
|
||||||
|
Version="1.0.0.0" />
|
||||||
|
|
||||||
|
<mp:PhoneIdentity PhoneProductId="dcfd6640-86d9-4ce7-bc17-24685f01b577" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||||
|
|
||||||
|
<Properties>
|
||||||
|
<DisplayName>Lattice.Example.DragDrop</DisplayName>
|
||||||
|
<PublisherDisplayName>frost</PublisherDisplayName>
|
||||||
|
<Logo>Assets\StoreLogo.png</Logo>
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<Dependencies>
|
||||||
|
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||||
|
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||||
|
</Dependencies>
|
||||||
|
|
||||||
|
<Resources>
|
||||||
|
<Resource Language="x-generate"/>
|
||||||
|
</Resources>
|
||||||
|
|
||||||
|
<Applications>
|
||||||
|
<Application Id="App"
|
||||||
|
Executable="$targetnametoken$.exe"
|
||||||
|
EntryPoint="$targetentrypoint$">
|
||||||
|
<uap:VisualElements
|
||||||
|
DisplayName="Lattice.Example.DragDrop"
|
||||||
|
Description="Lattice.Example.DragDrop"
|
||||||
|
BackgroundColor="transparent"
|
||||||
|
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||||
|
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||||
|
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||||
|
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||||
|
</uap:VisualElements>
|
||||||
|
</Application>
|
||||||
|
</Applications>
|
||||||
|
|
||||||
|
<Capabilities>
|
||||||
|
<rescap:Capability Name="runFullTrust" />
|
||||||
|
</Capabilities>
|
||||||
|
</Package>
|
||||||
10
Lattice.Example.DragDrop/Properties/launchSettings.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Lattice.Example.DragDrop (Package)": {
|
||||||
|
"commandName": "MsixPackage"
|
||||||
|
},
|
||||||
|
"Lattice.Example.DragDrop (Unpackaged)": {
|
||||||
|
"commandName": "Project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Lattice.Example.DragDrop/app.manifest
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="Lattice.Example.DragDrop.app"/>
|
||||||
|
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10.
|
||||||
|
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation.
|
||||||
|
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
</assembly>
|
||||||
19
Lattice.IDE/App.xaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Application
|
||||||
|
x:Class="Lattice.IDE.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:lt="using:Lattice.Themes"
|
||||||
|
xmlns:local="using:Lattice.IDE">
|
||||||
|
<Application.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||||
|
<ResourceDictionary Source="ms-appx:///Lattice.UI.Docking.WinUI/Themes/Generic.xaml" />
|
||||||
|
|
||||||
|
<!-- Other merged dictionaries here -->
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
<!-- Other app resources here -->
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
37
Lattice.IDE/App.xaml.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using Lattice.Themes;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
|
||||||
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
|
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||||
|
|
||||||
|
namespace Lattice.IDE
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides application-specific behavior to supplement the default Application class.
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
private Window? _window;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the singleton application object. This is the first line of authored code
|
||||||
|
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||||
|
/// </summary>
|
||||||
|
public App()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when the application is launched.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">Details about the launch request and process.</param>
|
||||||
|
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||||
|
{
|
||||||
|
ThemeManager.Current.ApplyTheme(new FluentThemePack());
|
||||||
|
|
||||||
|
_window = new MainWindow();
|
||||||
|
_window.Activate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Lattice.IDE/Assets/LockScreenLogo.scale-200.png
Normal file
|
After Width: | Height: | Size: 432 B |
BIN
Lattice.IDE/Assets/SplashScreen.scale-200.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
Lattice.IDE/Assets/Square150x150Logo.scale-200.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Lattice.IDE/Assets/Square44x44Logo.scale-200.png
Normal file
|
After Width: | Height: | Size: 637 B |
|
After Width: | Height: | Size: 283 B |
BIN
Lattice.IDE/Assets/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 456 B |
BIN
Lattice.IDE/Assets/Wide310x150Logo.scale-200.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
58
Lattice.IDE/Controls/EditorView.xaml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<UserControl
|
||||||
|
x:Class="Lattice.IDE.Controls.EditorView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:Lattice.IDE.Controls"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Grid Background="{ThemeResource Lattice.Brush.Background.Secondary}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<!-- Полоса номеров строк -->
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<!-- Текст кода -->
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Левая панель с номерами строк -->
|
||||||
|
<StackPanel Grid.Column="0" Background="{ThemeResource Lattice.Brush.Background.Primary}" Padding="10,5">
|
||||||
|
<TextBlock Text="1" Foreground="Gray" FontFamily="Cascadia Code, Consolas"/>
|
||||||
|
<TextBlock Text="2" Foreground="Gray" FontFamily="Cascadia Code, Consolas"/>
|
||||||
|
<TextBlock Text="3" Foreground="Gray" FontFamily="Cascadia Code, Consolas"/>
|
||||||
|
<TextBlock Text="4" Foreground="Gray" FontFamily="Cascadia Code, Consolas"/>
|
||||||
|
<TextBlock Text="5" Foreground="Gray" FontFamily="Cascadia Code, Consolas"/>
|
||||||
|
<TextBlock Text="6" Foreground="Gray" FontFamily="Cascadia Code, Consolas"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Основная область редактирования -->
|
||||||
|
<TextBox Grid.Column="1"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
IsSpellCheckEnabled="False"
|
||||||
|
TextWrapping="NoWrap"
|
||||||
|
FontFamily="Cascadia Code, Consolas"
|
||||||
|
FontSize="14"
|
||||||
|
BorderThickness="0"
|
||||||
|
Padding="10"
|
||||||
|
Background="Transparent"
|
||||||
|
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||||
|
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||||
|
xml:space="preserve">
|
||||||
|
<TextBox.Text>using System;
|
||||||
|
using Lattice.Core;
|
||||||
|
|
||||||
|
namespace Lattice.IDE.Demo;
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public void Main()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Hello, Lattice 2026!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</TextBox.Text>
|
||||||
|
</TextBox>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
15
Lattice.IDE/Controls/EditorView.xaml.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
|
||||||
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
|
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||||
|
|
||||||
|
namespace Lattice.IDE.Controls
|
||||||
|
{
|
||||||
|
public sealed partial class EditorView : UserControl
|
||||||
|
{
|
||||||
|
public EditorView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
Lattice.IDE/Controls/SolutionExplorerView.xaml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<UserControl
|
||||||
|
x:Class="Lattice.IDE.Controls.SolutionExplorerView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:Lattice.IDE.Controls"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Grid Background="{ThemeResource LayerFillColorDefaultBrush}" Padding="10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
Text="SOLUTION 'LATTICE' (2026)"
|
||||||
|
FontSize="11"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Opacity="0.6"/>
|
||||||
|
|
||||||
|
<TreeView Grid.Row="1" Margin="0,10,0,0">
|
||||||
|
<TreeView.RootNodes>
|
||||||
|
<TreeViewNode Content="Lattice.Core.Docking" IsExpanded="True">
|
||||||
|
<TreeViewNode.Children>
|
||||||
|
<TreeViewNode Content="Models" />
|
||||||
|
<TreeViewNode Content="Engine" />
|
||||||
|
</TreeViewNode.Children>
|
||||||
|
</TreeViewNode>
|
||||||
|
<TreeViewNode Content="Lattice.UI.Docking.WinUI" IsExpanded="True">
|
||||||
|
<TreeViewNode.Children>
|
||||||
|
<TreeViewNode Content="Controls" />
|
||||||
|
<TreeViewNode Content="Themes" />
|
||||||
|
</TreeViewNode.Children>
|
||||||
|
</TreeViewNode>
|
||||||
|
</TreeView.RootNodes>
|
||||||
|
</TreeView>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
15
Lattice.IDE/Controls/SolutionExplorerView.xaml.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
|
||||||
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
|
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||||
|
|
||||||
|
namespace Lattice.IDE.Controls
|
||||||
|
{
|
||||||
|
public sealed partial class SolutionExplorerView : UserControl
|
||||||
|
{
|
||||||
|
public SolutionExplorerView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
Lattice.IDE/Lattice.IDE.csproj
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFrameworks>net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0;net10.0-windows10.0.19041.0</TargetFrameworks>
|
||||||
|
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||||
|
<RootNamespace>Lattice.IDE</RootNamespace>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<Platforms>x86;x64;ARM64</Platforms>
|
||||||
|
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||||
|
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||||
|
<UseWinUI>true</UseWinUI>
|
||||||
|
<WinUISDKReferences>false</WinUISDKReferences>
|
||||||
|
<EnableMsixTooling>true</EnableMsixTooling>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Controls\EditorView.xaml" />
|
||||||
|
<None Remove="Controls\SolutionExplorerView.xaml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||||
|
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||||
|
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||||
|
<Content Include="Assets\Square44x44Logo.scale-200.png" />
|
||||||
|
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||||
|
<Content Include="Assets\StoreLogo.png" />
|
||||||
|
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Manifest Include="$(ApplicationManifest)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||||
|
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||||
|
package has not yet been restored.
|
||||||
|
-->
|
||||||
|
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||||
|
<ProjectCapability Include="Msix" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7463" />
|
||||||
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Lattice.Core.Docking\Lattice.Core.Docking.csproj" />
|
||||||
|
<ProjectReference Include="..\Lattice.Themes.Core\Lattice.Themes.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\Lattice.Themes.Fluent\Lattice.Themes.Fluent.csproj" />
|
||||||
|
<ProjectReference Include="..\Lattice.Themes.VS2026\Lattice.Themes.VS2026.csproj" />
|
||||||
|
<ProjectReference Include="..\Lattice.UI.Docking.WinUI\Lattice.UI.Docking.WinUI.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Controls\EditorView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Controls\SolutionExplorerView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
|
||||||
|
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
||||||
|
the Windows App SDK Nuget package has not yet been restored.
|
||||||
|
-->
|
||||||
|
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||||
|
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- Publish Properties -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||||
|
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||||
|
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||||
|
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
28
Lattice.IDE/Layout/DemoContent.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Lattice.Core.Docking.Abstractions;
|
||||||
|
|
||||||
|
namespace Lattice.IDE;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Реализация контента для демонстрации, принимающая любой UI-объект.
|
||||||
|
/// </summary>
|
||||||
|
public class DemoContent : IDockContent
|
||||||
|
{
|
||||||
|
public string Id { get; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сюда мы передаем наш UserControl (SolutionExplorerView и т.д.)
|
||||||
|
/// </summary>
|
||||||
|
public object View { get; set; }
|
||||||
|
|
||||||
|
public bool CanClose { get; set; } = true;
|
||||||
|
|
||||||
|
public DemoContent(string id, string title, object view)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Title = title;
|
||||||
|
View = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnClosing() => true;
|
||||||
|
}
|
||||||
29
Lattice.IDE/MainWindow.xaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Window
|
||||||
|
x:Class="Lattice.IDE.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:Lattice.IDE"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:lattice="using:Lattice.UI"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="Lattice.IDE">
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Меню управления Demo -->
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10" Padding="10" Background="{ThemeResource SystemControlBackgroundChromeMediumLowBrush}">
|
||||||
|
<Button Content="Fluent UI Theme" Click="SetFluentTheme"/>
|
||||||
|
<Button Content="VS 2026 Theme" Click="SetVSTheme"/>
|
||||||
|
<TextBlock Text="Lattice IDE Demo 2026" VerticalAlignment="Center" Margin="20,0" FontWeight="Bold"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Lattice Docking Host -->
|
||||||
|
<lattice:LatticeDockHost x:Name="DockHost" Grid.Row="1" />
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
64
Lattice.IDE/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using Lattice.Core.Docking.Engine;
|
||||||
|
using Lattice.Core.Docking.Models;
|
||||||
|
using Lattice.IDE.Controls;
|
||||||
|
using Lattice.Themes;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
// Ïðåäïîëîæèì, ÷òî VS2026Theme òîæå ðåàëèçîâàí àíàëîãè÷íî Fluent
|
||||||
|
// using Lattice.Themes.VisualStudio2026;
|
||||||
|
|
||||||
|
namespace Lattice.IDE;
|
||||||
|
|
||||||
|
public sealed partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
private LayoutManager _manager;
|
||||||
|
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
this.InitializeComponent();
|
||||||
|
WindowTracker.Register(this);
|
||||||
|
SystemBackdrop = new Microsoft.UI.Xaml.Media.MicaBackdrop();
|
||||||
|
InitLattice();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitLattice()
|
||||||
|
{
|
||||||
|
_manager = new LayoutManager();
|
||||||
|
|
||||||
|
// Ñîçäàåì êîíòåíò íà îñíîâå XAML UserControls
|
||||||
|
var solutionExplorer = new DemoContent(
|
||||||
|
"sln",
|
||||||
|
"Solution Explorer",
|
||||||
|
new SolutionExplorerView()
|
||||||
|
);
|
||||||
|
|
||||||
|
var editor = new DemoContent(
|
||||||
|
"code_01",
|
||||||
|
"Program.cs",
|
||||||
|
new EditorView()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ñîáèðàåì äåðåâî (êàê ðàíüøå)
|
||||||
|
var leftLeaf = new DockLeaf();
|
||||||
|
leftLeaf.AddContent(solutionExplorer);
|
||||||
|
|
||||||
|
var centerLeaf = new DockLeaf()
|
||||||
|
{
|
||||||
|
TabPlacement = TabPlacement.Top,
|
||||||
|
};
|
||||||
|
centerLeaf.AddContent(editor);
|
||||||
|
|
||||||
|
var rootGroup = new DockGroup(leftLeaf, centerLeaf, SplitDirection.Horizontal)
|
||||||
|
{
|
||||||
|
SplitRatio = 0.25
|
||||||
|
};
|
||||||
|
|
||||||
|
_manager.SetRoot(rootGroup);
|
||||||
|
DockHost.Manager = _manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetFluentTheme(object sender, RoutedEventArgs e) =>
|
||||||
|
ThemeManager.Current.ApplyTheme(new FluentThemePack());
|
||||||
|
|
||||||
|
private void SetVSTheme(object sender, RoutedEventArgs e) =>
|
||||||
|
ThemeManager.Current.ApplyTheme(new VS2026ThemePack());
|
||||||
|
}
|
||||||
51
Lattice.IDE/Package.appxmanifest
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<Package
|
||||||
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||||
|
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||||
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||||
|
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||||
|
IgnorableNamespaces="uap rescap">
|
||||||
|
|
||||||
|
<Identity
|
||||||
|
Name="c6596033-98b6-4778-8280-bc9256b9be07"
|
||||||
|
Publisher="CN=frost"
|
||||||
|
Version="1.0.0.0" />
|
||||||
|
|
||||||
|
<mp:PhoneIdentity PhoneProductId="c6596033-98b6-4778-8280-bc9256b9be07" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||||
|
|
||||||
|
<Properties>
|
||||||
|
<DisplayName>Lattice.IDE</DisplayName>
|
||||||
|
<PublisherDisplayName>frost</PublisherDisplayName>
|
||||||
|
<Logo>Assets\StoreLogo.png</Logo>
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<Dependencies>
|
||||||
|
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||||
|
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||||
|
</Dependencies>
|
||||||
|
|
||||||
|
<Resources>
|
||||||
|
<Resource Language="x-generate"/>
|
||||||
|
</Resources>
|
||||||
|
|
||||||
|
<Applications>
|
||||||
|
<Application Id="App"
|
||||||
|
Executable="$targetnametoken$.exe"
|
||||||
|
EntryPoint="$targetentrypoint$">
|
||||||
|
<uap:VisualElements
|
||||||
|
DisplayName="Lattice.IDE"
|
||||||
|
Description="Lattice.IDE"
|
||||||
|
BackgroundColor="transparent"
|
||||||
|
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||||
|
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||||
|
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||||
|
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||||
|
</uap:VisualElements>
|
||||||
|
</Application>
|
||||||
|
</Applications>
|
||||||
|
|
||||||
|
<Capabilities>
|
||||||
|
<rescap:Capability Name="runFullTrust" />
|
||||||
|
</Capabilities>
|
||||||
|
</Package>
|
||||||
10
Lattice.IDE/Properties/launchSettings.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Lattice.IDE (Package)": {
|
||||||
|
"commandName": "MsixPackage"
|
||||||
|
},
|
||||||
|
"Lattice.IDE (Unpackaged)": {
|
||||||
|
"commandName": "Project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Lattice.IDE/app.manifest
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="Lattice.IDE.app"/>
|
||||||
|
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10.
|
||||||
|
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation.
|
||||||
|
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
</assembly>
|
||||||
30
Lattice.Layout.UI.WinUI/Controls/WinUIGroupControl.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
|
||||||
|
namespace Lattice.Layout.UI.WinUI.Controls;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Контрол для отображения группы вкладок.
|
||||||
|
/// Содержит заголовки вкладок и область содержимого.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class WinUIGroupControl : Grid
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Контрол TabView, содержащий вкладки и их содержимое.
|
||||||
|
/// </summary>
|
||||||
|
public TabView TabView { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создаёт новый экземпляр <see cref="WinUIGroupControl"/>.
|
||||||
|
/// </summary>
|
||||||
|
public WinUIGroupControl()
|
||||||
|
{
|
||||||
|
TabView = new TabView
|
||||||
|
{
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||||
|
VerticalAlignment = VerticalAlignment.Stretch
|
||||||
|
};
|
||||||
|
|
||||||
|
Children.Add(TabView);
|
||||||
|
}
|
||||||
|
}
|
||||||
134
Lattice.Layout.UI.WinUI/Controls/WinUIItemControl.cs
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Lattice.Layout.UI.WinUI.Controls;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Контрол для отображения содержимого конечного элемента раскладки.
|
||||||
|
/// Является контейнером для реального UI-контента, подставляемого через ContentResolver.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class WinUIItemControl : ContentControl
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Идентификатор содержимого, связанного с данным элементом.
|
||||||
|
/// Используется для подстановки реального UI-контрола через ContentResolver.
|
||||||
|
/// </summary>
|
||||||
|
public string? ContentId
|
||||||
|
{
|
||||||
|
get => (string?)GetValue(ContentIdProperty);
|
||||||
|
set => SetValue(ContentIdProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty ContentIdProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(ContentId),
|
||||||
|
typeof(string),
|
||||||
|
typeof(WinUIItemControl),
|
||||||
|
new PropertyMetadata(default(string), OnContentIdChanged));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Делегат, который должен вернуть реальный UI-контент по ContentId.
|
||||||
|
/// Устанавливается WinUILayoutHost.
|
||||||
|
/// </summary>
|
||||||
|
public Func<string, UIElement?>? ContentResolver
|
||||||
|
{
|
||||||
|
get => (Func<string, UIElement?>?)GetValue(ContentResolverProperty);
|
||||||
|
set => SetValue(ContentResolverProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty ContentResolverProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(ContentResolver),
|
||||||
|
typeof(Func<string, UIElement?>),
|
||||||
|
typeof(WinUIItemControl),
|
||||||
|
new PropertyMetadata(null, OnContentResolverChanged));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Вызывается, когда контент успешно загружен.
|
||||||
|
/// </summary>
|
||||||
|
public event Action<WinUIItemControl>? ContentLoaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Вызывается, когда контент был очищен (Detach).
|
||||||
|
/// </summary>
|
||||||
|
public event Action<WinUIItemControl>? ContentCleared;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создаёт новый экземпляр <see cref="WinUIItemControl"/>.
|
||||||
|
/// </summary>
|
||||||
|
public WinUIItemControl()
|
||||||
|
{
|
||||||
|
HorizontalContentAlignment = HorizontalAlignment.Stretch;
|
||||||
|
VerticalContentAlignment = VerticalAlignment.Stretch;
|
||||||
|
|
||||||
|
// Fallback-контент, если ContentResolver не установлен
|
||||||
|
Content = CreatePlaceholder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnContentIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is WinUIItemControl control)
|
||||||
|
control.TryLoadContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnContentResolverChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is WinUIItemControl control)
|
||||||
|
control.TryLoadContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Пытается загрузить реальный контент по ContentId.
|
||||||
|
/// </summary>
|
||||||
|
private void TryLoadContent()
|
||||||
|
{
|
||||||
|
if (ContentId is null || ContentResolver is null)
|
||||||
|
{
|
||||||
|
Content = CreatePlaceholder();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolved = ContentResolver(ContentId);
|
||||||
|
|
||||||
|
if (resolved is null)
|
||||||
|
{
|
||||||
|
Content = CreatePlaceholder($"Контент '{ContentId}' не найден");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Content = resolved;
|
||||||
|
ContentLoaded?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Очищает контент (используется визуалом при Detach).
|
||||||
|
/// </summary>
|
||||||
|
public void ClearContent()
|
||||||
|
{
|
||||||
|
Content = CreatePlaceholder();
|
||||||
|
ContentCleared?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создаёт placeholder-контент, отображаемый до загрузки реального UI.
|
||||||
|
/// </summary>
|
||||||
|
private static UIElement CreatePlaceholder(string? message = null)
|
||||||
|
{
|
||||||
|
return new Border
|
||||||
|
{
|
||||||
|
Background = new SolidColorBrush(Microsoft.UI.Colors.Transparent),
|
||||||
|
BorderBrush = new SolidColorBrush(Microsoft.UI.Colors.Gray),
|
||||||
|
BorderThickness = new Thickness(1),
|
||||||
|
Padding = new Thickness(8),
|
||||||
|
Child = new TextBlock
|
||||||
|
{
|
||||||
|
Text = message ?? "Нет содержимого",
|
||||||
|
Opacity = 0.6,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
134
Lattice.Layout.UI.WinUI/Controls/WinUILayoutHost.cs
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
using Lattice.Layout.Abstractions;
|
||||||
|
using Lattice.Layout.UI.Docking;
|
||||||
|
using Lattice.Layout.UI.WinUI.Docking;
|
||||||
|
using Lattice.Layout.UI.WinUI.Rendering;
|
||||||
|
using Lattice.Layout.UI.WinUI.Visuals;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Lattice.Layout.UI.WinUI.Controls;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// WinUI-контрол, отображающий дерево раскладки.
|
||||||
|
/// Оборачивает LayoutRenderer и размещает визуальное дерево внутри себя.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class WinUILayoutHost : UserControl, ILayoutView
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Слой, в котором размещается визуальное дерево раскладки.
|
||||||
|
/// </summary>
|
||||||
|
public Grid LayoutLayer { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Слой для отображения подсветки зон докинга.
|
||||||
|
/// </summary>
|
||||||
|
public DockOverlayHost OverlayLayer { get; }
|
||||||
|
|
||||||
|
private readonly LayoutRenderer _renderer;
|
||||||
|
private readonly WinUIVisualFactory _factory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Функция, возвращающая UI-содержимое по ContentId.
|
||||||
|
/// Используется визуальными элементами для подстановки реального контрола.
|
||||||
|
/// </summary>
|
||||||
|
public Func<string, UIElement>? ContentResolver { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Корневой элемент раскладки, который необходимо отобразить.
|
||||||
|
/// </summary>
|
||||||
|
public ILayoutRoot? Root
|
||||||
|
{
|
||||||
|
get => (ILayoutRoot?)GetValue(RootProperty);
|
||||||
|
set => SetValue(RootProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Свойство зависимости для <see cref="Root"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty RootProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(Root),
|
||||||
|
typeof(ILayoutRoot),
|
||||||
|
typeof(WinUILayoutHost),
|
||||||
|
new PropertyMetadata(null, OnRootChanged));
|
||||||
|
|
||||||
|
private static void OnRootChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is WinUILayoutHost host)
|
||||||
|
{
|
||||||
|
host.Refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создаёт новый экземпляр <see cref="WinUILayoutHost"/>.
|
||||||
|
/// </summary>
|
||||||
|
public WinUILayoutHost()
|
||||||
|
{
|
||||||
|
LayoutLayer = new Grid();
|
||||||
|
OverlayLayer = new DockOverlayHost();
|
||||||
|
|
||||||
|
var rootGrid = new Grid();
|
||||||
|
rootGrid.Children.Add(LayoutLayer);
|
||||||
|
rootGrid.Children.Add(OverlayLayer);
|
||||||
|
|
||||||
|
Content = rootGrid;
|
||||||
|
|
||||||
|
_factory = new WinUIVisualFactory();
|
||||||
|
_renderer = new LayoutRenderer(_factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Выполняет полную перерисовку визуального дерева.
|
||||||
|
/// </summary>
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
LayoutLayer.Children.Clear();
|
||||||
|
|
||||||
|
if (Root?.Child is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var visual = _renderer.Build(Root.Child);
|
||||||
|
visual.Attach();
|
||||||
|
|
||||||
|
switch (visual)
|
||||||
|
{
|
||||||
|
case WinUISplitVisual splitVisual:
|
||||||
|
LayoutLayer.Children.Add(splitVisual.Control);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WinUIGroupVisual groupVisual:
|
||||||
|
LayoutLayer.Children.Add(groupVisual.Control);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WinUIItemVisual itemVisual:
|
||||||
|
LayoutLayer.Children.Add(itemVisual.Control);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Показывает подсветку зоны докинга для указанной цели.
|
||||||
|
/// </summary>
|
||||||
|
public void ShowDockOverlay(DockTarget target)
|
||||||
|
{
|
||||||
|
if (target.Visual is not IWinUIVisual winuiVisual)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var control = winuiVisual.Control;
|
||||||
|
|
||||||
|
var bounds = control.TransformToVisual(LayoutLayer)
|
||||||
|
.TransformBounds(new Windows.Foundation.Rect(0, 0, control.ActualWidth, control.ActualHeight));
|
||||||
|
|
||||||
|
OverlayLayer.ShowOverlay(target, bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Скрывает подсветку зон докинга.
|
||||||
|
/// </summary>
|
||||||
|
public void HideDockOverlay()
|
||||||
|
{
|
||||||
|
OverlayLayer.HideOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
85
Lattice.Layout.UI.WinUI/Controls/WinUISplitControl.cs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
|
||||||
|
namespace Lattice.Layout.UI.WinUI.Controls;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Контейнер для отображения сплит-элемента раскладки.
|
||||||
|
/// Использует Grid и автоматически создаёт строки/столбцы под детей.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class WinUISplitControl : Grid
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ориентация сплита (горизонтальная или вертикальная).
|
||||||
|
/// </summary>
|
||||||
|
public Orientation LayoutOrientation
|
||||||
|
{
|
||||||
|
get => (Orientation)GetValue(LayoutOrientationProperty);
|
||||||
|
set => SetValue(LayoutOrientationProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty LayoutOrientationProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(LayoutOrientation),
|
||||||
|
typeof(Orientation),
|
||||||
|
typeof(WinUISplitControl),
|
||||||
|
new PropertyMetadata(Orientation.Horizontal, OnOrientationChanged));
|
||||||
|
|
||||||
|
private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is WinUISplitControl control)
|
||||||
|
control.RebuildGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
public WinUISplitControl()
|
||||||
|
{
|
||||||
|
Loaded += (_, _) => RebuildGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Перестраивает структуру Grid в зависимости от ориентации и количества детей.
|
||||||
|
/// </summary>
|
||||||
|
public void RebuildGrid()
|
||||||
|
{
|
||||||
|
RowDefinitions.Clear();
|
||||||
|
ColumnDefinitions.Clear();
|
||||||
|
|
||||||
|
if (Children.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (LayoutOrientation == Orientation.Horizontal)
|
||||||
|
{
|
||||||
|
// Горизонтальный сплит → столбцы
|
||||||
|
for (int i = 0; i < Children.Count; i++)
|
||||||
|
{
|
||||||
|
ColumnDefinitions.Add(new ColumnDefinition
|
||||||
|
{
|
||||||
|
Width = new GridLength(1, GridUnitType.Star)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Children[i] is FrameworkElement fe) Grid.SetColumn(fe, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Вертикальный сплит → строки
|
||||||
|
for (int i = 0; i < Children.Count; i++)
|
||||||
|
{
|
||||||
|
RowDefinitions.Add(new RowDefinition
|
||||||
|
{
|
||||||
|
Height = new GridLength(1, GridUnitType.Star)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Children[i] is FrameworkElement fe) Grid.SetColumn(fe, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавляет дочерний элемент и перестраивает Grid.
|
||||||
|
/// </summary>
|
||||||
|
public new void ChildrenChanged()
|
||||||
|
{
|
||||||
|
RebuildGrid();
|
||||||
|
}
|
||||||
|
}
|
||||||
83
Lattice.Layout.UI.WinUI/Docking/DockOverlay.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using Lattice.Layout.Abstractions;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Windows.Foundation;
|
||||||
|
|
||||||
|
namespace Lattice.Layout.UI.WinUI.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Полупрозрачная подсветка зоны докинга.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DockOverlay : Control
|
||||||
|
{
|
||||||
|
public static readonly DependencyProperty ZoneProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(Zone),
|
||||||
|
typeof(DockZone),
|
||||||
|
typeof(DockOverlay),
|
||||||
|
new PropertyMetadata(DockZone.Center, OnVisualPropertyChanged));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty BoundsProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(Bounds),
|
||||||
|
typeof(Rect),
|
||||||
|
typeof(DockOverlay),
|
||||||
|
new PropertyMetadata(Rect.Empty, OnVisualPropertyChanged));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Зона докинга, которую нужно подсветить.
|
||||||
|
/// </summary>
|
||||||
|
public DockZone Zone
|
||||||
|
{
|
||||||
|
get => (DockZone)GetValue(ZoneProperty);
|
||||||
|
set => SetValue(ZoneProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Прямоугольник зоны в координатах родительского контейнера.
|
||||||
|
/// </summary>
|
||||||
|
public Rect Bounds
|
||||||
|
{
|
||||||
|
get => (Rect)GetValue(BoundsProperty);
|
||||||
|
set => SetValue(BoundsProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Border? _border;
|
||||||
|
|
||||||
|
public DockOverlay()
|
||||||
|
{
|
||||||
|
IsHitTestVisible = false;
|
||||||
|
DefaultStyleKey = typeof(DockOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate()
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate();
|
||||||
|
_border = GetTemplateChild("PART_Border") as Border;
|
||||||
|
UpdateVisual();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is DockOverlay overlay)
|
||||||
|
{
|
||||||
|
overlay.UpdateVisual();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateVisual()
|
||||||
|
{
|
||||||
|
if (_border is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Canvas.SetLeft(this, Bounds.X);
|
||||||
|
Canvas.SetTop(this, Bounds.Y);
|
||||||
|
Width = Bounds.Width;
|
||||||
|
Height = Bounds.Height;
|
||||||
|
|
||||||
|
_border.BorderBrush = new SolidColorBrush(Microsoft.UI.Colors.DeepSkyBlue);
|
||||||
|
_border.BorderThickness = new Thickness(2);
|
||||||
|
_border.Background = new SolidColorBrush(Microsoft.UI.Colors.LightSkyBlue) { Opacity = 0.25 };
|
||||||
|
}
|
||||||
|
}
|
||||||
47
Lattice.Layout.UI.WinUI/Docking/DockOverlayHost.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using Lattice.Layout.UI.Docking;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Windows.Foundation;
|
||||||
|
|
||||||
|
namespace Lattice.Layout.UI.WinUI.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Контейнер для отображения подсветки зон докинга.
|
||||||
|
/// Обычно используется как Overlay-слой внутри WinUILayoutHost.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DockOverlayHost : Canvas
|
||||||
|
{
|
||||||
|
private DockOverlay? _currentOverlay;
|
||||||
|
|
||||||
|
public DockOverlayHost()
|
||||||
|
{
|
||||||
|
IsHitTestVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Отображает подсветку для указанной цели докинга.
|
||||||
|
/// </summary>
|
||||||
|
public void ShowOverlay(DockTarget target, Rect bounds)
|
||||||
|
{
|
||||||
|
if (_currentOverlay is null)
|
||||||
|
{
|
||||||
|
_currentOverlay = new DockOverlay();
|
||||||
|
Children.Add(_currentOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentOverlay.Zone = target.Zone;
|
||||||
|
_currentOverlay.Bounds = bounds;
|
||||||
|
_currentOverlay.Visibility = Visibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Скрывает подсветку зоны докинга.
|
||||||
|
/// </summary>
|
||||||
|
public void HideOverlay()
|
||||||
|
{
|
||||||
|
if (_currentOverlay is not null)
|
||||||
|
{
|
||||||
|
_currentOverlay.Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
78
Lattice.Layout.UI.WinUI/Docking/DockZoneHitTester.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using Lattice.Layout.UI.Docking;
|
||||||
|
using Lattice.Layout.UI.WinUI.Controls;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Windows.Foundation;
|
||||||
|
|
||||||
|
namespace Lattice.Layout.UI.WinUI.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Выполняет hit-test для определения зоны докинга в WinUI.
|
||||||
|
/// </summary>
|
||||||
|
public static class DockZoneHitTester
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Выполняет hit-test по экранной точке и возвращает цель докинга.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="host">WinUI-хост раскладки.</param>
|
||||||
|
/// <param name="screenPoint">Точка в координатах окна.</param>
|
||||||
|
public static DockTarget? HitTest(WinUILayoutHost host, Point screenPoint)
|
||||||
|
{
|
||||||
|
if (host is null)
|
||||||
|
throw new ArgumentNullException(nameof(host));
|
||||||
|
|
||||||
|
// Предполагаем, что LayoutLayer — основной слой, в котором живёт визуальное дерево.
|
||||||
|
var layoutLayer = host.LayoutLayer;
|
||||||
|
if (layoutLayer is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Переводим координаты в систему координат layoutLayer.
|
||||||
|
var elements = VisualTreeHelper.FindElementsInHostCoordinates(screenPoint, host.LayoutLayer);
|
||||||
|
|
||||||
|
var firstElement = elements.FirstOrDefault();
|
||||||
|
|
||||||
|
if (firstElement is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Ищем ближайший IWinUIVisual.
|
||||||
|
var visual = FindVisual(firstElement);
|
||||||
|
if (visual is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (visual is not ILayoutVisual layoutVisual)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Вычисляем зону докинга для найденного контрола.
|
||||||
|
var control = visual.Control;
|
||||||
|
|
||||||
|
var bounds = control.TransformToVisual(layoutLayer)
|
||||||
|
.TransformBounds(new Rect(0, 0, control.ActualWidth, control.ActualHeight));
|
||||||
|
|
||||||
|
var localX = screenPoint.X - bounds.X;
|
||||||
|
var localY = screenPoint.Y - bounds.Y;
|
||||||
|
|
||||||
|
var zone = DockingUtils.GetZone(localX, localY, bounds.Width, bounds.Height);
|
||||||
|
|
||||||
|
return new DockTarget(layoutVisual, zone);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IWinUIVisual? FindVisual(UIElement element)
|
||||||
|
{
|
||||||
|
DependencyObject? current = element;
|
||||||
|
|
||||||
|
while (current is not null)
|
||||||
|
{
|
||||||
|
if (current is FrameworkElement fe && fe.DataContext is IWinUIVisual ctxVisual)
|
||||||
|
return ctxVisual;
|
||||||
|
|
||||||
|
if (current is IWinUIVisual winuiVisual)
|
||||||
|
return winuiVisual;
|
||||||
|
|
||||||
|
current = VisualTreeHelper.GetParent(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Lattice.Layout.UI.WinUI/Docking/IWinUIVisual.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
|
||||||
|
namespace Lattice.Layout.UI.WinUI.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Интерфейс для визуальных элементов WinUI, соответствующих элементам раскладки.
|
||||||
|
/// Нужен для hit-test и расчёта зон докинга.
|
||||||
|
/// </summary>
|
||||||
|
public interface IWinUIVisual
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Реальный WinUI-элемент, отображающий данный визуальный элемент раскладки.
|
||||||
|
/// </summary>
|
||||||
|
FrameworkElement Control { get; }
|
||||||
|
}
|
||||||
118
Lattice.Layout.UI.WinUI/Helpers/LayoutHostExtensions.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
using Lattice.Layout.Abstractions;
|
||||||
|
using Lattice.Layout.UI.WinUI.Controls;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Lattice.Layout.UI.WinUI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Набор вспомогательных методов для упрощённой работы с визуальным хостом раскладки.
|
||||||
|
/// Позволяет быстро подключать LayoutManager, обновлять UI и связывать содержимое.
|
||||||
|
/// </summary>
|
||||||
|
public static class LayoutHostExtensions
|
||||||
|
{
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 1. Подключение LayoutManager к любому ILayoutView
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Подписывает визуальный хост на события изменения раскладки.
|
||||||
|
/// При каждом изменении модели вызывается <see cref="ILayoutView.Refresh"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="view">Визуальный хост раскладки.</param>
|
||||||
|
/// <param name="manager">Менеджер раскладки.</param>
|
||||||
|
public static void BindToManager(this ILayoutView view, ILayoutManager manager)
|
||||||
|
{
|
||||||
|
manager.LayoutChanged += (_, _) => view.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 2. Полная инициализация раскладки (Root + Manager)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Устанавливает корневой элемент раскладки, подключает менеджер и выполняет начальную отрисовку.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="view">Визуальный хост раскладки.</param>
|
||||||
|
/// <param name="root">Корневой элемент раскладки.</param>
|
||||||
|
/// <param name="manager">Менеджер раскладки.</param>
|
||||||
|
public static void UseLayout(this ILayoutView view, ILayoutRoot root, ILayoutManager manager)
|
||||||
|
{
|
||||||
|
view.Root = root;
|
||||||
|
view.BindToManager(manager);
|
||||||
|
view.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 3. Удобный fluent-API
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Устанавливает корневой элемент раскладки и возвращает хост для fluent-цепочек.
|
||||||
|
/// </summary>
|
||||||
|
public static T WithRoot<T>(this T view, ILayoutRoot root)
|
||||||
|
where T : ILayoutView
|
||||||
|
{
|
||||||
|
view.Root = root;
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Подключает менеджер раскладки и возвращает хост для fluent-цепочек.
|
||||||
|
/// </summary>
|
||||||
|
public static T WithManager<T>(this T view, ILayoutManager manager)
|
||||||
|
where T : ILayoutView
|
||||||
|
{
|
||||||
|
view.BindToManager(manager);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Выполняет начальную отрисовку и возвращает хост для fluent-цепочек.
|
||||||
|
/// </summary>
|
||||||
|
public static T Initialize<T>(this T view)
|
||||||
|
where T : ILayoutView
|
||||||
|
{
|
||||||
|
view.Refresh();
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 4. Поддержка WinUILayoutHost: резолвер контента
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Устанавливает функцию, которая по ContentId возвращает реальный UI-элемент.
|
||||||
|
/// Используется для отображения содержимого вкладок.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="host">WinUI-хост раскладки.</param>
|
||||||
|
/// <param name="resolver">Функция, возвращающая UIElement по ContentId.</param>
|
||||||
|
public static void UseContentResolver(this WinUILayoutHost host, Func<string, UIElement> resolver)
|
||||||
|
{
|
||||||
|
host.ContentResolver = resolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 5. Полная WinUI-инициализация (Root + Manager + ContentResolver)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Полностью инициализирует WinUI-хост раскладки:
|
||||||
|
/// устанавливает корень, подключает менеджер, задаёт резолвер содержимого и выполняет отрисовку.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="host">WinUI-хост раскладки.</param>
|
||||||
|
/// <param name="root">Корневой элемент раскладки.</param>
|
||||||
|
/// <param name="manager">Менеджер раскладки.</param>
|
||||||
|
/// <param name="resolver">Функция получения UI-содержимого по ContentId.</param>
|
||||||
|
public static void UseLayout(
|
||||||
|
this WinUILayoutHost host,
|
||||||
|
ILayoutRoot root,
|
||||||
|
ILayoutManager manager,
|
||||||
|
Func<string, UIElement> resolver)
|
||||||
|
{
|
||||||
|
host.Root = root;
|
||||||
|
host.BindToManager(manager);
|
||||||
|
host.ContentResolver = resolver;
|
||||||
|
host.Refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Lattice.Layout.UI.WinUI/Lattice.Layout.UI.WinUI.csproj
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0;net10.0-windows10.0.19041.0</TargetFrameworks>
|
||||||
|
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||||
|
<RootNamespace>Lattice.Layout.UI.WinUI</RootNamespace>
|
||||||
|
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||||
|
<UseWinUI>true</UseWinUI>
|
||||||
|
<WinUISDKReferences>false</WinUISDKReferences>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7463" />
|
||||||
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Lattice.Layout.UI\Lattice.Layout.UI.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
30
Lattice.Layout.UI.WinUI/Rendering/WinUIVisualFactory.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Lattice.Layout.Abstractions;
|
||||||
|
using Lattice.Layout.UI.WinUI.Visuals;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Lattice.Layout.UI.WinUI.Rendering;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Фабрика визуальных элементов для WinUI.
|
||||||
|
/// Создаёт визуальные представления сплитов, групп и элементов.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class WinUIVisualFactory : ILayoutVisualFactory
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ILayoutVisual CreateSplit(ILayoutSplit split, IReadOnlyList<ILayoutVisual> children)
|
||||||
|
{
|
||||||
|
return new WinUISplitVisual(split, children);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ILayoutVisual CreateGroup(ILayoutGroup group, IReadOnlyList<ILayoutVisual> items)
|
||||||
|
{
|
||||||
|
return new WinUIGroupVisual(group, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ILayoutVisual CreateItem(ILayoutItem item)
|
||||||
|
{
|
||||||
|
return new WinUIItemVisual(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
81
Lattice.Layout.UI.WinUI/Visuals/WinUIGroupVisual.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
using Lattice.Layout.Abstractions;
|
||||||
|
using Lattice.Layout.UI.WinUI.Controls;
|
||||||
|
using Lattice.Layout.UI.WinUI.Docking;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Lattice.Layout.UI.WinUI.Visuals;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Визуальное представление группы вкладок для WinUI.
|
||||||
|
/// Управляет <see cref="WinUIGroupControl"/> и вкладками <see cref="TabViewItem"/>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class WinUIGroupVisual : LayoutVisual, IWinUIVisual
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Реальный WinUI-контрол группы вкладок.
|
||||||
|
/// </summary>
|
||||||
|
public WinUIGroupControl GroupControl { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Контрол, используемый докингом и рендерером.
|
||||||
|
/// </summary>
|
||||||
|
public FrameworkElement Control => GroupControl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Визуальные элементы вкладок.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<ILayoutVisual> Items { get; }
|
||||||
|
|
||||||
|
public WinUIGroupVisual(ILayoutGroup model, IReadOnlyList<ILayoutVisual> items)
|
||||||
|
: base(model)
|
||||||
|
{
|
||||||
|
Items = items;
|
||||||
|
GroupControl = new WinUIGroupControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
// Полная очистка
|
||||||
|
GroupControl.TabView.TabItems.Clear();
|
||||||
|
|
||||||
|
foreach (var visual in Items)
|
||||||
|
{
|
||||||
|
visual.Attach();
|
||||||
|
|
||||||
|
if (visual is WinUIItemVisual itemVisual)
|
||||||
|
{
|
||||||
|
var tab = new TabViewItem
|
||||||
|
{
|
||||||
|
Header = itemVisual.Header,
|
||||||
|
Content = itemVisual.Control // безопасно, т.к. Control — UserControl
|
||||||
|
};
|
||||||
|
|
||||||
|
GroupControl.TabView.TabItems.Add(tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Активная вкладка по модели
|
||||||
|
if (Model is ILayoutGroup group && group.ActiveItem is not null)
|
||||||
|
{
|
||||||
|
var index = group.Items is IList<ILayoutItem> list
|
||||||
|
? list.IndexOf(group.ActiveItem)
|
||||||
|
: group.Items.ToList().IndexOf(group.ActiveItem);
|
||||||
|
|
||||||
|
if (index >= 0 && index < GroupControl.TabView.TabItems.Count)
|
||||||
|
GroupControl.TabView.SelectedIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
foreach (var visual in Items)
|
||||||
|
visual.Detach();
|
||||||
|
|
||||||
|
GroupControl.TabView.TabItems.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Lattice.Layout.UI.WinUI/Visuals/WinUIItemVisual.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using Lattice.Layout.Abstractions;
|
||||||
|
using Lattice.Layout.UI.WinUI.Controls;
|
||||||
|
using Lattice.Layout.UI.WinUI.Docking;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
|
||||||
|
namespace Lattice.Layout.UI.WinUI.Visuals;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Визуальное представление конечного элемента раскладки для WinUI.
|
||||||
|
/// Оборачивает содержимое в <see cref="WinUIItemControl"/>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class WinUIItemVisual : LayoutVisual, IWinUIVisual
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Реальный WinUI-контрол, отображающий элемент.
|
||||||
|
/// </summary>
|
||||||
|
public WinUIItemControl ItemControl { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Контрол, используемый докингом и рендерером.
|
||||||
|
/// </summary>
|
||||||
|
public FrameworkElement Control => ItemControl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Заголовок вкладки или элемента.
|
||||||
|
/// </summary>
|
||||||
|
public string Header => ((ILayoutItem)Model).Title;
|
||||||
|
|
||||||
|
public WinUIItemVisual(ILayoutItem model)
|
||||||
|
: base(model)
|
||||||
|
{
|
||||||
|
ItemControl = new WinUIItemControl
|
||||||
|
{
|
||||||
|
ContentId = model.ContentId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
// Здесь можно привязать реальное содержимое по ContentId через Shell/Service.
|
||||||
|
// Например:
|
||||||
|
// ItemControl.Content = resolver(((ILayoutItem)Model).ContentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
ItemControl.Content = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
82
Lattice.Layout.UI.WinUI/Visuals/WinUISplitVisual.cs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
using Lattice.Layout.Abstractions;
|
||||||
|
using Lattice.Layout.UI.WinUI.Controls;
|
||||||
|
using Lattice.Layout.UI.WinUI.Docking;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Lattice.Layout.UI.WinUI.Visuals;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Визуальное представление сплит-элемента для WinUI.
|
||||||
|
/// Управляет жизненным циклом соответствующего <see cref="WinUISplitControl"/>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class WinUISplitVisual : LayoutVisual, IWinUIVisual
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Реальный WinUI-контрол, отображающий сплит.
|
||||||
|
/// </summary>
|
||||||
|
public WinUISplitControl SplitControl { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Контрол, используемый рендерером и докингом.
|
||||||
|
/// </summary>
|
||||||
|
public FrameworkElement Control => SplitControl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Дочерние визуальные элементы.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<ILayoutVisual> Children { get; }
|
||||||
|
|
||||||
|
public WinUISplitVisual(ILayoutSplit model, IReadOnlyList<ILayoutVisual> children)
|
||||||
|
: base(model)
|
||||||
|
{
|
||||||
|
Children = children;
|
||||||
|
|
||||||
|
SplitControl = new WinUISplitControl
|
||||||
|
{
|
||||||
|
LayoutOrientation = model.Orientation switch
|
||||||
|
{
|
||||||
|
Lattice.Layout.Abstractions.Orientation.Horizontal => Microsoft.UI.Xaml.Controls.Orientation.Horizontal,
|
||||||
|
Lattice.Layout.Abstractions.Orientation.Vertical => Microsoft.UI.Xaml.Controls.Orientation.Vertical,
|
||||||
|
_ => Microsoft.UI.Xaml.Controls.Orientation.Horizontal
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
SplitControl.Children.Clear();
|
||||||
|
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
child.Attach();
|
||||||
|
|
||||||
|
switch (child)
|
||||||
|
{
|
||||||
|
case WinUISplitVisual splitVisual:
|
||||||
|
SplitControl.Children.Add(splitVisual.Control);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WinUIGroupVisual groupVisual:
|
||||||
|
SplitControl.Children.Add(groupVisual.Control);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WinUIItemVisual itemVisual:
|
||||||
|
SplitControl.Children.Add(itemVisual.Control);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SplitControl.RebuildGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
foreach (var child in Children)
|
||||||
|
child.Detach();
|
||||||
|
|
||||||
|
SplitControl.Children.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
147
Lattice.Serialization.Docking.Json/JsonLayoutSerializer.cs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
using Lattice.Core.Docking.Abstractions;
|
||||||
|
using Lattice.Core.Docking.Engine;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Lattice.Serialization.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Реализация сериализатора макета док-системы в формате JSON с использованием System.Text.Json.
|
||||||
|
/// Предоставляет высокопроизводительную сериализацию и десериализацию с поддержкой
|
||||||
|
/// опций кастомизации через <see cref="JsonSerializerOptions"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Этот сериализатор использует преобразователь <see cref="LayoutConverter"/> для
|
||||||
|
/// трансформации между объектной моделью и DTO, что обеспечивает независимость
|
||||||
|
/// формата JSON от внутренней структуры данных.
|
||||||
|
/// </remarks>
|
||||||
|
public class JsonLayoutSerializer : ILayoutSerializer
|
||||||
|
{
|
||||||
|
private readonly JsonSerializerOptions _options;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый экземпляр JSON-сериализатора с настройками по умолчанию.
|
||||||
|
/// </summary>
|
||||||
|
public JsonLayoutSerializer() : this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инициализирует новый экземпляр JSON-сериализатора с указанными опциями.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">
|
||||||
|
/// Опции сериализации JSON. Если не указаны, используются стандартные опции.
|
||||||
|
/// </param>
|
||||||
|
public JsonLayoutSerializer(JsonSerializerOptions? options)
|
||||||
|
{
|
||||||
|
_options = options ?? CreateDefaultOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string FormatId => "json";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string MimeType => "application/json";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string DefaultFileExtension => ".json";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public byte[] Serialize(LayoutManager manager)
|
||||||
|
{
|
||||||
|
if (manager == null)
|
||||||
|
throw new ArgumentNullException(nameof(manager));
|
||||||
|
|
||||||
|
var dto = LayoutConverter.ConvertToDto(manager);
|
||||||
|
return JsonSerializer.SerializeToUtf8Bytes(dto, _options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string SerializeToString(LayoutManager manager)
|
||||||
|
{
|
||||||
|
if (manager == null)
|
||||||
|
throw new ArgumentNullException(nameof(manager));
|
||||||
|
|
||||||
|
var dto = LayoutConverter.ConvertToDto(manager);
|
||||||
|
return JsonSerializer.Serialize(dto, _options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void SerializeToStream(LayoutManager manager, Stream stream)
|
||||||
|
{
|
||||||
|
if (manager == null)
|
||||||
|
throw new ArgumentNullException(nameof(manager));
|
||||||
|
if (stream == null)
|
||||||
|
throw new ArgumentNullException(nameof(stream));
|
||||||
|
|
||||||
|
var dto = LayoutConverter.ConvertToDto(manager);
|
||||||
|
JsonSerializer.Serialize(stream, dto, _options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Deserialize(LayoutManager manager, byte[] data, Func<string, IDockContent?> contentResolver)
|
||||||
|
{
|
||||||
|
if (manager == null)
|
||||||
|
throw new ArgumentNullException(nameof(manager));
|
||||||
|
if (data == null)
|
||||||
|
throw new ArgumentNullException(nameof(data));
|
||||||
|
|
||||||
|
var dto = JsonSerializer.Deserialize<LayoutDto>(data, _options);
|
||||||
|
if (dto == null)
|
||||||
|
throw new InvalidOperationException("Failed to deserialize layout data");
|
||||||
|
|
||||||
|
LayoutConverter.RestoreFromDto(manager, dto, contentResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void DeserializeFromString(LayoutManager manager, string serializedData,
|
||||||
|
Func<string, IDockContent?> contentResolver)
|
||||||
|
{
|
||||||
|
if (manager == null)
|
||||||
|
throw new ArgumentNullException(nameof(manager));
|
||||||
|
if (serializedData == null)
|
||||||
|
throw new ArgumentNullException(nameof(serializedData));
|
||||||
|
|
||||||
|
var dto = JsonSerializer.Deserialize<LayoutDto>(serializedData, _options);
|
||||||
|
if (dto == null)
|
||||||
|
throw new InvalidOperationException("Failed to deserialize layout string");
|
||||||
|
|
||||||
|
LayoutConverter.RestoreFromDto(manager, dto, contentResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void DeserializeFromStream(LayoutManager manager, Stream stream,
|
||||||
|
Func<string, IDockContent?> contentResolver)
|
||||||
|
{
|
||||||
|
if (manager == null)
|
||||||
|
throw new ArgumentNullException(nameof(manager));
|
||||||
|
if (stream == null)
|
||||||
|
throw new ArgumentNullException(nameof(stream));
|
||||||
|
|
||||||
|
var dto = JsonSerializer.Deserialize<LayoutDto>(stream, _options);
|
||||||
|
if (dto == null)
|
||||||
|
throw new InvalidOperationException("Failed to deserialize layout stream");
|
||||||
|
|
||||||
|
LayoutConverter.RestoreFromDto(manager, dto, contentResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создает стандартные опции сериализации JSON.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Экземпляр <see cref="JsonSerializerOptions"/> с настройками по умолчанию.</returns>
|
||||||
|
private static JsonSerializerOptions CreateDefaultOptions()
|
||||||
|
{
|
||||||
|
return new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
Converters = { new JsonStringEnumConverter() },
|
||||||
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
|
ReferenceHandler = ReferenceHandler.IgnoreCycles,
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
AllowTrailingCommas = true,
|
||||||
|
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||||
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
48
Lattice.Serialization.Docking.Json/JsonSerializerOptions.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Lattice.Serialization.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Предоставляет предварительно настроенные опции сериализации JSON для различных сценариев.
|
||||||
|
/// </summary>
|
||||||
|
public static class JsonSerializerOptionsPresets
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает опции для красивого форматирования с отступами (для конфигурационных файлов).
|
||||||
|
/// </summary>
|
||||||
|
public static JsonSerializerOptions PrettyPrint => new()
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() },
|
||||||
|
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
|
||||||
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает опции для компактного форматирования (для сетевой передачи).
|
||||||
|
/// </summary>
|
||||||
|
public static JsonSerializerOptions Compact => new()
|
||||||
|
{
|
||||||
|
WriteIndented = false,
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() },
|
||||||
|
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault,
|
||||||
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает опции для строгого форматирования (для валидации схемы).
|
||||||
|
/// </summary>
|
||||||
|
public static JsonSerializerOptions Strict => new()
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() },
|
||||||
|
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.Never,
|
||||||
|
IgnoreReadOnlyProperties = false,
|
||||||
|
IgnoreReadOnlyFields = true,
|
||||||
|
IncludeFields = false,
|
||||||
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Default
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Lattice.Serialization.Docking\Lattice.Serialization.Docking.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
37
Lattice.Serialization.Docking/DTO/AutoHidePanelDto.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
namespace Lattice.Serialization.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO для автоскрываемой панели (AutoHidePanel).
|
||||||
|
/// </summary>
|
||||||
|
public class AutoHidePanelDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Уникальный идентификатор панели.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ссылка на контент панели.
|
||||||
|
/// </summary>
|
||||||
|
public ContentReferenceDto Content { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сторона прикрепления в виде строки.
|
||||||
|
/// </summary>
|
||||||
|
public string Side { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Размер панели в пикселях.
|
||||||
|
/// </summary>
|
||||||
|
public double Size { get; set; } = 300;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Показывает, видима ли панель.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsVisible { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Смещение для анимации (0.0 - 1.0).
|
||||||
|
/// </summary>
|
||||||
|
public double SlideOffset { get; set; } = 0.0;
|
||||||
|
}
|
||||||
32
Lattice.Serialization.Docking/DTO/ContentReferenceDto.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
namespace Lattice.Serialization.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO для ссылки на контент без сериализации самого контента.
|
||||||
|
/// </summary>
|
||||||
|
public class ContentReferenceDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Уникальный идентификатор контента.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Тип контента (для восстановления через ContentRegistry).
|
||||||
|
/// </summary>
|
||||||
|
public string TypeId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Отображаемое название контента.
|
||||||
|
/// </summary>
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Показывает, можно ли закрыть контент.
|
||||||
|
/// </summary>
|
||||||
|
public bool CanClose { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Дополнительные свойства контента для восстановления состояния.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, object?> Properties { get; set; } = new();
|
||||||
|
}
|
||||||
37
Lattice.Serialization.Docking/DTO/ElementDto.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
namespace Lattice.Serialization.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Базовый DTO для элементов дерева компоновки.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class ElementDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Уникальный идентификатор элемента.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Тип элемента (для десериализации).
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ширина элемента.
|
||||||
|
/// </summary>
|
||||||
|
public double Width { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Высота элемента.
|
||||||
|
/// </summary>
|
||||||
|
public double Height { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Минимальная ширина элемента.
|
||||||
|
/// </summary>
|
||||||
|
public double MinWidth { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Минимальная высота элемента.
|
||||||
|
/// </summary>
|
||||||
|
public double MinHeight { get; set; }
|
||||||
|
}
|
||||||
27
Lattice.Serialization.Docking/DTO/GroupDto.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
namespace Lattice.Serialization.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO для группы разделения (DockGroup).
|
||||||
|
/// </summary>
|
||||||
|
public class GroupDto : ElementDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Первый дочерний элемент (левая или верхняя область).
|
||||||
|
/// </summary>
|
||||||
|
public ElementDto First { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Второй дочерний элемент (правая или нижняя область).
|
||||||
|
/// </summary>
|
||||||
|
public ElementDto Second { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Направление разделения в виде строки.
|
||||||
|
/// </summary>
|
||||||
|
public string Orientation { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Соотношение разделения между первым и вторым элементами (0.0 - 1.0).
|
||||||
|
/// </summary>
|
||||||
|
public double SplitRatio { get; set; } = 0.5;
|
||||||
|
}
|
||||||
47
Lattice.Serialization.Docking/DTO/LayoutDto.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
namespace Lattice.Serialization.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data Transfer Object (DTO) для сериализации состояния макета док-системы.
|
||||||
|
/// Содержит все необходимые данные для сохранения и восстановления состояния макета.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Этот DTO является независимым от формата сериализации (JSON, XML, Binary) и используется
|
||||||
|
/// как промежуточное представление между объектной моделью и сериализованными данными.
|
||||||
|
/// </remarks>
|
||||||
|
public class LayoutDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Версия формата DTO для контроля совместимости.
|
||||||
|
/// </summary>
|
||||||
|
public string Version { get; set; } = "1.0";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Дата и время создания DTO в UTC.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Идентификатор приложения, создавшего DTO.
|
||||||
|
/// </summary>
|
||||||
|
public string? ApplicationId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Корневой элемент дерева компоновки.
|
||||||
|
/// </summary>
|
||||||
|
public ElementDto? Root { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Список плавающих окон.
|
||||||
|
/// </summary>
|
||||||
|
public List<WindowDto> FloatingWindows { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Список автоскрываемых панелей.
|
||||||
|
/// </summary>
|
||||||
|
public List<AutoHidePanelDto> AutoHidePanels { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Дополнительные метаданные, специфичные для приложения.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, string> Metadata { get; set; } = new();
|
||||||
|
}
|
||||||
22
Lattice.Serialization.Docking/DTO/LeafDto.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
namespace Lattice.Serialization.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO для контейнера вкладок (DockLeaf).
|
||||||
|
/// </summary>
|
||||||
|
public class LeafDto : ElementDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Список ссылок на контент, содержащийся в листе.
|
||||||
|
/// </summary>
|
||||||
|
public List<ContentReferenceDto> Contents { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Идентификатор активного контента (если есть).
|
||||||
|
/// </summary>
|
||||||
|
public string? ActiveContentId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Расположение вкладок в виде строки.
|
||||||
|
/// </summary>
|
||||||
|
public string TabPlacement { get; set; } = "Bottom";
|
||||||
|
}
|
||||||
52
Lattice.Serialization.Docking/DTO/WindowDto.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
namespace Lattice.Serialization.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO для плавающего окна (DockWindow).
|
||||||
|
/// </summary>
|
||||||
|
public class WindowDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Уникальный идентификатор окна.
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Позиция X окна на экране.
|
||||||
|
/// </summary>
|
||||||
|
public double X { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Позиция Y окна на экране.
|
||||||
|
/// </summary>
|
||||||
|
public double Y { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ширина окна.
|
||||||
|
/// </summary>
|
||||||
|
public double Width { get; set; } = 800;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Высота окна.
|
||||||
|
/// </summary>
|
||||||
|
public double Height { get; set; } = 600;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Заголовок окна.
|
||||||
|
/// </summary>
|
||||||
|
public string Title { get; set; } = "Lattice Tool Window";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Корневой элемент макета внутри окна.
|
||||||
|
/// </summary>
|
||||||
|
public ElementDto? Root { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Показывает, видимо ли окно.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsVisible { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Показывает, сфокусировано ли окно.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFocused { get; set; } = false;
|
||||||
|
}
|
||||||
103
Lattice.Serialization.Docking/ILayoutSerializer.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
using Lattice.Core.Docking.Abstractions;
|
||||||
|
using Lattice.Core.Docking.Engine;
|
||||||
|
|
||||||
|
namespace Lattice.Serialization.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Определяет контракт для сериализации и десериализации состояния макета док-системы.
|
||||||
|
/// Этот интерфейс позволяет реализовать различные форматы сериализации (JSON, XML, Binary)
|
||||||
|
/// и различные хранилища (файлы, базы данных, облако) без изменения основной логики.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Реализации этого интерфейса должны преобразовывать объектную модель док-системы
|
||||||
|
/// в промежуточный DTO (<see cref="LayoutDto"/>), который затем сериализуется в целевой формат.
|
||||||
|
/// Это обеспечивает независимость формата сериализации от структуры DTO.
|
||||||
|
/// </remarks>
|
||||||
|
public interface ILayoutSerializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает уникальный идентификатор формата сериализации.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Строковый идентификатор формата, например "json", "xml", "binary".</value>
|
||||||
|
string FormatId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает MIME-тип формата сериализации.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>MIME-тип, например "application/json", "application/xml".</value>
|
||||||
|
string MimeType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает расширение файла по умолчанию для данного формата.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Расширение файла, например ".json", ".xml".</value>
|
||||||
|
string DefaultFileExtension { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сериализует состояние менеджера макета в массив байтов.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manager">Менеджер макета для сериализации.</param>
|
||||||
|
/// <returns>Массив байтов, содержащий сериализованное состояние макета.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Выбрасывается, если <paramref name="manager"/> равен null.</exception>
|
||||||
|
byte[] Serialize(LayoutManager manager);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сериализует состояние менеджера макета в строку.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manager">Менеджер макета для сериализации.</param>
|
||||||
|
/// <returns>Строковое представление состояния макета.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">Выбрасывается, если <paramref name="manager"/> равен null.</exception>
|
||||||
|
string SerializeToString(LayoutManager manager);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сериализует состояние менеджера макета в поток.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manager">Менеджер макета для сериализации.</param>
|
||||||
|
/// <param name="stream">Поток для записи сериализованных данных.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, если <paramref name="manager"/> или <paramref name="stream"/> равен null.
|
||||||
|
/// </exception>
|
||||||
|
void SerializeToStream(LayoutManager manager, Stream stream);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Десериализует состояние макета из массива байтов и восстанавливает его в менеджере.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manager">Менеджер макета для восстановления состояния.</param>
|
||||||
|
/// <param name="data">Массив байтов с сериализованным состоянием макета.</param>
|
||||||
|
/// <param name="contentResolver">
|
||||||
|
/// Функция разрешения контента по идентификатору, используемая для восстановления
|
||||||
|
/// ссылок на контент в десериализованном состоянии.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, если <paramref name="manager"/> или <paramref name="data"/> равен null.
|
||||||
|
/// </exception>
|
||||||
|
void Deserialize(LayoutManager manager, byte[] data, Func<string, IDockContent?> contentResolver);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Десериализует состояние макета из строки и восстанавливает его в менеджере.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manager">Менеджер макета для восстановления состояния.</param>
|
||||||
|
/// <param name="serializedData">Строка с сериализованным состоянием макета.</param>
|
||||||
|
/// <param name="contentResolver">
|
||||||
|
/// Функция разрешения контента по идентификатору, используемая для восстановления
|
||||||
|
/// ссылок на контент в десериализованном состоянии.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, если <paramref name="manager"/> или <paramref name="serializedData"/> равен null.
|
||||||
|
/// </exception>
|
||||||
|
void DeserializeFromString(LayoutManager manager, string serializedData, Func<string, IDockContent?> contentResolver);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Десериализует состояние макета из потока и восстанавливает его в менеджере.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manager">Менеджер макета для восстановления состояния.</param>
|
||||||
|
/// <param name="stream">Поток с сериализованными данными.</param>
|
||||||
|
/// <param name="contentResolver">
|
||||||
|
/// Функция разрешения контента по идентификатору, используемая для восстановления
|
||||||
|
/// ссылок на контент в десериализованном состоянии.
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// Выбрасывается, если <paramref name="manager"/> или <paramref name="stream"/> равен null.
|
||||||
|
/// </exception>
|
||||||
|
void DeserializeFromStream(LayoutManager manager, Stream stream, Func<string, IDockContent?> contentResolver);
|
||||||
|
}
|
||||||
19
Lattice.Serialization.Docking/ISerializableContent.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace Lattice.Serialization.Docking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Интерфейс для контента, поддерживающего сериализацию дополнительного состояния.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISerializableContent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Получает состояние для сериализации.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Словарь свойств и их значений.</returns>
|
||||||
|
Dictionary<string, object?> GetSerializableState();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Восстанавливает состояние из десериализованных данных.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">Словарь свойств и их значений.</param>
|
||||||
|
void RestoreFromState(Dictionary<string, object?> state);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Lattice.Core.Docking\Lattice.Core.Docking.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||