A plugin for Processing
Processing is a very popular and simple development tool for writing java-like code, targeted towards artists. Processing is dedicated to developing small, extremely lightweight and simple interactive applets and this orientation affects most of its design and implementation decisions. It presents a slightly cut down version of the Java language — casts, imports, and class preambles are removed — in a cut down runtime environment. Push "play" and your files are manipulated back into Java, sent to Jikes and the resulting class-file run. Push "stop" and the applet disappears. Change your code, push "play" and so on.
Field has very little to say on the subject of making small Applets — while we respect the pedagogical ambitions of Processing, it's not something we've done or are interested in doing. What Processing does have is a wide-ranging community of users who are, uniquely, sharing ideas and code in a vibrant way. It's hard to ignore that.
This page is slightly developer-oriented. For a more straightforward tutorial: SimpleProcessingTutorial.
A PApplet / Field bridge
Can Field talk Processing? A pleasant afternoon yielded the ProcessingPlugin which seems to prove that we can replace Processing's text editor with the Field environment.
While one could, via the magic of Jython, define a perfectly valid PApplet subclass in Python and then load and run it using any technique that loads and runs Applets, this would really miss the point — or rather, simply duplicate what Processing already achieves in a different language. The trick is to understand that Processing, having packaged up its now-Java code, essentially wants to run all of its users's code during the PApplet.draw() at which point PApplet has conspired to have the necessary valid drawing context active. Any python code that is executed by the Field programmer needs to be executed "inside" that method.
If you want to skip to the punchline (and miss the Field-internal-developer level documentation below) it's this: anything that you can write in Processing in Java (then compile, then run, the stop, then edit etc) you can write with Field's text editor (without any compilation, stopping &c); any visual element can tagged such that its execution plays nicely with PApplet — these visual elements are just like any other, they can be executed by other elements, they can appear in time-lines, they can have GUI elements in them etc... Multiple code fragments can be cooperating simultaneously, or stopping and starting on demand. The PApplet is never torn down and restarted.
This transforms Processing in a way that is simultaneously gentle and radical. Gentle, because if we take, say, the BezierEllipse example that ships with Processing we can write it in Python/Field, line for line:
smooth() background(145); controlPtCol = 0x222222ff anchorPtCol = 0xbbbbbbff strokeColor = 0xf02030ff width=500 height=500 #draw ellipse with anchor/control points def drawEllipse(): strokeWeight(1.125); stroke(strokeColor); noFill(); #create ellipse for i in range(0, pts): bezier(px[i], py[i], cx[i], cy[i], cx2[i], cy2[i], px[(i+1)%len(px)], py[(i+1)%len(px)]) strokeWeight(1.75); stroke(0); rectMode(applet.CENTER); # control handles and tangent lines for i in range(0, pts): line(px[i], py[i], cx[i], cy[i]) for i in range(0, pts): fill(controlPtCol) noStroke(); #control handles ellipse(cx[i], cy[i], 4, 4); ellipse(cx2[i], cy2[i], 4, 4); fill(anchorPtCol); stroke(0); #anchor points rect(px[i], py[i], 5, 5); def setEllipse(points, radius, controlRadius): global pts pts= points; angle = 360.0/points; controlAngle1 = angle/3.0; controlAngle2 = controlAngle1*2.0; global px, py, cx, cy, cx2,cy2 r = range(0, 360, 360/points) px = [width/2+cos(radians(angle))*radius for angle in r] py = [height/2+sin(radians(angle))*radius for angle in r] cx = [width/2+cos(radians(angle+controlAngle1))* controlRadius/cos(radians(controlAngle1)) for angle in r] cy = [height/2+sin(radians(angle+controlAngle1))* controlRadius/sin(radians(controlAngle1)) for angle in r] cx2 = [width/2+cos(radians(angle+controlAngle2))* controlRadius/cos(radians(controlAngle1)) for angle in r] cy2 = [height/2+sin(radians(angle+controlAngle2))* controlRadius/sin(radians(controlAngle1)) for angle in r] def ongoing(): background(145) setEllipse(int(random(3, 12)), random(-100, 150), random(-100, 150)); drawEllipse(); _r = ongoing
Radical because we can cause this to start running by option-clicking on this element in the canvas. But unlike Processing we can alter the colors of the elements while it is running: either by typing (and hitting command-return), or with an embedded slider, or with some more code (a script, a time-slider, anything). Unlike Processing we can then add another set of drawing commands, in a completely different visual element, with a different lifecycle, that draw to the same canvas (they only have to agree on which one of them is going to call background(...)). If this has piqued your interest, take a look at (and contribute to) the SimpleProcessingTutorial.
Internal information (Developers only)
The solution turned out to be simple. Field already has the abstractions needed to execute code remotely — in place from OpenEnded's multi-machine works. Remotely, in this case, is not over the network to another Field instance, it's remote in time: it's inside PApplet's draw method. Two interfaces need to be implemented and exposed via properties iExecutesPromise — responsible for executing visual elements as a single unit (option-clicking or timeline dragging) and PythonPluginEditor.EditorExecutionInterface responsible for executing fragments of text inside a text editor. Both can defer their execution to later without much problems, except for the EditorExecutionInterface's executeReturningValue method used for autocompletion and inspection. In this case it can return a "Future" (strictly a LocalFuture) that promises to reveal it's value some point later.
This is two files — it would be one, but we want to defer the loading of the PApplet class until the ProcessingPlugin has had a chance to extend the classpath to include the necessary files from its parasitized Processing installation.
The rest is sugar. Firstly, Processing-ers are used to having incredibly flat namespaces, so we 'inject' functions bound to methods of the PApplet instance directly into globals() with this outrageous piece of Python:
def bind(meth, inst): globals()[meth.__name__]=meth for n in dir(applet): print n try: aa = getattr(applet, n) bind(aa, applet) except: pass
Ongoing work
A beta for the Processing Plugin is nearly complete #19 (we've gotten together something that looks like a good Beta on Marc's machine right now). This is very good news — demand for a Processing compatible Field has been large.