> ## Documentation Index
> Fetch the complete documentation index at: https://wiki.refineryteam.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# GUI Presets & Animation

> Ready-made picker and text-input GUIs, live collection binding, and frame-based menu animation.

Beyond building GUIs from scratch, `xyz.refineryteam.refinerycore.api.gui` ships presets for the most common menu flows, a binder that keeps paginated menus in sync with live data, and an animator for animated decorations — so you often don't need to write a subclass at all.

## `ListPickerGUI<T>` — pick one item from a list

`xyz.refineryteam.refinerycore.api.gui.preset.ListPickerGUI` is a ready-made paginated picker covering the extremely common "pick a player / kit / warp / home from a menu" flow, with no GUI subclass required:

```java WarpCommand.java icon="java" theme={"system"}
ListPickerGUI.of(
        "<dark_gray>Select a warp",
        warps,
        warp -> ItemBuilder.of(Material.ENDER_PEARL)
            .name("<gold>" + warp.name())
            .build(),
        (player, warp) -> teleport(player, warp))
    .open(player);
```

| Factory                                              | Description                                                                |
| ---------------------------------------------------- | -------------------------------------------------------------------------- |
| `of(title, items, renderer, onSelect)`               | Picker with the default 3-row content area (21 items per page)             |
| `of(title, items, renderer, onSelect, contentSlots)` | Custom content slot layout; the page size derives from the number of slots |

The renderer turns each element into its clickable `ItemStack`; `onSelect` fires after the GUI closes automatically. Navigation buttons (`◀ Previous page` / `Next page ▶`) are provided and wired up for you.

## `TextInputGUI` — anvil text input as a preset

`xyz.refineryteam.refinerycore.api.gui.preset.TextInputGUI` is a thin composition over [`AnvilPrompt`](/refinerycore/interaction#anvil-prompts-anvilprompt) with a simpler signature for the common "ask for a string" case:

```java KitCommand.java icon="java" theme={"system"}
TextInputGUI.of(plugin, "<gray>Name your kit")
    .initialText("my-kit")
    .validate(text -> text.length() <= 16, "<red>Max 16 characters.")
    .onInput((player, text) -> createKit(player, text))
    .onCancel(player -> player.sendMessage("Cancelled."))
    .open(player);
```

| Builder method                                    | Description                                                                                          |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `initialText(String)`                             | Pre-fills the anvil's rename field                                                                   |
| `validate(Predicate<String>, String failMessage)` | If the predicate fails on submit, the anvil stays open and the message is shown instead of resolving |
| `onInput(BiConsumer<Player, String>)`             | Submit handler; receives the player and validated text                                               |
| `onCancel(Consumer<Player>)`                      | Invoked when the player closes the anvil without submitting                                          |
| `open(Player)`                                    | Opens the input UI for the given player                                                              |

## `CollectionBinder<T>` — live data without manual refreshes

Instead of copying a list into a `PaginatedGUI` once at construction, `xyz.refineryteam.refinerycore.api.gui.pagination.CollectionBinder` re-reads it from a supplier on every render — so GUIs showing "all online players" or "all warps" stay current:

```java OnlinePlayersGUI.java icon="java" theme={"system"}
public class OnlinePlayersGUI extends PaginatedGUI<Player> {

    public OnlinePlayersGUI() {
        super(45, "<dark_gray>Online players",
              List.of(), Bukkit.getMaxPlayers());
        CollectionBinder.bind(this, () -> new ArrayList<>(Bukkit.getOnlinePlayers()));
    }
}
```

Or standalone, with change notifications:

```java LiveSearchGUI.java icon="java" theme={"system"}
CollectionBinder<Player> binder = CollectionBinder.of(() -> fetchItems());
binder.onChange(gui::refreshFor); // fires whenever snapshot() sees a change
```

| Method               | Description                                                                                                                |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `bind(gui, source)`  | Creates a binder around a live supplier and immediately pushes its first snapshot into the GUI                             |
| `of(source)`         | Creates an unbound binder; call `snapshot()` yourself (e.g. at the top of `onInitialize`)                                  |
| `snapshot()`         | Re-reads the backing collection, notifies change listeners if it differs from the last read, and returns an immutable copy |
| `onChange(listener)` | Registers a listener fired by `snapshot()` when the data changed                                                           |
| `lastSnapshot()`     | The most recent snapshot without re-reading the source                                                                     |

<Note>
  The supplier should return a **fresh copy** of the items each time — never a live view being mutated elsewhere.
</Note>

## `GUIAnimator` — animated menus

`xyz.refineryteam.refinerycore.api.gui.animation.GUIAnimator` drives periodic item updates for any [`RefineryGUI`](/refinerycore/gui): animated borders, blinking buttons, countdown indicators, rotating decorations.

```java AnimatedMenu.java icon="java" theme={"system"}
GUIAnimator.forGui(plugin, gui)
    .every(5) // ticks between frames
    .updater((gui, frame) -> {
        int slot = BORDER_SLOTS[(int) (frame % BORDER_SLOTS.length)];
        gui.setItem(slot, glowPane);
    })
    .start();
```

| Method                          | Description                                                                                           |
| ------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `forGui(plugin, gui)`           | Creates an animator for the given GUI                                                                 |
| `every(long periodTicks)`       | Ticks between frames (default 1); must be ≥ 1                                                         |
| `updater(FrameUpdater)`         | Registers a per-frame updater receiving the frame number; multiple updaters run in registration order |
| `stopWhenIdleAfter(int frames)` | Consecutive zero-viewer frames tolerated before auto-stop (default 100, \~5s at 20 TPS)               |
| `start()`                       | Begins the animation; safe to call twice                                                              |

The animator runs a repeating sync task and **stops itself** when the GUI is destroyed or has had no viewers past the idle grace period — you never leak tasks.
