275 lines
9.1 KiB
C#
275 lines
9.1 KiB
C#
namespace Lattice.Core.DragDrop.Utilities;
|
||
|
||
/// <summary>
|
||
/// Утилиты для работы с системой перетаскивания.
|
||
/// </summary>
|
||
public static class DragDropUtilities
|
||
{
|
||
#region Effect Utilities
|
||
|
||
/// <summary>
|
||
/// Проверяет, совместимы ли эффекты источника и цели.
|
||
/// </summary>
|
||
public static bool AreEffectsCompatible(Enums.DragDropEffects sourceEffects, Enums.DragDropEffects targetEffects)
|
||
{
|
||
if (sourceEffects == Enums.DragDropEffects.None || targetEffects == Enums.DragDropEffects.None)
|
||
return false;
|
||
|
||
return (sourceEffects & targetEffects) != Enums.DragDropEffects.None;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Получает наиболее подходящий эффект на основе доступных.
|
||
/// </summary>
|
||
public static Enums.DragDropEffects GetBestEffect(Enums.DragDropEffects available, Enums.DragDropEffects preferred)
|
||
{
|
||
if ((available & preferred) != Enums.DragDropEffects.None)
|
||
return available & preferred;
|
||
|
||
if ((available & Enums.DragDropEffects.Move) != Enums.DragDropEffects.None)
|
||
return Enums.DragDropEffects.Move;
|
||
|
||
if ((available & Enums.DragDropEffects.Copy) != Enums.DragDropEffects.None)
|
||
return Enums.DragDropEffects.Copy;
|
||
|
||
if ((available & Enums.DragDropEffects.Link) != Enums.DragDropEffects.None)
|
||
return Enums.DragDropEffects.Link;
|
||
|
||
return Enums.DragDropEffects.None;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Geometry Utilities
|
||
|
||
/// <summary>
|
||
/// Вычисляет расстояние между двумя точками.
|
||
/// </summary>
|
||
public static double CalculateDistance(Geometry.Point p1, Geometry.Point p2)
|
||
{
|
||
var dx = p2.X - p1.X;
|
||
var dy = p2.Y - p1.Y;
|
||
return Math.Sqrt(dx * dx + dy * dy);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Проверяет, превысило ли перемещение пороговое значение.
|
||
/// </summary>
|
||
public static bool HasExceededDragThreshold(Geometry.Point startPoint, Geometry.Point currentPoint, double threshold)
|
||
{
|
||
var distance = CalculateDistance(startPoint, currentPoint);
|
||
return distance >= threshold;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Определяет позицию сброса относительно прямоугольника.
|
||
/// </summary>
|
||
public static Enums.DropPosition GetDropPosition(Geometry.Point point, Geometry.Rect bounds, double edgeThreshold = 20.0)
|
||
{
|
||
if (!bounds.Contains(new Geometry.Point(point.X, point.Y)))
|
||
return Enums.DropPosition.Inside;
|
||
|
||
var relativeX = (point.X - bounds.X) / bounds.Width;
|
||
var relativeY = (point.Y - bounds.Y) / bounds.Height;
|
||
|
||
if (relativeX < edgeThreshold / bounds.Width)
|
||
return Enums.DropPosition.Left;
|
||
if (relativeX > 1 - edgeThreshold / bounds.Width)
|
||
return Enums.DropPosition.Right;
|
||
if (relativeY < edgeThreshold / bounds.Height)
|
||
return Enums.DropPosition.Top;
|
||
if (relativeY > 1 - edgeThreshold / bounds.Height)
|
||
return Enums.DropPosition.Bottom;
|
||
|
||
return Enums.DropPosition.Center;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Factory Methods
|
||
|
||
/// <summary>
|
||
/// Создает информацию о перетаскивании.
|
||
/// </summary>
|
||
public static Models.DragInfo CreateDragInfo(
|
||
object data,
|
||
Geometry.Point startPosition,
|
||
Enums.DragDropEffects allowedEffects = Enums.DragDropEffects.Copy | Enums.DragDropEffects.Move,
|
||
object? source = null,
|
||
Dictionary<string, object>? parameters = null)
|
||
{
|
||
var dragInfo = new Models.DragInfo(data, allowedEffects, startPosition, source);
|
||
|
||
if (parameters != null)
|
||
{
|
||
foreach (var param in parameters)
|
||
{
|
||
dragInfo.SetParameter(param.Key, param.Value);
|
||
}
|
||
}
|
||
|
||
return dragInfo;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Создает простую реализацию источника перетаскивания.
|
||
/// </summary>
|
||
public static Abstractions.IDragSource CreateSimpleDragSource(
|
||
Func<object> dataProvider,
|
||
Func<bool>? canDrag = null,
|
||
Action<Models.DragInfo, Enums.DragDropEffects>? onCompleted = null,
|
||
Action<Models.DragInfo>? onCancelled = null)
|
||
{
|
||
return new SimpleDragSource(dataProvider, canDrag, onCompleted, onCancelled);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Создает простую реализацию цели сброса.
|
||
/// </summary>
|
||
public static Abstractions.IDropTarget CreateSimpleDropTarget(
|
||
Func<Models.DropInfo, bool>? canAccept = null,
|
||
Action<Models.DropInfo>? onDragOver = null,
|
||
Action<Models.DropInfo>? onDrop = null,
|
||
Action? onDragLeave = null)
|
||
{
|
||
return new SimpleDropTarget(canAccept, onDragOver, onDrop, onDragLeave);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Data Extraction
|
||
|
||
/// <summary>
|
||
/// Извлекает данные из элемента с поддержкой различных паттернов.
|
||
/// </summary>
|
||
public static object? ExtractData(object? element)
|
||
{
|
||
if (element == null)
|
||
return null;
|
||
|
||
// Проверяем, реализует ли элемент специальный интерфейс
|
||
if (element is Abstractions.IDragSource dragSource)
|
||
{
|
||
if (dragSource.CanStartDrag(out var dragInfo) && dragInfo != null)
|
||
return dragInfo.Data;
|
||
}
|
||
|
||
// В реальной реализации здесь будет рефлексия для проверки свойств
|
||
// DataContext, Content и т.д.
|
||
|
||
// Возвращаем сам элемент как данные
|
||
return element;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Проверяет, совместимы ли данные с указанными типами.
|
||
/// </summary>
|
||
public static bool IsDataCompatible(object? data, IEnumerable<Type>? acceptedTypes)
|
||
{
|
||
if (data == null || acceptedTypes == null)
|
||
return false;
|
||
|
||
var dataType = data.GetType();
|
||
|
||
foreach (var acceptedType in acceptedTypes)
|
||
{
|
||
if (acceptedType.IsAssignableFrom(dataType))
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Helper Classes
|
||
|
||
private sealed class SimpleDragSource : Abstractions.IDragSource
|
||
{
|
||
private readonly Func<object> _dataProvider;
|
||
private readonly Func<bool>? _canDrag;
|
||
private readonly Action<Models.DragInfo, Enums.DragDropEffects>? _onCompleted;
|
||
private readonly Action<Models.DragInfo>? _onCancelled;
|
||
|
||
public SimpleDragSource(
|
||
Func<object> dataProvider,
|
||
Func<bool>? canDrag = null,
|
||
Action<Models.DragInfo, Enums.DragDropEffects>? onCompleted = null,
|
||
Action<Models.DragInfo>? onCancelled = null)
|
||
{
|
||
_dataProvider = dataProvider ?? throw new ArgumentNullException(nameof(dataProvider));
|
||
_canDrag = canDrag;
|
||
_onCompleted = onCompleted;
|
||
_onCancelled = onCancelled;
|
||
}
|
||
|
||
public bool CanStartDrag(out Models.DragInfo? dragInfo)
|
||
{
|
||
dragInfo = null;
|
||
|
||
if (_canDrag?.Invoke() == false)
|
||
return false;
|
||
|
||
var data = _dataProvider();
|
||
if (data == null)
|
||
return false;
|
||
|
||
dragInfo = CreateDragInfo(data, Geometry.Point.Zero, Enums.DragDropEffects.Copy | Enums.DragDropEffects.Move, this);
|
||
return true;
|
||
}
|
||
|
||
public bool StartDrag(Models.DragInfo dragInfo) => true;
|
||
|
||
public void DragCompleted(Models.DragInfo dragInfo, Enums.DragDropEffects effects)
|
||
{
|
||
_onCompleted?.Invoke(dragInfo, effects);
|
||
}
|
||
|
||
public void DragCancelled(Models.DragInfo dragInfo)
|
||
{
|
||
_onCancelled?.Invoke(dragInfo);
|
||
}
|
||
}
|
||
|
||
private sealed class SimpleDropTarget : Abstractions.IDropTarget
|
||
{
|
||
private readonly Func<Models.DropInfo, bool>? _canAccept;
|
||
private readonly Action<Models.DropInfo>? _onDragOver;
|
||
private readonly Action<Models.DropInfo>? _onDrop;
|
||
private readonly Action? _onDragLeave;
|
||
|
||
public SimpleDropTarget(
|
||
Func<Models.DropInfo, bool>? canAccept = null,
|
||
Action<Models.DropInfo>? onDragOver = null,
|
||
Action<Models.DropInfo>? onDrop = null,
|
||
Action? onDragLeave = null)
|
||
{
|
||
_canAccept = canAccept;
|
||
_onDragOver = onDragOver;
|
||
_onDrop = onDrop;
|
||
_onDragLeave = onDragLeave;
|
||
}
|
||
|
||
public bool CanAcceptDrop(Models.DropInfo dropInfo)
|
||
{
|
||
return _canAccept?.Invoke(dropInfo) ?? true;
|
||
}
|
||
|
||
public void DragOver(Models.DropInfo dropInfo)
|
||
{
|
||
_onDragOver?.Invoke(dropInfo);
|
||
}
|
||
|
||
public void Drop(Models.DropInfo dropInfo)
|
||
{
|
||
_onDrop?.Invoke(dropInfo);
|
||
}
|
||
|
||
public void DragLeave()
|
||
{
|
||
_onDragLeave?.Invoke();
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
} |