Transform Tool Developer Guide
This document describes how to develop for and use the Transform Tool provided here on senocular.com. The Transform Tool is an ActionScript-based component that provides visual controls for manipulating the shape and position of display objects on the screen.
Basic Design and Usage
The Transform Tool is a display object defined by the TransformTool class. It works with one or more child display objects known as controls - collectively within a Transform Tool known as a control set - to define how the tool is used and what within a target object can be transformed (scale, rotation, etc).
The following example shows a very simple usage of the Transform Tool.
// import for the Transform Tool classes used import com.senocular.display.transform.*; // create a box object to interact with var box:Sprite = new Sprite(); addChild(box); box.graphics.beginFill(0xAACCDD); box.graphics.drawRect(-50, -50, 100, 100); box.x = 100; box.y = 100; // create the Transform Tool var tool:TransformTool = new TransformTool(new ControlSetStandard()); addChild(tool); // select the box with the transform tool when clicked. // deselect when clicking on the stage box.addEventListener(MouseEvent.MOUSE_DOWN, tool.select); stage.addEventListener(MouseEvent.MOUSE_DOWN, tool.deselect);
Transform Tool example SWF
Breaking it down:
This imports the classes used by the script that are part of the classes used by the Transform Tool. All Transform Tool classes exist in this package. This example uses two: TransformTool and ControlSetStandard.
var box:Sprite = new Sprite(); addChild(box); box.graphics.beginFill(0xAACCDD); box.graphics.drawRect(-50, -50, 100, 100); box.x = 100; box.y = 100;
A simple box Sprite is made and added to the screen. For this example, it represents an object to interact with using the tool.
var tool:TransformTool = new TransformTool(new ControlSetStandard()); addChild(tool);
An instance of the TransformTool class is created and added to the display list. Most of the interactions with the Transform Tool will happen through an instance of the TransformTool class.
Passed in to the TransformTool constructor is an instance of ControlSetStandard. This defines what controls make up the Transform Tool, whether it be scaling controls, rotation controls, skewing controls, or any combination or those or any other control. The Transform Tool comes with a number of pre-defined controls and control sets, but you have the option of defining your own as well. The ControlSetStandard class creates a control set that is similar to the one found in the Flash Professional authoring tool but without the skewing controls.
box.addEventListener(MouseEvent.MOUSE_DOWN, tool.select); stage.addEventListener(MouseEvent.MOUSE_DOWN, tool.deselect);
Here, event listeners are set up to handle object selection with the Transform Tool. Using the built-in select handler within the TransformTool instance, the box will be automatically assigned the target of the Transform Tool when clicked. Similarly, when the stage is clicked (as in, not the box), the box is deselected or, more specifically, the tool's target is removed.
The TransformTool class is the main class of the Transform Tool. It represents the Transform Tool itself and is responsible for:
- Target selection
- Applying transformations
- Restrictions (min/max for target)
- Control sets
TransformTool instances are display objects. When created, they will need to be added to the display list.
var tool:TransformTool = new TransformTool(); addChild(tool);
They should be added to the same container as the target objects they will interact with.
Alone, a TransformTool instance is graphically inept. All visual and interactive components of the tool are represented through controls. When created, a TransformTool instance is typically created with a control set defining all of the controls that are contained within the tool.
var tool:TransformTool = new TransformTool(new ControlSetStandard()); // or var tool:TransformTool = new TransformTool(); tool.controls = new ControlSetStandard();
To be able to transform another display object on the screen, the Transform Tool must target that object. This can be done by setting the TransformTool.target property. You can also use the TransformTool.setTarget() method. The setTarget method includes another parameter for an event that indicates which event caused the selection to occur. This event is used by some controls like the ControlMove to begin moving a target object as soon as the mouse is clicked on it.
tool.target = myObjectToTransform; // or tool.setTarget(myObjectToTransform, myCurrentEvent);
To remove a target from the control of the Transform Tool, set the target of the TransformTool instance to null. This can be done through the target property or the setTarget method
tool.target = null; // or tool.setTarget(null);
The TransformTool class contains helper selection handlers, TransformTool.select() and TransformTool.deselect(), that work with mouse events that make object selection easy. Assign the select handler to mouse event listeners on objects you want to select (or a parent of those objects), and the deselect handler to mouse event listeners on objects you want to cause a deselect.
myObjectToTransform.addEventListener(MouseEvent.MOUSE_DOWN, tool.select); stage.addEventListener(MouseEvent.MOUSE_DOWN, tool.deselect);
The select handler will automatically set the target and target event through setTarget using either event.target or event.currentTarget depending on which is located within the same parent as the tool itself. The deselect handler will set the target to null.
When a target is selected, by default, it will automatically raise the target to the top of the display list in its parent (though not above the Transform Tool itself). This is an option that can be toggled through the TransformTool.autoRaise property.
It is possible to have the Transform Tool and the objects meant to be transformed in different parent display object containers. These containers should, however, be located in the same global position so that the Transform Tool would correctly draw in the expected location. When this is the case, the select handler will not function and you will need to use a custom approach to target object selection. Additionally, autoRaise will only be able to raise objects within their own containers display list which may or may not place them above other objects if not in the same container.
Restrictions allow you to limit the transformations being applied to target objects by the Transform Tool. Restrictions are represented by properties in the TransformTool class. They include:
There are two types of restrictions, scaling and rotation. Scaling restrictions include min and max limits for width, height and scale. Either or both can be used to restrict an object's size. When both are specified, the limit passed first is used. For example if an object has an initial width of 100px and is set as the target of a Transform Tool that has a minWidth of 100 and a minScaleX of 0.5 (50%), the Transform Tool will not scale the object below 100px in size restricting to the minWidth. If minWidth was 50 and minScaleX was 1.0 (100%), the object will still not scale the object below 100px, but this time because of minScaleX.
Flash usually measures width and height of an object entirely in the parent coordinate space without respecting the current transform (namely rotation) of that object. This can create conflicting data when dealing with both rotation and size. For example, after rotating a 100x100 px square 45 degrees, the reported width and height in Flash is no longer 100 px. Instead, it becomes something closer to 141 px. The Transform Tool, on the other hand, determines size based relatively on the target's transformed local x and y axes, but within the parent object's coordinate space. This allows an object to have the same size irrespective of rotation.
Scaling restrictions also use a non-min or max Boolean option called negativeScaling. This determines whether or not target objects can be given negative scale. When true, negative scaling is allowed, that is, an object can be scaled so that it becomes mirrored, or a reversed image of itself. When false, this negative scaling is not permitted. This option can cause some complications with rotation, but for normal Transform Tool usage, it's not something you have to worry about. When you need to be concerned is when Creating Custom Controls.
Rotation restrictions allow an object's rotation to be bound between two different angles using minRotation and maxRotation. When restriction rotation between two angles, there are two different ranges rotation can be limited. Which range depends on which value is greater, minRotation or maxRotation.
The range or restricted rotation depends on which (min or max) is greater
Control sets define what interactive components (controls) make up the Transform Tool. They are assigned to the TransformTool through the controls property or the controls parameter of the TransformTool constructor.
var controlSet:ControlSetStandard = new ControlSetStandard(); var tool:TransformTool = new TransformTool(controlSet); // or tool.controls = controlSet;
Control sets are, themselves, nothing more than arrays where each element is an interactive control. Each of these controls become a direct child of the TransformTool instance. Control sets are designed specifically for convenience. They allow all controls of a Transform Tool to be neatly wrapped into a single class instance. All controls could otherwise be added to a TransformTool instance individually using addChild. The process of setting TransformTool.controls to a control set instance simply makes this process easier by handling the addChild calls internally.
Note: Dynamic TransformTool.controls
The value assigned to TransformTool.controls is not the same that is returned. TransformTool.controls is dynamically generated based on the current state of the display list of the TransformTool instance. If a child object as added to a TransformTool instance after controls was set, the new value of controls would be a new array with that child object included.
The Transform Tool comes with a number of pre-defined control sets defined in Array classes such as ControlSetStandard, but control sets can also be defined manually through similar Array subclasses or standard ActionScript arrays that contain any combination of control instances.
var customSet:Array = [ new ControlOrigin(), new ControlMove() ]; var tool:TransformTool = new TransformTool(customSet);
At any point in time, you can change the controls within a Transform Tool to another set or selectively add or remove controls on an individual basis.
The control sets included with the Transform Tool are as follows:
Use these control sets as a guide for making your own.
Control objects are display objects that, as children of a TransformTool instance, provide visuals and a point of interaction for manipulating the transformation defined by that tool.
Each control works individually to perform a specific task, such as scaling the Transform Tool (and it's target) or providing a means of moving the registration point. Some controls provide no interaction and instead simply serve as visuals. The ControlBorder control class, for example, simply draws a line around the border of the Transform Tool's target object.
Controls available with the Transform Tool include:
Each of these exist in one or more control sets other than ControlHiddenMultifunction. This particular control works as as a single, standalone control that can move, scale, and rotate an object without the need for any other, additional controls. Instead of existing within a set, it can just be added to the Transform Tool individually.
var tool:TransformTool = new TransformTool(); addChild(new ControlHiddenMultifunction());
Different combinations of CTRL, SHIFT, and ALT change the behavior of this control determining if a target object is moved, scaled, rotated, or having its registration point changed.
Other control types are usually used in combination with one or more additional controls. When using multiple controls in combination, use of control sets become more practical.
The controls included with the Transform Tool are each drawn dynamically. Basic styling options that give users some simple control over their default look and style. Most controls support the Control.fillColor, Control.fillAlpha, Control.lineColor, Control.lineAlpha, and Control.lineThickness properties, each of which allows you to determine how the control appears on the screen when drawn.
Control drawing occurs once a control is added to a TransformTool instance. If you change a styling property of a control that is already within a Transform Tool, you will need to force the control to redraw itself using the Control.draw() method.
Many controls will also allow you to use custom graphics in place of their dynamically drawn graphics. These controls check for the existence of a child display object in their display list. If present, they do not draw themselves normally, instead assuming the child display object will represent their graphic appearance.
Controls that do not allow this kind of styling are those which require to be dynamically drawn, such as ControlBorder and ControlBoundingBox.
Transform Tool cursors are defined by controls and regulated by the TransformTool class. Controls inform the Transform Tool which cursor is to be used when a cursor is required. The Transform Tool then dispatches a CURSOR_CHANGED event indicating that there was a change in the cursor state of the tool.
Cursors themselves are DisplayObject instances. They represent what display object is to be used to represent the cursor when a cursor is to be displayed. Cusors included with the Transform Tool are:
TransformTool instances do not display these objects on their own. Instead, they rely on external listeners of the CURSOR_CHANGED event to handle the displaying of cursors. The ControlCursor control will do this automatically for you if used as a control of the Transform Tool. Many control sets include this control to display cursor objects, though you may also decide to manage cursors on your own by listening for CURSOR_CHANGED without the ControlCursor control.
Controls included with the Transform Tool of the type ControlInteractive each use a ControlInteractive.cursor property to define their cursors. They have no default value; rather, control sets define which cursors are used by which controls.
Cursors styling mirrors that used by the standard Transform Tool controls. They contain similar styling properties: Cursor.fillColor, Cursor.fillAlpha, Cursor.lineColor, Cursor.lineAlpha, and Cursor.lineThickness; and are drawn using the Cursor.draw() method. Child display objects can also be used in place of their dynamic graphics.
Display objects in Flash Player do not have individual registration points. All transformations are based around the (0,0) coordinate location of their local coordinate space. The Flash Professional authoring tool provides a registration point for timeline-based animations, as seen in its own free Transform Tool, but this is lost once the SWF is compiled as that registration point is an authoring-time only feature.
The TransformTool class simulates its own registration point. This can be seen and modified using the ControlRegistration control which is in many of the control set classes that ship with the Transform Tool. The RegistrationManager class is used to keep track of registration points. The RegistrationManager class defines registration point defaults and stores the locations of altered registration points in memory so that they can be recalled again later.
Every TransformTool instance has an instance of a RegistrationManager, though multiple TransformTool instances can share the same RegistrationManager. If working with multiple TransformTool instances, it may be necessary to share registration managers so that a single object doesn't show different registration points depending on which tool is interacting with it.
var tool1:TransformTool = new TransformTool(new ControlSetStandard()); var tool2:TransformTool = new TransformTool(new ControlSetFull(), tool1.registrationManager);
Registration points normally default to the (0, 0) local coordinate location of target objects, but using the registration manager, they can be elsewhere using the RegistrationManager.defaultXY or RegistrationManager.defaultUV properties.
Transforming objects through the Transform Tool generally takes place through mouse interaction with controls. Click and drag on a control and the target object transforms based on the type of control clicked.
Transformations made by the tool are represented by a single matrix, TransformTool.calculatedMatrix. As a user interacts with controls within the Transform Tool, transforming a target object, this matrix is updated to represent the current transform of the tool. This matrix is then applied to the target. How transformations in this matrix are applied to the target object depends on the value of TransformTool.transformMethod.
There are two kinds of transform methods, TRANSFORM_MATRIX and TRANSFORM_PROPERTIES. The TRANSFORM_MATRIX method takes the value of calculatedMatrix and assigns it directly to the current target's transform.matrix. This is the most compatible approach and supports all transformations possible using the various controls within the Transform Tool.
The TRANSFORM_PROPERTIES approach uses DisplayObject properties over directly setting the transformation matrix. Specifically, the properties used are: width, height, and rotation. This approach does not support all types of transformations, specifically skewing. However, this approach does work play more nicely with components that expect to be resized using width and height rather than through their transformation matrix.
Without using controls, you can also transform a target object through the Transform Tool by manually changing the property values of calculatedMatrix. This can be done by directly changing the values in calculatedMatrix, or by means of helper methods like TransformTool.setWidth(), TransformTool.setHeight(), and TransformTool.setRotation(). When changing calculatedMatrix directly, changes will only be applied after TransformTool.update() is called.
Creating Custom Controls
Restrictions are applied to the calculatedMatrix in TransformTool.calculateTransform method. This happens after the pre and post transforms are applied but before the registration offsets. When this happens, TransformTool dispatches a cancelable TransformTool.RESTRICT event. Controls can listen for this event to apply their own restrictions, such as snapping, either in addition to the standard restrictions or by replacing them altogether by using Event.preventDefault() to prevent the default restriction behavior.
The TransformTool class uses two methods to apply restrictions: TransformTool.restrictScale() and TransformTool.restrictRotation(). Controls can choose to selectively call these in a RESTRICT handler if preventing the default behavior to apply either of those restrictions without the other. For example a control that only transforms rotation would not need to restrict scaling, so it may chose not to call restrictScale. If, however, a control that rotates does enforce restricted scaling, it would most likely not want to enforce negative scaling. The restrictScale method has an option for disabling this (enforceNegativeScaling). The reason is because rotation in affine transformations (used by Flash) uses the same values used by scaling. By restricting scale, you could inadvertently restrict rotation. Complications around this only arise for the Transform Tool, when negativeScaling is false.
Consider rotating an object. If you rotate it 180 degrees, it would appear upside down. Now consider instead first scaling it negatively along its width making it a mirror image of itself, then from that position scaling it negatively along it's height. Its new position is now the same as rotating it 180 degrees. If negative scaling is restricted, this position could not be achieved meaning rotation would also be affected.
Scaling and rotating can result in the same transformation
So when rotating and restricting, restricting scale should not enforce negative scaling allowing rotation to function properly. Controls that ship with the Transform Tool take care of this for you, but if you make your own controls, this will be something you should be aware of.
There's a standard order of operations when performing transformations with the Transform Tool. These operations represent the transformation lifecycle which typically starts when you first click on a control and ends once you release that control. The operations are as follows:
- Commit target (updating base matrix)
- Define a pre or post transform
- Calculate a final transformation matrix
- Update Metrics
- Update Target
- Update Controls
- Commit target
The setup step (1) happens when you first press a Transform Tool control. The TransformTool.baseMatrix is updated (1.1) to reflect the current state of the target object the tool is attached. Changes to the Transform Tool while interacting with the control are based off of this state to limit data corruption resulting from rounding errors.
While interacting with a control, the process step (2) is continuously repeating. For every movement, the pre and/or post transforms of the tool are updated (2.1.1) and a final calculatedMatrix is derived using those values and the base matrix (2.1.2). Then the tool and the target object are then updated (2.2).
The update process includes 3 steps. First, metric values are updated (2.2.1). These are references used to describe certain positions within the transform tool, such as the locations of the corners of the transform tool used by the controls to position themselves. Next, the target object is updated (2.2.2). This will only happen if livePreview is enabled, otherwise the target will only be updated during a commit. Finally the controls are updated (2.2.3).
Once the control has been released, a final transformation is applied when the target is committed again (3.1) with the most recent version of the calculated matrix.
When defining your own controls, especially those that do not extend the ControlInteractive class, or if you're applying transforms to the Transform Tool manually, these steps will need to be followed in order for your changes to take place.
Pre and Post Transformations
Object transformations in Flash are represented by a 3x3 matrix. In ActionScript, this is accessible through DisplayObject.transform.martrix. When an object is transformed, its current transformation matrix is combined with another transformation matrix - one that represents the transform being applied. The result is a new matrix that represents a combination of both matrices.
The order in which these matrices are combined can influence the outcome of the final transformation. For example, consider combining rotation and scaling on a square. If rotated first, then scaled, the final transformaiton results in what like a wide diamond. If scaled first, then rotated, the final transformation results in what looks like a rotated rectangle.
When controls within a Transform Tool apply transformations to the tool, they can control how a final transformation is calculated having contol over the order in which matrix transformations are applied.
The TransformTool class uses three matrices to calculate the final transformation matrix, TransformTool.calculatedMatrix. They include TransformTool.preTransform, TransformTool.baseMatrix, and TransformTool.postTransform. Each are processed in that order.
The preTransform matrix is a matrix that represents the first matrix of the calculated matrix. The baseMatrix is added to this transform to start. Use preTransform for skewing and scaling - anything that should happen within the natural, un-transformed coordinate space of the target object.
The baseMatrix represents the initial state of the target object's transformation when the Transform Tool control first started applying transformations to the object. This matrix should remain unchanged, allowing other transformations to be built on top of it.
The postTransform matrix is applied to the result of the combination of preTransform and baseMatrix. This transformation matrix has its transformations applied on top of those defined by the prior transformation matrices. Use this matrix for operations like rotation.