Config menus
VanillaConfigMenu turns a config screen into a few lines: declare a title + ordered list of ConfigEntry, pass an onChange callback, call open(). The framework compiles a vanilla-themed screen, routes every control click and text submit, applies the standard mutation, fires your callback with the updated entry, and re-issues the screen.
Built on the Vanilla GUI kit — same gray-bevel panel, white shadow text, black-outlined buttons.
When to use it
Section titled “When to use it”- You need a settings / config / options menu in-world.
- You want toggles, cycles, steppers, text fields, and “Apply” buttons without hand-wiring each click handler.
- You don’t need pixel-precise custom layout. (Use the kit’s widgets directly if you do.)
Quick start
Section titled “Quick start”import me.zlex.conduit.screen.ScreenId;import me.zlex.conduit.screen.placement.Placements;import me.zlex.conduit.screen.ui.config.ConfigEntry;import me.zlex.conduit.screen.ui.config.VanillaConfigMenu;
int screenId = ScreenId.of("mymod", "settings");var place = Placements.inFrontOf(player, 2.6f, 1.3f); // farther + raised
VanillaConfigMenu.open(player, screenId, place, 4.0f, 4.0f, "Settings", List.of( new ConfigEntry.Toggle ("pvp", "PvP", cfg.pvp), new ConfigEntry.Cycle ("difficulty", "Difficulty", List.of("Easy", "Normal", "Hard"), 1), new ConfigEntry.Stepper("max_players", "Max players", cfg.maxPlayers, 2, 16, 1), new ConfigEntry.Text ("name", "Server name", cfg.name, "Enter a name", 32), new ConfigEntry.Action ("reset", "Reset to defaults")), (p, changed) -> { switch (changed) { case ConfigEntry.Toggle t -> cfg.pvp = t.value(); case ConfigEntry.Cycle c -> cfg.difficultyIdx = c.selectedIndex(); case ConfigEntry.Stepper s -> cfg.maxPlayers = s.value(); case ConfigEntry.Text tx -> cfg.name = tx.value(); case ConfigEntry.Action a -> { /* "reset" — re-open with defaults */ } } });Done hides the screen and unregisters the menu. Persistence is the consumer’s job.
Entry types
Section titled “Entry types”| Variant | Fields | Click mutation |
|---|---|---|
Toggle | key, label, value | Flips value |
Cycle | key, label, options[], selectedIndex | Advances selectedIndex (wraps) |
Stepper | key, label, value, min, max, step | Adjusts value via [−]/[+], clamped |
Slider | key, label, value, min, max, step | Sets value from click position along the track, snapped to step |
Action | key, label | Fires onChange; no auto-reshow |
Text | key, label, value, placeholder, maxLength | Opens vanilla text-input popup |
Records are immutable. The framework calls flipped / advanced / decremented / incremented / withValue / withFraction for you; consumer code only sees the result in the onChange callback.
Slider and Stepper both edit a clamped, step-snapped int — pick by feel: Stepper for small discrete ranges where exact ±step nudges matter (2–16 players), Slider for wide ranges where you want to point at a target (0–100 volume). The slider is click-to-position, not drag: an in-world screen emits a click rather than mouse-move, so the handle jumps to wherever you click and snaps to the nearest step. Routing goes through ServerScreenManager.onButtonAt (the positional handler carrying the click’s screen-UV) — the framework wires this for you.
Pagination
Section titled “Pagination”PAGE_SIZE = 6. Menus with more entries paginate with [< Prev] Page x/y [Next >] above the Done button. Directional buttons hide when they’d be dead (first / last page). Up to 6 entries render as a single page with no nav, identical to the basic case.
For multi-category configs (the vanilla Options → Video / Audio / Controls pattern), use VanillaConfigMenu.openTabbed(...) with a List<ConfigTab> instead of a flat entry list:
VanillaConfigMenu.openTabbed(player, screenId, place, 4.0f, 4.0f, "Settings", List.of( new ConfigTab("Rules", List.of(/* entries */)), new ConfigTab("World", List.of(/* entries */))), (p, changed) -> { /* same callback */ });A tab strip renders across the top; the active tab shows pressed-in with a white underline, its entries below. Pagination still applies within each tab. The flat open(...) is just the single-tab case.
Action callbacks
Section titled “Action callbacks”Unlike stateful entries, Action doesn’t auto-reshow — it fires onChange and lets the handler decide what’s next (close the menu, re-open with a fresh entry list, navigate to another menu). The canonical use is a “Reset to defaults” button that re-opens with new entries.
Live demo
Section titled “Live demo”Run the pillars mod’s dev client and /pillars debugconfig — opens a tabbed demo (Rules / World / Server) exercising every entry type, including a Music slider on the World tab, plus pagination.
- Always uses
Theme.vanilla(). Wrap inside aThemedGroupWidgetif you need a vanilla config sub-panel inside a non-vanilla screen. - One open menu per
(player, screenId). Re-opening replaces the registry entry — safe to call from any handler. - Available since engine
0.10.0+mc26.1.2.