using Lattice.Core.DragDrop.Models;
using Lattice.Core.DragDrop.Services;
using Lattice.Core.Geometry;
using Lattice.UI.DragDrop.Abstractions;
using Lattice.UI.DragDrop.Behaviors;
using Lattice.UI.DragDrop.WinUI.Services;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Lattice.UI.DragDrop.WinUI.Behaviors;
///
/// Реализация поведения источника перетаскивания для элементов WinUI.
/// Наследуется от для использования
/// общей логики управления операциями перетаскивания и интеграции с системой .
///
///
///
/// Этот класс предоставляет конкретную реализацию для платформы WinUI, обрабатывая события
/// указателя (мышь, тач, перо) и преобразуя их в операции перетаскивания через центральный
/// сервис .
///
///
/// Основные функции:
///
/// - Обработка событий PointerPressed, PointerMoved, PointerReleased
/// - Автоматическое отслеживание порога начала перетаскивания
/// - Создание информации о перетаскивании на основе данных элемента
/// - Интеграция с визуальной обратной связью через
/// - Преобразование координат между локальной системой элемента и экранными координатами
///
///
///
/// Для использования необходимо:
///
/// - Создать экземпляр поведения через фабрику
/// - Прикрепить к элементу с помощью метода
/// - Указать данные для перетаскивания (опционально, по умолчанию используется DataContext)
///
///
///
///
/// // Создание поведения
/// var behavior = WinUIDragDropFactory.CreateDragSourceBehavior(dragDropService, host);
///
/// // Прикрепление к элементу
/// behavior.Attach(myElement, myData);
///
/// // Или через attached properties
/// <Border x:Name="DragElement"
/// local:DragDropProperties.IsDragSource="True"
/// local:DragDropProperties.DragData="{Binding MyData}" />
///
///
///
public sealed class WinUIDragSourceBehavior : DragSourceBehaviorBase
{
#region Поля
private readonly IDragDropHost _host;
private object? _dragData;
#endregion
#region Конструктор
///
/// Инициализирует новый экземпляр класса .
///
///
/// Сервис управления операциями перетаскивания. Используется для координации
/// между источниками и целями перетаскивания.
///
///
/// Хост для управления визуальными элементами перетаскивания. Обеспечивает
/// отображение визуальной обратной связи во время операции.
///
///
/// Выбрасывается, если или
/// равны null.
///
///
/// Конструктор инициализирует базовый класс
/// и сохраняет ссылки на необходимые сервисы для последующего использования.
///
public WinUIDragSourceBehavior(
Core.DragDrop.Services.IDragDropService dragDropService,
WinUIDragDropHost host)
: base(dragDropService)
{
_host = host ?? throw new ArgumentNullException(nameof(host));
}
#endregion
#region Публичные методы
///
/// Прикрепляет поведение к указанному элементу WinUI.
///
///
/// Элемент , который должен стать источником перетаскивания.
/// Не может быть null.
///
///
/// Данные, которые будут перетаскиваться. Может быть null.
/// Если не указано, используется или
/// элемента.
///
///
/// Выбрасывается, если равен null.
///
///
///
/// После вызова этого метода:
///
/// - Элементу автоматически подписываются обработчики событий указателя
/// - Поведение начинает отслеживать взаимодействия с элементом
/// - При превышении порога перетаскивания инициируется операция через
///
///
///
/// Для открепления поведения используйте метод .
///
///
public void Attach(FrameworkElement element, object? dragData = null)
{
if (element == null)
throw new ArgumentNullException(nameof(element));
_dragData = dragData ?? element.DataContext ?? element.Tag;
AssociatedElement = element;
}
///
/// Открепляет поведение от текущего элемента.
///
///
///
/// Этот метод выполняет следующие действия:
///
/// - Отписывается от всех событий элемента
/// - Сбрасывает внутреннее состояние
/// - Освобождает ссылки на связанные объекты
///
///
///
/// Если в момент вызова активна операция перетаскивания, она будет автоматически отменена.
///
///
/// После вызова этого метода поведение может быть повторно прикреплено к другому элементу.
///
///
public new void Detach()
{
base.Detach();
_dragData = null;
}
#endregion
#region Реализация абстрактных методов DragSourceBehaviorBase
///
protected override void SubscribeToEvents(FrameworkElement element)
{
if (element == null) return;
element.PointerPressed += OnPointerPressed;
element.PointerMoved += OnPointerMoved;
element.PointerReleased += OnPointerReleased;
element.PointerCanceled += OnPointerCanceled;
element.PointerCaptureLost += OnPointerCaptureLost;
}
///
protected override void UnsubscribeFromEvents(FrameworkElement element)
{
if (element == null) return;
element.PointerPressed -= OnPointerPressed;
element.PointerMoved -= OnPointerMoved;
element.PointerReleased -= OnPointerReleased;
element.PointerCanceled -= OnPointerCanceled;
element.PointerCaptureLost -= OnPointerCaptureLost;
}
///
protected override Point ConvertToScreenCoordinates(Point point)
{
if (AssociatedElement == null)
return point;
return WinUIWindowHelper.ConvertToScreenCoordinates(AssociatedElement, point);
}
#endregion
#region Реализация интерфейса IDragSource
///
public override async Task TryStartDragAsync(
Point startPosition,
CancellationToken cancellationToken = default)
{
if (AssociatedElement == null || _dragData == null)
return null;
try
{
// Создаем информацию о перетаскивании
var dragInfo = new DragInfo(
data: _dragData,
allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy |
Core.DragDrop.Enums.DragDropEffects.Move |
Core.DragDrop.Enums.DragDropEffects.Link,
startPosition: startPosition,
source: this
);
// Добавляем дополнительные параметры
dragInfo.SetParameter("SourceElement", AssociatedElement);
dragInfo.SetParameter("SourceType", AssociatedElement.GetType().Name);
return await Task.FromResult(dragInfo);
}
catch (Exception ex)
{
// Логирование ошибки создания информации о перетаскивании
System.Diagnostics.Debug.WriteLine(
$"Ошибка создания DragInfo: {ex.Message}");
return null;
}
}
#endregion
#region Обработчики событий WinUI
private async void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
if (AssociatedElement == null) return;
var point = e.GetCurrentPoint(AssociatedElement);
var position = new Point(point.Position.X, point.Position.Y);
await OnInteractionStarted(position);
}
private async void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
if (AssociatedElement == null) return;
var point = e.GetCurrentPoint(AssociatedElement);
var position = new Point(point.Position.X, point.Position.Y);
await OnInteractionMoved(position);
}
private async void OnPointerReleased(object sender, PointerRoutedEventArgs e)
{
await OnInteractionEnded();
}
private async void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
{
await OnInteractionCancelled();
}
private async void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
{
await OnInteractionCancelled();
}
#endregion
#region Переопределение виртуальных методов
///
protected override void OnDragCompleted(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects)
{
base.OnDragCompleted(dragInfo, effects);
// Дополнительная логика для WinUI может быть добавлена здесь
// Например, обновление состояния элемента после успешного перетаскивания
}
///
protected override void OnDragCancelled(DragInfo dragInfo)
{
base.OnDragCancelled(dragInfo);
// Дополнительная логика для WinUI может быть добавлена здесь
// Например, восстановление визуального состояния элемента после отмены
}
#endregion
}