using Lattice.Core.Docking.Abstractions;
using Lattice.Core.Docking.Models;
using System.Collections.ObjectModel;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Lattice.Serialization.Docking")]
namespace Lattice.Core.Docking.Engine;
///
/// Расширенный менеджер макета, поддерживающий автоскрываемые панели, группы документов
/// и расширенные операции управления макетом.
///
///
/// Этот класс является центральным координатором всей док-системы, управляя деревом компоновки,
/// плавающими окнами, автоскрываемыми панелями и предоставляя API для манипуляции макетом.
///
public class LayoutManager
{
private readonly ObservableCollection _autoHidePanels = new();
///
/// Корневой элемент главного окна IDE.
///
public IDockElement? Root { get; internal set; }
///
/// Список активных плавающих окон.
///
public List FloatingWindows { get; } = new();
///
/// Коллекция автоскрываемых панелей.
///
public ReadOnlyObservableCollection AutoHidePanels { get; }
///
/// Реестр типов контента (опционально).
///
public Services.ContentRegistry? ContentRegistry { get; set; }
///
/// Уведомляет UI, что структура дерева изменилась.
///
public event Action? LayoutUpdated;
///
/// Уведомляет об изменении в коллекции автоскрываемых панелей.
///
public event EventHandler? AutoHidePanelsChanged;
///
/// Событие, возникающее при операции перетаскивания элемента.
///
public event EventHandler? DragDropOperation;
///
/// Инициализирует новый экземпляр менеджера макета.
///
public LayoutManager()
{
AutoHidePanels = new ReadOnlyObservableCollection(_autoHidePanels);
}
///
/// Добавляет автоскрываемую панель.
///
/// Содержимое панели.
/// Сторона для прикрепления.
/// Созданная автоскрываемая панель.
public AutoHidePanel AddAutoHidePanel(IDockContent content, DockSide side)
{
var panel = new AutoHidePanel(content, side);
_autoHidePanels.Add(panel);
AutoHidePanelsChanged?.Invoke(this, EventArgs.Empty);
return panel;
}
///
/// Удаляет автоскрываемую панель.
///
/// Панель для удаления.
public void RemoveAutoHidePanel(AutoHidePanel panel)
{
if (_autoHidePanels.Remove(panel))
{
AutoHidePanelsChanged?.Invoke(this, EventArgs.Empty);
}
}
///
/// Создает документ из зарегистрированного типа контента.
///
/// Идентификатор типа контента.
/// Уникальный идентификатор документа.
/// Созданный контент или null, если ContentRegistry не установлен.
public IDockContent? CreateDocument(string contentTypeId, string id)
{
if (ContentRegistry == null || !ContentRegistry.IsRegistered(contentTypeId))
return null;
return ContentRegistry.CreateContent(contentTypeId, id);
}
///
/// Основной метод перемещения элементов в макете.
///
/// Что перетаскиваем.
/// Куда приземляем.
/// Позиция относительно цели.
///
/// Если true, контент будет добавлен как документ в центральную область.
///
public void Move(IDockElement source, IDockElement? target, DockPosition position, bool asDocument = false)
{
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;
// 2. Вставляем в цель
if (target == null)
{
FloatingWindows.Add(new DockWindow { Root = source as IDockElement });
}
else
{
if (IsDescendantOf(target, Root))
{
Root = DockOperations.Insert(target, source, position, Root!);
}
else
{
InsertIntoFloatingWindow(target, source, position);
}
}
LayoutUpdated?.Invoke();
}
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;
}
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;
}
}
}
private bool IsDescendantOf(IDockElement element, IDockElement ancestor)
{
if (element == ancestor) return true;
if (ancestor is DockGroup group)
return IsDescendantOf(element, group.First) || IsDescendantOf(element, group.Second);
return false;
}
/// Поиск элемента по ID во всех окнах.
public IDockElement? FindById(string id)
{
var found = FindRecursive(Root, id);
if (found != null) return found;
foreach (var win in FloatingWindows)
{
found = FindRecursive(win.Root, id);
if (found != null) return found;
}
return null;
}
private IDockElement? FindRecursive(IDockElement? node, string id)
{
if (node == null || node.Id == id) return node;
if (node is DockGroup g) return FindRecursive(g.First, id) ?? FindRecursive(g.Second, id);
return null;
}
///
/// Сбрасывает макет к состоянию по умолчанию.
///
public void Reset()
{
Root = null;
FloatingWindows.Clear();
_autoHidePanels.Clear();
LayoutUpdated?.Invoke();
AutoHidePanelsChanged?.Invoke(this, EventArgs.Empty);
}
///
/// Обрабатывает операцию перетаскивания между элементами.
///
/// Источник перетаскивания.
/// Цель сброса.
/// Позиция сброса относительно цели.
/// Данные перетаскивания.
/// true, если операция успешно выполнена; иначе false.
public bool HandleDragDrop(IDockElement source, IDockElement target,
DockPosition position, object data)
{
try
{
if (source == target)
return false;
// Определяем тип операции на основе данных
if (data is ContentDragData contentData)
{
return HandleContentDragDrop(contentData, target, position);
}
else if (data is DockElementDragData elementData)
{
return HandleElementDragDrop(elementData, target, position);
}
return false;
}
catch (Exception ex)
{
DragDropOperation?.Invoke(this, new DragDropEventArgs(
source, target, position, false, ex.Message));
return false;
}
}
private bool HandleContentDragDrop(ContentDragData data, IDockElement target, DockPosition position)
{
// Находим исходный контейнер с контентом
var sourceContainer = FindElementById(data.ElementId) as IDockContainer;
if (sourceContainer == null)
return false;
// Находим контент
var content = sourceContainer.Children.FirstOrDefault(c => c.Id == data.ContentId);
if (content == null)
return false;
if (target is IDockContainer targetContainer && position == DockPosition.Center)
{
// Объединение вкладок
sourceContainer.RemoveContent(content);
targetContainer.AddContent(content);
DragDropOperation?.Invoke(this, new DragDropEventArgs(
sourceContainer as IDockElement ?? sourceContainer as IDockElement,
target, position, true, "Content merged"));
return true;
}
return false;
}
private bool HandleElementDragDrop(DockElementDragData data, IDockElement target, DockPosition position)
{
// Находим перетаскиваемый элемент
var sourceElement = FindElementById(data.ElementId);
if (sourceElement == null)
return false;
// Выполняем перемещение
Move(sourceElement, target, position);
DragDropOperation?.Invoke(this, new DragDropEventArgs(
sourceElement, target, position, true, "Element moved"));
return true;
}
///
/// Находит элемент по идентификатору.
///
public IDockElement? FindElementById(string id)
{
return FindElementByIdRecursive(Root, id) ??
FloatingWindows.Select(w => FindElementByIdRecursive(w.Root, id))
.FirstOrDefault(result => result != null);
}
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;
}
}
///
/// Аргументы события операции перетаскивания.
///
public class DragDropEventArgs : EventArgs
{
/// Источник перетаскивания.
public IDockElement Source { get; }
/// Цель сброса.
public IDockElement Target { get; }
/// Позиция сброса.
public DockPosition Position { get; }
/// Показывает, была ли операция успешной.
public bool Success { get; }
/// Сообщение о результате операции.
public string Message { get; }
/// Время выполнения операции.
public DateTime Timestamp { get; }
public DragDropEventArgs(IDockElement source, IDockElement target,
DockPosition position, bool success, string message)
{
Source = source;
Target = target;
Position = position;
Success = success;
Message = message;
Timestamp = DateTime.UtcNow;
}
}