The intricate stuff

So you made it here. Congratulations!

In this part I'll explain the deeper mechanics of the library. Maybe you're interested in contributing to the library; or want to create highly customized compontents; or you are just interested in how such a Framework works. Then you're right here. Or perhaps you just came here by accidents or because of the obscure title ;). Nevermind.

Let's get serious.

Basically, the only thing Processing does for this library is drawing the stuff and provide some functions to draw text, rects, lines etc. How everything is arranged, which items come on top of others, when to render what, HOW the stuff should look like is what guiSET deals with. Processing also tells us when the user pressed a key or did something with the mouse. But we need to decide which items are allowed to process the mouse events and how it is determined if the mouse is over a specific element.


Outline


Short summary about the inheritance of the component classes

Control is the (abstract) base class for all visual components. Container subclasses Control. As do Slider and the again abstract TextBased class. It is the base for all components that feature text in some form. These are Button, Label, Checkbox, MenuItem and ListItem.

Frame, VFlowContainer, HFlowContainer, ScrollArea, ListView and FlowContainer all extend Container.

VFlowContainer and HScrollContainer subclass the according flow containers and MultilineTextbox and Textbox are extensions of these two.

Finally we have Knob (inherits from Slider), ContextMenu (MenuItem), MenuBar (HFlowContainer) and MenuTextbox as well as MenuSeparator (both from MenuItem).

Graphics and updating

TODO: Default behaviour is now "unbuffered rendering", meaning no temporary PGraphics are created. This is more memory-efficient. Should be explained properly in this section.

Each instance of a component has a PGraphics object that stores the current look as pixel image. Its content is only changed within the render() method. Containers draw the PGraphics of their children onto their own PGraphics and so on. The Frame instance finally draws itself onto the sketch (the PApplet).

This way of rendering and storing is chosen to keep the number of drawing processes as low as possible. If an element is changed visually, render() will be called and thus the graphics updated. But any siblings or children do not need to be re-rendered as their look is still stored in their own PGraphics object. However, this comes at a prize of memory. This is not a problem with modern computers and moderate or even generous numbers of elements. If, nevertheless, you aspire to have a list with a few thousand items displayed on the screen, this technique can get expensive. I'm thinking about adding functionalty to enable rendering every time the graphics are required, without storing the image. This descision could be per-item.

Each time the user of the library sets the state of a property that changes the look of the element, the setter will call the update() method which sets a dirty flag and also initiates a propagation through the tree structure way up to Frame, setting the dirty flag for every parent, 'grand'-parent etc.

At render time, Frame checks if it needs to redraw itself and if so, proceeds to render all changed children in the described way.

A container is always responsible for drawing its content items. For that purpose, in its own render() method it calls the function renderItem(Control c, int x, int y) for each item, specifying with x and y where the item shall be placed on the containers own PGraphics. renderItem(Control c, int x, int y) takes care of checking if the item is 'dirty' or (semi-) transparent (but does not check for visibility). Here is also where i.e. flow or scroll containers give their items a new location.

For example this is what the implementation of render() in Container looks like.

@Override
protected void render() {
  drawDefaultBackground();

  for (Control c : content) {
    if (c.isVisible()) {
      renderItem(c, c.getX(), c.getY());
    }
  }
  drawDefaultDisabled();
}

In VFlowContainer the x and y properties of the item are ignored and replaced by other rules:

@Override
protected void render() {
  drawDefaultBackground();

  int usedSpace = getPaddingTop();

  for (Control c : items) {
    if (c.isVisible()) {
      renderItem(c, c.getMarginLeft() + getPaddingLeft(), usedSpace + c.getMarginTop());
      usedSpace += (c.getHeight() + c.getMarginTop() + c.getMarginBottom());
    }
  }
  drawDefaultDisabled();
}

More details about the render procedure

TODO: Explain the different "renderers" (unbuffered/buffered).

The actual rendering takes place like this: the container usually draws its own background and then calls renderItem(Control c, int x ,int y). This method sets the offsetX/offsetY properties of c according to given coordinates and sets them outside the container if the opacity is 0. This is not necessary if visibility is set to false because mouseEvent(int, int) already checks for visiblity. If the graphics of c are "dirty", preRender() is called which creates the PGraphics instance (only if size has changed) and calls pg.beginDraw(). Then the items custom render() method is executed and finally tint applied for opacity.

Mouse Events

guiSET uses relative coordinates (relative to parent) to make mouse event checks.

Generally, mouse events are received by Frame from the sketch (PApplet). As a container it checks if the mouse is currently locally within the space it takes up and if so, passes the event on to all children which in turn do the same.

In fact, it is sort of the other way round: After checking if mouse coordinates are within the containers bounds rectangle, it first gives the children the chance to process the event as they are always on top of and not below the container. Afterwards the container calls its own event routine.

This way the topmost elements (high z-coordinate) in the application get the event first. They can then call stopPropagation() which sets a flag that no more mouse events should be processed. This way you can click on a menu item without firing a button beneath the menu strip. PRESS, RELEASE and DRAG always stop the propagation.

Still, containers can override the method boolean containerPreItemsMouseEvent(int x, int y) which is called before the event is passed on to the children and if returns false, will prevent the children from receiving the event. Scroll containers i.e. use this to implement a scroll bar.

As mentioned above, containers use the method renderItem(Control c, int x, int y). This method also stores the given x and y in the elements properties offsetX and offsetY. These are the actual coordinates relative to the parent and they are the ones that are checked when this element receives a mouse event and it needs to decide whether the mouse is within itself or not.

When a container receives a mouse event (parent calls mouseEvent(int x, int y)), it passes x and y on to the children but subtracting the own offset coordinates. During setup Frame registers a mouseEvent(MouseEvent) method at Processings PApplet. This is called by PApplet before draw() each time there is a new mouse event. Frame then starts the mouse event propagation within guiSET. The MouseEvent object is stored in the static field Control.currentMouseEvent, is not passed around while the event is recursively handled but every element can access it.

Events in general

The programmer can add certain listeners to his objects. Mouse listeners are available for all classes. They can be assigned and given a callback method with the addMouseListener() method, specifying the type with a string ("press", "release"...). If no target is specified with the overloaded addMouseListener() method, papplet will be assumed.

How MouseListener works: Frame registers a mouseEvent() method at PApplet. Every container (also Frame) calls the mouseEvent for all its items. When an element gets a mouseEvent, it can decide to stop the propagation by calling stopPropagation(). This way elements with high z-index, which will be checked first and can prevent lower elements from getting the mouseEvent. This is needed for example with menus, spinners, popups etc.

Moreover classes like Frame and Textbox feature key listeners or an itemchanged-listener (ListView, MenuItem). These listeners are usually assigned with extra methods like setItemChangeListener() etc.

How to make an event listener in a new class

An example with no arguments.

protected EventListener myListener;
public void setMyListener(String methodName, Object target) {
    myListener = createEventListener(methodName, target, null);
}
public void setMyListener(String methodName) {
    setMyListener(methodName, getPApplet());
}
public void removeMyListener(){
    myListener = null; // setting listener to null is the way to do it
}
protected void someFunctionThatChangesObervedState(){
    // do stuff
    handleEvent(myListener, null);
}

An example with arguments (in this case a MouseEvent)

protected EventListener myListener;
public void setMyListener(String methodName, Object target) {
    myListener = createEventListener(methodName, target, MouseEvent.class);
}
public void setMyListener(String methodName) {
    setMyListener(methodName, getPApplet());
}
public void removeMyListener(){
    myListener = null; // setting listener to null is the way to do it
}
protected void someFunctionThatChangesObervedState(){
    // do stuff
    MouseEvent eventThatWeGotFromSomewhere;
handleEvent(myListener, eventThatWeGotFromSomewhere);
}

If arguments shall be passed to the callback function, then pass their class to the createEventListener() method as third argument. When handling the event, pass the argument here.

PApplet and the static Frame

No big deal: for creating the PGraphics instances we need to call PApplets createGraphics(int, int) method. Therefore we always need a reference to the sketch class which is solved by passing this to the Frame constructor in the sketch code.

Big deal: for getting things like text-width or text-leading we also need the PApplet or at least a functioning PGraphics which can only be achieved properly by having the PApplet (at least to my knowledge). Unfortunately, textWidth is needed for autosizing before the graphics are created for the first time during rendering. Autosizing may be performed multiple times during setup when setting text or padding of an element. In the current solution, a static PFont is created at the time that PApplet is know and it can be used by every element. Somehow, though the values differs just a little from the results of pg.textWidth(String) although I copied my implementation from Processings source code. If anyone knows the solution, please tell me.

Now, as we only need one instance of Frame per sketch and as every element needs access to it, it is stored in the static Frame.frame0 variable. This one is never null as it is filled by a dummy (called nullFrame) and replaced when using the Frame(PApplet parent) constructor.