-
Notifications
You must be signed in to change notification settings - Fork 237
Library Basics
A very basic example to build a Processing library from scratch.
package libraryexample;
import processing.core.*;
public class BasicLibrary {
PApplet parent;
public BasicLibrary(PApplet parent) {
this.parent = parent;
parent.registerMethod("dispose", this);
}
public void dispose() {
// Anything in here will be called automatically when
// the parent sketch shuts down. For instance, this might
// shut down a thread used by this library.
}
}
Usually you'll need to pass this
to a library's constructor so that the library can access functions from PApplet
, e.g. graphics methods like line()
and stroke()
or loadImage()
.
Using a static initializer such as javaYourLibrary.init(this)
is discouraged because it's not a good way to write OOP code, and makes assumptions about how your library connects to a sketch, and it encourages a problematic assumption of a single instance of your library. If you need to share information across library instances, use static variables or methods behind the scenes, but don't expose them to the user through the initializer.
If you'd like to use constants such as RGB, use the following:
public class BoringLibrary implements PConstants {
which will make all the constants available to that class.
public void pre()
Method that's called just after beginDraw(), meaning that it can affect drawing.
public void draw()
Method that's called at the end of draw(), but before endDraw().
public void post()
Method called after draw has completed and the frame is done. No drawing allowed.
public void mouseEvent(MouseEvent e)
Called when a mouse event occurs in the parent sketch. Drawing is allowed because mouse events are queued, unless the sketch has called noLoop().
public void keyEvent(KeyEvent e)
Called when a key event occurs in the parent sketch. Drawing is allowed because key events are queued, unless the sketch has called noLoop().
public void stop()
Called to halt execution. Can be called by users, for instance movie.stop() will shut down a movie that's being played, or camera.stop() stops capturing video. server.stop() will shut down the server and shut it down completely. May be called multiple times.
public void dispose()
Called to free resources before shutting down. This should only be called by PApplet
(do not call it from your own code). The dispose()
method is what gets called when the host sketch is being shut down, so this should stop any threads, disconnect from the net, unload memory, etc.
public void pause()
Called on Android when the sketch is paused (usually sent to the background).
public void resume()
Called on Android when the sketch is resumed.
To register any of these methods with the parent, call parent.registerMethod("_methodname_", this)
, replacing _methodname_
with the name of the function (pre, draw, post, etc) that you'd like to use.
Note that making these methods public
is extremely important. When running inside Processing, anything left blank has public added by the preprocessor, meaning void draw()
actually becomes public void draw()
after preprocessing.
You can only draw inside of pre()
, draw()
, mouseEvent()
, or keyEvent()
otherwise you will run into problems. If using OpenGL to render, for instance, you can easily make your machine (or at least the sketch) crash hard if you mess with the drawing surface when you're not supposed to. pre()
and draw()
happen while legitimate drawing is taking place, and the mouse/key events happen just before draw()
events are called, they're queued up by the host sketch until it's safe to draw. The exception is when noLoop()
has been called, and events have to fire immediately (otherwise a sketch wouldn't be able to restart itself on key or mouse input).
For this reason, you should use registerMethod("mouseEvent")
and mouseEvent()
(and same for the keys) to handle your events, rather than your class implementing MouseListener
from Java's AWT. (In addition, AWT is not available with OpenGL, nor with Android, so your code wouldn't work properly there.)
Note that method mouseEvent(MouseEvent event)
has a parameter of type MouseEvent. Because the MouseEvent
is a subclass of processing.event.Event
it is independent from Java's AWT.
To figure out what the mouse event is throwing back at you, this would be an example handler:
public void mouseEvent(MouseEvent event) {
int x = event.getX();
int y = event.getY();
switch (event.getAction()) {
case MouseEvent.PRESS:
// do something for the mouse being pressed
break;
case MouseEvent.RELEASE:
// do something for mouse released
break;
case MouseEvent.CLICK:
// do something for mouse clicked
break;
case MouseEvent.DRAG:
// do something for mouse dragged
break;
case MouseEvent.MOVE:
// do something for mouse moved
break;
}
}
Check out the code and API for the processing.event
package for more advanced features. Since 2.x, these are no longer AWT events, so you cannot use AWT methods on them.
Let's say you have a library named Babel. It would be distributed in a file called babel.zip
which contains a folder called babel
. That contents of babel.zip
would look like:
The properties file:
babel/library.properties
The main library code:
babel/library/babel.jar
Any other supporting .jar
files:
babel/library/support.jar
The core
library included with Processing is a thorough example of how a more complicated library directory should be laid out.
Processing 4 includes significant changes to how native libraries are loaded, because of the proliferation of new platforms (Apple Silicon, Raspberry Pi devices) that need to be supported. Read more about native code handling here.
The export.txt
file is not longer used in Processing 4. It was a relic of the “applet” era of Processing 1.x and 2.x, and is better handled by the folder layout described at the link above.
You must add a file named library.properties
to the base directory of your library. That file should look something like this:
# The name of your library as you want it formatted.
name = Most Pixels Ever
# List of authors. Links can be provided using the syntax [author name](url).
authors = [Daniel Shiffman](http://www.shiffman.net/) and Chris Kairalla
# A web page for your library, NOT a direct link to where to download it.
url = http://www.mostpixelsever.com/
# The category (or categories) of your library, must be from the following list:
# "3D" "Animation" "Compilations" "Data"
# "Fabrication" "Geometry" "GUI" "Hardware"
# "I/O" "Language" "Math" "Simulation"
# "Sound" "Utilities" "Typography" "Video & Vision"
#
# If a value other than those listed is used, your library will listed as
# "Other". Many categories must be comma-separated.
categories = Hardware
# A short sentence (or fragment) to summarize the library's function. This will
# be shown from inside the PDE when the library is being installed. Avoid
# repeating the name of your library here. Also, avoid saying anything redundant
# like mentioning that it's a library. This should start with a capitalized
# letter, and end with a period.
sentence = Framework for spanning Processing sketches across multiple screens.
# Additional information suitable for the Processing website. The value of
# 'sentence' always will be prepended, so you should start by writing the
# second sentence here. If your library only works on certain operating systems,
# mention it here.
paragraph =
# Links in the 'sentence' and 'paragraph' attributes can be inserted using the
# same syntax as for authors.
# That is, [here is a link to Processing](http://processing.org/)
# A version number that increments once with each release. This is used to
# compare different versions of the same library, and check if an update is
# available. You should think of it as a counter, counting the total number of
# releases you've had.
version = 22 # This must be parsable as an int
# The version as the user will see it. If blank, the version attribute will be
# used here. This should be a single word, with no spaces.
prettyVersion = 0.98a # This is treated as a String
# The min and max revision of Processing compatible with your library.
# Note that these fields use the revision and not the version of Processing,
# parsable as an int. For example, the revision number for 2.2.1 is 227.
# You can find the revision numbers in the change log:
# https://raw.githubusercontent.com/processing/processing4/master/build/shared/revisions.md
# Only use maxRevision (or minRevision), when your library is known to
# break in a later (or earlier) release. Otherwise, use the default value 0.
minRevision = 0
maxRevision = 227
Note: The
.properties
file should be encoded as UTF-8. This is different from other.properties
files in Java (reference) that use ISO 8859-1 encoding.
Since Processing 2.0, libraries that are advertised on Processing's website also have the option of being advertised through the PDE.
You must include the following attributes in library.properties
:
name
authors
url
categories
sentence
version
You should also set the minRevision
and maxRevision
attributes to indicate the Processing revisions compatible with your library. Note that these fields use the revision and not the version of Processing (e.g. the revision number for 2.2.1 is 227). Only use maxRevision
(or minRevision
), when your library is known to break in a later (or earlier) release. Otherwise, use 0
, which is the default value.
You will need to provide a link to the latest version of your library, and host a copy of your library.properties
alongside it. The result on your server should look something like this:
https://www.example.com/awesomelib.zip (The latest version of awesomelib)
https://www.example.com/awesomelib.txt (A copy of the library.properties file contained in awesomelib.zip)
Once everything is in place, send your static links to the Processing Librarian: Elie Zananiri. We'll review your properties file to make sure everything is in order and add your contribution to the list of advertised libraries.
On our end, we'll only record the location of awesomelib.txt
, and we'll assume that a copy of the library file is hosted at an address by the same name, except ending in zip
. Each day, a script on processing.org will scrape the information from all the .txt files and aggregate it into a single file, which is then used by each user to browse what's available and check for updates. Even if your library is not being advertised, you should still include the library.properties
file inside your zip.
When you want to release a new version of your library, here's what you'll need to do:
- Update your
library.properties
file inside your.zip
- Increment the value of
version
. This is an integer that we'll use to check for updates. You can think of it as a counter, counting the total number of releases you've had. - Write whatever you want for the new value of
prettyVersion
(as long as it's a single word with no spaces).
- Copy your latest release to the static address you gave us earlier (e.g. http://www.example.com/awesomelib.zip)
- Copy the
library.properties
file from your latest release to a.txt
file hosted at an address by the same name as your.zip
, except instead ending in.txt
(e.g. http://www.example.com/awesomelib.txt)
Option 2: Update using the Processing Library Template
- Update your
build.properties
file from theresources
folder.
- Increment the value of
library.version
. This is an integer that we'll use to check for updates. You can think of it as a counter, counting the total number of releases you've had. - Write whatever you want for the new value of
library.prettyVersion
(as long as it's a single word with no spaces).
- Recompile the project using the Ant script. This will generate the appropriate files in the
distribution/name-version/download
folder. - Copy your latest release and properties files to the static address you gave us earlier (e.g. http://www.example.com/awesomelib.zip and http://www.example.com/awesomelib.txt)
That's it. The next time the aggregator script runs and your users start the PDE, they'll be notified that updates are available.
So long as the code is inside a package, it can be set up for use as a library. For instance, if you want to make a library called poo
set up a folder as follows:
poo →
library →
poo.jar
Then, the folder should be placed in the libraries folder inside the user's sketchbook folder to be recognized by Processing and its "Import Library" menu. You must first restart Processing in order to use the library.
While this process may sound a little complicated, the intent is to make it easier for users than a typical Java IDE. A little added complexity for the developers of library code (who will generally be more advanced users) is traded for great simplicity by the users, since Processing is intended to target beginning programmers.
If your library is sonia.jar
, found at sonia/library/sonia.jar
, all the packages found in sonia.jar
will be added as imports into the user's sketch when they selected "Import Library".
In the case of Sonia, an additional JAR file can be found in the sonia/library/
folder, jsyn.jar
. The contents of jsyn.jar
will not be added to the import statements. This is to avoid every library having a ridiculously large number of import statements. For instance, if you want to use the video library, you don't want all 15-20 packages for the QuickTime libraries listed there to confuse your users.
Bottom line, if you want packages from the other JARs to be loaded by Processing, then you need to put those .class
files into the main .jar
file for the library (sonia/library/sonia.jar
in this case).
Since your code is inside a package, you need to make sure that it's inside subfolders in the .jar
file. It should be noted that JAR files are simply .zip
files (they can be opened with WinZip or Stuffit) with a "manifest" file.
In the past, you may have used:
javac *.java
to compile your files. Once they're inside a packages, you must use:
javac -d . *.java
which will create folders and subfolders for the packages. For instance, for all the stuff in processing.core.*
it would create:
processing/ ->
core/ ->
PApplet.class
PGraphics.class
..etc
then you can jar that stuff up using:
jar -cf core.jar processing
or with a command line zip utility:
zip -r core.jar processing
All "Import Library" does is add the import yourlibrary.*
; statement to the top of your sketch. If you've handwritten the import statements, then there's no need to use "Import Library".
So that your library can notify the host applet that something interesting has happened, this is how you implement an event method in the style of serialEvent
, serverEvent
, etc.
import java.lang.reflect.*;
public class FancyLibrary {
Method fancyEventMethod;
public FancyLibrary(PApplet parent) {
// your library init code here...
// check to see if the host sketch implements
// public void fancyEvent(FancyLibrary f)
try {
fancyEventMethod =
parent.getClass().getMethod("fancyEvent",
new Class[] { FancyLibrary.class });
} catch (Exception e) {
// no such method, or an error.. which is fine, just ignore
}
}
// then later, to fire that event
public void makeEvent() {
if (fancyEventMethod != null) {
try {
fancyEventMethod.invoke(parent, new Object[] { this });
} catch (Exception e) {
System.err.println("Disabling fancyEvent() for " + name +
" because of an error.");
e.printStackTrace();
fancyEventMethod = null;
}
}
}
Many methods in PApplet
are made static for use by other libraries and code that interfaces with Processing. For instance, createInput()
requires an applet object, but there's a version of loadStrings()
that is a static method that can be run on any InputStream
. See the developer's reference for more information about which methods are available.
To open files for use with a library, use the createInput()
method. This is the most compatible means for loading data, and makes use of many hours of headaches that were the result of attempts to create functions that loaded data across platforms (Mac, Windows, and Linux) and circumstances (applet, application, and other). Similarly, to get an OutputStream
for writing files, use createOutput()
.
The functions sketchPath()
, sketchFile()
, savePath()
, dataPath()
, and createPath()
also facilitate reading and writing files relative to the sketch folder. They should be used to ensure that file I/O works consistently between your library and functions like loadImage()
or loadStrings()
. Their documentation can be seen as part of the developer's reference for PApplet
. The variable sketchPath
is also available for convenience, but in nearly all cases, the sketchPath()
method is a better (and more compatible) route. The sketchFile()
method works like sketchPath()
, except that it wraps the path as a java.io.File
object instead of the full path as a String
.
Processing 4.x uses Java 17. If you compile your library code with a later Java release, it may be incompatible with the Java 17 class file format, which will give users a message like:
java.lang.UnsupportedClassVersionError: blah/SomeClass (Unsupported major.minor version 62.0)
This is because later versions of Java use their own class file format that's not backwards compatible.
When compiling a library, use something like:
javac -source 17 -target 17 -d . -classpath /path/to/core.jar path/to/java/source/*.java
Libraries, or classes inside them, should not be prefixed with "P" the way that the core Processing classes are (PImage
, PGraphics
, etc). It's tempting to prefix everything that way to identify it with Processing, but we'd like to reserve that naming for “official” things that are inside processing.core
and other associated classes.
Same goes for using “Processing”, “Pro”, or “P5” just like “P”, or whether it's a prefix or a suffix.
If, however, your library is a Processing port of or a bridge to another framework, you can name it something along the lines of “XXX for Processing”.
Similarly, please don't use processing
as the prefix for your library packages. We need to keep that name space clear for official things as well.