🗃️ Modular GUIs
Adding inheritance to GUIs.
By using GuiObject as an object and a container for child GuiObjects you can create advanced GUIs which can be easily edited as well. HammerLib offers a Unity-like approach to constructing your GUI (GameObject alternative - GuiObject).
📝 Creating GuiObjects
🌳 Root
To begin your journey on using this system, you must first create a root (scene).
To do this, simply call GuiObject.root()
which will return a new GuiRootObject
object for you.
This root has a few special methods, which you might use when chain-building a scene, like:
GuiRootObject add(GuiObject child)
- similar to a more genericGuiObject addChild(GuiObject child)
, but it returnsGuiRootObject
to enable chain-building;GuiRootObject onTick(Runnable task)
- allows hooking into the tick before any other child object gets ticked;GuiRootObject onPreRender(FloatConsumer task)
- calls the task with partialTicks prior to rendering any child objects;
After you're done building your hierarchy, add the GuiRootObject
to your GUI.
For this you will need three things:
protected GuiRootObject scene;
field in your GUI class;this.scene = addRenderableWidget(<GuiRootObject goes here>);
insideinit()
method of your GUI;this.scene.sendUpdate();
inside thetick()
(orcontainerTick()
for AbstractContainerScreen instances)
Alternatively... Consider using FlowguiScreen<T extends AbstractContainerMenu>
to get the flowgui integrated out-of-box.
It is advised to use XML files for FlowguiScreen
, but you may override GuiRootObject createRoot(ResourceLocation id)
to create GUI in code.
📦 Object
After you have made your root, you can start adding children to it. If you're looking to use stock objects provided by HammerLib, create a new object builder by using GuiObject.create("NAME")
. Right now there are a few existing objects that you can use. Call one of these functions on the bulder:
GuiObject empty()
- Returns an empty object (usually for inheritance purposes);GuiSlotLinkObject slot(Slot slot)
- Links the slot to the newly createdGuiSlotLinkObject
. You can offset/rotate/scale it however you'd like, the slot will be displayed where this link object will be;GuiImageObject image(ResourceLocation tex, float uOffset, float vOffset, int width, int height, int txWidth, int txHeight)
- Blits an image where the object is located. You can specify the file's texture dimensions;GuiImageObject image(ResourceLocation tex, float uOffset, float vOffset, int width, int height)
- Blits an image where the object is located. This method uses default 256x256 texture dimensions;GuiTextObject text(Component text, int color, boolean shadow)
- Creates a label with desired text component, color and shadow;GuiTextObject text(Component text)
- Creates a label with desired text component, white color and shadow;GuiButtonObject.GuiButtonObjectBuilder button()
- Creates a button builder, which you must complete to obtain yourGuiButtonObject
:.message(Component message)
- Sets a label to the button component; (optional).callback(OnPress callback)
- Sets a click callback to this button component;.alpha(float alpha)
- Sets transparency for the button textures and label;.enabled(boolean enabled)
- Toggles the button enabled state;.packedFGColor(int packedFGColor)
- Changes the color for the button's label;.pressSound(Holder<SoundEvent> pressSound)
- Changes the sound played when clicking on the button; (null for no sound)
GuiSpriteButtonObject.SpriteButtonBuilder spriteButton()
- Similar to button, but with additional properties:.customTexture(ResourceLocation texture)
- Provides a path to image with image of 3 vertically stacked sprites. Image's width and height will be used as dimensions for one sprite;.color(Vec3 color)
- RGB tinting for the button;
GuiEditBoxObject.EditBoxBuilder editBox()
- Creates an editable textbox wrapped into a gui component;.canLoseFocus(boolean canLoseFocus)
- Allows/disallows the textbox to loose focus;.bordered(boolean bordered)
- Changes the textbox border visibility;.editable(boolean editable)
- Changes if the textbox is editable;.responder(Consumer<String> responder)
- Callback for when the text contents change;.filter(Predicate<String> validator)
- Filter to the input validation;.suggestion(String suggestion)
- Sets a suggestion to be drawn in the input;.hint(Component hint)
- Displays a hint when the text field is empty and not focused;.formatter(BiFunction<String, Integer, FormattedCharSequence> textFormatter)
- Converts the input text with its cursor position into a renderable text component;.textColor(int textColor)
- Changes the text color of the string; (Default is #e0e0e0).textColorUneditable(int textColor)
- Changes the text color of the string when the field is uneditable; (Default is #707070).maxLength(int length)
- Maximum number of characters allowed to be typed into the textbox;.value(String value)
- Sets the default value inside the textbox;
⛓️💥 Inheritance
Now that you have both the root and child objects, let's put things together.
Add your objects to another object using GuiObject.addChild(GuiObject child)
.
This will attempt to add given child into the context object.
Please avoid using same names when adding multiple objects to same object, othewise the most recently added object may get a name change.
🍵 Example!
Let's create a test GUI and use some of the functions. I will be using chain building, but feel free to declare fields/variables for any child component. This is just a demo.
public static GuiRootObject assemble(int width, int height)
{
GuiObject slot, c1, c2, c3, c4;
var g = GuiObject.root()
.add(GuiObject.create("core")
.image(HLConstants.id("textures/gui/test_machine.png"), 0, 0, 176, 166)
.centered(width, height)
.addChild(GuiObject.create("btn").button()
.message(Component.literal("Test Button"))
.callback(b -> log.info("Clicked test button."))
.build()
.setEnabled(false)
.size(100, 20)
.pos(8, 18)
.rotation(-90).offset(0, 50) // offset +50 on Y.
.scale(0.5F)
.addChild(GuiObject.create("label")
.text(Component.literal("Original Value"))
.offset(0, 20) // add to current position
)
)
.addChild(slot = GuiObject.create("input")
.image(HLConstants.id("textures/block/test_machine_front.png"), 0, 0, 16, 16, 16, 16)
.pos(56, 17)
.pivotAtCenter()
.rotation(45)
.addChild(c1 = GuiObject.create("1")
.image(HLConstants.id("textures/block/test_machine_front.png"), 0, 0, 8, 8, 8, 8)
.pivotAtCenter()
)
.addChild(c2 = GuiObject.create("2")
.image(HLConstants.id("textures/block/test_machine_front.png"), 0, 0, 8, 8, 8, 8)
.pivotAtCenter()
)
.addChild(c3 = GuiObject.create("3")
.image(HLConstants.id("textures/block/test_machine_front.png"), 0, 0, 8, 8, 8, 8)
.pivotAtCenter()
)
.addChild(c4 = GuiObject.create("4")
.image(HLConstants.id("textures/block/test_machine_front.png"), 0, 0, 8, 8, 8, 8)
.pivotAtCenter()
)
)
)
.onPreRender(partialTicks ->
{
long sys = System.currentTimeMillis();
float dist = Mth.sin(sys % 36000L / 100F);
float r = sys % 3600L / 10F;
slot.rotation(r); c1.rotation(r + 45); c2.rotation(r + 45); c3.rotation(r + 45); c4.rotation(r + 45);
c1.pos(16 + 8 * dist, 16 + 8 * dist); c2.pos(-8 - 8 * dist, 16 + 8 * dist); c3.pos(16 + 8 * dist, -8 - 8 * dist); c4.pos(-8 - 8 * dist, -8 - 8 * dist);
});
GuiButtonObject cbtn = g.findByPath("core/btn", GuiButtonObject.class);
if(cbtn != null) cbtn.message = Component.literal("Changed Text");
GuiTextObject lbl = g.findByPath("core/btn/label", GuiTextObject.class);
if(lbl != null) lbl.setText(Component.literal("I'm under button!"));
// Enable this to see AABBs of all components
g.debugBoundaries = true;
g.debugBoundaryColor = 0xFF669999;
return g;
}