Event and paint cascading for Processing, implemented using Processing.js

Processing, the visualisation programming language, uses a javalike syntax, with full support for Object Oriented programming, but does not come with its own cascaded event and paint model. These are relatively easy to implement (at least naively), and so what follows is a simple framework that you can download and use in your own code. I personally use it for work that I do in processing.js, the javascript library for using raw Processing (or P5, as it's also known) code inside script tags, or by script file inclusion.

Component cascading

Basic processing code

The main key in this framework is that every event runs through a master "Components" container, so all draw instructions, mouse events and key events are sent to it:

 toggle code 

You will notice a reference to the method initUI(), which is a convenient place to put all your UI initialisation and component filling.

Components container

The code for the Components object is relatively straight forward, and takes care of three things: 1) event cascading, 2) draw() cascading and 3) focus tracking based on a "focus follows the mouse" principle.

 toggle code 

The component class

Of course, we can't do any event/draw cascading without Component objects, so there is also a superancestor for components. Components can have focus or not, be visible or not, be set to debug mode or not, and can indicate whether they are interested in mouse events, key presses, and key releases:

 toggle code 

Drawable components

While reasonably a component should be drawable, those properties are actually part of things that are "more" than just a component, so we stick those properties in a new superancestor, called "Drawable". This is an extension on Component with things like stroke and fill properties, as well as methods that offer an interface to the active drawable surface:

 toggle code 

Drawable components have a location in their parent container, and have stroke and fill color properties. In addition, the "listensAt" method now refers to the inArea method, so that subclasses only have to implement an "inArea" check for interfacing with the drawable surface

Bounding boxes

All drawable components have a bounding box, which is basically just a rectange that indicates the extremities of a shape. For some shapes the bounding box is simply defined by the x and y coordinates of the points on the shape, such as lines and rectangles, but for some shapes the bounding box depends on the shape of the line segments drawn, rather than the shape's points, such as for circles and bezier curves.

 toggle code 

Normally, the bounding box is hidden, but because it's always good to be able to play around with, it can be made visible by setting a component's debug flag to true

Using objects instead of draw primitives

The point of an event/draw tree is that you can treat what you draw as individual components. So let's make that happen: lets define the basic drawing primitives in Processing as proper objects instead:

Ellipse

 toggle code 

Circle

technically this is not a Processing primitive, you'd use ellipse(x,y,d,d), but it's worth giving its own class.

 toggle code 

Rect

 toggle code 

Line

 toggle code 

Bezier

The bezier curve is a spectacular beast, mostly because its bounding box and "is the mouse hovering over it" computation are rather funky bits of math:

 toggle code 

Extended components

Just primitives only get us so far, so in this framework there are also two extra, extended components: The Panel, and the Button.

Panels

Panels allow us to define a region on the screen, and place components relative to that region. For example, if we define a 200x200 region of the screen, with its top-left corner at 150x250, then we can add a component to the panel docked nicely to the upper-left corner by giving the component coordinates 0x0, rather than coordinates 150x250. This is really useful! The code for this is not very complicated:

 toggle code 

Buttons

Buttons are generally useful when it comes to making your code do something based on functional mouse events. Of course, in processing.js you can use on-page UI elements, rather than in-sketch elements, but then you are making your sketch impossible to load in Processing... so let's look at the button definition too:

 toggle code 

In order for a button to trigger functionality in another Component, that component needs to have been added as a listener to the button, using button.addActionListener(component). This will then automatically make the button trigger the component's actionPerformed(source, action) method, with the "source" being the button, and "action" being an action string as defined by the button, for switch running.

Putting it together

Let's look at a practical example - A panel with lots of clickable "buttons". For the purpose of this exercise, we'll use rect, ellipse, circle and bezier buttons. First off, the code we'll use in our initUI():

 toggle code 

And then the code that actually defines our custom clickable panel, and the button objects we will be using.

We want our main panel to be a dull grey that changes to white when it has focus, changing to blue when it is clicked. We want our buttons white with a black stroke, unless they receive focus, in which case their stroke should be pink. A pressed button is a dull blue-grey. Bezier curve "buttons" are black, unless they have focus, in which case they're pink, or they've been pressed, in which case they're dull blue-grey. Finally, we also want an extra "nonsense" panel below our main panel, and we want it to change to a random colour every time any button is clicked, but not when the main panel is clicked.

 toggle code 

So... what does this sketch look like?

I'll let you come up with a name for this sketch yourself, but my associations are marine biology inspired...

Note that the big white border is actually still part of the sketch - the sketch itself takes up 600x600 pixels, but the panel with content is only 400x400, and placed at an offset of 100x100. Note that the bezier curves may be bigger than the main panel, so they'll be drawn "outside" what you might think is the drawing surface!

Get the code

To save you some copy/paste work, click here to download the complete source code file!