From 93ec0443ea10e677c23059f0e2cd5ab9c0b59ac8 Mon Sep 17 00:00:00 2001 From: v-rob Date: Thu, 12 Sep 2024 16:11:41 -0700 Subject: [PATCH] Add documentation for UI API --- doc/experimental_ui_api.md | 2027 ++++++++++++++++++++++++++++++++++++ 1 file changed, 2027 insertions(+) create mode 100644 doc/experimental_ui_api.md diff --git a/doc/experimental_ui_api.md b/doc/experimental_ui_api.md new file mode 100644 index 0000000000000..a4266e3a9b8d3 --- /dev/null +++ b/doc/experimental_ui_api.md @@ -0,0 +1,2027 @@ +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. + +**Note**: 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. The documentation +always makes a note of when it refers to unsupported features. + +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: + +```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) +``` + +Note that, since the UI API has no theme bundled by default at this point, a +few extra style properties are required to make anything visible. + +### 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 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 +`style`. Both properties use a `ui.Style` object 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 `style` property is intended for styling that is local to a single window. +It takes higher precedence than the `theme` property, meaning that properties +set by `style` will override properties set by `theme`. Any element styling +that is specific to this particular window should either reside in the `style` +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. + style = ui.Style { + 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", + }, + }, +} +``` + +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 style found in the window, each element may have a local +style of its own that only applies to itself. Effectively, this style is the +same as appending a nested style to the window's global style 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 a local style 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 = "Yellow when hovered on red", + + style = ui.Style { + box_fill = "red", + + ui.Style "$hovered" { + text_color = "yellow", + }, + }, +} + +-- This button inlines the style property into the element itself, which is +-- more concise but loses the ability to use advanced features like state +-- selectors and nested styles like the previous button. +ui.Button "inline" { + label = "Yellow on red", + + text_color = "yellow", + box_fill = "red", +} +``` + +### 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", + }, + }, +} +``` + +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 UI API doesn't support scrollbars yet, but when implemented, the +box hierarchy will look like the following: + +``` ++-----+--------------+-------------+-------------------------------+-----+ +| /__ | | = | | __\ | +| \ | | = | | / | ++-----+--------------+-------------+-------------------------------+-----+ +^^^^^^^. .^^^^^^^^^^^^^^^. .^^^^^^^ +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 events and let them pass through to the box +directly below 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, 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 below it (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. Lastly, if the mouse is double clicked outside of any box +except the backdrop of the root element, then the window will be closed if it +is not uncloseable. + +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 it is not uncloseable, 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 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 combine. A style for pressed and hovered buttons will +override styles for only pressed or only hovered buttons. The highest state +will always win: a style for disabled buttons will override a style for buttons +with every other state combined. + +```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", +} +``` + +### 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 for the window's `style` property: + +```lua +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[,,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. + +`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()`. +* `style` (`ui.Style`): Specifies a style to apply across the entire element + tree. Defaults to an empty style. +* `uncloseable` (boolean): Indicates whether the user is able to close the + window via the escape key or similar. Defaults to false. 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 the `uncloseable` field set. 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 `
` 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. +* `style` (`ui.Style`): A local style that only applies to this element. If + this property is omitted, the properties contained in `StyleSpec` 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 everything. 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`. +* `@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 `@all` box selector can be used to select every +box in the element. For instance, `accordion@all` 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 `?<(sel)` 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. + +### Valid predicates + +The complete list of predicates aside from `!` is as follows: + +* `?empty` matches all elements with no children. +* `?first_child` matches all elements that are the first child of their parent + element. This also matches the root element, since it is considered the first + and only "child" of the window. +* `?last_child` matches all elements that are the last child of their parent + element. This also matches the root element. +* `?only_child` matches all elements that are the only child of their parent + element. This also matches the root element. +* `?nth_child(index)` matches all elements that are at the `index` position + from the start of their parent. So, `?nth_child(3)` matches the third child + of each element. +* `?nth_last_child(index)` matches all elements that are at the `index` + position from the end of their parent. +* `?<(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 any elements that have any family at all. + +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. + * 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 1, 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 + ), 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.