Tutorials, extensions, and source files for ActionScript, Flash, and other Adobe products.

 

Pages: 1 | 2 | 3 | 4 | 5

Advanced Movement Options

There are many properties in a RegisterMoveParms object that control how points (control points or nodes) are moved. Some relate to many of the available register move functions while others are specific to one or a few. The following chart shows which properties relate to which register move functions.

 
constrainX        
constrainXKey        
constrainY        
constrainYKey        
constrain45Key        
constrain90Key        
constrainAngles        
constrainRotateKey        
disableRotateKey        
deltaXtoX      
deltaXtoY      
deltaYtoX      
deltaYtoY      
deltaLinearToLinear        
deltaRtoR        
deltaShortestSideToX        
deltaShortestSideToY        
deltaLongestSideToX        
deltaLongestSideToY        
incrementX        
incrementY        
incrementRadius        
minX        
maxX        
minY        
maxY        
minMaxRelative        
minLinear        
maxLinear        
minAngle        
maxAngle        
minRadius        
maxRadius        
movePred  
movePt  
moveSucc  
rotate      
For more details on what they represent, see Extending Fireworks PDF.

Depending on how you want your points to move, you would need to first select the appropriate register move function and then provide it with the proper RegisterMoveParms object to further outline the details of its behavior. The properties of that object, such as those in the chart above, control how that point will move within the DragControlPoint event. It would be too time consuming to cover each and everyone of those properties in detail, but I'll do my best to at least use most of them throughout the various Auto Shape examples that will follow.

Right now I'm going to focus on the first four register move functions and leave RegisterInsertBBoxMove for later since it deals with Auto Shape tools. These four, RegisterMove, RegisterLinearMove, RegisterCircularMove, and RegisterPolygonMove, all behave slightly differently. The following chart attempts to sum up the differences.

Words (and pictures) alone can only tell you so much about user interaction. The best way to understand is to experience it on hand. Enter an example Auto Shape that uses all four. Create a new JSF file for this Auto Shape and take it for a test drive

Auto Shape: Register Moves [ Show/Hide Code | Download ]

Each four corners on that shape (a square) move with a respective control point by means of a different register move function. In that particular example, each uses the defaults provided by the parms variable (obtained from smartShape.GetDefaultMoveParms()). With defaults, you'll notice that RegisterMove and RegisterPolygonMove seem to be the same. Some differences become a little more apparent once variables are assigned for the parms object to further restrict the interaction.

Auto Shape: Register Moves 2 [ Show/Hide Code | Download ]

Each register move function in this example was given a RegisterMoveParms object with an added min and max properties that related to that movement type. Some differences between RegisterMove and RegisterPolygonMove can now be seen.

 

The most common of movements among Auto Shapes is probably linear movement controlled (normally) by RegisterLinearMove. This lets you drag points along a straight line. Fireworks's Cog Auto Shape, for example, has 7 control points, all of which are moved in a linear fashion (or at least results in a linear movement when used).

Linear movement is great for extending the size of shapes and for providing slider-like scales for adjusting value ranges. The Cog shape uses both of these. For now lets just focus on basic linear movement and apply that to the Triangle example from earlier as a practical application. Here is an updated script for the Triangle shape that now exhibits linear movement.

Auto Shape: Play Button Triangle 3 [ Show/Hide Code | Download ]

Here, the original RegisterMove calls for the control points and nodes have changed to RegisterLinearMove. RegisterLinearMove takes, along with a RegisterMoveParms object, a point from which it is to base the linear movement from. The line of movement is based off that point and the point which is being moved. Draw a line through those points and that is the line which the point travels. A horiz variable is created to represent a point 100 pixels to the right of the point which is being moved so that when points are moved, they always move horizontally.

By altering RegisterMoveParms object properties (for the parms variable) we can further modify this movement. For instance, what if you don't want to be able to drag the tip beyond the location of the end of the triangle or vise versa? The minLinear and maxLinear properties can be altered to restrict movement for this.

Auto Shape: Play Button Triangle 4 [ Show/Hide Code | Download ]

Since the two control points are directly across from each other, taking the difference of their x position would give you their distance away from each other. Using this value will let you restrict the movement along the line in which they travel with either minLinear or maxLinear. minLinear represents the allowed distance along the line the point can travel to the left while maxLinear represents the distance to the right. The Tip control point only needs not to go beyond the End control point which resides to its left, so minLinear is used to restrict it. It can, however, move as far as it wants to the right. Similarly, the End control point is restricted in moving right (maxLinear), but can be moved any distance to the left.

 

Min/max restraints are nice, but they're just not as fun as some of the other properties provided by a RegisterMoveParms object. More interesting interaction can be had from the delta properties. These properties, such as deltaXtoX and deltaXtoY, let you control how much a point moves in reaction to the mouse adjusting the otherwise 1:1 correlation between mouse and point movement. For example, deltaXtoX, if given a value of .5, will let you move a point's x position only half that which has been moved by the mouse. Using a value of 2 would make the point move twice the distance. Better yet, these values can be negative, so a deltaXtoX of -1 will make the point move in the opposite direction of the mouse. The following cross shape demonstrates some uses of some delta properties.

Auto Shape: Cross [ Show/Hide Code | Download ]

This particular example only uses negative values but still shows how the level of interactivity and style of interaction can change. For both control points the delta properties assigned allow a type of scaling from the center as some points move with the mouse, others move opposite the mouse and others move with the mouse only in one direction and opposite it in the other.

Let's move on to something a little more complicated now. We'll stick with a shape that uses a control point with linear movement, but this time make it a slider control point (it controls a number value rather than the location of one or more nodes). Also, we'll throw in a new node that uses RegisterCircularMove that allows a point to be rotated around a center. This shape? An asterisk.

Asterisks look like stars but usually don't have pointed ends. Commonly, as most fonts depict them, they also have 5 or 6 points, but for this asterisk shape we'll allow something like 3 to 10 which can be controlled with the slider control point. We'll also include the ability to rotate it dynamically using an additional control point (the second of two total control points). If you haven't figured it out already, this is the control point that would use RegisterCircularMove. In my mind I see an asterisk as a series of almost triangle-like shapes with rounded ends rotated around and joined at a common center.

At this conceptual stage, it seems simple enough. Don't be fooled. Even something as seemingly simple as this can get quite complicated.

In dealing with a shape that involves rotation around a center and a variable number of items around that center (points on the asterisk), undoubtedly, trigonometry will be involved. If you're not to keen on trig, you may want to do a search on the internet and look up some resources. There are a few key concepts that are used extensively in these kinds of calculations and it's a good idea to be familiar with them. If you know nothing about trig and don't care to, that's fine as well. Many of the uses to follow are condensed into functions and should be easy enough to implement on your own if you need to. Those used by the Asterisk shape include the following.

AngleBetween = function(pt1, pt2){
	return Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x);
}
// and
PointFromVector = function(origin, angle, power){
	return {
		x: origin.x + Math.cos(angle)*power,
		y: origin.y + Math.sin(angle)*power
	}
}

AngleBetween figures and returns the angle between two coordinate points while PointFromVector returns a point from a central point, an angle and a power (radius).

To create the round ends of each asterisk "arm", some of the nodes will need to have their control points modified. Actually, this is for every node except those in the center. Those modified, though, will have only either the predecessor or successor control point modified depending on where the node is on the arm in which it exists. Their positions can easily be taken care of with the functions just mentioned.

A big difference with this Auto Shape, and a reason those previously mentioned functions are needed, is that each asterisk arm will be generated dynamically from a calculation and not necessarily a static position as was the case before. Since each arm will change based on how many arms make up the Asterisk and and the Asterisk's set rotation (controlled with the new control point), simple position values for their location just won't cut it. Because the shape will pretty much need to be recreated each time the number of points changes, it will make things easier to contain the creation process (at least for the paths of the shape) to its own function. A good name for this function would be Draw.

Draw functions can be useful in many shapes, especially those that tend to alter their appearance to a large degree as opposed to just changing existing points. Draw is generally used to completely recreate a shape from scratch or completely re-evaluate element shape based on the current variable environment. By "variable environment" I mean those factors that contribute to how the shape looks. This is usually simply control point position. At the least, Draw is called during the InsertSmartShapeAt event but normally includes events such as EndControlPoint and DragControlPoint; and that can be dependant on how the user interacts with the shape and whether or not the shape needs to react by redrawing itself completely. The Asterisk Smart Shape will use its Draw function when its created and during EndDragControlPoint to ensure the shape is fully rendered as it needs to be when the user has finished interacting with the shape.

The reason Draw is needed for the Asterisk shape is because user interaction is a lot different than that which has been seen in previous shapes. No longer are control points simply moving shape nodes, they are instead drastically altering the entire shape. Think of the slider control point that will determine how many points the asterisk will have. This control point does not move with any node within the shape, it will sit at the side of the shape and gets moved up and down along a set path. Depending on the location of that control point on that path, the shape will be instructed to create itself with a different number of points. Because of this difference in behavior, a little math will be needed to actually figure out not only where the control point can move but just how many points its position represents. Determining the points will be handled in its own function called GetArmCount.

Putting everything together, you get the following for the Asterisk Auto Shape.

Auto Shape: Asterisk [ Show/Hide Code | Download ]

When you interact with the Asterisk shape on the screen, you'll notice that you don't see a preview of the shape as its being altered. The reason for this is that no nodes are actually being moved, only control points. The only time the shape physically updates is when the Draw function is called and that only happens in InsertSmartShapeAt and EndDragControlPoint. Now, to change this, we could add a DragControlPoint function in the operation object for the DragControlPoint event and call Draw there. This will work, however, when control points are registered with move functions, Fireworks automatically disables the DragControlPoint event from being invoked since Fireworks thinks all actions within that event will be taken care of automatically with the register move function(s) used. Hope is not lost, though. You can get your DragControlPoint event back by setting the getsDragEvents property in smartShape to be true.

smartShape.getsDragEvents = true;

When placed in BeginDragControlPoint, the DragControlPoint again executes the Auto Shape script.

Additionally, and though this may not be that much of an issue here since the Asterisk shape is not that complex, it may be important to consider performance when setting up interaction for an Auto Shape. For instance, there are times when you can move the Count (slider) control point and not effect the shape of the asterisk at all. Would it then be necessary to call Draw and redraw the shape even though nothing has changed? Probably not. Similarly, the Rotate control point has the awkward job of needing to rotate (and actually redraw) every point for the shape as it "rotates" it. We can assume it to be a tedious job and take steps to prevent that kind of number-crunching while the user is interacting with the shape. What we could do is analyze the shape and try to determine its current point count by counting contours, but there's another way that also shows a useful technique for information storage.

All elements in Fireworks, including Auto Shapes, have a customData property associated with them that represents a generic object which you can save variables to. The best part about these objects is that variables within them remain even when the file is saved, closed and re-opened. For Auto Shapes they can be a great place to store information that can be checked during different operations involving the shape. For the Asterisk shape, it can be used to store the last known number of points the shape had during any event (namely a DragControlPoint event). This way, the shape can only draw itself when it sees that the Count control point has moved enough to change the point count of the shape to something other than what it was last. Since customData is a property of a Fireworks Element, an Auto Shape's customData is located in smartShape.elem.

smartShape.elem.customData;

The count will be stored there and checked within DragControlPoint to see whether or not it's different from the count as specified by the current location of the Count control point. If so, the shape needs to be drawn and Draw can be called.

For the Rotate control point, a different approach will be taken. The idea here is that updating the whole shape when interacting with this point is just too much for Fireworks to draw on the screen repeatedly when the mouse moves (we're pretending it is, at least). In response to this, we can decide not to draw the whole shape and, instead, create a a new shape to act as a preview. Once user interaction is complete, this shape can be removed and the original shape updated. Certainly this preview would need to be simpler than the shape itself or there would be no increase in performance.

For the Asterisk shape, I chose to use a single line to show the current rotation. This line is created in BeginDragControlPoint and removed in EndDragControlPoint--its life span only lasting as long as the user is dragging a control point. In this case, a register move function is used add interaction to this preview shape, but a new draw function could just as well be defined for drawing just the preview and only within the DragControlPoint event.

Before applying these concepts to the Asterisk shape, I want to add something else that can become very useful and is made possible thanks to the DragControlPoint event. These are dynamic tool tips. The toolTipTracksDrag property for control points, when true, lets tool tips follow the mouse as the control point is being used. This lets you put critical information within that tool tip that can help aide in the interaction. This can be particularly useful since most of the interaction of control points for Auto Shapes are not precise. That is, most are positioned by mouse only and provide no means of exact input like that which the properties inspector provides for selection position and size. Showing the current status of a control point as it's being moved helps the user know what they're getting with their mouse movement. Setting toolTipTracksDrag for the Asterisk's control points and using an UpdateToolTips function in the DragControlPoint event will provide this functionality in the Asterisk Auto Shape.

Here is what the updated Asterisk code looks like.

Auto Shape: Asterisk 2 [ Show/Hide Code | Download ]

 

The only register move function (not including RegisterInsertBBoxMove) yet to be applied to a practical shape is RegisterPolygonMove. The Register Moves shape demonstrated how, by default, movement of this type has no restrictions and behaves much like RegisterMove. When some restraints were added its movement was restricted to a doughnut area. What you couldn't see so much with the Register Move shape (at least when without the constraints) is how RegisterPolygonMove bases movement around its specified center point. Where RegisterMove follows the mouse directly, RegisterPolygonMove follows the mouse around and away from a central location; it's really more a combination of both RegisterLinearMove and RegisterCircularMove.

A quick example of RegisterPolygonMove would be the scaling and rotation of a polygon like a pentagon. A single control point would instruct all points to rotate around and move outward from (or inward towards) the origin of the polygon.

Auto Shape: Pentagon [ Show/Hide Code | Download ]

A simple shape, the Pentagon consists of only 5 points on a single contour that are situated around a common center. A single control point moves them all with a RegisterPolygonMove away (or towards) and around that center.

Luckily, for a shape that's as simple as this, all the points that would need to move as a result of the rotation and scaling are the same distance from the center of the shape. If this was not the case, some distortion would occur as a result of a disproportional scale since RegisterPolygonMove uses the mouse's movement away from the center as a measurement movement of all the points registered to be moved. This means points close to the center as well as those far away from the center move outward from that center at equal distances despite that, for a proportional scale, those closer to the center would need to be moved a far shorter distance than those further away. Watch what happens when the Pentagon shape goes hollow and then gets scaled and rotated with RegisterPolygonMove.

Auto Shape: Pentagon 2 [ Show/Hide Code | Download ]

Here, proportion is not kept as each point moves away from the center at a consistent distance every time the mouse is moved. Of course, this very well could be a desired outcome and may be beneficial if you want something like a solid border to remain a consistent width.

 

Adding Colors And Effects

By default, Auto Shapes created in Fireworks inherit the colors currently selected for fill and stroke (document fill and stroke). When making your own Auto Shapes, you can decide to instead add your own colors within the shape code that will override those otherwise provided by the document as well as add your own effects.

Color properties for Fireworks elements reside in a pathAttributes property. This is an object which contains specific properties for adjusting brush (stroke) color, brush style, fill color, and fill style. These properties (brush, brushColor, fill, and fillColor respectively) and the properties they contain are outlined in further detail in the Extending Fireworks PDF. Elements also contain a property called effectList which is an object that contains a list (array) of all effects added to that element.

So let's say we wanted to give a shape a blue fill color with a black outline that has an added Blur More effect. Applying that in code would look like this.

someShapeElement.pathAttributes.brushColor = "#000000";
someShapeElement.pathAttributes.brush = {
	category:"bc_Basic", 
	name:"bn_Soft Line", 
	alphaRemap:"none",
	angle:0, 
	antiAliased:true, 
	aspect:100, 
	blackness:0, 
	concentration:100, 
	dashOffSize1:2, 
	dashOffSize2:2, 
	dashOffSize3:2, 
	dashOnSize1:10, 
	dashOnSize2:1, 
	dashOnSize3:1, 
	diameter:5, 
	feedback:"brush", 
	flowRate:0, 
	maxCount:14, 
	minSize:1, 
	numDashes:0, 
	shape:"circle", 
	softenMode:"bell curve", 
	softness:100, 
	spacing:6, 
	textureBlend:0, 
	textureEdge:0, 
	tipColoringMode:"random", 
	tipCount:1, 
	tipSpacing:0, 
	tipSpacingMode:"random", 
	type:"simple"
};
someShapeElement.pathAttributes.fillColor = "#0000FF";
someShapeElement.pathAttributes.fill = {
	category:"fc_Solid",
	name:"fn_Normal",
	ditherColors:[ "#000000", "#000000" ], 
	edgeType:"antialiased", 
	feather:0, 
	gradient:null, 
	pattern:null, 
	shape:"solid", 
	stampingMode:"blend opaque", 
	textureBlend:0, 
	webDitherTransparent:false
};
someShapeElement.effectList = {
	category:"UNUSED",
	name:"UNUSED",
	effects: [
		{
			category:"Blur",
			name:"Blur More",
			EffectIsVisible:true,
			EffectMoaID:"{f1cfce42-718e-11d1-8c8200a024cdc039}"
		}
	]
};

The resulting shape is something like the following.

Because there are so many properties to consider, formatting object and array assignment as seen above makes the code easier to read (however, it can also add a lot of length to your final script so you could assign these in a single line to keep the script more vertically compact). Often, the easiest way to get these values quickly is to create a vector shape in Fireworks and simply give that shape the properties you wish to use. Then, in the History Panel, select and copy the steps that applied the colors and/or effects. Paste them in any text editor and you can quickly get those properties and values from the commands that applied them in the first place.

Lets add some effects and color to the Pentagon shape.

Auto Shape: Pentagon 3 [ Show/Hide Code | Download ]

Now the pentagon comes with its own default color, texture and even a drop shadow.

This is applied when the shape is created during InsertSmartShapeAt so if at any time the user decides to change the color and/or effects, he/she can. In fact, you could make that process a little easier by taking advantage of Fireworks's fw.popupColorPickerOverMouse command. This command will instruct Fireworks to display the color picker next to the mouse when it's called. When the user selects a color, it is returned and can be saved as a variable to be used in the script that called it. We can add it to the pentagon's Center control point so the user can quickly change the shape's color by clicking on it.

Auto Shape: Pentagon 4 [ Show/Hide Code | Download ]

For the most part, it's as simple as that. By setting these properties you can easily change the colors of, and add effects to, any element within the elements array of an Auto Shape or even the Auto Shape element itself (smartShape.elem). However, in doing so, you should be careful not to create a defiant shape. For example, If you intend on using a Draw function to redraw your shape like the Asterisk shape, you may decide that, along with shape creation, colors and effects be added there as well. That's fine, but what if the user changes the colors of the shape sometime between when it was created and the next time Draw will be called (the next time the user interacts with the shape)? Should the shape be so bold as to remove the colors applied by the user and replace it with the defaults that you specified? Probably not. Instead, colors and effects would be better off applied within an InsertSmartShapeAt event but outside any drawing functions. On top of that, it may be necessary to store the current color and effects values for a shape or element if that element is completely recreated from scratch (at which point it resorts to using document defaults).

 

Fully understand the creation of your basic Auto Shape yet? What? No? Ok, well then how about a command that will make your Auto Shape for you? The following command will do just that. Simply create a path object in your Fireworks document with any shape, number of contours, colors, and/or effects and run the following script as a command (you can do this through drag and drop or by saving this in your Configuration\Commands folder in your Fireworks install directory where it then becomes available from the Commands menu).

GenerateAutoShapeCode = function(){
	if (fw.selection.length != 1 || fw.selection[0].__proto__ != Path.prototype){
		alert("Error: Please select one Path object.");
		return true;
	}
	var sel = fw.selection[0];
	var fill = (sel.pathAttributes.fill) ? sel.pathAttributes.fill.toSource() : "null";
	var fillColor = (sel.pathAttributes.fillColor) ? sel.pathAttributes.fillColor.toSource() : "null";
	var brush = (sel.pathAttributes.brush) ? sel.pathAttributes.brush.toSource() : "null";
	var brushColor = (sel.pathAttributes.brushColor) ? sel.pathAttributes.brushColor.toSource() : "null";
	var effectList = (sel.effectList) ? sel.effectList.toSource() : null;
	var code = "var mouse = smartShape.currentMousePos;\n";
	code += "var operation = new Object();\n";
	code += "operation.InsertSmartShapeAt = function(){\n";
	code += "\tvar nods;\n";
	code += "\tsmartShape.elem.elements[0] = new Path();\n";
	code += "\tsmartShape.elem.elements[0].pathAttributes.fill = "+fill+ ";\n";
	code += "\tsmartShape.elem.elements[0].pathAttributes.fillColor = "+fillColor+ ";\n";
	code += "\tsmartShape.elem.elements[0].pathAttributes.brush = "+brush+ ";\n";
	code += "\tsmartShape.elem.elements[0].pathAttributes.brushColor = "+brushColor+ ";\n";
	code += "\tsmartShape.elem.elements[0].effectList = "+effectList+ ";\n";
	var conts = sel.contours;
	for (var c=0; c<conts.length; c++){
		code += "\tsmartShape.elem.elements[0].contours["+c+"] = new Contour();\n";
		if (conts[c].isClosed) code += "\tsmartShape.elem.elements[0].contours["+c+"].isClosed = true;\n";
		var nods = conts[c].nodes;
		code += "\tnods = smartShape.elem.elements[0].contours["+c+"].nodes;\n";
		code += "\tnods.length = "+nods.length+";\n";
		for (var n=0; n<nods.length; n++){
			var predX = Math.round(nods[n].predX);
			var predY = Math.round(nods[n].predY);
			var x = Math.round(nods[n].x);
			var y = Math.round(nods[n].y);
			var succX = Math.round(nods[n].succX);
			var succY = Math.round(nods[n].succY);
			code += "\tSetBezierNodePosition(nods["+n+"], AddPoints(mouse, {x:"+predX+", y:"+predY+"}), AddPoints(mouse, {x:"+x+", y:"+y+"}), AddPoints(mouse, {x:"+succX+", y:"+succY+"}));\n";
		}
	}
	code += "}\n";
	code += "SetBezierNodePosition = function(node, ptp, pt, pts){\n";
	code += "\tnode.predX = ptp.x; node.predY = ptp.y;\n";
	code += "\tnode.x = pt.x;		node.y = pt.y;\n";
	code += "\tnode.succX = pts.x; node.succY = pts.y;\n";
	code += "}\n";
	code += "AddPoints = function(pt1, pt2){\n";
	code += "\treturn {x:pt1.x + pt2.x, y:pt1.y + pt2.y};\n";
	code += "}\n";
	code += "if (operation[smartShape.operation]) operation[smartShape.operation]();\n";
	return code;
}
SaveTextFile = function(filename, writestring){
	if (filename){
		var thisfile;
		if (Files.deleteFileIfExisting(filename)){
			var ext = Files.getExtension(filename);
			if (!ext){
				ext = ".jsf";
				filename += ext;
			}
			if(Files.createFile(filename, ext, "FWMX")){
				thisfile = Files.open(filename, true);
			}else alert("Error: Could not create file for saving.");
		}else alert("Error: Could not save because file name is invalid.");
		if (thisfile){
			if (!thisfile.write(writestring)) alert("Error: Save failed for unknown reasons.");
			thisfile.close();
			return true;
		}
	}
	return false;
}
var code = GenerateAutoShapeCode();
if (code) var filename = fw.browseForFileURL("save");
if (filename) SaveTextFile(filename, code);

This will create for you Auto Shape code that is translated from a selected path in the current Fireworks document. Just create your shape (path), select it, and run this script. It will prompt you to save. The resulting file is a Auto Shape script that represents the shape you selected almost exactly. One thing to watch out for is that it bases position as though (0,0) in the current document is the mouse position. Also, no control points are created the shape generated. You would have to add them yourself.

Continue reading »

Pages: 1 | 2 | 3 | 4 | 5