Modules2 min read
Server UI
Declarative screens rendered in Teho's own design system.
ui:1Tier A · DecorateGood for
- Shops and kit pickers that work with a controller
- Settings menus that are not chest GUIs
- Live widgets on the HUD
Server UI lets you describe a screen as a tree of nodes, and Teho draws it in its own design system. Shops, kit pickers, party menus and settings look native, work with a controller automatically, and cannot be dressed up to look like Teho's own screens, because servers never send HTML, JavaScript or free-form drawing.
Open a screen
Screen shop = Screen.modal("shop", "Skyline Shop")
.width(560)
.add(
Node.tabs("tabs",
Node.tab("blocks", "Blocks",
Node.grid("blockGrid", 3,
Node.item("wool", "white_wool"),
Node.item("planks", "oak_planks"),
Node.item("glass", "glass")),
Node.button("buyBlocks", "Buy 16 for 4 iron").style(Node.Style.PRIMARY)),
Node.tab("tools", "Tools",
Node.row("pick",
Node.item("pickIcon", "iron_pickaxe"),
Node.text("pickText", "Iron pickaxe"),
Node.button("buyPick", "Buy").icon("iron_ingot")))),
Node.divider("rule"),
Node.progress("budget", "Iron", 0.4))
.onInteract((player, interaction) -> {
if (interaction.clicked("buyBlocks")) {
buyBlocks(player);
}
})
.build();
teho.ui().open(shop);Surfaces
| Factory | What it is |
|---|---|
Screen.modal(id, title) | A centred dialog that takes input focus. |
Screen.panel(id, title) | A side panel that leaves the game visible. |
Screen.hud(id) | A HUD widget with no input focus, such as a live scoreboard card. |
| Builder method | Notes |
|---|---|
add(Node…) | Top-level nodes. A screen holds at most 400 nodes. |
width(int) | Width in pixels, up to 1,200. |
closeable(boolean) | Whether the player can close it themselves. |
anchor(Anchor) | CENTER, LEFT, RIGHT, TOP or BOTTOM. |
onInteract(UiHandler) | Called for every interaction on this screen. |
Nodes
| Factory | What it draws |
|---|---|
stack(id, children…), row(id, children…), grid(id, columns, children…) | Vertical, horizontal and grid layout. |
list(id, children…) | A scrolling list. |
tabs(id, tab…), tab(id, label, children…) | Tabs. |
text(id, text) | Text. Use style(TITLE) or style(CAPTION) for hierarchy. |
image(id, Asset, width, height) | A PNG asset. |
item(id, itemId) | An item icon. |
head(id, playerUuid, size) | A player head. |
button(id, label) | A button. |
toggle(id, label, on) | A switch. |
slider(id, label, min, max, step, value) | A slider. |
input(id, label, placeholder, maxLength) | A text field. |
select(id, label, options, selected) | A dropdown. |
progress(id, label, fraction) | A progress bar. |
countdown(id, label, endsAtEpochMs) | A live countdown the client ticks. |
divider(id) | A rule. |
Every node takes style(Style) (DEFAULT, PRIMARY, DANGER, SUBTLE, TITLE, CAPTION), color(argb), disabled(boolean), tooltip(String), icon(itemId) and size(width, height).
Interactions
Each interaction has the screen id, node id, an action and a value.
| Action | Sent when | Value |
|---|---|---|
CLICK | A button is pressed. | Empty |
CHANGE | A toggle, slider, select or input changes. | The new value |
SUBMIT | An input is submitted. | The text |
CLOSED | The player closed the screen. | Empty |
.onInteract((player, interaction) -> {
switch (interaction.node()) {
case "music":
setMusic(player, interaction.booleanValue());
break;
case "volume":
setVolume(player, interaction.intValue());
break;
case "nickname":
if (interaction.action() == UiInteraction.Action.SUBMIT) {
rename(player, interaction.value());
}
break;
}
})You can also listen globally with TehoUiEvent on Bukkit or TehoListener.onUi.
Update a live screen
teho.ui().patch("shop", Node.progress("budget", "Iron", 0.7));
teho.ui().remove("shop", "rule");
teho.ui().retitle("shop", "Skyline Shop · Sale");
teho.ui().close("shop");patch replaces nodes with the same ids and leaves the rest alone, so live values such as balances update without redrawing the screen. openScreens() lists what this player has open.