Selaa lähdekoodia

Stencil example

tentone 6 vuotta sitten
vanhempi
commit
12986ea0ae

+ 136 - 35
build/trenette.js

@@ -270,8 +270,11 @@
 		{
 			var angle = Math.atan2(this.y, this.x);
 
-			if(angle < 0) angle += 2 * Math.PI;
-
+			if(angle < 0)
+			{
+				angle += 2 * Math.PI;
+			}
+			
 			return angle;
 		},
 
@@ -421,17 +424,28 @@
 	};
 
 	/**
-	 * Compose this transformation matrix with position scale and rotation.
+	 * Compose this transformation matrix with position scale and rotation and origin point.
 	 */
-	Matrix.prototype.compose = function(px, py, sx, sy, a)
+	Matrix.prototype.compose = function(px, py, sx, sy, ox, oy, a)
 	{
 		this.m = [1, 0, 0, 1, px, py];
 
-		var c = Math.cos(a);
-		var s = Math.sin(a);
-		this.multiply(new Matrix([c, s, -s, c, 0, 0]));
+		if(a !== 0)
+		{		
+			var c = Math.cos(a);
+			var s = Math.sin(a);
+			this.multiply(new Matrix([c, s, -s, c, 0, 0]));
+		}
 
-		this.scale(sx, sy);
+		if(ox !== 0 || oy !== 0)
+		{	
+			this.multiply(new Matrix([1, 0, 0, 1, -ox, -oy]));
+		}
+
+		if(sx !== 1 || sy !== 1)
+		{
+			this.scale(sx, sy);
+		}
 	};
 
 	/**
@@ -622,6 +636,11 @@
 		 */
 		this.position = new Vector2(0, 0);
 
+		/**
+		 * Origin of the object used as point of rotation.
+		 */
+		this.origin = new Vector2(0, 0);
+
 		/**
 		 * Scale of the object.
 		 */
@@ -673,7 +692,28 @@
 		 *
 		 * If true the onPointerDrag callback is used to update the state of the object.
 		 */
-		this.draggable = true;
+		this.draggable = false;
+
+		/**
+		 * Flag to indicate wheter this objet ignores the viewport transformation.
+		 */
+		this.ignoreViewport = false;
+
+		/**
+		 * Flag to indicate if the context of canvas should be saved before render.
+		 */
+		this.saveContextState = true;
+
+		/**
+		 * Flag to indicate if the context of canvas should be restored after render.
+		 */
+		this.restoreContextState = true;
+
+		/**
+		 * Flag to indicate if the context of canvas should be restored after render.
+		 */
+		this.restoreContextState = true;
+
 
 		/**
 		 * Flag indicating if the pointer is inside of the element.
@@ -746,7 +786,7 @@
 	{
 		if(this.matrixNeedsUpdate)
 		{
-			this.matrix.compose(this.position.x, this.position.y, this.scale.x, this.scale.y, this.rotation);
+			this.matrix.compose(this.position.x, this.position.y, this.scale.x, this.scale.y, this.origin.x, this.origin.y, this.rotation);
 			this.globalMatrix.copy(this.matrix);
 
 			if(this.parent !== null)
@@ -759,6 +799,16 @@
 		}
 	};
 
+	/**
+	 * Apply the transform to the rendering context.
+	 *
+	 * Can also be used for pre rendering logic.
+	 */
+	Object2D.prototype.transform = function(context, viewport)
+	{
+		this.globalMatrix.tranformContext(context);
+	};
+
 	/**
 	 * Draw the object into the canvas.
 	 *
@@ -766,7 +816,7 @@
 	 *
 	 * @param context Canvas 2d drawing context.
 	 */
-	Object2D.prototype.draw = function(context){};
+	Object2D.prototype.draw = function(context, viewport){};
 
 	/**
 	 * Callback method called every time before the object is draw into the canvas.
@@ -801,7 +851,10 @@
 	 *
 	 * Receives (pointer, viewport, delta) as arguments. Delta is the movement of the pointer already translated into local object coordinates.
 	 */
-	Object2D.prototype.onPointerDrag = null;
+	Object2D.prototype.onPointerDrag = function(pointer, viewport, delta)
+	{
+		this.position.add(delta);
+	};
 
 	/**
 	 * Callback method called while the pointer button is pressed.
@@ -1369,7 +1422,7 @@
 		for(var i = 0; i < objects.length; i++)
 		{
 			var child = objects[i];
-			var childPoint = child.inverseGlobalMatrix.transformPoint(viewportPoint);
+			var childPoint = child.inverseGlobalMatrix.transformPoint(child.ignoreViewport ? point : viewportPoint);
 
 			// Check if the pointer pointer is inside
 			if(child.isInside(childPoint))
@@ -1442,7 +1495,7 @@
 			var child = objects[i];
 
 			if(child.beingDragged)
-			{
+			{	
 				var lastPosition = pointer.position.clone();
 				lastPosition.sub(pointer.delta);
 
@@ -1452,9 +1505,6 @@
 				// Mouse delta in world coordinates
 				positionWorld.sub(lastWorld);
 
-				// Update child position
-				child.position.add(positionWorld);
-
 				if(child.onPointerDrag !== null)
 				{
 					child.onPointerDrag(pointer, viewport, positionWorld);
@@ -1488,11 +1538,24 @@
 
 		// Render into the canvas
 		for(var i = 0; i < objects.length; i++)
-		{
-			context.save();
-			objects[i].globalMatrix.tranformContext(context);
+		{	
+			if(objects[i].saveContextState)
+			{
+				context.save();
+			}
+
+			if(objects[i].ignoreViewport)
+			{
+				context.setTransform(1, 0, 0, 1, 0, 0);
+			}
+
+			objects[i].transform(context, viewport);
 			objects[i].draw(context, viewport);
-			context.restore();
+
+			if(objects[i].restoreContextState)
+			{
+				context.restore();
+			}
 		}
 	};
 
@@ -1580,7 +1643,7 @@
 	{
 		if(this.matrixNeedsUpdate)
 		{
-			this.matrix.compose(this.position.x, this.position.y, this.scale, this.scale, this.rotation);
+			this.matrix.compose(this.position.x, this.position.y, this.scale, this.scale, 0, 0, this.rotation);
 			this.inverseMatrix = this.matrix.getInverse();
 			//this.matrixNeedsUpdate = false;
 		}
@@ -1813,6 +1876,22 @@
 
 	function Helpers(){}
 
+
+	/**
+	 * Create a rotation tool
+	 */
+	Helpers.rotateTool = function(object)
+	{
+		var tool = new Circle();
+		tool.radius = 4;
+		tool.layer = object.layer + 1;
+		tool.onPointerDrag = function(pointer, viewport, delta)
+		{
+			object.rotation += delta.x * 1e-3;
+		};
+		object.add(tool);
+	};
+
 	/**
 	 * Create a box resize helper and attach it to an object to change the size of the object box.
 	 *
@@ -1820,11 +1899,11 @@
 	 *
 	 * This method required to object to have a box property.
 	 */
-	Helpers.createBoxResize = function(object)
+	Helpers.boxResizeTool = function(object)
 	{
 		if(object.box === undefined)
 		{
-			console.warn("trenette.js: Helpers.createBoxResize(), object box property missing.");
+			console.warn("trenette.js: Helpers.boxResizeTool(), object box property missing.");
 			return;
 		}
 
@@ -1838,8 +1917,12 @@
 
 		var topRight = new Circle();
 		topRight.radius = 4;
+		topRight.layer = object.layer + 1;
+		topRight.draggable = true;
 		topRight.onPointerDrag = function(pointer, viewport, delta)
 		{
+			Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
+
 			object.box.min.copy(topRight.position);
 			updateHelpers();
 		};
@@ -1847,8 +1930,12 @@
 
 		var topLeft = new Circle();
 		topLeft.radius = 4;
+		topLeft.layer = object.layer + 1;
+		topLeft.draggable = true;
 		topLeft.onPointerDrag = function(pointer, viewport, delta)
 		{
+			Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
+
 			object.box.max.x = topLeft.position.x;
 			object.box.min.y = topLeft.position.y;
 			updateHelpers();
@@ -1857,8 +1944,12 @@
 
 		var bottomLeft = new Circle();
 		bottomLeft.radius = 4;
+		bottomLeft.layer = object.layer + 1;
+		bottomLeft.draggable = true;
 		bottomLeft.onPointerDrag = function(pointer, viewport, delta)
 		{
+			Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
+
 			object.box.max.copy(bottomLeft.position);
 			updateHelpers();
 		};
@@ -1866,8 +1957,12 @@
 
 		var bottomRight = new Circle();
 		bottomRight.radius = 4;
+		bottomRight.layer = object.layer + 1;
+		bottomRight.draggable = true;
 		bottomRight.onPointerDrag = function(pointer, viewport, delta)
 		{
+			Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
+
 			object.box.min.x = bottomRight.position.x;
 			object.box.max.y = bottomRight.position.y;
 			updateHelpers();
@@ -1880,7 +1975,7 @@
 	/**
 	 * Box object draw a box.
 	 */
-	function Box(resizable)
+	function Box()
 	{
 		Object2D.call(this);
 
@@ -1898,11 +1993,6 @@
 		 * Background color of the box.
 		 */
 		this.fillStyle = "#FFFFFF";
-
-		if(resizable)
-		{
-			Helpers.createBoxResize(this);
-		}
 	}
 
 	Box.prototype = Object.create(Object2D.prototype);
@@ -2053,7 +2143,7 @@
 
 	Image.prototype.draw = function(context)
 	{
-		context.drawImage(this.image, 0, 0);
+		context.drawImage(this.image, 0, 0, this.image.naturalWidth, this.image.naturalHeight, this.box.min.x, this.box.min.y, this.box.max.x - this.box.min.x, this.box.max.y - this.box.min.y);
 	};
 
 	/**
@@ -2078,10 +2168,16 @@
 		this.element.style.position = "absolute";
 		this.element.style.top = "0px";
 		this.element.style.bottom = "0px";
-		this.element.style.width = "100px";
-		this.element.style.height = "100px";
-		this.element.style.backgroundColor = "rgba(0.0, 0.0, 0.0, 0.8)";
 		this.element.style.transformOrigin = "0px 0px";
+		this.element.style.overflow = "auto";
+		
+		/**
+		 * Size of the DOM element (in world coordinates).
+		 */
+		this.size = new Vector2(100, 100);
+
+		this.origin.set(50, 50);
+
 		parent.appendChild(this.element);
 	}
 
@@ -2089,10 +2185,14 @@
 
 	DOM.prototype.draw = function(context, viewport)
 	{
+		// CSS trasnformation matrix
 		var projection = viewport.matrix.clone();
 		projection.multiply(this.globalMatrix);
-
 		this.element.style.transform = projection.cssTransform();
+
+		// Size of the element
+		this.element.style.width = this.size.x + "px";
+		this.element.style.height = this.size.y + "100px";
 	};
 
 	exports.Box = Box;
@@ -2100,6 +2200,7 @@
 	exports.Circle = Circle;
 	exports.DOM = DOM;
 	exports.EventManager = EventManager;
+	exports.Helpers = Helpers;
 	exports.Image = Image;
 	exports.Key = Key;
 	exports.Line = Line;

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
build/trenette.min.js


+ 136 - 36
build/trenette.module.js

@@ -264,8 +264,11 @@ Object.assign(Vector2.prototype,
 	{
 		var angle = Math.atan2(this.y, this.x);
 
-		if(angle < 0) angle += 2 * Math.PI;
-
+		if(angle < 0)
+		{
+			angle += 2 * Math.PI;
+		}
+		
 		return angle;
 	},
 
@@ -415,17 +418,28 @@ Matrix.prototype.premultiply = function(mat)
 };
 
 /**
- * Compose this transformation matrix with position scale and rotation.
+ * Compose this transformation matrix with position scale and rotation and origin point.
  */
-Matrix.prototype.compose = function(px, py, sx, sy, a)
+Matrix.prototype.compose = function(px, py, sx, sy, ox, oy, a)
 {
 	this.m = [1, 0, 0, 1, px, py];
 
-	var c = Math.cos(a);
-	var s = Math.sin(a);
-	this.multiply(new Matrix([c, s, -s, c, 0, 0]));
+	if(a !== 0)
+	{		
+		var c = Math.cos(a);
+		var s = Math.sin(a);
+		this.multiply(new Matrix([c, s, -s, c, 0, 0]));
+	}
 
-	this.scale(sx, sy);
+	if(ox !== 0 || oy !== 0)
+	{	
+		this.multiply(new Matrix([1, 0, 0, 1, -ox, -oy]));
+	}
+
+	if(sx !== 1 || sy !== 1)
+	{
+		this.scale(sx, sy);
+	}
 };
 
 /**
@@ -616,6 +630,11 @@ function Object2D()
 	 */
 	this.position = new Vector2(0, 0);
 
+	/**
+	 * Origin of the object used as point of rotation.
+	 */
+	this.origin = new Vector2(0, 0);
+
 	/**
 	 * Scale of the object.
 	 */
@@ -667,7 +686,28 @@ function Object2D()
 	 *
 	 * If true the onPointerDrag callback is used to update the state of the object.
 	 */
-	this.draggable = true;
+	this.draggable = false;
+
+	/**
+	 * Flag to indicate wheter this objet ignores the viewport transformation.
+	 */
+	this.ignoreViewport = false;
+
+	/**
+	 * Flag to indicate if the context of canvas should be saved before render.
+	 */
+	this.saveContextState = true;
+
+	/**
+	 * Flag to indicate if the context of canvas should be restored after render.
+	 */
+	this.restoreContextState = true;
+
+	/**
+	 * Flag to indicate if the context of canvas should be restored after render.
+	 */
+	this.restoreContextState = true;
+
 
 	/**
 	 * Flag indicating if the pointer is inside of the element.
@@ -740,7 +780,7 @@ Object2D.prototype.updateMatrix = function(context)
 {
 	if(this.matrixNeedsUpdate)
 	{
-		this.matrix.compose(this.position.x, this.position.y, this.scale.x, this.scale.y, this.rotation);
+		this.matrix.compose(this.position.x, this.position.y, this.scale.x, this.scale.y, this.origin.x, this.origin.y, this.rotation);
 		this.globalMatrix.copy(this.matrix);
 
 		if(this.parent !== null)
@@ -753,6 +793,16 @@ Object2D.prototype.updateMatrix = function(context)
 	}
 };
 
+/**
+ * Apply the transform to the rendering context.
+ *
+ * Can also be used for pre rendering logic.
+ */
+Object2D.prototype.transform = function(context, viewport)
+{
+	this.globalMatrix.tranformContext(context);
+};
+
 /**
  * Draw the object into the canvas.
  *
@@ -760,7 +810,7 @@ Object2D.prototype.updateMatrix = function(context)
  *
  * @param context Canvas 2d drawing context.
  */
-Object2D.prototype.draw = function(context){};
+Object2D.prototype.draw = function(context, viewport){};
 
 /**
  * Callback method called every time before the object is draw into the canvas.
@@ -795,7 +845,10 @@ Object2D.prototype.onPointerOver = null;
  *
  * Receives (pointer, viewport, delta) as arguments. Delta is the movement of the pointer already translated into local object coordinates.
  */
-Object2D.prototype.onPointerDrag = null;
+Object2D.prototype.onPointerDrag = function(pointer, viewport, delta)
+{
+	this.position.add(delta);
+};
 
 /**
  * Callback method called while the pointer button is pressed.
@@ -1363,7 +1416,7 @@ Renderer.prototype.update = function(object, viewport)
 	for(var i = 0; i < objects.length; i++)
 	{
 		var child = objects[i];
-		var childPoint = child.inverseGlobalMatrix.transformPoint(viewportPoint);
+		var childPoint = child.inverseGlobalMatrix.transformPoint(child.ignoreViewport ? point : viewportPoint);
 
 		// Check if the pointer pointer is inside
 		if(child.isInside(childPoint))
@@ -1436,7 +1489,7 @@ Renderer.prototype.update = function(object, viewport)
 		var child = objects[i];
 
 		if(child.beingDragged)
-		{
+		{	
 			var lastPosition = pointer.position.clone();
 			lastPosition.sub(pointer.delta);
 
@@ -1446,9 +1499,6 @@ Renderer.prototype.update = function(object, viewport)
 			// Mouse delta in world coordinates
 			positionWorld.sub(lastWorld);
 
-			// Update child position
-			child.position.add(positionWorld);
-
 			if(child.onPointerDrag !== null)
 			{
 				child.onPointerDrag(pointer, viewport, positionWorld);
@@ -1482,11 +1532,24 @@ Renderer.prototype.update = function(object, viewport)
 
 	// Render into the canvas
 	for(var i = 0; i < objects.length; i++)
-	{
-		context.save();
-		objects[i].globalMatrix.tranformContext(context);
+	{	
+		if(objects[i].saveContextState)
+		{
+			context.save();
+		}
+
+		if(objects[i].ignoreViewport)
+		{
+			context.setTransform(1, 0, 0, 1, 0, 0);
+		}
+
+		objects[i].transform(context, viewport);
 		objects[i].draw(context, viewport);
-		context.restore();
+
+		if(objects[i].restoreContextState)
+		{
+			context.restore();
+		}
 	}
 };
 
@@ -1574,7 +1637,7 @@ Viewport.prototype.updateMatrix = function()
 {
 	if(this.matrixNeedsUpdate)
 	{
-		this.matrix.compose(this.position.x, this.position.y, this.scale, this.scale, this.rotation);
+		this.matrix.compose(this.position.x, this.position.y, this.scale, this.scale, 0, 0, this.rotation);
 		this.inverseMatrix = this.matrix.getInverse();
 		//this.matrixNeedsUpdate = false;
 	}
@@ -1807,6 +1870,22 @@ Circle.prototype.draw = function(context)
 
 function Helpers(){}
 
+
+/**
+ * Create a rotation tool
+ */
+Helpers.rotateTool = function(object)
+{
+	var tool = new Circle();
+	tool.radius = 4;
+	tool.layer = object.layer + 1;
+	tool.onPointerDrag = function(pointer, viewport, delta)
+	{
+		object.rotation += delta.x * 1e-3;
+	};
+	object.add(tool);
+};
+
 /**
  * Create a box resize helper and attach it to an object to change the size of the object box.
  *
@@ -1814,11 +1893,11 @@ function Helpers(){}
  *
  * This method required to object to have a box property.
  */
-Helpers.createBoxResize = function(object)
+Helpers.boxResizeTool = function(object)
 {
 	if(object.box === undefined)
 	{
-		console.warn("trenette.js: Helpers.createBoxResize(), object box property missing.");
+		console.warn("trenette.js: Helpers.boxResizeTool(), object box property missing.");
 		return;
 	}
 
@@ -1832,8 +1911,12 @@ Helpers.createBoxResize = function(object)
 
 	var topRight = new Circle();
 	topRight.radius = 4;
+	topRight.layer = object.layer + 1;
+	topRight.draggable = true;
 	topRight.onPointerDrag = function(pointer, viewport, delta)
 	{
+		Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
+
 		object.box.min.copy(topRight.position);
 		updateHelpers();
 	};
@@ -1841,8 +1924,12 @@ Helpers.createBoxResize = function(object)
 
 	var topLeft = new Circle();
 	topLeft.radius = 4;
+	topLeft.layer = object.layer + 1;
+	topLeft.draggable = true;
 	topLeft.onPointerDrag = function(pointer, viewport, delta)
 	{
+		Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
+
 		object.box.max.x = topLeft.position.x;
 		object.box.min.y = topLeft.position.y;
 		updateHelpers();
@@ -1851,8 +1938,12 @@ Helpers.createBoxResize = function(object)
 
 	var bottomLeft = new Circle();
 	bottomLeft.radius = 4;
+	bottomLeft.layer = object.layer + 1;
+	bottomLeft.draggable = true;
 	bottomLeft.onPointerDrag = function(pointer, viewport, delta)
 	{
+		Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
+
 		object.box.max.copy(bottomLeft.position);
 		updateHelpers();
 	};
@@ -1860,8 +1951,12 @@ Helpers.createBoxResize = function(object)
 
 	var bottomRight = new Circle();
 	bottomRight.radius = 4;
+	bottomRight.layer = object.layer + 1;
+	bottomRight.draggable = true;
 	bottomRight.onPointerDrag = function(pointer, viewport, delta)
 	{
+		Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
+
 		object.box.min.x = bottomRight.position.x;
 		object.box.max.y = bottomRight.position.y;
 		updateHelpers();
@@ -1874,7 +1969,7 @@ Helpers.createBoxResize = function(object)
 /**
  * Box object draw a box.
  */
-function Box(resizable)
+function Box()
 {
 	Object2D.call(this);
 
@@ -1892,11 +1987,6 @@ function Box(resizable)
 	 * Background color of the box.
 	 */
 	this.fillStyle = "#FFFFFF";
-
-	if(resizable)
-	{
-		Helpers.createBoxResize(this);
-	}
 }
 
 Box.prototype = Object.create(Object2D.prototype);
@@ -2047,7 +2137,7 @@ Image.prototype.isInside = function(point)
 
 Image.prototype.draw = function(context)
 {
-	context.drawImage(this.image, 0, 0);
+	context.drawImage(this.image, 0, 0, this.image.naturalWidth, this.image.naturalHeight, this.box.min.x, this.box.min.y, this.box.max.x - this.box.min.x, this.box.max.y - this.box.min.y);
 };
 
 /**
@@ -2072,10 +2162,16 @@ function DOM(parent, type)
 	this.element.style.position = "absolute";
 	this.element.style.top = "0px";
 	this.element.style.bottom = "0px";
-	this.element.style.width = "100px";
-	this.element.style.height = "100px";
-	this.element.style.backgroundColor = "rgba(0.0, 0.0, 0.0, 0.8)";
 	this.element.style.transformOrigin = "0px 0px";
+	this.element.style.overflow = "auto";
+	
+	/**
+	 * Size of the DOM element (in world coordinates).
+	 */
+	this.size = new Vector2(100, 100);
+
+	this.origin.set(50, 50);
+
 	parent.appendChild(this.element);
 }
 
@@ -2083,10 +2179,14 @@ DOM.prototype = Object.create(Object2D.prototype);
 
 DOM.prototype.draw = function(context, viewport)
 {
+	// CSS trasnformation matrix
 	var projection = viewport.matrix.clone();
 	projection.multiply(this.globalMatrix);
-
 	this.element.style.transform = projection.cssTransform();
+
+	// Size of the element
+	this.element.style.width = this.size.x + "px";
+	this.element.style.height = this.size.y + "100px";
 };
 
-export { Box, Box2, Circle, DOM, EventManager, Image, Key, Line, Matrix, Object2D, Pointer, Renderer, Text, UUID, Vector2, Viewport };
+export { Box, Box2, Circle, DOM, EventManager, Helpers, Image, Key, Line, Matrix, Object2D, Pointer, Renderer, Text, UUID, Vector2, Viewport };

+ 62 - 22
examples/diagram.html

@@ -2,13 +2,15 @@
 <html>
 <head>
 	<meta charset="utf-8">
-	<title>diagram.js</title>
+	<title>Example</title>
 </head>
 <body>
 	<script type="text/javascript" src="../build/trenette.js"></script>
 
 	<script type="text/javascript">
 
+		window.runLoop = true;
+
 		var division = document.createElement("div");
 		division.style.position = "absolute";
 		division.style.width = "100%";
@@ -43,50 +45,92 @@
 		canvas.ondblclick = function(event)
 		{
 			var p = new Trenette.Vector2(event.clientX, event.clientY);
-			console.log(p);
-
 			p = viewport.inverseMatrix.transformPoint(p);
 
-			var box = new Trenette.Box(true);
-			box.position.copy(p);
-			group.add(box);
-
+			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);
+			}
+
+			if(group.children.length > 2)
+			{
+				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;
+				group.add(line);
+
+				console.log(line);
+			}
 		};
 
 		// Create the diagram
 		var group = new Trenette.Object2D();
 		var image = new Trenette.Image("grid.png");
 		image.position.set(-300, -400);
-		image.scale.set(1, 1);
 		image.layer = 5;
+		image.draggable = true;
 		group.add(image);
+		Trenette.Helpers.boxResizeTool(image);
 
 		var background = new Trenette.Image("hexagon.jpg");
 		background.position.set(-300, -400);
-		background.scale.set(1, 1);
+		Trenette.Helpers.boxResizeTool(background);
 		background.layer = -2;
+		background.draggable = true;
 		group.add(background);
 
-		var boxA = new Trenette.Box(true);
+		var boxA = new Trenette.Box();
+		Trenette.Helpers.boxResizeTool(boxA);
+		boxA.draggable = true;
 		group.add(boxA);
 
 		var div = new Trenette.DOM(division);
+		div.size.set(100, 50);
+		div.origin.set(50, 25);
+		var text = document.createElement("div");
+		text.style.fontFamily = "Arial";
+		text.style.textAlign = "center";
+		text.innerHTML = "DOM text!";
+		div.element.appendChild(text);
 		boxA.add(div);
 
-		var text = new Trenette.Text();
-		text.text = "diagram.js";
-		boxA.add(text);
-
 		var boxB = new Trenette.Box();
 		boxB.position.set(100, 100);
+		boxB.draggable = true;
 		group.add(boxB);
 		
+		var div = new Trenette.DOM(division);
+		div.size.set(100, 50);
+		div.origin.set(50, 25);
+		var text = document.createElement("div");
+		text.style.fontFamily = "Arial";
+		text.style.textAlign = "center";
+		text.innerHTML = "Double click to add box!";
+		div.element.appendChild(text);
+		boxB.add(div);
+
 		var circle = new Trenette.Circle();
-		circle.radius = 40;
+		circle.radius = 60;
 		circle.position.set(300, 0);
-		circle.scale.set(2, 1);
+		circle.draggable = true;
 		group.add(circle);
 
+		var text = new Trenette.Text();
+		text.text = "Canvas Text";
+		circle.add(text);
+
 		var line = new Trenette.Line();
 		line.from = boxA.position;
 		line.to = boxB.position;
@@ -111,13 +155,9 @@
 
 		// Update and render the diagram
 		function loop()
-		{	
-			//group.rotation += 0.001;
-			boxA.rotation += 0.01;
-			image.rotation += 0.01;
-
+		{			
 			renderer.update(group, viewport);
-			
+
 			requestAnimationFrame(loop);
 		}
 

+ 133 - 0
examples/stencil.html

@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta charset="utf-8">
+	<title>Example Stencil</title>
+</head>
+<body>
+	<script type="text/javascript" src="../build/trenette.js"></script>
+
+	<script type="text/javascript">
+
+		window.runLoop = true;
+
+		var division = document.createElement("div");
+		division.style.position = "absolute";
+		division.style.width = "100%";
+		division.style.height = "100%";
+		division.style.top = "0px";
+		division.style.left = "0px";
+		division.style.overflow = "hidden";
+		document.body.appendChild(division);
+
+		// Setup the display canvas
+		var canvas = document.createElement("canvas");
+		canvas.style.position = "absolute";
+		canvas.style.width = "100%";
+		canvas.style.height = "100%";
+		canvas.style.top = "0px";
+		canvas.style.left = "0px";
+		canvas.width = window.innerWidth;
+		canvas.height = window.innerHeight;
+		canvas.oncontextmenu = function(event)
+		{
+			event.preventDefault();
+			return false;
+		};
+		division.appendChild(canvas);
+
+		window.onresize = function()
+		{
+			canvas.width = window.innerWidth;
+			canvas.height = window.innerHeight;
+		};
+
+		var noBox = true;
+
+		canvas.ondblclick = function(event)
+		{
+			var box = new Trenette.Box();
+			box.draggable = true;
+			box.layer = 9;
+			box.draw = function(context)
+			{
+				var width = this.box.max.x - this.box.min.x;
+
+				context.beginPath();
+				context.rect(this.box.min.x - 1e4, -5e3, 1e4, 1e4);
+				context.rect(this.box.max.x, -5e3, 1e4, 1e4);
+				context.rect(this.box.min.x, this.box.min.y - 1e4, width, 1e4);
+				context.rect(this.box.min.x, this.box.max.y, width, 1e4);
+				context.clip();
+			};
+			box.saveContextState = noBox;
+			box.restoreContextState = false;
+			group.add(box);
+
+			if(!noBox)
+			{
+				box.transform = function(context, viewport)
+				{
+					viewport.matrix.setContextTransform(context);
+					this.globalMatrix.tranformContext(context);
+				};
+			}
+
+			if(event !== undefined)
+			{
+				var p = new Trenette.Vector2(event.clientX, event.clientY);
+				p = viewport.inverseMatrix.transformPoint(p);
+				box.position.copy(p);
+			}
+
+			Trenette.Helpers.boxResizeTool(box);
+
+			noBox = false;
+		};
+
+
+		// Create the diagram
+		var group = new Trenette.Object2D();
+
+		// Image
+		var background = new Trenette.Image("hexagon.jpg");
+		background.position.set(-300, -400);
+		background.layer = -20;
+		group.add(background);
+
+		// Black layer
+		var blackLayer = new Trenette.Object2D();
+		blackLayer.ignoreViewport = true;
+		blackLayer.layer = 10;
+		blackLayer.draw = function(context)
+		{
+			context.fillStyle = "rgba(0.0, 0.0, 0.0, 0.3)";
+			context.fillRect(0, 0, 1e5, 1e5);
+		};
+		blackLayer.isInside = function(point)
+		{
+			return false;
+		};
+		blackLayer.saveContextState = false;
+		blackLayer.restoreContextState = true;
+		group.add(blackLayer);
+
+		canvas.ondblclick();
+
+		var viewport = new Trenette.Viewport();
+
+		var renderer = new Trenette.Renderer(canvas);
+
+
+		// Update and render the diagram
+		function loop()
+		{			
+			renderer.update(group, viewport);
+
+			requestAnimationFrame(loop);
+		}
+
+		loop();
+	</script>
+</body>
+</html>

+ 43 - 4
source/Object2D.js

@@ -31,6 +31,11 @@ function Object2D()
 	 */
 	this.position = new Vector2(0, 0);
 
+	/**
+	 * Origin of the object used as point of rotation.
+	 */
+	this.origin = new Vector2(0, 0);
+
 	/**
 	 * Scale of the object.
 	 */
@@ -82,7 +87,28 @@ function Object2D()
 	 *
 	 * If true the onPointerDrag callback is used to update the state of the object.
 	 */
-	this.draggable = true;
+	this.draggable = false;
+
+	/**
+	 * Flag to indicate wheter this objet ignores the viewport transformation.
+	 */
+	this.ignoreViewport = false;
+
+	/**
+	 * Flag to indicate if the context of canvas should be saved before render.
+	 */
+	this.saveContextState = true;
+
+	/**
+	 * Flag to indicate if the context of canvas should be restored after render.
+	 */
+	this.restoreContextState = true;
+
+	/**
+	 * Flag to indicate if the context of canvas should be restored after render.
+	 */
+	this.restoreContextState = true;
+
 
 	/**
 	 * Flag indicating if the pointer is inside of the element.
@@ -155,7 +181,7 @@ Object2D.prototype.updateMatrix = function(context)
 {
 	if(this.matrixNeedsUpdate)
 	{
-		this.matrix.compose(this.position.x, this.position.y, this.scale.x, this.scale.y, this.rotation);
+		this.matrix.compose(this.position.x, this.position.y, this.scale.x, this.scale.y, this.origin.x, this.origin.y, this.rotation);
 		this.globalMatrix.copy(this.matrix);
 
 		if(this.parent !== null)
@@ -168,6 +194,16 @@ Object2D.prototype.updateMatrix = function(context)
 	}
 };
 
+/**
+ * Apply the transform to the rendering context.
+ *
+ * Can also be used for pre rendering logic.
+ */
+Object2D.prototype.transform = function(context, viewport)
+{
+	this.globalMatrix.tranformContext(context);
+};
+
 /**
  * Draw the object into the canvas.
  *
@@ -175,7 +211,7 @@ Object2D.prototype.updateMatrix = function(context)
  *
  * @param context Canvas 2d drawing context.
  */
-Object2D.prototype.draw = function(context){};
+Object2D.prototype.draw = function(context, viewport){};
 
 /**
  * Callback method called every time before the object is draw into the canvas.
@@ -210,7 +246,10 @@ Object2D.prototype.onPointerOver = null;
  *
  * Receives (pointer, viewport, delta) as arguments. Delta is the movement of the pointer already translated into local object coordinates.
  */
-Object2D.prototype.onPointerDrag = null;
+Object2D.prototype.onPointerDrag = function(pointer, viewport, delta)
+{
+	this.position.add(delta);
+};
 
 /**
  * Callback method called while the pointer button is pressed.

+ 19 - 9
source/Renderer.js

@@ -76,7 +76,7 @@ Renderer.prototype.update = function(object, viewport)
 	for(var i = 0; i < objects.length; i++)
 	{
 		var child = objects[i];
-		var childPoint = child.inverseGlobalMatrix.transformPoint(viewportPoint);
+		var childPoint = child.inverseGlobalMatrix.transformPoint(child.ignoreViewport ? point : viewportPoint);
 
 		// Check if the pointer pointer is inside
 		if(child.isInside(childPoint))
@@ -149,7 +149,7 @@ Renderer.prototype.update = function(object, viewport)
 		var child = objects[i];
 
 		if(child.beingDragged)
-		{
+		{	
 			var lastPosition = pointer.position.clone();
 			lastPosition.sub(pointer.delta);
 
@@ -159,9 +159,6 @@ Renderer.prototype.update = function(object, viewport)
 			// Mouse delta in world coordinates
 			positionWorld.sub(lastWorld);
 
-			// Update child position
-			child.position.add(positionWorld);
-
 			if(child.onPointerDrag !== null)
 			{
 				child.onPointerDrag(pointer, viewport, positionWorld);
@@ -195,11 +192,24 @@ Renderer.prototype.update = function(object, viewport)
 
 	// Render into the canvas
 	for(var i = 0; i < objects.length; i++)
-	{
-		context.save();
-		objects[i].globalMatrix.tranformContext(context);
+	{	
+		if(objects[i].saveContextState)
+		{
+			context.save();
+		}
+
+		if(objects[i].ignoreViewport)
+		{
+			context.setTransform(1, 0, 0, 1, 0, 0);
+		}
+
+		objects[i].transform(context, viewport);
 		objects[i].draw(context, viewport);
-		context.restore();
+
+		if(objects[i].restoreContextState)
+		{
+			context.restore();
+		}
 	}
 };
 

+ 2 - 0
source/Trenette.js

@@ -19,3 +19,5 @@ export {Line} from "./objects/Line.js";
 export {Text} from "./objects/Text.js";
 export {Image} from "./objects/Image.js";
 export {DOM} from "./objects/DOM.js";
+
+export {Helpers} from "./utils/Helpers.js";

+ 1 - 1
source/Viewport.js

@@ -89,7 +89,7 @@ Viewport.prototype.updateMatrix = function()
 {
 	if(this.matrixNeedsUpdate)
 	{
-		this.matrix.compose(this.position.x, this.position.y, this.scale, this.scale, this.rotation);
+		this.matrix.compose(this.position.x, this.position.y, this.scale, this.scale, 0, 0, this.rotation);
 		this.inverseMatrix = this.matrix.getInverse();
 		//this.matrixNeedsUpdate = false;
 	}

+ 17 - 6
source/math/Matrix.js

@@ -78,17 +78,28 @@ Matrix.prototype.premultiply = function(mat)
 };
 
 /**
- * Compose this transformation matrix with position scale and rotation.
+ * Compose this transformation matrix with position scale and rotation and origin point.
  */
-Matrix.prototype.compose = function(px, py, sx, sy, a)
+Matrix.prototype.compose = function(px, py, sx, sy, ox, oy, a)
 {
 	this.m = [1, 0, 0, 1, px, py];
 
-	var c = Math.cos(a);
-	var s = Math.sin(a);
-	this.multiply(new Matrix([c, s, -s, c, 0, 0]));
+	if(a !== 0)
+	{		
+		var c = Math.cos(a);
+		var s = Math.sin(a);
+		this.multiply(new Matrix([c, s, -s, c, 0, 0]));
+	}
+
+	if(ox !== 0 || oy !== 0)
+	{	
+		this.multiply(new Matrix([1, 0, 0, 1, -ox, -oy]));
+	}
 
-	this.scale(sx, sy);
+	if(sx !== 1 || sy !== 1)
+	{
+		this.scale(sx, sy);
+	}
 };
 
 /**

+ 5 - 2
source/math/Vector2.js

@@ -200,8 +200,11 @@ Object.assign(Vector2.prototype,
 	{
 		var angle = Math.atan2(this.y, this.x);
 
-		if(angle < 0) angle += 2 * Math.PI;
-
+		if(angle < 0)
+		{
+			angle += 2 * Math.PI;
+		}
+		
 		return angle;
 	},
 

+ 1 - 6
source/objects/Box.js

@@ -9,7 +9,7 @@ import {Circle} from "./Circle.js";
 /**
  * Box object draw a box.
  */
-function Box(resizable)
+function Box()
 {
 	Object2D.call(this);
 
@@ -29,11 +29,6 @@ function Box(resizable)
 	 * Background color of the box.
 	 */
 	this.fillStyle = "#FFFFFF";
-
-	if(resizable)
-	{
-		Helpers.createBoxResize(this);
-	}
 }
 
 Box.prototype = Object.create(Object2D.prototype);

+ 14 - 4
source/objects/DOM.js

@@ -25,10 +25,16 @@ function DOM(parent, type)
 	this.element.style.position = "absolute";
 	this.element.style.top = "0px";
 	this.element.style.bottom = "0px";
-	this.element.style.width = "100px";
-	this.element.style.height = "100px";
-	this.element.style.backgroundColor = "rgba(0.0, 0.0, 0.0, 0.8)";
 	this.element.style.transformOrigin = "0px 0px";
+	this.element.style.overflow = "auto";
+	
+	/**
+	 * Size of the DOM element (in world coordinates).
+	 */
+	this.size = new Vector2(100, 100);
+
+	this.origin.set(50, 50);
+
 	parent.appendChild(this.element);
 }
 
@@ -36,10 +42,14 @@ DOM.prototype = Object.create(Object2D.prototype);
 
 DOM.prototype.draw = function(context, viewport)
 {
+	// CSS trasnformation matrix
 	var projection = viewport.matrix.clone();
 	projection.multiply(this.globalMatrix);
-
 	this.element.style.transform = projection.cssTransform();
+
+	// Size of the element
+	this.element.style.width = this.size.x + "px";
+	this.element.style.height = this.size.y + "100px";
 };
 
 export {DOM};

+ 1 - 1
source/objects/Image.js

@@ -50,7 +50,7 @@ Image.prototype.isInside = function(point)
 
 Image.prototype.draw = function(context)
 {
-	context.drawImage(this.image, 0, 0);
+	context.drawImage(this.image, 0, 0, this.image.naturalWidth, this.image.naturalHeight, this.box.min.x, this.box.min.y, this.box.max.x - this.box.min.x, this.box.max.y - this.box.min.y);
 };
 
 export {Image};

+ 35 - 2
source/utils/Helpers.js

@@ -1,9 +1,26 @@
 "use strict";
 
 import {Circle} from "../objects/Circle.js";
+import {Object2D} from "../Object2D.js";
 
 function Helpers(){}
 
+
+/**
+ * Create a rotation tool
+ */
+Helpers.rotateTool = function(object)
+{
+	var tool = new Circle();
+	tool.radius = 4;
+	tool.layer = object.layer + 1;
+	tool.onPointerDrag = function(pointer, viewport, delta)
+	{
+		object.rotation += delta.x * 1e-3;
+	};
+	object.add(tool);
+};
+
 /**
  * Create a box resize helper and attach it to an object to change the size of the object box.
  *
@@ -11,11 +28,11 @@ function Helpers(){}
  *
  * This method required to object to have a box property.
  */
-Helpers.createBoxResize = function(object)
+Helpers.boxResizeTool = function(object)
 {
 	if(object.box === undefined)
 	{
-		console.warn("trenette.js: Helpers.createBoxResize(), object box property missing.");
+		console.warn("trenette.js: Helpers.boxResizeTool(), object box property missing.");
 		return;
 	}
 
@@ -29,8 +46,12 @@ Helpers.createBoxResize = function(object)
 
 	var topRight = new Circle();
 	topRight.radius = 4;
+	topRight.layer = object.layer + 1;
+	topRight.draggable = true;
 	topRight.onPointerDrag = function(pointer, viewport, delta)
 	{
+		Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
+
 		object.box.min.copy(topRight.position);
 		updateHelpers();
 	};
@@ -38,8 +59,12 @@ Helpers.createBoxResize = function(object)
 
 	var topLeft = new Circle();
 	topLeft.radius = 4;
+	topLeft.layer = object.layer + 1;
+	topLeft.draggable = true;
 	topLeft.onPointerDrag = function(pointer, viewport, delta)
 	{
+		Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
+
 		object.box.max.x = topLeft.position.x;
 		object.box.min.y = topLeft.position.y;
 		updateHelpers();
@@ -48,8 +73,12 @@ Helpers.createBoxResize = function(object)
 
 	var bottomLeft = new Circle();
 	bottomLeft.radius = 4;
+	bottomLeft.layer = object.layer + 1;
+	bottomLeft.draggable = true;
 	bottomLeft.onPointerDrag = function(pointer, viewport, delta)
 	{
+		Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
+
 		object.box.max.copy(bottomLeft.position);
 		updateHelpers();
 	};
@@ -57,8 +86,12 @@ Helpers.createBoxResize = function(object)
 
 	var bottomRight = new Circle();
 	bottomRight.radius = 4;
+	bottomRight.layer = object.layer + 1;
+	bottomRight.draggable = true;
 	bottomRight.onPointerDrag = function(pointer, viewport, delta)
 	{
+		Object2D.prototype.onPointerDrag.call(this, pointer, viewport, delta);
+
 		object.box.min.x = bottomRight.position.x;
 		object.box.max.y = bottomRight.position.y;
 		updateHelpers();

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä