using Lattice.Core.DragDrop.Abstractions; using Lattice.Core.DragDrop.Models; using Lattice.Core.DragDrop.Services; using Lattice.Core.Geometry; using Lattice.UI.DragDrop.Abstractions; using Lattice.UI.DragDrop.WinUI.Behaviors; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Input; using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; namespace Lattice.UI.DragDrop.WinUI.Integration; /// /// Сервис интеграции Drag & Drop с WinUI приложением. /// public sealed class WinUIDragDropIntegrationService : IDisposable { #region Вложенные типы private sealed class DragSourceAdapter : IDragSource { private readonly FrameworkElement _element; private readonly Func _dragInfoFactory; private readonly IDragDropService _dragDropService; private Point _dragStartPosition; private bool _isDragging; private bool _disposed; public DragSourceAdapter( FrameworkElement element, Func dragInfoFactory, IDragDropService dragDropService) { _element = element ?? throw new ArgumentNullException(nameof(element)); _dragInfoFactory = dragInfoFactory ?? throw new ArgumentNullException(nameof(dragInfoFactory)); _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); SubscribeToEvents(); } private void SubscribeToEvents() { _element.PointerPressed += OnPointerPressed; _element.PointerMoved += OnPointerMoved; _element.PointerReleased += OnPointerReleased; _element.PointerCanceled += OnPointerCanceled; _element.PointerCaptureLost += OnPointerCaptureLost; } private void UnsubscribeFromEvents() { _element.PointerPressed -= OnPointerPressed; _element.PointerMoved -= OnPointerMoved; _element.PointerReleased -= OnPointerReleased; _element.PointerCanceled -= OnPointerCanceled; _element.PointerCaptureLost -= OnPointerCaptureLost; } private void OnPointerPressed(object sender, PointerRoutedEventArgs e) { if (_isDragging || _disposed) return; var point = e.GetCurrentPoint(_element); _dragStartPosition = new Point(point.Position.X, point.Position.Y); } private void OnPointerMoved(object sender, PointerRoutedEventArgs e) { if (_isDragging || _disposed) return; var currentPoint = e.GetCurrentPoint(_element); var currentPosition = new Point(currentPoint.Position.X, currentPoint.Position.Y); var distance = CalculateDistance(_dragStartPosition, currentPosition); if (distance > _dragDropService.DragStartThreshold) { StartDragOperation(currentPosition); } } private async Task StartDragOperation(Point position) { try { var dragInfo = _dragInfoFactory(); // Обновляем позицию в dragInfo var screenPosition = GetScreenPosition(position); dragInfo = new DragInfo( dragInfo.Data, dragInfo.AllowedEffects, screenPosition, dragInfo.Source ); _isDragging = await _dragDropService.StartDragAsync(this, screenPosition); } catch (Exception ex) { Debug.WriteLine($"Error starting drag: {ex.Message}"); _isDragging = false; } } private Point GetScreenPosition(Point relativePosition) { var transform = _element.TransformToVisual(null); var point = transform.TransformPoint(new Windows.Foundation.Point(relativePosition.X, relativePosition.Y)); return new Point(point.X, point.Y); } private double CalculateDistance(Point p1, Point p2) { var dx = p2.X - p1.X; var dy = p2.Y - p1.Y; return Math.Sqrt(dx * dx + dy * dy); } private async void OnPointerReleased(object sender, PointerRoutedEventArgs e) { if (!_isDragging || _disposed) return; await _dragDropService.EndDragAsync(); _isDragging = false; } private void OnPointerCanceled(object sender, PointerRoutedEventArgs e) { if (!_isDragging || _disposed) return; _dragDropService.CancelDragAsync(); _isDragging = false; } private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e) { if (!_isDragging || _disposed) return; _dragDropService.CancelDragAsync(); _isDragging = false; } #region IDragSource Implementation public async Task<(bool, DragInfo? dragInfo)> CancelDragAsync() { try { dragInfo = _dragInfoFactory(); return dragInfo != null; } catch { dragInfo = null; return false; } } public async Task StartDragAsync(DragInfo dragInfo) { return true; } public async Task DragCompletedAsync(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects) { _isDragging = false; } public async Task DragCancelledAsync(DragInfo dragInfo) { _isDragging = false; } #endregion public void Dispose() { if (_disposed) return; UnsubscribeFromEvents(); _disposed = true; GC.SuppressFinalize(this); } public Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync() { throw new NotImplementedException(); } } #endregion #region Поля private readonly IDragDropService _dragDropService; private readonly IDragVisualProvider _dragVisualProvider; private readonly IDragDropHost _dragDropHost; private readonly Dictionary _dragSources = new(); private readonly Dictionary _dropTargets = new(); private bool _disposed; #endregion #region Конструктор public WinUIDragDropIntegrationService( IDragDropService dragDropService, IDragVisualProvider dragVisualProvider, IDragDropHost dragDropHost) { _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); _dragVisualProvider = dragVisualProvider ?? throw new ArgumentNullException(nameof(dragVisualProvider)); _dragDropHost = dragDropHost ?? throw new ArgumentNullException(nameof(dragDropHost)); } #endregion #region Публичные методы /// /// Регистрирует элемент как источник перетаскивания. /// public void RegisterDragSource(FrameworkElement element, Func dragInfoFactory) { if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService)); ArgumentNullException.ThrowIfNull(element); ArgumentNullException.ThrowIfNull(dragInfoFactory); if (_dragSources.ContainsKey(element)) return; var adapter = new DragSourceAdapter(element, dragInfoFactory, _dragDropService); _dragSources[element] = adapter; } /// /// Регистрирует элемент как источник перетаскивания с данными. /// public void RegisterDragSource(FrameworkElement element, object dragData) { RegisterDragSource(element, () => { var position = GetScreenPosition(element, new Point(0, 0)); return new DragInfo( dragData, Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move, position, element ); }); } /// /// Регистрирует элемент как цель сброса. /// public void RegisterDropTarget(FrameworkElement element) { if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService)); ArgumentNullException.ThrowIfNull(element); if (_dropTargets.ContainsKey(element)) return; var behavior = new WinUIDropTargetBehavior(ServiceProviderHelper.GetServiceProvider()) { AssociatedElement = element }; _dropTargets[element] = behavior; // Настраиваем события WinUI element.AllowDrop = true; element.DragEnter += OnDragEnter; element.DragOver += OnDragOver; element.DragLeave += OnDragLeave; element.Drop += OnDrop; } private Point GetScreenPosition(FrameworkElement element, Point relativePoint) { var transform = element.TransformToVisual(null); var point = transform.TransformPoint(new Windows.Foundation.Point(relativePoint.X, relativePoint.Y)); return new Point(point.X, point.Y); } private void OnDragEnter(object sender, DragEventArgs e) { if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior)) { var position = GetScreenPosition(element, e.GetPosition(element).ToCorePoint()); var dropInfo = CreateDropInfo(e, position, element); if (behavior.CanAcceptDrop(dropInfo)) { e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy; behavior.DragOver(dropInfo); } else { e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.None; } } } private void OnDragOver(object sender, DragEventArgs e) { if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior)) { var position = GetScreenPosition(element, e.GetPosition(element).ToCorePoint()); var dropInfo = CreateDropInfo(e, position, element); if (behavior.CanAcceptDrop(dropInfo)) { e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy; behavior.DragOver(dropInfo); } } } private void OnDragLeave(object sender, DragEventArgs e) { if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior)) { behavior.DragLeave(); } } private void OnDrop(object sender, DragEventArgs e) { if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior)) { var position = GetScreenPosition(element, e.GetPosition(element).ToCorePoint()); var dropInfo = CreateDropInfo(e, position, element); behavior.Drop(dropInfo); } } private DropInfo CreateDropInfo(DragEventArgs e, Point position, FrameworkElement target) { // Извлекаем данные из DragEventArgs object? data = null; // В реальной реализации нужно извлечь данные из e.DataView // Это упрощенная версия if (e.DataView.Properties.TryGetValue("DragData", out var dragData)) { data = dragData; } return new DropInfo( data: data, position: position, allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move, target: target ); } /// /// Удаляет регистрацию элемента как источника. /// public void UnregisterDragSource(FrameworkElement element) { if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService)); if (_dragSources.Remove(element, out var adapter)) { adapter.Dispose(); } } /// /// Удаляет регистрацию элемента как цели. /// public void UnregisterDropTarget(FrameworkElement element) { if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService)); if (_dropTargets.Remove(element, out var behavior)) { behavior.Detach(); element.AllowDrop = false; element.DragEnter -= OnDragEnter; element.DragOver -= OnDragOver; element.DragLeave -= OnDragLeave; element.Drop -= OnDrop; } } #endregion #region IDisposable public void Dispose() { if (_disposed) return; foreach (var adapter in _dragSources.Values) { adapter.Dispose(); } _dragSources.Clear(); foreach (var behavior in _dropTargets.Values) { behavior.Detach(); } _dropTargets.Clear(); _disposed = true; GC.SuppressFinalize(this); } #endregion } /// /// Методы расширения для преобразования координат. /// internal static class PointExtensions { public static Point ToCorePoint(this Windows.Foundation.Point point) { return new Point(point.X, point.Y); } }