Переработаны методы.
This commit is contained in:
@@ -1,13 +1,52 @@
|
||||
namespace Lattice.Core.DragDrop.Services;
|
||||
using Lattice.Core.DragDrop.Abstractions;
|
||||
using Lattice.Core.DragDrop.Constants;
|
||||
using Lattice.Core.DragDrop.Enums;
|
||||
using Lattice.Core.DragDrop.Models;
|
||||
using Lattice.Core.Geometry;
|
||||
|
||||
namespace Lattice.Core.DragDrop.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Реализация сервиса управления операциями перетаскивания.
|
||||
/// Полностью потокобезопасная реализация с поддержкой async/await.
|
||||
/// Центральный сервис управления операциями перетаскивания.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="DragDropService"/> является основным компонентом системы drag-and-drop,
|
||||
/// который координирует взаимодействие между источниками данных (<see cref="Abstractions.IDragSource"/>)
|
||||
/// и целями сброса (<see cref="Abstractions.IDropTarget"/>).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Основные функции сервиса:
|
||||
/// </para>
|
||||
/// <list type="bullet">
|
||||
/// <item>Регистрация и управление целями сброса</item>
|
||||
/// <item>Оркестрация жизненного цикла операций перетаскивания</item>
|
||||
/// <item>Обработка событий мыши и клавиатуры</item>
|
||||
/// <item>Распространение информации между компонентами</item>
|
||||
/// <item>Обеспечение потокобезопасности операций</item>
|
||||
/// </list>
|
||||
/// <para>
|
||||
/// Сервис поддерживает полностью асинхронную модель работы, уведомления через события
|
||||
/// и статистику использования. Все операции защищены от параллельного доступа
|
||||
/// и обеспечивают корректную очистку ресурсов.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Для использования сервиса необходимо:
|
||||
/// <list type="number">
|
||||
/// <item>Зарегистрировать цели сброса с помощью <see cref="RegisterDropTarget"/></item>
|
||||
/// <item>Вызывать методы <see cref="StartDragAsync"/>, <see cref="UpdateDragAsync"/>,
|
||||
/// <see cref="EndDragAsync"/> в ответ на действия пользователя</item>
|
||||
/// <item>Подписаться на события для отслеживания состояния операций</item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class DragDropService : IDragDropService
|
||||
{
|
||||
#region Nested Types
|
||||
|
||||
/// <summary>
|
||||
/// Информация о зарегистрированной цели сброса.
|
||||
/// </summary>
|
||||
private sealed class DropTargetInfo : IDisposable
|
||||
{
|
||||
public required Abstractions.IDropTarget Target { get; init; }
|
||||
@@ -25,6 +64,9 @@ public sealed class DragDropService : IDragDropService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Контекст текущей операции перетаскивания.
|
||||
/// </summary>
|
||||
private sealed class DragOperationContext : IDisposable
|
||||
{
|
||||
public Abstractions.IDragSource? Source { get; set; }
|
||||
@@ -33,6 +75,7 @@ public sealed class DragDropService : IDragDropService
|
||||
public CancellationTokenSource? CancellationTokenSource { get; set; }
|
||||
public DateTime StartTime { get; set; } = DateTime.UtcNow;
|
||||
public bool ThresholdExceeded { get; set; }
|
||||
public Point LastPosition { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
@@ -74,48 +117,60 @@ public sealed class DragDropService : IDragDropService
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsDragActive => Volatile.Read(ref _currentDragOperation) != null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Models.DragInfo? CurrentDragInfo => _currentDragOperation?.DragInfo;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Abstractions.IDropTarget? CurrentDropTarget => _currentDragOperation?.CurrentDropTarget;
|
||||
|
||||
public double DragStartThreshold { get; set; } = 3.0;
|
||||
/// <inheritdoc/>
|
||||
public double DragStartThreshold { get; set; } = DragDropConstants.DefaultDragThreshold;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool EnableAsyncOperations { get; set; } = true;
|
||||
|
||||
public int AsyncOperationTimeout { get; set; } = 5000;
|
||||
/// <inheritdoc/>
|
||||
public int AsyncOperationTimeout { get; set; } = DragDropConstants.DefaultAsyncTimeout;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<DragStartedEventArgs> DragStarted
|
||||
{
|
||||
add => _dragStarted += value;
|
||||
remove => _dragStarted -= value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<DragUpdatedEventArgs> DragUpdated
|
||||
{
|
||||
add => _dragUpdated += value;
|
||||
remove => _dragUpdated -= value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<DropTargetChangedEventArgs> DropTargetChanged
|
||||
{
|
||||
add => _dropTargetChanged += value;
|
||||
remove => _dropTargetChanged -= value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<DragCompletedEventArgs> DragCompleted
|
||||
{
|
||||
add => _dragCompleted += value;
|
||||
remove => _dragCompleted -= value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<DragCancelledEventArgs> DragCancelled
|
||||
{
|
||||
add => _dragCancelled += value;
|
||||
remove => _dragCancelled -= value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<DragDropErrorEventArgs> ErrorOccurred
|
||||
{
|
||||
add => _errorOccurred += value;
|
||||
@@ -126,6 +181,17 @@ public sealed class DragDropService : IDragDropService
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Инициализирует новый экземпляр класса <see cref="DragDropService"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Создает сервис с настройками по умолчанию:
|
||||
/// <list type="bullet">
|
||||
/// <item>Порог начала перетаскивания: <see cref="Constants.DragDropConstants.DefaultDragThreshold"/> пикселей</item>
|
||||
/// <item>Таймаут асинхронных операций: <see cref="Constants.DragDropConstants.DefaultAsyncTimeout"/> миллисекунд</item>
|
||||
/// <item>Включены асинхронные операции: true</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public DragDropService()
|
||||
{
|
||||
// Инициализация таймера очистки (каждые 5 минут)
|
||||
@@ -138,6 +204,7 @@ public sealed class DragDropService : IDragDropService
|
||||
|
||||
#region Registration Methods
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string RegisterDropTarget(Abstractions.IDropTarget target, Geometry.Rect bounds, int priority = 0, string? group = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -165,6 +232,7 @@ public sealed class DragDropService : IDragDropService
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool UpdateDropTargetBounds(string id, Geometry.Rect bounds)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -193,6 +261,7 @@ public sealed class DragDropService : IDragDropService
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool UnregisterDropTarget(string id)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -213,6 +282,7 @@ public sealed class DragDropService : IDragDropService
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UnregisterDropTargetsInGroup(string group)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -249,7 +319,8 @@ public sealed class DragDropService : IDragDropService
|
||||
|
||||
#region Async Operations
|
||||
|
||||
public async Task<bool> StartDragAsync(Abstractions.IDragSource source, Geometry.Point startPosition)
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> StartDragAsync(IDragSource source, Point startPosition)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
@@ -264,60 +335,34 @@ public sealed class DragDropService : IDragDropService
|
||||
{
|
||||
Interlocked.Increment(ref _totalDragOperations);
|
||||
|
||||
Models.DragInfo? dragInfo = null;
|
||||
DragInfo? dragInfo;
|
||||
|
||||
// Проверка возможности начала перетаскивания
|
||||
if (EnableAsyncOperations && source is Abstractions.IDragSource asyncSource)
|
||||
// Пытаемся начать перетаскивание
|
||||
if (EnableAsyncOperations)
|
||||
{
|
||||
var result = await ExecuteWithTimeoutAsync(
|
||||
asyncSource.CanStartDragAsync(),
|
||||
"CanStartDragAsync",
|
||||
dragInfo = await ExecuteWithTimeoutAsync(
|
||||
source.TryStartDragAsync(startPosition),
|
||||
"TryStartDragAsync",
|
||||
source);
|
||||
|
||||
if (!result.CanStart || result.DragInfo == null)
|
||||
return false;
|
||||
|
||||
dragInfo = result.DragInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
var startDragResult = await source.CanStartDragAsync();
|
||||
if (!startDragResult.CanStart || startDragResult.DragInfo == null)
|
||||
return false;
|
||||
|
||||
dragInfo = startDragResult.DragInfo;
|
||||
dragInfo = await source.TryStartDragAsync(startPosition);
|
||||
}
|
||||
|
||||
if (dragInfo == null)
|
||||
return false;
|
||||
|
||||
var updatedDragInfo = dragInfo.CloneWithPosition(startPosition);
|
||||
|
||||
// Начало перетаскивания
|
||||
bool started;
|
||||
|
||||
if (EnableAsyncOperations && source is Abstractions.IDragSource asyncSource2)
|
||||
{
|
||||
started = await ExecuteWithTimeoutAsync(
|
||||
asyncSource2.StartDragAsync(updatedDragInfo),
|
||||
"StartDragAsync",
|
||||
source);
|
||||
}
|
||||
else
|
||||
{
|
||||
started = await source.StartDragAsync(updatedDragInfo);
|
||||
}
|
||||
|
||||
if (!started)
|
||||
{
|
||||
updatedDragInfo.Dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_dragOperationLock)
|
||||
{
|
||||
_currentDragOperation = new DragOperationContext
|
||||
{
|
||||
Source = source,
|
||||
DragInfo = updatedDragInfo,
|
||||
CancellationTokenSource = new CancellationTokenSource()
|
||||
CancellationTokenSource = new CancellationTokenSource(),
|
||||
LastPosition = startPosition
|
||||
};
|
||||
}
|
||||
|
||||
@@ -333,7 +378,8 @@ public sealed class DragDropService : IDragDropService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateDragAsync(Geometry.Point position)
|
||||
/// <inheritdoc/>
|
||||
public async Task UpdateDragAsync(Point position)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
@@ -359,8 +405,9 @@ public sealed class DragDropService : IDragDropService
|
||||
}
|
||||
|
||||
var updatedDragInfo = context.DragInfo.CloneWithPosition(position);
|
||||
context.DragInfo = updatedDragInfo;
|
||||
context.DragInfo.Dispose();
|
||||
context.DragInfo = updatedDragInfo;
|
||||
context.LastPosition = position;
|
||||
|
||||
// Поиск новой цели сброса
|
||||
var newDropTarget = await FindDropTargetAsync(position, updatedDragInfo);
|
||||
@@ -372,9 +419,8 @@ public sealed class DragDropService : IDragDropService
|
||||
{
|
||||
await ExecuteTargetOperationAsync(
|
||||
context.CurrentDropTarget,
|
||||
t => t.DragLeaveAsync(),
|
||||
t => t.DragLeaveAsync(),
|
||||
"DragLeave");
|
||||
t => t.OnDragLeaveAsync(),
|
||||
"OnDragLeave");
|
||||
}
|
||||
|
||||
context.CurrentDropTarget = newDropTarget?.Target;
|
||||
@@ -383,14 +429,14 @@ public sealed class DragDropService : IDragDropService
|
||||
{
|
||||
newDropTarget.UsageCount++;
|
||||
_dropTargetChanged?.Invoke(this, new DropTargetChangedEventArgs(
|
||||
updatedDragInfo, newDropTarget.Target, newDropTarget.Bounds));
|
||||
updatedDragInfo, position, newDropTarget.Target, newDropTarget.Bounds));
|
||||
}
|
||||
}
|
||||
|
||||
// Уведомление текущей цели
|
||||
if (context.CurrentDropTarget != null)
|
||||
{
|
||||
var dropInfo = new Models.DropInfo(
|
||||
var dropInfo = new DropInfo(
|
||||
updatedDragInfo.Data,
|
||||
position,
|
||||
updatedDragInfo.AllowedEffects,
|
||||
@@ -398,9 +444,8 @@ public sealed class DragDropService : IDragDropService
|
||||
|
||||
await ExecuteTargetOperationAsync(
|
||||
context.CurrentDropTarget,
|
||||
t => t.DragOverAsync(dropInfo),
|
||||
t => t.DragOverAsync(dropInfo),
|
||||
"DragOver");
|
||||
t => t.OnDragOverAsync(dropInfo),
|
||||
"OnDragOver");
|
||||
}
|
||||
|
||||
_dragUpdated?.Invoke(this, new DragUpdatedEventArgs(updatedDragInfo, position));
|
||||
@@ -412,7 +457,8 @@ public sealed class DragDropService : IDragDropService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Enums.DragDropEffects> EndDragAsync(Geometry.Point position)
|
||||
/// <inheritdoc/>
|
||||
public async Task<DragDropEffects> EndDragAsync(Point position)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
@@ -426,19 +472,19 @@ public sealed class DragDropService : IDragDropService
|
||||
if (context == null || context.DragInfo == null || context.Source == null)
|
||||
{
|
||||
Reset();
|
||||
return Enums.DragDropEffects.None;
|
||||
return DragDropEffects.None;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var effects = Enums.DragDropEffects.None;
|
||||
var effects = DragDropEffects.None;
|
||||
var operationTime = DateTime.UtcNow - context.StartTime;
|
||||
Interlocked.Add(ref _totalOperationTicks, operationTime.Ticks);
|
||||
|
||||
// Выполнение сброса
|
||||
if (context.CurrentDropTarget != null)
|
||||
{
|
||||
var dropInfo = new Models.DropInfo(
|
||||
var dropInfo = new DropInfo(
|
||||
context.DragInfo.Data,
|
||||
position,
|
||||
context.DragInfo.AllowedEffects,
|
||||
@@ -446,9 +492,8 @@ public sealed class DragDropService : IDragDropService
|
||||
|
||||
await ExecuteTargetOperationAsync(
|
||||
context.CurrentDropTarget,
|
||||
t => t.DropAsync(dropInfo),
|
||||
t => t.DropAsync(dropInfo),
|
||||
"Drop");
|
||||
t => t.OnDropAsync(dropInfo),
|
||||
"OnDrop");
|
||||
|
||||
if (dropInfo.Handled)
|
||||
{
|
||||
@@ -460,10 +505,8 @@ public sealed class DragDropService : IDragDropService
|
||||
// Уведомление источника
|
||||
await ExecuteSourceOperationAsync(
|
||||
context.Source,
|
||||
s => s.DragCompletedAsync(context.DragInfo, effects),
|
||||
s => s.DragCompletedAsync(context.DragInfo, effects),
|
||||
"DragCompleted",
|
||||
effects);
|
||||
s => s.OnDragCompletedAsync(context.DragInfo, effects),
|
||||
"OnDragCompleted");
|
||||
|
||||
// Событие завершения
|
||||
_dragCompleted?.Invoke(this, new DragCompletedEventArgs(
|
||||
@@ -475,7 +518,7 @@ public sealed class DragDropService : IDragDropService
|
||||
{
|
||||
Interlocked.Increment(ref _errorCount);
|
||||
HandleError(ex, "EndDragAsync", context);
|
||||
return Enums.DragDropEffects.None;
|
||||
return DragDropEffects.None;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -483,6 +526,7 @@ public sealed class DragDropService : IDragDropService
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task CancelDragAsync()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -507,11 +551,10 @@ public sealed class DragDropService : IDragDropService
|
||||
|
||||
await ExecuteSourceOperationAsync(
|
||||
context.Source,
|
||||
s => s.DragCancelledAsync(context.DragInfo),
|
||||
s => s.DragCancelledAsync(context.DragInfo),
|
||||
"DragCancelled");
|
||||
s => s.OnDragCancelledAsync(context.DragInfo),
|
||||
"OnDragCancelled");
|
||||
|
||||
_dragCancelled?.Invoke(this, new DragCancelledEventArgs(context.DragInfo));
|
||||
_dragCancelled?.Invoke(this, new DragCancelledEventArgs(context.DragInfo, context.LastPosition));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -528,6 +571,7 @@ public sealed class DragDropService : IDragDropService
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ClearAllDropTargets()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -547,6 +591,7 @@ public sealed class DragDropService : IDragDropService
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DragDropStats GetStats()
|
||||
{
|
||||
return new DragDropStats
|
||||
@@ -609,23 +654,22 @@ public sealed class DragDropService : IDragDropService
|
||||
}
|
||||
|
||||
private async Task ExecuteTargetOperationAsync(
|
||||
Abstractions.IDropTarget target,
|
||||
Func<Abstractions.IDropTarget, Task> asyncOperation,
|
||||
Action<Abstractions.IDropTarget> syncOperation,
|
||||
IDropTarget target,
|
||||
Func<IDropTarget, Task> operation,
|
||||
string operationName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (EnableAsyncOperations && target is Abstractions.IDropTarget asyncTarget)
|
||||
if (EnableAsyncOperations)
|
||||
{
|
||||
await ExecuteWithTimeoutAsync(
|
||||
asyncOperation(asyncTarget),
|
||||
operation(target),
|
||||
$"{operationName}Async",
|
||||
target);
|
||||
}
|
||||
else
|
||||
{
|
||||
syncOperation(target);
|
||||
await operation(target);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -636,24 +680,22 @@ public sealed class DragDropService : IDragDropService
|
||||
}
|
||||
|
||||
private async Task ExecuteSourceOperationAsync(
|
||||
Abstractions.IDragSource source,
|
||||
Func<Abstractions.IDragSource, Task> asyncOperation,
|
||||
Action<Abstractions.IDragSource> syncOperation,
|
||||
string operationName,
|
||||
Enums.DragDropEffects effects = Enums.DragDropEffects.None)
|
||||
IDragSource source,
|
||||
Func<IDragSource, Task> operation,
|
||||
string operationName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (EnableAsyncOperations && source is Abstractions.IDragSource asyncSource)
|
||||
if (EnableAsyncOperations)
|
||||
{
|
||||
await ExecuteWithTimeoutAsync(
|
||||
asyncOperation(asyncSource),
|
||||
operation(source),
|
||||
$"{operationName}Async",
|
||||
source);
|
||||
}
|
||||
else
|
||||
{
|
||||
syncOperation(source);
|
||||
await operation(source);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
Reference in New Issue
Block a user