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,96 @@
using Lattice.UI.Docking.Abstractions;
namespace Lattice.UI.Docking.Services;
/// <summary>
/// Базовая реализация менеджера контекстных меню.
/// </summary>
public abstract class DockContextManagerBase : IDockContextManager
{
private readonly Dictionary<string, IDockCommand> _commands = new();
private IDockControl? _currentContextTarget;
/// <inheritdoc/>
public event EventHandler<ContextMenuShownEventArgs>? ContextMenuShown;
/// <inheritdoc/>
public event EventHandler? ContextMenuHidden;
/// <inheritdoc/>
public abstract void ShowContextMenu(IDockControl element, double x, double y);
/// <inheritdoc/>
public abstract void HideContextMenu();
/// <inheritdoc/>
public virtual void RegisterCommand(string commandId, IDockCommand command)
{
if (string.IsNullOrEmpty(commandId))
throw new ArgumentNullException(nameof(commandId));
_commands[commandId] = command ?? throw new ArgumentNullException(nameof(command));
}
/// <inheritdoc/>
public virtual void UnregisterCommand(string commandId)
{
_commands.Remove(commandId);
}
/// <summary>
/// Получает команду по идентификатору.
/// </summary>
public IDockCommand? GetCommand(string commandId)
{
_commands.TryGetValue(commandId, out var command);
return command;
}
/// <summary>
/// Получает все доступные команды для указанного элемента.
/// </summary>
protected virtual IEnumerable<IDockCommand> GetCommandsForElement(IDockControl element)
{
// Фильтрация команд по типу элемента и его состоянию
return _commands.Values.Where(c => CanExecuteCommand(c, element));
}
/// <summary>
/// Проверяет, можно ли выполнить команду для указанного элемента.
/// </summary>
protected virtual bool CanExecuteCommand(IDockCommand command, IDockControl element)
{
return command.CanExecute(element);
}
/// <summary>
/// Выполняет команду для указанного элемента.
/// </summary>
protected virtual void ExecuteCommand(IDockCommand command, IDockControl element)
{
command.Execute(element);
}
/// <summary>
/// Вызывает событие показа контекстного меню.
/// </summary>
protected virtual void OnContextMenuShown(IDockControl target, double x, double y)
{
_currentContextTarget = target;
ContextMenuShown?.Invoke(this, new ContextMenuShownEventArgs(target, x, y));
}
/// <summary>
/// Вызывает событие скрытия контекстного меню.
/// </summary>
protected virtual void OnContextMenuHidden()
{
_currentContextTarget = null;
ContextMenuHidden?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Получает текущий целевой элемент контекстного меню.
/// </summary>
protected IDockControl? CurrentContextTarget => _currentContextTarget;
}

View File

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

View File

@@ -0,0 +1,113 @@
using Lattice.UI.Docking.Abstractions;
namespace Lattice.UI.Docking.Services;
/// <summary>
/// Базовая реализация UI-сервиса с общими функциями.
/// </summary>
public abstract class DockUIServiceBase : IDockUIService
{
private IDockTheme? _currentTheme;
/// <inheritdoc/>
public abstract object CreateMainWindow(IDockHost host);
/// <inheritdoc/>
public virtual bool? ShowDialog(string title, object content)
{
// Базовая реализация - просто возвращает null
// В производных классах должна быть реальная реализация
return null;
}
/// <inheritdoc/>
public virtual void ShowMessage(string message, string caption)
{
// Базовая реализация не делает ничего
// В производных классах должна быть реальная реализация
}
/// <inheritdoc/>
public virtual bool Confirm(string message, string caption)
{
// Базовая реализация всегда возвращает true
// В производных классах должна быть реальная реализация
return true;
}
/// <inheritdoc/>
public virtual string? Prompt(string prompt, string? defaultValue = null)
{
// Базовая реализация возвращает значение по умолчанию
// В производных классах должна быть реальная реализация
return defaultValue;
}
/// <inheritdoc/>
public virtual void InvokeOnUIThread(Action action)
{
// Базовая реализация просто выполняет действие
// В производных классах должна быть синхронизация с UI-потоком
action?.Invoke();
}
/// <inheritdoc/>
public virtual IDockTheme GetCurrentTheme()
{
return _currentTheme ?? CreateDefaultTheme();
}
/// <inheritdoc/>
public virtual void SetTheme(IDockTheme theme)
{
_currentTheme = theme;
theme.Apply();
}
/// <summary>
/// Создает тему по умолчанию.
/// </summary>
protected virtual IDockTheme CreateDefaultTheme()
{
return new DefaultDockTheme();
}
}
/// <summary>
/// Тема оформления по умолчанию.
/// </summary>
public class DefaultDockTheme : IDockTheme
{
public string Name => "Default";
public string BackgroundColor { get; set; } = "#1E1E1E";
public string PanelBackgroundColor { get; set; } = "#252526";
public string TabBackgroundColor { get; set; } = "#2D2D2D";
public string ActiveTabBackgroundColor { get; set; } = "#3E3E3E";
public string BorderColor { get; set; } = "#3F3F46";
public string SplitterColor { get; set; } = "#2D2D2D";
public string TextColor { get; set; } = "#CCCCCC";
public string AccentColor { get; set; } = "#007ACC";
public double CornerRadius { get; set; } = 3.0;
public double BorderThickness { get; set; } = 1.0;
public double SplitterWidth { get; set; } = 4.0;
public void Apply()
{
// В UI-реализациях этот метод должен применять тему к элементам
}
public void Reset()
{
BackgroundColor = "#1E1E1E";
PanelBackgroundColor = "#252526";
TabBackgroundColor = "#2D2D2D";
ActiveTabBackgroundColor = "#3E3E3E";
BorderColor = "#3F3F46";
SplitterColor = "#2D2D2D";
TextColor = "#CCCCCC";
AccentColor = "#007ACC";
CornerRadius = 3.0;
BorderThickness = 1.0;
SplitterWidth = 4.0;
}
}