mirror of
https://github.com/luanti-org/luanti.git
synced 2025-06-27 16:36:03 +00:00
2088 lines
86 KiB
Markdown
2088 lines
86 KiB
Markdown
|
UI API
|
||
|
======
|
||
|
|
||
|
The UI API is the unified replacement for the older formspec and player HUD
|
||
|
systems, exposing a new system that is simpler, more robust and powerful, while
|
||
|
additionally being less buggy and quirky than its predecessors. It is not yet
|
||
|
stable, and feedback is encouraged.
|
||
|
|
||
|
**Warning**: The UI API is entirely experimental and may only be used for
|
||
|
testing purposes, not for stable mods. The API can and will change without
|
||
|
warning between versions until it is feature complete and stabilized, including
|
||
|
the network protocol.
|
||
|
|
||
|
To use the UI API, Luanti must be compiled with the `BUILD_UI` CMake option
|
||
|
turned on. The UI additionally requires SDL2 support, so `USE_SDL2` must also
|
||
|
be set. If Luanti is built without `BUILD_UI`, the `ui` namespace will still
|
||
|
exist in Lua, but the client will not have any C++ UI functionality and all UI
|
||
|
network packets will be silently ignored.
|
||
|
|
||
|
> This documentation will sometimes refer to features that are not implemented
|
||
|
> in the current version of the UI API, such as scrollbars and edit fields.
|
||
|
> These are included in the documentation since they are particularly useful
|
||
|
> for explaining some of the features of the UI API that can't be explained as
|
||
|
> precisely otherwise. When this occurs, a block like this precedes the example
|
||
|
> to note the discrepancy between the documentation and current implementation.
|
||
|
|
||
|
API design
|
||
|
----------
|
||
|
|
||
|
The UI API is exposed to Lua through the global `ui` namespace of functions and
|
||
|
classes. Most of these classes are opaque and effectively immutable, meaning
|
||
|
they have no user-visible properties or methods. Users must not access or
|
||
|
modify undocumented properties or inherit from any UI class.
|
||
|
|
||
|
All tables passed into UI API functions are defensively copied by the API.
|
||
|
Modifying a table after passing it in to a function will not change the
|
||
|
constructed object in any way.
|
||
|
|
||
|
### Example
|
||
|
|
||
|
Here is a simple example of a working UI made with the UI API:
|
||
|
|
||
|
> Since the UI API has no core theme bundled by default at this point (aside
|
||
|
> from the prelude theme), a few extra style properties are required to make
|
||
|
> anything visible.
|
||
|
|
||
|
```lua
|
||
|
local function builder(context, player, state, param)
|
||
|
return ui.Window "gui" {
|
||
|
root = ui.Root {
|
||
|
size = {108, 76},
|
||
|
padding = {4},
|
||
|
|
||
|
box_fill = "black#8C",
|
||
|
|
||
|
ui.Label {
|
||
|
pos = {0, 0}, span = {1, 1/2},
|
||
|
|
||
|
label = "Hello, world!",
|
||
|
},
|
||
|
|
||
|
ui.Button "close" {
|
||
|
pos = {0, 1/2}, span = {1, 1/2},
|
||
|
box_fill = "maroon",
|
||
|
|
||
|
label = "Close",
|
||
|
|
||
|
on_press = function(ev)
|
||
|
context:close()
|
||
|
minetest.chat_send_player(player, "The window has been closed")
|
||
|
end,
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
end
|
||
|
|
||
|
core.register_on_joinplayer(function(player)
|
||
|
ui.Context(builder, player:get_player_name()):open()
|
||
|
end)
|
||
|
```
|
||
|
|
||
|
### ID strings
|
||
|
|
||
|
Elements require unique IDs, which are represented as strings. ID strings may
|
||
|
only contain letters, numbers, dashes, underscores, and colons, and may not be
|
||
|
the empty string.
|
||
|
|
||
|
All IDs starting with a dash or underscore are reserved for use by the engine,
|
||
|
and should not be used unless otherwise specified. IDs should not use a colon
|
||
|
except to include a `mod_name:` prefix.
|
||
|
|
||
|
Element and group IDs are local to a single window, so the `mod_name:` prefix
|
||
|
used elsewhere in Luanti is generally unnecessary for them. However, if a
|
||
|
library mod creates themes, elements, or styles, then using a mod prefix for
|
||
|
the library's element and group IDs is highly encouraged to avoid ID conflicts.
|
||
|
|
||
|
Derived element type names are placed in a global namespace, so mods should
|
||
|
always use a `mod_name:` prefix for mod-created derived elements. Only the
|
||
|
engine is allowed to make elements with unprefixed type names like `switch`.
|
||
|
|
||
|
### Constructors
|
||
|
|
||
|
The UI API makes heavy use of tables, currying, and Lua's syntactic sugar for
|
||
|
function calls to provide a convenient DSL-like syntax for creating UIs. For
|
||
|
instance, the curried function signature for elements is `ui.Elem(id)(props)`
|
||
|
or `ui.Elem(props)`, depending on whether the element is given an ID. To
|
||
|
illustrate how this is used, here are some examples for creating a label:
|
||
|
|
||
|
```lua
|
||
|
-- For elements, the first curried argument is the element ID and the second is
|
||
|
-- a property table defining the element:
|
||
|
ui.Label("my_label")({
|
||
|
label = "My label text",
|
||
|
})
|
||
|
|
||
|
-- Using Lua's syntactic sugar, we can drop the function call parentheses for
|
||
|
-- string and table literals, which is the preferred convention for the UI API:
|
||
|
ui.Label "my_label" {
|
||
|
label = "My label text",
|
||
|
}
|
||
|
|
||
|
-- If the ID string or property table is a variable or expression, parentheses
|
||
|
-- are still required around one or both arguments:
|
||
|
local id = "my_label"
|
||
|
ui.Label(id) {
|
||
|
label = "My label text",
|
||
|
}
|
||
|
|
||
|
-- If the ID is not necessary, it can be omitted altogether:
|
||
|
ui.Label {
|
||
|
label = "My label text",
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The constructors for `ui.Window` and `ui.Style` use a similar curried function
|
||
|
signature.
|
||
|
|
||
|
To further increase the convenience of element and style constructors, certain
|
||
|
properties may be "inlined" into the constructor table rather than specified as
|
||
|
an explicit table. For example, the list of children for an element can be
|
||
|
specified explicitly in the `children` property, or it can be put directly in
|
||
|
the constructor table if the `children` property is omitted:
|
||
|
|
||
|
```lua
|
||
|
-- The `children` property explicitly specifies the list of children.
|
||
|
ui.Group {
|
||
|
children = {
|
||
|
ui.Label {label="Child 1"},
|
||
|
ui.Label {label="Child 2"},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
-- The `children` property is omitted, so the list of children is taken from
|
||
|
-- constructor table instead.
|
||
|
ui.Group {
|
||
|
ui.Label {label="Child 1"},
|
||
|
ui.Label {label="Child 2"},
|
||
|
}
|
||
|
```
|
||
|
|
||
|
If the `children` property is explicitly specified, then elements placed
|
||
|
directly in the constructor table will be ignored. Other properties that may be
|
||
|
inlined follow similar rules.
|
||
|
|
||
|
Unless otherwise documented, it should be assumed that all fields in
|
||
|
constructor tables are optional.
|
||
|
|
||
|
Windows
|
||
|
-------
|
||
|
|
||
|
_Windows_ represent discrete self-contained UIs formed by a tree of elements
|
||
|
and other parameters that affecting the entire window. Windows are represented
|
||
|
by the `ui.Window` class.
|
||
|
|
||
|
### Root element
|
||
|
|
||
|
The window contains a single element, the _root element_, in which the entire
|
||
|
element tree is contained. Because the root element has no parent, it is
|
||
|
positioned in the entire screen. The root element must be of type `ui.Root`.
|
||
|
|
||
|
```lua
|
||
|
-- This creates a HUD-type window with the root element as its only element.
|
||
|
ui.Window "hud" {
|
||
|
root = ui.Root {
|
||
|
span = {1, 1},
|
||
|
label = "Example HUD text",
|
||
|
},
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Window types
|
||
|
|
||
|
Windows require a _window type_, which determines whether they can receive user
|
||
|
input, what type of scaling to apply to the pixels, and the Z order of how they
|
||
|
are drawn in relation to other things on the screen. These are the following:
|
||
|
|
||
|
* `filter`: Used for things that need to be drawn before everything else, such
|
||
|
as vignettes or filters covering the outside world.
|
||
|
* `mask`: Used for visual effects in between the camera and wieldhand, such as
|
||
|
masks or other such objects.
|
||
|
* `hud`: Used for normal HUD purposes. Hidden when the HUD is hidden.
|
||
|
* `chat`: Used to display GUI-like popup boxes or chat messages that can't be
|
||
|
interacted with. Hidden when the chat is hidden.
|
||
|
* `gui`: Used for GUIs that the user can interact with.
|
||
|
|
||
|
If there are no formspecs open, then the topmost window (that is, the one that
|
||
|
was opened last) on the `gui` layer will receive user input from the keyboard,
|
||
|
mouse, and touchscreen. No other layer can receive user input. See the [Events
|
||
|
and input] section for more information on how `gui` windows handle user input.
|
||
|
|
||
|
The `gui` and `chat` window types scale all dimensions by `real_gui_scaling`
|
||
|
pixels in size from `core.get_player_window_information()`, whereas every other
|
||
|
window type scales them by `real_hud_scaling`.
|
||
|
|
||
|
The Z order for window types and other things displayed on the screen is:
|
||
|
|
||
|
* Luanti world
|
||
|
* `filter` window types
|
||
|
* Wieldhand and crosshairs
|
||
|
* `mask` window types
|
||
|
* Player HUD API elements
|
||
|
* `hud` window types
|
||
|
* Nametags
|
||
|
* `chat` window types
|
||
|
* `gui` window types
|
||
|
* Formspecs
|
||
|
|
||
|
If two or more windows of the same type are displayed at the same time, then
|
||
|
the windows that were opened more recently will be displayed on top of the
|
||
|
less recent ones.
|
||
|
|
||
|
### Styling
|
||
|
|
||
|
There are two properties in the window relevant to styling: `theme` and
|
||
|
`styles`. Both properties use `ui.Style` objects to select elements from the
|
||
|
entire element tree and apply styles to them.
|
||
|
|
||
|
The `theme` property is meant for using an external theme provided by a game or
|
||
|
mod that gives default styling to different elements. If one is not specified
|
||
|
explicitly, the current default theme from `ui.get_default_theme()` will be
|
||
|
used instead. See [Theming] for more information.
|
||
|
|
||
|
The `styles` property is intended for styling that is globally applied to a
|
||
|
single window. It takes higher precedence than the `theme` property, meaning
|
||
|
that properties set by any style within `styles` will override properties set
|
||
|
by `theme`. Any element styling that is specific to this particular window
|
||
|
should either reside in the `styles` property or in local element styles.
|
||
|
|
||
|
```lua
|
||
|
ui.Window "gui" {
|
||
|
-- Set the theme for this window to a theme provided by the mod `my_mod`.
|
||
|
-- If this line is removed, the default theme set by the game will be used.
|
||
|
theme = my_mod.get_cool_theme(),
|
||
|
|
||
|
-- This window also has its own particular styles, such as changing the
|
||
|
-- text color for some labels. These override properties set by the theme.
|
||
|
styles = {
|
||
|
ui.Style "label%warning" {
|
||
|
text_color = "yellow",
|
||
|
},
|
||
|
ui.Style "label%error" {
|
||
|
text_color = "red",
|
||
|
},
|
||
|
},
|
||
|
|
||
|
root = ui.Root {
|
||
|
size = {100, 40},
|
||
|
|
||
|
-- Aside from having any styling from the theme, this label will also
|
||
|
-- have red text due to the window style. Local styles could also be
|
||
|
-- added to the element itself to override any of these styles.
|
||
|
ui.Label {
|
||
|
groups = {"error"},
|
||
|
label = "A problem has occurred",
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
ui.Window "gui" {
|
||
|
theme = my_mod.get_cool_theme(),
|
||
|
|
||
|
-- If the `styles` property is omitted, global styles can be inlined
|
||
|
-- directly into the window for convenience.
|
||
|
ui.Style "label%warning" {
|
||
|
text_color = "yellow",
|
||
|
},
|
||
|
ui.Style "label%error" {
|
||
|
text_color = "red",
|
||
|
},
|
||
|
|
||
|
root = ui.Root {},
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Elements
|
||
|
--------
|
||
|
|
||
|
_Elements_ are the basic units of interface in the UI API and include such
|
||
|
things as sizers, buttons, and edit fields. Elements are represented by the
|
||
|
`ui.Elem` class and its subclasses.
|
||
|
|
||
|
### Element IDs
|
||
|
|
||
|
Each element in a window is required to have a unique _element ID_ that is
|
||
|
different from every other ID in that window. This ID uniquely identifies the
|
||
|
element for both network communication and styling. Elements that have user
|
||
|
interaction require an ID to be provided whereas static elements will
|
||
|
automatically generate an ID if none is provided.
|
||
|
|
||
|
```lua
|
||
|
-- Buttons are an example of an element that require an ID, since they are
|
||
|
-- dynamic and have state on the client.
|
||
|
ui.Button "my_button" {
|
||
|
label = "My button",
|
||
|
}
|
||
|
|
||
|
-- Labels are fully static, so they don't require an ID.
|
||
|
ui.Label {
|
||
|
label = "My label",
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Each element's [Type info] section lists whether IDs must be provided. Elements
|
||
|
that are not given an ID will automatically generate one with `ui.new_id()`.
|
||
|
|
||
|
```lua
|
||
|
-- Both of these elements will throw an error because buttons need unique IDs
|
||
|
-- that have not been automatically generated.
|
||
|
ui.Button {
|
||
|
label = "Missing ID",
|
||
|
}
|
||
|
|
||
|
ui.Button(ui.new_id()) {
|
||
|
label = "Auto-generated ID",
|
||
|
}
|
||
|
```
|
||
|
|
||
|
If the ID for a dynamic element changes when the UI is updated, this will
|
||
|
result in the loss of the element's persistent state, as detailed below.
|
||
|
|
||
|
### Styling
|
||
|
|
||
|
Each element has a specific _type name_ that is used when referring to the
|
||
|
element in a `SelectorSpec`. The type name of each element is listed in the
|
||
|
element's [Type info] section.
|
||
|
|
||
|
Elements can be styled according to their unique ID. Additionally, elements
|
||
|
also have a list of _group IDs_ that allow selectors to style multiple elements
|
||
|
at once. Group IDs are only used for styling.
|
||
|
|
||
|
```lua
|
||
|
ui.Window "gui" {
|
||
|
style = ui.Style {
|
||
|
-- Style all `ui.Button` elements to have a maroon background by
|
||
|
-- styling the type name `button`.
|
||
|
ui.Style "button" {
|
||
|
box_fill = "maroon",
|
||
|
},
|
||
|
|
||
|
-- Style all elements with the group `yellow` to have yellow text.
|
||
|
ui.Style "%yellow" {
|
||
|
text_color = "yellow",
|
||
|
},
|
||
|
},
|
||
|
|
||
|
root = ui.Root {
|
||
|
size = {212, 76},
|
||
|
|
||
|
scale = 1,
|
||
|
padding = {4},
|
||
|
|
||
|
box_fill = "black#8C",
|
||
|
|
||
|
ui.Label {
|
||
|
pos = {0, 0}, span = {100, 32},
|
||
|
|
||
|
label = "No style",
|
||
|
},
|
||
|
ui.Label {
|
||
|
pos = {104, 0}, span = {100, 32},
|
||
|
|
||
|
groups = {"yellow"},
|
||
|
label = "Yellow text",
|
||
|
},
|
||
|
ui.Button "a" {
|
||
|
pos = {0, 36}, span = {100, 32},
|
||
|
|
||
|
label = "Maroon background",
|
||
|
},
|
||
|
ui.Button "b" {
|
||
|
pos = {104, 36}, span = {100, 32},
|
||
|
|
||
|
groups = {"yellow"},
|
||
|
label = "Yellow text on maroon background",
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Aside from the global styles found in the window, each element may have local
|
||
|
styles of its own that only apply to itself. Effectively, these styles are the
|
||
|
same as appending nested styles to the window's list of global styles with a
|
||
|
selector that only selects the element's ID; however, a local style is often
|
||
|
more convenient. Local styles have higher precedence than styles specified in
|
||
|
the window.
|
||
|
|
||
|
```lua
|
||
|
-- This button uses local styles to set the button's background color to red
|
||
|
-- and also to make the text yellow when the box is hovered.
|
||
|
ui.Button "local" {
|
||
|
label = "Hovered yellow",
|
||
|
|
||
|
styles = {
|
||
|
ui.Style {
|
||
|
box_fill = "red",
|
||
|
},
|
||
|
ui.Style "$hovered" {
|
||
|
text_color = "yellow",
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
-- This button inlines the styles into the element itself. Since the first
|
||
|
-- style has no selector, its properties may be inlined into the constructor
|
||
|
-- directly without being wrapped in a `ui.Style` with no selector.
|
||
|
ui.Button "inline" {
|
||
|
label = "Hovered yellow",
|
||
|
|
||
|
box_fill = "red",
|
||
|
|
||
|
ui.Style "$hovered" {
|
||
|
text_color = "yellow",
|
||
|
},
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Child elements
|
||
|
|
||
|
Each element has a list of _child elements_. Child elements are positioned
|
||
|
inside their parent element, and are thus subject to any special positioning
|
||
|
rules that a specific element has, such as in the case of scrolled elements.
|
||
|
|
||
|
```lua
|
||
|
-- This group element has two buttons as children, positioned side-by-side.
|
||
|
ui.Group {
|
||
|
ui.Button "left" {
|
||
|
pos = {0, 0}, span = {1/2, 1},
|
||
|
label = "Left",
|
||
|
},
|
||
|
ui.Button "right" {
|
||
|
pos = {1/2, 0}, span = {1/2, 1},
|
||
|
label = "Right",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
-- Alternatively, we can use the `children` property to include an explicit
|
||
|
-- list of children rather than inlining them into the constructor.
|
||
|
ui.Group {
|
||
|
children = {
|
||
|
ui.Button "left" {
|
||
|
pos = {0, 0}, span = {1/2, 1},
|
||
|
label = "Left",
|
||
|
},
|
||
|
ui.Button "right" {
|
||
|
pos = {1/2, 0}, span = {1/2, 1},
|
||
|
label = "Right",
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
-- Both child elements and local styles may be inlined into the constructor
|
||
|
-- table at the same time and mixed freely.
|
||
|
ui.Group {
|
||
|
ui.Style {
|
||
|
box_fill = "red",
|
||
|
},
|
||
|
|
||
|
ui.Button "fill" {
|
||
|
label = "Button",
|
||
|
},
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The order in which elements are drawn is the parent element first, followed by
|
||
|
the first child and its descendants, then the second child, and so on, i.e.
|
||
|
drawing takes a pre-order search path.
|
||
|
|
||
|
Each `ui.Elem` object can only be used once. After being set as the child
|
||
|
element of some other element or set as the root of a window, it cannot be
|
||
|
reused, either in the same window or in another window.
|
||
|
|
||
|
### Persistent fields
|
||
|
|
||
|
Certain elements and the window have properties that can be modified by the
|
||
|
user, such as checkboxes or edit fields. However, it must also be possible for
|
||
|
the server to modify them. Since there may be substantial latency between
|
||
|
client and server, it is undesirable for the server to update every
|
||
|
user-modifiable field every time the window is updated, as is the case with
|
||
|
formspecs, since that may overwrite the user's input.
|
||
|
|
||
|
These fields that contain user input are called _persistent fields_. Normal
|
||
|
fields use a default value if the server omits the field. Persistent fields, on
|
||
|
the other hand, keep their previous value if the server omits the field.
|
||
|
|
||
|
For example, suppose there is a window with a checkbox labelled `Check me`
|
||
|
which the user has checked. Then, the server updates the window, omitting both
|
||
|
the `label` and `selected` properties. Since `label` is a normal field, the
|
||
|
checkbox's label will become empty. However, `selected` is a persistent field,
|
||
|
so the checkbox will remain checked. If the server had explicitly set the
|
||
|
`selected` property to false, the checkbox would become unchecked.
|
||
|
|
||
|
Changing persistent fields often has side effects. For instance, the UI API
|
||
|
doesn't support edit fields yet, but setting the `text` property on an edit
|
||
|
field would cause the caret to move to the end of the text. Similarly, the user
|
||
|
may have changed the state of a checkbox before a `ui.Context:update()` reached
|
||
|
the client, so always setting the `selected` property on that checkbox could
|
||
|
overwrite the user's input. As such, it is highly recommended to leave the
|
||
|
value for persistent fields at `nil` unless the server explicitly needs to
|
||
|
change the value.
|
||
|
|
||
|
Note that omitted persistent fields are set to a default value when the element
|
||
|
is first created, such as when the window is opened or reopened. The `selected`
|
||
|
property, for instance, will be false when the window is first opened unless
|
||
|
the server gives it a value.
|
||
|
|
||
|
### Derived elements
|
||
|
|
||
|
Often, there are different types of elements that work the same way, but are
|
||
|
styled in vastly different ways that make them look like entirely different
|
||
|
controls to the user. For instance, `ui.Check` and `ui.Switch` are simply
|
||
|
toggle buttons like `ui.Toggle`, but have their own conventional appearances.
|
||
|
|
||
|
Specialized appearances for these different controls can be made by using group
|
||
|
IDs, but it is often more convenient to have them act as different element
|
||
|
types entirely. Such elements are called _derived elements_, and can be created
|
||
|
using the `ui.derive_elem()` function.
|
||
|
|
||
|
Derived elements are a purely server-side construct for styling, and act
|
||
|
exactly like their normal counterpart on the client. Moreover, all the fields
|
||
|
that can be provided to the constructor of the original type can also be
|
||
|
provided to the derived type.
|
||
|
|
||
|
As an example, if a mod wanted to create a new special kind of toggle switch,
|
||
|
it could create a `MyToggle`, which acts exactly like a `ui.Toggle` except for
|
||
|
the lack of default styling:
|
||
|
|
||
|
```lua
|
||
|
local MyToggle = ui.derive_elem(ui.Toggle, "my_mod:toggle")
|
||
|
|
||
|
local function builder(context, player, state, param)
|
||
|
return ui.Window "gui" {
|
||
|
style = ui.Style {
|
||
|
-- We style our specific toggle with a basic blue color.
|
||
|
ui.Style "my_mod:toggle" {
|
||
|
box_fill = "navy",
|
||
|
},
|
||
|
ui.Style "my_mod:toggle$selected" {
|
||
|
box_fill = "blue",
|
||
|
},
|
||
|
|
||
|
-- Standard toggles are styled entirely independently of elements
|
||
|
-- derived from them.
|
||
|
ui.Style "toggle" {
|
||
|
box_fill = "olive",
|
||
|
},
|
||
|
ui.Style "toggle$selected" {
|
||
|
box_fill = "yellow",
|
||
|
},
|
||
|
},
|
||
|
|
||
|
root = ui.Root {
|
||
|
size = {108, 76},
|
||
|
padding = {4},
|
||
|
|
||
|
box_fill = "black#8C",
|
||
|
|
||
|
MyToggle "my" {
|
||
|
pos = {0, 0}, span = {1, 1/2},
|
||
|
label = "My toggle",
|
||
|
selected = true,
|
||
|
},
|
||
|
ui.Toggle "ui" {
|
||
|
pos = {0, 1/2}, span = {1, 1/2},
|
||
|
label = "Standard toggle",
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
end
|
||
|
|
||
|
core.register_on_joinplayer(function(player)
|
||
|
ui.Context(builder, player:get_player_name()):open()
|
||
|
end)
|
||
|
```
|
||
|
|
||
|
All standard derived elements can be found in the [Derived elements] section of
|
||
|
their respective element's documentation.
|
||
|
|
||
|
Boxes
|
||
|
-----
|
||
|
|
||
|
Elements handle state and behavior, but the elements themselves are invisible
|
||
|
and can't be styled on their own. Instead, elements contain _boxes_, which
|
||
|
are rectangular regions inside each element that can be styled and denote the
|
||
|
visible bounds of each part of the element.
|
||
|
|
||
|
Boxes can be styled with any of the style properties listed in `StyleSpec`,
|
||
|
such as box images or padding. Boxes also contain certain types of state
|
||
|
information relevant to styling, such as whether the mouse was pressed down
|
||
|
within the box's boundaries.
|
||
|
|
||
|
### Box positioning
|
||
|
|
||
|
Much like elements, boxes are arranged in a tree where each box can have one or
|
||
|
more child boxes. Every element has a `main` box which serves as the ancestor
|
||
|
of every other box in the element. The only exception to this rule is
|
||
|
`ui.Root`, which has a `backdrop` box as the parent of the `main` box.
|
||
|
|
||
|
Each element type has a predefined set of boxes in a fixed hierarchy. For
|
||
|
instance, the box hierarchy for scrollbars looks like the following:
|
||
|
|
||
|
> **Note**: Scrollbars are not implemented yet, so this documentation only
|
||
|
> serves as a representative example of box hierarchies.
|
||
|
|
||
|
```
|
||
|
+-----+--------------+-------------+-------------------------------+-----+
|
||
|
| /__ | | = | | __\ |
|
||
|
| \ | | = | | / |
|
||
|
+-----+--------------+-------------+-------------------------------+-----+
|
||
|
^^^^^^^. .^^^^^^^^^^^^^^^. .^^^^^^^
|
||
|
decrease . thumb . increase
|
||
|
. . . . . .
|
||
|
. ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .
|
||
|
. . before after . .
|
||
|
. . . .
|
||
|
. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .
|
||
|
. track .
|
||
|
. .
|
||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
main
|
||
|
```
|
||
|
|
||
|
Specifically, the `main` box has the scrollbar `track` box and the `decrease`
|
||
|
and `increase` boxes as children. The `track` box, in turn, has the `thumb` box
|
||
|
as a child to indicate the value of the scrollbar, plus `before` and `after`
|
||
|
boxes on either side of the thumb that can be used to give a separate style to
|
||
|
each half of the scrolled region.
|
||
|
|
||
|
Boxes can have special positioning rules for their children. For scrollbars,
|
||
|
the `decrease`, `increase`, and `track` boxes have a bounding rectangle that
|
||
|
spans the entire `main` box. As such, they can be positioned freely, e.g. the
|
||
|
buttons could be moved to the left side of the scrollbar or even hidden
|
||
|
altogether. The bounding rectangle for the `thumb` box, however, depends on the
|
||
|
value of the scrollbar since the thumb moves as the value changes. Therefore,
|
||
|
`thumb` can only be positioned in a limited fashion within its bounding
|
||
|
rectangle. The situation is similar for `before` and `after`.
|
||
|
|
||
|
Boxes are also in charge of where the content of the element is placed. For
|
||
|
simple elements like `ui.Button`, the children of the element and the label are
|
||
|
both positioned within the `main` box. More complex elements often handle
|
||
|
things differently. For instance, the `ui.Caption` element uses separate boxes
|
||
|
for these, `caption` for the label and `content` for the child elements.
|
||
|
|
||
|
The list of boxes for each element is described in the element's [Type info]
|
||
|
section. The children and content of each box, plus any special behavior they
|
||
|
might have, is documented there.
|
||
|
|
||
|
### Input and interaction
|
||
|
|
||
|
Elements do not react to mouse or touch input directly. Instead, boxes within
|
||
|
the element handle these types of input, reacting to various situations such as
|
||
|
when the box is pressed or the cursor is hovering over the box.
|
||
|
|
||
|
In the scrollbar example above, the `decrease` and `increase` boxes act like
|
||
|
buttons that can change the value of the scrollbar. The `thumb` is a box that
|
||
|
can be dragged by the pointer within the bounds of the track. These are known
|
||
|
as _dynamic_ boxes since they respond to mouse and touch input. On the other
|
||
|
hand, the `main` and `track` boxes are inert and ignore mouse and touch input
|
||
|
altogether. These are called _static_ boxes.
|
||
|
|
||
|
Static boxes are invisible to mouse or touch events and let them pass through
|
||
|
to the box directly underneath them, and hence styling the `hovered` or
|
||
|
`pressed` states never has any effect. Dynamic boxes, on the other hand,
|
||
|
respond to events, so they always respond to styling the `pressed`, `hovered`,
|
||
|
and `focused` states. Moreover, dynamic boxes generally do not let mouse or
|
||
|
touch events pass through them. In particular, only the topmost dynamic box
|
||
|
under the cursor will have the `hovered` state set.
|
||
|
|
||
|
To illustrate how events pass through different boxes, consider a button with
|
||
|
two children, a checkbox and a static label. If the mouse hovers over the
|
||
|
checkbox, which has a dynamic `main` box, the checkbox will have the `hovered`
|
||
|
state whereas the parent button will not. However, if the mouse hovers over the
|
||
|
label, which has a static `main` box, the event will pass through the label and
|
||
|
the parent button will have the `hovered` state instead.
|
||
|
|
||
|
Events and input
|
||
|
----------------
|
||
|
|
||
|
_Events_ are the means through which user input is communicated from the client
|
||
|
to the server. They range from detecting when a checkbox was pressed to
|
||
|
notifying when focus changed from one element to another.
|
||
|
|
||
|
Events come in two variants: _window events_ are events that are global to the
|
||
|
entire window, such as when the window has been closed or focus has changed.
|
||
|
_Element events_, on the other hand, fire when an event happens to a specific
|
||
|
element, such as a scrollbar being moved or a checkbox being toggled.
|
||
|
|
||
|
When a server receives an event from the client, it calls the appropriate
|
||
|
_event handler_. Event handlers are callback functions that can be provided to
|
||
|
the window or to elements as properties. The signature of every event handler
|
||
|
is `function(ev)`, where `ev` is an `EventSpec` containing information about
|
||
|
the event, such as the new value of the scrollbar that was changed. See the
|
||
|
`EventSpec` documentation for the generic fields supported by all events.
|
||
|
|
||
|
### Network latency
|
||
|
|
||
|
Beware of the effects of network latency on event handlers, since an event
|
||
|
coming from the previous state of the element may surface after the element has
|
||
|
been updated by the server. For instance, the user might click a button, and
|
||
|
the server disables a checkbox in response. However, the user clicked the
|
||
|
checkbox before the client received the server's update, causing the server to
|
||
|
receive a checkbox event after it disabled the checkbox.
|
||
|
|
||
|
The server could filter out these events, e.g. dropping all events for disabled
|
||
|
checkboxes, but this might lead to inconsistent state between the client and
|
||
|
server. In the previous example, the client would see the checkbox as checked,
|
||
|
but the server would still believe the checkbox was unchecked. This is a bad
|
||
|
situation, so the server will never drop events outright (except if the event
|
||
|
was for an element that was since removed). However, it will ensure that the
|
||
|
data contained in the event is valid, e.g. by clamping a scrollbar's value
|
||
|
to the current range of the scrollbar.
|
||
|
|
||
|
Additionally, note that fake events may come from malicious clients, such as a
|
||
|
button press for a button that was never enabled. The server will filter out
|
||
|
events that are obviously incorrect, such as when they come from the wrong
|
||
|
player or from a window that is no longer open. Validation of the data
|
||
|
contained within each event is another line of defense. However, any stronger
|
||
|
validation, such as checking whether the user could have clicked that button at
|
||
|
all (given that it was always disabled) is impractical for the UI API to check
|
||
|
automatically, and hence the responsibility lies on the event handler if
|
||
|
absolute security is necessary.
|
||
|
|
||
|
### Input dispatching
|
||
|
|
||
|
Since windows usually contain many elements that may be nested or overlapping,
|
||
|
user input is dispatched to elements in a specific order. This also has
|
||
|
important impacts on if and when event handlers are called.
|
||
|
|
||
|
First, the window contains a _focused element_, which is the element that has
|
||
|
keyboard focus. Elements that contain dynamic boxes, such as buttons and
|
||
|
scrollbars, can be focused. Static elements can never be focused.
|
||
|
|
||
|
When a keyboard key is pressed or released, the window allows the focused
|
||
|
element to use the event first. If it ignores the input (such as if the element
|
||
|
has no dynamic boxes), the window allows its parent to use the input, and so
|
||
|
forth. If none of them used the keyboard input, or if there is no focused
|
||
|
element, then the window itself gets to use the input, possibly sending it to
|
||
|
the server.
|
||
|
|
||
|
The window also contains a _hovered element_, which is the element that
|
||
|
primarily receives mouse and touch input. Just like the focused element, only
|
||
|
dynamic boxes may be the hovered element.
|
||
|
|
||
|
When mouse or touch input is received by the window, it first allows the
|
||
|
focused element and its parents to peek at the event, such as to let buttons
|
||
|
become unpressed. Then, it sends the input to the hovered element. If it
|
||
|
ignores the input, the input passes to the element directly underneath it at
|
||
|
the mouse/touch position (which is not necessarily its parent element), and so
|
||
|
on. If none of them used the input, the window again gets to use the input,
|
||
|
possibly sending it to the server.
|
||
|
|
||
|
### Window events
|
||
|
|
||
|
The window has a few predefined ways of using user input besides passing the
|
||
|
input along to the server.
|
||
|
|
||
|
The mouse is used for a number of features. When the mouse moves, the window
|
||
|
updates the currently hovered element. Similarly, when the left mouse button is
|
||
|
pressed, the window sets the focused element to the topmost focusable element
|
||
|
under the cursor.
|
||
|
|
||
|
If certain keys are not handled by the focused element, then the window uses a
|
||
|
few of them for special purposes. The Escape key will cause the window to be
|
||
|
closed if `allow_close` is not set to false for the window, which will
|
||
|
naturally cause the `on_close` event to be fired. The Enter key will cause the
|
||
|
`on_submit` event to be sent to the server, which the server may use for
|
||
|
whatever purpose it desires, such as by closing the window and using the form's
|
||
|
data somewhere.
|
||
|
|
||
|
The Tab key is used for changing which element has user focus via the keyboard.
|
||
|
Pressing Tab will cause the next focusable element in a preorder traversal of
|
||
|
the element tree to become focused, whereas pressing Shift + Tab will transfer
|
||
|
focus to the previous focusable element.
|
||
|
|
||
|
Styles
|
||
|
------
|
||
|
|
||
|
_Styles_ are the interface through which the display and layout characteristics
|
||
|
of boxes are changed. They offer a wide variety of style properties that are
|
||
|
supported by every box. Styles can vary based on _states_, which indicate how
|
||
|
the user is interacting with a box. Styles are represented with the `ui.Style`
|
||
|
class.
|
||
|
|
||
|
There are three components of a style:
|
||
|
|
||
|
1. A `SelectorSpec` that decides which boxes and states to apply the style to.
|
||
|
2. A `StyleSpec` that contains the actual styling properties.
|
||
|
3. A list of nested styles with their own selectors and properties that are
|
||
|
cascaded with the ones in the parent style.
|
||
|
|
||
|
One simple example of a style is the following:
|
||
|
|
||
|
```lua
|
||
|
-- Select all buttons with the group ID `important` and give them yellow text
|
||
|
-- and a blue background.
|
||
|
ui.Style "button%important" {
|
||
|
text_color = "yellow",
|
||
|
box_fill = "blue",
|
||
|
}
|
||
|
|
||
|
-- Alternatively, a `StyleSpec` with style properties can be included
|
||
|
-- explicitly with the `props` property rather than being inlined:
|
||
|
ui.Style "button%important" {
|
||
|
props = {
|
||
|
text_color = "yellow",
|
||
|
box_fill = "blue",
|
||
|
},
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Selectors and properties
|
||
|
|
||
|
See the respective sections on `SelectorSpec` and `StyleSpec` for advanced
|
||
|
details on selectors and the list of supported style properties. Additionally,
|
||
|
a comprehensive description of how style properties affect the layout and
|
||
|
display of boxes is also discussed in the [Layout and visuals] section.
|
||
|
|
||
|
### States
|
||
|
|
||
|
The list of states is as follows, from highest precedence to lowest:
|
||
|
|
||
|
* `disabled`: The box is disabled, which means that user interaction with it
|
||
|
does nothing.
|
||
|
* `pressed`: The left mouse button was pressed down inside the boundaries of
|
||
|
the box, and has not yet been released.
|
||
|
* `hovered`: The mouse cursor is currently inside the boundaries of the box.
|
||
|
* `selected`: The box is currently selected in some way, e.g. a checkbox is
|
||
|
checked or a list item is selected.
|
||
|
* `focused`: The box currently has keyboard focus.
|
||
|
|
||
|
The `pressed` and `hovered` states are properties of the box itself, and hence
|
||
|
only apply to dynamic boxes. Any dynamic box can be hovered and pressed,
|
||
|
although different boxes may have different requirements for what constitutes
|
||
|
being pressed.
|
||
|
|
||
|
The `disabled`, `selected`, and `focused` states are often shared among
|
||
|
multiple boxes, and hence may apply to both static and dynamic boxes. For
|
||
|
example, when an individual scrollbar is disabled, every box within that
|
||
|
scrollbar has the `disabled` state.
|
||
|
|
||
|
By default, it should be assumed that the `focused` state applies to every box
|
||
|
within the element if the element has focus. Moreover, if the element has a
|
||
|
`disabled` property, then the `disabled` state will also apply to every box
|
||
|
when that property is set. Boxes that have different behavior document how they
|
||
|
behave instead.
|
||
|
|
||
|
### State cascading
|
||
|
|
||
|
States fully cascade over each other. For instance, if there are styles that
|
||
|
give hovered buttons yellow text and pressed buttons blue backgrounds, then a
|
||
|
hovered and pressed button will have yellow text on a blue background.
|
||
|
|
||
|
```lua
|
||
|
-- Creating two styles with separate states like so...
|
||
|
ui.Style "button$hovered" {
|
||
|
text_color = "yellow",
|
||
|
}
|
||
|
ui.Style "button$pressed" {
|
||
|
box_fill = "blue",
|
||
|
}
|
||
|
|
||
|
-- ...automatically implies the following cascaded style as well:
|
||
|
ui.Style "button$hovered$pressed" {
|
||
|
text_color = "yellow",
|
||
|
box_fill = "blue",
|
||
|
}
|
||
|
```
|
||
|
|
||
|
However, if an element is currently in multiple states, then states with higher
|
||
|
precedence will override states with lower precedences. For instance, if one
|
||
|
style makes hovered buttons red and another makes pressed buttons blue, then a
|
||
|
button that is simultaneously hovered and pressed will be blue.
|
||
|
|
||
|
```lua
|
||
|
-- One style makes hovered buttons red, and another makes pressed buttons blue.
|
||
|
ui.Style "button$hovered" {
|
||
|
box_fill = "red",
|
||
|
}
|
||
|
ui.Style "button$pressed" {
|
||
|
box_fill = "blue",
|
||
|
}
|
||
|
|
||
|
-- Then, the implicit cascaded style will make buttons blue:
|
||
|
ui.Style "button$hovered$pressed" {
|
||
|
box_fill = "blue",
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Lastly, state precedences can combine to form a higher precedence state. A
|
||
|
style for pressed and hovered buttons will override styles for only pressed or
|
||
|
only hovered buttons. Similarly, a style for disabled buttons will override the
|
||
|
style for pressed and hovered buttons since it has higher precedence than
|
||
|
either one.
|
||
|
|
||
|
```lua
|
||
|
-- The first style makes hovered buttons red.
|
||
|
ui.Style "button$hovered" {
|
||
|
box_fill = "red",
|
||
|
}
|
||
|
|
||
|
-- The second style makes buttons that are both hovered and pressed blue.
|
||
|
-- Therefore, hovered buttons will only be red if they are not pressed.
|
||
|
ui.Style "button$hovered$pressed" {
|
||
|
box_fill = "blue",
|
||
|
}
|
||
|
|
||
|
-- If a button is disabled, this style makes it gray regardless of whether it
|
||
|
-- is hovered or pressed.
|
||
|
ui.Style "button$disabled" {
|
||
|
box_fill = "gray",
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Nested styles
|
||
|
|
||
|
_Nested styles_ are a way of adding styles to a base style that apply extra
|
||
|
properties to a subset of the boxes. For instance, here is an example that
|
||
|
shows how nested styles work:
|
||
|
|
||
|
```lua
|
||
|
-- Here is a style with nested styles contained within it.
|
||
|
ui.Style "button" {
|
||
|
box_fill = "yellow",
|
||
|
|
||
|
ui.Style "$hovered" {
|
||
|
box_fill = "red",
|
||
|
},
|
||
|
ui.Style "$pressed" {
|
||
|
box_fill = "blue",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
-- That style is equivalent to having the following three styles:
|
||
|
ui.Style "button" {
|
||
|
box_fill = "gray",
|
||
|
}
|
||
|
ui.Style "button$hovered" {
|
||
|
box_fill = "red",
|
||
|
}
|
||
|
ui.Style "button$pressed" {
|
||
|
box_fill = "blue",
|
||
|
}
|
||
|
|
||
|
-- The nested styles can also be explicitly included with the `nested` property
|
||
|
-- rather than being inlined into the style table:
|
||
|
ui.Style "button" {
|
||
|
box_fill = "yellow",
|
||
|
|
||
|
nested = {
|
||
|
ui.Style "$hovered" {
|
||
|
box_fill = "red",
|
||
|
},
|
||
|
ui.Style "$pressed" {
|
||
|
box_fill = "blue",
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Nested styles are always evaluated top to bottom, so the parent style is
|
||
|
applied to the box first, and then each nested style is applied to the box in
|
||
|
order. This order-dependent styling is in direct contrast to CSS, which
|
||
|
calculates precedence based on weights associated with each part of the
|
||
|
selector. Order-dependence was deliberately chosen because it gives mods more
|
||
|
control over the style of their own windows without external themes causing
|
||
|
problems. The only weighting done by the UI API is for state selectors, as
|
||
|
described above, due to states being calculated on the client.
|
||
|
|
||
|
There is no reason why a parent style containing nested styles must have a
|
||
|
selector or properties; nested styles can just be used for organizational
|
||
|
purposes by placing related styles in an empty parent style. Omitting the
|
||
|
selector causes it to be automatically set to the universal selector `*`. For
|
||
|
example, this style might be used as a theme for `ui.set_default_theme()`:
|
||
|
|
||
|
```lua
|
||
|
local theme = ui.Style {
|
||
|
ui.Style "button, toggle, option" {
|
||
|
-- Style properties for all the different types of buttons.
|
||
|
},
|
||
|
ui.Style "check, switch" {
|
||
|
-- Style properties for checkboxes and switches.
|
||
|
},
|
||
|
ui.Style "radio" {
|
||
|
-- Style properties for radio buttons.
|
||
|
},
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Style resets
|
||
|
|
||
|
Sometimes, it is desireable to remove all existing style from a certain element
|
||
|
type. This can be done via _style resets_. The `reset` boolean, when set,
|
||
|
resets all style properties for the selected elements to their default values.
|
||
|
|
||
|
For instance, if one style gives buttons a red background and nonzero padding,
|
||
|
then setting the `reset` property on a later style for buttons will reset that
|
||
|
to the defaults of a transparent background and no padding. Note that this will
|
||
|
also reset any properties set by the prelude theme, described in [Theming].
|
||
|
|
||
|
```lua
|
||
|
-- One style somewhere adds a bunch of properties to buttons.
|
||
|
ui.Style "button" {
|
||
|
box_fill = "red",
|
||
|
padding = {2, 2, 2, 2},
|
||
|
}
|
||
|
|
||
|
-- The button with the ID "special" needs unique styling, and hence uses the
|
||
|
-- `reset` property to get a clean slate for styling.
|
||
|
ui.Style "#special" {
|
||
|
reset = true,
|
||
|
text_color = "yellow",
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Style resets cascade with style states as well. Resetting buttons with the
|
||
|
`hovered` state will also reset the properties for buttons that are both
|
||
|
hovered and pressed, for instance. Resetting `button` directly will reset every
|
||
|
state for the button as well.
|
||
|
|
||
|
Theming
|
||
|
-------
|
||
|
|
||
|
It is usually the case that different windows share the same basic element
|
||
|
styles. This concept is supported natively by the UI API through the use of
|
||
|
_themes_, which are large groups of styles that can be used in the `theme`
|
||
|
property of windows or set as the globally default theme.
|
||
|
|
||
|
If a window doesn't have the `theme` property set, it will automatically use
|
||
|
the _default theme_, which is retrieved by `ui.get_default_theme()`. The engine
|
||
|
initially sets the default theme to the prelude theme, but games and mods can
|
||
|
change the default theme with the `ui.set_default_theme()` function. In the
|
||
|
future, the UI API will include a default theme called the "core theme", which
|
||
|
games can optionally use if they don't want to make their own theme or are in
|
||
|
early stages of development.
|
||
|
|
||
|
### Prelude theme
|
||
|
|
||
|
Nearly all themes should be based on the _prelude theme_, a theme that serves
|
||
|
no other purpose than being a base for other themes. The prelude theme sets
|
||
|
style properties that are essential to the functionality of the element, or are
|
||
|
at least a basic part of the element's intended design. It can be accessed with
|
||
|
the `ui.get_prelude_theme()` function.
|
||
|
|
||
|
For instance, it is part of the basic functionality of `ui.Accordion` to hide
|
||
|
and show its contents, so the prelude hides the `content` box if the `selected`
|
||
|
state is not active. As another example, `ui.Image` almost always wants its
|
||
|
image to fill as much of the element as possible, so the prelude sets their
|
||
|
`icon_scale` property to zero.
|
||
|
|
||
|
It is important to stress that the prelude theme does _not_ change the default
|
||
|
visuals of any elements. Box and icon images, tints, fills, paddings, margins,
|
||
|
and so on are never changed by the prelude theme, meaning all elements are
|
||
|
totally invisible by default and free to be themed.
|
||
|
|
||
|
The prelude theme is designed to be highly stable, and should rarely change in
|
||
|
any substantial way. Moreover, each element documents what default styling the
|
||
|
prelude theme gives it in its [Theming] section, making it easy for new themes
|
||
|
to override prelude styling where necessary.
|
||
|
|
||
|
When creating a new theme, the prelude theme should be included like so:
|
||
|
|
||
|
```lua
|
||
|
local new_theme = ui.Style {
|
||
|
-- Include the prelude theme into this theme.
|
||
|
ui.get_prelude_theme(),
|
||
|
|
||
|
-- Add any number of new styles to the theme, possibly overriding
|
||
|
-- properties set by the prelude theme.
|
||
|
ui.Style "root" {
|
||
|
box_fill = "navy#8C",
|
||
|
},
|
||
|
}
|
||
|
```
|
||
|
|
||
|
To make a window with no theme, it is recommended to set the `theme` property
|
||
|
to `ui.get_prelude_theme()`. On rare occasions, it may be useful to make a
|
||
|
window that contains no styles, including those set by the prelude. In that
|
||
|
case, the `theme` property can be set to a blank `ui.Style {}`.
|
||
|
|
||
|
Layout and visuals
|
||
|
------------------
|
||
|
|
||
|
The UI API has powerful support for positioning, sizing, and styling the
|
||
|
visuals and display of boxes. Rather than just supporting the strange
|
||
|
coordinate system of formspecs or the normalized coordinates of the player HUD
|
||
|
API, the UI API supports both pixel positioning and normalized coordinates.
|
||
|
Future versions will also support more advanced flex and grid layouts.
|
||
|
Moreover, every box supports a universal set of style properties for visuals,
|
||
|
rather than the limited and element-dependent styling of formspecs.
|
||
|
|
||
|
See `StyleSpec` for a full list of supported style properties.
|
||
|
|
||
|
### Box layout
|
||
|
|
||
|
Boxes have a number of paddings and margins can be applied to add space inside
|
||
|
and around themselves. This leads to a number of conceptual rectangular areas
|
||
|
inside each box that serve various purposes. The following diagram illustrates
|
||
|
each of these rectangles:
|
||
|
|
||
|
```
|
||
|
+---------------------------------------------------------+
|
||
|
| Layout rect |
|
||
|
| +-------------------------------------------------+ |
|
||
|
| |* Display rect * * * * * * * * * * * * * * * * *|<->|
|
||
|
| | * +-----------------------------------------+ * | Margin
|
||
|
| |* *| Middle rect |* *| |
|
||
|
| | * | +--------------------+ |<->| |
|
||
|
| |* *| +--------+ | Content rect | | Middle rect
|
||
|
| | * | |* Icon *| | | | . | | * border
|
||
|
| |* *| | * rect |<->| |---| | |<->|* *| |
|
||
|
| | * | |* * * * | Gutter | | | | Padding |
|
||
|
| |* *| +--------+ | | |* *| |
|
||
|
| | * | +--------------------+ | * | |
|
||
|
| |* *| |* *| |
|
||
|
| | * +-----------------------------------------+ * | |
|
||
|
| |* * * * * * * * * * * * * * * * * * * * * * * * *| |
|
||
|
| +-------------------------------------------------+ |
|
||
|
| |
|
||
|
+---------------------------------------------------------+
|
||
|
```
|
||
|
|
||
|
After the box is positioned within its bounding rectangle (as described below),
|
||
|
the resulting positioned rectangle is called the _layout rectangle_. The
|
||
|
contents of the layout rectangle are inset by the `margin` property, making it
|
||
|
possible to add space between adjacent boxes.
|
||
|
|
||
|
Inside the layout rectangle is the _display rectangle_, which as the name
|
||
|
implies is where the box is displayed. This is where the `box_*` style
|
||
|
properties draw their contents, e.g. `box_image` draws an image that is
|
||
|
stretched to the boundaries of the bounding rectangle. Additionally, the
|
||
|
display rectangle is where mouse and touch input are detected.
|
||
|
|
||
|
If the `box_middle` property is set, then the `box_image` is drawn as a
|
||
|
nine-slice image where the image borders are scaled by the `box_scale`
|
||
|
property. The contents of the display rectangle are automatically inset by the
|
||
|
size of this border. This results in the _middle rectangle_, which in turn
|
||
|
insets its contents by the `padding` property to make the _padding rectangle_.
|
||
|
|
||
|
Placed inside the padding rectangle is the _content rectangle_, which is where
|
||
|
the content of the box is placed. This might include text, child boxes, and/or
|
||
|
other special types of content contained in the element, or it might contain
|
||
|
nothing at all, such as in the case of scrollbar buttons and thumbs.
|
||
|
|
||
|
Finally, if the `icon_image` property is set, then an _icon rectangle_ is
|
||
|
allocated to make space for the icon. The size of this rectangle is based on
|
||
|
the size of the icon image scaled by the `icon_scale` property. By default, the
|
||
|
icon rectangle is placed in the center of the content rectangle, but it can be
|
||
|
moved to any side of the content by using the `icon_place` property. The
|
||
|
`icon_overlap` property controls whether the content rectangle should overlap
|
||
|
the icon rectangle, which will cause any content to be displayed on top of the
|
||
|
icon. If they are not set to overlap, the `icon_gutter` property can be used to
|
||
|
control how much space will be placed between them.
|
||
|
|
||
|
### Content layout
|
||
|
|
||
|
TODO
|
||
|
|
||
|
#### Place layout
|
||
|
|
||
|
#### Flex and grid layout
|
||
|
|
||
|
### Visual properties
|
||
|
|
||
|
Some style properties that control the visual aspects of boxes have been
|
||
|
described above. However, there are other properties that can modify the
|
||
|
appearance of the box.
|
||
|
|
||
|
The `display` property controls whether the box and its contents are visible
|
||
|
and/or clipped without affecting the layout of the box. The default is
|
||
|
`visible`, which makes the box and its contents visible, but they will be
|
||
|
clipped to the bounding rectangle. To prevent the box from being clipped at
|
||
|
all, the `overflow` value can be used instead. If the `hidden` value is used,
|
||
|
then the content will be displayed as normal, but the box and its icon will not
|
||
|
be drawn, even though they are still present and can be clicked as normal.
|
||
|
Lastly, the `clipped` value can be used to clip the box and its contents away
|
||
|
entirely. The box and contents still exist and take up space, but the mouse
|
||
|
will be unable to interact with them (although the keyboard still can).
|
||
|
Descendants of a `clipped` box can be shown by using the `overflow` value.
|
||
|
|
||
|
The `box_*` and `icon_*` properties have some overlap in terms of style
|
||
|
properties. The `*_fill` properties fill the respective rectangle with a solid
|
||
|
color. As mentioned before, the `*_image` properties choose the image to draw
|
||
|
inside the rectangle. To draw only part of the image, a source rectangle can be
|
||
|
specified in normalized coordinates with `*_source`. The image can also be
|
||
|
colorized by using `*_tint`. Finally, for animated images, `*_frames` can be
|
||
|
used to specify the number of frames in the image, along with `*_frame_time` to
|
||
|
set the length of each frame in milliseconds.
|
||
|
|
||
|
There are also some properties unique to both. As mentioned, the icon can use
|
||
|
`icon_scale` to set a scale factor for the icon. If this scale is set to 0,
|
||
|
then the icon rectangle fills up as much of the box as possible without
|
||
|
changing the aspect ratio of the image.
|
||
|
|
||
|
For the display rectangle, the aforementioned `box_middle` property sets the
|
||
|
image to be a nine-slice image. Like `box_source`, it is specified in
|
||
|
normalized coordinates based on the image size. The `box_tile` property can be
|
||
|
used to have the image be tiled rather than streched, which tiles each slice
|
||
|
individually for a nine-slice image. Finally, the `box_scale` property scales
|
||
|
the size of each tile in a tiled image and the borders of a nine-slice image.
|
||
|
|
||
|
Contexts
|
||
|
--------
|
||
|
|
||
|
In order to show a window, the UI API provides _contexts_, represented by the
|
||
|
`ui.Context` class. Contexts encapsulate the state of a single window and the
|
||
|
player it is shown to, and provide a means of opening, closing, rebuilding, and
|
||
|
updating the window. Unlike most classes in the UI API, `ui.Context` is not
|
||
|
immutable and contains public methods that can modify its state.
|
||
|
|
||
|
### Builder functions
|
||
|
|
||
|
In order to create a context, a _builder function_ must be provided. When
|
||
|
called, this function should return a window containing the desired element
|
||
|
tree. Every time the window is shown to a player or updated, the builder
|
||
|
function is called again. This allows the function to change the number of
|
||
|
elements or the properties they have. Since window and element objects are
|
||
|
immutable to the user, rebuilding everything using the builder function is the
|
||
|
only way to modify the UI.
|
||
|
|
||
|
Each `ui.Window` object can only be used once. After being returned from a
|
||
|
builder function once, the same object cannot be returned from a builder
|
||
|
function again. The same applies to elements inside the window.
|
||
|
|
||
|
Besides the builder function, the `ui.Context` constructor requires the name of
|
||
|
the player that the window is shown to. The builder function and player can be
|
||
|
extracted from a context using the `get_builder()` and `get_player()` methods
|
||
|
respectively.
|
||
|
|
||
|
### Opening, updating, and closing windows
|
||
|
|
||
|
After a context has been constructed, it can be shown to the player using the
|
||
|
`open()` method. If the state has changed and the UI needs to be rebuilt and
|
||
|
shown to the player, the `update()` method can be called. To close the window
|
||
|
programmatically, use the `close()` method. Open windows will be automatically
|
||
|
closed when the player leaves the game.
|
||
|
|
||
|
When a window is closed, the `update()` and `close()` methods do nothing.
|
||
|
Similarly, when the window is open, the `open()` method does nothing. If
|
||
|
multiple windows need to be shown to a player, then multiple contexts must be
|
||
|
created since a context represents a single window.
|
||
|
|
||
|
To query whether a context is open, use the `is_open()` method. To get a list of
|
||
|
all currently open contexts, use the `ui.get_open_contexts()` function.
|
||
|
|
||
|
### Non-updatable properties
|
||
|
|
||
|
Some properties cannot be updated via the `update()` method since that could
|
||
|
lead to race conditions where the server changed a property, but the client
|
||
|
sent an event that relied on the old property before it received the server's
|
||
|
changes. For most situations, this is not problematic, but it could cause weird
|
||
|
behavior for some changes, such as when changing the window type from `gui` to
|
||
|
another window type that doesn't use events.
|
||
|
|
||
|
To change these non-updatable properties, use the `reopen()` method, which
|
||
|
is effectively the same as calling `context:close():open()`, but will do so in
|
||
|
a single atomic step (i.e. the player won't see the window disappear and
|
||
|
reappear). Additionally, `reopen()` will do nothing if the window is already
|
||
|
closed, just like `update()`.
|
||
|
|
||
|
The `reopen()` method is not an exact replacement for `update()`. For one, it
|
||
|
will change the window's Z order by moving it in front of all other windows
|
||
|
with the same window type. Additionally, all persistent properties will be
|
||
|
changed by this operation, just like `open()`.
|
||
|
|
||
|
### State and parameter tables
|
||
|
|
||
|
Since UIs generally have some state associated with each open window, contexts
|
||
|
provide a means of holding state across calls to the builder function via a
|
||
|
_state table_. When a `ui.Context` is constructed, a state table can be
|
||
|
provided that holds state that will be passed to the builder function every
|
||
|
time it is called for this UI. This table can be modified by the builder
|
||
|
function or by event handlers in the UI. The UI API will never modify a state
|
||
|
table by itself. The state table can be obtained from a context via the
|
||
|
`get_state()` method or replaced entirely via `set_state()`.
|
||
|
|
||
|
The following is a basic example that uses a state table:
|
||
|
|
||
|
```lua
|
||
|
local function builder(context, player, state, param)
|
||
|
return ui.Window "gui" {
|
||
|
root = ui.Root {
|
||
|
size = {108, 40},
|
||
|
padding = {4},
|
||
|
|
||
|
box_fill = "black#8C",
|
||
|
|
||
|
ui.Button "counter" {
|
||
|
box_fill = "maroon",
|
||
|
label = "Clicks: " .. state.num_clicks,
|
||
|
|
||
|
on_press = function(ev)
|
||
|
state.num_clicks = state.num_clicks + 1
|
||
|
context:update()
|
||
|
end,
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
end
|
||
|
|
||
|
core.register_on_joinplayer(function(player)
|
||
|
local state = {num_clicks = 0}
|
||
|
ui.Context(builder, player:get_player_name(), state):open()
|
||
|
end)
|
||
|
```
|
||
|
|
||
|
The state table is primarily for persistent data. However, it is often useful
|
||
|
to send temporary data that only applies to a single call of the builder
|
||
|
function, such as when setting persistent fields. This can be provided in the
|
||
|
form of a _parameter table_. Parameter tables can be provided to the methods
|
||
|
`open()`, `update()`, and `reopen()` and will be passed directly to the builder
|
||
|
function. After the builder function returns, the parameter table is discarded.
|
||
|
|
||
|
The following is a basic example that sets a persistent field when first
|
||
|
opening the window by using parameter tables (note that it uses a slider, which
|
||
|
is not implemented yet):
|
||
|
|
||
|
```lua
|
||
|
local function builder(context, player, state, param)
|
||
|
return ui.Window "gui" {
|
||
|
root = ui.Root {
|
||
|
size = {500, 40},
|
||
|
padding = {4},
|
||
|
|
||
|
box_fill = "black#8C",
|
||
|
|
||
|
ui.Slider "percent" {
|
||
|
label = state.scroll .. "%",
|
||
|
|
||
|
min = 0,
|
||
|
max = 100,
|
||
|
|
||
|
-- `param.set_scroll` is only set in `open()`, not in the
|
||
|
-- `update()` in `on_scroll`, so `value` is only set to
|
||
|
-- `state.scroll` when the window is first opened.
|
||
|
value = param.set_scroll and state.scroll,
|
||
|
|
||
|
on_scroll = function(ev)
|
||
|
state.scroll = ev.value
|
||
|
context:update()
|
||
|
end,
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
end
|
||
|
|
||
|
core.register_on_joinplayer(function(player)
|
||
|
local state = {scroll = 50}
|
||
|
local param = {set_scroll = true}
|
||
|
|
||
|
ui.Context(builder, player:get_player_name(), state):open(param)
|
||
|
end)
|
||
|
```
|
||
|
|
||
|
Utility functions
|
||
|
-----------------
|
||
|
|
||
|
* `ui.new_id()`: Returns a new unique ID string.
|
||
|
* **This function may not be used for elements that require an ID to be
|
||
|
provided, such as buttons or edit field elements!** These elements
|
||
|
require an ID that will stay constant across window updates!
|
||
|
* It is usually not necessary to use this function directly since the API
|
||
|
will automatically generate IDs when none is required.
|
||
|
* The format of this ID is not precisely specified, but it will have the
|
||
|
format of an engine reserved ID and will not conflict with any other IDs
|
||
|
generated during this session.
|
||
|
* `ui.is_id(str)`: Checks whether the argument is a string that follows the
|
||
|
format of an ID string.
|
||
|
* `ui.get_coord_size()`: Returns the size of a single coordinate in a
|
||
|
fixed-size formspec, i.e. a formspec with a size of `size[<x>,<y>,true]`. Can
|
||
|
be used when transitioning from formspecs to the UI API.
|
||
|
* `ui.derive_elem(elem, name)`: Creates and returns a new derived element type.
|
||
|
* `elem`: The element class to derive from, e.g. `ui.Toggle`.
|
||
|
* `name`: The type name for the derived element to use. The name should use
|
||
|
a `mod_name:` prefix.
|
||
|
* Returns the constructor for the new type, which can be used to create new
|
||
|
elements of the new derived type.
|
||
|
* `ui.get_prelude_theme()`: Returns the style defining the prelude theme.
|
||
|
* `ui.get_default_theme()`: Returns the style used as the default theme for
|
||
|
windows without an explicit theme. Defaults to the prelude theme for now.
|
||
|
* `ui.set_default_theme(theme)`: Sets the default theme to a new style.
|
||
|
* `ui.get_open_contexts()`: Returns a table containing the context objects for
|
||
|
all currently open windows.
|
||
|
|
||
|
`ui.Context`
|
||
|
------------
|
||
|
|
||
|
Contexts encapsulate the state of a single window shown to a specific player,
|
||
|
as described in the [Contexts] section.
|
||
|
|
||
|
### Constructor
|
||
|
|
||
|
* `ui.Context(builder, player[, state])`: Creates a new context with a player
|
||
|
and an initial state table. The window is initially not open.
|
||
|
* `builder` (function): The builder function for the context. This function
|
||
|
takes four parameters, `function(context, player, state, param)`:
|
||
|
* `context` (`ui.Context`): The context itself.
|
||
|
* `player` (string): The name of the player that the window will be
|
||
|
shown to, equivalent to `context:get_player()`.
|
||
|
* `state` (table): The state table associated with this context,
|
||
|
equivalent to `context:get_state()`.
|
||
|
* `param` (table): The parameter table for this call to the builder
|
||
|
function.
|
||
|
* The function should return a freshly created `ui.Window` object.
|
||
|
* `player` (string): The player the context is associated with.
|
||
|
* `state` (table, optional): The initial state table for the window. If not
|
||
|
provided, defaults to an empty table.
|
||
|
|
||
|
### Methods
|
||
|
|
||
|
* `open([param])`: Builds a window and shows it to the player. Does nothing if
|
||
|
the window is already open.
|
||
|
* `param` (table, optional): The parameter table for this call to the
|
||
|
builder function. If not provided, defaults to an empty table.
|
||
|
* Returns `self` for method chaining.
|
||
|
* `update([param])`: Updates a window by rebuilding it and propagating the
|
||
|
changes to the player. Does nothing if the window is not open.
|
||
|
* `param` (table, optional): The parameter table for this call to the
|
||
|
builder function. If not provided, defaults to an empty table.
|
||
|
* Returns `self` for method chaining.
|
||
|
* `reopen([param])`: Reopens a window by rebuilding it, closing the player's
|
||
|
old window, and showing the new window to the player atomically. Does nothing
|
||
|
if the window is not open.
|
||
|
* `param` (table, optional): The parameter table for this call to the
|
||
|
builder function. If not provided, defaults to an empty table.
|
||
|
* Returns `self` for method chaining.
|
||
|
* `close()`: Closes a window that is currently shown to the player. Does
|
||
|
nothing if the window is not currently open.
|
||
|
* Returns `self` for method chaining.
|
||
|
* `is_open()`: Returns true if the window is currently open, otherwise false.
|
||
|
* `get_builder()`: Returns the builder function associated with the context.
|
||
|
* `get_player()`: Returns the player associated with the context.
|
||
|
* `get_state()`: Returns the state table associated with the context.
|
||
|
* `set_state(state)`: Sets a new state table for the context, replacing the
|
||
|
existing one.
|
||
|
* Returns `self` for method chaining.
|
||
|
|
||
|
`ui.Window`
|
||
|
-----------
|
||
|
|
||
|
Windows represent discrete self-contained UIs as described in the [Windows]
|
||
|
section.
|
||
|
|
||
|
### Constructor
|
||
|
|
||
|
* `ui.Window(type)(props)`: Creates a new window object.
|
||
|
* `type` (string): The window type for this window. This field cannot be
|
||
|
changed by `ui.Context:update()`.
|
||
|
* `props` (table): A table containing various fields for configuring the
|
||
|
window. See the [Fields] section for a list of all accepted fields.
|
||
|
|
||
|
### Fields
|
||
|
|
||
|
The following fields can be provided to the `ui.Window` constructor:
|
||
|
|
||
|
* `root` (`ui.Root`, required): The root element for the element tree.
|
||
|
* `theme` (`ui.Style`): Specifies a style to use as the window's theme.
|
||
|
Defaults to the theme provided by `ui.get_default_theme()`.
|
||
|
* `styles` (table of `ui.Style`s): A table of global styles that apply across
|
||
|
the entire element tree. If this property is omitted, global styles may be
|
||
|
inlined into the constructor table.
|
||
|
* `allow_close` (boolean): Indicates whether the user is able to close the
|
||
|
window via the Escape key or similar. Defaults to true. This field cannot be
|
||
|
changed by `ui.Context:update()`.
|
||
|
|
||
|
The following persistent fields can also be provided:
|
||
|
|
||
|
* `focused` (ID string): If present, specifies the ID of an element to set as
|
||
|
the focused element. If set to the empty string, no element will have focus.
|
||
|
Newly created windows default to having no focused element.
|
||
|
|
||
|
The following event handlers can also be provided:
|
||
|
|
||
|
* `on_close`: Fired if the window was closed by the user. This event will never
|
||
|
be fired for windows with `allow_close` set to false. Additionally, the
|
||
|
player leaving the game and `ui.Context:close()` will never fire this event.
|
||
|
* `on_submit`: Fired if the user pressed the Enter key and that keypress was
|
||
|
not used by the focused element.
|
||
|
* `on_focus_change`: Fired when the user changed the currently focused element.
|
||
|
Additional `EventSpec` fields:
|
||
|
* `unfocused`: Contains the ID of the element that just lost focus, or the
|
||
|
empty string if no element was previously focused.
|
||
|
* `focused`: Contains the ID of the element that just gained focus, or the
|
||
|
empty string if the user unfocused the current element.
|
||
|
|
||
|
`ui.Elem`
|
||
|
---------
|
||
|
|
||
|
Elements are the basic units of interface in the UI API, as described in the
|
||
|
[Elements] section. All elements inherit from the `ui.Elem` class.
|
||
|
|
||
|
In general, plain `ui.Elem`s should not be used when there is a more
|
||
|
appropriate derived element, such as `ui.Image` for an element that has the
|
||
|
sole purpose of displaying an image. Moreover, plain `ui.Elem`s should never be
|
||
|
given default styling by any theme.
|
||
|
|
||
|
### Type info
|
||
|
|
||
|
* Type name: `elem`
|
||
|
* ID required: No
|
||
|
* Boxes:
|
||
|
* `main` (static): The main box of the element. Unless otherwise stated in
|
||
|
the documentation for other boxes, text content and child elements are
|
||
|
placed within this box.
|
||
|
|
||
|
### Derived elements
|
||
|
|
||
|
* `ui.Group`
|
||
|
* Type name: `group`
|
||
|
* A static element meant for holding generic grouping of elements for
|
||
|
layout purposes, similar to the HTML `<div>` element.
|
||
|
* `ui.Label`
|
||
|
* Type name: `label`
|
||
|
* A static element that is meant to be used for static textual labels in a
|
||
|
window. The regular `label` property should be used to display the text
|
||
|
for the label.
|
||
|
* `ui.Image`
|
||
|
* Type name: `image`
|
||
|
* A static element that only meant for displaying an image, either static
|
||
|
or animated. The `icon_image` style property should be used to display
|
||
|
the image, not the `box_image` property.
|
||
|
|
||
|
### Theming
|
||
|
|
||
|
For `ui.Image`, the prelude sets `icon_image` to zero to make the image fill as
|
||
|
much space as possible. There is no prelude theming for either `ui.Elem` or
|
||
|
`ui.Label`.
|
||
|
|
||
|
### Constructor
|
||
|
|
||
|
All elements have the same function signatures as `ui.Elem` for their
|
||
|
constructors and only differ in the properties accepted for `props`.
|
||
|
|
||
|
* `ui.Elem(id)(props)`: Creates a new element object.
|
||
|
* `id` (ID string): The unique ID for this element. It is only required if
|
||
|
stated as such in the element's [Type info] section.
|
||
|
* `props` (table): A table containing various fields for configuring the
|
||
|
element. See the [Fields] section for a list of all accepted fields.
|
||
|
* `ui.Elem(props)`: Same as the above constructor, but generates an ID with
|
||
|
`ui.new_id()` automatically.
|
||
|
|
||
|
### Fields
|
||
|
|
||
|
The following fields can be provided to the `ui.Elem` constructor:
|
||
|
|
||
|
* `label` (string): The text label to display for the element.
|
||
|
* `groups` (table of ID strings): The list of group IDs for this element.
|
||
|
* `props` (`StyleSpec`): A table of style properties that only apply to this
|
||
|
element's `main` box without any state selectors applied. If this property is
|
||
|
omitted, these style properties may be inlined into the constructor table.
|
||
|
* `styles` (table of `ui.Style`s): A table of local styles that only apply to
|
||
|
this element. If this property is omitted, local styles may be inlined into
|
||
|
the constructor table.
|
||
|
* `children` (table of `ui.Elem`s): The list of elements that are children of
|
||
|
this element. If this property is omitted, children may be inlined into the
|
||
|
constructor table.
|
||
|
|
||
|
`ui.Root`
|
||
|
---------
|
||
|
|
||
|
The root element is a special type of element that is used for the sole purpose
|
||
|
of being the root of the element tree. Root elements may not be used anywhere
|
||
|
else in the element tree.
|
||
|
|
||
|
Aside from its `main` box, the root element also has a `backdrop` box as the
|
||
|
the parent of its `main` box. The backdrop takes up the entire screen behind
|
||
|
the window and is intended to be used for dimming or hiding things behind a
|
||
|
window with a translucent or opaque background.
|
||
|
|
||
|
The root element is fully static, but the `backdrop` box will have the
|
||
|
`focused` state set if the window is a GUI window that has user focus. In
|
||
|
general, the `backdrop` box should be invisible unless it is focused. This
|
||
|
prevents backdrops from different windows from stacking on top of each other,
|
||
|
which generally leads to an undesirable appearance.
|
||
|
|
||
|
The `backdrop` box does not count as part of the element for mouse clicks, so
|
||
|
clicking on the backdrop counts as clicking outside the window.
|
||
|
|
||
|
### Type info
|
||
|
|
||
|
* Type name: `root`
|
||
|
* ID required: No
|
||
|
* Boxes:
|
||
|
* `backdrop` (static): The backdrop box, which has the entire screen as its
|
||
|
layout rectangle. It is the parent of the `main` box.
|
||
|
* `main` (static): The main box. See `ui.Elem` for more details.
|
||
|
|
||
|
### Theming
|
||
|
|
||
|
The prelude centers the `main` box on the screen with `pos` and `anchor`, but
|
||
|
gives it no size, so users must explicitly choose a size with `span` and/or
|
||
|
`size`. Additionally, the prelude sets `display` to `hidden` for the backdrop
|
||
|
unless it has the `focused` state set. The backdrop also sets `clip` to `both`
|
||
|
to ensure that the backdrop never expands past the screen size even if its
|
||
|
content does.
|
||
|
|
||
|
### Fields
|
||
|
|
||
|
The fields that can be provided to the `ui.Root` constructor are the same as
|
||
|
those in `ui.Elem`.
|
||
|
|
||
|
`ui.Button`
|
||
|
-----------
|
||
|
|
||
|
The button is a very simple interactive element that can do nothing except be
|
||
|
clicked. When the button is clicked with the mouse or pressed with the
|
||
|
spacebar, then the `on_press` event is fired unless the button is disabled.
|
||
|
|
||
|
### Type info
|
||
|
|
||
|
* Type name: `button`
|
||
|
* ID required: Yes
|
||
|
* Boxes:
|
||
|
* `main` (dynamic): The main box, which constitutes the pressable portion
|
||
|
of the button. Also see `ui.Elem` for more details.
|
||
|
|
||
|
### Theming
|
||
|
|
||
|
There is no prelude theming for `ui.Button`.
|
||
|
|
||
|
### Fields
|
||
|
|
||
|
In additional to all fields in `ui.Elem`, the following fields can be provided
|
||
|
to the `ui.Button` constructor:
|
||
|
|
||
|
* `disabled` (boolean): Indicates whether the button is disabled, meaning the
|
||
|
user cannot interact with it. Default false.
|
||
|
|
||
|
The following event handlers can also be provided:
|
||
|
|
||
|
* `on_press`: Fired if the button was just pressed.
|
||
|
|
||
|
`ui.Toggle`
|
||
|
-----------
|
||
|
|
||
|
The toggle button is a type of button that has two states: selected and
|
||
|
deselected. In its simplest form, the state of the toggle button flips between
|
||
|
selected and deselected whenever it is pressed. The state of the toggle button
|
||
|
can be controlled programmatically by the persistent `selected` property.
|
||
|
|
||
|
Toggle buttons have two events, `on_press` and `on_change`, which fire
|
||
|
simultaneously. Although `on_change` is a strict superset of the functionality
|
||
|
of `on_press`, both are provided for parity with `ui.Option`.
|
||
|
|
||
|
### Type info
|
||
|
|
||
|
* Type name: `toggle`
|
||
|
* ID required: Yes
|
||
|
* Boxes:
|
||
|
* `main` (dynamic): The main box, which constitutes the pressable portion
|
||
|
of the toggle button. The `selected` state is active when the toggle
|
||
|
button is selected. Also see `ui.Elem` for more details.
|
||
|
|
||
|
### Derived elements
|
||
|
|
||
|
* `ui.Check`
|
||
|
* Type name: `check`
|
||
|
* A variant of the toggle button that is meant to be styled like a
|
||
|
traditional checkbox rather than a pushable button.
|
||
|
* `ui.Switch`
|
||
|
* Type name: `switch`
|
||
|
* A variant of the toggle button that is meant to be styled like a switch
|
||
|
control; that is, a horizontal switch where left corresponds to
|
||
|
deselected and right corresponds to selected.
|
||
|
|
||
|
### Theming
|
||
|
|
||
|
The prelude horizontally aligns the icon image for `ui.Check` and `ui.Switch`
|
||
|
to the left. There is no prelude theming for `ui.Toggle`.
|
||
|
|
||
|
### Fields
|
||
|
|
||
|
In additional to all fields in `ui.Elem`, the following fields can be provided
|
||
|
to the `ui.Toggle` constructor:
|
||
|
|
||
|
* `disabled` (boolean): Indicates whether the toggle button is disabled,
|
||
|
meaning the user cannot interact with it. Default false.
|
||
|
|
||
|
The following persistent fields can also be provided:
|
||
|
|
||
|
* `selected` (boolean): If present, changes the state of the toggle button to a
|
||
|
new value. Newly created toggle buttons default to false.
|
||
|
|
||
|
The following event handlers can also be provided:
|
||
|
|
||
|
* `on_press`: Fired if the toggle button was just pressed.
|
||
|
* `on_change`: Fired if the value of the toggle button changed. Additional
|
||
|
`EventSpec` fields:
|
||
|
* `selected` (boolean): The state of the toggle button.
|
||
|
|
||
|
`ui.Option`
|
||
|
-----------
|
||
|
|
||
|
The option button is similar to a toggle button in the sense that it can be
|
||
|
either selected or deselected. However, option buttons are grouped into
|
||
|
_families_ such that exactly one option button in each family can be selected
|
||
|
at a time by the user.
|
||
|
|
||
|
The family of an option button is controlled by the `family` property, which is
|
||
|
set to a ID string that is shared by each option button in the family. If no
|
||
|
family is provided, the option button acts as if it were alone in a family with
|
||
|
one member.
|
||
|
|
||
|
When the user presses a non-disabled option button, that button is selected and
|
||
|
all the other buttons in the family (including disabled ones) are deselected.
|
||
|
Although the user can only select one option button, zero or more option
|
||
|
buttons may be set programmatically via the persistent `selected` property.
|
||
|
|
||
|
Option buttons have two events: `on_press` and `on_change`. The `on_press`
|
||
|
event occurs whenever a user presses an option button, even if that button was
|
||
|
already selected. The `on_change` event occurs whenever the value of an option
|
||
|
button changes, whether that be the button the user selected or the other
|
||
|
buttons in the family that were deselected. The `on_change` event will fire for
|
||
|
the selected button first, followed by the deselected buttons.
|
||
|
|
||
|
### Type info
|
||
|
|
||
|
* Type name: `option`
|
||
|
* ID required: Yes
|
||
|
* Boxes:
|
||
|
* `main` (dynamic): The main box, which constitutes the pressable portion
|
||
|
of the option button. The `selected` state is active when the option
|
||
|
button is selected. Also see `ui.Elem` for more details.
|
||
|
|
||
|
### Derived elements
|
||
|
|
||
|
* `ui.Radio`
|
||
|
* Type name: `radio`
|
||
|
* A variant of the option button that is meant to be styled like a
|
||
|
traditional radio button rather than a pushable button.
|
||
|
|
||
|
### Theming
|
||
|
|
||
|
The prelude horizontally aligns the icon image for `ui.Radio` to the left.
|
||
|
There is no prelude theming for `ui.Option`.
|
||
|
|
||
|
### Fields
|
||
|
|
||
|
In additional to all fields in `ui.Elem`, the following fields can be provided
|
||
|
to the `ui.Option` constructor:
|
||
|
|
||
|
* `disabled` (boolean): Indicates whether the option button is disabled,
|
||
|
meaning the user cannot interact with it. Default false.
|
||
|
* `family` (ID string): Sets the family of the option button. If none is
|
||
|
provided, the option button works independently of any others. Default none.
|
||
|
|
||
|
The following persistent fields can also be provided:
|
||
|
|
||
|
* `selected` (boolean): If present, changes the state of the option button to a
|
||
|
new value. Newly created option buttons default to false.
|
||
|
|
||
|
The following event handlers can also be provided:
|
||
|
|
||
|
* `on_press`: Fired if the option button was just pressed.
|
||
|
* `on_change`: Fired if the value of the option button changed. Additional
|
||
|
`EventSpec` fields:
|
||
|
* `selected` (boolean): The state of the option button.
|
||
|
|
||
|
`ui.Style`
|
||
|
----------
|
||
|
|
||
|
Styles are the interface through which the display and layout characteristics
|
||
|
of element boxes are changed, as described in [Styles].
|
||
|
|
||
|
### Constructor
|
||
|
|
||
|
* `ui.Style(sel)(props)`: Creates a new style object.
|
||
|
* `sel` (`SelectorSpec`): The primary selector that this style applies to.
|
||
|
* `props` (table): A table containing various fields for configuring the
|
||
|
style. See the [Fields] section for a list of all accepted fields.
|
||
|
* `ui.Style(props)`: Same as the above constructor, but the selector defaults
|
||
|
to `*` instead.
|
||
|
|
||
|
### Fields
|
||
|
|
||
|
The following fields can be provided to the `ui.Style` constructor:
|
||
|
|
||
|
* `props` (`StyleSpec`): The table of properties applied by this style. If
|
||
|
this property is omitted, style properties may be inlined into the
|
||
|
constructor table.
|
||
|
* `nested` (table of `ui.Style`s): A list of `ui.Style`s that should be used as
|
||
|
the cascading nested styles of this style. If this property is omitted,
|
||
|
nested styles may be inlined into the constructor table.
|
||
|
* `reset` (boolean): If true, resets all style properties for the selected
|
||
|
elements to their default values before applying the new properties.
|
||
|
|
||
|
`SelectorSpec`
|
||
|
--------------
|
||
|
|
||
|
A _selector_ is a string similar to a CSS selector that matches elements by
|
||
|
various attributes, such as their ID, group, and state, what children they
|
||
|
have, etc. Many of the same concepts apply from CSS, and there are a few
|
||
|
similarities in the syntax, but there is no compatibility between the two.
|
||
|
|
||
|
### Usage and syntax
|
||
|
|
||
|
A selector is composed of one or more _terms_. For instance, the `button` term
|
||
|
selects all button elements, whereas the `$hovered` term selects all boxes in
|
||
|
the hovered state. Terms can be combined with each other by concatenation to
|
||
|
form a longer and more specific selector. For instance, the selector
|
||
|
`button$hovered` selects elements that are both buttons and are hovered.
|
||
|
|
||
|
Using a comma between terms forms the union of both terms. So, `button,
|
||
|
$hovered` selects terms that are either buttons or are hovered. These can be
|
||
|
combined freely, e.g. `button$pressed, scrollbar$hovered` selects elements that
|
||
|
are pressed buttons or hovered scrollbars.
|
||
|
|
||
|
The order of operations for these operations can be controlled by parentheses,
|
||
|
so `(button, check)($pressed, $hovered)` is the same as the much longer
|
||
|
selector `button$pressed, button$hovered, check$pressed, check$hovered`.
|
||
|
|
||
|
Note that it is not invalid for a selector to have contradictory terms. The
|
||
|
selector `#abc#xyz` is valid, but will never select anything since an element
|
||
|
can only have a single ID.
|
||
|
|
||
|
Whitespace between terms is ignored, so both `button$hovered` and `button
|
||
|
$hovered` are valid and equivalent, although it is customary to only put
|
||
|
whitespace after commas. The order that selectors are written in is also
|
||
|
irrelevant, so `button$hovered` and `$hovered button` are equivalent, although
|
||
|
the former order is preferred by convention.
|
||
|
|
||
|
### Basic terms
|
||
|
|
||
|
The full list of basic terms is as follows, listed in the conventional order
|
||
|
that they should be written in:
|
||
|
|
||
|
* `/window/` matches any element inside a window with window type `window`.
|
||
|
* `*` matches every element. This is necessary since empty selectors and terms
|
||
|
are invalid. It is redundant when combined with other terms.
|
||
|
* `type` matches any element with the type name `type`. Inheritance is ignored,
|
||
|
so `elem` matches `ui.Elem` but not `ui.Label` or `ui.Button`.
|
||
|
* `#id` matches any element with the ID `id`.
|
||
|
* `%group` matches any element that contains the group `group`.
|
||
|
* `@*` matches any box within any element, not just the `main` box.
|
||
|
* `@box` matches any box with the name `box` within any element.
|
||
|
* `$state` matches any box currently in the state `state` within any element.
|
||
|
|
||
|
### Box selectors
|
||
|
|
||
|
The `@box` selector selects a specific box to style. For instance, a selector
|
||
|
of `scrollbar%overflow@thumb` can be used to style the thumb of any scrollbar
|
||
|
with the group `overflow`. On the other hand, `button@thumb` will not match
|
||
|
anything since buttons don't have a `thumb` box. Similarly, `@main@thumb` will
|
||
|
not match anything since a box cannot be two separate boxes at once.
|
||
|
|
||
|
By default, a selector that contains no box selector will only match the `main`
|
||
|
box. Alternatively, the special `@*` box selector can be used to select every
|
||
|
box in the element. For instance, `accordion@*` would be equivalent to
|
||
|
`accordion(@main, @caption, @content)`. This selector is especially useful when
|
||
|
performing style resets for an entire element and all the boxes within it.
|
||
|
|
||
|
### Predicates
|
||
|
|
||
|
Terms can be combined with more complicated operators called _predicates_. The
|
||
|
simplest predicate is the `!` predicate, which inverts a selector. So `!%tall`
|
||
|
selects all elements without the `tall` group. Note that `!` only applies to
|
||
|
the term directly after it, so `!button%tall` means `(!button)%tall`. To invert
|
||
|
both terms, use `!(button%tall)`.
|
||
|
|
||
|
Predicates can only work with certain types of selectors, called _predicate
|
||
|
selectors_. Predicate selectors cannot use box or state selectors, since the
|
||
|
server has no knowledge of which boxes are in which states at any given time.
|
||
|
For example, the selector `button!$pressed` is invalid. On the other hand,
|
||
|
the selector that `ui.Style` uses is called a _primary selector_, which is a
|
||
|
selector that is allowed to use boxes and states outside of predicates.
|
||
|
|
||
|
All predicates other than `!` start with the `?` symbol. _Simple predicates_
|
||
|
are one such predicate type, which match an element based on some intrinsic
|
||
|
property of that element. The `?first_child` predicate, for instance, checks
|
||
|
if an element is the first child of its parent.
|
||
|
|
||
|
There are also _function predicates_ which take extra parameters, usually a
|
||
|
selector, to select the element. For instance, the `?<()` predicate tries to
|
||
|
match a selector `sel` against a parent element. So, `?<(button, check)`
|
||
|
matches all elements that have a button or a checkbox as their parent. Not all
|
||
|
function predicates take selectors as parameters, such as `?nth_child()`, which
|
||
|
takes a positive integer instead.
|
||
|
|
||
|
### List of predicates
|
||
|
|
||
|
The complete list of predicates aside from `!` is as follows:
|
||
|
|
||
|
* `?no_children` matches all elements with no children.
|
||
|
* `?first_child` matches all elements that are the first child of their parent
|
||
|
element.
|
||
|
* `?last_child` matches all elements that are the last child of their parent
|
||
|
element.
|
||
|
* `?only_child` matches all elements that are the only child of their parent
|
||
|
element.
|
||
|
* `?nth_child(index)` matches all elements that are at the `index`th child
|
||
|
from the start of their parent.
|
||
|
* `?nth_last_child(index)` matches all elements that are the `index`th
|
||
|
child from the end of their parent.
|
||
|
* `?first_match(sel)` matches all elements that are the first child of their
|
||
|
parent to be matched by the selector `sel`.
|
||
|
* `?last_match(sel)` matches all elements that are the last child of their
|
||
|
parent to be matched by the selector `sel`.
|
||
|
* `?only_match(sel)` matches all elements that are the only child of their
|
||
|
parent to be matched by the selector `sel`.
|
||
|
* `?nth_match(sel; index)` matches all elements that are the `index`th child
|
||
|
from the start of their parent to be matched by the selector `sel`.
|
||
|
* `?nth_last_match(sel; index)` matches all elements that are the `index`th
|
||
|
child from the end of their parent to be matched by the selector `sel`.
|
||
|
* `?<(sel)` matches all elements whose parent is matched by the selector `sel`.
|
||
|
* `?>(sel)` matches all elements that have a child that is matched by the
|
||
|
selector `sel`.
|
||
|
* `?<<(sel)` matches all elements that have an ancestor that is matched by the
|
||
|
selector `sel`.
|
||
|
* `?>>(sel)` matches all elements that have a descendant that is matched by the
|
||
|
selector `sel`.
|
||
|
* `?<>(sel)` matches all elements that have a sibling that is matched by the
|
||
|
selector `sel`.
|
||
|
* `?family(name)` matches all elements that have a family of `name`. If `name`
|
||
|
is `*`, then it matches elements that have a family, regardless of which one.
|
||
|
|
||
|
Note that the root element is considered to be the only "child" of the window,
|
||
|
so it is matched by e.g. `?first_child` and `?only_match(root)`.
|
||
|
|
||
|
Unlike CSS, there are no child or descendant combinators since the more
|
||
|
powerful `?<()` and `?<<()` predicates can be used instead. Note that
|
||
|
predicates like `?>>()` should be used with care since they may have to check a
|
||
|
large number of elements in order to see if there is a match, which may cause
|
||
|
loss of performance for particularly large UIs.
|
||
|
|
||
|
`StyleSpec`
|
||
|
------------
|
||
|
|
||
|
A `StyleSpec` is a plain table of properties for use in `ui.Style` or as
|
||
|
inlined properties in `ui.Elem`.
|
||
|
|
||
|
### Field formats
|
||
|
|
||
|
`StyleSpec` has specific field formats for positions and rectangles:
|
||
|
|
||
|
* 2D vector: A table `{x, y}` representing a position, size, or offset. The
|
||
|
shorthand `{num}` can be used instead of `{num, num}`.
|
||
|
* Rectangle: A table `{left, top, right, bottom}` representing a rectangle or
|
||
|
set of borders. The shorthand `{x, y}` can be used instead of `{x, y, x, y}`,
|
||
|
and `{num}` can be used instead of `{num, num, num, num}`.
|
||
|
|
||
|
### Fields
|
||
|
|
||
|
All properties are optional. Invalid properties are ignored.
|
||
|
|
||
|
#### Layout fields
|
||
|
|
||
|
* `layout` (string): Chooses what layout scheme this box will use when laying
|
||
|
its children out. Currently, the only valid option is `place`.
|
||
|
* `clip` (string): Normally, a box expands its minimum size if there's not
|
||
|
enough space for the content, but this property can specify that the content
|
||
|
be clipped in the horizontal and/or vertical directions instead. One of
|
||
|
`none`, `x`, `y`, or `both`. Default `none`.
|
||
|
* `scale` (number): The scale factor by which positions and sizes will be
|
||
|
multiplied by in `place` layouts. Default 0.
|
||
|
* A scale of zero will scale coordinates by the width and height of the
|
||
|
parent box, effectively creating normalized coordinates.
|
||
|
|
||
|
#### Sizing fields
|
||
|
|
||
|
* `size` (2D vector): The minimum size of the box in pixels. May not be
|
||
|
negative. Default `{0, 0}`.
|
||
|
* `span` (2D vector): The size that the layout scheme will allocate for the
|
||
|
box, scaled by `scale`. Default `{1, 1}`.
|
||
|
* `pos` (2D vector): The position that the layout scheme will place the box at,
|
||
|
scaled by `scale`. Default `{0, 0}`.
|
||
|
* `anchor` (2D vector): The point which should be considered the origin for
|
||
|
placing the box. Default `{0, 0}`.
|
||
|
* Specified in normalized coordinates relative to the size of the box
|
||
|
itself, e.g. `{1/2, 1/2}` means to position the box from its center.
|
||
|
* `margin` (rectangle): Margin in pixels of blank space between the layout
|
||
|
rectangle and the display rectangle. It is valid for margins to be negative.
|
||
|
Default `{0, 0, 0, 0}`.
|
||
|
* `padding` (rectangle): Padding in pixels of blank space between the middle
|
||
|
rectangle and the padding rectangle. It is valid for padding to be negative.
|
||
|
Default `{0, 0, 0, 0}`.
|
||
|
|
||
|
#### Visual fields
|
||
|
|
||
|
* `display` (string): Specifies how to display this box and its contents. One
|
||
|
of `visible`, `overflow`, `hidden`, or `clipped`. Default `visible`.
|
||
|
|
||
|
#### Box fields
|
||
|
|
||
|
* `box_image` (texture): Image to draw in the display rectangle of the box, or
|
||
|
`""` for no image. The image is stretched to fit the rectangle. Default `""`.
|
||
|
* `box_fill` (`ColorSpec`): Color to fill the display rectangle of the box
|
||
|
with, drawn behind the box image. Default transparent.
|
||
|
* `box_tint` (`ColorSpec`): Color to multiply the box image by. Default white.
|
||
|
* `box_source` (rectangle): Allows a sub-rectangle of the box image to be drawn
|
||
|
from instead of the whole image. Default `{0, 0, 1, 1}`.
|
||
|
* Uses normalized coordinates relative to the size of the texture. E.g.
|
||
|
`{1/2, 1/2, 1, 1}` draws the lower right quadrant of the texture. This
|
||
|
makes source rectangles friendly to texture packs with varying base
|
||
|
texture sizes.
|
||
|
* The top left coordinates may be greater than the bottom right
|
||
|
coordinates, which flips the image. E.g. `{1, 0, 0, 1}` flips the image
|
||
|
horizontally.
|
||
|
* Coordinates may extend past the image boundaries, including being
|
||
|
negative, which repeats the texture. E.g. `{-1, 0, 2, 1}` displays three
|
||
|
copies of the texture side by side.
|
||
|
* `box_frames` (integer): If the box image should be animated, the source
|
||
|
rectangle is vertically split into this many frames that will be animated,
|
||
|
starting with the top. Must be positive. If set to one, the image will be
|
||
|
static. Default 1.
|
||
|
* `box_frame_time` (integer): Time in milliseconds to display each frame in an
|
||
|
animated box image for. Must be positive. Default 1000.
|
||
|
* `box_middle` (rectangle): If the box image is to be a nine-slice image (see
|
||
|
<https://en.wikipedia.org/wiki/9-slice_scaling>), then this defines the size
|
||
|
of each border of the nine-slice image. Default `{0, 0, 0, 0}`.
|
||
|
* Uses normalized coordinates relative to the size of the texture. E.g.
|
||
|
`{2/16, 1/16, 2/16, 1/16}` will make the horizontal borders 2 pixels and
|
||
|
the vertical borders 1 pixel on a 16 by 16 image. May not be negative.
|
||
|
* In conjunction with `box_scale`, this also defines the space between the
|
||
|
display rectangle and the middle rectangle.
|
||
|
* `box_tile` (string): Specifies whether the image should be tiled rather than
|
||
|
stretched in the horizontal or vertical direction. One of `none`, `x`, `y`,
|
||
|
or `both`. Default `none`.
|
||
|
* If used in conjunction with `box_middle`, then each slice of the image
|
||
|
will be tiled individually.
|
||
|
* `box_scale` (number): Defines the scaling factor by which each tile in a
|
||
|
tiled image and the borders of a nine-slice image should be scaled by. May
|
||
|
not be negative. Default 1.
|
||
|
|
||
|
#### Icon fields
|
||
|
|
||
|
* `icon_image` (texture): Image to draw in the icon rectangle of the box, or
|
||
|
`""` for no image. The image always maintains its aspect ratio. Default `""`.
|
||
|
* `icon_fill` (`ColorSpec`): Color to fill the icon rectangle of the box with,
|
||
|
drawn behind the icon image. Default transparent.
|
||
|
* `icon_tint` (`ColorSpec`): See `box_tint`, but for `icon_image`.
|
||
|
* `icon_source` (rectangle): See `box_source`, but for `icon_image`.
|
||
|
* `icon_frames` (integer): See `box_frames`, but for `icon_image`.
|
||
|
* `icon_frame_time` (integer): See `box_frame_time`, but for `icon_image`.
|
||
|
* `icon_scale` (number): Scales the icon up by a specific factor. Default 1.
|
||
|
* For instance, a factor of two will make the icon twice as large as the
|
||
|
size of the texture.
|
||
|
* A scale of 0 will make the icon take up as much room as possible without
|
||
|
being larger than the box itself. The scale may not be negative.
|
||
|
* `icon_place` (string): Determines which side of the padding rectangle the
|
||
|
icon rectangle should be placed on. One of `center`, `left`, `top`, `right`,
|
||
|
or `bottom`. Default `center`.
|
||
|
* `icon_overlap` (boolean): Determines whether the content rectangle should
|
||
|
overlap the icon rectangle. If `icon_place` is `center`, then they will
|
||
|
always overlap and this property has no effect. Default false.
|
||
|
* `icon_gutter` (number): Space in pixels between the content and icon
|
||
|
rectangles if they are not set to overlap. It is valid for the gutter to be
|
||
|
negative. This property has no effect if no icon image is set. Default 0.
|
||
|
|
||
|
`EventSpec`
|
||
|
-----------
|
||
|
|
||
|
An `EventSpec` is a plain table that is passed to event handler functions to
|
||
|
give detailed information about what changed during the event and where the
|
||
|
event was targeted.
|
||
|
|
||
|
An `EventSpec` always contains the following fields:
|
||
|
|
||
|
* `context` (`ui.Context`): The context that the event originates from.
|
||
|
* `player` (string): The name of the player that the window is shown to,
|
||
|
equivalent to `context:get_player()`.
|
||
|
* `state` (table): The state table associated with the context, equivalent to
|
||
|
`context:get_state()`.
|
||
|
|
||
|
Additionally, `EventSpec`s that are sent to elements have an additional field:
|
||
|
|
||
|
* `target` (ID string): The ID of the element that the event originates from.
|
||
|
|
||
|
`EventSpec`s may have other fields, depending on the specific event. See each
|
||
|
event handler's documentation for more detail.
|