using Lattice.Core.DragDrop.Abstractions; using Lattice.Core.DragDrop.Models; using Lattice.Core.DragDrop.Services; using Lattice.Core.Geometry; 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; } /// /// Получает значение, указывающее, выполняется ли в данный момент операция перетаскивания. /// protected bool IsDragging => _isDragging; /// /// Инициализирует новый экземпляр класса . /// /// Сервис перетаскивания. /// /// Выбрасывается, когда равен null. /// protected DragSourceBehaviorBase(IDragDropService dragDropService) { DragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); } /// /// Вызывается при прикреплении поведения к элементу. /// /// /// Реализация по умолчанию подписывается на события элемента через . /// Производные классы могут переопределить этот метод для дополнительной инициализации. /// 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(); } }