Theme and density
Color your widgets from the theme, pin a built-in theme or one of your own, and size your controls for a mouse or a finger.
Colors and sizes from the theme
A theme maps named tokens to colors and sizes. theme.token returns a
color that follows the theme: give it to a widget and the widget
repaints when the theme changes. theme.metric reads a size, such as a
font size or a gap, as your app's theme and density set it. The
Theme format lists every token.
Write a theme file
A theme file is KDL: a colors block of tokens and 0xRRGGBB colors,
and a metrics block of sizes. Set only the tokens you change; those
you leave out take the built-in dark theme's values.
colors {
panel_background 0x3b2a3a
surface_sunken 0x2a1e2a
text_primary 0xf6e3d2
text_secondary 0xd9b8a8
accent_primary 0xf08a4b
button_normal 0x553a52
button_text 0xf6e3d2
}
metrics {
border_radius 12
}
A theme can also define shader materials in a shaders block, and give
them to kinds of widget in a widget_shaders block. A widget takes its
surface from its own material, else from its theme's material for its
kind, else from its background color. The
Theme format describes both blocks.
Pin a theme
This program offers three themes, the built-in light and dark ones and the file above, and a switch for touch-sized controls.
local theme = datumhue.theme
local ui = datumhue.ui
local themes = {
theme.default_light(),
theme.default_dark(),
datumhue.args.dirs.themes:read("sunset.kdl"):theme(),
}
theme.pin(themes[3])
local panel = ui.root:panel({ width = 360, gap = 12 })
panel:label({ text = "Appearance", font_size = "1.5rem" })
panel:radio_group({
options = { "Light", "Dark", "Sunset" },
selected = 3,
on_change = function(i)
theme.pin(themes[i])
end,
})
panel:checkbox({
label = "Larger controls for touch",
on_change = function(on)
theme.density = on and "touch" or "standard"
end,
})
local done = panel:button({ text = "Done" })
done.color = theme.token("accent_primary")
done:set_text_color(theme.token("panel_background"))
In this example: datumhue.theme datumhue.ui datumhue.theme.default_light datumhue.theme.default_dark datumhue.args Dir:read Bytes:theme datumhue.theme.pin datumhue.ui.root UiElement:panel UiElement:label UiElement:radio_group UiElement:checkbox datumhue.theme.density UiElement:button UiElement.color datumhue.theme.token UiElement:set_text_color
Run it with the themes folder granted to it:
$ datumhue run theme.lua --dir themes:themes
Bytes:themeturns the file into aTheme, whichtheme.pintakes.theme.pinapplies a theme to your app until you pin another, or calltheme.unpinto follow the theme of the app above yours again.- Done takes its fill and its label color from tokens, so it follows
each theme you pick. Its border takes its fill's color. A button that
keeps the theme's fill draws its border in
button_borderinstead, so it stands out from the surface under it.
An app that pins no theme of its own follows the nearest app above it
that does, and changes when that app's theme changes. A window manager
can pin a theme on an app it manages, with theme.pin(dark, app).
theme.current() returns the theme in effect.
The theme event tells your app the theme in effect as you subscribe,
then each time it changes, by your own pin or by an app above yours.
Widgets colored from tokens repaint on their own; listen for the event
to redo what you colored from the theme yourself, such as a drawing. A
theme file you read again is the same theme, even when the file
changed: pinning it repaints your widgets but does not call the event's
callback, so redo your drawing after that pin.
local theme = datumhue.theme
datumhue.events.on("theme", function(e)
print(e.theme.name)
end)
datumhue.time.after(0.2, function()
theme.pin(theme.default_dark())
end)
In this example: datumhue.theme datumhue.events.on datumhue.time.after datumhue.theme.pin datumhue.theme.default_dark
- The callback runs once right away, with the light theme, then once more after the pin, with the dark one.
e.themeis the handletheme.current()returns, so==compares it with a theme you hold.- Several pins in one frame tell the callback once, with the last.
Size controls for a mouse or a finger
Density sizes your app's controls for its pointer: "standard" for a
mouse or a trackpad, and "touch" for fingers, with larger text,
spacing and controls, and buttons at least 48 UI px tall. Set
theme.density, and your widgets resize in place. Apps you start follow
your density unless they set their own, as with themes. A workstation
opened in a browser on a phone or a tablet starts at "touch".
- The
densityevent tells your app when its density changes, by its own write or by an app above it, so a layout that depends on it can rebuild. - Text sized in
remkeeps its size at every density. - A metric your theme sets wins over the density's.
- What you set on an element yourself stays yours through every theme
and density change: sizes, padding, borders, corner rounding, gaps,
colors and fonts, whether set when you make it or later with
UiElement:update. - A scroll bar lies over the edge of the element it scrolls, on top of
the element's padding and content.
theme.metric("scrollbar_width")is how much of that edge it takes: pad the edge at least that much to keep what the element holds clear of the bar, and update that padding on thethemeanddensityevents, since a theme can set the metric too.
See also
- Lay out a screen: build the widgets these tokens color.
- Shaders: the materials a theme can give its widgets.
datumhue.theme: every theme call.
Reference used on this page: datumhue.theme datumhue.ui datumhue.theme.default_light datumhue.theme.default_dark datumhue.args Dir:read Bytes:theme datumhue.theme.pin datumhue.ui.root UiElement:panel UiElement:label UiElement:radio_group UiElement:checkbox datumhue.theme.density UiElement:button UiElement.color datumhue.theme.token UiElement:set_text_color datumhue.events.on datumhue.time.after datumhue.theme.metric datumhue.theme.unpin datumhue.theme.current UiElement:update