Skip to content
Gui Helper

Quick rating

Community listing page, reviews may not be monitored by the author.

Gui Helper

By Hdi

No reviews yet

Useful framework for mod developpers

Mod Loaders
Minecraft

About

Description

Gui Helper

A Fabric 1.20.6 framework for building Minecraft GUIs from a single Gui object that holds every object and its data.

You describe a screen once, register it under an Identifier, and the server opens it by that id. Values come back the same way.


Quick start

// client — build and register
Gui shop = GuiRegistry.register(GuiHelper.id("shop"), new Gui()
        .setTitle(Text.literal("Shop"))
        .setBackground(new Texture(GuiHelper.id("textures/gui/shop.png"), 0, 0, 176, 166, 256, 256))
        .setDarkBackground(true));

shop.addObject(new TextObject("price", new TextWithDetail(Text.literal("0"), ColorHelper.WHITE, 0.5f),
        8, 20, 60, 10, Align.CENTER));

// server — open it
GuiNetwork.open(player, GuiHelper.id("shop"), new GuiData().set("price", 2000));

Every object takes a String id first. That id is the key for lookup, for server-sent data, and for values sent back — so it must be unique within a Gui (addObject throws otherwise).


Objects

All extend GuiObject. Coordinates are relative to the GUI's top-left corner.

Object Purpose Value type
TextObject Text, with Align / VerticalAlign inside a box —
TextureObject A texture —
ItemRenderObject An ItemStack with count, optional vanilla item tooltip —
ModelItemRenderObject An item's 3D model, spinning, with a fixed tilt —
EntityGuiObject A live entity, scaled to fit, optionally cursor-tracking —
ButtonObject Clickable, Runnable onPress —
ToggleObject On/off, two textures boolean
RadioButtonObject One group, exclusive options String
StepperObject Arrows and ticks to pick a step int
DropdownObject Vector-drawn expanding list, wheel or draggable indicator, optional search String
TextFieldObject Text input String
IntegerFieldObject / DecimalFieldObject Validated numeric input, optional setRange(min, max) String (getIntValue() / getNumber())
PercentFieldObject Decimal 0–100, drawn as 65 % String (getNumber())
TextAreaObject Multi-line text input String
BoxObject Container, clips and scrolls its children —
ScrollBarObject Bar owned by a BoxObject —
SlotGridObject Client proxy placing a server slot grid inside a BoxObject —

Shared by every object

object.setLabel(new TextWithDetail(Text.literal("Name"), 0.5f));   // drawn above, left-aligned
object.setTooltip(Text.literal("Line one"), Text.literal("Line two"));
object.setShowWhen(toggle::getValue);                              // re-evaluated every frame
object.setDisabledWhen(() -> !toggle.getValue());                  // 50% opacity, input blocked

setShowWhen / setDisabledWhen return GuiObject, so call them last in a chain.

Dropdown search

dropdown.setSearchable(true).setSearchPlaceholder(Text.literal("Search..."));

A search row filters options by label (case-insensitive contains); Enter picks the first match. Cleared on close.

Reading values

gui.getObject("sound", ToggleObject.class).getValue();
gui.collectValues();   // GuiData of every stateful object, boxes included

getObject recurses into boxes.


State

Values live on the object, not the widget, which decides when they reset:

Event Result
Window resize Preserved — widgets rebuild and re-read the object
Reopening the GUI Reset to defaults

A Gui is a singleton once registered, so resetState() runs from the GuiRenderer constructor — one per screen instance, not per init(). Set your own baseline with setDefaultValue(...).


Data flow

GuiData is a String -> String map with typed accessors, used in both directions.

// server -> client, on open
GuiNetwork.open(player, id, new GuiData().set("price", 2000).set("sound", true));

// client -> server, on demand
ClientPlayNetworking.send(new GuiValuesPayload(id, gui.collectValues()));

An object picks up the entry matching its own id via applyData, and writes it back via collectData. ItemRenderObject and ModelItemRenderObject also read <id>.count.

Defaults are applied first, then server data — so an id absent from the payload leaves the default alone.

Receiving values

GuiValuesRegistry.register(GuiHelper.id("shop"), (player, values) -> {
    // runs on the server thread
});

The receiver rejects a payload unless the player has that GUI open (GuiSessions) and a listener is registered. The wire format is capped at 64 entries, 64-char keys, 256-char values.

GuiData is untrusted. The server cannot validate ids or types — Gui is client-only — so re-check everything against server state.


Handler GUIs (inventories)

Slots are the sync protocol: index, order and count must match on both sides. So the layout is common (src/main) and the look is client (src/client), keyed by the same Identifier.

// common — onInitialize, both sides
GuiHandlerRegistry.register(GuiHelper.id("forge"), new GuiHandler()
        .setTitle(Text.literal("Forge"))
        .addSlotGrid("input", 0, 8, 18, 3, 3)     // idPrefix, startIndex, x, y, columns, rows
        .addSlot(new SlotObject("output", 9, 120, 35))
        .setPlayerInventory(8, 84));

// client — a Gui under the same id
GuiRegistry.register(GuiHelper.id("forge"), buildForgeGui());

// server
GuiNetwork.openHandler(player, GuiHelper.id("forge"), data, inventory);

getInventorySize() is derived from the highest slot index, so size is never declared twice. To decorate slots, read their positions back rather than repeating them:

for (SlotObject slot : GuiHandlerRegistry.get(id).getSlots()) {
    gui.addObject(new TextureObject(slot.getId(), Textures.TEXTURE_SLOT,
            slot.getObjectStartX() - 1, slot.getObjectStartY() - 1));
}

Slots inside a box

// common: the grid's coordinates only define its shape
new GuiHandler().addSlotGrid("storage", 9, 0, 0, 3, 6);

// client: id = the grid prefix
box.addObject(new SlotGridObject("storage", GuiHelper.id("forge"), 0, 0));

The server keeps the slots and the sync; the client places them. Scrolling is smooth — partly visible slots are clipped and only their visible part takes hover and clicks (HandledScreenMixin + an access widener making Slot.x/y mutable). box.setScrollSnap(18) scrolls row by row instead.

One ExtendedScreenHandlerType<CustomScreenHandler, GuiOpenData> carries the id and data in the vanilla open packet, so handlers can be registered at any time.


Textures

new Texture(id, drawStartX, drawStartY, drawSizeX, drawSizeY, textureSizeX, textureSizeY)

drawStart + drawSize select a region of a sheet; textureSize is the file's dimensions. The region stretches to whatever size the object declares.

Nine-slice keeps corners crisp at any size — it works on sub-regions, unlike vanilla's GUI sprite atlas:

new Texture(...).setNineSlice(3);                 // all sides
new Texture(...).setNineSlice(6, 3, 6, 3);        // left, top, right, bottom

Scaling

gui.setSizeMultiplicator(2) renders everything twice as big. Mouse coordinates are divided to match, and slot hit-testing follows. Tooltips stay at normal size at the real cursor.


Overlays

A DropdownObject's open list floats above everything and takes input first — clicks, wheel and ESC — so a widget underneath can't steal a click or show its tooltip. Implement OverlayRenderer for anything else that needs to escape draw order and clipping; BoxWidget forwards all five members with its scroll offset, so overlays work inside boxes.


Debug

GuiHelper.DEBUG follows FabricLoader.isDevelopmentEnvironment(), so these exist under runClient and not in a built jar:

Command Opens
/testGui [id] a registered Gui as a plain screen (default debug)
/testHandler [id] a registered GuiHandler (default debug_handler, tab-completes)

Known limits

  • A registered Gui is a shared instance — one screen at a time on the client, which is fine, but it is not re-entrant.
  • SlotObject.backgroundSprite goes through GuiSlot, so the identifier must be a sprite stitched into the block atlas, not a loose textures/gui/*.png.
  • Shift-click can insert into slots scrolled out of a box; setDisabledWhen on a SlotGridObject only dims it — lock slots server-side.
  • SlotGridObject only works in a handler GUI, and only claims slots named <id>_<digits>.
  • The dropdown search text uses the first labelled option's style and needs rows at least 9px tall.
  • GuiObject still carries texture / text / onPress fields that most subclasses leave null.