417 lines
14 KiB
C#
417 lines
14 KiB
C#
using Lattice.Core.DragDrop.Abstractions;
|
||
using Lattice.Core.DragDrop.Services;
|
||
using Lattice.UI.Docking.Abstractions;
|
||
using Lattice.UI.Docking.Models;
|
||
|
||
namespace Lattice.UI.Docking.Services;
|
||
|
||
/// <summary>
|
||
/// Реализация сервиса перетаскивания для UI-слоя док-системы.
|
||
/// Координирует взаимодействие между базовым менеджером перетаскивания
|
||
/// и UI-контролами, обеспечивая визуальную обратную связь.
|
||
/// </summary>
|
||
public class DockDragDropService : IDockDragDropService
|
||
{
|
||
private readonly DragDropManagerEx _dragDropManager;
|
||
private readonly Dictionary<IDockControl, IDragSource> _registeredDragSources = new();
|
||
private readonly Dictionary<IDockControl, IDropTarget> _registeredDropTargets = new();
|
||
private UiDragInfo? _currentUiDragInfo;
|
||
private UiDropInfo? _currentUiDropInfo;
|
||
private IDropTarget? _lastDropTarget;
|
||
|
||
/// <summary>
|
||
/// Инициализирует новый экземпляр <see cref="DockDragDropService"/>.
|
||
/// </summary>
|
||
public DockDragDropService()
|
||
{
|
||
_dragDropManager = new DragDropManagerEx();
|
||
HookEvents();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Инициализирует новый экземпляр с указанным менеджером перетаскивания.
|
||
/// </summary>
|
||
public DockDragDropService(DragDropManagerEx dragDropManager)
|
||
{
|
||
_dragDropManager = dragDropManager;
|
||
HookEvents();
|
||
}
|
||
|
||
private void HookEvents()
|
||
{
|
||
_dragDropManager.DragStarted += OnDragStarted;
|
||
_dragDropManager.DragUpdated += OnDragUpdated;
|
||
_dragDropManager.DragCompleted += OnDragCompleted;
|
||
_dragDropManager.DragCancelled += OnDragCancelled;
|
||
_dragDropManager.DropTargetChanged += OnDropTargetChanged;
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
public void RegisterDragSource(IDockControl element, IDragSource dragSource)
|
||
{
|
||
if (element == null) throw new ArgumentNullException(nameof(element));
|
||
if (dragSource == null) throw new ArgumentNullException(nameof(dragSource));
|
||
|
||
_registeredDragSources[element] = dragSource;
|
||
|
||
// Регистрируем границы элемента в менеджере
|
||
var bounds = CalculateBounds(element);
|
||
_dragDropManager.RegisterDropTarget(dragSource as IDropTarget ?? new AdapterDropTarget(dragSource),
|
||
bounds, 0, element.GetType().Name);
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
public void RegisterDropTarget(IDockControl element, IDropTarget dropTarget)
|
||
{
|
||
if (element == null) throw new ArgumentNullException(nameof(element));
|
||
if (dropTarget == null) throw new ArgumentNullException(nameof(dropTarget));
|
||
|
||
_registeredDropTargets[element] = dropTarget;
|
||
|
||
var bounds = CalculateBounds(element);
|
||
_dragDropManager.RegisterDropTarget(dropTarget, bounds, 0, element.GetType().Name);
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
public void UnregisterDragSource(IDockControl element)
|
||
{
|
||
if (element == null) throw new ArgumentNullException(nameof(element));
|
||
|
||
_registeredDragSources.Remove(element);
|
||
// TODO: Реализовать отмену регистрации в менеджере
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
public void UnregisterDropTarget(IDockControl element)
|
||
{
|
||
if (element == null) throw new ArgumentNullException(nameof(element));
|
||
|
||
_registeredDropTargets.Remove(element);
|
||
// TODO: Реализовать отмену регистрации в менеджере
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
public void StartDrag(IDockControl element, Core.DragDrop.Models.DragInfo dragInfo)
|
||
{
|
||
if (element == null) throw new ArgumentNullException(nameof(element));
|
||
if (dragInfo == null) throw new ArgumentNullException(nameof(dragInfo));
|
||
|
||
if (_registeredDragSources.TryGetValue(element, out var dragSource))
|
||
{
|
||
_currentUiDragInfo = new UiDragInfo(dragInfo, element);
|
||
_dragDropManager.StartDrag(dragSource, dragInfo.StartPosition);
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
public void UpdateDragVisual(double x, double y)
|
||
{
|
||
var position = new Core.DragDrop.Geometry.Point(x, y);
|
||
_dragDropManager.UpdateDrag(position);
|
||
|
||
if (_currentUiDragInfo != null)
|
||
{
|
||
// Обновляем позицию визуального представления
|
||
OnDragVisualUpdated?.Invoke(this, new DragVisualUpdatedEventArgs(
|
||
_currentUiDragInfo, position));
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
public void EndDrag(double x, double y)
|
||
{
|
||
var position = new Core.DragDrop.Geometry.Point(x, y);
|
||
_dragDropManager.EndDrag(position);
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
public void CancelDrag()
|
||
{
|
||
_dragDropManager.CancelDrag();
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
public void ShowDropHint(IDockControl element, DropPosition position)
|
||
{
|
||
if (_currentUiDropInfo != null)
|
||
{
|
||
_currentUiDropInfo.DropPosition = position;
|
||
_currentUiDropInfo.IsOverValidTarget = true;
|
||
_currentUiDropInfo.HighlightIntensity = 0.8;
|
||
|
||
OnDropHintChanged?.Invoke(this, new DropHintEventArgs(
|
||
element, position, true, _currentUiDropInfo.HighlightIntensity));
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
public void HideDropHint()
|
||
{
|
||
if (_currentUiDropInfo != null)
|
||
{
|
||
_currentUiDropInfo.IsOverValidTarget = false;
|
||
_currentUiDropInfo.HighlightIntensity = 0.0;
|
||
|
||
OnDropHintChanged?.Invoke(this, new DropHintEventArgs(
|
||
_currentUiDropInfo.TargetControl,
|
||
_currentUiDropInfo.DropPosition,
|
||
false,
|
||
0.0));
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc/>
|
||
public event EventHandler<DragStartedEventArgs>? DragStarted;
|
||
|
||
/// <inheritdoc/>
|
||
public event EventHandler<DragUpdatedEventArgs>? DragUpdated;
|
||
|
||
/// <inheritdoc/>
|
||
public event EventHandler<DragCompletedEventArgs>? DragCompleted;
|
||
|
||
/// <inheritdoc/>
|
||
public event EventHandler? DragCancelled;
|
||
|
||
/// <summary>
|
||
/// Событие, возникающее при обновлении визуального представления перетаскивания.
|
||
/// </summary>
|
||
public event EventHandler<DragVisualUpdatedEventArgs>? OnDragVisualUpdated;
|
||
|
||
/// <summary>
|
||
/// Событие, возникающее при изменении визуальной подсказки сброса.
|
||
/// </summary>
|
||
public event EventHandler<DropHintEventArgs>? OnDropHintChanged;
|
||
|
||
private void OnDragStarted(object? sender, DragStartedEventArgs e)
|
||
{
|
||
// Обновляем UI-информацию
|
||
if (_currentUiDragInfo != null)
|
||
{
|
||
_currentUiDragInfo.BaseDragInfo.StartPosition = e.StartPosition;
|
||
|
||
// Создаем визуальное представление
|
||
CreateDragVisual(_currentUiDragInfo);
|
||
|
||
DragStarted?.Invoke(this, new DragStartedEventArgs(
|
||
_currentUiDragInfo.SourceControl,
|
||
_currentUiDragInfo.BaseDragInfo));
|
||
}
|
||
}
|
||
|
||
private void OnDragUpdated(object? sender, DragUpdatedEventArgs e)
|
||
{
|
||
if (_currentUiDragInfo != null)
|
||
{
|
||
// Обновляем позицию визуального представления
|
||
UpdateDragVisualPosition(e.Position);
|
||
|
||
DragUpdated?.Invoke(this, new DragUpdatedEventArgs(
|
||
_currentUiDragInfo.SourceControl,
|
||
e.Position.X,
|
||
e.Position.Y,
|
||
_currentUiDragInfo.BaseDragInfo));
|
||
}
|
||
}
|
||
|
||
private void OnDragCompleted(object? sender, DragCompletedEventArgs e)
|
||
{
|
||
var targetControl = _currentUiDropInfo?.TargetControl;
|
||
var dropPosition = _currentUiDropInfo?.DropPosition ?? DropPosition.Center;
|
||
|
||
DragCompleted?.Invoke(this, new DragCompletedEventArgs(
|
||
_currentUiDragInfo?.SourceControl,
|
||
targetControl,
|
||
dropPosition,
|
||
_currentUiDragInfo?.BaseDragInfo,
|
||
e.Effects != Core.DragDrop.Enums.DragDropEffects.None));
|
||
|
||
// Очищаем визуальные представления
|
||
CleanupDragVisual();
|
||
CleanupDropHint();
|
||
|
||
_currentUiDragInfo = null;
|
||
_currentUiDropInfo = null;
|
||
_lastDropTarget = null;
|
||
}
|
||
|
||
private void OnDragCancelled(object? sender, DragCancelledEventArgs e)
|
||
{
|
||
DragCancelled?.Invoke(this, EventArgs.Empty);
|
||
|
||
CleanupDragVisual();
|
||
CleanupDropHint();
|
||
|
||
_currentUiDragInfo = null;
|
||
_currentUiDropInfo = null;
|
||
_lastDropTarget = null;
|
||
}
|
||
|
||
private void OnDropTargetChanged(object? sender, DropTargetChangedEventArgs e)
|
||
{
|
||
var dropTarget = e.Target;
|
||
|
||
// Обновляем UI-информацию о сбросе
|
||
if (dropTarget != null)
|
||
{
|
||
// Находим соответствующий UI-контрол
|
||
var targetControl = _registeredDropTargets
|
||
.FirstOrDefault(kv => kv.Value == dropTarget)
|
||
.Key;
|
||
|
||
_currentUiDropInfo = new UiDropInfo(
|
||
new Core.DragDrop.Models.DropInfo(
|
||
_dragDropManager.CurrentDragInfo?.Data,
|
||
e.TargetBounds.Center,
|
||
_dragDropManager.CurrentDragInfo?.AllowedEffects ?? Core.DragDrop.Enums.DragDropEffects.None,
|
||
dropTarget),
|
||
targetControl);
|
||
|
||
_currentUiDropInfo.DropPosition = CalculateDropPosition(
|
||
_currentUiDragInfo?.BaseDragInfo.StartPosition ?? Core.DragDrop.Geometry.Point.Zero,
|
||
e.TargetBounds);
|
||
}
|
||
else
|
||
{
|
||
_currentUiDropInfo = null;
|
||
}
|
||
|
||
// Уведомляем об изменении цели сброса
|
||
if (_lastDropTarget != dropTarget)
|
||
{
|
||
if (_lastDropTarget != null)
|
||
{
|
||
HideDropHint();
|
||
}
|
||
|
||
if (dropTarget != null && _currentUiDropInfo != null)
|
||
{
|
||
ShowDropHint(_currentUiDropInfo.TargetControl, _currentUiDropInfo.DropPosition);
|
||
}
|
||
|
||
_lastDropTarget = dropTarget;
|
||
}
|
||
}
|
||
|
||
private Core.DragDrop.Geometry.Rect CalculateBounds(IDockControl element)
|
||
{
|
||
// В UI-реализациях этот метод должен быть переопределен
|
||
// для вычисления реальных границ элемента на экране
|
||
return new Core.DragDrop.Geometry.Rect(0, 0, 100, 100);
|
||
}
|
||
|
||
private DropPosition CalculateDropPosition(Core.DragDrop.Geometry.Point cursorPos, Core.DragDrop.Geometry.Rect targetBounds)
|
||
{
|
||
// Простая логика определения позиции сброса
|
||
var center = targetBounds.Center;
|
||
var relativeX = (cursorPos.X - targetBounds.X) / targetBounds.Width;
|
||
var relativeY = (cursorPos.Y - targetBounds.Y) / targetBounds.Height;
|
||
|
||
if (relativeX < 0.25) return DropPosition.Left;
|
||
if (relativeX > 0.75) return DropPosition.Right;
|
||
if (relativeY < 0.25) return DropPosition.Top;
|
||
if (relativeY > 0.75) return DropPosition.Bottom;
|
||
|
||
return DropPosition.Center;
|
||
}
|
||
|
||
private void CreateDragVisual(UiDragInfo dragInfo)
|
||
{
|
||
// В UI-реализациях этот метод должен создавать визуальное представление
|
||
OnDragVisualUpdated?.Invoke(this, new DragVisualUpdatedEventArgs(
|
||
dragInfo, dragInfo.BaseDragInfo.StartPosition));
|
||
}
|
||
|
||
private void UpdateDragVisualPosition(Core.DragDrop.Geometry.Point position)
|
||
{
|
||
if (_currentUiDragInfo != null)
|
||
{
|
||
OnDragVisualUpdated?.Invoke(this, new DragVisualUpdatedEventArgs(
|
||
_currentUiDragInfo, position));
|
||
}
|
||
}
|
||
|
||
private void CleanupDragVisual()
|
||
{
|
||
// В UI-реализациях этот метод должен очищать визуальное представление
|
||
OnDragVisualUpdated?.Invoke(this, new DragVisualUpdatedEventArgs(null, Core.DragDrop.Geometry.Point.Zero));
|
||
}
|
||
|
||
private void CleanupDropHint()
|
||
{
|
||
HideDropHint();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Адаптер для преобразования IDragSource в IDropTarget.
|
||
/// </summary>
|
||
internal class AdapterDropTarget : IDropTarget
|
||
{
|
||
private readonly IDragSource _dragSource;
|
||
|
||
public AdapterDropTarget(IDragSource dragSource)
|
||
{
|
||
_dragSource = dragSource;
|
||
}
|
||
|
||
public bool CanAcceptDrop(Core.DragDrop.Models.DropInfo dropInfo) => false;
|
||
public void DragOver(Core.DragDrop.Models.DropInfo dropInfo) { }
|
||
public void Drop(Core.DragDrop.Models.DropInfo dropInfo) { }
|
||
public void DragLeave() { }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Аргументы события обновления визуального представления перетаскивания.
|
||
/// </summary>
|
||
public class DragVisualUpdatedEventArgs : EventArgs
|
||
{
|
||
/// <summary>
|
||
/// Информация о перетаскивании.
|
||
/// </summary>
|
||
public UiDragInfo? DragInfo { get; }
|
||
|
||
/// <summary>
|
||
/// Текущая позиция.
|
||
/// </summary>
|
||
public Core.DragDrop.Geometry.Point Position { get; }
|
||
|
||
public DragVisualUpdatedEventArgs(UiDragInfo? dragInfo, Core.DragDrop.Geometry.Point position)
|
||
{
|
||
DragInfo = dragInfo;
|
||
Position = position;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Аргументы события изменения визуальной подсказки сброса.
|
||
/// </summary>
|
||
public class DropHintEventArgs : EventArgs
|
||
{
|
||
/// <summary>
|
||
/// Целевой элемент.
|
||
/// </summary>
|
||
public IDockControl? Target { get; }
|
||
|
||
/// <summary>
|
||
/// Позиция сброса.
|
||
/// </summary>
|
||
public DropPosition Position { get; }
|
||
|
||
/// <summary>
|
||
/// Показывает, видима ли подсказка.
|
||
/// </summary>
|
||
public bool IsVisible { get; }
|
||
|
||
/// <summary>
|
||
/// Интенсивность подсветки.
|
||
/// </summary>
|
||
public double Intensity { get; }
|
||
|
||
public DropHintEventArgs(IDockControl? target, DropPosition position, bool isVisible, double intensity)
|
||
{
|
||
Target = target;
|
||
Position = position;
|
||
IsVisible = isVisible;
|
||
Intensity = intensity;
|
||
}
|
||
} |