From 141ba3b775d6385fa81ac74e63fd1526411a8b65 Mon Sep 17 00:00:00 2001 From: akwkevin Date: Mon, 3 Feb 2025 15:22:00 +0800 Subject: [PATCH] =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AIStudio.Wpf.DiagramDesigner.csproj | 2 +- .../Adorners/DrawingRubberbandAdorner.cs | 15 + .../Adorners/RubberbandAdorner.cs | 21 +- .../AttachedProperties/DragAndDropProps.cs | 2 + .../Common/BindableBase.cs | 4 +- .../Controls/DesignerCanvas.cs | 100 +-- .../Helpers/CopyHelper.cs | 15 +- .../Models/DragObject.cs | 11 + .../Models/ToolBoxData.cs | 8 + .../Routers/Pather.cs | 727 ++++++++++++++++++ .../UserControls/ConnectorContainer.xaml.cs | 17 + .../Connector/FullyCreatedConnectorInfo.cs | 4 + .../DrawingDesignerItemViewModelBase.cs | 8 +- .../ViewModels/LogicalGateItemViewModel.cs | 3 +- 14 files changed, 871 insertions(+), 66 deletions(-) create mode 100644 AIStudio.Wpf.DiagramDesigner/Routers/Pather.cs diff --git a/AIStudio.Wpf.DiagramDesigner/AIStudio.Wpf.DiagramDesigner.csproj b/AIStudio.Wpf.DiagramDesigner/AIStudio.Wpf.DiagramDesigner.csproj index 81ea81a..0868aa8 100644 --- a/AIStudio.Wpf.DiagramDesigner/AIStudio.Wpf.DiagramDesigner.csproj +++ b/AIStudio.Wpf.DiagramDesigner/AIStudio.Wpf.DiagramDesigner.csproj @@ -8,7 +8,7 @@ A.png - 1.1.9 + 1.2.0 一个Wpf的Diagram控件基础库 diff --git a/AIStudio.Wpf.DiagramDesigner/Adorners/DrawingRubberbandAdorner.cs b/AIStudio.Wpf.DiagramDesigner/Adorners/DrawingRubberbandAdorner.cs index 66319ec..06fe7b8 100644 --- a/AIStudio.Wpf.DiagramDesigner/Adorners/DrawingRubberbandAdorner.cs +++ b/AIStudio.Wpf.DiagramDesigner/Adorners/DrawingRubberbandAdorner.cs @@ -36,6 +36,21 @@ namespace AIStudio.Wpf.DiagramDesigner } } + private IDrawModeViewModel DrawModeViewModel + { + get + { + if (_viewModel.DrawModeViewModel != null) + { + return _viewModel.DrawModeViewModel; + } + else + { + return _service.DrawModeViewModel; + } + } + } + private DrawMode DrawMode { get diff --git a/AIStudio.Wpf.DiagramDesigner/Adorners/RubberbandAdorner.cs b/AIStudio.Wpf.DiagramDesigner/Adorners/RubberbandAdorner.cs index d2c62fd..015c7ee 100644 --- a/AIStudio.Wpf.DiagramDesigner/Adorners/RubberbandAdorner.cs +++ b/AIStudio.Wpf.DiagramDesigner/Adorners/RubberbandAdorner.cs @@ -36,6 +36,21 @@ namespace AIStudio.Wpf.DiagramDesigner } } + private IDrawModeViewModel DrawModeViewModel + { + get + { + if (_viewModel.DrawModeViewModel != null) + { + return _viewModel.DrawModeViewModel; + } + else + { + return _service.DrawModeViewModel; + } + } + } + public RubberbandAdorner(DesignerCanvas designerCanvas, Point? dragStartPoint) : base(designerCanvas) { @@ -77,11 +92,11 @@ namespace AIStudio.Wpf.DiagramDesigner if (adornerLayer != null) adornerLayer.Remove(this); - if (this._service.DrawModeViewModel.GetDrawMode() == DrawMode.Text) + if (this.DrawModeViewModel.GetDrawMode() == DrawMode.Text) { if (this.startPoint.HasValue && this.endPoint.HasValue) { - if (this._service.DrawModeViewModel.GetDrawMode() == DrawMode.Text) + if (this.DrawModeViewModel.GetDrawMode() == DrawMode.Text) { TextDesignerItemViewModel itemBase = new TextDesignerItemViewModel(); Point position = e.GetPosition(this); @@ -93,7 +108,7 @@ namespace AIStudio.Wpf.DiagramDesigner _viewModel.AddCommand.Execute(itemBase); } } - this._service.DrawModeViewModel.ResetDrawMode(); + this.DrawModeViewModel.ResetDrawMode(); } e.Handled = true; diff --git a/AIStudio.Wpf.DiagramDesigner/AttachedProperties/DragAndDropProps.cs b/AIStudio.Wpf.DiagramDesigner/AttachedProperties/DragAndDropProps.cs index 8c59fdf..d927628 100644 --- a/AIStudio.Wpf.DiagramDesigner/AttachedProperties/DragAndDropProps.cs +++ b/AIStudio.Wpf.DiagramDesigner/AttachedProperties/DragAndDropProps.cs @@ -83,9 +83,11 @@ namespace AIStudio.Wpf.DiagramDesigner dataObject.ContentType = toolBoxData.Type; dataObject.DesiredSize = toolBoxData.DesiredSize; dataObject.DesiredMinSize = toolBoxData.DesiredMinSize; + dataObject.ConnectorInfo = toolBoxData.ConnectorInfo; dataObject.Icon = toolBoxData.Icon; dataObject.Text = toolBoxData.Text; dataObject.ColorViewModel = toolBoxData.ColorViewModel; + dataObject.FontViewModel = toolBoxData.FontViewModel; if (toolBoxData.Addition is DesignerItemViewModelBase designerItemViewModelBase) { dataObject.DesignerItem = designerItemViewModelBase.ToSerializableItem(); diff --git a/AIStudio.Wpf.DiagramDesigner/Common/BindableBase.cs b/AIStudio.Wpf.DiagramDesigner/Common/BindableBase.cs index 1b13bc1..90209d8 100644 --- a/AIStudio.Wpf.DiagramDesigner/Common/BindableBase.cs +++ b/AIStudio.Wpf.DiagramDesigner/Common/BindableBase.cs @@ -66,7 +66,7 @@ namespace AIStudio.Wpf.DiagramDesigner if (EqualityComparer.Default.Equals(storage, value)) return false; - if (propertyName == "IsSelected") + if (propertyName == "IsSelected" || propertyName == "IsReadOnly") { } @@ -100,7 +100,7 @@ namespace AIStudio.Wpf.DiagramDesigner { if (EqualityComparer.Default.Equals(storage, value)) return false; - if (propertyName == "IsSelected") + if (propertyName == "IsSelected" || propertyName == "IsReadOnly") { } diff --git a/AIStudio.Wpf.DiagramDesigner/Controls/DesignerCanvas.cs b/AIStudio.Wpf.DiagramDesigner/Controls/DesignerCanvas.cs index 87ad511..a8ad819 100644 --- a/AIStudio.Wpf.DiagramDesigner/Controls/DesignerCanvas.cs +++ b/AIStudio.Wpf.DiagramDesigner/Controls/DesignerCanvas.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; @@ -57,7 +58,7 @@ namespace AIStudio.Wpf.DiagramDesigner Point point = sourceDataItem.MiddlePosition; - _partialConnection = new ConnectionViewModel(_viewModel, sourceDataItem, new PartCreatedConnectorInfo(point.X, point.Y), LineDrawMode, RouterMode); + _partialConnection = new ConnectionViewModel(_viewModel, sourceDataItem, new PartCreatedConnectorInfo(point.X, point.Y), DrawModeViewModel.LineDrawMode, DrawModeViewModel.LineRouterMode); _viewModel.Add(_partialConnection); _partialConnection.ZIndex = -1; @@ -110,6 +111,20 @@ namespace AIStudio.Wpf.DiagramDesigner } } + private IDrawModeViewModel DrawModeViewModel + { + get + { + if (_viewModel.DrawModeViewModel != null) + { + return _viewModel.DrawModeViewModel; + } + else + { + return _service.DrawModeViewModel; + } + } + } private DrawMode DrawMode { get @@ -125,36 +140,6 @@ namespace AIStudio.Wpf.DiagramDesigner } } - private DrawMode LineDrawMode - { - get - { - if (_viewModel.DrawModeViewModel != null) - { - return _viewModel.DrawModeViewModel.LineDrawMode; - } - else - { - return _service.DrawModeViewModel.LineDrawMode; - } - } - } - - private RouterMode RouterMode - { - get - { - if (_viewModel.DrawModeViewModel != null) - { - return _viewModel.DrawModeViewModel.LineRouterMode; - } - else - { - return _service.DrawModeViewModel.LineRouterMode; - } - } - } - #region GridCellSize public static readonly DependencyProperty GridCellSizeProperty = @@ -511,22 +496,22 @@ namespace AIStudio.Wpf.DiagramDesigner { if (e.PropertyName == nameof(CursorMode)) { - if (_service.DrawModeViewModel.CursorMode == CursorMode.Format) + if (DrawModeViewModel.CursorMode == CursorMode.Format) { EnterFormat(); } - else if (_service.DrawModeViewModel.CursorMode == CursorMode.Move) + else if (DrawModeViewModel.CursorMode == CursorMode.Move) { EnterMove(); } - else if (_service.DrawModeViewModel.CursorMode == CursorMode.Exit) + else if (DrawModeViewModel.CursorMode == CursorMode.Exit) { ExitCursor(); } } - else if (e.PropertyName == nameof(_service.DrawModeViewModel.DrawingDrawMode)) + else if (e.PropertyName == nameof(DrawModeViewModel.DrawingDrawMode)) { - if (_service.DrawModeViewModel.DrawingDrawMode == DrawMode.ColorPicker) + if (DrawModeViewModel.DrawingDrawMode == DrawMode.ColorPicker) { EnterColorPicker(); } @@ -576,7 +561,7 @@ namespace AIStudio.Wpf.DiagramDesigner { item.IsHitTestVisible = true; } - _service.DrawModeViewModel.CursorMode = CursorMode.Normal; + DrawModeViewModel.CursorMode = CursorMode.Normal; } private void Format(SelectableDesignerItemViewModelBase source, SelectableDesignerItemViewModelBase target) @@ -596,7 +581,7 @@ namespace AIStudio.Wpf.DiagramDesigner - if (_service.DrawModeViewModel.CursorMode == CursorMode.Format) + if (DrawModeViewModel.CursorMode == CursorMode.Format) { var element = (e.OriginalSource as FrameworkElement); if (element.DataContext is SelectableDesignerItemViewModelBase target) @@ -607,7 +592,7 @@ namespace AIStudio.Wpf.DiagramDesigner ExitCursor(); } - else if (_service.DrawModeViewModel.CursorMode == CursorMode.Move) + else if (DrawModeViewModel.CursorMode == CursorMode.Move) { ExitCursor(); return; @@ -629,8 +614,8 @@ namespace AIStudio.Wpf.DiagramDesigner } - if (_service.DrawModeViewModel.SharpDrawModeSelected || - (_service.DrawModeViewModel.DrawingDrawModeSelected && _service.DrawModeViewModel.DrawingDrawMode != DrawMode.Select)) + if (DrawModeViewModel.SharpDrawModeSelected || + (DrawModeViewModel.DrawingDrawModeSelected && DrawModeViewModel.DrawingDrawMode != DrawMode.Select)) { // create rubberband adorner AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this); @@ -643,7 +628,7 @@ namespace AIStudio.Wpf.DiagramDesigner } } } - else if (_service.DrawModeViewModel.LineDrawModeSelected)//画线模式,可以不命中实体 + else if (DrawModeViewModel.LineDrawModeSelected)//画线模式,可以不命中实体 { if (SourceConnector == null) { @@ -676,7 +661,7 @@ namespace AIStudio.Wpf.DiagramDesigner _viewModel.CurrentColor = ColorPickerManager.GetColor(point.X, point.Y); //移动 - if (_service.DrawModeViewModel.CursorMode == CursorMode.Move) + if (DrawModeViewModel.CursorMode == CursorMode.Move) { _viewModel.SelectedItems.OfType().ToList().ForEach(p => { p.Left = currentPoint.X; @@ -692,7 +677,7 @@ namespace AIStudio.Wpf.DiagramDesigner _partialConnection.SinkConnectorInfo = new PartCreatedConnectorInfo(currentPoint.X, currentPoint.Y); SinkConnector = HitTesting(currentPoint); - if (SinkConnector?.Info?.CanAttachTo(SourceConnector?.Info) == false) + if (SinkConnector != SourceConnector && SinkConnector?.Info?.CanAttachTo(SourceConnector?.Info) == false) { SinkConnector.Info.DisableAttachTo = true; } @@ -759,19 +744,19 @@ namespace AIStudio.Wpf.DiagramDesigner ConnectorInfoBase sinkDataItem = SinkConnector.Info; _viewModel.Delete(_partialConnection); - _viewModel.AddCommand.Execute(new ConnectionViewModel(_viewModel, sourceDataItem, sinkDataItem, LineDrawMode, RouterMode)); + _viewModel.AddCommand.Execute(new ConnectionViewModel(_viewModel, sourceDataItem, sinkDataItem, DrawModeViewModel.LineDrawMode, DrawModeViewModel.LineRouterMode)); } else if (_partialConnection.IsFullConnection)//自动连接模式 { _viewModel.ClearAttachTo(); } - else if (_service.DrawModeViewModel.LineDrawModeSelected) + else if (DrawModeViewModel.LineDrawModeSelected) { Point currentPoint = e.GetPosition(this); ConnectorInfoBase sinkDataItem = new PartCreatedConnectorInfo(currentPoint.X, currentPoint.Y); _viewModel.Delete(_partialConnection); - _viewModel.AddCommand.Execute(new ConnectionViewModel(_viewModel, sourceDataItem, sinkDataItem, LineDrawMode, RouterMode)); + _viewModel.AddCommand.Execute(new ConnectionViewModel(_viewModel, sourceDataItem, sinkDataItem, DrawModeViewModel.LineDrawMode, DrawModeViewModel.LineRouterMode)); } else { @@ -791,7 +776,7 @@ namespace AIStudio.Wpf.DiagramDesigner SourceItemsContainer = null; - _service.DrawModeViewModel.ResetDrawMode(); + DrawModeViewModel.ResetDrawMode(); } protected override void OnPreviewKeyDown(KeyEventArgs e) @@ -944,6 +929,10 @@ namespace AIStudio.Wpf.DiagramDesigner itemBase.MinItemWidth = dragObject.DesiredMinSize.Value.Width; itemBase.MinItemHeight = dragObject.DesiredMinSize.Value.Height; } + if (dragObject.ConnectorInfo != null) + { + InitConnectorInfo(itemBase, dragObject.ConnectorInfo); + } _viewModel.AddCommand.Execute(itemBase); if (itemBase is BlockDesignerItemViewModel block) @@ -966,6 +955,7 @@ namespace AIStudio.Wpf.DiagramDesigner if (!string.IsNullOrEmpty(dragObject.Icon)) itemBase.Icon = dragObject.Icon; itemBase.ColorViewModel = CopyHelper.Mapper(dragObject.ColorViewModel); + itemBase.FontViewModel = CopyHelper.Mapper(dragObject.FontViewModel); if (dragObject.DesiredSize != null) { itemBase.ItemWidth = dragObject.DesiredSize.Value.Width; @@ -976,7 +966,10 @@ namespace AIStudio.Wpf.DiagramDesigner itemBase.MinItemWidth = dragObject.DesiredMinSize.Value.Width; itemBase.MinItemHeight = dragObject.DesiredMinSize.Value.Height; } - + if (dragObject.ConnectorInfo != null) + { + InitConnectorInfo(itemBase, dragObject.ConnectorInfo); + } } itemBase.Left = Math.Max(0, position.X - itemBase.GetItemWidth() / 2); itemBase.Top = Math.Max(0, position.Y - itemBase.GetItemHeight() / 2); @@ -1013,5 +1006,14 @@ namespace AIStudio.Wpf.DiagramDesigner this.Focus(); } + + private void InitConnectorInfo(DesignerItemViewModelBase itemBase, List connectorInfos) + { + itemBase.ClearConnectors(); + foreach (var connectorInfo in connectorInfos) + { + itemBase.AddConnector(new FullyCreatedConnectorInfo(itemBase, connectorInfo.Orientation, connectorInfo.IsInnerPoint, connectorInfo.IsPortless) { XRatio = connectorInfo.XRatio, YRatio = connectorInfo.YRatio }); + } + } } } \ No newline at end of file diff --git a/AIStudio.Wpf.DiagramDesigner/Helpers/CopyHelper.cs b/AIStudio.Wpf.DiagramDesigner/Helpers/CopyHelper.cs index 7334b74..13516bb 100644 --- a/AIStudio.Wpf.DiagramDesigner/Helpers/CopyHelper.cs +++ b/AIStudio.Wpf.DiagramDesigner/Helpers/CopyHelper.cs @@ -90,15 +90,18 @@ namespace AIStudio.Wpf.DiagramDesigner D d = Activator.CreateInstance(); //构造新实例 try { - var Types = s.GetType();//获得类型 - var Typed = typeof(D); - foreach (PropertyInfo sp in Types.GetProperties().Where(p => p.CanRead))//获得类型的属性字段 + if (s != null) { - foreach (PropertyInfo dp in Typed.GetProperties().Where(p => p.CanWrite)) + var Types = s.GetType();//获得类型 + var Typed = typeof(D); + foreach (PropertyInfo sp in Types.GetProperties().Where(p => p.CanRead))//获得类型的属性字段 { - if (dp.Name == sp.Name && dp.PropertyType == sp.PropertyType)//判断属性名是否相同 + foreach (PropertyInfo dp in Typed.GetProperties().Where(p => p.CanWrite)) { - dp.SetValue(d, sp.GetValue(s, null), null);//获得s对象属性的值复制给d对象的属性 + if (dp.Name == sp.Name && dp.PropertyType == sp.PropertyType)//判断属性名是否相同 + { + dp.SetValue(d, sp.GetValue(s, null), null);//获得s对象属性的值复制给d对象的属性 + } } } } diff --git a/AIStudio.Wpf.DiagramDesigner/Models/DragObject.cs b/AIStudio.Wpf.DiagramDesigner/Models/DragObject.cs index 74720f2..4d30b99 100644 --- a/AIStudio.Wpf.DiagramDesigner/Models/DragObject.cs +++ b/AIStudio.Wpf.DiagramDesigner/Models/DragObject.cs @@ -17,6 +17,10 @@ namespace AIStudio.Wpf.DiagramDesigner { get; set; } + public List ConnectorInfo + { + get; set; + } public Type ContentType { get; set; @@ -33,9 +37,16 @@ namespace AIStudio.Wpf.DiagramDesigner { get; set; } + + public IFontViewModel FontViewModel + { + get; set; + } public object DesignerItem { get; set; } + + } } diff --git a/AIStudio.Wpf.DiagramDesigner/Models/ToolBoxData.cs b/AIStudio.Wpf.DiagramDesigner/Models/ToolBoxData.cs index d18a121..9466fcf 100644 --- a/AIStudio.Wpf.DiagramDesigner/Models/ToolBoxData.cs +++ b/AIStudio.Wpf.DiagramDesigner/Models/ToolBoxData.cs @@ -25,6 +25,10 @@ namespace AIStudio.Wpf.DiagramDesigner { get; set; } + public IFontViewModel FontViewModel + { + get; set; + } public double Width { get; set; @@ -41,6 +45,10 @@ namespace AIStudio.Wpf.DiagramDesigner { get; set; } + public List ConnectorInfo + { + get; set; + } public string Description { get; set; diff --git a/AIStudio.Wpf.DiagramDesigner/Routers/Pather.cs b/AIStudio.Wpf.DiagramDesigner/Routers/Pather.cs new file mode 100644 index 0000000..bb65477 --- /dev/null +++ b/AIStudio.Wpf.DiagramDesigner/Routers/Pather.cs @@ -0,0 +1,727 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Documents; +using AIStudio.Wpf.DiagramDesigner.Geometrys; + +namespace AIStudio.Wpf.DiagramDesigner +{ + internal class PathFinder + { + const int margin = 8; + const double CORNER = 6; + + private static List AddCornerPoints(List linePoints) + { + if (linePoints.Count < 3 || CORNER <= 0) return linePoints; + List points = new List(); + points.Add(linePoints[0]); + for (int i = 1; i < linePoints.Count - 1; i++) + { + double x1 = linePoints[i].X; + double x0 = linePoints[i - 1].X; + double y1 = linePoints[i].Y; + double y0 = linePoints[i - 1].Y; + if (x1 == x0) + { + double x2 = linePoints[i + 1].X; + if (y1 > y0) + { + if (y1 - y0 > CORNER && Math.Abs(x2 - x1) > CORNER) + { + points.Add(new PointBase(x1, y1 - CORNER)); + points.Add(new PointBase(x2 > x1 ? x1 + CORNER : x1 - CORNER, y1)); + } + } + else + { + if (y0 - y1 > CORNER && Math.Abs(x2 - x1) > CORNER) + { + points.Add(new PointBase(x1, y1 + CORNER)); + points.Add(new PointBase(x2 > x1 ? x1 + CORNER : x1 - CORNER, y1)); + } + } + } + else if (y1 == y0) + { + double y2 = linePoints[i + 1].Y; + if (x1 > x0) + { + if (x1 - x0 > CORNER && Math.Abs(y2 - y1) > CORNER) + { + points.Add(new PointBase(x1 - CORNER, y1)); + points.Add(new PointBase(x1, y2 > y1 ? y1 + CORNER : y1 - CORNER)); + } + } + else + { + if (x0 - x1 > CORNER && Math.Abs(y2 - y1) > CORNER) + { + points.Add(new PointBase(x1 + CORNER, y1)); + points.Add(new PointBase(x1, y2 > y1 ? y1 + CORNER : y1 - CORNER)); + } + } + } + } + points.Add(linePoints[linePoints.Count - 1]); + return points.Count % 2 == 0 ? points : linePoints; + } + + internal static List GetDirectLine(ConnectorInfoBase source, ConnectorInfoBase sink) + { + List linePoints = new List(); + linePoints.Add(source.MiddlePosition); + linePoints.Add(sink.MiddlePosition); + return linePoints; + } + + internal static List GetConnectionLine(ConnectorInfoBase source, ConnectorInfoBase sink, bool showLastLine) + { + List linePoints = new List(); + + RectangleBase rectSource = GetRectWithMargin(source, margin); + RectangleBase rectSink = GetRectWithMargin(sink, margin); + + PointBase startPoint = GetOffsetPoint(source, rectSource); + PointBase endPoint = GetOffsetPoint(sink, rectSink); + + linePoints.Add(startPoint); + PointBase currentPoint = startPoint; + + if (!rectSink.Contains(currentPoint) && !rectSource.Contains(endPoint)) + { + while (true) + { + #region source node + + if (IsPointVisible(currentPoint, endPoint, new RectangleBase[] { rectSource, rectSink })) + { + linePoints.Add(endPoint); + currentPoint = endPoint; + break; + } + + PointBase neighbour = GetNearestVisibleNeighborSink(currentPoint, endPoint, sink, rectSource, rectSink); + if (!double.IsNaN(neighbour.X)) + { + linePoints.Add(neighbour); + linePoints.Add(endPoint); + currentPoint = endPoint; + break; + } + + if (currentPoint == startPoint) + { + bool flag; + PointBase n = GetNearestNeighborSource(source, endPoint, rectSource, rectSink, out flag); + linePoints.Add(n); + currentPoint = n; + + if (!IsRectVisible(currentPoint, rectSink, new RectangleBase[] { rectSource })) + { + PointBase n1, n2; + GetOppositeCorners(source.Orientation, rectSource, out n1, out n2); + if (flag) + { + linePoints.Add(n1); + currentPoint = n1; + } + else + { + linePoints.Add(n2); + currentPoint = n2; + } + if (!IsRectVisible(currentPoint, rectSink, new RectangleBase[] { rectSource })) + { + if (flag) + { + linePoints.Add(n2); + currentPoint = n2; + } + else + { + linePoints.Add(n1); + currentPoint = n1; + } + } + } + } + #endregion + + #region sink node + + else // from here on we jump to the sink node + { + PointBase n1, n2; // neighbour corner + PointBase s1, s2; // opposite corner + GetNeighborCorners(sink.Orientation, rectSink, out s1, out s2); + GetOppositeCorners(sink.Orientation, rectSink, out n1, out n2); + + bool n1Visible = IsPointVisible(currentPoint, n1, new RectangleBase[] { rectSource, rectSink }); + bool n2Visible = IsPointVisible(currentPoint, n2, new RectangleBase[] { rectSource, rectSink }); + + if (n1Visible && n2Visible) + { + if (rectSource.Contains(n1)) + { + linePoints.Add(n2); + if (rectSource.Contains(s2)) + { + linePoints.Add(n1); + linePoints.Add(s1); + } + else + linePoints.Add(s2); + + linePoints.Add(endPoint); + currentPoint = endPoint; + break; + } + + if (rectSource.Contains(n2)) + { + linePoints.Add(n1); + if (rectSource.Contains(s1)) + { + linePoints.Add(n2); + linePoints.Add(s2); + } + else + linePoints.Add(s1); + + linePoints.Add(endPoint); + currentPoint = endPoint; + break; + } + + if ((Distance(n1, endPoint) <= Distance(n2, endPoint))) + { + linePoints.Add(n1); + if (rectSource.Contains(s1)) + { + linePoints.Add(n2); + linePoints.Add(s2); + } + else + linePoints.Add(s1); + linePoints.Add(endPoint); + currentPoint = endPoint; + break; + } + else + { + linePoints.Add(n2); + if (rectSource.Contains(s2)) + { + linePoints.Add(n1); + linePoints.Add(s1); + } + else + linePoints.Add(s2); + linePoints.Add(endPoint); + currentPoint = endPoint; + break; + } + } + else if (n1Visible) + { + linePoints.Add(n1); + if (rectSource.Contains(s1)) + { + linePoints.Add(n2); + linePoints.Add(s2); + } + else + linePoints.Add(s1); + linePoints.Add(endPoint); + currentPoint = endPoint; + break; + } + else + { + linePoints.Add(n2); + if (rectSource.Contains(s2)) + { + linePoints.Add(n1); + linePoints.Add(s1); + } + else + linePoints.Add(s2); + linePoints.Add(endPoint); + currentPoint = endPoint; + break; + } + } + #endregion + } + } + else + { + linePoints.Add(endPoint); + } + + linePoints = OptimizeLinePoints(linePoints, new RectangleBase[] { rectSource, rectSink }, source.Orientation, sink.Orientation); + + CheckPathEnd(source, sink, showLastLine, linePoints); + //return linePoints; + return AddCornerPoints(linePoints); + } + + internal static List GetConnectionLine(ConnectorInfoBase source, PointBase sinkPoint, ConnectorOrientation preferredOrientation) + { + List linePoints = new List(); + RectangleBase rectSource = GetRectWithMargin(source, 5); + PointBase startPoint = GetOffsetPoint(source, rectSource); + PointBase endPoint = sinkPoint; + + linePoints.Add(startPoint); + PointBase currentPoint = startPoint; + + if (!rectSource.Contains(endPoint)) + { + while (true) + { + if (IsPointVisible(currentPoint, endPoint, new RectangleBase[] { rectSource })) + { + linePoints.Add(endPoint); + break; + } + + bool sideFlag; + PointBase n = GetNearestNeighborSource(source, endPoint, rectSource, out sideFlag); + linePoints.Add(n); + currentPoint = n; + + if (IsPointVisible(currentPoint, endPoint, new RectangleBase[] { rectSource })) + { + linePoints.Add(endPoint); + break; + } + else + { + PointBase n1, n2; + GetOppositeCorners(source.Orientation, rectSource, out n1, out n2); + if (sideFlag) + linePoints.Add(n1); + else + linePoints.Add(n2); + + linePoints.Add(endPoint); + break; + } + } + } + else + { + linePoints.Add(endPoint); + } + + if (preferredOrientation != ConnectorOrientation.None) + linePoints = OptimizeLinePoints(linePoints, new RectangleBase[] { rectSource }, source.Orientation, preferredOrientation); + else + linePoints = OptimizeLinePoints(linePoints, new RectangleBase[] { rectSource }, source.Orientation, GetOpositeOrientation(source.Orientation)); + + return linePoints; + } + + private static List OptimizeLinePoints(List linePoints, RectangleBase[] rectangles, ConnectorOrientation sourceOrientation, ConnectorOrientation sinkOrientation) + { + List points = new List(); + int cut = 0; + + for (int i = 0; i < linePoints.Count; i++) + { + if (i >= cut) + { + for (int k = linePoints.Count - 1; k > i; k--) + { + if (IsPointVisible(linePoints[i], linePoints[k], rectangles)) + { + cut = k; + break; + } + } + points.Add(linePoints[i]); + } + } + + #region Line + for (int j = 0; j < points.Count - 1; j++) + { + if (points[j].X != points[j + 1].X && points[j].Y != points[j + 1].Y) + { + ConnectorOrientation orientationFrom; + ConnectorOrientation orientationTo; + + // orientation from point + if (j == 0) + orientationFrom = sourceOrientation; + else + orientationFrom = GetOrientation(points[j], points[j - 1]); + + // orientation to pint + if (j == points.Count - 2) + orientationTo = sinkOrientation; + else + orientationTo = GetOrientation(points[j + 1], points[j + 2]); + + + if ((orientationFrom == ConnectorOrientation.Left || orientationFrom == ConnectorOrientation.Right) && + (orientationTo == ConnectorOrientation.Left || orientationTo == ConnectorOrientation.Right)) + { + double centerX = Math.Min(points[j].X, points[j + 1].X) + Math.Abs(points[j].X - points[j + 1].X) / 2; + points.Insert(j + 1, new PointBase(centerX, points[j].Y)); + points.Insert(j + 2, new PointBase(centerX, points[j + 2].Y)); + if (points.Count - 1 > j + 3) + points.RemoveAt(j + 3); + return points; + } + + if ((orientationFrom == ConnectorOrientation.Top || orientationFrom == ConnectorOrientation.Bottom) && + (orientationTo == ConnectorOrientation.Top || orientationTo == ConnectorOrientation.Bottom)) + { + double centerY = Math.Min(points[j].Y, points[j + 1].Y) + Math.Abs(points[j].Y - points[j + 1].Y) / 2; + points.Insert(j + 1, new PointBase(points[j].X, centerY)); + points.Insert(j + 2, new PointBase(points[j + 2].X, centerY)); + if (points.Count - 1 > j + 3) + points.RemoveAt(j + 3); + return points; + } + + if ((orientationFrom == ConnectorOrientation.Left || orientationFrom == ConnectorOrientation.Right) && + (orientationTo == ConnectorOrientation.Top || orientationTo == ConnectorOrientation.Bottom)) + { + points.Insert(j + 1, new PointBase(points[j + 1].X, points[j].Y)); + return points; + } + + if ((orientationFrom == ConnectorOrientation.Top || orientationFrom == ConnectorOrientation.Bottom) && + (orientationTo == ConnectorOrientation.Left || orientationTo == ConnectorOrientation.Right)) + { + points.Insert(j + 1, new PointBase(points[j].X, points[j + 1].Y)); + return points; + } + } + } + #endregion + + return points; + } + + private static ConnectorOrientation GetOrientation(PointBase p1, PointBase p2) + { + if (p1.X == p2.X) + { + if (p1.Y >= p2.Y) + return ConnectorOrientation.Bottom; + else + return ConnectorOrientation.Top; + } + else if (p1.Y == p2.Y) + { + if (p1.X >= p2.X) + return ConnectorOrientation.Right; + else + return ConnectorOrientation.Left; + } + throw new Exception("Failed to retrieve orientation"); + } + /* + private static Orientation GetOrientation(ConnectorOrientation sourceOrientation) + { + switch (sourceOrientation) + { + case ConnectorOrientation.Left: + return Orientation.Horizontal; + case ConnectorOrientation.Top: + return Orientation.Vertical; + case ConnectorOrientation.Right: + return Orientation.Horizontal; + case ConnectorOrientation.Bottom: + return Orientation.Vertical; + default: + throw new Exception("Unknown ConnectorOrientation"); + } + }*/ + + private static PointBase GetNearestNeighborSource(ConnectorInfoBase source, PointBase endPoint, RectangleBase rectSource, RectangleBase rectSink, out bool flag) + { + PointBase n1, n2; // neighbors + GetNeighborCorners(source.Orientation, rectSource, out n1, out n2); + + if (rectSink.Contains(n1)) + { + flag = false; + return n2; + } + + if (rectSink.Contains(n2)) + { + flag = true; + return n1; + } + + if ((Distance(n1, endPoint) <= Distance(n2, endPoint))) + { + flag = true; + return n1; + } + else + { + flag = false; + return n2; + } + } + + private static PointBase GetNearestNeighborSource(ConnectorInfoBase source, PointBase endPoint, RectangleBase rectSource, out bool flag) + { + PointBase n1, n2; // neighbors + GetNeighborCorners(source.Orientation, rectSource, out n1, out n2); + + if ((Distance(n1, endPoint) <= Distance(n2, endPoint))) + { + flag = true; + return n1; + } + else + { + flag = false; + return n2; + } + } + + private static PointBase GetNearestVisibleNeighborSink(PointBase currentPoint, PointBase endPoint, ConnectorInfoBase sink, RectangleBase rectSource, RectangleBase rectSink) + { + PointBase s1, s2; // neighbors on sink side + GetNeighborCorners(sink.Orientation, rectSink, out s1, out s2); + + bool flag1 = IsPointVisible(currentPoint, s1, new RectangleBase[] { rectSource, rectSink }); + bool flag2 = IsPointVisible(currentPoint, s2, new RectangleBase[] { rectSource, rectSink }); + + if (flag1) // s1 visible + { + if (flag2) // s1 and s2 visible + { + if (rectSink.Contains(s1)) + return s2; + + if (rectSink.Contains(s2)) + return s1; + + if ((Distance(s1, endPoint) <= Distance(s2, endPoint))) + return s1; + else + return s2; + + } + else + { + return s1; + } + } + else // s1 not visible + { + if (flag2) // only s2 visible + { + return s2; + } + else // s1 and s2 not visible + { + return new PointBase(double.NaN, double.NaN); + } + } + } + + private static bool IsPointVisible(PointBase fromPoint, PointBase targetPoint, RectangleBase[] rectangles) + { + foreach (RectangleBase rect in rectangles) + { + if (RectangleIntersectsLine(rect, fromPoint, targetPoint)) + return false; + } + return true; + } + + private static bool IsRectVisible(PointBase fromPoint, RectangleBase targetRect, RectangleBase[] rectangles) + { + if (IsPointVisible(fromPoint, targetRect.TopLeft, rectangles)) + return true; + + if (IsPointVisible(fromPoint, targetRect.TopRight, rectangles)) + return true; + + if (IsPointVisible(fromPoint, targetRect.BottomLeft, rectangles)) + return true; + + if (IsPointVisible(fromPoint, targetRect.BottomRight, rectangles)) + return true; + + return false; + } + + private static bool RectangleIntersectsLine(RectangleBase rect, PointBase startPoint, PointBase endPoint) + { + rect.Inflate(-1, -1); + return rect.IntersectsWith(new RectangleBase(startPoint, endPoint)); + } + + private static void GetOppositeCorners(ConnectorOrientation orientation, RectangleBase rect, out PointBase n1, out PointBase n2) + { + switch (orientation) + { + case ConnectorOrientation.Left: + n1 = rect.TopRight; n2 = rect.BottomRight; + break; + case ConnectorOrientation.Top: + n1 = rect.BottomLeft; n2 = rect.BottomRight; + break; + case ConnectorOrientation.Right: + n1 = rect.TopLeft; n2 = rect.BottomLeft; + break; + case ConnectorOrientation.Bottom: + n1 = rect.TopLeft; n2 = rect.TopRight; + break; + default: + throw new Exception("No opposite corners found!"); + } + } + + private static void GetNeighborCorners(ConnectorOrientation orientation, RectangleBase rect, out PointBase n1, out PointBase n2) + { + switch (orientation) + { + case ConnectorOrientation.Left: + n1 = rect.TopLeft; n2 = rect.BottomLeft; + break; + case ConnectorOrientation.Top: + n1 = rect.TopLeft; n2 = rect.TopRight; + break; + case ConnectorOrientation.Right: + n1 = rect.TopRight; n2 = rect.BottomRight; + break; + case ConnectorOrientation.Bottom: + n1 = rect.BottomLeft; n2 = rect.BottomRight; + break; + default: + throw new Exception("No neighour corners found!"); + } + } + + private static double Distance(PointBase p1, PointBase p2) + { + return PointBase.Subtract(p1, p2).Length; + } + + private static RectangleBase GetRectWithMargin(ConnectorInfoBase connectorThumb, double margin) + { + RectangleBase rect; + if (connectorThumb is FullyCreatedConnectorInfo fullyCreated) + { + rect = fullyCreated.DataItem.GetBounds(); + } + else + { + rect = new RectangleBase(); + } + + rect.Inflate(margin, margin); + return rect; + } + + private static PointBase GetOffsetPoint(ConnectorInfoBase connector, RectangleBase rect) + { + PointBase offsetPoint = new PointBase(); + + switch (connector.Orientation) + { + case ConnectorOrientation.Left: + offsetPoint = new PointBase(rect.Left, connector.MiddlePosition.Y); + break; + case ConnectorOrientation.Top: + offsetPoint = new PointBase(connector.MiddlePosition.X, rect.Top); + break; + case ConnectorOrientation.Right: + offsetPoint = new PointBase(rect.Right, connector.MiddlePosition.Y); + break; + case ConnectorOrientation.Bottom: + offsetPoint = new PointBase(connector.MiddlePosition.X, rect.Bottom); + break; + default: + break; + } + + return offsetPoint; + } + + private static void CheckPathEnd(ConnectorInfoBase source, ConnectorInfoBase sink, bool showLastLine, List linePoints) + { + if (showLastLine) + { + PointBase startPoint = new PointBase(0, 0); + PointBase endPoint = new PointBase(0, 0); + double marginPath = 10; + switch (source.Orientation) + { + case ConnectorOrientation.Left: + startPoint = new PointBase(source.MiddlePosition.X - marginPath, source.MiddlePosition.Y); + break; + case ConnectorOrientation.Top: + startPoint = new PointBase(source.MiddlePosition.X, source.MiddlePosition.Y - marginPath); + break; + case ConnectorOrientation.Right: + startPoint = new PointBase(source.MiddlePosition.X + marginPath, source.MiddlePosition.Y); + break; + case ConnectorOrientation.Bottom: + startPoint = new PointBase(source.MiddlePosition.X, source.MiddlePosition.Y + marginPath); + break; + default: + break; + } + + switch (sink.Orientation) + { + case ConnectorOrientation.Left: + endPoint = new PointBase(sink.MiddlePosition.X - marginPath, sink.MiddlePosition.Y); + break; + case ConnectorOrientation.Top: + endPoint = new PointBase(sink.MiddlePosition.X, sink.MiddlePosition.Y - marginPath); + break; + case ConnectorOrientation.Right: + endPoint = new PointBase(sink.MiddlePosition.X + marginPath, sink.MiddlePosition.Y); + break; + case ConnectorOrientation.Bottom: + endPoint = new PointBase(sink.MiddlePosition.X, sink.MiddlePosition.Y + marginPath); + break; + default: + break; + } + linePoints.Insert(0, startPoint); + linePoints.Add(endPoint); + } + else + { + linePoints.Insert(0, source.MiddlePosition); + linePoints.Add(sink.MiddlePosition); + } + } + + private static ConnectorOrientation GetOpositeOrientation(ConnectorOrientation connectorOrientation) + { + switch (connectorOrientation) + { + case ConnectorOrientation.Left: + return ConnectorOrientation.Right; + case ConnectorOrientation.Top: + return ConnectorOrientation.Bottom; + case ConnectorOrientation.Right: + return ConnectorOrientation.Left; + case ConnectorOrientation.Bottom: + return ConnectorOrientation.Top; + default: + return ConnectorOrientation.Top; + } + } + } + + +} diff --git a/AIStudio.Wpf.DiagramDesigner/UserControls/ConnectorContainer.xaml.cs b/AIStudio.Wpf.DiagramDesigner/UserControls/ConnectorContainer.xaml.cs index b62c47b..7210616 100644 --- a/AIStudio.Wpf.DiagramDesigner/UserControls/ConnectorContainer.xaml.cs +++ b/AIStudio.Wpf.DiagramDesigner/UserControls/ConnectorContainer.xaml.cs @@ -38,6 +38,8 @@ namespace AIStudio.Wpf.DiagramDesigner var connector = ItemContainerGenerator.ContainerFromItem(item) as ContentPresenter; if (connector != null) { + vm.PropertyChanged -= Vm_PropertyChanged; + vm.PropertyChanged += Vm_PropertyChanged; Canvas.SetLeft(connector, vm.DataItem.GetItemWidth() * vm.XRatio - vm.ConnectorWidth / 2); Canvas.SetTop(connector, vm.DataItem.GetItemHeight() * vm.YRatio - vm.ConnectorHeight / 2); } @@ -65,6 +67,21 @@ namespace AIStudio.Wpf.DiagramDesigner { var vm = connector.DataContext as FullyCreatedConnectorInfo; if (vm != null) + { + vm.PropertyChanged -= Vm_PropertyChanged; + vm.PropertyChanged += Vm_PropertyChanged; + Canvas.SetLeft(connector, vm.DataItem.GetItemWidth() * vm.XRatio - vm.ConnectorWidth / 2); + Canvas.SetTop(connector, vm.DataItem.GetItemHeight() * vm.YRatio - vm.ConnectorHeight / 2); + } + } + } + + private void Vm_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (sender is FullyCreatedConnectorInfo vm) + { + var connector = ItemContainerGenerator.ContainerFromItem(vm) as ContentPresenter; + if (connector != null) { Canvas.SetLeft(connector, vm.DataItem.GetItemWidth() * vm.XRatio - vm.ConnectorWidth / 2); Canvas.SetTop(connector, vm.DataItem.GetItemHeight() * vm.YRatio - vm.ConnectorHeight / 2); diff --git a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/Connector/FullyCreatedConnectorInfo.cs b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/Connector/FullyCreatedConnectorInfo.cs index e17cfd5..f6db966 100644 --- a/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/Connector/FullyCreatedConnectorInfo.cs +++ b/AIStudio.Wpf.DiagramDesigner/ViewModels/BaseViewModel/Connector/FullyCreatedConnectorInfo.cs @@ -11,6 +11,10 @@ namespace AIStudio.Wpf.DiagramDesigner { public class FullyCreatedConnectorInfo : ConnectorInfoBase { + public FullyCreatedConnectorInfo(ConnectorOrientation orientation, bool isInnerPoint = false, bool isPortless = false) + : this(null, orientation, isInnerPoint, isPortless) + { + } public FullyCreatedConnectorInfo(DesignerItemViewModelBase dataItem, ConnectorOrientation orientation, bool isInnerPoint = false, bool isPortless = false) : this(null, dataItem, orientation, isInnerPoint, isPortless) { diff --git a/AIStudio.Wpf.DiagramDesigner/ViewModels/DrawingViewModel/Erasable/DrawingDesignerItemViewModelBase.cs b/AIStudio.Wpf.DiagramDesigner/ViewModels/DrawingViewModel/Erasable/DrawingDesignerItemViewModelBase.cs index 00fe83d..8ed6180 100644 --- a/AIStudio.Wpf.DiagramDesigner/ViewModels/DrawingViewModel/Erasable/DrawingDesignerItemViewModelBase.cs +++ b/AIStudio.Wpf.DiagramDesigner/ViewModels/DrawingViewModel/Erasable/DrawingDesignerItemViewModelBase.cs @@ -249,8 +249,8 @@ namespace AIStudio.Wpf.DiagramDesigner } else { - ItemWidth = Geometry.Bounds.Width;// + ColorViewModel.LineWidth * 0.5; - ItemHeight = Geometry.Bounds.Height;// + ColorViewModel.LineWidth * 0.5; + ItemWidth = Geometry.Bounds.Width + ColorViewModel.LineWidth * 1 - 1; + ItemHeight = Geometry.Bounds.Height + ColorViewModel.LineWidth * 1 - 1; Left = point.X; Top = point.Y; } @@ -270,8 +270,8 @@ namespace AIStudio.Wpf.DiagramDesigner ScaleTransform scaleTransform = transformGroup.Children.OfType().FirstOrDefault(); transformGroup.Children.Remove(scaleTransform); ; - double radiox = ItemWidth / Geometry.Bounds.Width; - double radioy = ItemHeight / Geometry.Bounds.Height; + double radiox = (ItemWidth - ColorViewModel.LineWidth * 1 + 1) / Geometry.Bounds.Width; + double radioy = (ItemHeight - ColorViewModel.LineWidth * 1 + 1) / Geometry.Bounds.Height; transformGroup.Children.Add(new ScaleTransform(radiox, radioy)); } diff --git a/Extensions/AIStudio.Wpf.Logical/ViewModels/LogicalGateItemViewModel.cs b/Extensions/AIStudio.Wpf.Logical/ViewModels/LogicalGateItemViewModel.cs index a00001a..cb72031 100644 --- a/Extensions/AIStudio.Wpf.Logical/ViewModels/LogicalGateItemViewModel.cs +++ b/Extensions/AIStudio.Wpf.Logical/ViewModels/LogicalGateItemViewModel.cs @@ -2530,7 +2530,8 @@ namespace AIStudio.Wpf.Logical.ViewModels { var first = Input.Values.FirstOrDefault(); Value = first.ConnectorValue; - LinkPoint.Value = first.ConnectorValue; + if (LinkPoint !=null) + LinkPoint.Value = first.ConnectorValue; foreach (var output in Output) { output.Value.ConnectorValue = first.ConnectorValue;