From 584df249f66f02a6294eb0a1fdb535838bc403d3 Mon Sep 17 00:00:00 2001 From: FrigaT Date: Tue, 27 Jan 2026 05:17:35 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=20Docking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Abstractions/IDockCommand.cs | 17 +- .../Abstractions/IDockContainer.cs | 28 +- .../Abstractions/IDockContent.cs | 30 +- .../Abstractions/IDockElement.cs | 82 +- .../Abstractions/IDockElementDragSource.cs | 19 - .../Abstractions/IDockElementDropTarget.cs | 19 - .../Abstractions/IDragService.cs | 76 -- Lattice.Core.Docking/Engine/DockOperations.cs | 49 +- Lattice.Core.Docking/Engine/LayoutManager.cs | 319 +++--- .../Lattice.Core.Docking.csproj | 1 - Lattice.Core.Docking/Models/AutoHidePanel.cs | 92 +- Lattice.Core.Docking/Models/DockGroup.cs | 272 +---- Lattice.Core.Docking/Models/DockLeaf.cs | 391 +------ Lattice.Core.Docking/Models/DockPosition.cs | 24 +- Lattice.Core.Docking/Models/DockSide.cs | 16 +- Lattice.Core.Docking/Models/DockWindow.cs | 55 +- Lattice.Core.Docking/Models/SplitDirection.cs | 13 +- Lattice.Core.Docking/Models/TabPlacement.cs | 15 + .../Serialization/ILayoutSerializer.cs | 15 +- .../Serialization/ISerializableLayout.cs | 13 +- .../Services/ContentRegistry.cs | 129 ++- .../DragDropServiceTests.cs | 115 --- .../Lattice.Core.DragDrop.Tests.csproj | 27 - .../Abstractions/IDragSource.cs | 84 -- .../Abstractions/IDropTarget.cs | 118 --- .../Constants/DragDropConstants.cs | 27 - .../Enums/DragDropEffects.cs | 115 --- Lattice.Core.DragDrop/Enums/DropPosition.cs | 14 - .../Exceptions/DragDropException.cs | 85 -- .../Extensions/DropInfoExtensions.cs | 74 -- .../Factories/DragDropFactory.cs | 581 ----------- .../Lattice.Core.DragDrop.csproj | 20 - Lattice.Core.DragDrop/Models/DragInfo.cs | 247 ----- Lattice.Core.DragDrop/Models/DropInfo.cs | 281 ------ Lattice.Core.DragDrop/README.md | 829 --------------- .../Services/DragDropService.cs | 846 ---------------- .../Services/EventArgs/DragEventArgs.cs | 237 ----- .../Services/IDragDropService.cs | 240 ----- Lattice.Example.DragDrop/App.xaml.cs | 2 +- .../Handlers/CustomDropHandler.cs | 46 - .../Lattice.Example.DragDrop.csproj | 5 +- Lattice.Example.DragDrop/MainWindow.xaml | 355 +++---- Lattice.Example.DragDrop/MainWindow.xaml.cs | 412 +------- .../Models/DragDropItem.cs | 72 -- .../PriorityToTextConverter.cs | 27 - Lattice.Example.DragDrop/WindowExtensions.cs | 15 - .../Controls/LatticeDockGroup.cs | 309 ++++-- .../Controls/LatticeDockHost.cs | 23 +- .../Controls/LatticeTabControl.cs | 231 ++--- .../Factories/WinUIDockControlFactory.cs | 113 ++- .../Services/WinUIDockUIService.cs | 147 ++- .../Abstractions/IAutoHidePanelControl.cs | 10 +- .../Abstractions/IDockCommand.cs | 34 +- .../Abstractions/IDockContextManager.cs | 13 +- .../Abstractions/IDockControl.cs | 66 +- .../Abstractions/IDockDragDropService.cs | 256 ----- .../Abstractions/IDockGroupControl.cs | 22 +- Lattice.UI.Docking/Abstractions/IDockHost.cs | 20 +- .../Abstractions/IDockLeafControl.cs | 68 +- .../Abstractions/IDockSplitterControl.cs | 72 ++ Lattice.UI.Docking/Abstractions/IDockTheme.cs | 16 +- .../Abstractions/IDockUIService.cs | 37 +- .../Abstractions/IFloatingWindowControl.cs | 12 +- .../Commands/DockCommandBase.cs | 279 ++++- .../Factories/DockControlFactoryBase.cs | 143 ++- .../Factories/IDockControlFactory.cs | 105 +- .../Implementations/DockControlBase.cs | 113 +++ Lattice.UI.Docking/Lattice.UI.Docking.csproj | 4 - Lattice.UI.Docking/LatticeUIFramework.cs | 293 ++---- Lattice.UI.Docking/Models/UiDragDropModels.cs | 148 --- .../Services/DockDragDropService.cs | 417 -------- .../Services/DockUIServiceBase.cs | 99 +- Lattice.UI.Docking/Utilities/DockUtilities.cs | 19 +- .../Behaviors/WinUIDragSourceBehavior.cs | 301 ------ .../Behaviors/WinUIDropTargetBehavior.cs | 514 ---------- .../Controls/DragAdorner.cs | 235 ----- .../Controls/DragDropOverlay.cs | 167 --- .../Controls/DropPreviewAdorner.cs | 151 --- .../DragDropProperties.cs | 168 ---- .../Factories/WinUIDragDropFactory.cs | 950 ------------------ .../Helpers/ResourceHelper.cs | 102 -- .../Helpers/WinUIWindowHelper.cs | 89 -- .../Lattice.UI.DragDrop.WinUI.csproj | 25 - Lattice.UI.DragDrop.WinUI/README.md | 313 ------ .../Services/WinUIDragDropHost.cs | 160 --- .../Services/WinUIDragDropManager.cs | 625 ------------ .../Services/WinUIDragVisualProvider.cs | 44 - .../Themes/DragAdorner.xaml | 48 - .../Themes/DragDropStyles.xaml | 158 --- .../Themes/DropPreviewAdorner.xaml | 56 -- Lattice.UI.DragDrop.WinUI/Themes/Generic.xaml | 23 - .../Abstractions/IDragDropHost.cs | 81 -- .../Abstractions/IDragVisualProvider.cs | 67 -- .../Abstractions/IDropVisualAdorner.cs | 59 -- .../Behaviors/DragSourceBehaviorBase.cs | 366 ------- .../Behaviors/DropTargetBehaviorBase.cs | 332 ------ .../Lattice.UI.DragDrop.csproj | 16 - Lattice.UI.DragDrop/README.md | 378 ------- Lattice.slnx | 16 +- 99 files changed, 2270 insertions(+), 12792 deletions(-) delete mode 100644 Lattice.Core.Docking/Abstractions/IDockElementDragSource.cs delete mode 100644 Lattice.Core.Docking/Abstractions/IDockElementDropTarget.cs delete mode 100644 Lattice.Core.Docking/Abstractions/IDragService.cs delete mode 100644 Lattice.Core.DragDrop.Tests/DragDropServiceTests.cs delete mode 100644 Lattice.Core.DragDrop.Tests/Lattice.Core.DragDrop.Tests.csproj delete mode 100644 Lattice.Core.DragDrop/Abstractions/IDragSource.cs delete mode 100644 Lattice.Core.DragDrop/Abstractions/IDropTarget.cs delete mode 100644 Lattice.Core.DragDrop/Constants/DragDropConstants.cs delete mode 100644 Lattice.Core.DragDrop/Enums/DragDropEffects.cs delete mode 100644 Lattice.Core.DragDrop/Enums/DropPosition.cs delete mode 100644 Lattice.Core.DragDrop/Exceptions/DragDropException.cs delete mode 100644 Lattice.Core.DragDrop/Extensions/DropInfoExtensions.cs delete mode 100644 Lattice.Core.DragDrop/Factories/DragDropFactory.cs delete mode 100644 Lattice.Core.DragDrop/Lattice.Core.DragDrop.csproj delete mode 100644 Lattice.Core.DragDrop/Models/DragInfo.cs delete mode 100644 Lattice.Core.DragDrop/Models/DropInfo.cs delete mode 100644 Lattice.Core.DragDrop/README.md delete mode 100644 Lattice.Core.DragDrop/Services/DragDropService.cs delete mode 100644 Lattice.Core.DragDrop/Services/EventArgs/DragEventArgs.cs delete mode 100644 Lattice.Core.DragDrop/Services/IDragDropService.cs delete mode 100644 Lattice.Example.DragDrop/Handlers/CustomDropHandler.cs delete mode 100644 Lattice.Example.DragDrop/Models/DragDropItem.cs delete mode 100644 Lattice.Example.DragDrop/PriorityToTextConverter.cs delete mode 100644 Lattice.Example.DragDrop/WindowExtensions.cs delete mode 100644 Lattice.UI.Docking/Abstractions/IDockDragDropService.cs create mode 100644 Lattice.UI.Docking/Abstractions/IDockSplitterControl.cs create mode 100644 Lattice.UI.Docking/Implementations/DockControlBase.cs delete mode 100644 Lattice.UI.Docking/Models/UiDragDropModels.cs delete mode 100644 Lattice.UI.Docking/Services/DockDragDropService.cs delete mode 100644 Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs delete mode 100644 Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs delete mode 100644 Lattice.UI.DragDrop.WinUI/Controls/DragAdorner.cs delete mode 100644 Lattice.UI.DragDrop.WinUI/Controls/DragDropOverlay.cs delete mode 100644 Lattice.UI.DragDrop.WinUI/Controls/DropPreviewAdorner.cs delete mode 100644 Lattice.UI.DragDrop.WinUI/DragDropProperties.cs delete mode 100644 Lattice.UI.DragDrop.WinUI/Factories/WinUIDragDropFactory.cs delete mode 100644 Lattice.UI.DragDrop.WinUI/Helpers/ResourceHelper.cs delete mode 100644 Lattice.UI.DragDrop.WinUI/Helpers/WinUIWindowHelper.cs delete mode 100644 Lattice.UI.DragDrop.WinUI/Lattice.UI.DragDrop.WinUI.csproj delete mode 100644 Lattice.UI.DragDrop.WinUI/README.md delete mode 100644 Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropHost.cs delete mode 100644 Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropManager.cs delete mode 100644 Lattice.UI.DragDrop.WinUI/Services/WinUIDragVisualProvider.cs delete mode 100644 Lattice.UI.DragDrop.WinUI/Themes/DragAdorner.xaml delete mode 100644 Lattice.UI.DragDrop.WinUI/Themes/DragDropStyles.xaml delete mode 100644 Lattice.UI.DragDrop.WinUI/Themes/DropPreviewAdorner.xaml delete mode 100644 Lattice.UI.DragDrop.WinUI/Themes/Generic.xaml delete mode 100644 Lattice.UI.DragDrop/Abstractions/IDragDropHost.cs delete mode 100644 Lattice.UI.DragDrop/Abstractions/IDragVisualProvider.cs delete mode 100644 Lattice.UI.DragDrop/Abstractions/IDropVisualAdorner.cs delete mode 100644 Lattice.UI.DragDrop/Behaviors/DragSourceBehaviorBase.cs delete mode 100644 Lattice.UI.DragDrop/Behaviors/DropTargetBehaviorBase.cs delete mode 100644 Lattice.UI.DragDrop/Lattice.UI.DragDrop.csproj delete mode 100644 Lattice.UI.DragDrop/README.md diff --git a/Lattice.Core.Docking/Abstractions/IDockCommand.cs b/Lattice.Core.Docking/Abstractions/IDockCommand.cs index e7b064f..bcbfaf2 100644 --- a/Lattice.Core.Docking/Abstractions/IDockCommand.cs +++ b/Lattice.Core.Docking/Abstractions/IDockCommand.cs @@ -1,8 +1,23 @@ namespace Lattice.Core.Docking.Abstractions; +/// +/// Определяет контракт для команды в системе докинга. +/// Команды представляют действия, которые могут быть выполнены над элементами док-системы. +/// public interface IDockCommand : System.Windows.Input.ICommand { + /// + /// Получает отображаемое имя команды. + /// string Name { get; } + + /// + /// Получает идентификатор ресурса для иконки команды. + /// string Icon { get; } + + /// + /// Получает текстовое представление жеста (горячей клавиши) для команды. + /// string GestureText { get; } -} +} \ No newline at end of file diff --git a/Lattice.Core.Docking/Abstractions/IDockContainer.cs b/Lattice.Core.Docking/Abstractions/IDockContainer.cs index 404924d..803d73a 100644 --- a/Lattice.Core.Docking/Abstractions/IDockContainer.cs +++ b/Lattice.Core.Docking/Abstractions/IDockContainer.cs @@ -3,22 +3,36 @@ namespace Lattice.Core.Docking.Abstractions; /// -/// Интерфейс для элементов (листьев дерева), которые физически содержат внутри себя коллекцию вкладок. +/// Определяет контракт для контейнеров, содержащих коллекцию вкладок. +/// Контейнеры являются листьями дерева компоновки и непосредственно отображают содержимое. /// public interface IDockContainer : IDockElement { - /// Список вкладок, находящихся в данном контейнере. + /// + /// Получает список вкладок, находящихся в данном контейнере. + /// IList Children { get; } - /// Ссылка на текущую выбранную и отображаемую вкладку. + /// + /// Получает или задает текущую активную (выбранную) вкладку. + /// IDockContent? ActiveContent { get; set; } - /// Добавляет контент в контейнер и делает его активным. + /// + /// Добавляет контент в контейнер и делает его активным. + /// + /// Контент для добавления. void AddContent(IDockContent content); - /// Удаляет контент. Если Children становится пустым, контейнер может быть удален из дерева макета. + /// + /// Удаляет контент из контейнера. Если коллекция становится пустой, + /// контейнер может быть удален из дерева макета. + /// + /// Контент для удаления. void RemoveContent(IDockContent content); - /// Положение вкладок в интерфейсе. + /// + /// Получает или задает положение панели вкладок в интерфейсе. + /// TabPlacement TabPlacement { get; set; } -} +} \ No newline at end of file diff --git a/Lattice.Core.Docking/Abstractions/IDockContent.cs b/Lattice.Core.Docking/Abstractions/IDockContent.cs index cd719d5..35a2446 100644 --- a/Lattice.Core.Docking/Abstractions/IDockContent.cs +++ b/Lattice.Core.Docking/Abstractions/IDockContent.cs @@ -1,25 +1,37 @@ namespace Lattice.Core.Docking.Abstractions; /// -/// Описывает объект содержимого (вкладку), который может быть размещен внутри IDockContainer. +/// Определяет контракт для содержимого (вкладки), которое может быть размещено внутри контейнера. /// public interface IDockContent { - /// Уникальный идентификатор контента (например, путь к файлу или ID инструмента). + /// + /// Получает уникальный идентификатор контента. + /// Используется для идентификации вкладки в системе. + /// string Id { get; } - /// Заголовок, отображаемый пользователю в интерфейсе (на вкладке). + /// + /// Получает заголовок, отображаемый пользователю на вкладке. + /// string Title { get; } - /// - /// Сам визуальный элемент (например, Microsoft.UI.Xaml.UIElement). - /// Lattice просто отображает этот объект в теле вкладки. + /// + /// Получает или задает визуальный элемент для отображения в теле вкладки. /// object View { get; set; } - /// Флаг, определяющий доступность кнопки закрытия для пользователя. + /// + /// Получает значение, указывающее, можно ли закрыть вкладку. + /// bool CanClose { get; } - /// Вызывается системой при попытке закрытия контента. Возвращает true, если закрытие разрешено. + /// + /// Вызывается системой при попытке закрытия контента. + /// Позволяет выполнить дополнительные проверки или сохранить состояние. + /// + /// + /// true, если закрытие разрешено; в противном случае false. + /// bool OnClosing(); -} +} \ No newline at end of file diff --git a/Lattice.Core.Docking/Abstractions/IDockElement.cs b/Lattice.Core.Docking/Abstractions/IDockElement.cs index 9131a85..39b49aa 100644 --- a/Lattice.Core.Docking/Abstractions/IDockElement.cs +++ b/Lattice.Core.Docking/Abstractions/IDockElement.cs @@ -1,25 +1,91 @@ namespace Lattice.Core.Docking.Abstractions; /// -/// Базовый интерфейс для любого элемента, который может быть частью дерева компоновки Lattice. +/// Базовый интерфейс для любого элемента, являющегося частью дерева компоновки. +/// Определяет общие свойства и методы для всех элементов док-системы. /// +/// +/// Элементы док-системы образуют древовидную структуру, где каждый элемент может иметь +/// родителя и дочерние элементы. Эта иерархия используется для организации пространства +/// главного окна и плавающих окон в IDE-подобных приложениях. +/// public interface IDockElement { - /// Уникальный идентификатор элемента. + /// + /// Получает уникальный идентификатор элемента. + /// Используется для поиска элементов, сериализации состояния и отслеживания изменений. + /// + /// + /// Строковый идентификатор, гарантированно уникальный в пределах дерева компоновки. + /// Обычно представляет собой GUID в строковом формате. + /// string Id { get; } - /// Родительский элемент в иерархии. Если null — элемент является корневым. + /// + /// Получает или задает родительский элемент в иерархии дерева компоновки. + /// + /// + /// Родительский элемент или null, если элемент является корневым. + /// Это свойство управляется системой компоновки при добавлении или удалении элементов. + /// + /// + /// Изменение этого свойства вручную может привести к нарушению целостности дерева. + /// Для манипуляции структурой дерева следует использовать методы . + /// IDockElement? Parent { get; set; } - /// Желаемая ширина элемента в относительных или абсолютных единицах. + /// + /// Получает или задает желаемую ширину элемента. + /// + /// + /// Ширина элемента в пикселях или относительных единицах. + /// Может быть выражена как абсолютное значение (в пикселях) или как пропорция + /// (например, 0.5 для 50% доступного пространства). + /// + /// + /// Фактическая ширина элемента определяется родительским контейнером с учетом + /// минимальных размеров и соотношений разделения. + /// double Width { get; set; } - /// Желаемая высота элемента в относительных или абсолютных единицах. + /// + /// Получает или задает желаемую высоту элемента. + /// + /// + /// Высота элемента в пикселях или относительных единицах. + /// Может быть выражена как абсолютное значение (в пикселях) или как пропорция. + /// + /// + /// Фактическая высота элемента определяется родительским контейнером с учетом + /// минимальных размеров и соотношений разделения. + /// double Height { get; set; } - /// Минимально допустимая ширина, при которой элемент сохраняет функциональность. + /// + /// Получает минимально допустимую ширину элемента. + /// + /// + /// Минимальная ширина элемента в пикселях, при которой элемент сохраняет + /// базовую функциональность и читаемость содержимого. + /// + /// + /// Система компоновки не позволит уменьшить элемент ниже этого значения. + /// Для групп разделения минимальная ширина вычисляется рекурсивно на основе + /// минимальных размеров дочерних элементов. + /// double MinWidth { get; } - /// Минимально допустимая высота, при которой элемент сохраняет функциональность. + /// + /// Получает минимально допустимую высоту элемента. + /// + /// + /// Минимальная высота элемента в пикселях, при которой элемент сохраняет + /// базовую функциональность и читаемость содержимого. + /// + /// + /// Система компоновки не позволит уменьшить элемент ниже этого значения. + /// Для групп разделения минимальная высота вычисляется рекурсивно на основе + /// минимальных размеров дочерних элементов. + /// double MinHeight { get; } -} +} \ No newline at end of file diff --git a/Lattice.Core.Docking/Abstractions/IDockElementDragSource.cs b/Lattice.Core.Docking/Abstractions/IDockElementDragSource.cs deleted file mode 100644 index 42ac214..0000000 --- a/Lattice.Core.Docking/Abstractions/IDockElementDragSource.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Lattice.Core.DragDrop.Abstractions; - -namespace Lattice.Core.Docking.Abstractions; - -/// -/// Расширяет интерфейс элемента док-системы для поддержки операций перетаскивания. -/// -public interface IDockElementDragSource : IDockElement, IDragSource -{ - /// - /// Получает или устанавливает признак того, что элемент можно перетаскивать. - /// - bool CanDrag { get; set; } - - /// - /// Получает тип данных для перетаскивания этого элемента. - /// - string DragDataType { get; } -} diff --git a/Lattice.Core.Docking/Abstractions/IDockElementDropTarget.cs b/Lattice.Core.Docking/Abstractions/IDockElementDropTarget.cs deleted file mode 100644 index 642a810..0000000 --- a/Lattice.Core.Docking/Abstractions/IDockElementDropTarget.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Lattice.Core.DragDrop.Abstractions; - -namespace Lattice.Core.Docking.Abstractions; - -/// -/// Расширяет интерфейс элемента док-системы для возможности быть целью сброса. -/// -public interface IDockElementDropTarget : IDockElement, IDropTarget -{ - /// - /// Получает или устанавливает признак того, что элемент может принимать сброс. - /// - bool CanDrop { get; set; } - - /// - /// Получает типы данных, которые может принимать элемент. - /// - IEnumerable AcceptableDropTypes { get; } -} \ No newline at end of file diff --git a/Lattice.Core.Docking/Abstractions/IDragService.cs b/Lattice.Core.Docking/Abstractions/IDragService.cs deleted file mode 100644 index 1fbc4bc..0000000 --- a/Lattice.Core.Docking/Abstractions/IDragService.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Lattice.Core.Docking.Models; -using Lattice.Core.Geometry; - -namespace Lattice.Core.Docking.Abstractions; - -/// -/// Предоставляет абстракцию для операции перетаскивания в док-системе. -/// Эта абстракция позволяет отделить логику перетаскивания от конкретной UI-платформы. -/// -public interface IDragService -{ - /// - /// Начинает операцию перетаскивания указанного элемента. - /// - /// Элемент для перетаскивания. - /// Визуальная обратная связь (зависит от платформы). - void StartDrag(IDockElement element, object? visualFeedback = null); - - /// - /// Обновляет позицию перетаскивания. - /// - /// Координата X. - /// Координата Y. - void UpdateDrag(double x, double y); - - /// - /// Завершает операцию перетаскивания. - /// - /// Координата X завершения. - /// Координата Y завершения. - void EndDrag(double x, double y); - - /// - /// Отменяет операцию перетаскивания. - /// - void CancelDrag(); -} - -/// -/// Представляет область для сброса при операции перетаскивания. -/// -public class DropArea -{ - /// - /// Целевой элемент для сброса. - /// - public IDockElement Target { get; set; } - - /// - /// Позиция сброса относительно цели. - /// - public DockPosition Position { get; set; } - - /// - /// Границы области в экранных координатах. - /// - public Rect Bounds { get; set; } - - /// - /// Видимость области (для анимации). - /// - public double Visibility { get; set; } = 0.0; - - /// - /// Инициализирует новый экземпляр области сброса. - /// - /// Целевой элемент. - /// Позиция сброса. - /// Границы области. - public DropArea(IDockElement target, DockPosition position, Rect bounds) - { - Target = target; - Position = position; - Bounds = bounds; - } -} \ No newline at end of file diff --git a/Lattice.Core.Docking/Engine/DockOperations.cs b/Lattice.Core.Docking/Engine/DockOperations.cs index 4c6a5c5..b3c5bf6 100644 --- a/Lattice.Core.Docking/Engine/DockOperations.cs +++ b/Lattice.Core.Docking/Engine/DockOperations.cs @@ -4,20 +4,30 @@ using Lattice.Core.Docking.Models; namespace Lattice.Core.Docking.Engine; /// -/// Статический движок для манипуляции иерархией дерева компоновки. -/// Содержит чистые алгоритмы трансформации графа. +/// Предоставляет статические методы для манипуляции иерархией дерева компоновки. +/// Содержит чистые алгоритмы трансформации графа без зависимости от UI. /// public static class DockOperations { /// - /// Извлекает элемент из дерева. Если родительская группа остается с одним ребенком, - /// она удаляется, а ребенок занимает её место. + /// Извлекает элемент из дерева компоновки. + /// Если родительская группа остается с одним ребенком, она удаляется, + /// а оставшийся ребенок занимает её место в иерархии. /// - /// Элемент для удаления. - /// Текущий корень дерева. - /// Новый корень дерева после оптимизации. + /// Элемент для удаления из дерева. + /// Текущий корневой элемент дерева. + /// + /// Новый корневой элемент дерева после удаления и оптимизации структуры. + /// Возвращает null, если дерево становится пустым. + /// + /// + /// Выбрасывается, когда равен null. + /// public static IDockElement? Remove(IDockElement element, IDockElement root) { + if (element == null) throw new ArgumentNullException(nameof(element)); + if (root == null) throw new ArgumentNullException(nameof(root)); + if (element == root) return null; var parent = element.Parent as DockGroup; @@ -43,15 +53,36 @@ public static class DockOperations } /// - /// Вставляет элемент в дерево, создавая новую группу разделения или объединяя контент. + /// Вставляет элемент в дерево компоновки относительно целевого элемента. + /// Создает новую группу разделения или объединяет контент в зависимости от позиции. /// - public static IDockElement Insert(IDockElement target, IDockElement source, DockPosition pos, IDockElement root) + /// Целевой элемент, относительно которого выполняется вставка. + /// Вставляемый элемент. + /// Позиция вставки относительно целевого элемента. + /// Текущий корневой элемент дерева. + /// + /// Новый корневой элемент дерева после вставки и оптимизации структуры. + /// + /// + /// Выбрасывается, когда , + /// или равны null. + /// + /// + /// Выбрасывается, когда имеет недопустимое значение. + /// + public static IDockElement Insert(IDockElement target, IDockElement source, + DockPosition pos, IDockElement root) { + if (target == null) throw new ArgumentNullException(nameof(target)); + if (source == null) throw new ArgumentNullException(nameof(source)); + if (root == null) throw new ArgumentNullException(nameof(root)); + // Случай 1: Объединение вкладок в центре if (pos == DockPosition.Center) { if (target is IDockContainer targetContainer && source is IDockContainer sourceContainer) { + // Переносим все вкладки из источника в целевой контейнер var items = new List(sourceContainer.Children); foreach (var item in items) { diff --git a/Lattice.Core.Docking/Engine/LayoutManager.cs b/Lattice.Core.Docking/Engine/LayoutManager.cs index f652021..64c33d7 100644 --- a/Lattice.Core.Docking/Engine/LayoutManager.cs +++ b/Lattice.Core.Docking/Engine/LayoutManager.cs @@ -8,54 +8,87 @@ using System.Runtime.CompilerServices; namespace Lattice.Core.Docking.Engine; /// -/// Расширенный менеджер макета, поддерживающий автоскрываемые панели, группы документов -/// и расширенные операции управления макетом. +/// Центральный менеджер макета, управляющий всей структурой док-системы. +/// Координирует дерево компоновки, плавающие окна, автоскрываемые панели +/// и предоставляет API для манипуляции макетом. /// /// -/// Этот класс является центральным координатором всей док-системы, управляя деревом компоновки, -/// плавающими окнами, автоскрываемыми панелями и предоставляя API для манипуляции макетом. +/// Этот класс является основным координатором док-системы. Он управляет: +/// +/// Деревом компоновки главного окна +/// Коллекцией плавающих окон +/// Коллекцией автоскрываемых панелей +/// Реестром типов контента +/// +/// Все изменения в структуре макета должны выполняться через методы этого класса. /// public class LayoutManager { private readonly ObservableCollection _autoHidePanels = new(); + private IDockElement? _root; /// - /// Корневой элемент главного окна IDE. + /// Получает или задает корневой элемент дерева компоновки главного окна. /// - public IDockElement? Root { get; internal set; } + /// + /// Корневой элемент или null, если макет пуст. + /// + /// + /// При изменении этого свойства генерируется событие . + /// + public IDockElement? Root + { + get => _root; + internal set + { + if (_root != value) + { + _root = value; + LayoutUpdated?.Invoke(); + } + } + } /// - /// Список активных плавающих окон. + /// Получает список активных плавающих окон. /// + /// + /// Коллекция объектов , представляющих плавающие окна. + /// public List FloatingWindows { get; } = new(); /// - /// Коллекция автоскрываемых панелей. + /// Получает коллекцию автоскрываемых панелей. /// + /// + /// Доступная только для чтения коллекция объектов . + /// public ReadOnlyObservableCollection AutoHidePanels { get; } /// - /// Реестр типов контента (опционально). + /// Получает или задает реестр типов контента. /// + /// + /// Реестр типов контента или null, если реестр не установлен. + /// public Services.ContentRegistry? ContentRegistry { get; set; } /// - /// Уведомляет UI, что структура дерева изменилась. + /// Происходит при изменении структуры дерева компоновки. /// + /// + /// Событие генерируется при любых изменениях в дереве компоновки, + /// включая добавление, удаление или перемещение элементов. + /// public event Action? LayoutUpdated; /// - /// Уведомляет об изменении в коллекции автоскрываемых панелей. + /// Происходит при изменении коллекции автоскрываемых панелей. /// public event EventHandler? AutoHidePanelsChanged; /// - /// Событие, возникающее при операции перетаскивания элемента. - /// - public event EventHandler? DragDropOperation; - - /// - /// Инициализирует новый экземпляр менеджера макета. + /// Инициализирует новый экземпляр класса . /// public LayoutManager() { @@ -63,13 +96,20 @@ public class LayoutManager } /// - /// Добавляет автоскрываемую панель. + /// Добавляет автоскрываемую панель с указанным содержимым к заданной стороне окна. /// /// Содержимое панели. - /// Сторона для прикрепления. - /// Созданная автоскрываемая панель. + /// Сторона окна для прикрепления панели. + /// + /// Созданная автоскрываемая панель. + /// + /// + /// Выбрасывается, когда равен null. + /// public AutoHidePanel AddAutoHidePanel(IDockContent content, DockSide side) { + if (content == null) throw new ArgumentNullException(nameof(content)); + var panel = new AutoHidePanel(content, side); _autoHidePanels.Add(panel); AutoHidePanelsChanged?.Invoke(this, EventArgs.Empty); @@ -77,23 +117,36 @@ public class LayoutManager } /// - /// Удаляет автоскрываемую панель. + /// Удаляет автоскрываемую панель из коллекции. /// /// Панель для удаления. - public void RemoveAutoHidePanel(AutoHidePanel panel) + /// + /// true, если панель была успешно удалена; в противном случае false. + /// + /// + /// Выбрасывается, когда равен null. + /// + public bool RemoveAutoHidePanel(AutoHidePanel panel) { + if (panel == null) throw new ArgumentNullException(nameof(panel)); + if (_autoHidePanels.Remove(panel)) { AutoHidePanelsChanged?.Invoke(this, EventArgs.Empty); + return true; } + return false; } /// - /// Создает документ из зарегистрированного типа контента. + /// Создает документ указанного типа контента с заданным идентификатором. /// /// Идентификатор типа контента. /// Уникальный идентификатор документа. - /// Созданный контент или null, если ContentRegistry не установлен. + /// + /// Созданный контент или null, если ContentRegistry не установлен + /// или тип контента не зарегистрирован. + /// public IDockContent? CreateDocument(string contentTypeId, string id) { if (ContentRegistry == null || !ContentRegistry.IsRegistered(contentTypeId)) @@ -103,16 +156,31 @@ public class LayoutManager } /// - /// Основной метод перемещения элементов в макете. + /// Выполняет перемещение элемента в макете относительно целевого элемента. /// - /// Что перетаскиваем. - /// Куда приземляем. - /// Позиция относительно цели. + /// Перемещаемый элемент. + /// Целевой элемент, относительно которого выполняется перемещение. + /// Позиция перемещения относительно цели. /// /// Если true, контент будет добавлен как документ в центральную область. + /// В текущей реализации этот параметр не используется. /// - public void Move(IDockElement source, IDockElement? target, DockPosition position, bool asDocument = false) + /// + /// Выбрасывается, когда равен null. + /// + /// + /// Метод выполняет следующие шаги: + /// + /// Удаляет источник из текущего местоположения + /// Вставляет источник в новое местоположение относительно цели + /// Обновляет структуру дерева компоновки + /// + /// Если равен null, элемент помещается в новое плавающее окно. + /// + public void Move(IDockElement source, IDockElement? target, + DockPosition position, bool asDocument = false) { + if (source == null) throw new ArgumentNullException(nameof(source)); if (source == target) return; // 1. Удаляем источник из текущего местоположения @@ -145,13 +213,14 @@ public class LayoutManager // 2. Вставляем в цель if (target == null) { - FloatingWindows.Add(new DockWindow { Root = source as IDockElement }); + // Создаем новое плавающее окно + FloatingWindows.Add(new DockWindow { Root = source }); } else { - if (IsDescendantOf(target, Root)) + if (Root != null && IsDescendantOf(target, Root)) { - Root = DockOperations.Insert(target, source, position, Root!); + Root = DockOperations.Insert(target, source, position, Root); } else { @@ -162,6 +231,13 @@ public class LayoutManager LayoutUpdated?.Invoke(); } + /// + /// Удаляет элемент из всех плавающих окон. + /// + /// Элемент для удаления. + /// + /// true, если элемент был найден и удален; в противном случае false. + /// private bool RemoveFromFloatingWindows(IDockElement element) { foreach (var win in FloatingWindows.ToArray()) @@ -177,7 +253,14 @@ public class LayoutManager return false; } - private void InsertIntoFloatingWindow(IDockElement target, IDockElement source, DockPosition position) + /// + /// Вставляет элемент в плавающее окно, содержащее целевой элемент. + /// + /// Целевой элемент в плавающем окне. + /// Вставляемый элемент. + /// Позиция вставки. + private void InsertIntoFloatingWindow(IDockElement target, IDockElement source, + DockPosition position) { foreach (var win in FloatingWindows) { @@ -189,6 +272,14 @@ public class LayoutManager } } + /// + /// Определяет, является ли элемент потомком указанного предка. + /// + /// Проверяемый элемент. + /// Предполагаемый предок. + /// + /// true, если элемент является потомком предка; в противном случае false. + /// private bool IsDescendantOf(IDockElement element, IDockElement ancestor) { if (element == ancestor) return true; @@ -197,9 +288,17 @@ public class LayoutManager return false; } - /// Поиск элемента по ID во всех окнах. + /// + /// Находит элемент по его идентификатору во всех окнах (главном и плавающих). + /// + /// Идентификатор элемента для поиска. + /// + /// Найденный элемент или null, если элемент с таким идентификатором не найден. + /// public IDockElement? FindById(string id) { + if (string.IsNullOrEmpty(id)) return null; + var found = FindRecursive(Root, id); if (found != null) return found; @@ -211,16 +310,37 @@ public class LayoutManager return null; } + /// + /// Рекурсивно ищет элемент по идентификатору в поддереве. + /// + /// Корневой узел поддерева для поиска. + /// Идентификатор элемента для поиска. + /// + /// Найденный элемент или null, если элемент не найден. + /// private IDockElement? FindRecursive(IDockElement? node, string id) { - if (node == null || node.Id == id) return node; - if (node is DockGroup g) return FindRecursive(g.First, id) ?? FindRecursive(g.Second, id); + if (node == null) return null; + if (node.Id == id) return node; + + if (node is DockGroup g) + return FindRecursive(g.First, id) ?? FindRecursive(g.Second, id); + return null; } /// /// Сбрасывает макет к состоянию по умолчанию. /// + /// + /// Метод выполняет следующие действия: + /// + /// Очищает корневой элемент + /// Закрывает все плавающие окна + /// Удаляет все автоскрываемые панели + /// Генерирует соответствующие события + /// + /// public void Reset() { Root = null; @@ -231,86 +351,12 @@ public class LayoutManager } /// - /// Обрабатывает операцию перетаскивания между элементами. - /// - /// Источник перетаскивания. - /// Цель сброса. - /// Позиция сброса относительно цели. - /// Данные перетаскивания. - /// true, если операция успешно выполнена; иначе false. - public bool HandleDragDrop(IDockElement source, IDockElement target, - DockPosition position, object data) - { - try - { - if (source == target) - return false; - - // Определяем тип операции на основе данных - if (data is ContentDragData contentData) - { - return HandleContentDragDrop(contentData, target, position); - } - else if (data is DockElementDragData elementData) - { - return HandleElementDragDrop(elementData, target, position); - } - - return false; - } - catch (Exception ex) - { - DragDropOperation?.Invoke(this, new DragDropEventArgs( - source, target, position, false, ex.Message)); - return false; - } - } - - private bool HandleContentDragDrop(ContentDragData data, IDockElement target, DockPosition position) - { - // Находим исходный контейнер с контентом - var sourceContainer = FindElementById(data.ElementId) as IDockContainer; - if (sourceContainer == null) - return false; - - // Находим контент - var content = sourceContainer.Children.FirstOrDefault(c => c.Id == data.ContentId); - if (content == null) - return false; - - if (target is IDockContainer targetContainer && position == DockPosition.Center) - { - // Объединение вкладок - sourceContainer.RemoveContent(content); - targetContainer.AddContent(content); - - DragDropOperation?.Invoke(this, new DragDropEventArgs( - sourceContainer as IDockElement ?? sourceContainer as IDockElement, - target, position, true, "Content merged")); - return true; - } - - return false; - } - - private bool HandleElementDragDrop(DockElementDragData data, IDockElement target, DockPosition position) - { - // Находим перетаскиваемый элемент - var sourceElement = FindElementById(data.ElementId); - if (sourceElement == null) - return false; - - // Выполняем перемещение - Move(sourceElement, target, position); - - DragDropOperation?.Invoke(this, new DragDropEventArgs( - sourceElement, target, position, true, "Element moved")); - return true; - } - - /// - /// Находит элемент по идентификатору. + /// Находит элемент по идентификатору в дереве компоновки. /// + /// Идентификатор элемента для поиска. + /// + /// Найденный элемент или null, если элемент с таким идентификатором не найден. + /// public IDockElement? FindElementById(string id) { return FindElementByIdRecursive(Root, id) ?? @@ -318,6 +364,14 @@ public class LayoutManager .FirstOrDefault(result => result != null); } + /// + /// Рекурсивно ищет элемент по идентификатору в поддереве. + /// + /// Корневой элемент поддерева для поиска. + /// Идентификатор элемента для поиска. + /// + /// Найденный элемент или null, если элемент не найден. + /// private IDockElement? FindElementByIdRecursive(IDockElement? element, string id) { if (element == null) return null; @@ -331,39 +385,4 @@ public class LayoutManager return null; } -} - -/// -/// Аргументы события операции перетаскивания. -/// -public class DragDropEventArgs : EventArgs -{ - /// Источник перетаскивания. - public IDockElement Source { get; } - - /// Цель сброса. - public IDockElement Target { get; } - - /// Позиция сброса. - public DockPosition Position { get; } - - /// Показывает, была ли операция успешной. - public bool Success { get; } - - /// Сообщение о результате операции. - public string Message { get; } - - /// Время выполнения операции. - public DateTime Timestamp { get; } - - public DragDropEventArgs(IDockElement source, IDockElement target, - DockPosition position, bool success, string message) - { - Source = source; - Target = target; - Position = position; - Success = success; - Message = message; - Timestamp = DateTime.UtcNow; - } } \ No newline at end of file diff --git a/Lattice.Core.Docking/Lattice.Core.Docking.csproj b/Lattice.Core.Docking/Lattice.Core.Docking.csproj index a05bde6..a571f74 100644 --- a/Lattice.Core.Docking/Lattice.Core.Docking.csproj +++ b/Lattice.Core.Docking/Lattice.Core.Docking.csproj @@ -8,6 +8,5 @@ - diff --git a/Lattice.Core.Docking/Models/AutoHidePanel.cs b/Lattice.Core.Docking/Models/AutoHidePanel.cs index 03df5ed..fe2a0d3 100644 --- a/Lattice.Core.Docking/Models/AutoHidePanel.cs +++ b/Lattice.Core.Docking/Models/AutoHidePanel.cs @@ -5,44 +5,81 @@ namespace Lattice.Core.Docking.Models; /// /// Представляет автоскрываемую панель, которая может быть прикреплена к одной из сторон окна. -/// Автоскрываемые панели скрываются, оставляя только заголовок, и появляются при наведении курсора. +/// Автоскрываемые панели скрываются, оставляя видимой только полоску-заголовок, +/// и разворачиваются при наведении курсора или клике. /// /// -/// Автоскрываемые панели являются ключевым элементом интерфейса современных IDE, +/// Автоскрываемые панели являются важным элементом современных IDE-подобных приложений, /// позволяя экономить пространство экрана при сохранении быстрого доступа к инструментам. /// public class AutoHidePanel : INotifyPropertyChanged { + /// + /// Происходит при изменении значения свойства. + /// public event PropertyChangedEventHandler? PropertyChanged; - protected void OnPropertyChanged([CallerMemberName] string? name = null) => - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); private bool _isVisible = false; private double _slideOffset = 0; /// - /// Уникальный идентификатор автоскрываемой панели. + /// Получает уникальный идентификатор автоскрываемой панели. /// + /// + /// Строковый идентификатор, сгенерированный с помощью GUID. + /// public string Id { get; } = Guid.NewGuid().ToString(); /// - /// Содержимое панели. + /// Получает или задает содержимое панели. /// - public Abstractions.IDockContent Content { get; set; } + /// + /// Объект, реализующий . + /// + /// + /// Выбрасывается при попытке установить значение null. + /// + public Abstractions.IDockContent Content + { + get => _content; + set + { + if (_content != value) + { + _content = value ?? throw new ArgumentNullException(nameof(value)); + OnPropertyChanged(); + OnPropertyChanged(nameof(Title)); + } + } + } + private Abstractions.IDockContent _content; /// - /// Сторона окна, к которой прикреплена панель. + /// Получает или задает сторону окна, к которой прикреплена панель. /// + /// + /// Значение перечисления , указывающее сторону прикрепления. + /// public DockSide Side { get; set; } /// - /// Ширина панели (для левой/правой сторон) или высота (для верхней/нижней сторон). + /// Получает или задает ширину панели (для левой/правой сторон) + /// или высоту (для верхней/нижней сторон). /// + /// + /// Размер панели в пикселях. Значение по умолчанию: 300. + /// public double Size { get; set; } = 300; /// - /// Признак видимости панели. + /// Получает или задает признак видимости панели. /// + /// + /// true, если панель развернута и видима; в противном случае false. + /// + /// + /// При изменении этого свойства генерируется событие . + /// public bool IsVisible { get => _isVisible; @@ -57,8 +94,14 @@ public class AutoHidePanel : INotifyPropertyChanged } /// - /// Смещение для анимации выезда/заезда панели (0-1). + /// Получает или задает смещение для анимации выезда/заезда панели. /// + /// + /// Значение от 0.0 до 1.0, где 0.0 - полностью скрыта, 1.0 - полностью развернута. + /// + /// + /// Используется для плавной анимации отображения/скрытия панели. + /// public double SlideOffset { get => _slideOffset; @@ -73,15 +116,22 @@ public class AutoHidePanel : INotifyPropertyChanged } /// - /// Заголовок панели (обычно берется из содержимого). + /// Получает заголовок панели. /// + /// + /// Заголовок, взятый из содержимого панели. + /// Если содержимое не установлено, возвращает "Auto-hide Panel". + /// public string Title => Content?.Title ?? "Auto-hide Panel"; /// - /// Инициализирует новый экземпляр автоскрываемой панели. + /// Инициализирует новый экземпляр класса . /// /// Содержимое панели. /// Сторона окна для прикрепления. + /// + /// Выбрасывается, когда равен null. + /// public AutoHidePanel(Abstractions.IDockContent content, DockSide side) { Content = content ?? throw new ArgumentNullException(nameof(content)); @@ -91,6 +141,9 @@ public class AutoHidePanel : INotifyPropertyChanged /// /// Переключает видимость панели. /// + /// + /// Если панель была видимой, становится скрытой, и наоборот. + /// public void Toggle() { IsVisible = !IsVisible; @@ -111,4 +164,15 @@ public class AutoHidePanel : INotifyPropertyChanged { IsVisible = false; } -} + + /// + /// Вызывает событие . + /// + /// + /// Имя изменившегося свойства. Если не указано, определяется автоматически. + /// + protected void OnPropertyChanged([CallerMemberName] string? name = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } +} \ No newline at end of file diff --git a/Lattice.Core.Docking/Models/DockGroup.cs b/Lattice.Core.Docking/Models/DockGroup.cs index 08bc6ff..c6745cd 100644 --- a/Lattice.Core.Docking/Models/DockGroup.cs +++ b/Lattice.Core.Docking/Models/DockGroup.cs @@ -1,8 +1,4 @@ using Lattice.Core.Docking.Abstractions; -using Lattice.Core.DragDrop.Abstractions; -using Lattice.Core.DragDrop.Enums; -using Lattice.Core.DragDrop.Models; -using Lattice.Core.Geometry; using System.ComponentModel; using System.Runtime.CompilerServices; @@ -14,32 +10,25 @@ namespace Lattice.Core.Docking.Models; /// элементом для создания сложных макетов с разделителями. /// /// -/// -/// реализует как (для -/// возможности перетаскивания всей группы), так и -/// (для возможности сброса на группу), что делает его полностью интегрированным -/// в систему перетаскивания док-системы. -/// -/// -/// Каждая группа содержит два дочерних элемента ( и -/// ), которые могут быть либо другими группами (для -/// создания вложенной структуры), либо листами () -/// с контентом. Направление разделения определяется свойством -/// . -/// +/// Каждая группа содержит два дочерних элемента ( и ), +/// которые могут быть либо другими группами (для создания вложенной структуры), +/// либо листами () с контентом. +/// Направление разделения определяется свойством . /// -public class DockGroup : IDockElement, IDragSource, IDropTarget, INotifyPropertyChanged +public class DockGroup : IDockElement, INotifyPropertyChanged { /// - /// Событие, возникающее при изменении значения свойства. + /// Происходит при изменении значения свойства. /// public event PropertyChangedEventHandler? PropertyChanged; private double _splitRatio = 0.5; private string _id; + private IDockElement _first; + private IDockElement _second; /// - /// Получает уникальный идентификатор группы. + /// Получает или задает уникальный идентификатор группы. /// /// /// Строковый идентификатор, уникальный в пределах дерева компоновки. @@ -86,7 +75,19 @@ public class DockGroup : IDockElement, IDragSource, IDropTarget, INotifyProperty /// При установке нового значения автоматически обновляется свойство /// у дочернего элемента. /// - public IDockElement First { get; set; } + public IDockElement First + { + get => _first; + set + { + if (_first != value) + { + _first = value ?? throw new ArgumentNullException(nameof(value)); + _first.Parent = this; + OnPropertyChanged(); + } + } + } /// /// Получает или задает второй дочерний элемент (правую или нижнюю область). @@ -101,7 +102,19 @@ public class DockGroup : IDockElement, IDragSource, IDropTarget, INotifyProperty /// При установке нового значения автоматически обновляется свойство /// у дочернего элемента. /// - public IDockElement Second { get; set; } + public IDockElement Second + { + get => _second; + set + { + if (_second != value) + { + _second = value ?? throw new ArgumentNullException(nameof(value)); + _second.Parent = this; + OnPropertyChanged(); + } + } + } /// /// Получает или задает направление разделения данной группы. @@ -111,12 +124,10 @@ public class DockGroup : IDockElement, IDragSource, IDropTarget, INotifyProperty /// как разделена область: горизонтально или вертикально. /// /// - /// - /// создает левую и правую области. - /// - /// - /// создает верхнюю и нижнюю области. - /// + /// + /// создает левую и правую области + /// создает верхнюю и нижнюю области + /// /// public SplitDirection Orientation { get; set; } @@ -218,7 +229,8 @@ public class DockGroup : IDockElement, IDragSource, IDropTarget, INotifyProperty /// у дочерних элементов на текущую группу и генерирует уникальный идентификатор, /// если он не был предоставлен. /// - public DockGroup(IDockElement first, IDockElement second, SplitDirection orientation, string? id = null) + public DockGroup(IDockElement first, IDockElement second, + SplitDirection orientation, string? id = null) { First = first ?? throw new ArgumentNullException(nameof(first)); Second = second ?? throw new ArgumentNullException(nameof(second)); @@ -239,206 +251,4 @@ public class DockGroup : IDockElement, IDragSource, IDropTarget, INotifyProperty { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } - - #region Реализация IDragSource - - /// - /// Определяет, может ли группа начать операцию перетаскивания. - /// - /// - /// При успешном возврате содержит информацию о перетаскивании; - /// в противном случае — null. - /// - /// - /// true, если группа может начать перетаскивание; в противном случае — false. - /// - /// - /// - /// Группа может быть перетащена только если она не является корневым - /// элементом дерева (имеет родителя). - /// - /// - /// При успешной проверке метод заполняет - /// данными типа . - /// - /// - public bool CanStartDrag(out DragInfo? dragInfo) - { - dragInfo = null; - - // DockGroup можно перетаскивать только если он не является корневым элементом - if (Parent == null) - return false; - - // Создаем данные для перетаскивания - var data = new DockElementDragData - { - ElementId = Id, - ElementType = GetType().Name, - IsGroup = true - }; - - dragInfo = new DragInfo(data, DragDropEffects.Move, Point.Zero, this); - return true; - } - - /// - /// Начинает операцию перетаскивания для группы. - /// - /// - /// Информация о перетаскивании, полученная из . - /// - /// - /// Всегда возвращает true, так как группа не требует специальной подготовки - /// для начала перетаскивания. - /// - /// - /// Для этот метод не выполняет дополнительных действий, - /// так как все необходимые данные уже содержатся в . - /// - public bool StartDrag(DragInfo dragInfo) - { - // DockGroup не требует дополнительной подготовки для перетаскивания - return true; - } - - /// - /// Вызывается при завершении операции перетаскивания. - /// - /// - /// Исходная информация о перетаскивании. - /// - /// - /// Эффекты, которые были применены при сбросе. - /// - /// - /// - /// Этот метод вызывается после того, как операция перетаскивания была - /// завершена (успешно или неуспешно). - /// - /// - /// Для этот метод не выполняет действий, так как - /// все изменения в структуре дерева уже обработаны . - /// - /// - public void DragCompleted(DragInfo dragInfo, DragDropEffects effects) - { - // Если группа была перемещена, ничего не делаем - LayoutManager уже обработал изменение - } - - /// - /// Вызывается при отмене операции перетаскивания. - /// - /// - /// Исходная информация о перетаскивании. - /// - /// - /// Для отмена перетаскивания не требует специальных - /// действий, так как структура дерева не была изменена. - /// - public void DragCancelled(DragInfo dragInfo) - { - // Отмена перетаскивания не требует действий - } - - #endregion - - #region Реализация IDropTarget - - /// - /// Определяет, может ли группа принять сбрасываемые данные. - /// - /// - /// Информация о потенциальном сбросе. - /// - /// - /// true, если группа может принять данные; в противном случае — false. - /// - /// - /// - /// Группа может принимать только данные типа - /// для элементов док-системы ( или ). - /// - /// - /// Группа не может принять сброс самой себя (проверяется по идентификатору). - /// - /// - public bool CanAcceptDrop(DropInfo dropInfo) - { - if (dropInfo.Data is not DockElementDragData dragData) - return false; - - // Нельзя сбросить элемент на самого себя - if (dragData.ElementId == Id) - return false; - - // Можно принимать только элементы док-системы - return dragData.ElementType == nameof(DockGroup) || dragData.ElementType == nameof(DockLeaf); - } - - /// - /// Вызывается, когда перетаскиваемый объект находится над группой. - /// - /// - /// Информация о текущем положении перетаскивания. - /// - /// - /// - /// Этот метод вызывается постоянно, пока пользователь перемещает объект - /// над целью. Для группы он устанавливает предлагаемые эффекты в - /// . - /// - /// - /// Если группа может принять сброс, предлагается эффект перемещения; - /// в противном случае эффекты не предлагаются. - /// - /// - public void DragOver(DropInfo dropInfo) - { - if (CanAcceptDrop(dropInfo)) - { - dropInfo.SuggestedEffects = DragDropEffects.Move; - } - else - { - dropInfo.SuggestedEffects = DragDropEffects.None; - } - } - - /// - /// Вызывается, когда пользователь сбрасывает данные на группу. - /// - /// - /// Информация о сбросе. - /// - /// - /// - /// Для обработка сброса делегируется - /// , поэтому метод просто помечает операцию - /// как обработанную. - /// - /// - /// Фактическое изменение структуры дерева выполняется менеджером макета - /// на основе данных из . - /// - /// - public void Drop(DropInfo dropInfo) - { - // Обработка сброса делегируется LayoutManager - dropInfo.MarkAsHandled(); - } - - /// - /// Вызывается, когда перетаскиваемый объект покидает область группы. - /// - /// - /// Для группы этот метод не выполняет действий, так как очистка визуальной - /// обратной связи выполняется в UI-слое. - /// - public void DragLeave() - { - // Очистка визуальной обратной связи (будет выполнена в UI слое) - } - - #endregion } \ No newline at end of file diff --git a/Lattice.Core.Docking/Models/DockLeaf.cs b/Lattice.Core.Docking/Models/DockLeaf.cs index f8a7537..793c79e 100644 --- a/Lattice.Core.Docking/Models/DockLeaf.cs +++ b/Lattice.Core.Docking/Models/DockLeaf.cs @@ -1,8 +1,4 @@ using Lattice.Core.Docking.Abstractions; -using Lattice.Core.DragDrop.Abstractions; -using Lattice.Core.DragDrop.Enums; -using Lattice.Core.DragDrop.Models; -using Lattice.Core.Geometry; using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; @@ -15,34 +11,24 @@ namespace Lattice.Core.Docking.Models; /// отображаемого пользователю содержимого. /// /// -/// -/// реализует интерфейсы , -/// и , что позволяет ему: -/// -/// -/// Управлять коллекцией вкладок -/// Быть источником перетаскивания (как всего листа, так и отдельных вкладок) -/// Принимать сброс других элементов или вкладок -/// -/// /// Лист является основным элементом, с которым взаимодействует пользователь /// при работе с документами или инструментальными панелями в IDE-подобных /// приложениях. -/// /// -public class DockLeaf : IDockContainer, INotifyPropertyChanged, IDragSource, IDropTarget +public class DockLeaf : IDockContainer, INotifyPropertyChanged { /// - /// Событие, возникающее при изменении значения свойства. + /// Происходит при изменении значения свойства. /// public event PropertyChangedEventHandler? PropertyChanged; private readonly ObservableCollection _items = new(); private IDockContent? _activeContent; private string _id; + private TabPlacement _tabPlacement = TabPlacement.Bottom; /// - /// Получает уникальный идентификатор листа. + /// Получает или задает уникальный идентификатор листа. /// /// /// Строковый идентификатор, уникальный в пределах дерева компоновки. @@ -88,13 +74,9 @@ public class DockLeaf : IDockContainer, INotifyPropertyChanged, IDragSource, IDr /// Активная вкладка или null, если в контейнере нет вкладок. /// /// - /// /// При установке нового значения проверяется, что вкладка действительно /// содержится в коллекции . - /// - /// /// Изменение этого свойства вызывает событие . - /// /// public IDockContent? ActiveContent { @@ -152,7 +134,18 @@ public class DockLeaf : IDockContainer, INotifyPropertyChanged, IDragSource, IDr /// /// Поддерживаются все четыре стороны: верх, низ, лево, право. /// - public TabPlacement TabPlacement { get; set; } = TabPlacement.Bottom; + public TabPlacement TabPlacement + { + get => _tabPlacement; + set + { + if (_tabPlacement != value) + { + _tabPlacement = value; + OnPropertyChanged(); + } + } + } /// /// Инициализирует новый экземпляр класса . @@ -187,17 +180,15 @@ public class DockLeaf : IDockContainer, INotifyPropertyChanged, IDragSource, IDr /// Контент для добавления. /// /// - /// /// Если контент уже содержится в коллекции, он не добавляется повторно, /// но становится активным. - /// - /// /// Этот метод обновляет свойство и вызывает /// соответствующее событие изменения свойства. - /// /// public void AddContent(IDockContent content) { + if (content == null) return; + if (!_items.Contains(content)) { _items.Add(content); @@ -212,18 +203,16 @@ public class DockLeaf : IDockContainer, INotifyPropertyChanged, IDragSource, IDr /// Контент для удаления. /// /// - /// /// Если удаляемый контент является активным, автоматически выбирается /// новая активная вкладка (следующая в списке или предыдущая, если удалена /// последняя). - /// - /// /// Если после удаления контейнер становится пустым, он может быть удален /// из дерева макета системой компоновки. - /// /// public void RemoveContent(IDockContent content) { + if (content == null) return; + int index = _items.IndexOf(content); if (index == -1) return; @@ -237,344 +226,4 @@ public class DockLeaf : IDockContainer, INotifyPropertyChanged, IDragSource, IDr ActiveContent = null; } } - - #region Реализация IDragSource - - /// - /// Определяет, может ли лист начать операцию перетаскивания. - /// - /// - /// При успешном возврате содержит информацию о перетаскивании; - /// в противном случае — null. - /// - /// - /// true, если лист может начать перетаскивание; в противном случае — false. - /// - /// - /// - /// Лист может быть перетащен, если: - /// - /// - /// Он имеет родителя (не является корневым) - /// Или имеет хотя бы одну вкладку (не пустой) - /// - /// - /// В зависимости от наличия активного контента создаются разные данные: - /// - /// - /// - /// Если есть активный контент - создается - /// для перетаскивания конкретной вкладки - /// - /// - /// Если нет активного контента - создается - /// для перетаскивания всего листа - /// - /// - /// - public bool CanStartDrag(out DragInfo? dragInfo) - { - dragInfo = null; - - // DockLeaf можно перетаскивать - if (Parent == null && Children.Count == 0) - return false; // Не перетаскиваем пустые корневые листья - - object data; - - // Если есть активный контент, перетаскиваем контент, иначе перетаскиваем весь лист - if (ActiveContent != null) - { - data = new ContentDragData - { - ElementId = Id, - ContentId = ActiveContent.Id, - ContentTitle = ActiveContent.Title, - ContentType = ActiveContent.GetType().Name - }; - } - else - { - data = new DockElementDragData - { - ElementId = Id, - ElementType = GetType().Name, - IsGroup = false, - Width = Width, - Height = Height - }; - } - - dragInfo = new DragInfo(data, DragDropEffects.Move | DragDropEffects.Copy, Point.Zero, this); - return true; - } - - /// - /// Начинает операцию перетаскивания для листа. - /// - /// - /// Информация о перетаскивании. - /// - /// - /// Всегда возвращает true. - /// - /// - /// Для этот метод не выполняет дополнительных действий. - /// - public bool StartDrag(DragInfo dragInfo) - { - // DockLeaf не требует дополнительной подготовки - return true; - } - - /// - /// Вызывается при завершении операции перетаскивания. - /// - /// - /// Исходная информация о перетаскивании. - /// - /// - /// Эффекты, которые были применены при сбросе. - /// - /// - /// Для этот метод не выполняет действий. - /// - public void DragCompleted(DragInfo dragInfo, DragDropEffects effects) - { - // Если лист был перемещен или скопирован, LayoutManager уже обработал это - } - - /// - /// Вызывается при отмене операции перетаскивания. - /// - /// - /// Исходная информация о перетаскивании. - /// - /// - /// Для отмена перетаскивания не требует действий. - /// - public void DragCancelled(DragInfo dragInfo) - { - // Отмена не требует действий - } - - #endregion - - #region Реализация IDropTarget - - /// - /// Определяет, может ли лист принять сбрасываемые данные. - /// - /// - /// Информация о потенциальном сбросе. - /// - /// - /// true, если лист может принять данные; в противном случае — false. - /// - /// - /// Лист может принимать: - /// - /// - /// для других листов и групп - /// (для объединения или разделения) - /// - /// - /// для вкладок (для объединения вкладок) - /// - /// - /// - public bool CanAcceptDrop(DropInfo dropInfo) - { - if (dropInfo.Data is DockElementDragData elementData) - { - // Можно принимать другие листы и группы - return elementData.ElementType == nameof(DockLeaf) || - elementData.ElementType == nameof(DockGroup); - } - else if (dropInfo.Data is ContentDragData contentData) - { - // Можно принимать контент для объединения вкладок - return true; - } - - return false; - } - - /// - /// Вызывается, когда перетаскиваемый объект находится над листом. - /// - /// - /// Информация о текущем положении перетаскивания. - /// - /// - /// - /// В зависимости от типа данных устанавливаются разные предлагаемые эффекты: - /// - /// - /// - /// Для - эффект копирования (объединение вкладок) - /// - /// - /// Для - эффект перемещения - /// - /// - /// - public void DragOver(DropInfo dropInfo) - { - if (CanAcceptDrop(dropInfo)) - { - if (dropInfo.Data is ContentDragData) - { - // Для контента предлагаем копирование (объединение вкладок) - dropInfo.SuggestedEffects = DragDropEffects.Copy; - } - else - { - // Для элементов предлагаем перемещение - dropInfo.SuggestedEffects = DragDropEffects.Move; - } - } - else - { - dropInfo.SuggestedEffects = DragDropEffects.None; - } - } - - /// - /// Вызывается, когда пользователь сбрасывает данные на лист. - /// - /// - /// Информация о сбросе. - /// - /// - /// Обработка сброса делегируется . - /// - public void Drop(DropInfo dropInfo) - { - // Обработка делегируется LayoutManager - dropInfo.MarkAsHandled(); - } - - /// - /// Вызывается, когда перетаскиваемый объект покидает область листа. - /// - /// - /// Очистка визуальной обратной связи выполняется в UI-слое. - /// - public void DragLeave() - { - // Очистка визуальной обратной связи - } - - #endregion -} - -/// -/// Представляет данные для перетаскивания элементов док-системы (групп или листов). -/// Используется при перетаскивании целых структурных элементов дерева компоновки. -/// -/// -/// Этот класс сериализуется и передается между компонентами системы перетаскивания -/// для идентификации перетаскиваемого элемента и его свойств. -/// -public class DockElementDragData -{ - /// - /// Получает или задает уникальный идентификатор элемента. - /// - /// - /// Идентификатор элемента, соответствующий свойству . - /// - public string ElementId { get; set; } = string.Empty; - - /// - /// Получает или задает тип элемента. - /// - /// - /// Имя типа элемента (обычно "DockGroup" или "DockLeaf"). - /// - public string ElementType { get; set; } = string.Empty; - - /// - /// Получает или задает значение, указывающее, является ли элемент группой. - /// - /// - /// true, если элемент является ; false, если . - /// - public bool IsGroup { get; set; } - - /// - /// Получает или задает идентификатор родительского элемента. - /// - /// - /// Идентификатор родительского элемента или null, если элемент корневой. - /// - public string? ParentId { get; set; } - - /// - /// Получает или задает ширину элемента. - /// - /// - /// Текущая ширина элемента в пикселях. - /// - public double Width { get; set; } - - /// - /// Получает или задает высоту элемента. - /// - /// - /// Текущая высота элемента в пикселях. - /// - public double Height { get; set; } -} - -/// -/// Представляет данные для перетаскивания контента (вкладок). -/// Используется при перетаскивании отдельных вкладок между контейнерами. -/// -/// -/// Этот класс позволяет идентифицировать конкретную вкладку для операций -/// объединения или перемещения между контейнерами. -/// -public class ContentDragData -{ - /// - /// Получает или задает идентификатор контейнера (листа), содержащего контент. - /// - /// - /// Идентификатор , в котором находится перетаскиваемая вкладка. - /// - public string ElementId { get; set; } = string.Empty; - - /// - /// Получает или задает уникальный идентификатор контента. - /// - /// - /// Идентификатор контента, соответствующий свойству . - /// - public string ContentId { get; set; } = string.Empty; - - /// - /// Получает или задает заголовок контента. - /// - /// - /// Текст, отображаемый на вкладке. - /// - public string ContentTitle { get; set; } = string.Empty; - - /// - /// Получает или задает тип контента. - /// - /// - /// Имя типа контента (например, "TextEditor", "Toolbox", и т.д.). - /// - public string ContentType { get; set; } = string.Empty; - - /// - /// Получает или задает значение, указывающее, можно ли закрыть контент. - /// - /// - /// true, если контент можно закрыть; в противном случае — false. - /// - public bool CanClose { get; set; } = true; } \ No newline at end of file diff --git a/Lattice.Core.Docking/Models/DockPosition.cs b/Lattice.Core.Docking/Models/DockPosition.cs index f34ea72..1c705aa 100644 --- a/Lattice.Core.Docking/Models/DockPosition.cs +++ b/Lattice.Core.Docking/Models/DockPosition.cs @@ -1,13 +1,33 @@ namespace Lattice.Core.Docking.Models; /// -/// Определяет позицию вставки при операции Drag-and-Drop. +/// Определяет позицию вставки элемента относительно целевого элемента. +/// Используется при операциях перемещения и вставки элементов в дерево компоновки. /// public enum DockPosition { + /// + /// Слева от целевого элемента. + /// Left, + + /// + /// Справа от целевого элемента. + /// Right, + + /// + /// Сверху от целевого элемента. + /// Top, + + /// + /// Снизу от целевого элемента. + /// Bottom, + + /// + /// В центре целевого элемента (для объединения вкладок). + /// Center, -} +} \ No newline at end of file diff --git a/Lattice.Core.Docking/Models/DockSide.cs b/Lattice.Core.Docking/Models/DockSide.cs index 6975ee6..4fb714c 100644 --- a/Lattice.Core.Docking/Models/DockSide.cs +++ b/Lattice.Core.Docking/Models/DockSide.cs @@ -5,15 +5,23 @@ /// public enum DockSide { - /// Левая сторона окна. + /// + /// Левая сторона окна. + /// Left, - /// Правая сторона окна. + /// + /// Правая сторона окна. + /// Right, - /// Верхняя сторона окна. + /// + /// Верхняя сторона окна. + /// Top, - /// Нижняя сторона окна. + /// + /// Нижняя сторона окна. + /// Bottom } \ No newline at end of file diff --git a/Lattice.Core.Docking/Models/DockWindow.cs b/Lattice.Core.Docking/Models/DockWindow.cs index cb73925..408f3f9 100644 --- a/Lattice.Core.Docking/Models/DockWindow.cs +++ b/Lattice.Core.Docking/Models/DockWindow.cs @@ -3,21 +3,66 @@ namespace Lattice.Core.Docking.Models; /// -/// Описывает состояние плавающего окна в системе Lattice. +/// Представляет плавающее окно в системе докинга. +/// Плавающие окна могут перемещаться по экрану независимо от главного окна. /// public class DockWindow { - /// Уникальный ID окна для сохранения его позиции в конфиге. + /// + /// Получает уникальный идентификатор окна. + /// + /// + /// Строковый идентификатор, сгенерированный с помощью GUID. + /// Используется для сохранения позиции и размера окна в конфигурации. + /// public string Id { get; } = Guid.NewGuid().ToString(); - /// Корневой элемент макета внутри данного окна. + /// + /// Получает или задает корневой элемент макета внутри данного окна. + /// + /// + /// Корневой элемент дерева компоновки плавающего окна. + /// public IDockElement? Root { get; set; } + /// + /// Получает или задает позицию X окна на экране. + /// + /// + /// Координата X левого верхнего угла окна в пикселях. + /// public double X { get; set; } + + /// + /// Получает или задает позицию Y окна на экране. + /// + /// + /// Координата Y левого верхнего угла окна в пикселях. + /// public double Y { get; set; } + + /// + /// Получает или задает ширину окна. + /// + /// + /// Ширина окна в пикселях. Значение по умолчанию: 800. + /// public double Width { get; set; } = 800; + + /// + /// Получает или задает высоту окна. + /// + /// + /// Высота окна в пикселях. Значение по умолчанию: 600. + /// public double Height { get; set; } = 600; - /// Заголовок окна (обычно берется из активного контента). + /// + /// Получает или задает заголовок окна. + /// + /// + /// Текст заголовка окна. Обычно берется из активного контента. + /// Значение по умолчанию: "Lattice Tool Window". + /// public string Title { get; set; } = "Lattice Tool Window"; -} +} \ No newline at end of file diff --git a/Lattice.Core.Docking/Models/SplitDirection.cs b/Lattice.Core.Docking/Models/SplitDirection.cs index faee1bf..c80f524 100644 --- a/Lattice.Core.Docking/Models/SplitDirection.cs +++ b/Lattice.Core.Docking/Models/SplitDirection.cs @@ -1,12 +1,17 @@ namespace Lattice.Core.Docking.Models; /// -/// Перечисление направлений разделения пространства внутри группы. +/// Определяет направление разделения пространства внутри группы. /// public enum SplitDirection { - /// Разделение по горизонтали (создает левую и правую области). + /// + /// Разделение по горизонтали (создает левую и правую области). + /// Horizontal, - /// Разделение по вертикали (создает верхнюю и нижнюю области). + + /// + /// Разделение по вертикали (создает верхнюю и нижнюю области). + /// Vertical -} +} \ No newline at end of file diff --git a/Lattice.Core.Docking/Models/TabPlacement.cs b/Lattice.Core.Docking/Models/TabPlacement.cs index 5dbd7d4..fe962d5 100644 --- a/Lattice.Core.Docking/Models/TabPlacement.cs +++ b/Lattice.Core.Docking/Models/TabPlacement.cs @@ -5,8 +5,23 @@ /// public enum TabPlacement { + /// + /// Вкладки располагаются сверху. + /// Top, + + /// + /// Вкладки располагаются снизу. + /// Bottom, + + /// + /// Вкладки располагаются слева. + /// Left, + + /// + /// Вкладки располагаются справа. + /// Right, } \ No newline at end of file diff --git a/Lattice.Core.Docking/Serialization/ILayoutSerializer.cs b/Lattice.Core.Docking/Serialization/ILayoutSerializer.cs index a4b6246..496602e 100644 --- a/Lattice.Core.Docking/Serialization/ILayoutSerializer.cs +++ b/Lattice.Core.Docking/Serialization/ILayoutSerializer.cs @@ -1,7 +1,7 @@ namespace Lattice.Core.Docking.Serialization; /// -/// Абстракция для сериализации и десериализации состояния макета док-системы. +/// Определяет контракт для сериализации и десериализации состояния макета док-системы. /// Позволяет сохранять и восстанавливать расположение панелей, окон и их состояние. /// /// @@ -14,7 +14,12 @@ public interface ILayoutSerializer /// Сериализует состояние менеджера макета в строку. /// /// Менеджер макета для сериализации. - /// Строковое представление состояния макета. + /// + /// Строковое представление состояния макета. + /// + /// + /// Выбрасывается, когда равен null. + /// string Serialize(Engine.LayoutManager manager); /// @@ -26,6 +31,10 @@ public interface ILayoutSerializer /// Функция разрешения контента по идентификатору, используемая для восстановления /// ссылок на контент в десериализованном состоянии. /// + /// + /// Выбрасывается, когда или + /// равны null. + /// void Deserialize(Engine.LayoutManager manager, string serializedLayout, Func contentResolver); -} +} \ No newline at end of file diff --git a/Lattice.Core.Docking/Serialization/ISerializableLayout.cs b/Lattice.Core.Docking/Serialization/ISerializableLayout.cs index 92167ed..3ca8be3 100644 --- a/Lattice.Core.Docking/Serialization/ISerializableLayout.cs +++ b/Lattice.Core.Docking/Serialization/ISerializableLayout.cs @@ -1,19 +1,24 @@ namespace Lattice.Core.Docking.Serialization; /// -/// Контракт для объектов, которые могут предоставлять состояние для сериализации. +/// Определяет контракт для объектов, которые могут предоставлять состояние для сериализации. /// public interface ISerializableLayout { /// - /// Получает состояние для сериализации. + /// Получает состояние объекта для сериализации. /// - /// Объект состояния, готовый к сериализации. + /// + /// Объект состояния, готовый к сериализации. + /// object GetSerializableState(); /// - /// Восстанавливает состояние из десериализованного объекта. + /// Восстанавливает состояние объекта из десериализованного объекта. /// /// Десериализованное состояние. + /// + /// Выбрасывается, когда равен null. + /// void RestoreFromState(object state); } \ No newline at end of file diff --git a/Lattice.Core.Docking/Services/ContentRegistry.cs b/Lattice.Core.Docking/Services/ContentRegistry.cs index 0daee2c..0b49376 100644 --- a/Lattice.Core.Docking/Services/ContentRegistry.cs +++ b/Lattice.Core.Docking/Services/ContentRegistry.cs @@ -2,7 +2,7 @@ /// /// Реестр типов содержимого, который позволяет создавать экземпляры контента по типу. -/// Этот сервис является центральным для динамического создания панелей инструментов и документов в IDE. +/// Этот сервис является центральным для динамического создания панелей инструментов и документов. /// /// /// Реализует шаблон "Фабрика" для создания экземпляров . @@ -16,12 +16,19 @@ public class ContentRegistry /// /// Регистрирует фабричный метод для создания контента указанного типа. /// - /// Тип контента, реализующий . + /// + /// Тип контента, реализующий . + /// /// Уникальный идентификатор типа контента. /// Фабричный метод для создания экземпляров контента. /// Метаданные типа контента (опционально). - /// Выбрасывается, если contentTypeId или factory равны null. - /// Выбрасывается, если contentTypeId уже зарегистрирован. + /// + /// Выбрасывается, если или + /// равны null. + /// + /// + /// Выбрасывается, если уже зарегистрирован. + /// public void Register(string contentTypeId, Func factory, ContentMetadata? metadata = null) where T : Abstractions.IDockContent { @@ -31,7 +38,7 @@ public class ContentRegistry throw new ArgumentNullException(nameof(factory)); if (_contentTypes.ContainsKey(contentTypeId)) - throw new ArgumentException($"Content type '{contentTypeId}' is already registered."); + throw new ArgumentException($"Тип контента '{contentTypeId}' уже зарегистрирован."); _contentTypes[contentTypeId] = new ContentDescriptor( typeof(T), @@ -45,14 +52,25 @@ public class ContentRegistry /// /// Идентификатор типа контента. /// Уникальный идентификатор для создаваемого экземпляра контента. - /// Новый экземпляр контента. - /// Выбрасывается, если тип контента не зарегистрирован. + /// + /// Новый экземпляр контента. + /// + /// + /// Выбрасывается, если равен null или пустой строке. + /// + /// + /// Выбрасывается, если тип контента не зарегистрирован. + /// public Abstractions.IDockContent CreateContent(string contentTypeId, string id) { + if (string.IsNullOrWhiteSpace(contentTypeId)) + throw new ArgumentNullException(nameof(contentTypeId)); + if (!_contentTypes.TryGetValue(contentTypeId, out var descriptor)) - throw new KeyNotFoundException($"Content type '{contentTypeId}' is not registered."); + throw new KeyNotFoundException($"Тип контента '{contentTypeId}' не зарегистрирован."); var content = descriptor.Factory(); + // Устанавливаем ID через рефлексию, если есть свойство Id var property = content.GetType().GetProperty("Id"); if (property != null && property.CanWrite) @@ -67,9 +85,14 @@ public class ContentRegistry /// Получает метаданные для указанного типа контента. /// /// Идентификатор типа контента. - /// Метаданные типа контента или null, если тип не найден. + /// + /// Метаданные типа контента или null, если тип не найден. + /// public ContentMetadata? GetMetadata(string contentTypeId) { + if (string.IsNullOrWhiteSpace(contentTypeId)) + return null; + return _contentTypes.TryGetValue(contentTypeId, out var descriptor) ? descriptor.Metadata : null; @@ -78,24 +101,54 @@ public class ContentRegistry /// /// Получает все зарегистрированные типы контента. /// - /// Коллекция идентификаторов зарегистрированных типов контента. + /// + /// Коллекция идентификаторов зарегистрированных типов контента. + /// public IEnumerable GetRegisteredTypes() => _contentTypes.Keys; /// /// Проверяет, зарегистрирован ли указанный тип контента. /// - public bool IsRegistered(string contentTypeId) => _contentTypes.ContainsKey(contentTypeId); + /// Идентификатор типа контента. + /// + /// true, если тип контента зарегистрирован; в противном случае false. + /// + public bool IsRegistered(string contentTypeId) + { + if (string.IsNullOrWhiteSpace(contentTypeId)) + return false; + + return _contentTypes.ContainsKey(contentTypeId); + } /// - /// Дескриптор типа контента, содержащий информацию о фабричном методе и метаданных. + /// Представляет дескриптор типа контента, содержащий информацию о фабричном методе и метаданных. /// private class ContentDescriptor { + /// + /// Получает тип контента. + /// public Type ContentType { get; } + + /// + /// Получает фабричный метод для создания экземпляров контента. + /// public Func Factory { get; } + + /// + /// Получает метаданные типа контента. + /// public ContentMetadata Metadata { get; } - public ContentDescriptor(Type contentType, Func factory, ContentMetadata metadata) + /// + /// Инициализирует новый экземпляр класса . + /// + /// Тип контента. + /// Фабричный метод. + /// Метаданные. + public ContentDescriptor(Type contentType, Func factory, + ContentMetadata metadata) { ContentType = contentType; Factory = factory; @@ -105,54 +158,80 @@ public class ContentRegistry } /// -/// Метаданные типа контента, предоставляющие дополнительную информацию для отображения в UI. +/// Представляет метаданные типа контента, предоставляющие дополнительную информацию для отображения в UI. /// public class ContentMetadata { /// - /// Идентификатор типа контента. + /// Получает идентификатор типа контента. /// + /// + /// Уникальный строковый идентификатор типа контента. + /// public string ContentTypeId { get; } /// - /// Отображаемое имя типа контента. + /// Получает или задает отображаемое имя типа контента. /// + /// + /// Имя типа контента, отображаемое пользователю. + /// public string DisplayName { get; set; } /// - /// Описание типа контента. + /// Получает или задает описание типа контента. /// + /// + /// Текстовое описание функциональности контента. + /// public string Description { get; set; } /// - /// Имя ресурса для иконки (опционально). + /// Получает или задает имя ресурса для иконки типа контента. /// + /// + /// Имя ресурса иконки или null, если иконка не определена. + /// public string? IconResource { get; set; } /// - /// Признак того, что контент является документом (а не инструментальной панелью). + /// Получает или задает значение, указывающее, является ли контент документом + /// (а не инструментальной панелью). /// + /// + /// true, если контент является документом; в противном случае false. + /// public bool IsDocument { get; set; } /// - /// Минимальная ширина контента в пикселях. + /// Получает или задает ширину контента по умолчанию. /// + /// + /// Ширина контента в пикселях. Значение по умолчанию: 300. + /// public double DefaultWidth { get; set; } = 300; /// - /// Минимальная высота контента в пикселях. + /// Получает или задает высоту контента по умолчанию. /// + /// + /// Высота контента в пикселях. Значение по умолчанию: 200. + /// public double DefaultHeight { get; set; } = 200; /// - /// Инициализирует новый экземпляр метаданных контента. + /// Инициализирует новый экземпляр класса . /// /// Идентификатор типа контента. - /// Отображаемое имя. + /// Отображаемое имя типа контента. + /// + /// Выбрасывается, если или + /// равны null. + /// public ContentMetadata(string contentTypeId, string displayName) { - ContentTypeId = contentTypeId; - DisplayName = displayName; + ContentTypeId = contentTypeId ?? throw new ArgumentNullException(nameof(contentTypeId)); + DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName)); Description = string.Empty; } } \ No newline at end of file diff --git a/Lattice.Core.DragDrop.Tests/DragDropServiceTests.cs b/Lattice.Core.DragDrop.Tests/DragDropServiceTests.cs deleted file mode 100644 index 4b02109..0000000 --- a/Lattice.Core.DragDrop.Tests/DragDropServiceTests.cs +++ /dev/null @@ -1,115 +0,0 @@ -using Lattice.Core.DragDrop.Abstractions; -using Lattice.Core.DragDrop.Enums; -using Lattice.Core.DragDrop.Models; -using Lattice.Core.DragDrop.Services; -using Lattice.Core.Geometry; -using Moq; -using Xunit; - -namespace Lattice.Core.DragDrop.Tests; - -public class DragDropServiceTests -{ - [Fact] - public void StartDrag_WithValidSource_StartsDragOperation() - { - // Arrange - var service = new DragDropService(); - var mockSource = new Mock(); - var dragInfo = new DragInfo("test", DragDropEffects.Copy, new Point(0, 0)); - - mockSource.Setup(s => s.CanStartDrag(out dragInfo)).Returns(true); - mockSource.Setup(s => s.StartDrag(It.IsAny())).Returns(true); - - // Act - var result = service.StartDrag(mockSource.Object, new Point(0, 0)); - - // Assert - Assert.True(result); - Assert.True(service.IsDragActive); - Assert.NotNull(service.CurrentDragInfo); - } - - [Fact] - public void RegisterDropTarget_ReturnsValidId() - { - // Arrange - var service = new DragDropService(); - var mockTarget = new Mock(); - var bounds = new Rect(0, 0, 100, 100); - - // Act - var id = service.RegisterDropTarget(mockTarget.Object, bounds); - - // Assert - Assert.NotNull(id); - Assert.NotEmpty(id); - } - - [Fact] - public void UpdateDrag_WithValidDropTarget_CallsDragOver() - { - // Arrange - var service = new DragDropService(); - var mockSource = new Mock(); - var mockTarget = new Mock(); - - var dragInfo = new DragInfo("test", DragDropEffects.Copy, new Point(0, 0)); - mockSource.Setup(s => s.CanStartDrag(out dragInfo)).Returns(true); - mockSource.Setup(s => s.StartDrag(It.IsAny())).Returns(true); - - var targetId = service.RegisterDropTarget(mockTarget.Object, new Rect(0, 0, 100, 100)); - service.StartDrag(mockSource.Object, new Point(0, 0)); - - // Act - service.UpdateDrag(new Point(50, 50)); - - // Assert - mockTarget.Verify(t => t.DragOver(It.IsAny()), Times.AtLeastOnce()); - } - - [Fact] - public void EndDrag_WithValidDrop_CallsDrop() - { - // Arrange - var service = new DragDropService(); - var mockSource = new Mock(); - var mockTarget = new Mock(); - - var dragInfo = new DragInfo("test", DragDropEffects.Copy, new Point(0, 0)); - mockSource.Setup(s => s.CanStartDrag(out dragInfo)).Returns(true); - mockSource.Setup(s => s.StartDrag(It.IsAny())).Returns(true); - - service.RegisterDropTarget(mockTarget.Object, new Rect(0, 0, 100, 100)); - service.StartDrag(mockSource.Object, new Point(0, 0)); - service.UpdateDrag(new Point(50, 50)); - - // Act - var effects = service.EndDrag(new Point(50, 50)); - - // Assert - mockTarget.Verify(t => t.Drop(It.IsAny()), Times.Once()); - Assert.False(service.IsDragActive); - } - - [Fact] - public void CancelDrag_WithActiveDrag_CallsDragCancelled() - { - // Arrange - var service = new DragDropService(); - var mockSource = new Mock(); - var dragInfo = new DragInfo("test", DragDropEffects.Copy, new Point(0, 0)); - - mockSource.Setup(s => s.CanStartDrag(out dragInfo)).Returns(true); - mockSource.Setup(s => s.StartDrag(It.IsAny())).Returns(true); - - service.StartDrag(mockSource.Object, new Point(0, 0)); - - // Act - service.CancelDrag(); - - // Assert - mockSource.Verify(s => s.DragCancelled(It.IsAny()), Times.Once()); - Assert.False(service.IsDragActive); - } -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop.Tests/Lattice.Core.DragDrop.Tests.csproj b/Lattice.Core.DragDrop.Tests/Lattice.Core.DragDrop.Tests.csproj deleted file mode 100644 index 32a369c..0000000 --- a/Lattice.Core.DragDrop.Tests/Lattice.Core.DragDrop.Tests.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - net8.0 - enable - false - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Abstractions/IDragSource.cs b/Lattice.Core.DragDrop/Abstractions/IDragSource.cs deleted file mode 100644 index 989b274..0000000 --- a/Lattice.Core.DragDrop/Abstractions/IDragSource.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace Lattice.Core.DragDrop.Abstractions; - -/// -/// Определяет контракт для объектов, которые могут быть источником данных -/// в операции перетаскивания. -/// -/// -/// -/// Объекты, реализующие этот интерфейс, могут инициировать операции перетаскивания -/// и предоставлять данные для передачи другим элементам через механизм drag-and-drop. -/// -/// -/// Интерфейс полностью асинхронный и поддерживает отмену операций через CancellationToken. -/// Все методы должны быть потокобезопасными и поддерживать вызов из любого потока. -/// -/// -public interface IDragSource -{ - /// - /// Пытается начать операцию перетаскивания из указанной позиции. - /// - /// Начальная позиция операции в координатах экрана. - /// Токен отмены операции. - /// - /// Информация о перетаскивании, если операция может быть начата; в противном случае — null. - /// Возвращаемый объект должен быть полностью инициализирован, - /// включая данные, разрешенные эффекты и ссылку на источник. - /// - /// - /// - /// Этот метод вызывается сервисом перетаскивания при попытке начать операцию - /// (обычно при нажатии и перемещении мыши). Метод должен проверить, может ли - /// источник начать перетаскивание в текущем контексте. - /// - /// - /// Реализация должна быть быстрой и не выполнять длительных операций. - /// Если подготовка данных требует времени, ее следует выполнить асинхронно - /// после подтверждения возможности начала. - /// - /// - Task TryStartDragAsync(Geometry.Point startPosition, CancellationToken cancellationToken = default); - - /// - /// Уведомляет источник о завершении операции перетаскивания. - /// - /// Информация о перетаскивании, полученная при начале операции. - /// Эффекты, которые были применены при сбросе. - /// Токен отмены операции. - /// Задача, представляющая асинхронную операцию. - /// - /// - /// Этот метод вызывается после завершения операции перетаскивания - /// (успешного или неуспешного). Реализация может: - /// - /// - /// Выполнить очистку ресурсов, связанных с операцией - /// Обновить состояние на основе результата (например, удалить данные при перемещении) - /// Отобразить визуальную обратную связь о результате - /// - /// - /// Если операция завершилась с эффектом , - /// источник обычно должен удалить или обновить исходные данные. - /// - /// - Task OnDragCompletedAsync(Models.DragInfo dragInfo, Enums.DragDropEffects effects, CancellationToken cancellationToken = default); - - /// - /// Уведомляет источник об отмене операции перетаскивания. - /// - /// Информация о перетаскивании, полученная при начале операции. - /// Токен отмены операции. - /// Задача, представляющая асинхронную операцию. - /// - /// - /// Этот метод вызывается, когда операция перетаскивания была отменена - /// пользователем (например, нажатием клавиши Escape) или системой. - /// - /// - /// Реализация должна выполнить очистку и восстановить исходное состояние. - /// Обычно это включает освобождение ресурсов и сброс визуальных индикаторов. - /// - /// - Task OnDragCancelledAsync(Models.DragInfo dragInfo, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Abstractions/IDropTarget.cs b/Lattice.Core.DragDrop/Abstractions/IDropTarget.cs deleted file mode 100644 index 5a15f2d..0000000 --- a/Lattice.Core.DragDrop/Abstractions/IDropTarget.cs +++ /dev/null @@ -1,118 +0,0 @@ -namespace Lattice.Core.DragDrop.Abstractions; - -/// -/// Определяет контракт для объектов, которые могут принимать сбрасываемые данные -/// в операции перетаскивания. -/// -/// -/// -/// Объекты, реализующие этот интерфейс, могут обрабатывать данные, сброшенные -/// пользователем, и предоставлять визуальную обратную связь во время перетаскивания. -/// -/// -/// Интерфейс поддерживает асинхронные операции и отмену через CancellationToken. -/// Все методы должны быть потокобезопасными и идемпотентными (многократный вызов -/// с одинаковыми параметрами должен давать одинаковый результат). -/// -/// -public interface IDropTarget -{ - /// - /// Определяет, может ли объект принять сбрасываемые данные. - /// - /// Информация о потенциальном сбросе. - /// Токен отмены операции. - /// - /// true, если объект может принять данные; в противном случае — false. - /// - /// - /// - /// Этот метод вызывается, когда перетаскиваемый объект находится над целью. - /// Реализация должна проверить, совместимы ли данные с целью, и установить - /// предлагаемые эффекты в свойстве . - /// - /// - /// Метод может вызываться многократно при перемещении курсора над целью. - /// Реализация должна быть эффективной и избегать длительных операций. - /// - /// - /// Если метод возвращает false, система не будет вызывать другие методы - /// для этой цели до тех пор, пока курсор не покинет ее область. - /// - /// - Task CanAcceptDropAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default); - - /// - /// Вызывается, когда перетаскиваемый объект перемещается над целью. - /// - /// Информация о текущем положении перетаскивания. - /// Токен отмены операции. - /// Задача, представляющая асинхронную операцию. - /// - /// - /// Этот метод вызывается постоянно, пока пользователь перемещает объект над целью. - /// Реализация может: - /// - /// - /// Обновить визуальную обратную связь (подсветка, изменение курсора) - /// Вычислить точную позицию сброса (например, между элементами списка) - /// Уточнить предлагаемые эффекты на основе текущей позиции - /// Прокрутить содержимое, если цель поддерживает прокрутку - /// - /// - /// Метод должен быть оптимизирован для частого вызова. Длительные операции - /// должны выполняться асинхронно без блокировки потока. - /// - /// - Task OnDragOverAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default); - - /// - /// Вызывается, когда пользователь сбрасывает данные на цель. - /// - /// Информация о сбросе, включая данные и позицию. - /// Токен отмены операции. - /// Задача, представляющая асинхронную операцию. - /// - /// - /// Этот метод вызывается, когда пользователь отпускает кнопку мыши над целью. - /// Реализация должна обработать принятие данных и выполнить соответствующее действие: - /// - /// - /// Добавить данные в коллекцию (для копирования) - /// Переместить данные (при поддержке перемещения) - /// Создать ссылку на данные - /// Выполнить пользовательскую логику обработки - /// - /// - /// После успешной обработки данных следует вызвать , - /// чтобы указать системе, что операция обработана и дополнительная обработка не требуется. - /// - /// - /// Если операция завершилась успешно, система уведомит источник через - /// с соответствующими эффектами. - /// - /// - Task OnDropAsync(Models.DropInfo dropInfo, CancellationToken cancellationToken = default); - - /// - /// Вызывается, когда перетаскиваемый объект покидает область цели. - /// - /// Токен отмены операции. - /// Задача, представляющая асинхронную операцию. - /// - /// - /// Этот метод вызывается, когда пользователь перемещает объект за пределы цели. - /// Реализация должна: - /// - /// - /// Очистить любую визуальную обратную связь, установленную ранее - /// Сбросить временное состояние, связанное с операцией - /// Освободить ресурсы, выделенные для предварительного просмотра - /// - /// - /// Метод гарантированно вызывается после любого успешного или неуспешного - /// вызова , если курсор покидает область цели. - /// - /// - Task OnDragLeaveAsync(CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Constants/DragDropConstants.cs b/Lattice.Core.DragDrop/Constants/DragDropConstants.cs deleted file mode 100644 index 6bb931c..0000000 --- a/Lattice.Core.DragDrop/Constants/DragDropConstants.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Lattice.Core.DragDrop.Constants; - -/// -/// Константы для системы перетаскивания. -/// -public static class DragDropConstants -{ - /// - /// Порог начала перетаскивания по умолчанию (пикселей). - /// - public const double DefaultDragThreshold = 3.0; - - /// - /// Интервал очистки по умолчанию (миллисекунды). - /// - public const int DefaultCleanupInterval = 60000; - - /// - /// Таймаут асинхронных операций по умолчанию (миллисекунды). - /// - public const int DefaultAsyncTimeout = 5000; - - /// - /// Время жизни неиспользуемых целей (минуты). - /// - public const int TargetLifetimeMinutes = 10; -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Enums/DragDropEffects.cs b/Lattice.Core.DragDrop/Enums/DragDropEffects.cs deleted file mode 100644 index 705232a..0000000 --- a/Lattice.Core.DragDrop/Enums/DragDropEffects.cs +++ /dev/null @@ -1,115 +0,0 @@ -namespace Lattice.Core.DragDrop.Enums; - -/// -/// Определяет эффекты, которые могут быть применены при операции перетаскивания. -/// -/// -/// Этот перечисление используется для указания допустимых операций перетаскивания -/// и передачи информации о результате операции между источником и целью. -/// -[Flags] -public enum DragDropEffects -{ - /// - /// Операция перетаскивания не разрешена. - /// - None = 0, - - /// - /// Данные копируются из источника в цель. - /// - Copy = 1 << 0, - - /// - /// Данные перемещаются из источника в цель. - /// - Move = 1 << 1, - - /// - /// Создается ссылка на исходные данные. - /// - Link = 1 << 2, - - /// - /// Целевой элемент может прокручиваться во время перетаскивания. - /// - Scroll = 1 << 3, - - /// - /// Комбинированный эффект копирования и перемещения. - /// - CopyOrMove = Copy | Move, - - /// - /// Все эффекты разрешены. - /// - All = Copy | Move | Link | Scroll -} - -/// -/// Расширения для работы с DragDropEffects. -/// -public static class DragDropEffectsExtensions -{ - /// - /// Проверяет, содержит ли эффекты указанный эффект. - /// - /// Эффекты для проверки. - /// Эффект для поиска. - /// true, если эффект присутствует; в противном случае — false. - public static bool HasEffect(this DragDropEffects effects, DragDropEffects effect) - { - return (effects & effect) == effect; - } - - /// - /// Проверяет, содержат ли эффекты копирование. - /// - /// Эффекты для проверки. - /// true, если разрешено копирование; в противном случае — false. - public static bool CanCopy(this DragDropEffects effects) - { - return effects.HasEffect(DragDropEffects.Copy); - } - - /// - /// Проверяет, содержат ли эффекты перемещение. - /// - /// Эффекты для проверки. - /// true, если разрешено перемещение; в противном случае — false. - public static bool CanMove(this DragDropEffects effects) - { - return effects.HasEffect(DragDropEffects.Move); - } - - /// - /// Проверяет, содержат ли эффекты ссылку. - /// - /// Эффекты для проверки. - /// true, если разрешена ссылка; в противном случае — false. - public static bool CanLink(this DragDropEffects effects) - { - return effects.HasEffect(DragDropEffects.Link); - } - - /// - /// Получает наиболее подходящий эффект на основе модификаторов клавиатуры. - /// - /// Нажата ли клавиша Control. - /// Нажата ли клавиша Shift. - /// Нажата ли клавиша Alt. - /// Наиболее подходящий эффект перетаскивания. - public static DragDropEffects GetEffectFromKeys(bool controlKey, bool shiftKey, bool altKey) - { - if (controlKey && shiftKey) - return DragDropEffects.Link; - if (controlKey) - return DragDropEffects.Copy; - if (shiftKey) - return DragDropEffects.Move; - if (altKey) - return DragDropEffects.Link; - - return DragDropEffects.Move; // По умолчанию - } -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Enums/DropPosition.cs b/Lattice.Core.DragDrop/Enums/DropPosition.cs deleted file mode 100644 index 50e15da..0000000 --- a/Lattice.Core.DragDrop/Enums/DropPosition.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Lattice.Core.DragDrop.Enums; - -/// -/// Позиция сброса относительно цели. -/// -public enum DropPosition -{ - Inside, - Top, - Bottom, - Left, - Right, - Center -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Exceptions/DragDropException.cs b/Lattice.Core.DragDrop/Exceptions/DragDropException.cs deleted file mode 100644 index 66c8e91..0000000 --- a/Lattice.Core.DragDrop/Exceptions/DragDropException.cs +++ /dev/null @@ -1,85 +0,0 @@ -namespace Lattice.Core.DragDrop.Exceptions; - -/// -/// Исключение, возникающее при ошибках в системе перетаскивания. -/// -public class DragDropException : Exception -{ - /// - /// Код ошибки. - /// - public string ErrorCode { get; } - - /// - /// Инициализирует новый экземпляр класса . - /// - public DragDropException() - : base("Drag & Drop operation failed.") - { - ErrorCode = "DRAGDROP_0001"; - } - - /// - /// Инициализирует новый экземпляр класса с указанным сообщением. - /// - public DragDropException(string message) - : base(message) - { - ErrorCode = "DRAGDROP_0002"; - } - - /// - /// Инициализирует новый экземпляр класса с кодом ошибки. - /// - public DragDropException(string errorCode, string message) - : base(message) - { - ErrorCode = errorCode; - } - - /// - /// Инициализирует новый экземпляр класса - /// с указанным сообщением и внутренним исключением. - /// - public DragDropException(string message, Exception innerException) - : base(message, innerException) - { - ErrorCode = "DRAGDROP_0003"; - } - - /// - /// Инициализирует новый экземпляр класса - /// с кодом ошибки, сообщением и внутренним исключением. - /// - public DragDropException(string errorCode, string message, Exception innerException) - : base(message, innerException) - { - ErrorCode = errorCode; - } -} - -/// -/// Коды ошибок Drag and Drop системы. -/// -public static class DragDropErrorCodes -{ - // Общие ошибки - public const string OperationAlreadyActive = "DRAGDROP_1001"; - public const string OperationNotActive = "DRAGDROP_1002"; - public const string InvalidData = "DRAGDROP_1003"; - public const string Timeout = "DRAGDROP_1004"; - - // Ошибки источников - public const string SourceCannotDrag = "DRAGDROP_2001"; - public const string SourceStartFailed = "DRAGDROP_2002"; - - // Ошибки целей - public const string TargetNotFound = "DRAGDROP_3001"; - public const string TargetCannotAccept = "DRAGDROP_3002"; - public const string TargetDropFailed = "DRAGDROP_3003"; - - // Ошибки системы - public const string SystemNotInitialized = "DRAGDROP_4001"; - public const string SystemDisposed = "DRAGDROP_4002"; - public const string MemoryAllocationFailed = "DRAGDROP_4003"; -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Extensions/DropInfoExtensions.cs b/Lattice.Core.DragDrop/Extensions/DropInfoExtensions.cs deleted file mode 100644 index a6e9c55..0000000 --- a/Lattice.Core.DragDrop/Extensions/DropInfoExtensions.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Lattice.Core.DragDrop.Enums; -using Lattice.Core.Geometry; - -namespace Lattice.Core.DragDrop.Extensions; - -/// -/// Методы расширения для DropInfo. -/// -public static class DropInfoExtensions -{ - /// - /// Проверяет, могут ли данные быть приведены к указанному типу. - /// - /// Тип данных для проверки. - /// Информация о сбросе. - /// true, если данные могут быть приведены к типу T; в противном случае — false. - public static bool CanAccept(this Models.DropInfo dropInfo) - where T : class - { - return dropInfo.Data is T; - } - - /// - /// Пытается получить данные как указанный тип. - /// - /// Тип, к которому нужно привести данные. - /// Информация о сбросе. - /// Данные как тип T или null. - public static T? GetDataAs(this Models.DropInfo dropInfo) - where T : class - { - return dropInfo.Data as T; - } - - /// - /// Получает данные как указанный тип или выбрасывает исключение. - /// - /// Тип, к которому нужно привести данные. - /// Информация о сбросе. - /// Данные как тип T. - /// Выбрасывается, если данные не могут быть приведены к типу T. - public static T GetRequiredDataAs(this Models.DropInfo dropInfo) - where T : class - { - if (dropInfo.Data is not T data) - { - throw new InvalidCastException( - $"Ожидался тип {typeof(T).Name}, но получен {dropInfo.Data?.GetType().Name ?? "null"}"); - } - return data; - } - - /// - /// Проверяет, содержится ли позиция в указанных границах. - /// - /// Информация о сбросе. - /// Границы для проверки. - /// true, если позиция находится в границах; в противном случае — false. - public static bool IsInBounds(this Models.DropInfo dropInfo, Rect bounds) - { - return bounds.Contains(dropInfo.Position); - } - - /// - /// Проверяет можно ли добавить эффект перетаскивания. - /// - /// - /// - /// - public static bool CanAcceptEffect(this Models.DropInfo dropInfo, DragDropEffects effect) - { - return dropInfo.AllowedEffects.HasEffect(effect); - } -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Factories/DragDropFactory.cs b/Lattice.Core.DragDrop/Factories/DragDropFactory.cs deleted file mode 100644 index df48263..0000000 --- a/Lattice.Core.DragDrop/Factories/DragDropFactory.cs +++ /dev/null @@ -1,581 +0,0 @@ -using Lattice.Core.DragDrop.Abstractions; -using Lattice.Core.DragDrop.Enums; -using Lattice.Core.DragDrop.Models; -using Lattice.Core.DragDrop.Services; -using Lattice.Core.Geometry; - -namespace Lattice.Core.DragDrop.Factories; - -/// -/// Фабрика для создания компонентов системы перетаскивания. -/// Предоставляет методы для создания сервисов, источников и целей перетаскивания. -/// -/// -/// Эта фабрика позволяет создавать компоненты системы перетаскивания без использования -/// Dependency Injection, предоставляя простой и понятный API для наиболее распространенных сценариев. -/// -public static class DragDropFactory -{ - #region Сервисы перетаскивания - - /// - /// Создает новый экземпляр сервиса перетаскивания с настройками по умолчанию. - /// - /// - /// Экземпляр с настройками по умолчанию. - /// - /// - /// Созданный сервис имеет следующие настройки по умолчанию: - /// - /// Порог начала перетаскивания: 3.0 пикселей - /// Таймаут асинхронных операций: 5000 миллисекунд - /// Асинхронные операции: включены - /// - /// - public static IDragDropService CreateDragDropService() - { - return new DragDropService(); - } - - /// - /// Создает новый экземпляр сервиса перетаскивания с пользовательскими настройками. - /// - /// - /// Делегат для настройки опций сервиса. Передает экземпляр - /// для настройки параметров. - /// - /// - /// Настроенный экземпляр . - /// - /// - /// - /// var service = DragDropFactory.CreateDragDropService(options => - /// { - /// options.DragStartThreshold = 5.0; - /// options.AsyncOperationTimeout = 3000; - /// options.EnableAsyncOperations = true; - /// }); - /// - /// - public static IDragDropService CreateDragDropService(Action configure) - { - var options = new DragDropServiceOptions(); - configure(options); - - return new DragDropService - { - DragStartThreshold = options.DragStartThreshold, - AsyncOperationTimeout = options.AsyncOperationTimeout, - EnableAsyncOperations = options.EnableAsyncOperations - }; - } - - /// - /// Создает сервис перетаскивания, оптимизированный для сенсорных устройств. - /// - /// - /// Экземпляр с увеличенным порогом перетаскивания. - /// - /// - /// Этот метод создает сервис с увеличенным порогом начала перетаскивания (10.0 пикселей), - /// что уменьшает вероятность случайного начала перетаскивания при использовании сенсорного экрана. - /// - public static IDragDropService CreateTouchOptimizedService() - { - return new DragDropService - { - DragStartThreshold = 10.0, - AsyncOperationTimeout = 3000, - EnableAsyncOperations = true - }; - } - - /// - /// Создает сервис перетаскивания для точных операций (графические редакторы, карты). - /// - /// - /// Экземпляр с уменьшенным порогом перетаскивания. - /// - /// - /// Этот метод создает сервис с минимальным порогом начала перетаскивания (1.0 пиксель), - /// что позволяет начинать перетаскивание с максимальной точностью. - /// - public static IDragDropService CreatePrecisionDragService() - { - return new DragDropService - { - DragStartThreshold = 1.0, - AsyncOperationTimeout = 10000, // Больше времени для сложных операций - EnableAsyncOperations = true - }; - } - - #endregion - - #region Источники перетаскивания - - /// - /// Создает простой источник перетаскивания с фиксированными данными. - /// - /// - /// Данные, которые будут перетаскиваться. Не может быть null. - /// - /// - /// Разрешенные эффекты перетаскивания. По умолчанию разрешены копирование и перемещение. - /// - /// - /// Экземпляр , который всегда предоставляет указанные данные. - /// - /// - /// Выбрасывается, когда равен null. - /// - /// - /// Этот источник подходит для случаев, когда данные для перетаскивания известны заранее - /// и не изменяются в процессе операции. - /// - public static IDragSource CreateSimpleSource(object data, DragDropEffects allowedEffects = DragDropEffects.Copy | DragDropEffects.Move) - { - if (data == null) - throw new ArgumentNullException(nameof(data)); - - return new SimpleDragSource(data, allowedEffects); - } - - /// - /// Создает источник перетаскивания с отложенной загрузкой данных. - /// - /// - /// Фабрика данных, которая будет вызвана при начале перетаскивания. - /// - /// - /// Функция проверки возможности начала перетаскивания. - /// - /// - /// Разрешенные эффекты перетаскивания. - /// - /// - /// Экземпляр с отложенной загрузкой данных. - /// - /// - /// Этот источник полезен, когда данные для перетаскивания дорого создавать заранее - /// или зависят от контекста в момент начала операции. - /// - public static IDragSource CreateLazySource( - Func dataFactory, - Func canDragChecker = null, - DragDropEffects allowedEffects = DragDropEffects.Copy | DragDropEffects.Move) - { - if (dataFactory == null) - throw new ArgumentNullException(nameof(dataFactory)); - - return new LazyDragSource(dataFactory, canDragChecker, allowedEffects); - } - - /// - /// Создает источник перетаскивания для коллекции элементов. - /// - /// - /// Тип элементов в коллекции. - /// - /// - /// Коллекция элементов для перетаскивания. - /// - /// - /// Функция выбора конкретного элемента для перетаскивания из коллекции. - /// - /// - /// Разрешенные эффекты перетаскивания. - /// - /// - /// Экземпляр для коллекции элементов. - /// - /// - /// Этот источник позволяет перетаскивать элементы из коллекции. Функция - /// определяет, какой именно элемент из коллекции будет перетаскиваться в текущем контексте. - /// - public static IDragSource CreateCollectionSource( - IEnumerable items, - Func, T> itemSelector, - DragDropEffects allowedEffects = DragDropEffects.Copy | DragDropEffects.Move) - { - if (items == null) - throw new ArgumentNullException(nameof(items)); - if (itemSelector == null) - throw new ArgumentNullException(nameof(itemSelector)); - - return new CollectionDragSource(items, itemSelector, allowedEffects); - } - - #endregion - - #region Цели сброса - - /// - /// Создает простую цель сброса, которая принимает данные любого типа. - /// - /// - /// Обработчик, вызываемый при сбросе данных. - /// - /// - /// Экземпляр , который принимает любые данные. - /// - /// - /// Эта цель подходит для простых сценариев, когда не требуется валидация типа данных. - /// - public static IDropTarget CreateSimpleTarget(Action onDrop) - { - if (onDrop == null) - throw new ArgumentNullException(nameof(onDrop)); - - return new SimpleDropTarget(onDrop); - } - - /// - /// Создает цель сброса с фильтрацией по типу данных. - /// - /// - /// Тип данных, которые может принимать цель. - /// - /// - /// Обработчик, вызываемый при сбросе данных. - /// - /// - /// Экземпляр , который принимает только данные типа . - /// - /// - /// Эта цель автоматически проверяет тип сбрасываемых данных и вызывает обработчик только - /// если данные могут быть приведены к указанному типу. - /// - public static IDropTarget CreateTypedTarget(Action onDrop) where T : class - { - if (onDrop == null) - throw new ArgumentNullException(nameof(onDrop)); - - return new TypedDropTarget(onDrop); - } - - /// - /// Создает цель сброса с пользовательской логикой валидации. - /// - /// - /// Функция проверки возможности приема данных. - /// - /// - /// Обработчик, вызываемый при сбросе данных. - /// - /// - /// Экземпляр с пользовательской логикой валидации. - /// - /// - /// Эта цель позволяет реализовать сложную логику валидации, выходящую за рамки простой проверки типа. - /// - public static IDropTarget CreateConditionalTarget(Func canAccept, Action onDrop) - { - if (canAccept == null) - throw new ArgumentNullException(nameof(canAccept)); - if (onDrop == null) - throw new ArgumentNullException(nameof(onDrop)); - - return new ConditionalDropTarget(canAccept, onDrop); - } - - #endregion - - #region Вспомогательные методы - - /// - /// Создает стандартные эффекты перетаскивания на основе модификаторов клавиатуры. - /// - /// - /// Нажата ли клавиша Control. - /// - /// - /// Нажата ли клавиша Shift. - /// - /// - /// Нажата ли клавиша Alt. - /// - /// - /// , соответствующие комбинации клавиш. - /// - /// - /// Стандартная логика: - /// - /// Control + Shift: Link - /// Control: Copy - /// Shift: Move - /// Alt: Link - /// Без модификаторов: Move - /// - /// - public static DragDropEffects GetEffectsFromKeys(bool controlKey, bool shiftKey, bool altKey) - { - return DragDropEffectsExtensions.GetEffectFromKeys(controlKey, shiftKey, altKey); - } - - #endregion -} - -/// -/// Опции для настройки сервиса перетаскивания. -/// -public class DragDropServiceOptions -{ - /// - /// Получает или задает порог начала перетаскивания в пикселях. - /// - /// - /// Минимальное расстояние, которое должен пройти курсор, чтобы началась операция перетаскивания. - /// Значение по умолчанию: 3.0. - /// - public double DragStartThreshold { get; set; } = Constants.DragDropConstants.DefaultDragThreshold; - - /// - /// Получает или задает максимальное время ожидания асинхронных операций в миллисекундах. - /// - /// - /// Время в миллисекундах, после которого асинхронная операция будет прервана. - /// Значение по умолчанию: 5000. - /// - public int AsyncOperationTimeout { get; set; } = Constants.DragDropConstants.DefaultAsyncTimeout; - - /// - /// Получает или задает значение, указывающее, включены ли асинхронные операции. - /// - /// - /// true, если асинхронные операции включены; в противном случае — false. - /// Значение по умолчанию: true. - /// - public bool EnableAsyncOperations { get; set; } = true; - - /// - /// Получает или задает интервал автоматической очистки неиспользуемых целей в минутах. - /// - /// - /// Интервал в минутах, через который будут удаляться цели сброса, к которым не было обращений. - /// Значение по умолчанию: 10. - /// - public int CleanupInterval { get; set; } = Constants.DragDropConstants.TargetLifetimeMinutes; -} - -#region Внутренние реализации - -internal class SimpleDragSource : IDragSource -{ - private readonly object _data; - private readonly DragDropEffects _allowedEffects; - - public SimpleDragSource(object data, DragDropEffects allowedEffects) - { - _data = data ?? throw new ArgumentNullException(nameof(data)); - _allowedEffects = allowedEffects; - } - - public Task TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default) - { - var dragInfo = new DragInfo(_data, _allowedEffects, startPosition, this); - return Task.FromResult(dragInfo); - } - - public Task OnDragCompletedAsync(DragInfo dragInfo, DragDropEffects effects, CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } - - public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } -} - -internal class LazyDragSource : IDragSource -{ - private readonly Func _dataFactory; - private readonly Func _canDragChecker; - private readonly DragDropEffects _allowedEffects; - - public LazyDragSource(Func dataFactory, Func canDragChecker, DragDropEffects allowedEffects) - { - _dataFactory = dataFactory ?? throw new ArgumentNullException(nameof(dataFactory)); - _canDragChecker = canDragChecker ?? (() => true); - _allowedEffects = allowedEffects; - } - - public Task TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default) - { - if (!_canDragChecker()) - return Task.FromResult(null); - - var data = _dataFactory(); - if (data == null) - return Task.FromResult(null); - - var dragInfo = new DragInfo(data, _allowedEffects, startPosition, this); - return Task.FromResult(dragInfo); - } - - public Task OnDragCompletedAsync(DragInfo dragInfo, DragDropEffects effects, CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } - - public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } -} - -internal class CollectionDragSource : IDragSource -{ - private readonly IEnumerable _items; - private readonly Func, T> _itemSelector; - private readonly DragDropEffects _allowedEffects; - - public CollectionDragSource(IEnumerable items, Func, T> itemSelector, DragDropEffects allowedEffects) - { - _items = items ?? throw new ArgumentNullException(nameof(items)); - _itemSelector = itemSelector ?? throw new ArgumentNullException(nameof(itemSelector)); - _allowedEffects = allowedEffects; - } - - public Task TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default) - { - var selectedItem = _itemSelector(_items); - if (selectedItem == null) - return Task.FromResult(null); - - var dragInfo = new DragInfo(selectedItem, _allowedEffects, startPosition, this); - return Task.FromResult(dragInfo); - } - - public Task OnDragCompletedAsync(DragInfo dragInfo, DragDropEffects effects, CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } - - public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } -} - -internal class SimpleDropTarget : IDropTarget -{ - private readonly Action _onDrop; - - public SimpleDropTarget(Action onDrop) - { - _onDrop = onDrop ?? throw new ArgumentNullException(nameof(onDrop)); - } - - public Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - return Task.FromResult(dropInfo.Data != null); - } - - public Task OnDragOverAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - if (dropInfo.Data != null) - { - dropInfo.SuggestedEffects = DragDropEffects.Move; - } - return Task.CompletedTask; - } - - public Task OnDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - if (dropInfo.Data != null) - { - _onDrop(dropInfo); - dropInfo.MarkAsHandled(); - } - return Task.CompletedTask; - } - - public Task OnDragLeaveAsync(CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } -} - -internal class TypedDropTarget : IDropTarget where T : class -{ - private readonly Action _onDrop; - - public TypedDropTarget(Action onDrop) - { - _onDrop = onDrop ?? throw new ArgumentNullException(nameof(onDrop)); - } - - public Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - return Task.FromResult(dropInfo.Data is T); - } - - public Task OnDragOverAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - if (dropInfo.Data is T) - { - dropInfo.SuggestedEffects = DragDropEffects.Move; - } - return Task.CompletedTask; - } - - public Task OnDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - if (dropInfo.Data is T data) - { - _onDrop(data, dropInfo); - dropInfo.MarkAsHandled(); - } - return Task.CompletedTask; - } - - public Task OnDragLeaveAsync(CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } -} - -internal class ConditionalDropTarget : IDropTarget -{ - private readonly Func _canAccept; - private readonly Action _onDrop; - - public ConditionalDropTarget(Func canAccept, Action onDrop) - { - _canAccept = canAccept ?? throw new ArgumentNullException(nameof(canAccept)); - _onDrop = onDrop ?? throw new ArgumentNullException(nameof(onDrop)); - } - - public Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - return Task.FromResult(_canAccept(dropInfo)); - } - - public Task OnDragOverAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - if (_canAccept(dropInfo)) - { - dropInfo.SuggestedEffects = DragDropEffects.Move; - } - return Task.CompletedTask; - } - - public Task OnDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - if (_canAccept(dropInfo)) - { - _onDrop(dropInfo); - dropInfo.MarkAsHandled(); - } - return Task.CompletedTask; - } - - public Task OnDragLeaveAsync(CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } -} - -#endregion \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Lattice.Core.DragDrop.csproj b/Lattice.Core.DragDrop/Lattice.Core.DragDrop.csproj deleted file mode 100644 index c18ab9e..0000000 --- a/Lattice.Core.DragDrop/Lattice.Core.DragDrop.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - net8.0;net9.0;net10.0 - enable - enable - latest - true - true - Lattice.Core.DragDrop - 1.0.0 - FrigaT - Professional drag-and-drop system for Lattice UI Framework - ui;framework;drag;drop;docking;toolbox - - - - - - diff --git a/Lattice.Core.DragDrop/Models/DragInfo.cs b/Lattice.Core.DragDrop/Models/DragInfo.cs deleted file mode 100644 index 8547d1e..0000000 --- a/Lattice.Core.DragDrop/Models/DragInfo.cs +++ /dev/null @@ -1,247 +0,0 @@ -using Lattice.Core.Geometry; -using System.Collections.Concurrent; - -namespace Lattice.Core.DragDrop.Models; - -/// -/// Содержит информацию о начале операции перетаскивания. -/// Этот класс передается от источника перетаскивания к системе перетаскивания -/// для инициализации и управления операцией. -/// -/// -/// -/// является ключевым компонентом системы перетаскивания, -/// инкапсулирующим все необходимые данные для начала операции. Он содержит: -/// -/// -/// Данные для передачи -/// Разрешенные эффекты перетаскивания -/// Начальную позицию операции -/// Ссылку на источник перетаскивания -/// Дополнительные параметры операции -/// -/// -/// Этот класс используется как внутренний механизм передачи данных между -/// и системой управления перетаскиванием. -/// -/// -public class DragInfo : IDisposable, ICloneable -{ - private readonly ConcurrentDictionary _parameters = new(); - private bool _disposed; - - /// - /// Получает данные, которые передаются в операции перетаскивания. - /// - /// - /// Объект, содержащий данные для передачи. Может быть любого типа, - /// поддерживаемого системой перетаскивания. - /// - /// - /// Эти данные будут доступны цели сброса через . - /// Важно, чтобы данные были сериализуемыми, если операция перетаскивания - /// может выходить за пределы процесса приложения. - /// - public object Data { get; } - - /// - /// Получает разрешенные эффекты для этой операции перетаскивания. - /// - /// - /// Комбинация флагов , определяющая, - /// какие операции разрешены для этого перетаскивания. - /// - /// - /// Этот параметр используется системой для фильтрации допустимых операций - /// и предоставления соответствующей визуальной обратной связи пользователю. - /// - public Enums.DragDropEffects AllowedEffects { get; } - - /// - /// Получает начальную позицию операции перетаскивания в координатах экрана. - /// - /// - /// Точка в экранных координатах, где была начата операция перетаскивания. - /// - /// - /// Эта позиция используется для вычисления смещения при создании визуального - /// представления перетаскивания и для определения порога начала операции. - /// - public Point StartPosition { get; } - - /// - /// Получает источник перетаскивания, который инициировал операцию. - /// - /// - /// Объект, реализующий , или null, - /// если источник не доступен или не требуется. - /// - /// - /// Эта ссылка может использоваться для уведомления источника о результате - /// операции перетаскивания (завершении или отмене). - /// - public object? Source { get; } - - /// - /// Получает или задает дополнительные параметры, специфичные для конкретной - /// реализации перетаскивания. - /// - /// - /// Словарь, содержащий пары ключ-значение с дополнительными параметрами. - /// - /// - /// Используется для передачи контекстной информации, которая не входит - /// в стандартный набор свойств, но может быть полезной для обработки - /// операции перетаскивания. - /// - public IReadOnlyDictionary Parameters => _parameters; - - /// - /// Инициализирует новый экземпляр класса . - /// - /// - /// Данные, которые передаются в операции перетаскивания. - /// Не может быть null. - /// - /// - /// Разрешенные эффекты для этой операции перетаскивания. - /// - /// - /// Начальная позиция операции перетаскивания в координатах экрана. - /// - /// - /// Источник перетаскивания, который инициировал операцию. Может быть null. - /// - /// - /// Выбрасывается, когда равен null. - /// - /// - /// Конструктор создает экземпляр с указанными - /// параметрами и инициализирует коллекцию параметров пустым словарем. - /// - public DragInfo(object data, Enums.DragDropEffects allowedEffects, Point startPosition, object? source = null) - { - Data = data ?? throw new ArgumentNullException(nameof(data)); - - // Проверка допустимых значений перечисления - if (!Enum.IsDefined(typeof(Enums.DragDropEffects), allowedEffects)) - { - throw new ArgumentException( - $"Недопустимое значение для {nameof(allowedEffects)}: {allowedEffects}", - nameof(allowedEffects)); - } - - AllowedEffects = allowedEffects; - StartPosition = startPosition; - Source = source; - } - - /// - /// Создает новый экземпляр с теми же данными, - /// но новой позицией. - /// - /// - /// Новая позиция для информации о перетаскивании. - /// - /// - /// Новый экземпляр с обновленной позицией. - /// - /// - /// Этот метод используется для обновления информации о перетаскивании - /// при перемещении курсора, сохраняя исходные данные и параметры. - /// - public DragInfo CloneWithPosition(Point newPosition) - { - ThrowIfDisposed(); - - var clone = new DragInfo(Data, AllowedEffects, newPosition, Source) - { - _disposed = false, - }; - - foreach (var kvp in _parameters) - { - clone._parameters[kvp.Key] = kvp.Value; - } - - return clone; - } - - /// - /// Создает новый экземпляр с теми же данными. - /// - public DragInfo Clone() => new DragInfo(Data, AllowedEffects, StartPosition, Source); - - /// - object ICloneable.Clone() => this.Clone(); - - /// - /// Получает или дополнительные параметры, специфичные для конкретной - /// реализации перетаскивания. - /// - public T? GetParameter(string key, T? defaultValue = default) - { - if (Parameters.TryGetValue(key, out var value) && value is T typedValue) - { - return typedValue; - } - return defaultValue; - } - - /// - /// Получает или дополнительные параметры, специфичные для конкретной - /// реализации перетаскивания. - /// - public bool TryGetParameter(string key, out T? value) - { - value = default; - - if (_parameters.TryGetValue(key, out var objValue) && objValue is T typedValue) - { - value = typedValue; - return true; - } - - return false; - } - - /// - /// Задает дополнительные параметры, специфичные для конкретной - /// реализации перетаскивания. - /// - public void SetParameter(string key, T value) - { - _parameters[key] = value!; - } - - /// - /// Освобождает ресурсы. - /// - public void Dispose() - { - if (_disposed) return; - - foreach (var value in _parameters.Values) - { - if (value is IDisposable disposable) - { - disposable.Dispose(); - } - } - - _parameters.Clear(); - _disposed = true; - GC.SuppressFinalize(this); - } - - private void ThrowIfDisposed() - { - if (_disposed) - throw new ObjectDisposedException(nameof(DragInfo)); - } - - ~DragInfo() - { - Dispose(); - } -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Models/DropInfo.cs b/Lattice.Core.DragDrop/Models/DropInfo.cs deleted file mode 100644 index 66dd2f6..0000000 --- a/Lattice.Core.DragDrop/Models/DropInfo.cs +++ /dev/null @@ -1,281 +0,0 @@ -using Lattice.Core.DragDrop.Enums; -using Lattice.Core.Geometry; - -namespace Lattice.Core.DragDrop.Models; - -/// -/// Содержит информацию о потенциальном или фактическом сбросе в операции перетаскивания. -/// Этот класс используется для передачи данных между системой перетаскивания -/// и целью сброса (). -/// -/// -/// -/// предоставляет цель сброса всей необходимой информацией -/// для принятия решения о возможности сброса и выполнения соответствующей операции. -/// Ключевые аспекты включают: -/// -/// -/// Предлагаемые для сброса данные -/// Текущую позицию курсора -/// Разрешенные эффекты от источника -/// Предлагаемые эффекты для сброса -/// Ссылку на цель сброса -/// Флаг обработки операции -/// -/// -/// Этот класс является изменяемым, позволяя цели сброса обновлять предлагаемые -/// эффекты и помечать операцию как обработанную. -/// -/// -public class DropInfo -{ - private DragDropEffects _effects = DragDropEffects.None; - - /// - /// Получает или задает позицию сброса относительно цели. - /// - public DropPosition DropPosition { get; set; } = DropPosition.Inside; - - /// - /// Получает или задает значение, указывающее, нужно ли показывать визуальную обратную связь. - /// - public bool ShowVisualFeedback { get; set; } = true; - - /// - /// Получает или задает данные для визуальной обратной связи. - /// - public object? VisualFeedbackData { get; set; } - - /// - /// Получает данные, которые предлагаются для сброса. - /// - /// - /// Данные, переданные от источника перетаскивания, или null, если данные - /// не доступны или операция была отменена. - /// - /// - /// Эти данные соответствуют свойству из - /// исходной информации о перетаскивании. - /// - public object? Data { get; } - - /// - /// Получает текущую позицию курсора в координатах экрана. - /// - /// - /// Точка в экранных координатах, представляющая текущее положение курсора - /// мыши во время операции перетаскивания. - /// - /// - /// Эта позиция используется для определения точного места сброса и может - /// влиять на предлагаемые эффекты (например, различные операции для - /// разных областей цели сброса). - /// - public Point Position { get; } - - /// - /// Получает разрешенные эффекты от источника перетаскивания. - /// - /// - /// Комбинация флагов , определяющая, - /// какие операции разрешил источник. - /// - /// - /// Цель сброса должна уважать эти ограничения и не предлагать эффекты, - /// которые не разрешены источником. - /// - public Enums.DragDropEffects AllowedEffects { get; } - - /// - /// Получает или задает предлагаемые эффекты для операции сброса. - /// - /// - /// Комбинация флагов , предлагаемая - /// целью сброса. По умолчанию равно . - /// - /// - /// - /// Цель сброса должна установить это свойство в методе - /// на основе анализа предоставленных данных и текущего контекста. - /// - /// - /// Если цель не устанавливает это свойство, система перетаскивания - /// будет использовать эффекты по умолчанию. - /// - /// - public Enums.DragDropEffects SuggestedEffects - { - get => _effects; - set => _effects = value; - } - - /// - /// Получает цель сброса, которая обрабатывает эту информацию. - /// - /// - /// Объект, реализующий , или null, - /// если цель не определена. - /// - /// - /// Эта ссылка позволяет системе идентифицировать, какая цель обрабатывает - /// информацию о сбросе, и используется для отслеживания изменений цели - /// во время операции перетаскивания. - /// - public object? Target { get; } - - /// - /// Получает или задает дополнительные параметры, специфичные для конкретной - /// реализации перетаскивания. - /// - /// - /// Словарь, содержащий пары ключ-значение с дополнительными параметрами. - /// - /// - /// Может использоваться для передачи контекстной информации между - /// различными компонентами системы перетаскивания или для хранения - /// временных данных во время обработки операции. - /// - public Dictionary Parameters { get; set; } - - /// - /// Получает значение, указывающее, был ли сброс уже обработан. - /// - /// - /// true, если операция сброса была помечена как обработанная; - /// в противном случае — false. - /// - /// - /// - /// Это свойство используется для предотвращения множественной обработки - /// одной и той же операции сброса. После вызова метода , - /// свойство становится true. - /// - /// - /// Система перетаскивания может проверять это свойство, чтобы определить, - /// нужно ли выполнять дополнительную обработку по умолчанию. - /// - /// - public bool Handled { get; private set; } - - /// - /// Получает дополнительные параметры, специфичные для конкретной - /// реализации перетаскивания. - /// - public T? GetParameter(string key, T? defaultValue = default) - { - if (Parameters.TryGetValue(key, out var value) && value is T typedValue) - { - return typedValue; - } - return defaultValue; - } - - /// - /// Получает дополнительные параметры, специфичные для конкретной - /// реализации перетаскивания. - /// - public bool TryGetParameter(string key, out T? value) - { - value = default; - - if (Parameters.TryGetValue(key, out var objValue) && objValue is T typedValue) - { - value = typedValue; - return true; - } - - return false; - } - - /// - /// Задает дополнительные параметры, специфичные для конкретной - /// реализации перетаскивания. - /// - public void SetParameter(string key, T value) - { - Parameters[key] = value!; - } - - /// - /// Инициализирует новый экземпляр класса . - /// - /// - /// Данные, которые предлагаются для сброса. Может быть null. - /// - /// - /// Текущая позиция курсора в координатах экрана. - /// - /// - /// Разрешенные эффекты от источника перетаскивания. - /// - /// - /// Цель сброса, которая обрабатывает эту информацию. Может быть null. - /// - /// - /// Конструктор создает экземпляр с указанными - /// параметрами, инициализирует коллекцию параметров пустым словарем - /// и устанавливает флаг в false. - /// - public DropInfo(object? data, Point position, Enums.DragDropEffects allowedEffects, object? target = null) - { - Data = data; - Position = position; - AllowedEffects = allowedEffects; - Target = target; - Parameters = new Dictionary(); - Handled = false; - } - - /// - /// Помечает сброс как обработанный. - /// - /// - /// - /// Этот метод должен вызываться целью сброса в методе , - /// если она успешно обработала операцию сброса. - /// - /// - /// После вызова этого метода свойство становится true, - /// что сигнализирует системе перетаскивания о том, что дополнительная - /// обработка не требуется. - /// - /// - public void MarkAsHandled() - { - Handled = true; - } - - /// - /// Создает новый экземпляр с теми же данными, - /// но новой позицией. - /// - /// - /// Новая позиция для информации о сбросе. - /// - /// - /// Новый экземпляр с обновленной позицией. - /// - /// - /// Этот метод используется для обновления информации о сбросе при - /// перемещении курсора, сохраняя исходные данные и параметры. - /// - public DropInfo WithPosition(Point newPosition) - { - return new DropInfo(Data, newPosition, AllowedEffects, Target) - { - Parameters = new Dictionary(Parameters), - SuggestedEffects = _effects, - DropPosition = DropPosition, - ShowVisualFeedback = ShowVisualFeedback, - VisualFeedbackData = VisualFeedbackData - }; - } - - /// - /// Проверка установки эффекта перетаскивания в разрешенные эффекты. - /// - public bool CanAcceptEffect(Enums.DragDropEffects effect) - { - return (AllowedEffects & effect) != Enums.DragDropEffects.None; - } -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/README.md b/Lattice.Core.DragDrop/README.md deleted file mode 100644 index 2a1dba8..0000000 --- a/Lattice.Core.DragDrop/README.md +++ /dev/null @@ -1,829 +0,0 @@ -# Lattice.Core.DragDrop - -Профессиональная, асинхронная система перетаскивания для .NET приложений. Полностью потокобезопасная, расширяемая архитектура с поддержкой кросс-платформенности. - -## 📋 Особенности - -- ✅ **Полная асинхронная поддержка** - async/await для всех операций -- ✅ **Потокобезопасность** - `ReaderWriterLockSlim` для эффективной синхронизации -- ✅ **Производительность** - Оптимизированные алгоритмы, кэширование, минимальные аллокации -- ✅ **Расширяемость** - Легкая интеграция с любыми UI фреймворками -- ✅ **Надежность** - Таймауты, обработка ошибок, корректное освобождение ресурсов -- ✅ **Статистика** - Встроенный мониторинг производительности - -## 🏗️ Архитектура - -### Основные компоненты - -``` -Lattice.Core.DragDrop/ -├── Abstractions/ # Интерфейсы -│ ├── IDragSource.cs # Источник перетаскивания (синхронный) -│ ├── IAsyncDragSource.cs # Асинхронный источник -│ ├── IDropTarget.cs # Цель сброса (синхронная) -│ └── IAsyncDropTarget.cs # Асинхронная цель -├── Enums/ # Перечисления -├── Exceptions/ # Исключения с кодами ошибок -├── Extensions/ # Расширения для DI -├── Models/ # Модели данных -│ ├── DragInfo.cs # Информация о перетаскивании -│ └── DropInfo.cs # Информация о сбросе -└── Services/ # Сервисы - ├── IDragDropService.cs # Основной интерфейс - ├── DragDropService.cs # Реализация сервиса - └── EventArgs/ # Аргументы событий -``` - -## 🚀 Быстрый старт - -### 1. Установка - -```csharp -// Добавьте проект Lattice.Core.DragDrop в ваше решение -// или создайте NuGet пакет -``` - -### 2. Базовое использование - -```csharp -using Lattice.Core.DragDrop; -using Lattice.Core.DragDrop.Abstractions; -using Lattice.Core.DragDrop.Services; -using Lattice.Core.Geometry; - -// Создаем сервис -var dragDropService = new DragDropService(); - -// Создаем простой источник перетаскивания -var dragSource = DragDropUtilities.CreateSimpleDragSource( - dataProvider: () => "Example Data", - canDrag: () => true, - onCompleted: (dragInfo, effects) => - Console.WriteLine($"Drag completed with effects: {effects}"), - onCancelled: dragInfo => - Console.WriteLine("Drag cancelled") -); - -// Создаем простую цель сброса -var dropTarget = DragDropUtilities.CreateSimpleDropTarget( - canAccept: dropInfo => dropInfo.Data is string, - onDragOver: dropInfo => - dropInfo.SuggestedEffects = DragDropEffects.Copy, - onDrop: dropInfo => - { - Console.WriteLine($"Dropped: {dropInfo.Data}"); - dropInfo.MarkAsHandled(); - } -); - -// Регистрируем цель -string targetId = dragDropService.RegisterDropTarget( - dropTarget, - new Rect(100, 100, 300, 200) -); - -// Начинаем перетаскивание -bool started = dragDropService.StartDrag( - dragSource, - new Point(50, 50) -); - -if (started) -{ - // Обновляем позицию - dragDropService.UpdateDrag(new Point(150, 150)); - - // Завершаем - var effects = dragDropService.EndDrag(new Point(200, 200)); -} -``` - -## 📖 Подробное руководство - -### Сервис перетаскивания - -Основной класс системы - `DragDropService`, реализующий `IDragDropService`. - -```csharp -// Создание с кастомными настройками -var service = new DragDropService(options => -{ - options.DragStartThreshold = 5.0; - options.EnableAsyncOperations = true; - options.AsyncOperationTimeout = 3000; - options.EnableAutoCleanup = true; -}); - -// Свойства -bool isActive = service.IsDragActive; // Активна ли операция -DragInfo? currentDrag = service.CurrentDragInfo; // Текущая информация -double threshold = service.DragStartThreshold; // Порог начала - -// События -service.DragStarted += OnDragStarted; -service.DragUpdated += OnDragUpdated; -service.DragCompleted += OnDragCompleted; -service.DragCancelled += OnDragCancelled; -service.ErrorOccurred += OnErrorOccurred; - -// Регистрация целей -string id = service.RegisterDropTarget( - target, // IDropTarget - bounds, // Rect - priority: 1, // Приоритет (выше = выше приоритет) - group: "main" // Группа для группового удаления -); - -// Обновление границ -service.UpdateDropTargetBounds(id, newBounds); - -// Удаление -service.UnregisterDropTarget(id); -service.UnregisterDropTargetsInGroup("main"); -``` - -### Асинхронное использование - -```csharp -// Асинхронные методы -bool started = await service.StartDragAsync(source, startPosition); -await service.UpdateDragAsync(currentPosition); -DragDropEffects effects = await service.EndDragAsync(dropPosition); -await service.CancelDragAsync(); - -// Статистика -var stats = service.GetStats(); -Console.WriteLine($"Operations: {stats.TotalDragOperations}"); -Console.WriteLine($"Success rate: {stats.SuccessfulDrops}/{stats.TotalDragOperations}"); -Console.WriteLine($"Avg time: {stats.AverageOperationTime.TotalMilliseconds}ms"); -``` - -### Создание кастомных источников и целей - -#### Синхронная реализация - -```csharp -public class FileDragSource : IDragSource -{ - private readonly FileInfo _file; - - public FileDragSource(FileInfo file) => _file = file; - - public bool CanStartDrag(out DragInfo? dragInfo) - { - // Проверяем условия - if (!_file.Exists || _file.Length > 100 * 1024 * 1024) // 100 MB limit - { - dragInfo = null; - return false; - } - - // Создаем DragInfo - dragInfo = new DragInfo( - data: _file, - allowedEffects: DragDropEffects.Copy | DragDropEffects.Move, - startPosition: Point.Zero, - source: this - ); - - // Добавляем дополнительные параметры - dragInfo.SetParameter("FileSize", _file.Length); - dragInfo.SetParameter("MimeType", GetMimeType(_file)); - - return true; - } - - public bool StartDrag(DragInfo dragInfo) - { - // Подготовка к перетаскиванию - // Можно создать визуальное представление и т.д. - Console.WriteLine($"Starting drag of {_file.Name}"); - return true; - } - - public void DragCompleted(DragInfo dragInfo, DragDropEffects effects) - { - Console.WriteLine($"File drag completed with {effects}"); - - if (effects == DragDropEffects.Move) - { - // Файл был перемещен - возможно, удалить оригинал - // _file.Delete(); - } - } - - public void DragCancelled(DragInfo dragInfo) - { - Console.WriteLine("File drag cancelled"); - } -} -``` - -#### Асинхронная реализация - -```csharp -public class DatabaseItemDragSource : IAsyncDragSource -{ - private readonly DatabaseService _db; - private readonly int _itemId; - - public async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync() - { - try - { - // Асинхронные проверки - var canDrag = await _db.CanDragItemAsync(_itemId); - if (!canDrag) return (false, null); - - // Асинхронная загрузка данных - var data = await _db.GetItemForDragAsync(_itemId); - if (data == null) return (false, null); - - var dragInfo = new DragInfo( - data: data, - allowedEffects: DragDropEffects.Copy | DragDropEffects.Move, - startPosition: Point.Zero, - source: this - ); - - return (true, dragInfo); - } - catch (Exception ex) - { - // Логирование ошибки - return (false, null); - } - } - - public Task StartDragAsync(DragInfo dragInfo) - { - // Асинхронная подготовка - return Task.FromResult(true); - } - - public async Task DragCompletedAsync(DragInfo dragInfo, DragDropEffects effects) - { - // Асинхронная обработка завершения - await _db.LogDragOperationAsync(_itemId, effects); - - if (effects == DragDropEffects.Move) - { - await _db.MarkItemAsMovedAsync(_itemId); - } - } - - public Task DragCancelledAsync(DragInfo dragInfo) - { - return Task.CompletedTask; - } - - // Синхронные методы для совместимости - public bool CanStartDrag(out DragInfo? dragInfo) - { - var result = Task.Run(() => CanStartDragAsync()).GetAwaiter().GetResult(); - dragInfo = result.DragInfo; - return result.CanStart; - } - - // ... остальные синхронные методы -} -``` - -### Работа с моделями данных - -#### DragInfo - -```csharp -// Создание -var dragInfo = new DragInfo( - data: myObject, - allowedEffects: DragDropEffects.Copy | DragDropEffects.Move, - startPosition: new Point(x, y), - source: this -); - -// Параметры -dragInfo.SetParameter("Timestamp", DateTime.UtcNow); -dragInfo.SetParameter("UserId", currentUser.Id); - -// Получение параметров -if (dragInfo.TryGetParameter("Category", out var category)) -{ - // Используем категорию -} - -// Клонирование с новой позицией -var updatedDragInfo = dragInfo.CloneWithPosition(newPosition); - -// Очистка ресурсов -dragInfo.Dispose(); -``` - -#### DropInfo - -```csharp -// Создается сервисом автоматически -// Работа с DropInfo в методах цели: - -public void DragOver(DropInfo dropInfo) -{ - // Проверяем данные - if (dropInfo.Data is MyDataType myData) - { - // Определяем позицию относительно цели - dropInfo.DropPosition = CalculateDropPosition(dropInfo.Position); - - // Предлагаем эффекты - if (CanAcceptData(myData)) - { - dropInfo.SuggestedEffects = DragDropEffects.Move; - dropInfo.ShowVisualFeedback = true; - dropInfo.VisualFeedbackData = CreatePreview(myData); - } - else - { - dropInfo.SuggestedEffects = DragDropEffects.None; - } - } -} - -public void Drop(DropInfo dropInfo) -{ - if (dropInfo.Data is MyDataType myData) - { - // Обработка сброса - ProcessDrop(myData, dropInfo.DropPosition); - - // Помечаем как обработанное - dropInfo.MarkAsHandled(); - } -} -``` - -### Утилиты и фабрики - -#### Синхронные утилиты - -```csharp -// Простые реализации -var simpleSource = DragDropUtilities.CreateSimpleDragSource( - () => data, - () => true, - (dragInfo, effects) => Console.WriteLine("Completed"), - dragInfo => Console.WriteLine("Cancelled") -); - -var simpleTarget = DragDropUtilities.CreateSimpleDropTarget( - dropInfo => dropInfo.Data != null, - dropInfo => dropInfo.SuggestedEffects = DragDropEffects.Copy, - dropInfo => Console.WriteLine($"Dropped: {dropInfo.Data}"), - () => Console.WriteLine("Drag left") -); - -// Геометрия -double distance = DragDropUtilities.CalculateDistance(p1, p2); -bool exceeded = DragDropUtilities.HasExceededDragThreshold(start, current, threshold); -DropPosition position = DragDropUtilities.GetDropPosition(point, bounds, edgeThreshold); - -// Проверка совместимости -bool compatible = DragDropUtilities.AreEffectsCompatible(sourceEffects, targetEffects); -bool typeMatch = DragDropUtilities.IsDataCompatible(data, new[] { typeof(string), typeof(int) }); -``` - -#### Асинхронные утилиты - -```csharp -// Асинхронные реализации -var asyncSource = AsyncDragDropUtilities.CreateAsyncDragSource( - async () => await LoadDataAsync(), - async () => await CanDragAsync(), - async (dragInfo, effects) => await OnCompletedAsync(dragInfo, effects), - async dragInfo => await OnCancelledAsync(dragInfo) -); - -var asyncTarget = AsyncDragDropUtilities.CreateAsyncDropTarget( - async dropInfo => await CanAcceptAsync(dropInfo.Data), - async dropInfo => await OnDragOverAsync(dropInfo), - async dropInfo => await OnDropAsync(dropInfo), - async () => await OnDragLeaveAsync() -); - -// Адаптеры для синхронных интерфейсов -IAsyncDragSource asyncFromSync = AsyncDragDropUtilities.CreateAsyncAdapter(syncSource); -IAsyncDropTarget asyncTargetFromSync = AsyncDragDropUtilities.CreateAsyncAdapter(syncTarget); - -// Комбинированные реализации (fallback стратегия) -var combined = AsyncDragDropUtilities.Combine( - syncSource, - asyncSource, - preferAsync: true // При ошибке в async использует sync -); - -// Таймауты -var result = await AsyncDragDropUtilities.ExecuteWithTimeoutAsync( - task: LongOperationAsync(), - timeout: TimeSpan.FromSeconds(5), - defaultValue: fallbackValue -); -``` - -### Обработка ошибок - -```csharp -// Подписка на ошибки -service.ErrorOccurred += (sender, e) => -{ - Console.WriteLine($"Error in {e.Operation}: {e.Exception.Message}"); - - // Коды ошибок определены в DragDropErrorCodes - switch (e.ErrorCode) - { - case DragDropErrorCodes.Timeout: - Console.WriteLine("Operation timed out"); - break; - case DragDropErrorCodes.SourceCannotDrag: - Console.WriteLine("Source cannot drag"); - break; - case DragDropErrorCodes.TargetCannotAccept: - Console.WriteLine("Target cannot accept"); - break; - } -}; - -// Использование в коде -try -{ - await service.StartDragAsync(source, position); -} -catch (DragDropException ex) -{ - // Обработка специфичных для DragDrop ошибок - Console.WriteLine($"DragDrop error {ex.ErrorCode}: {ex.Message}"); -} -catch (Exception ex) -{ - // Обработка других ошибок - Console.WriteLine($"General error: {ex.Message}"); -} -``` - -## 🔧 Интеграция с UI фреймворками - -### Базовый адаптер для WinUI/WPF - -```csharp -public class UIElementDragSource : IAsyncDragSource -{ - private readonly FrameworkElement _element; - private readonly Func _dataProvider; - - public UIElementDragSource(FrameworkElement element, Func dataProvider) - { - _element = element; - _dataProvider = dataProvider; - - // Подписка на события - _element.PointerPressed += OnPointerPressed; - _element.PointerMoved += OnPointerMoved; - _element.PointerReleased += OnPointerReleased; - } - - private Point _dragStartPosition; - private bool _isDragging; - - private void OnPointerPressed(object sender, PointerRoutedEventArgs e) - { - var point = e.GetCurrentPoint(_element); - _dragStartPosition = new Point(point.Position.X, point.Position.Y); - } - - private async void OnPointerMoved(object sender, PointerRoutedEventArgs e) - { - if (_isDragging) return; - - var point = e.GetCurrentPoint(_element); - var current = new Point(point.Position.X, point.Position.Y); - - var distance = Math.Sqrt( - Math.Pow(current.X - _dragStartPosition.X, 2) + - Math.Pow(current.Y - _dragStartPosition.Y, 2)); - - if (distance > 3.0) // Порог - { - _isDragging = true; - - // Начинаем перетаскивание через сервис - var service = GetDragDropService(); - await service.StartDragAsync(this, ConvertToScreen(_dragStartPosition)); - } - } - - public async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync() - { - var data = _dataProvider(); - if (data == null) return (false, null); - - var dragInfo = new DragInfo( - data, - DragDropEffects.Copy | DragDropEffects.Move, - Point.Zero, - this - ); - - return (true, dragInfo); - } - - // ... остальная реализация -} -``` - -## 🧪 Тестирование - -### Примеры модульных тестов - -```csharp -[TestClass] -public class DragDropServiceTests -{ - private DragDropService _service; - private Mock _mockSource; - private Mock _mockTarget; - - [TestInitialize] - public void Setup() - { - _service = new DragDropService(); - _mockSource = new Mock(); - _mockTarget = new Mock(); - } - - [TestMethod] - public async Task StartDrag_ValidSource_ReturnsTrue() - { - // Arrange - var dragInfo = new DragInfo("test", DragDropEffects.Copy, Point.Zero); - _mockSource.Setup(s => s.CanStartDragAsync()) - .ReturnsAsync((true, dragInfo)); - _mockSource.Setup(s => s.StartDragAsync(It.IsAny())) - .ReturnsAsync(true); - - // Act - var result = await _service.StartDragAsync(_mockSource.Object, Point.Zero); - - // Assert - Assert.IsTrue(result); - Assert.IsTrue(_service.IsDragActive); - } - - [TestMethod] - public async Task UpdateDrag_FindsTarget_CallsDragOver() - { - // Arrange - var targetId = _service.RegisterDropTarget( - _mockTarget.Object, - new Rect(0, 0, 100, 100) - ); - - await StartTestDrag(); - - _mockTarget.Setup(t => t.CanAcceptDropAsync(It.IsAny())) - .ReturnsAsync(true); - - // Act - await _service.UpdateDragAsync(new Point(50, 50)); - - // Assert - _mockTarget.Verify(t => t.DragOverAsync(It.IsAny()), Times.Once); - } - - private async Task StartTestDrag() - { - var dragInfo = new DragInfo("test", DragDropEffects.Copy, Point.Zero); - _mockSource.Setup(s => s.CanStartDragAsync()) - .ReturnsAsync((true, dragInfo)); - _mockSource.Setup(s => s.StartDragAsync(It.IsAny())) - .ReturnsAsync(true); - - await _service.StartDragAsync(_mockSource.Object, Point.Zero); - } -} -``` - -## 📊 Мониторинг и производительность - -### Сбор статистики - -```csharp -// Получение статистики -var stats = service.GetStats(); - -Console.WriteLine($"Total operations: {stats.TotalDragOperations}"); -Console.WriteLine($"Successful: {stats.SuccessfulDrops}"); -Console.WriteLine($"Cancelled: {stats.CancelledOperations}"); -Console.WriteLine($"Errors: {stats.ErrorCount}"); -Console.WriteLine($"Avg time: {stats.AverageOperationTime.TotalMilliseconds}ms"); - -// Мониторинг в реальном времени -private Stopwatch _operationTimer; - -service.DragStarted += (s, e) => -{ - _operationTimer = Stopwatch.StartNew(); - Console.WriteLine($"Drag started from {e.DragInfo.Source}"); -}; - -service.DragCompleted += (s, e) => -{ - _operationTimer.Stop(); - Console.WriteLine($"Drag completed in {_operationTimer.ElapsedMilliseconds}ms"); - - if (service.EnableAsyncOperations) - { - var stats = service.GetStats(); - Console.WriteLine($"Success rate: {(double)stats.SuccessfulDrops / stats.TotalDragOperations:P}"); - } -}; -``` - -### Оптимизация производительности - -```csharp -// 1. Настройка параметров -var service = new DragDropService(options => -{ - options.DragStartThreshold = 4.0; // Увеличить порог для предотвращения случайных перетаскиваний - options.AsyncOperationTimeout = 2000; // Уменьшить таймаут для отзывчивости - options.EnableAutoCleanup = true; // Автоочистка неиспользуемых целей -}); - -// 2. Группировка целей -_service.RegisterDropTarget(target1, bounds1, group: "toolbox"); -_service.RegisterDropTarget(target2, bounds2, group: "toolbox"); -// Быстрое удаление всех целей группы -_service.UnregisterDropTargetsInGroup("toolbox"); - -// 3. Приоритеты для оптимизации поиска -_service.RegisterDropTarget(importantTarget, bounds, priority: 100); // Высокий приоритет -_service.RegisterDropTarget(defaultTarget, bounds, priority: 0); // Низкий приоритет - -// 4. Периодическая очистка -service.ClearAllDropTargets(); // При смене контекста -``` - -## 🚀 Продвинутые сценарии - -### Переупорядочивание элементов - -```csharp -public class ReorderableListDropTarget : IAsyncDropTarget -{ - private readonly IList _items; - - public async Task CanAcceptDropAsync(DropInfo dropInfo) - { - return dropInfo.Data is object && _items.Contains(dropInfo.Data); - } - - public async Task DropAsync(DropInfo dropInfo) - { - var item = dropInfo.Data; - var insertIndex = CalculateInsertIndex(dropInfo); - - // Удаляем из старой позиции - _items.Remove(item); - - // Вставляем в новую позицию - if (insertIndex < _items.Count) - _items.Insert(insertIndex, item); - else - _items.Add(item); - - dropInfo.MarkAsHandled(); - } - - private int CalculateInsertIndex(DropInfo dropInfo) - { - // Логика определения позиции вставки на основе dropInfo.Position - // и визуального расположения элементов - return 0; - } -} -``` - -### Мультиселект и групповое перетаскивание - -```csharp -public class MultiSelectionDragSource : IAsyncDragSource -{ - private readonly IEnumerable _selectedItems; - - public async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync() - { - if (!_selectedItems.Any()) return (false, null); - - // Создаем коллекцию для перетаскивания - var dragData = new DragItemCollection(_selectedItems); - - var dragInfo = new DragInfo( - dragData, - DragDropEffects.Copy | DragDropEffects.Move, - Point.Zero, - this - ); - - dragInfo.SetParameter("ItemCount", _selectedItems.Count()); - dragInfo.SetParameter("IsMultiSelect", true); - - return (true, dragInfo); - } -} -``` - -## 📚 API Reference - -### Основные интерфейсы - -#### IDragDropService -```csharp -bool IsDragActive { get; } -DragInfo? CurrentDragInfo { get; } -IDropTarget? CurrentDropTarget { get; } -double DragStartThreshold { get; set; } -bool EnableAsyncOperations { get; set; } - -// Регистрация целей -string RegisterDropTarget(IDropTarget target, Rect bounds, int priority = 0, string? group = null); -bool UpdateDropTargetBounds(string id, Rect bounds); -bool UnregisterDropTarget(string id); -void UnregisterDropTargetsInGroup(string group); - -// Асинхронные операции -Task StartDragAsync(IDragSource source, Point startPosition); -Task UpdateDragAsync(Point position); -Task EndDragAsync(Point position); -Task CancelDragAsync(); - -// Синхронные операции -bool StartDrag(IDragSource source, Point startPosition); -void UpdateDrag(Point position); -DragDropEffects EndDrag(Point position); -void CancelDrag(); - -// Утилиты -void ClearAllDropTargets(); -DragDropStats GetStats(); -``` - -#### IAsyncDragSource -```csharp -Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync(); -Task StartDragAsync(DragInfo dragInfo); -Task DragCompletedAsync(DragInfo dragInfo, DragDropEffects effects); -Task DragCancelledAsync(DragInfo dragInfo); -``` - -#### IAsyncDropTarget -```csharp -Task CanAcceptDropAsync(DropInfo dropInfo); -Task DragOverAsync(DropInfo dropInfo); -Task DropAsync(DropInfo dropInfo); -Task DragLeaveAsync(); -``` - -### Перечисления - -#### DragDropEffects -```csharp -[Flags] -None = 0 -Copy = 1 << 0 // Копирование данных -Move = 1 << 1 // Перемещение данных -Link = 1 << 2 // Ссылка на данные -CopyOrMove = Copy | Move -All = Copy | Move | Link - -// Методы расширения: -bool CanCopy(this DragDropEffects effects) -bool CanMove(this DragDropEffects effects) -bool CanLink(this DragDropEffects effects) -DragDropEffects GetEffectFromKeys(bool controlKey, bool shiftKey, bool altKey) -``` - -#### DropPosition -```csharp -Inside // Внутри элемента -Top // Сверху -Bottom // Снизу -Left // Слева -Right // Справа -Center // По центру -``` - -## 🔮 Планы развития - -1. **Интеграция с популярными UI фреймворками** (WinUI, Uno Platform, Avalonia) -2. **Поддержка жестов** (тач, мультитач) -3. **Виртуализация** для работы с большими наборами данных -4. **Продвинутые визуальные эффекты** (анимации, превью) -5. **Source Generators** для автоматической генерации кода -6. **Инструменты разработчика** (дебаггер, профилировщик) \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Services/DragDropService.cs b/Lattice.Core.DragDrop/Services/DragDropService.cs deleted file mode 100644 index fab0243..0000000 --- a/Lattice.Core.DragDrop/Services/DragDropService.cs +++ /dev/null @@ -1,846 +0,0 @@ -using Lattice.Core.DragDrop.Abstractions; -using Lattice.Core.DragDrop.Constants; -using Lattice.Core.DragDrop.Enums; -using Lattice.Core.DragDrop.Models; -using Lattice.Core.Geometry; - -namespace Lattice.Core.DragDrop.Services; - -/// -/// Центральный сервис управления операциями перетаскивания. -/// -/// -/// -/// является основным компонентом системы drag-and-drop, -/// который координирует взаимодействие между источниками данных () -/// и целями сброса (). -/// -/// -/// Основные функции сервиса: -/// -/// -/// Регистрация и управление целями сброса -/// Оркестрация жизненного цикла операций перетаскивания -/// Обработка событий мыши и клавиатуры -/// Распространение информации между компонентами -/// Обеспечение потокобезопасности операций -/// -/// -/// Сервис поддерживает полностью асинхронную модель работы, уведомления через события -/// и статистику использования. Все операции защищены от параллельного доступа -/// и обеспечивают корректную очистку ресурсов. -/// -/// -/// Для использования сервиса необходимо: -/// -/// Зарегистрировать цели сброса с помощью -/// Вызывать методы , , -/// в ответ на действия пользователя -/// Подписаться на события для отслеживания состояния операций -/// -/// -/// -public sealed class DragDropService : IDragDropService -{ - #region Nested Types - - /// - /// Информация о зарегистрированной цели сброса. - /// - private sealed class DropTargetInfo : IDisposable - { - public required Abstractions.IDropTarget Target { get; init; } - public required Geometry.Rect Bounds { get; set; } - public required int Priority { get; init; } - public required string? Group { get; init; } - public required string Id { get; init; } - public DateTime LastAccessTime { get; set; } = DateTime.UtcNow; - public int UsageCount { get; set; } - - public void Dispose() - { - if (Target is IDisposable disposable) - disposable.Dispose(); - } - } - - /// - /// Контекст текущей операции перетаскивания. - /// - private sealed class DragOperationContext : IDisposable - { - public Abstractions.IDragSource? Source { get; set; } - public Models.DragInfo? DragInfo { get; set; } - public Abstractions.IDropTarget? CurrentDropTarget { get; set; } - public CancellationTokenSource? CancellationTokenSource { get; set; } - public DateTime StartTime { get; set; } = DateTime.UtcNow; - public bool ThresholdExceeded { get; set; } - public Point LastPosition { get; set; } - - public void Dispose() - { - DragInfo?.Dispose(); - CancellationTokenSource?.Dispose(); - } - } - - #endregion - - #region Fields - - private readonly Dictionary _dropTargets = new(); - private readonly ReaderWriterLockSlim _dropTargetsLock = new(LockRecursionPolicy.NoRecursion); - private readonly object _dragOperationLock = new(); - - private DragOperationContext? _currentDragOperation; - private Timer? _cleanupTimer; - private bool _disposed; - - private int _totalDragOperations; - private int _successfulDrops; - private int _cancelledOperations; - private int _errorCount; - private long _totalOperationTicks; - - #endregion - - #region Events - - private event EventHandler? _dragStarted; - private event EventHandler? _dragUpdated; - private event EventHandler? _dropTargetChanged; - private event EventHandler? _dragCompleted; - private event EventHandler? _dragCancelled; - private event EventHandler? _errorOccurred; - - #endregion - - #region Properties - - /// - public bool IsDragActive => Volatile.Read(ref _currentDragOperation) != null; - - /// - public Models.DragInfo? CurrentDragInfo => _currentDragOperation?.DragInfo; - - /// - public Abstractions.IDropTarget? CurrentDropTarget => _currentDragOperation?.CurrentDropTarget; - - /// - public double DragStartThreshold { get; set; } = DragDropConstants.DefaultDragThreshold; - - /// - public bool EnableAsyncOperations { get; set; } = true; - - /// - public int AsyncOperationTimeout { get; set; } = DragDropConstants.DefaultAsyncTimeout; - - /// - public event EventHandler DragStarted - { - add => _dragStarted += value; - remove => _dragStarted -= value; - } - - /// - public event EventHandler DragUpdated - { - add => _dragUpdated += value; - remove => _dragUpdated -= value; - } - - /// - public event EventHandler DropTargetChanged - { - add => _dropTargetChanged += value; - remove => _dropTargetChanged -= value; - } - - /// - public event EventHandler DragCompleted - { - add => _dragCompleted += value; - remove => _dragCompleted -= value; - } - - /// - public event EventHandler DragCancelled - { - add => _dragCancelled += value; - remove => _dragCancelled -= value; - } - - /// - public event EventHandler ErrorOccurred - { - add => _errorOccurred += value; - remove => _errorOccurred -= value; - } - - #endregion - - #region Constructor - - /// - /// Инициализирует новый экземпляр класса . - /// - /// - /// Создает сервис с настройками по умолчанию: - /// - /// Порог начала перетаскивания: пикселей - /// Таймаут асинхронных операций: миллисекунд - /// Включены асинхронные операции: true - /// - /// - public DragDropService() - { - // Инициализация таймера очистки (каждые 5 минут) - _cleanupTimer = new Timer(CleanupExpiredTargets, null, - TimeSpan.FromMinutes(Constants.DragDropConstants.TargetLifetimeMinutes / 2), - TimeSpan.FromMinutes(Constants.DragDropConstants.TargetLifetimeMinutes / 2)); - } - - #endregion - - #region Registration Methods - - /// - public string RegisterDropTarget(Abstractions.IDropTarget target, Geometry.Rect bounds, int priority = 0, string? group = null) - { - ThrowIfDisposed(); - if (target == null) throw new ArgumentNullException(nameof(target)); - - var id = Guid.NewGuid().ToString("N"); - var info = new DropTargetInfo - { - Target = target, - Bounds = bounds, - Priority = priority, - Group = group, - Id = id - }; - - _dropTargetsLock.EnterWriteLock(); - try - { - _dropTargets[id] = info; - return id; - } - finally - { - _dropTargetsLock.ExitWriteLock(); - } - } - - /// - public bool UpdateDropTargetBounds(string id, Geometry.Rect bounds) - { - ThrowIfDisposed(); - - _dropTargetsLock.EnterUpgradeableReadLock(); - try - { - if (!_dropTargets.TryGetValue(id, out var info)) - return false; - - _dropTargetsLock.EnterWriteLock(); - try - { - info.Bounds = bounds; - info.LastAccessTime = DateTime.UtcNow; - return true; - } - finally - { - _dropTargetsLock.ExitWriteLock(); - } - } - finally - { - _dropTargetsLock.ExitUpgradeableReadLock(); - } - } - - /// - public bool UnregisterDropTarget(string id) - { - ThrowIfDisposed(); - - _dropTargetsLock.EnterWriteLock(); - try - { - if (_dropTargets.Remove(id, out var info)) - { - info.Dispose(); - return true; - } - return false; - } - finally - { - _dropTargetsLock.ExitWriteLock(); - } - } - - /// - public void UnregisterDropTargetsInGroup(string group) - { - ThrowIfDisposed(); - if (string.IsNullOrEmpty(group)) return; - - _dropTargetsLock.EnterWriteLock(); - try - { - var idsToRemove = new List(); - - foreach (var kvp in _dropTargets) - { - if (kvp.Value.Group == group) - { - idsToRemove.Add(kvp.Key); - } - } - - foreach (var id in idsToRemove) - { - if (_dropTargets.Remove(id, out var info)) - { - info.Dispose(); - } - } - } - finally - { - _dropTargetsLock.ExitWriteLock(); - } - } - - #endregion - - #region Async Operations - - /// - public async Task StartDragAsync(IDragSource source, Point startPosition) - { - ThrowIfDisposed(); - if (source == null) throw new ArgumentNullException(nameof(source)); - - lock (_dragOperationLock) - { - if (_currentDragOperation != null) - return false; - } - - try - { - Interlocked.Increment(ref _totalDragOperations); - - DragInfo? dragInfo; - - // Пытаемся начать перетаскивание - if (EnableAsyncOperations) - { - dragInfo = await ExecuteWithTimeoutAsync( - source.TryStartDragAsync(startPosition), - "TryStartDragAsync", - source); - } - else - { - dragInfo = await source.TryStartDragAsync(startPosition); - } - - if (dragInfo == null) - return false; - - var updatedDragInfo = dragInfo.CloneWithPosition(startPosition); - - lock (_dragOperationLock) - { - _currentDragOperation = new DragOperationContext - { - Source = source, - DragInfo = updatedDragInfo, - CancellationTokenSource = new CancellationTokenSource(), - LastPosition = startPosition - }; - } - - // Вызов события - _dragStarted?.Invoke(this, new DragStartedEventArgs(updatedDragInfo, startPosition)); - return true; - } - catch (Exception ex) - { - Interlocked.Increment(ref _errorCount); - HandleError(ex, "StartDragAsync", source); - return false; - } - } - - /// - public async Task UpdateDragAsync(Point position) - { - ThrowIfDisposed(); - - DragOperationContext? context; - lock (_dragOperationLock) - { - context = _currentDragOperation; - } - - if (context?.DragInfo == null || context.Source == null) - return; - - try - { - // Проверка порога начала - if (!context.ThresholdExceeded && DragStartThreshold > 0) - { - var distance = CalculateDistance(context.DragInfo.StartPosition, position); - if (distance < DragStartThreshold) - return; - - context.ThresholdExceeded = true; - } - - var updatedDragInfo = context.DragInfo.CloneWithPosition(position); - context.DragInfo.Dispose(); - context.DragInfo = updatedDragInfo; - context.LastPosition = position; - - // Поиск новой цели сброса - var newDropTarget = await FindDropTargetAsync(position, updatedDragInfo); - - // Обработка смены цели - if (context.CurrentDropTarget != newDropTarget?.Target) - { - if (context.CurrentDropTarget != null) - { - await ExecuteTargetOperationAsync( - context.CurrentDropTarget, - t => t.OnDragLeaveAsync(), - "OnDragLeave"); - } - - context.CurrentDropTarget = newDropTarget?.Target; - - if (newDropTarget != null) - { - newDropTarget.UsageCount++; - _dropTargetChanged?.Invoke(this, new DropTargetChangedEventArgs( - updatedDragInfo, position, newDropTarget.Target, newDropTarget.Bounds)); - } - } - - // Уведомление текущей цели - if (context.CurrentDropTarget != null) - { - var dropInfo = new DropInfo( - updatedDragInfo.Data, - position, - updatedDragInfo.AllowedEffects, - context.CurrentDropTarget); - - await ExecuteTargetOperationAsync( - context.CurrentDropTarget, - t => t.OnDragOverAsync(dropInfo), - "OnDragOver"); - } - - _dragUpdated?.Invoke(this, new DragUpdatedEventArgs(updatedDragInfo, position)); - } - catch (Exception ex) - { - Interlocked.Increment(ref _errorCount); - HandleError(ex, "UpdateDragAsync", context); - } - } - - /// - public async Task EndDragAsync(Point position) - { - ThrowIfDisposed(); - - DragOperationContext? context; - lock (_dragOperationLock) - { - context = _currentDragOperation; - _currentDragOperation = null; - } - - if (context == null || context.DragInfo == null || context.Source == null) - { - Reset(); - return DragDropEffects.None; - } - - try - { - var effects = DragDropEffects.None; - var operationTime = DateTime.UtcNow - context.StartTime; - Interlocked.Add(ref _totalOperationTicks, operationTime.Ticks); - - // Выполнение сброса - if (context.CurrentDropTarget != null) - { - var dropInfo = new DropInfo( - context.DragInfo.Data, - position, - context.DragInfo.AllowedEffects, - context.CurrentDropTarget); - - await ExecuteTargetOperationAsync( - context.CurrentDropTarget, - t => t.OnDropAsync(dropInfo), - "OnDrop"); - - if (dropInfo.Handled) - { - effects = dropInfo.SuggestedEffects; - Interlocked.Increment(ref _successfulDrops); - } - } - - // Уведомление источника - await ExecuteSourceOperationAsync( - context.Source, - s => s.OnDragCompletedAsync(context.DragInfo, effects), - "OnDragCompleted"); - - // Событие завершения - _dragCompleted?.Invoke(this, new DragCompletedEventArgs( - context.DragInfo, position, effects)); - - return effects; - } - catch (Exception ex) - { - Interlocked.Increment(ref _errorCount); - HandleError(ex, "EndDragAsync", context); - return DragDropEffects.None; - } - finally - { - context.Dispose(); - } - } - - /// - public async Task CancelDragAsync() - { - ThrowIfDisposed(); - - DragOperationContext? context; - lock (_dragOperationLock) - { - context = _currentDragOperation; - _currentDragOperation = null; - } - - if (context == null || context.DragInfo == null || context.Source == null) - { - Reset(); - return; - } - - try - { - context.CancellationTokenSource?.Cancel(); - Interlocked.Increment(ref _cancelledOperations); - - await ExecuteSourceOperationAsync( - context.Source, - s => s.OnDragCancelledAsync(context.DragInfo), - "OnDragCancelled"); - - _dragCancelled?.Invoke(this, new DragCancelledEventArgs(context.DragInfo, context.LastPosition)); - } - catch (Exception ex) - { - Interlocked.Increment(ref _errorCount); - HandleError(ex, "CancelDragAsync", context); - } - finally - { - context.Dispose(); - } - } - - #endregion - - #region Utility Methods - - /// - public void ClearAllDropTargets() - { - ThrowIfDisposed(); - - _dropTargetsLock.EnterWriteLock(); - try - { - foreach (var info in _dropTargets.Values) - { - info.Dispose(); - } - _dropTargets.Clear(); - } - finally - { - _dropTargetsLock.ExitWriteLock(); - } - } - - /// - public DragDropStats GetStats() - { - return new DragDropStats - { - TotalDragOperations = _totalDragOperations, - SuccessfulDrops = _successfulDrops, - CancelledOperations = _cancelledOperations, - ErrorCount = _errorCount, - RegisteredTargets = _dropTargets.Count, - AverageOperationTime = _totalDragOperations > 0 - ? TimeSpan.FromTicks(_totalOperationTicks / _totalDragOperations) - : TimeSpan.Zero - }; - } - - #endregion - - #region Private Helper Methods - - private async Task FindDropTargetAsync(Geometry.Point position, Models.DragInfo dragInfo) - { - DropTargetInfo? bestTarget = null; - - _dropTargetsLock.EnterReadLock(); - try - { - // Фильтруем цели по границам и сортируем по приоритету - var candidates = _dropTargets.Values - .Where(info => info.Bounds.Contains(position)) - .OrderByDescending(info => info.Priority) - .ToList(); - - foreach (var info in candidates) - { - var dropInfo = new Models.DropInfo( - dragInfo.Data, - position, - dragInfo.AllowedEffects, - info.Target); - - bool canAccept = await ExecuteWithTimeoutAsync( - info.Target.CanAcceptDropAsync(dropInfo), - "CanAcceptDropAsync", - info.Target); - - if (canAccept) - { - info.LastAccessTime = DateTime.UtcNow; - bestTarget = info; - break; // Берем первую подходящую с наивысшим приоритетом - } - } - } - finally - { - _dropTargetsLock.ExitReadLock(); - } - - return bestTarget; - } - - private async Task ExecuteTargetOperationAsync( - IDropTarget target, - Func operation, - string operationName) - { - try - { - if (EnableAsyncOperations) - { - await ExecuteWithTimeoutAsync( - operation(target), - $"{operationName}Async", - target); - } - else - { - await operation(target); - } - } - catch (Exception ex) - { - Interlocked.Increment(ref _errorCount); - HandleError(ex, operationName, target); - } - } - - private async Task ExecuteSourceOperationAsync( - IDragSource source, - Func operation, - string operationName) - { - try - { - if (EnableAsyncOperations) - { - await ExecuteWithTimeoutAsync( - operation(source), - $"{operationName}Async", - source); - } - else - { - await operation(source); - } - } - catch (Exception ex) - { - Interlocked.Increment(ref _errorCount); - HandleError(ex, operationName, source); - } - } - - private async Task ExecuteWithTimeoutAsync(Task task, string operationName, object? context = null) - { - if (AsyncOperationTimeout <= 0) - return await task; - - var timeoutTask = Task.Delay(AsyncOperationTimeout); - var completedTask = await Task.WhenAny(task, timeoutTask); - - if (completedTask == timeoutTask) - { - throw new TimeoutException($"{operationName} timed out after {AsyncOperationTimeout}ms"); - } - - return await task; - } - - private async Task ExecuteWithTimeoutAsync(Task task, string operationName, object? context = null) - { - if (AsyncOperationTimeout <= 0) - { - await task; - return; - } - - var timeoutTask = Task.Delay(AsyncOperationTimeout); - var completedTask = await Task.WhenAny(task, timeoutTask); - - if (completedTask == timeoutTask) - { - throw new TimeoutException($"{operationName} timed out after {AsyncOperationTimeout}ms"); - } - - await task; - } - - private double CalculateDistance(Geometry.Point p1, Geometry.Point p2) - { - var dx = p2.X - p1.X; - var dy = p2.Y - p1.Y; - return Math.Sqrt(dx * dx + dy * dy); - } - - private void Reset() - { - lock (_dragOperationLock) - { - _currentDragOperation?.Dispose(); - _currentDragOperation = null; - } - } - - private void CleanupExpiredTargets(object? state) - { - var expirationTime = DateTime.UtcNow.AddMinutes(-Constants.DragDropConstants.TargetLifetimeMinutes); - - _dropTargetsLock.EnterWriteLock(); - try - { - var idsToRemove = new List(); - var currentTarget = _currentDragOperation?.CurrentDropTarget; - - foreach (var kvp in _dropTargets) - { - if (kvp.Value.LastAccessTime < expirationTime && !ReferenceEquals(kvp.Value.Target, currentTarget)) - { - idsToRemove.Add(kvp.Key); - } - } - - foreach (var id in idsToRemove) - { - if (_dropTargets.Remove(id, out var info)) - { - info.Dispose(); - } - } - } - finally - { - _dropTargetsLock.ExitWriteLock(); - } - } - - private void HandleError(Exception exception, string operation, object? context = null) - { - _errorOccurred?.Invoke(this, new DragDropErrorEventArgs(exception, operation, context)); - } - - private void ThrowIfDisposed() - { - if (_disposed) - throw new ObjectDisposedException(GetType().Name); - } - - #endregion - - #region IDisposable Implementation - - public void Dispose() - { - if (_disposed) return; - - lock (_dragOperationLock) - { - if (_disposed) return; - - var timer = Interlocked.Exchange(ref _cleanupTimer, null); - timer?.Dispose(); - - _cleanupTimer?.Dispose(); - _cleanupTimer = null; - - if (_currentDragOperation != null) - { - _currentDragOperation.CancellationTokenSource?.Cancel(); - _currentDragOperation.Dispose(); - _currentDragOperation = null; - } - - ClearAllDropTargets(); - - _dropTargetsLock.Dispose(); - - // Очистка событий - _dragStarted = null; - _dragUpdated = null; - _dropTargetChanged = null; - _dragCompleted = null; - _dragCancelled = null; - _errorOccurred = null; - - _disposed = true; - } - - GC.SuppressFinalize(this); - } - - #endregion -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Services/EventArgs/DragEventArgs.cs b/Lattice.Core.DragDrop/Services/EventArgs/DragEventArgs.cs deleted file mode 100644 index 1138456..0000000 --- a/Lattice.Core.DragDrop/Services/EventArgs/DragEventArgs.cs +++ /dev/null @@ -1,237 +0,0 @@ -using Lattice.Core.DragDrop.Models; -using Lattice.Core.Geometry; - -namespace Lattice.Core.DragDrop.Services; - -/// -/// Предоставляет базовые данные для событий перетаскивания. -/// -/// -/// Этот класс содержит общие свойства, которые используются в большинстве событий -/// системы перетаскивания. Является базовым классом для специализированных событий. -/// -public abstract class DragEventArgs : EventArgs -{ - /// - /// Получает информацию о текущей операции перетаскивания. - /// - /// - /// Объект , содержащий данные, эффекты и метаданные операции. - /// Всегда возвращает актуальную информацию на момент возникновения события. - /// - public DragInfo DragInfo { get; } - - /// - /// Получает текущую позицию курсора в координатах экрана. - /// - /// - /// Точка, представляющая положение курсора мыши в момент события. - /// Используется для точного позиционирования и визуальной обратной связи. - /// - public Point Position { get; } - - /// - /// Инициализирует новый экземпляр класса . - /// - /// Информация о перетаскивании. - /// Текущая позиция курсора. - /// - /// Выбрасывается, когда равен null. - /// - protected DragEventArgs(DragInfo dragInfo, Point position) - { - DragInfo = dragInfo ?? throw new ArgumentNullException(nameof(dragInfo)); - Position = position; - } -} - -/// -/// Предоставляет данные для события начала перетаскивания. -/// -/// -/// Возникает, когда пользователь начинает операцию перетаскивания. -/// Это первое событие в жизненном цикле операции. -/// -public sealed class DragStartedEventArgs : DragEventArgs -{ - /// - /// Инициализирует новый экземпляр класса . - /// - /// Информация о перетаскивании. - /// Начальная позиция перетаскивания. - public DragStartedEventArgs(DragInfo dragInfo, Point position) - : base(dragInfo, position) - { - } -} - -/// -/// Предоставляет данные для события обновления позиции перетаскивания. -/// -/// -/// Возникает при каждом перемещении курсора во время операции перетаскивания. -/// Может вызываться многократно с высокой частотой, поэтому обработчики -/// должны быть оптимизированы для производительности. -/// -public sealed class DragUpdatedEventArgs : DragEventArgs -{ - /// - /// Инициализирует новый экземпляр класса . - /// - /// Информация о перетаскивании. - /// Текущая позиция курсора. - public DragUpdatedEventArgs(DragInfo dragInfo, Point position) - : base(dragInfo, position) - { - } -} - -/// -/// Предоставляет данные для события завершения перетаскивания. -/// -/// -/// Возникает, когда пользователь завершает операцию перетаскивания -/// (отпускает кнопку мыши над целью или вне области сброса). -/// Содержит информацию о примененных эффектах и результатах операции. -/// -public sealed class DragCompletedEventArgs : DragEventArgs -{ - /// - /// Получает эффекты, примененные при завершении операции. - /// - /// - /// Комбинация флагов , указывающая, - /// как были обработаны данные (копирование, перемещение и т.д.). - /// - public Enums.DragDropEffects Effects { get; } - - /// - /// Инициализирует новый экземпляр класса . - /// - /// Информация о перетаскивании. - /// Позиция завершения операции. - /// Примененные эффекты перетаскивания. - public DragCompletedEventArgs(DragInfo dragInfo, Point position, Enums.DragDropEffects effects) - : base(dragInfo, position) - { - Effects = effects; - } -} - -/// -/// Предоставляет данные для события отмены перетаскивания. -/// -/// -/// Возникает, когда операция перетаскивания была отменена пользователем -/// (например, нажатием клавиши Escape) или системой (например, при ошибке). -/// После этого события система возвращается в исходное состояние. -/// -public sealed class DragCancelledEventArgs : DragEventArgs -{ - /// - /// Инициализирует новый экземпляр класса . - /// - /// Информация о перетаскивании. - /// Позиция в момент отмены. - public DragCancelledEventArgs(DragInfo dragInfo, Point position) - : base(dragInfo, position) - { - } -} - -/// -/// Предоставляет данные для события изменения цели сброса. -/// -/// -/// Возникает, когда курсор перемещается с одной цели сброса на другую -/// или покидает область всех целей. Позволяет обновлять визуальную -/// обратную связь при изменении контекста сброса. -/// -public sealed class DropTargetChangedEventArgs : DragEventArgs -{ - /// - /// Получает новую цель сброса, над которой находится курсор. - /// - /// - /// Объект , готовый принять данные, - /// или null, если курсор покинул область всех целей. - /// - public Abstractions.IDropTarget? Target { get; } - - /// - /// Получает границы новой цели сброса. - /// - /// - /// Прямоугольник, определяющий область цели в координатах экрана. - /// Может использоваться для точного позиционирования визуальной обратной связи. - /// - public Rect TargetBounds { get; } - - /// - /// Инициализирует новый экземпляр класса . - /// - /// Информация о перетаскивании. - /// Текущая позиция курсора. - /// Новая цель сброса. - /// Границы цели сброса. - public DropTargetChangedEventArgs(DragInfo dragInfo, Point position, Abstractions.IDropTarget? target, Rect targetBounds) - : base(dragInfo, position) - { - Target = target; - TargetBounds = targetBounds; - } -} - -/// -/// Предоставляет данные для события ошибки в операции перетаскивания. -/// -/// -/// Возникает при возникновении исключения в любом из компонентов -/// системы перетаскивания. Позволяет централизованно обрабатывать ошибки -/// и предоставлять пользователю информацию о проблемах. -/// -public sealed class DragDropErrorEventArgs : EventArgs -{ - /// - /// Получает исключение, вызвавшее ошибку. - /// - /// - /// Объект , содержащий информацию об ошибке. - /// Может быть любого типа, в зависимости от источника ошибки. - /// - public Exception Exception { get; } - - /// - /// Получает название операции, во время которой произошла ошибка. - /// - /// - /// Строка, идентифицирующая операцию (например, "StartDragAsync", - /// "OnDropAsync", "UpdateDropTargetBounds"). - /// - public string Operation { get; } - - /// - /// Получает контекст, в котором произошла ошибка. - /// - /// - /// Объект, содержащий дополнительную информацию о контексте ошибки, - /// или null, если контекст недоступен. - /// - public object? Context { get; } - - /// - /// Инициализирует новый экземпляр класса . - /// - /// Исключение, вызвавшее ошибку. - /// Название операции. - /// Контекст ошибки. - /// - /// Выбрасывается, когда или равны null. - /// - public DragDropErrorEventArgs(Exception exception, string operation, object? context = null) - { - Exception = exception ?? throw new ArgumentNullException(nameof(exception)); - Operation = operation ?? throw new ArgumentNullException(nameof(operation)); - Context = context; - } -} \ No newline at end of file diff --git a/Lattice.Core.DragDrop/Services/IDragDropService.cs b/Lattice.Core.DragDrop/Services/IDragDropService.cs deleted file mode 100644 index 2440076..0000000 --- a/Lattice.Core.DragDrop/Services/IDragDropService.cs +++ /dev/null @@ -1,240 +0,0 @@ -namespace Lattice.Core.DragDrop.Services; - -/// -/// Предоставляет централизованный сервис для управления операциями перетаскивания. -/// -public interface IDragDropService : IDisposable -{ - #region Свойства - - /// - /// Получает значение, указывающее, активна ли операция перетаскивания. - /// - /// true, если операция перетаскивания активна; в противном случае — false. - bool IsDragActive { get; } - - /// - /// Получает информацию о текущей операции перетаскивания. - /// - /// - /// Объект , содержащий данные текущей операции, - /// или null, если операция не активна. - /// - Models.DragInfo? CurrentDragInfo { get; } - - /// - /// Получает текущую цель сброса. - /// - /// - /// Объект , над которым находится курсор, - /// или null, если курсор не над зарегистрированной целью. - /// - Abstractions.IDropTarget? CurrentDropTarget { get; } - - /// - /// Получает или задает порог начала перетаскивания в пикселях. - /// - /// - /// Минимальное расстояние, которое должен пройти курсор мыши, чтобы начать операцию перетаскивания. - /// Значение по умолчанию: . - /// - double DragStartThreshold { get; set; } - - /// - /// Получает или задает значение, указывающее, включены ли асинхронные операции. - /// - /// true, если асинхронные операции включены; в противном случае — false. - bool EnableAsyncOperations { get; set; } - - /// - /// Получает или задает максимальное время ожидания асинхронной операции в миллисекундах. - /// - /// - /// Время ожидания в миллисекундах. Значение 0 или меньше означает отсутствие таймаута. - /// Значение по умолчанию: . - /// - int AsyncOperationTimeout { get; set; } - - #endregion - - #region События - - /// - /// Происходит при начале операции перетаскивания. - /// - event EventHandler DragStarted; - - /// - /// Происходит при обновлении позиции перетаскивания. - /// - event EventHandler DragUpdated; - - /// - /// Происходит при изменении цели сброса. - /// - event EventHandler DropTargetChanged; - - /// - /// Происходит при завершении операции перетаскивания. - /// - event EventHandler DragCompleted; - - /// - /// Происходит при отмене операции перетаскивания. - /// - event EventHandler DragCancelled; - - /// - /// Происходит при возникновении ошибки в операции перетаскивания. - /// - event EventHandler ErrorOccurred; - - #endregion - - #region Регистрация целей сброса - - /// - /// Регистрирует цель сброса в системе. - /// - /// Цель сброса для регистрации. - /// Границы области цели в координатах экрана. - /// Приоритет цели (высшие значения обрабатываются первыми). - /// Имя группы для групповой отмены регистрации. - /// Уникальный идентификатор зарегистрированной цели. - /// Выбрасывается, когда равен null. - /// Выбрасывается, если сервис был удален. - string RegisterDropTarget(Abstractions.IDropTarget target, Geometry.Rect bounds, int priority = 0, string? group = null); - - /// - /// Обновляет границы цели сброса. - /// - /// Идентификатор цели сброса. - /// Новые границы области цели. - /// true, если границы успешно обновлены; в противном случае — false. - /// Выбрасывается, если сервис был удален. - bool UpdateDropTargetBounds(string id, Geometry.Rect bounds); - - /// - /// Отменяет регистрацию цели сброса. - /// - /// Идентификатор цели сброса. - /// true, если цель успешно удалена; в противном случае — false. - /// Выбрасывается, если сервис был удален. - bool UnregisterDropTarget(string id); - - /// - /// Отменяет регистрацию всех целей сброса в указанной группе. - /// - /// Имя группы для удаления. - /// Выбрасывается, если сервис был удален. - void UnregisterDropTargetsInGroup(string group); - - #endregion - - #region Асинхронные операции - - /// - /// Начинает операцию перетаскивания из указанной позиции. - /// - /// Источник данных для перетаскивания. - /// Начальная позиция операции в координатах экрана. - /// - /// Задача, представляющая асинхронную операцию. Результат содержит true, если операция успешно начата; - /// в противном случае — false. - /// - /// Выбрасывается, когда равен null. - /// Выбрасывается, если сервис был удален. - /// - /// Этот метод следует вызывать в ответ на событие нажатия кнопки мыши или начала жеста перетаскивания. - /// - Task StartDragAsync(Abstractions.IDragSource source, Geometry.Point startPosition); - - /// - /// Обновляет позицию текущей операции перетаскивания. - /// - /// Новая позиция курсора в координатах экрана. - /// Задача, представляющая асинхронную операцию. - /// Выбрасывается, если сервис был удален. - /// - /// Этот метод следует вызывать при каждом перемещении мыши во время операции перетаскивания. - /// - Task UpdateDragAsync(Geometry.Point position); - - /// - /// Завершает текущую операцию перетаскивания в указанной позиции. - /// - /// Позиция завершения операции в координатах экрана. - /// - /// Задача, представляющая асинхронную операцию. Результат содержит эффекты, примененные при завершении операции. - /// - /// Выбрасывается, если сервис был удален. - /// - /// Этот метод следует вызывать при отпускании кнопки мыши или завершении жеста перетаскивания. - /// - Task EndDragAsync(Geometry.Point position); - - /// - /// Отменяет текущую операцию перетаскивания. - /// - /// Задача, представляющая асинхронную операцию. - /// Выбрасывается, если сервис был удален. - /// - /// Этот метод следует вызывать при отмене операции пользователем (например, нажатием клавиши Escape) - /// или при возникновении ошибки. - /// - Task CancelDragAsync(); - - #endregion - - #region Утилиты - - /// - /// Очищает все зарегистрированные цели сброса. - /// - /// Выбрасывается, если сервис был удален. - void ClearAllDropTargets(); - - /// - /// Получает статистику использования системы перетаскивания. - /// - /// Объект со статистикой использования. - DragDropStats GetStats(); - - #endregion -} - -/// -/// Содержит статистику использования системы перетаскивания. -/// -public class DragDropStats -{ - /// - /// Получает или задает общее количество операций перетаскивания. - /// - public int TotalDragOperations { get; set; } - - /// - /// Получает или задает количество успешных сбросов. - /// - public int SuccessfulDrops { get; set; } - - /// - /// Получает или задает количество отмененных операций. - /// - public int CancelledOperations { get; set; } - - /// - /// Получает или задает количество ошибок. - /// - public int ErrorCount { get; set; } - - /// - /// Получает или задает количество зарегистрированных целей сброса. - /// - public int RegisteredTargets { get; set; } - - /// - /// Получает или задает среднее время операции перетаскивания. - /// - public TimeSpan AverageOperationTime { get; set; } -} \ No newline at end of file diff --git a/Lattice.Example.DragDrop/App.xaml.cs b/Lattice.Example.DragDrop/App.xaml.cs index 5177bda..a47c723 100644 --- a/Lattice.Example.DragDrop/App.xaml.cs +++ b/Lattice.Example.DragDrop/App.xaml.cs @@ -21,7 +21,7 @@ public partial class App : Application themeManager.RegisterTheme(new FluentThemePack(true)); // Dark тема // Применяем тему по умолчанию - themeManager.ApplyTheme("Fluent"); + themeManager.ApplyTheme("Fluent Dark"); // Создаем главное окно _window = new MainWindow(); diff --git a/Lattice.Example.DragDrop/Handlers/CustomDropHandler.cs b/Lattice.Example.DragDrop/Handlers/CustomDropHandler.cs deleted file mode 100644 index 9f2cb3f..0000000 --- a/Lattice.Example.DragDrop/Handlers/CustomDropHandler.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Lattice.Core.DragDrop.Abstractions; -using Lattice.Core.DragDrop.Models; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Lattice.Example.DragDrop; - -public class CustomDropHandler : IDropTarget -{ - private readonly Action _onDrop; - - public CustomDropHandler(Action onDrop) - { - _onDrop = onDrop; - } - - public async Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - // Принимаем только объекты типа DragDropItem - return dropInfo.Data is DragDropItem; - } - - public async Task OnDragOverAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - if (dropInfo.Data is DragDropItem) - { - dropInfo.SuggestedEffects = Core.DragDrop.Enums.DragDropEffects.Copy; - dropInfo.ShowVisualFeedback = true; - } - } - - public async Task OnDropAsync(DropInfo dropInfo, CancellationToken cancellationToken = default) - { - if (dropInfo.Data is DragDropItem item) - { - _onDrop(item); - dropInfo.MarkAsHandled(); - } - } - - public Task OnDragLeaveAsync(CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/Lattice.Example.DragDrop/Lattice.Example.DragDrop.csproj b/Lattice.Example.DragDrop/Lattice.Example.DragDrop.csproj index 7027f4b..f13a7f7 100644 --- a/Lattice.Example.DragDrop/Lattice.Example.DragDrop.csproj +++ b/Lattice.Example.DragDrop/Lattice.Example.DragDrop.csproj @@ -1,4 +1,4 @@ - + WinExe net8.0-windows10.0.19041.0 @@ -41,11 +41,8 @@ - - - - - - - - - + + - - - - - - - -