DragAndDrop core

This commit is contained in:
FrigaT
2026-01-18 16:33:35 +03:00
parent 9ea82af329
commit 79bdd8bc62
229 changed files with 21214 additions and 2494 deletions

View File

@@ -0,0 +1,275 @@
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
}