-
Notifications
You must be signed in to change notification settings - Fork 0
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.
The core container widget.
// 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);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);panel
.hoverBackground(0xFF2A3050)
.hoverBorder(0xFFE8A24A);// 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);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 assigningstyle.marginTopAuto = truedirectly when you need CSS cascade to correctly override it later — the setter marks the field as explicitly defined somerge()handles it properly.
### Layout
Call `layout()` after adding all children or after resizing:
```java
panel.layout();
// Measure height needed to fit all content
int height = panel.fitContentHeight();
panel.setSize(panel.getWidth(), height);
panel.layout();panel.onClick(() -> System.out.println("clicked"));
panel.onRightClick(() -> openContextMenu());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.
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 — 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());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 btn = new TesseraButton(0, 0, 80, 14);
btn
.label("Save")
.bgColor(0xFF5C3A1E)
.labelColor(TesseraPalette.COPPER_HI)
.fontSize(7f)
.onClick(this::onSave)
.hoverBgColor(0xFF7C5A2E);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());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");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 cb = new TesseraCheckbox(0, 0, 10, 10, /* checked= */ false);
cb.onToggle(checked -> this.enabled = checked);TesseraSlider slider = new TesseraSlider(0, 0, 100, 10, 0f, 100f, 50f);
slider.onInput(value -> this.volume = Float.parseFloat(value));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 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 badge = new TesseraBadge(0, 0, 12, "Epic", 0xFF8B5CF6);
badge.textColor(0xFFFFFFFF).paddingH(6);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);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);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.