Files
Lattice/Lattice.Core.DragDrop/Utilities/DragDropUtilities.cs
2026-01-18 16:33:35 +03:00

275 lines
9.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}