JelloUI public API documentation for Minecraft Fabric addon developers.
This chapter covers the service surfaces that are not modules: HUD widgets, render listeners, keybinds, notifications, custom screens and themes.
A HudWidget (io.github.sst.remake.api.hud.HudWidget) is a piece of
screen-space drawing dispatched during the JelloUI HUD pass. Supply the
preferred size, a visibility predicate and a renderer.
HudWidget widget = new HudWidget() {
@Override public Owner owner() { return owner; }
@Override public JelloId id() { return JelloId.of("example", "coords"); }
@Override public String displayName() { return "Coords"; }
@Override public HudAnchor defaultAnchor() { return HudAnchor.TOP_LEFT; }
@Override public Optional<float[]> preferredSize() { return Optional.of(new float[]{120f, 18f}); }
@Override public void render(RenderContext ctx, float width, float height) {
// The current dispatcher does not translate to an anchor position.
ctx.text().draw("x=0 y=0", 2f, 2f, 0xFFFFFFFF);
}
};
api.hud().register(widget);
visible() - a Predicate<RenderContext> evaluated every framepreferredSize() - optional; otherwise the dispatcher uses one quarter of
the screen width and a height of 16. A returned array must contain at least
the width; a second element supplies height. This callback is not isolated
from exceptions, so it must not throw.Current limitation: the runtime accepts defaultAnchor() but does not yet
apply anchor-based placement, dragging, edit mode or HUD-position persistence.
Every widget receives the same un-translated, screen-origin RenderContext;
the context contains screen dimensions but no widget position. Widget code
must therefore choose its own screen-space coordinates, and widgets can
overlap. Do not promise configurable placement to users yet.
A render listener is a per-frame callback at a named phase. JelloUI splits
the frame into three phases (RenderPhase):
WORLD_3D - the 3D world pass, before the hand renders; camera-relative
transforms are activeHUD_2D - the 2D HUD pass, before chat and the active screenSCREEN_OVERLAY - on top of whatever screen is openRenderSubscription sub = api.renderListeners().register(
owner, JelloId.of("example", "hud_line"), RenderPhase.HUD_2D,
0 /* priority, lower runs first */,
ctx -> { ctx.text().draw("hi", 10, 10, 0xFFFFFFFF); });
There is also a default-priority overload. Listeners run once per frame at their phase, inside the error isolation guard. A listener that throws is reported and the frame continues.
RenderContext is the immutable snapshot JelloUI hands to listeners, HUD
widgets and custom setting renderers. It exposes the PoseStack, the partial
tick, the logical screen size, the gui scale and a TextRenderer.
ctx.poseStack(); // keep it balanced: every pushPose gets a popPose
ctx.partialTick(); // [0,1)
ctx.screenWidth(); // logical pixels
ctx.screenHeight();
ctx.guiScale();
ctx.text().draw("Hello", 4f, 4f, 0xFFFFFFFF);
ctx.text().drawCentered("Hi", 0f, 0f, 100f, 0xFFFFFFFF);
float w = ctx.text().width("Hello");
Use ctx.text() instead of the vanilla font for user-facing text: it keeps
addon text on Jello’s filtered glyph atlas so it stays visually consistent.
Do not retain a RenderContext past the callback; the PoseStack it wraps
is owned by the renderer.
A keybind is an InputBinding mapped to a KeybindAction. To ship one, add
a KeybindDefinition to api.keybinds():
KeybindDefinition def = new KeybindDefinition(
owner, JelloId.of("example", "sprint_key"),
"Auto Sprint", "Movement",
InputBinding.key(org.lwjgl.glfw.GLFW.GLFW_KEY_R, 0),
new KeybindAction() {
@Override public Trigger trigger() { return Trigger.TOGGLE; }
@Override public void onPress() { /* ... */ }
});
api.keybinds().register(def);
Two trigger modes:
TOGGLE - onPress() fires once on key downHOLD - onPress() on key down, onRelease() on key upBindings are InputBinding.key(int, int modifiers) or
InputBinding.mouse(int, int modifiers), following Mojang’s
InputConstants/GLFW conventions. InputBinding.UNBOUND is the default for
a not-yet-assigned keybind.
The keybind picker lets the user rebind; JelloUI persists the user’s binding and restores it on startup. The action always stays the one you registered.
api.keybinds().binding(id); // Optional<InputBinding>, includes UNBOUND
api.keybinds().setBinding(id, binding); // updates the live binding
The host includes live bindings when its client configuration is saved and
restores them after addon registration. setBinding itself does not force an
immediate disk write.
Keybind actions run inside the error isolation guard, so a throwing action cannot stop input dispatch.
Push notifications through NotificationSink (from api.notifications()).
NotificationRequest req = NotificationRequest.builder(
NotificationType.INFO, "Title", "Description")
.duration(4000) // ms
.key("uploading")
.build();
api.notifications().show(req);
api.notifications().info("Ready");
api.notifications().success("Saved");
api.notifications().warning("Check flags");
api.notifications().error("Failed");
Types: INFO, SUCCESS, WARNING, ERROR, LOADING. The sink returns
immediately. Current limitation: the legacy bridge does not consume a
request’s key or custom icon; keyed requests still stack and notification
types use JelloUI’s built-in icons.
ComponentProvider describes a custom screen using opaque component handles:
api.components().register(new ComponentProvider(
owner, JelloId.of("example", "demo_screen"), "Demo",
(factory, sink) -> {
ComponentSpec where = new ComponentSpec(20, 20, 200, 20);
sink.add(factory.text(where, "Hello"));
sink.add(factory.button(where, "Go", () -> runFeature()));
sink.add(factory.checkbox(where, toggleHandle));
sink.add(factory.slider(where, speedHandle));
sink.add(factory.dropdown(where, modeHandle));
sink.add(factory.textField(where, textHandle));
}));
boolean opened = api.components().open(JelloId.of("example", "demo_screen"));
The factory also declares iconButton, image and scrollPanel. Registration
and lookup work, but the current core does not install the screen-opener
bridge. Consequently open(id) logs a diagnostic and returns false; the
provider is not built. Treat this surface as unavailable until core wiring is
shipped, and do not depend on internal screen classes as a workaround.
JelloUI themes surface an immutable ThemeTokens snapshot (colors, radii,
spacing, font sizes) that addons read instead of hardcoding values.
activeTokens() returns the cached snapshot for the active selection; obtain
it again when the active theme may have changed rather than retaining it
indefinitely.
ThemeTokens tokens = api.theme().activeTokens();
int accent = tokens.accent();
boolean dark = tokens.dark();
float radius = tokens.radiusMedium();
Addons can also register their own theme:
api.theme().register(new ThemeDefinition(
owner, JelloId.of("example", "brand"), "My Theme",
() -> tokensSupplier.get()));
setActive(JelloId) evaluates the selected theme’s token supplier once and
caches the result. An unknown id selects the built-in jello:dark theme. If a
token supplier throws, the runtime uses the built-in token snapshot; the
current implementation does not report that failure through
ErrorIsolators. There is currently no public theme-switcher UI wiring, and
the legacy ClickGUI does not automatically consume these tokens. Addons may
call setActive and use the resulting snapshot in their own drawing.
Theme suppliers must return a non-null snapshot; a null return is currently
cached as-is rather than replaced by the fallback.
AnimationSpec is an immutable description of animate from A to B over N ms
with this easing. The public API currently has no playback or registration
endpoint; addons can retain specs and evaluate their easing curves themselves.
AnimationSpec spec = AnimationSpec.builder()
.from(0f).to(1f)
.duration(300L).delay(0L)
.easing(Easings.OUT_CUBIC)
.respectReducedMotion(true)
.build();
The built-in curves are in Easings: LINEAR, IN_QUAD, OUT_QUAD,
IN_OUT_QUAD, OUT_CUBIC, IN_OUT_CUBIC, OUT_BACK and custom
outBack(overshoot). A duration of zero represents an immediate target. A
custom driver must read respectReducedMotion() and choose whether to replace
durationMs() with zero; AnimationSpec has no duration-resolution helper.
Keep animation purposeful. JelloUI’s open/close/hover transitions are interruptible and reversible; do not add motion purely as decoration.