-
Notifications
You must be signed in to change notification settings - Fork 59
Extending lienzo
The Serialization framework changed significantly in version 1.1. See Extending Lienzo 1.0 for Lienzo version 1.0. |
To add a new Shape class:
- extend the class from Shape
- add new attribute names (if necessary)
- add new attribute types (if necessary)
- write Javascript wrapper class for new attribute type (if necessary)
- add getters/setters for its attributes
- implement
draw
method - add support for copying and Serialization
- add an IFactory
- register IFactory with FactoryRegistry
- add getFactory method to Shape class
To illustrate the process we'll add a fictitious Pear shape. The Pear has two properties: "pearWidth", which is a double and "pearity", which is represented by the Java object "Pearity". The Javascript object that represents a "Pearity" has the following attributes: "flavor" (string), "leafCount" (number) and "leafColor" (color String).
Somehow these attributes describe the Pear's pearness and influence how the Pear will be drawn :-)
public class Pear extends Shape<Pear>
{
public static final ShapeType PEAR_SHAPE = new ShapeType("Pear");
}
"Pear"
is used as the shape type identifier when serializing the shape.
Look at ShapeType Javadoc and NodeType Javadoc for the type names used by Lienzo, to avoid conflicts.
The Attribute
class is a type-safe, extensible enumeration of all possible Node attributes.
When adding a new Shape class, it may be necessary to define new Attributes.
To add a new Attribute for PEAR_WIDTH
and PEARITY
we need to create two static variables (anywhere will do, we'll put them inside the Pear class):
public final static Attribute PEAR_WIDTH = new Attribute("pearWidth", "pearWidthLabel",
"pearWidthDescription", AttributeType.NUMBER_TYPE);
public final static Attribute PEARITY = new Attribute("pearity", "pearityLabel",
"pearityDescription", Pear.PEARITY_TYPE);
The first argument will be used internally as the attribute key. It is also used when deserializing the node to JSON.
The 2nd and 3rd value (label and description) values are placeholders for attribute sheets in bean editors. These values should be internationalized if you want to share your new class with international users.
The last argument describes what AttributeType the values of the new attribute have.
When adding a new Attribute
, it may be necessary to add a new AttributeType
(but try to use the existing AttributeTypes if possible.)
AttributeType
is a type-safe, extensible enumeration of attribute types for all possible Node attributes.
Here are some of the built-in AttributeType
s:
AttributeType | Java Type | Javascript Type | Notes |
---|---|---|---|
STRING_TYPE | String | String | |
NUMBER_TYPE | int, Integer, double, Double etc. | Number | |
BOOLEAN_TYPE | boolean | Boolean | |
COLOR_TYPE | String | String | Any valid CSS Color String. See Color. |
POINT2D_TYPE | Point2D | Object with attributes: x (Number), y (Number) | |
POINT2D_ARRAY | Point2DArray | Array with Point2D objects | |
SHADOW_TYPE | Shadow | Object with attributes: color (String), blur (Number), offset (Number) | |
DASH_ARRAY_TYPE | DashArray | Array of Numbers | |
LINEAR_GRADIENT_TYPE | LinearGradient | Object with attributes: start, end, colorStops and type="LinearGradient" |
start and end are Point2D objects. colorStops is an Array of Objects with stop (Number) and color (String) attributes. |
PATTERN_GRADIENT_TYPE | PatternGradient | Object with attributes: image (TODO), repeat (see enum FillRepeat) and type="PatternGradient" | |
RADIAL_GRADIENT_TYPE | RadialGradient | Object with attributes: start, end, colorStops and type="RadialGradient" |
start and end are Objects with x (Number), y (Number) and radius (Number) attributes. colorStops is an Array of Objects with stop (Number) and color (String) attributes. |
IMAGE_TYPE | |||
DRAG_BOUNDS_TYPE | DragBounds | Object with attributes: x1, y1, x2, y2 (all Numbers) | |
FILL_TYPE | Color, LinearGradient, PatternGradient or RadialGradient | see above | |
STROKE_TYPE | String | String (same as COLOR_TYPE) |
Each enumeration has its own type (the internal value is the string value obtained with EnumWithValue.getValue
):
AttributeType | Enum | Javadoc |
---|---|---|
LINE_CAP_TYPE | LineCap | Javadoc |
LINE_JOIN_TYPE | LineJoin | Javadoc |
DRAG_CONSTRAINT_TYPE | DragConstraint | Javadoc |
TEXT_ALIGN_TYPE | TextAlign | Javadoc |
TEXT_BASELINE_TYPE | TextBaseLine | Javadoc |
COMPOSITE_OPERATION_TYPE | CompositeOperation | Javadoc |
ARROW_TYPE | ArrowType | Javadoc |
Here is an example for adding the new AttributeType
for the "pearity".
Pearity will need a custom validator. This is used during deserialization.
public static AttributeType PEARITY_TYPE = new AttributeType(new PearityValidator());
To represent the "pearity" type, we would probably write a Java class called "Pearity". It's a wrapper around a Javascript object. We normally create a separate class that extends JavaScriptObject (PearityJSO in this case.) See the Lienzo source code for examples on how to implement these classes (e.g. DragBounds).
public static class Pearity
{
private final PearityJSO m_jso;
public Pearity(PearityJSO jso)
{ ... }
public final PearityJSO getJSO()
{
return m_jso;
}
public static final class PearityJSO extends JavaScriptObject
{ ... }
}
public Pear setPearity(Pearity pearity)
{
if (null == pearity)
getAttributes().delete(PEARITY.getProperty());
else
getAttributes.put(PEARITY.getProperty(), pearity.getJSO())
return cast();
}
public Pearity getPearity()
{
JavaScriptObject jso = getAttributes().getObject(PEARITY.getProperty());
if (null == jso)
return null;
else
return new Pearity(jso);
}
public Pear setPearWidth(double pearWidth)
{
getAttributes().put(PEAR_WIDTH.getProperty(), pearWidth);
return this;
}
public double getPearWidth()
{
return getAttributes().getDouble(PEAR_WIDTH.getProperty());
}
The draw method in the Shape class should assume that the Transform related attributes and fill/stroke related attributes have already been applied to the Context2D.
Here is the draw method for the Circle class. Note that it draws the center at (0,0) because the Transform related attributes (such as X and Y) have already been applied to the Context2D.
public void draw(Context2D context)
{
context.beginPath();
context.arc(0, 0, getRadius(), 0, Math.PI * 2, true);
context.closePath();
}
If the new Node class is a type of ContainerNode, it should also draw its children here. See the source code for Group.draw() for an example. (Usually you don't need to derive from ContainerNode. Just use a Group with child nodes instead.)
By adding support for Serialization to JSON we get a copy
method for free.
copy
simply serializes to JSON and then deserializes the JSON into a new object.
All we need to do is add validators for the new AttributeTypes, in this case PearityValidator
which was used by PEARITY_TYPE above.
public class PearityValidator extends ObjectValidator
{
public PearityValidator ()
{
super("Pearity");
// the 3rd parameter indicates whether the attribute is required
addAttribute("flavor", StringValidator.INSTANCE, true);
addAttribute("leafCount", NumberValidator.INSTANCE, true);
addAttribute("leafColor", ColorValidator.INSTANCE, true);
}
}
public static class PearFactory extends ShapeFactory<Pear>
{
public PearFactory()
{
super(Pear.PEAR_SHAPE);
addAttribute(PEAR_WIDTH, true); // true indicates that it's a required attribute
addAttribute(PEARITY, true);
}
@Override
public Pear create(JSONObject node, ValidationContext ctx)
{
return new Pear(node);
}
}
If you're implementing a subclass of ContainerNode, extend the ContainerNodeFactory instead. This requires you to also implement the isValidForContainer
method to restrict the which child nodes can be added.
And register the factory with FactoryRegistry:
FactoryRegistry registry = FactoryRegistry.getInstance();
registry.registerFactory(new Pear.PearFactory());
In the Pear class, add a method that returns the PearFactory:
@Override
public IFactory<Pear> getFactory()
{
return new PearFactory();
}
Also add the Pear constructor that takes a JSONObject.