DUXPLIMA Documentation

Items

Static, dynamic, stateful and animated icons, the render context they resolve against, and display modifiers.

A GuiItem is an icon to show plus an action to run on click. It is a sealed set of four kinds, all resolved through a RenderContext, which is why one menu can look different for every viewer.

KindIconShareable across viewers
StaticFixedYes
DynamicComputed per viewer at render timeNo
StatefulThe first matching state, per viewerNo
AnimatedCycles frames on a timerNo

Factories

GuiItem.display(stack);                          // no click
GuiItem.button(stack, event -> ...);             // fixed icon, handler

GuiItem.dynamic(ctx -> headOf(ctx.viewer()));    // per viewer, no click
GuiItem.dynamic(ctx -> icon(ctx), event -> ...); // per viewer, with a handler

GuiItem.stateful()
        .display(ctx -> ctx.viewer().hasPermission("vip"), vipIcon)
        .display(ctx -> true, normalIcon)
        .build();

GuiItem.animated(List.of(frame1, frame2), Duration.ofMillis(250));
GuiItem.animated(frames, interval, event -> ...);

stateful picks the first matching state, so order the predicates from most specific down to a true fallback. Without a fallback, a viewer matching nothing sees an empty slot.

state(...) on the stateful builder is the fuller form when a state also needs its own click behaviour; display(...) is the shorthand for a state that is display-only.

Ready-made navigation buttons

GuiItem.back(navigator, backArrow);
GuiItem.nextPage(paginatedGui, rightArrow);
GuiItem.previousPage(paginatedGui, leftArrow);
GuiItem.scrollNext(scrollingGui, downArrow);
GuiItem.scrollPrevious(scrollingGui, upArrow);

These exist so a back button does not need to know which menu it came from and a page arrow does not need a closure over the menu you are still building.

Declarative buttons

The handler receives an immutable snapshot and returns the effects to apply, instead of mutating the menu itself.

GuiItem.responding(icon, ctx -> List.of(
        GuiResponse.playSound(clickSound),
        GuiResponse.updateItem(ctx.slot(), newIcon),
        GuiResponse.close()));
GuiItem.respondingAsyncButton(icon, ctx ->
        loadBalance(ctx.viewer().getUniqueId())
                .thenApply(balance -> List.of(GuiResponse.open(balanceMenu(balance)))));

The full model, and why it exists, is in Clicks and safety.

RenderContext

Every non-static icon resolves against one:

public record RenderContext(Player viewer, Gui gui, int slot, Player effectivePlayer)
MemberMeaning
viewer()The player the menu is being rendered for
gui()The menu itself
slot()The slot being rendered
effectivePlayer()Whose data the icon describes
locale()The viewer's locale, for translated text
withEffectivePlayer(Player)A copy pointing at a different subject

effectivePlayer defaults to the viewer and matters when an admin opens somebody else's menu: the viewer is the admin, the effective player is the target, and an icon that reads a balance should read the target's.

GuiAction

The sealed set behind a click:

KindBehaviour
GuiAction.NoneNothing happens
GuiAction.RunRuns a Consumer<InventoryClickEvent>
GuiAction.RespondingReturns a future of GuiResponses the framework applies

You rarely name these; the factories above produce the right one.

Display modifiers

A DisplayModifier post-processes an icon after it is resolved and before it is shown, so a cross-cutting concern is written once rather than in every icon.

GuiItem head = DisplayModifiers.apply(
        GuiItem.display(ItemBuilder.of(Material.PLAYER_HEAD).build()),
        DisplayModifiers.of(
                DisplayModifiers.viewerSkull(),
                DisplayModifiers.placeholders((player, text) -> Placeholders.apply(player, text)),
                DisplayModifiers.loreSplit("|")));
ModifierEffect
viewerSkull()Points a player head at the viewer
placeholders(resolver)Runs the name and lore through a resolver, typically PlaceholderAPI
loreSplit(token)Splits a single lore line on a token into several lines
of(...)Composes several into one, applied in order

loreSplit exists for config-defined menus, where a lore line arrives as one string an operator wrote with a separator in it.