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; } }