Important: This is adapted for use in Ultraschall-API, which makes initializing the GUI a little easier to do.
This wiki will (hopefully) provide a thorough explanation of how everything in Lokasenna_GUI works.
Please let me know if anything is missing, incomplete, or vague.
Just add these lines to your script:
dofile(reaper.GetResourcePath().."/UserPlugins/ultraschall_api.lua")
ultraschall.Lokasenna_LoadGuiLib_v2()
and it will load the core-features and all classes available.
Setting up the window:
GUI.name = "My script's window"
GUI.x, GUI.y = 128, 128
GUI.w, GUI.h = 384, 96
Adding a few elements to it:
GUI.New("my_txtbox", "Textbox", 1, 96, 28, 96, 20, "Track name:")
GUI.New("my_slider", "Slider", 1, 256, 32, 48, "Track number:", 1, 10, 10, 1)
GUI.New("my_button", "Button", 1, 160, 64, 64, 24, "Set name", set_track_name)
Making the elements do something. _set_track_name_ would, in this case, be a function declared earlier in the script:
local function set_track_name()
local name = GUI.Val("my_txtbox")
local num = GUI.Val("my_slider")
-- Do something with that information. Presumably renaming a track.
end
Opening the script window:
GUI.Init()
And starting the main loop:
GUI.Main()
That's it - not even two dozen lines of code and you've got a window, it has things in it, and you can get the values from those things for your script to work with.
First, you need to add Ultraschall-API and the Gui-Lib to your script:
dofile(reaper.GetResourcePath().."/UserPlugins/ultraschall_api.lua")
ultraschall.Lokasenna_LoadGuiLib_v2()
and it will load the core-features and all classes available.
Next you set up the script window. This doesn't have to be done now; just make sure the values are set prior to calling GUI.Init. These values are passed directly to gfx.init, but GUI will hold on to them in case they're wanted later. For example, you might check to see if the window has been resized and then force it back to your original dimensions.
GUI.name = "My script's window"
GUI.x, GUI.y = 128, 128
GUI.w, GUI.h = 384, 96
You can also have the window positioned dynamically. "Centered on the mouse cursor" is always a popular choice:
GUI.anchor, GUI.corner = "mouse", "C"
Possible anchor settings are as follows. The corner strings refer to the corners and sides of the window - that is, "screen" and "TR" would place the window's top-right corner in the top-right corner of the screen.
anchor "screen" or "mouse"
corner "TL"
"T"
"TR"
"R"
"BR"
"B"
"BL"
"L"
"C"
In this case the _x_ and _y_ above are relative to the specified anchor oint.
Create all of your GUI elements. Only one parameter, [_z_], is actually required - the rest are up to individual classes. See the individual class pages for their parameters.
name class z x y w, h, caption
GUI.New("my_txtbox", "Textbox", 1, 96, 28, 96, 20, "Track name:")
min max steps default
GUI.New("my_slider", "Slider", 1, 256, 32, 48, "Track number:", 1, 10, 10, 1)
function
GUI.New("my_button", "Button", 1, 160, 64, 64, 24, "Set name", set_track_name)
Note that the elements must be created via GUI.New in order for the GUI to properly track them. Don't call their :new methods directly.
Since this is Lua, creating elements can make use of whatever helper functions or variables you like. For instance, elements can use a common reference for coordinates to facilitate placing or moving them as a group:
local ref1 = {x = 32, y = 32}
GUI.New("my_txtbox", "Textbox", 1, ref1.x, ref1.y....
GUI.New("my_txtbox", "Textbox", 1, ref1.x, ref1.y + 24....
GUI.New("my_txtbox", "Textbox", 1, ref1.x, ref1.y + 48....
Elements can created at run-time as well.
Every element's state can be checked by name using GUI.Val.
local function set_track_name()
local name = GUI.Val("my_txtbox")
local num = GUI.Val("my_slider")
-- Do something with that information. Presumably renaming a track.
end
Changing the value of an element is just as simple - call GUI.Val with the new value as a parameter:
local function get_track_name() local name = ...get the current track's name somehow... GUI.Val("my_txtbox", name) end
Each class will accept/return its value in a particular format. Some of the more adventurous classes will even accept multiple value types and sort it out for themselves. See each element's Class page.
GUI.Init()
...well that was underwhelming. To be fair, Init does set up a lot of stuff under the hood.
_Lokasenna_GUI_ includes two hooks for your own functions to be run as part of the main loop.
GUI.func = my_function
will be run on every update, or with a scheduled frequency (in seconds)
by adding GUI.freq = 3
- the latter is probably a good idea if your functions are particularly
CPU-intensive.
GUI.exit = save_my_data
will be run when the script exits using reaper.atexit.
GUI.Main()
As described above, this will kick off the GUI library's logic and keep everything going until the
user exits. Scripts can also request an early exit (after performing a one-time task, say) by
setting GUI.quit = true
.
All of the core functionality for _Lokasenna_GUI_ lives in, er, Core.lua. Strictly speaking it's the only file that's necessary for a GUI script, although it would be a pretty boring script if it just opened a window and couldn't do anything with it.
The various bells and whistles are provided by classes in the - duh - /Classes folder. Labels, text boxes, sliders, even a Slurpee machine - they're all there. You can also go without the stock classes entirely and write your own instead; a blank template is provided.
_z_ is used to establish a front-to-back hierarchy for the various elements. This concept will be familiar to most as "layers", in Photoshop for instance. A _z_ value of 1 would be "front", with no practical limit (okay, roughly a thousand) on how many layers are allowed.
When GUI.Main checks for user input, it updates the elements on each z-layer from front to back so that elements in front will always catch the input first.
Likewise, when elements have changed and request a redraw, they use their z-layer. Scripts can also call for redraws on either specific layers or all the visible ones.
All of the elements are stored in a table, GUI.elms. Basic scripts may be perfectly fine just using GUI.Val here and there to get things done, but all of the classes have additional properties and methods that can be accessed directly if necessary.
GUI.elms.my_slider.caption = "Pan"
GUI.elms.my_button:exec()
Any visual changes, such as to fonts or colors, will probably necessitate a redraw for that element. _Lokasena_GUI_ only redraws them when asked, either by the element itself or manually:
GUI.redraw_z[ GUI.elms.my_slider.z ] = true
Also, it's best to avoid tinkering with an element's value directly. GUI.Val will prompt a redraw, make sure the element updates various internal values, etc, so it should be used unless absolutely necessary.
In the interest of making _Lokasenna_GUI_ as straightforward as possible, and since Reaper's API does the opposite, fonts are handled by way of presets.
1 -- Title
2 -- Header
3 -- Label
4 -- Value
GUI.font(2)
Most classes will have one or two font parameters:
GUI.elms.my_label.font = 3
Presets can be overridden or added to at any point:
font size flags
GUI.fonts[4] = {"Calibri", 12, "bi"}
GUI.fonts[5] = {"Arial", 10}
GUI.fonts["meme"] = {"Impact", 20, "b"}
GUI.font will also directly accept a table in the same format.
Note: Sizes are internally multiplied by 0.7 on OSX, because Mac and Windows apparently can't agree on something as simple as font sizes.
Colors are dealt with in a similar fashion.
wnd_bg -- Window BG
tab_bg -- Tabs BG
elm_bg -- Element BG
elm_frame -- Element Frame
elm_fill -- Element Fill
elm_outline -- Element Outline
txt -- Text
GUI.color("elm_fill")
GUI.elms.my_label.color = "cyan"
As with fonts, the color presets can be added to or amended:
R G B A
GUI.colors["red"] = {16, 103, 192, 255}
GUI.colors["my_ugly_button"] = {96, 128, 192, 255}
And GUI.color can likewise be given a color table directly.
The 16 standard web colors are also available, by name: GUI.color("magenta")
Important: Prior to calling GUI.Init, the color presets are stored as 0-255 values. Init converts them to 0-1, since that's what REAPER's gfx functions use. Be aware of this if you need to work with any color values directly after the script has started. In all honesty, though, you probably won't need to.
Some scripts may need to tinker with the gfx color values directly, for instance to create the
Label class' :fade. This is perfectly fine, but make sure you set gfx.a = 1
when you're
finished. GUI.Main checks it a couple of times, but forgetting to reset _a_ can mess up the
appearance of the entire GUI.
All elements have the following methods available for scripts to work with. When overriding a method, be aware of the whether or not the class is doing anything with it - if it is, you'll have to include a call to the base class' method:
function GUI.elms.my_slider:onmouseup()
-- Add your own code here...
GUI.Slider.onmouseup(self)
-- ...or here
end
Called when a) GUI.Init opens the script window or b) once the window is open, immediately after an element is created. Elements that use blitting should open a buffer and draw whatever they need here.
Called when GUI asks the element's z layer to redraw itself.
Called on every update loop. Return true
if the element doesn't need to check for user input
(i.e. an overlay that doesn't do anything on its own and shouldn't "intercept" the mouse or
keyboard).
Runs when an element is deleted, either automatically by GUI if its _z_ has been set to _-1_, or
if a script calls my_element:delete()
.
Runs for each element if the script window has been resized on a given update loop, allowing for elements that automatically redraw themselves to scale up and down with the window.
Called on every update loop, if the mouse is over this element.
Called when the left mouse button is first pressed down on this element.
Called when the left mouse button is released, if it was originally clicked on this element and is still over it.
Called when a left double-click is detected on this element. (Time is hard-coded at 100ms)
Called on each update loop if the left mouse button was pressed down on this element and the mouse is moved. Will continue being called on each loop even if the mouse moves elsewhere.
Right mouse button methods.
Middle mouse button equivalents of the above.
Called when the mousewheel is scrolled over this element. Passes a positive integer if the wheel was scrolled up, and a negative integer if the wheel was scrolled down.
Called if this element currently has focus (was clicked, or given .focus = true
) and keyboard
input is detected.
Called when the element loses focus (mouse was clicked elsewhere, or given .focus = false
,
or by Textbox if Return is pressed).
Redraws the element's z layer on the next round of drawing. Generally needs to be called if a script
makes any changes to an element's visual properties, or if an element's value is changed directly
rather than via GUI.Val
.
Deletes the element.
Gets or sets the element's value. Normally accessed via GUI.Val
, but can be called directly
if you want, I suppose.
For development/debugging purposes. Returns a string with the specified properties. If called with no arguments, returns all of the Element's properties.
String. If specified for an element (GUI.elms.my_button.toolip = "I am a button!"
), GUI will display a tooltip when the mouse is hovered over it. The delay time is specified by GUI.tooltip_time
, and defaults to 0.8s.
It's a button. You click on it. Things happen.
z Element depth, used for hiding and disabling layers. 1 is the highest.
x, y Coordinates of top-left corner
w, h Button size
caption Label
func Function to perform when clicked.
Note that you only need give a reference to the function:
GUI.New("my_button", "Button", 1, 32, 32, 64, 32, "Button", my_func)
Unless the function is returning a function (hey, Lua is weird), you don't want to actually run it:
GUI.New("my_button", "Button", 1, 32, 32, 64, 32, "Button", my_func())
... Any parameters to pass to that function, separated by commas as they
would be if calling the function directly.
r_func Function to perform when right-clicked
r_params If provided, any parameters to pass to that function
font Button label's font
col_txt Button label's color
col_fill Button color.
If you change this at any time after having run GUI.Init(), call
GUI.elms.my_button:init() so the buffer can be redrawn.
Programmatically force a button-click and run func, i.e. for allowing buttons to have an associated hotkey.
r Boolean, optional. r = true will run r_func instead.
This class draws a basic frame for visually organizing the other elements in your GUI. It can also display text, wrapped to fit, for things like a Help or About screen.
Frame is also a good choice for scripts that want to display information in a way that isn't covered by the other classes - just overwrite the frame's :draw() method with your own code.
z Element depth, used for hiding and disabling layers. 1 is the highest.
x, y Coordinates of top-left corner
w, h Frame size
shadow Boolean. Draw a shadow beneath the frame? Defaults to False.
fill Boolean. Fill in the frame? Defaults to False.
color Frame (and fill) color. Defaults to "elm_frame".
round Radius of the frame's corners. Defaults to 0.
text Text to be written inside the frame. Will automatically be wrapped
to fit (self.w - 2 * self.pad).
txt_indent Number of spaces to indent the first line of each paragraph
txt_pad Number of spaces to indent wrapped lines (to match up with bullet
points, etc)
pad Padding between the frame's edges and text. Defaults to 0.
bg Color to be drawn underneath the text. Defaults to "wnd_bg",
but will use the frame's fill color instead if Fill = True
font Text font. Defaults to preset 4.
col_txt Text color. Defaults to "txt".
Returns the frame's text.
Sets the frame's text and formats it to fit within the frame, as above.
I really hope we all know what a Knob is.
z Element depth, used for hiding and disabling layers. 1 is the highest.
x, y, w Coordinates of top-left corner, width. Height is fixed.
caption Label
min, max Minimum and maximum values
default Default step for the knob. Steps are counted from min to max starting at 0.
inc Amount to increment the value per step. Defaults to 1.
vals Boolean. Display value labels?
For knobs with a lot of steps, i.e. Pan from -100 to +100, set this
to false and manually update the Knob's caption instead
Example:
A knob from 0 to 11, defaulting to 11, with a step size of 0.25:
min = 0
max = 11
default = 44
inc = 0.25
bg Color to be drawn underneath the label. Defaults to "wnd_bg"
font_a Caption font
font_b Value font
col_txt Text color
col_head Knob head color
col_body Knob body color
cap_x, cap_y Offset values for the knob's caption.
output Allows the value labels to be modified; accepts several different types:
table Replaces each value label with output[step], with the steps
being numbered as above
Useful for a knob with named settings:
{"Clean", "Dirty", Aggressive", "XXXTREME"}
functions Replaces each value with the returned value from
output(step), numbered as above
Useful for appending text to the knob values:
output(step) = function return step.."db" end
Output will always count steps starting from 0, so you'll have to account for minimum
values in the final string yourself.
Returns the current value of the knob. i.e. for a Pan knob, -100 to +100, Val will return an integer from -100 to +100.
Sets the value of the knob, as above.
The most basic element, though not without some neat tricks.
z Element depth, used for hiding and disabling layers. 1 is the highest.
x, y Coordinates of top-left corner
caption Label text
shadow Boolean. Draw a shadow?
font Which of the GUI's font values to use
color Use one of the GUI.colors keys to override the standard text color
bg Color to be drawn underneath the label. Defaults to "wnd_bg"
w, h These are set when the Label is initially drawn, and updated any time the
label's text is changed via GUI.Val().
Allows a label to fade out and disappear. Nice for briefly displaying a status message like "Saved to disk..."
len Length of the fade, in seconds
z_new z layer to move the label to when called
i.e. popping up a tooltip
z_end z layer to move the label to when finished
i.e. putting the tooltip label back in a
frozen layer until you need it again
Set to -1 to have the label deleted instead
curve Optional. Sets the "shape" of the fade.
1 will produce a linear fade
>1 will keep the text at full-strength longer,
but with a sharper fade at the end
<1 will drop off very steeply
Defaults to 3 if not specified
Use negative values to fade in on z_new, rather
than fading out. In this case, the value of
z_end doesn't matter.
Note: While fading, the label's z layer will be redrawn on every update loop, which may impact CPU usage for scripts with many elements. If this is the case, try to put the label on a layer with as few other elements as possible.
Provides a scrolling list of options, from which the user can select one or more.
z Element depth, used for hiding and disabling layers. 1 is the highest.
x, y Coordinates of top-left corner
w, h Width and height of the text editor
list Accepts either a comma-separated string of options or a table
If you change the list at run-time using a comma-separated string,
you'll need to call GUI.elms.my_list:init() afterward to have the
string parsed into a table.
multi Boolean. Can the user select multiple items (Ctrl, Shift)?
caption Label shown to the left of the text editor
pad Padding between the label and the text editor
cap_bg Color to be drawn underneath the label. Defaults to "wnd_bg"
bg List background. Defaults to "elm_bg"
shadow Boolean. Draw a shadow beneath the label?
color Text color
font_a Caption font
font_b List font
Returns either a single item number (multi = false), or a table (multi = true) with the indices of each selected item.
Accepts a table of boolean values for items to be selected or not selected.
If your script needs to resize the listbox, move it around, etc, run this afterward so it can update a few internal values.
Ye olde menu bar. If you don't recognize this I'm not really sure how to help you.
z Element depth, used for hiding and disabling layers. 1 is the highest.
x, y Coordinates of top-left corner
w, h Width and height of the Menubar
menus Accepts a specifically formatted table.
menus = {
-- Menu title
{title = "File", options = {
-- Menu item Function to run when clicked
{"New", mnu_file.new},
{""},
{"Open", mnu_file.open},
{">Recent Files"},
{"blah.txt", mnu_file.recent_blah},
{"stuff.txt", mnu_file.recent_stuff},
{"<readme.md", mnu_file.recent_readme},
{"Save", mnu_file.save},
{"Save As", mnu_file.save_as},
{""},
{"#Print", mnu_file.print},
{"#Print Preview", mnu_file.print_preview},
{""},
{"Exit", mnu_file.exit}
}},
{title = "Edit", options = {....}},
...etc...
}
Menu options can be prefixed with the following:
! : Checked
# : grayed out
> : this menu item shows a submenu
< : last item in the current submenu
An empty item ({""},
) will appear as a separator in the menu.
Functions don't need to be in any particular format or even associated with each other; this would also work:
{"New", new_func),
{"Open", open),
{"Save", savestuff),
w, h Specify an overall width and height. If omitted, these will be calculated
automatically from the menu titles
pad Extra width added between menus. Defaults to 0.
font Font for the menu titles
col_txt Color the menu titles
col_bg Color for the menu bar
col_over Color for the highlighted menu
fullwidth Boolean. Extends the menubar to the right edge of the script window.
Defaults to true.
Returns the menu table.
Accepts a new menu table and reinitializes some internal values.
This should only be necessary if the menu titles themselves need to be changed; for things
like checking off/graying out menu items, or even updating dynamic menus like "Recent Files",
it would probably be easier to just edit the options = {....}
yourself and directly
replace it:
local new_file_options = GUI.elms.my_menubar.menus[1].options
...edit the names or whatever...
GUI.elms.my_menubar.menus[1].options = new_file_options
Provides a standard dropdown menu, with the usual separators and folders.
z Element depth, used for hiding and disabling layers. 1 is the highest.
x, y Coordinates of top-left corner, element size.
w, h
caption Label displayed to the left of the menu box
opts Comma-separated string of options. As with gfx.showmenu, there are
a few special symbols that can be added at the beginning of an option:
# : grayed out
> : this menu item shows a submenu
< : last item in the current submenu
An empty field will appear as a separator in the menu.
pad Padding between the label and the box
noarrow Boolean. Removes the arrow from the menubox.
bg Color to be drawn underneath the label. Defaults to "wnd_bg"
font_a Font for the menu's label
font_b Font for the menu's current value
col_cap Caption color
col_txt Text color
align Flags for gfx.drawstr:
flags&1: center horizontally
flags&2: right justify
flags&4: center vertically
flags&8: bottom justify
flags&256: ignore right/bottom,
otherwise text is clipped to (gfx.x, gfx.y, right, bottom)
Returns the current menu option, numbered from 1. Numbering includes separators and submenus:
New 1
--
Open 3
Save 4
--
Recent > a.txt 7
b.txt 8
c.txt 9
--
Options 11
Quit 12
Sets the current menu option, numbered as above.
This class provides two separate, but very similar, element classes:
Radio | Checklist |
---|---|
Both classes take the same parameters on creation, and offer the same parameters afterward - their usage only differs when it comes to how their :val() methods behave.
z Element depth, used for hiding and disabling layers. 1 is the highest.
x, y Coordinates of top-left corner
caption Element title. Feel free to just use a blank string: ""
opts Accepts either a table* or a comma-separated string of options.
Options can be skipped to create a gap in the list by using "_":
opts = "Alice,Bob,Charlie,_,Edward,Francine"
->
Alice 1
Bob 2
Charlie 3
Edward 5
Francine 6
* Must be indexed contiguously, starting from 1.
dir "h" Options will extend to the right, with labels above them
"v" Options will extend downward, with labels to their right
pad Separation in px between options. Defaults to 4.
bg Color to be drawn underneath the caption. Defaults to "wnd_bg"
frame Boolean. Draw a frame around the options.
size Width of the unfilled options in px. Defaults to 20.
* Changing this might mess up the spacing *
col_txt Text color
col_fill Filled option color
font_a List title font
font_b List option font
shadow Boolean. Draw a shadow under the text? Defaults to true.
swap Causes the options and their text to swap places. Purely visual.
Returns the current option, numbered from 1.
Sets the current option, numbered from 1.
Returns a table of boolean values for each option. Indexed from 1.
Accepts a table of boolean values for each option. Indexed from 1.
Provides a slider with one or more handles for the user to select values.
z Element depth, used for hiding and disabling layers. 1 is the highest.
x, y Coordinates of top-left corner
w Width of the slider track. Height is fixed.
caption Label shown above the slider track.
min, max Minimum and maximum values
defaults Table of default steps for each slider handle.
- Steps are inclusive, and start from 0.
- If only one handle is needed, it can be given as a number rather than a table.
inc Amount to increment the value per step. Defaults to 1.
dir "h" Horizontal slider (default)
"v" Vertical slider
Slider values are counted in steps from min to max. The total number of
steps will be:
inc * |max - min|
Examples:
A slider from 1 to 10, defaulting to 5:
min = 1
max = 10
defaults = 4 <-- 0,1,2,3,[4]
A pan slider from -100 to 100, defaulting to 0:
min = -100
max = 100
defaults = 100
Five sliders from 0 to 30, with a step size of 0.25,
defaulting to 5, 10, 15, 20, 25:
min = 0
max = 30
defaults = {20, 40, 60, 80, 100}
inc = 0.25
bg Color to be drawn underneath the label. Defaults to "wnd_bg"
font_a Label font
font_b Value font
col_txt Text color
col_hnd Handle color
col_fill Fill bar color
show_handles Boolean. If false, will hide the slider handles.
i.e. displaying a VU meter
show_values Boolean. If false, will hide the handles' value labels.
cap_x, cap_y Offset values for the slider's caption
output Allows the value labels to be modified; accepts several different types:
table Replaces each value label with output[step], with the steps
being numbered as above
Useful for a slider with named settings:
{"Clean", "Dirty", Aggressive", "XXXTREME"}
functions Replaces each value with the returned value from
output(step), numbered as above
Useful for appending text to the slider values:
output(step) = function return step.."db" end
Output will always count steps starting from 0, so you'll have to account for minimum
values in the final string yourself.
Returns a table of values for each handle, sorted from smallest to largest
Accepts a table of values for each handle, as above
Recalculates the number of steps and resets the handles to their default positions. Must be called after changing inc, min, or max.
Provides a set of tabs that swap between different sets of z-layers, allowing for a tabbed view.
z Element depth, used for hiding and disabling layers. 1 is the highest.
x, y Coordinates of top-left corner
tab_w, tab_h Size of individual tabs
opts Comma-separated string, or a table, of tab names
pad Padding between tabs. Defaults to 8.
w, h Overall width and height of the tab strip. These are determined
automatically based on the number of tabs and their size.
bg Color to be drawn underneath the tabs. Defaults to "elm_bg".
col_txt Text color. Defaults to "txt".
col_tab_a Active tab color. Defaults to "wnd_bg"
col_tab_b Inactive tab color. Defaults to "tab_bg"
font_a Active tab font. Defaults to 3.
font_b Inactive tab font. Defaults to 4.
fullwidth Boolean. Extends the tab background to the right edge of the script window.
Defaults to true.
state The current tab. Numbered from left to right, starting at 1.
Returns the active tab. Numbered from 1.
Sets the active tab. Numbered from 1.
Assigns the z layers that are shown for each tab. Sets are defined thusly:
(Only needs to be done once, at startup)
GUI.elms.my_tabs:update_sets(
{
z-layers shown on that tab
tab /
/ |
| |
v v
[1] = {2, 3, 4},
[2] = {2, 5, 6},
[3] = {2, 7, 8},
}
)
_update_sets()_ will be called automatically when a tab is clicked, or if a new tab is set using GUI.Val.
Provides a single-line textbox.
z Element depth, used for hiding and disabling layers. 1 is the highest.
x, y Coordinates of top-left corner
w, h Width and height of the textbox
caption Label shown to the left of the textbox
pad Padding between the label and the textbox
bg Color to be drawn underneath the label. Defaults to "wnd_bg"
shadow Boolean. Draw a shadow beneath the label?
color Text color
font_a Label font
font_b Text font
cap_pos Position of the text box's label.
"left", "right", "top", "bottom"
focus Boolean. Whether the textbox is "in focus" or not, allowing users to
type. This setting is automatically updated, so you shouldn't need to
change it yourself in most cases.
Returns the contents of the textbox.
Sets the contents of the textbox.
Provides a multiline text editor.
z Element depth, used for hiding and disabling layers. 1 is the highest.
x, y Coordinates of top-left corner
w, h Width and height of the text editor
text Multiline string of text
caption Label shown to the left of the text editor
pad Padding between the label and the text editor
cap_bg Color to be drawn underneath the label. Defaults to "wnd_bg"
bg Editor background. Defaults to "elm_bg"
shadow Boolean. Draw a shadow beneath the label?
color Text color
font_a Label font
font_b Text font.
*** Only monospaced fonts will work properly ***
focus Whether the text editor is "in focus" or not, allowing users to type.
This setting is automatically updated, so you shouldn't need to
change it yourself in most cases.
undo_limit How many undo states can be stored before the first is erased.
Defaults to 20.
Returns self.retval as a string.
Accepts a either a table or a multiline string.
If your script needs to resize the text editor, move it around, etc, run this afterward so it can update a few internal values.
Allows scripts to open subwindow for preferences, dialog messages, etc.
z Element depth, used for hiding and disabling layers. 1 is the highest.
x, y, w, h Coordinates of top-left corner, width, and height.
caption Label
z_set A table of z layers to display. Must include the window's own layer.
i.e. {4, 5, 6}
center Boolean. If true, will center the window element in the script window,
ignoring the window's given x,y coordinates. Defaults to true.
title_height Height, in pixels, of the window's title bar. Defaults to 20.
noclose Boolean. If true, will hide the X button. Defaults to false.
Opens the window. Any arguments given are passed directly to :onopen
Hook for a function to perform when the window is opened. This is where you might have the window's elements populated with information from the main script, for instance.
Closes the window. Any arguments given are passed directly to :onclose
Hook for a function to perform when the window is closed. This is where you might apply or revert values from the window to the main script, depending on what argument is given.
Accepts a GUI element. The element is repositioned with its given x,y coordinates relative to
the window element, i.e. a button created at 48,48
will be adjusted to
Window.x + 48, Window.y + Window.title_height + 48
. The original coordinates are stored as
elm.ox, elm.oy.
Creates a new GUI element, overwriting any existing element of the same name. Can be used in two different ways:
The element's required and/or optional parameters are passed as function arguments:
name type z x y caption shadow font
GUI.New("my_label", "Label", 2, 16, 16, "Hello!", true, 1)
These parameters, and any parameters listed as Additional, can be accessed afterward via
GUI.elms.my_label.parameter
.
The element's required, optional, and any additional parameters are given as a table:
GUI.New({
name = "my_label",
type = "Label"
z = 2,
x = 16,
y = 16,
caption = "Hello!",
shadow = true,
font = 1
})
Allows for the creation of multiple elements from a set of keyed tables at once. First, a table containing the element tables described above is created:
local elements = {}
elements.my_label = {
type = "Label"
z = 2,
...
}
elements.my_button = {
type = "Button",
z = 2,
...
}
And then the table is passed to our function:
GUI.CreateElms(elements)
CreateElms
doesn't do anything special; it simply iterates through all of the keyed tables
and passes them individually to GUI.New
.
Updated on every loop with the mouse coordinates, relative to the script window. Identical to
gfx.mouse_x, gfx.mouse_y
.
Updated on every loop with the state of Ctrl, Shift, Alt, etc. Identical to gfx.mouse_cap
.
1 Left mouse button
2 Right mouse button
4 Control key
8 Shift key
16 Alt key
32 Windows key
64 Middle mouse button
Values can be checked using bitwise comparison:
if GUI.mouse.cap & 8 == 8 then shift_is_pressed end
.
Updated on every loop with the position (relative to the last value) of the mouse wheel. Shouldn't
need to be accessed directly; all GUI elements receive a value, inc
, when :onwheel
is
called. Identical to gfx.mouse_wheel
.
While a mouse button is down, these store the element that was clicked.
Also: rmouse_down_elm, mmouse_down_elm
.
When the mouse is being dragged (i.e. an element's :ondrag logic is being called), these store the the mouse's original coordinates in the script window.
Also: r_ox, r_oy, m_ox, m_oy
.
When the mouse is being dragged these store the mouse's original position relative to the element
being dragged. i.e. If the element was at 32,32
, and the mouse was pressed down at
64,48
, _off_x_ and _off_y_ would be 32,16
.
Also: r_off_x, r_off_y, m_off_x, m_off_y
.
Updated on every loop with the keyboard state. Identical to calling gfx.getchar()
.
Boolean. When set to true
, pressing Esc will not close the script window. Use this if your
script needs to use Esc for something itself.
Boolean. True
if the SWS extension is available (currently only SWS v2.9.7 or newer).
Boolean. True
if the script is running in "restricted permissions" mode.
Number. Specifies the delay time when the mouse is hovered over an element before a tooltip is shown (if GUI.elms.my_elm.tooltip = "Hello!"
is specified).
_Lokasenna_GUI_ provides several hooks for user functions, to be run at appropriate times as part of the
update loop. All of them are set the same way: GUI.func = my_func
Called on every update loop, after updating elements and checking for user input but prior to
redrawing the elements and window. For functions that would be CPU-heavy if run on every loop,
it can be set to run every _X_ seconds by adding GUI.freq = X
.
Called whenever the window has been resized. Can be used to reposition elements (i.e. "glueing" them to the right side) or to simply force the window to stay at a certain size:
local function force_size()
gfx.quit()
gfx.init(GUI.name, GUI.w, GUI.h, GUI.dock, GUI.x, GUI.y)
-- So .onresize isn't called again on the next loop
GUI.cur_w, GUI.cur_h = GUI.w, GUI.h
end
GUI.onresize = force_size
Called whenever the user moves the mouse. I had a cool reason for doing this once, but I'll be damned if I can remember what it was now.
Copies the contents of one table to another, including metatables and subtables.
source A table
base Will use this as the primary source for copying and then grab any additional keys
from 'source' didn't exist here.
Returns a table.
For development/debugging purposes. Returns a table's contents as a string, indented to show nesting, etc.
t A table
max_depth How many levels of nesting to read through.
cur_depth Will "pad" the indenting by this amount of tabs.
Returns a string.
Recursively compares the contents of two tables, since Lua doesn't do this on its own.
t_a A table
t_b Another table
Returns a boolean.
Looks through a table and returns the key of the first matching value.
t A table
find Search term - a string, a number, a boolean
f Sorting function. Defaults to ```ipairs```.
If you need to find multiple values in the same table, and each is known to only occur once, it
will be much more efficient to copy the table with GUI.table_invert
and just check for keys
as normal.
Returns a string or number.
Returns a table with the keys and values swapped.
t A table
Returns a table.
For use in place of pairs or ipairs - iterates through a table in alphabetical/numerical order.
t A table
f An iterator function, as with 'pairs' or 'ipairs'. Specify f = "full" to perform
a comparison across types. i.e.
12 > "6 apples" -> true
Creates a table with the widths of every character in every font specified in GUI.fonts.
Uses the table above to measure a given string at a given font size. For scripts that need to work with a lot of text, this can be more than ten times faster than gfx.measurestr.
str A string
font A GUI font preset
Returns a width in pixels.
Measures a string to see how much of it will fit in the given width.
str A string
font A GUI font preset
w Width in pixels
Returns two strings - the text that will fit, and the excess.
Measures a string and adds line breaks where appropriate to fit within a specified width.
str String. Can include line breaks/paragraphs; they should be preserved.
font A GUI font preset
w Pixel width
indent Number of spaces to indent the first line of each paragraph. Defaults to 0.
*The algorithm skips tab characters and leading spaces, so
use this parameter instead*
i.e. Blah blah blah blah -> indent = 2 -> Blah blah blah blah
blah blah blah blah blah blah blah blah
pad Indent wrapped lines by the first __ characters of the paragraph. Defaults to 0.
(For use with bullet points, etc)
i.e. - Blah blah blah blah -> pad = 2 -> - Blah blah blah blah
blah blah blah blah blah blah blah blah
Returns a string.
(This function expands on the "greedy" algorithm found here: https://en.wikipedia.org/wiki/Line_wrap_and_word_wrap#Algorithm)
Draws a string with a shadow effect.
str A string
col1 Text color
col2 Shadow color. If not specified, will use "shadow".
Draws a string with a colored outline.
str A string
col1 Text color
col2 Outline color
Draws a string over a solid background, slightly larger than the string itself. This is necessary if drawing on an "empty" buffer where antialiasing will cause the text to look thin and pixellated. This should be called with gfx.x/y, GUI.font, and GUI.color already set appropriately for the text.
str A string
col Background color
Converts a hex color to 8-bit RGB.
num A color in hex format - RRGGBB or 0xRRGGBB
Returns R, G, B as values from 0-255.
Converts from RGB (optionally _A_) to HSV. Arguments and returns are values from 0-1.
r, g, b Color values from 0-1.
a Alpha value from 0-1. Defaults to 1.
Returns H, S, V, A as values from 0-1.
And back the other way.
h, s, v Color values from 0-1.
a Alpha value from 0-1. Defaults to 1.
Returns R, G, B, A as values from 0-1.
Accepts two RGB color tables and calculates the color at a specified position on a gradient between them.
col_a Color tables: {R, G, B[, A]}, values from 0-1
col_b
pos Position along the gradient from 0 (col_a) to 1 (col_b)
Returns R, G, B, A as values from 0-1.
Rounds a number to the nearest integer.
num A number
places How many decimal places to round to. Defaults to 0.
Returns a number.
Rounds a number to the nearest multiple of another.
num A number
snap Number to "snap" to multiples of.
Returns a number.
Used to limit a value to a specified range, hence the function name. The order of arguments doesn't actually matter.
num A number
min
max
Returns a number.
Returns an ordinal - "1st, 2nd, 3rd", etc.
num A number
Returns a string.
Converts an angle from polar coordinates to Cartesian.
angle An angle in radians. Omit Pi, i.e. pi/4 -> 0.25
radius A number.
ox Origin point. Defaults to 0, 0.
oy
Returns x, y relative to the origin.
Converts a point from Cartesian to polar coordinates.
x A point.
y
ox Origin point. Defaults to 0, 0.
oy
Returns angle, radius. As above, the angle is given without reference to Pi,
i.e. pi/4 -> 0.25
.
Compares two values using a boolean exclusive or.
a,b Values.
Returns true
if one value is true, but not both.
Draws a rounded rectangle. This function is an improved version of gfx.roundrect
, adapted
from an EEL example by mwe.
x,y,w,h Coordinates and dimensions of the roundrect
r Radius of the corners
antialias Boolean. Whether to antialias the corners. Defaults to true.
fill Boolean. Whether to fill in the roundrect.
Draws a triangle (or any polygon), with optional fill.
fill Boolean. Whether to fill in the triangle.
x1,y1 Each point of the triangle.
x2,y2
x3,y3
x4,y4 Additional points, for larger polygons. The last point will always be connected
... back to the first point.
Assigns one or more image buffers, so elements have their own space to draw without inteference.
num Number of buffers required
Returns either a buffer number, or a table of buffer numbers: {1020,1021,1022}
.
Lets the GUI know that a given buffer, or buffers, are no longer in use.
num A buffer number, or a table of buffer numbers as above.
Gets or sets an element's value. It's preferred to access element values this way rather than
GUI.elms.my_element.retval = 5
, as many of classes will need to format their output or
sanitize their input.
elm_name Element name
newval New value. Type is determined by each element class. If not specified, the function
will simply return the current value.
Returned values are of varying types, depending on the element class. See each class's documentation.
Determines if a given position is within the element specified.
elm A GUI element, or a table containing:
{x = _, y = _, w = _, h = _}
x,y A coordinate pair. If not specified, the mouse position will be used.
Returns a boolean.
Gives x,y coordinates that would center elm1 within elm2.
elm1 A GUI element, or a table as above.
elm2 A GUI element, or a table as above.
If not specified, elm1 will be centered in the script window.
Returns x,y.
Classes, in Lua, are really just tables that have been set up to reference a different table in the event that they don't contain a requested value. That is:
Does Susan have wings? We don't know, because Susan's description doesn't tell us. We can do this instead:
Susan's description still doesn't tell us, but since we know that she's a Person we can look and see that people do not, in fact, have wings.
(If you'd like a more detailed description of this behavior, do a Google search for "lua classes metatables" and try not to fall asleep.)
For our purposes, your class just needs to reference GUI.Element like so:
GUI.Thing = GUI.Element:new()
GUI.Element doesn't do much of anything; it exists simply to keep the script from crashing when it goes to look for a method you didn't add.
'Methods' refer to all of the actions a class can perform. i.e.
Person:walk("2 feet", "east")
Person:sleep("4 hours")
Person:thinkabout("N'Sync")
The first method a class needs is a way to store all of its parameters, and for it know that it is, in fact, part of a class:
function GUI.Thing:new(name, z, x, y, ...whatever parameters you want...)
local Thing = {}
Thing.name = name
Thing.z = z
Thing.x, Thing.y = x, y
Thing.optional_parameter = optional_parameter or 5
Thing.internal_value = human_readable_value * (2 / math.pi)
Thing.retval = human_readable_value
setmetatable(Thing, self)
self.__index = self
return Thing
end
That last bit tells our temporary Thing to use 'self' as a backup if it can't find a value
or method. Because this function uses a :
up top, self is included as a hidden
variable referring to whatever called it. i.e. In GUI.Thing:new()
self refers to
GUI.Thing.
By giving the temporary Thing instructions to look up GUI.Thing, we've created a new, independent copy of our base class. It can be modified, told to do stuff, even rewritten, and other Things won't be affected. If any of them are asked for a value they don't have, they'll look at their class for an answer.
Many elements will want to do a bit of prep work when first created, such as getting a buffer and drawing themselves to it for blitting later. init is called:
function GUI.Thing:init()
self.buffers = GUI.GetBuffer(3)
...draw some stuff to the buffers...
if self.caption ~= "" then
GUI.font(self.font)
self.cap_w, cap_h = gfx.measurestr(self.caption)
end
end
The call to GUI.GetBuffer simply asks the GUI logic to assign 3 graphics buffers to this element so that there's no risk of other elements drawing in them. Classes can also have a common buffer for all child elements, for instance if your buttons are all going to be the same color and size. In this case, you would assign the buffer to the class as a whole:
GUI.Thing = GUI.Element:new()
GUI.Thing.buffers = GUI.GetBuffer(2)
function GUI.Thing:new(....)
...etc...
end
Because of the metatable stuff, an element looking for 'self.buffer' will come up blank and go looking to see if GUI.Thing.buffer exists.
Classes probably need to be able to draw themselves, right? This method is called any time the element's z layer is asked to redraw.
function GUI.Thing:draw()
gfx.rect(self.x, self.y, self.w, self.h)
gfx.x, gfx.y = self.x, self.y
gfx.drawstr(self.caption)
end
If your class made use of a buffer to pre-draw a few things, you instead have to copy ('blit' being the computer term) the buffer's contents (or simply part of it) to the main drawing area.
function GUI.Thing:draw()
gfx.blit(self.buff[1], 1, 0)
gfx.blit(self.buff[2], 1, 0)
gfx.x, gfx.y = self.x, self.y
gfx.drawstr(self.caption)
end
It's also perfectly fine to use a combination of blitting and drawing - most of the stock classes do. In the Slider class, for instance:
init...
draw...
(I apologize if the Slider class has been updated at some point and no longer works that way by the time you read this)
There are also more complicated ways to use blitting such as scaling, rotation, cropping, and different blending modes. See the ReaScript API's gfx functions for more detail on that topic.
Most classes will also need a way to return their value, or have a new value assigned to them. In some cases this can be done directly, like so:
local label_caption = GUI.elms.my_label.retval
GUI.elms.my_label.retval = "Ha! I changed the text!"
but many classes will want to do some processing to convert their internal parameters to a human-readable string, and vice versa. In this case, use:
function GUI.Thing:val(newval)
if newval then
self.retval = (newval - self.min_value) / (self.number_of_values)
self:redraw()
else
return ((self.retval * self.number_of_values) + self.min_value)
end
end
All elements' :val() methods can be accessed via GUI.Val():
-- Get the value
local thing_value = GUI.Val("my_thing")
-- Set a new one
GUI.Val("my_thing", 12)
Another thing most classes might need is a way to respond to the user's input. We'll use :onclick as an example.
function GUI.Thing:onclick()
local x_val = (GUI.mouse.x - self.x) / self.w
local y_val = (GUI.mouse.y - self.y) / self.h
self.caption("x_val = "..x_val.."\ny_val = "..y_val)
self:redraw()
end
Note the last line there, and remember that elements are only redrawn if their z layer
is told to redraw. If the method didn't include that line, the new caption would never
be displayed because the GUI would just keep blitting the previous copy.
Classes are by no means limited to these methods, although the GUI logic will only pass
input to an element based on the existing input methods.
You can also add as many extra methods as you like - the stock classes have dozens, particularly for complicated class behaviors that would leave you with a three-hundred-line function if you tried to do it all in one function.
For a full list of the input methods provided by Lokasenna_GUI, see the comments for GUI.Element in Core.lua.