DragAndDrop core

This commit is contained in:
FrigaT
2026-01-18 16:33:35 +03:00
parent 9ea82af329
commit 79bdd8bc62
229 changed files with 21214 additions and 2494 deletions

View File

@@ -0,0 +1,213 @@
using Lattice.Core.Geometry;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System;
namespace Lattice.UI.DragDrop.WinUI.Controls;
/// <summary>
/// Визуальный элемент для отображения перетаскиваемого объекта.
/// </summary>
public class DragAdorner : Control
{
/// <summary>
/// Идентификатор свойства для данных перетаскивания.
/// </summary>
public static readonly DependencyProperty DragDataProperty =
DependencyProperty.Register(
"DragData",
typeof(object),
typeof(DragAdorner),
new PropertyMetadata(null, OnDragDataChanged));
/// <summary>
/// Идентификатор свойства для смещения относительно курсора.
/// </summary>
public static readonly DependencyProperty OffsetProperty =
DependencyProperty.Register(
"Offset",
typeof(Point),
typeof(DragAdorner),
new PropertyMetadata(new Point(0, 0)));
/// <summary>
/// Идентификатор свойства для угла поворота.
/// </summary>
public static readonly DependencyProperty RotationAngleProperty =
DependencyProperty.Register(
"RotationAngle",
typeof(double),
typeof(DragAdorner),
new PropertyMetadata(0.0));
/// <summary>
/// Идентификатор свойства для прозрачности.
/// </summary>
public static readonly DependencyProperty OpacityLevelProperty =
DependencyProperty.Register(
"OpacityLevel",
typeof(double),
typeof(DragAdorner),
new PropertyMetadata(0.7));
private ContentPresenter? _contentPresenter;
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragAdorner"/>.
/// </summary>
public DragAdorner()
{
DefaultStyleKey = typeof(DragAdorner);
// Устанавливаем свойства для корректного отображения поверх других элементов
IsHitTestVisible = false;
UseSystemFocusVisuals = false;
}
/// <summary>
/// Получает или задает данные перетаскивания.
/// </summary>
public object DragData
{
get => GetValue(DragDataProperty);
set => SetValue(DragDataProperty, value);
}
/// <summary>
/// Получает или задает смещение относительно курсора.
/// </summary>
public Point Offset
{
get => (Point)GetValue(OffsetProperty);
set => SetValue(OffsetProperty, value);
}
/// <summary>
/// Получает или задает угол поворота.
/// </summary>
public double RotationAngle
{
get => (double)GetValue(RotationAngleProperty);
set => SetValue(RotationAngleProperty, value);
}
/// <summary>
/// Получает или задает уровень прозрачности.
/// </summary>
public double OpacityLevel
{
get => (double)GetValue(OpacityLevelProperty);
set => SetValue(OpacityLevelProperty, value);
}
/// <inheritdoc/>
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
_contentPresenter = GetTemplateChild("PART_ContentPresenter") as ContentPresenter;
UpdateContent();
}
/// <summary>
/// Обновляет позицию элемента относительно курсора.
/// </summary>
/// <param name="cursorPosition">Позиция курсора в экранных координатах.</param>
public void UpdatePosition(Point cursorPosition)
{
var transform = new TranslateTransform
{
X = cursorPosition.X + Offset.X,
Y = cursorPosition.Y + Offset.Y
};
RenderTransform = new TransformGroup
{
Children =
{
transform,
new RotateTransform { Angle = RotationAngle }
}
};
}
/// <summary>
/// Показывает элемент с анимацией.
/// </summary>
public void Show()
{
Visibility = Visibility.Visible;
// Анимация появления
var animation = new Microsoft.UI.Xaml.Media.Animation.DoubleAnimation
{
From = 0,
To = OpacityLevel,
Duration = TimeSpan.FromMilliseconds(150),
EasingFunction = new Microsoft.UI.Xaml.Media.Animation.CubicEase
{
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut
}
};
var storyboard = new Microsoft.UI.Xaml.Media.Animation.Storyboard();
storyboard.Children.Add(animation);
Microsoft.UI.Xaml.Media.Animation.Storyboard.SetTarget(animation, this);
Microsoft.UI.Xaml.Media.Animation.Storyboard.SetTargetProperty(animation, "Opacity");
storyboard.Begin();
}
/// <summary>
/// Скрывает элемент с анимацией.
/// </summary>
public void Hide()
{
var animation = new Microsoft.UI.Xaml.Media.Animation.DoubleAnimation
{
From = Opacity,
To = 0,
Duration = TimeSpan.FromMilliseconds(100),
EasingFunction = new Microsoft.UI.Xaml.Media.Animation.CubicEase
{
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseIn
}
};
animation.Completed += (s, e) =>
{
Visibility = Visibility.Collapsed;
};
var storyboard = new Microsoft.UI.Xaml.Media.Animation.Storyboard();
storyboard.Children.Add(animation);
Microsoft.UI.Xaml.Media.Animation.Storyboard.SetTarget(animation, this);
Microsoft.UI.Xaml.Media.Animation.Storyboard.SetTargetProperty(animation, "Opacity");
storyboard.Begin();
}
private static void OnDragDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is DragAdorner adorner)
{
adorner.UpdateContent();
}
}
private void UpdateContent()
{
if (_contentPresenter != null)
{
// Можно добавить DataTemplateSelector для разных типов данных
_contentPresenter.Content = DragData;
// Автоматически вычисляем смещение для приятного вида
if (DragData is FrameworkElement element)
{
Offset = new Point(-element.ActualWidth / 2, -element.ActualHeight / 2);
}
}
}
}

View File

@@ -0,0 +1,156 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Lattice.UI.DragDrop.WinUI.Controls;
/// <summary>
/// Оверлей для отображения визуальных элементов перетаскивания.
/// </summary>
public class DragDropOverlay : Canvas
{
private readonly List<UIElement> _dragVisuals = new();
private readonly List<DropPreviewAdorner> _dropAdorners = new();
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DragDropOverlay"/>.
/// </summary>
public DragDropOverlay()
{
IsHitTestVisible = false;
Background = null;
// Устанавливаем высокий Z-Index, чтобы быть поверх всего
Canvas.SetZIndex(this, 10000);
}
/// <summary>
/// Показывает визуальное представление перетаскивания.
/// </summary>
/// <param name="dragVisual">Визуальное представление.</param>
/// <param name="initialX">Начальная позиция X.</param>
/// <param name="initialY">Начальная позиция Y.</param>
public void ShowDragVisual(UIElement dragVisual, double initialX, double initialY)
{
if (!Children.Contains(dragVisual))
{
Children.Add(dragVisual);
_dragVisuals.Add(dragVisual);
}
SetLeft(dragVisual, initialX);
SetTop(dragVisual, initialY);
dragVisual.Visibility = Visibility.Visible;
}
/// <summary>
/// Обновляет позицию визуального представления перетаскивания.
/// </summary>
/// <param name="dragVisual">Визуальное представление.</param>
/// <param name="x">Новая позиция X.</param>
/// <param name="y">Новая позиция Y.</param>
public void UpdateDragVisualPosition(UIElement dragVisual, double x, double y)
{
if (Children.Contains(dragVisual))
{
SetLeft(dragVisual, x);
SetTop(dragVisual, y);
}
}
/// <summary>
/// Скрывает визуальное представление перетаскивания.
/// </summary>
/// <param name="dragVisual">Визуальное представление.</param>
public void HideDragVisual(UIElement dragVisual)
{
dragVisual.Visibility = Visibility.Collapsed;
if (Children.Contains(dragVisual))
{
Children.Remove(dragVisual);
_dragVisuals.Remove(dragVisual);
}
}
/// <summary>
/// Показывает предварительный просмотр области сброса.
/// </summary>
/// <param name="bounds">Границы области.</param>
/// <returns>Созданный элемент предварительного просмотра.</returns>
public DropPreviewAdorner ShowDropPreview(Core.Geometry.Rect bounds)
{
var adorner = new DropPreviewAdorner
{
PreviewColor = Windows.UI.Color.FromArgb(100, 0, 120, 215),
PreviewThickness = 2.0
};
Children.Add(adorner);
_dropAdorners.Add(adorner);
adorner.Show(bounds);
return adorner;
}
/// <summary>
/// Обновляет предварительный просмотр области сброса.
/// </summary>
/// <param name="adorner">Элемент предварительного просмотра.</param>
/// <param name="bounds">Новые границы.</param>
public void UpdateDropPreview(DropPreviewAdorner adorner, Core.Geometry.Rect bounds)
{
adorner.UpdatePosition(bounds);
}
/// <summary>
/// Скрывает все предварительные просмотры областей сброса.
/// </summary>
public void HideAllDropPreviews()
{
foreach (var adorner in _dropAdorners.ToList())
{
adorner.Hide();
var timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(200)
};
timer.Tick += (s, e) =>
{
timer.Stop();
if (Children.Contains(adorner))
{
Children.Remove(adorner);
}
_dropAdorners.Remove(adorner);
};
timer.Start();
}
}
/// <summary>
/// Скрывает все визуальные элементы.
/// </summary>
public void ClearAllVisuals()
{
foreach (var visual in _dragVisuals.ToList())
{
HideDragVisual(visual);
}
HideAllDropPreviews();
}
/// <summary>
/// Получает текущий элемент перетаскивания.
/// </summary>
/// <returns>Элемент перетаскивания или null.</returns>
public UIElement? GetCurrentDragVisual()
{
return _dragVisuals.FirstOrDefault();
}
}

View File

@@ -0,0 +1,141 @@
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System;
using Windows.UI;
namespace Lattice.UI.DragDrop.WinUI.Controls;
/// <summary>
/// Визуальный элемент для предварительного просмотра области сброса.
/// </summary>
[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualState(Name = "Highlighted", GroupName = "CommonStates")]
public class DropPreviewAdorner : Control
{
/// <summary>
/// Идентификатор свойства для цвета предварительного просмотра.
/// </summary>
public static readonly DependencyProperty PreviewColorProperty =
DependencyProperty.Register(
"PreviewColor",
typeof(Color),
typeof(DropPreviewAdorner),
new PropertyMetadata(Colors.DodgerBlue));
/// <summary>
/// Идентификатор свойства для толщины границы.
/// </summary>
public static readonly DependencyProperty PreviewThicknessProperty =
DependencyProperty.Register(
"PreviewThickness",
typeof(double),
typeof(DropPreviewAdorner),
new PropertyMetadata(2.0));
/// <summary>
/// Идентификатор свойства для кисти границы.
/// </summary>
public static readonly DependencyProperty PreviewBrushProperty =
DependencyProperty.Register(
"PreviewBrush",
typeof(Brush),
typeof(DropPreviewAdorner),
new PropertyMetadata(null));
/// <summary>
/// Инициализирует новый экземпляр класса <see cref="DropPreviewAdorner"/>.
/// </summary>
public DropPreviewAdorner()
{
DefaultStyleKey = typeof(DropPreviewAdorner);
IsHitTestVisible = false;
}
/// <summary>
/// Получает или задает цвет предварительного просмотра.
/// </summary>
public Color PreviewColor
{
get => (Color)GetValue(PreviewColorProperty);
set => SetValue(PreviewColorProperty, value);
}
/// <summary>
/// Получает или задает толщину границы.
/// </summary>
public double PreviewThickness
{
get => (double)GetValue(PreviewThicknessProperty);
set => SetValue(PreviewThicknessProperty, value);
}
/// <summary>
/// Получает или задает кисть границы.
/// </summary>
public Brush PreviewBrush
{
get => (Brush)GetValue(PreviewBrushProperty);
set => SetValue(PreviewBrushProperty, value);
}
/// <summary>
/// Показывает элемент с указанными границами.
/// </summary>
/// <param name="bounds">Границы для отображения.</param>
public void Show(Core.Geometry.Rect bounds)
{
Width = bounds.Width;
Height = bounds.Height;
var translateTransform = new TranslateTransform
{
X = bounds.X,
Y = bounds.Y
};
RenderTransform = translateTransform;
Visibility = Visibility.Visible;
VisualStateManager.GoToState(this, "Highlighted", true);
}
/// <summary>
/// Скрывает элемент.
/// </summary>
public void Hide()
{
VisualStateManager.GoToState(this, "Normal", true);
// Отложенное скрытие для плавной анимации
var timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(150)
};
timer.Tick += (s, e) =>
{
timer.Stop();
Visibility = Visibility.Collapsed;
};
timer.Start();
}
/// <summary>
/// Обновляет позицию элемента.
/// </summary>
/// <param name="bounds">Новые границы.</param>
public void UpdatePosition(Core.Geometry.Rect bounds)
{
if (RenderTransform is TranslateTransform transform)
{
transform.X = bounds.X;
transform.Y = bounds.Y;
}
Width = bounds.Width;
Height = bounds.Height;
}
}