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; } }