using System; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Xaml.Interactivity; namespace Avalonia.Xaml.Interactions.DragAndDrop; /// /// /// public class ContextDragBehavior : StyledElementBehavior { private Point _dragStartPoint; private PointerEventArgs? _triggerEvent; private bool _lock; private bool _captured; /// /// /// public static readonly StyledProperty ContextProperty = AvaloniaProperty.Register(nameof(Context)); /// /// /// public static readonly StyledProperty HandlerProperty = AvaloniaProperty.Register(nameof(Handler)); /// /// /// public static readonly StyledProperty HorizontalDragThresholdProperty = AvaloniaProperty.Register(nameof(HorizontalDragThreshold), 3); /// /// /// public static readonly StyledProperty VerticalDragThresholdProperty = AvaloniaProperty.Register(nameof(VerticalDragThreshold), 3); /// /// /// public object? Context { get => GetValue(ContextProperty); set => SetValue(ContextProperty, value); } /// /// /// public IDragHandler? Handler { get => GetValue(HandlerProperty); set => SetValue(HandlerProperty, value); } /// /// /// public double HorizontalDragThreshold { get => GetValue(HorizontalDragThresholdProperty); set => SetValue(HorizontalDragThresholdProperty, value); } /// /// /// public double VerticalDragThreshold { get => GetValue(VerticalDragThresholdProperty); set => SetValue(VerticalDragThresholdProperty, value); } /// protected override void OnAttachedToVisualTree() { AssociatedObject?.AddHandler(InputElement.PointerPressedEvent, AssociatedObject_PointerPressed, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble); AssociatedObject?.AddHandler(InputElement.PointerReleasedEvent, AssociatedObject_PointerReleased, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble); AssociatedObject?.AddHandler(InputElement.PointerMovedEvent, AssociatedObject_PointerMoved, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble); AssociatedObject?.AddHandler(InputElement.PointerCaptureLostEvent, AssociatedObject_CaptureLost, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble); } /// protected override void OnDetachedFromVisualTree() { AssociatedObject?.RemoveHandler(InputElement.PointerPressedEvent, AssociatedObject_PointerPressed); AssociatedObject?.RemoveHandler(InputElement.PointerReleasedEvent, AssociatedObject_PointerReleased); AssociatedObject?.RemoveHandler(InputElement.PointerMovedEvent, AssociatedObject_PointerMoved); AssociatedObject?.RemoveHandler(InputElement.PointerCaptureLostEvent, AssociatedObject_CaptureLost); } private async Task DoDragDrop(PointerEventArgs triggerEvent, object? value) { var data = new DataObject(); data.Set(ContextDropBehavior.DataFormat, value!); var effect = DragDropEffects.None; if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Alt)) { effect |= DragDropEffects.Link; } else if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Shift)) { effect |= DragDropEffects.Move; } else if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Control)) { effect |= DragDropEffects.Copy; } else { effect |= DragDropEffects.Move; } await DragDrop.DoDragDrop(triggerEvent, data, effect); } private void Released() { _triggerEvent = null; _lock = false; } private void AssociatedObject_PointerPressed(object? sender, PointerPressedEventArgs e) { var properties = e.GetCurrentPoint(AssociatedObject).Properties; if (properties.IsLeftButtonPressed) { if (e.Source is Control control && AssociatedObject?.DataContext == control.DataContext) { if ((control as ISelectable ?? control.Parent as ISelectable ?? control.FindLogicalAncestorOfType())?.IsSelected ?? false) e.Handled = true; //avoid deselection on drag _dragStartPoint = e.GetPosition(null); _triggerEvent = e; _lock = true; _captured = true; } } } private void AssociatedObject_PointerReleased(object? sender, PointerReleasedEventArgs e) { if (_captured) { if (e.InitialPressMouseButton == MouseButton.Left && _triggerEvent is not null) { Released(); } _captured = false; } } private async void AssociatedObject_PointerMoved(object? sender, PointerEventArgs e) { var properties = e.GetCurrentPoint(AssociatedObject).Properties; if (_captured && properties.IsLeftButtonPressed && _triggerEvent is not null) { var point = e.GetPosition(null); var diff = _dragStartPoint - point; var horizontalDragThreshold = HorizontalDragThreshold; var verticalDragThreshold = VerticalDragThreshold; if (Math.Abs(diff.X) > horizontalDragThreshold || Math.Abs(diff.Y) > verticalDragThreshold) { if (_lock) { _lock = false; } else { return; } var context = Context ?? AssociatedObject?.DataContext; Handler?.BeforeDragDrop(sender, _triggerEvent, context); await DoDragDrop(_triggerEvent, context); Handler?.AfterDragDrop(sender, _triggerEvent, context); _triggerEvent = null; } } } private void AssociatedObject_CaptureLost(object? sender, PointerCaptureLostEventArgs e) { Released(); _captured = false; } }