crystal.ui
This module contains building blocks to create interactive menus and HUDs, with the UIElement class as its cornerstone.
Overview
The UI system implemented by this module is a retained mode UI toolkit, in which you maintain and update trees of UI elements. Different types of UI elements have different functionality, like drawing images, text, or positioning child elements relative to each other.
One unusual design choice in this system is that it does not have the concept of a single root UI element that all other elements are transitively parented to. Instead, any element without a parent is a root and works on its own as long as you call update_tree and draw_tree on it (usually every frame). For example, you can make a game scene that manages a HUD and a pause menu completely independently (example below). This also means UI elements can exist as drawable components and be drawn as part of a game world.
local HUD = Class("HUD", crystal.Widget);
local PauseMenu = Class("PauseMenu", crystal.Widget);
-- Implement HUD and PauseMenu widgets here
local MyScene = Class("MyScene", crystal.Scene);
MyScene.init = function(self)
self.hud = HUD:new();
self.pause_menu = PauseMenu:new();
end
MyScene.update = function(self, dt)
self.hud:update_tree(dt);
self.pause_menu:update_tree(dt);
end
MyScene.action_pressed = function(self, player_index, action)
self.pause_menu:action_pressed(player_index, action);
end
MyScene.action_released = function(self, player_index, action)
self.pause_menu:action_released(player_index, action);
end
MyScene.draw = function(self)
self.hud:draw_tree(dt);
self.pause_menu:draw_tree(dt);
end
Layout Concepts
The layout logic in this UI system works in two passes within update_tree:
- One bottom-up pass where elements report their desired size, which parent elements use to compute their own desired size.
- One top-down pass where elements are given their final size based on how much is available.
When an element is parented to another, their relationship is represented by a Joint. Joints are used to specify child-specific properties on how they should be laid out by their parent. Because different types of container elements (HorizontalList, VerticalList, Overlay, etc.) offer different options, they also work with different joint types. The documentation type for elements that can have child elements always mentions what type of joints they create for their children.
For convenience, methods on an element’s joint can be accessed transparently through the element itself. This is the same aliasing mechanism which allows Component methods to be called through entities.
local inventory_item = crystal.Overlay:new();
local icon = inventory_item:add_child(crystal.Image:new(crystal.assets.get("sword.png")));
local equipped_checkmark = inventory_item:add_child(crystal.Image:new(crystal.assets.get("checkmark.png")));
-- `set_alignment` is a method defined on OverlayJoint
-- We could also access it as icon:joint():set_alignment()
icon:set_alignment("center", "center");
equipped_checkmark:set_alignment("right", "bottom");
Player Interactions
UI elements can respond to the mouse pointer being on or away from them with a number of callbacks.
UI elements can also have action inputs bound to them, to be executed either any time the corresponding key is pressed or only while they are focused. These input bindings are processed whenever you ask a tree of UI element to handle an input via action_pressed or action_released.
Animation
This module does not provide any functionality specific to animation. However, Widget elements have a script associated with them, which you can use to drive change over time. The example below illustrates how to implement a flashing image:
local FlashingImage = Class("FlashingImage", crystal.Widget);
FlashingImage.init = function(self, texture)
FlashingImage.super.init(self);
local image = self:set_child(crystal.Image:new(texture));
self:script():run_thread(function(self)
while true do
image:set_opacity(math.cos(self:time()));
self:wait_frame();
end
end);
end
Adding Custom Elements
Most of the time, you will be building HUD widgets and menus by combining existing element types. However, it is possible your game needs to draw or layout content in a way that is not achievable with built-in element types. In this situation, you can implement your own element types by inheriting from UIElement, Wrapper, or Container.
If you do, make sure to consult the advanced UI Element methods you are likely to need in the process.
- To implement a new leaf element (like Border or Text), you should reference the implementation of Image.
- To implement a new single-child element (like Painter or Widget), you should reference the implementations of Widget and its Wrapper parent class.
- To implement a new multi-child element (like Overlay or VerticalList), you should reference the implementations of Overlay and its Container parent class.
Functions
| Name | Description |
|---|---|
| crystal.ui.font | Returns a previously registered font. |
| crystal.ui.register_font | Registers a font. |
Classes
Leaf UI elements
| Name | Description |
|---|---|
| crystal.Border | A UI element which draws a border around itself. |
| crystal.Image | A UI element which draws a texture or a solid color. |
| crystal.Text | A UI element which draws text. |
| crystal.UIElement | Base class for all UI building blocks. |
Containers & Wrappers
| Name | Description |
|---|---|
| crystal.Container | Base class for UI elements which can contain multiple child elements. |
| crystal.HorizontalList | A Container which positions its children in a row. |
| crystal.Overlay | A Container which aligns children relatively to itself. |
| crystal.Painter | A Wrapper which applies a shader when drawing its child. |
| crystal.RoundedCorners | A Painter which crops the corners of its child. |
| crystal.Switcher | A Container which draws only one of its children at a time. |
| crystal.VerticalList | A Container which positions its children in a column. |
| crystal.Widget | A Wrapper which manages a Script. |
| crystal.Wrapper | Base class for UI elements which can contain one child element. |
Joints
| Name | Description |
|---|---|
| crystal.BasicJoint | A Joint with common padding and alignment options. |
| crystal.HorizontalListJoint | A Joint specifying how elements are positioned in a HorizontalList. |
| crystal.Joint | Defines how a UI element should be laid out by its parent. |
| crystal.Padding | Utility class storing up/down/left/right padding amounts. |
| crystal.VerticalListJoint | A Joint specifying how elements are positioned in a VerticalList. |
Enums
| Name | Description |
|---|---|
| BindingRelevance | Describes in which context an input binding can be executed. |
| Direction | A cardinal direction. |
| HorizontalAlignment | Distinct ways to align content horizontally. |
| VerticalAlignment | Distinct ways to align content vertically. |