Skip to content

Programmatic API

curveo edited this page May 19, 2026 · 8 revisions

Programmatic API

The programmatic API lets you build TesseraUI panels directly in Java — useful for highly dynamic screens, reusable widgets, or situations where an HTML template adds unnecessary indirection.


TesseraPanel

The core container widget.

Creating panels

// Column (vertical stack)
TesseraPanel panel = TesseraPanel.column(x, y, width, height);

// Row (horizontal stack)
TesseraPanel row = TesseraPanel.row(x, y, width, height);

// Grid (N columns)
TesseraPanel grid = TesseraPanel.grid(3, x, y, width, height);

Styling

panel
    .background(0xFF1A1208)          // ARGB colour
    .border(1, 0xFFB87333)           // thickness, ARGB colour
    .borderRadius(4)
    .padding(8)                      // all sides
    .padding(4, 8, 4, 8)             // top, right, bottom, left
    .gap(6)                          // space between children
    .opacity(0.9f);

Hover styles

panel
    .hoverBackground(0xFF2A3050)
    .hoverBorder(0xFFE8A24A);

Adding children

// Natural size, no flex participation
panel.add(child);

// With flex weight (grow and shrink equally)
panel.add(child, 1);

// Full CSS flex parameters: grow, shrink, basis, order, zIndex, alignSelf, marginTopAuto, margins
panel.add(child, 1f, 1f, 0, 0, 0, null, false, 0, 0, 0, 0);

margin-top: auto

To push a child to the end of a column (equivalent to CSS margin-top: auto), use TesseraStyle.setMarginTopAuto() when building the child's style, or pass true as the marginTopAuto argument in panel.add():

// Via panel.add — pushes the child to the bottom of the column
panel.add(footerWidget, 0f, 0f, TesseraStyle.UNSET, 0, 0, null, true, 0, 0, 0, 0);

// Via TesseraStyle — preferred when the style is built separately
TesseraStyle style = new TesseraStyle();
style.setMarginTopAuto(true);

Note: Use setMarginTopAuto(boolean) rather than assigning style.marginTopAuto = true directly when you need CSS cascade to correctly override it later — the setter marks the field as explicitly defined so merge() handles it properly.


### Layout

Call `layout()` after adding all children or after resizing:

```java
panel.layout();

Sizing utilities

// Measure height needed to fit all content
int height = panel.fitContentHeight();
panel.setSize(panel.getWidth(), height);
panel.layout();

Events

panel.onClick(() -> System.out.println("clicked"));
panel.onRightClick(() -> openContextMenu());

Animations — keyframes (no CSS file needed)

Build a keyframe animation entirely in Java with TesseraKeyframes.builder(), then attach it with animate():

TesseraKeyframes pulse = TesseraKeyframes.builder("pulse")
    .from(s -> { s.background = 0xFF1a2a1a; s.borderColor = 0xFF22c55e; })
    .at(50, s -> { s.background = 0xFF14532d; s.borderColor = 0xFF4ade80; })
    .to(s ->   { s.background = 0xFF1a2a1a; s.borderColor = 0xFF22c55e; })
    .build();

// Infinite loop
panel.animate(pulse, 1500, TesseraEasing.EASE_IN_OUT);

// N iterations
panel.animate(pulse, 1500, TesseraEasing.EASE_IN_OUT, 3);

// Infinite, ping-pong
panel.animate(pulse, 800, TesseraEasing.EASE_IN_OUT, -1, true);

Animatable properties inside a TesseraStyle stop: background, borderColor, color, opacity.

Animations — hover transitions (no CSS file needed)

panel.hoverTransition("background",  250, TesseraEasing.EASE_OUT, 0xFF1e2433, 0xFF2a3450)
     .hoverTransition("border-color", 250, TesseraEasing.EASE_OUT, 0xFFB87333, 0xFFE8A24A);

Supported properties: background, border-color, color.

v-for / v-if helpers

// v-for — add one widget per item
panel.addFor(playerList, player ->
    new TesseraLabel(0, 0, 120, 10, player.getName()).color(TesseraPalette.CREAM));

// v-if — add widget only when condition is true
panel.addIf(isAdmin, adminButton);

// v-if with lazy factory — widget is never created when condition is false
panel.addIf(isAdmin, () -> buildAdminPanel());

TesseraLabel

Single-line or wrapping text label.

TesseraLabel label = new TesseraLabel(0, 0, width, height, "Hello world");
label
    .color(TesseraPalette.CREAM)
    .fontSize(7f)
    .fontWeight(700)             // bold
    .textAlign("center")
    .wrap(true)                  // enable word-wrap
    .opacity(0.8f);

TesseraButton

TesseraButton btn = new TesseraButton(0, 0, 80, 14);
btn
    .label("Save")
    .bgColor(0xFF5C3A1E)
    .labelColor(TesseraPalette.COPPER_HI)
    .fontSize(7f)
    .onClick(this::onSave)
    .hoverBgColor(0xFF7C5A2E);

TesseraInput

Single-line text field.

TesseraInput input = new TesseraInput(0, 0, 120, 14);
input
    .placeholder("Enter name…")
    .text("default")
    .maxLength(32)
    .suggestions(List.of("minecraft:diamond", "minecraft:emerald"))
    .autocomplete(true)
    .bgColor(0xFF2A2010)
    .borderColor(TesseraPalette.COPPER_LO)
    .textColor(TesseraPalette.CREAM)
    .padding(5, 3)
    .onChange(value -> this.name = value)
    .onSubmit(value -> this.save());

Persistent state

Attach a TesseraInputState to preserve text across rebuilds:

private final TesseraInputState nameState = new TesseraInputState();

// On rebuild:
input.state(nameState);

For HTML templates, prefer TesseraRenderContext: give each <input> / <textarea> a stable id and pass the same context to TesseraTemplateRenderer.build(...) on every rebuild.

private final TesseraRenderContext renderContext = new TesseraRenderContext();

root = TesseraTemplateRenderer.build(
    TesseraTemplate.load("yourmod:ui/form"),
    model,
    clickHandlers,
    inputHandlers,
    renderContext,
    x, y, w, h
);

When rows are added, deleted, or reordered, clear stale input state before rebuilding:

renderContext.clearInput("name");
renderContext.clearInputsWithPrefix("phase");
renderContext.clearInputsMatching(id -> id.startsWith("reward") || id.startsWith("drop"));

When code changes an input-backed model value, update the persisted widget state before rebuilding:

renderContext.setInputText("name", "Alice");

TesseraTextArea

Multi-line text field.

TesseraTextArea ta = new TesseraTextArea(0, 0, 200, 60);
ta
    .placeholder("Write here…")
    .maxLength(2048)
    .bgColor(0xFF2A2010)
    .onChange(value -> this.notes = value);

TesseraCheckbox

TesseraCheckbox cb = new TesseraCheckbox(0, 0, 10, 10, /* checked= */ false);
cb.onToggle(checked -> this.enabled = checked);

TesseraSlider

TesseraSlider slider = new TesseraSlider(0, 0, 100, 10, 0f, 100f, 50f);
slider.onInput(value -> this.volume = Float.parseFloat(value));

TesseraDropdown

TesseraDropdown dd = new TesseraDropdown(0, 0, 100, 14);
dd.addOption("easy",   "Easy");
dd.addOption("normal", "Normal");
dd.addOption("hard",   "Hard");
dd.select("normal");
dd.onSelect(value -> this.difficulty = value);

TesseraIcon

TesseraIcon icon = new TesseraIcon(0, 0, 16, 16);
icon
    .texture(ResourceLocation.fromNamespaceAndPath("yourmod", "textures/gui/icons/settings.png"))
    .tint(TesseraPalette.COPPER_HI)
    .size(16, 16)
    .onClick(this::openSettings);

TesseraBadge

TesseraBadge badge = new TesseraBadge(0, 0, 12, "Epic", 0xFF8B5CF6);
badge.textColor(0xFFFFFFFF).paddingH(6);

TesseraTabPanel

TesseraTabPanel tabs = new TesseraTabPanel(0, 0, 200, 120);

TesseraPanel generalTab = TesseraPanel.column(0, 0, 200, 106).padding(6).gap(4);
generalTab.add(new TesseraLabel(0, 0, 188, 10, "General settings"));
generalTab.layout();

TesseraPanel advancedTab = TesseraPanel.column(0, 0, 200, 106).padding(6).gap(4);
advancedTab.layout();

tabs.addTab("General",  generalTab);
tabs.addTab("Advanced", advancedTab);

TesseraVirtualList

Efficient scrolling list that renders only visible rows:

List<TesseraModel> items = /* ... */;
int rowHeight = 18;

TesseraVirtualList list = TesseraVirtualList.of(items, rowHeight, model -> {
    TesseraPanel row = TesseraPanel.row(0, 0, listWidth, rowHeight).gap(4).padding(2, 4, 2, 4);
    row.add(new TesseraLabel(0, 0, 0, 10, model.resolve("name")).color(TesseraPalette.CREAM));
    row.layout();
    return row;
});
list.setSize(listWidth, 100);
list.background(TesseraPalette.BG1);
panel.add(list);

TesseraScreen

Base class for screens that use TesseraUI:

public class MyScreen extends TesseraScreen {

    private TesseraPanel root;

    public MyScreen() {
        super(Component.literal("My Screen"));
    }

    @Override
    protected void init() {
        rebuild();
    }

    private void rebuild() {
        int pw = Math.min(width, 400);
        int ph = Math.min(height, 260);
        root = TesseraPanel.column((width - pw) / 2, (height - ph) / 2, pw, ph)
                .background(TesseraPalette.BG0)
                .border(1, TesseraPalette.COPPER_LO)
                .padding(8).gap(6);

        root.add(new TesseraLabel(0, 0, pw - 16, 10, "My Screen")
                .color(TesseraPalette.COPPER_HI).fontSize(8f));

        root.layout();
    }

    @Override
    protected TesseraPanel tesseraRoot() { return root; }

    @Override
    public boolean isPauseScreen() { return false; }
}

TesseraScreen automatically wires:

  • render() — background + root panel + overlays
  • mouseClicked(), mouseDragged(), mouseReleased(), mouseScrolled()
  • keyPressed(), charTyped()
  • renderTesseraOverlays() — inventory picker, drag ghost, tooltips

The built-in debug overlay keyboard toggle is disabled by default so normal menu/game keys are not stolen. Enable it with -Dtesseraui.debug.hotkey=true, then use Ctrl+Alt+I.

Clone this wiki locally