You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by jb...@apache.org on 2010/08/10 16:18:54 UTC

svn commit: r984027 - /commons/sandbox/gsoc/2010/scxml-js/trunk/demo/drawing-tool/behaviour/canvas.xml

Author: jbeard
Date: Tue Aug 10 14:18:53 2010
New Revision: 984027

URL: http://svn.apache.org/viewvc?rev=984027&view=rev
Log:
Made serious revisions to use of statecharts in this demo. Centralized logic in the canvas. Started implementing this. This is an intermediate, non-tested, broken commit.

Modified:
    commons/sandbox/gsoc/2010/scxml-js/trunk/demo/drawing-tool/behaviour/canvas.xml

Modified: commons/sandbox/gsoc/2010/scxml-js/trunk/demo/drawing-tool/behaviour/canvas.xml
URL: http://svn.apache.org/viewvc/commons/sandbox/gsoc/2010/scxml-js/trunk/demo/drawing-tool/behaviour/canvas.xml?rev=984027&r1=984026&r2=984027&view=diff
==============================================================================
--- commons/sandbox/gsoc/2010/scxml-js/trunk/demo/drawing-tool/behaviour/canvas.xml (original)
+++ commons/sandbox/gsoc/2010/scxml-js/trunk/demo/drawing-tool/behaviour/canvas.xml Tue Aug 10 14:18:53 2010
@@ -1,3 +1,52 @@
+<!-- 
+Natural-language behavioural specification:
+
+We have a canvas, nodes, rotation and scale handles. 
+
+Canvas has three tools: drawing rects, drawing ellipses, and transforming them.
+Selects between tools by clicking buttons on a toolbar.
+
+Only one tool can be selected at a time. 
+
+Nodes can be selected or not selected. When selected, they are surrounded by a dashed rect.
+
+When transform tool is selected:
+	If there are selected nodes, then either the rotate or translate handles will be visible, and will eb psoitioned on the aggregate bbox of all selected nodes; otherwise, if no nodes are selected, then neither rotate nor transform handles will be shown.
+
+	Dragging rotation handle rotates the nodes about the center point of their aggregate bbox. On mouseup, handle positions are reset.
+	Dragging scale handle scales the nodes.
+	Dragging selected node results in all selected nodes being dragged.
+	Dragging non-selected node results in node being selected, and all other nodes being deselected.
+	Dragging (no shift) on canvas results in marquee being drawn, all nodes inside of marquee being selected, and all other nodes being deselected,
+	Dragging (with shift) on canvas results in marquee being drawn, all nodes inside of marquee being selected (added to selection). 
+
+	Mouseclick (no shift) on selected node results in rotation/scale handles being toggled.
+
+	Dragging node (no shift) will deselect other nodes, drag the node, and select this node.
+	Dragging node (with shift) has same effect as dragging on canvas.
+
+	Additionally, when no nodes are selected, and nodes are first selected, then the scale rotation handles will be shown first
+		UNLESS, the last time nodes were selected, rotation handles were shown when they were deselected; 
+			AND, the way the nodes are being selected now is via a drag (not a click)
+
+When rect or ellipse drawing tools are selected:
+	Rotate and translate handles will never be visible.
+	When only one node is selected, then that node's resize/roundness controls will be shown.
+
+	Mouseclick (no shift) on selected node HAS NO EFFECT.
+
+	Dragging (on canvas, rect, wherever) results in a new element being draw, created and selected. On mouseup, all other elements are deselect.
+
+In both:
+	Mouseclick (no shift) on canvas results in all nodes being selected.
+	Mouseclick (with shift) on node results in node's selection state being toggled.
+
+	Mouseclick (no shift) on non-selected node results in node being selected, and all other nodes being deselected.
+
+	Ctrl+A selects all
+	Delete deletes selected nodes (and deselects them in the process, natch)
+-->
+
 <scxml 
 	xmlns="http://www.w3.org/2005/07/scxml"
 	version="1.0"
@@ -28,134 +77,325 @@
 			node.rx.baseVal.value = dxByTwo;
 			node.ry.baseVal.value = dyByTwo;
 		}
+
+		function updateTransformHandles(rotationButtonHandle,scaleButtonHandle){
+			var sePtX = cachedBBox.width + cachedBBox.x;
+			var sePtY = cachedBBox.height + cachedBBox.y;
+
+			var sScale = scaleButtonHandle.transform.baseVal.getItem(0);
+			var newM = identity.translate(sePtX,sePtY);
+			sScale.setMatrix(newM);
+
+			var rScale = rotationButtonHandle.transform.baseVal.getItem(0);
+			var newM = identity.translate(sePtX,sePtY);
+			rScale.setMatrix(newM);
+		}
+
+		function getCenterPoint(canvas){
+			var bbox = canvas.getBBox();
+			var cPt = {
+				'x': bbox.x + (bbox.width / 2),
+				'y': bbox.y + (bbox.height / 2)
+			}	
+
+			return cPt;
+		}
+
+
+		function  computeRDelta(oldEvent,newEvent,cachedCenterPoint){
+			return _computeRotationAngle(cachedCenterPoint,{x:oldEvent.localX,y:oldEvent.localY},{x:newEvent.localX,y:newEvent.localY});
+		}
+
+		function  _computeRotationAngle(cpt,pt1,pt2){
+			var a1 = _computeAngle(cpt,pt1)
+			var a2 = _computeAngle(cpt,pt2)
+			var angleInDegrees = a2-a1
+			return angleInDegrees; 
+		}
+
+		function  _computeAngle(cpt,pt){
+			var angle=0;
+
+			//put him on the trig circle
+			var signedPt= {
+				x:pt.x-cpt.x,
+				y:pt.y-cpt.y
+			}
+
+			//put him in upper right corner
+			var normalizedPt= {
+				x:Math.abs(signedPt.x), 
+				y:Math.abs(signedPt.y)
+			};
+
+			//compute the angle
+			var angle=Math.abs(Math.atan2(normalizedPt.y, normalizedPt.x));
+
+			//get angle for signed coordinates
+			if(signedPt.x<0 && signedPt.y>=0){
+				angle=Math.PI-angle;
+			}else if(signedPt.x<0 && signedPt.y<0){
+				angle=Math.PI+angle;
+			}else if(signedPt.x>=0 && signedPt.y<0){
+				angle=2*Math.PI-angle;
+			}
+			return angle;
+		}
+
+		function applyTransformToBBox(bbox,matrix){
+			//takes a bbox and a matrix and returns the bbox with the matrix applied to all points
+			var nwX = bbox.x;
+			var nwY = bbox.y;
+			var neX = nwX + bbox.width;
+			var neY = nwY;
+			var swX = nwX;
+			var swY = nwX + bbox.height;
+			var seX = neX;
+			var seY = swY;
+
+			var nw = canvas.createSVGPoint();
+			nw.x = nwX;
+			nw.y + nwY;
+
+			var ne = canvas.createSVGPoint();
+			ne.x = neX;
+			ne.y = neY;
+
+			var sw = canvas.createSVGPoint();
+			sw.x = swX;
+			sw.y = swY;
+
+			var se = canvas.createSVGPoint();
+			se.x = seX;
+			se.y = seY;
+
+			nw = nw.matrixTransform(matrix);
+			ne = ne.matrixTransform(matrix);
+			sw = sw.matrixTransform(matrix);
+			se = se.matrixTransform(matrix);
+
+			var newWidth = Math.abs(ne.x - nw.x);
+			var newHeight = Math.abs(se.y - ne.y);
+
+			return {x:nw.x,y:nw.y,width:newWidth,height:newHeight}
+		}
+
+		function applyLeftTransform(canvas,transformMatrix){
+			var t = canvas.transform.baseVal.getItem(0);
+			var m = t.matrix;
+			var newM = transformMatrix.multiply(m); //note the left transform, pre-muliplication here
+			t.setMatrix(newM);
+			return t;
+		}
+
+		function applyLeftTransformFromNodeToOtherNode(fromNode,toNode){
+			var t = fromNode.transform.baseVal.getItem(0);
+			var m = t.matrix;
+			return applyLeftTransform(toNode,m);
+		}
+
 	]]></script>
 
 
 	<datamodel>
+		<!-- these get passed in on initiation -->
 		<data id="svg"/>
-		<data id="api"/>
 		<data id="toolbarStatechart"/>
+		<data id="scaleButtonHandle"/>
+		<data id="rotationButtonHandle"/>
+		<data id="canvas"/>
+		<data id="transformModule"/>
 		<data id="scaleHandle"/>
 		<data id="rotationHandle"/>
 
-		<data id="tDelta"/>
-		<data id="firstEvent"/>
-		<data id="eventStamp"/>
-		<data id="nodeBeingDrawn"/>
 		<data id="svgNs" expr="'http://www.w3.org/2000/svg'"/>
+
 		<data id="selectedNodes" expr="[]"/>
+		<data id="nodesOnCanvas" expr="[]"/>
+
+		<data id="firstEvent"/>
+		<data id="eventStamp"/>
+		<data id="tDelta"/>
+		<data id="cachedCenterPoint"/>
+		<data id="cachedBBox"/>
+
 	</datamodel>
 
 	<state id="initial_default">
-		<transition event="init" target="idle">
+		<transition event="init" target="main">
 			<assign location="svg" expr="_event.data.svg"/>
 			<assign location="toolbarStatechart" expr="_event.data.toolbarStatechart"/>
-			<assign location="api" expr="_event.data.api"/>
+			<assign location="scaleButtonHandle" expr="_event.data.scaleButtonHandle"/>
+			<assign location="rotationButtonHandle" expr="_event.data.rotationButtonHandle"/>
 		</transition>
 	</state>
 
-	<state id="idle">
-		<transition event="mousedown" target="before_drawing_mode" 
-			cond="!(_event.data.target==scaleHandle || _event.data.target==rotationHandle)">
-
-			<assign location="firstEvent" expr="_event.data"/>
-			<assign location="eventStamp" expr="_event.data"/>
-		</transition>
-		<transition event="mousedown" target="idle" cond="_event.data.target==scaleHandle">
-			<script>
-				//send event to selected guys
-				$(selectedNodes).each(function(n){
-					n["scale_mousedown"](_event.data);
-				});
-			</script>
-		</transition>
-		<transition event="mousedown" target="idle" cond="_event.data.target==rotationHandle">
-			<script>
-				//send event to selected guys
-				$(selectedNodes).each(function(n){
-					n["rotation_mousedown"](_event.data);
-				});
-			</script>
-		</transition>
-
-		<!-- TODO: refactor the following to be static reactions (eventless transitions) -->
-		<transition event="mousemove" target="idle">
-			$(selectedNodes).each(function(n){
-				n["mousemove"](event._data);
-			});
-		</transition>
-		<transition event="CSGROUP_HAS_MOVED" target="idle">
-			<!-- TODO -->
-		</transition>
-		<transition event="OBJ_FINISHED_TRANSFORM" target="idle">
-			<!-- TODO -->
-		</transition>
-		<transition event="OBJ_SELECTED" target="idle">
-			<script>
-				selectedNodes.push(_event.data.target);
 
-				
-			</script>
-		</transition>
-		<transition event="OBJ_DESELECTED" target="idle">
-			<script>
-				selectedNodes.splice(selectedNodes.indexOf(_event.data.target),1);
-			</script>
-		</transition>
-	</state>
+	<parallel id="main">
 
-	<state id="before_drawing_mode">
-		<transition event="mouseup" target="idle">
-			<script>
-				//TODO
-				//obj.canvasDeselect();
-			</script>
-		</transition>
-		<transition event="mousemove" target="drawing_rect" cond="toolbarStatechart.$in(toolbarStatechart._states.rect_selected)">
-			<assign location="tDelta" expr="computeTDelta(eventStamp,_event.data)"/>
-			<script>
-				nodeBeingDrawn = svg.rect(_event.data.clientX,_event.data.clientY,1,1,{"fill":"red","stroke":"black"});
-			</script>
-		</transition>
-		<transition event="mousemove" target="drawing_ellipse" cond="toolbarStatechart.$in(toolbarStatechart._states.ellipse_selected)">
-			<assign location="tDelta" expr="computeTDelta(eventStamp,_event.data)"/>
-			<script>
-				nodeBeingDrawn = svg.ellipse(_event.data.clientX,_event.data.clientY,1,1,{"fill":"blue","stroke":"black"});
-			</script>
-		</transition>
-	</state>
-
-	<state id="drawing">
-		<transition event="mouseup" target="idle">
-			<script>
-				api.addBehaviourToNode(nodeBeingDrawn);
-			</script>
-		</transition>
+		<state id="presentation_state">
 
-		<!-- if escape keypress, kill the node -->
-		<!--
-		<transition event="keydown">
-		</transition>
-		-->
+			<state id="transform_tool_selected">
 
-		<state id="drawing_rect">
-			<transition event="mousemove" target="drawing_rect">
-				<assign location="tDelta" expr="computeTDelta(eventStamp,_event.data)"/>
-				<script>
-					updateRect(nodeBeingDrawn,tDelta);
-				</script>
-			</transition>
+				<state id="no_nodes_selected">
+					<transition target="nodes_selected" event="NODES_SELECTED_WITH_CLICK"/>
+					<transition target="nodes_selected_history" event="NODES_SELECTED_WITH_DRAG"/>
+				</state>
+
+				<state id="nodes_selected" initial="ready_to_scale">
+
+					<history type="shallow" id="nodes_selected_history">
+						<target id="ready_to_scale"/>
+					</history>
+
+					<state id="ready_to_rotate">
+						<onentry>
+							<script>
+								rotationHandle.setAttributeNS(null,"visibility","visible");
+							</script>
+						</onentry>
+						<onexit>
+							<script>
+								rotationHandle.setAttributeNS(null,"visibility","hidden");
+							</script>
+						</onexit>
+						<target id="ready_to_scale" event="TOGGLE_TRANSFORM"/>
+					</state>
+
+					<state id="ready_to_scale">
+						<onentry>
+							<script>
+								scaleHandle.setAttributeNS(null,"visibility","visible");
+							</script>
+						</onentry>
+						<onexit>
+							<script>
+								scaleHandle.setAttributeNS(null,"visibility","hidden");
+							</script>
+						</onexit>
+						<target id="ready_to_rotate" event="TOGGLE_TRANSFORM"/>
+					</state>
+
+					<transition target="no_nodes_selected" event="CHECK_NODES" cond="selectedNodes.length === 0"/>
+
+				</state>
+		
+				<transition target="circle_tool_selected" event="mousedown" cond="_event.data.target === rotationButtonHandle"/>
+				<transition target="rect_tool_selected" event="mousedown" cond="_event.data.target === scaleButtonHandle"/>
+			</state>
+
+			<state id="drawing_tool_selected">
+				<state id="circle_tool_selected">
+					<transition target="rect_tool_selected" event="mousedown" cond="_event.data.target === scaleButtonHandle"/>
+				</state>
+
+				<state id="rect_tool_selected">
+					<transition target="circle_tool_selected" event="mousedown" cond="_event.data.target === rotationButtonHandle"/>
+				</state>
+
+				<!-- TODO: add reference to transform button handle -->
+				<transition target="transform_tool_selected" event="mousedown" cond="_event.data.target === transformButtonHandle"/>
+			<state>
 		</state>
 
-		<state id="drawing_ellipse">
-			<transition event="mousemove" target="drawing_ellipse">
-				<script>
-					updateCircle(nodeBeingDrawn,eventStamp,_event.data);
-				</script>
-			</transition>
-		</state>
+		<state id="processing_events">
+			<datamodel>
+				<data id="clickedNode"/>
+			</datamodel>
+
+			<state id="ready">
+				<onexit>
+					<assign location="clickedNode" expr="_event.data.target"/>
+				</onexit>
+
+				<transition target="after_mousedown_on_selected_nodes" event="mousedown" 
+					cond="selectedNodes.indexOf(_event.data.target) !== -1 && !_event.data.shiftKey"/>
+
+				<transition target="after_mousedown_with_shift_key_on_selected_nodes" event="mousedown" 
+					cond="selectedNodes.indexOf(_event.data.target) !== -1 && _event.data.shiftKey"/>
+
+				<transition target="after_mousedown_on_nonselected_nodes" event="mousedown"
+					cond="allNodes.indexOf(_event.data.target) !== -1 && !_event.data.shiftKey"/>
+
+				<transition target="after_mousedown_with_shift_key_on_nonselected_nodes" event="mousedown"
+					cond="allNodes.indexOf(_event.data.target) !== -1 && _event.data.shiftKey"/>
+
+				<transition target="after_mousedown_on_canvas" event="mousedown" cond="_event.data.target === canvas"/>
+			</state>
+
+			<state id="after_mousedown_on_selected_nodes">
+				<transition target="dragging" event="mousemove"/>
+
+				<transition target="ready" event="mouseup">
+					<send event="TOGGLE_TRANSFORM"/>
+				</transition>
+			</state>
+
+			<state id="after_mousedown_with_shift_key_on_selected_nodes">
+				<transition target="ready" event="mouseup">
+					<script>
+						//remove selected node from selection
+						selectedNodes.splice(selectedNodes.indexOf(clickNode),1);
+					</script>
+					<send event="CHECK_NODES"/>
+				</transition>
+
+				<!-- TODO: add marquee-drawing logic -->
+				<transition target="drawing_marquee" event="mousemove"/>
+			</state>
+
+			<!-- TODO: add dragging behaviour with the tdeltas and stuff -->
+			<state id="dragging">
+				<transition target="dragging" event="mousemove"/>
+			</state>
+
+			<state id="after_mousedown_on_nonselected_nodes">
+				<transition target="ready" event="mouseup">
+					<assign location="selectedNodes" expr="[clickedNode]"/>
+					<send event="NODES_SELECTED_WITH_CLICK"/>
+				</transition>
+				<transition target="dragging" event="mousemove">
+					<assign location="selectedNodes" expr="[clickedNode]"/>
+					<send event="NODES_SELECTED_WITH_DRAG"/>
+					<!-- TODO: add dragging logic -->
+				</transition>
+			</state>
+
+			<!-- TODO-->
+			<state id="after_mousedown_with_shift_key_on_nonselected_nodes">
+				<transition target="ready" event="mouseup">
+					<script>
+						selectedNodes.push(clickNode);
+					</script>
+					<send event="NODES_SELECTED_WITH_CLICK"/>
+				</transition>
+
+				<!-- TODO: add marquee-drawing logic -->
+				<transition target="drawing_marquee" event="mousemove"/>
+			</state>
+
+			<state id="after_mousedown_on_canvas">
+				<transition target="ready" event="mouseup">
+					<assign location="selectedNodes" expr="[]"/>
+					<send event="CHECK_NODES"/>
+				</transition>
+
+				<transition target="drawing_marquee" event="mousemove"/>
+			</state>
+
+			<!-- TODO: add marquee-drawing logic -->
+			<state id="drawing_marquee">
+				<transition target="ready" event="mouseup">
+					<script>
+						<!-- TODO: select what is inside marquee -->
+					</script>
+					<send event="CHECK_NODES"/>
+				</transition>
 
-	</state>
+				<transition target="drawing_marquee" event="mousemove"/>
+			</state>
+		</state>
+	</parallel>
 
 </scxml>