using Lattice.Core.DragDrop.Abstractions;
using Lattice.Core.DragDrop.Models;
using Lattice.Core.DragDrop.Services;
using Lattice.Core.Geometry;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Lattice.UI.DragDrop.Behaviors;
///
/// Базовый класс поведения источника перетаскивания для UI элементов.
///
/// Тип UI элемента, к которому прикрепляется поведение.
///
///
/// Этот класс предоставляет базовую реализацию поведения перетаскивания для UI элементов.
/// Он обрабатывает события мыши/тач, управляет порогом начала перетаскивания и
/// интегрируется с сервисом из ядра.
///
///
/// Производные классы должны реализовать абстрактные методы для конкретной
/// UI-платформы и предоставить логику создания информации о перетаскивании.
///
///
public abstract class DragSourceBehaviorBase : IDragSource
where TElement : class
{
private IDragDropService? _dragDropService;
private Point _dragStartPosition;
private bool _isDragging;
private TElement? _associatedElement;
private CancellationTokenSource? _dragCancellationTokenSource;
///
/// Получает или задает связанный UI элемент.
///
///
/// Элемент UI, к которому прикреплено поведение перетаскивания.
/// При изменении значения автоматически выполняется переподключение событий.
///
protected TElement? AssociatedElement
{
get => _associatedElement;
set
{
if (_associatedElement != value)
{
DetachFromElement();
_associatedElement = value;
AttachToElement();
}
}
}
///
/// Получает сервис перетаскивания из контейнера зависимостей.
///
///
/// Экземпляр , используемый для управления операциями перетаскивания.
/// При первом обращении выполняется получение сервиса из .
///
protected IDragDropService DragDropService
{
get
{
if (_dragDropService == null)
{
_dragDropService = ServiceProvider.GetRequiredService();
}
return _dragDropService;
}
}
///
/// Получает провайдер сервисов для разрешения зависимостей.
///
protected IServiceProvider ServiceProvider { get; }
///
/// Получает значение, указывающее, выполняется ли в данный момент операция перетаскивания.
///
protected bool IsDragging => _isDragging;
///
/// Инициализирует новый экземпляр класса .
///
/// Провайдер сервисов для разрешения зависимостей.
///
/// Выбрасывается, когда равен null.
///
protected DragSourceBehaviorBase(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
///
/// Вызывается при прикреплении поведения к элементу.
///
///
/// Реализация по умолчанию подписывается на события элемента через .
/// Производные классы могут переопределить этот метод для дополнительной инициализации.
///
protected virtual void AttachToElement()
{
if (_associatedElement != null)
{
SubscribeToEvents(_associatedElement);
}
}
///
/// Вызывается при откреплении поведения от элемента.
///
///
/// Реализация по умолчанию отписывается от событий элемента через .
/// Производные классы могут переопределить этот метод для дополнительной очистки.
///
protected virtual void DetachFromElement()
{
if (_associatedElement != null)
{
UnsubscribeFromEvents(_associatedElement);
}
}
///
/// Подписывается на события элемента, необходимые для отслеживания начала перетаскивания.
///
/// Элемент, к событиям которого нужно подписаться.
///
/// Производные классы должны реализовать этот метод для подписки на события конкретной
/// UI-платформы (например, MouseDown для WPF, PointerPressed для Avalonia).
///
protected abstract void SubscribeToEvents(TElement element);
///
/// Отписывается от событий элемента.
///
/// Элемент, от событий которого нужно отписаться.
///
/// Производные классы должны реализовать этот метод для корректной отписки
/// от событий, на которые была выполнена подписка в .
///
protected abstract void UnsubscribeFromEvents(TElement element);
///
/// Обрабатывает начало взаимодействия с элементом (например, нажатие кнопки мыши).
///
/// Позиция взаимодействия в координатах элемента.
/// Задача, представляющая асинхронную операцию.
///
///
/// Этот метод вызывается из обработчиков событий UI-платформы при начале
/// взаимодействия, которое может привести к перетаскиванию.
///
///
/// Реализация по умолчанию сохраняет начальную позицию для последующей
/// проверки порога перетаскивания.
///
///
protected virtual Task OnInteractionStarted(Point position)
{
if (_isDragging)
return Task.CompletedTask;
_dragStartPosition = position;
_dragCancellationTokenSource = new CancellationTokenSource();
return Task.CompletedTask;
}
///
/// Обрабатывает перемещение во время взаимодействия с элементом.
///
/// Текущая позиция взаимодействия в координатах элемента.
/// Задача, представляющая асинхронную операцию.
///
///
/// Этот метод вызывается при перемещении курсора/тач-точки во время удержания
/// взаимодействия (например, перемещение мыши с нажатой кнопкой).
///
///
/// Реализация по умолчанию проверяет, превышено ли расстояние от начальной
/// точки порога перетаскивания, и если да - начинает операцию перетаскивания.
///
///
protected virtual async Task OnInteractionMoved(Point position)
{
if (_isDragging || AssociatedElement == null)
return;
var distance = CalculateDistance(_dragStartPosition, position);
if (distance > DragDropService.DragStartThreshold)
{
await StartDragOperation();
}
}
///
/// Обрабатывает завершение взаимодействия с элементом.
///
/// Задача, представляющая асинхронную операцию.
///
///
/// Этот метод вызывается при завершении взаимодействия (например, отпускании кнопки мыши).
///
///
/// Реализация по умолчанию сбрасывает состояние поведения, если перетаскивание не было начато.
///
///
protected virtual Task OnInteractionEnded()
{
// Сброс состояния, если перетаскивание не началось
if (!_isDragging)
{
Reset();
}
return Task.CompletedTask;
}
///
/// Обрабатывает отмену взаимодействия с элементом.
///
/// Задача, представляющая асинхронную операцию.
///
///
/// Этот метод вызывается при отмене взаимодействия (например, нажатии клавиши Escape
/// или выходе за пределы допустимой области).
///
///
/// Реализация по умолчанию отменяет текущую операцию перетаскивания, если она активна,
/// и сбрасывает состояние поведения.
///
///
protected virtual async Task OnInteractionCancelled()
{
if (_isDragging)
{
await DragDropService.CancelDragAsync();
}
Reset();
}
///
/// Начинает операцию перетаскивания.
///
/// Задача, представляющая асинхронную операцию.
///
///
/// Этот метод преобразует начальную позицию в экранные координаты и вызывает
/// сервис перетаскивания для начала операции.
///
///
/// Операция начинается только если поведение прикреплено к элементу и
/// не выполняется другая операция перетаскивания.
///
///
protected virtual async Task StartDragOperation()
{
if (_isDragging || AssociatedElement == null || _dragCancellationTokenSource == null)
return;
// Получаем начальную позицию в экранных координатах
var screenPosition = ConvertToScreenCoordinates(_dragStartPosition);
// Начинаем перетаскивание
try
{
_isDragging = await DragDropService.StartDragAsync(this, screenPosition);
}
catch (OperationCanceledException)
{
// Операция была отменена
Reset();
}
}
///
/// Преобразует координаты элемента в экранные координаты.
///
/// Точка в координатах элемента.
/// Точка в экранных координатах.
///
/// Производные классы должны реализовать этот метод для преобразования
/// координат в соответствии с конкретной UI-платформой.
///
protected abstract Point ConvertToScreenCoordinates(Point point);
///
/// Вычисляет расстояние между двумя точками.
///
/// Первая точка.
/// Вторая точка.
/// Расстояние между точками.
protected virtual 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);
}
///
/// Сбрасывает состояние поведения.
///
///
/// Этот метод очищает все временные данные и отменяет токены отмены,
/// связанные с текущей операцией перетаскивания.
///
protected virtual void Reset()
{
_isDragging = false;
_dragStartPosition = default;
_dragCancellationTokenSource?.Dispose();
_dragCancellationTokenSource = null;
}
#region IDragSource Implementation
///
public abstract Task TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default);
///
public async Task OnDragCompletedAsync(DragInfo dragInfo, Lattice.Core.DragDrop.Enums.DragDropEffects effects, CancellationToken cancellationToken = default)
{
_isDragging = false;
OnDragCompleted(dragInfo, effects);
}
///
public async Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default)
{
_isDragging = false;
OnDragCancelled(dragInfo);
}
#endregion
#region Virtual Methods for Derived Classes
///
/// Вызывается при успешном завершении операции перетаскивания.
///
/// Информация о перетаскивании, использованная в операции.
/// Эффекты, примененные при завершении операции.
///
/// Производные классы могут переопределить этот метод для выполнения
/// дополнительных действий после успешного завершения перетаскивания,
/// например, удаления исходного элемента при перемещении.
///
protected virtual void OnDragCompleted(DragInfo dragInfo, Lattice.Core.DragDrop.Enums.DragDropEffects effects)
{
}
///
/// Вызывается при отмене операции перетаскивания.
///
/// Информация о перетаскивании, использованная в операции.
///
/// Производные классы могут переопределить этот метод для выполнения
/// действий по восстановлению состояния после отмены перетаскивания.
///
protected virtual void OnDragCancelled(DragInfo dragInfo)
{
}
#endregion
///
/// Открепляет поведение от элемента и освобождает ресурсы.
///
///
/// После вызова этого метода поведение больше не будет обрабатывать события
/// элемента и может быть безопасно удалено.
///
public virtual void Detach()
{
DetachFromElement();
_associatedElement = null;
Reset();
}
}