The Castle UI API allows you to add user interface components that allow interaction with your game. Castle manages the laying out of your UI relative to the rest of Castle's UI. Uses can range from simple debug tools while developing games to user-facing level editors or text-based adventure games etc.
To use the UI API, simply define the castle.uiupdate
function and put your UI calls in it:
function castle.uiupdate()
castle.ui.markdown([[
## Hello world!
Welcome to UI.
]])
end
Since using UI components usually involves a lot of calls to functions in the castle.ui
module, it helps to make a local variable referencing it:
local ui = castle.ui
function castle.uiupdate()
ui.markdown([[
## Hello world!
Welcome to UI.
]])
end
Components that work as inputs usually take a label and the current value, then return the new value:
local ui = castle.ui
local myString = 'Edit me!'
function castle.uiupdate()
myString = ui.textArea('myString', myString)
end
function love.draw()
love.graphics.print('myString:\n' .. myString, 20, 20)
end
castle.uiupdate
, like love.draw
, is called repeatedly at a certain frequency. For the UI this is currently 20 times a second, which seems to make for reasonable responsiveness. On each update, you just need to describe the UI for the current state of your game, and don't have to worry about removing or updating the state of 'old' UI components. Simply don't make a call to have something not be displayed. In this sense, UI calls are like LÖVE draw calls.
local ui = castle.ui
local shouldShowText = true
function castle.uiupdate()
shouldShowText = ui.checkbox('Show text', shouldShowText)
if shouldShowText then
ui.markdown('The display of this text is toggled by the above checkbox!')
end
end
See the code for the 'Circles' demo for an example of showing UI for many game entities.
These functions affect top-level properties of the UI as a whole and aren't related to any particular UI component.
ui.setVisible
ui.setVisible(visible)
Set whether the UI panel is visible. This function can be called outside ui.update
.
Arguments
visible
(boolean, required): Whether the UI should be visible.Returns
This function doesn't return anything
ui.getVisible
visible = ui.getVisible()
Get whether the UI panel is visible. This function can be called outside ui.update
.
Arguments
This function doesn't have any arguments.
Returns
visible
(boolean): Whether the UI is visible.Each of these functions corresponds to one type of UI component, and must be called inside ui.update
.
All component functions take some required parameters, and one props
parameter for additional configuration. All keys in props
are optional. props
itself is always optional and defaults to {}
.
Parent components (components that have more components inside) take an inside
parameter. The inside
parameter should be a function that makes more UI calls -- the components created by these calls then become children of the parent component.
Input components generally have required label and value parameters. Labels are strings shown next to inputs to describe their function, and are also used by the system to distinguish the inputs from each other. Value parameters provide the current value of the input. Input components generally return the new values (which may be equal to the values passed in if no changes were made by the user).
A container to allow custom layouts for components. Children can be laid out horizontally or vertically and the parent can have configurable background color, border, width and height.
ui.box(id, props, inner)
ui.box(id, inner)
Arguments
id
(string, required): An identifying string for this box. Needs to be unique within the parent of the box. This id isn't displayed anywhere and is just used internally to distinguish boxes from each other.props
(table, optional): The table of props:margin
or m
(number or string): Sets margin
in CSS.marginTop
or mt
(number or string): Sets margin-top
in CSS.marginRight
or mr
(number or string): Sets margin-right
in CSS.marginBottom
or mb
(number or string): Sets margin-bottom
in CSS.marginLeft
or ml
(number or string): Sets margin-left
in CSS.marginX
or mx
(number or string): Sets both marginLeft
and marginRight
.marginY
or my
(number or string): Sets both marginTop
and marginBottom
.padding
or p
(number or string): Sets padding
in CSS.paddingTop
or pt
(number or string): Sets padding-top
in CSS.paddingRight
or pr
(number or string): Sets padding-right
in CSS.paddingBottom
or pb
(number or string): Sets padding-bottom
in CSS.paddingLeft
or pl
(number or string): Sets padding-left
in CSS.paddingX
or px
(number or string): Sets both paddingLeft
and paddingRight
.paddingY
or py
(number or string): Sets both paddingTop
and paddingBottom
.color
(string): Sets the text color. Must be a CSS color such as '#ff0000'
or 'green'
.backgroundColor
or bg
(string): Sets the background color. Must be a CSS color such as '#ff0000'
or 'green'
.width
, height
, minWidth
, minHeight
, maxWidth
, maxHeight
(number or string): Each of these sets the respective CSS property. Numbers from 0-1 are converted to percentages. Numbers greater than 1 are converted to pixel values. Strings are passed as raw CSS (eg. '100%'
, '2em'
).border
, borderTop
, borderRight
, borderBottom
, borderLeft
(string): Each of these sets the respective CSS border property. Strings are directly passed to CSS, so an example string is '1px solid white'
.borderStyle
(string): Sets the CSS border-style
property.borderColor
(string): Sets the CSS border-color
property.borderRadius
(number or string): Sets the CSS border-radius
property. Numbers are converted to pixel values. Strings are passed directly to CSS (eg. '50%'
).alignItems
, alignContent
, justifyItems
, justifyContent
, flexWrap
, flexDirection
, flex
, flexGrow
, flexShrink
, flexBasis
, justifySelf
, alignSelf
, order
(string): Each of these sets the respective CSS Flexbox property.inner
(function, required): A function that makes UI calls defining contents of this box. Children are laid out in the direction specified by the flexDirection
prop. By default the direction is 'column'
.Returns
This function doesn't return anything
Allows the user to perform an action by clicking.
clicked = ui.button(label, props)
Arguments
label
(string, required): The labelprops
(table, optional): The table of props:disabled
(boolean): Whether the button should be disabledbig
(function): Whether the button should be a bigger variant.kind
(string): One of 'primary'
, 'secondary'
, 'danger'
or 'ghost'
. Is 'secondary'
by default. A 'primary' button is highlighted and meant for important actions. A 'danger' button is meant for dangerous actions (such as deleting something). A 'ghost' button has even less visual dominance than a secondary button.icon
(string): The path to an image file to use as an icon. Can be a path relative to the main Lua file for the game, or can be an absolute URL of an image on the web.iconOnly
(string): Whether to only show the icon and hide the label text.onClick
(function): A function to call when the button is clicked. You can use this instead of using the return value directly if you prefer callbacks.Returns
clicked
(boolean): Whether the button was clicked in this update.Allows the user to toggle a boolean value. Checkboxes generally represent one input in a larger flow with a final confirmation step (eg. choosing among many settings then clicking a button to perform an action with those settings). Prefer toggle switches instead if the resulting action immediately affects something in your game without another step.
newChecked = ui.checkbox(label, checked, props)
Arguments
label
(string, required): The labelchecked
(string, required): Whether currently checkedprops
(table, optional): The table of props:indeterminate
(boolean): Whether the checkbox is in an 'indeterminate' state between checked and unchecked. This useful when you want to express that the checkbox has a sublist of selections, some selected and some unselected.disabled
(boolean): Whether the input should be disabledhideLabel
(boolean): Whether to hide the labelonChange
(function): A function to call with the new value whenever the input is updated. You can use this instead of using the return value directly if you prefer callbacks. If your function returns a value, that value is used as the new value instead.Returns
newChecked
(string): The new checked state. Is equal to checked
if no change occured in this update.Display a text editor specifically designed for code (has syntax highlighting, monospace fonts, etc.). The value
represents the contents of the editor as a string. You can use, for example, the load
function to compile the string if it's meant to be Lua code.
newValue = ui.codeEditor(label, value, props)
Arguments
label
(string, required): The labelvalue
(string, required): The current valueprops
(table, optional): The table of props:disabled
(boolean): Whether the input should be disabledhideLabel
(boolean): Whether to hide the labelhelperText
(string): Text that is used alongside the label for additional helponChange
(function): A function to call with the new value whenever the input is updated. You can use this instead of using the return value directly if you prefer callbacks. If your function returns a value, that value is used as the new value instead.onChangeCursorPosition
(function): A function to call when the user moves the cursor in the text editor. The function is called with one parameter, position
, which is a table with the following keys:line
(number): The line number that the cursor is on. The first line is numbered 1.column
(number): The column that the cursor is on. The first column is numbered 1.offset
(number): The offset of the cursor in the value
string. Starts at 0.word
(string): The word in value
that is at the current cursor position, or nil
if there is no word.completions
(table): A table containing auto-completion suggestions to display. The editor will automatically filter these suggestions based on what the user is typing. The table is expected to be in the form of a Lua array, where each element represents a suggestion. Each suggestion must itself be a table with the following keys:label
(string, required): The text to display for this item in the completion menu.kind
(string, required): The kind of item this is. Used to decide what icon to in the menu. Must be one of 'Class'
, 'Color'
, 'Constant'
, 'Constructor'
, 'Enum'
, 'EnumMember'
, 'Event'
, 'Field'
, 'File'
, 'Folder'
, 'Function'
, 'Interface'
, 'Keyword'
, 'Method'
, 'Module'
, 'Operator'
, 'Property'
, 'Reference'
, 'Snippet'
, 'Struct'
, 'Text'
, 'TypeParameter'
, 'Unit'
, 'Value'
or 'Variable'
.insertText
(string, optional): The actual text to insert when this item is selected. Is equal to label
by default. This is useful if you want to use a shorter label than the entire code that is inserted, eg. a label
"Entity template" that has the entire template code for an entity as its insertText
.documentation
(string, optional): Documentation to show for this item. Markdown is allowed for formatting. The user can see the documentation by pressing Ctrl + Space when an item is highlighted or by pressing the info icon.preselect
(boolean, optional): Whether to prioritize this item for selection. The editor will still select the best match based on what the user has typed in.sortText
(string, optional): A string to use when comparing this item to other items for sorting. label
is used by default.Returns
newValue
(string): The new value input by the user. Is equal to value
if no change occured in this update.Allows the user to select a color using a visual input.
newR, newG, newB, newA = ui.colorPicker(label, r, g, b, a, props)
Arguments
label
(string, required): The labelr
(number, required): The red component of the current color, in the 0-1 range.g
(number, required): The green component of the current color, in the 0-1 range.b
(number, required): The blue component of the current color, in the 0-1 range.a
(number, required): The alpha component of the current color, in the 0-1 range. If you want to skip the alpha component, just pass 1
here and pass false
for enableAlpha
in props
.props
(table, optional): The table of props:enableAlpha
(boolean): Whether to show a slider for editing the alpha component. By default this is true
.mode
(string): Format of color components for numeric input. Can be 'RGB'
, 'HSB'
or 'HSL'
. An input in hex format is always shown to alongside this.disabled
(boolean): Whether the input should be disabledhideLabel
(boolean): Whether to hide the labelhelperText
(string): Text that is used alongside the label for additional helponChange
(function): A function to call with the new value whenever the input is updated. You can use this instead of using the return value directly if you prefer callbacks. If your function returns a value, that value is used as the new value instead.Returns
newR
(number): The red component of the new value input by the user, in the 0-1 range.newG
(number): The green component of the new value input by the user, in the 0-1 range.newB
(number): The blue component of the new value input by the user, in the 0-1 range.newA
(number): The alpha component of the new value input by the user, in the 0-1 range. If enableAlpha
was false
in props
, this is equal to the given a
value.Allows the user to select from list of values.
newValue = ui.dropdown(label, value, items, props)
Arguments
label
(string, required): The labelvalue
(string, required): The current value. Must be one of the values in items
, or nil
to indicate that nothing has been selected. When nothing is selected, the control displays props.placeholder
, which defaults to 'Select an option...'
.items
(table, required): A table containing strings that are possible values for the user to select from. The table is expected to be in the form of a Lua array, eg. { 'option1', 'option2', 'option3' }
.props
(table, optional): The table of props:disabled
(boolean): Whether the input should be disabledplaceholder
(string): Text to show when no item is selected (i.e., value
is nil
). Defaults to 'Select an option...'
.hideLabel
(boolean): Whether to hide the labelinvalid
(boolean): Whether the value is currently invalidinvalidText
(string): An error message to display when the value is invalidhelperText
(string): Text that is used alongside the label for additional helponChange
(function): A function to call with the new value whenever the input is updated. You can use this instead of using the return value directly if you prefer callbacks. If your function returns a value, that value is used as the new value instead.Returns
newValue
(string): The new value input by the user. Is equal to value
if no change occured in this update.Allows the user to pick a file. The user can either browse for files with the system file dialog or drag and drop files onto the control. The file is uploaded to Castle's servers and a URI is returned. This URI can then be used with LÖVE resource-loading functions. Since the file is uploaded, the URI is safe to put in storage or use with multiplayer APIs -- to let a user pick a custom character avatar in a multiplayer game, for example.
newValue = ui.filePicker(label, value, props)
Arguments
label
(string, required): The labelvalue
(string, required): The current value. Must be a URI to an existing file.props
(table, optional): The table of props:type
(string): A string specifying what the file type should be. Currently suppors 'image'
to specify that the file should be of any image type. In this case a preview of the currently selected image is shown in the UI, and the user can only browse for image files.disabled
(boolean): Whether the input should be disabledhideLabel
(boolean): Whether to hide the labelhelperText
(string): Text that is used alongside the label for additional helponChange
(function): A function to call with the new value whenever the input is updated. You can use this instead of using the return value directly if you prefer callbacks. If your function returns a value, that value is used as the new value instead.Returns
newValue
(string): The URI for the new file picked by the user. Is equal to value
if no change occured in this update.Notes
Since the result is a URI, you need to use love.graphics.newImage
(or a similar asset-loading function for the relevant asset type) to create a LÖVE resource out of it. So, to load the resulting file as an image:
-- `imageUrl` stores the URL for the image, `image` will store the image itself
imageUrl = ui.filePicker('Image', imageUrl, {
type = 'image',
onChange = function(newImageUrl)
if newImageUrl == nil then
image = nil
else
network.async(function()
image = love.graphics.newImage(newImageUrl)
end)
end
end,
})
Displays an image.
ui.image(path, props)
Arguments
path
(string, required): The path to the image file to display. Can be a path relative to the main Lua file for the game, or can be an absolute URL of an image on the web.props
(table, optional): Same as the props for box.Returns
This function doesn't return anything.
Displays formatted text based on Markdown source. react-markdown is used to render the Markdown, so this component supports all of the Markdown syntax supported by it.
Image URLs in the source can be relative to the main Lua file (eg. if you have an image file named 'logo.png' next to your main Lua file, you can use 
in the Markdown source to display it). Absolute URLs to images on the web are also supported.
ui.markdown(source, props)
Arguments
source
(string, required): The Markdown source to renderprops
(table, optional): The table of props:ui.markdown
doesn't have any props, some may be added laterReturns
This function doesn't return anything.
Notes
Since Markdown is indentation-sensitive (code blocks are defined by indent), multi-line Lua strings should be defined without indent for expected behavior. So in the following example, all the contents are rendered as code (due to the indent):
ui.markdown([[
# A heading!
Some text.
]])
While in this one, there is a heading and some text:
ui.markdown([[
# A heading!
Some text.
]])
Allows the user to input a number. Contains controls to increase or decrease the number incrementally.
newValue = ui.numberInput(label, value, props)
Arguments
label
(string, required): The labelvalue
(number, required): The current valueprops
(table, optional): The table of props:disabled
(boolean): Whether the input should be disabledhideLabel
(boolean): Whether to hide the labelmin
(number): The minimum valuemax
(number): The maximum valuestep
(number): How much the value should increase or decrease when clicking the up or down buttoninvalid
(boolean): Whether the value is currently invalidinvalidText
(string): An error message to display when the value is invalidhelperText
(string): Text that is used alongside the label for additional helponChange
(function): A function to call with the new value whenever the input is updated. You can use this instead of using the return value directly if you prefer callbacks. If your function returns a value, that value is used as the new value instead.Returns
newValue
(number): The new value input by the user. Is equal to value
if no change occured in this update.For selecting from a list of two or more options that are mutually exclusive (only one can be selected).
newValue = ui.radioButtonGroup(label, value, items, props)
Arguments
label
(string, required): The labelvalue
(string, required): The current value. Must be one of the values in items
.items
(table, required): A table containing strings that are possible values for the user to select from. The table is expected to be in the form of a Lua array, eg. { 'option1', 'option2', 'option3' }
.props
(table, optional): The table of props:disabled
(boolean): Whether the input should be disabledhideLabel
(boolean): Whether to hide the labelhelperText
(string): Text that is used alongside the label for additional helponChange
(function): A function to call with the new value whenever the input is updated. You can use this instead of using the return value directly if you prefer callbacks. If your function returns a value, that value is used as the new value instead.Returns
newValue
(string): The new value input by the user. Is equal to value
if no change occured in this update.Like box, but when the contents to overflow its size it displays a scroll bar and allows scrolling with the mouse.
ui.scrollBox(id, props, inner)
ui.scrollBox(id, inner)
Arguments
id
(string, required): An identifying string for this scroll box. Needs to be unique within the parent of the scroll box. This id isn't displayed anywhere and is just used internally to distinguish scroll boxes from each other.props
(table, optional): Same as the props for box.inner
(function, required): A function that makes UI calls defining contents of this scroll box. Children are laid out in the direction specified by the flexDirection
prop. By default the direction is 'column'
.Returns
This function doesn't return anything
An expandable section with a label, containing more UI components inside. Helps with grouping and reducing clutter.
newOpen = ui.section(label, props, inner)
newOpen = ui.section(label, inner)
Arguments
label
(string, required): The section titleprops
(table, optional): The table of props:defaultOpen
(boolean): Whether the section should be open when first displayed. Sections are closed initially by default.open
(boolean): Whether the section should be currently open. If you leave this out, the section has normal open / close toggling behavior. If you provide this value, the section always reflects this value. The return value of the function will be true
if the user clicked on the section to open it, but if you pass false
for this parameter, it will still not open. Thus, you can override the default behavior.inner
(function, required): A function that makes UI calls defining contents of this section.Returns
newOpen
(boolean): Whether the section is open.Indicates a number visually and allows the user to adjust it by dragging a handle along a horizontal track.
newValue = ui.slider(label, value, min, max, props)
Arguments
label
(string, required): The labelvalue
(number, required): The current valuemin
(number, required): The minimum valuemax
(number, required): The maximum valueprops
(table, optional): The table of props:disabled
(boolean): Whether the input should be disabledhideTextInput
(boolean): Whether to hide the number input box that appears next to the slider allowing direct entryhideLabel
(boolean): Whether to hide the labelminLabel
(string): The label associated with the minimum valuemaxLabel
(string): The label associated with the maximum valuestep
(number): How much the value should increase or decrease when sliding the handle by mousestepMultiplier
(number): How much the value should increase or decrease when using shift + arrow keys (the actual step will be (max - min) / stepMultiplier
)onChange
(function): A function to call with the new value whenever the input is updated. You can use this instead of using the return value directly if you prefer callbacks. If your function returns a value, that value is used as the new value instead.Returns
newValue
(number): The new value input by the user. Is equal to value
if no change occured in this update.For choosing between multiple views in the same context. There are two functions involved: one is ui.tabs
which is a parent in which you put a group of tabs, and the other is ui.tab
which represents each tab in the group and contains the components that should be visible in that tab inside it. So, an example layout of these calls could be:
ui.tabs('tab group 1', function()
ui.tab('Tab 1', function()
-- Content of tab 1
end)
ui.tab('Tab 2', function()
-- Content of tab 2
end)
end)
ui.tabs
ui.tabs(id, props, inner)
ui.tabs(id, inner)
Arguments
id
(string, required): An identifying string for this tab group. Needs to be unique within the parent of the tab group. This id isn't displayed anywhere and is just used internally to distinguish tab groups from each other.props
(table, optional): The table of props:selected
(number): The index of the currently selected tab. Use this if you want to control which tab is selected.inner
(function, required): A function that makes ui.tab
calls defining each tab in this group. Any non-ui.tab
calls are ignored.Returns
This function doesn't return anything
ui.tab
newOpen = ui.tab(label, props, inner)
newOpen = ui.tab(label, inner)
Arguments
label
(string, required): The section titleprops
(table, optional): The table of props:ui.tabs
doesn't have any props, some may be added laterinner
(function, required): A function that makes UI calls defining contents of this tab.Returns
newOpen
(boolean): Whether this tab is open.Allows the user to input a string. The display for the field is multi-line, so if you expect only short single-line strings to be input prefer using a text input instead.
newValue = ui.textArea(label, value, props)
Arguments
label
(string, required): The labelvalue
(string, required): The current valueprops
(table, optional): The table of props:disabled
(boolean): Whether the input should be disabledplaceholder
(string): Text to show when the input is empty. It disappears when the user begins entering data and should not contain crucial information. It does not affect the actual value returned.rows
(string): The initial height of the text area in number of rows of text.hideLabel
(boolean): Whether to hide the labelinvalid
(boolean): Whether the value is currently invalidinvalidText
(string): An error message to display when the value is invalidhelperText
(string): Text that is used alongside the label for additional helpcharCount
(boolean): Whether to show the character countmaxLength
(number): The maximum allowed value lengthonChange
(function): A function to call with the new value whenever the input is updated. You can use this instead of using the return value directly if you prefer callbacks. If your function returns a value, that value is used as the new value instead.Allows the user to input a string. The display for the field is just a single line, so if you expect multi-line strings to be input prefer using a text area instead.
newValue = ui.textInput(label, value, props)
Arguments
label
(string, required): The labelvalue
(string, required): The current valueprops
(table, optional): The table of props:disabled
(boolean): Whether the input should be disabledplaceholder
(string): Text to show when the input is empty. It disappears when the user begins entering data and should not contain crucial information. It does not affect the actual value returned.hideLabel
(boolean): Whether to hide the labelinvalid
(boolean): Whether the value is currently invalidinvalidText
(string): An error message to display when the value is invalidhelperText
(string): Text that is used alongside the label for additional helpcharCount
(boolean): Whether to show the character countmaxLength
(number): The maximum allowed value lengthonChange
(function): A function to call with the new value whenever the input is updated. You can use this instead of using the return value directly if you prefer callbacks. If your function returns a value, that value is used as the new value instead.Returns
newValue
(string): The new value input by the user. Is equal to value
if no change occured in this update.Allows the user to toggle a boolean state. Toggle switches are generally used if the resulting action immediately affects something in your game without another step. Use a checkbox instead if the input just represents one value in a larger flow that includes a later confirmation step.
newToggled = ui.toggle(labelA, labelB, toggled, props)
Arguments
labelA
(string, required): The label when in off statelabelB
(string, required): The label when in on statetoggled
(string, required): Whether currently onprops
(table, optional): The table of props:onToggle
(function): A function to call with the new value whenever the input is updated. You can use this instead of using the return value directly if you prefer callbacks. If your function returns a value, that value is used as the new value instead.Returns
newValue
(string): The new value input by the user. Is equal to value
if no change occured in this update.