267 lines
8.7 KiB
C#
267 lines
8.7 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.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
|
||
} |