Files
Lattice/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs

267 lines
8.7 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.WinUI.Services;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Lattice.UI.DragDrop.WinUI.Behaviors;
/// <summary>
/// Реализация поведения источника перетаскивания для элементов WinUI.
/// </summary>
/// <remarks>
/// <para>
/// Этот класс обрабатывает события мыши/тач и преобразует их в операции перетаскивания.
/// Он реализует интерфейс <see cref="IDragSource"/> для интеграции с системой перетаскивания.
/// </para>
/// <para>
/// Поведение автоматически отслеживает начало перемещения мыши, проверяет порог
/// перетаскивания и инициирует операцию через <see cref="IDragDropService"/>.
/// </para>
/// </remarks>
public sealed class WinUIDragSourceBehavior : IDragSource
{
#region Поля
private readonly IDragDropService _dragDropService;
private readonly WinUIDragDropHost _host;
private FrameworkElement? _element;
private object? _dragData;
private Point _dragStartPosition;
private bool _isDragging;
private CancellationTokenSource? _cancellationTokenSource;
#endregion
#region Конструктор
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="WinUIDragSourceBehavior"/>.
/// </summary>
/// <param name="dragDropService">Сервис для управления операциями перетаскивания.</param>
/// <param name="host">Хост для отображения визуальных элементов.</param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если любой из параметров равен null.
/// </exception>
public WinUIDragSourceBehavior(IDragDropService dragDropService, WinUIDragDropHost host)
{
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
_host = host ?? throw new ArgumentNullException(nameof(host));
}
#endregion
#region Публичные методы
/// <summary>
/// Прикрепляет поведение к указанному элементу.
/// </summary>
/// <param name="element">Элемент, к которому прикрепляется поведение.</param>
/// <param name="dragData">Данные для перетаскивания. Если не указано, используются
/// DataContext или Tag элемента.</param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="element"/> равен null.
/// </exception>
/// <remarks>
/// После вызова этого метода элемент начинает обрабатывать события перетаскивания.
/// </remarks>
public void Attach(FrameworkElement element, object? dragData = null)
{
Detach();
_element = element ?? throw new ArgumentNullException(nameof(element));
_dragData = dragData ?? element.DataContext ?? element.Tag;
SubscribeToEvents();
}
/// <summary>
/// Открепляет поведение от элемента.
/// </summary>
public void Detach()
{
if (_element == null) return;
UnsubscribeFromEvents();
if (_isDragging)
{
_dragDropService.CancelDragAsync().ConfigureAwait(false);
}
_element = null;
_dragData = null;
_isDragging = false;
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
#endregion
#region Обработчики событий
private void SubscribeToEvents()
{
if (_element == null) return;
_element.PointerPressed += OnPointerPressed;
_element.PointerMoved += OnPointerMoved;
_element.PointerReleased += OnPointerReleased;
_element.PointerCanceled += OnPointerCanceled;
_element.PointerCaptureLost += OnPointerCaptureLost;
}
private void UnsubscribeFromEvents()
{
if (_element == null) return;
_element.PointerPressed -= OnPointerPressed;
_element.PointerMoved -= OnPointerMoved;
_element.PointerReleased -= OnPointerReleased;
_element.PointerCanceled -= OnPointerCanceled;
_element.PointerCaptureLost -= OnPointerCaptureLost;
}
private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
if (_element == null || _isDragging) return;
var point = e.GetCurrentPoint(_element);
_dragStartPosition = new Point(point.Position.X, point.Position.Y);
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = new CancellationTokenSource();
}
private async void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
if (_element == null || _isDragging || _cancellationTokenSource?.IsCancellationRequested == true)
return;
var point = e.GetCurrentPoint(_element);
var currentPosition = new Point(point.Position.X, point.Position.Y);
var distance = CalculateDistance(_dragStartPosition, currentPosition);
if (distance > _dragDropService.DragStartThreshold)
{
await StartDragAsync(currentPosition);
}
}
private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
{
ResetState();
}
private void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
{
ResetState();
}
private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
{
ResetState();
}
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);
}
/// <summary>
/// Преобразует координаты элемента в экранные координаты.
/// </summary>
/// <param name="point">Точка в координатах элемента.</param>
/// <returns>Точка в экранных координатах.</returns>
private Point ConvertToScreenCoordinates(Point point)
{
if (_element == null) return point;
try
{
var transform = _element.TransformToVisual(Window.Current.Content);
var screenPoint = transform.TransformPoint(new Windows.Foundation.Point(point.X, point.Y));
return new Point(screenPoint.X, screenPoint.Y);
}
catch
{
return point;
}
}
private async Task StartDragAsync(Point position)
{
if (_element == null || _dragData == null || _isDragging) return;
try
{
var screenPosition = ConvertToScreenCoordinates(position);
var started = await _dragDropService.StartDragAsync(this, screenPosition);
_isDragging = started;
}
catch
{
ResetState();
}
}
private void ResetState()
{
_isDragging = false;
_dragStartPosition = default;
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
#endregion
#region IDragSource Implementation
public async Task<DragInfo?> TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default)
{
if (_element == null || _dragData == null)
return null;
try
{
var dragInfo = new DragInfo(
data: _dragData,
allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy |
Core.DragDrop.Enums.DragDropEffects.Move,
startPosition: startPosition,
source: this
);
return dragInfo;
}
catch
{
return null;
}
}
public Task OnDragCompletedAsync(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects, CancellationToken cancellationToken = default)
{
_isDragging = false;
return Task.CompletedTask;
}
public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default)
{
_isDragging = false;
return Task.CompletedTask;
}
#endregion
}