> 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/fundamentals.md).

# UI Fundamentals

Adding UI to your game requires writing code, but with the help of AI it's substantially easier to make sophisticated interactive UIs

***

Adding UI to your game requires writing code, but once you learn a few patterns you can build clean, scalable interfaces quickly. This guide walks you through a step-by-step tutorial that ends with a complete dialog example.

{% hint style="info" %}
All UI for a player must be drawn inside that player's `ao_late_update` call stack and wrapped with `is_local_or_server()`. This prevents UI from appearing for the wrong client. Do not draw player UI from global or non-player component callbacks.
{% endhint %}

At any time, you can jump to [UI Reference](/ui/ui-reference.md) for more patterns and deeper details.

### Step 1: Create a UI Entry Point

All interactive UI should be drawn from the local player's `ao_late_update`:

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

### Step 2: Start From a Screen Rect

Screen UI should always be derived from `UI.get_safe_screen_rect()` or `UI.get_screen_rect()`. Remember: (0, 0) is the bottom-left and Y grows upward.

Screen-space sizes passed to the regular rect helpers are **points** on a 1080-point-tall reference canvas. The engine scales those points to the device's actual pixels, so the same layout keeps a consistent relative size across resolutions.

```go
draw_ui :: proc(player: Player) {
    screen := UI.get_safe_screen_rect();

    // A simple centered panel
    panel := screen.center_rect().grow(120, 200, 120, 200);
    UI.quad(panel, core_globals.white_sprite, {0, 0, 0, 0.7});
}
```

### Step 3: Use Cut For Layout

When laying out multiple elements, cut space out of a rect instead of positioning everything from the same origin.

```go
draw_panel_layout :: proc() {
    panel := UI.get_safe_screen_rect().center_rect().grow(140, 220, 140, 220);

    header := panel.cut_top(80);
    footer := panel.cut_bottom(70);
    body := panel;

    UI.quad(header, 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});
    UI.quad(footer, core_globals.white_sprite, {0.1, 0.1, 0.1, 0.8});
}
```

### Step 4: Add Text

Use text settings to control size, color, and alignment.

```go
draw_header_text :: proc(rect: Rect) {
    ts := UI.default_text_settings();
    ts.size = 52;
    ts.color = {1, 1, 1, 1};
    ts.halign = .CENTER;
    ts.valign = .CENTER;

    UI.text(rect, ts, "New Quest!");
}
```

### Step 5: Add Buttons

Buttons combine layout and interaction. Use a button sprite from the engine supplied assets (see [UI Reference](/ui/ui-reference.md) for more).

```go
draw_button :: proc(rect: Rect) {
    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;

    if UI.button(rect, bs, ts, "Accept").clicked {
        log_info("Accepted!");
    }
}
```

### Step 6: Build a Dialog

Now combine all of the steps into a reusable dialog layout.

```go
draw_dialog :: proc(title: string, body: string) {
    screen := UI.get_safe_screen_rect();
    dialog := screen.center_rect().grow(200, 300, 200, 300);

    UI.quad(dialog, core_globals.white_sprite, {0, 0, 0, 0.85});

    content := dialog.inset(30);
    header := content.cut_top(80);
    buttons := content.cut_bottom(90);
    body_rect := content;

    ts := UI.default_text_settings();
    ts.halign = .CENTER;
    ts.valign = .CENTER;

    ts.size = 52;
    UI.text(header, ts, title);

    ts.size = 36;
    UI.text(body_rect, ts, body);

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

    left := buttons.cut_left(210);
    buttons.cut_left(20);
    right := buttons.cut_left(210);

    if UI.button(left, bs, ts, "Cancel").clicked {
        log_info("Cancelled");
    }
    if UI.button(right, bs, ts, "Confirm").clicked {
        log_info("Confirmed");
    }
}
```
