DragAndDrop core
This commit is contained in:
213
Lattice.UI.DragDrop.WinUI/Controls/DragAdorner.cs
Normal file
213
Lattice.UI.DragDrop.WinUI/Controls/DragAdorner.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
156
Lattice.UI.DragDrop.WinUI/Controls/DragDropOverlay.cs
Normal file
156
Lattice.UI.DragDrop.WinUI/Controls/DragDropOverlay.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
141
Lattice.UI.DragDrop.WinUI/Controls/DropPreviewAdorner.cs
Normal file
141
Lattice.UI.DragDrop.WinUI/Controls/DropPreviewAdorner.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user