-
Notifications
You must be signed in to change notification settings - Fork 1
CatFrame UI Components
These are the UI building blocks provided by CatFrame — the library mod that CreateWorldUI (6.0.0+) depends on. If you're writing a custom tab or any screen that needs cycling buttons, panel rendering, or the tab system, this is your go-to reference.
Package: decok.dfcdvadstf.catframe.ui.GuiCyclableButton
A generic cyclable button — similar to modern Minecraft's CyclingButtonWidget<T>. Click it or scroll your mouse wheel to cycle through a typed list of values. Left-click or scroll down goes forward; scroll up goes backward.
You build one with a Builder — give it a value-to-text function, a list of values, and an update callback. The button handles all the cycling internally; you just react to value changes.
Key pieces:
-
ValueToText<T>— converts your value to the display string. Gets called on every text refresh. -
Values<T>— provides the list of values to cycle through (supports dynamic lists too). -
UpdateCallback<T>— fires when the value changes:(button, newValue) -> { ... } -
getValue()/setValue(T)— read or programmatically set the current value.
import decok.dfcdvadstf.catframe.ui.GuiCyclableButton;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.client.resources.I18n;
// A button that cycles through difficulties
GuiCyclableButton<EnumDifficulty> diffButton = GuiCyclableButton.<EnumDifficulty>builder(
d -> I18n.format("options.difficulty") + ": " + I18n.format(d.getDifficultyResourceKey()))
.values(EnumDifficulty.values())
.initially(EnumDifficulty.NORMAL)
.build(200, width / 2 - 104, height / 2, 208, 20, (button, difficulty) -> {
// React to the new value — no manual index juggling needed
yourDifficultySetter(difficulty);
});
addButton(diffButton);Need a label prefix? Pass it as the String label argument in build() — the display becomes "label: valueText" automatically:
// With a label — display shows "Difficulty: Easy" etc.
.build(200, x, y, w, h, I18n.format("options.difficulty"), callback);
// Without a label — your ValueToText controls the full string
.build(200, x, y, w, h, callback);Boolean on/off toggle? There's a shortcut:
GuiCyclableButton<Boolean> cheatsBtn = GuiCyclableButton.onOffBuilder()
.initially(false)
.build(201, x, y, 208, 20, (button, value) -> setAllowCheats(value));Tip: Call
button.updateText()in yourdrawScreen()if the display depends on external state. And usesetValue(T)to sync the button when something else changes the underlying value.
Package: decok.dfcdvadstf.catframe.ui.ContentPanelRenderer
A shared renderer for content panels — gives you the header separator, a tiled panel background, and the footer separator. Use the pieces you want individually, or let drawContentPanel() do the whole thing in one shot.
| Method | What it draws |
|---|---|
drawContentPanel(x, top, width, bottom) |
The whole thing — header line, tiled background, footer line. |
drawHeaderSeparator(x, y, width) |
Just the top separator line (2px tall, tiled). |
drawFooterSeparator(x, y, width) |
Just the bottom separator line (2px tall, tiled). |
drawSeparator(x, y, width, texture) |
A separator line with your own 32×2 texture. |
drawPanelBackground(x, y, width, height) |
Just the panel background (tiles the 16×16 texture). |
| Field | Path | Size |
|---|---|---|
HEADER_SEPARATOR |
createworldui:textures/gui/header_separator.png |
32×2 |
FOOTER_SEPARATOR |
createworldui:textures/gui/footer_separator.png |
32×2 |
PANEL_BACKGROUND |
createworldui:textures/gui/panel_background.png |
16×16 |
You can override these in your resource pack — or pass a custom ResourceLocation to drawSeparator() if you want a different style.
import decok.dfcdvadstf.catframe.ui.ContentPanelRenderer;
// Draw a full content panel in one call
ContentPanelRenderer.drawContentPanel(panelX, panelTop, panelWidth, panelBottom);
// Or draw pieces separately
ContentPanelRenderer.drawHeaderSeparator(panelX, panelTop, panelWidth);
ContentPanelRenderer.drawPanelBackground(panelX, panelTop + 2, panelWidth, panelHeight - 4);
ContentPanelRenderer.drawFooterSeparator(panelX, panelBottom - 2, panelWidth);Note: The separator height is always 2 GUI pixels (
SEPARATOR_HEIGHT = 2). When usingdrawContentPanel(), the background is automatically placed between the two separators.
The tab system from CatFrame provides a clean way to build multi-tab screens. Each tab implements the Tab interface, and you can extend AbstractScreenTab for convenience.
Package: decok.dfcdvadstf.catframe.ui.tab.Tab
The core contract for a tab:
public interface Tab {
void initGui(TabManager tabManager, int width, int height);
void drawScreen(int mouseX, int mouseY, float partialTicks);
void actionPerformed(GuiButton button);
void mouseClicked(int mouseX, int mouseY, int mouseButton);
void keyTyped(char typedChar, int keyCode);
int getTabId();
String getTabName();
void setVisible(boolean visible);
/** Default: {@code catframe:textures/gui/tabs.png} */
default ResourceLocation getTabTexture();
}Package: decok.dfcdvadstf.catframe.ui.tab.AbstractScreenTab
A base implementation that handles the boring stuff — button visibility, localization, button management, and tab texture. Extend this for your custom tabs.
Two constructors are available:
| Constructor | When to use |
|---|---|
AbstractScreenTab(int tabId, String tabNameKey) |
Traditional I18n key (no colon) — automatically falls back to I18n.format(tabNameKey). If the key contains a colon (:), it's treated as a CatFrame-style domain:key and auto-wrapped in Text.translatable(). |
AbstractScreenTab(int tabId, Text tabTitle) |
Explicit Text title — gives you lazy translation via CatFrame's LocalizationManager. Use when you need namespace-based i18n. |
import decok.dfcdvadstf.catframe.ui.Text;
import decok.dfcdvadstf.catframe.ui.tab.AbstractScreenTab;
import decok.dfcdvadstf.catframe.ui.tab.TabManager;
public class MyCustomTab extends AbstractScreenTab {
// Option A: String key — no colon → I18n.format("mymod.tab.custom")
public MyCustomTab() {
super(103, "mymod.tab.custom");
}
// Option B: String key with colon → auto Text.translatable("mymod:tab.custom")
// public MyCustomTab() {
// super(104, "mymod:tab.custom");
// }
// Option C: Explicit Text title
// public MyCustomTab() {
// super(105, Text.translatable("mymod", "tab.custom"));
// }
@Override
public void initGui(TabManager tabManager, int width, int height) {
super.initGui(tabManager, width, height);
// Add your buttons and fields here
}
// ... rest of the tab methods
}Whichever way you go, the tab title shows up correctly in TabButton. The framework handles the translation routing for you.
Tip: Call
setTabTexture(ResourceLocation)from the constructor orinitGui()if you prefer a setter over overridinggetTabTexture(). The default texture iscatframe:textures/gui/tabs.png.
Package: decok.dfcdvadstf.catframe.ui.tab.TabManager
TabManager handles tab switching, state persistence (survives window resizes), and gives you access to the parent screen.
| Method | What it does |
|---|---|
addButton(GuiButton) |
Add a button to the screen's button list. |
registerTab(Tab) |
Register a tab instance (internal — use TabRegistry instead). |
switchToTab(int tabId) |
Switch to a tab by ID. |
drawScreen(...) |
Delegate drawing to the current tab. |
actionPerformed(GuiButton) |
Delegate button clicks to the current tab. |
mouseClicked(...) |
Delegate mouse clicks to the current tab. |
keyTyped(...) |
Delegate keyboard input to the current tab. |
reinitializeTabs(width, height) |
Re-init all tabs on window resize (preserves current tab). |
getScreen() |
Get the parent GuiScreen instance. |
getCurrentTabId() |
Get the currently active tab ID. |
getTabCount() |
Get the total number of registered tabs. |
getTabBar() |
Get the optional TabBar associated with this manager, or null. |
Two ways to construct a TabManager — both require a barId so it knows which bucket of tabs to load:
// Option 1: With a TabBar — barId is taken from bar.getBarId() automatically
TabBar bar = new MyModTabBar();
TabManager manager = new TabManager(screen, buttonList, width, height, bar);
// Option 2: With a bare barId string — no TabBar, no custom background
TabManager manager = new TabManager(screen, buttonList, width, height, "my_mod_bar");Either way, TabManager only loads entries registered under that specific barId — tabs from other bars won't leak in.
Package: decok.dfcdvadstf.catframe.ui.tab.TabState
An enum that defines texture coordinates and text colors for the four tab interaction states — NORMAL, HOVER, SELECTED, SELECTED_HOVER. Used by the tab button renderer to pick the right texture slice and color.
| State | Texture (u, v) | Text Color |
|---|---|---|
NORMAL |
(0, 0) | White (0xFFFFFF) |
HOVER |
(0, 24) | Yellow (0xFFFF55) |
SELECTED |
(0, 48) | White (0xFFFFFF) |
SELECTED_HOVER |
(0, 72) | Yellow (0xFFFF55) |
Package: decok.dfcdvadstf.catframe.ui.tab.TabButton
A tab button component managed by TabBar, rendered with nine-patch stretching across four state textures (normal / hovered / selected / selected+highlighted).
Each TabButton has three instance-level colour fields — no more hardcoded static final constants. External mods can set them individually on any button:
TabButton btn = new TabButton(myTab);
btn.setColorSelected(0xFFFFFF); // White — selected tab
btn.setColorHovered(0xFFFF55); // Yellow — mouse hover
btn.setColorNormal(0xA0A0A0); // Grey — defaultDefaults (same as before — purely informational, no dependency needed):
| State | Default Colour | Customisation Method |
|---|---|---|
| Selected |
0xFFFFFF (white) |
setColorSelected(int) |
| Hovered |
0xFFFF55 (yellow) |
setColorHovered(int) |
| Normal |
0xA0A0A0 (grey) |
setColorNormal(int) |
If you have a custom TabBar, you can apply a uniform colour scheme to all its tab buttons after TabManager initialises them:
// Inside your screen's initGui()
TabBar bar = new MyModTabBar();
TabManager manager = new TabManager(this, buttonList, width, height, bar);
// Customise every tab button in this bar
for (TabRegistry.TabEntry entry : TabRegistry.getEntries(bar.getBarId())) {
TabButton btn = bar.getTabButton(entry.tabId); // retrieve the button
if (btn != null) {
btn.setColorSelected(0x55FF55); // green for selected
btn.setColorHovered(0xFFFF55); // keep yellow for hover
btn.setColorNormal(0xAAAAAA); // lighter grey for normal
}
}Note:
TabButtontextures (the four state .png files) are still controlled viaTabBar.setTabTexture()and individualTab.getTabTexture(). The colour customisation above only affects the text colour drawn on top of those textures.
TabBar exposes tab buttons via:
// After arrangeNavElements() has been called
TabButton btn = tabBar.getTabButton(int tabId);Returns null if no button exists for that ID.
Package: decok.dfcdvadstf.catframe.ui.tab.TabBar
TabBar is an abstract container bar for tabs. It provides a common background for the tab area and holds the tabs that belong to it. Each TabBar has a unique ID, and subclasses customise the look (solid colour fill + optional tiled texture, defaults to solid black).
This is the piece you extend if you want a distinct visual style for your screen's tab area.
| Property | Type | Default | Description |
|---|---|---|---|
barId |
String |
(required) | Unique identifier for this bar, set via constructor. |
backgroundColor |
int |
(optional) default: 0xFF000000
|
Solid background colour in ARGB format. |
backgroundTexture |
ResourceLocation |
(optional) default: null
|
Optional tiled background texture (16×16 tile). |
tabTexture |
ResourceLocation |
(optional) default: catframe:textures/gui/tabs.png
|
Default tab button texture for all tabs in this bar. |
| Method | What it does |
|---|---|
drawBackground(x, y, width, height) |
Draws the bar background — solid colour fill first, then optional tiled texture on top. |
registerEntry(TabEntry) |
Register a TabRegistry.TabEntry into this bar. Called automatically by TabManager during construction. |
getAllEntries() |
Get all registered entries (ordered by insertion). |
getEntry(int tabId) |
Get an entry by its tab ID. |
registerTab(Tab) |
Register a tab instance directly into this bar. |
getAllTabs() |
Get all tab instances (ordered by creation). |
getTab(int tabId) |
Get a tab by its ID. |
containsTab(int tabId) |
Check if this bar contains a tab with the given ID. |
getTabCount() |
Get the number of tab instances. |
getBarId() |
Get the unique bar identifier. |
getTabButton(int tabId) |
Get the TabButton for the given tab ID, or null if not found. After arrangeNavElements() has been called. |
Subclasses may override drawTiledBackground(x, y, width, height) to customise how the tiled texture is rendered.
import decok.dfcdvadstf.catframe.ui.tab.TabBar;
import net.minecraft.util.ResourceLocation;
public class MyModTabBar extends TabBar {
public MyModTabBar() {
super("my_mod_bar"); // Unique bar ID
setBackgroundColor(0xFF2D2D2D); // Optional dark grey solid fill
setBackgroundTexture( // Optional tiled texture
new ResourceLocation("mymod", "textures/gui/bar_bg.png"));
setTabTexture( // Optional: override default tab texture
new ResourceLocation("mymod", "textures/gui/tab_buttons.png"));
}
}Notes: Although except bar ID, rest of these are optional and can leave it unimplemented, this will end up with a soild black fill. Which means:
public MyModTabBar() {super("my_mod_bar");}equalspublic MyModTabBar() { super("my_mod_bar"); setBackgroundColor("0XFF000000"); setTabTexture( new ResourceLocation("catframe", "textures/gui/tabs/buttons.png")); }
// 1. Register tabs during preInit — specify which bar they belong to
TabRegistry.registerTab("my_mod_bar", MyCustomTab::new, 103, "mymod.tab.custom", 5);
// 2. Create your TabBar (its barId must match what you registered under)
TabBar bar = new MyModTabBar(); // super("my_mod_bar") inside
// 3. Pass it to TabManager — only "my_mod_bar" entries get loaded
TabManager manager = new TabManager(screen, buttonList, width, height, bar);Package: decok.dfcdvadstf.catframe.ui.tab.TabRegistry
The static registry for external mods to register custom tabs. Entries are bucketed by barId — so a statistics screen's tabs won't pollute the create-world screen, and vice versa. Register your tabs during mod init with the target barId, and when the corresponding screen opens, TabManager freezes that bucket and creates only its own tabs.
| Method | What it does |
|---|---|
registerTab(String, Supplier<Tab>, int, String, int) |
Register with a String name key + priority. |
registerTab(String, Supplier<Tab>, int, String) |
Register with a String name key (priority defaults to tabId). |
registerTab(String, Supplier<Tab>, int, Text, int) |
Register with a Text title + priority. |
registerTab(String, Supplier<Tab>, int, Text) |
Register with a Text title (priority defaults to tabId). |
getEntries(String barId) |
Get all entries for that bar, sorted by priority (unmodifiable). |
isFrozen(String barId) |
Check if a specific bar's bucket is frozen. |
freeze(String barId) |
Freeze a bar's bucket. Called internally by TabManager. |
clear(String barId) |
Clear a specific bar's bucket (testing/reset). |
clearAll() |
Clear everything (testing/reset). |
| Parameter | Type | What it means |
|---|---|---|
barId |
String |
Which bar this tab belongs to. Must match the TabBar's barId (or the string you pass to TabManager). |
factory |
Supplier<Tab> |
A factory that creates your Tab instance. Called once when TabManager is constructed. |
tabId |
int |
Unique tab ID within a bar. Built-in tabs use 100–102, so start from 103. |
nameKey / nameText
|
String or Text
|
Tab title. Use String for simple I18n keys (no colon → I18n.format), or Text for namespace-based lazy translation via CatFrame's LocalizationManager. Internally stored as both nameKey (raw string, always present) and nameText (Text, may be null if registered with String). |
priority |
int |
Sort order — lower comes first. Defaults to tabId if not specified. Built-in tabs use 0, 1, 2. |
Entries are sorted by priority ascending, then tabId ascending as a tiebreaker. The uniqueness check for tabId only applies within the same barId bucket — two different bars can safely share the same numeric ID.
String key (classic I18n):
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import decok.dfcdvadstf.catframe.ui.tab.AbstractScreenTab;
import decok.dfcdvadstf.catframe.ui.tab.Tab;
import decok.dfcdvadstf.catframe.ui.tab.TabManager;
import decok.dfcdvadstf.catframe.ui.tab.TabRegistry;
import net.minecraft.client.gui.GuiButton;
@Mod(modid = "mymod", name = "My Mod", version = "1.0")
public class MyMod {
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
TabRegistry.registerTab(
"create_world", // Target bar ID
MyCustomTab::new, // Supplier<Tab> factory
103, // Unique tab ID (start from 103)
"mymod.tab.custom", // Localization key (no colon → I18n.format)
5 // Sort priority (lower = earlier)
);
}
}// Tab constructor — String key, no colon, auto-fallback to I18n.format
public MyCustomTab() {
super(103, "mymod.tab.custom");
}And don't forget the localization in your lang file:
mymod.tab.custom=My TabText key (CatFrame namespace-based):
import decok.dfcdvadstf.catframe.ui.Text;
// ... same imports as above
public class MyMod {
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
TabRegistry.registerTab(
"create_world",
MyCustomTab::new,
104,
Text.translatable("mymod", "tab.fancy"), // Text title
5
);
}
}// Tab constructor — explicit Text, lazy translation via LocalizationManager
public MyCustomTab() {
super(104, Text.translatable("mymod", "tab.fancy"));
}Or use the colon shorthand (auto-wraps in Text.translatable):
TabRegistry.registerTab("create_world", MyCustomTab::new, 105, "mymod:tab.quick");
// Tab constructor
public MyCustomTab() {
super(105, "mymod:tab.quick"); // colon → auto Text.translatable
}-
Register early — like
FMLPreInitializationEvent. OnceTabManageris constructed for a given bar, that bar's bucket freezes; registering to it after that throws anIllegalStateException. -
Tab IDs must be unique within a bar — duplicate IDs in the same bucket throw an
IllegalArgumentException. Different bars can reuse the same numeric IDs without conflict. -
barIdmust match — the string you pass toregisterTab()must be the same as theTabBar'sbarId(or the string passed directly toTabManager). Typos mean your tab silently won't appear. -
AbstractScreenTabconstructor tabId must match registration —TabManagernow enforces this at init time. Iftab.getTabId()doesn't equal the entry'stabId, aTabUncorrespondExceptionis thrown with a clear message. No more silent mismatches. -
String vs Text — both
registerTab(... String nameKey)andregisterTab(... Text nameText)overloads exist. Pick whichever matches your tab's constructor. The String overload setsentry.nameText = null; the Text overload setsentry.nameKey = text.getRaw()and keepsentry.nameTextfor display. -
initGui()gets called byTabManager— on init and on resize. Always callsuper.initGui().
Package: decok.dfcdvadstf.catframe.ui.layouts
A layout system for arranging UI elements — think of it as flexible containers that automatically compute child positions so you don't have to hardcode coordinates everywhere. Every layout is itself an ILayout, so you can nest layouts inside other layouts.
The idea is simple: you add children (any ILayout), set a few properties like padding and spacing, call recalculate(), and bam — everything falls into place. You can even switch layouts at runtime without touching child positions.
ILayout ← base: x, y, width, height
└── Layout ← adds children, padding, spacing, recalculate, draw
└── AbstractLayout ← base impl: manages children, auto-recalculates
├── SimpleLayout ← bounding-box container (no auto-arrangement)
├── LinearLayout ← single row/column (HORIZONTAL / VERTICAL)
│ ├── HorizontalLayout ← preset horizontal + centre
│ └── VerticalLayout ← preset vertical + centre
├── GridLayout ← fixed-column grid
├── EqualSpacingLayout ← evenly distributed children
└── HeaderFooterLayout ← three zones, draws ContentPanel background
All layouts live under decok.dfcdvadstf.catframe.ui.layouts.
Every layout inherits these from AbstractLayout:
| Property | Type | Default | Description |
|---|---|---|---|
padding |
int |
4 | Space around the content area (applied on all sides). Changing it triggers recalculate(). |
spacing |
int |
2 | Gap between children. Changing it triggers recalculate(). |
children |
List<ILayout> |
(empty) | Managed via add(), remove(), clear(). |
x, y
|
int |
0 | Top-left position of the layout. |
width, height
|
int |
0 | Calculated automatically by recalculate(), or set externally. |
// Chaining works because add() returns the layout itself
layout.add(child1).add(child2).setPadding(8).setSpacing(4);And there's a batch variant:
layout.addAll(header, content, footer);A general-purpose container. It doesn't rearrange children at all — they keep whatever position they already have. recalculate() just computes the bounding box of all children plus padding, so the layout knows its own size.
Use this when you want to group elements but handle positioning yourself.
SimpleLayout group = new SimpleLayout(6); // padding = 6
// Manually position each child relative to the group
group.add(someWidget);
someWidget.setPosition(group.getX() + 8, group.getY() + 8);Why would you use this? Sometimes you just need a container for drawing or event-handling purposes, without any automatic arrangement.
SimpleLayoutgives you that without imposing a layout strategy.
These arrange children in a single line — either left-to-right or top-to-bottom. HorizontalLayout and VerticalLayout are convenience wrappers with sensible defaults.
LinearLayout has two enums:
-
Axis:HORIZONTALorVERTICAL— the primary arrangement direction. -
Alignment: controls how children sit on the perpendicular axis:-
START— clump at the left/top edge. -
CENTER— centred. -
END— clump at the right/bottom edge. -
FILL— stretch to fill the available space.
-
// Vertical column, children centred horizontally
LinearLayout column = new LinearLayout(Axis.VERTICAL, Alignment.CENTER);
column.setSpacing(6).setPadding(10);
column.add(button1).add(button2).add(button3);
column.setPosition(20, 30);
column.recalculate();Or grab the convenience versions:
// Horizontal row, children vertically centred by default
HorizontalLayout row = new HorizontalLayout();
row.setSpacing(8).add(icon).add(label).add(button);
// Vertical column, centred horizontally
VerticalLayout col = new VerticalLayout();
col.addAll(title, description, actionRow);Both convenience layouts default to CENTER alignment on the perpendicular axis — so HorizontalLayout centres children vertically, VerticalLayout centres them horizontally. Need a different alignment? Pass it to the constructor or call setAlignment().
Arranges children in a fixed-column grid, left-to-right then top-to-bottom. Each cell can be auto-sized from the largest child, or you can fix a cell size.
Children are centred within their cell — so mismatched sizes still look tidy.
// Three columns, auto-sized cells (largest child dictates cell size)
GridLayout grid = new GridLayout(3);
grid.setSpacing(4).setPadding(6);
grid.add(btn1).add(btn2).add(btn3).add(btn4);
grid.recalculate();
// Or use fixed 60x20 cells
GridLayout fixed = new GridLayout(2, 60, 20);Properties you can tweak:
| Method | Description |
|---|---|
setColumns(int) |
Change column count (min 1). |
setCellSize(w, h) |
Fix cell dimensions. Set to 0 to auto-size again. |
getColumns() / getCellWidth() / getCellHeight()
|
Read current values. |
Distributes children evenly across the available space. Instead of a fixed gap (like LinearLayout), it calculates the gap automatically so that children span the full content width or height.
This is your go-to for things like evenly spaced toolbar buttons or a row of action icons.
EqualSpacingLayout toolbar = new EqualSpacingLayout(EqualSpacingLayout.Axis.HORIZONTAL);
toolbar.add(undoBtn).add(redoBtn).add(saveBtn).add(deleteBtn);
// Children are now evenly spread across the toolbar width
toolbar.setPosition(10, 10);
toolbar.recalculate();If you set a fixed width/height beforehand, children are distributed within that space. If not, the layout auto-sizes from its children's total width plus the calculated gaps.
A three-zone layout: header at the top, content in the middle, footer at the bottom. By default it draws itself using ContentPanelRenderer.drawContentPanel() — the tiled background with header/footer separator lines, no extra effort needed.
The header and footer are centred horizontally; the content area stretches to fill whatever vertical space remains.
HeaderFooterLayout panel = new HeaderFooterLayout();
panel.setHeader(titleWidget);
panel.setContent(scrollableList);
panel.setFooter(doneButton);
// Position and size — the recalculate(availableWidth, availableHeight)
// variant is handy when the parent knows the exact space
panel.setPosition(10, 10);
panel.recalculate(panelWidth, panelHeight);
// Drawing includes ContentPanel background automatically
// Call this in your drawScreen()
panel.draw(mouseX, mouseY, partialTicks);Want to skip the background rendering? new HeaderFooterLayout(false) or setDrawPanel(false).
| Method | Returns | Description |
|---|---|---|
setHeader(ILayout) |
Layout |
Sets the header slot (index 0). |
setContent(ILayout) |
Layout |
Sets the content slot (index 1). |
setFooter(ILayout) |
Layout |
Sets the footer slot (index 2). |
getHeader() |
ILayout / null
|
Returns the header child. |
getContent() |
ILayout / null
|
Returns the content child. |
getFooter() |
ILayout / null
|
Returns the footer child. |
recalculate(w, h) |
— | Recalculate with explicit dimensions. |
The draw(mouseX, mouseY, partialTicks) method is a no-op by default, but HeaderFooterLayout overrides it to render the ContentPanelRenderer background. If you're making a custom layout that draws something, just override draw() in your subclass.
Since every layout is an ILayout, you can mix and match freely — a GridLayout inside a VerticalLayout inside a HeaderFooterLayout, for instance. Each child gets positioned by its parent layout, then positions its own children in turn.
HeaderFooterLayout screen = new HeaderFooterLayout();
screen.setHeader(new TextWidget("Settings"));
VerticalLayout body = new VerticalLayout();
body.add(new SomeWidget());
GridLayout grid = new GridLayout(2);
grid.add(opt1).add(opt2).add(opt3).add(opt4);
body.add(grid);
screen.setContent(body);
screen.setFooter(new TextWidget("v1.0"));
screen.recalculate(width, height);One thing to keep in mind: always call
recalculate()on the outermost layout after you're done adding children — it cascades down because each layout'sadd()already triggers its own recalculation.
Package: decok.dfcdvadstf.catframe.ui.tab.GridLayoutTab
An abstract tab backed by a GridLayout — arranges children in a fixed-column grid and centres the whole thing within the screen. Subclasses only need to override buildLayout() to populate content; no coordinate juggling required.
| Constructor | Description |
|---|---|
GridLayoutTab(int tabId, String tabNameKey) |
Default 2 columns, auto-sized cells |
GridLayoutTab(int tabId, String tabNameKey, int columns) |
Custom column count |
GridLayoutTab(int tabId, String tabNameKey, int columns, int cellW, int cellH) |
Custom columns + fixed cell size |
protected abstract void buildLayout(GridLayout layout);Called once during initGui(), before the layout is arranged. Subclasses add children to layout here — the framework handles positioning and centring.
initGui() flow:
- Clear the layout (
layout.clear()) - Call
buildLayout(layout)— subclass fills in children - Call
arrangeAndCenter(width, height)— arrange and centre the grid - Walk children and auto-register any
GuiButtonwithTabManager
Centring rules — matching 26.1's alignInRectangle(layout, rect, 0.5F, 0.1666F):
- Horizontal: centred (0.5)
- Vertical: roughly one-sixth from the top (0.16666667)
import decok.dfcdvadstf.catframe.ui.tab.GridLayoutTab;
import decok.dfcdvadstf.catframe.ui.tab.TabRegistry;
import decok.dfcdvadstf.catframe.ui.layouts.GridLayout;
import net.minecraft.client.gui.GuiButton;
public class OptionsTab extends GridLayoutTab {
public OptionsTab() {
super(103, "mymod.tab.options", 2); // 2 columns
}
@Override
protected void buildLayout(GridLayout layout) {
layout.add(new GuiButton(1, 0, 0, "Option A"));
layout.add(new GuiButton(2, 0, 0, "Option B"));
layout.add(new GuiButton(3, 0, 0, "Option C"));
layout.add(new GuiButton(4, 0, 0, "Option D"));
}
@Override
public void actionPerformed(GuiButton button) {
switch (button.id) {
case 1: /* handle A */ break;
case 2: /* handle B */ break;
}
}
}
// Register during preInit
TabRegistry.registerTab("create_world", OptionsTab::new, 103, "mymod.tab.options", 5);| Method | Description |
|---|---|
buildLayout(GridLayout) |
Abstract — subclass fills in children here |
getLayout() |
Direct access to the underlying GridLayout (advanced usage) |
arrangeAndCenter(int, int) |
Re-arrange and centre the grid (called automatically on resize) |
CatFrame provides a unified component system inspired by higher-version Minecraft's LayoutElement + Renderable + GuiEventListener. All UI controls implement the Component interface.
Package: decok.dfcdvadstf.catframe.ui.components.Component
public interface Component extends ILayout {
int getX(); void setX(int x);
int getY(); void setY(int y);
int getWidth(); int getHeight();
boolean isVisible(); void setVisible(boolean visible);
boolean isActive(); void setActive(boolean active);
default void render(int mouseX, int mouseY, float partialTicks) {}
default void mouseClicked(int mouseX, int mouseY, int mouseButton) {}
default void keyTyped(char typedChar, int keyCode) {}
default void mouseScrolled(int delta) {}
default void mouseDrag(int mouseX, int mouseY, int mouseButton, long timeSinceLastClick) {}
default void mouseReleased(int mouseX, int mouseY, int mouseButton) {}
default boolean isMouseOver(int mouseX, int mouseY) { ... }
}Component (interface)
└── AbstractComponent (base impl: position, visibility, drawing utils)
├── AbstractButton (press/hover/sound logic)
│ ├── Button (simple text button)
│ └── CyclingButton<T> (generic cycling button)
├── CyclingArea<T> (cycling with textured background)
├── EditBox (text input field)
├── StringWidget (text display)
├── WaitingPanel (loading spinner)
├── GuiButtonAdapter (wraps vanilla GuiButton as Component)
└── Toast (notification system)
├── BaseToast
│ ├── SimpleToast
│ ├── SystemToast
│ └── ItemToast
└── ...
Package: decok.dfcdvadstf.catframe.ui.components.CyclingButton
A generic cycling button, extending AbstractButton with unified texture backgrounds. Supports click and scroll wheel cycling.
GuiCyclableButton<T> |
CyclingButton<T> |
|
|---|---|---|
| Base class |
GuiButton (vanilla) |
AbstractButton (Component) |
| Texture | Vanilla button texture | CatFrame custom texture |
| Builder | .build(id, x, y, w, h, callback) |
.build(x, y, w, h, callback) |
| Event dispatch | actionPerformed(GuiButton) |
mouseClicked() / render()
|
New code should use CyclingButton.
CyclingButton<EnumDifficulty> diffBtn = CyclingButton.<EnumDifficulty>builder(
d -> I18n.format(d.getDifficultyResourceKey()))
.values(EnumDifficulty.values())
.initially(EnumDifficulty.NORMAL)
.label("Difficulty:")
.build(x, y, 200, 20, (button, value) -> {
setDifficulty(value);
});Boolean on/off convenience:
CyclingButton<Boolean> cheatsBtn = CyclingButton.onOffBuilder()
.initially(false)
.build(x, y, 200, 20, (button, value) -> setAllowCheats(value));Package: decok.dfcdvadstf.catframe.ui.components.CyclingArea
A generic cycling selection area, extending AbstractComponent. Supports textured backgrounds (9-slice stretching), click and scroll wheel cycling, and label text display. Integrates with the unified component system.
CyclingArea<EnumDifficulty> area = CyclingArea.<EnumDifficulty>builder(
d -> I18n.format(d.getDifficultyResourceKey()))
.values(EnumDifficulty.values())
.initially(EnumDifficulty.NORMAL)
.build(x, y, 200, 20, (area, value) -> { ... });Package: decok.dfcdvadstf.catframe.ui.components.EditBox
Text input field inspired by higher-version Minecraft's EditBox. Supports textured backgrounds or vanilla black/white style, and vanilla-style cursor.
EditBox seedBox = new EditBox(x, y, 200, 20);
seedBox.setHint("Enter seed...");
seedBox.setMaxLength(64);
seedBox.setUseVanillaTexture(false); // Use CatFrame textureCatFrame provides a higher-version-style toast notification system with queuing, animations, and multi-slot display.
Package: decok.dfcdvadstf.catframe.ui.components.toast.Toast
public interface Toast extends Component {
int DEFAULT_WIDTH = 160;
int SLOT_HEIGHT = 32;
Visibility getWantedVisibility();
void update(ToastManager manager, long fullyVisibleForMs);
void render(int mouseX, int mouseY, float partialTicks);
default Object getToken() { return this; }
default int occupiedSlotCount() { ... }
default float xPos(int screenWidth, float visiblePortion) { ... }
default float yPos(int firstSlotIndex) { ... }
}ToastManager toastManager = new ToastManager(mc);
// Called in tick
toastManager.update();
// Called in render
toastManager.render();- Maximum 5 slots displayed simultaneously
- Slide-in/slide-out animation: 600ms
- Automatic queue management
Simple text notification with adaptive sizing:
// Basic usage
toastManager.addToast(new SimpleToast("Hello World"));
toastManager.addToast(new SimpleToast("Title", "Description"));
// Convenience factories
SimpleToast.success("Saved!"); // Green ✓
SimpleToast.warning("Low space"); // Yellow ⚠
SimpleToast.error("Failed!"); // Red ✗
SimpleToast.info("Tip: ..."); // Cyan ℹSystem notification with icon and automatic word wrap:
SystemToast.SystemToastId id = new SystemToast.SystemToastId(8000L);
// Add new
SystemToast.add(toastManager, id, "Update Available", "Version 2.0 is out!");
// Update existing Toast (resets content if exists)
SystemToast.addOrUpdate(toastManager, id, "Downloading...", "50% complete");Toast with an item icon:
toastManager.addToast(new ItemToast(
new ItemStack(Items.diamond),
"Achievement Get!",
"Diamonds!"
));Extend BaseToast to create custom toasts:
public class MyToast extends BaseToast {
public MyToast() {
setBackgroundTexture(new ResourceLocation("mymod", "textures/gui/toast.png"));
// Or null for solid color fallback
}
@Override
protected void renderContent(FontRenderer font, long fullyVisibleForMs) {
font.drawString("Custom!", 10, 10, 0xFFFFFF);
}
@Override
public void update(ToastManager manager, long fullyVisibleForMs) {
wantedVisibility = fullyVisibleForMs < 5000L ? Visibility.SHOW : Visibility.HIDE;
}
}Package: decok.dfcdvadstf.catframe.ui.Style
Text style class inspired by higher-version Minecraft's net.minecraft.network.chat.Style. Controls color, formatting, click/hover events, etc.
Style style = Style.EMPTY
.withColor(TextColor.fromRgb(0xFF5555))
.withBold(true)
.withItalic(true)
.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://example.com"))
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal("Click me!")));
Text styled = Text.literal("Hello", style);Supported formatting:
| Attribute | Method |
|---|---|
| Color | withColor(TextColor) |
| Bold | withBold(boolean) |
| Italic | withItalic(boolean) |
| Underlined | withUnderlined(boolean) |
| Strikethrough | withStrikethrough(boolean) |
| Obfuscated | withObfuscated(boolean) |
| Click event | withClickEvent(ClickEvent) |
| Hover event | withHoverEvent(HoverEvent) |
Package: decok.dfcdvadstf.catframe.ui.layouts.FrameLayout
A stacking layout — children are placed in layers, with each child aligned within the frame according to LayoutSettings. The frame size is determined by the largest child (including padding).
FrameLayout frame = new FrameLayout(200, 100);
// Center placement
frame.addChild(someWidget);
// Custom alignment (0.0=left/top, 0.5=center, 1.0=right/bottom)
frame.addChild(otherWidget, settings -> settings.align(0.0F, 1.0F));
frame.recalculate();Static utility methods:
// Center within a rectangle
FrameLayout.centerInRectangle(widget, x, y, width, height);
// Custom alignment within a rectangle
FrameLayout.alignInRectangle(widget, x, y, width, height, 0.5F, 0.1666F);To use CatFrame as a dependency, add this to your build.gradle:
dependencies {
implementation 'com.github.song682:CatFrame:Tag'
}Replace Tag with the version you want from the repository.