Files
Lattice/Lattice.UI.DragDrop.WinUI/Integration/WinUIDragDropIntegrationService.cs

426 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}