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,97 @@
using Lattice.Core.Docking.Models;
namespace Lattice.UI.Docking.Abstractions;
/// <summary>
/// Определяет контракт для UI-контрола, который представляет автоскрываемую панель.
/// </summary>
/// <remarks>
/// Автоскрываемые панели прикреплены к одной из сторон окна и скрываются,
/// оставляя видимым только заголовок. При наведении курсора панель выезжает.
/// </remarks>
public interface IAutoHidePanelControl : IDockControl
{
/// <summary>
/// Получает или задает сторону прикрепления панели.
/// </summary>
/// <value>
/// Сторона окна, к которой прикреплена панель.
/// </value>
DockSide Side { get; set; }
/// <summary>
/// Получает или задает ширину панели (для левой/правой сторон).
/// </summary>
/// <value>
/// Ширина панели в пикселях.
/// </value>
double Width { get; set; }
/// <summary>
/// Получает или задает высоту панели (для верхней/нижней сторон).
/// </summary>
/// <value>
/// Высота панели в пикселях.
/// </value>
double Height { get; set; }
/// <summary>
/// Получает или задает признак видимости панели.
/// </summary>
/// <value>
/// true, если панель видима; в противном случае — false.
/// </value>
bool IsVisible { get; set; }
/// <summary>
/// Получает или задает признак того, что панель всегда видима.
/// </summary>
/// <value>
/// true, если панель всегда видима; в противном случае — false.
/// </value>
bool IsPinned { get; set; }
/// <summary>
/// Получает или задает задержку перед скрытием панели (в миллисекундах).
/// </summary>
/// <value>
/// Задержка в миллисекундах.
/// </value>
int AutoHideDelay { get; set; }
/// <summary>
/// Показывает панель.
/// </summary>
void Show();
/// <summary>
/// Скрывает панель.
/// </summary>
void Hide();
/// <summary>
/// Переключает состояние видимости панели.
/// </summary>
void Toggle();
/// <summary>
/// Задает фиксированное состояние панели.
/// </summary>
/// <param name="pinned">true, чтобы зафиксировать панель; false, чтобы разрешить автоскрытие.</param>
void SetPinned(bool pinned);
/// <summary>
/// Событие, возникающее при изменении видимости панели.
/// </summary>
event EventHandler VisibilityChanged;
/// <summary>
/// Событие, возникающее при наведении курсора на панель.
/// </summary>
event EventHandler MouseEntered;
/// <summary>
/// Событие, возникающее при уходе курсора с панели.
/// </summary>
event EventHandler MouseLeft;
}

View File

@@ -0,0 +1,50 @@
namespace Lattice.UI.Docking.Abstractions;
/// <summary>
/// Определяет контракт для команды док-системы.
/// </summary>
public interface IDockCommand
{
/// <summary>
/// Получает идентификатор команды.
/// </summary>
string Id { get; }
/// <summary>
/// Получает отображаемое имя команды.
/// </summary>
string Name { get; }
/// <summary>
/// Получает описание команды.
/// </summary>
string Description { get; }
/// <summary>
/// Получает значок команды.
/// </summary>
string Icon { get; }
/// <summary>
/// Получает комбинацию клавиш для команды.
/// </summary>
string Shortcut { get; }
/// <summary>
/// Определяет, можно ли выполнить команду.
/// </summary>
/// <param name="parameter">Параметр команды.</param>
/// <returns>true, если команду можно выполнить; в противном случае — false.</returns>
bool CanExecute(object? parameter);
/// <summary>
/// Выполняет команду.
/// </summary>
/// <param name="parameter">Параметр команды.</param>
void Execute(object? parameter);
/// <summary>
/// Событие, возникающее при изменении возможности выполнения команды.
/// </summary>
event EventHandler CanExecuteChanged;
}

View File

@@ -0,0 +1,43 @@
namespace Lattice.UI.Docking.Abstractions;
/// <summary>
/// Определяет контракт для менеджера контекстных меню док-системы.
/// </summary>
public interface IDockContextManager
{
/// <summary>
/// Показывает контекстное меню для указанного элемента.
/// </summary>
/// <param name="element">Элемент, для которого показывается меню.</param>
/// <param name="x">Координата X для отображения меню.</param>
/// <param name="y">Координата Y для отображения меню.</param>
void ShowContextMenu(IDockControl element, double x, double y);
/// <summary>
/// Скрывает текущее контекстное меню.
/// </summary>
void HideContextMenu();
/// <summary>
/// Регистрирует команду в контекстном меню.
/// </summary>
/// <param name="commandId">Идентификатор команды.</param>
/// <param name="command">Команда для регистрации.</param>
void RegisterCommand(string commandId, IDockCommand command);
/// <summary>
/// Удаляет команду из контекстного меню.
/// </summary>
/// <param name="commandId">Идентификатор команды.</param>
void UnregisterCommand(string commandId);
/// <summary>
/// Событие, возникающее при показе контекстного меню.
/// </summary>
event EventHandler<ContextMenuShownEventArgs> ContextMenuShown;
/// <summary>
/// Событие, возникающее при скрытии контекстного меню.
/// </summary>
event EventHandler ContextMenuHidden;
}

View File

@@ -0,0 +1,103 @@
using Lattice.Core.Docking.Abstractions;
using Lattice.Core.Docking.Engine;
using System.ComponentModel;
namespace Lattice.UI.Docking.Abstractions;
/// <summary>
/// Определяет базовый контракт для всех UI-контролов, участвующих в системе докинга.
/// Этот интерфейс предоставляет общие свойства и методы, необходимые для интеграции
/// с менеджером макета и системой перетаскивания.
/// </summary>
/// <remarks>
/// Реализации этого интерфейса должны отображать элементы док-системы (DockGroup, DockLeaf)
/// и обеспечивать взаимодействие пользователя с ними через жесты мыши, клавиатуру и сенсорный ввод.
/// </remarks>
public interface IDockControl : INotifyPropertyChanged
{
/// <summary>
/// Получает или задает модель данных, связанную с этим контролом.
/// </summary>
/// <value>
/// Экземпляр класса, реализующего <see cref="IDockElement"/>, который представляет
/// состояние и структуру отображаемого элемента док-системы.
/// </value>
IDockElement? Model { get; set; }
/// <summary>
/// Получает или задает менеджер макета, к которому принадлежит этот контрол.
/// </summary>
/// <value>
/// Экземпляр <see cref="LayoutManager"/>, управляющий структурой док-системы.
/// </value>
LayoutManager? LayoutManager { get; set; }
/// <summary>
/// Получает или задает сервис перетаскивания, используемый этим контролом.
/// </summary>
/// <value>
/// Реализация <see cref="IDragDropService"/> для обработки операций перетаскивания.
/// </value>
IDragDropService? DragDropService { get; set; }
/// <summary>
/// Получает или задает контекстный менеджер для этого контрола.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockContextManager"/>, управляющий контекстными меню и действиями.
/// </value>
IDockContextManager? ContextManager { get; set; }
/// <summary>
/// Получает или задает признак того, что контрол выбран.
/// </summary>
/// <value>
/// true, если контрол выбран; в противном случае — false.
/// </value>
bool IsSelected { get; set; }
/// <summary>
/// Получает или задает признак того, что контрол активен.
/// </summary>
/// <value>
/// true, если контрол активен; в противном случае — false.
/// </value>
bool IsActive { get; set; }
/// <summary>
/// Получает или задает признак того, что контрол можно перетаскивать.
/// </summary>
/// <value>
/// true, если контрол можно перетаскивать; в противном случае — false.
/// </value>
bool CanDrag { get; set; }
/// <summary>
/// Получает или задает признак того, что контрол может принимать сброс.
/// </summary>
/// <value>
/// true, если контрол может принимать сброс; в противном случае — false.
/// </value>
bool CanDrop { get; set; }
/// <summary>
/// Обновляет внешний вид контрола в соответствии с текущим состоянием модели.
/// </summary>
/// <remarks>
/// Этот метод должен вызываться при изменении свойств модели или при необходимости
/// принудительного обновления UI (например, после изменения темы или масштаба).
/// </remarks>
void Refresh();
/// <summary>
/// Применяет указанную тему к контролу.
/// </summary>
/// <param name="theme">Тема для применения.</param>
void ApplyTheme(IDockTheme theme);
/// <summary>
/// Вызывается при изменении состояния модели для обновления UI.
/// </summary>
/// <param name="propertyName">Имя изменившегося свойства модели.</param>
void OnModelPropertyChanged(string propertyName);
}

View File

@@ -0,0 +1,256 @@
namespace Lattice.UI.Docking.Abstractions;
/// <summary>
/// Предоставляет сервис для операций перетаскивания в UI-слое док-системы.
/// Абстрагирует платформенно-зависимую логику перетаскивания и обеспечивает
/// единый интерфейс для управления операциями drag-and-drop.
/// </summary>
/// <remarks>
/// Этот интерфейс служит мостом между базовым менеджером перетаскивания из Core
/// и UI-контролами, добавляя визуальную обратную связь и обработку событий,
/// специфичных для пользовательского интерфейса.
/// </remarks>
public interface IDockDragDropService
{
/// <summary>
/// Начинает операцию перетаскивания для указанного элемента.
/// </summary>
/// <param name="element">UI-контрол, который инициирует перетаскивание.</param>
/// <param name="dragInfo">
/// Информация о перетаскивании, содержащая данные и параметры операции.
/// </param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="element"/> или <paramref name="dragInfo"/> равны null.
/// </exception>
/// <remarks>
/// Этот метод должен создавать визуальное представление перетаскиваемого элемента
/// и инициировать отслеживание перемещения мыши.
/// </remarks>
void StartDrag(IDockControl element, Core.DragDrop.Models.DragInfo dragInfo);
/// <summary>
/// Обновляет позицию текущей операции перетаскивания.
/// </summary>
/// <param name="x">Новая координата X курсора в экранных координатах.</param>
/// <param name="y">Новая координата Y курсора в экранных координатах.</param>
/// <remarks>
/// Вызывается при каждом перемещении мыши во время операции перетаскивания.
/// Должен обновлять позицию визуального представления и проверять возможные цели сброса.
/// </remarks>
void UpdateDrag(double x, double y);
/// <summary>
/// Завершает текущую операцию перетаскивания в указанной позиции.
/// </summary>
/// <param name="x">Координата X завершения перетаскивания.</param>
/// <param name="y">Координата Y завершения перетаскивания.</param>
/// <remarks>
/// Выполняет сброс данных на текущую цель (если она есть) и очищает ресурсы,
/// выделенные для операции перетаскивания.
/// </remarks>
void EndDrag(double x, double y);
/// <summary>
/// Отменяет текущую операцию перетаскивания.
/// </summary>
/// <remarks>
/// Вызывается при нажатии клавиши Escape или других действиях, приводящих к отмене.
/// Должен восстанавливать исходное состояние элементов и очищать ресурсы.
/// </remarks>
void CancelDrag();
/// <summary>
/// Показывает визуальную подсказку о возможной позиции сброса.
/// </summary>
/// <param name="element">UI-контрол, для которого показывается подсказка.</param>
/// <param name="position">Предполагаемая позиция сброса.</param>
/// <remarks>
/// Используется для визуальной обратной связи, чтобы пользователь видел,
/// куда будет помещен элемент при отпускании кнопки мыши.
/// </remarks>
void ShowDropHint(IDockControl element, Models.DropPosition position);
/// <summary>
/// Скрывает текущую визуальную подсказку о сбросе.
/// </summary>
/// <remarks>
/// Вызывается, когда курсор покидает допустимую область сброса
/// или операция перетаскивания завершается.
/// </remarks>
void HideDropHint();
/// <summary>
/// Событие, возникающее при начале операции перетаскивания.
/// </summary>
event EventHandler<DragStartedEventArgs> DragStarted;
/// <summary>
/// Событие, возникающее при обновлении позиции перетаскивания.
/// </summary>
event EventHandler<DragUpdatedEventArgs> DragUpdated;
/// <summary>
/// Событие, возникающее при завершении операции перетаскивания.
/// </summary>
event EventHandler<DragCompletedEventArgs> DragCompleted;
/// <summary>
/// Событие, возникающее при отмене операции перетаскивания.
/// </summary>
event EventHandler DragCancelled;
}
/// <summary>
/// Предоставляет данные для события начала перетаскивания.
/// </summary>
public class DragStartedEventArgs : EventArgs
{
/// <summary>
/// Получает UI-контрол, который инициировал перетаскивание.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockControl"/>, представляющий источник перетаскивания.
/// Может быть null, если перетаскивание инициировано не из UI-элемента.
/// </value>
public IDockControl? Source { get; }
/// <summary>
/// Получает информацию о перетаскивании.
/// </summary>
/// <value>
/// Экземпляр <see cref="Core.DragDrop.Models.DragInfo"/> с данными перетаскивания.
/// </value>
public Core.DragDrop.Models.DragInfo DragInfo { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragStartedEventArgs"/>.
/// </summary>
/// <param name="source">Источник перетаскивания.</param>
/// <param name="dragInfo">Информация о перетаскивании.</param>
public DragStartedEventArgs(IDockControl? source, Core.DragDrop.Models.DragInfo dragInfo)
{
Source = source;
DragInfo = dragInfo;
}
}
/// <summary>
/// Предоставляет данные для события обновления перетаскивания.
/// </summary>
public class DragUpdatedEventArgs : EventArgs
{
/// <summary>
/// Получает UI-контрол, который инициировал перетаскивание.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockControl"/>, представляющий источник перетаскивания.
/// </value>
public IDockControl? Source { get; }
/// <summary>
/// Получает текущую координату X курсора.
/// </summary>
/// <value>
/// Координата X в экранных координатах.
/// </value>
public double X { get; }
/// <summary>
/// Получает текущую координату Y курсора.
/// </summary>
/// <value>
/// Координата Y в экранных координатах.
/// </value>
public double Y { get; }
/// <summary>
/// Получает информацию о перетаскивании.
/// </summary>
/// <value>
/// Экземпляр <see cref="Core.DragDrop.Models.DragInfo"/> с текущими данными перетаскивания.
/// </value>
public Core.DragDrop.Models.DragInfo DragInfo { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragUpdatedEventArgs"/>.
/// </summary>
/// <param name="source">Источник перетаскивания.</param>
/// <param name="x">Текущая координата X.</param>
/// <param name="y">Текущая координата Y.</param>
/// <param name="dragInfo">Информация о перетаскивании.</param>
public DragUpdatedEventArgs(IDockControl? source, double x, double y, Core.DragDrop.Models.DragInfo dragInfo)
{
Source = source;
X = x;
Y = y;
DragInfo = dragInfo;
}
}
/// <summary>
/// Предоставляет данные для события завершения перетаскивания.
/// </summary>
public class DragCompletedEventArgs : EventArgs
{
/// <summary>
/// Получает UI-контрол, который инициировал перетаскивание.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockControl"/>, представляющий источник перетаскивания.
/// Может быть null, если операция была инициирована не из UI.
/// </value>
public IDockControl? Source { get; }
/// <summary>
/// Получает UI-контрол, на который был выполнен сброс.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockControl"/>, представляющий цель сброса.
/// Может быть null, если сброс был выполнен вне допустимой области.
/// </value>
public IDockControl? Target { get; }
/// <summary>
/// Получает позицию сброса относительно целевого элемента.
/// </summary>
/// <value>
/// Значение перечисления <see cref="DropPosition"/>, указывающее позицию сброса.
/// </value>
public Models.DropPosition DropPosition { get; }
/// <summary>
/// Получает информацию о перетаскивании.
/// </summary>
/// <value>
/// Экземпляр <see cref="Core.DragDrop.Models.DragInfo"/> с данными завершенной операции.
/// Может быть null, если операция была отменена.
/// </value>
public Core.DragDrop.Models.DragInfo? DragInfo { get; }
/// <summary>
/// Получает значение, указывающее успешность операции сброса.
/// </summary>
/// <value>
/// true, если данные были успешно сброшены на цель; false, если операция была отменена
/// или сброс не был выполнен.
/// </value>
public bool Success { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragCompletedEventArgs"/>.
/// </summary>
/// <param name="source">Источник перетаскивания.</param>
/// <param name="target">Цель сброса.</param>
/// <param name="dropPosition">Позиция сброса.</param>
/// <param name="dragInfo">Информация о перетаскивании.</param>
/// <param name="success">Признак успешности операции.</param>
public DragCompletedEventArgs(IDockControl? source, IDockControl? target,
Models.DropPosition dropPosition, Core.DragDrop.Models.DragInfo? dragInfo, bool success)
{
Source = source;
Target = target;
DropPosition = dropPosition;
DragInfo = dragInfo;
Success = success;
}
}

View File

@@ -0,0 +1,108 @@
using Lattice.Core.Docking.Models;
namespace Lattice.UI.Docking.Abstractions;
/// <summary>
/// Определяет контракт для UI-контрола, который отображает группу разделения (DockGroup).
/// Этот контрол управляет отображением двух дочерних элементов с разделителем между ними.
/// </summary>
/// <remarks>
/// Реализации этого интерфейса должны обеспечивать возможность изменения соотношения сторон
/// через перетаскивание разделителя и корректное отображение ориентации разделения.
/// </remarks>
public interface IDockGroupControl : IDockControl
{
/// <summary>
/// Получает или задает ориентацию разделения группы.
/// </summary>
/// <value>
/// Направление разделения (горизонтальное или вертикальное).
/// </value>
SplitDirection Orientation { get; set; }
/// <summary>
/// Получает или задает соотношение разделения между первым и вторым элементами.
/// </summary>
/// <value>
/// Значение от 0.0 до 1.0, где 0.5 означает равное разделение.
/// </value>
double SplitRatio { get; set; }
/// <summary>
/// Получает или задает минимальный размер разделителя.
/// </summary>
/// <value>
/// Минимальный размер разделителя в пикселях.
/// </value>
double SplitterSize { get; set; }
/// <summary>
/// Получает контрол для первого дочернего элемента.
/// </summary>
/// <value>
/// Контрол, отображающий первый дочерний элемент.
/// </value>
IDockControl? FirstChild { get; }
/// <summary>
/// Получает контрол для второго дочернего элемента.
/// </summary>
/// <value>
/// Контрол, отображающий второй дочерний элемент.
/// </value>
IDockControl? SecondChild { get; }
/// <summary>
/// Устанавливает дочерние контролы для отображения.
/// </summary>
/// <param name="firstChild">Контрол для первого элемента.</param>
/// <param name="secondChild">Контрол для второго элемента.</param>
void SetChildren(IDockControl? firstChild, IDockControl? secondChild);
/// <summary>
/// Событие, возникающее при изменении соотношения разделения.
/// </summary>
event EventHandler<SplitRatioChangedEventArgs> SplitRatioChanged;
}
/// <summary>
/// Аргументы события изменения соотношения разделения.
/// </summary>
public class SplitRatioChangedEventArgs : EventArgs
{
/// <summary>
/// Новое соотношение разделения.
/// </summary>
public double NewRatio { get; }
/// <summary>
/// Источник изменения (пользователь или программа).
/// </summary>
public SplitRatioChangeSource Source { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="SplitRatioChangedEventArgs"/>.
/// </summary>
/// <param name="newRatio">Новое соотношение разделения.</param>
/// <param name="source">Источник изменения.</param>
public SplitRatioChangedEventArgs(double newRatio, SplitRatioChangeSource source)
{
NewRatio = newRatio;
Source = source;
}
}
/// <summary>
/// Источник изменения соотношения разделения.
/// </summary>
public enum SplitRatioChangeSource
{
/// <summary>Изменение выполнено пользователем.</summary>
User,
/// <summary>Изменение выполнено программой.</summary>
Programmatic,
/// <summary>Изменение выполнено при восстановлении состояния.</summary>
Restore
}

View File

@@ -0,0 +1,235 @@
using Lattice.Core.Docking.Abstractions;
using Lattice.Core.Docking.Models;
namespace Lattice.UI.Docking.Abstractions;
/// <summary>
/// Определяет контракт для главного контейнера док-системы, который служит корневым элементом
/// для всего пользовательского интерфейса системы докинга. Управляет размещением и состоянием
/// всех дочерних элементов, включая плавающие окна и автоскрываемые панели.
/// </summary>
/// <remarks>
/// Реализации этого интерфейса представляют собой центральный координатор UI-слоя,
/// который интегрирует функциональность менеджера макета, системы перетаскивания
/// и контекстных меню в единый визуальный компонент.
/// </remarks>
public interface IDockHost : IDockControl
{
/// <summary>
/// Получает коллекцию контролов плавающих окон, связанных с этим хостом.
/// </summary>
/// <value>
/// Коллекция объектов, реализующих <see cref="IFloatingWindowControl"/>,
/// представляющих все активные плавающие окна в системе.
/// </value>
/// <remarks>
/// Плавающие окна могут быть созданы пользователем путем перетаскивания элементов
/// за пределы основного окна или программно через методы API.
/// </remarks>
IEnumerable<IFloatingWindowControl> FloatingWindows { get; }
/// <summary>
/// Получает коллекцию контролов автоскрываемых панелей, прикрепленных к краям окна.
/// </summary>
/// <value>
/// Коллекция объектов, реализующих <see cref="IAutoHidePanelControl"/>,
/// представляющих автоскрываемые панели на разных сторонах окна.
/// </value>
/// <remarks>
/// Автоскрываемые панели скрываются, оставляя видимой только полоску-заголовок,
/// и разворачиваются при наведении курсора или клике.
/// </remarks>
IEnumerable<IAutoHidePanelControl> AutoHidePanels { get; }
/// <summary>
/// Получает или задает значение, указывающее, отображается ли панель инструментов (Toolbox).
/// </summary>
/// <value>
/// true, если панель инструментов видима; в противном случае — false.
/// Значение по умолчанию зависит от реализации.
/// </value>
/// <remarks>
/// Панель инструментов обычно содержит элементы для быстрого доступа к командам
/// или создания новых компонентов в приложении.
/// </remarks>
bool ShowToolbox { get; set; }
/// <summary>
/// Получает или задает значение, указывающее, отображается ли строка состояния.
/// </summary>
/// <value>
/// true, если строка состояния видима; в противном случае — false.
/// Значение по умолчанию зависит от реализации.
/// </value>
/// <remarks>
/// Строка состояния обычно отображает текущий статус приложения,
/// информацию о выбранном элементе или прогресс выполнения операций.
/// </remarks>
bool ShowStatusBar { get; set; }
/// <summary>
/// Получает или задает значение, указывающее, отображается ли главное меню приложения.
/// </summary>
/// <value>
/// true, если главное меню видимо; в противном случае — false.
/// Значение по умолчанию зависит от реализации.
/// </remarks>
bool ShowMenu { get; set; }
/// <summary>
/// Создает новое плавающее окно для размещения указанного элемента док-системы.
/// </summary>
/// <param name="element">
/// Элемент док-системы (группа или лист), который будет размещен в плавающем окне.
/// </param>
/// <param name="title">Заголовок создаваемого окна.</param>
/// <returns>
/// Экземпляр <see cref="IFloatingWindowControl"/>, представляющий созданное плавающее окно.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="element"/> равен null.
/// </exception>
/// <remarks>
/// Созданное окно может быть перемещено пользователем в любое место экрана,
/// изменено в размерах и обычно содержит стандартные элементы управления окном
/// (заголовок, кнопки закрытия/сворачивания).
/// </remarks>
IFloatingWindowControl CreateFloatingWindow(IDockElement element, string title);
/// <summary>
/// Закрывает указанное плавающее окно и возвращает его содержимое в основной макет.
/// </summary>
/// <param name="window">
/// Плавающее окно, которое необходимо закрыть.
/// </param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="window"/> равен null.
/// </exception>
/// <remarks>
/// При закрытии плавающего окна его содержимое обычно возвращается в то место
/// в основном макете, откуда оно было извлечено, или в ближайшую допустимую позицию.
/// </remarks>
void CloseFloatingWindow(IFloatingWindowControl window);
/// <summary>
/// Добавляет автоскрываемую панель с указанным содержимым к заданной стороне окна.
/// </summary>
/// <param name="content">
/// Контент, который будет отображаться в автоскрываемой панели.
/// </param>
/// <param name="side">
/// Сторона окна, к которой будет прикреплена панель.
/// </param>
/// <returns>
/// Экземпляр <see cref="IAutoHidePanelControl"/>, представляющий созданную панель.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="content"/> равен null.
/// </exception>
/// <remarks>
/// Автоскрываемые панели полезны для инструментов, к которым нужен частый,
/// но не постоянный доступ, так как они экономят пространство экрана.
/// </remarks>
IAutoHidePanelControl AddAutoHidePanel(Core.Docking.Abstractions.IDockContent content, DockSide side);
/// <summary>
/// Удаляет автоскрываемую панель из интерфейса.
/// </summary>
/// <param name="panel">
/// Автоскрываемая панель, которую необходимо удалить.
/// </param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="panel"/> равен null.
/// </exception>
/// <remarks>
/// После удаления панели её содержимое обычно либо закрывается полностью,
/// либо преобразуется в обычную закрепленную панель, в зависимости от настроек.
/// </remarks>
void RemoveAutoHidePanel(IAutoHidePanelControl panel);
/// <summary>
/// Событие, возникающее при изменении структуры макета док-системы.
/// </summary>
/// <remarks>
/// Может вызываться при добавлении/удалении элементов, изменении размеров,
/// создании/закрытии плавающих окон и других операциях, влияющих на компоновку.
/// </remarks>
event EventHandler LayoutChanged;
/// <summary>
/// Событие, возникающее при создании нового плавающего окна.
/// </summary>
event EventHandler<FloatingWindowCreatedEventArgs> FloatingWindowCreated;
/// <summary>
/// Событие, возникающее при закрытии плавающего окна.
/// </summary>
event EventHandler<FloatingWindowClosedEventArgs> FloatingWindowClosed;
}
/// <summary>
/// Предоставляет данные для события создания плавающего окна.
/// Содержит ссылку на созданное окно и информацию о его содержимом.
/// </summary>
public class FloatingWindowCreatedEventArgs : EventArgs
{
/// <summary>
/// Получает созданное плавающее окно.
/// </summary>
/// <value>
/// Экземпляр <see cref="IFloatingWindowControl"/>, представляющий новое окно.
/// </value>
public IFloatingWindowControl Window { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="FloatingWindowCreatedEventArgs"/>.
/// </summary>
/// <param name="window">Созданное плавающее окно.</param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="window"/> равен null.
/// </exception>
public FloatingWindowCreatedEventArgs(IFloatingWindowControl window)
{
Window = window ?? throw new ArgumentNullException(nameof(window));
}
}
/// <summary>
/// Предоставляет данные для события закрытия плавающего окна.
/// Содержит ссылку на закрываемое окно и информацию о причине закрытия.
/// </summary>
public class FloatingWindowClosedEventArgs : EventArgs
{
/// <summary>
/// Получает закрытое плавающее окно.
/// </summary>
/// <value>
/// Экземпляр <see cref="IFloatingWindowControl"/>, представляющее закрытое окно.
/// </value>
public IFloatingWindowControl Window { get; }
/// <summary>
/// Получает значение, указывающее, было ли окно закрыто пользователем.
/// </summary>
/// <value>
/// true, если окно было закрыто действием пользователя (клик на крестик);
/// false, если закрытие было инициировано программно.
/// </value>
public bool IsUserInitiated { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="FloatingWindowClosedEventArgs"/>.
/// </summary>
/// <param name="window">Закрытое плавающее окно.</param>
/// <param name="isUserInitiated">
/// Признак того, что закрытие было инициировано пользователем.
/// </param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="window"/> равен null.
/// </exception>
public FloatingWindowClosedEventArgs(IFloatingWindowControl window, bool isUserInitiated = true)
{
Window = window ?? throw new ArgumentNullException(nameof(window));
IsUserInitiated = isUserInitiated;
}
}

View File

@@ -0,0 +1,180 @@
using Lattice.Core.Docking.Abstractions;
using Lattice.Core.Docking.Models;
namespace Lattice.UI.Docking.Abstractions;
/// <summary>
/// Определяет контракт для UI-контрола, который отображает контейнер вкладок (DockLeaf).
/// Этот контрол управляет отображением коллекции вкладок с содержимым.
/// </summary>
/// <remarks>
/// Реализации этого интерфейса должны обеспечивать навигацию между вкладками,
/// закрытие вкладок, изменение порядка вкладок и поддержку различных положений
/// панели вкладок (сверху, снизу, слева, справа).
/// </remarks>
public interface IDockLeafControl : IDockControl
{
/// <summary>
/// Получает или задает положение панели вкладок.
/// </summary>
/// <value>
/// Положение панели вкладок относительно содержимого.
/// </value>
TabPlacement TabPlacement { get; set; }
/// <summary>
/// Получает или задает признак отображения кнопки закрытия на вкладках.
/// </summary>
/// <value>
/// true, если кнопки закрытия отображаются; в противном случае — false.
/// </value>
bool ShowCloseButtons { get; set; }
/// <summary>
/// Получает или задает признак возможности изменения порядка вкладок.
/// </summary>
/// <value>
/// true, если порядок вкладок можно изменять; в противном случае — false.
/// </value>
bool CanReorderTabs { get; set; }
/// <summary>
/// Получает или задает активную вкладку.
/// </summary>
/// <value>
/// Активная вкладка или null, если вкладок нет.
/// </value>
IDockContent? ActiveContent { get; set; }
/// <summary>
/// Добавляет вкладку в контрол.
/// </summary>
/// <param name="content">Контент для добавления.</param>
void AddContent(IDockContent content);
/// <summary>
/// Удаляет вкладку из контрола.
/// </summary>
/// <param name="content">Контент для удаления.</param>
void RemoveContent(IDockContent content);
/// <summary>
/// Закрывает указанную вкладку.
/// </summary>
/// <param name="content">Контент для закрытия.</param>
/// <returns>true, если вкладка была закрыта; в противном случае — false.</returns>
bool CloseContent(IDockContent content);
/// <summary>
/// Закрывает все вкладки, кроме указанной.
/// </summary>
/// <param name="exceptContent">Вкладка, которую нужно оставить открытой.</param>
void CloseAllExcept(IDockContent exceptContent);
/// <summary>
/// Закрывает все вкладки.
/// </summary>
void CloseAll();
/// <summary>
/// Событие, возникающее при изменении активной вкладки.
/// </summary>
event EventHandler<ActiveContentChangedEventArgs> ActiveContentChanged;
/// <summary>
/// Событие, возникающее при запросе закрытия вкладки.
/// </summary>
event EventHandler<ContentClosingEventArgs> ContentClosing;
/// <summary>
/// Событие, возникающее при изменении порядка вкладок.
/// </summary>
event EventHandler<TabsReorderedEventArgs> TabsReordered;
}
/// <summary>
/// Аргументы события изменения активного контента.
/// </summary>
public class ActiveContentChangedEventArgs : EventArgs
{
/// <summary>
/// Предыдущий активный контент.
/// </summary>
public IDockContent? OldContent { get; }
/// <summary>
/// Новый активный контент.
/// </summary>
public IDockContent? NewContent { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="ActiveContentChangedEventArgs"/>.
/// </summary>
public ActiveContentChangedEventArgs(IDockContent? oldContent, IDockContent? newContent)
{
OldContent = oldContent;
NewContent = newContent;
}
}
/// <summary>
/// Аргументы события закрытия контента.
/// </summary>
public class ContentClosingEventArgs : EventArgs
{
/// <summary>
/// Контент, который закрывается.
/// </summary>
public IDockContent Content { get; }
/// <summary>
/// Показывает, можно ли отменить закрытие.
/// </summary>
public bool CanCancel { get; set; }
/// <summary>
/// Получает или задает признак отмены закрытия.
/// </summary>
public bool Cancel { get; set; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="ContentClosingEventArgs"/>.
/// </summary>
public ContentClosingEventArgs(IDockContent content)
{
Content = content;
CanCancel = true;
Cancel = false;
}
}
/// <summary>
/// Аргументы события изменения порядка вкладок.
/// </summary>
public class TabsReorderedEventArgs : EventArgs
{
/// <summary>
/// Старый индекс вкладки.
/// </summary>
public int OldIndex { get; }
/// <summary>
/// Новый индекс вкладки.
/// </summary>
public int NewIndex { get; }
/// <summary>
/// Перемещаемый контент.
/// </summary>
public IDockContent Content { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="TabsReorderedEventArgs"/>.
/// </summary>
public TabsReorderedEventArgs(int oldIndex, int newIndex, IDockContent content)
{
OldIndex = oldIndex;
NewIndex = newIndex;
Content = content;
}
}

View File

@@ -0,0 +1,108 @@
namespace Lattice.UI.Docking.Abstractions;
/// <summary>
/// Определяет контракт для темы оформления док-системы.
/// </summary>
public interface IDockTheme
{
/// <summary>
/// Получает имя темы.
/// </summary>
string Name { get; }
/// <summary>
/// Получает или задает основной цвет фона.
/// </summary>
string BackgroundColor { get; set; }
/// <summary>
/// Получает или задает цвет фона панелей.
/// </summary>
string PanelBackgroundColor { get; set; }
/// <summary>
/// Получает или задает цвет фона вкладок.
/// </summary>
string TabBackgroundColor { get; set; }
/// <summary>
/// Получает или задает цвет активной вкладки.
/// </summary>
string ActiveTabBackgroundColor { get; set; }
/// <summary>
/// Получает или задает цвет границ.
/// </summary>
string BorderColor { get; set; }
/// <summary>
/// Получает или задает цвет разделителей.
/// </summary>
string SplitterColor { get; set; }
/// <summary>
/// Получает или задает цвет текста.
/// </summary>
string TextColor { get; set; }
/// <summary>
/// Получает или задает цвет акцента.
/// </summary>
string AccentColor { get; set; }
/// <summary>
/// Получает или задает радиус скругления углов.
/// </summary>
double CornerRadius { get; set; }
/// <summary>
/// Получает или задает толщину границ.
/// </summary>
double BorderThickness { get; set; }
/// <summary>
/// Получает или задает ширину разделителей.
/// </summary>
double SplitterWidth { get; set; }
/// <summary>
/// Применяет тему к системе.
/// </summary>
void Apply();
/// <summary>
/// Сбрасывает тему к значениям по умолчанию.
/// </summary>
void Reset();
}
/// <summary>
/// Аргументы события показа контекстного меню.
/// </summary>
public class ContextMenuShownEventArgs : EventArgs
{
/// <summary>
/// Элемент, для которого показано меню.
/// </summary>
public IDockControl Target { get; }
/// <summary>
/// Координата X меню.
/// </summary>
public double X { get; }
/// <summary>
/// Координата Y меню.
/// </summary>
public double Y { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="ContextMenuShownEventArgs"/>.
/// </summary>
public ContextMenuShownEventArgs(IDockControl target, double x, double y)
{
Target = target;
X = x;
Y = y;
}
}

View File

@@ -0,0 +1,114 @@
namespace Lattice.UI.Docking.Abstractions;
/// <summary>
/// Предоставляет абстракцию для платформенно-зависимых UI-операций.
/// Инкапсулирует вызовы к системным диалогам, управление окнами и
/// синхронизацию с UI-потоком.
/// </summary>
/// <remarks>
/// Этот интерфейс позволяет отделить бизнес-логику док-системы от конкретной
/// UI-платформы (WinUI, WPF, Avalonia и т.д.), обеспечивая возможность
/// кроссплатформенной разработки.
/// </remarks>
public interface IDockUIService
{
/// <summary>
/// Создает главное окно приложения для размещения док-хоста.
/// </summary>
/// <param name="host">
/// Экземпляр <see cref="IDockHost"/>, который будет содержаться в окне.
/// </param>
/// <returns>
/// Платформенно-зависимый объект окна, который можно отобразить.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="host"/> равен null.
/// </exception>
/// <remarks>
/// Реализация должна создавать окно с соответствующими стилями и поведением
/// для целевой платформы, настроенное для работы с док-системой.
/// </remarks>
object CreateMainWindow(IDockHost host);
/// <summary>
/// Отображает модальное диалоговое окно с указанным содержимым.
/// </summary>
/// <param name="title">Заголовок диалогового окна.</param>
/// <param name="content">Содержимое диалогового окна.</param>
/// <returns>
/// Nullable boolean значение, указывающее результат диалога:
/// true - пользователь подтвердил действие,
/// false - пользователь отменил действие,
/// null - диалог был закрыт без выбора.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="title"/> или <paramref name="content"/> равны null.
/// </exception>
/// <remarks>
/// Реализация должна блокировать взаимодействие с родительским окном
/// до закрытия диалога.
/// </remarks>
bool? ShowDialog(string title, object content);
/// <summary>
/// Отображает информационное сообщение с кнопкой OK.
/// </summary>
/// <param name="message">Текст сообщения.</param>
/// <param name="caption">Заголовок окна сообщения.</param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="message"/> или <paramref name="caption"/> равны null.
/// </exception>
/// <remarks>
/// Реализация должна использовать стандартные диалоги платформы
/// или создавать кастомные окна сообщений.
/// </remarks>
void ShowMessage(string message, string caption);
/// <summary>
/// Отображает диалог подтверждения с кнопками Yes/No.
/// </summary>
/// <param name="message">Текст вопроса.</param>
/// <param name="caption">Заголовок окна подтверждения.</param>
/// <returns>
/// true, если пользователь выбрал "Yes"; false, если пользователь выбрал "No".
/// </returns>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="message"/> или <paramref name="caption"/> равны null.
/// </exception>
/// <remarks>
/// Используется для получения подтверждения от пользователя перед выполнением
/// критических операций (закрытие вкладок, сброс настроек и т.д.).
/// </remarks>
bool Confirm(string message, string caption);
/// <summary>
/// Отображает диалог ввода текста.
/// </summary>
/// <param name="prompt">Текст подсказки для пользователя.</param>
/// <param name="defaultValue">Значение по умолчанию для поля ввода.</param>
/// <returns>
/// Введенный пользователем текст или null, если диалог был отменен.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="prompt"/> равен null.
/// </exception>
/// <remarks>
/// Реализация должна предоставлять однострочное поле ввода текста
/// с возможностью отмены операции.
/// </remarks>
string? Prompt(string prompt, string? defaultValue = null);
/// <summary>
/// Выполняет указанное действие в UI-потоке.
/// </summary>
/// <param name="action">Действие для выполнения.</param>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="action"/> равен null.
/// </exception>
/// <remarks>
/// Этот метод гарантирует, что действие будет выполнено в потоке,
/// связанном с пользовательским интерфейсом, что необходимо для
/// безопасного обновления UI-элементов.
/// </remarks>
void InvokeOnUIThread(Action action);
}

View File

@@ -0,0 +1,110 @@
namespace Lattice.UI.Docking.Abstractions;
/// <summary>
/// Определяет контракт для UI-контрола, который представляет плавающее окно док-системы.
/// </summary>
/// <remarks>
/// Плавающие окна могут перемещаться по экрану, изменять размер и содержать
/// любой элемент док-системы (группу или лист).
/// </remarks>
public interface IFloatingWindowControl : IDockControl
{
/// <summary>
/// Получает или задает заголовок окна.
/// </summary>
/// <value>
/// Текст заголовка окна.
/// </value>
string Title { get; set; }
/// <summary>
/// Получает или задает позицию X окна на экране.
/// </summary>
/// <value>
/// Координата X левого верхнего угла окна.
/// </value>
double Left { get; set; }
/// <summary>
/// Получает или задает позицию Y окна на экране.
/// </summary>
/// <value>
/// Координата Y левого верхнего угла окна.
/// </value>
double Top { get; set; }
/// <summary>
/// Получает или задает ширину окна.
/// </summary>
/// <value>
/// Ширина окна в пикселях.
/// </value>
double Width { get; set; }
/// <summary>
/// Получает или задает высоту окна.
/// </summary>
/// <value>
/// Высота окна в пикселях.
/// </value>
double Height { get; set; }
/// <summary>
/// Получает или задает признак того, что окно можно изменять.
/// </summary>
/// <value>
/// true, если размеры окна можно изменять; в противном случае — false.
/// </value>
bool CanResize { get; set; }
/// <summary>
/// Получает или задает признак того, что окно можно перемещать.
/// </summary>
/// <value>
/// true, если окно можно перемещать; в противном случае — false.
/// </value>
bool CanMove { get; set; }
/// <summary>
/// Получает или задает признак того, что окно всегда поверх других окон.
/// </summary>
/// <value>
/// true, если окно всегда поверх; в противном случае — false.
/// </value>
bool AlwaysOnTop { get; set; }
/// <summary>
/// Показывает окно.
/// </summary>
void Show();
/// <summary>
/// Скрывает окно.
/// </summary>
void Hide();
/// <summary>
/// Закрывает окно.
/// </summary>
void Close();
/// <summary>
/// Активирует окно (переводит фокус).
/// </summary>
void Activate();
/// <summary>
/// Событие, возникающее при закрытии окна.
/// </summary>
event EventHandler Closing;
/// <summary>
/// Событие, возникающее при изменении положения окна.
/// </summary>
event EventHandler LocationChanged;
/// <summary>
/// Событие, возникающее при изменении размера окна.
/// </summary>
event EventHandler SizeChanged;
}

View File

@@ -0,0 +1,115 @@
using Lattice.UI.Docking.Abstractions;
namespace Lattice.UI.Docking.Commands;
/// <summary>
/// Базовая реализация команды док-системы.
/// </summary>
public abstract class DockCommandBase : IDockCommand
{
private bool _canExecute = true;
/// <inheritdoc/>
public abstract string Id { get; }
/// <inheritdoc/>
public abstract string Name { get; }
/// <inheritdoc/>
public virtual string Description => string.Empty;
/// <inheritdoc/>
public virtual string Icon => string.Empty;
/// <inheritdoc/>
public virtual string Shortcut => string.Empty;
/// <summary>
/// Получает или задает признак возможности выполнения команды.
/// </summary>
public bool CanExecute
{
get => _canExecute;
set
{
if (_canExecute != value)
{
_canExecute = value;
OnCanExecuteChanged();
}
}
}
/// <inheritdoc/>
public event EventHandler? CanExecuteChanged;
/// <inheritdoc/>
public virtual bool CanExecute(object? parameter)
{
return _canExecute;
}
/// <inheritdoc/>
public abstract void Execute(object? parameter);
/// <summary>
/// Вызывает событие изменения возможности выполнения команды.
/// </summary>
protected virtual void OnCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
/// <summary>
/// Базовая команда для закрытия контента.
/// </summary>
public class CloseContentCommand : DockCommandBase
{
public override string Id => "CloseContent";
public override string Name => "Close";
public override string Description => "Close the current tab";
public override string Icon => "Close";
public override string Shortcut => "Ctrl+F4";
public override void Execute(object? parameter)
{
if (parameter is Abstractions.IDockLeafControl leafControl &&
leafControl.ActiveContent != null)
{
leafControl.CloseContent(leafControl.ActiveContent);
}
}
}
/// <summary>
/// Базовая команда для создания плавающего окна.
/// </summary>
public class FloatWindowCommand : DockCommandBase
{
public override string Id => "FloatWindow";
public override string Name => "Float";
public override string Description => "Float the window as a separate window";
public override string Icon => "Float";
public override void Execute(object? parameter)
{
// Реализация зависит от конкретного UI
}
}
/// <summary>
/// Базовая команда для закрепления окна.
/// </summary>
public class DockWindowCommand : DockCommandBase
{
public override string Id => "DockWindow";
public override string Name => "Dock";
public override string Description => "Dock the window to the main window";
public override string Icon => "Dock";
public override void Execute(object? parameter)
{
// Реализация зависит от конкретного UI
}
}

View File

@@ -0,0 +1,65 @@
using Lattice.Core.Docking.Abstractions;
using Lattice.Core.Docking.Models;
using Lattice.UI.Docking.Abstractions;
namespace Lattice.UI.Docking.Factories;
/// <summary>
/// Базовая фабрика для создания UI-контролов док-системы.
/// </summary>
public abstract class DockControlFactoryBase : IDockControlFactory
{
/// <summary>
/// Получает или задает сервис перетаскивания для создаваемых контролов.
/// </summary>
public Services.IDockDragDropService? DragDropService { get; set; }
/// <summary>
/// Получает или задает менеджер контекста для создаваемых контролов.
/// </summary>
public Services.IDockContextManager? ContextManager { get; set; }
/// <inheritdoc/>
public abstract IDockGroupControl CreateGroupControl(DockGroup group);
/// <inheritdoc/>
public abstract IDockLeafControl CreateLeafControl(DockLeaf leaf);
/// <inheritdoc/>
public abstract IFloatingWindowControl CreateFloatingWindowControl(DockWindow window);
/// <inheritdoc/>
public abstract IAutoHidePanelControl CreateAutoHidePanelControl(AutoHidePanel panel);
/// <inheritdoc/>
public abstract IDockSplitterControl CreateSplitterControl(SplitDirection orientation);
/// <summary>
/// Создает контрол для произвольного элемента док-системы.
/// </summary>
public virtual IDockControl? CreateControlForElement(IDockElement element)
{
return element switch
{
DockGroup group => CreateGroupControl(group),
DockLeaf leaf => CreateLeafControl(leaf),
_ => null
};
}
/// <summary>
/// Настраивает общие свойства контрола.
/// </summary>
protected virtual void ConfigureControl(IDockControl control)
{
if (DragDropService != null)
{
control.DragDropService = DragDropService;
}
if (ContextManager != null)
{
control.ContextManager = ContextManager;
}
}
}

View File

@@ -0,0 +1,101 @@
using Lattice.Core.Docking.Models;
using Lattice.UI.Docking.Abstractions;
namespace Lattice.UI.Docking.Factories;
/// <summary>
/// Определяет контракт для фабрики, создающей UI-контролы для элементов док-системы.
/// </summary>
public interface IDockControlFactory
{
/// <summary>
/// Создает контрол для группы разделения.
/// </summary>
/// <param name="group">Модель группы.</param>
/// <returns>Созданный контрол группы.</returns>
IDockGroupControl CreateGroupControl(DockGroup group);
/// <summary>
/// Создает контрол для контейнера вкладок.
/// </summary>
/// <param name="leaf">Модель листа.</param>
/// <returns>Созданный контрол листа.</returns>
IDockLeafControl CreateLeafControl(DockLeaf leaf);
/// <summary>
/// Создает контрол для плавающего окна.
/// </summary>
/// <param name="window">Модель окна.</param>
/// <returns>Созданный контрол окна.</returns>
IFloatingWindowControl CreateFloatingWindowControl(DockWindow window);
/// <summary>
/// Создает контрол для автоскрываемой панели.
/// </summary>
/// <param name="panel">Модель панели.</param>
/// <returns>Созданный контрол панели.</returns>
IAutoHidePanelControl CreateAutoHidePanelControl(AutoHidePanel panel);
/// <summary>
/// Создает контрол для разделителя.
/// </summary>
/// <param name="orientation">Ориентация разделителя.</param>
/// <returns>Созданный контрол разделителя.</returns>
IDockSplitterControl CreateSplitterControl(SplitDirection orientation);
}
/// <summary>
/// Определяет контракт для контрола разделителя.
/// </summary>
public interface IDockSplitterControl : IDockControl
{
/// <summary>
/// Получает или задает ориентацию разделителя.
/// </summary>
SplitDirection Orientation { get; set; }
/// <summary>
/// Получает или задает признак того, что разделитель активен.
/// </summary>
bool IsActive { get; set; }
/// <summary>
/// Событие, возникающее при начале перетаскивания разделителя.
/// </summary>
event EventHandler DragStarted;
/// <summary>
/// Событие, возникающее при перетаскивании разделителя.
/// </summary>
event EventHandler<SplitterDraggedEventArgs> DragDelta;
/// <summary>
/// Событие, возникающее при завершении перетаскивания разделителя.
/// </summary>
event EventHandler DragCompleted;
}
/// <summary>
/// Аргументы события перетаскивания разделителя.
/// </summary>
public class SplitterDraggedEventArgs : EventArgs
{
/// <summary>
/// Изменение по горизонтали.
/// </summary>
public double HorizontalChange { get; }
/// <summary>
/// Изменение по вертикали.
/// </summary>
public double VerticalChange { get; }
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="SplitterDraggedEventArgs"/>.
/// </summary>
public SplitterDraggedEventArgs(double horizontalChange, double verticalChange)
{
HorizontalChange = horizontalChange;
VerticalChange = verticalChange;
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Lattice.Core.Docking\Lattice.Core.Docking.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Converters\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,439 @@
using Lattice.Core.Docking.Abstractions;
using Lattice.Core.Docking.Engine;
using Lattice.Core.Docking.Services;
using Lattice.UI.Docking.Abstractions;
using Lattice.UI.Docking.Factories;
namespace Lattice.UI.Docking;
/// <summary>
/// Предоставляет статический API для инициализации и управления UI-фреймворком Lattice.
/// Является точкой входа для интеграции док-системы в приложение и централизованным
/// хранилищем для основных сервисов и компонентов.
/// </summary>
/// <remarks>
/// Этот класс реализует шаблон Singleton для доступа к глобальным сервисам.
/// Все компоненты инициализируются через строитель <see cref="LatticeBuilder"/>,
/// что обеспечивает гибкую конфигурацию и соблюдение принципа инверсии зависимостей.
/// </remarks>
public static class LatticeUIFramework
{
private static bool _isInitialized;
private static LatticeBuilder? _currentBuilder;
/// <summary>
/// Получает значение, указывающее, инициализирован ли фреймворк.
/// </summary>
/// <value>
/// true, если фреймворк был инициализирован вызовом <see cref="Initialize"/>;
/// в противном случае — false.
/// </value>
public static bool IsInitialized => _isInitialized;
/// <summary>
/// Получает текущий строитель конфигурации фреймворка.
/// </summary>
/// <value>
/// Экземпляр <see cref="LatticeBuilder"/>, используемый для настройки фреймворка.
/// Возвращает null, если фреймворк не инициализирован.
/// </value>
/// <exception cref="InvalidOperationException">
/// Выбрасывается при попытке доступа к свойству до инициализации фреймворка.
/// </exception>
public static LatticeBuilder CurrentBuilder
{
get
{
if (!_isInitialized)
throw new InvalidOperationException("Lattice framework is not initialized. Call Initialize() first.");
return _currentBuilder!;
}
}
/// <summary>
/// Получает менеджер макета из текущего строителя.
/// </summary>
/// <value>
/// Экземпляр <see cref="LayoutManager"/>, управляющий структурой док-системы.
/// </value>
public static LayoutManager? LayoutManager => _currentBuilder?.LayoutManager;
/// <summary>
/// Получает реестр контента из текущего строителя.
/// </summary>
/// <value>
/// Экземпляр <see cref="ContentRegistry"/>, содержащий зарегистрированные типы контента.
/// </value>
public static ContentRegistry? ContentRegistry => _currentBuilder?.ContentRegistry;
/// <summary>
/// Получает фабрику контролов из текущего строителя.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockControlFactory"/> для создания UI-контролов.
/// </value>
public static IDockControlFactory? ControlFactory => _currentBuilder?.ControlFactory;
/// <summary>
/// Получает сервис перетаскивания из текущего строителя.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockDragDropService"/> для управления операциями drag-and-drop.
/// </value>
public static IDockDragDropService? DragDropService => _currentBuilder?.DragDropService;
/// <summary>
/// Получает менеджер контекстных меню из текущего строителя.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockContextManager"/> для управления контекстными меню.
/// </value>
public static IDockContextManager? ContextManager => _currentBuilder?.ContextManager;
/// <summary>
/// Получает UI-сервис из текущего строителя.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockUIService"/> для выполнения платформенно-зависимых операций.
/// </value>
public static IDockUIService? UIService => _currentBuilder?.UIService;
/// <summary>
/// Инициализирует фреймворк Lattice с указанными параметрами.
/// </summary>
/// <param name="options">
/// Настройки инициализации. Если null, используются параметры по умолчанию.
/// </param>
/// <returns>
/// Экземпляр <see cref="LatticeBuilder"/> для дальнейшей конфигурации фреймворка.
/// </returns>
/// <exception cref="InvalidOperationException">
/// Выбрасывается, если фреймворк уже инициализирован.
/// </exception>
/// <remarks>
/// Этот метод должен вызываться один раз при запуске приложения, перед любыми
/// попытками использования компонентов док-системы.
/// </remarks>
public static LatticeBuilder Initialize(LatticeOptions? options = null)
{
if (_isInitialized)
throw new InvalidOperationException("Lattice framework is already initialized");
options ??= new LatticeOptions();
// Создаем основные компоненты Core-слоя
var layoutManager = new LayoutManager();
var contentRegistry = new ContentRegistry();
// Создаем строитель с основными компонентами
_currentBuilder = new LatticeBuilder(layoutManager, contentRegistry, options);
_isInitialized = true;
return _currentBuilder;
}
/// <summary>
/// Сбрасывает состояние фреймворка к неинициализированному.
/// </summary>
/// <remarks>
/// Используется в основном для целей тестирования. В рабочем приложении
/// фреймворк должен инициализироваться один раз на протяжении жизненного цикла.
/// </remarks>
public static void Reset()
{
_isInitialized = false;
_currentBuilder = null;
}
}
/// <summary>
/// Представляет настройки инициализации фреймворка Lattice.
/// Позволяет кастомизировать поведение системы при запуске.
/// </summary>
public class LatticeOptions
{
/// <summary>
/// Получает или задает значение, указывающее, следует ли автоматически
/// регистрировать стандартные команды (закрыть, сделать плавающим и т.д.).
/// </summary>
/// <value>
/// true, чтобы зарегистрировать стандартные команды; в противном случае — false.
/// Значение по умолчанию: true.
/// </value>
public bool RegisterDefaultCommands { get; set; } = true;
/// <summary>
/// Получает или задает значение, указывающее, следует ли автоматически
/// создавать сервисы при их первом запросе.
/// </summary>
/// <value>
/// true, чтобы автоматически создавать сервисы; в противном случае — false.
/// Значение по умолчанию: true.
/// </value>
public bool AutoCreateServices { get; set; } = true;
/// <summary>
/// Получает или задает идентификатор приложения, используемый при
/// сериализации макета для различения конфигураций разных приложений.
/// </summary>
/// <value>
/// Строковый идентификатор приложения или null, если идентификатор не задан.
/// </value>
public string? ApplicationId { get; set; }
/// <summary>
/// Получает или задает значение, указывающее, следует ли включить
/// расширенное логирование операций системы.
/// </summary>
/// <value>
/// true, чтобы включить подробное логирование; в противном случае — false.
/// Значение по умолчанию: false.
/// </value>
public bool EnableVerboseLogging { get; set; } = false;
}
/// <summary>
/// Предоставляет fluent-интерфейс для конфигурации фреймворка Lattice.
/// Инкапсулирует процесс настройки всех компонентов системы и обеспечивает
/// согласованное состояние после инициализации.
/// </summary>
public sealed class LatticeBuilder
{
private readonly LayoutManager _layoutManager;
private readonly ContentRegistry _contentRegistry;
private readonly LatticeOptions _options;
private IDockControlFactory? _controlFactory;
private IDockDragDropService? _dragDropService;
private IDockContextManager? _contextManager;
private IDockUIService? _uiService;
private bool _isBuilt;
/// <summary>
/// Получает менеджер макета, связанный с этим строителем.
/// </summary>
/// <value>
/// Экземпляр <see cref="LayoutManager"/> для управления структурой док-системы.
/// </value>
public LayoutManager LayoutManager => _layoutManager;
/// <summary>
/// Получает реестр контента, связанный с этим строителем.
/// </summary>
/// <value>
/// Экземпляр <see cref="ContentRegistry"/> для регистрации типов контента.
/// </value>
public ContentRegistry ContentRegistry => _contentRegistry;
/// <summary>
/// Получает фабрику контролов, связанную с этим строителем.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockControlFactory"/> или null, если фабрика не задана.
/// </value>
public IDockControlFactory? ControlFactory => _controlFactory;
/// <summary>
/// Получает сервис перетаскивания, связанный с этим строителем.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockDragDropService"/> или null, если сервис не задан.
/// </value>
public IDockDragDropService? DragDropService => _dragDropService;
/// <summary>
/// Получает менеджер контекстных меню, связанный с этим строителем.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockContextManager"/> или null, если менеджер не задан.
/// </value>
public IDockContextManager? ContextManager => _contextManager;
/// <summary>
/// Получает UI-сервис, связанный с этим строителем.
/// </summary>
/// <value>
/// Экземпляр <see cref="IDockUIService"/> или null, если сервис не задан.
/// </value>
public IDockUIService? UIService => _uiService;
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="LatticeBuilder"/>.
/// </summary>
/// <param name="layoutManager">Менеджер макета.</param>
/// <param name="contentRegistry">Реестр контента.</param>
/// <param name="options">Опции инициализации.</param>
internal LatticeBuilder(LayoutManager layoutManager, ContentRegistry contentRegistry, LatticeOptions options)
{
_layoutManager = layoutManager ?? throw new ArgumentNullException(nameof(layoutManager));
_contentRegistry = contentRegistry ?? throw new ArgumentNullException(nameof(contentRegistry));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
/// <summary>
/// Регистрирует фабрику контролов для создания UI-элементов.
/// </summary>
/// <param name="factory">
/// Фабрика контролов, реализующая <see cref="IDockControlFactory"/>.
/// </param>
/// <returns>
/// Текущий экземпляр строителя для цепочки вызовов.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="factory"/> равен null.
/// </exception>
public LatticeBuilder WithControlFactory(IDockControlFactory factory)
{
_controlFactory = factory ?? throw new ArgumentNullException(nameof(factory));
return this;
}
/// <summary>
/// Регистрирует сервис перетаскивания для управления операциями drag-and-drop.
/// </summary>
/// <param name="service">
/// Сервис перетаскивания, реализующий <see cref="IDockDragDropService"/>.
/// </param>
/// <returns>
/// Текущий экземпляр строителя для цепочки вызовов.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="service"/> равен null.
/// </exception>
public LatticeBuilder WithDragDropService(IDockDragDropService service)
{
_dragDropService = service ?? throw new ArgumentNullException(nameof(service));
return this;
}
/// <summary>
/// Регистрирует менеджер контекстных меню для управления контекстными действиями.
/// </summary>
/// <param name="manager">
/// Менеджер контекстных меню, реализующий <see cref="IDockContextManager"/>.
/// </param>
/// <returns>
/// Текущий экземпляр строителя для цепочки вызовов.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="manager"/> равен null.
/// </exception>
public LatticeBuilder WithContextManager(IDockContextManager manager)
{
_contextManager = manager ?? throw new ArgumentNullException(nameof(manager));
return this;
}
/// <summary>
/// Регистрирует UI-сервис для выполнения платформенно-зависимых операций.
/// </summary>
/// <param name="service">
/// UI-сервис, реализующий <see cref="IDockUIService"/>.
/// </param>
/// <returns>
/// Текущий экземпляр строителя для цепочки вызовов.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="service"/> равен null.
/// </exception>
public LatticeBuilder WithUIService(IDockUIService service)
{
_uiService = service ?? throw new ArgumentNullException(nameof(service));
return this;
}
/// <summary>
/// Регистрирует тип контента в реестре для последующего создания экземпляров.
/// </summary>
/// <typeparam name="T">
/// Тип контента, реализующий <see cref="IDockContent"/>.
/// </typeparam>
/// <param name="contentTypeId">Уникальный идентификатор типа контента.</param>
/// <param name="factory">Фабричный метод для создания экземпляров контента.</param>
/// <param name="metadata">Метаданные типа контента (опционально).</param>
/// <returns>
/// Текущий экземпляр строителя для цепочки вызовов.
/// </returns>
/// <exception cref="ArgumentNullException">
/// Выбрасывается, если <paramref name="contentTypeId"/> или <paramref name="factory"/> равны null.
/// </exception>
public LatticeBuilder RegisterContentType<T>(string contentTypeId, Func<T> factory, ContentMetadata? metadata = null)
where T : Core.Docking.Abstractions.IDockContent
{
if (string.IsNullOrWhiteSpace(contentTypeId))
throw new ArgumentNullException(nameof(contentTypeId));
_contentRegistry.Register(contentTypeId, factory, metadata);
return this;
}
/// <summary>
/// Завершает конфигурацию и создает готовый к использованию док-хост.
/// </summary>
/// <returns>
/// Экземпляр <see cref="IDockHost"/>, настроенный в соответствии с текущей конфигурацией.
/// </returns>
/// <exception cref="InvalidOperationException">
/// Выбрасывается, если не задана фабрика контролов или метод уже был вызван.
/// </exception>
public IDockHost Build()
{
if (_isBuilt)
throw new InvalidOperationException("Builder has already been built.");
if (_controlFactory == null)
throw new InvalidOperationException("Control factory must be specified. Call WithControlFactory() first.");
// Автоматически создаем отсутствующие сервисы, если включена опция
if (_options.AutoCreateServices)
{
_dragDropService ??= CreateDefaultDragDropService();
_contextManager ??= CreateDefaultContextManager();
_uiService ??= CreateDefaultUIService();
}
// Создаем хост через фабрику
// (предполагается, что фабрика имеет метод CreateDockHost)
if (_controlFactory is WinUI.Factories.WinUIDockControlFactory winUIFactory)
{
var host = winUIFactory.CreateDockHost();
// Настраиваем хост
host.LayoutManager = _layoutManager;
host.DragDropService = _dragDropService;
host.ContextManager = _contextManager;
_isBuilt = true;
return host;
}
throw new NotSupportedException($"Control factory of type {_controlFactory.GetType().Name} is not supported.");
}
/// <summary>
/// Создает сервис перетаскивания по умолчанию.
/// </summary>
private IDockDragDropService CreateDefaultDragDropService()
{
// Реализация зависит от платформы
// В реальном коде здесь должна быть проверка платформы
return new WinUI.Services.WinUIDragDropService();
}
/// <summary>
/// Создает менеджер контекстных меню по умолчанию.
/// </summary>
private IDockContextManager CreateDefaultContextManager()
{
// Реализация зависит от платформы
return new WinUI.Services.WinUIDockContextManager();
}
/// <summary>
/// Создает UI-сервис по умолчанию.
/// </summary>
private IDockUIService CreateDefaultUIService()
{
// Реализация зависит от платформы
return new WinUI.Services.WinUIDockUIService();
}
}

View File

@@ -0,0 +1,148 @@
using Lattice.Core.Geometry;
using Lattice.UI.Docking.Abstractions;
namespace Lattice.UI.Docking.Models;
/// <summary>
/// Расширенная информация о перетаскивании для UI-слоя.
/// Добавляет визуальные аспекты и UI-контекст к базовой информации о перетаскивании.
/// </summary>
public class UiDragInfo
{
/// <summary>
/// Базовые данные перетаскивания.
/// </summary>
public Core.DragDrop.Models.DragInfo BaseDragInfo { get; }
/// <summary>
/// UI-контрол, который является источником перетаскивания.
/// </summary>
public IDockControl? SourceControl { get; }
/// <summary>
/// Визуальное представление перетаскиваемого элемента.
/// </summary>
public object? DragVisual { get; set; }
/// <summary>
/// Смещение курсора относительно элемента при начале перетаскивания.
/// </summary>
public Point VisualOffset { get; set; }
/// <summary>
/// Размер визуального представления.
/// </summary>
public Size VisualSize { get; set; }
/// <summary>
/// Прозрачность визуального представления.
/// </summary>
public double VisualOpacity { get; set; } = 0.7;
/// <summary>
/// Инициализирует новый экземпляр <see cref="UiDragInfo"/>.
/// </summary>
public UiDragInfo(Core.DragDrop.Models.DragInfo baseDragInfo, IDockControl? sourceControl = null)
{
BaseDragInfo = baseDragInfo;
SourceControl = sourceControl;
}
}
/// <summary>
/// Расширенная информация о сбросе для UI-слоя.
/// Добавляет визуальные подсказки и UI-контекст.
/// </summary>
public class UiDropInfo
{
/// <summary>
/// Базовые данные сброса.
/// </summary>
public Core.DragDrop.Models.DropInfo BaseDropInfo { get; }
/// <summary>
/// UI-контрол, который является целью сброса.
/// </summary>
public IDockControl? TargetControl { get; }
/// <summary>
/// Позиция сброса относительно элемента.
/// </summary>
public DropPosition DropPosition { get; set; }
/// <summary>
/// Визуальная подсказка для области сброса.
/// </summary>
public object? DropHintVisual { get; set; }
/// <summary>
/// Признак того, что курсор находится над допустимой областью сброса.
/// </summary>
public bool IsOverValidTarget { get; set; }
/// <summary>
/// Интенсивность подсветки области сброса (0.0 - 1.0).
/// </summary>
public double HighlightIntensity { get; set; }
/// <summary>
/// Инициализирует новый экземпляр <see cref="UiDropInfo"/>.
/// </summary>
public UiDropInfo(Core.DragDrop.Models.DropInfo baseDropInfo, IDockControl? targetControl = null)
{
BaseDropInfo = baseDropInfo;
TargetControl = targetControl;
DropPosition = DropPosition.Center;
}
}
/// <summary>
/// Определяет позицию сброса относительно элемента.
/// </summary>
public enum DropPosition
{
/// <summary> Слева от элемента. </summary>
Left,
/// <summary> Справа от элемента. </summary>
Right,
/// <summary> Сверху от элемента. </summary>
Top,
/// <summary> Снизу от элемента. </summary>
Bottom,
/// <summary> В центре элемента (для объединения вкладок). </summary>
Center,
/// <summary> В виде новой вкладки. </summary>
Tab
}
public class DragStartedEventArgs : EventArgs
{
public IDockControl? Source { get; }
public Core.DragDrop.Models.DragInfo DragInfo { get; }
// ... конструктор
}
public class DragUpdatedEventArgs : EventArgs
{
public IDockControl? Source { get; }
public double X { get; }
public double Y { get; }
public Core.DragDrop.Models.DragInfo DragInfo { get; }
// ... конструктор
}
public class DragCompletedEventArgs : EventArgs
{
public IDockControl? Source { get; }
public IDockControl? Target { get; }
public Models.DropPosition DropPosition { get; }
public Core.DragDrop.Models.DragInfo? DragInfo { get; }
public bool Success { get; }
// ... конструктор
}

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;
}
}

View File

@@ -0,0 +1,93 @@
using Lattice.Core.Docking.Abstractions;
using Lattice.Core.Docking.Engine;
using Lattice.Core.Docking.Models;
using Lattice.UI.Docking.Abstractions;
using Lattice.UI.Docking.Factories;
namespace Lattice.UI.Docking.Utilities;
/// <summary>
/// Предоставляет утилитарные методы для работы с док-системой.
/// </summary>
public static class DockUtilities
{
/// <summary>
/// Создает дерево контролов для указанного менеджера макета.
/// </summary>
public static IDockControl? CreateControlTree(LayoutManager manager,
IDockControlFactory factory, IDockControl? parentControl = null)
{
if (manager == null) throw new ArgumentNullException(nameof(manager));
if (factory == null) throw new ArgumentNullException(nameof(factory));
return CreateControlForElement(manager.Root, factory, parentControl);
}
/// <summary>
/// Создает контрол для указанного элемента.
/// </summary>
public static IDockControl? CreateControlForElement(IDockElement? element,
IDockControlFactory factory, IDockControl? parentControl = null)
{
if (element == null) return null;
var control = factory.CreateControlForElement(element);
if (control == null) return null;
// Устанавливаем родительский контрол
if (parentControl != null)
{
// Здесь может быть установка дополнительных связей
}
// Рекурсивно создаем дочерние контролы
if (element is DockGroup group)
{
if (control is IDockGroupControl groupControl)
{
var firstChild = CreateControlForElement(group.First, factory, control);
var secondChild = CreateControlForElement(group.Second, factory, control);
groupControl.SetChildren(firstChild, secondChild);
}
}
return control;
}
/// <summary>
/// Находит контрол для указанного элемента в дереве контролов.
/// </summary>
public static IDockControl? FindControlForElement(IDockControl root, IDockElement element)
{
if (root == null) throw new ArgumentNullException(nameof(root));
if (element == null) throw new ArgumentNullException(nameof(element));
if (root.Model?.Id == element.Id)
return root;
if (root is IDockGroupControl groupControl)
{
var found = FindControlForElement(groupControl.FirstChild, element);
if (found != null) return found;
found = FindControlForElement(groupControl.SecondChild, element);
if (found != null) return found;
}
return null;
}
/// <summary>
/// Обновляет дерево контролов при изменении макета.
/// </summary>
public static void UpdateControlTree(IDockControl root, LayoutManager manager,
IDockControlFactory factory)
{
if (root == null) throw new ArgumentNullException(nameof(root));
if (manager == null) throw new ArgumentNullException(nameof(manager));
if (factory == null) throw new ArgumentNullException(nameof(factory));
// TODO: Реализовать эффективное обновление дерева контролов
// вместо полной перестройки
}
}