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 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 : IDisposable { private readonly UIElement _element; private readonly Func> _dragInfoFactory; private readonly IDragDropService _dragDropService; private Point _dragStartPosition; private bool _isDragging; private bool _disposed; private object? _dragData; public DragSourceAdapter( UIElement 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(); } public DragSourceAdapter( UIElement element, object dragData, IDragDropService dragDropService) { _element = element ?? throw new ArgumentNullException(nameof(element)); _dragData = dragData ?? throw new ArgumentNullException(nameof(dragData)); _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); _dragInfoFactory = async () => new DragInfo( _dragData, Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move, Point.Zero, _element); 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 async 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) { await StartDragAsync(currentPosition); } } private async Task StartDragAsync(Point position) { try { var dragInfo = await _dragInfoFactory(); var screenPosition = GetScreenPosition(position); _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; var point = e.GetCurrentPoint(_element); var position = GetScreenPosition(new Point(point.Position.X, point.Position.Y)); await _dragDropService.EndDragAsync(position); _isDragging = false; } private async void OnPointerCanceled(object sender, PointerRoutedEventArgs e) { if (!_isDragging || _disposed) return; await _dragDropService.CancelDragAsync(); _isDragging = false; } private async void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e) { if (!_isDragging || _disposed) return; await _dragDropService.CancelDragAsync(); _isDragging = false; } public void Dispose() { if (_disposed) return; UnsubscribeFromEvents(); if (_isDragging) { Task.Run(async () => await _dragDropService.CancelDragAsync()).Wait(1000); } _disposed = true; GC.SuppressFinalize(this); } } private sealed class DropTargetAdapter : IDisposable { private readonly UIElement _element; private readonly IDropTarget _dropTarget; private readonly IDragDropService _dragDropService; private string? _registrationId; private bool _disposed; public DropTargetAdapter( UIElement element, IDropTarget dropTarget, IDragDropService dragDropService) { _element = element ?? throw new ArgumentNullException(nameof(element)); _dropTarget = dropTarget ?? throw new ArgumentNullException(nameof(dropTarget)); _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); SubscribeToEvents(); UpdateRegistration(); } private void SubscribeToEvents() { _element.LayoutUpdated += OnLayoutUpdated; _element.Unloaded += OnElementUnloaded; } private void UnsubscribeFromEvents() { _element.LayoutUpdated -= OnLayoutUpdated; _element.Unloaded -= OnElementUnloaded; } private void UpdateRegistration() { var bounds = GetElementBounds(); if (_registrationId is null) { _registrationId = _dragDropService.RegisterDropTarget(_dropTarget, bounds); } else { _dragDropService.UpdateDropTargetBounds(_registrationId, bounds); } } private Rect GetElementBounds() { var transform = _element.TransformToVisual(null); var topLeft = transform.TransformPoint(new Windows.Foundation.Point(0, 0)); var bottomRight = transform.TransformPoint(new Windows.Foundation.Point(_element.ActualWidth, _element.ActualHeight)); return new Rect( topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); } private void OnLayoutUpdated(object? sender, object e) { if (_disposed) return; UpdateRegistration(); } private void OnElementUnloaded(object sender, RoutedEventArgs e) { Dispose(); } public void Dispose() { if (_disposed) return; UnsubscribeFromEvents(); if (_registrationId is not null) { _dragDropService.UnregisterDropTarget(_registrationId); _registrationId = null; } if (_dropTarget is IDisposable disposable) disposable.Dispose(); _disposed = true; GC.SuppressFinalize(this); } } #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 readonly Window _window; private bool _disposed; #endregion #region Конструктор /// /// Инициализирует новый экземпляр. /// public WinUIDragDropIntegrationService( Window window, IDragDropService dragDropService, IDragVisualProvider dragVisualProvider, IDragDropHost dragDropHost) { _window = window ?? throw new ArgumentNullException(nameof(window)); _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); _dragVisualProvider = dragVisualProvider ?? throw new ArgumentNullException(nameof(dragVisualProvider)); _dragDropHost = dragDropHost ?? throw new ArgumentNullException(nameof(dragDropHost)); InitializeEvents(); } #endregion #region Публичные методы /// /// Регистрирует элемент как источник перетаскивания. /// public void RegisterDragSource(UIElement element, Func> dragInfoFactory) { ThrowIfDisposed(); ArgumentNullException.ThrowIfNull(element); ArgumentNullException.ThrowIfNull(dragInfoFactory); if (_dragSources.ContainsKey(element)) return; var adapter = new DragSourceAdapter(element, dragInfoFactory, _dragDropService); _dragSources[element] = adapter; } /// /// Регистрирует элемент как источник перетаскивания с данными. /// public void RegisterDragSource(UIElement element, object dragData) { ThrowIfDisposed(); ArgumentNullException.ThrowIfNull(element); ArgumentNullException.ThrowIfNull(dragData); if (_dragSources.ContainsKey(element)) return; var adapter = new DragSourceAdapter(element, dragData, _dragDropService); _dragSources[element] = adapter; } /// /// Регистрирует элемент как цель сброса. /// public void RegisterDropTarget(UIElement element, IDropTarget dropTarget) { ThrowIfDisposed(); ArgumentNullException.ThrowIfNull(element); ArgumentNullException.ThrowIfNull(dropTarget); if (_dropTargets.ContainsKey(element)) return; var adapter = new DropTargetAdapter(element, dropTarget, _dragDropService); _dropTargets[element] = adapter; } /// /// Удаляет регистрацию элемента как источника. /// public void UnregisterDragSource(UIElement element) { ThrowIfDisposed(); if (_dragSources.Remove(element, out var adapter)) { adapter.Dispose(); } } /// /// Удаляет регистрацию элемента как цели. /// public void UnregisterDropTarget(UIElement element) { ThrowIfDisposed(); if (_dropTargets.Remove(element, out var adapter)) { adapter.Dispose(); } } #endregion #region Обработка событий private void InitializeEvents() { _dragDropService.DragStarted += OnDragStarted; _dragDropService.DragUpdated += OnDragUpdated; _dragDropService.DragCompleted += OnDragCompleted; _dragDropService.DragCancelled += OnDragCancelled; _dragDropService.ErrorOccurred += OnErrorOccurred; } private void UnsubscribeFromEvents() { _dragDropService.DragStarted -= OnDragStarted; _dragDropService.DragUpdated -= OnDragUpdated; _dragDropService.DragCompleted -= OnDragCompleted; _dragDropService.DragCancelled -= OnDragCancelled; _dragDropService.ErrorOccurred -= OnErrorOccurred; } private void OnDragStarted(object? sender, DragStartedEventArgs e) { try { var visual = _dragVisualProvider.CreateDragVisual(e.DragInfo, e.StartPosition); _dragDropHost.ShowDragVisual(visual, e.StartPosition); } catch (Exception ex) { Debug.WriteLine($"Error in OnDragStarted: {ex.Message}"); } } private void OnDragUpdated(object? sender, DragUpdatedEventArgs e) { try { // Обновление визуала будет через хост } catch (Exception ex) { Debug.WriteLine($"Error in OnDragUpdated: {ex.Message}"); } } private void OnDragCompleted(object? sender, DragCompletedEventArgs e) { try { _dragDropHost.HideDragVisual(null!); // В реальности нужно передать конкретный визуал } catch (Exception ex) { Debug.WriteLine($"Error in OnDragCompleted: {ex.Message}"); } } private void OnDragCancelled(object? sender, DragCancelledEventArgs e) { try { _dragDropHost.HideDragVisual(null!); } catch (Exception ex) { Debug.WriteLine($"Error in OnDragCancelled: {ex.Message}"); } } private void OnErrorOccurred(object? sender, DragDropErrorEventArgs e) { Debug.WriteLine($"DragDrop error in {e.Operation}: {e.Exception.Message}"); } #endregion #region Вспомогательные методы private void ThrowIfDisposed() { if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService)); } #endregion #region IDisposable /// public void Dispose() { if (_disposed) return; UnsubscribeFromEvents(); foreach (var adapter in _dragSources.Values) { adapter.Dispose(); } _dragSources.Clear(); foreach (var adapter in _dropTargets.Values) { adapter.Dispose(); } _dropTargets.Clear(); _disposed = true; GC.SuppressFinalize(this); } #endregion }