uxmlib-gui
An inventory-menu framework: five menu types, per-viewer items, a safe-by-default click policy, navigation, text input and menus from config.
An inventory-menu framework built on the public Paper API. No packets, no NMS. Depends on
uxmlib-item.
The module is deliberately larger than a menu builder. A menu that survives contact with real players has to answer questions a builder alone does not: what happens when two hundred people open the same shop, what happens when a player shift-spams a button, what happens when the icon changed between the render and the click, what happens when the click needs a database round trip. Each of those has a named answer here.
Installing
Guis.install(plugin, scheduler);
Once, in onEnable. This registers the single GuiListener that routes every click, drag, open,
close and quit to the right menu.
The Scheduler overload is what enables animated items and auto-refreshing menus, and it is what
lets an asynchronous click handler marshal its result back onto the viewer's region thread. The
overload without a scheduler works, with those three features inert.
Guis.uninstall(); // in onDisable
Guis.isInstalled(); // whether install() has run
Guis.clickLog(); // the rolling click log, or null when not installed
Guis.install is idempotent per plugin but the listener it registers is owned by the plugin you
pass. Calling it from a second plugin gives that plugin its own listener and its own click debounce
table, which is what you want. Calling it twice from the same plugin is not.
A first menu
SimpleGui menu = Guis.gui()
.title(Text.mini("<dark_aqua>Menu"))
.rows(3)
.build();
menu.filler().fillBorder(GuiItem.display(pane));
menu.set(2, 5, GuiItem.button(icon, event -> click()));
menu.onClose(event -> persist());
menu.open(player);
set(int row, int col, ...) is 1-indexed. set(int slot, ...) takes a raw slot when you want one.
An unconfigured menu can never leak items. Every interaction class starts denied and you opt in, per
class, with allow(...). A StorageGui opts into take and place for you. Nothing is left to
remembering to cancel an event.
Builder options
Every builder except typed() shares these:
| Method | Effect |
|---|---|
title(Component) | The window title, MiniMessage through Text.mini |
rows(int) | 1 to 6 rows of nine slots |
allow(InteractionModifier...) | Opt into interaction classes |
apply(Consumer<Gui>) | Run a block against the menu at build time |
autoRefresh(Duration) | Re-resolve every item on a timer while open |
clickSound(Sound) | Feedback sound on an accepted click |
openSound(Sound) | Feedback sound when the menu opens |
Guis.paginated() adds contentSlots(List<Integer>) to choose which slots hold page content.
Guis.typed(GuiType) takes only a title, because its shape fixes its size.
What the whole module holds
| Page | Covers |
|---|---|
| Menu types | Simple, paginated, scrolling, storage, typed; the shared Gui surface |
| Items | Static, dynamic, stateful and animated icons; render context; display modifiers |
| Clicks and safety | The cancel policy, interaction classes, debounce, the anti-desync re-check, declarative and async handlers |
| Layout and animation | Fillers, masks, adaptive slot layouts, slot patterns |
| Navigation | The back-stack, bound menus, confirmations |
| Text input | Anvil, chat and sign prompts behind one contract |
| Menus from config | HOCON layouts, named actions and conditions, the in-game config editor |
| Dialogs | Paper's native server-side dialogs |
Was this page helpful?