Lay out a screen
Build a screen from panels, labels and buttons, arrange them in flex rows and columns, and follow the space your app is given.
Build on a parent
Every widget is a method on the element it goes in. ui.root:panel
builds at the top of your app, UiElement:button builds inside another
element, and each returns the new UiElement. A panel stacks its
children in a column; give it flex_direction = "row" to put them side
by side.
The program below lays out a small library: a header, a list beside a shelf of books, and a status line.
local ui = datumhue.ui
local token = datumhue.theme.token
local page = ui.root:panel({
width = "100%",
height = "100%",
gap = 12,
})
local header = page:panel({
flex_direction = "row",
align_items = "center",
padding = 0,
})
header:label({ text = "Library", font_size = "1.5rem", flex_grow = 1 })
local add = header:button({ text = "Add book" })
add.color = token("accent_primary")
add:set_text_color(token("panel_background"))
local body = page:panel({
flex_direction = "row",
flex_grow = 1,
gap = 12,
padding = 0,
})
body:list({
items = { "Reading", "Finished", "Wishlist" },
width = 160,
})
local shelf = body:panel({
flex_direction = "row",
flex_wrap = "wrap",
align_content = "start",
flex_grow = 1,
gap = 12,
color = token("surface_sunken"),
})
local status = page:label({ color = token("text_secondary") })
local function add_book(title, cover)
local card = shelf:button({
accessible_label = title,
flex_direction = "column",
width = 140,
gap = 8,
on_click = function()
status.text = "Opened " .. title
end,
})
card:panel({ width = "100%", height = 80, color = token(cover) })
card:label({ text = title })
end
add_book("Dune", "accent_data")
add_book("Emma", "accent_secondary")
add_book("Ulysses", "info")
add:on_click(function()
add_book("New book", "accent_tertiary")
end)
ui.root:on_resize(function(size)
if size then
status.text = string.format("%.0f x %.0f", size.x, size.y)
end
end)
In this example: datumhue.ui datumhue.theme.token datumhue.ui.root UiElement:panel UiElement:label UiElement:button UiElement.color UiElement:set_text_color UiElement:list UiElement.text UiElement:on_click UiElement:on_resize
pagefills the window. The body'sflex_grow = 1gives it the height the header and the status line leave.- The shelf wraps its books onto a new row when it runs out of width;
align_content = "start"keeps the rows at the top. - Each book is a card (below): a button with no
text, holding a cover and a title. Add book builds another one. - Every color is a theme token, so the screen follows the theme.
ui.root:on_resizewrites your app's size into the status line each time it changes.
Rows, columns and space
| To | Set |
|---|---|
| put children side by side | flex_direction = "row" on the parent |
| give one child the space the others leave | flex_grow = 1 on it |
| space children apart | gap on the parent |
| keep content off the edges | padding |
| line children up across a row or column | align_items on the parent |
| spread children along it | justify_content on the parent |
| wrap children onto new rows | flex_wrap = "wrap" on the parent |
| place children in columns | display = "grid" and grid_template_columns on the parent |
Panels and buttons start with the theme's padding and rounded corners.
Pass padding = 0 or border_radius = 0 to drop them; the header and
the body above drop their padding.
A size is a number of UI px, or a string with a unit such as "50%" of
the parent; Coordinates and units lists the
units. padding, margin and border take any of these:
- one value, for all four sides;
- a table naming the sides you set, such as
{ left = 8, top = 4 }, the rest 0; - a CSS shorthand array, from
{ a }to{ a, b, c, d }.
To center an element with its margins, name both sides:
margin = { left = "auto", right = "auto" }.
Place children in columns
A panel with display = "grid" places its children in the columns
grid_template_columns gives, filling them a row at a time. A count
gives that many equal columns. A list gives each column its own width:
a size, "max_content" for a column as wide as its widest child, or
"1fr" for a share of the width the other columns leave.
local ui = datumhue.ui
local token = datumhue.theme.token
local page = ui.root:panel({
width = "100%",
height = "100%",
gap = 12,
})
local shelf = page:panel({
display = "grid",
grid_template_columns = 3,
gap = 8,
padding = 0,
})
for _, cover in ipairs({
"accent_data",
"accent_secondary",
"info",
"accent_primary",
}) do
shelf:panel({ height = 60, color = token(cover) })
end
local form = page:panel({
display = "grid",
grid_template_columns = { "max_content", "1fr" },
align_items = "center",
gap = 8,
padding = 0,
})
form:label({ text = "Title" })
form:text_input({ value = "Dune" })
form:label({ text = "First published" })
form:text_input({ value = "1965" })
In this example: datumhue.ui datumhue.theme.token datumhue.ui.root UiElement:panel UiElement:label UiElement:text_input
- The fourth cover starts a second row and keeps its column's width.
- The labels' column is as wide as the longer label, and the fields take the rest of the row.
- A shared column never narrows below 0 px, so content wider than its column runs past it.
- To change the count as the width changes, pass a new one to
UiElement:updatefromUiElement:on_resize.
Labels that wrap
A label wraps its text between words, and breaks a word too wide for its
box between two characters. In a row, a label is never narrower than its
widest word unless you let it be: give it min_width = 0,
max_width = "100%" or a width. Its min_width and min_height only
keep it from shrinking; it always grows to fit its text.
Cards
A button made without text is a card. The panels, labels, images
and code you build in it are its content, and the whole card is one
focus stop and one target, so a tap anywhere on it presses it. Give it
an accessible_label, since it has no text to be named by. Show code
there with UiElement:code, which draws it as a book page draws a
listing; a text field cannot go inside a card.
Nothing interactive goes inside a card: no buttons, links or tables, and no book page with links or widgets. Place a card's quick actions beside it. Its content takes no presses of its own, so set press, drag and context handlers on the card itself.
Show code as typed
The font DatumHue draws text in joins some signs into one shape: >=
draws as one sign, == as one long bar and != as a crossed equals
sign. Where someone reads code to type it, set ligatures = false on
the element that shows it, and each character draws as typed. Code
elements, text fields and labels take it, and Book:mount takes it for
a whole page.
Make a button stand out
Give it a fill and a label color from the theme, as Add book has, so
both follow the theme. Assign UiElement.color a token such as
theme.token("accent_primary"), and pass UiElement:set_text_color a
token that reads on that fill. The fill stays through hover and press,
which lay a faint wash of the label color over it, and a disabled
button still shows the theme's disabled colors.
Follow the space you are given
Layout runs after your code, once a frame. UiElement.bounds reads the
box an element was given, in UI px from the top-left of your app's
content area. A size you set reads back from the next frame, and bounds
is nil before the first layout and while the element or a parent has
display = "none".
React to changes instead of reading bounds every frame:
UiElement:on_resizeruns each time an element's size changes. Onui.rootit follows your whole app as the window, the zoom or a window manager resizes it.UiElement:on_visibilityruns when an element's ownvisiblechanges, whoever changed it: your code, or a popover closing on a press outside it or on Back.
See also
- Fit any window size: switch a layout between one and two columns as the window changes.
- Theme and density: the tokens these colors come from.
- Keyboard and gamepad: how focus moves between the widgets you lay out.
UiElement: every widget and its options.
Reference used on this page: datumhue.ui datumhue.theme.token datumhue.ui.root UiElement:panel UiElement:label UiElement:button UiElement.color UiElement:set_text_color UiElement:list UiElement.text UiElement:on_click UiElement:on_resize UiElement:text_input UiElement:update UiElement:code Book:mount UiElement.bounds UiElement:on_visibility