> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/ui/ui-reference.md).

# UI Reference

This page collects UI rules, patterns, and examples that are helpful once you know the basics.

Read [UI Fundamentals](/ui/fundamentals.md) first.

### Core principles

1. **Y grows upward.** Screen coordinates use (0, 0) at the bottom-left.
2. **Start from screen rects.** Use `UI.get_safe_screen_rect()` or `UI.get_screen_rect()`, then derive everything from those rects.
3. **Push/pop pattern.** Use `defer` for every push to avoid leaking UI state.
4. **Mobile-first.** Avoid hover-only interactions.
5. **ASCII only.** No emoji or Unicode in UI text.
6. **UI must render in `ao_late_update`.** This is required for correct interaction.

### Quick start: a simple HUD button

```csl
Player :: class : Player_Base {
    ao_late_update :: method(dt: float) {
        if this.is_local_or_server() {
            draw_my_hud(this);
        }
    }
}

draw_my_hud :: proc(player: Player) {
    rect := UI.get_safe_screen_rect()
        .bottom_right_rect()
        .grow(40, 150, 40, 150)
        .offset(-50, 50);

    bs := UI.default_button_settings();
    ts := UI.default_text_settings();

    if UI.button(rect, bs, ts, "Action").clicked {
        do_action(player);
    }
}
```

### Basic drawing

#### Quads and images

```csl
draw_ui :: proc() {
    rect := UI.get_screen_rect().center_rect().grow(100); // 200x200
    texture := get_asset(Texture_Asset, "my_icon.png");
    UI.quad(rect, texture);

    // With color tint (RGBA 0-1)
    UI.quad(rect, texture, {1, 0, 0, 0.5});

    // Solid color using the white sprite
    UI.quad(rect, core_globals.white_sprite, {0, 0, 0, 0.75});
}
```

#### Text

```csl
draw_text :: proc() {
    rect := UI.get_screen_rect().center_rect().grow(200, 300, 50, 300);

    ts := UI.default_text_settings();
    ts.size = 48;
    ts.color = {1, 1, 1, 1};
    ts.halign = .CENTER;
    ts.valign = .CENTER;

    UI.text(rect, ts, "Hello, World!");

    score := 1500;
    UI.text(rect, ts, "Score: %", {score});

    pct := 67;
    UI.text(rect, ts, "% %%", {pct}); // "67 %"

    // Returns the actual rendered rect
    actual_rect := UI.text_sync(rect, ts, "Dynamic text");
}
```

### Layouting: start from rects

```csl
layout_example :: proc() {
    screen := UI.get_screen_rect();
    safe := UI.get_safe_screen_rect();

    center := screen.center_rect();
    top_left := screen.top_left_rect();
    bottom_right := screen.bottom_right_rect();

    button_rect := center.grow(50, 150, 50, 150);
    icon_rect := center.grow(64);
}
```

### Use cut for layout

The cut functions **must** be used for layouting when placing multiple UI elements:

```csl
draw_panel :: proc() {
    rect := UI.get_safe_screen_rect().inset(20);

    header := rect.cut_top(80);
    footer := rect.cut_bottom(60);
    body := rect;

    UI.quad(header, core_globals.white_sprite, {0.1, 0.1, 0.1, 0.8});
    UI.quad(footer, core_globals.white_sprite, {0.1, 0.1, 0.1, 0.8});
    UI.quad(body, core_globals.white_sprite, {0.05, 0.05, 0.05, 0.8});
}
```

### Auto-scaling and unscaled rects

Rect functions are scaled by `UI.get_current_scale_factor()` (based on 1080p).

If you need exact pixel values, use the `_unscaled` variants:

```csl
layout_with_unscaled :: proc(window_rect: Rect) {
    list_item := window_rect.top_rect().grow_bottom(20);

    for item: items {
        draw_item(list_item, item);

        // Use unscaled offset with computed pixel values
        list_item = list_item.offset_unscaled(0, -list_item.height());
    }
}
```

### Buttons

```csl
draw_buttons :: proc() {
    rect := UI.get_safe_screen_rect()
        .bottom_center_rect()
        .grow(40, 150, 40, 150)
        .offset(0, 100);

    bs := UI.default_button_settings();
    ts := UI.default_text_settings();

    bs.sprite = get_asset(Texture_Asset, "$AO/new/modal/buttons_2/button_2.png");
    bs.press_scaling = 0.35;

    result := UI.button(rect, bs, ts, "Click Me!");
    if result.clicked {
        log_info("Button was clicked!");
    }
}
```

#### Button sprite rules

You must use one of these sprites when drawing buttons:

* `"$AO/new/modal/buttons_2/button_1.png"` (Orange)
* `"$AO/new/modal/buttons_2/button_2.png"` (Green)
* `"$AO/new/modal/buttons_2/button_3.png"` (Red)
* `"$AO/new/modal/buttons_2/button_5.png"` (Blue)
* `"$AO/new/modal/buttons_2/button_7.png"` (Pink)
* `"$AO/new/modal/buttons_2/button_8.png"` (Grey)
* `"$AO/new/modal/buttons_2/button_9.png"` (White)

### Modals

Use `UI.begin_modal` / `UI.end_modal` for a dimmed backdrop that closes when the player taps outside the modal or presses Escape.

```csl
Player :: class : Player_Base {
    settings_open: bool;
}

draw_settings_modal :: proc(player: Player) {
    if !player.settings_open return;

    UI.begin_modal(UI.get_screen_rect(), "settings_modal", ref player.settings_open);
    defer UI.end_modal();

    window := UI.get_safe_screen_rect().center_rect().grow(240, 360, 240, 360);
    UI.quad(window, core_globals.white_sprite, {0, 0, 0, 0.9});

    ts := UI.default_text_settings();
    ts.halign = .CENTER;
    ts.valign = .CENTER;
    UI.text(window, ts, "Settings");
}
```

### UI state management

Use `defer` for every push/pop pair:

```csl
draw_layered_ui :: proc() {
    UI.push_screen_draw_context();
    defer UI.pop_draw_context();

    UI.push_layer(100);
    defer UI.pop_layer();

    UI.push_color_multiplier({1, 1, 1, 0.5});
    defer UI.pop_color_multiplier();

    // Draw UI here
}
```

### IDs for repeated elements

When drawing lists or repeated items, push unique IDs:

```csl
draw_list :: proc(items: []Item) {
    rect := UI.get_safe_screen_rect().inset(20);
    bs := UI.default_button_settings();
    ts := UI.default_text_settings();

    for i: 0..<items.count {
        UI.push_id("item_%", {i});
        defer UI.pop_id();

        item_rect := rect.cut_top(60);
        if UI.button(item_rect, bs, ts, items[i].name).clicked {
            select_item(i);
        }
    }
}
```

### Common sizes

Some sizes that are known to look good across devices:

* Text: Title 52, Body 36, World space text 0.30
* Rects: Simple dialog 600x400, Standard button 210x74, Exit button 65x65

### Best practices

* Use `defer` for every push/pop pair.
* Push IDs for repeated elements.
* Use unscaled functions with computed dimensions.
* Fit icon aspect ratios with `rect.fit_aspect(texture.get_aspect())`.
* Always check `is_local_or_server()` before drawing interactive UI.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.allout.game/ui/ui-reference.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
