using Lattice.Core.DragDrop.Abstractions; using Lattice.Core.DragDrop.Models; using Lattice.Core.Geometry; using Lattice.UI.DragDrop.WinUI.Services; using Microsoft.UI.Xaml; using System; using System.Threading; using System.Threading.Tasks; namespace Lattice.UI.DragDrop.WinUI.Behaviors; /// /// Реализация поведения цели сброса для элементов WinUI. /// /// /// /// Этот класс обрабатывает события перетаскивания WinUI и преобразует их в вызовы /// методов интерфейса . /// /// /// Поведение автоматически регистрирует элемент в системе перетаскивания, /// обновляет его границы при изменении размера и обрабатывает все этапы операции сброса. /// /// public sealed class WinUIDropTargetBehavior : IDropTarget { #region Поля private readonly Lattice.Core.DragDrop.Services.IDragDropService _dragDropService; private readonly WinUIDragDropHost _host; private FrameworkElement? _element; private string? _registrationId; private Rect _currentBounds; #endregion #region Конструктор /// /// Инициализирует новый экземпляр класса . /// /// Сервис для управления операциями перетаскивания. /// Хост для отображения визуальных элементов. /// /// Выбрасывается, если любой из параметров равен null. /// public WinUIDropTargetBehavior(Lattice.Core.DragDrop.Services.IDragDropService dragDropService, WinUIDragDropHost host) { _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); _host = host ?? throw new ArgumentNullException(nameof(host)); } #endregion #region Публичные методы /// /// Прикрепляет поведение к указанному элементу. /// /// Элемент, к которому прикрепляется поведение. /// /// Выбрасывается, если равен null. /// /// /// После вызова этого метода: /// 1. Элементу устанавливается = true /// 2. Поведение подписывается на события перетаскивания /// 3. Элемент регистрируется в системе перетаскивания /// public void Attach(FrameworkElement element) { Detach(); _element = element ?? throw new ArgumentNullException(nameof(element)); element.AllowDrop = true; SubscribeToEvents(); UpdateBounds(); RegisterToService(); } /// /// Открепляет поведение от элемента. /// public void Detach() { if (_element == null) return; UnsubscribeFromEvents(); UnregisterFromService(); _element.AllowDrop = false; _element = null; _currentBounds = Rect.Empty; } #endregion #region Обработчики событий private void SubscribeToEvents() { if (_element == null) return; _element.DragEnter += OnDragEnter; _element.DragOver += OnDragOver; _element.DragLeave += OnDragLeave; _element.Drop += OnDrop; _element.SizeChanged += OnSizeChanged; } private void UnsubscribeFromEvents() { if (_element == null) return; _element.DragEnter -= OnDragEnter; _element.DragOver -= OnDragOver; _element.DragLeave -= OnDragLeave; _element.Drop -= OnDrop; _element.SizeChanged -= OnSizeChanged; } private async void OnDragEnter(object sender, DragEventArgs e) { if (_element == null) return; try { var position = e.GetPosition(_element); var dropInfo = CreateDropInfo(e, new Point(position.X, position.Y)); if (await CanAcceptDropAsync(dropInfo)) { e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy; } } catch { } } private async void OnDragOver(object sender, DragEventArgs e) { if (_element == null) return; try { var position = e.GetPosition(_element); var dropInfo = CreateDropInfo(e, new Point(position.X, position.Y)); if (await CanAcceptDropAsync(dropInfo)) { e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy; await OnDragOverAsync(dropInfo); } e.Handled = true; } catch { } } private async void OnDragLeave(object sender, DragEventArgs e) { await OnDragLeaveAsync(); } private async void OnDrop(object sender, DragEventArgs e) { if (_element == null) return; try { var position = e.GetPosition(_element); var dropInfo = CreateDropInfo(e, new Point(position.X, position.Y)); if (await CanAcceptDropAsync(dropInfo)) { await OnDropAsync(dropInfo); dropInfo.MarkAsHandled(); e.Handled = true; } } catch { } } private void OnSizeChanged(object sender, SizeChangedEventArgs e) { UpdateBounds(); } private DropInfo CreateDropInfo(DragEventArgs e, Point position) { object? data = null; if (e.DataView.Properties.TryGetValue("DragData", out var dragData)) { data = dragData; } return new DropInfo( data: data, position: position, allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move, target: _element ); } /// /// Получает границы элемента в экранных координатах. /// /// Прямоугольник с границами элемента или , если /// элемент не загружен или не может быть преобразован. private Rect GetScreenBounds() { if (_element == null || !_element.IsLoaded) return Rect.Empty; try { var transform = _element.TransformToVisual(Window.Current.Content); var position = transform.TransformPoint(new Windows.Foundation.Point(0, 0)); return new Rect( position.X, position.Y, _element.ActualWidth, _element.ActualHeight ); } catch { return Rect.Empty; } } private void UpdateBounds() { if (_element == null) return; _currentBounds = GetScreenBounds(); if (_registrationId != null && _currentBounds != Rect.Empty) { _dragDropService.UpdateDropTargetBounds(_registrationId, _currentBounds); } } private void RegisterToService() { if (_element == null) return; _currentBounds = GetScreenBounds(); if (_currentBounds != Rect.Empty && _registrationId == null) { _registrationId = _dragDropService.RegisterDropTarget(this, _currentBounds); } } private void UnregisterFromService() { if (_registrationId != null) { _dragDropService.UnregisterDropTarget(_registrationId); _registrationId = null; } } #endregion #region IDropTarget Implementation public Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct = default) { return Task.FromResult(true); // Принимаем все по умолчанию } public Task OnDragOverAsync(DropInfo dropInfo, CancellationToken ct = default) { dropInfo.SuggestedEffects = Core.DragDrop.Enums.DragDropEffects.Move; return Task.CompletedTask; } public Task OnDropAsync(DropInfo dropInfo, CancellationToken ct = default) { return Task.CompletedTask; } public Task OnDragLeaveAsync(CancellationToken ct = default) { return Task.CompletedTask; } #endregion }