Доработан проект UI под новый Core

This commit is contained in:
2026-01-25 02:52:07 +03:00
parent be12154262
commit 2bd7d3c474
7 changed files with 923 additions and 181 deletions

View File

@@ -10,9 +10,20 @@ using System.Threading.Tasks;
namespace Lattice.UI.DragDrop.Behaviors;
/// <summary>
/// Базовый класс поведения источника перетаскивания.
/// Базовый класс поведения источника перетаскивания для UI элементов.
/// </summary>
/// <typeparam name="TElement">Тип UI элемента.</typeparam>
/// <typeparam name="TElement">Тип UI элемента, к которому прикрепляется поведение.</typeparam>
/// <remarks>
/// <para>
/// Этот класс предоставляет базовую реализацию поведения перетаскивания для UI элементов.
/// Он обрабатывает события мыши/тач, управляет порогом начала перетаскивания и
/// интегрируется с сервисом <see cref="IDragDropService"/> из ядра.
/// </para>
/// <para>
/// Производные классы должны реализовать абстрактные методы для конкретной
/// UI-платформы и предоставить логику создания информации о перетаскивании.
/// </para>
/// </remarks>
public abstract class DragSourceBehaviorBase<TElement> : IDragSource
where TElement : class
{
@@ -20,10 +31,15 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
private Point _dragStartPosition;
private bool _isDragging;
private TElement? _associatedElement;
private CancellationTokenSource? _dragCancellationTokenSource;
/// <summary>
/// Получает или задает связанный элемент.
/// Получает или задает связанный UI элемент.
/// </summary>
/// <value>
/// Элемент UI, к которому прикреплено поведение перетаскивания.
/// При изменении значения автоматически выполняется переподключение событий.
/// </value>
protected TElement? AssociatedElement
{
get => _associatedElement;
@@ -39,8 +55,12 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
}
/// <summary>
/// Получает сервис перетаскивания.
/// Получает сервис перетаскивания из контейнера зависимостей.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDragDropService"/>, используемый для управления операциями перетаскивания.
/// При первом обращении выполняется получение сервиса из <see cref="ServiceProvider"/>.
/// </value>
protected IDragDropService DragDropService
{
get
@@ -54,22 +74,34 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
}
/// <summary>
/// Получает провайдер сервисов.
/// Получает провайдер сервисов для разрешения зависимостей.
/// </summary>
protected IServiceProvider ServiceProvider { get; }
/// <summary>
/// Получает значение, указывающее, выполняется ли в данный момент операция перетаскивания.
/// </summary>
protected bool IsDragging => _isDragging;
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragSourceBehaviorBase{TElement}"/>.
/// </summary>
/// <param name="serviceProvider">Провайдер сервисов.</param>
/// <param name="serviceProvider">Провайдер сервисов для разрешения зависимостей.</param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, когда <paramref name="serviceProvider"/> равен null.
/// </exception>
protected DragSourceBehaviorBase(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
/// <summary>
/// Вызывается при прикреплении к элементу.
/// Вызывается при прикреплении поведения к элементу.
/// </summary>
/// <remarks>
/// Реализация по умолчанию подписывается на события элемента через <see cref="SubscribeToEvents"/>.
/// Производные классы могут переопределить этот метод для дополнительной инициализации.
/// </remarks>
protected virtual void AttachToElement()
{
if (_associatedElement != null)
@@ -79,8 +111,12 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
}
/// <summary>
/// Вызывается при откреплении от элемента.
/// Вызывается при откреплении поведения от элемента.
/// </summary>
/// <remarks>
/// Реализация по умолчанию отписывается от событий элемента через <see cref="UnsubscribeFromEvents"/>.
/// Производные классы могут переопределить этот метод для дополнительной очистки.
/// </remarks>
protected virtual void DetachFromElement()
{
if (_associatedElement != null)
@@ -90,36 +126,68 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
}
/// <summary>
/// Подписывается на события элемента.
/// Подписывается на события элемента, необходимые для отслеживания начала перетаскивания.
/// </summary>
/// <param name="element">Элемент.</param>
/// <param name="element">Элемент, к событиям которого нужно подписаться.</param>
/// <remarks>
/// Производные классы должны реализовать этот метод для подписки на события конкретной
/// UI-платформы (например, MouseDown для WPF, PointerPressed для Avalonia).
/// </remarks>
protected abstract void SubscribeToEvents(TElement element);
/// <summary>
/// Отписывается от событий элемента.
/// </summary>
/// <param name="element">Элемент.</param>
/// <param name="element">Элемент, от событий которого нужно отписаться.</param>
/// <remarks>
/// Производные классы должны реализовать этот метод для корректной отписки
/// от событий, на которые была выполнена подписка в <see cref="SubscribeToEvents"/>.
/// </remarks>
protected abstract void UnsubscribeFromEvents(TElement element);
/// <summary>
/// Обрабатывает начало взаимодействия (например, нажатие мыши).
/// Обрабатывает начало взаимодействия с элементом (например, нажатие кнопки мыши).
/// </summary>
/// <param name="position">Позиция в координатах элемента.</param>
protected virtual async Task OnInteractionStarted(Point position)
/// <param name="position">Позиция взаимодействия в координатах элемента.</param>
/// <returns>Задача, представляющая асинхронную операцию.</returns>
/// <remarks>
/// <para>
/// Этот метод вызывается из обработчиков событий UI-платформы при начале
/// взаимодействия, которое может привести к перетаскиванию.
/// </para>
/// <para>
/// Реализация по умолчанию сохраняет начальную позицию для последующей
/// проверки порога перетаскивания.
/// </para>
/// </remarks>
protected virtual Task OnInteractionStarted(Point position)
{
if (_isDragging)
return;
return Task.CompletedTask;
_dragStartPosition = position;
_dragCancellationTokenSource = new CancellationTokenSource();
return Task.CompletedTask;
}
/// <summary>
/// Обрабатывает перемещение во время взаимодействия.
/// Обрабатывает перемещение во время взаимодействия с элементом.
/// </summary>
/// <param name="position">Позиция в координатах элемента.</param>
/// <param name="position">Текущая позиция взаимодействия в координатах элемента.</param>
/// <returns>Задача, представляющая асинхронную операцию.</returns>
/// <remarks>
/// <para>
/// Этот метод вызывается при перемещении курсора/тач-точки во время удержания
/// взаимодействия (например, перемещение мыши с нажатой кнопкой).
/// </para>
/// <para>
/// Реализация по умолчанию проверяет, превышено ли расстояние от начальной
/// точки порога перетаскивания, и если да - начинает операцию перетаскивания.
/// </para>
/// </remarks>
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<TElement> : IDragSource
}
/// <summary>
/// Обрабатывает завершение взаимодействия.
/// Обрабатывает завершение взаимодействия с элементом.
/// </summary>
protected virtual async Task OnInteractionEnded()
/// <returns>Задача, представляющая асинхронную операцию.</returns>
/// <remarks>
/// <para>
/// Этот метод вызывается при завершении взаимодействия (например, отпускании кнопки мыши).
/// </para>
/// <para>
/// Реализация по умолчанию сбрасывает состояние поведения, если перетаскивание не было начато.
/// </para>
/// </remarks>
protected virtual Task OnInteractionEnded()
{
// Сброс состояния, если перетаскивание не началось
if (!_isDragging)
{
Reset();
}
return Task.CompletedTask;
}
/// <summary>
/// Обрабатывает отмену взаимодействия.
/// Обрабатывает отмену взаимодействия с элементом.
/// </summary>
/// <returns>Задача, представляющая асинхронную операцию.</returns>
/// <remarks>
/// <para>
/// Этот метод вызывается при отмене взаимодействия (например, нажатии клавиши Escape
/// или выходе за пределы допустимой области).
/// </para>
/// <para>
/// Реализация по умолчанию отменяет текущую операцию перетаскивания, если она активна,
/// и сбрасывает состояние поведения.
/// </para>
/// </remarks>
protected virtual async Task OnInteractionCancelled()
{
if (_isDragging)
@@ -156,16 +245,35 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
/// <summary>
/// Начинает операцию перетаскивания.
/// </summary>
/// <returns>Задача, представляющая асинхронную операцию.</returns>
/// <remarks>
/// <para>
/// Этот метод преобразует начальную позицию в экранные координаты и вызывает
/// сервис перетаскивания для начала операции.
/// </para>
/// <para>
/// Операция начинается только если поведение прикреплено к элементу и
/// не выполняется другая операция перетаскивания.
/// </para>
/// </remarks>
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();
}
}
/// <summary>
@@ -173,11 +281,18 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
/// </summary>
/// <param name="point">Точка в координатах элемента.</param>
/// <returns>Точка в экранных координатах.</returns>
/// <remarks>
/// Производные классы должны реализовать этот метод для преобразования
/// координат в соответствии с конкретной UI-платформой.
/// </remarks>
protected abstract Point ConvertToScreenCoordinates(Point point);
/// <summary>
/// Вычисляет расстояние между двумя точками.
/// </summary>
/// <param name="p1">Первая точка.</param>
/// <param name="p2">Вторая точка.</param>
/// <returns>Расстояние между точками.</returns>
protected virtual double CalculateDistance(Point p1, Point p2)
{
var dx = p2.X - p1.X;
@@ -188,32 +303,33 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
/// <summary>
/// Сбрасывает состояние поведения.
/// </summary>
/// <remarks>
/// Этот метод очищает все временные данные и отменяет токены отмены,
/// связанные с текущей операцией перетаскивания.
/// </remarks>
protected virtual void Reset()
{
_isDragging = false;
_dragStartPosition = default;
_dragCancellationTokenSource?.Dispose();
_dragCancellationTokenSource = null;
}
#region IDragSource Implementation
/// <inheritdoc/>
public abstract Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync(CancellationToken ct = default);
public abstract Task<DragInfo?> TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default);
/// <inheritdoc/>
public virtual async Task<bool> StartDragAsync(DragInfo dragInfo, CancellationToken ct = default)
{
return true;
}
/// <inheritdoc/>
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);
}
/// <inheritdoc/>
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<TElement> : IDragSource
#region Virtual Methods for Derived Classes
/// <summary>
/// Вызывается при успешном завершении перетаскивания.
/// Вызывается при успешном завершении операции перетаскивания.
/// </summary>
/// <param name="dragInfo">Информация о перетаскивании.</param>
/// <param name="effects">Примененные эффекты.</param>
protected virtual void OnDragCompleted(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects)
/// <param name="dragInfo">Информация о перетаскивании, использованная в операции.</param>
/// <param name="effects">Эффекты, примененные при завершении операции.</param>
/// <remarks>
/// Производные классы могут переопределить этот метод для выполнения
/// дополнительных действий после успешного завершения перетаскивания,
/// например, удаления исходного элемента при перемещении.
/// </remarks>
protected virtual void OnDragCompleted(DragInfo dragInfo, Lattice.Core.DragDrop.Enums.DragDropEffects effects)
{
}
/// <summary>
/// Вызывается при отмене перетаскивания.
/// Вызывается при отмене операции перетаскивания.
/// </summary>
/// <param name="dragInfo">Информация о перетаскивании.</param>
/// <param name="dragInfo">Информация о перетаскивании, использованная в операции.</param>
/// <remarks>
/// Производные классы могут переопределить этот метод для выполнения
/// действий по восстановлению состояния после отмены перетаскивания.
/// </remarks>
protected virtual void OnDragCancelled(DragInfo dragInfo)
{
}
@@ -243,11 +368,16 @@ public abstract class DragSourceBehaviorBase<TElement> : IDragSource
#endregion
/// <summary>
/// Освобождает ресурсы.
/// Открепляет поведение от элемента и освобождает ресурсы.
/// </summary>
/// <remarks>
/// После вызова этого метода поведение больше не будет обрабатывать события
/// элемента и может быть безопасно удалено.
/// </remarks>
public virtual void Detach()
{
DetachFromElement();
_associatedElement = null;
Reset();
}
}