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