
# Screens

## `screen`

| Function | Does |
|----------|------|
| `screen.current()` | Returns `{type, title, handled}`, or `nil` when no screen is open |
| `screen.close()` | Closes the open screen the way Escape does |
| `screen.open([title])` | Opens an empty screen that frees the cursor without pausing, for UIs drawn in `render_gui` |
| `screen.widgets()` | Every [widget](#widgets) of the open screen, in layout order |
| `screen.widget(label)` | First widget whose text contains `label`, or `nil` |
| `screen.click_text(text)` | Runs the click event of a [text component](text.md) on the open screen |
| `screen.dialog()` | The server [dialog](#dialogs) on screen, or `nil` |
| `screen.dialog_input(key, value)` | Sets a dialog input by its key |
| `screen.sign()` / `screen.set_sign(lines)` | Reads or replaces the four lines of a [sign editor](#signs) |
| `screen.book()` / `screen.set_book_page(page)` | Reads or turns an open [book](#books) |

`type` is a stable name for the screens the client recognizes (`"inventory"`,
`"generic_container"`, `"anvil"`, `"furnace"`, `"merchant"`, `"chat"`, `"game_menu"`,
`"death"`, `"sign"`, `"book"`, `"lectern"`, `"dialog"`, `"waiting_for_response"`, …), `"script"`
for `screen.open`, and `"other"` for the rest. `title` is a [text component](text.md). `handled`
marks container screens, whose slots [`interaction.click_slot`](interaction.md) can click and
`container` can read.

`screen.close()` does what Escape does on that screen: a container tells the server, a dialog
runs its cancel action, a sign editor sends its lines. With no screen shown but a server menu
still open (its [`screen_open`](events.md#screens) was cancelled) it closes that menu.

```lua
local s = screen.current()
if s and s.type == "generic_container" then
    print("looking at", s.title:string())
end
```

[`screen_open`](events.md#screens) fires before a screen appears and `screen_close` before it
goes. A screen builds its widgets as it opens, so act on them from the next `tick`.

### Widgets

`screen.widgets()` works on any screen: dialogs, confirmations, the anvil's name field, merchant
trade buttons, book page arrows. A handle reads the widget live and acts through the vanilla
widget code, so the server sees exactly what a player's click would send.

| Method | Returns |
|--------|---------|
| `w:kind()` | `"button"`, `"checkbox"`, `"cycle"`, `"slider"`, `"text_field"`, `"text_area"`, `"label"`, `"item"`, `"other"` |
| `w:text()` | The message as a [text component](text.md): a button's label, a field's label, a label's text |
| `w:value()` | Field text, checkbox state, cycle option (its id in a dialog), slider position `0..1`; `nil` otherwise |
| `w:set_value(v)` | Sets that value; `false` when the widget is inactive, stale, or a cycle has no such option |
| `w:press([button])` | Clicks the middle of the widget; `false` when inactive, hidden or stale |
| `w:active()` / `w:visible()` / `w:focused()` | Widget state |
| `w:valid()` | Whether the widget's screen is still the open one |
| `w:bounds()` | `x, y, width, height` in GUI-scaled pixels |

A handle belongs to the screen it came from. Once another screen opens, `press` and
`set_value` do nothing and return `false`.

```lua
-- answer an anvil prompt: the rename reaches the server, then take the result slot
for _, w in ipairs(screen.widgets()) do
    if w:kind() == "text_field" then w:set_value("my answer") end
end
interaction.click_slot(interaction.sync_id(), 2, 0, "pickup")
```

### Dialogs

Servers show dialogs: screens made of text, items, inputs and buttons.
`screen.dialog()` returns what the server sent:

| Field | Holds |
|-------|-------|
| `kind` | `"minecraft:notice"`, `"minecraft:confirmation"`, `"minecraft:multi_action"`, `"minecraft:server_links"`, `"minecraft:dialog_list"` |
| `title`, `external_title` | [Text](text.md) |
| `escape` | Whether Escape closes it |
| `pause` | Whether it pauses singleplayer |
| `after_action` | `"close"`, `"none"` or `"wait_for_response"` |
| `body` | List of `{kind, text?, item?}`: `"minecraft:plain_message"` with `text`, `"minecraft:item"` with an [item](item.md) and maybe `text` |
| `inputs` | List of `{key, kind, label, value, widget}` |

Each input also carries what its kind needs: `max_length` for `"minecraft:text"`, `options`
(`{id, text}` list) for `"minecraft:single_option"`, `min`, `max`, `step` for
`"minecraft:number_range"`. `value` is the value the server would get now: a string, a
boolean, an option id, or a number.

`screen.dialog_input(key, value)` sets an input the same way: a string, a boolean, an option
id, or a number within `min..max`. The dialog's buttons are plain widgets: press them by label.
Clickable body text runs through `screen.click_text`.

```lua
module:event("tick", function()
    local d = screen.dialog()
    if not d or d.title:string() ~= "Register" then return end
    screen.dialog_input("password", "hunter2")
    screen.dialog_input("remember", true)
    local submit = screen.widget("Submit")
    if submit then submit:press() end
end)
```

### Clicking text

`screen.click_text(text)` runs a component's click event, its own or the first one in its
siblings, the way clicking it would on the open screen. A dialog runs it as a dialog action,
a book turns pages or runs its command, anything else (chat included) takes the vanilla path.
Commands, page changes, dialogs, custom server actions, suggestions and clipboard copies go
through. Links and files are refused: read them from `text:style().click` instead.

```lua
local book = screen.book()
if book then
    for _, part in ipairs(book.pages[book.page]:siblings()) do
        if part:string():find("Accept") then screen.click_text(part) end
    end
end
```

### Signs

A server that asks for text with a sign editor gets its lines when the editor closes.
`screen.sign()` returns the four lines, `screen.set_sign(lines)` replaces them (missing ones
become empty).

```lua
if screen.sign() then
    screen.set_sign({ "1000", "", "", "" })
    screen.close()
end
```

### Books

`screen.book()` returns `{pages, page}` for a written book or a lectern: `pages` is a list of
[text components](text.md), `page` is 1-based. `screen.set_book_page(page)` turns to a page;
on a lectern the server turns it.

## `container`

The slots of the open screen handler: chest, shulker, villager, a server's custom menu. These
are **handler** indices, the ones `interaction.click_slot` takes, not the player-screen slots
[`inventory`](inventory.md) uses.

| Function | Returns |
|----------|---------|
| `container.size()` | Slot count of the handler, container and player rows together |
| `container.storage()` | How many leading slots belong to the container itself |
| `container.revision()` | Server revision of the handler, `0` until the first content sync |
| `container.kind()` | Menu type (`"minecraft:generic_9x6"`, `"minecraft:anvil"`, …), `nil` with only your inventory open |
| `container.carried()` | [Item handle](item.md) of the stack on the cursor |
| `container.item(slot)` | Live [item handle](item.md), or `nil` |
| `container.find(id[, from[, to]])` | First slot holding that item, or `nil` |
| `container.count(id[, from[, to]])` | Total count of that item, optionally within a range |
| `container.empty([from[, to]])` | First free slot, or `nil` when the range is full |

A container screen opens before the server has sent what's inside: the slots read as empty
for the first ticks. `revision()` flips from `0` once the full content sync arrives, so an
automation that opens chests itself should wait for it before reading slots (the
[`container_content` packet](packets.md) carries the same signal as an event).

### Merchants

`container.trades()` lists the offers of an open merchant, villager or server shop alike, and
returns `nil` for any other handler. Each trade is `{cost_a, cost_b?, result, uses, max_uses,
out_of_stock}`, the costs and result as [item handles](item.md) with discounts applied.
`container.select_trade(index)` selects one (1-based, as listed): it moves the price into the
payment slots and tells the server, like clicking it in the list. The result then waits in
slot `2`.

```lua
for i, trade in ipairs(container.trades() or {}) do
    if trade.result:id() == "minecraft:emerald" and not trade.out_of_stock then
        container.select_trade(i)
        interaction.click_slot(interaction.sync_id(), 2, 0, "quick_move")
        break
    end
end
```

### Slot layout

The container's own slots come first, your inventory after:

| Range | Holds |
|-------|-------|
| `0` … `storage() - 1` | The container, so `54` slots for a six-row chest |
| `storage()` … `storage() + 26` | Your main inventory rows |
| `storage() + 27` … `size() - 1` | Your hotbar |

With no container open this is your own inventory handler, where `storage()` covers the
crafting result and grid, and the armor slots count as inventory.

### Moving a whole stack

`click_slot` with `"quick_move"` is a shift-click: one packet moves the entire stack to the
other side of the handler.

```lua
module:event("tick", function()
    local open = screen.current()
    if not open or not open.handled or open.title:string() ~= "Sell" then return end

    local sync, storage = interaction.sync_id(), container.storage()
    for slot = storage, container.size() - 1 do
        local item = container.item(slot)
        if item and item:id() == "minecraft:cobblestone" then
            interaction.click_slot(sync, slot, 0, "quick_move")
        end
    end
end)
```
