using Lattice.Core.DragDrop.Services;
using Lattice.Core.Geometry;
using Lattice.UI.DragDrop.WinUI.Controls;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
namespace Lattice.UI.DragDrop.WinUI.Services;
///
/// Центральный менеджер для управления операциями drag-and-drop в WinUI приложении.
///
///
///
/// Этот класс реализует шаблон Singleton и предоставляет единую точку для
/// настройки и управления всеми операциями перетаскивания в приложении.
///
///
/// Менеджер отвечает за:
/// - Инициализацию системы перетаскивания
/// - Регистрацию и отслеживание источников и целей перетаскивания
/// - Создание и управление визуальной обратной связью
/// - Координацию между поведением элементов и базовым сервисом перетаскивания
///
///
/// Для использования необходимо вызвать при запуске приложения
/// и использовать attached properties или методы расширения для настройки элементов.
///
///
public sealed class WinUIDragDropManager : IDisposable
{
#region Singleton
private static WinUIDragDropManager? _instance;
private static readonly object _lock = new();
///
/// Получает единственный экземпляр менеджера.
///
public static WinUIDragDropManager Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
_instance ??= new WinUIDragDropManager();
}
}
return _instance;
}
}
#endregion
#region Поля
private readonly DragDropService _dragDropService;
private readonly WinUIDragDropHost _host;
private readonly Dictionary _dragSources = new();
private readonly Dictionary _dropTargets = new();
private DragAdorner? _currentDragVisual;
private bool _disposed;
#endregion
#region Свойства
///
/// Получает основной сервис перетаскивания.
///
public IDragDropService DragDropService => _dragDropService;
///
/// Получает или задает смещение визуального элемента перетаскивания относительно курсора.
///
///
/// Точка, определяющая смещение по осям X и Y. Значение по умолчанию: (-20, -20).
/// Отрицательные значения поднимают визуальный элемент вверх и влево относительно курсора.
///
public Point DragVisualOffset { get; set; } = new Point(-20, -20);
#endregion
#region Конструктор
private WinUIDragDropManager()
{
_dragDropService = new DragDropService();
_host = new WinUIDragDropHost();
}
#endregion
#region Публичные методы
///
/// Инициализирует систему перетаскивания для указанного окна.
///
/// Основное окно приложения, в котором будет работать перетаскивание.
///
/// Выбрасывается, если менеджер был удален.
///
///
/// Этот метод должен быть вызван один раз при запуске приложения, обычно в методе
/// .
///
public void Initialize(Window window)
{
if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropManager));
_host.Initialize(window);
// Подписываемся на события
_dragDropService.DragStarted += OnDragStarted;
_dragDropService.DragUpdated += OnDragUpdated;
_dragDropService.DragCompleted += OnDragCompleted;
_dragDropService.DragCancelled += OnDragCancelled;
}
///
/// Делает указанный элемент источником перетаскивания.
///
/// Элемент, который станет источником перетаскивания.
/// Данные, которые будут перетаскиваться. Если не указано, используются
/// DataContext или Tag элемента.
///
/// Если элемент уже зарегистрирован как источник перетаскивания, метод не выполняет действий.
///
public void MakeDragSource(FrameworkElement element, object? dragData = null)
{
if (_disposed || _dragSources.ContainsKey(element)) return;
var behavior = new Behaviors.WinUIDragSourceBehavior(_dragDropService, _host);
behavior.Attach(element, dragData);
_dragSources[element] = behavior;
}
///
/// Делает указанный элемент целью сброса.
///
/// Элемент, который станет целью сброса.
///
/// Если элемент уже зарегистрирован как цель сброса, метод не выполняет действий.
///
public void MakeDropTarget(FrameworkElement element)
{
if (_disposed || _dropTargets.ContainsKey(element)) return;
var behavior = new Behaviors.WinUIDropTargetBehavior(_dragDropService, _host);
behavior.Attach(element);
_dropTargets[element] = behavior;
}
///
/// Удаляет возможность перетаскивания.
///
public void RemoveDragSource(FrameworkElement element)
{
if (_dragSources.Remove(element, out var behavior))
{
behavior.Detach();
}
}
///
/// Удаляет возможность сброса.
///
public void RemoveDropTarget(FrameworkElement element)
{
if (_dropTargets.Remove(element, out var behavior))
{
behavior.Detach();
}
}
///
/// Очищает все регистрации.
///
public void Clear()
{
foreach (var behavior in _dragSources.Values)
{
behavior.Detach();
}
_dragSources.Clear();
foreach (var behavior in _dropTargets.Values)
{
behavior.Detach();
}
_dropTargets.Clear();
}
#endregion
#region Обработчики событий
private void OnDragStarted(object? sender, DragStartedEventArgs e)
{
// Создаем визуальное представление
_currentDragVisual = new DragAdorner
{
DragData = e.DragInfo.Data,
Opacity = 0.8
};
var position = new Point(
e.Position.X + DragVisualOffset.X,
e.Position.Y + DragVisualOffset.Y
);
_currentDragVisual.UpdatePosition(position);
_host.ShowDragVisual(_currentDragVisual, position);
}
private void OnDragUpdated(object? sender, DragUpdatedEventArgs e)
{
if (_currentDragVisual != null)
{
var position = new Point(
e.Position.X + DragVisualOffset.X,
e.Position.Y + DragVisualOffset.Y
);
_currentDragVisual.UpdatePosition(position);
}
}
private void OnDragCompleted(object? sender, DragCompletedEventArgs e)
{
CleanupDragVisual();
}
private void OnDragCancelled(object? sender, DragCancelledEventArgs e)
{
CleanupDragVisual();
}
private void CleanupDragVisual()
{
if (_currentDragVisual != null)
{
_currentDragVisual.Hide();
_currentDragVisual = null;
}
}
#endregion
#region IDisposable
public void Dispose()
{
if (_disposed) return;
Clear();
// Отписываемся от событий
_dragDropService.DragStarted -= OnDragStarted;
_dragDropService.DragUpdated -= OnDragUpdated;
_dragDropService.DragCompleted -= OnDragCompleted;
_dragDropService.DragCancelled -= OnDragCancelled;
_dragDropService.Dispose();
_host.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
#endregion
}