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,259 @@
using Lattice.UI.DragDrop.WinUI.Controls;
using Lattice.UI.DragDrop.WinUI.Extensions;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
namespace Lattice.UI.DragDrop.WinUI.Services;
/// <summary>
/// Сервис для настройки перетаскивания с поддержкой различных сценариев.
/// </summary>
public class DragDropConfigurationService
{
private readonly Dictionary<UIElement, Configuration> _configurations = new();
private readonly DragDropOverlay _overlay;
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragDropConfigurationService"/>.
/// </summary>
public DragDropConfigurationService()
{
_overlay = new DragDropOverlay();
}
/// <summary>
/// Настройка элемента как источника перетаскивания.
/// </summary>
public void ConfigureAsDragSource(UIElement element, DragSourceConfiguration config)
{
element.MakeDragSource(config.Data);
if (config.AllowedEffects.HasValue)
{
element.SetAllowedEffects(config.AllowedEffects.Value);
}
if (config.VisualOffset.HasValue)
{
element.SetDragVisualOffset(config.VisualOffset.Value.X, config.VisualOffset.Value.Y);
}
_configurations[element] = new Configuration { DragSourceConfig = config };
}
/// <summary>
/// Настройка элемента как цели сброса.
/// </summary>
public void ConfigureAsDropTarget(UIElement element, DropTargetConfiguration config)
{
element.MakeDropTarget(
config.AcceptedTypes,
config.Handler,
config.ShowVisualFeedback,
config.FeedbackStyle);
if (!_configurations.ContainsKey(element))
{
_configurations[element] = new Configuration();
}
_configurations[element].DropTargetConfig = config;
}
/// <summary>
/// Настройка элемента для переупорядочивания в контейнере.
/// </summary>
public void ConfigureForReorder(UIElement element, Panel container, ReorderConfiguration config)
{
ConfigureAsDragSource(element, new DragSourceConfiguration
{
Data = element,
AllowedEffects = Core.DragDrop.Enums.DragDropEffects.Move,
VisualOffset = new Windows.Foundation.Point(-20, -20)
});
ConfigureAsDropTarget(container, new DropTargetConfiguration
{
AcceptedTypes = new[] { typeof(UIElement) },
ShowVisualFeedback = config.ShowVisualFeedback,
FeedbackStyle = config.FeedbackStyle
});
// Настраиваем логику переупорядочивания
container.Drop += (sender, e) => HandleReorderDrop(sender as Panel, element, e);
}
/// <summary>
/// Подключает оверлей к указанному контейнеру.
/// </summary>
public void AttachOverlayTo(Panel container)
{
if (container.Children.Contains(_overlay))
return;
container.Children.Add(_overlay);
}
/// <summary>
/// Отключает все настройки перетаскивания для элемента.
/// </summary>
public void DisableDragDrop(UIElement element)
{
element.RemoveDragSource();
element.RemoveDropTarget();
_configurations.Remove(element);
}
/// <summary>
/// Очищает все настройки.
/// </summary>
public void Clear()
{
foreach (var element in _configurations.Keys)
{
element.RemoveDragSource();
element.RemoveDropTarget();
}
_configurations.Clear();
}
private void HandleReorderDrop(Panel? container, UIElement draggedElement, Microsoft.UI.Xaml.DragEventArgs e)
{
if (container == null) return;
var position = e.GetPosition(container);
int insertIndex = CalculateInsertIndex(container, position);
if (insertIndex >= 0 && insertIndex < container.Children.Count)
{
container.Children.Remove(draggedElement);
container.Children.Insert(insertIndex, draggedElement);
}
}
private int CalculateInsertIndex(Panel container, Windows.Foundation.Point position)
{
for (int i = 0; i < container.Children.Count; i++)
{
var child = container.Children[i];
if (child is FrameworkElement element)
{
var childBounds = new Windows.Foundation.Rect(
Canvas.GetLeft(element),
Canvas.GetTop(element),
element.ActualWidth,
element.ActualHeight);
if (position.Y < childBounds.Y + childBounds.Height / 2)
{
return i;
}
}
}
return container.Children.Count;
}
private class Configuration
{
public DragSourceConfiguration? DragSourceConfig { get; set; }
public DropTargetConfiguration? DropTargetConfig { get; set; }
}
}
/// <summary>
/// Конфигурация источника перетаскивания.
/// </summary>
public class DragSourceConfiguration
{
/// <summary>
/// Данные для перетаскивания.
/// </summary>
public required object Data { get; set; }
/// <summary>
/// Разрешенные эффекты.
/// </summary>
public Core.DragDrop.Enums.DragDropEffects? AllowedEffects { get; set; }
/// <summary>
/// Смещение визуального элемента.
/// </summary>
public Windows.Foundation.Point? VisualOffset { get; set; }
/// <summary>
/// Пользовательский обработчик.
/// </summary>
public Action<UIElement>? OnDragStarted { get; set; }
/// <summary>
/// Пользовательский обработчик завершения.
/// </summary>
public Action<UIElement, Core.DragDrop.Enums.DragDropEffects>? OnDragCompleted { get; set; }
}
/// <summary>
/// Конфигурация цели сброса.
/// </summary>
public class DropTargetConfiguration
{
/// <summary>
/// Принимаемые типы данных.
/// </summary>
public Type[]? AcceptedTypes { get; set; }
/// <summary>
/// Обработчик сброса.
/// </summary>
public Core.DragDrop.Abstractions.IDropTarget? Handler { get; set; }
/// <summary>
/// Показывать визуальную обратную связь.
/// </summary>
public bool ShowVisualFeedback { get; set; } = true;
/// <summary>
/// Стиль визуальной обратной связи.
/// </summary>
public Style? FeedbackStyle { get; set; }
/// <summary>
/// Пользовательский обработчик валидации.
/// </summary>
public Func<object, bool>? CustomValidation { get; set; }
/// <summary>
/// Пользовательский обработчик сброса.
/// </summary>
public Action<object>? OnDrop { get; set; }
}
/// <summary>
/// Конфигурация переупорядочивания.
/// </summary>
public class ReorderConfiguration
{
/// <summary>
/// Показывать визуальную обратную связь.
/// </summary>
public bool ShowVisualFeedback { get; set; } = true;
/// <summary>
/// Стиль визуальной обратной связи.
/// </summary>
public Style? FeedbackStyle { get; set; }
/// <summary>
/// Включать анимацию при переупорядочивании.
/// </summary>
public bool EnableAnimation { get; set; } = true;
/// <summary>
/// Порог для начала перетаскивания (в пикселях).
/// </summary>
public double DragThreshold { get; set; } = 5.0;
}

View File

@@ -0,0 +1,132 @@
using Lattice.Core.DragDrop.Services;
using Lattice.UI.DragDrop.Abstractions;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
namespace Lattice.UI.DragDrop.WinUI.Services;
/// <summary>
/// Сервис интеграции Drag & Drop с WinUI приложением.
/// </summary>
public class WinUIDragDropIntegrationService : IDisposable
{
private readonly IDragDropService _dragDropService;
private readonly IDragVisualProvider _dragVisualProvider;
private readonly Canvas _overlayCanvas;
private readonly Window _window;
private object? _currentDragVisual;
private bool _disposed;
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="WinUIDragDropIntegrationService"/>.
/// </summary>
public WinUIDragDropIntegrationService(
Window window,
IDragDropService dragDropService,
IDragVisualProvider dragVisualProvider)
{
_window = window ?? throw new ArgumentNullException(nameof(window));
_dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService));
_dragVisualProvider = dragVisualProvider ?? throw new ArgumentNullException(nameof(dragVisualProvider));
// Создаем оверлейный Canvas для визуальных элементов
_overlayCanvas = new Canvas
{
IsHitTestVisible = false,
Background = null
};
// Подписываемся на события перетаскивания
SubscribeToEvents();
}
/// <summary>
/// Встраивает оверлей в указанный контейнер.
/// </summary>
public void AttachToContainer(Panel container)
{
if (container == null)
throw new ArgumentNullException(nameof(container));
// Убеждаемся, что оверлей находится поверх всех элементов
Canvas.SetZIndex(_overlayCanvas, int.MaxValue);
container.Children.Add(_overlayCanvas);
}
private void SubscribeToEvents()
{
_dragDropService.DragStarted += OnDragStarted;
_dragDropService.DragUpdated += OnDragUpdated;
_dragDropService.DragCompleted += OnDragCompleted;
_dragDropService.DragCancelled += OnDragCancelled;
}
private void UnsubscribeFromEvents()
{
_dragDropService.DragStarted -= OnDragStarted;
_dragDropService.DragUpdated -= OnDragUpdated;
_dragDropService.DragCompleted -= OnDragCompleted;
_dragDropService.DragCancelled -= OnDragCancelled;
}
private void OnDragStarted(object? sender, DragStartedEventArgs e)
{
// Создаем визуальное представление
_currentDragVisual = _dragVisualProvider.CreateDragVisual(
e.DragInfo,
e.StartPosition);
// Добавляем на оверлей
if (_currentDragVisual is UIElement element)
{
_overlayCanvas.Children.Add(element);
}
}
private void OnDragUpdated(object? sender, DragUpdatedEventArgs e)
{
if (_currentDragVisual != null)
{
_dragVisualProvider.UpdateDragVisualPosition(_currentDragVisual, e.Position);
}
}
private void OnDragCompleted(object? sender, DragCompletedEventArgs e)
{
CleanupDragVisual();
}
private void OnDragCancelled(object? sender, DragCancelledEventArgs e)
{
CleanupDragVisual();
}
private void CleanupDragVisual()
{
if (_currentDragVisual != null)
{
_dragVisualProvider.ReleaseDragVisual(_currentDragVisual);
_currentDragVisual = null;
}
}
/// <inheritdoc/>
public void Dispose()
{
if (!_disposed)
{
UnsubscribeFromEvents();
CleanupDragVisual();
if (_overlayCanvas.Parent is Panel parent)
{
parent.Children.Remove(_overlayCanvas);
}
_disposed = true;
}
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,87 @@
using Lattice.Core.DragDrop.Models;
using Lattice.Core.Geometry;
using Lattice.UI.DragDrop.Abstractions;
using Lattice.UI.DragDrop.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
namespace Lattice.UI.DragDrop.WinUI.Services;
/// <summary>
/// Поставщик визуального представления для WinUI.
/// </summary>
public class WinUIDragVisualProvider : IDragVisualProvider
{
private readonly ResourceDictionary _resources;
private DragAdorner? _currentAdorner;
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="WinUIDragVisualProvider"/>.
/// </summary>
/// <param name="resources">Ресурсы для стилей.</param>
public WinUIDragVisualProvider(ResourceDictionary resources)
{
_resources = resources ?? throw new ArgumentNullException(nameof(resources));
}
/// <inheritdoc/>
public object CreateDragVisual(DragInfo dragInfo, Point initialPosition)
{
// Создаем новый DragAdorner
_currentAdorner = new DragAdorner
{
DragData = dragInfo.Data,
OpacityLevel = 0.8
};
// Применяем стиль из ресурсов, если есть
if (_resources.ContainsKey("DragAdornerStyle"))
{
_currentAdorner.Style = _resources["DragAdornerStyle"] as Style;
}
// Настраиваем начальную позицию
_currentAdorner.UpdatePosition(initialPosition);
_currentAdorner.Show();
return _currentAdorner;
}
/// <inheritdoc/>
public void UpdateDragVisualPosition(object dragVisual, Point position)
{
if (dragVisual is DragAdorner adorner)
{
adorner.UpdatePosition(position);
}
}
/// <inheritdoc/>
public void ReleaseDragVisual(object dragVisual)
{
if (dragVisual is DragAdorner adorner)
{
adorner.Hide();
// Отложенное удаление после анимации
var timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(150)
};
timer.Tick += (s, e) =>
{
timer.Stop();
if (adorner.Parent is Panel panel)
{
panel.Children.Remove(adorner);
}
};
timer.Start();
}
_currentAdorner = null;
}
}