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
}