using Lattice.Core.Models; using Lattice.Core.Models.Enums; using Lattice.UI.Controls; using Lattice.UI.Services; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; using Windows.Foundation; namespace Lattice.UI.DragDrop; /// /// Обработчик перетаскивания панелей и вкладок для системы Lattice. /// public class DockTabHandler { private bool _isDragging; private readonly LatticeDockHost _host; // Состояние текущей операции перетаскивания private LayoutNode? _sourceNode; private LayoutNode? _targetNode; private DockDirection _currentSide; public DockTabHandler(LatticeDockHost host) { _host = host; } /// /// Привязывает логику перетаскивания к визуальному элементу (заголовку панели). /// /// Элемент, за который пользователь "хватает" панель. /// Узел макета, связанный с этой панелью. public void Attach(FrameworkElement header, LayoutNode node) { header.PointerPressed += (s, e) => { _isDragging = true; _sourceNode = node; header.CapturePointer(e.Pointer); if (_host.AnchorOverlay != null) _host.AnchorOverlay.Visibility = Visibility.Visible; }; header.PointerMoved += (s, e) => { if (!_isDragging) return; // Получаем позицию курсора относительно всего хоста Point pointerPos = e.GetCurrentPoint(_host).Position; UpdateOverlayPosition(pointerPos); }; header.PointerReleased += (s, e) => { if (!_isDragging) return; _isDragging = false; header.ReleasePointerCapture(e.Pointer); CompleteDocking(); }; } /// /// Обновляет положение визуальных подсказок и рассчитывает зоны сброса. /// private void UpdateOverlayPosition(Point pointerPosition) { var overlay = _host.AnchorOverlay; if (overlay == null) return; // 1. Позиционируем "ромб" с кнопками докинга overlay.PositionAnchors(pointerPosition); // 2. Хит-тестинг: ищем LatticePane под курсором (исключая саму перетаскиваемую панель) var elements = VisualTreeHelper.FindElementsInHostCoordinates(pointerPosition, _host); var targetPane = elements.OfType() .FirstOrDefault(p => (p.DataContext as LayoutNode)?.Id != _sourceNode?.Id); if (targetPane != null && targetPane.DataContext is LayoutNode targetNode) { _targetNode = targetNode; // 3. Расчет локальной позиции для определения стороны var transform = targetPane.TransformToVisual(_host); Point localPoint = transform.Inverse.TransformPoint(pointerPosition); // 4. Определяем сторону через сервис _currentSide = VisualTreeService.GetHitZone(targetPane, localPoint); // 5. Показываем синее превью зоны сброса Rect previewRect = CalculatePreviewRect(targetPane, _currentSide); Rect globalPreviewRect = transform.TransformBounds(previewRect); overlay.ShowPreview(globalPreviewRect); } else { _targetNode = null; overlay.HidePreview(); } } /// /// Рассчитывает прямоугольник предпросмотра на основе выбранной стороны. /// private Rect CalculatePreviewRect(FrameworkElement pane, DockDirection side) { double w = pane.ActualWidth; double h = pane.ActualHeight; return side switch { DockDirection.Left => new Rect(0, 0, w / 2, h), DockDirection.Right => new Rect(w / 2, 0, w / 2, h), DockDirection.Top => new Rect(0, 0, w, h / 2), DockDirection.Bottom => new Rect(0, h / 2, w, h / 2), _ => new Rect(0, 0, w, h) // Center }; } /// /// Завершает операцию докинга, передавая данные в Core Engine. /// private void CompleteDocking() { if (_sourceNode != null && _targetNode != null && _host.Service != null) { // Вызываем логику перестроения дерева в Lattice.Core _host.Service.Dock(_sourceNode, _targetNode, _currentSide); } // Очистка UI var overlay = _host.AnchorOverlay; if (overlay != null) { overlay.Visibility = Visibility.Collapsed; overlay.HidePreview(); } _sourceNode = null; _targetNode = null; } }