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