JelloUI Documentation

JelloUI public API documentation for Minecraft Fabric addon developers.

View the Project on GitHub yoimasama/jelloui-docs

HUD, rendering, keybinds, notifications and components

This chapter covers the service surfaces that are not modules: HUD widgets, render listeners, keybinds, notifications, custom screens and themes.

HUD widgets

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);

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.

Render listeners

A render listener is a per-frame callback at a named phase. JelloUI splits the frame into three phases (RenderPhase):

RenderSubscription 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 and text

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.

Keybinds

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:

Bindings 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.

Querying and changing bindings

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.

Notifications

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.

Custom screens (components)

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.

Themes

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.

Animation

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.