/**
* A general purpose SVG outline layer.
*/
class SVGLayer extends SVGItem
{
/**
* The layer ID, plus setter
*/
int layerid=-1;
void setLayerId(int l) { layerid = l; }
/**
* The hooking indicator. If true, this lets the sketch
* interact with the page by running plain javascript code
*/
boolean hooked = false;
void setHooked(boolean h) { hooked = h; }
/**
* general administration
*/
String pathstring; // the SVG outline path
String svgstring; // the SVG representation of this layer
XMLElement xmldoc; // the XMLElement representation of that SVG code
PShape layershape; // the PShape that wraps that XMLElement
/**
* The SVG Control Box for this SVG layer
*/
SVGControlBox controlbox;
void updateControlBox() {
controlbox = new SVGControlBox(this, getWidth(), getHeight()); }
/**
* The state indicator that tells us whether or not this
* layer is currently selected by the user
*/
boolean selected = false;
void setSelected(boolean s) { selected = s; }
boolean isSelected() { return selected; }
/**
* The state indicator that tells use whether or not this
* layer is "lit", the result of the layer being considered
* highlighted.
*/
boolean lit=false;
boolean isLit() { return lit; }
/**
* What to do when we highlight this layer: color the path
*/
void highlight() { if(!lit) { lit=true; setPathColor(highlight_path_color); }}
/**
* What to do when we lose highlight for this layer: make the path the default color again
*/
void unhighlight() { if(lit) { lit=false; setPathColor(default_path_color); }}
/**
* The various path colors we use in this layer
*/
String default_path_color = "black";
String selected_path_color = "#220099";
String highlight_path_color = "#990022";
String current_path_color = default_path_color;
/**
* set the current_path_color to some value. If this value is different
* from what it was, update the layer by coloring the path this new color
*/
void setPathColor(String color) {
if(!current_path_color.equals(color)) {
current_path_color = color;
colorPath(); }}
/**
* The child position of the
element in its containing
* in the SVG code for this layer
*/
int g_index_for_path;
/**
* colors the path by setting the SVG "fill" attribute to whatever "current_path_color" is
*/
void colorPath() {
XMLElement g = xmldoc.getChild(0);
XMLElement path = g.getChild(g_index_for_path);
path.setAttribute("fill",current_path_color);
rebindXML(); }
/**
* Layer position information.
* xpos and ypos are in screen coordinates.
*/
int xpos = 0;
int ypos = 0;
int getXPos() { return ((int)xpos); }
int getYPos() { return ((int)ypos); }
/**
* setters, [v] in screen coordinates
*/
void setXPos(int v) {
xpos=v;
if(hooked) {
document.getElementById("layer"+layerid+"xpos").setAttribute("value", getXPos()); }
updateControlBox(); }
void setYPos(int v) {
ypos=v;
if(hooked) {
document.getElementById("layer"+layerid+"ypos").setAttribute("value", getYPos()); }
updateControlBox(); }
/**
* Repositioning, [v] in screen coordinates
*/
void moveRight(int v) { setXPos(getXPos()+v); }
void moveLeft(int v) { setXPos(getXPos()-v); }
void moveUp(int v) { setYPos(getYPos()-v); }
void moveDown(int v) { setYPos(getYPos()+v); }
// reposition based on another layer
void setPosition(SVGLayer other) { setXPos(other.getXPos()); setYPos(other.getYPos()); }
/**
* Layer scaling, based on the fact that SVG units do not
* correspond to screen units in the slightest. The scaling
* is achieved by setting the appropriate "scale" transgorm
* in the SVG element.
*/
float xscale=1.0;
float yscale=1.0;
float getXScale() { return xscale; }
float getYScale() { return yscale; }
/**
* setters
*/
void setXScale(float v) { xscale = v; updateControlBox(); }
void setYScale(float v) { yscale = v; updateControlBox(); }
void setScale(float v) { setXScale(v); setYScale(v); }
/**
* Converts full SVG units to scaled units,
* according to this layer's scaling factors
*/
float getXScaled(float v) { return getXScale() * v; }
float getYScaled(float v) { return getYScale() * v; }
/**
* Flip administration
*/
boolean xflip = false;
boolean yflip = false;
boolean getXFlip() { return xflip; }
boolean getYFlip() { return yflip; }
/**
* trigger methods.
*/
void flipX() { xflip = !xflip; }
void flipY() { yflip = !yflip; }
/**
* Repositioning variables, used for computing layer movement
* (such as through mouse or keyboard manipulation)
*/
int xmark = 0; // marks the location from which we're moving
int ymark = 0;
int xdiff = 0; // marks the difference with respect to the mark
int ydiff = 0;
/**
* forms the SVG header for this layer
*/
String makeHeader()
{
String header = makeSVGHeader();
// handle flipping
if(xflip&&yflip) { header += ""; }
else if(xflip) { header += ""; }
else if(yflip) { header += ""; }
else { header += ""; }
// show layer dimensions
header += ""; g_index_for_path++;
// add svg outline
header += "" + makeSVGFooter(); }
/**
* And then finally, the layer's constructor
*
* path: the outline path string.
* width: the glyph's advance width.
* height: the glyph's quad height (not outline shape height!).
* xflip: flag to indicate whether or not to render this layer flipped horizontally.
* yflip: flag to indicate whether or not to render this layer flipped vertically.
*/
SVGLayer(String path, int width, int height, boolean xflip, boolean yflip)
{
super(width,height);
this.pathstring = path;
this.xflip=xflip;
this.yflip=yflip;
bindXML();
}
/**
* This method rebuilds the SVG string, and then bind the layer's PShape
* so that it reflects the current content.
*/
void bindXML() {
controlbox = new SVGControlBox(this, getWidth(), getHeight());
svgstring = makeHeader() + pathstring + makeFooter();
xmldoc = new XMLElement(svgstring);
layershape = new PShapeSVG(xmldoc);
}
/**
* This method rebinds the layer's PShape to reflect any changes made to the local XMLElement
*/
void rebindXML() { layershape = new PShapeSVG(xmldoc); }
/**
* Draws this layer onto the canvas, using the shape(PShape, x, y, width, height) call,
* then tells the control box to draw itself.
*/
void draw()
{
int x = getXPos()+xdiff;
int y = getYPos()+ydiff;
int w = (int)(getWidth()*getXScale());
int h = (int)(getHeight()*getYScale());
shape(layershape, x, y, w, h);
controlbox.draw();
}
/**
* This method checks whether a (mouse) coordinate falls inside this layer
*/
boolean contains(int mx, int my)
{
int x = getXPos()+xdiff;
int y = getYPos()+ydiff;
int w = getXScaled(getWidth());
int h = getYScaled(getHeight());
// evaluate
boolean xin = (x<=mx && mx<=(x+w));
boolean yin = (y<=my && my<=(y+h));
return xin && yin;
}
// ---------------
// Mouse Handlers
// ---------------
// processing has no mouseOver/mouseOut event handling
boolean mouseover = false;
int this_cursor = MOVE;
int getCursor() { return this_cursor; }
void mouseMoved(int mx, int my) {
// handle only if controlbox didn't handle
if(!controlbox.mouseMoved(mx,my)) {
if(contains(mx,my)) {
if(!mouseover) { highlight(); }
controlbox.setVisible(true);
cursor(this_cursor);
mouseover = true; }
else if(mouseover) {
mouseover=false;
mouseOut(mx,my); }}}
void mouseOut(int mx, int my) {
unhighlight();
controlbox.setVisible(false); }
void mousePressed(int mx, int my) {
// handle only if controlbox didn't handle
if(!controlbox.mousePressed(mx,my) && mouseover) {
selected = true;
xmark = mx-xdiff;
ymark = my-ydiff; }}
void mouseReleased(int mx, int my) {
controlbox.mouseReleased(mx,my);
// always handle
if(mouseover) {
selected = false;
setXPos(xpos + xdiff);
setYPos(ypos + ydiff);
xdiff=0;
ydiff=0; }}
void mouseDragged(int mx, int my) {
// handle only if controlbox didn't handle
if(!controlbox.mouseDragged(mx,my) && mouseover && selected) {
xdiff = mx-xmark;
controlbox.setXDiff(xdiff);
ydiff = my-ymark;
controlbox.setYDiff(ydiff); }}
}