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
}