Table of Contents
- LazyGui is a GUI library for Processing
- How do I start using this?
- Minimal code example
- Control elements
- Hotkeys
- Mouse interaction
- Drawing the GUI manually
- Saving and loading values
- Paths and folders
- Constructor settings
- Window restoration
- Live shader reloading
- Input
- Compatibility
- Dependencies
- Further reading
- How to contribute
- How to compile and run this library
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
-
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
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.
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
- 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
// 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
// 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)
// 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
// 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
// getter that is only true once after being clicked and then switches to false
if(gui.button("do the thing!")){
println("it is done");
}
- 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)
// 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
// 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>
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 |
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
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
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 a new save with the button at
saves/create new save
orCTRL + 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 rules
folder
- 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
saves
window- 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
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.
float frq = gui.slider("wave/frequency");
float amp = gui.slider("wave/amplitude");
boolean state = gui.toggle("off\\/on");
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.
gui.pushFolder("wave");
float frq = gui.slider("frequency");
float amp = gui.slider("amplitude");
gui.popFolder();
println(gui.getFolder());
see javadocs: pushFolder(), popFolder(), getFolder()
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
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.
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)
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 (except for conflicting instructions):
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)
// windows will never 'restore' when loading a save (allowed once at startup by default)
.setWindowRestoreNever()
);
- for a list of all the options, see the LazyGuiSettings javadocs
When you load a save, the GUI will try to restore the window state to what it was when the save was made. This includes the position, size, and open/closed state of each window. There are three available modes, selected using the Constructor settings.
setWindowRestoreNever()
setWindowRestoreOnlyOnStartup()
<= default, a balance of startup convenience and control at runtimesetWindowRestoreAlways()
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
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
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
- 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
- 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
- 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
develop
branch (or make your own branch based ondevelop
), test your changes and submit a pull request
- Clone and open the library in IntelliJ IDEA
- Do
link gradle project
which should callgradle build
and set all the source folders correctly - The
com/krab/lazy/examples_intellij
directory 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