Explorar o código

Socket connector

tentone %!s(int64=5) %!d(string=hai) anos
pai
achega
72c6555a46

+ 127 - 49
build/escher.js

@@ -1299,7 +1299,9 @@
 	Object2D.prototype.onButtonUp = null;
 
 	/**
-	 * Text element, used to draw text into the canvas.
+	 * Text element, used to draw single line text into the canvas.
+	 *
+	 * For multi line text with support for line break check {MultiLineText} object.
 	 *
 	 * @class
 	 * @extends {Object2D}
@@ -1309,38 +1311,57 @@
 		Object2D.call(this);
 
 		/**
-		 * Text value.
+		 * Text value displayed by this element.
+		 *
+		 * @type {string}
 		 */
 		this.text = "";
 
 		/**
 		 * Font of the text.
+		 *
+		 * @type {string}
 		 */
 		this.font = "16px Arial";
 
 		/**
-		 * Style of the object border line.
+		 * Style of the object border line. If set null it is ignored.
 		 *
-		 * If set null it is ignored.
+		 * @type {string}
 		 */
 		this.strokeStyle = null;
 
 		/**
 		 * Line width, only used if a valid strokeStyle is defined.
+		 *
+		 * @type {number}
 		 */
 		this.lineWidth = 1;
 
 		/**
-		 * Background color of the box.
+		 * CSS background color of the box. If set null it is ignored.
 		 *
-		 * If set null it is ignored.
+		 * @type {string}
 		 */
 		this.fillStyle = "#000000";
 
 		/**
-		 * Text align property.
+		 * Text align property. Same values as used for canvas text applies
+		 *
+		 * Check documentation at https://developer.mozilla.org/en-US/docs/Web/CSS/text-align for mode details about this property.
+		 *
+		 * @type {string}
 		 */
 		this.textAlign = "center";
+
+		/**
+		 * Text baseline defines the vertical position of the text relative to the imaginary line Y position. Same values as used for canvas text applies
+		 *
+		 * Check documentation at https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline for mode details about this property.
+		 *
+		 * @type {string}
+		 */
+		this.textBaseline = "middle";
 	}
 
 	Text.prototype = Object.create(Object2D.prototype);
@@ -1349,7 +1370,7 @@
 	{
 		context.font = this.font;
 		context.textAlign = this.textAlign;
-		context.textBaseline = "middle";
+		context.textBaseline = this.textBaseline ;
 		
 		if(this.fillStyle !== null)
 		{
@@ -1376,13 +1397,6 @@
 	{
 		Text.call(this);
 
-		/**
-		 * Text baseline defines the vertical position of the text relative to the imaginary line Y position.
-		 *
-		 * @type {string}
-		 */
-		this.textBaseline = "middle";
-
 		/**
 		 * Maximum width of the text content. After text reaches the max width a line break is placed.
 		 *
@@ -3430,13 +3444,16 @@
 		Line.call(this);
 
 		/**
-		 * Intial position control point, indicates the tangent of the bezier curve on the first point.
+		 * Initial position control point, indicates the tangent of the bezier curve on the first point.
+		 *
+		 * @type {Vector2}
 		 */
 		this.fromCp = new Vector2();
 
-
 		/**
 		 * Final position control point, indicates the tangent of the bezier curve on the last point.
+		 *
+		 * @type {Vector2}
 		 */
 		this.toCp = new Vector2();
 	}
@@ -3508,6 +3525,8 @@
 		 * Control point of the quadratic curve used to control the curvature of the line between the from and to point.
 		 *
 		 * The curve is interpolated in the direction of the control point it defined the path of the curve.
+		 *
+		 * @type {Vector2}
 		 */
 		this.controlPoint = new Vector2();
 	}
@@ -3564,7 +3583,9 @@
 	 */
 	function NodeConnector()
 	{
-		Line.call(this);
+		BezierCurve.call(this);
+
+		this.lineWidth = 2;
 
 		/**
 		 * Origin hook that is attached to a node.
@@ -3581,13 +3602,21 @@
 		this.inputSocket = null;
 	}
 
-	NodeConnector.prototype = Object.create(Line.prototype);
+	NodeConnector.prototype = Object.create(BezierCurve.prototype);
 
 	NodeConnector.prototype.destroy = function()
 	{
-		Line.prototype.destroy.call(this);
+		if(this.outputSocket !== null)
+		{
+			this.outputSocket.connector = null;
+		}
+
+		if(this.inputSocket !== null)
+		{
+			this.inputSocket.connector = null;
+		}
 
-		// TODO <REMOVE FROM HOOKS>
+		BezierCurve.prototype.destroy.call(this);
 	};
 
 	NodeConnector.prototype.onUpdate = function()
@@ -3601,6 +3630,30 @@
 		{
 			this.to.copy(this.inputSocket.position);
 		}
+
+		// Center control points
+		this.fromCp.copy(this.from);
+		this.fromCp.add(this.to);
+		this.fromCp.multiplyScalar(0.5);
+		this.toCp.copy(this.fromCp);
+
+		var curvature = 0.5;
+
+		// Check vertical/horizontal distances
+		var yDistance = this.to.y - this.from.y;
+		var xDistance = this.to.x - this.from.x;
+
+		// Apply a offset to the control points
+		if(Math.abs(xDistance) > Math.abs(yDistance))
+		{
+			this.toCp.x += xDistance * curvature;
+			this.fromCp.x -= xDistance * curvature;
+		}
+		else
+		{
+			this.toCp.y += yDistance * curvature;
+			this.fromCp.y -= yDistance * curvature;
+		}
 	};
 
 	/**
@@ -3611,34 +3664,30 @@
 	 * @class NodeSocket
 	 * @param {Node} node Node of this hook.
 	 * @param {number} direction Direction of the hook.
+	 * @param {string} type Data type of the node socket.
+	 * @param {string} name Name of the node socket.
 	 */
-	function NodeSocket(node, direction)
+	function NodeSocket(node, direction, type, name)
 	{
 		Circle.call(this);
 
 		this.draggable = true;
-
 		this.radius = 6;
 		this.layer = 1;
 
 		/**
 		 * Name of the hook presented to the user.
-		 */
-		this.name = "";
-
-		/**
-		 * Type of hook. Hooks of the same type can be connected.
 		 *
 		 * @type {string}
 		 */
-		this.type = "";
+		this.name = name !== undefined ? name : "";
 
 		/**
-		 * If set true the hook can be connected to multiple hooks.
+		 * Type of data available from this socket, only hooks of the same type can be connected.
 		 *
-		 * @type {boolean}
+		 * @type {string}
 		 */
-		this.multiple = false;
+		this.type = type !== undefined ? type : "";
 
 		/**
 		 * Direction of the node hook.
@@ -3660,6 +3709,25 @@
 		 * @type {NodeConnector}
 		 */
 		this.connector = null;
+
+		/**
+		 * Text object used to present the name of the socket.
+		 *
+		 * @type {Text}
+		 */
+		this.text = new Text();
+		this.text.text = this.name;
+		if(this.direction === NodeSocket.INPUT)
+		{
+			this.text.position.x -= 10;
+			this.text.textAlign = "right";
+		}
+		else if(this.direction === NodeSocket.OUTPUT)
+		{
+			this.text.position.x += 10;
+			this.text.textAlign = "left";
+		}
+		this.add(this.text);
 	}
 
 	/**
@@ -3707,6 +3775,20 @@
 		}
 	};
 
+	/**
+	 * Check if this socket can be connected (is compatible) with another socket.
+	 *
+	 * For two sockets to be compatible the data flow should be correct (one input and a output) and they should carry the same data type.
+	 *
+	 * @param {NodeSocket} socket Socket to verify compatibility with.
+	 * @return {boolean} Returns true if the two sockets are compatible.
+	 */
+	NodeSocket.prototype.isCompatible = function(socket)
+	{
+		return this.direction !== socket.direction && this.type === socket.type;
+	};
+
+
 	NodeSocket.prototype.onPointerDragStart = function(pointer, viewport)
 	{
 		if(this.connector === null)
@@ -3742,7 +3824,7 @@
 			{
 				if(objects[i] instanceof NodeSocket)
 				{
-					if(this.direction !== objects[i].direction)
+					if(this.isCompatible(objects[i]))
 					{
 						objects[i].attachConnector(this.connector);
 						found = true;
@@ -3754,11 +3836,7 @@
 			if(!found)
 			{
 				this.connector.destroy();
-				this.connector = null;
 			}
-
-			// TODO <REMOVE THIS>
-			console.log("Finished drag.", objects);
 		}
 	};
 
@@ -3795,27 +3873,27 @@
 	Node.prototype = Object.create(Box.prototype);
 
 	/**
-	 * Add input to this node.
+	 * Add input to this node, can be connected to other nodes to receive data.
 	 *
-	 * @param type
+	 * @param {string} type Data type of the node socket.
+	 * @param {string} name Name of the node socket.
 	 */
-	Node.prototype.addInput = function(type)
+	Node.prototype.addInput = function(type, name)
 	{
-		var hook = new NodeSocket(this, NodeSocket.INPUT);
-		hook.type = type;
+		var hook = new NodeSocket(this, NodeSocket.INPUT, type, name);
 		this.inputs.push(hook);
 		this.parent.add(hook);
 	};
 
 	/**
-	 * Add output hook to this node.
+	 * Add output socket to this node, can be connected to other nodes to send data.
 	 *
-	 * @param type
+	 * @param {string} type Data type of the node socket.
+	 * @param {string} name Name of the node socket.
 	 */
-	Node.prototype.addOutput = function(type)
+	Node.prototype.addOutput = function(type, name)
 	{
-		var hook = new NodeSocket(this, NodeSocket.OUTPUT);
-		hook.type = type;
+		var hook = new NodeSocket(this, NodeSocket.OUTPUT, type, name);
 		this.outputs.push(hook);
 		this.parent.add(hook);
 	};
@@ -3834,8 +3912,8 @@
 		}
 
 		// Output hooks position
-		var step = height / (this.outputs.length + 1);
-		var start = this.box.min.y + step;
+		step = height / (this.outputs.length + 1);
+		start = this.box.min.y + step;
 
 		for(var i = 0; i < this.outputs.length; i++)
 		{

+ 6 - 6
examples/node.html

@@ -70,17 +70,17 @@
 		node.position.set(100, 100);
 		group.add(node);
 
-		node.addInput("test");
-		node.addOutput("test");
+		node.addInput("test", "aaaaaaaaaa");
+		node.addOutput("test", "aaaaaaaaaa");
 
 		var node = new Escher.Node();
 		node.position.set(100, 100);
 		group.add(node);
 
-		node.addInput("test");
-		node.addInput("test");
-		node.addInput("test");
-		node.addOutput("test");
+		node.addInput("test", "aaaaa");
+		node.addInput("test", "bbb");
+		node.addInput("test", "ccccccccc");
+		node.addOutput("test", "aaaaaaaa");
 
 
 		// Line (connection)

+ 5 - 2
source/objects/BezierCurve.js

@@ -14,13 +14,16 @@ function BezierCurve()
 	Line.call(this);
 
 	/**
-	 * Intial position control point, indicates the tangent of the bezier curve on the first point.
+	 * Initial position control point, indicates the tangent of the bezier curve on the first point.
+	 *
+	 * @type {Vector2}
 	 */
 	this.fromCp = new Vector2();
 
-
 	/**
 	 * Final position control point, indicates the tangent of the bezier curve on the last point.
+	 *
+	 * @type {Vector2}
 	 */
 	this.toCp = new Vector2();
 }

+ 0 - 7
source/objects/MultiLineText.js

@@ -12,13 +12,6 @@ function MultiLineText()
 {
 	Text.call(this);
 
-	/**
-	 * Text baseline defines the vertical position of the text relative to the imaginary line Y position.
-	 *
-	 * @type {string}
-	 */
-	this.textBaseline = "middle";
-
 	/**
 	 * Maximum width of the text content. After text reaches the max width a line break is placed.
 	 *

+ 2 - 0
source/objects/QuadraticCurve.js

@@ -17,6 +17,8 @@ function QuadraticCurve()
 	 * Control point of the quadratic curve used to control the curvature of the line between the from and to point.
 	 *
 	 * The curve is interpolated in the direction of the control point it defined the path of the curve.
+	 *
+	 * @type {Vector2}
 	 */
 	this.controlPoint = new Vector2();
 }

+ 29 - 8
source/objects/Text.js

@@ -1,7 +1,9 @@
 import {Object2D} from "../Object2D.js";
 
 /**
- * Text element, used to draw text into the canvas.
+ * Text element, used to draw single line text into the canvas.
+ *
+ * For multi line text with support for line break check {MultiLineText} object.
  *
  * @class
  * @extends {Object2D}
@@ -11,38 +13,57 @@ function Text()
 	Object2D.call(this);
 
 	/**
-	 * Text value.
+	 * Text value displayed by this element.
+	 *
+	 * @type {string}
 	 */
 	this.text = "";
 
 	/**
 	 * Font of the text.
+	 *
+	 * @type {string}
 	 */
 	this.font = "16px Arial";
 
 	/**
-	 * Style of the object border line.
+	 * Style of the object border line. If set null it is ignored.
 	 *
-	 * If set null it is ignored.
+	 * @type {string}
 	 */
 	this.strokeStyle = null;
 
 	/**
 	 * Line width, only used if a valid strokeStyle is defined.
+	 *
+	 * @type {number}
 	 */
 	this.lineWidth = 1;
 
 	/**
-	 * Background color of the box.
+	 * CSS background color of the box. If set null it is ignored.
 	 *
-	 * If set null it is ignored.
+	 * @type {string}
 	 */
 	this.fillStyle = "#000000";
 
 	/**
-	 * Text align property.
+	 * Text align property. Same values as used for canvas text applies
+	 *
+	 * Check documentation at https://developer.mozilla.org/en-US/docs/Web/CSS/text-align for mode details about this property.
+	 *
+	 * @type {string}
 	 */
 	this.textAlign = "center";
+
+	/**
+	 * Text baseline defines the vertical position of the text relative to the imaginary line Y position. Same values as used for canvas text applies
+	 *
+	 * Check documentation at https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline for mode details about this property.
+	 *
+	 * @type {string}
+	 */
+	this.textBaseline = "middle";
 }
 
 Text.prototype = Object.create(Object2D.prototype);
@@ -51,7 +72,7 @@ Text.prototype.draw = function(context, viewport, canvas)
 {
 	context.font = this.font;
 	context.textAlign = this.textAlign;
-	context.textBaseline = "middle";
+	context.textBaseline = this.textBaseline ;
 	
 	if(this.fillStyle !== null)
 	{

+ 18 - 14
source/objects/node/Node.js

@@ -34,29 +34,33 @@ function Node()
 Node.prototype = Object.create(Box.prototype);
 
 /**
- * Add input to this node.
+ * Add input to this node, can be connected to other nodes to receive data.
  *
- * @param type
+ * @param {string} type Data type of the node socket.
+ * @param {string} name Name of the node socket.
+ * @return {NodeSocket} Node socket created for this node.
  */
-Node.prototype.addInput = function(type)
+Node.prototype.addInput = function(type, name)
 {
-	var hook = new NodeSocket(this, NodeSocket.INPUT);
-	hook.type = type;
-	this.inputs.push(hook);
-	this.parent.add(hook);
+	var socket = new NodeSocket(this, NodeSocket.INPUT, type, name);
+	this.inputs.push(socket);
+	this.parent.add(socket);
+	return socket;
 };
 
 /**
- * Add output hook to this node.
+ * Add output socket to this node, can be connected to other nodes to send data.
  *
- * @param type
+ * @param {string} type Data type of the node socket.
+ * @param {string} name Name of the node socket.
+ * @return {NodeSocket} Node socket created for this node.
  */
-Node.prototype.addOutput = function(type)
+Node.prototype.addOutput = function(type, name)
 {
-	var hook = new NodeSocket(this, NodeSocket.OUTPUT);
-	hook.type = type;
-	this.outputs.push(hook);
-	this.parent.add(hook);
+	var socket = new NodeSocket(this, NodeSocket.OUTPUT, type, name);
+	this.outputs.push(socket);
+	this.parent.add(socket);
+	return socket;
 };
 
 Node.prototype.onUpdate = function()

+ 39 - 5
source/objects/node/NodeConnector.js

@@ -1,4 +1,4 @@
-import {Line} from "../Line";
+import {BezierCurve} from "../BezierCurve";
 
 /**
  * Node connector is used to connect a output of a node to a input of another node.
@@ -9,7 +9,9 @@ import {Line} from "../Line";
  */
 function NodeConnector()
 {
-	Line.call(this);
+	BezierCurve.call(this);
+
+	this.lineWidth = 2;
 
 	/**
 	 * Origin hook that is attached to a node.
@@ -26,13 +28,21 @@ function NodeConnector()
 	this.inputSocket = null;
 }
 
-NodeConnector.prototype = Object.create(Line.prototype);
+NodeConnector.prototype = Object.create(BezierCurve.prototype);
 
 NodeConnector.prototype.destroy = function()
 {
-	Line.prototype.destroy.call(this);
+	if(this.outputSocket !== null)
+	{
+		this.outputSocket.connector = null;
+	}
+
+	if(this.inputSocket !== null)
+	{
+		this.inputSocket.connector = null;
+	}
 
-	// TODO <REMOVE FROM HOOKS>
+	BezierCurve.prototype.destroy.call(this);
 };
 
 NodeConnector.prototype.onUpdate = function()
@@ -46,6 +56,30 @@ NodeConnector.prototype.onUpdate = function()
 	{
 		this.to.copy(this.inputSocket.position);
 	}
+
+	// Center control points
+	this.fromCp.copy(this.from);
+	this.fromCp.add(this.to);
+	this.fromCp.multiplyScalar(0.5);
+	this.toCp.copy(this.fromCp);
+
+	var curvature = 0.5;
+
+	// Check vertical/horizontal distances
+	var yDistance = this.to.y - this.from.y;
+	var xDistance = this.to.x - this.from.x;
+
+	// Apply a offset to the control points
+	if(Math.abs(xDistance) > Math.abs(yDistance))
+	{
+		this.toCp.x += xDistance * curvature;
+		this.fromCp.x -= xDistance * curvature;
+	}
+	else
+	{
+		this.toCp.y += yDistance * curvature;
+		this.fromCp.y -= yDistance * curvature;
+	}
 };
 
 

+ 21 - 0
source/objects/node/NodeGraph.js

@@ -0,0 +1,21 @@
+import {Object2D} from "../../Object2D";
+
+/**
+ * Node graph object should be used as a container for node elements.
+ *
+ * The node graph object specifies how the nodes are processed, each individual node can store and process data, the node graph specified how this information is processed.
+ *
+ * All node elements are stored as children of the node graph.
+ *
+ * @class NodeGraph
+ */
+function NodeGraph()
+{
+	Object2D.call(this);
+
+	// TODO <ADD CODE HERE>
+}
+
+NodeGraph.prototype = Object.create(Object2D.prototype);
+
+export {NodeGraph};

+ 49 - 10
source/objects/node/NodeSocket.js

@@ -1,6 +1,7 @@
 import {Circle} from "../Circle";
 import {Node} from "./Node";
 import {NodeConnector} from "./NodeConnector";
+import {Text} from "../Text";
 
 /**
  * Represents a node hook point. Is attached to the node element and represented visually.
@@ -10,27 +11,30 @@ import {NodeConnector} from "./NodeConnector";
  * @class NodeSocket
  * @param {Node} node Node of this hook.
  * @param {number} direction Direction of the hook.
+ * @param {string} type Data type of the node socket.
+ * @param {string} name Name of the node socket.
  */
-function NodeSocket(node, direction)
+function NodeSocket(node, direction, type, name)
 {
 	Circle.call(this);
 
 	this.draggable = true;
-
 	this.radius = 6;
 	this.layer = 1;
 
 	/**
 	 * Name of the hook presented to the user.
+	 *
+	 * @type {string}
 	 */
-	this.name = "";
+	this.name = name !== undefined ? name : "";
 
 	/**
-	 * Type of hook. Hooks of the same type can be connected.
+	 * Type of data available from this socket, only hooks of the same type can be connected.
 	 *
 	 * @type {string}
 	 */
-	this.type = "";
+	this.type = type !== undefined ? type : "";
 
 	/**
 	 * Direction of the node hook.
@@ -52,6 +56,25 @@ function NodeSocket(node, direction)
 	 * @type {NodeConnector}
 	 */
 	this.connector = null;
+
+	/**
+	 * Text object used to present the name of the socket.
+	 *
+	 * @type {Text}
+	 */
+	this.text = new Text();
+	this.text.text = this.name;
+	if(this.direction === NodeSocket.INPUT)
+	{
+		this.text.position.x -= 10;
+		this.text.textAlign = "right";
+	}
+	else if(this.direction === NodeSocket.OUTPUT)
+	{
+		this.text.position.x += 10;
+		this.text.textAlign = "left";
+	}
+	this.add(this.text);
 }
 
 /**
@@ -74,6 +97,26 @@ NodeSocket.OUTPUT = 2;
 
 NodeSocket.prototype = Object.create(Circle.prototype);
 
+/**
+ * Connect this node socket to another socket.
+ *
+ * Sockets have to be compatible otherwise the connection cannot be made and an error will be thrown.
+ *
+ * @param {NodeSocket} socket Socket to be connected with this
+ * @return {NodeConnector} Node connector created.
+ */
+NodeSocket.prototype.connectTo = function(socket)
+{
+	if(!this.isCompatible(socket))
+	{
+		throw new Error("Sockets are not compatible they cannot be connected.");
+	}
+
+	var connector = new NodeConnector();
+	this.attachConnector(connector);
+	socket.attachConnector(connector);
+	return connector;
+};
 /**
  * Attach a node connector to this socket. Sets the correct input/output attributes on the socket and the connector.
  *
@@ -148,7 +191,7 @@ NodeSocket.prototype.onPointerDragEnd = function(pointer, viewport)
 		{
 			if(objects[i] instanceof NodeSocket)
 			{
-				if(this.direction !== objects[i].direction && this.type === objects[i].type)
+				if(this.isCompatible(objects[i]))
 				{
 					objects[i].attachConnector(this.connector);
 					found = true;
@@ -160,11 +203,7 @@ NodeSocket.prototype.onPointerDragEnd = function(pointer, viewport)
 		if(!found)
 		{
 			this.connector.destroy();
-			this.connector = null;
 		}
-
-		// TODO <REMOVE THIS>
-		console.log("Finished drag.", objects);
 	}
 };