426 lines
14 KiB
C#
426 lines
14 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// Сервис интеграции Drag & Drop с WinUI приложением.
|
||
/// </summary>
|
||
public sealed class WinUIDragDropIntegrationService : IDisposable
|
||
{
|
||
#region Вложенные типы
|
||
|
||
private sealed class DragSourceAdapter : IDragSource
|
||
{
|
||
private readonly FrameworkElement _element;
|
||
private readonly Func<DragInfo> _dragInfoFactory;
|
||
private readonly IDragDropService _dragDropService;
|
||
private Point _dragStartPosition;
|
||
private bool _isDragging;
|
||
private bool _disposed;
|
||
|
||
public DragSourceAdapter(
|
||
FrameworkElement element,
|
||
Func<DragInfo> 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<bool> 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<FrameworkElement, DragSourceAdapter> _dragSources = new();
|
||
private readonly Dictionary<FrameworkElement, WinUIDropTargetBehavior> _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 Публичные методы
|
||
|
||
/// <summary>
|
||
/// Регистрирует элемент как источник перетаскивания.
|
||
/// </summary>
|
||
public void RegisterDragSource(FrameworkElement element, Func<DragInfo> 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Регистрирует элемент как источник перетаскивания с данными.
|
||
/// </summary>
|
||
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
|
||
);
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// Регистрирует элемент как цель сброса.
|
||
/// </summary>
|
||
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
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Удаляет регистрацию элемента как источника.
|
||
/// </summary>
|
||
public void UnregisterDragSource(FrameworkElement element)
|
||
{
|
||
if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService));
|
||
|
||
if (_dragSources.Remove(element, out var adapter))
|
||
{
|
||
adapter.Dispose();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Удаляет регистрацию элемента как цели.
|
||
/// </summary>
|
||
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
|
||
}
|
||
|
||
/// <summary>
|
||
/// Методы расширения для преобразования координат.
|
||
/// </summary>
|
||
internal static class PointExtensions
|
||
{
|
||
public static Point ToCorePoint(this Windows.Foundation.Point point)
|
||
{
|
||
return new Point(point.X, point.Y);
|
||
}
|
||
} |