using Lattice.Core.DragDrop.Abstractions; using Lattice.Core.DragDrop.Enums; using Lattice.Core.DragDrop.Models; using Lattice.Core.DragDrop.Services; using Lattice.Core.Geometry; namespace Lattice.Core.DragDrop.Factories; /// /// Фабрика для создания компонентов системы перетаскивания. /// Предоставляет методы для создания сервисов, источников и целей перетаскивания. /// /// /// Эта фабрика позволяет создавать компоненты системы перетаскивания без использования /// Dependency Injection, предоставляя простой и понятный API для наиболее распространенных сценариев. /// public static class DragDropFactory { #region Сервисы перетаскивания /// /// Создает новый экземпляр сервиса перетаскивания с настройками по умолчанию. /// /// /// Экземпляр с настройками по умолчанию. /// /// /// Созданный сервис имеет следующие настройки по умолчанию: /// /// Порог начала перетаскивания: 3.0 пикселей /// Таймаут асинхронных операций: 5000 миллисекунд /// Асинхронные операции: включены /// /// public static IDragDropService CreateDragDropService() { return new DragDropService(); } /// /// Создает новый экземпляр сервиса перетаскивания с пользовательскими настройками. /// /// /// Делегат для настройки опций сервиса. Передает экземпляр /// для настройки параметров. /// /// /// Настроенный экземпляр . /// /// /// /// var service = DragDropFactory.CreateDragDropService(options => /// { /// options.DragStartThreshold = 5.0; /// options.AsyncOperationTimeout = 3000; /// options.EnableAsyncOperations = true; /// }); /// /// public static IDragDropService CreateDragDropService(Action configure) { var options = new DragDropServiceOptions(); configure(options); return new DragDropService { DragStartThreshold = options.DragStartThreshold, AsyncOperationTimeout = options.AsyncOperationTimeout, EnableAsyncOperations = options.EnableAsyncOperations }; } /// /// Создает сервис перетаскивания, оптимизированный для сенсорных устройств. /// /// /// Экземпляр с увеличенным порогом перетаскивания. /// /// /// Этот метод создает сервис с увеличенным порогом начала перетаскивания (10.0 пикселей), /// что уменьшает вероятность случайного начала перетаскивания при использовании сенсорного экрана. /// public static IDragDropService CreateTouchOptimizedService() { return new DragDropService { DragStartThreshold = 10.0, AsyncOperationTimeout = 3000, EnableAsyncOperations = true }; } /// /// Создает сервис перетаскивания для точных операций (графические редакторы, карты). /// /// /// Экземпляр с уменьшенным порогом перетаскивания. /// /// /// Этот метод создает сервис с минимальным порогом начала перетаскивания (1.0 пиксель), /// что позволяет начинать перетаскивание с максимальной точностью. /// public static IDragDropService CreatePrecisionDragService() { return new DragDropService { DragStartThreshold = 1.0, AsyncOperationTimeout = 10000, // Больше времени для сложных операций EnableAsyncOperations = true }; } #endregion #region Источники перетаскивания /// /// Создает простой источник перетаскивания с фиксированными данными. /// /// /// Данные, которые будут перетаскиваться. Не может быть null. /// /// /// Разрешенные эффекты перетаскивания. По умолчанию разрешены копирование и перемещение. /// /// /// Экземпляр , который всегда предоставляет указанные данные. /// /// /// Выбрасывается, когда равен null. /// /// /// Этот источник подходит для случаев, когда данные для перетаскивания известны заранее /// и не изменяются в процессе операции. /// public static IDragSource CreateSimpleSource(object data, DragDropEffects allowedEffects = DragDropEffects.Copy | DragDropEffects.Move) { if (data == null) throw new ArgumentNullException(nameof(data)); return new SimpleDragSource(data, allowedEffects); } /// /// Создает источник перетаскивания с отложенной загрузкой данных. /// /// /// Фабрика данных, которая будет вызвана при начале перетаскивания. /// /// /// Функция проверки возможности начала перетаскивания. /// /// /// Разрешенные эффекты перетаскивания. /// /// /// Экземпляр с отложенной загрузкой данных. /// /// /// Этот источник полезен, когда данные для перетаскивания дорого создавать заранее /// или зависят от контекста в момент начала операции. /// public static IDragSource CreateLazySource( Func dataFactory, Func canDragChecker = null, DragDropEffects allowedEffects = DragDropEffects.Copy | DragDropEffects.Move) { if (dataFactory == null) throw new ArgumentNullException(nameof(dataFactory)); return new LazyDragSource(dataFactory, canDragChecker, allowedEffects); } /// /// Создает источник перетаскивания для коллекции элементов. /// /// /// Тип элементов в коллекции. /// /// /// Коллекция элементов для перетаскивания. /// /// /// Функция выбора конкретного элемента для перетаскивания из коллекции. /// /// /// Разрешенные эффекты перетаскивания. /// /// /// Экземпляр для коллекции элементов. /// /// /// Этот источник позволяет перетаскивать элементы из коллекции. Функция /// определяет, какой именно элемент из коллекции будет перетаскиваться в текущем контексте. /// public static IDragSource CreateCollectionSource( IEnumerable items, Func, T> itemSelector, DragDropEffects allowedEffects = DragDropEffects.Copy | DragDropEffects.Move) { if (items == null) throw new ArgumentNullException(nameof(items)); if (itemSelector == null) throw new ArgumentNullException(nameof(itemSelector)); return new CollectionDragSource(items, itemSelector, allowedEffects); } #endregion #region Цели сброса /// /// Создает простую цель сброса, которая принимает данные любого типа. /// /// /// Обработчик, вызываемый при сбросе данных. /// /// /// Экземпляр , который принимает любые данные. /// /// /// Эта цель подходит для простых сценариев, когда не требуется валидация типа данных. /// public static IDropTarget CreateSimpleTarget(Action onDrop) { if (onDrop == null) throw new ArgumentNullException(nameof(onDrop)); return new SimpleDropTarget(onDrop); } /// /// Создает цель сброса с фильтрацией по типу данных. /// /// /// Тип данных, которые может принимать цель. /// /// /// Обработчик, вызываемый при сбросе данных. /// /// /// Экземпляр , который принимает только данные типа . /// /// /// Эта цель автоматически проверяет тип сбрасываемых данных и вызывает обработчик только /// если данные могут быть приведены к указанному типу. /// public static IDropTarget CreateTypedTarget(Action onDrop) where T : class { if (onDrop == null) throw new ArgumentNullException(nameof(onDrop)); return new TypedDropTarget(onDrop); } /// /// Создает цель сброса с пользовательской логикой валидации. /// /// /// Функция проверки возможности приема данных. /// /// /// Обработчик, вызываемый при сбросе данных. /// /// /// Экземпляр с пользовательской логикой валидации. /// /// /// Эта цель позволяет реализовать сложную логику валидации, выходящую за рамки простой проверки типа. /// public static IDropTarget CreateConditionalTarget(Func canAccept, Action onDrop) { if (canAccept == null) throw new ArgumentNullException(nameof(canAccept)); if (onDrop == null) throw new ArgumentNullException(nameof(onDrop)); return new ConditionalDropTarget(canAccept, onDrop); } #endregion #region Вспомогательные методы /// /// Создает стандартные эффекты перетаскивания на основе модификаторов клавиатуры. /// /// /// Нажата ли клавиша Control. /// /// /// Нажата ли клавиша Shift. /// /// /// Нажата ли клавиша Alt. /// /// /// , соответствующие комбинации клавиш. /// /// /// Стандартная логика: /// /// Control + Shift: Link /// Control: Copy /// Shift: Move /// Alt: Link /// Без модификаторов: Move /// /// public static DragDropEffects GetEffectsFromKeys(bool controlKey, bool shiftKey, bool altKey) { return DragDropEffectsExtensions.GetEffectFromKeys(controlKey, shiftKey, altKey); } #endregion } /// /// Опции для настройки сервиса перетаскивания. /// public class DragDropServiceOptions { /// /// Получает или задает порог начала перетаскивания в пикселях. /// /// /// Минимальное расстояние, которое должен пройти курсор, чтобы началась операция перетаскивания. /// Значение по умолчанию: 3.0. /// public double DragStartThreshold { get; set; } = Constants.DragDropConstants.DefaultDragThreshold; /// /// Получает или задает максимальное время ожидания асинхронных операций в миллисекундах. /// /// /// Время в миллисекундах, после которого асинхронная операция будет прервана. /// Значение по умолчанию: 5000. /// public int AsyncOperationTimeout { get; set; } = Constants.DragDropConstants.DefaultAsyncTimeout; /// /// Получает или задает значение, указывающее, включены ли асинхронные операции. /// /// /// true, если асинхронные операции включены; в противном случае — false. /// Значение по умолчанию: true. /// public bool EnableAsyncOperations { get; set; } = true; /// /// Получает или задает интервал автоматической очистки неиспользуемых целей в минутах. /// /// /// Интервал в минутах, через который будут удаляться цели сброса, к которым не было обращений. /// Значение по умолчанию: 10. /// public int CleanupInterval { get; set; } = Constants.DragDropConstants.TargetLifetimeMinutes; } #region Внутренние реализации internal class SimpleDragSource : IDragSource { private readonly object _data; private readonly DragDropEffects _allowedEffects; public SimpleDragSource(object data, DragDropEffects allowedEffects) { _data = data ?? throw new ArgumentNullException(nameof(data)); _allowedEffects = allowedEffects; } public Task TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default) { var dragInfo = new DragInfo(_data, _allowedEffects, startPosition, this); return Task.FromResult(dragInfo); } public Task OnDragCompletedAsync(DragInfo dragInfo, DragDropEffects effects, CancellationToken cancellationToken = default) { return Task.CompletedTask; } public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default) { return Task.CompletedTask; } } internal class LazyDragSource : IDragSource { private readonly Func _dataFactory; private readonly Func _canDragChecker; private readonly DragDropEffects _allowedEffects; public LazyDragSource(Func dataFactory, Func canDragChecker, DragDropEffects allowedEffects) { _dataFactory = dataFactory ?? throw new ArgumentNullException(nameof(dataFactory)); _canDragChecker = canDragChecker ?? (() => true); _allowedEffects = allowedEffects; } public Task TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default) { if (!_canDragChecker()) return Task.FromResult(null); var data = _dataFactory(); if (data == null) return Task.FromResult(null); var dragInfo = new DragInfo(data, _allowedEffects, startPosition, this); return Task.FromResult(dragInfo); } public Task OnDragCompletedAsync(DragInfo dragInfo, DragDropEffects effects, CancellationToken cancellationToken = default) { return Task.CompletedTask; } public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default) { return Task.CompletedTask; } } internal class CollectionDragSource : IDragSource { private readonly IEnumerable _items; private readonly Func, T> _itemSelector; private readonly DragDropEffects _allowedEffects; public CollectionDragSource(IEnumerable items, Func, T> itemSelector, DragDropEffects allowedEffects) { _items = items ?? throw new ArgumentNullException(nameof(items)); _itemSelector = itemSelector ?? throw new ArgumentNullException(nameof(itemSelector)); _allowedEffects = allowedEffects; } public Task TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default) { var selectedItem = _itemSelector(_items); if (selectedItem == null) return Task.FromResult(null); var dragInfo = new DragInfo(selectedItem, _allowedEffects, startPosition, this); return Task.FromResult(dragInfo); } public Task OnDragCompletedAsync(DragInfo dragInfo, DragDropEffects effects, CancellationToken cancellationToken = default) { return Task.CompletedTask; } public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default) { return Task.CompletedTask; } } internal class SimpleDropTarget : IDropTarget { private readonly Action _onDrop; public SimpleDropTarget(Action onDrop) { _onDrop = onDrop ?? throw new ArgumentNullException(nameof(onDrop)); } public Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) { return Task.FromResult(dropInfo.Data != null); } public Task OnDragOverAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) { if (dropInfo.Data != null) { dropInfo.SuggestedEffects = DragDropEffects.Move; } return Task.CompletedTask; } public Task OnDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) { if (dropInfo.Data != null) { _onDrop(dropInfo); dropInfo.MarkAsHandled(); } return Task.CompletedTask; } public Task OnDragLeaveAsync(CancellationToken cancellationToken = default) { return Task.CompletedTask; } } internal class TypedDropTarget : IDropTarget where T : class { private readonly Action _onDrop; public TypedDropTarget(Action onDrop) { _onDrop = onDrop ?? throw new ArgumentNullException(nameof(onDrop)); } public Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) { return Task.FromResult(dropInfo.Data is T); } public Task OnDragOverAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) { if (dropInfo.Data is T) { dropInfo.SuggestedEffects = DragDropEffects.Move; } return Task.CompletedTask; } public Task OnDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) { if (dropInfo.Data is T data) { _onDrop(data, dropInfo); dropInfo.MarkAsHandled(); } return Task.CompletedTask; } public Task OnDragLeaveAsync(CancellationToken cancellationToken = default) { return Task.CompletedTask; } } internal class ConditionalDropTarget : IDropTarget { private readonly Func _canAccept; private readonly Action _onDrop; public ConditionalDropTarget(Func canAccept, Action onDrop) { _canAccept = canAccept ?? throw new ArgumentNullException(nameof(canAccept)); _onDrop = onDrop ?? throw new ArgumentNullException(nameof(onDrop)); } public Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) { return Task.FromResult(_canAccept(dropInfo)); } public Task OnDragOverAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) { if (_canAccept(dropInfo)) { dropInfo.SuggestedEffects = DragDropEffects.Move; } return Task.CompletedTask; } public Task OnDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) { if (_canAccept(dropInfo)) { _onDrop(dropInfo); dropInfo.MarkAsHandled(); } return Task.CompletedTask; } public Task OnDragLeaveAsync(CancellationToken cancellationToken = default) { return Task.CompletedTask; } } #endregion