DatumHueEnterprise v0.0.24datumhue.com
Contents

Fit any window size

Fit your UI to any window, zoom and screen: follow the size you are given, keep a shape, move parts between layouts, lay out for a design size, and go full screen.

Measure in UI px

Your UI is measured in UI px: the window's logical pixels divided by the zoom, a comfort setting the person using the workstation picks. At 100% a UI px is one CSS px in a browser and one logical pixel natively; at 125% a button 100 UI px wide spans 125 logical pixels. Sizes, offsets, screen.width and UiElement.bounds are all in UI px, so a layout looks the same at every zoom. Coordinates and units covers every frame a position can be in.

Your app's datumhue.screen is its own window, which its window manager sizes; for the root app it is the workstation window (The app window).

Follow the size you are given

fit.lualua
local ui = datumhue.ui
local screen = datumhue.screen
local token = datumhue.theme.token

local page = ui.root:panel({
    width = "100%",
    height = "100%",
    gap = 12,
})
local menu = page:panel({ gap = 8, color = token("surface_raised") })
menu:button({ text = "Home" })
menu:button({ text = "Search" })
if screen.fullscreen_available then
    menu:button({
        text = "Full screen",
        on_click = function()
            if screen.is_fullscreen then
                screen.fullscreen(false)
            else
                screen.fullscreen("borderless")
            end
        end,
    })
end
local main = page:panel({
    flex_grow = 1,
    color = token("surface_sunken"),
})
local info = main:label({ text = "" })

ui.root:on_resize(function(size)
    if size == nil then
        return
    end
    local wide = size.x >= 720
    page:update({ flex_direction = wide and "row" or "column" })
    menu:update({
        flex_direction = wide and "column" or "row",
        width = wide and 200 or "auto",
    })
    info.text = string.format(
        "%.0f x %.0f UI px at %.0f%% zoom",
        size.x,
        size.y,
        screen.zoom * 100
    )
end)

In this example: datumhue.ui datumhue.screen datumhue.theme.token datumhue.ui.root UiElement:panel UiElement:button datumhue.screen.fullscreen_available datumhue.screen.is_fullscreen datumhue.screen.fullscreen UiElement:label UiElement:on_resize UiElement:update UiElement.text datumhue.screen.zoom

  • ui.root:on_resize runs once after the first layout, then each time your app's size changes: the window, the zoom, or a window manager resizing your app.
  • From 720 UI px wide the menu sits beside the content; narrower, it runs across the top.
  • The Full screen button shows only where the workstation can go full screen. In the root app it turns full screen on and off; under a window manager, each press asks for your window to be maximized.

Keep a shape

aspect_ratio is an element's width over its height. A width or height you leave automatic follows the other through the ratio, and a minimum or maximum you set on only one of them carries over to the other. Two forms lose the shape:

  • Limits on both. With max_width and max_height both set, each holds only its own size, so once both apply, the element comes out wider or taller than its ratio.
  • Shrinking. An element that shrinks to make room for the others in its column gets shorter and keeps its width.

To fit the largest box of a shape in both the width and the height you have, such as a stage above a row of buttons, use two panels. The frame takes the width, up to the widest stage, and is as tall as the shape makes that width; it shrinks to the height the buttons leave and never grows. The stage inside it takes the frame's height, and the ratio gives its width.

shape.lualua
local ui = datumhue.ui
local token = datumhue.theme.token

local RATIO = 4 / 3
local WIDEST = 960
local NARROWEST = 160

local page = ui.root:panel({
    width = "100%",
    height = "100%",
    align_items = "center",
    gap = 12,
    overflow = "scroll_y",
})
-- The room for the stage: the page's width, as tall as the shape
-- makes it. It shrinks to the height the buttons leave.
local frame = page:panel({
    width = "100%",
    max_width = WIDEST,
    max_height = WIDEST / RATIO,
    min_height = NARROWEST / RATIO,
    aspect_ratio = RATIO,
    flex_shrink = 1,
    align_items = "center",
    justify_content = "center",
    padding = 0,
    border_radius = 0,
})
-- The largest box of the shape in the room: the room's height, and
-- the width that height gives.
local stage = frame:panel({
    height = "100%",
    min_width = NARROWEST,
    aspect_ratio = RATIO,
    color = token("surface_raised"),
})
local buttons = page:panel({
    flex_direction = "row",
    gap = 8,
    padding = 0,
})
buttons:button({ text = "Run" })
buttons:button({ text = "Reset" })

In this example: datumhue.ui datumhue.theme.token datumhue.ui.root UiElement:panel UiElement:button

  • The frame's max_height is its max_width over the ratio, so its two limits agree.
  • At every window size the stage keeps its shape in the middle of the frame, and the buttons sit right under it. No code runs on a resize.
  • The frame's min_height and the stage's min_width keep the stage at least 160 by 120 UI px. In a window too short for that, the page scrolls.

Put a part in another place

A layout for another size can need a part somewhere else:

  • UiElement:reorder puts a parent's children in the order a layout needs. The elements stay, with their focus, scroll and state, and the Tab order and the arrow keys follow the new order.
  • An element stays in the element it was built in. For a light part that sits in a different parent in each layout, such as a button or a line of text, build one in each place. Show the one the layout needs with display. When the hidden one held focus, focus the one you show, so a keyboard or gamepad user keeps their place.
  • For a part with more in it, keep what it shows as data. Remove it with UiElement:remove and build it again in its new place.
  • An app hosted in an element ends when the element is removed, so offer to run it again (Run a child inside your layout).
place.lualua
local ui = datumhue.ui
local token = datumhue.theme.token

local page = ui.root:panel({
    width = "100%",
    height = "100%",
    gap = 12,
})
local main = page:panel({
    flex_grow = 1,
    gap = 8,
    color = token("surface_sunken"),
})
local side = page:panel({ gap = 8, color = token("surface_raised") })

-- A Review button in each place; the layout shows one of them.
local function review(parent)
    return parent:button({
        text = "Review 3 cards",
        align_self = "flex_start",
    })
end
main:label({ text = "Today" })
local in_main = review(main)
local in_side = review(side)
side:label({ text = "Units" })

ui.root:on_resize(function(size)
    if size == nil then
        return
    end
    local wide = size.x >= 720
    local shown = wide and in_side or in_main
    local hidden = wide and in_main or in_side
    page:update({ flex_direction = wide and "row" or "column" })
    page:reorder(wide and { side, main } or { main, side })
    local had_focus = ui.focused == hidden
    hidden.display = "none"
    shown.display = "flex"
    if had_focus then
        shown:focus()
    end
end)

In this example: datumhue.ui datumhue.theme.token datumhue.ui.root UiElement:panel UiElement:button UiElement:label UiElement:on_resize UiElement:update UiElement:reorder datumhue.ui.focused UiElement:focus

  • From 720 UI px wide the side column sits on the left; narrower, it goes under the main panel. reorder swaps the two panels.
  • The Review button shows in the side column on a wide window and under Today on a narrow one.

Lay out for a design size

Four requests shape the space your UI is laid out in:

RequestMade byChanges
screen.request_resolutionyour app, at startthe window's size; a browser page sets its own
screen.request_design_resolutionyour app, at startthe size of the canvas your UI is laid out on
screen.request_stretch_modeyour app, at starthow that canvas fits the window
screen.request_zoomthe person using your appthe comfort zoom on top of all three

Each is a request, not a setting: your app's request goes to its window manager, which answers it. Read what is in effect from screen.width, screen.height, screen.design_resolution, screen.stretch_mode and screen.zoom. Under a design resolution, a UI px is a pixel of the design canvas as scaled to fit, times the zoom. The display's own scale, such as a high-density screen's, always applies on top.

Go full screen

screen.fullscreen takes a full-screen mode, or false to leave it. For the root app it changes the workstation window; any other app asks its window manager to maximize its window instead.

  • Read screen.fullscreen_available before you offer full screen. Where the host has none, as on iPhone Safari, the root app's call raises for a full-screen mode.
  • screen.is_fullscreen reports whether the workstation window is in full screen, including exits your app did not make; a maximized app window does not count. The fullscreen event reports each change.
  • In a Chromium-based browser in full screen, a press of Escape reaches your app and holding Escape leaves full screen. The browser can first ask the person using the workstation to allow this. Where they decline, and in other browsers, Escape leaves full screen.

In a browser

In a browser, a few things happen only right after a tap, a click or a key press: opening a link or a file dialog, copying with a copy button, and entering full screen. A gamepad press does not count, and neither does a press from too long ago.

When one of these has no such press behind it, the workstation shows a small Continue chip in the middle of the app that asked. The operation runs when the person taps, clicks or presses Enter on the chip. Natively every press counts and the chip never shows, so your app does nothing for it either way.

  • The chip takes keyboard and gamepad focus when it appears while its app is in front, whatever ui.focus_navigation says.
  • Escape, gamepad B or the browser's Back dismisses the chip, and the operation does not happen: on_copy or on_pick does not fire. Focus goes back where it was.
  • A newer such operation from the same app replaces the one waiting.

Opening the sign-in window needs such a press too. A sign-in whose chip is dismissed ends as cancelled. One with no window to show its chip in, as while the root app is a service app, ends as flow_unavailable, and one whose window the browser blocks ends as popup_blocked.

See also

Reference used on this page: datumhue.ui datumhue.screen datumhue.theme.token datumhue.ui.root UiElement:panel UiElement:button datumhue.screen.fullscreen_available datumhue.screen.is_fullscreen datumhue.screen.fullscreen UiElement:label UiElement:on_resize UiElement:update UiElement.text datumhue.screen.zoom UiElement:reorder datumhue.ui.focused UiElement:focus datumhue.screen.request_resolution datumhue.screen.request_design_resolution datumhue.screen.request_stretch_mode datumhue.screen.request_zoom datumhue.screen.width UiElement.bounds datumhue.input.mouse_position datumhue.input.touches datumhue.input.app_mouse_position UiElement:remove datumhue.screen.height datumhue.screen.design_resolution datumhue.screen.stretch_mode datumhue.ui.focus_navigation

datumhue.com

(c) 2026 Shuusei AB. Quote excerpts of this manual, with attribution to DatumHue, in your own documentation, tutorials, course materials, reviews, and other writing or video. Republishing it in whole or in substantial part needs written permission: support@datumhue.com.

Victor Mono is licensed under the SIL Open Font License 1.1
Copyright (c) 2024, Rune Bjørnerås (https://github.com/rubjo)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Try

    ↑↓ selectEnter openTab filter

    Settings

    Theme
    Text
    Print

    Keyboard shortcuts

    / or Ctrl KSearch
    ↑ ↓ EnterPick a result
    TabNext result filter
    j kNext or previous entry
    [ ]Previous or next page
    yCopy a link to the current entry
    ?These shortcuts
    EscClose

    On this page