From 04db0ef13b5b2ec68d201dd6214238f1eb45444f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=89=BE=E7=AB=B9?= Date: Thu, 26 Jan 2023 20:05:21 +0800 Subject: [PATCH] =?UTF-8?q?EnableSnapping=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Links/PathGeneratorsViewModel.cs | 40 ++++++ .../ViewModels/Links/RoutersViewModel.cs | 40 ++++++ .../ViewModels/Links/SnappingViewModel.cs | 32 +++++ .../Views/Links/PathGeneratorsView.xaml | 16 +++ .../Views/Links/PathGeneratorsView.xaml.cs | 26 ++++ .../Views/Links/RoutersView.xaml | 16 +++ .../Views/Links/RoutersView.xaml.cs | 26 ++++ .../Views/Links/SnappingView.xaml | 16 +++ .../Views/Links/SnappingView.xaml.cs | 26 ++++ .../Controls/DesignerCanvas.cs | 124 +++++++++++++++--- .../UserControls/LineControl.xaml | 6 +- .../Connector/FullyCreatedConnectorInfo.cs | 13 +- .../BaseViewModel/DiagramViewModel.cs | 35 ++++- .../ViewModels/IDiagramViewModel.cs | 8 ++ 14 files changed, 392 insertions(+), 32 deletions(-) create mode 100644 AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/Links/PathGeneratorsViewModel.cs create mode 100644 AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/Links/RoutersViewModel.cs create mode 100644 AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/Links/SnappingViewModel.cs create mode 100644 AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/PathGeneratorsView.xaml create mode 100644 AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/PathGeneratorsView.xaml.cs create mode 100644 AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/RoutersView.xaml create mode 100644 AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/RoutersView.xaml.cs create mode 100644 AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/SnappingView.xaml create mode 100644 AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/SnappingView.xaml.cs diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/Links/PathGeneratorsViewModel.cs b/AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/Links/PathGeneratorsViewModel.cs new file mode 100644 index 0000000..22a7e5e --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/Links/PathGeneratorsViewModel.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; + +namespace AIStudio.Wpf.DiagramDesigner.Demo.ViewModels +{ + class PathGeneratorsViewModel : BaseViewModel + { + public PathGeneratorsViewModel() + { + Title = "Link Path Generators"; + Info = "Path generators are functions that take as input the calculated route and output SVG paths, " + + "alongside the markers positions and their angles. There are currently two generators: Straight and Smooth."; + + _service.ColorViewModel.FillColor.Color = System.Windows.Media.Colors.Orange; + + DiagramViewModel = new DiagramViewModel(); + DiagramViewModel.PageSizeType = PageSizeType.Custom; + DiagramViewModel.PageSize = new Size(double.NaN, double.NaN); + + DefaultDesignerItemViewModel node1 = new DefaultDesignerItemViewModel() { Left = 50, Top = 80, Text = "1" }; + DiagramViewModel.DirectAddItemCommand.Execute(node1); + + DefaultDesignerItemViewModel node2 = new DefaultDesignerItemViewModel() { Left = 300, Top = 350, Text = "2" }; + DiagramViewModel.DirectAddItemCommand.Execute(node2); + + DefaultDesignerItemViewModel node3 = new DefaultDesignerItemViewModel() { Left = 400, Top = 100, Text = "3" }; + DiagramViewModel.DirectAddItemCommand.Execute(node3); + + ConnectionViewModel connector1 = new ConnectionViewModel(node1.RightConnector, node2.LeftConnector, DrawMode.ConnectingLineStraight, RouterMode.RouterNormal); + connector1.AddLabel("Straight"); + DiagramViewModel.DirectAddItemCommand.Execute(connector1); + + ConnectionViewModel connector2 = new ConnectionViewModel(node2.RightConnector, node3.LeftConnector, DrawMode.ConnectingLineSmooth, RouterMode.RouterNormal); + connector2.AddLabel("Smooth"); + DiagramViewModel.DirectAddItemCommand.Execute(connector2); + } + } +} diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/Links/RoutersViewModel.cs b/AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/Links/RoutersViewModel.cs new file mode 100644 index 0000000..406bb2d --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/Links/RoutersViewModel.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; + +namespace AIStudio.Wpf.DiagramDesigner.Demo.ViewModels +{ + class RoutersViewModel : BaseViewModel + { + public RoutersViewModel() + { + Title = "Link Routers"; + Info = "Routers are functions that take as input the link's vertices and can add points in between. " + + "There are currently two routers: Normal and Orthogonal."; + + _service.ColorViewModel.FillColor.Color = System.Windows.Media.Colors.Orange; + + DiagramViewModel = new DiagramViewModel(); + DiagramViewModel.PageSizeType = PageSizeType.Custom; + DiagramViewModel.PageSize = new Size(double.NaN, double.NaN); + + DefaultDesignerItemViewModel node1 = new DefaultDesignerItemViewModel() { Left = 50, Top = 80, Text = "1" }; + DiagramViewModel.DirectAddItemCommand.Execute(node1); + + DefaultDesignerItemViewModel node2 = new DefaultDesignerItemViewModel() { Left = 300, Top = 350, Text = "2" }; + DiagramViewModel.DirectAddItemCommand.Execute(node2); + + DefaultDesignerItemViewModel node3 = new DefaultDesignerItemViewModel() { Left = 350, Top = 100, Text = "3" }; + DiagramViewModel.DirectAddItemCommand.Execute(node3); + + ConnectionViewModel connector1 = new ConnectionViewModel(node1.RightConnector, node2.LeftConnector, DrawMode.ConnectingLineSmooth, RouterMode.RouterNormal); + connector1.AddLabel("Normal"); + DiagramViewModel.DirectAddItemCommand.Execute(connector1); + + ConnectionViewModel connector2 = new ConnectionViewModel(node2.RightConnector, node3.LeftConnector, DrawMode.ConnectingLineStraight, RouterMode.RouterOrthogonal); + connector2.AddLabel("Orthogonal"); + DiagramViewModel.DirectAddItemCommand.Execute(connector2); + } + } +} \ No newline at end of file diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/Links/SnappingViewModel.cs b/AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/Links/SnappingViewModel.cs new file mode 100644 index 0000000..4962f2b --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner.Demo/ViewModels/Links/SnappingViewModel.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; + +namespace AIStudio.Wpf.DiagramDesigner.Demo.ViewModels +{ + class SnappingViewModel : BaseViewModel + { + public SnappingViewModel() + { + Title = "Link Snapping"; + Info = "While dragging a new link, it will try to find (and link) to the closest target within a radius."; + + _service.ColorViewModel.FillColor.Color = System.Windows.Media.Colors.Orange; + + DiagramViewModel = new DiagramViewModel(); + DiagramViewModel.PageSizeType = PageSizeType.Custom; + DiagramViewModel.PageSize = new Size(double.NaN, double.NaN); + DiagramViewModel.EnableSnapping = true; + + DefaultDesignerItemViewModel node1 = new DefaultDesignerItemViewModel() { Left = 50, Top = 50, Text = "1" }; + DiagramViewModel.DirectAddItemCommand.Execute(node1); + + DefaultDesignerItemViewModel node2 = new DefaultDesignerItemViewModel() { Left = 300, Top = 300, Text = "2" }; + DiagramViewModel.DirectAddItemCommand.Execute(node2); + + DefaultDesignerItemViewModel node3 = new DefaultDesignerItemViewModel() { Left = 300, Top = 50, Text = "3" }; + DiagramViewModel.DirectAddItemCommand.Execute(node3); + } + } +} diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/PathGeneratorsView.xaml b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/PathGeneratorsView.xaml new file mode 100644 index 0000000..725637f --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/PathGeneratorsView.xaml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/PathGeneratorsView.xaml.cs b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/PathGeneratorsView.xaml.cs new file mode 100644 index 0000000..5dbbc52 --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/PathGeneratorsView.xaml.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace AIStudio.Wpf.DiagramDesigner.Demo.Views +{ + /// + /// PathGeneratorsView.xaml 的交互逻辑 + /// + public partial class PathGeneratorsView : UserControl + { + public PathGeneratorsView() + { + InitializeComponent(); + } + } +} diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/RoutersView.xaml b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/RoutersView.xaml new file mode 100644 index 0000000..ec7b57f --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/RoutersView.xaml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/RoutersView.xaml.cs b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/RoutersView.xaml.cs new file mode 100644 index 0000000..adc09aa --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/RoutersView.xaml.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace AIStudio.Wpf.DiagramDesigner.Demo.Views +{ + /// + /// RoutersView.xaml 的交互逻辑 + /// + public partial class RoutersView : UserControl + { + public RoutersView() + { + InitializeComponent(); + } + } +} diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/SnappingView.xaml b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/SnappingView.xaml new file mode 100644 index 0000000..ee7e670 --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/SnappingView.xaml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/SnappingView.xaml.cs b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/SnappingView.xaml.cs new file mode 100644 index 0000000..4fddc73 --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner.Demo/Views/Links/SnappingView.xaml.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace AIStudio.Wpf.DiagramDesigner.Demo.Views +{ + /// + /// SnappingView.xaml 的交互逻辑 + /// + public partial class SnappingView : UserControl + { + public SnappingView() + { + InitializeComponent(); + } + } +} diff --git a/AIStudio.Wpf.DiagramDesigner/Controls/DesignerCanvas.cs b/AIStudio.Wpf.DiagramDesigner/Controls/DesignerCanvas.cs index 2f07c50..e5c9ac0 100644 --- a/AIStudio.Wpf.DiagramDesigner/Controls/DesignerCanvas.cs +++ b/AIStudio.Wpf.DiagramDesigner/Controls/DesignerCanvas.cs @@ -17,8 +17,20 @@ namespace AIStudio.Wpf.DiagramDesigner { public class DesignerCanvas : Canvas { - private IDiagramViewModel _viewModel { get { return DataContext as IDiagramViewModel; } } - private IDiagramServiceProvider _service { get { return DiagramServicesProvider.Instance.Provider; } } + private IDiagramViewModel _viewModel + { + get + { + return DataContext as IDiagramViewModel; + } + } + private IDiagramServiceProvider _service + { + get + { + return DiagramServicesProvider.Instance.Provider; + } + } private ConnectionViewModel partialConnection; private List connectorsHit = new List(); @@ -54,6 +66,22 @@ namespace AIStudio.Wpf.DiagramDesigner } } + private bool EnableSnapping + { + get + { + return _viewModel.EnableSnapping; + } + } + + private double SnappingRadius + { + get + { + return _viewModel.SnappingRadius; + } + } + #region GridCellSize public static readonly DependencyProperty GridCellSizeProperty = @@ -64,8 +92,14 @@ namespace AIStudio.Wpf.DiagramDesigner public Size GridCellSize { - get { return (Size)GetValue(GridCellSizeProperty); } - set { SetValue(GridCellSizeProperty, value); } + get + { + return (Size)GetValue(GridCellSizeProperty); + } + set + { + SetValue(GridCellSizeProperty, value); + } } #endregion @@ -80,8 +114,14 @@ namespace AIStudio.Wpf.DiagramDesigner public bool ShowGrid { - get { return (bool)GetValue(ShowGridProperty); } - set { SetValue(ShowGridProperty, value); } + get + { + return (bool)GetValue(ShowGridProperty); + } + set + { + SetValue(ShowGridProperty, value); + } } #endregion @@ -96,8 +136,14 @@ namespace AIStudio.Wpf.DiagramDesigner public Color GridColor { - get { return (Color)GetValue(GridColorProperty); } - set { SetValue(GridColorProperty, value); } + get + { + return (Color)GetValue(GridColorProperty); + } + set + { + SetValue(GridColorProperty, value); + } } #endregion @@ -112,8 +158,14 @@ namespace AIStudio.Wpf.DiagramDesigner public Size GridMarginSize { - get { return (Size)GetValue(GridMarginSizeProperty); } - set { SetValue(GridMarginSizeProperty, value); } + get + { + return (Size)GetValue(GridMarginSizeProperty); + } + set + { + SetValue(GridMarginSizeProperty, value); + } } #endregion @@ -204,7 +256,10 @@ namespace AIStudio.Wpf.DiagramDesigner private Connector sourceConnector; public Connector SourceConnector { - get { return sourceConnector; } + get + { + return sourceConnector; + } set { if (sourceConnector != value) @@ -218,7 +273,7 @@ namespace AIStudio.Wpf.DiagramDesigner Point point = new Point(rectangleBounds.Left + (rectangleBounds.Width / 2), rectangleBounds.Bottom + (rectangleBounds.Height / 2)); partialConnection = new ConnectionViewModel(_viewModel, sourceDataItem, new PartCreatedConnectorInfo(point.X, point.Y), DrawMode, RouterMode); - + _viewModel.DirectAddItemCommand.Execute(partialConnection); } } @@ -227,7 +282,10 @@ namespace AIStudio.Wpf.DiagramDesigner private FullyCreatedConnectorInfo sourceConnectorInfo; public FullyCreatedConnectorInfo SourceConnectorInfo { - get { return sourceConnectorInfo; } + get + { + return sourceConnectorInfo; + } set { if (sourceConnectorInfo != value) @@ -288,7 +346,7 @@ namespace AIStudio.Wpf.DiagramDesigner { if (connectorsHit.Count == 0) { - LinkPointDesignerItemViewModel pointItemView = new LinkPointDesignerItemViewModel(rubberbandSelectionStartPoint.Value); + LinkPointDesignerItemViewModel pointItemView = new LinkPointDesignerItemViewModel(rubberbandSelectionStartPoint.Value); _viewModel.DirectAddItemCommand.Execute(pointItemView); SourceConnectorInfo = pointItemView.TopConnector; } @@ -311,8 +369,7 @@ namespace AIStudio.Wpf.DiagramDesigner if (_service.DrawModeViewModel.CursorMode == CursorMode.Move) { - _viewModel.SelectedItems.OfType().ToList().ForEach(p => - { + _viewModel.SelectedItems.OfType().ToList().ForEach(p => { p.Left = currentPoint.X; p.Top = currentPoint.Y; }); @@ -325,6 +382,19 @@ namespace AIStudio.Wpf.DiagramDesigner { partialConnection.SinkConnectorInfo = new PartCreatedConnectorInfo(currentPoint.X, currentPoint.Y); HitTesting(currentPoint); + + if (EnableSnapping) + { + var nearPort = FindNearPortToAttachTo(); + if (nearPort != null || partialConnection.SinkConnectorInfoFully != null) + { + //var oldPort = _ongoingLink.TargetPort; + //_ongoingLink.SetTargetPort(nearPort); + //oldPort?.Refresh(); + //nearPort?.Refresh(); + partialConnection.SinkConnectorInfo = nearPort; + } + } } } else @@ -377,6 +447,10 @@ namespace AIStudio.Wpf.DiagramDesigner sinkDataItem.DataItem.Root.DirectRemoveItemCommand.Execute( sinkDataItem.DataItem.Root.Items[indexOfLastTempConnection]); sinkDataItem.DataItem.Root.AddItemCommand.Execute(new ConnectionViewModel(_viewModel, sourceDataItem, sinkDataItem, DrawMode, RouterMode)); + } + else if (partialConnection.IsFullConnection) + { + } else if (_service.DrawModeViewModel.GetDrawMode() == DrawMode.DirectLine && connectorsHit.Count() == 1) { @@ -403,7 +477,7 @@ namespace AIStudio.Wpf.DiagramDesigner } } - + connectorsHit = new List(); sourceConnector = null; sourceConnectorInfo = null; @@ -508,7 +582,7 @@ namespace AIStudio.Wpf.DiagramDesigner itemBase.Suffix = System.IO.Path.GetExtension(itemBase.Icon).ToLower(); itemBase.InitWidthAndHeight(); itemBase.AutoSize(); - + itemBase.Left = Math.Max(0, position.X - itemBase.ItemWidth / 2); itemBase.Top = Math.Max(0, position.Y - itemBase.ItemHeight / 2); (DataContext as IDiagramViewModel).AddItemCommand.Execute(itemBase); @@ -516,5 +590,17 @@ namespace AIStudio.Wpf.DiagramDesigner } e.Handled = true; } + + public FullyCreatedConnectorInfo FindNearPortToAttachTo() + { + foreach (var port in _viewModel.Items.OfType().ToList().SelectMany(n => n.Connectors)) + { + if (partialConnection.OnGoingPosition.DistanceTo(port.Position) < SnappingRadius && + partialConnection.SourceConnectorInfo.CanAttachTo(port)) + return port; + } + + return null; + } } -} +} \ No newline at end of file diff --git a/AIStudio.Wpf.DiagramDesigner/UserControls/LineControl.xaml b/AIStudio.Wpf.DiagramDesigner/UserControls/LineControl.xaml index d39c732..08da5df 100644 --- a/AIStudio.Wpf.DiagramDesigner/UserControls/LineControl.xaml +++ b/AIStudio.Wpf.DiagramDesigner/UserControls/LineControl.xaml @@ -44,8 +44,7 @@ port != this && !port.IsReadOnly && DataItem != port.DataItem; } diff --git a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DiagramViewModel.cs b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DiagramViewModel.cs index e3fceeb..daf8d92 100644 --- a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DiagramViewModel.cs +++ b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/DiagramViewModel.cs @@ -366,6 +366,31 @@ namespace AIStudio.Wpf.DiagramDesigner } } + private bool _enableSnapping; + public bool EnableSnapping + { + get + { + return _enableSnapping; + } + set + { + SetProperty(ref _enableSnapping, value); + } + } + + private double _snappingRadius = 50; + public double SnappingRadius + { + get + { + return _snappingRadius; + } + set + { + SetProperty(ref _snappingRadius, value); + } + } private bool _isEditName; [Browsable(false)] @@ -1089,7 +1114,7 @@ namespace AIStudio.Wpf.DiagramDesigner }); } } - + private void ExecuteBringForwardCommand(object parameter) { @@ -1391,7 +1416,7 @@ namespace AIStudio.Wpf.DiagramDesigner SelectedItems.OfType().ToList(); List selectedConnections = - SelectedItems.OfType().ToList(); + SelectedItems.OfType().ToList(); foreach (ConnectionViewModel connection in Items.OfType()) { @@ -1444,7 +1469,7 @@ namespace AIStudio.Wpf.DiagramDesigner foreach (var diagramItemData in copyitem.DesignerItems) { DesignerItemViewModelBase newItem = null; - + Type type = TypeHelper.GetType(diagramItemData.ModelTypeName); DesignerItemViewModelBase itemBase = Activator.CreateInstance(type, this, diagramItemData, ".json") as DesignerItemViewModelBase; @@ -1460,7 +1485,7 @@ namespace AIStudio.Wpf.DiagramDesigner { items.Add(newItem); } - } + } DirectAddItemCommand.Execute(items); OffsetX += 10; @@ -1751,7 +1776,7 @@ namespace AIStudio.Wpf.DiagramDesigner { } - + diff --git a/AIStudio.Wpf.DiagramDesigner/ViewModels/IDiagramViewModel.cs b/AIStudio.Wpf.DiagramDesigner/ViewModels/IDiagramViewModel.cs index fc613a9..a594274 100644 --- a/AIStudio.Wpf.DiagramDesigner/ViewModels/IDiagramViewModel.cs +++ b/AIStudio.Wpf.DiagramDesigner/ViewModels/IDiagramViewModel.cs @@ -228,6 +228,14 @@ namespace AIStudio.Wpf.DiagramDesigner { get; set; } + bool EnableSnapping + { + get; set; + } + double SnappingRadius + { + get; set; + } Size GridMarginSize { get; set;