An immediate Mode GUI Library for Picotron
Consider a donation!
Through Ko-fi:
Through Buy me a coffee:
- PGUI
- Table of contents
Just copy the the source code, a file called pgui.lua
from the src folder in this repository, into your Picotron project folder.
Then include the file in your main Picotron code with include "pgui.lua"
Because this this GUI library follows the Immediate Mode pattern, all GUI components are updated and rendered each frame in the program. The code execution is divided in two parts, following the game loop used in Picotron: an update part, where most calculations for the GUI are made and you get return values from the components, and a draw part, where the graphical elements of the components are rendered.
For pgui to work, you have two put two basic functions:
pgui:refresh()
at the beginning of your _update function. This will restart the list of components to render and make some general calculations.
pgui:draw()
in your _draw function. This will render the components. If you pass a function as argument, it will call it before rendering layer 4 (usually not necessary).
Additionally, in _update, after the refresh function, you put all the components that are part of your desired GUI. There is a set of basic components like buttons, text input boxes, radio buttons, checkboxes, sliders, and others, and there are some layout components that can nest other components and are useful for grouping and organizing, like dropdowns, vertical and horizontal stacks, a covenience top menu bar, and a scrollable container.
All components are created by using the component function. The first argument is the name of component, and the second is a table containing options and values:
pgui:component(NAME, {OPTIONS})
Components return values that you can use as you like in your code. In most cases, you have to feed the return value back into a particular option of the component so it can keep track of its state.
This is a simple example of a slider controlling the size of a circle:
include "pgui.lua"
function _init()
-- Define the initial value of the slider
slidervalue = 10
end
function _update()
-- Refresh pgui each frame
pgui:refresh()
-- Create a slider and set its value back from its return value
slidervalue = pgui:component("hslider",{pos=vec(190,20),value=slidervalue})
end
function _draw()
cls(5)
-- Draw the circle, its size is based on the return value of the slider
circfill(240,140,slidervalue,8)
-- Draw all pgui components
pgui:draw()
end
This is the detailed list of component available in the library, their options with default values and return values.
NOTE 1: If you want to use default values, you can omit them in the options table you pass to the component function.
NOTE 2: Some components need to store some persistent information. To achieve this, an internal dictionary keeps track of the state of the component. For these components you need to include a unique label in the options. Specially if you have more than one of the same component, so the library can differentiate between them.
NOTE 3: There are a couple of options that ALL components have, so they will not be listed but you can set them:
- pos. vector. The position of the component relative to its container (or to the app's window if it has no container). Default:
pos=vec(0,0)
. - color. table. The color palette used. Default:
color= {7,18,12,0}
. See the palette section below. - layer. number. Order of rendering. Default:
layer=0
. pgui normally just renders the components in the order they are created, and that's enough for most cases. However, if you want more control over it, you can set a higher layer number. pgui always renders dropdowns contents one layer higher than its parent toggle button.
Just a filled rectangle with a border
pgui:component("box",{size=vec(16,16),stroke=true,active=false,hover=false})
Options:
- size. vector. Size of the component in width and height.
- stroke. boolean. Show border or not.
- active. boolean. Change color when clicked.
- hover. boolean. Change color when hovered on.
Returns: "box". string.
A box with text inside
pgui:component("text_box",{text="TEXTBOX",margin=2,stroke=true,active=false,hover=false})
Options:
- text. string. Text inside the box.
- margin. number. Margin separating text from border from all sides.
- stroke. boolean. Show border or not.
- active. boolean. Change color when clicked.
- hover. boolean. Change color when hovered on.
Returns: "text_box". string.
A box with a sprite inside
pgui:component("sprite_box",{sprite=0,margin=2,stroke=true,active=false,hover=false,fn=function() end}})
Options:
- sprite. number. Number of sprite in spritesheet.
- margin. number. Margin separating sprite from border from all sides.
- stroke. boolean. Show border or not.
- active. boolean. Change color of box when clicked.
- hover. boolean. Change color of box when hovered on.
- fn. function. Function to run before drawing the sprite. This can be used to set colors transparence with
palt()
before drawing the sprite.
Returns: if was clicked. boolean.
A text input box with a cursor
pgui:component("input",{label="input",text="INPUT",margin=2,charlen=16})
Options:
- label. string. REQUIRED. Unique name for keeping internal state.
- text. string. Text inside the box.
- margin. number. Margin separating text from border from all sides.
- charlen. number. Maximum characters allowed.
Returns: text. string.
A button
pgui:component("button",{text="BUTTON",margin=2,stroke=true,disable=false})
Options:
- text. string. Text inside the box.
- margin. number. Margin separating text from border from all sides.
- stroke. boolean. Show border or not.
- disable. boolean. Disable clicking
Returns: if was clicked. boolean.
Note: when clicked, it will return true for just one frame!
A horizontal slider
pgui:component("hslider",{min=0,max=100,value=50,size=vec(100,10),stroke=true,format=function(v) return v end,flr=false}})
options:
- min. number. Minimum value allowed.
- max. number. Maximum value allowed.
- value. number. Current value of slider.
- size. vector. Size of the component. Width and height.
- format. function. Function to format the value display inside the slider.
- flr. boolean. floor / snap value to an integer
Returns: value. number.
Radio buttons for selecting one of multiple options in a list
pgui:component("radio",{gap=3,r=3,sep=4,selected=1,options={}})
Options:
- gap. number. Vertical gap between options.
- r. number. Radius of selector.
- sep. number. Separation between selector and option text.
- selected. number. Index of currently selected option.
- options. table of strings. Text for each one of the options
Returns: selected. number.
Buttons for selecting multiple options in a list
pgui:component("multi_select",{gap=3,box_size=7,sep=4,options={},selected={}})
Options:
- gap. number. Vertical gap between options.
- box_size. number. Size of selector button. Just one number for width and height because it's a square.
- sep. number. Separation between selector and option text.
- options. table of strings. Text for each one of the options
- selected. table of booleans. for each option, true indicates it is selected, false indicates it is not.
Returns: selected. table of booleans.
A toggle button with text
pgui:component("checkbox",{text="CHECKBOX",box_size=8,sep=4,value=false})
Options:
- text. string. Descriptive text for the checkbox.
- box_size. number. Size of selector button. Just one number for width and height because it's a square.
- sep. number. Separation between selector and option text.
- value. boolean. If the checkbox is activated or not.
returns: value. boolean.
Shows selectable sample boxes from a list of colors
pgui:component("palette",{columns=4,gap=3,box_size=10,colors={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},selected=0})
Options:
- columns. numbers. Max number of samples per rows.
- gap. number. Vertical and horizontal gap between samples.
- box_size. number. Size of each sample. Just one number for width and height because it's a square.
- colors. table of numbers. Indexes of colors to include in the palett.
- selected. number. number code of currently selected color.
Returns: selected. number. currently selected color
Layout components are used to group and organize basic components or other layout components.
Groups a list of components horizontally. Its size will adapt to the size of the stacked contents.
pgui:component("hstack",{stroke=true,width=0,margin=3,gap=3,box=true,contents={}})
Options:
- stroke. boolean. Show border or not.
- width. number. If 0, the width of the stack wil adapt to its contents, if > 0 it will be set to the value specified.
- margin. number. Margin separating contents from border from all sides.
- gap. number. Horizontal gap between components.
- box. boolean. Draw containing box.
- contents. list of tables. A list of tables, each subtable represents a component to put inside the stack and must contain:
{NAME_OF_COMPONENT, {OPTIONS_OF_COMPONENT}}
Returns. Table containing the return values of the contained components, in order. Table.
A convenient horizontal stack formatted as a top menu bar. It will be positioned in vec(0,0)
pgui:component("topbar",{width=479,gap=3,contents={}})
Options:
- width. number. If 0, the width of the stack wil adapt to its contents, if > 0 it will be set to the value specified.
- gap. number. horizontal gap between components.
- contents. list of tables. A list of tables, each subtable represents a component to put inside the stack and must contain:
{NAME_OF_COMPONENT, {OPTIONS_OF_COMPONENT}}
Returns: Table containing the return values of the contained components, in order. Table.
Groups a list of components vertically. Its size will adapt to the size of the stacked contents.
pgui:component("vstack",{stroke=true,height=0,margin=3,gap=3,box=true,contents={}})
Options:
- stroke. boolean. Show border or not.
- height. number. If 0, the height of the stack wil adapt to its contents, if > 0 it will be set to the value specified.
- margin. number. Margin separating contents from border from all sides.
- gap. number. horizontal gap between components.
- box. boolean. Draw containing box.
- contents. list of tables. A list of tables, each subtable represents a component to put inside the stack and must contain:
{NAME_OF_COMPONENT, {OPTIONS_OF_COMPONENT}}
Returns: Table containing the return values of the contained components, in order. Table.
A vertical stack with a button that toggles the display of it's contents
pgui:component("dropdown",{label="dd",text="DROPDOWN",stroke=true,margin=2,gap=3,contents={},disable=false})
Options:
- label. string. REQUIRED. Unique name for keeping internal state.
- text. string. Text inside the button.
- stroke. boolean. Show border or not.
- margin. number. Margin separating contents from border from all sides.
- gap. number. Vertical gap between components in vstack.
- contents. list of tables. A list of tables, each subtable represents a component to put inside the vstack and must contain:
{NAME_OF_COMPONENT, {OPTIONS_OF_COMPONENT}}
. See vstack. - disable. boolean. disable dropdown button
Returns: Table containing the return values of the contained components in its stack, in order. Table.
NOTE: if you want to close the dropdown with code, for instance after clicking a button inside, you can use pgui:close_dropdown(LABEL_OF_DROPDOWN)
, by replacing "LABEL_OF_DROPDOWN" with the label you used for your dropdown. This will set the store that keeps track of the internal state of the dropdown to false and will effectively close it.
A container box that clips the content that exceeds its size and can be scrolled with the mousewheel or a trackpad gesture.
To use this component, you must run the function pgui:activate_clipping()
in your _init function first.
pgui:component("scrollable",label="scrll",scroll_x=true,scroll_y=false,size=vec(50,50),sensibility=4,content={}})
Options:
- label. string. REQUIRED. Unique name for keeping internal state.
- size. vector. Desired size of scrollable area. If any dimension is bigger than content, it will shrink to content's size. If you see undesired clipping in a dimesion you don't want to scroll, set it to a big number (to be safe, 500)
- scroll_x. boolean. Allow x axis scrolling.
- scroll_y. boolean. Allow y axis scrolling.
- content. table. A table representing the data of a component to put inside the scrollable area, it must contain:
{NAME_OF_COMPONENT, {OPTIONS_OF_COMPONENT}}
.
Returns: Return value of content component.
There's also a line component that can be used as a separator in stacks.
pgui:component("line",{size=vec(100,0)})
- size. vector. x and y positions with respect to pos.
Check out the examples folder to see some ways to use the library
pgui uses a table to store a color palette of four colors. This palette is used in all of the components by default, unless you specify a different palette for a particular component.
The colors in the palette are used in components as follows:
Index in table | Used for | Default value |
---|---|---|
1 | fill color for boxes, buttons, etc. | 7 |
2 | on hover fill for buttons | 18 |
3 | active color for buttons, checkboxes, sliders, etc. | 12 |
4 | stroke colors for borders and text | 0 |
You can set a new table for your general palette with the function pgui:set_palette(TABLE)
.
In the future, I would like to add a couple of extra components: a text area, a typewritter text effect, an xy kaoss style pad box , a knob, a plot and oscilloscope...
Text input is very simple, it doesn't respond to double clicking or to supr key, only to backspace.
I tried to make the library efficient, but depending on the number of components you use, it can still can become resource intensive. There are two tricks you can use to consume less cpu:
- Clipping of components is disabled by default, you can keep it like this if you are not using any scrollable component. This will reduce computations a little bit.
- You can refresh and update your components every other frame by using a counter and a modulo operator.
If the library is too heavy and you need space, you can delete the components that you are not using, just take into account that some components require others (mostly text, boxes, text boxes and buttons, so don't delete those!)
This library was made by Sergio Rodríguez Gómez
If you like this library please consider supporting my work!
You can use this Ko-fi button:
Or go to my Buy me a coffee page: buymeacoffee.com/srsergior
Everything counts. I want to develop other cool tools and games.
You can also check some of my other work on itch or in this github account, like my Pico-8 games or bebop, a music generator for games and video soundtracks.
- Minor performance improvements
- Implemented layering
- Fixed offset error in scrollable and fixed button response miscalculations inside scrollable
- Added flr option to hslider
- Fixed minor layout adjustments
- Added sprite_box component based on @MaddoScientisto's suggestion
- Fixed color palette not being passed to children components
- Performance update, now components consume less cpu
- Refactored codebase to make it cleaner and easier to expand
- Minor display improvements in hslider
- Sprite component now returns if it was clicked so it can act like a button
- Fixed set_palette function
- Added close_dropdown function
- Minor ajustments on multiselect and radio buttons
- Added better support for special characters, based on @yeetree's suggestion