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 }