582 lines
24 KiB
C#
582 lines
24 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Reactive.Disposables;
|
|
using System.Reactive.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Controls.Primitives;
|
|
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;
|
|
using DynamicData;
|
|
using NodeNetwork.Utilities;
|
|
using NodeNetwork.ViewModels;
|
|
using NodeNetwork.Views.Controls;
|
|
using ReactiveUI;
|
|
|
|
namespace NodeNetwork.Views
|
|
{
|
|
public partial class NetworkView : IViewFor<NetworkViewModel>
|
|
{
|
|
#region ViewModel
|
|
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(nameof(ViewModel),
|
|
typeof(NetworkViewModel), typeof(NetworkView), new PropertyMetadata(null));
|
|
|
|
public NetworkViewModel ViewModel
|
|
{
|
|
get => (NetworkViewModel)GetValue(ViewModelProperty);
|
|
set => SetValue(ViewModelProperty, value);
|
|
}
|
|
|
|
object IViewFor.ViewModel
|
|
{
|
|
get => ViewModel;
|
|
set => ViewModel = (NetworkViewModel)value;
|
|
}
|
|
#endregion
|
|
|
|
#region NetworkViewportRegion
|
|
/// <summary>
|
|
/// The rectangle to use as a clipping mask for contentContainer
|
|
/// </summary>
|
|
public Rect NetworkViewportRegion
|
|
{
|
|
get
|
|
{
|
|
double left = Canvas.GetLeft(contentContainer);
|
|
if (Double.IsNaN(left))
|
|
{
|
|
left = 0;
|
|
}
|
|
|
|
double top = Canvas.GetTop(contentContainer);
|
|
if (Double.IsNaN(top))
|
|
{
|
|
top = 0;
|
|
}
|
|
|
|
if (contentContainer.RenderTransform is ScaleTransform)
|
|
{
|
|
GeneralTransform transform = this.TransformToDescendant(contentContainer);
|
|
return transform.TransformBounds(new Rect(0, 0, this.ActualWidth, this.ActualHeight));
|
|
}
|
|
return new Rect(-left, -top, this.ActualWidth, this.ActualHeight);
|
|
}
|
|
}
|
|
private BindingExpressionBase _viewportBinding;
|
|
#endregion
|
|
|
|
#region Node move events
|
|
public class NodeMovementEventArgs : EventArgs
|
|
{
|
|
public IEnumerable<NodeViewModel> Nodes { get; }
|
|
public NodeMovementEventArgs(IEnumerable<NodeViewModel> nodes) => Nodes = nodes.ToList();
|
|
}
|
|
|
|
//Start
|
|
public class NodeMoveStartEventArgs : NodeMovementEventArgs
|
|
{
|
|
public DragStartedEventArgs DragEvent { get; }
|
|
|
|
public NodeMoveStartEventArgs(IEnumerable<NodeViewModel> nodes, DragStartedEventArgs dragEvent) :
|
|
base(nodes)
|
|
{
|
|
DragEvent = dragEvent;
|
|
}
|
|
}
|
|
public delegate void NodeMoveStartDelegate(object sender, NodeMoveStartEventArgs e);
|
|
/// <summary>Occurs when a (set of) node(s) is selected and starts moving.</summary>
|
|
public event NodeMoveStartDelegate NodeMoveStart;
|
|
|
|
//Move
|
|
public class NodeMoveEventArgs : NodeMovementEventArgs
|
|
{
|
|
public DragDeltaEventArgs DragEvent { get; }
|
|
|
|
public NodeMoveEventArgs(IEnumerable<NodeViewModel> nodes, DragDeltaEventArgs dragEvent) : base(nodes)
|
|
{
|
|
DragEvent = dragEvent;
|
|
}
|
|
}
|
|
public delegate void NodeMoveDelegate(object sender, NodeMoveEventArgs e);
|
|
/// <summary>Occurs one or more times as the mouse changes position when a (set of) node(s) is selected and has mouse capture.</summary>
|
|
public event NodeMoveDelegate NodeMove;
|
|
|
|
//End
|
|
public class NodeMoveEndEventArgs : NodeMovementEventArgs
|
|
{
|
|
public DragCompletedEventArgs DragEvent { get; }
|
|
|
|
public NodeMoveEndEventArgs(IEnumerable<NodeViewModel> nodes, DragCompletedEventArgs dragEvent) : base(nodes)
|
|
{
|
|
DragEvent = dragEvent;
|
|
}
|
|
}
|
|
public delegate void NodeMoveEndDelegate(object sender, NodeMoveEndEventArgs e);
|
|
/// <summary>Occurs when a (set of) node(s) loses mouse capture.</summary>
|
|
public event NodeMoveEndDelegate NodeMoveEnd;
|
|
#endregion
|
|
|
|
#region NetworkBackground
|
|
public static readonly DependencyProperty NetworkBackgroundProperty = DependencyProperty.Register(nameof(NetworkBackground),
|
|
typeof(Brush), typeof(NetworkView), new PropertyMetadata(null));
|
|
|
|
public Brush NetworkBackground
|
|
{
|
|
get => (Brush)GetValue(NetworkBackgroundProperty);
|
|
set => SetValue(NetworkBackgroundProperty, value);
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// The element that is used as an origin for the position of the elements of the network.
|
|
/// </summary>
|
|
/// <example>
|
|
/// Can be used for calculating the mouse position relative to the network.
|
|
/// <code>
|
|
/// Mouse.GetPosition(network.CanvasOriginElement)
|
|
/// </code>
|
|
/// </example>
|
|
public IInputElement CanvasOriginElement => contentContainer;
|
|
|
|
#region StartCutGesture
|
|
public static readonly DependencyProperty StartCutGestureProperty = DependencyProperty.Register(nameof(StartCutGesture),
|
|
typeof(MouseGesture), typeof(NetworkView), new PropertyMetadata(new MouseGesture(MouseAction.RightClick)));
|
|
|
|
/// <summary>
|
|
/// This mouse gesture starts a cut, making the cutline visible. Right click by default.
|
|
/// </summary>
|
|
public MouseGesture StartCutGesture
|
|
{
|
|
get => (MouseGesture)GetValue(StartCutGestureProperty);
|
|
set => SetValue(StartCutGestureProperty, value);
|
|
}
|
|
#endregion
|
|
|
|
#region StartSelectionRectangleGesture
|
|
public static readonly DependencyProperty StartSelectionRectangleGestureProperty = DependencyProperty.Register(nameof(StartSelectionRectangleGesture),
|
|
typeof(MouseGesture), typeof(NetworkView), new PropertyMetadata(new MouseGesture(MouseAction.LeftClick, ModifierKeys.Shift)));
|
|
|
|
/// <summary>
|
|
/// This mouse gesture starts a selection, making the selection rectangle visible. Left click + Shift by default.
|
|
/// </summary>
|
|
public MouseGesture StartSelectionRectangleGesture
|
|
{
|
|
get => (MouseGesture)GetValue(StartSelectionRectangleGestureProperty);
|
|
set => SetValue(StartSelectionRectangleGestureProperty, value);
|
|
}
|
|
#endregion
|
|
|
|
public NetworkView()
|
|
{
|
|
InitializeComponent();
|
|
if (DesignerProperties.GetIsInDesignMode(this)) { return; }
|
|
|
|
SetupNodes();
|
|
SetupConnections();
|
|
SetupCutLine();
|
|
SetupViewportBinding();
|
|
SetupKeyboardShortcuts();
|
|
SetupErrorMessages();
|
|
SetupDragAndDrop();
|
|
SetupSelectionRectangle();
|
|
}
|
|
|
|
#region Setup
|
|
private void SetupNodes()
|
|
{
|
|
this.WhenActivated(d => d(
|
|
this.BindList(ViewModel, vm => vm.Nodes, v => v.nodesControl.ItemsSource)
|
|
));
|
|
}
|
|
|
|
private void SetupConnections()
|
|
{
|
|
this.WhenActivated(d =>
|
|
{
|
|
this.BindList(ViewModel, vm => vm.Connections, v => v.connectionsControl.ItemsSource).DisposeWith(d);
|
|
this.OneWayBind(ViewModel, vm => vm.PendingConnection, v => v.pendingConnectionView.ViewModel).DisposeWith(d);
|
|
|
|
this.Events().MouseMove
|
|
.Select(e => e.GetPosition(contentContainer))
|
|
.BindTo(this, v => v.ViewModel.PendingConnection.LooseEndPoint)
|
|
.DisposeWith(d);
|
|
|
|
this.Events().MouseLeftButtonUp
|
|
.Where(_ => ViewModel.PendingConnection != null)
|
|
.Subscribe(_ => ViewModel.OnPendingConnectionDropped())
|
|
.DisposeWith(d);
|
|
});
|
|
}
|
|
|
|
private void SetupKeyboardShortcuts()
|
|
{
|
|
this.WhenActivated(d =>
|
|
{
|
|
this.Events().MouseLeftButtonDown.Subscribe(_ => Focus()).DisposeWith(d);
|
|
this.OneWayBind(ViewModel, vm => vm.DeleteSelectedNodes, v => v.deleteBinding.Command).DisposeWith(d);
|
|
});
|
|
}
|
|
|
|
private void SetupCutLine()
|
|
{
|
|
this.WhenActivated(d =>
|
|
{
|
|
this.OneWayBind(ViewModel, vm => vm.CutLine.StartPoint.X, v => v.cutLine.X1).DisposeWith(d);
|
|
this.OneWayBind(ViewModel, vm => vm.CutLine.StartPoint.Y, v => v.cutLine.Y1).DisposeWith(d);
|
|
this.OneWayBind(ViewModel, vm => vm.CutLine.EndPoint.X, v => v.cutLine.X2).DisposeWith(d);
|
|
this.OneWayBind(ViewModel, vm => vm.CutLine.EndPoint.Y, v => v.cutLine.Y2).DisposeWith(d);
|
|
this.OneWayBind(ViewModel, vm => vm.CutLine.IsVisible, v => v.cutLine.Visibility,
|
|
isVisible => isVisible ? Visibility.Visible : Visibility.Collapsed)
|
|
.DisposeWith(d);
|
|
|
|
bool cutGestured = false;
|
|
dragCanvas.Events().MouseDown.Subscribe(e =>
|
|
{
|
|
if (StartCutGesture.Matches(this, e))
|
|
{
|
|
Point pos = e.GetPosition(contentContainer);
|
|
ViewModel.CutLine.StartPoint = pos;
|
|
ViewModel.CutLine.EndPoint = pos;
|
|
cutGestured = true;
|
|
|
|
e.Handled = true;
|
|
}
|
|
}).DisposeWith(d);
|
|
|
|
dragCanvas.Events().MouseMove.Subscribe(e =>
|
|
{
|
|
if (!ViewModel.CutLine.IsVisible && cutGestured)
|
|
{
|
|
ViewModel.StartCut();
|
|
}
|
|
|
|
if (ViewModel.CutLine.IsVisible)
|
|
{
|
|
ViewModel.CutLine.EndPoint = e.GetPosition(contentContainer);
|
|
|
|
ViewModel.CutLine.IntersectingConnections.Edit(l =>
|
|
{
|
|
l.Clear();
|
|
l.AddRange(FindIntersectingConnections().Where(val => val.intersects).Select(val => val.con));
|
|
});
|
|
|
|
e.Handled = true;
|
|
}
|
|
|
|
}).DisposeWith(d);
|
|
|
|
dragCanvas.Events().MouseUp.Subscribe(e =>
|
|
{
|
|
cutGestured = false;
|
|
if (ViewModel.CutLine.IsVisible)
|
|
{
|
|
//Do cuts
|
|
ViewModel.FinishCut();
|
|
|
|
e.Handled = true;
|
|
}
|
|
}).DisposeWith(d);
|
|
});
|
|
}
|
|
|
|
private void SetupViewportBinding()
|
|
{
|
|
this.WhenActivated(d =>
|
|
{
|
|
this.Bind(ViewModel, vm => vm.ZoomFactor, v => v.dragCanvas.ZoomFactor);
|
|
this.Bind(ViewModel, vm => vm.MaxZoomLevel, v => v.dragCanvas.MaxZoomFactor);
|
|
this.Bind(ViewModel, vm => vm.MinZoomLevel, v => v.dragCanvas.MinZoomFactor);
|
|
this.Bind(ViewModel, vm => vm.DragOffset, v => v.dragCanvas.DragOffset);
|
|
});
|
|
|
|
Binding binding = new Binding
|
|
{
|
|
Source = this,
|
|
Path = new PropertyPath(nameof(NetworkViewportRegion)),
|
|
Mode = BindingMode.OneWay,
|
|
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
|
|
};
|
|
_viewportBinding = BindingOperations.SetBinding(clippingGeometry, RectangleGeometry.RectProperty, binding);
|
|
}
|
|
|
|
private void SetupErrorMessages()
|
|
{
|
|
messageHostBorder.Visibility = Visibility.Collapsed; //Start collapsed
|
|
messagePopup.VerticalOffset = -15;
|
|
|
|
this.WhenActivated(d =>
|
|
{
|
|
this.OneWayBind(ViewModel, vm => vm.LatestValidation.IsValid, v => v.messageHostBorder.Visibility,
|
|
isValid => isValid ? Visibility.Collapsed : Visibility.Visible)
|
|
.DisposeWith(d);
|
|
this.OneWayBind(ViewModel, vm => vm.LatestValidation.MessageViewModel, v => v.messageHost.ViewModel)
|
|
.DisposeWith(d);
|
|
|
|
this.WhenAnyValue(v => v.ViewModel.PendingConnection.Validation)
|
|
.Select(_ => ViewModel.PendingConnection?.Validation?.MessageViewModel != null)
|
|
.BindTo(this, v => v.messagePopup.IsOpen)
|
|
.DisposeWith(d);
|
|
this.WhenAnyValue(v => v.ViewModel.PendingConnection.Validation)
|
|
.Select(_ => ViewModel.PendingConnection?.Validation?.MessageViewModel)
|
|
.BindTo(this, v => v.messagePopupHost.ViewModel)
|
|
.DisposeWith(d);
|
|
|
|
this.WhenAnyValue(vm => vm.ViewModel.PendingConnection.BoundingBox)
|
|
.Select(b => new Rect(contentContainer.TranslatePoint(b.TopLeft, this), contentContainer.TranslatePoint(b.BottomRight, this)))
|
|
.BindTo(this, v => v.messagePopup.PlacementRectangle)
|
|
.DisposeWith(d);
|
|
this.WhenAnyValue(vm => vm.ViewModel.PendingConnection.BoundingBox)
|
|
.Select(b => (b.Width / 2d) - (messagePopup.Child.RenderSize.Width / 2d))
|
|
.BindTo(this, v => v.messagePopup.HorizontalOffset)
|
|
.DisposeWith(d);
|
|
});
|
|
}
|
|
|
|
private void SetupDragAndDrop()
|
|
{
|
|
this.WhenActivated(d =>
|
|
{
|
|
this.OneWayBind(ViewModel, vm => vm.PendingNode, v => v.pendingNodeView.ViewModel).DisposeWith(d);
|
|
this.OneWayBind(ViewModel, vm => vm.PendingNode, v => v.pendingNodeView.Visibility,
|
|
node => node == null ? Visibility.Collapsed : Visibility.Visible)
|
|
.DisposeWith(d);
|
|
|
|
this.WhenAnyValue(v => v.ViewModel.PendingNode.Position).Subscribe(pos =>
|
|
{
|
|
Canvas.SetLeft(pendingNodeView, pos.X);
|
|
Canvas.SetTop(pendingNodeView, pos.Y);
|
|
}).DisposeWith(d);
|
|
|
|
this.Events().DragOver.Subscribe(e =>
|
|
{
|
|
object data = e.Data.GetData("nodeVM");
|
|
NodeViewModel newNodeVm = data as NodeViewModel;
|
|
|
|
ViewModel.PendingNode = newNodeVm;
|
|
if (ViewModel.PendingNode != null)
|
|
{
|
|
ViewModel.PendingNode.Position = e.GetPosition(contentContainer);
|
|
}
|
|
|
|
e.Effects = newNodeVm != null ? DragDropEffects.Copy : DragDropEffects.None;
|
|
}).DisposeWith(d);
|
|
|
|
this.Events().Drop.Subscribe(e =>
|
|
{
|
|
object data = e.Data.GetData("nodeVM");
|
|
NodeViewModel newNodeVm = data as NodeViewModel;
|
|
if (newNodeVm != null)
|
|
{
|
|
this.ViewModel.PendingNode =
|
|
new NodeViewModel(); //Fixes issue with newNodeVm sticking around in pendingNodeView, messing up position updates
|
|
this.ViewModel.PendingNode = null;
|
|
newNodeVm.Position = e.GetPosition(contentContainer);
|
|
ViewModel.Nodes.Add(newNodeVm);
|
|
}
|
|
}).DisposeWith(d);
|
|
|
|
this.Events().DragLeave.Subscribe(_ => ViewModel.PendingNode = null).DisposeWith(d);
|
|
});
|
|
}
|
|
|
|
private void SetupSelectionRectangle()
|
|
{
|
|
this.WhenActivated(d =>
|
|
{
|
|
this.WhenAnyValue(vm => vm.ViewModel.SelectionRectangle.Rectangle.Left)
|
|
.Subscribe(left => Canvas.SetLeft(selectionRectangle, left))
|
|
.DisposeWith(d);
|
|
this.WhenAnyValue(vm => vm.ViewModel.SelectionRectangle.Rectangle.Top)
|
|
.Subscribe(top => Canvas.SetTop(selectionRectangle, top))
|
|
.DisposeWith(d);
|
|
this.OneWayBind(ViewModel, vm => vm.SelectionRectangle.Rectangle.Width, v => v.selectionRectangle.Width).DisposeWith(d);
|
|
this.OneWayBind(ViewModel, vm => vm.SelectionRectangle.Rectangle.Height, v => v.selectionRectangle.Height).DisposeWith(d);
|
|
this.OneWayBind(ViewModel, vm => vm.SelectionRectangle.IsVisible, v => v.selectionRectangle.Visibility).DisposeWith(d);
|
|
|
|
this.Events().PreviewMouseDown.Subscribe(e =>
|
|
{
|
|
if (ViewModel != null && StartSelectionRectangleGesture.Matches(this, e))
|
|
{
|
|
CaptureMouse();
|
|
dragCanvas.IsDraggingEnabled = false;
|
|
ViewModel.StartRectangleSelection();
|
|
ViewModel.SelectionRectangle.StartPoint = e.GetPosition(contentContainer);
|
|
ViewModel.SelectionRectangle.EndPoint = ViewModel.SelectionRectangle.StartPoint;
|
|
}
|
|
}).DisposeWith(d);
|
|
|
|
this.Events().MouseMove.Subscribe(e =>
|
|
{
|
|
if (ViewModel != null && ViewModel.SelectionRectangle.IsVisible)
|
|
{
|
|
ViewModel.SelectionRectangle.EndPoint = e.GetPosition(contentContainer);
|
|
UpdateSelectionRectangleIntersections();
|
|
}
|
|
}).DisposeWith(d);
|
|
|
|
this.Events().MouseUp.Subscribe(e =>
|
|
{
|
|
if (ViewModel != null && ViewModel.SelectionRectangle.IsVisible)
|
|
{
|
|
ViewModel.FinishRectangleSelection();
|
|
dragCanvas.IsDraggingEnabled = true;
|
|
ReleaseMouseCapture();
|
|
}
|
|
}).DisposeWith(d);
|
|
});
|
|
}
|
|
|
|
// Real, accurate but expensive hittesting
|
|
/*private void UpdateSelectionRectangleIntersections()
|
|
{
|
|
RectangleGeometry geometry = new RectangleGeometry(ViewModel.SelectionRectangle.Rectangle);
|
|
|
|
ViewModel.SelectionRectangle.IntersectingNodes.Clear();
|
|
VisualTreeHelper.HitTest(nodesControl, element =>
|
|
{
|
|
if (element is NodeView)
|
|
{
|
|
//return HitTestFilterBehavior.ContinueSkipChildren;
|
|
}
|
|
|
|
return HitTestFilterBehavior.Continue;
|
|
}, result =>
|
|
{
|
|
if ((result.VisualHit as FrameworkElement)?.DataContext is NodeViewModel nodeVm &&
|
|
!ViewModel.SelectionRectangle.IntersectingNodes.Contains(nodeVm))
|
|
{
|
|
Debug.WriteLine(result.VisualHit);
|
|
ViewModel.SelectionRectangle.IntersectingNodes.Add(nodeVm);
|
|
}
|
|
|
|
return HitTestResultBehavior.Continue;
|
|
}, new GeometryHitTestParameters(geometry));
|
|
}*/
|
|
|
|
// Approximate but cheap boundingbox-based hittesting
|
|
private void UpdateSelectionRectangleIntersections()
|
|
{
|
|
var selectionRect = ViewModel.SelectionRectangle.Rectangle;
|
|
|
|
var nodesHit = WPFUtils.FindDescendantsOfType<NodeViewBase>(nodesControl, true)
|
|
.Where(nodeView =>
|
|
{
|
|
//return selectionRect.Contains(new Rect(nodeView.ViewModel.Position, nodeView.RenderSize));
|
|
var viewModel = (NodeViewModel)((dynamic)nodeView).ViewModel;
|
|
return selectionRect.IntersectsWith(new Rect(viewModel.Position, nodeView.RenderSize));
|
|
})
|
|
.Select(view => (NodeViewModel)((dynamic)view).ViewModel);
|
|
|
|
ViewModel.SelectionRectangle.IntersectingNodes.Clear();
|
|
ViewModel.SelectionRectangle.IntersectingNodes.AddRange(nodesHit);
|
|
}
|
|
#endregion
|
|
|
|
#region Viewport bound updates
|
|
private void DragCanvas_OnZoom(object source, ZoomEventArgs args)
|
|
{
|
|
_viewportBinding?.UpdateTarget();
|
|
}
|
|
|
|
private void ContentContainer_OnLayoutUpdated(object sender, EventArgs e)
|
|
{
|
|
_viewportBinding?.UpdateTarget();
|
|
}
|
|
#endregion
|
|
|
|
#region Node move events
|
|
private void OnNodeDragStart(object sender, DragStartedEventArgs e)
|
|
{
|
|
// Hacky fix for issue #78. A nested thumb being dragged would also drag the node around, which is incorrect.
|
|
// For some reason, trying to stop the MouseMove event from bubbling up does not work, so instead we check
|
|
// here what caused this drag event. Only the Thumb around the node may cause drag events.
|
|
|
|
bool isCorrectSource = WPFUtils.GetVisualAncestorNLevelsUp((DependencyObject)e.OriginalSource, 6) == nodesControl;
|
|
if (NodeMoveStart != null && isCorrectSource)
|
|
{
|
|
var args = new NodeMoveStartEventArgs(ViewModel.SelectedNodes.Items, e);
|
|
NodeMoveStart(sender, args);
|
|
}
|
|
}
|
|
|
|
private void OnNodeDrag(object sender, DragDeltaEventArgs e)
|
|
{
|
|
// See OnNodeDragStart
|
|
bool isCorrectSource = WPFUtils.GetVisualAncestorNLevelsUp((DependencyObject)e.OriginalSource, 6) == nodesControl;
|
|
if (isCorrectSource)
|
|
{
|
|
foreach (NodeViewModel node in ViewModel.SelectedNodes.Items)
|
|
{
|
|
node.Position = new Point(node.Position.X + e.HorizontalChange, node.Position.Y + e.VerticalChange);
|
|
}
|
|
|
|
if (NodeMove != null)
|
|
{
|
|
var args = new NodeMoveEventArgs(ViewModel.SelectedNodes.Items, e);
|
|
NodeMove(sender, args);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnNodeDragEnd(object sender, DragCompletedEventArgs e)
|
|
{
|
|
// See OnNodeDragStart
|
|
bool isCorrectSource = WPFUtils.GetVisualAncestorNLevelsUp((DependencyObject)e.OriginalSource, 6) == nodesControl;
|
|
if (NodeMoveEnd != null && isCorrectSource)
|
|
{
|
|
var args = new NodeMoveEndEventArgs(ViewModel.SelectedNodes.Items, e);
|
|
NodeMoveEnd(sender, args);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
private void OnClickCanvas(object sender, MouseButtonEventArgs e)
|
|
{
|
|
ViewModel.ClearSelection();
|
|
}
|
|
|
|
private IEnumerable<(ConnectionViewModel con, bool intersects)> FindIntersectingConnections()
|
|
{
|
|
foreach (ConnectionViewModel con in ViewModel.Connections.Items)
|
|
{
|
|
PathGeometry conGeom = ConnectionView.BuildSmoothBezier(con.Input.Port.CenterPoint, con.Input.PortPosition, con.Output.Port.CenterPoint, con.Output.PortPosition);
|
|
LineGeometry cutLineGeom = new LineGeometry(ViewModel.CutLine.StartPoint, ViewModel.CutLine.EndPoint);
|
|
bool hasIntersections = WPFUtils.GetIntersectionPoints(conGeom, cutLineGeom).Any();
|
|
yield return (con, hasIntersections);
|
|
}
|
|
}
|
|
|
|
public void CenterAndZoomView()
|
|
{
|
|
if (ViewModel.Nodes.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var bounding = ViewModel.Nodes.Items.Select(node =>
|
|
{
|
|
var currentTopLeft = node.Position;
|
|
var currentBottomRight = Point.Add(node.Position, new Vector(node.Size.Width, node.Size.Height));
|
|
var nodeBounding = new Rect(currentTopLeft, currentBottomRight);
|
|
return nodeBounding;
|
|
}).Aggregate((r1, r2) =>
|
|
{
|
|
r1.Union(r2);
|
|
return r1;
|
|
});
|
|
|
|
this.dragCanvas?.SetViewport(bounding);
|
|
}
|
|
}
|
|
}
|