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 }