tentone 6 years ago
parent
commit
116a08cbf0

+ 40 - 4
README.md

@@ -1,13 +1,49 @@
 # trenette.js
 # trenette.js
 
 
  - Web based diagram and graph building framework.
  - Web based diagram and graph building framework.
- - Entity based diagram build system.
+ - Entity based diagram build system, entities are stores as a tree. Parent elements transformations affect the children transforms.
+ - Boxes, circle, custom shapes, lines, customizable elements.
+ - Support for DOM elements using CSS transforms (useful for text input, or more complex user interaction).
+ - Built in viewport controls with drag, zoom and move functions.
+ - Supports mobile web browsers.
 
 
 
 
-## Basic example
 
 
+![graph](C:\Users\joseferrao\Documents\Git\trenette.js\examples\graph.png)
 
 
-## Rendering pipeline
+### Getting started
 
 
- - 
+- There are a couple of example in the example folder, they can be used as base for your project.
+
+
+
+### Setup
+
+- Trenette is based on web canvas, it requires a DOM canvas element to draw its content.
+- It is necessary for the canvas element width and height parameters to be properly configured since their values are used to process user input.
+- When using other DOM elements with the framework is also necessary to setup a DOM div to store these elements. (Booth the canvas and division should have the same position and size and should be aligned).
+
+
+
+Custom Objects
+
+- Its possible to create custom graph elements by expanding the Object2D class, and overriding its draw(), transform() methods.
+
+
+
+Pointer events
+
+- The system supports multiple pointer events that can be used to control the objects and interact with the users.
+
+
+
+DOM Objects
+
+- Its possible to use DOM elements in the graph, by applying CSS transform to absolute positioned elements the system already provides a DOM base object that creates a basic division.
+
+
+
+### License
+
+ - This project is distributed under MIT license available on the repository page.
 
 

+ 68 - 13
build/trenette.js

@@ -701,6 +701,13 @@
 		 */
 		 */
 		this.parent = null;
 		this.parent = null;
 
 
+		/**
+		 * Depth level in the object tree, objects with higher depth are drawn on top.
+		 *
+		 * The layer value is considered first. 
+		 */
+		this.level = 0;
+
 		/**
 		/**
 		 * Position of the object.
 		 * Position of the object.
 		 */
 		 */
@@ -831,6 +838,8 @@
 	Object2D.prototype.add = function(object)
 	Object2D.prototype.add = function(object)
 	{
 	{
 		object.parent = this;
 		object.parent = this;
+		object.level = this.level + 1;
+
 		this.children.push(object);
 		this.children.push(object);
 	};
 	};
 
 
@@ -842,9 +851,18 @@
 	Object2D.prototype.remove = function(object)
 	Object2D.prototype.remove = function(object)
 	{
 	{
 		var index = this.children.indexOf(object);
 		var index = this.children.indexOf(object);
+		
 		if(index !== -1)
 		if(index !== -1)
 		{
 		{
-			this.children[index].parent = null;
+			var object = this.children[index];
+			object.parent = null;
+			object.level = 0;
+			
+			if(object.onDestroy !== null)
+			{
+				object.onDestroy();
+			}
+
 			this.children.splice(index, 1);
 			this.children.splice(index, 1);
 		}
 		}
 	};
 	};
@@ -892,6 +910,8 @@
 		this.globalMatrix.tranformContext(context);
 		this.globalMatrix.tranformContext(context);
 	};
 	};
 
 
+
+
 	/**
 	/**
 	 * Draw the object into the canvas.
 	 * Draw the object into the canvas.
 	 *
 	 *
@@ -903,6 +923,11 @@
 	 */
 	 */
 	Object2D.prototype.draw = function(context, viewport, canvas){};
 	Object2D.prototype.draw = function(context, viewport, canvas){};
 
 
+	/**
+	 * Method called when the object gets removed from its parent
+	 */
+	Object2D.prototype.onDestroy = null;
+
 	/**
 	/**
 	 * Callback method called every time before the object is draw into the canvas.
 	 * Callback method called every time before the object is draw into the canvas.
 	 *
 	 *
@@ -1440,8 +1465,16 @@
 	 *
 	 *
 	 * @class
 	 * @class
 	 */
 	 */
-	function Renderer(canvas)
+	function Renderer(canvas, options)
 	{
 	{
+		if(options === undefined)
+		{
+			options = 
+			{
+				alpha: true
+			};
+		}
+
 		/**
 		/**
 		 * Canvas DOM element, has to be managed by the user.
 		 * Canvas DOM element, has to be managed by the user.
 		 */
 		 */
@@ -1450,7 +1483,7 @@
 		/**
 		/**
 		 * Canvas 2D rendering context used to draw content.
 		 * Canvas 2D rendering context used to draw content.
 		 */
 		 */
-		this.context = canvas.getContext("2d");
+		this.context = canvas.getContext("2d", {alpha: options.alpha});
 		this.context.imageSmoothingEnabled = true;
 		this.context.imageSmoothingEnabled = true;
 		this.context.globalCompositeOperation = "source-over";
 		this.context.globalCompositeOperation = "source-over";
 
 
@@ -1520,6 +1553,11 @@
 		// Sort objects by layer
 		// Sort objects by layer
 		objects.sort(function(a, b)
 		objects.sort(function(a, b)
 		{
 		{
+			if(b.layer === a.layer)
+			{
+				return b.level - a.level;
+			}
+			
 			return b.layer - a.layer;
 			return b.layer - a.layer;
 		});
 		});
 
 
@@ -1647,7 +1685,6 @@
 		{
 		{
 			child.updateMatrix();
 			child.updateMatrix();
 		});
 		});
-		
 
 
 		this.context.setTransform(1, 0, 0, 1, 0, 0);
 		this.context.setTransform(1, 0, 0, 1, 0, 0);
 		
 		
@@ -1752,6 +1789,13 @@
 		 * For some application its easier to focus the target if the viewport moves to the pointer location while scalling.
 		 * For some application its easier to focus the target if the viewport moves to the pointer location while scalling.
 		 */
 		 */
 		this.moveOnScale = true;
 		this.moveOnScale = true;
+
+		/**
+		 * Value of the initial point of rotation if the viewport is being rotated.
+		 *
+		 * Is set to null when the viewport is not being rotated.
+		 */
+		this.rotationPoint = null;
 	}
 	}
 
 
 	/**
 	/**
@@ -1774,11 +1818,17 @@
 			}
 			}
 		}
 		}
 
 
-		if(pointer.buttonPressed(Pointer.RIGHT))
+		if(pointer.buttonPressed(Pointer.RIGHT) && pointer.buttonPressed(Pointer.LEFT))
+		{
+			this.rotation += pointer.delta.angle() * 1e-2;
+		}
+		else if(pointer.buttonPressed(Pointer.RIGHT))
 		{
 		{
 			this.position.x += pointer.delta.x;
 			this.position.x += pointer.delta.x;
 			this.position.y += pointer.delta.y;
 			this.position.y += pointer.delta.y;
 		}
 		}
+
+
 	};
 	};
 
 
 	/**
 	/**
@@ -2147,17 +2197,14 @@
 
 
 	Circle.prototype.draw = function(context, viewport, canvas)
 	Circle.prototype.draw = function(context, viewport, canvas)
 	{
 	{
-		context.fillStyle = this.fillStyle;
-
 		context.beginPath();
 		context.beginPath();
 		context.arc(0, 0, this.radius, 0, 2 * Math.PI);
 		context.arc(0, 0, this.radius, 0, 2 * Math.PI);
+		
+		context.fillStyle = this.fillStyle;
 		context.fill();
 		context.fill();
 
 
 		context.lineWidth = 1;
 		context.lineWidth = 1;
 		context.strokeStyle = this.strokeStyle;
 		context.strokeStyle = this.strokeStyle;
-
-		context.beginPath();
-		context.arc(0, 0, this.radius, 0, 2 * Math.PI);
 		context.stroke();
 		context.stroke();
 	};
 	};
 
 
@@ -2494,6 +2541,11 @@
 	{
 	{
 		Object2D.call(this);
 		Object2D.call(this);
 
 
+		/**
+		 * Parent element that contains this DOM div.
+		 */
+		this.parent = parent;
+
 		/**
 		/**
 		 * DOM element contained by this object.
 		 * DOM element contained by this object.
 		 *
 		 *
@@ -2507,7 +2559,7 @@
 		this.element.style.transformOrigin = "0px 0px";
 		this.element.style.transformOrigin = "0px 0px";
 		this.element.style.overflow = "auto";
 		this.element.style.overflow = "auto";
 		this.element.style.pointerEvents = "none";
 		this.element.style.pointerEvents = "none";
-		parent.appendChild(this.element);
+		this.parent.appendChild(this.element);
 		
 		
 		/**
 		/**
 		 * Size of the DOM element (in world coordinates).
 		 * Size of the DOM element (in world coordinates).
@@ -2517,6 +2569,11 @@
 
 
 	DOM.prototype = Object.create(Object2D.prototype);
 	DOM.prototype = Object.create(Object2D.prototype);
 
 
+	DOM.prototype.onDestroy = function()
+	{
+		this.parent.removeChild(this.element);
+	};
+
 	DOM.prototype.transform = function(context, viewport, canvas)
 	DOM.prototype.transform = function(context, viewport, canvas)
 	{
 	{
 		// CSS trasnformation matrix
 		// CSS trasnformation matrix
@@ -2596,8 +2653,6 @@
 		if(this.image.src.length > 0)
 		if(this.image.src.length > 0)
 		{
 		{
 			var pattern = context.createPattern(this.image, this.repetition);
 			var pattern = context.createPattern(this.image, this.repetition);
-			
-			//pattern.setTransform();
 
 
 			context.fillStyle = pattern;
 			context.fillStyle = pattern;
 			context.fillRect(this.box.min.x, this.box.min.y, width, height);
 			context.fillRect(this.box.min.x, this.box.min.y, width, height);

File diff suppressed because it is too large
+ 0 - 0
build/trenette.min.js


+ 68 - 13
build/trenette.module.js

@@ -695,6 +695,13 @@ function Object2D()
 	 */
 	 */
 	this.parent = null;
 	this.parent = null;
 
 
+	/**
+	 * Depth level in the object tree, objects with higher depth are drawn on top.
+	 *
+	 * The layer value is considered first. 
+	 */
+	this.level = 0;
+
 	/**
 	/**
 	 * Position of the object.
 	 * Position of the object.
 	 */
 	 */
@@ -825,6 +832,8 @@ Object2D.prototype.traverse = function(callback)
 Object2D.prototype.add = function(object)
 Object2D.prototype.add = function(object)
 {
 {
 	object.parent = this;
 	object.parent = this;
+	object.level = this.level + 1;
+
 	this.children.push(object);
 	this.children.push(object);
 };
 };
 
 
@@ -836,9 +845,18 @@ Object2D.prototype.add = function(object)
 Object2D.prototype.remove = function(object)
 Object2D.prototype.remove = function(object)
 {
 {
 	var index = this.children.indexOf(object);
 	var index = this.children.indexOf(object);
+	
 	if(index !== -1)
 	if(index !== -1)
 	{
 	{
-		this.children[index].parent = null;
+		var object = this.children[index];
+		object.parent = null;
+		object.level = 0;
+		
+		if(object.onDestroy !== null)
+		{
+			object.onDestroy();
+		}
+
 		this.children.splice(index, 1);
 		this.children.splice(index, 1);
 	}
 	}
 };
 };
@@ -886,6 +904,8 @@ Object2D.prototype.transform = function(context, viewport)
 	this.globalMatrix.tranformContext(context);
 	this.globalMatrix.tranformContext(context);
 };
 };
 
 
+
+
 /**
 /**
  * Draw the object into the canvas.
  * Draw the object into the canvas.
  *
  *
@@ -897,6 +917,11 @@ Object2D.prototype.transform = function(context, viewport)
  */
  */
 Object2D.prototype.draw = function(context, viewport, canvas){};
 Object2D.prototype.draw = function(context, viewport, canvas){};
 
 
+/**
+ * Method called when the object gets removed from its parent
+ */
+Object2D.prototype.onDestroy = null;
+
 /**
 /**
  * Callback method called every time before the object is draw into the canvas.
  * Callback method called every time before the object is draw into the canvas.
  *
  *
@@ -1434,8 +1459,16 @@ Pointer.dispose = function()
  *
  *
  * @class
  * @class
  */
  */
-function Renderer(canvas)
+function Renderer(canvas, options)
 {
 {
+	if(options === undefined)
+	{
+		options = 
+		{
+			alpha: true
+		};
+	}
+
 	/**
 	/**
 	 * Canvas DOM element, has to be managed by the user.
 	 * Canvas DOM element, has to be managed by the user.
 	 */
 	 */
@@ -1444,7 +1477,7 @@ function Renderer(canvas)
 	/**
 	/**
 	 * Canvas 2D rendering context used to draw content.
 	 * Canvas 2D rendering context used to draw content.
 	 */
 	 */
-	this.context = canvas.getContext("2d");
+	this.context = canvas.getContext("2d", {alpha: options.alpha});
 	this.context.imageSmoothingEnabled = true;
 	this.context.imageSmoothingEnabled = true;
 	this.context.globalCompositeOperation = "source-over";
 	this.context.globalCompositeOperation = "source-over";
 
 
@@ -1514,6 +1547,11 @@ Renderer.prototype.update = function(object, viewport)
 	// Sort objects by layer
 	// Sort objects by layer
 	objects.sort(function(a, b)
 	objects.sort(function(a, b)
 	{
 	{
+		if(b.layer === a.layer)
+		{
+			return b.level - a.level;
+		}
+		
 		return b.layer - a.layer;
 		return b.layer - a.layer;
 	});
 	});
 
 
@@ -1641,7 +1679,6 @@ Renderer.prototype.update = function(object, viewport)
 	{
 	{
 		child.updateMatrix();
 		child.updateMatrix();
 	});
 	});
-	
 
 
 	this.context.setTransform(1, 0, 0, 1, 0, 0);
 	this.context.setTransform(1, 0, 0, 1, 0, 0);
 	
 	
@@ -1746,6 +1783,13 @@ function Viewport()
 	 * For some application its easier to focus the target if the viewport moves to the pointer location while scalling.
 	 * For some application its easier to focus the target if the viewport moves to the pointer location while scalling.
 	 */
 	 */
 	this.moveOnScale = true;
 	this.moveOnScale = true;
+
+	/**
+	 * Value of the initial point of rotation if the viewport is being rotated.
+	 *
+	 * Is set to null when the viewport is not being rotated.
+	 */
+	this.rotationPoint = null;
 }
 }
 
 
 /**
 /**
@@ -1768,11 +1812,17 @@ Viewport.prototype.updateControls = function(pointer)
 		}
 		}
 	}
 	}
 
 
-	if(pointer.buttonPressed(Pointer.RIGHT))
+	if(pointer.buttonPressed(Pointer.RIGHT) && pointer.buttonPressed(Pointer.LEFT))
+	{
+		this.rotation += pointer.delta.angle() * 1e-2;
+	}
+	else if(pointer.buttonPressed(Pointer.RIGHT))
 	{
 	{
 		this.position.x += pointer.delta.x;
 		this.position.x += pointer.delta.x;
 		this.position.y += pointer.delta.y;
 		this.position.y += pointer.delta.y;
 	}
 	}
+
+
 };
 };
 
 
 /**
 /**
@@ -2141,17 +2191,14 @@ Circle.prototype.onPointerLeave = function(pointer, viewport)
 
 
 Circle.prototype.draw = function(context, viewport, canvas)
 Circle.prototype.draw = function(context, viewport, canvas)
 {
 {
-	context.fillStyle = this.fillStyle;
-
 	context.beginPath();
 	context.beginPath();
 	context.arc(0, 0, this.radius, 0, 2 * Math.PI);
 	context.arc(0, 0, this.radius, 0, 2 * Math.PI);
+	
+	context.fillStyle = this.fillStyle;
 	context.fill();
 	context.fill();
 
 
 	context.lineWidth = 1;
 	context.lineWidth = 1;
 	context.strokeStyle = this.strokeStyle;
 	context.strokeStyle = this.strokeStyle;
-
-	context.beginPath();
-	context.arc(0, 0, this.radius, 0, 2 * Math.PI);
 	context.stroke();
 	context.stroke();
 };
 };
 
 
@@ -2488,6 +2535,11 @@ function DOM(parent, type)
 {
 {
 	Object2D.call(this);
 	Object2D.call(this);
 
 
+	/**
+	 * Parent element that contains this DOM div.
+	 */
+	this.parent = parent;
+
 	/**
 	/**
 	 * DOM element contained by this object.
 	 * DOM element contained by this object.
 	 *
 	 *
@@ -2501,7 +2553,7 @@ function DOM(parent, type)
 	this.element.style.transformOrigin = "0px 0px";
 	this.element.style.transformOrigin = "0px 0px";
 	this.element.style.overflow = "auto";
 	this.element.style.overflow = "auto";
 	this.element.style.pointerEvents = "none";
 	this.element.style.pointerEvents = "none";
-	parent.appendChild(this.element);
+	this.parent.appendChild(this.element);
 	
 	
 	/**
 	/**
 	 * Size of the DOM element (in world coordinates).
 	 * Size of the DOM element (in world coordinates).
@@ -2511,6 +2563,11 @@ function DOM(parent, type)
 
 
 DOM.prototype = Object.create(Object2D.prototype);
 DOM.prototype = Object.create(Object2D.prototype);
 
 
+DOM.prototype.onDestroy = function()
+{
+	this.parent.removeChild(this.element);
+};
+
 DOM.prototype.transform = function(context, viewport, canvas)
 DOM.prototype.transform = function(context, viewport, canvas)
 {
 {
 	// CSS trasnformation matrix
 	// CSS trasnformation matrix
@@ -2590,8 +2647,6 @@ Pattern.prototype.draw = function(context, viewport, canvas)
 	if(this.image.src.length > 0)
 	if(this.image.src.length > 0)
 	{
 	{
 		var pattern = context.createPattern(this.image, this.repetition);
 		var pattern = context.createPattern(this.image, this.repetition);
-		
-		//pattern.setTransform();
 
 
 		context.fillStyle = pattern;
 		context.fillStyle = pattern;
 		context.fillRect(this.box.min.x, this.box.min.y, width, height);
 		context.fillRect(this.box.min.x, this.box.min.y, width, height);

+ 59 - 30
examples/diagram.html

@@ -9,8 +9,7 @@
 
 
 	<script type="text/javascript">
 	<script type="text/javascript">
 
 
-		window.runLoop = true;
-
+		// Division
 		var division = document.createElement("div");
 		var division = document.createElement("div");
 		division.style.position = "absolute";
 		division.style.position = "absolute";
 		division.style.width = "100%";
 		division.style.width = "100%";
@@ -20,7 +19,7 @@
 		division.style.overflow = "hidden";
 		division.style.overflow = "hidden";
 		document.body.appendChild(division);
 		document.body.appendChild(division);
 
 
-		// Setup the display canvas
+		// Canvas
 		var canvas = document.createElement("canvas");
 		var canvas = document.createElement("canvas");
 		canvas.style.position = "absolute";
 		canvas.style.position = "absolute";
 		canvas.style.width = "100%";
 		canvas.style.width = "100%";
@@ -29,98 +28,116 @@
 		canvas.style.left = "0px";
 		canvas.style.left = "0px";
 		canvas.width = window.innerWidth;
 		canvas.width = window.innerWidth;
 		canvas.height = window.innerHeight;
 		canvas.height = window.innerHeight;
-		canvas.oncontextmenu = function(event)
+		division.appendChild(canvas);
+
+		// Prevent context menu events
+		document.body.oncontextmenu = function(event)
 		{
 		{
 			event.preventDefault();
 			event.preventDefault();
 			return false;
 			return false;
 		};
 		};
-		division.appendChild(canvas);
 
 
+		// Resize canvas on window resize
 		window.onresize = function()
 		window.onresize = function()
 		{
 		{
 			canvas.width = window.innerWidth;
 			canvas.width = window.innerWidth;
 			canvas.height = window.innerHeight;
 			canvas.height = window.innerHeight;
 		};
 		};
 
 
+		// Create boxes on double click
 		canvas.ondblclick = function(event)
 		canvas.ondblclick = function(event)
 		{
 		{
 			var p = new Trenette.Vector2(event.clientX, event.clientY);
 			var p = new Trenette.Vector2(event.clientX, event.clientY);
 			p = viewport.inverseMatrix.transformPoint(p);
 			p = viewport.inverseMatrix.transformPoint(p);
 
 
-			if(Math.random() > 0.5)
-			{
-				var box = new Trenette.Box();
-				box.position.copy(p);
-				box.draggable = true;
-				group.add(box);
-			}
-			else
-			{
-				var circle = new Trenette.Circle();
-				circle.position.copy(p);
-				circle.radius = Math.random() * 20 + 30;
-				circle.draggable = true;
-				group.add(circle);
-			}
+			var box = new Trenette.Box();
+			box.position.copy(p);
+			box.draggable = true;
+			boxes.add(box);
+
+			var text = new Trenette.Text();
+			text.text = "Box";
+			box.add(text);
 
 
-			if(group.children.length > 2)
+			if(boxes.children.length > 1)
 			{
 			{
 				var line = new Trenette.Line();
 				var line = new Trenette.Line();
-				line.from = group.children[group.children.length - 1].position;
-				line.to = group.children[group.children.length - 2].position;
 				line.layer = -1;
 				line.layer = -1;
+				line.from = boxes.children[boxes.children.length - 1].position;
+				line.to = boxes.children[boxes.children.length - 2].position;
 				group.add(line);
 				group.add(line);
+			}
+		};
 
 
-				console.log(line);
+		window.onkeydown = function(event)
+		{
+			if(selected !== null && event.keyCode === 46)
+			{
+				group.remove(selected);
 			}
 			}
 		};
 		};
 
 
-		// Create the diagram
+		var selected = null;
+
+		// Group to store other objects
 		var group = new Trenette.Object2D();
 		var group = new Trenette.Object2D();
+		
+		// Pattern image object
 		var image = new Trenette.Pattern("grid.png");
 		var image = new Trenette.Pattern("grid.png");
 		image.position.set(-300, -400);
 		image.position.set(-300, -400);
 		image.layer = 5;
 		image.layer = 5;
 		image.draggable = true;
 		image.draggable = true;
 		group.add(image);
 		group.add(image);
-		Trenette.Helpers.boxResizeTool(image);
 
 
+		// Image object
 		var background = new Trenette.Image("hexagon.jpg");
 		var background = new Trenette.Image("hexagon.jpg");
 		background.position.set(-300, -400);
 		background.position.set(-300, -400);
-		Trenette.Helpers.boxResizeTool(background);
 		background.layer = -2;
 		background.layer = -2;
 		background.draggable = true;
 		background.draggable = true;
 		group.add(background);
 		group.add(background);
 
 
+		// Box
 		var boxA = new Trenette.Box();
 		var boxA = new Trenette.Box();
-		Trenette.Helpers.boxResizeTool(boxA);
 		boxA.draggable = true;
 		boxA.draggable = true;
+		boxA.onButtonDown = function(object)
+		{
+			boxA.strokeStyle = "#0000FF";
+			selected = boxA;
+		};
 		group.add(boxA);
 		group.add(boxA);
+		Trenette.Helpers.boxResizeTool(boxA);
 
 
+		// DOM
 		var div = new Trenette.DOM(division);
 		var div = new Trenette.DOM(division);
 		div.size.set(100, 50);
 		div.size.set(100, 50);
 		div.origin.set(50, 25);
 		div.origin.set(50, 25);
+		boxA.add(div);
+
 		var text = document.createElement("div");
 		var text = document.createElement("div");
 		text.style.fontFamily = "Arial";
 		text.style.fontFamily = "Arial";
 		text.style.textAlign = "center";
 		text.style.textAlign = "center";
 		text.innerHTML = "DOM text!";
 		text.innerHTML = "DOM text!";
 		div.element.appendChild(text);
 		div.element.appendChild(text);
-		boxA.add(div);
 
 
+		// Box
 		var boxB = new Trenette.Box();
 		var boxB = new Trenette.Box();
 		boxB.position.set(100, 100);
 		boxB.position.set(100, 100);
 		boxB.draggable = true;
 		boxB.draggable = true;
 		group.add(boxB);
 		group.add(boxB);
 		
 		
+		// DOM
 		var div = new Trenette.DOM(division);
 		var div = new Trenette.DOM(division);
 		div.size.set(100, 50);
 		div.size.set(100, 50);
 		div.origin.set(50, 25);
 		div.origin.set(50, 25);
+		boxB.add(div);
+
 		var text = document.createElement("div");
 		var text = document.createElement("div");
 		text.style.fontFamily = "Arial";
 		text.style.fontFamily = "Arial";
 		text.style.textAlign = "center";
 		text.style.textAlign = "center";
 		text.innerHTML = "Double click to add box!";
 		text.innerHTML = "Double click to add box!";
 		div.element.appendChild(text);
 		div.element.appendChild(text);
-		boxB.add(div);
 
 
+		// Circle
 		var circle = new Trenette.Circle();
 		var circle = new Trenette.Circle();
 		circle.radius = 60;
 		circle.radius = 60;
 		circle.position.set(300, 0);
 		circle.position.set(300, 0);
@@ -131,26 +148,38 @@
 		text.text = "Canvas Text";
 		text.text = "Canvas Text";
 		circle.add(text);
 		circle.add(text);
 
 
+		// Line (connection)
 		var line = new Trenette.Line();
 		var line = new Trenette.Line();
 		line.from = boxA.position;
 		line.from = boxA.position;
 		line.to = boxB.position;
 		line.to = boxB.position;
 		line.layer = -1;
 		line.layer = -1;
+		line.dashPattern = [];
 		group.add(line);
 		group.add(line);
 
 
+		// Line (connection)
 		var line = new Trenette.Line();
 		var line = new Trenette.Line();
 		line.from = boxA.position;
 		line.from = boxA.position;
 		line.to = circle.position;
 		line.to = circle.position;
 		line.layer = -1;
 		line.layer = -1;
+		line.dashPattern = [];
+		line.lineWidth = 2;
 		group.add(line);
 		group.add(line);
 
 
+		// Line (connection)
 		var line = new Trenette.Line();
 		var line = new Trenette.Line();
 		line.from = boxB.position;
 		line.from = boxB.position;
 		line.to = circle.position;
 		line.to = circle.position;
 		line.layer = -1;
 		line.layer = -1;
 		group.add(line);
 		group.add(line);
 
 
+		// Boxes group
+		var boxes = new Trenette.Object2D();
+		group.add(boxes);
+
+		// Viewport
 		var viewport = new Trenette.Viewport();
 		var viewport = new Trenette.Viewport();
 
 
+		// Renderer
 		var renderer = new Trenette.Renderer(canvas);
 		var renderer = new Trenette.Renderer(canvas);
 		renderer.createRenderLoop(group, viewport);
 		renderer.createRenderLoop(group, viewport);
 	</script>
 	</script>

BIN
examples/graph.png


+ 3 - 2
examples/stress.html

@@ -82,12 +82,13 @@
 			var div = new Trenette.DOM(division);
 			var div = new Trenette.DOM(division);
 			div.size.set(100, 50);
 			div.size.set(100, 50);
 			div.origin.set(50, 25);
 			div.origin.set(50, 25);
+			box.add(div);
+
 			var text = document.createElement("div");
 			var text = document.createElement("div");
 			text.style.fontFamily = "Arial";
 			text.style.fontFamily = "Arial";
 			text.style.textAlign = "center";
 			text.style.textAlign = "center";
 			text.innerHTML = "DOM text!";
 			text.innerHTML = "DOM text!";
 			div.element.appendChild(text);
 			div.element.appendChild(text);
-			box.add(div);
 		}
 		}
 
 
 		var viewport = new Trenette.Viewport();
 		var viewport = new Trenette.Viewport();
@@ -102,7 +103,7 @@
 			time = newTime;
 			time = newTime;
 
 
 			perfDelta.innerHTML = delta + "ms";
 			perfDelta.innerHTML = delta + "ms";
-			
+
 			console.log(delta);
 			console.log(delta);
 		});
 		});
 	</script>
 	</script>

+ 49 - 6
source/Object2D.js

@@ -26,6 +26,13 @@ function Object2D()
 	 */
 	 */
 	this.parent = null;
 	this.parent = null;
 
 
+	/**
+	 * Depth level in the object tree, objects with higher depth are drawn on top.
+	 *
+	 * The layer value is considered first. 
+	 */
+	this.level = 0;
+
 	/**
 	/**
 	 * Position of the object.
 	 * Position of the object.
 	 */
 	 */
@@ -156,6 +163,13 @@ Object2D.prototype.traverse = function(callback)
 Object2D.prototype.add = function(object)
 Object2D.prototype.add = function(object)
 {
 {
 	object.parent = this;
 	object.parent = this;
+	object.level = this.level + 1;
+
+	if(object.õnAdd !== null)
+	{
+		object.õnAdd(object, this);
+	}
+
 	this.children.push(object);
 	this.children.push(object);
 };
 };
 
 
@@ -167,9 +181,18 @@ Object2D.prototype.add = function(object)
 Object2D.prototype.remove = function(object)
 Object2D.prototype.remove = function(object)
 {
 {
 	var index = this.children.indexOf(object);
 	var index = this.children.indexOf(object);
+	
 	if(index !== -1)
 	if(index !== -1)
 	{
 	{
-		this.children[index].parent = null;
+		var object = this.children[index];
+		object.parent = null;
+		object.level = 0;
+
+		if(object.onRemove !== null)
+		{
+			object.onRemove(object, this);
+		}
+
 		this.children.splice(index, 1)
 		this.children.splice(index, 1)
 	}
 	}
 };
 };
@@ -228,38 +251,54 @@ Object2D.prototype.transform = function(context, viewport)
  */
  */
 Object2D.prototype.draw = function(context, viewport, canvas){};
 Object2D.prototype.draw = function(context, viewport, canvas){};
 
 
+/**
+ * Method called when the object its added to a parent.
+ *
+ * Receives (object, parent) as arguments.
+ */
+Object2D.prototype.onAdd = null;
+
+/**
+ * Method called when the object gets removed from its parent
+ *
+ * Receives (object, parent) as arguments.
+ */
+Object2D.prototype.onRemove = null;
+
 /**
 /**
  * Callback method called every time before the object is draw into the canvas.
  * Callback method called every time before the object is draw into the canvas.
  *
  *
  * Can be used to run preparation code, move the object, etc.
  * Can be used to run preparation code, move the object, etc.
+ *
+ * Receives (object) as argument.
  */
  */
 Object2D.prototype.onUpdate = null;
 Object2D.prototype.onUpdate = null;
 
 
 /**
 /**
  * Callback method called when the pointer enters the object.
  * Callback method called when the pointer enters the object.
  *
  *
- * Receives (pointer, viewport) as arguments.
+ * Receives (object, pointer, viewport) as arguments.
  */
  */
 Object2D.prototype.onPointerEnter = null;
 Object2D.prototype.onPointerEnter = null;
 
 
 /**
 /**
  * Callback method called when the was inside of the object and leaves the object.
  * Callback method called when the was inside of the object and leaves the object.
  *
  *
- * Receives (pointer, viewport) as arguments.
+ * Receives (object, pointer, viewport) as arguments.
  */
  */
 Object2D.prototype.onPointerLeave = null;
 Object2D.prototype.onPointerLeave = null;
 
 
 /**
 /**
  * Callback method while the pointer is over (inside) of the object.
  * Callback method while the pointer is over (inside) of the object.
  *
  *
- * Receives (pointer, viewport) as arguments.
+ * Receives (object, pointer, viewport) as arguments.
  */
  */
 Object2D.prototype.onPointerOver = null;
 Object2D.prototype.onPointerOver = null;
 
 
 /**
 /**
  * Callback method while the object is being dragged across the screen.
  * Callback method while the object is being dragged across the screen.
  *
  *
- * Receives (pointer, viewport, delta) as arguments. Delta is the movement of the pointer already translated into local object coordinates.
+ * Receives (object, pointer, viewport, delta) as arguments. Delta is the movement of the pointer already translated into local object coordinates.
  */
  */
 Object2D.prototype.onPointerDrag = function(pointer, viewport, delta)
 Object2D.prototype.onPointerDrag = function(pointer, viewport, delta)
 {
 {
@@ -269,17 +308,21 @@ Object2D.prototype.onPointerDrag = function(pointer, viewport, delta)
 /**
 /**
  * Callback method called while the pointer button is pressed.
  * Callback method called while the pointer button is pressed.
  *
  *
- * Receives (pointer, viewport) as arguments.
+ * Receives (object, pointer, viewport) as arguments.
  */
  */
 Object2D.prototype.onButtonPressed = null;
 Object2D.prototype.onButtonPressed = null;
 
 
 /**
 /**
  * Callback method called when the pointer button is pressed down (single time).
  * Callback method called when the pointer button is pressed down (single time).
+ *
+ * Receives (object, pointer, viewport) as arguments.
  */
  */
 Object2D.prototype.onButtonDown = null;
 Object2D.prototype.onButtonDown = null;
 
 
 /**
 /**
  * Callback method called when the pointer button is released (single time).
  * Callback method called when the pointer button is released (single time).
+ *
+ * Receives (object, pointer, viewport) as arguments.
  */
  */
 Object2D.prototype.onButtonUp = null;
 Object2D.prototype.onButtonUp = null;
 
 

+ 15 - 3
source/Renderer.js

@@ -9,8 +9,16 @@ import {Pointer} from "./input/Pointer.js";
  *
  *
  * @class
  * @class
  */
  */
-function Renderer(canvas)
+function Renderer(canvas, options)
 {
 {
+	if(options === undefined)
+	{
+		options = 
+		{
+			alpha: true
+		};
+	}
+
 	/**
 	/**
 	 * Canvas DOM element, has to be managed by the user.
 	 * Canvas DOM element, has to be managed by the user.
 	 */
 	 */
@@ -19,7 +27,7 @@ function Renderer(canvas)
 	/**
 	/**
 	 * Canvas 2D rendering context used to draw content.
 	 * Canvas 2D rendering context used to draw content.
 	 */
 	 */
-	this.context = canvas.getContext("2d");
+	this.context = canvas.getContext("2d", {alpha: options.alpha});
 	this.context.imageSmoothingEnabled = true;
 	this.context.imageSmoothingEnabled = true;
 	this.context.globalCompositeOperation = "source-over";
 	this.context.globalCompositeOperation = "source-over";
 
 
@@ -89,6 +97,11 @@ Renderer.prototype.update = function(object, viewport)
 	// Sort objects by layer
 	// Sort objects by layer
 	objects.sort(function(a, b)
 	objects.sort(function(a, b)
 	{
 	{
+		if(b.layer === a.layer)
+		{
+			return b.level - a.level;
+		}
+		
 		return b.layer - a.layer;
 		return b.layer - a.layer;
 	});
 	});
 
 
@@ -216,7 +229,6 @@ Renderer.prototype.update = function(object, viewport)
 	{
 	{
 		child.updateMatrix();
 		child.updateMatrix();
 	});
 	});
-	
 
 
 	this.context.setTransform(1, 0, 0, 1, 0, 0);
 	this.context.setTransform(1, 0, 0, 1, 0, 0);
 	
 	

+ 2 - 3
source/Trenette.js

@@ -5,9 +5,6 @@ export {Object2D} from "./Object2D.js";
 export {Renderer} from "./Renderer.js";
 export {Renderer} from "./Renderer.js";
 export {Viewport} from "./Viewport.js";
 export {Viewport} from "./Viewport.js";
 
 
-export {Mask} from "./mask/Mask.js";
-export {BoxMask} from "./mask/BoxMask.js";
-
 export {Key} from "./input/Key.js";
 export {Key} from "./input/Key.js";
 export {Pointer} from "./input/Pointer.js";
 export {Pointer} from "./input/Pointer.js";
 
 
@@ -16,6 +13,8 @@ export {Matrix} from "./math/Matrix.js";
 export {UUID} from "./math/UUID.js";
 export {UUID} from "./math/UUID.js";
 export {Vector2} from "./math/Vector2.js";
 export {Vector2} from "./math/Vector2.js";
 
 
+export {Mask} from "./objects/mask/Mask.js";
+export {BoxMask} from "./objects/mask/BoxMask.js";
 export {Box} from "./objects/Box.js";
 export {Box} from "./objects/Box.js";
 export {Circle} from "./objects/Circle.js";
 export {Circle} from "./objects/Circle.js";
 export {Line} from "./objects/Line.js";
 export {Line} from "./objects/Line.js";

+ 14 - 1
source/Viewport.js

@@ -53,6 +53,13 @@ function Viewport()
 	 * For some application its easier to focus the target if the viewport moves to the pointer location while scalling.
 	 * For some application its easier to focus the target if the viewport moves to the pointer location while scalling.
 	 */
 	 */
 	this.moveOnScale = true;
 	this.moveOnScale = true;
+
+	/**
+	 * Value of the initial point of rotation if the viewport is being rotated.
+	 *
+	 * Is set to null when the viewport is not being rotated.
+	 */
+	this.rotationPoint = null;
 }
 }
 
 
 /**
 /**
@@ -75,11 +82,17 @@ Viewport.prototype.updateControls = function(pointer)
 		}
 		}
 	}
 	}
 
 
-	if(pointer.buttonPressed(Pointer.RIGHT))
+	if(pointer.buttonPressed(Pointer.RIGHT) && pointer.buttonPressed(Pointer.LEFT))
+	{
+		this.rotation += pointer.delta.angle() * 1e-2;
+	}
+	else if(pointer.buttonPressed(Pointer.RIGHT))
 	{
 	{
 		this.position.x += pointer.delta.x;
 		this.position.x += pointer.delta.x;
 		this.position.y += pointer.delta.y;
 		this.position.y += pointer.delta.y;
 	}
 	}
+
+
 };
 };
 
 
 /**
 /**

+ 2 - 5
source/objects/Circle.js

@@ -47,17 +47,14 @@ Circle.prototype.onPointerLeave = function(pointer, viewport)
 
 
 Circle.prototype.draw = function(context, viewport, canvas)
 Circle.prototype.draw = function(context, viewport, canvas)
 {
 {
-	context.fillStyle = this.fillStyle;
-
 	context.beginPath();
 	context.beginPath();
 	context.arc(0, 0, this.radius, 0, 2 * Math.PI);
 	context.arc(0, 0, this.radius, 0, 2 * Math.PI);
+	
+	context.fillStyle = this.fillStyle;
 	context.fill();
 	context.fill();
 
 
 	context.lineWidth = 1;
 	context.lineWidth = 1;
 	context.strokeStyle = this.strokeStyle;
 	context.strokeStyle = this.strokeStyle;
-
-	context.beginPath();
-	context.arc(0, 0, this.radius, 0, 2 * Math.PI);
 	context.stroke();
 	context.stroke();
 };
 };
 
 

+ 17 - 3
source/objects/DOM.js

@@ -11,13 +11,18 @@ import {Vector2} from "../math/Vector2.js";
  * Use the normal DOM events for interaction.
  * Use the normal DOM events for interaction.
  *
  *
  * @class
  * @class
- * @param parent Parent DOM element that contains the drawing canvas.
+ * @param parentDOM Parent DOM element that contains the drawing canvas.
  * @param type Type of the DOM element (e.g. "div", "p", ...)
  * @param type Type of the DOM element (e.g. "div", "p", ...)
  */
  */
-function DOM(parent, type)
+function DOM(parentDOM, type)
 {
 {
 	Object2D.call(this);
 	Object2D.call(this);
 
 
+	/**
+	 * Parent element that contains this DOM div.
+	 */
+	this.parentDOM = parentDOM;
+
 	/**
 	/**
 	 * DOM element contained by this object.
 	 * DOM element contained by this object.
 	 *
 	 *
@@ -31,7 +36,6 @@ function DOM(parent, type)
 	this.element.style.transformOrigin = "0px 0px";
 	this.element.style.transformOrigin = "0px 0px";
 	this.element.style.overflow = "auto";
 	this.element.style.overflow = "auto";
 	this.element.style.pointerEvents = "none";
 	this.element.style.pointerEvents = "none";
-	parent.appendChild(this.element);
 	
 	
 	/**
 	/**
 	 * Size of the DOM element (in world coordinates).
 	 * Size of the DOM element (in world coordinates).
@@ -41,6 +45,16 @@ function DOM(parent, type)
 
 
 DOM.prototype = Object.create(Object2D.prototype);
 DOM.prototype = Object.create(Object2D.prototype);
 
 
+DOM.prototype.onAdd = function()
+{
+	this.parentDOM.appendChild(this.element);
+};
+
+DOM.prototype.onRemove = function()
+{
+	this.parentDOM.removeChild(this.element);
+};
+
 DOM.prototype.transform = function(context, viewport, canvas)
 DOM.prototype.transform = function(context, viewport, canvas)
 {
 {
 	// CSS trasnformation matrix
 	// CSS trasnformation matrix

+ 0 - 2
source/objects/Pattern.js

@@ -70,8 +70,6 @@ Pattern.prototype.draw = function(context, viewport, canvas)
 	if(this.image.src.length > 0)
 	if(this.image.src.length > 0)
 	{
 	{
 		var pattern = context.createPattern(this.image, this.repetition);
 		var pattern = context.createPattern(this.image, this.repetition);
-		
-		//pattern.setTransform();
 
 
 		context.fillStyle = pattern;
 		context.fillStyle = pattern;
 		context.fillRect(this.box.min.x, this.box.min.y, width, height);
 		context.fillRect(this.box.min.x, this.box.min.y, width, height);

+ 2 - 2
source/mask/BoxMask.js → source/objects/mask/BoxMask.js

@@ -1,8 +1,8 @@
 "use strict";
 "use strict";
 
 
 import {Mask} from "./Mask.js";
 import {Mask} from "./Mask.js";
-import {Vector2} from "../math/Vector2.js";
-import {Box2} from "../math/Box2.js";
+import {Vector2} from "../../math/Vector2.js";
+import {Box2} from "../../math/Box2.js";
 
 
 /**
 /**
  * Box mask can be used to clear a box mask region.
  * Box mask can be used to clear a box mask region.

+ 3 - 3
source/mask/Mask.js → source/objects/mask/Mask.js

@@ -1,8 +1,8 @@
 "use strict";
 "use strict";
 
 
-import {Object2D} from "../Object2D.js";
-import {Vector2} from "../math/Vector2.js";
-import {Box2} from "../math/Box2.js";
+import {Object2D} from "../../Object2D.js";
+import {Vector2} from "../../math/Vector2.js";
+import {Box2} from "../../math/Box2.js";
 
 
 /**
 /**
  * A mask can be used to set the drawing region.
  * A mask can be used to set the drawing region.

Some files were not shown because too many files changed in this diff