using Lattice.Core.DragDrop.Services;
using Lattice.Core.Geometry;
using Lattice.UI.DragDrop.WinUI.Behaviors;
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 и служит единой точкой
/// входа для настройки и управления операциями перетаскивания в WinUI-приложении.
///
///
/// Основные функции менеджера:
///
/// - Инициализация системы перетаскивания для конкретного окна
/// - Регистрация и отслеживание источников и целей перетаскивания
/// - Управление жизненным циклом операций перетаскивания
/// - Обеспечение визуальной обратной связи через
/// - Координация взаимодействия между и
///
///
///
/// Для использования менеджера необходимо:
///
/// - Вызвать при создании главного окна приложения
/// - Настроить элементы как источники или цели через методы и
/// - Использовать attached properties для декларативной настройки в XAML
///
///
///
///
/// // Инициализация в коде
/// public partial class MainWindow : Window
/// {
/// private WinUIDragDropManager _manager;
///
/// public MainWindow()
/// {
/// InitializeComponent();
/// _manager = WinUIDragDropManager.Instance;
/// _manager.Initialize(this);
///
/// // Настройка элементов
/// _manager.MakeDragSource(myElement, myData);
/// _manager.MakeDropTarget(myDropArea);
/// }
/// }
///
/// // Или через attached properties в XAML
/// <Border x:Name="DragElement"
/// local:DragDropProperties.IsDragSource="True"
/// local:DragDropProperties.DragData="{Binding MyData}" />
/// <Border x:Name="DropArea"
/// local:DragDropProperties.IsDropTarget="True" />
///
///
///
public sealed class WinUIDragDropManager : IDisposable
{
#region Singleton Implementation
private static WinUIDragDropManager? _instance;
private static readonly object _lockObject = new();
///
/// Получает единственный экземпляр .
/// Реализует шаблон Singleton с ленивой инициализацией и потокобезопасностью.
///
///
/// Единственный экземпляр менеджера перетаскивания для всего приложения.
/// Если экземпляр еще не создан, он будет инициализирован при первом обращении.
///
///
/// Использование Singleton гарантирует, что во всем приложении существует только один
/// экземпляр менеджера, что обеспечивает согласованное управление всеми операциями
/// перетаскивания и предотвращает конфликты между разными компонентами системы.
///
public static WinUIDragDropManager Instance
{
get
{
if (_instance == null)
{
lock (_lockObject)
{
_instance ??= new WinUIDragDropManager();
}
}
return _instance;
}
}
#endregion
#region Fields
private readonly IDragDropService _dragDropService;
private readonly WinUIDragDropHost _host;
private readonly Dictionary _dragSources = new();
private readonly Dictionary _dropTargets = new();
private DragAdorner? _currentDragVisual;
private bool _disposed;
private bool _initialized;
#endregion
#region Properties
///
/// Получает сервис перетаскивания, используемый менеджером для координации операций.
///
///
/// Экземпляр , через который менеджер взаимодействует
/// с ядром системы перетаскивания.
///
///
/// Этот сервис предоставляет низкоуровневый API для управления операциями перетаскивания
/// и может использоваться для расширенной настройки системы.
///
public IDragDropService DragDropService => _dragDropService;
///
/// Получает хост для управления визуальными элементами перетаскивания.
///
///
/// Экземпляр , отвечающий за отображение и позиционирование
/// визуальной обратной связи во время операций перетаскивания.
///
public WinUIDragDropHost Host => _host;
///
/// Получает или задает смещение визуального элемента перетаскивания относительно курсора.
///
///
/// Точка, определяющая смещение по осям X и Y в пикселях.
/// Значение по умолчанию: (-20, -20).
///
///
///
/// Отрицательные значения смещают визуальный элемент вверх и влево относительно курсора,
/// что является стандартным поведением в большинстве систем drag-and-drop.
///
///
/// Настройка смещения позволяет:
///
/// - Предотвратить перекрытие курсора визуальным элементом
/// - Обеспечить лучшую видимость области под курсором
/// - Создать более естественное визуальное восприятие
///
///
///
///
/// // Настройка смещения через фабрику
/// var manager = WinUIDragDropFactory.CreateManager(window, m =>
/// {
/// m.DragVisualOffset = new Point(-15, -15); // Более близко к курсору
/// });
///
///
///
public Point DragVisualOffset { get; set; } = new Point(-20, -20);
///
/// Получает значение, указывающее, инициализирован ли менеджер.
///
///
/// true, если метод был успешно вызван;
/// в противном случае — false.
///
///
/// Проверка этого свойства позволяет избежать повторной инициализации менеджера
/// и гарантирует, что система перетаскивания готова к использованию.
///
public bool IsInitialized => _initialized;
#endregion
#region Constructor
///
/// Инициализирует новый экземпляр класса .
/// Конструктор является приватным в соответствии с шаблоном Singleton.
///
///
///
/// Внутренний конструктор создает:
///
/// - Экземпляр для управления операциями перетаскивания
/// - Экземпляр для визуальной обратной связи
///
///
///
/// Для получения экземпляра менеджера используйте свойство .
///
///
private WinUIDragDropManager()
{
_dragDropService = new DragDropService();
_host = new WinUIDragDropHost();
}
#endregion
#region Public Methods
///
/// Инициализирует систему перетаскивания для указанного окна WinUI.
/// Этот метод должен быть вызван один раз при запуске приложения.
///
///
/// Главное окно приложения, для которого настраивается система перетаскивания.
/// Не может быть null.
///
///
/// Выбрасывается, если равен null.
///
///
/// Выбрасывается, если менеджер уже инициализирован или был удален.
///
///
///
/// Этот метод выполняет следующие действия:
///
/// - Настраивает хост визуальных элементов для работы с указанным окном
/// - Подписывается на события сервиса перетаскивания для управления визуальной обратной связью
/// - Помечает менеджер как инициализированный
///
///
///
/// Метод должен быть вызван до использования любых других методов менеджера.
/// Рекомендуется вызывать его в конструкторе главного окна приложения.
///
///
///
/// public MainWindow()
/// {
/// InitializeComponent();
/// WinUIDragDropManager.Instance.Initialize(this);
/// }
///
///
///
public void Initialize(Window window)
{
if (_disposed)
throw new ObjectDisposedException(nameof(WinUIDragDropManager));
if (_initialized)
throw new InvalidOperationException("Менеджер уже инициализирован.");
if (window == null)
throw new ArgumentNullException(nameof(window));
// Инициализируем хост для работы с окном
_host.Initialize(window);
// Подписываемся на события сервиса перетаскивания
_dragDropService.DragStarted += OnDragStarted;
_dragDropService.DragUpdated += OnDragUpdated;
_dragDropService.DragCompleted += OnDragCompleted;
_dragDropService.DragCancelled += OnDragCancelled;
_initialized = true;
}
///
/// Настраивает указанный элемент как источник перетаскивания.
///
///
/// Элемент
///
/// Данные, которые будут перетаскиваться. Может быть null.
/// Если не указано, используются или
/// элемента.
///
///
/// Выбрасывается, если равен null.
///
///
/// Выбрасывается, если менеджер не инициализирован или был удален.
///
///
///
/// После вызова этого метода элемент приобретает следующие возможности:
///
/// - Реагирует на жесты перетаскивания (удержание и перемещение указателя)
/// - Предоставляет указанные данные для перетаскивания
/// - Интегрируется с системой визуальной обратной связи
///
///
///
/// Если элемент уже зарегистрирован как источник перетаскивания, метод не выполняет действий.
///
///
/// Для отмены регистрации используйте метод .
///
///
public void MakeDragSource(FrameworkElement element, object? dragData = null)
{
ValidateManagerState();
if (element == null)
throw new ArgumentNullException(nameof(element));
// Если элемент уже зарегистрирован, ничего не делаем
if (_dragSources.ContainsKey(element))
return;
// Создаем и настраиваем поведение
var behavior = new WinUIDragSourceBehavior(_dragDropService, _host);
behavior.Attach(element, dragData);
_dragSources[element] = behavior;
}
///
/// Настраивает указанный элемент как цель сброса.
///
///
/// Элемент , который должен стать целью сброса.
/// Не может быть null.
///
///
/// Выбрасывается, если равен null.
///
///
/// Выбрасывается, если менеджер не инициализирован или был удален.
///
///
///
/// После вызова этого метода элемент приобретает следующие возможности:
///
/// - Принимает данные, сбрасываемые пользователем
/// - Предоставляет визуальную обратную связь при наведении
/// - Автоматически обновляет свои границы при изменении размера или позиции
///
///
///
/// По умолчанию цель принимает данные любого типа. Для настройки фильтрации типов
/// используйте методы и
/// .
///
///
/// Если элемент уже зарегистрирован как цель сброса, метод не выполняет действий.
///
///
/// Для отмены регистрации используйте метод .
///
///
public void MakeDropTarget(FrameworkElement element)
{
ValidateManagerState();
if (element == null)
throw new ArgumentNullException(nameof(element));
// Если элемент уже зарегистрирован, ничего не делаем
if (_dropTargets.ContainsKey(element))
return;
// Создаем и настраиваем поведение
var behavior = new WinUIDropTargetBehavior(_dragDropService, _host);
behavior.Attach(element);
_dropTargets[element] = behavior;
}
///
/// Удаляет возможность перетаскивания у указанного элемента.
///
///
/// Элемент, у которого нужно отключить возможность перетаскивания.
/// Если элемент не зарегистрирован как источник перетаскивания, метод не выполняет действий.
///
///
///
/// Этот метод выполняет следующие действия:
///
/// - Открепляет поведение перетаскивания от элемента
/// - Отписывается от всех событий элемента
/// - Удаляет элемент из внутреннего словаря источников
/// - Освобождает ресурсы, связанные с поведением
///
///
///
/// Метод безопасен для вызова даже если элемент не был зарегистрирован как источник.
///
///
public void RemoveDragSource(FrameworkElement element)
{
if (element == null || _disposed || !_dragSources.ContainsKey(element))
return;
if (_dragSources.Remove(element, out var behavior))
{
behavior.Detach();
}
}
///
/// Удаляет возможность сброса у указанного элемента.
///
///
/// Элемент, у которого нужно отключить возможность сброса.
/// Если элемент не зарегистрирован как цель сброса, метод не выполняет действий.
///
///
///
/// Этот метод выполняет следующие действия:
///
/// - Открепляет поведение цели сброса от элемента
/// - Восстанавливает свойство = false
/// - Удаляет элемент из внутреннего словаря целей
/// - Освобождает ресурсы, связанные с поведением
///
///
///
/// Метод безопасен для вызова даже если элемент не был зарегистрирован как цель.
///
///
public void RemoveDropTarget(FrameworkElement element)
{
if (element == null || _disposed || !_dropTargets.ContainsKey(element))
return;
if (_dropTargets.Remove(element, out var behavior))
{
behavior.Detach();
}
}
///
/// Очищает все регистрации источников и целей перетаскивания.
///
///
///
/// Этот метод полезен в следующих сценариях:
///
/// - При перезагрузке содержимого интерфейса
/// - При смене контекста данных
/// - При освобождении ресурсов перед удалением менеджера
///
///
///
/// После вызова этого метода все элементы теряют возможность участвовать в операциях
/// перетаскивания. Для восстановления функциональности необходимо повторно
/// зарегистрировать элементы через и .
///
///
public void Clear()
{
if (_disposed) return;
// Открепляем все источники
foreach (var behavior in _dragSources.Values)
{
behavior.Detach();
}
_dragSources.Clear();
// Открепляем все цели
foreach (var behavior in _dropTargets.Values)
{
behavior.Detach();
}
_dropTargets.Clear();
}
#endregion
#region Event Handlers
///
/// Обрабатывает событие начала перетаскивания.
/// Создает и отображает визуальный элемент для обратной связи.
///
private void OnDragStarted(object? sender, Core.DragDrop.Services.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, Core.DragDrop.Services.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, Core.DragDrop.Services.DragCompletedEventArgs e)
{
CleanupDragVisual();
}
///
/// Обрабатывает событие отмены перетаскивания.
/// Очищает визуальные элементы и восстанавливает состояние.
///
private void OnDragCancelled(object? sender, Core.DragDrop.Services.DragCancelledEventArgs e)
{
CleanupDragVisual();
}
///
/// Освобождает ресурсы визуального элемента перетаскивания.
///
private void CleanupDragVisual()
{
if (_currentDragVisual != null)
{
_currentDragVisual.Hide();
_currentDragVisual = null;
}
}
#endregion
#region Helper Methods
///
/// Проверяет состояние менеджера перед выполнением операций.
///
///
/// Выбрасывается, если менеджер был удален.
///
///
/// Выбрасывается, если менеджер не инициализирован.
///
private void ValidateManagerState()
{
if (_disposed)
throw new ObjectDisposedException(nameof(WinUIDragDropManager));
if (!_initialized)
throw new InvalidOperationException(
"Менеджер не инициализирован. Вызовите метод Initialize перед использованием.");
}
#endregion
#region IDisposable Implementation
///
/// Освобождает все ресурсы, используемые .
///
///
///
/// Этот метод выполняет следующие действия:
///
/// - Отписывается от всех событий сервиса перетаскивания
/// - Очищает все зарегистрированные источники и цели
/// - Освобождает ресурсы хоста визуальных элементов
/// - Освобождает ресурсы сервиса перетаскивания
///
///
///
/// После вызова этого метода менеджер перестает быть пригодным для использования.
/// Попытка использовать методы менеджера после удаления приведет к исключению
/// .
///
///
/// Метод безопасен для многократного вызова.
///
///
public void Dispose()
{
if (_disposed) return;
// Отписываемся от событий
_dragDropService.DragStarted -= OnDragStarted;
_dragDropService.DragUpdated -= OnDragUpdated;
_dragDropService.DragCompleted -= OnDragCompleted;
_dragDropService.DragCancelled -= OnDragCancelled;
// Очищаем все регистрации
Clear();
// Освобождаем ресурсы
_dragDropService.Dispose();
_host.Dispose();
_disposed = true;
_initialized = false;
GC.SuppressFinalize(this);
}
#endregion
}