Game UI

Game UI in Turian is authored like the web or Unity's UI Toolkit: a .ui XML document describes the elements,
a .uss style sheet describes how they look, and a C# UiController or data bindings bring them to life.
It is drawn by Guinevere, the same toolkit that draws Turian Studio.
Putting a document on screen
- Create
Assets/UI/menu.uiandAssets/UI/theme.uss(below). - Add a node, then Add Component → UI → UI Document.
- Drop
menu.uion its Document field.
| Property | Meaning |
|---|---|
Document |
The .ui document to render. |
StyleSheets |
Extra .uss sheets applied on top of the document's own. |
Mode |
ScreenSpaceOverlay (drawn over the scene) or WorldSpace (on a quad in the scene). |
SortOrder |
Draw order among screen-space documents. |
ScaleMode, ReferenceResolution |
Constant pixel size, or scale with the screen from a design resolution. |
PanelSize |
Pixel size of a world-space panel. |
World-space documents render in Studio's views and CLI renders; exported games do not draw them yet. Add a UI Raycaster component to make one clickable.
A document
<?xml version="1.0" encoding="utf-8"?>
<UI xmlns="https://turian.dev/ui" controller="Usercode.MainMenu">
<Style src="Assets/UI/theme.uss" />
<VisualElement name="root" class="screen">
<Label class="title" text="{Title}" />
<Button name="play" text="Play" click="OnPlay" />
<Toggle name="music" label="Music" value="{Music, mode=TwoWay}" />
<Button name="quit" text="Quit" click="OnQuit" />
</VisualElement>
</UI>
- The root is always
<UI>; it holds<Style>sheets, optional<Template>s, and one visual element. - Common attributes:
name(unique id),class(style classes),style(inline declarations),tooltip,enabled. - A leaf element's inner text becomes its
text:<Label>Main Menu</Label>.
Elements
| Element | Notes |
|---|---|
VisualElement |
Generic container; lay it out with flex-direction, gap, sizes and alignment. |
Label |
Text. |
Button |
text; raises click. |
Image |
src, width, height. |
ImageButton |
image-normal, image-hover, image-pressed, nine-slice="l,t,r,b"; raises click. |
TextField |
value, placeholder; raises value-changed. |
Toggle |
value, label; raises value-changed. |
ScrollView |
Scrolls its children vertically. |
Tabs / Tab |
<Tab header="…"> per page; raises changed. |
Give stateful controls (TextField, Toggle, Tabs) a name so their state is kept between frames.
Templates and lists
<Template name="StatRow">
<VisualElement class="row">
<Label text="{label}" />
<Label binding-text="{path}" />
</VisualElement>
</Template>
<Instance template="StatRow" label="Gold" path="Player.Gold" />
<ScrollView>
<Repeat items="{Inventory}" as="item">
<Label binding-text="{item.Name}" />
</Repeat>
</ScrollView>
Styling with .uss
--accent: #4a90e2;
.screen { flex-direction: column; gap: 12; padding: 24; align-items: center; }
.title { font-size: 32; color: #ffffff; text-shadow: #000000 2 2; }
Button { width: 220; height: 44; border-radius: 8; background-color: #2a2f3a; }
Button:hover { background-color: var(--accent); }
#quit { background-color: #6b2b2b; }
- Selectors:
Type,.class,#name,*, compounds such asButton.primary, and groupsa, b. Descendant and child combinators are not supported. - States:
:hover,:active,:focus,:disabled. - Variables: declare
--name: value;at the top level and usevar(--name). - Layout:
flex-direction,flex-wrap,flex-grow,gap,width/height(pixels or%), min/max sizes,padding,margin,align-items,justify-content,align-self. - Box:
background-color,border-color,border-width,border-radius. - Text:
color,font-size,font-family(a project.ttf/.otfor a system font),text-outline,text-shadow,text-inner-shadow,text-gradient.
Code-behind and data binding
namespace Usercode;
public sealed class MainMenu : UiController
{
public string Title => "My Game";
public bool Music { get; set; } = true;
public void OnPlay() => Log.Logger.LogInformation("Play pressed");
public void OnQuit() => Log.Logger.LogInformation("Quit pressed");
}
- Every public, parameterless
voidmethod is an event handler, referenced by name fromclick,value-changed,changed,submit,activatedorselection-changed. {Path}in an attribute binds it to a property of the controller (or of a separate data context), re-read every frame — no change notifications needed.binding-text="{…}"is the explicit form, and a<Bindings>block can group them.- Modes:
OneWay(default),TwoWay(text fields and toggles write back),OneTime. - Converters:
{Health | percent}— built in arenot,percent,thousands,upperandlower; register your own withValueConverters.Register("name", converter). - Override
OnBind,OnUpdate(float deltaTime)andOnUnbindfor setup, per-frame logic and teardown.
Localized text
Label text is looked up in the project's string tables automatically; text-key names an explicit key. See
Localization.
Previewing without the game
The CLI renders a document to a PNG without a GPU, binding JSON as its data:
turian-cli ui --file Assets/UI/menu.ui --out menu.png --data '{"Title":"Preview","Music":true}'
Parse errors report the line and column of the offending XML.