Skip to content

UI theme + region + widgets

Three small classes that turn screen content from “UV math by hand” into “compose named building blocks”:

  • Region — an immutable rectangle in UV space [0, 1] × [0, 1] with fluent helpers (inset, top, bottom, left, right, belowOf).
  • Theme — a colour palette and scale set (bgColor, panelColor, titleScale, bodyScale, …) with presets (darkBlue(), amongUs()).
  • Widgets — high-level helpers that push raw Rect/Text/Button elements onto a ScreenContent.Builder using a Theme and a Region.
  • You’re building any screen and want one place to look up “what’s the title colour again”.
  • You want to compose a layout from regions like screen.inset(0.02f).top(0.10f) instead of magic UV numbers.
  • You want consistent typography across every panel in your mod.
package me.zlex.conduit.screen.ui;
public record Region(float x, float y, float w, float h) {
public static final Region FULL;
public Region inset(float pad);
public Region inset(float padX, float padY);
public Region top(float height);
public Region bottom(float height);
public Region left(float width);
public Region right(float width);
public Region belowOf(Region other, float gap);
public float right(); public float bottom();
public float centerX(); public float centerY();
public static final Codec<Region> CODEC;
}
public final class Theme {
public final int bgColor, panelColor, rowColor;
public final int titleColor, textColor, subtleColor, dividerColor;
public final int buttonBg, buttonText;
public final int primaryColor, dangerColor, accentColor;
public final float titleScale, headerScale, bodyScale, captionScale, padding;
public static Theme darkBlue();
public static Theme amongUs();
}
public final class Widgets {
public static void panel(ScreenContent.Builder b, Theme t, Region r);
public static void divider(ScreenContent.Builder b, Theme t, Region r);
public static void title(ScreenContent.Builder b, Theme t, float x, float y, String text);
public static void sectionHeader(ScreenContent.Builder b, Theme t, float x, float y, String text);
public static void label(ScreenContent.Builder b, Theme t, float x, float y, String text);
public static void caption(ScreenContent.Builder b, Theme t, float x, float y, String text);
public static void primaryAction(ScreenContent.Builder b, Theme t, Region r, String id, String label);
public static void secondaryAction(ScreenContent.Builder b, Theme t, Region r, String id, String label);
public static void dangerAction(ScreenContent.Builder b, Theme t, Region r, String id, String label);
public static void titleBar(ScreenContent.Builder b, Theme t, Region r, String title, String closeButtonId);
}
import me.zlex.conduit.screen.ScreenContent;
import me.zlex.conduit.screen.ui.Region;
import me.zlex.conduit.screen.ui.Theme;
import me.zlex.conduit.screen.ui.Widgets;
public final class MyConfigScreen {
public static ScreenContent build(int lockCount) {
Theme theme = Theme.darkBlue();
Region screen = Region.FULL.inset(0.02f);
Region header = screen.top(0.10f);
Region body = screen.belowOf(header, 0.01f);
ScreenContent.Builder b = ScreenContent.builder().background(0xD0000000);
Widgets.panel(b, theme, screen);
Widgets.titleBar(b, theme, header, "CONFIG", "close");
Widgets.sectionHeader(b, theme, body.x() + 0.02f, body.y() + 0.02f, "Locks per round");
Widgets.label(b, theme, body.x() + 0.02f, body.y() + 0.08f, Integer.toString(lockCount));
Widgets.primaryAction(b, theme, new Region(0.34f, 0.84f, 0.32f, 0.10f),
"lock-plus", "+1");
return b.build();
}
}
  • Region is a value type — every helper returns a new region. Chain them, don’t mutate.
  • Theme has its own scales for title / header / body / caption text. Using Widgets.title instead of raw TextElement keeps every panel typographically aligned.
  • The Widgets helpers all take a builder so you stay in your own builder chain instead of re-entering it for each call.