# Server UI Source: https://tehoclient.dev/modules/ui SDK: 1.0.0 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 ```java 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](https://tehoclient.dev/developers/assets). | | `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 | ```java .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`. **Player input is data** Values from inputs reach your server as plain text, clipped to 256 characters. Treat them like chat: validate and sanitise them. Interactions for node ids that are not on the open screen are dropped by the client. ## Update a live screen ```java 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.