diff --git a/.gitignore b/.gitignore
index 566550e..051ce2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -279,3 +279,5 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk
+NodeEditor/NodeEditor.nuspec
+NodeEditor/nuget.exe
diff --git a/NodeEditor/DrawInfo.cs b/NodeEditor/DrawInfo.cs
new file mode 100644
index 0000000..2ad2991
--- /dev/null
+++ b/NodeEditor/DrawInfo.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+
+namespace NodeEditor
+{
+ ///
+ /// Class used to provide custom drawing elements to node editor, such connection styles, node graphics etc.
+ ///
+ public class DrawInfo
+ {
+ private static Pen boldPen;
+
+ ///
+ /// Gets pen used to draw connections between nodes.
+ ///
+ /// Type indicating data type passing through connection
+ /// If user is currently dragging connection between nodes
+ /// Pen to draw line
+ public virtual Pen GetConnectionStyle(Type dataType, bool isConnecting)
+ {
+ if(isConnecting)
+ {
+ return boldPen ?? (boldPen = new Pen(Brushes.Black, 3));
+ }
+ return Pens.Black;
+ }
+ }
+}
diff --git a/NodeEditor/IZoomable.cs b/NodeEditor/IZoomable.cs
new file mode 100644
index 0000000..d802795
--- /dev/null
+++ b/NodeEditor/IZoomable.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace NodeEditor
+{
+ public interface IZoomable
+ {
+ ///
+ /// Indicates current zoom (scale), higher value means bigger elements
+ ///
+ float Zoom { get; set; }
+ }
+}
diff --git a/NodeEditor/NodeEditor.csproj b/NodeEditor/NodeEditor.csproj
index b979688..b0daf36 100644
--- a/NodeEditor/NodeEditor.csproj
+++ b/NodeEditor/NodeEditor.csproj
@@ -2,7 +2,7 @@
- Debug
+ Release
AnyCPU
{2A51D621-3E0D-4293-A2AD-964721BFFF7B}
Library
@@ -20,6 +20,7 @@
DEBUG;TRACE
prompt
4
+ bin\Debug\NodeEditor.xml
pdbonly
@@ -28,6 +29,7 @@
TRACE
prompt
4
+ bin\Release\NodeEditor.xml
@@ -41,11 +43,13 @@
+
+
diff --git a/NodeEditor/NodeVisual.cs b/NodeEditor/NodeVisual.cs
index e6e9825..e6b95af 100644
--- a/NodeEditor/NodeVisual.cs
+++ b/NodeEditor/NodeVisual.cs
@@ -242,8 +242,11 @@ public SizeF GetNodeBounds()
var csize = new SizeF();
if (CustomEditor != null)
{
- csize = new SizeF(CustomEditor.ClientSize.Width + 2 + 80 +SocketVisual.SocketHeight*2,
- CustomEditor.ClientSize.Height + HeaderHeight + 8);
+ var zoomable = CustomEditor as IZoomable;
+ float zoom = zoomable == null ? 1f : (float)Math.Sqrt(zoomable.Zoom);
+
+ csize = new SizeF(CustomEditor.ClientSize.Width/zoom + 2 + 80 +SocketVisual.SocketHeight*2,
+ CustomEditor.ClientSize.Height/zoom + HeaderHeight + 8);
}
var inputs = GetInputs().Length;
@@ -358,11 +361,11 @@ internal void Execute(INodesContext context)
}
}
- internal void LayoutEditor()
+ internal void LayoutEditor(float zoom)
{
if (CustomEditor != null)
{
- CustomEditor.Location = new Point((int)( X + 1 + 40 + SocketVisual.SocketHeight), (int) (Y + HeaderHeight + 4));
+ CustomEditor.Location = new Point((int)(zoom * (X + 1 + 40 + SocketVisual.SocketHeight)), (int)(zoom * (Y + HeaderHeight + 4)));
}
}
}
diff --git a/NodeEditor/NodesControl.cs b/NodeEditor/NodesControl.cs
index 1676075..4ac94d8 100644
--- a/NodeEditor/NodesControl.cs
+++ b/NodeEditor/NodesControl.cs
@@ -34,7 +34,7 @@ namespace NodeEditor
/// Main control of Node Editor Winforms
///
[ToolboxBitmap(typeof(NodesControl), "nodeed")]
- public partial class NodesControl : UserControl
+ public partial class NodesControl : UserControl, IZoomable
{
internal class NodeToken
{
@@ -76,6 +76,37 @@ public INodesContext Context
}
}
+
+ private float zoom = 1f;
+
+ ///
+ /// Indicates scale factor of visual appearance of node graph
+ ///
+ public float Zoom
+ {
+ get { return zoom; }
+ set
+ {
+ zoom = value;
+ PassZoomToNodes();
+ Invalidate();
+ }
+ }
+
+ private DrawInfo customDrawInfo = new DrawInfo();
+
+ public DrawInfo CustomDrawInfo
+ {
+ get { return customDrawInfo; }
+ set { customDrawInfo = value; }
+ }
+
+
+ ///
+ /// If true, drawing events will use fast painting modes instead of high quality ones
+ ///
+ public bool PreferFastRendering { get; set; }
+
///
/// Occurs when user selects a node. In the object will be passed node settings for unplugged inputs/outputs.
///
@@ -92,6 +123,11 @@ public INodesContext Context
///
public event Action OnShowLocation = delegate { };
+ ///
+ /// Use this event to paint custom background - scaling is already applied into Graphics context.
+ ///
+ public event EventHandler OnPaintNodesBackground = delegate { };
+
private readonly Dictionary allContextItems = new Dictionary();
private Point lastMouseLocation;
@@ -157,15 +193,18 @@ private void TimerOnTick(object sender, EventArgs eventArgs)
private void NodesControl_Paint(object sender, PaintEventArgs e)
{
- e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
- e.Graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;
+ e.Graphics.SmoothingMode = PreferFastRendering ? SmoothingMode.HighSpeed : SmoothingMode.HighQuality;
+ e.Graphics.InterpolationMode = PreferFastRendering ? InterpolationMode.Low : InterpolationMode.HighQualityBilinear;
+ e.Graphics.ScaleTransform(zoom, zoom);
+
+ OnPaintNodesBackground(sender, e);
- graph.Draw(e.Graphics, PointToClient(MousePosition), MouseButtons);
+ graph.Draw(e.Graphics, GetLocationWithZoom(PointToClient(MousePosition)), MouseButtons, PreferFastRendering, customDrawInfo);
if (dragSocket != null)
{
- var pen = new Pen(Color.Black, 2);
- NodesGraph.DrawConnection(e.Graphics, pen, dragConnectionBegin, dragConnectionEnd);
+ var pen = customDrawInfo.GetConnectionStyle(dragSocket.Type, true);
+ NodesGraph.DrawConnection(e.Graphics, pen, dragConnectionBegin, dragConnectionEnd, PreferFastRendering);
}
if (selectionStart != PointF.Empty)
@@ -189,10 +228,12 @@ private static RectangleF MakeRect(PointF a, PointF b)
private void NodesControl_MouseMove(object sender, MouseEventArgs e)
{
- var em = PointToScreen(e.Location);
+ var loc = GetLocationWithZoom(e.Location);
+ var em = PointToScreen(loc);
+
if (selectionStart != PointF.Empty)
{
- selectionEnd = e.Location;
+ selectionEnd = loc;
}
if (mdown)
{
@@ -201,7 +242,7 @@ private void NodesControl_MouseMove(object sender, MouseEventArgs e)
node.X += em.X - lastmpos.X;
node.Y += em.Y - lastmpos.Y;
node.DiscardCache();
- node.LayoutEditor();
+ node.LayoutEditor(zoom);
}
if (graph.Nodes.Exists(x => x.IsSelected))
{
@@ -241,7 +282,9 @@ private void NodesControl_MouseMove(object sender, MouseEventArgs e)
}
private void NodesControl_MouseDown(object sender, MouseEventArgs e)
- {
+ {
+ var loc = GetLocationWithZoom(e.Location);
+
if (e.Button == MouseButtons.Left)
{
selectionStart = PointF.Empty;
@@ -255,7 +298,7 @@ private void NodesControl_MouseDown(object sender, MouseEventArgs e)
var node =
graph.Nodes.OrderBy(x => x.Order).FirstOrDefault(
- x => new RectangleF(new PointF(x.X, x.Y), x.GetHeaderSize()).Contains(e.Location));
+ x => new RectangleF(new PointF(x.X, x.Y), x.GetHeaderSize()).Contains(loc));
if (node != null && !mdown)
{
@@ -266,9 +309,10 @@ private void NodesControl_MouseDown(object sender, MouseEventArgs e)
if (node.CustomEditor != null)
{
node.CustomEditor.BringToFront();
+ PassZoomToNodeCustomEditor(node.CustomEditor);
}
mdown = true;
- lastmpos = PointToScreen(e.Location);
+ lastmpos = PointToScreen(loc);
Refresh();
}
@@ -276,11 +320,11 @@ private void NodesControl_MouseDown(object sender, MouseEventArgs e)
{
var nodeWhole =
graph.Nodes.OrderBy(x => x.Order).FirstOrDefault(
- x => new RectangleF(new PointF(x.X, x.Y), x.GetNodeBounds()).Contains(e.Location));
+ x => new RectangleF(new PointF(x.X, x.Y), x.GetNodeBounds()).Contains(loc));
if (nodeWhole != null)
{
node = nodeWhole;
- var socket = nodeWhole.GetSockets().FirstOrDefault(x => x.GetBounds().Contains(e.Location));
+ var socket = nodeWhole.GetSockets().FirstOrDefault(x => x.GetBounds().Contains(loc));
if (socket != null)
{
if ((ModifierKeys & Keys.Control) == Keys.Control)
@@ -319,15 +363,15 @@ private void NodesControl_MouseDown(object sender, MouseEventArgs e)
dragSocket = socket;
dragSocketNode = nodeWhole;
}
- dragConnectionBegin = e.Location;
- dragConnectionEnd = e.Location;
+ dragConnectionBegin = loc;
+ dragConnectionEnd = loc;
mdown = true;
- lastmpos = PointToScreen(e.Location);
+ lastmpos = PointToScreen(loc);
}
}
else
{
- selectionStart = selectionEnd = e.Location;
+ selectionStart = selectionEnd = loc;
}
}
if (node != null)
@@ -339,6 +383,15 @@ private void NodesControl_MouseDown(object sender, MouseEventArgs e)
needRepaint = true;
}
+ private void PassZoomToNodeCustomEditor(Control control)
+ {
+ var zoomable = control as IZoomable;
+ if (zoomable != null)
+ {
+ zoomable.Zoom = zoom * zoom;
+ }
+ }
+
private bool IsConnectable(SocketVisual a, SocketVisual b)
{
var input = a.Input ? a : b;
@@ -370,6 +423,8 @@ private Assembly AssemblyResolver(AssemblyName assemblyName)
private void NodesControl_MouseUp(object sender, MouseEventArgs e)
{
+ var loc = GetLocationWithZoom(e.Location);
+
if (selectionStart != PointF.Empty)
{
var rect = MakeRect(selectionStart, selectionEnd);
@@ -382,10 +437,10 @@ private void NodesControl_MouseUp(object sender, MouseEventArgs e)
{
var nodeWhole =
graph.Nodes.OrderBy(x => x.Order).FirstOrDefault(
- x => new RectangleF(new PointF(x.X, x.Y), x.GetNodeBounds()).Contains(e.Location));
+ x => new RectangleF(new PointF(x.X, x.Y), x.GetNodeBounds()).Contains(loc));
if (nodeWhole != null)
{
- var socket = nodeWhole.GetSockets().FirstOrDefault(x => x.GetBounds().Contains(e.Location));
+ var socket = nodeWhole.GetSockets().FirstOrDefault(x => x.GetBounds().Contains(loc));
if (socket != null)
{
if (IsConnectable(dragSocket,socket) && dragSocket.Input != socket.Input)
@@ -461,7 +516,8 @@ private void AddToMenu(ToolStripItemCollection items, NodeToken token, string pa
private void NodesControl_MouseClick(object sender, MouseEventArgs e)
{
- lastMouseLocation = e.Location;
+ var loc = GetLocationWithZoom(e.Location);
+ lastMouseLocation = loc;
if (Context == null) return;
@@ -547,9 +603,10 @@ private void NodesControl_MouseClick(object sender, MouseEventArgs e)
if (ctrl != null)
{
ctrl.Tag = nv;
- Controls.Add(ctrl);
+ Controls.Add(ctrl);
+ PassZoomToNodeCustomEditor(ctrl);
}
- nv.LayoutEditor();
+ nv.LayoutEditor(zoom);
}
graph.Nodes.Add(nv);
@@ -561,6 +618,27 @@ private void NodesControl_MouseClick(object sender, MouseEventArgs e)
}
}
+ private Point GetLocationWithZoom(Point location)
+ {
+ var zx = location.X / zoom;
+ var zy = location.Y / zoom;
+ var zl = new Point((int)zx, (int)zy);
+ return zl;
+ }
+
+ private void PassZoomToNodes()
+ {
+ foreach(var node in graph.Nodes)
+ {
+ if(node.CustomEditor != null)
+ {
+ PassZoomToNodeCustomEditor(node.CustomEditor);
+ node.DiscardCache();
+ node.LayoutEditor(zoom);
+ }
+ }
+ }
+
private void ChangeSelectedNodesColor()
{
ColorDialog cd = new ColorDialog();
@@ -598,7 +676,11 @@ private void DuplicateSelectedNodes()
}
graph.Nodes.ForEach(x => x.IsSelected = false);
cloned.ForEach(x => x.IsSelected = true);
- cloned.Where(x => x.CustomEditor != null).ToList().ForEach(x => x.CustomEditor.BringToFront());
+ cloned.Where(x => x.CustomEditor != null).ToList().ForEach(x =>
+ {
+ x.CustomEditor.BringToFront();
+ PassZoomToNodeCustomEditor(x.CustomEditor);
+ });
graph.Nodes.AddRange(cloned);
Invalidate();
}
@@ -966,8 +1048,9 @@ private NodeVisual DeserializeNode(BinaryReader br)
{
ctrl.Tag = nv;
Controls.Add(ctrl);
+ PassZoomToNodeCustomEditor(ctrl);
}
- nv.LayoutEditor();
+ nv.LayoutEditor(zoom);
}
return nv;
}
diff --git a/NodeEditor/NodesGraph.cs b/NodeEditor/NodesGraph.cs
index 72663e8..d3edb04 100644
--- a/NodeEditor/NodesGraph.cs
+++ b/NodeEditor/NodesGraph.cs
@@ -29,22 +29,20 @@ internal class NodesGraph
{
internal List Nodes = new List();
internal List Connections = new List();
+ static Pen executionPen;
+ static Pen executionPen2;
- public void Draw(Graphics g, Point mouseLocation, MouseButtons mouseButtons)
- {
- g.InterpolationMode = InterpolationMode.Low;
- g.SmoothingMode = SmoothingMode.HighSpeed;
-
+ public void Draw(Graphics g, Point mouseLocation, MouseButtons mouseButtons, bool preferFastRendering, DrawInfo info)
+ {
foreach (var node in Nodes)
{
g.FillRectangle(Brushes.Black, new RectangleF(new PointF(node.X+6, node.Y+6), node.GetNodeBounds()));
}
g.FillRectangle(new SolidBrush(Color.FromArgb(200, Color.White)), g.ClipBounds);
-
- var cpen = Pens.Black;
- var epen = new Pen(Color.Gold, 3);
- var epen2 = new Pen(Color.Black, 5);
+
+ executionPen = (executionPen ?? new Pen(Color.Gold, 3));
+ executionPen2 = (executionPen2 ?? new Pen(Color.Black, 5));
foreach (var connection in Connections.Where(x=>x.IsExecution))
{
var osoc = connection.OutputNode.GetSockets().FirstOrDefault(x => x.Name == connection.OutputSocketName);
@@ -54,8 +52,8 @@ public void Draw(Graphics g, Point mouseLocation, MouseButtons mouseButtons)
var begin = beginSocket.Location + new SizeF(beginSocket.Width / 2f, beginSocket.Height / 2f);
var end = endSocket.Location += new SizeF(endSocket.Width / 2f, endSocket.Height / 2f);
- DrawConnection(g, epen2, begin, end);
- DrawConnection(g, epen, begin, end);
+ DrawConnection(g, executionPen2, begin, end, preferFastRendering);
+ DrawConnection(g, executionPen, begin, end, preferFastRendering);
}
foreach (var connection in Connections.Where(x => !x.IsExecution))
{
@@ -65,8 +63,9 @@ public void Draw(Graphics g, Point mouseLocation, MouseButtons mouseButtons)
var endSocket = isoc.GetBounds();
var begin = beginSocket.Location + new SizeF(beginSocket.Width / 2f, beginSocket.Height / 2f);
var end = endSocket.Location += new SizeF(endSocket.Width / 2f, endSocket.Height / 2f);
-
- DrawConnection(g, cpen, begin, end);
+
+ var cpen = info.GetConnectionStyle(connection.InputSocket.Type, false);
+ DrawConnection(g, cpen, begin, end, preferFastRendering);
}
@@ -77,13 +76,10 @@ public void Draw(Graphics g, Point mouseLocation, MouseButtons mouseButtons)
}
}
- public static void DrawConnection(Graphics g, Pen pen, PointF output, PointF input)
- {
- g.InterpolationMode = InterpolationMode.HighQualityBilinear;
- g.SmoothingMode = SmoothingMode.HighQuality;
-
+ public static void DrawConnection(Graphics g, Pen pen, PointF output, PointF input, bool preferFastRendering = false)
+ {
if (input == output) return;
- const int interpolation = 48;
+ int interpolation = preferFastRendering ? 16 : 48;
PointF[] points = new PointF[interpolation];
for (int i = 0; i < interpolation; i++)