using System; using System.Collections; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media.Transformation; using Avalonia.Xaml.Interactivity; namespace Avalonia.Xaml.Interactions.Draggable; /// /// /// public class ItemDragBehavior : StyledElementBehavior { private bool _enableDrag; private bool _dragStarted; private Point _start; private int _draggedIndex; private int _targetIndex; private ItemsControl? _itemsControl; private Control? _draggedContainer; private bool _captured; /// /// /// public static readonly StyledProperty OrientationProperty = AvaloniaProperty.Register(nameof(Orientation)); /// /// /// public static readonly StyledProperty HorizontalDragThresholdProperty = AvaloniaProperty.Register(nameof(HorizontalDragThreshold), 3); /// /// /// public static readonly StyledProperty VerticalDragThresholdProperty = AvaloniaProperty.Register(nameof(VerticalDragThreshold), 3); /// /// /// public Orientation Orientation { get => GetValue(OrientationProperty); set => SetValue(OrientationProperty, 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() { if (AssociatedObject is not null) { AssociatedObject.AddHandler(InputElement.PointerReleasedEvent, PointerReleased, RoutingStrategies.Tunnel); AssociatedObject.AddHandler(InputElement.PointerPressedEvent, PointerPressed, RoutingStrategies.Tunnel); AssociatedObject.AddHandler(InputElement.PointerMovedEvent, PointerMoved, RoutingStrategies.Tunnel); AssociatedObject.AddHandler(InputElement.PointerCaptureLostEvent, PointerCaptureLost, RoutingStrategies.Tunnel); } } /// protected override void OnDetachedFromVisualTree() { if (AssociatedObject is not null) { AssociatedObject.RemoveHandler(InputElement.PointerReleasedEvent, PointerReleased); AssociatedObject.RemoveHandler(InputElement.PointerPressedEvent, PointerPressed); AssociatedObject.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved); AssociatedObject.RemoveHandler(InputElement.PointerCaptureLostEvent, PointerCaptureLost); } } private void PointerPressed(object? sender, PointerPressedEventArgs e) { var properties = e.GetCurrentPoint(AssociatedObject).Properties; if (properties.IsLeftButtonPressed && AssociatedObject?.Parent is ItemsControl itemsControl) { _enableDrag = true; _dragStarted = false; _start = e.GetPosition(itemsControl); _draggedIndex = -1; _targetIndex = -1; _itemsControl = itemsControl; _draggedContainer = AssociatedObject; if (_draggedContainer is not null) { SetDraggingPseudoClasses(_draggedContainer, true); } AddTransforms(_itemsControl); _captured = true; } } private void PointerReleased(object? sender, PointerReleasedEventArgs e) { if (_captured) { if (e.InitialPressMouseButton == MouseButton.Left) { Released(); } _captured = false; } } private void PointerCaptureLost(object? sender, PointerCaptureLostEventArgs e) { Released(); _captured = false; } private void Released() { if (!_enableDrag) { return; } RemoveTransforms(_itemsControl); if (_itemsControl is not null) { foreach (var control in _itemsControl.GetRealizedContainers()) { SetDraggingPseudoClasses(control, true); } } if (_dragStarted) { if (_draggedIndex >= 0 && _targetIndex >= 0 && _draggedIndex != _targetIndex) { MoveDraggedItem(_itemsControl, _draggedIndex, _targetIndex); } } if (_itemsControl is not null) { foreach (var control in _itemsControl.GetRealizedContainers()) { SetDraggingPseudoClasses(control, false); } } if (_draggedContainer is not null) { SetDraggingPseudoClasses(_draggedContainer, false); } _draggedIndex = -1; _targetIndex = -1; _enableDrag = false; _dragStarted = false; _itemsControl = null; _draggedContainer = null; } private void AddTransforms(ItemsControl? itemsControl) { if (itemsControl?.Items is null) { return; } var i = 0; foreach (var _ in itemsControl.Items) { var container = itemsControl.ContainerFromIndex(i); if (container is not null) { SetTranslateTransform(container, 0, 0); } i++; } } private void RemoveTransforms(ItemsControl? itemsControl) { if (itemsControl?.Items is null) { return; } var i = 0; foreach (var _ in itemsControl.Items) { var container = itemsControl.ContainerFromIndex(i); if (container is not null) { SetTranslateTransform(container, 0, 0); } i++; } } private void MoveDraggedItem(ItemsControl? itemsControl, int draggedIndex, int targetIndex) { if (itemsControl?.ItemsSource is IList itemsSource) { var draggedItem = itemsSource[draggedIndex]; itemsSource.RemoveAt(draggedIndex); itemsSource.Insert(targetIndex, draggedItem); if (itemsControl is SelectingItemsControl selectingItemsControl) { selectingItemsControl.SelectedIndex = targetIndex; } } else { if (itemsControl?.Items is {IsReadOnly: false} itemCollection) { var draggedItem = itemCollection[draggedIndex]; itemCollection.RemoveAt(draggedIndex); itemCollection.Insert(targetIndex, draggedItem); if (itemsControl is SelectingItemsControl selectingItemsControl) { selectingItemsControl.SelectedIndex = targetIndex; } } } } private void PointerMoved(object? sender, PointerEventArgs e) { var properties = e.GetCurrentPoint(AssociatedObject).Properties; if (_captured && properties.IsLeftButtonPressed) { if (_itemsControl?.Items is null || _draggedContainer?.RenderTransform is null || !_enableDrag) { return; } var orientation = Orientation; var position = e.GetPosition(_itemsControl); var delta = orientation == Orientation.Horizontal ? position.X - _start.X : position.Y - _start.Y; if (!_dragStarted) { var diff = _start - position; var horizontalDragThreshold = HorizontalDragThreshold; var verticalDragThreshold = VerticalDragThreshold; if (orientation == Orientation.Horizontal) { if (Math.Abs(diff.X) > horizontalDragThreshold) { _dragStarted = true; } else { return; } } else { if (Math.Abs(diff.Y) > verticalDragThreshold) { _dragStarted = true; } else { return; } } } if (orientation == Orientation.Horizontal) { SetTranslateTransform(_draggedContainer, delta, 0); } else { SetTranslateTransform(_draggedContainer, 0, delta); } _draggedIndex = _itemsControl.IndexFromContainer(_draggedContainer); _targetIndex = -1; var draggedBounds = _draggedContainer.Bounds; var draggedStart = orientation == Orientation.Horizontal ? draggedBounds.X : draggedBounds.Y; var draggedDeltaStart = orientation == Orientation.Horizontal ? draggedBounds.X + delta : draggedBounds.Y + delta; var draggedDeltaEnd = orientation == Orientation.Horizontal ? draggedBounds.X + delta + draggedBounds.Width : draggedBounds.Y + delta + draggedBounds.Height; var i = 0; foreach (var _ in _itemsControl.Items) { var targetContainer = _itemsControl.ContainerFromIndex(i); if (targetContainer?.RenderTransform is null || ReferenceEquals(targetContainer, _draggedContainer)) { i++; continue; } var targetBounds = targetContainer.Bounds; var targetStart = orientation == Orientation.Horizontal ? targetBounds.X : targetBounds.Y; var targetMid = orientation == Orientation.Horizontal ? targetBounds.X + targetBounds.Width / 2 : targetBounds.Y + targetBounds.Height / 2; var targetIndex = _itemsControl.IndexFromContainer(targetContainer); if (targetStart > draggedStart && draggedDeltaEnd >= targetMid) { if (orientation == Orientation.Horizontal) { SetTranslateTransform(targetContainer, -draggedBounds.Width, 0); } else { SetTranslateTransform(targetContainer, 0, -draggedBounds.Height); } _targetIndex = _targetIndex == -1 ? targetIndex : targetIndex > _targetIndex ? targetIndex : _targetIndex; } else if (targetStart < draggedStart && draggedDeltaStart <= targetMid) { if (orientation == Orientation.Horizontal) { SetTranslateTransform(targetContainer, draggedBounds.Width, 0); } else { SetTranslateTransform(targetContainer, 0, draggedBounds.Height); } _targetIndex = _targetIndex == -1 ? targetIndex : targetIndex < _targetIndex ? targetIndex : _targetIndex; } else { if (orientation == Orientation.Horizontal) { SetTranslateTransform(targetContainer, 0, 0); } else { SetTranslateTransform(targetContainer, 0, 0); } } i++; } } } private void SetDraggingPseudoClasses(Control control, bool isDragging) { if (isDragging) { ((IPseudoClasses)control.Classes).Add(":dragging"); } else { ((IPseudoClasses)control.Classes).Remove(":dragging"); } } private void SetTranslateTransform(Control control, double x, double y) { var transformBuilder = new TransformOperations.Builder(1); transformBuilder.AppendTranslate(x, y); control.RenderTransform = transformBuilder.Build(); } }