LazyGui
LazyGui copied to clipboard
Feature rich, visually minimalist GUI library for a smooth tinkering experience in Processing 3 or 4.

Table of Contents
- LazyGui is a GUI library for Processing
- How do I start using this?
- with PDE (the default Processing editor)
- with other IDEs like IntelliJ IDEA
- Minimal code example
- Control elements
- Slider
- Plot
- Color picker
- Gradient picker
- Button
- Toggle
- Text input
- Radio
- Hotkeys
- Mouse interaction
- Drawing the GUI manually
- Saving and loading values
- Create save
- Load save
- Paths and folders
- Creating a folder with the forward slash
- Escaping the forward slash
- Global path prefix stack
- Folder made by using the stack
- See the current stack for debugging
- Hide and show anything
- Has a value changed last frame?
- Folder visuals
- Constructor settings
- Live shader reloading
- Input
- Compatibility
- Dependencies
- Further reading
- How to contribute
- How to compile and run this library
LazyGui is a GUI library for Processing
Main ideas
- no need to register your windows or controls in
setup() - ask for values in
draw()at a unique path, which will- lazily initialize a control element
- place it in a window hierarchy
- return its current value
Quality of life features
- very customizable look and feel
- mouse or keyboard value input
- save / load your gui state as json files
- autosave on program exit
- autoload on program start
- hotkeys for common actions
- copy / paste any value or folder
- undo / redo any change
- reloading shaders at runtime
How do I start using this?
with PDE (the default Processing editor)
-
Find LazyGui in the Contribution Manager under the Libraries tab and click
Install -
See example sketches:
File -> Examples... -> Contributed Libraries -
Leaf through the offline javadocs:
Help -> Libraries Reference -> LazyGui
with other IDEs like IntelliJ IDEA
Get the latest jar file from releases and then import it into your project using your IDE as a standard java library just like you imported Processing.
Minimal code example
import com.krab.lazy.*;
LazyGui gui;
void setup(){
size(800,800,P2D);
gui = new LazyGui(this);
}
void draw(){
background(gui.colorPicker("background").hex);
}

The gui displays itself at the end of draw() and by default it shows a root folder that can't be closed with two built in folders
- options for tweaking the various gui settings
- saves for managing your save files
Control elements
- getters and setters initialize controls when first called
- subsequent calls at the same path use the existing control element
- optional default parameters are used when first called and then ignored
- visually, rows of controls are ordered by when they were first initialized
Slider

// simplest getter for an infinite slider
float x = gui.slider("x");
// alternative getters that specify defaults and constraints
gui.slider("x", defaultFloat);
gui.slider("x", defaultFloat, minimumFloat, maximumFloat);
// setters
gui.sliderAdd("x", floatToAdd);
gui.sliderSet("x", floatToSet);
- mouse wheel changes the selected precision when mouse is over the slider
- click and drag mouse horizontally - change value by (pixels * precision)
- supports keyboard input with mouse over the slider - tries to parse the string as Float or Int
- there is a
sliderInt()alternative that uses and returnsint
Plot

// simplest getter
PVector pos = gui.plotXY("position");
// alternative getters that specify defaults
gui.plotXY("position", defaultFloatXYZ);
gui.plotXY("position", defaultFloatX, defaultFloatY);
gui.plotXY("position", defaultPVector);
//setters
gui.plotSet("position", valueFloat);
gui.plotSet("position", valueFloatX, valueFloatY);
gui.plotSet("position", valuePVector);
- drag the grid with your mouse to change both X and Y at the same time
- keyboard input for both values with mouse over the grid
- change both of their precisions at the same time with the mouse wheel over the grid
- change just one of their precisions with mouse over one of the x,y sliders
- there is a
plotXYZ()variant with an extra Z slider (not connected to the grid)
Color picker

// simplest getter
PickerColor myColor = gui.colorPicker("background");
// use it with .hex in place of color
background(myColor.hex);
// alternative getters that specify the default color
gui.colorPicker("background", color(36));
gui.colorPicker("background", grayNorm); // 'norm' meaning float in the range [0, 1]
gui.colorPicker("background", hueNorm, saturationNorm, brightnessNorm);
gui.colorPicker("background", hueNorm, saturationNorm, brightnessNorm, alphaNorm);
// setters
gui.colorPickerSet("background", color(36));
gui.colorPickerHueAdd("background", hueToAdd);
- HSBA color picker with a hex string display
- returns a read-only PickerColor object with an integer 'hex' field
- this hex integer is the same thing as the Processing color datatype
- displays the correct color in any Processing color mode
- paste in values from sites like colorhunt.co
- copy and paste the hex value with mouse over the desired color row / preview / hex string
Gradient picker

// simple getter
PGraphics bgGradient = gui.gradient("background gradient");
image(bgGradient, 0, 0);
// alternative getter that specifies default color(s)
gui.gradient("name", color(255,0,150));
gui.gradient("name", new int[]{color(255,0,150), color(0,150,0), color(0,100,150)});
// special getter for a color inside the gradient at a position in range [0, 1]
// faster than texture.get(x, y) thanks to a color look up table
PickerColor myColor = gui.gradientColorAt("name", positionNorm);
- allows you to set the position and value of individual colors and get the result as a PGraphics
- output texture size is kept equal to main sketch size
Button

// getter that is only true once after being clicked and then switches to false
if(gui.button("do the thing!")){
println("it is done");
}
Toggle

- click to flip the boolean state
- off by default
// simple getter
boolean isToggledOn = gui.toggle("spam every frame")
if(isToggledOn){
println("I'm trapped in a string factory");
}
// alternative getter that specifies a default
gui.toggle("spam every frame", booleanDefault)
// setter
gui.toggleSet("spam every frame", booleanValue)
Text input

// simple getter
String userInput = gui.text("text header");
// getter that specifies a default content
gui.text("text header", "this default text can be edited");
gui.text("", "this will rename its parent folder");
// one time setter that also blocks any interaction when called every frame
gui.textSet("text header", "content")
| Mouse Hotkey | Action under mouse |
|---|---|
| Enter | insert new line |
| Delete | delete entire string |
| Backspace | delete last character |
- typing with mouse over the text appends to its last line
- see folder visuals on how to rename the parent folder at runtime using this text control with a specific path
- the text editor is pretty basic, but you can paste the text into it from elsewhere
Radio

// simplest getter
String mode = gui.radio("mode", new String[]{"square", "circle"});
if (mode.equals("square")) {
rect(175, 175, 50, 50);
} else {
ellipse(200, 200, 50, 50);
}
// getter that specifies a default
gui.radio("mode", stringArray, defaultOption);
// setter that changes the currently selected option
gui.radioSet("mode", "square");
// setter that specifies new options for an existing radio
gui.radioSetOptions("mode", new String[]{"square", "circle", "triangle"});
- opens a folder of toggles where setting one to true sets all others to false
- returns the selected option as a string
- changes to the options parameter will be ignored after the radio is first initialized
- the options can only be changed at runtime with
radioSetOptions() - instead of the
String[]array of options you can also useList<String>orArrayList<String>
Hotkeys
| Global hotkey | Action |
|---|---|
| H | Hide GUI / Show GUI |
| D | Close windows |
| I | Save screenshot |
| CTRL + Z | Undo |
| CTRL + Y | Redo |
| CTRL + S | New save |
| Mouse hotkey | Action on element under mouse |
|---|---|
| Right click | Close window |
| R | Reset value to default |
| CTRL + C | Copy value or folder |
| CTRL + V | Paste to value or folder |
Mouse interaction
Interacting with your sketch using the mouse can be very useful, but the GUI can get in the way, usually you don't want the sketch to react when you're dragging a slider in the GUI.
Unfortunately the GUI has no way to block the sketch from receiving the mouse event, but it can tell you whether the mouse has interacted with the GUI thanks to the isMouseOutsideGui() method.
void mousePressed(){
if(gui.isMouseOutsideGui()){
// do something at the mouse
}
}
see: isMouseOutsideGui(), MouseDrawing
Drawing the GUI manually
The GUI draws itself at the end of draw() by default, but you can override this by calling gui.draw() before that happens. The GUI will never draw itself more than once per frame, so the automatic execution is skipped when this is called manually.
This can be useful in cases like including the GUI in a recording or using the PeasyCam library where you probably want to display the GUI between cam.beginHUD() and cam.endHUD() to separate the GUI overlay from the camera controlled 3D scene.
see: draw(), PeasyCamExample
Saving and loading values
The GUI can save its current values to disk in a json file. It can also load these values to overwrite the current GUI state.
You can control this from the saves folder under the root window of the GUI. Any new, renamed and deleted save files will be detected by this window at runtime.

Create save
- create a new save with the button at
saves/create new saveorCTRL + S- or create a new save from code with createSave() or createSave(path)
- an autosave is created by default when the sketch exits gracefully (like by pressing the Escape key)
- the autosave includes endless loop detection that prevents autosaving
- you can edit this behavior in the
saves/autosave rulesfolder
Load save
- the sketch tries to load the latest save on startup
- this is usually helpful, but when bad values in a save are breaking your sketch, you can either delete the offending json file or use constructor settings to ignore it on startup
- load a save manually by clicking on its row in the
saveswindow- or load saves from code with loadSave(path).
- loading will not initialize any new control elements
- for a value to be overwritten in the current GUI its path needs to match exactly with the saved path for that value
- this means you lose saved values when you rename a folder or a control element
- but you can freely copy saves between sketches, the sketch name does not matter
Paths and folders
The path is the first string parameter to every control element function, and it must be unique.
It exists only in memory to inform the GUI - it's not a directory structure in any file storage.
The forward slash / is a reserved character used to make folders, but it can be escaped with \\ like this: \\/ which won't separate folders.
Creating a folder with the forward slash

float frq = gui.slider("wave/frequency");
float amp = gui.slider("wave/amplitude");
Escaping the forward slash

boolean state = gui.toggle("off\\/on");
Global path prefix stack
Repeating the whole path in every control element call can get tiresome, especially with multiple nested folders.
Which is why there's a helpful path stack that you can interact with using pushFolder() and popFolder().
Just like using pushMatrix() and popMatrix() in Processing, you can change your "current directory"
by pushing a new folder name to a stack with gui.pushFolder("folder name") and have every control element called after that be placed into that folder automatically
as if the contents of the whole current stack got prefixed to every path parameter you use while calling the GUI.
popFolder()doesn't have a parameter - it just returns by one level
You can nest a pushFolder() inside another pushFolder() - your path stack can be many levels deep.
Just remember to call popFolder() the same number of times - the stack does get cleared after the end of draw() before the GUI starts drawing itself, but it's better not to rely on that.
Folder made by using the stack
gui.pushFolder("wave");
float frq = gui.slider("frequency");
float amp = gui.slider("amplitude");
gui.popFolder();
See the current stack for debugging
println(gui.getFolder());
see javadocs: pushFolder(), popFolder(), getFolder()
Hide and show anything
You can hide folders and single elements from code, while still receiving their values in code - the only change is visual. This is helpful when you have a loop for folders whose paths differ by the index, and you create too many of these folders and then want to hide some of them. You can also use this to hide the default 'options' or 'saves' folders.
gui.hide("myPath") // hide anything at this path (the prefix stack applies here like everywhere else)
gui.show("myPath") // reveal anything previously hidden at this path
gui.hideCurrentFolder() // hide the folder at the current path prefix stack
gui.showCurrentFolder() // show the folder at the current path prefix stack if it has been previously hidden
Has a value changed last frame?
You can check whether a value has changed last frame with gui.hasChanged("myPath") and gui.hasChanged().
This can be useful when you don't want to do an expensive operation every frame but only when its controlling parameters change.
This works with single control elements, but it also works recursively through any child elements, so you can call it on a folder, and it will return true if any value nested under it has changed.
The result after a change is only true for one frame after a change and then gets reset to false for the next frame. These functions respect the current path stack. They do not initialize any new control elements or folders. Calling the function does not flip the value to false by itself.
See the UtilityMethods example which uses it to load a PFont whenever the user changes the font name or size.
Folder visuals
Runtime changes of what a folder row looks like in its parent window. This helps with organizing folders, especially with folder paths that differ only by the index inside a loop.
A folder will display a little 'on' light in its icon when at least one toggle inside the folder is set to true and its name matches one of the following:
- starts with
- "active"
- "enabled"
- "visible"
- equal to "" (empty string)
A folder will display a name editable at runtime when there is a text control whose name matches one of the following:
- starts with
- "label"
- "name"
- is equal to "" (empty string)
Constructor settings
You can initialize your gui with an extra settings object to set various global defaults and affect startup and exit behavior. Loading a save overwrites these, but you can also disable loading on startup here.
Here's how to use it in a builder chain where the ordering does not matter:
gui = new LazyGui(this, new LazyGuiSettings()
// set as false to not load anything on startup, true by default
.setLoadLatestSaveOnStartup(false)
// expects filenames like "1" or "auto.json", overrides 'load latest'
.setLoadSpecificSaveOnStartup("1")
// controls whether to autosave, true by default
.setAutosaveOnExit(false)
);
- for a list of all the options, see the LazyGuiSettings javadocs
Live shader reloading
This GUI includes the (slightly out of scope) ShaderReloader class that watches your shader files as you edit them and re-compiles them when changes are made. If an error occurs during compilation, it keeps using the last compiled state and prints out the error to console.
Example using a fragment shader:
String shaderPath = "template.glsl";
PShader shader = ShaderReloader.getShader(shaderPath);
shader.set("time", (float) 0.001 * millis());
ShaderReloader.filter(shaderPath);
For shader compilation to work, ShaderReloader needs a reference to a PApplet, so in setup():
- either call
new LazyGui(this)as seen in the minimal code example - or call
ShaderReloader.setApplet(this)in case you don't need the GUI in your sketch
Input
This GUI also includes the Input utility that makes it easier to see whether any number of keys are currently pressed on the keyboard. Processing only shows you one key at a time while this utility keeps track of past events and can tell you whether any char or keyCode was just pressed, is currently held down or if it was just released.
Example detecting CTRL + SPACE with Input class static methods:
boolean isControlDown = Input.getCode(CONTROL).down;
boolean spaceWasJustPressed = Input.getChar(' ').pressed;
if(isControlDown && spaceWasJustPressed){
println("ctrl + space pressed");
}
see: Input javadocs
Compatibility
LazyGui runs on all of these:
- Linux (tested on Ubuntu)
- Windows (tested on Windows 10)
- Mac OS
- including Silicon with its smooth() level limitation, fixed here
Dependencies
- This library is compiled with Processing 3.3.7, which makes it compatible with
- This library uses and includes gson-2.8.9, mainly due to its handy @Expose annotation
Further reading
- LazyGui javadocs with function comments going into more depth than this readme
- Processing examples for the PDE
- IntelliJ examples for use in an IDE like IntelliJ IDEA
- LazySketches - bigger sketches using this GUI in my other repo
- How to run this GUI in Kotlin
How to contribute
- Create a new GitHub issue if you don't find your problem already in there
- Talk to me on the dedicated library discord server
- If you want to code something yourself you can fork the repository, make some edits on the
developbranch (or make your own branch based ondevelop), test your changes and submit a pull request
How to compile and run this library
- Clone and open the library in IntelliJ IDEA
- Do
link gradle projectwhich should callgradle buildand set all the source folders correctly - The
com/krab/lazy/examples_intellijdirectory contains runnable examples which you can edit and run to test your changes to the GUI - If you're making a new feature, making a new example sketch there is probably a good idea