/** * This code is provided to you under the Creative Commons license, under attribute/non-commercial restrictions. * If you wish to use this code commercially, please contact me (not just so you can use the code, but because I'm * also interested in knowing what the product will be). You are allowed to modify this code to suit your taste, of course, * but please credit me in some way so that people can find the original work and associated author, so they can * discuss things with me when they have questions =) * * Michiel Kamermans, 2010 * pomax at nihongoresources dot com */ // ========================================================= // Event Driven Framework code // ========================================================= /** * A UI component has the same event handlers that canvas has, except they're * only called (thanks to the framework code all the way at the bottom) when they * are triggered inside the component's bounding box. */ class Component { boolean visible = true; // visibility boolean hasfocus = false; // focus boolean debug = false; // debug status int xoffset = 0; // nested positioning x-offset int yoffset = 0; // nested positioning y-offset // empty constructor Component() {} // draw method void draw() {} // set offsets void setOffsets(int x, int y) { xoffset=x; yoffset=y; } int getXOffset() { return xoffset; } int getYOffset() { return yoffset; } // debug void setDebug(boolean d) { debug = d; } // handle visibility void setVisible(boolean v) { visible = v; } boolean isVisible() { return visible; } // event handling checks: mouse boolean listensAt(int x, int y) { return false; } // event handling checks: keybaord boolean listensForKeyPress() { return false; } // event handling checks: keyboard boolean listensForKeyRelease() { return false; } // event handling super methods void mouseMoved(int mouseX, int mouseY) {} void mouseClicked(int mouseX, int mouseY) {} void mouseDragged(int mouseX, int mouseY) {} void mousePressed(int mouseX, int mouseY) {} void mouseReleased(int mouseX, int mouseY) {} void keyPressed(int key, int keyCode) {} void keyReleased(int key, int keyCode) {} // action listening from other components void actionPerformed(Component source, int event, String action) {} // focus listening void setFocus(boolean f) { hasfocus = f; } boolean hasFocus() { return hasfocus; } void focusReceived() {} void focusLost() {} } /** * Think of this as the master list of UI component */ class Components { ArrayList components; Component focussed = null; Components() { components = new ArrayList(); } void add(Component component) { components.add(component); } Component get(int index) { return (Component) components.get(index); } int size() { return components.size(); } // cascades a debug down void setDebug(boolean debug) { for(int c=0; c=0; c--) { Component component = ((Component)components.get(c)); if(component.listensAt(mouseX,mouseY)) { if(focussed!=component) { if(focussed!=null) { focussed.setFocus(false); focussed.focusLost(); } component.setFocus(true); component.focusReceived(); focussed = component; } else if(!focussed.hasFocus()) { focussed.setFocus(true); focussed.focusReceived(); } component.mouseMoved(mouseX,mouseY); cfound = true; break; }} if(!cfound && focussed!=null) { focussed.focusLost(); focussed=null; } redraw(); return cfound; } // standard cascade void mouseClicked(int mouseX, int mouseY) { for(int c=components.size()-1; c>=0; c--) { Component component = ((Component)components.get(c)); if(component.listensAt(mouseX,mouseY)) { component.mouseClicked(mouseX,mouseY); break; }} redraw(); } // standard cascade void mousePressed(int mouseX, int mouseY) { for(int c=components.size()-1; c>=0; c--) { Component component = ((Component)components.get(c)); if(component.listensAt(mouseX,mouseY)) { component.mousePressed(mouseX,mouseY); break; }} redraw(); } // standard cascade void mouseDragged(int mouseX, int mouseY) { for(int c=components.size()-1; c>=0; c--) { Component component = ((Component)components.get(c)); if(component.listensAt(mouseX,mouseY)) { component.mouseDragged(mouseX,mouseY); break; }} redraw(); } // standard cascade void mouseReleased(int mouseX, int mouseY) { for(int c=components.size()-1; c>=0; c--) { Component component = ((Component)components.get(c)); if(component.listensAt(mouseX,mouseY)) { component.mouseReleased(mouseX,mouseY); break; }} redraw(); } // standard cascade void keyPressed(int key, int keyCode) { for(int c=0; c=0) { if(bounds.getMinX() > inflectionpoint) { bounds.setMinX((int)inflectionpoint); } else if(bounds.getMaxX() < inflectionpoint) { bounds.setMaxX((int)inflectionpoint); }}} if(0<=t2 && t2<=1) { double inflectionpoint = evaluateX(t2); if(inflectionpoint>=0) { if(bounds.getMinX() > inflectionpoint) { bounds.setMinX((int)inflectionpoint); } else if(bounds.getMaxX() < inflectionpoint) { bounds.setMaxX((int)inflectionpoint); }}} } // recompute bounds projected on the y-axis, if the control points lie outside the bounding box y-bounds // no comments, because it's virtually identical code. Kept as duplicate, though, to emphasise that we have // to do this for the x-axis and y-axis separately. if(!bounds.inBoundsY(cy1) || !bounds.inBoundsY(cy2)) { double a = dcy0; double b = dcy1; double c = dcy2; if(a+c != 2*b) { b+=0.01; } double numerator = 2*(a - b); double denominator = 2*(a - 2*b + c); double doubleroot = (2*b-2*a)*(2*b-2*a) - 2*a*denominator; double root = sqrt((float)doubleroot); double t1 = (numerator + root) / denominator; double t2 = (numerator - root) / denominator; if(0<=t1 && t1<=1) { double inflectionpoint = evaluateY(t1); if(inflectionpoint>=0) { if(bounds.getMinY() > inflectionpoint) { bounds.setMinY((int)inflectionpoint); } else if(bounds.getMaxY() < inflectionpoint) { bounds.setMaxY((int)inflectionpoint); }}} if(0<=t2 && t2<=1) { double inflectionpoint = evaluateY(t2); if(inflectionpoint>=0) { if(bounds.getMinY() > inflectionpoint) { bounds.setMinY((int)inflectionpoint); } else if(bounds.getMaxY() < inflectionpoint) { bounds.setMaxY((int)inflectionpoint); }}} } } // evaluates the x-projection of the bezier curve for parameter t double evaluateX(double t) { double it = (1-t); return ((double)x*it*it*it + 3.0*(double)cx1*t*it*it + 3.0*(double)cx2*it*t*t + (double)x2*t*t*t); } // evaluates the y-projection of the bezier curve for parameter t double evaluateY(double t) { double it = (1-t); return ((double)y*it*it*it + 3.0*(double)cy1*t*it*it + 3.0*(double)cy2*it*t*t + (double)y2*t*t*t); } // override - instead of drawing a straight line, we draw the bezier curve void drawLine() { bezier(x+xoffset,y+yoffset, cx1+xoffset,cy1+yoffset, cx2+xoffset,cy2+yoffset, x2+xoffset,y2+yoffset); } // override on move, because not only do we have to move our start and end point, but also our // bezier control points. void move(int dx, int dy) { super.move(dx,dy); this.cx1 += dx; this.cy1 += dy; this.cx2 += dx; this.cy2 += dy; } // run through the bezier parametric curve and see if we pass the provided coordinate. // this check can be optimised by setting the step size for t based on what the bezier // parameters in the constructor are. however, for simplicity (right now), we simply use // step 1/100 so we'll probably always catch any mouseover boolean onLine(int x, int y) { for(double t=0.0; t<=1.0; t+=0.01) { int ftx = (int) evaluateX(t); int fty = (int) evaluateY(t); if(abs(ftx-x)<=bounding_thickness && abs(fty-y)<=bounding_thickness) { return true; }} return false; } } class Label extends Drawable { String label = ""; Label(String l, int x, int y) { super(x,y); label=l; } // draw void draw() { super.draw(); text(label, x+xoffset, y+yoffset); } // getter and setter String getLabel() { return label; } void setLabel(String l) { label=l; } } // ========================================================= // Extended components // ========================================================= class Button extends Component { public int BUTTON_PRESSED = 0; public int BUTTON_UNPRESSED = 1; private boolean pressed = false; ArrayList actionlisteners = new ArrayList(); private Drawable face; private String action; public Button(Drawable face, String action) { this.face=face; this.action=action; release(); } void draw() { face.draw(); } void setDebug(boolean debug) { face.setDebug(debug); } void setStroke(int r, int g, int b, int a) { face.setStroke(r,g,b,a); } void setFill(int r, int g, int b, int a) { face.setFill(r,g,b,a); } void setOffsets(int x, int y) { face.setOffsets(x,y); } // standard getter/setters int getX() { return face.getX(); } int getY() { return face.getY(); } void setX(int v) { face.setX(v); } void setY(int v) { face.setY(v); } BoundingBox getBoundingBox() { return face.getBoundingBox(); } void move(int dx, int dy) { if(face!=null) { face.move(dx,dy); }} // superancestral methods for determining whether focus and events should propagate boolean listensAt(int x, int y) { return face.listensAt(x,y); } boolean inArea(int x, int y) { return face.inArea(x,y); } // getter Drawable getFace() { return face; } // action listeners void addActionListener(Component c) { actionlisteners.add(c); } String getActionString() { return action; } // coloring void setNormalColors() { setStroke(0,0,0,255); setFill(255,255,255,255); } void setMouseoverColors() { setStroke(255,0,175,255); } void setClickedColors() { setStroke(0,0,0,255); setFill(200,200,255,255); } // button has been pressed void press() { pressed = true; setClickedColors(); for(int a=0; a