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 DropTargetBehaviorBase : IDropTarget
where TElement : class
{
private IDragDropService? _dragDropService;
private string? _registrationId;
private TElement? _associatedElement;
private Rect _currentBounds;
///
/// Получает или задает связанный UI элемент.
///
///
/// Элемент UI, к которому прикреплено поведение цели сброса.
/// При изменении значения автоматически выполняется перерегистрация в сервисе перетаскивания.
///
protected TElement? AssociatedElement
{
get => _associatedElement;
set
{
if (_associatedElement != value)
{
UnregisterFromService();
_associatedElement = value;
RegisterToService();
}
}
}
///
/// Получает или задает приоритет цели сброса.
///
///
/// Цели с более высоким приоритетом проверяются первыми при нахождении курсора
/// в области нескольких целей. Значение по умолчанию: 0.
///
public int Priority { get; set; }
///
/// Получает или задает группу цели сброса.
///
///
/// Имя группы для группового управления целями сброса. Может использоваться
/// для массовой отмены регистрации целей или применения общих настроек.
///
public string? Group { get; set; }
///
/// Получает сервис перетаскивания из контейнера зависимостей.
///
///
/// Экземпляр , используемый для регистрации цели сброса.
/// При первом обращении выполняется получение сервиса из .
///
protected IDragDropService DragDropService
{
get
{
if (_dragDropService == null)
{
_dragDropService = ServiceProvider.GetRequiredService();
}
return _dragDropService;
}
}
///
/// Получает провайдер сервисов для разрешения зависимостей.
///
protected IServiceProvider ServiceProvider { get; }
///
/// Получает текущие границы элемента в экранных координатах.
///
///
/// Прямоугольник, описывающий границы элемента в экранных координатах.
/// Значение автоматически обновляется при изменении размера или позиции элемента.
///
protected Rect CurrentBounds => _currentBounds;
///
/// Получает уникальный идентификатор регистрации цели в сервисе перетаскивания.
///
///
/// Идентификатор, возвращенный методом ,
/// или null, если цель не зарегистрирована.
///
protected string? RegistrationId => _registrationId;
///
/// Инициализирует новый экземпляр класса .
///
/// Провайдер сервисов для разрешения зависимостей.
///
/// Выбрасывается, когда равен null.
///
protected DropTargetBehaviorBase(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
///
/// Вызывается при прикреплении поведения к элементу.
///
///
/// Реализация по умолчанию подписывается на события элемента, обновляет границы
/// и регистрирует цель в сервисе перетаскивания.
///
protected virtual void AttachToElement()
{
if (_associatedElement != null)
{
SubscribeToEvents(_associatedElement);
UpdateBounds();
RegisterToService();
}
}
///
/// Вызывается при откреплении поведения от элемента.
///
///
/// Реализация по умолчанию отписывается от событий элемента и отменяет
/// регистрацию цели в сервисе перетаскивания.
///
protected virtual void DetachFromElement()
{
if (_associatedElement != null)
{
UnsubscribeFromEvents(_associatedElement);
UnregisterFromService();
}
}
///
/// Подписывается на события элемента, необходимые для отслеживания изменений размера и позиции.
///
/// Элемент, к событиям которого нужно подписаться.
///
/// Производные классы должны реализовать этот метод для подписки на события конкретной
/// UI-платформы (например, SizeChanged, LayoutUpdated для WPF).
///
protected abstract void SubscribeToEvents(TElement element);
///
/// Отписывается от событий элемента.
///
/// Элемент, от событий которого нужно отписаться.
///
/// Производные классы должны реализовать этот метод для корректной отписки
/// от событий, на которые была выполнена подписка в .
///
protected abstract void UnsubscribeFromEvents(TElement element);
///
/// Обновляет границы элемента в экранных координатах.
///
///
/// Этот метод вызывается при изменении размера или позиции элемента для
/// обновления области, в которой цель может принимать сбрасываемые данные.
///
protected virtual void UpdateBounds()
{
if (_associatedElement != null)
{
_currentBounds = GetScreenBounds(_associatedElement);
// Обновляем регистрацию в сервисе
if (_registrationId != null)
{
DragDropService.UpdateDropTargetBounds(_registrationId, _currentBounds);
}
}
}
///
/// Получает границы элемента в экранных координатах.
///
/// Элемент, границы которого нужно получить.
/// Границы элемента в экранных координатах.
///
/// Производные классы должны реализовать этот метод для получения границ
/// элемента в соответствии с конкретной UI-платформой.
///
protected abstract Rect GetScreenBounds(TElement element);
///
/// Регистрирует цель в сервисе перетаскивания.
///
///
///
/// Этот метод регистрирует текущий объект как цель сброса в сервисе перетаскивания
/// с указанными приоритетом и группой.
///
///
/// Регистрация выполняется только если поведение прикреплено к элементу и
/// цель еще не зарегистрирована.
///
///
protected virtual void RegisterToService()
{
if (_associatedElement != null && _registrationId == null)
{
UpdateBounds();
_registrationId = DragDropService.RegisterDropTarget(this, _currentBounds, Priority, Group);
}
}
///
/// Отменяет регистрацию цели в сервисе перетаскивания.
///
///
/// Этот метод отменяет регистрацию цели сброса, освобождая ресурсы
/// в сервисе перетаскивания и предотвращая дальнейшую обработку событий.
///
protected virtual void UnregisterFromService()
{
if (_registrationId != null)
{
DragDropService.UnregisterDropTarget(_registrationId);
_registrationId = null;
}
}
///
/// Вызывается при изменении размера или позиции элемента.
///
///
/// Производные классы должны вызывать этот метод из обработчиков событий
/// изменения размера или позиции элемента для обновления границ цели.
///
protected virtual void OnElementLayoutChanged()
{
UpdateBounds();
}
#region IDropTarget Implementation
///
public abstract Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct = default);
///
public virtual async Task OnDragOverAsync(DropInfo dropInfo, CancellationToken ct = default)
{
// Базовая реализация устанавливает эффект по умолчанию
if (await CanAcceptDropAsync(dropInfo))
{
// Установить эффект по умолчанию, если он разрешен
if (dropInfo.CanAcceptEffect(Core.DragDrop.Enums.DragDropEffects.Move))
{
dropInfo.SuggestedEffects = Core.DragDrop.Enums.DragDropEffects.Move;
}
else if (dropInfo.CanAcceptEffect(Core.DragDrop.Enums.DragDropEffects.Copy))
{
dropInfo.SuggestedEffects = Core.DragDrop.Enums.DragDropEffects.Copy;
}
else if (dropInfo.CanAcceptEffect(Core.DragDrop.Enums.DragDropEffects.Link))
{
dropInfo.SuggestedEffects = Core.DragDrop.Enums.DragDropEffects.Link;
}
}
else
{
dropInfo.SuggestedEffects = Core.DragDrop.Enums.DragDropEffects.None;
}
}
///
public abstract Task OnDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default);
///
public virtual Task OnDragLeaveAsync(CancellationToken cancellationToken = default)
{
// Базовая реализация не выполняет действий при выходе курсора из области цели
return Task.CompletedTask;
}
#endregion
///
/// Открепляет поведение от элемента и освобождает все связанные ресурсы.
///
///
///
/// Этот метод выполняет следующие действия:
///
///
/// - Отписывается от всех событий связанного элемента
/// - Отменяет регистрацию цели в сервисе перетаскивания
/// - Освобождает ссылку на связанный элемент
/// - Очищает все временные данные и состояние
///
///
/// После вызова этого метода поведение больше не будет обрабатывать события
/// элемента и не будет реагировать на операции перетаскивания. Поведение
/// можно безопасно удалить после вызова этого метода.
///
///
/// Важно: Этот метод должен быть вызван перед удалением
/// элемента из визуального дерева или перед заменой поведения, чтобы
/// предотвратить утечки памяти и непредсказуемое поведение системы.
///
///
///
/// // Пример использования
/// var dropBehavior = new MyDropTargetBehavior(serviceProvider);
/// dropBehavior.AssociatedElement = myElement;
///
/// // ... использование поведения ...
///
/// // Перед удалением элемента или поведения
/// dropBehavior.Detach();
///
///
///
public virtual void Detach()
{
DetachFromElement();
_associatedElement = null;
_registrationId = null;
_currentBounds = default;
}
}