diff --git a/Lattice.Core.DragDrop/Exceptions/DragDropException.cs b/Lattice.Core.DragDrop/Exceptions/DragDropException.cs index 59874c0..66c8e91 100644 --- a/Lattice.Core.DragDrop/Exceptions/DragDropException.cs +++ b/Lattice.Core.DragDrop/Exceptions/DragDropException.cs @@ -59,7 +59,7 @@ public class DragDropException : Exception } /// -/// Коды ошибок Drag & Drop системы. +/// Коды ошибок Drag and Drop системы. /// public static class DragDropErrorCodes { diff --git a/Lattice.UI.DragDrop.WinUI/Behaviors/DragSource.cs b/Lattice.UI.DragDrop.WinUI/Behaviors/DragSource.cs deleted file mode 100644 index 55454d0..0000000 --- a/Lattice.UI.DragDrop.WinUI/Behaviors/DragSource.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Microsoft.UI.Xaml; - -namespace Lattice.UI.DragDrop.WinUI.Behaviors; - -/// -/// Attached properties для DragSource. -/// -public static class DragSource -{ - public static readonly DependencyProperty IsEnabledProperty = - DependencyProperty.RegisterAttached( - "IsEnabled", - typeof(bool), - typeof(DragSource), - new PropertyMetadata(false, OnIsEnabledChanged)); - - public static readonly DependencyProperty DragDataProperty = - DependencyProperty.RegisterAttached( - "DragData", - typeof(object), - typeof(DragSource), - new PropertyMetadata(null)); - - public static bool GetIsEnabled(UIElement element) => - (bool)element.GetValue(IsEnabledProperty); - - public static void SetIsEnabled(UIElement element, bool value) => - element.SetValue(IsEnabledProperty, value); - - public static object GetDragData(UIElement element) => - element.GetValue(DragDataProperty); - - public static void SetDragData(UIElement element, object value) => - element.SetValue(DragDataProperty, value); - - private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is not UIElement element) return; - - // TODO: Здесь нужно создать экземпляр WinUIDragSourceBehavior - // и прикрепить его к элементу через DI - // Пока что устанавливаем данные в Tag - if ((bool)e.NewValue) - { - var data = GetDragData(element); - element.Tag = data; - } - } -} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs index aa738c1..d1f97e6 100644 --- a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs +++ b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDragSourceBehavior.cs @@ -1,290 +1,267 @@ -using Lattice.Core.DragDrop.Models; +using Lattice.Core.DragDrop.Abstractions; +using Lattice.Core.DragDrop.Models; +using Lattice.Core.DragDrop.Services; using Lattice.Core.Geometry; -using Lattice.UI.DragDrop.Behaviors; +using Lattice.UI.DragDrop.WinUI.Services; using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; using System; +using System.Threading; using System.Threading.Tasks; namespace Lattice.UI.DragDrop.WinUI.Behaviors; /// -/// Поведение источника перетаскивания для WinUI FrameworkElement. +/// Реализация поведения источника перетаскивания для элементов WinUI. /// -public class WinUIDragSourceBehavior : DragSourceBehaviorBase +/// +/// +/// Этот класс обрабатывает события мыши/тач и преобразует их в операции перетаскивания. +/// Он реализует интерфейс для интеграции с системой перетаскивания. +/// +/// +/// Поведение автоматически отслеживает начало перемещения мыши, проверяет порог +/// перетаскивания и инициирует операцию через . +/// +/// +public sealed class WinUIDragSourceBehavior : IDragSource { - /// - /// Прикрепленное свойство для данных перетаскивания. - /// - public static readonly DependencyProperty DragDataProperty = - DependencyProperty.RegisterAttached( - "DragData", - typeof(object), - typeof(WinUIDragSourceBehavior), - new PropertyMetadata(null)); + #region Поля + + private readonly IDragDropService _dragDropService; + private readonly WinUIDragDropHost _host; + + private FrameworkElement? _element; + private object? _dragData; + private Point _dragStartPosition; + private bool _isDragging; + private CancellationTokenSource? _cancellationTokenSource; + + #endregion + + #region Конструктор /// - /// Прикрепленное свойство для включения перетаскивания. + /// Инициализирует новый экземпляр класса . /// - public static readonly DependencyProperty IsEnabledProperty = - DependencyProperty.RegisterAttached( - "IsEnabled", - typeof(bool), - typeof(WinUIDragSourceBehavior), - new PropertyMetadata(false, OnIsEnabledChanged)); - - /// - /// Получает значение DragData. - /// - public static object GetDragData(FrameworkElement element) + /// Сервис для управления операциями перетаскивания. + /// Хост для отображения визуальных элементов. + /// + /// Выбрасывается, если любой из параметров равен null. + /// + public WinUIDragSourceBehavior(IDragDropService dragDropService, WinUIDragDropHost host) { - return element.GetValue(DragDataProperty); + _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); + _host = host ?? throw new ArgumentNullException(nameof(host)); + } + + #endregion + + #region Публичные методы + + /// + /// Прикрепляет поведение к указанному элементу. + /// + /// Элемент, к которому прикрепляется поведение. + /// Данные для перетаскивания. Если не указано, используются + /// DataContext или Tag элемента. + /// + /// Выбрасывается, если равен null. + /// + /// + /// После вызова этого метода элемент начинает обрабатывать события перетаскивания. + /// + public void Attach(FrameworkElement element, object? dragData = null) + { + Detach(); + + _element = element ?? throw new ArgumentNullException(nameof(element)); + _dragData = dragData ?? element.DataContext ?? element.Tag; + + SubscribeToEvents(); } /// - /// Устанавливает значение DragData. + /// Открепляет поведение от элемента. /// - public static void SetDragData(FrameworkElement element, object value) + public void Detach() { - element.SetValue(DragDataProperty, value); - } + if (_element == null) return; - /// - /// Получает значение IsEnabled. - /// - public static bool GetIsEnabled(FrameworkElement element) - { - return (bool)element.GetValue(IsEnabledProperty); - } + UnsubscribeFromEvents(); - /// - /// Устанавливает значение IsEnabled. - /// - public static void SetIsEnabled(FrameworkElement element, bool value) - { - element.SetValue(IsEnabledProperty, value); - } - - private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is FrameworkElement element) + if (_isDragging) { - // Получаем или создаем экземпляр поведения через Attached Property - var behavior = GetBehavior(element); - - if ((bool)e.NewValue) - { - if (behavior == null) - { - behavior = new WinUIDragSourceBehavior(); - SetBehavior(element, behavior); - } - - behavior.AssociatedElement = element; - } - else - { - if (behavior != null) - { - behavior.Detach(); - SetBehavior(element, null); - } - } + _dragDropService.CancelDragAsync().ConfigureAwait(false); } + + _element = null; + _dragData = null; + _isDragging = false; + + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; } - public WinUIDragSourceBehavior() - : base(ServiceProviderHelper.GetServiceProvider()) + #endregion + + #region Обработчики событий + + private void SubscribeToEvents() { + if (_element == null) return; + + _element.PointerPressed += OnPointerPressed; + _element.PointerMoved += OnPointerMoved; + _element.PointerReleased += OnPointerReleased; + _element.PointerCanceled += OnPointerCanceled; + _element.PointerCaptureLost += OnPointerCaptureLost; } - protected override void SubscribeToEvents(FrameworkElement element) + private void UnsubscribeFromEvents() { - element.PointerPressed += OnPointerPressed; - element.PointerMoved += OnPointerMoved; - element.PointerReleased += OnPointerReleased; - element.PointerCanceled += OnPointerCanceled; - element.PointerCaptureLost += OnPointerCaptureLost; - element.LostFocus += OnLostFocus; - } + if (_element == null) return; - protected override void UnsubscribeFromEvents(FrameworkElement element) - { - element.PointerPressed -= OnPointerPressed; - element.PointerMoved -= OnPointerMoved; - element.PointerReleased -= OnPointerReleased; - element.PointerCanceled -= OnPointerCanceled; - element.PointerCaptureLost -= OnPointerCaptureLost; - element.LostFocus -= OnLostFocus; + _element.PointerPressed -= OnPointerPressed; + _element.PointerMoved -= OnPointerMoved; + _element.PointerReleased -= OnPointerReleased; + _element.PointerCanceled -= OnPointerCanceled; + _element.PointerCaptureLost -= OnPointerCaptureLost; } private void OnPointerPressed(object sender, PointerRoutedEventArgs e) { - if (AssociatedElement == null) return; + if (_element == null || _isDragging) return; - var point = e.GetCurrentPoint(AssociatedElement); - OnInteractionStarted(new Point(point.Position.X, point.Position.Y)); + var point = e.GetCurrentPoint(_element); + _dragStartPosition = new Point(point.Position.X, point.Position.Y); + + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = new CancellationTokenSource(); } - private void OnPointerMoved(object sender, PointerRoutedEventArgs e) + private async void OnPointerMoved(object sender, PointerRoutedEventArgs e) { - if (AssociatedElement == null) return; + if (_element == null || _isDragging || _cancellationTokenSource?.IsCancellationRequested == true) + return; - var point = e.GetCurrentPoint(AssociatedElement); - OnInteractionMoved(new Point(point.Position.X, point.Position.Y)); + var point = e.GetCurrentPoint(_element); + var currentPosition = new Point(point.Position.X, point.Position.Y); + + var distance = CalculateDistance(_dragStartPosition, currentPosition); + if (distance > _dragDropService.DragStartThreshold) + { + await StartDragAsync(currentPosition); + } } private void OnPointerReleased(object sender, PointerRoutedEventArgs e) { - OnInteractionEnded(); + ResetState(); } private void OnPointerCanceled(object sender, PointerRoutedEventArgs e) { - OnInteractionCancelled(); + ResetState(); } private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e) { - OnInteractionCancelled(); + ResetState(); } - private void OnLostFocus(object sender, RoutedEventArgs e) + private double CalculateDistance(Point p1, Point p2) { - OnInteractionCancelled(); + var dx = p2.X - p1.X; + var dy = p2.Y - p1.Y; + return Math.Sqrt(dx * dx + dy * dy); } - protected override Point ConvertToScreenCoordinates(Point point) + /// + /// Преобразует координаты элемента в экранные координаты. + /// + /// Точка в координатах элемента. + /// Точка в экранных координатах. + private Point ConvertToScreenCoordinates(Point point) { - if (AssociatedElement == null) + if (_element == null) return point; + + try + { + var transform = _element.TransformToVisual(Window.Current.Content); + var screenPoint = transform.TransformPoint(new Windows.Foundation.Point(point.X, point.Y)); + return new Point(screenPoint.X, screenPoint.Y); + } + catch + { return point; - - var transform = AssociatedElement.TransformToVisual(null); - var screenPoint = transform.TransformPoint(new Windows.Foundation.Point(point.X, point.Y)); - return new Point(screenPoint.X, screenPoint.Y); - } - - /// - public override async Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync() - { - if (AssociatedElement == null) - { - return (false, null); - } - - var data = GetDragData(AssociatedElement); - if (data == null) - { - // Пробуем получить данные из Tag или других источников - data = AssociatedElement.Tag ?? AssociatedElement.DataContext; - } - - if (data == null) - { - return (false, null); - } - - // Получаем начальную позицию в экранных координатах - var startPosition = ConvertToScreenCoordinates(_dragStartPosition); - - // Создаем DragInfo с учетом вашего конструктора - var dragInfo = new DragInfo( - data: data, - allowedEffects: Core.DragDrop.Enums.DragDropEffects.Move | - Core.DragDrop.Enums.DragDropEffects.Copy, - startPosition: startPosition, - source: this - ); - - return (true, dragInfo); - } - - protected override void OnDragCompleted(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects) - { - base.OnDragCompleted(dragInfo, effects); - - // Визуальная обратная связь при завершении - SetVisualState(AssociatedElement, "Normal"); - } - - protected override void OnDragCancelled(DragInfo dragInfo) - { - base.OnDragCancelled(dragInfo); - - // Визуальная обратная связь при отмене - SetVisualState(AssociatedElement, "Normal"); - } - - private void SetVisualState(FrameworkElement? element, string stateName) - { - if (element is Control control) - { - try - { - VisualStateManager.GoToState(control, stateName, true); - } - catch - { - // Fallback - control.Opacity = 1.0; - } - } - else if (element != null) - { - // Альтернативная визуальная обратная связь для не-Control элементов - element.Opacity = 1.0; } } - // Attached property для хранения экземпляра поведения - private static readonly DependencyProperty BehaviorProperty = - DependencyProperty.RegisterAttached( - "Behavior", - typeof(WinUIDragSourceBehavior), - typeof(WinUIDragSourceBehavior), - new PropertyMetadata(null)); - - private static WinUIDragSourceBehavior GetBehavior(FrameworkElement element) + private async Task StartDragAsync(Point position) { - return (WinUIDragSourceBehavior)element.GetValue(BehaviorProperty); - } + if (_element == null || _dragData == null || _isDragging) return; - private static void SetBehavior(FrameworkElement element, WinUIDragSourceBehavior? value) - { - element.SetValue(BehaviorProperty, value); - } -} - -/// -/// Вспомогательный класс для получения IServiceProvider. -/// -internal static class ServiceProviderHelper -{ - private static IServiceProvider? _serviceProvider; - - public static IServiceProvider GetServiceProvider() - { - if (_serviceProvider == null) + try { - // Ищем IServiceProvider в Application.Current.Resources - if (Application.Current.Resources.TryGetValue("ServiceProvider", out var provider) && - provider is IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - else - { - throw new InvalidOperationException( - "IServiceProvider не найден. Убедитесь, что ServiceProvider зарегистрирован в ресурсах приложения."); - } + var screenPosition = ConvertToScreenCoordinates(position); + var started = await _dragDropService.StartDragAsync(this, screenPosition); + _isDragging = started; + } + catch + { + ResetState(); } - - return _serviceProvider; } - public static void SetServiceProvider(IServiceProvider serviceProvider) + private void ResetState() { - _serviceProvider = serviceProvider; + _isDragging = false; + _dragStartPosition = default; + + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; } + + #endregion + + #region IDragSource Implementation + + public async Task TryStartDragAsync(Point startPosition, CancellationToken cancellationToken = default) + { + if (_element == null || _dragData == null) + return null; + + try + { + var dragInfo = new DragInfo( + data: _dragData, + allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy | + Core.DragDrop.Enums.DragDropEffects.Move, + startPosition: startPosition, + source: this + ); + + return dragInfo; + } + catch + { + return null; + } + } + + public Task OnDragCompletedAsync(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects, CancellationToken cancellationToken = default) + { + _isDragging = false; + return Task.CompletedTask; + } + + public Task OnDragCancelledAsync(DragInfo dragInfo, CancellationToken cancellationToken = default) + { + _isDragging = false; + return Task.CompletedTask; + } + + #endregion } \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs index a5848e9..23bb6f7 100644 --- a/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs +++ b/Lattice.UI.DragDrop.WinUI/Behaviors/WinUIDropTargetBehavior.cs @@ -1,345 +1,295 @@ -using Lattice.Core.DragDrop.Models; +using Lattice.Core.DragDrop.Abstractions; +using Lattice.Core.DragDrop.Models; using Lattice.Core.Geometry; -using Lattice.UI.DragDrop.Behaviors; +using Lattice.UI.DragDrop.WinUI.Services; using Microsoft.UI.Xaml; using System; -using System.Collections.Concurrent; +using System.Threading; using System.Threading.Tasks; -namespace Lattice.UI.DragDrop.WinUI.Behaviors +namespace Lattice.UI.DragDrop.WinUI.Behaviors; + +/// +/// Реализация поведения цели сброса для элементов WinUI. +/// +/// +/// +/// Этот класс обрабатывает события перетаскивания WinUI и преобразует их в вызовы +/// методов интерфейса . +/// +/// +/// Поведение автоматически регистрирует элемент в системе перетаскивания, +/// обновляет его границы при изменении размера и обрабатывает все этапы операции сброса. +/// +/// +public sealed class WinUIDropTargetBehavior : IDropTarget { + #region Поля + + private readonly Lattice.Core.DragDrop.Services.IDragDropService _dragDropService; + private readonly WinUIDragDropHost _host; + + private FrameworkElement? _element; + private string? _registrationId; + private Rect _currentBounds; + + #endregion + + #region Конструктор + /// - /// Поведение цели сброса для элементов WinUI. - /// Позволяет элементам принимать данные при операции перетаскивания. + /// Инициализирует новый экземпляр класса . /// - /// - /// - /// Это поведение должно быть прикреплено к , который должен выступать в качестве цели сброса. - /// Поведение автоматически регистрирует элемент в системе перетаскивания и обрабатывает все аспекты операции сброса. - /// - /// - /// Для использования необходимо: - /// 1. Создать экземпляр поведения с помощью или через DI. - /// 2. Переопределить методы и для реализации логики принятия данных. - /// 3. При необходимости переопределить для настройки визуальной обратной связи. - /// - /// - public class WinUIDropTargetBehavior : DropTargetBehaviorBase + /// Сервис для управления операциями перетаскивания. + /// Хост для отображения визуальных элементов. + /// + /// Выбрасывается, если любой из параметров равен null. + /// + public WinUIDropTargetBehavior(Lattice.Core.DragDrop.Services.IDragDropService dragDropService, WinUIDragDropHost host) { - private static readonly ConcurrentDictionary _attachedBehaviors = new(); - private readonly WeakReference? _weakElement; - - /// - /// Инициализирует новый экземпляр класса . - /// - /// Провайдер сервисов. - /// - /// Конструктор создает экземпляр поведения, но не прикрепляет его к элементу. - /// Для прикрепления используйте метод . - /// - public WinUIDropTargetBehavior(IServiceProvider serviceProvider) - : base(serviceProvider) - { - } - - /// - /// Инициализирует новый экземпляр класса . - /// - /// Провайдер сервисов. - /// Элемент, к которому прикрепляется поведение. - /// - /// Конструктор создает экземпляр поведения и сразу прикрепляет его к указанному элементу. - /// - public WinUIDropTargetBehavior(IServiceProvider serviceProvider, FrameworkElement element) - : base(serviceProvider) - { - AssociatedElement = element ?? throw new ArgumentNullException(nameof(element)); - } - - /// - /// Прикрепляет поведение к указанному элементу. - /// - /// Элемент, к которому прикрепляется поведение. - /// Провайдер сервисов. - /// - /// Экземпляр поведения, прикрепленного к элементу. Если к элементу уже прикреплено поведение, - /// возвращает существующий экземпляр. - /// - /// - /// Выбрасывается, когда или равны null. - /// - /// - /// - /// Этот метод обеспечивает, что к каждому элементу прикреплен только один экземпляр поведения. - /// Если метод вызывается повторно для того же элемента, возвращается существующий экземпляр. - /// - /// - /// Прикрепленное поведение автоматически отслеживает изменения макета элемента и обновляет - /// его границы в системе перетаскивания. - /// - /// - public static WinUIDropTargetBehavior Attach(FrameworkElement element, IServiceProvider serviceProvider) - { - if (element == null) throw new ArgumentNullException(nameof(element)); - if (serviceProvider == null) throw new ArgumentNullException(nameof(serviceProvider)); - - return _attachedBehaviors.GetOrAdd(element, key => - { - var behavior = new WinUIDropTargetBehavior(serviceProvider, key); - - // Подписка на события жизненного цикла элемента - key.Unloaded += OnElementUnloaded; - return behavior; - }); - } - - /// - /// Открепляет поведение от указанного элемента. - /// - /// Элемент, от которого открепляется поведение. - /// - /// true, если поведение было успешно откреплено; false, если поведение не было прикреплено к элементу. - /// - /// - /// Этот метод освобождает все ресурсы, связанные с поведением, и отписывается от событий элемента. - /// После вызова этого метода элемент перестает быть целью сброса. - /// - public static bool Detach(FrameworkElement element) - { - if (element == null) return false; - - if (_attachedBehaviors.TryRemove(element, out var behavior)) - { - element.Unloaded -= OnElementUnloaded; - behavior.Detach(); - return true; - } - - return false; - } - - /// - /// Получает поведение, прикрепленное к указанному элементу. - /// - /// Элемент, для которого требуется получить поведение. - /// - /// Экземпляр поведения, прикрепленного к элементу, или null, если поведение не прикреплено. - /// - public static WinUIDropTargetBehavior? GetAttachedBehavior(FrameworkElement element) - { - _attachedBehaviors.TryGetValue(element, out var behavior); - return behavior; - } - - /// - /// Подписывается на события элемента. - /// - /// Элемент, к которому прикрепляется поведение. - /// - /// - /// Этот метод подписывается на следующие события: - /// - /// - /// - для отслеживания изменений макета - /// - для отслеживания изменений размера - /// - для инициализации при загрузке элемента - /// - /// - /// Переопределите этот метод, чтобы добавить подписку на дополнительные события. - /// - /// - protected override void SubscribeToEvents(FrameworkElement element) - { - if (element == null) return; - - element.LayoutUpdated += OnLayoutUpdated; - element.SizeChanged += OnSizeChanged; - element.Loaded += OnLoaded; - - // Если элемент уже загружен, сразу обновляем границы - if (element.IsLoaded) - { - UpdateBounds(); - } - } - - /// - /// Отписывается от событий элемента. - /// - /// Элемент, от которого отписывается поведение. - /// - /// Этот метод отписывается от всех событий, на которые подписался . - /// - protected override void UnsubscribeFromEvents(FrameworkElement element) - { - if (element == null) return; - - element.LayoutUpdated -= OnLayoutUpdated; - element.SizeChanged -= OnSizeChanged; - element.Loaded -= OnLoaded; - } - - /// - /// Получает границы элемента в экранных координатах. - /// - /// Элемент, границы которого нужно получить. - /// - /// Прямоугольник, описывающий границы элемента в экранных координатах. - /// - /// - /// - /// Метод использует преобразование координат через - /// для получения глобальных координат элемента. - /// - /// - /// Если элемент не прикреплен к визуальному дереву или его границы не могут быть вычислены, - /// возвращается пустой прямоугольник. - /// - /// - protected override Rect GetScreenBounds(FrameworkElement element) - { - if (element == null || !element.IsLoaded) - return Rect.Empty; - - try - { - // Получаем корневой элемент окна - var rootVisual = element.XamlRoot?.Content as UIElement; - if (rootVisual == null) - return Rect.Empty; - - // Преобразуем границы элемента в координаты корневого элемента - var transform = element.TransformToVisual(rootVisual); - var position = transform.TransformPoint(new Windows.Foundation.Point(0, 0)); - - return new Rect( - position.X, - position.Y, - element.ActualWidth, - element.ActualHeight); - } - catch - { - // В случае ошибки возвращаем пустой прямоугольник - return Rect.Empty; - } - } - - /// - /// Определяет, может ли элемент принять сбрасываемые данные. - /// - /// Информация о сбросе. - /// - /// true, если элемент может принять данные; в противном случае — false. - /// - /// - /// - /// Этот метод является абстрактным и должен быть переопределен в производных классах - /// для реализации логики принятия данных. - /// - /// - /// Базовая реализация всегда возвращает false. Переопределите этот метод, чтобы определить, - /// какие типы данных может принимать ваш элемент и при каких условиях. - /// - /// - /// Пример реализации: - /// - /// public override bool CanAcceptDrop(DropInfo dropInfo) - /// { - /// // Принимаем только строковые данные - /// return dropInfo.Data is string; - /// } - /// - /// - /// - public override async Task CanAcceptDropAsync(DropInfo dropInfo) - { - // Базовая реализация - не принимает никакие данные. - // Переопределите этот метод в производных классах. - return false; - } - - /// - /// Обрабатывает сброс данных на элемент. - /// - /// Информация о сбросе. - /// - /// - /// Этот метод вызывается, когда пользователь отпускает кнопку мыши над элементом, - /// и данные должны быть приняты. - /// - /// - /// Базовая реализация ничего не делает. Переопределите этот метод, чтобы реализовать - /// логику обработки принятых данных. - /// - /// - /// Пример реализации: - /// - /// public override void Drop(DropInfo dropInfo) - /// { - /// if (dropInfo.Data is string text) - /// { - /// // Обработка текстовых данных - /// AssociatedElement.SetValue(TextBlock.TextProperty, text); - /// dropInfo.MarkAsHandled(); - /// } - /// } - /// - /// - /// - public override async Task DropAsync(DropInfo dropInfo) - { - // Базовая реализация ничего не делает. - // Переопределите этот метод в производных классах. - } - - /// - /// Освобождает ресурсы, связанные с поведением. - /// - /// - /// - /// Этот метод отписывается от всех событий, отменяет регистрацию в сервисе перетаскивания - /// и очищает все ресурсы. - /// - /// - /// После вызова этого метода поведение больше не может быть использовано. - /// - /// - public override void Detach() - { - if (AssociatedElement != null && _attachedBehaviors.TryGetValue(AssociatedElement, out _)) - { - _attachedBehaviors.TryRemove(AssociatedElement, out _); - } - - base.Detach(); - } - - #region Event Handlers - - private void OnLayoutUpdated(object? sender, object e) - { - OnElementLayoutChanged(); - } - - private void OnSizeChanged(object sender, SizeChangedEventArgs e) - { - OnElementLayoutChanged(); - } - - private void OnLoaded(object sender, RoutedEventArgs e) - { - UpdateBounds(); - } - - private static void OnElementUnloaded(object sender, RoutedEventArgs e) - { - if (sender is FrameworkElement element) - { - Detach(element); - } - } - - #endregion + _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); + _host = host ?? throw new ArgumentNullException(nameof(host)); } + + #endregion + + #region Публичные методы + + /// + /// Прикрепляет поведение к указанному элементу. + /// + /// Элемент, к которому прикрепляется поведение. + /// + /// Выбрасывается, если равен null. + /// + /// + /// После вызова этого метода: + /// 1. Элементу устанавливается = true + /// 2. Поведение подписывается на события перетаскивания + /// 3. Элемент регистрируется в системе перетаскивания + /// + public void Attach(FrameworkElement element) + { + Detach(); + + _element = element ?? throw new ArgumentNullException(nameof(element)); + element.AllowDrop = true; + + SubscribeToEvents(); + UpdateBounds(); + RegisterToService(); + } + + /// + /// Открепляет поведение от элемента. + /// + public void Detach() + { + if (_element == null) return; + + UnsubscribeFromEvents(); + UnregisterFromService(); + + _element.AllowDrop = false; + _element = null; + _currentBounds = Rect.Empty; + } + + #endregion + + #region Обработчики событий + + private void SubscribeToEvents() + { + if (_element == null) return; + + _element.DragEnter += OnDragEnter; + _element.DragOver += OnDragOver; + _element.DragLeave += OnDragLeave; + _element.Drop += OnDrop; + _element.SizeChanged += OnSizeChanged; + } + + private void UnsubscribeFromEvents() + { + if (_element == null) return; + + _element.DragEnter -= OnDragEnter; + _element.DragOver -= OnDragOver; + _element.DragLeave -= OnDragLeave; + _element.Drop -= OnDrop; + _element.SizeChanged -= OnSizeChanged; + } + + private async void OnDragEnter(object sender, DragEventArgs e) + { + if (_element == null) return; + + try + { + var position = e.GetPosition(_element); + var dropInfo = CreateDropInfo(e, new Point(position.X, position.Y)); + + if (await CanAcceptDropAsync(dropInfo)) + { + e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy; + } + } + catch { } + } + + private async void OnDragOver(object sender, DragEventArgs e) + { + if (_element == null) return; + + try + { + var position = e.GetPosition(_element); + var dropInfo = CreateDropInfo(e, new Point(position.X, position.Y)); + + if (await CanAcceptDropAsync(dropInfo)) + { + e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy; + await OnDragOverAsync(dropInfo); + } + + e.Handled = true; + } + catch { } + } + + private async void OnDragLeave(object sender, DragEventArgs e) + { + await OnDragLeaveAsync(); + } + + private async void OnDrop(object sender, DragEventArgs e) + { + if (_element == null) return; + + try + { + var position = e.GetPosition(_element); + var dropInfo = CreateDropInfo(e, new Point(position.X, position.Y)); + + if (await CanAcceptDropAsync(dropInfo)) + { + await OnDropAsync(dropInfo); + dropInfo.MarkAsHandled(); + e.Handled = true; + } + } + catch { } + } + + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + UpdateBounds(); + } + + private DropInfo CreateDropInfo(DragEventArgs e, Point position) + { + object? data = null; + + if (e.DataView.Properties.TryGetValue("DragData", out var dragData)) + { + data = dragData; + } + + return new DropInfo( + data: data, + position: position, + allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy | + Core.DragDrop.Enums.DragDropEffects.Move, + target: _element + ); + } + + /// + /// Получает границы элемента в экранных координатах. + /// + /// Прямоугольник с границами элемента или , если + /// элемент не загружен или не может быть преобразован. + private Rect GetScreenBounds() + { + if (_element == null || !_element.IsLoaded) + return Rect.Empty; + + try + { + var transform = _element.TransformToVisual(Window.Current.Content); + var position = transform.TransformPoint(new Windows.Foundation.Point(0, 0)); + + return new Rect( + position.X, + position.Y, + _element.ActualWidth, + _element.ActualHeight + ); + } + catch + { + return Rect.Empty; + } + } + + private void UpdateBounds() + { + if (_element == null) return; + + _currentBounds = GetScreenBounds(); + + if (_registrationId != null && _currentBounds != Rect.Empty) + { + _dragDropService.UpdateDropTargetBounds(_registrationId, _currentBounds); + } + } + + private void RegisterToService() + { + if (_element == null) return; + + _currentBounds = GetScreenBounds(); + + if (_currentBounds != Rect.Empty && _registrationId == null) + { + _registrationId = _dragDropService.RegisterDropTarget(this, _currentBounds); + } + } + + private void UnregisterFromService() + { + if (_registrationId != null) + { + _dragDropService.UnregisterDropTarget(_registrationId); + _registrationId = null; + } + } + + #endregion + + #region IDropTarget Implementation + + public Task CanAcceptDropAsync(DropInfo dropInfo, CancellationToken ct = default) + { + return Task.FromResult(true); // Принимаем все по умолчанию + } + + public Task OnDragOverAsync(DropInfo dropInfo, CancellationToken ct = default) + { + dropInfo.SuggestedEffects = Core.DragDrop.Enums.DragDropEffects.Move; + return Task.CompletedTask; + } + + public Task OnDropAsync(DropInfo dropInfo, CancellationToken ct = default) + { + return Task.CompletedTask; + } + + public Task OnDragLeaveAsync(CancellationToken ct = default) + { + return Task.CompletedTask; + } + + #endregion } \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Controls/DragAdorner.cs b/Lattice.UI.DragDrop.WinUI/Controls/DragAdorner.cs index 8d69ff9..6fbc446 100644 --- a/Lattice.UI.DragDrop.WinUI/Controls/DragAdorner.cs +++ b/Lattice.UI.DragDrop.WinUI/Controls/DragAdorner.cs @@ -7,8 +7,18 @@ using System; namespace Lattice.UI.DragDrop.WinUI.Controls; /// -/// Визуальный элемент для отображения перетаскиваемого объекта. +/// Визуальный элемент, отображаемый во время перетаскивания. /// +/// +/// +/// Этот элемент отображает репрезентативное представление перетаскиваемых данных +/// и следует за курсором мыши во время операции перетаскивания. +/// +/// +/// Элемент поддерживает настройку прозрачности, смещения и угла поворота, +/// а также анимированное появление и скрытие. +/// +/// public class DragAdorner : Control { /// @@ -66,8 +76,11 @@ public class DragAdorner : Control } /// - /// Получает или задает данные перетаскивания. + /// Получает или задает данные, которые отображаются в визуальном элементе. /// + /// + /// Объект данных для отображения. Обычно это те же данные, которые перетаскиваются. + /// public object DragData { get => GetValue(DragDataProperty); @@ -75,8 +88,13 @@ public class DragAdorner : Control } /// - /// Получает или задает смещение относительно курсора. + /// Получает или задает смещение элемента относительно позиции курсора. /// + /// + /// Смещение по осям X и Y. Используется для позиционирования элемента так, + /// чтобы он не перекрывал курсор. Значение по умолчанию вычисляется автоматически + /// на основе размера элемента. + /// public Point Offset { get => (Point)GetValue(OffsetProperty); @@ -111,9 +129,13 @@ public class DragAdorner : Control } /// - /// Обновляет позицию элемента относительно курсора. + /// Обновляет позицию элемента в соответствии с позицией курсора. /// - /// Позиция курсора в экранных координатах. + /// Текущая позиция курсора в экранных координатах. + /// + /// Метод применяет трансформации для позиционирования элемента с учетом + /// заданного смещения и угла поворота. + /// public void UpdatePosition(Point cursorPosition) { var transform = new TranslateTransform diff --git a/Lattice.UI.DragDrop.WinUI/Controls/DragDropOverlay.cs b/Lattice.UI.DragDrop.WinUI/Controls/DragDropOverlay.cs index 0d828a6..8d7182b 100644 --- a/Lattice.UI.DragDrop.WinUI/Controls/DragDropOverlay.cs +++ b/Lattice.UI.DragDrop.WinUI/Controls/DragDropOverlay.cs @@ -7,8 +7,19 @@ using System.Linq; namespace Lattice.UI.DragDrop.WinUI.Controls; /// -/// Оверлей для отображения визуальных элементов перетаскивания. +/// Оверлейный слой для отображения всех визуальных элементов перетаскивания. /// +/// +/// +/// Этот элемент добавляется поверх всего содержимого окна и содержит: +/// - Drag-визуализации (элементы, следующие за курсором) +/// - Drop-превью (подсветка областей сброса) +/// +/// +/// Элемент имеет = false, чтобы не перехватывать +/// пользовательский ввод во время операций перетаскивания. +/// +/// public class DragDropOverlay : Canvas { private readonly List _dragVisuals = new(); diff --git a/Lattice.UI.DragDrop.WinUI/Controls/DropPreviewAdorner.cs b/Lattice.UI.DragDrop.WinUI/Controls/DropPreviewAdorner.cs index a6711f2..7fcb552 100644 --- a/Lattice.UI.DragDrop.WinUI/Controls/DropPreviewAdorner.cs +++ b/Lattice.UI.DragDrop.WinUI/Controls/DropPreviewAdorner.cs @@ -8,8 +8,12 @@ using Windows.UI; namespace Lattice.UI.DragDrop.WinUI.Controls; /// -/// Визуальный элемент для предварительного просмотра области сброса. +/// Визуальный элемент для подсветки области сброса. /// +/// +/// Этот элемент отображается вокруг целевого элемента при наведении перетаскиваемого +/// объекта для визуального указания возможности сброса. +/// [TemplateVisualState(Name = "Normal", GroupName = "CommonStates")] [TemplateVisualState(Name = "Highlighted", GroupName = "CommonStates")] public class DropPreviewAdorner : Control @@ -54,8 +58,11 @@ public class DropPreviewAdorner : Control } /// - /// Получает или задает цвет предварительного просмотра. + /// Получает или задает цвет подсветки области сброса. /// + /// + /// Цвет границы и фона подсветки. Значение по умолчанию берется из ресурсов темы. + /// public Color PreviewColor { get => (Color)GetValue(PreviewColorProperty); @@ -81,9 +88,12 @@ public class DropPreviewAdorner : Control } /// - /// Показывает элемент с указанными границами. + /// Показывает элемент подсветки для указанной области. /// - /// Границы для отображения. + /// Границы области для подсветки. + /// + /// Метод позиционирует элемент по указанным границам и запускает анимацию появления. + /// public void Show(Core.Geometry.Rect bounds) { Width = bounds.Width; @@ -124,9 +134,9 @@ public class DropPreviewAdorner : Control } /// - /// Обновляет позицию элемента. + /// Обновляет позицию и размер элемента подсветки. /// - /// Новые границы. + /// Новые границы области для подсветки. public void UpdatePosition(Core.Geometry.Rect bounds) { if (RenderTransform is TranslateTransform transform) diff --git a/Lattice.UI.DragDrop.WinUI/DragDropProperties.cs b/Lattice.UI.DragDrop.WinUI/DragDropProperties.cs new file mode 100644 index 0000000..2ffac4b --- /dev/null +++ b/Lattice.UI.DragDrop.WinUI/DragDropProperties.cs @@ -0,0 +1,168 @@ +using Microsoft.UI.Xaml; + +namespace Lattice.UI.DragDrop.WinUI; + +/// +/// Предоставляет attached properties для настройки drag-and-drop поведения элементов WinUI. +/// +/// +/// Этот класс содержит attached properties, которые позволяют включать и настраивать +/// возможности перетаскивания и сброса для любых FrameworkElement в приложении WinUI. +/// +public static class DragDropProperties +{ + #region Drag Source Properties + + /// + /// Прикрепленное свойство для включения перетаскивания. + /// + public static readonly DependencyProperty IsDragSourceProperty = + DependencyProperty.RegisterAttached( + "IsDragSource", + typeof(bool), + typeof(DragDropProperties), + new PropertyMetadata(false, OnIsDragSourceChanged)); + + /// + /// Прикрепленное свойство для данных перетаскивания. + /// + public static readonly DependencyProperty DragDataProperty = + DependencyProperty.RegisterAttached( + "DragData", + typeof(object), + typeof(DragDropProperties), + new PropertyMetadata(null)); + + /// + /// Получает значение IsDragSource. + /// + public static bool GetIsDragSource(UIElement element) => + (bool)element.GetValue(IsDragSourceProperty); + + /// + /// Устанавливает значение IsDragSource. + /// + public static void SetIsDragSource(UIElement element, bool value) => + element.SetValue(IsDragSourceProperty, value); + + /// + /// Получает значение DragData. + /// + public static object? GetDragData(UIElement element) => + element.GetValue(DragDataProperty); + + /// + /// Устанавливает значение DragData. + /// + public static void SetDragData(UIElement element, object? value) => + element.SetValue(DragDataProperty, value); + + private static void OnIsDragSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is FrameworkElement element) + { + if ((bool)e.NewValue) + { + var data = GetDragData(element); + Services.WinUIDragDropManager.Instance.MakeDragSource(element, data); + } + else + { + Services.WinUIDragDropManager.Instance.RemoveDragSource(element); + } + } + } + + #endregion + + #region Drop Target Properties + + /// + /// Прикрепленное свойство для включения цели сброса. + /// + public static readonly DependencyProperty IsDropTargetProperty = + DependencyProperty.RegisterAttached( + "IsDropTarget", + typeof(bool), + typeof(DragDropProperties), + new PropertyMetadata(false, OnIsDropTargetChanged)); + + /// + /// Получает значение IsDropTarget. + /// + public static bool GetIsDropTarget(UIElement element) => + (bool)element.GetValue(IsDropTargetProperty); + + /// + /// Устанавливает значение IsDropTarget. + /// + public static void SetIsDropTarget(UIElement element, bool value) => + element.SetValue(IsDropTargetProperty, value); + + private static void OnIsDropTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is FrameworkElement element) + { + if ((bool)e.NewValue) + { + Services.WinUIDragDropManager.Instance.MakeDropTarget(element); + } + else + { + Services.WinUIDragDropManager.Instance.RemoveDropTarget(element); + } + } + } + + #endregion + + #region Helper Methods + + /// + /// Включает перетаскивание для элемента с указанными данными. + /// + /// Элемент, для которого включается перетаскивание. + /// Данные для перетаскивания. Если не указано, используются DataContext или Tag элемента. + /// + /// + /// myElement.EnableDrag(item); + /// + /// + public static void EnableDrag(this FrameworkElement element, object? dragData = null) + { + if (dragData != null) + { + SetDragData(element, dragData); + } + SetIsDragSource(element, true); + } + + /// + /// Отключает перетаскивание для элемента. + /// + /// Элемент, для которого отключается перетаскивание. + public static void DisableDrag(this FrameworkElement element) + { + SetIsDragSource(element, false); + } + + /// + /// Включает возможность сброса для элемента. + /// + /// Элемент, для которого включается возможность сброса. + public static void EnableDrop(this FrameworkElement element) + { + SetIsDropTarget(element, true); + } + + /// + /// Отключает возможность сброса для элемента. + /// + /// Элемент, для которого отключается возможность сброса. + public static void DisableDrop(this FrameworkElement element) + { + SetIsDropTarget(element, false); + } + + #endregion +} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Extensions/DragDropExtensions.cs b/Lattice.UI.DragDrop.WinUI/Extensions/DragDropExtensions.cs deleted file mode 100644 index 1cd8ccf..0000000 --- a/Lattice.UI.DragDrop.WinUI/Extensions/DragDropExtensions.cs +++ /dev/null @@ -1,217 +0,0 @@ -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Media; -using System; -using System.Linq; - -namespace Lattice.UI.DragDrop.WinUI.Extensions; - -/// -/// Методы расширения для настройки перетаскивания в WinUI. -/// -public static class DragDropExtensions -{ - #region Drag Source Extensions - - /// - /// Делает элемент источником перетаскивания с указанными данными. - /// - public static void MakeDragSource(this FrameworkElement element, object dragData) - { - Behaviors.WinUIDragSourceBehavior.SetDragData(element, dragData); - Behaviors.WinUIDragSourceBehavior.SetIsEnabled(element, true); - } - - /// - /// Делает элемент источником перетаскивания с фабрикой данных. - /// - public static void MakeDragSource(this FrameworkElement element, Func dataFactory) - { - element.MakeDragSource(dataFactory()); - } - - /// - /// Удаляет возможность перетаскивания с элемента. - /// - public static void RemoveDragSource(this FrameworkElement element) - { - Behaviors.WinUIDragSourceBehavior.SetIsEnabled(element, false); - } - - /// - /// Проверяет, является ли элемент источником перетаскивания. - /// - public static bool IsDragSource(this FrameworkElement element) - { - return Behaviors.WinUIDragSourceBehavior.GetIsEnabled(element); - } - - /// - /// Получает данные перетаскивания из элемента. - /// - public static object? GetDragData(this FrameworkElement element) - { - return Behaviors.WinUIDragSourceBehavior.GetDragData(element); - } - - #endregion - - #region Drop Target Extensions - - /// - /// Делает элемент целью сброса. - /// - public static void MakeDropTarget(this FrameworkElement element) - { - // Включаем AllowDrop для WinUI - element.AllowDrop = true; - element.SetValue(IsDropTargetProperty, true); - } - - /// - /// Делает элемент целью сброса с фильтром типов данных. - /// - public static void MakeDropTarget(this FrameworkElement element, params Type[] acceptedTypes) - { - element.SetValue(AcceptsDataTypesProperty, acceptedTypes); - element.MakeDropTarget(); - } - - /// - /// Удаляет возможность сброса с элемента. - /// - public static void RemoveDropTarget(this FrameworkElement element) - { - element.AllowDrop = false; - element.SetValue(IsDropTargetProperty, false); - } - - /// - /// Проверяет, является ли элемент целью сброса. - /// - public static bool IsDropTarget(this FrameworkElement element) - { - return (bool)element.GetValue(IsDropTargetProperty); - } - - /// - /// Attached property для отметки цели сброса. - /// - public static readonly DependencyProperty IsDropTargetProperty = - DependencyProperty.RegisterAttached( - "IsDropTarget", - typeof(bool), - typeof(DragDropExtensions), - new PropertyMetadata(false)); - - /// - /// Attached property для фильтра типов данных. - /// - public static readonly DependencyProperty AcceptsDataTypesProperty = - DependencyProperty.RegisterAttached( - "AcceptsDataTypes", - typeof(Type[]), - typeof(DragDropExtensions), - new PropertyMetadata(null)); - - /// - /// Получает фильтр типов данных. - /// - public static Type[]? GetAcceptsDataTypes(this FrameworkElement element) - { - return (Type[]?)element.GetValue(AcceptsDataTypesProperty); - } - - #endregion - - #region Style Extensions - - /// - /// Применяет стиль перетаскивания к элементу. - /// - public static void ApplyDragStyle(this Control control) - { - var style = Application.Current.Resources["DragEnabledStyle"] as Style; - if (style != null) - { - control.Style = style; - } - else - { - // Fallback стиль - var brush = Application.Current.Resources["SystemControlBackgroundAccentBrush"] as SolidColorBrush; - if (brush != null) - { - control.Background = brush; - } - } - } - - /// - /// Переключает визуальное состояние элемента для перетаскивания. - /// - public static void SetDragVisualState(this Control control, string stateName, bool useTransitions = true) - { - try - { - VisualStateManager.GoToState(control, stateName, useTransitions); - } - catch - { - // Fallback для элементов без визуальных состояний - switch (stateName) - { - case "Dragging": - control.Opacity = 0.7; - break; - case "DragOver": - control.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(50, 0, 120, 215)); - break; - case "Normal": - control.ClearValue(Control.OpacityProperty); - control.ClearValue(Control.BackgroundProperty); - break; - } - } - } - - #endregion - - #region Advanced Configuration - - /// - /// Создает контейнер с поддержкой перетаскивания для элементов. - /// - public static Panel CreateDragDropContainer( - Orientation orientation = Orientation.Vertical, - double spacing = 8, - bool enableReordering = true) - { - var container = new StackPanel - { - Orientation = orientation, - Spacing = spacing - }; - - if (enableReordering) - { - container.MakeDropTarget(typeof(FrameworkElement)); - } - - return container; - } - - /// - /// Делает все дочерние элементы перетаскиваемыми. - /// - public static void MakeChildrenDraggable(this Panel container, Func dataSelector) - { - foreach (var child in container.Children.OfType()) - { - var data = dataSelector(child); - child.MakeDragSource(data); - } - } - - #endregion -} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Extensions/FrameworkElementExtensions.cs b/Lattice.UI.DragDrop.WinUI/Extensions/FrameworkElementExtensions.cs deleted file mode 100644 index 69b82f5..0000000 --- a/Lattice.UI.DragDrop.WinUI/Extensions/FrameworkElementExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.UI.Xaml; - -namespace Lattice.UI.DragDrop.WinUI.Helpers; - -public static class FrameworkElementExtensions -{ - /// - /// Получает фактические размеры FrameworkElement. - /// - public static Windows.Foundation.Size GetActualSize(this FrameworkElement element) - { - return new Windows.Foundation.Size(element.ActualWidth, element.ActualHeight); - } - - /// - /// Получает границы элемента в экранных координатах. - /// - public static Windows.Foundation.Rect GetScreenBounds(this FrameworkElement element) - { - var transform = element.TransformToVisual(null); - var topLeft = transform.TransformPoint(new Windows.Foundation.Point(0, 0)); - var bottomRight = transform.TransformPoint(new Windows.Foundation.Point(element.ActualWidth, element.ActualHeight)); - - return new Windows.Foundation.Rect(topLeft, bottomRight); - } -} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Extensions/ThemeExtensions.cs b/Lattice.UI.DragDrop.WinUI/Extensions/ThemeExtensions.cs deleted file mode 100644 index 0a86b61..0000000 --- a/Lattice.UI.DragDrop.WinUI/Extensions/ThemeExtensions.cs +++ /dev/null @@ -1,120 +0,0 @@ -using Lattice.Themes; -using Lattice.Themes.Core.Tokens; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; - -namespace Lattice.UI.DragDrop.WinUI.Extensions; - -/// -/// Методы расширения для работы с темами в DragDrop. -/// -public static class ThemeExtensions -{ - /// - /// Применяет стиль перетаскивания, основанный на токенах темы. - /// - public static void ApplyLatticeDragStyle(this Control control) - { - var style = Application.Current.Resources["Lattice.DragDrop.DragSourceStyle"] as Style; - if (style != null) - { - control.Style = style; - } - else - { - // Fallback на старый стиль - control.ApplyDragStyle(); - } - } - - /// - /// Применяет стиль цели сброса, основанный на токенах темы. - /// - public static void ApplyLatticeDropTargetStyle(this Control control) - { - var style = Application.Current.Resources["Lattice.DragDrop.DropTargetStyle"] as Style; - if (style != null) - { - control.Style = style; - } - else - { - // Fallback на старый стиль - control.ApplyDropTargetStyle(); - } - } - - /// - /// Переключает визуальное состояние элемента с использованием токенов темы. - /// - public static void SetLatticeDragVisualState(this Control control, string stateName, bool useTransitions = true) - { - try - { - VisualStateManager.GoToState(control, stateName, useTransitions); - } - catch - { - // Fallback на альтернативные методы с использованием токенов - var themeManager = ThemeManager.Current; - - switch (stateName) - { - case "Dragging": - control.Opacity = themeManager.GetTokenValue(LatticeTokens.OpacityDrag) ?? 0.7; - control.RenderTransform = new Microsoft.UI.Xaml.Media.ScaleTransform - { - ScaleX = 0.95, - ScaleY = 0.95 - }; - break; - - case "DragOver": - var dragOverBrush = themeManager.GetTokenValue( - LatticeTokens.BrushDragOverlay); - control.Background = dragOverBrush ?? - Application.Current.Resources["Lattice.DragDrop.DragOverBackgroundBrush"] as Microsoft.UI.Xaml.Media.Brush; - break; - - case "Normal": - control.ClearValue(Control.OpacityProperty); - control.ClearValue(Control.RenderTransformProperty); - control.ClearValue(Control.BackgroundProperty); - control.ClearValue(Control.BorderBrushProperty); - break; - } - } - } - - /// - /// Получает значение токена для использования в DragDrop. - /// - public static T? GetDragDropToken(this Control control, string tokenKey) where T : class - { - var themeManager = ThemeManager.Current; - return themeManager.GetTokenValue(tokenKey); - } - - /// - /// Создает DragAdorner с использованием токенов темы. - /// - public static Controls.DragAdorner CreateLatticeDragAdorner(object dragData) - { - return new Controls.DragAdorner - { - DragData = dragData, - Style = Application.Current.Resources["Lattice.DragDrop.DragSourceStyle"] as Style - }; - } - - /// - /// Создает DropPreviewAdorner с использованием токенов темы. - /// - public static Controls.DropPreviewAdorner CreateLatticeDropPreviewAdorner() - { - return new Controls.DropPreviewAdorner - { - Style = Application.Current.Resources[typeof(Controls.DropPreviewAdorner)] as Style - }; - } -} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Integration/WinUIDragDropIntegrationService.cs b/Lattice.UI.DragDrop.WinUI/Integration/WinUIDragDropIntegrationService.cs deleted file mode 100644 index d7b7327..0000000 --- a/Lattice.UI.DragDrop.WinUI/Integration/WinUIDragDropIntegrationService.cs +++ /dev/null @@ -1,426 +0,0 @@ -using Lattice.Core.DragDrop.Abstractions; -using Lattice.Core.DragDrop.Models; -using Lattice.Core.DragDrop.Services; -using Lattice.Core.Geometry; -using Lattice.UI.DragDrop.Abstractions; -using Lattice.UI.DragDrop.WinUI.Behaviors; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Input; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading.Tasks; - -namespace Lattice.UI.DragDrop.WinUI.Integration; - -/// -/// Сервис интеграции Drag & Drop с WinUI приложением. -/// -public sealed class WinUIDragDropIntegrationService : IDisposable -{ - #region Вложенные типы - - private sealed class DragSourceAdapter : IDragSource - { - private readonly FrameworkElement _element; - private readonly Func _dragInfoFactory; - private readonly IDragDropService _dragDropService; - private Point _dragStartPosition; - private bool _isDragging; - private bool _disposed; - - public DragSourceAdapter( - FrameworkElement element, - Func dragInfoFactory, - IDragDropService dragDropService) - { - _element = element ?? throw new ArgumentNullException(nameof(element)); - _dragInfoFactory = dragInfoFactory ?? throw new ArgumentNullException(nameof(dragInfoFactory)); - _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); - - SubscribeToEvents(); - } - - private void SubscribeToEvents() - { - _element.PointerPressed += OnPointerPressed; - _element.PointerMoved += OnPointerMoved; - _element.PointerReleased += OnPointerReleased; - _element.PointerCanceled += OnPointerCanceled; - _element.PointerCaptureLost += OnPointerCaptureLost; - } - - private void UnsubscribeFromEvents() - { - _element.PointerPressed -= OnPointerPressed; - _element.PointerMoved -= OnPointerMoved; - _element.PointerReleased -= OnPointerReleased; - _element.PointerCanceled -= OnPointerCanceled; - _element.PointerCaptureLost -= OnPointerCaptureLost; - } - - private void OnPointerPressed(object sender, PointerRoutedEventArgs e) - { - if (_isDragging || _disposed) return; - - var point = e.GetCurrentPoint(_element); - _dragStartPosition = new Point(point.Position.X, point.Position.Y); - } - - private void OnPointerMoved(object sender, PointerRoutedEventArgs e) - { - if (_isDragging || _disposed) return; - - var currentPoint = e.GetCurrentPoint(_element); - var currentPosition = new Point(currentPoint.Position.X, currentPoint.Position.Y); - - var distance = CalculateDistance(_dragStartPosition, currentPosition); - if (distance > _dragDropService.DragStartThreshold) - { - StartDragOperation(currentPosition); - } - } - - private async Task StartDragOperation(Point position) - { - try - { - var dragInfo = _dragInfoFactory(); - - // Обновляем позицию в dragInfo - var screenPosition = GetScreenPosition(position); - dragInfo = new DragInfo( - dragInfo.Data, - dragInfo.AllowedEffects, - screenPosition, - dragInfo.Source - ); - - _isDragging = await _dragDropService.StartDragAsync(this, screenPosition); - } - catch (Exception ex) - { - Debug.WriteLine($"Error starting drag: {ex.Message}"); - _isDragging = false; - } - } - - private Point GetScreenPosition(Point relativePosition) - { - var transform = _element.TransformToVisual(null); - var point = transform.TransformPoint(new Windows.Foundation.Point(relativePosition.X, relativePosition.Y)); - return new Point(point.X, point.Y); - } - - private double CalculateDistance(Point p1, Point p2) - { - var dx = p2.X - p1.X; - var dy = p2.Y - p1.Y; - return Math.Sqrt(dx * dx + dy * dy); - } - - private async void OnPointerReleased(object sender, PointerRoutedEventArgs e) - { - if (!_isDragging || _disposed) return; - - await _dragDropService.EndDragAsync(); - _isDragging = false; - } - - private void OnPointerCanceled(object sender, PointerRoutedEventArgs e) - { - if (!_isDragging || _disposed) return; - - _dragDropService.CancelDragAsync(); - _isDragging = false; - } - - private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e) - { - if (!_isDragging || _disposed) return; - - _dragDropService.CancelDragAsync(); - _isDragging = false; - } - - #region IDragSource Implementation - - public async Task<(bool, DragInfo? dragInfo)> CancelDragAsync() - { - try - { - dragInfo = _dragInfoFactory(); - return dragInfo != null; - } - catch - { - dragInfo = null; - return false; - } - } - - public async Task StartDragAsync(DragInfo dragInfo) - { - return true; - } - - public async Task DragCompletedAsync(DragInfo dragInfo, Core.DragDrop.Enums.DragDropEffects effects) - { - _isDragging = false; - } - - public async Task DragCancelledAsync(DragInfo dragInfo) - { - _isDragging = false; - } - - #endregion - - public void Dispose() - { - if (_disposed) return; - - UnsubscribeFromEvents(); - _disposed = true; - GC.SuppressFinalize(this); - } - - public Task<(bool CanStart, DragInfo? DragInfo)> CanStartDragAsync() - { - throw new NotImplementedException(); - } - } - - #endregion - - #region Поля - - private readonly IDragDropService _dragDropService; - private readonly IDragVisualProvider _dragVisualProvider; - private readonly IDragDropHost _dragDropHost; - private readonly Dictionary _dragSources = new(); - private readonly Dictionary _dropTargets = new(); - private bool _disposed; - - #endregion - - #region Конструктор - - public WinUIDragDropIntegrationService( - IDragDropService dragDropService, - IDragVisualProvider dragVisualProvider, - IDragDropHost dragDropHost) - { - _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); - _dragVisualProvider = dragVisualProvider ?? throw new ArgumentNullException(nameof(dragVisualProvider)); - _dragDropHost = dragDropHost ?? throw new ArgumentNullException(nameof(dragDropHost)); - } - - #endregion - - #region Публичные методы - - /// - /// Регистрирует элемент как источник перетаскивания. - /// - public void RegisterDragSource(FrameworkElement element, Func dragInfoFactory) - { - if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService)); - ArgumentNullException.ThrowIfNull(element); - ArgumentNullException.ThrowIfNull(dragInfoFactory); - - if (_dragSources.ContainsKey(element)) return; - - var adapter = new DragSourceAdapter(element, dragInfoFactory, _dragDropService); - _dragSources[element] = adapter; - } - - /// - /// Регистрирует элемент как источник перетаскивания с данными. - /// - public void RegisterDragSource(FrameworkElement element, object dragData) - { - RegisterDragSource(element, () => - { - var position = GetScreenPosition(element, new Point(0, 0)); - return new DragInfo( - dragData, - Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move, - position, - element - ); - }); - } - - /// - /// Регистрирует элемент как цель сброса. - /// - public void RegisterDropTarget(FrameworkElement element) - { - if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService)); - ArgumentNullException.ThrowIfNull(element); - - if (_dropTargets.ContainsKey(element)) return; - - var behavior = new WinUIDropTargetBehavior(ServiceProviderHelper.GetServiceProvider()) - { - AssociatedElement = element - }; - - _dropTargets[element] = behavior; - - // Настраиваем события WinUI - element.AllowDrop = true; - element.DragEnter += OnDragEnter; - element.DragOver += OnDragOver; - element.DragLeave += OnDragLeave; - element.Drop += OnDrop; - } - - private Point GetScreenPosition(FrameworkElement element, Point relativePoint) - { - var transform = element.TransformToVisual(null); - var point = transform.TransformPoint(new Windows.Foundation.Point(relativePoint.X, relativePoint.Y)); - return new Point(point.X, point.Y); - } - - private void OnDragEnter(object sender, DragEventArgs e) - { - if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior)) - { - var position = GetScreenPosition(element, e.GetPosition(element).ToCorePoint()); - var dropInfo = CreateDropInfo(e, position, element); - - if (behavior.CanAcceptDrop(dropInfo)) - { - e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy; - behavior.DragOver(dropInfo); - } - else - { - e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.None; - } - } - } - - private void OnDragOver(object sender, DragEventArgs e) - { - if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior)) - { - var position = GetScreenPosition(element, e.GetPosition(element).ToCorePoint()); - var dropInfo = CreateDropInfo(e, position, element); - - if (behavior.CanAcceptDrop(dropInfo)) - { - e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy; - behavior.DragOver(dropInfo); - } - } - } - - private void OnDragLeave(object sender, DragEventArgs e) - { - if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior)) - { - behavior.DragLeave(); - } - } - - private void OnDrop(object sender, DragEventArgs e) - { - if (sender is FrameworkElement element && _dropTargets.TryGetValue(element, out var behavior)) - { - var position = GetScreenPosition(element, e.GetPosition(element).ToCorePoint()); - var dropInfo = CreateDropInfo(e, position, element); - behavior.Drop(dropInfo); - } - } - - private DropInfo CreateDropInfo(DragEventArgs e, Point position, FrameworkElement target) - { - // Извлекаем данные из DragEventArgs - object? data = null; - - // В реальной реализации нужно извлечь данные из e.DataView - // Это упрощенная версия - if (e.DataView.Properties.TryGetValue("DragData", out var dragData)) - { - data = dragData; - } - - return new DropInfo( - data: data, - position: position, - allowedEffects: Core.DragDrop.Enums.DragDropEffects.Copy | Core.DragDrop.Enums.DragDropEffects.Move, - target: target - ); - } - - /// - /// Удаляет регистрацию элемента как источника. - /// - public void UnregisterDragSource(FrameworkElement element) - { - if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService)); - - if (_dragSources.Remove(element, out var adapter)) - { - adapter.Dispose(); - } - } - - /// - /// Удаляет регистрацию элемента как цели. - /// - public void UnregisterDropTarget(FrameworkElement element) - { - if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropIntegrationService)); - - if (_dropTargets.Remove(element, out var behavior)) - { - behavior.Detach(); - element.AllowDrop = false; - element.DragEnter -= OnDragEnter; - element.DragOver -= OnDragOver; - element.DragLeave -= OnDragLeave; - element.Drop -= OnDrop; - } - } - - #endregion - - #region IDisposable - - public void Dispose() - { - if (_disposed) return; - - foreach (var adapter in _dragSources.Values) - { - adapter.Dispose(); - } - _dragSources.Clear(); - - foreach (var behavior in _dropTargets.Values) - { - behavior.Detach(); - } - _dropTargets.Clear(); - - _disposed = true; - GC.SuppressFinalize(this); - } - - #endregion -} - -/// -/// Методы расширения для преобразования координат. -/// -internal static class PointExtensions -{ - public static Point ToCorePoint(this Windows.Foundation.Point point) - { - return new Point(point.X, point.Y); - } -} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Services/DragDropConfigurationService.cs b/Lattice.UI.DragDrop.WinUI/Services/DragDropConfigurationService.cs deleted file mode 100644 index 22d8199..0000000 --- a/Lattice.UI.DragDrop.WinUI/Services/DragDropConfigurationService.cs +++ /dev/null @@ -1,259 +0,0 @@ -using Lattice.UI.DragDrop.WinUI.Controls; -using Lattice.UI.DragDrop.WinUI.Extensions; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using System; -using System.Collections.Generic; - -namespace Lattice.UI.DragDrop.WinUI.Services; - -/// -/// Сервис для настройки перетаскивания с поддержкой различных сценариев. -/// -public class DragDropConfigurationService -{ - private readonly Dictionary _configurations = new(); - private readonly DragDropOverlay _overlay; - - /// - /// Инициализирует новый экземпляр класса . - /// - public DragDropConfigurationService() - { - _overlay = new DragDropOverlay(); - } - - /// - /// Настройка элемента как источника перетаскивания. - /// - public void ConfigureAsDragSource(UIElement element, DragSourceConfiguration config) - { - element.MakeDragSource(config.Data); - - if (config.AllowedEffects.HasValue) - { - element.SetAllowedEffects(config.AllowedEffects.Value); - } - - if (config.VisualOffset.HasValue) - { - element.SetDragVisualOffset(config.VisualOffset.Value.X, config.VisualOffset.Value.Y); - } - - _configurations[element] = new Configuration { DragSourceConfig = config }; - } - - /// - /// Настройка элемента как цели сброса. - /// - public void ConfigureAsDropTarget(UIElement element, DropTargetConfiguration config) - { - element.MakeDropTarget( - config.AcceptedTypes, - config.Handler, - config.ShowVisualFeedback, - config.FeedbackStyle); - - if (!_configurations.ContainsKey(element)) - { - _configurations[element] = new Configuration(); - } - - _configurations[element].DropTargetConfig = config; - } - - /// - /// Настройка элемента для переупорядочивания в контейнере. - /// - public void ConfigureForReorder(UIElement element, Panel container, ReorderConfiguration config) - { - ConfigureAsDragSource(element, new DragSourceConfiguration - { - Data = element, - AllowedEffects = Core.DragDrop.Enums.DragDropEffects.Move, - VisualOffset = new Windows.Foundation.Point(-20, -20) - }); - - ConfigureAsDropTarget(container, new DropTargetConfiguration - { - AcceptedTypes = new[] { typeof(UIElement) }, - ShowVisualFeedback = config.ShowVisualFeedback, - FeedbackStyle = config.FeedbackStyle - }); - - // Настраиваем логику переупорядочивания - container.Drop += (sender, e) => HandleReorderDrop(sender as Panel, element, e); - } - - /// - /// Подключает оверлей к указанному контейнеру. - /// - public void AttachOverlayTo(Panel container) - { - if (container.Children.Contains(_overlay)) - return; - - container.Children.Add(_overlay); - } - - /// - /// Отключает все настройки перетаскивания для элемента. - /// - public void DisableDragDrop(UIElement element) - { - element.RemoveDragSource(); - element.RemoveDropTarget(); - - _configurations.Remove(element); - } - - /// - /// Очищает все настройки. - /// - public void Clear() - { - foreach (var element in _configurations.Keys) - { - element.RemoveDragSource(); - element.RemoveDropTarget(); - } - - _configurations.Clear(); - } - - private void HandleReorderDrop(Panel? container, UIElement draggedElement, Microsoft.UI.Xaml.DragEventArgs e) - { - if (container == null) return; - - var position = e.GetPosition(container); - int insertIndex = CalculateInsertIndex(container, position); - - if (insertIndex >= 0 && insertIndex < container.Children.Count) - { - container.Children.Remove(draggedElement); - container.Children.Insert(insertIndex, draggedElement); - } - } - - private int CalculateInsertIndex(Panel container, Windows.Foundation.Point position) - { - for (int i = 0; i < container.Children.Count; i++) - { - var child = container.Children[i]; - if (child is FrameworkElement element) - { - var childBounds = new Windows.Foundation.Rect( - Canvas.GetLeft(element), - Canvas.GetTop(element), - element.ActualWidth, - element.ActualHeight); - - if (position.Y < childBounds.Y + childBounds.Height / 2) - { - return i; - } - } - } - - return container.Children.Count; - } - - private class Configuration - { - public DragSourceConfiguration? DragSourceConfig { get; set; } - public DropTargetConfiguration? DropTargetConfig { get; set; } - } -} - -/// -/// Конфигурация источника перетаскивания. -/// -public class DragSourceConfiguration -{ - /// - /// Данные для перетаскивания. - /// - public required object Data { get; set; } - - /// - /// Разрешенные эффекты. - /// - public Core.DragDrop.Enums.DragDropEffects? AllowedEffects { get; set; } - - /// - /// Смещение визуального элемента. - /// - public Windows.Foundation.Point? VisualOffset { get; set; } - - /// - /// Пользовательский обработчик. - /// - public Action? OnDragStarted { get; set; } - - /// - /// Пользовательский обработчик завершения. - /// - public Action? OnDragCompleted { get; set; } -} - -/// -/// Конфигурация цели сброса. -/// -public class DropTargetConfiguration -{ - /// - /// Принимаемые типы данных. - /// - public Type[]? AcceptedTypes { get; set; } - - /// - /// Обработчик сброса. - /// - public Core.DragDrop.Abstractions.IDropTarget? Handler { get; set; } - - /// - /// Показывать визуальную обратную связь. - /// - public bool ShowVisualFeedback { get; set; } = true; - - /// - /// Стиль визуальной обратной связи. - /// - public Style? FeedbackStyle { get; set; } - - /// - /// Пользовательский обработчик валидации. - /// - public Func? CustomValidation { get; set; } - - /// - /// Пользовательский обработчик сброса. - /// - public Action? OnDrop { get; set; } -} - -/// -/// Конфигурация переупорядочивания. -/// -public class ReorderConfiguration -{ - /// - /// Показывать визуальную обратную связь. - /// - public bool ShowVisualFeedback { get; set; } = true; - - /// - /// Стиль визуальной обратной связи. - /// - public Style? FeedbackStyle { get; set; } - - /// - /// Включать анимацию при переупорядочивании. - /// - public bool EnableAnimation { get; set; } = true; - - /// - /// Порог для начала перетаскивания (в пикселях). - /// - public double DragThreshold { get; set; } = 5.0; -} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropHost.cs b/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropHost.cs index cddd3c6..ac96d44 100644 --- a/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropHost.cs +++ b/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropHost.cs @@ -7,14 +7,53 @@ using System; namespace Lattice.UI.DragDrop.WinUI.Services; -public class WinUIDragDropHost : IDragDropHost +/// +/// Хост для управления визуальными элементами перетаскивания в окне WinUI. +/// +/// +/// +/// Этот класс отвечает за отображение и управление визуальными элементами +/// во время операций перетаскивания, включая: +/// - Drag-визуализацию (элемент, следующий за курсором) +/// - Drop-превью (подсветка областей сброса) +/// +/// +/// Хост создает оверлейный слой поверх всего содержимого окна для корректного +/// отображения визуальных элементов поверх других UI-компонентов. +/// +/// +public sealed class WinUIDragDropHost : IDragDropHost, IDisposable { - private readonly DragDropOverlay _overlay; - private readonly Window _window; + private DragDropOverlay? _overlay; + private Window? _window; + private bool _disposed; - public WinUIDragDropHost(Window window) + /// + /// Инициализирует хост для работы с указанным окном. + /// + /// Окно, в котором будет работать перетаскивание. + /// + /// Выбрасывается, если равен null. + /// + /// + /// Выбрасывается, если хост уже был удален. + /// + /// + /// + /// Этот метод создает оверлейный слой и добавляет его в визуальное дерево окна. + /// Если содержимое окна не является , создается контейнер . + /// + /// + /// Метод должен быть вызван один раз перед использованием других методов хоста. + /// + /// + public void Initialize(Window window) { + if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropHost)); + _window = window ?? throw new ArgumentNullException(nameof(window)); + + // Создаем оверлей _overlay = new DragDropOverlay(); // Добавляем оверлей в окно @@ -22,33 +61,58 @@ public class WinUIDragDropHost : IDragDropHost { panel.Children.Add(_overlay); } + else + { + // Если контент не Panel, создаем Grid + var grid = new Grid(); + grid.Children.Add(_window.Content as UIElement ?? new Grid()); + grid.Children.Add(_overlay); + _window.Content = grid; + } } + /// + /// Отображает визуальное представление перетаскиваемого элемента. + /// + /// Визуальный элемент для отображения. + /// Позиция отображения в координатах экрана. + /// + /// Визуальный элемент будет отображен на оверлейном слое в указанной позиции + /// и будет следовать за курсором при обновлении через . + /// public void ShowDragVisual(object dragVisual, Point position) { + if (_overlay == null || _disposed) return; + if (dragVisual is UIElement element) { _overlay.ShowDragVisual(element, position.X, position.Y); } } + /// public void UpdateDragVisualPosition(object dragVisual, Point position) { + if (_overlay == null || _disposed) return; + if (dragVisual is UIElement element) { _overlay.UpdateDragVisualPosition(element, position.X, position.Y); } } + /// public void HideDragVisual(object dragVisual) { + if (_overlay == null || _disposed) return; + if (dragVisual is UIElement element) { _overlay.HideDragVisual(element); } else { - // Скрываем все, если передан null + // Скрываем все визуальные элементы var current = _overlay.GetCurrentDragVisual(); if (current != null) { @@ -57,16 +121,40 @@ public class WinUIDragDropHost : IDragDropHost } } + /// public void ShowDropAdorner(IDropVisualAdorner adorner) { + if (_overlay == null || _disposed) return; + if (adorner is DropPreviewAdorner dropAdorner) { - // TODO: Показываем превью сброса + // Для WinUI пока просто игнорируем } } + /// public void HideDropAdorner(IDropVisualAdorner adorner) { + if (_overlay == null || _disposed) return; + _overlay.HideAllDropPreviews(); } + + /// + public void Dispose() + { + if (_disposed) return; + + if (_overlay != null && _window?.Content is Panel panel) + { + panel.Children.Remove(_overlay); + _overlay.ClearAllVisuals(); + } + + _overlay = null; + _window = null; + _disposed = true; + + GC.SuppressFinalize(this); + } } \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropIntegrationService.cs b/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropIntegrationService.cs deleted file mode 100644 index 9981842..0000000 --- a/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropIntegrationService.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Lattice.Core.DragDrop.Services; -using Lattice.UI.DragDrop.Abstractions; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using System; - -namespace Lattice.UI.DragDrop.WinUI.Services; - -/// -/// Сервис интеграции Drag & Drop с WinUI приложением. -/// -public class WinUIDragDropIntegrationService : IDisposable -{ - private readonly IDragDropService _dragDropService; - private readonly IDragVisualProvider _dragVisualProvider; - private readonly Canvas _overlayCanvas; - private readonly Window _window; - private object? _currentDragVisual; - private bool _disposed; - - /// - /// Инициализирует новый экземпляр класса . - /// - public WinUIDragDropIntegrationService( - Window window, - IDragDropService dragDropService, - IDragVisualProvider dragVisualProvider) - { - _window = window ?? throw new ArgumentNullException(nameof(window)); - _dragDropService = dragDropService ?? throw new ArgumentNullException(nameof(dragDropService)); - _dragVisualProvider = dragVisualProvider ?? throw new ArgumentNullException(nameof(dragVisualProvider)); - - // Создаем оверлейный Canvas для визуальных элементов - _overlayCanvas = new Canvas - { - IsHitTestVisible = false, - Background = null - }; - - // Подписываемся на события перетаскивания - SubscribeToEvents(); - } - - /// - /// Встраивает оверлей в указанный контейнер. - /// - public void AttachToContainer(Panel container) - { - if (container == null) - throw new ArgumentNullException(nameof(container)); - - // Убеждаемся, что оверлей находится поверх всех элементов - Canvas.SetZIndex(_overlayCanvas, int.MaxValue); - container.Children.Add(_overlayCanvas); - } - - private void SubscribeToEvents() - { - _dragDropService.DragStarted += OnDragStarted; - _dragDropService.DragUpdated += OnDragUpdated; - _dragDropService.DragCompleted += OnDragCompleted; - _dragDropService.DragCancelled += OnDragCancelled; - } - - private void UnsubscribeFromEvents() - { - _dragDropService.DragStarted -= OnDragStarted; - _dragDropService.DragUpdated -= OnDragUpdated; - _dragDropService.DragCompleted -= OnDragCompleted; - _dragDropService.DragCancelled -= OnDragCancelled; - } - - private void OnDragStarted(object? sender, DragStartedEventArgs e) - { - // Создаем визуальное представление - _currentDragVisual = _dragVisualProvider.CreateDragVisual( - e.DragInfo, - e.StartPosition); - - // Добавляем на оверлей - if (_currentDragVisual is UIElement element) - { - _overlayCanvas.Children.Add(element); - } - } - - private void OnDragUpdated(object? sender, DragUpdatedEventArgs e) - { - if (_currentDragVisual != null) - { - _dragVisualProvider.UpdateDragVisualPosition(_currentDragVisual, e.Position); - } - } - - private void OnDragCompleted(object? sender, DragCompletedEventArgs e) - { - CleanupDragVisual(); - } - - private void OnDragCancelled(object? sender, DragCancelledEventArgs e) - { - CleanupDragVisual(); - } - - private void CleanupDragVisual() - { - if (_currentDragVisual != null) - { - _dragVisualProvider.ReleaseDragVisual(_currentDragVisual); - _currentDragVisual = null; - } - } - - /// - public void Dispose() - { - if (!_disposed) - { - UnsubscribeFromEvents(); - CleanupDragVisual(); - - if (_overlayCanvas.Parent is Panel parent) - { - parent.Children.Remove(_overlayCanvas); - } - - _disposed = true; - } - - GC.SuppressFinalize(this); - } -} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropManager.cs b/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropManager.cs new file mode 100644 index 0000000..b0eb8fa --- /dev/null +++ b/Lattice.UI.DragDrop.WinUI/Services/WinUIDragDropManager.cs @@ -0,0 +1,274 @@ +using Lattice.Core.DragDrop.Services; +using Lattice.Core.Geometry; +using Lattice.UI.DragDrop.WinUI.Controls; +using Microsoft.UI.Xaml; +using System; +using System.Collections.Generic; + +namespace Lattice.UI.DragDrop.WinUI.Services; + +/// +/// Центральный менеджер для управления операциями drag-and-drop в WinUI приложении. +/// +/// +/// +/// Этот класс реализует шаблон Singleton и предоставляет единую точку для +/// настройки и управления всеми операциями перетаскивания в приложении. +/// +/// +/// Менеджер отвечает за: +/// - Инициализацию системы перетаскивания +/// - Регистрацию и отслеживание источников и целей перетаскивания +/// - Создание и управление визуальной обратной связью +/// - Координацию между поведением элементов и базовым сервисом перетаскивания +/// +/// +/// Для использования необходимо вызвать при запуске приложения +/// и использовать attached properties или методы расширения для настройки элементов. +/// +/// +public sealed class WinUIDragDropManager : IDisposable +{ + #region Singleton + + private static WinUIDragDropManager? _instance; + private static readonly object _lock = new(); + + /// + /// Получает единственный экземпляр менеджера. + /// + public static WinUIDragDropManager Instance + { + get + { + if (_instance == null) + { + lock (_lock) + { + _instance ??= new WinUIDragDropManager(); + } + } + return _instance; + } + } + + #endregion + + #region Поля + + private readonly DragDropService _dragDropService; + private readonly WinUIDragDropHost _host; + private readonly Dictionary _dragSources = new(); + private readonly Dictionary _dropTargets = new(); + private DragAdorner? _currentDragVisual; + private bool _disposed; + + #endregion + + #region Свойства + + /// + /// Получает основной сервис перетаскивания. + /// + public IDragDropService DragDropService => _dragDropService; + + /// + /// Получает или задает смещение визуального элемента перетаскивания относительно курсора. + /// + /// + /// Точка, определяющая смещение по осям X и Y. Значение по умолчанию: (-20, -20). + /// Отрицательные значения поднимают визуальный элемент вверх и влево относительно курсора. + /// + public Point DragVisualOffset { get; set; } = new Point(-20, -20); + + #endregion + + #region Конструктор + + private WinUIDragDropManager() + { + _dragDropService = new DragDropService(); + _host = new WinUIDragDropHost(); + } + + #endregion + + #region Публичные методы + + /// + /// Инициализирует систему перетаскивания для указанного окна. + /// + /// Основное окно приложения, в котором будет работать перетаскивание. + /// + /// Выбрасывается, если менеджер был удален. + /// + /// + /// Этот метод должен быть вызван один раз при запуске приложения, обычно в методе + /// . + /// + public void Initialize(Window window) + { + if (_disposed) throw new ObjectDisposedException(nameof(WinUIDragDropManager)); + + _host.Initialize(window); + + // Подписываемся на события + _dragDropService.DragStarted += OnDragStarted; + _dragDropService.DragUpdated += OnDragUpdated; + _dragDropService.DragCompleted += OnDragCompleted; + _dragDropService.DragCancelled += OnDragCancelled; + } + + /// + /// Делает указанный элемент источником перетаскивания. + /// + /// Элемент, который станет источником перетаскивания. + /// Данные, которые будут перетаскиваться. Если не указано, используются + /// DataContext или Tag элемента. + /// + /// Если элемент уже зарегистрирован как источник перетаскивания, метод не выполняет действий. + /// + public void MakeDragSource(FrameworkElement element, object? dragData = null) + { + if (_disposed || _dragSources.ContainsKey(element)) return; + + var behavior = new Behaviors.WinUIDragSourceBehavior(_dragDropService, _host); + behavior.Attach(element, dragData); + _dragSources[element] = behavior; + } + + /// + /// Делает указанный элемент целью сброса. + /// + /// Элемент, который станет целью сброса. + /// + /// Если элемент уже зарегистрирован как цель сброса, метод не выполняет действий. + /// + public void MakeDropTarget(FrameworkElement element) + { + if (_disposed || _dropTargets.ContainsKey(element)) return; + + var behavior = new Behaviors.WinUIDropTargetBehavior(_dragDropService, _host); + behavior.Attach(element); + _dropTargets[element] = behavior; + } + + /// + /// Удаляет возможность перетаскивания. + /// + public void RemoveDragSource(FrameworkElement element) + { + if (_dragSources.Remove(element, out var behavior)) + { + behavior.Detach(); + } + } + + /// + /// Удаляет возможность сброса. + /// + public void RemoveDropTarget(FrameworkElement element) + { + if (_dropTargets.Remove(element, out var behavior)) + { + behavior.Detach(); + } + } + + /// + /// Очищает все регистрации. + /// + public void Clear() + { + foreach (var behavior in _dragSources.Values) + { + behavior.Detach(); + } + _dragSources.Clear(); + + foreach (var behavior in _dropTargets.Values) + { + behavior.Detach(); + } + _dropTargets.Clear(); + } + + #endregion + + #region Обработчики событий + + private void OnDragStarted(object? sender, DragStartedEventArgs e) + { + // Создаем визуальное представление + _currentDragVisual = new DragAdorner + { + DragData = e.DragInfo.Data, + Opacity = 0.8 + }; + + var position = new Point( + e.Position.X + DragVisualOffset.X, + e.Position.Y + DragVisualOffset.Y + ); + + _currentDragVisual.UpdatePosition(position); + _host.ShowDragVisual(_currentDragVisual, position); + } + + private void OnDragUpdated(object? sender, DragUpdatedEventArgs e) + { + if (_currentDragVisual != null) + { + var position = new Point( + e.Position.X + DragVisualOffset.X, + e.Position.Y + DragVisualOffset.Y + ); + + _currentDragVisual.UpdatePosition(position); + } + } + + private void OnDragCompleted(object? sender, DragCompletedEventArgs e) + { + CleanupDragVisual(); + } + + private void OnDragCancelled(object? sender, DragCancelledEventArgs e) + { + CleanupDragVisual(); + } + + private void CleanupDragVisual() + { + if (_currentDragVisual != null) + { + _currentDragVisual.Hide(); + _currentDragVisual = null; + } + } + + #endregion + + #region IDisposable + + public void Dispose() + { + if (_disposed) return; + + Clear(); + + // Отписываемся от событий + _dragDropService.DragStarted -= OnDragStarted; + _dragDropService.DragUpdated -= OnDragUpdated; + _dragDropService.DragCompleted -= OnDragCompleted; + _dragDropService.DragCancelled -= OnDragCancelled; + + _dragDropService.Dispose(); + _host.Dispose(); + + _disposed = true; + GC.SuppressFinalize(this); + } + + #endregion +} \ No newline at end of file diff --git a/Lattice.UI.DragDrop.WinUI/Themes/DragAdorner.xaml b/Lattice.UI.DragDrop.WinUI/Themes/DragAdorner.xaml index 4e6ef21..1ed2404 100644 --- a/Lattice.UI.DragDrop.WinUI/Themes/DragAdorner.xaml +++ b/Lattice.UI.DragDrop.WinUI/Themes/DragAdorner.xaml @@ -1,3 +1,7 @@ + diff --git a/Lattice.UI.DragDrop.WinUI/Themes/DropPreviewAdorner.xaml b/Lattice.UI.DragDrop.WinUI/Themes/DropPreviewAdorner.xaml index 35ce3c9..0642789 100644 --- a/Lattice.UI.DragDrop.WinUI/Themes/DropPreviewAdorner.xaml +++ b/Lattice.UI.DragDrop.WinUI/Themes/DropPreviewAdorner.xaml @@ -1,3 +1,7 @@ +