From 2bd7d3c47403ad3971125101d710526e3027e5d1 Mon Sep 17 00:00:00 2001 From: FrigaT Date: Sun, 25 Jan 2026 02:52:07 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=20UI=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20Core?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Abstractions/IDragDropHost.cs | 56 ++- .../Abstractions/IDragVisualProvider.cs | 52 ++- .../Abstractions/IDropVisualAdorner.cs | 37 +- .../Behaviors/DragSourceBehaviorBase.cs | 208 +++++++-- .../Behaviors/DropTargetBehaviorBase.cs | 157 ++++++- .../Extensions/ServiceCollectionExtensions.cs | 182 +++++++- Lattice.UI.DragDrop/README.md | 412 ++++++++++++++---- 7 files changed, 923 insertions(+), 181 deletions(-) diff --git a/Lattice.UI.DragDrop/Abstractions/IDragDropHost.cs b/Lattice.UI.DragDrop/Abstractions/IDragDropHost.cs index 999d9d6..427c590 100644 --- a/Lattice.UI.DragDrop/Abstractions/IDragDropHost.cs +++ b/Lattice.UI.DragDrop/Abstractions/IDragDropHost.cs @@ -5,37 +5,77 @@ namespace Lattice.UI.DragDrop.Abstractions; /// /// Хост для отображения визуальных элементов перетаскивания. /// +/// +/// +/// Интерфейс предоставляет абстракцию для управления визуальными элементами +/// перетаскивания в различных UI-фреймворках (WPF, Avalonia, MAUI и т.д.). +/// +/// +/// Реализация должна обеспечивать корректное отображение визуальных элементов +/// поверх других элементов UI и их своевременное удаление при завершении операций. +/// +/// public interface IDragDropHost { /// - /// Показывает визуальное представление перетаскивания. + /// Показывает визуальное представление перетаскиваемого элемента. /// - /// Визуальное представление. - /// Начальная позиция. + /// Визуальное представление элемента для перетаскивания. + /// Начальная позиция визуального элемента в экранных координатах. + /// + /// + /// Метод должен отобразить переданный визуальный элемент в указанной позиции. + /// Визуальный элемент должен следовать за курсором мыши при обновлении через . + /// + /// + /// Визуальный элемент должен отображаться поверх всех других элементов интерфейса. + /// + /// void ShowDragVisual(object dragVisual, Point position); /// /// Обновляет позицию визуального представления перетаскивания. /// - /// Визуальное представление. - /// Новая позиция. + /// Визуальное представление, позиция которого должна быть обновлена. + /// Новая позиция в экранных координатах. + /// + /// Метод должен обновлять позицию уже отображаемого визуального элемента + /// с минимальной задержкой для плавного перемещения. + /// void UpdateDragVisualPosition(object dragVisual, Point position); /// /// Скрывает визуальное представление перетаскивания. /// - /// Визуальное представление. + /// Визуальное представление для скрытия. + /// + /// После вызова этого метода визуальный элемент должен быть полностью + /// удален из визуального дерева и его ресурсы освобождены. + /// void HideDragVisual(object dragVisual); /// /// Показывает визуальную обратную связь для цели сброса. /// - /// Элемент обратной связи. + /// Элемент обратной связи для отображения. + /// + /// + /// Метод должен отобразить элемент обратной связи (например, подсветку, рамку или индикатор позиции) + /// для визуального указания возможности сброса на целевой элемент. + /// + /// + /// Элемент обратной связи должен отображаться поверх целевого элемента, но под перетаскиваемым визуальным элементом. + /// + /// void ShowDropAdorner(IDropVisualAdorner adorner); /// /// Скрывает визуальную обратную связь для цели сброса. /// - /// Элемент обратной связи. + /// Элемент обратной связи для скрытия. + /// + /// После вызова этого метода элемент обратной связи должен быть + /// удален из визуального дерева и его ресурсы освобождены. + /// void HideDropAdorner(IDropVisualAdorner adorner); } \ No newline at end of file diff --git a/Lattice.UI.DragDrop/Abstractions/IDragVisualProvider.cs b/Lattice.UI.DragDrop/Abstractions/IDragVisualProvider.cs index dbdc393..e307125 100644 --- a/Lattice.UI.DragDrop/Abstractions/IDragVisualProvider.cs +++ b/Lattice.UI.DragDrop/Abstractions/IDragVisualProvider.cs @@ -6,26 +6,62 @@ namespace Lattice.UI.DragDrop.Abstractions; /// /// Поставщик визуального представления для перетаскиваемого элемента. /// +/// +/// +/// Интерфейс предоставляет абстракцию для создания и управления визуальными +/// представлениями элементов при операции перетаскивания. +/// +/// +/// Реализации могут предоставлять различные стили визуального представления: +/// от простого клонирования оригинального элемента до сложных анимированных представлений. +/// +/// public interface IDragVisualProvider { /// - /// Создает визуальное представление для перетаскивания. + /// Создает визуальное представление для перетаскивания на основе информации о перетаскивании. /// - /// Информация о перетаскивании. + /// Информация о перетаскивании, содержащая данные и метаданные операции. /// Начальная позиция в экранных координатах. - /// Объект, представляющий визуальное отображение. - object CreateDragVisual(DragInfo dragInfo, Point initialPosition); + /// Объект, представляющий визуальное отображение для перетаскивания. + /// + /// + /// Созданный визуальный элемент должен: + /// 1. Отображать репрезентативное представление перетаскиваемых данных + /// 2. Иметь прозрачный фон или альфа-канал для плавного отображения + /// 3. Быть легковесным для обеспечения плавной анимации + /// 4. Поддерживать возможность изменения позиции через + /// + /// + /// Метод может возвращать null, если визуальное представление не требуется. + /// + /// + object? CreateDragVisual(DragInfo dragInfo, Point initialPosition); /// - /// Обновляет позицию визуального представления. + /// Обновляет позицию визуального представления перетаскивания. /// - /// Визуальное представление. - /// Новая позиция. + /// Визуальное представление, созданное методом . + /// Новая позиция в экранных координатах. + /// + /// Метод должен обновить позицию визуального элемента максимально эффективно, + /// так как он вызывается часто во время операции перетаскивания. + /// void UpdateDragVisualPosition(object dragVisual, Point position); /// /// Освобождает ресурсы визуального представления. /// - /// Визуальное представление. + /// Визуальное представление для освобождения. + /// + /// + /// Метод должен освободить все ресурсы, связанные с визуальным представлением, + /// включая графические ресурсы, подписки на события и временные данные. + /// + /// + /// Этот метод гарантированно вызывается после завершения операции перетаскивания, + /// независимо от её успешности. + /// + /// void ReleaseDragVisual(object dragVisual); } \ No newline at end of file diff --git a/Lattice.UI.DragDrop/Abstractions/IDropVisualAdorner.cs b/Lattice.UI.DragDrop/Abstractions/IDropVisualAdorner.cs index 1affbdd..49f0b47 100644 --- a/Lattice.UI.DragDrop/Abstractions/IDropVisualAdorner.cs +++ b/Lattice.UI.DragDrop/Abstractions/IDropVisualAdorner.cs @@ -6,23 +6,54 @@ namespace Lattice.UI.DragDrop.Abstractions; /// /// Визуальный элемент, показывающий обратную связь при наведении на цель сброса. /// +/// +/// +/// Интерфейс предоставляет абстракцию для визуальных индикаторов, которые +/// показывают пользователю возможность сброса данных на целевом элементе. +/// +/// +/// Реализации могут предоставлять различные типы визуальной обратной связи: +/// подсветку элемента, отображение индикатора позиции, изменение курсора и т.д. +/// +/// public interface IDropVisualAdorner { /// /// Показывает визуальную обратную связь для цели сброса. /// - /// Информация о сбросе. - /// Границы цели. + /// Информация о потенциальном сбросе, включая данные и позицию. + /// Границы целевого элемента в экранных координатах. + /// + /// + /// Метод должен отобразить визуальную обратную связь, соответствующую типу данных + /// и контексту сброса. Обратная связь должна явно указывать на возможность + /// сброса и ожидаемый эффект (копирование, перемещение и т.д.). + /// + /// + /// Обратная связь должна учитывать свойства + /// и для кастомизации отображения. + /// + /// void Show(DropInfo dropInfo, Rect targetBounds); /// /// Обновляет позицию и состояние визуальной обратной связи. /// - /// Информация о сбросе. + /// Текущая информация о сбросе, включая обновленную позицию и состояние. + /// + /// Метод вызывается при каждом перемещении курсора над целью и должен + /// обновлять визуальную обратную связь в соответствии с новой позицией + /// и состоянием операции. + /// void Update(DropInfo dropInfo); /// /// Скрывает визуальную обратную связь. /// + /// + /// Метод должен скрыть и очистить все визуальные элементы обратной связи. + /// После вызова этого метода ресурсы могут быть освобождены или переиспользованы + /// для следующих операций. + /// void Hide(); } \ No newline at end of file diff --git a/Lattice.UI.DragDrop/Behaviors/DragSourceBehaviorBase.cs b/Lattice.UI.DragDrop/Behaviors/DragSourceBehaviorBase.cs index d21c917..4a2c91f 100644 --- a/Lattice.UI.DragDrop/Behaviors/DragSourceBehaviorBase.cs +++ b/Lattice.UI.DragDrop/Behaviors/DragSourceBehaviorBase.cs @@ -10,9 +10,20 @@ using System.Threading.Tasks; namespace Lattice.UI.DragDrop.Behaviors; /// -/// Базовый класс поведения источника перетаскивания. +/// Базовый класс поведения источника перетаскивания для UI элементов. /// -/// Тип UI элемента. +/// Тип UI элемента, к которому прикрепляется поведение. +/// +/// +/// Этот класс предоставляет базовую реализацию поведения перетаскивания для UI элементов. +/// Он обрабатывает события мыши/тач, управляет порогом начала перетаскивания и +/// интегрируется с сервисом из ядра. +/// +/// +/// Производные классы должны реализовать абстрактные методы для конкретной +/// UI-платформы и предоставить логику создания информации о перетаскивании. +/// +/// public abstract class DragSourceBehaviorBase : IDragSource where TElement : class { @@ -20,10 +31,15 @@ public abstract class DragSourceBehaviorBase : IDragSource private Point _dragStartPosition; private bool _isDragging; private TElement? _associatedElement; + private CancellationTokenSource? _dragCancellationTokenSource; /// - /// Получает или задает связанный элемент. + /// Получает или задает связанный UI элемент. /// + /// + /// Элемент UI, к которому прикреплено поведение перетаскивания. + /// При изменении значения автоматически выполняется переподключение событий. + /// protected TElement? AssociatedElement { get => _associatedElement; @@ -39,8 +55,12 @@ public abstract class DragSourceBehaviorBase : IDragSource } /// - /// Получает сервис перетаскивания. + /// Получает сервис перетаскивания из контейнера зависимостей. /// + /// + /// Экземпляр , используемый для управления операциями перетаскивания. + /// При первом обращении выполняется получение сервиса из . + /// protected IDragDropService DragDropService { get @@ -54,22 +74,34 @@ public abstract class DragSourceBehaviorBase : IDragSource } /// - /// Получает провайдер сервисов. + /// Получает провайдер сервисов для разрешения зависимостей. /// protected IServiceProvider ServiceProvider { get; } + /// + /// Получает значение, указывающее, выполняется ли в данный момент операция перетаскивания. + /// + protected bool IsDragging => _isDragging; + /// /// Инициализирует новый экземпляр класса . /// - /// Провайдер сервисов. + /// Провайдер сервисов для разрешения зависимостей. + /// + /// Выбрасывается, когда равен null. + /// protected DragSourceBehaviorBase(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } /// - /// Вызывается при прикреплении к элементу. + /// Вызывается при прикреплении поведения к элементу. /// + /// + /// Реализация по умолчанию подписывается на события элемента через . + /// Производные классы могут переопределить этот метод для дополнительной инициализации. + /// protected virtual void AttachToElement() { if (_associatedElement != null) @@ -79,8 +111,12 @@ public abstract class DragSourceBehaviorBase : IDragSource } /// - /// Вызывается при откреплении от элемента. + /// Вызывается при откреплении поведения от элемента. /// + /// + /// Реализация по умолчанию отписывается от событий элемента через . + /// Производные классы могут переопределить этот метод для дополнительной очистки. + /// protected virtual void DetachFromElement() { if (_associatedElement != null) @@ -90,36 +126,68 @@ public abstract class DragSourceBehaviorBase : IDragSource } /// - /// Подписывается на события элемента. + /// Подписывается на события элемента, необходимые для отслеживания начала перетаскивания. /// - /// Элемент. + /// Элемент, к событиям которого нужно подписаться. + /// + /// Производные классы должны реализовать этот метод для подписки на события конкретной + /// UI-платформы (например, MouseDown для WPF, PointerPressed для Avalonia). + /// protected abstract void SubscribeToEvents(TElement element); /// /// Отписывается от событий элемента. /// - /// Элемент. + /// Элемент, от событий которого нужно отписаться. + /// + /// Производные классы должны реализовать этот метод для корректной отписки + /// от событий, на которые была выполнена подписка в . + /// protected abstract void UnsubscribeFromEvents(TElement element); /// - /// Обрабатывает начало взаимодействия (например, нажатие мыши). + /// Обрабатывает начало взаимодействия с элементом (например, нажатие кнопки мыши). /// - /// Позиция в координатах элемента. - protected virtual async Task OnInteractionStarted(Point position) + /// Позиция взаимодействия в координатах элемента. + /// Задача, представляющая асинхронную операцию. + /// + /// + /// Этот метод вызывается из обработчиков событий UI-платформы при начале + /// взаимодействия, которое может привести к перетаскиванию. + /// + /// + /// Реализация по умолчанию сохраняет начальную позицию для последующей + /// проверки порога перетаскивания. + /// + /// + protected virtual Task OnInteractionStarted(Point position) { if (_isDragging) - return; + return Task.CompletedTask; _dragStartPosition = position; + _dragCancellationTokenSource = new CancellationTokenSource(); + return Task.CompletedTask; } /// - /// Обрабатывает перемещение во время взаимодействия. + /// Обрабатывает перемещение во время взаимодействия с элементом. /// - /// Позиция в координатах элемента. + /// Текущая позиция взаимодействия в координатах элемента. + /// Задача, представляющая асинхронную операцию. + /// + /// + /// Этот метод вызывается при перемещении курсора/тач-точки во время удержания + /// взаимодействия (например, перемещение мыши с нажатой кнопкой). + /// + /// + /// Реализация по умолчанию проверяет, превышено ли расстояние от начальной + /// точки порога перетаскивания, и если да - начинает операцию перетаскивания. + /// + /// protected virtual async Task OnInteractionMoved(Point position) { - if (_isDragging) + if (_isDragging || AssociatedElement == null) return; var distance = CalculateDistance(_dragStartPosition, position); @@ -130,20 +198,41 @@ public abstract class DragSourceBehaviorBase : IDragSource } /// - /// Обрабатывает завершение взаимодействия. + /// Обрабатывает завершение взаимодействия с элементом. /// - protected virtual async Task OnInteractionEnded() + /// Задача, представляющая асинхронную операцию. + /// + /// + /// Этот метод вызывается при завершении взаимодействия (например, отпускании кнопки мыши). + /// + /// + /// Реализация по умолчанию сбрасывает состояние поведения, если перетаскивание не было начато. + /// + /// + protected virtual Task OnInteractionEnded() { // Сброс состояния, если перетаскивание не началось if (!_isDragging) { Reset(); } + return Task.CompletedTask; } /// - /// Обрабатывает отмену взаимодействия. + /// Обрабатывает отмену взаимодействия с элементом. /// + /// Задача, представляющая асинхронную операцию. + /// + /// + /// Этот метод вызывается при отмене взаимодействия (например, нажатии клавиши Escape + /// или выходе за пределы допустимой области). + /// + /// + /// Реализация по умолчанию отменяет текущую операцию перетаскивания, если она активна, + /// и сбрасывает состояние поведения. + /// + /// protected virtual async Task OnInteractionCancelled() { if (_isDragging) @@ -156,16 +245,35 @@ public abstract class DragSourceBehaviorBase : IDragSource /// /// Начинает операцию перетаскивания. /// + /// Задача, представляющая асинхронную операцию. + /// + /// + /// Этот метод преобразует начальную позицию в экранные координаты и вызывает + /// сервис перетаскивания для начала операции. + /// + /// + /// Операция начинается только если поведение прикреплено к элементу и + /// не выполняется другая операция перетаскивания. + /// + /// protected virtual async Task StartDragOperation() { - if (_isDragging || AssociatedElement == null) + if (_isDragging || AssociatedElement == null || _dragCancellationTokenSource == null) return; // Получаем начальную позицию в экранных координатах var screenPosition = ConvertToScreenCoordinates(_dragStartPosition); // Начинаем перетаскивание - _isDragging = await DragDropService.StartDragAsync(this, screenPosition); + try + { + _isDragging = await DragDropService.StartDragAsync(this, screenPosition); + } + catch (OperationCanceledException) + { + // Операция была отменена + Reset(); + } } /// @@ -173,11 +281,18 @@ public abstract class DragSourceBehaviorBase : IDragSource /// /// Точка в координатах элемента. /// Точка в экранных координатах. + /// + /// Производные классы должны реализовать этот метод для преобразования + /// координат в соответствии с конкретной UI-платформой. + /// protected abstract Point ConvertToScreenCoordinates(Point point); /// /// Вычисляет расстояние между двумя точками. /// + /// Первая точка. + /// Вторая точка. + /// Расстояние между точками. protected virtual double CalculateDistance(Point p1, Point p2) { var dx = p2.X - p1.X; @@ -188,32 +303,33 @@ public abstract class DragSourceBehaviorBase : IDragSource /// /// Сбрасывает состояние поведения. /// + /// + /// Этот метод очищает все временные данные и отменяет токены отмены, + /// связанные с текущей операцией перетаскивания. + /// protected virtual void Reset() { _isDragging = false; _dragStartPosition = default; + + _dragCancellationTokenSource?.Dispose(); + _dragCancellationTokenSource = null; } #region IDragSource Implementation /// - public abstract Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync(CancellationToken ct = default); + public abstract Task TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default); /// - public virtual async Task StartDragAsync(DragInfo dragInfo, CancellationToken ct = default) - { - return true; - } - - /// - public virtual async Task DragCompletedAsync(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects, CancellationToken ct = default) + public async Task OnDragCompletedAsync(DragInfo dragInfo, Lattice.Core.DragDrop.Enums.DragDropEffects effects, CancellationToken cancellationToken = default) { _isDragging = false; OnDragCompleted(dragInfo, effects); } /// - public virtual async Task DragCancelledAsync(DragInfo dragInfo, CancellationToken ct = default) + public async Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default) { _isDragging = false; OnDragCancelled(dragInfo); @@ -224,18 +340,27 @@ public abstract class DragSourceBehaviorBase : IDragSource #region Virtual Methods for Derived Classes /// - /// Вызывается при успешном завершении перетаскивания. + /// Вызывается при успешном завершении операции перетаскивания. /// - /// Информация о перетаскивании. - /// Примененные эффекты. - protected virtual void OnDragCompleted(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects) + /// Информация о перетаскивании, использованная в операции. + /// Эффекты, примененные при завершении операции. + /// + /// Производные классы могут переопределить этот метод для выполнения + /// дополнительных действий после успешного завершения перетаскивания, + /// например, удаления исходного элемента при перемещении. + /// + protected virtual void OnDragCompleted(DragInfo dragInfo, Lattice.Core.DragDrop.Enums.DragDropEffects effects) { } /// - /// Вызывается при отмене перетаскивания. + /// Вызывается при отмене операции перетаскивания. /// - /// Информация о перетаскивании. + /// Информация о перетаскивании, использованная в операции. + /// + /// Производные классы могут переопределить этот метод для выполнения + /// действий по восстановлению состояния после отмены перетаскивания. + /// protected virtual void OnDragCancelled(DragInfo dragInfo) { } @@ -243,11 +368,16 @@ public abstract class DragSourceBehaviorBase : IDragSource #endregion /// - /// Освобождает ресурсы. + /// Открепляет поведение от элемента и освобождает ресурсы. /// + /// + /// После вызова этого метода поведение больше не будет обрабатывать события + /// элемента и может быть безопасно удалено. + /// public virtual void Detach() { DetachFromElement(); _associatedElement = null; + Reset(); } } \ No newline at end of file diff --git a/Lattice.UI.DragDrop/Behaviors/DropTargetBehaviorBase.cs b/Lattice.UI.DragDrop/Behaviors/DropTargetBehaviorBase.cs index 19fa0ae..135f7b1 100644 --- a/Lattice.UI.DragDrop/Behaviors/DropTargetBehaviorBase.cs +++ b/Lattice.UI.DragDrop/Behaviors/DropTargetBehaviorBase.cs @@ -10,9 +10,20 @@ using System.Threading.Tasks; namespace Lattice.UI.DragDrop.Behaviors; /// -/// Базовый класс поведения цели сброса. +/// Базовый класс поведения цели сброса для UI элементов. /// -/// Тип UI элемента. +/// Тип UI элемента, к которому прикрепляется поведение. +/// +/// +/// Этот класс предоставляет базовую реализацию поведения цели сброса для UI элементов. +/// Он автоматически регистрирует элемент в сервисе перетаскивания, обновляет его границы +/// при изменении размера или позиции и предоставляет методы для обработки событий сброса. +/// +/// +/// Производные классы должны реализовать абстрактные методы для конкретной +/// UI-платформы и предоставить логику проверки и обработки сбрасываемых данных. +/// +/// public abstract class DropTargetBehaviorBase : IDropTarget where TElement : class { @@ -22,8 +33,12 @@ public abstract class DropTargetBehaviorBase : IDropTarget private Rect _currentBounds; /// - /// Получает или задает связанный элемент. + /// Получает или задает связанный UI элемент. /// + /// + /// Элемент UI, к которому прикреплено поведение цели сброса. + /// При изменении значения автоматически выполняется перерегистрация в сервисе перетаскивания. + /// protected TElement? AssociatedElement { get => _associatedElement; @@ -41,16 +56,28 @@ public abstract class DropTargetBehaviorBase : IDropTarget /// /// Получает или задает приоритет цели сброса. /// + /// + /// Цели с более высоким приоритетом проверяются первыми при нахождении курсора + /// в области нескольких целей. Значение по умолчанию: 0. + /// public int Priority { get; set; } /// /// Получает или задает группу цели сброса. /// + /// + /// Имя группы для группового управления целями сброса. Может использоваться + /// для массовой отмены регистрации целей или применения общих настроек. + /// public string? Group { get; set; } /// - /// Получает сервис перетаскивания. + /// Получает сервис перетаскивания из контейнера зависимостей. /// + /// + /// Экземпляр , используемый для регистрации цели сброса. + /// При первом обращении выполняется получение сервиса из . + /// protected IDragDropService DragDropService { get @@ -64,27 +91,47 @@ public abstract class DropTargetBehaviorBase : IDropTarget } /// - /// Получает провайдер сервисов. + /// Получает провайдер сервисов для разрешения зависимостей. /// protected IServiceProvider ServiceProvider { get; } /// /// Получает текущие границы элемента в экранных координатах. /// + /// + /// Прямоугольник, описывающий границы элемента в экранных координатах. + /// Значение автоматически обновляется при изменении размера или позиции элемента. + /// protected Rect CurrentBounds => _currentBounds; + /// + /// Получает уникальный идентификатор регистрации цели в сервисе перетаскивания. + /// + /// + /// Идентификатор, возвращенный методом , + /// или null, если цель не зарегистрирована. + /// + protected string? RegistrationId => _registrationId; + /// /// Инициализирует новый экземпляр класса . /// - /// Провайдер сервисов. + /// Провайдер сервисов для разрешения зависимостей. + /// + /// Выбрасывается, когда равен null. + /// protected DropTargetBehaviorBase(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } /// - /// Вызывается при прикреплении к элементу. + /// Вызывается при прикреплении поведения к элементу. /// + /// + /// Реализация по умолчанию подписывается на события элемента, обновляет границы + /// и регистрирует цель в сервисе перетаскивания. + /// protected virtual void AttachToElement() { if (_associatedElement != null) @@ -96,8 +143,12 @@ public abstract class DropTargetBehaviorBase : IDropTarget } /// - /// Вызывается при откреплении от элемента. + /// Вызывается при откреплении поведения от элемента. /// + /// + /// Реализация по умолчанию отписывается от событий элемента и отменяет + /// регистрацию цели в сервисе перетаскивания. + /// protected virtual void DetachFromElement() { if (_associatedElement != null) @@ -108,20 +159,32 @@ public abstract class DropTargetBehaviorBase : IDropTarget } /// - /// Подписывается на события элемента. + /// Подписывается на события элемента, необходимые для отслеживания изменений размера и позиции. /// - /// Элемент. + /// Элемент, к событиям которого нужно подписаться. + /// + /// Производные классы должны реализовать этот метод для подписки на события конкретной + /// UI-платформы (например, SizeChanged, LayoutUpdated для WPF). + /// protected abstract void SubscribeToEvents(TElement element); /// /// Отписывается от событий элемента. /// - /// Элемент. + /// Элемент, от событий которого нужно отписаться. + /// + /// Производные классы должны реализовать этот метод для корректной отписки + /// от событий, на которые была выполнена подписка в . + /// protected abstract void UnsubscribeFromEvents(TElement element); /// /// Обновляет границы элемента в экранных координатах. /// + /// + /// Этот метод вызывается при изменении размера или позиции элемента для + /// обновления области, в которой цель может принимать сбрасываемые данные. + /// protected virtual void UpdateBounds() { if (_associatedElement != null) @@ -139,13 +202,27 @@ public abstract class DropTargetBehaviorBase : IDropTarget /// /// Получает границы элемента в экранных координатах. /// - /// Элемент. - /// Границы в экранных координатах. + /// Элемент, границы которого нужно получить. + /// Границы элемента в экранных координатах. + /// + /// Производные классы должны реализовать этот метод для получения границ + /// элемента в соответствии с конкретной UI-платформой. + /// protected abstract Rect GetScreenBounds(TElement element); /// /// Регистрирует цель в сервисе перетаскивания. /// + /// + /// + /// Этот метод регистрирует текущий объект как цель сброса в сервисе перетаскивания + /// с указанными приоритетом и группой. + /// + /// + /// Регистрация выполняется только если поведение прикреплено к элементу и + /// цель еще не зарегистрирована. + /// + /// protected virtual void RegisterToService() { if (_associatedElement != null && _registrationId == null) @@ -158,6 +235,10 @@ public abstract class DropTargetBehaviorBase : IDropTarget /// /// Отменяет регистрацию цели в сервисе перетаскивания. /// + /// + /// Этот метод отменяет регистрацию цели сброса, освобождая ресурсы + /// в сервисе перетаскивания и предотвращая дальнейшую обработку событий. + /// protected virtual void UnregisterFromService() { if (_registrationId != null) @@ -170,6 +251,10 @@ public abstract class DropTargetBehaviorBase : IDropTarget /// /// Вызывается при изменении размера или позиции элемента. /// + /// + /// Производные классы должны вызывать этот метод из обработчиков событий + /// изменения размера или позиции элемента для обновления границ цели. + /// protected virtual void OnElementLayoutChanged() { UpdateBounds(); @@ -181,7 +266,7 @@ public abstract class DropTargetBehaviorBase : IDropTarget public abstract Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct = default); /// - public virtual async Task DragOverAsync(DropInfo dropInfo, CancellationToken ct = default) + public virtual async Task OnDragOverAsync(DropInfo dropInfo, CancellationToken ct = default) { // Базовая реализация устанавливает эффект по умолчанию if (await CanAcceptDropAsync(dropInfo)) @@ -207,22 +292,58 @@ public abstract class DropTargetBehaviorBase : IDropTarget } /// - public abstract Task DropAsync(DropInfo dropInfo, CancellationToken ct = default); + public abstract Task OnDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default); /// - public virtual async Task DragLeaveAsync(CancellationToken ct = default) + public virtual Task OnDragLeaveAsync(CancellationToken cancellationToken = default) { - // Базовая реализация не делает ничего + // Базовая реализация не выполняет действий при выходе курсора из области цели + return Task.CompletedTask; } #endregion /// - /// Освобождает ресурсы. + /// Открепляет поведение от элемента и освобождает все связанные ресурсы. /// + /// + /// + /// Этот метод выполняет следующие действия: + /// + /// + /// Отписывается от всех событий связанного элемента + /// Отменяет регистрацию цели в сервисе перетаскивания + /// Освобождает ссылку на связанный элемент + /// Очищает все временные данные и состояние + /// + /// + /// После вызова этого метода поведение больше не будет обрабатывать события + /// элемента и не будет реагировать на операции перетаскивания. Поведение + /// можно безопасно удалить после вызова этого метода. + /// + /// + /// Важно: Этот метод должен быть вызван перед удалением + /// элемента из визуального дерева или перед заменой поведения, чтобы + /// предотвратить утечки памяти и непредсказуемое поведение системы. + /// + /// + /// + /// // Пример использования + /// var dropBehavior = new MyDropTargetBehavior(serviceProvider); + /// dropBehavior.AssociatedElement = myElement; + /// + /// // ... использование поведения ... + /// + /// // Перед удалением элемента или поведения + /// dropBehavior.Detach(); + /// + /// + /// public virtual void Detach() { DetachFromElement(); _associatedElement = null; + _registrationId = null; + _currentBounds = default; } } \ No newline at end of file diff --git a/Lattice.UI.DragDrop/Extensions/ServiceCollectionExtensions.cs b/Lattice.UI.DragDrop/Extensions/ServiceCollectionExtensions.cs index 54715da..b6223bb 100644 --- a/Lattice.UI.DragDrop/Extensions/ServiceCollectionExtensions.cs +++ b/Lattice.UI.DragDrop/Extensions/ServiceCollectionExtensions.cs @@ -1,52 +1,194 @@ -using Lattice.UI.DragDrop.Abstractions; +using Lattice.Core.DragDrop.Services; +using Lattice.UI.DragDrop.Abstractions; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace Lattice.UI.DragDrop.Extensions; /// -/// Методы расширения для регистрации сервисов перетаскивания. +/// Методы расширения для регистрации сервисов перетаскивания в контейнере зависимостей. /// public static class ServiceCollectionExtensions { /// - /// Добавляет сервисы перетаскивания. + /// Добавляет сервисы перетаскивания в контейнер зависимостей. /// + /// Коллекция сервисов для регистрации. + /// Коллекция сервисов с зарегистрированными сервисами перетаскивания. + /// + /// + /// Этот метод регистрирует основные сервисы перетаскивания, необходимые для работы системы: + /// + /// + /// Основной сервис управления перетаскиванием () + /// Абстракции для визуального представления и обратной связи + /// Провайдеры по умолчанию с безопасной реализацией + /// + /// + /// Для полноценной работы в конкретной UI-платформе (WPF, Avalonia, MAUI и т.д.) + /// необходимо переопределить регистрации платформенными реализациями. + /// + /// public static IServiceCollection AddLatticeDragDrop(this IServiceCollection services) { - // Регистрируем абстракции, которые будут реализованы в платформенных проектах - services.AddSingleton(typeof(IDragVisualProvider), typeof(DefaultDragVisualProvider)); - services.AddSingleton(typeof(IDropVisualAdorner), typeof(DefaultDropVisualAdorner)); - services.AddSingleton(typeof(IDragDropHost), typeof(DefaultDragDropHost)); + // Регистрация основного сервиса перетаскивания + services.TryAddSingleton(); + + // Регистрация UI-специфичных абстракций с реализациями по умолчанию + // Эти реализации безопасны (ничего не делают), но могут быть переопределены платформенными реализациями + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); return services; } /// - /// Реализация по умолчанию для платформ, которые еще не имеют своей реализации. + /// Добавляет или заменяет реализацию поставщика визуального представления перетаскивания. /// + /// Тип поставщика визуального представления. + /// Коллекция сервисов. + /// Коллекция сервисов. + /// + /// Используйте этот метод для регистрации платформенной реализации поставщика визуального представления. + /// + public static IServiceCollection AddDragVisualProvider(this IServiceCollection services) + where TProvider : class, IDragVisualProvider + { + services.Replace(ServiceDescriptor.Singleton()); + return services; + } + + /// + /// Добавляет или заменяет реализацию визуальной обратной связи для цели сброса. + /// + /// Тип элемента визуальной обратной связи. + /// Коллекция сервисов. + /// Коллекция сервисов. + /// + /// Используйте этот метод для регистрации платформенной реализации визуальной обратной связи. + /// + public static IServiceCollection AddDropVisualAdorner(this IServiceCollection services) + where TAdorner : class, IDropVisualAdorner + { + services.Replace(ServiceDescriptor.Singleton()); + return services; + } + + /// + /// Добавляет или заменяет реализацию хоста для отображения визуальных элементов. + /// + /// Тип хоста визуальных элементов. + /// Коллекция сервисов. + /// Коллекция сервисов. + /// + /// Используйте этот метод для регистрации платформенной реализации хоста визуальных элементов. + /// + public static IServiceCollection AddDragDropHost(this IServiceCollection services) + where THost : class, IDragDropHost + { + services.Replace(ServiceDescriptor.Singleton()); + return services; + } + + #region Default Implementations + + /// + /// Безопасная реализация поставщика визуального представления по умолчанию. + /// + /// + /// Эта реализация ничего не делает и используется как заглушка до тех пор, + /// пока не будет предоставлена платформенная реализация. + /// private class DefaultDragVisualProvider : IDragVisualProvider { - public object CreateDragVisual(Core.DragDrop.Models.DragInfo dragInfo, Core.Geometry.Point initialPosition) - => new object(); + /// + public object? CreateDragVisual(Core.DragDrop.Models.DragInfo dragInfo, Core.Geometry.Point initialPosition) + { + // Безопасная реализация: не создает визуальное представление + return null; + } - public void UpdateDragVisualPosition(object dragVisual, Core.Geometry.Point position) { } + /// + public void UpdateDragVisualPosition(object dragVisual, Core.Geometry.Point position) + { + // Ничего не делаем, так как визуальное представление не было создано + } - public void ReleaseDragVisual(object dragVisual) { } + /// + public void ReleaseDragVisual(object dragVisual) + { + // Ничего не делаем, так как визуальное представление не было создано + } } + /// + /// Безопасная реализация визуальной обратной связи по умолчанию. + /// + /// + /// Эта реализация ничего не делает и используется как заглушка до тех пор, + /// пока не будет предоставлена платформенная реализация. + /// private class DefaultDropVisualAdorner : IDropVisualAdorner { - public void Show(Core.DragDrop.Models.DropInfo dropInfo, Core.Geometry.Rect targetBounds) { } - public void Update(Core.DragDrop.Models.DropInfo dropInfo) { } - public void Hide() { } + /// + public void Show(Core.DragDrop.Models.DropInfo dropInfo, Core.Geometry.Rect targetBounds) + { + // Безопасная реализация: не отображает визуальную обратную связь + } + + /// + public void Update(Core.DragDrop.Models.DropInfo dropInfo) + { + // Ничего не делаем, так как обратная связь не отображается + } + + /// + public void Hide() + { + // Ничего не делаем, так как обратная связь не отображается + } } + /// + /// Безопасная реализация хоста визуальных элементов по умолчанию. + /// + /// + /// Эта реализация ничего не делает и используется как заглушка до тех пор, + /// пока не будет предоставлена платформенная реализация. + /// private class DefaultDragDropHost : IDragDropHost { - public void ShowDragVisual(object dragVisual, Core.Geometry.Point position) { } - public void UpdateDragVisualPosition(object dragVisual, Core.Geometry.Point position) { } - public void HideDragVisual(object dragVisual) { } - public void ShowDropAdorner(IDropVisualAdorner adorner) { } - public void HideDropAdorner(IDropVisualAdorner adorner) { } + /// + public void ShowDragVisual(object dragVisual, Core.Geometry.Point position) + { + // Безопасная реализация: не отображает визуальный элемент + } + + /// + public void UpdateDragVisualPosition(object dragVisual, Core.Geometry.Point position) + { + // Ничего не делаем, так как визуальный элемент не отображается + } + + /// + public void HideDragVisual(object dragVisual) + { + // Ничего не делаем, так как визуальный элемент не отображается + } + + /// + public void ShowDropAdorner(IDropVisualAdorner adorner) + { + // Безопасная реализация: не отображает обратную связь + } + + /// + public void HideDropAdorner(IDropVisualAdorner adorner) + { + // Ничего не делаем, так как обратная связь не отображается + } } + + #endregion } \ No newline at end of file diff --git a/Lattice.UI.DragDrop/README.md b/Lattice.UI.DragDrop/README.md index 4de7289..eb2039d 100644 --- a/Lattice.UI.DragDrop/README.md +++ b/Lattice.UI.DragDrop/README.md @@ -1,136 +1,378 @@ # Lattice.UI.DragDrop -![Lattice Framework](https://img.shields.io/badge/Lattice-UI%20Framework-blueviolet) -![Version](https://img.shields.io/badge/version-1.0.0-green) -![License](https://img.shields.io/badge/license-MIT-blue) +UI-слой библиотеки перетаскивания (drag-and-drop) для платформ .NET. -Кроссплатформенные абстракции для системы перетаскивания в Lattice UI Framework. +## 📋 Обзор -## 📦 О проекте +Lattice.UI.DragDrop предоставляет абстракции и базовые реализации для интеграции системы перетаскивания Lattice.Core.DragDrop с различными UI-фреймворками (WPF, Avalonia, MAUI и т.д.). -`Lattice.UI.DragDrop` предоставляет платформонезависимые интерфейсы и базовые классы для реализации drag-and-drop функциональности. -Этот проект служит основой для конкретных реализаций на различных платформах (WinUI, Uno Platform, MAUI и т.д.). +### ✨ Основные возможности -## 🎯 Особенности +- ✅ **Кросс-платформенные абстракции** - единый API для всех UI-фреймворков +- ✅ **Готовые базовые классы** для быстрой реализации поведения перетаскивания +- ✅ **Поддержка визуальной обратной связи** через плагинную архитектуру +- ✅ **Интеграция с DI-контейнерами** через Microsoft.Extensions.DependencyInjection +- ✅ **Безопасные реализации по умолчанию** для упрощения разработки +- ✅ **Расширяемость** через наследование и переопределение -- **Абстрактные интерфейсы** для источников перетаскивания и целей сброса -- **Базовые классы поведения** для упрощения реализации -- **Платформонезависимая архитектура** -- **Поддержка сложных сценариев** (переупорядочивание, вложенное перетаскивание) -- **Расширяемая система событий** +## 🏗️ Архитектура -## 🔧 Интерфейсы +### Основные компоненты -### IDragVisualProvider -```csharp -public interface IDragVisualProvider -{ - object CreateDragVisual(DragInfo dragInfo, Point initialPosition); - void UpdateDragVisualPosition(object dragVisual, Point position); - void ReleaseDragVisual(object dragVisual); -} -``` +#### 1. **IDragDropHost** +Абстракция для управления визуальными элементами перетаскивания на уровне платформы. -### IDropVisualAdorner -```csharp -public interface IDropVisualAdorner -{ - void Show(DropInfo dropInfo, Rect targetBounds); - void Update(DropInfo dropInfo); - void Hide(); -} -``` +#### 2. **IDragVisualProvider** +Поставщик визуального представления для перетаскиваемых элементов. -## 📦 Установка +#### 3. **IDropVisualAdorner** +Элемент визуальной обратной связи при наведении на цель сброса. -Добавьте проект как ссылку в ваше решение или установите как NuGet пакет: +#### 4. **DragSourceBehaviorBase\** +Базовый класс для реализации поведения источника перетаскивания. -```xml - -``` - -## 🔗 Зависимости - -- `Lattice.Core.DragDrop` >= 1.0.0 -- `Lattice.Core.Geometry` >= 1.0.0 -- `Microsoft.Extensions.DependencyInjection.Abstractions` >= 8.0.0 +#### 5. **DropTargetBehaviorBase\** +Базовый класс для реализации поведения цели сброса. ## 🚀 Быстрый старт -### 1. Регистрация сервисов +### 1. Установка ```csharp -using Lattice.UI.DragDrop.Extensions; +// В методе ConfigureServices вашего приложения +services.AddLatticeDragDrop(); +``` +### 2. Регистрация платформенных реализаций + +```csharp +// Для WPF +services.AddDragVisualProvider(); +services.AddDropVisualAdorner(); +services.AddDragDropHost(); + +// Для Avalonia +services.AddDragVisualProvider(); +services.AddDropVisualAdorner(); +services.AddDragDropHost(); +``` + +### 3. Создание поведения источника перетаскивания + +```csharp +public class MyDragSourceBehavior : DragSourceBehaviorBase +{ + public MyDragSourceBehavior(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } + + protected override void SubscribeToEvents(Control element) + { + element.MouseDown += OnMouseDown; + element.MouseMove += OnMouseMove; + element.MouseUp += OnMouseUp; + } + + protected override void UnsubscribeFromEvents(Control element) + { + element.MouseDown -= OnMouseDown; + element.MouseMove -= OnMouseMove; + element.MouseUp -= OnMouseUp; + } + + public override async Task TryStartDragAsync(Point startPosition, CancellationToken ct) + { + // Проверяем, можно ли начать перетаскивание + if (!CanDrag()) + return null; + + // Создаем информацию о перетаскивании + return new DragInfo( + data: GetDragData(), + allowedEffects: DragDropEffects.Copy | DragDropEffects.Move, + startPosition: startPosition, + source: this + ); + } + + // ... остальная реализация +} +``` + +### 4. Создание поведения цели сброса + +```csharp +public class MyDropTargetBehavior : DropTargetBehaviorBase +{ + public MyDropTargetBehavior(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } + + protected override void SubscribeToEvents(Panel element) + { + element.SizeChanged += OnSizeChanged; + element.LayoutUpdated += OnLayoutUpdated; + } + + protected override void UnsubscribeFromEvents(Panel element) + { + element.SizeChanged -= OnSizeChanged; + element.LayoutUpdated -= OnLayoutUpdated; + } + + public override async Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct) + { + // Проверяем тип данных + if (dropInfo.Data is not MyDataType data) + return false; + + // Проверяем бизнес-правила + return await ValidateDropAsync(data, ct); + } + + public override async Task OnDropAsync(DropInfo dropInfo, CancellationToken ct) + { + var data = (MyDataType)dropInfo.Data; + await ProcessDropAsync(data, ct); + dropInfo.MarkAsHandled(); + } + + // ... остальная реализация +} +``` + +## 🔌 Интеграция с UI-фреймворками + +### WPF + +```csharp +public static class WpfDragDropExtensions +{ + public static IServiceCollection AddWpfDragDrop(this IServiceCollection services) + { + return services + .AddLatticeDragDrop() + .AddDragVisualProvider() + .AddDropVisualAdorner() + .AddDragDropHost(); + } +} +``` + +### Avalonia + +```csharp +public static class AvaloniaDragDropExtensions +{ + public static IServiceCollection AddAvaloniaDragDrop(this IServiceCollection services) + { + return services + .AddLatticeDragDrop() + .AddDragVisualProvider() + .AddDropVisualAdorner() + .AddDragDropHost(); + } +} +``` + +## 🎨 Визуальная обратная связь + +### Создание кастомного визуального представления + +```csharp +public class CustomDragVisualProvider : IDragVisualProvider +{ + public object CreateDragVisual(DragInfo dragInfo, Point initialPosition) + { + var border = new Border + { + Background = new SolidColorBrush(Colors.LightBlue), + BorderBrush = new SolidColorBrush(Colors.Blue), + BorderThickness = new Thickness(1), + CornerRadius = new CornerRadius(4), + Padding = new Thickness(8), + Child = new TextBlock + { + Text = $"Dragging: {dragInfo.Data}", + Foreground = new SolidColorBrush(Colors.Black) + }, + Opacity = 0.8 + }; + + return border; + } + + public void UpdateDragVisualPosition(object dragVisual, Point position) + { + if (dragVisual is FrameworkElement element) + { + element.SetValue(Canvas.LeftProperty, position.X); + element.SetValue(Canvas.TopProperty, position.Y); + } + } + + public void ReleaseDragVisual(object dragVisual) + { + if (dragVisual is IDisposable disposable) + disposable.Dispose(); + } +} +``` + +### Создание кастомной обратной связи + +```csharp +public class CustomDropVisualAdorner : IDropVisualAdorner +{ + private Border? _adorner; + + public void Show(DropInfo dropInfo, Rect targetBounds) + { + _adorner = new Border + { + Background = new SolidColorBrush(Colors.Transparent), + BorderBrush = new SolidColorBrush(Colors.Green), + BorderThickness = new Thickness(2), + CornerRadius = new CornerRadius(4) + }; + + // Установка позиции и размера + Canvas.SetLeft(_adorner, targetBounds.X); + Canvas.SetTop(_adorner, targetBounds.Y); + _adorner.Width = targetBounds.Width; + _adorner.Height = targetBounds.Height; + + // Добавление в визуальное дерево + AdornerLayer.GetAdornerLayer(targetElement)?.Add(_adorner); + } + + public void Update(DropInfo dropInfo) + { + if (_adorner != null) + { + // Обновление стиля в зависимости от эффекта + var brush = dropInfo.SuggestedEffects.HasFlag(DragDropEffects.Move) + ? Colors.Green + : Colors.Blue; + + _adorner.BorderBrush = new SolidColorBrush(brush); + } + } + + public void Hide() + { + if (_adorner != null) + { + var layer = AdornerLayer.GetAdornerLayer(_adorner); + layer?.Remove(_adorner); + _adorner = null; + } + } +} +``` + +## ⚙️ Конфигурация + +### Настройка сервисов + +```csharp public void ConfigureServices(IServiceCollection services) { + // Базовая регистрация services.AddLatticeDragDrop(); + + // Платформенные реализации + services.AddDragVisualProvider(); + services.AddDropVisualAdorner(); + services.AddDragDropHost(); + + // Регистрация поведений + services.AddTransient, MyDragSourceBehavior>(); + services.AddTransient, MyDropTargetBehavior>(); } ``` -### 2. Создание кастомного поведения +### Использование с ViewModel ```csharp -using Lattice.UI.DragDrop.Behaviors; - -public class MyDragSource : DragSourceBehaviorBase +public class MainViewModel { - protected override void SubscribeToEvents(MyElement element) + private readonly IServiceProvider _serviceProvider; + + public MainViewModel(IServiceProvider serviceProvider) { - // Подписка на события элемента + _serviceProvider = serviceProvider; } - - public override bool CanStartDrag(out DragInfo? dragInfo) + + public void AttachDragBehavior(UIElement element) { - // Реализация проверки возможности перетаскивания + var behavior = _serviceProvider.GetRequiredService>(); + behavior.AssociatedElement = element; + } + + public void AttachDropBehavior(Panel panel) + { + var behavior = _serviceProvider.GetRequiredService>(); + behavior.AssociatedElement = panel; } } ``` -## 📚 API Reference +## 📝 Best Practices -### Основные типы +### 1. **Эффективность визуального представления** +- Создавайте легковесные визуальные элементы для плавной анимации +- Используйте кэширование для часто используемых представлений +- Избегайте сложных преобразований и эффектов -| Тип | Описание | -|-----|----------| -| `DragSourceBehaviorBase` | Базовый класс для поведения источника | -| `DropTargetBehaviorBase` | Базовый класс для поведения цели | -| `IDragVisualProvider` | Поставщик визуального представления | -| `IDropVisualAdorner` | Визуальный элемент обратной связи | +### 2. **Обработка событий** +- Всегда отписывайтесь от событий при откреплении поведения +- Используйте слабые ссылки для предотвращения утечек памяти +- Обрабатывайте исключения в обработчиках событий -### Расширения DI +### 3. **Ресурсы** +- Освобождайте графические ресурсы в методах Release/Dispose +- Используйте пулы объектов для часто создаваемых элементов +- Мониторьте использование памяти при активном перетаскивании -- `AddLatticeDragDrop()` - регистрация сервисов перетаскивания +### 4. **Пользовательский опыт** +- Предоставляйте понятную визуальную обратную связь +- Поддерживайте настраиваемые стили и темы +- Обеспечивайте плавность анимации даже на слабых устройствах -## 🔄 Интеграция с платформенными проектами +## 🔄 Миграция -Этот проект предназначен для наследования платформенными реализациями: +### С версии 1.x на 2.0 -1. **WinUI**: `Lattice.UI.DragDrop.WinUI` -2. **Uno Platform**: `Lattice.UI.DragDrop.Uno` (планируется) -3. **MAUI**: `Lattice.UI.DragDrop.Maui` (планируется) +1. **Обновление интерфейсов**: + - Методы переименованы в соответствии с Core + - Добавлена поддержка CancellationToken -## 🧪 Тестирование +2. **Изменения в базовых классах**: + - `DragSourceBehaviorBase` теперь реализует новый интерфейс `IDragSource` + - `DropTargetBehaviorBase` теперь реализует новый интерфейс `IDropTarget` -Проект включает модульные тесты для всех публичных API: - -```bash -dotnet test Lattice.UI.DragDrop.Tests -``` +3. **Регистрация сервисов**: + - Метод `AddLatticeDragDrop` теперь регистрирует только базовые сервисы + - Для платформенных реализаций используйте методы `AddDragVisualProvider`, `AddDropVisualAdorner`, `AddDragDropHost` ## 📄 Лицензия -MIT License. Подробности в файле [LICENSE](LICENSE). +Библиотека распространяется под лицензией MIT. Подробности см. в файле LICENSE. -## 🤝 Участие в разработке +## 🤝 Вклад в разработку -1. Форкните репозиторий -2. Создайте ветку для вашей функции -3. Сделайте коммит изменений -4. Отправьте пул-реквест +Для интеграции с новой UI-платформой необходимо: +1. Реализовать интерфейсы `IDragDropHost`, `IDragVisualProvider`, `IDropVisualAdorner` +2. Создать производные классы от `DragSourceBehaviorBase` и `DropTargetBehaviorBase` +3. Предоставить метод расширения для регистрации всех компонентов -## 📞 Поддержка +## 🐛 Отчеты об ошибках -- Документация: [lattice-framework.github.io](https://lattice-framework.github.io) -- Issues: [GitHub Issues](https://github.com/lattice-framework/ui-dragdrop/issues) -- Обсуждения: [GitHub Discussions](https://github.com/lattice-framework/ui-dragdrop/discussions) \ No newline at end of file +Для сообщения об ошибках используйте Issues на GitHub. Пожалуйста, указывайте: +- Платформу и версию UI-фреймворка +- Шаги для воспроизведения +- Ожидаемое и фактическое поведение +- Пример кода для демонстрации проблемы \ No newline at end of file