Skip to content
This repository was archived by the owner on Jan 6, 2026. It is now read-only.

Introduction

Garkatron edited this page Dec 26, 2024 · 3 revisions

2.0.0 Main Changes

This update brings significant changes:

  • XML Support for HTML
  • YAML Support for CSS
  • Various other improvements, fixes and features

Why are XML and YAML so important?

Because they offer an easier and faster way to build your GUI without the need for loops or complex math.

BUI implements a "DOM" (Document Object Model) and styling sheets.

This means you can use the following code:

BUI Main Menu

<?xml version="1.0" encoding="UTF-8"?>

<root xmlns="GUI-LIBRARY-HTML-SCHEMA">

    <link src="/assets/guilib/uis/defaults/defaults.yaml"/>
    <link src="/assets/guilib/uis/UIbui/styles.yaml"/>

    <body>
        <div id="mainContainer">
            <label id="mainTitle" maxTextLength="1000">Welcome to BUI</label>

            <button id="backButton">
                <label>
                    x
                </label>
            </button>
            <label id="copyright">
                @Garkatron
            </label>
            <panel id="centerContainer">
                <expandableButton id="detectedProjectsButton">
                    <label>
                        Detected Projects
                    </label>
                </expandableButton>
            </panel>
        </div>

    </body>

</root>
SharedProperties:
  textShadow: false

# Selectors

Select:

  - at: expandablebutton
    panelLengthX: 80
    childrenPlacement: CENTER
    panelLengthY: 10
    border: -1px 000000

  - at: .bui.options.pathButton
    width: 100%
    childrenPlacement: CENTER
    backgroundColor: "FF0000"

  - at: $mainContainer
    width: 100%
    height: 100%
    backgroundColor: "585250"

  - at: $centerContainer
    #tileSize: 4px
    #backgroundImage: /assets/guilib/textures/gui/themes/default/SmallTinyPanel.png
    panelLengthY: 50
    panelLengthX: 80
    selfPlacement: CENTER
    childrenPlacement: CENTER

  - at: $mainTitle
    selfPlacement: TOP

    textColor: "000000"

MainGuiMenu.class

public class MainPage extends Page {

	public MainPage(Router router, String... text) {
		super(GuiLib.class, router);

		xmlPath = "/assets/guilib/uis/UIbui/index.xml";

		// ! DON'T DELETE IT
		reloadXml();
		reloadStyles();

		// ! Work here

		IButton b = (IButton) getDocument().getNodeById("detectedProjectsButton");
		b.setOnReleaseAction(
			(v) -> {
				router.navigateTo("detectedProjectsPage");
			}
		);
	}
}

Instead of this code

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package net.minecraft.client.gui;

import java.awt.Desktop;
import java.awt.Desktop.Action;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Random;
import javax.imageio.ImageIO;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.options.GuiOptions;
import net.minecraft.client.gui.options.data.OptionsPages;
import net.minecraft.client.render.RenderBlocks;
import net.minecraft.client.render.block.model.BlockModel;
import net.minecraft.client.render.block.model.BlockModelDispatcher;
import net.minecraft.client.render.stitcher.TextureRegistry;
import net.minecraft.client.render.tessellator.Tessellator;
import net.minecraft.core.block.Block;
import net.minecraft.core.lang.I18n;
import net.minecraft.core.sound.SoundCategory;
import net.minecraft.core.util.helper.MathHelper;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;

public class GuiMainMenu extends GuiScreen {
    private static final Block[] BLOCKS_TO_DRAW;
    private static String[] logoBlockLayers;
    private static final int[] KEY_CHAIN;
    private static final Random rand;
    private String splashText = "missingno";
    private GuiButton multiplayerButton;
    private LogoBlock[][] logoBlocks;
    private int logoClickCounter = 0;
    private int indexToDraw = 0;
    private int codeProgress = 0;
    private boolean showBuildTime = false;

    public GuiMainMenu() {
        GuiIngameMenu.photoModeDisabled = false;
        InputStream stream = null;

        try {
            stream = GuiMainMenu.class.getResourceAsStream("/title/splashes.txt");
            List<String> allSplashes = new ArrayList();
            BufferedReader br = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));

            while(true) {
                String line = br.readLine();
                if (line == null) {
                    this.splashText = (String)allSplashes.get(rand.nextInt(allSplashes.size()));
                    break;
                }

                line = line.trim();
                if (!line.isEmpty()) {
                    allSplashes.add(line);
                }
            }
        } catch (Exception var13) {
        } finally {
            try {
                stream.close();
            } catch (Exception var12) {
            }

        }

    }

    public void keyTyped(char c, int i, int mouseX, int mouseY) {
        if (i == KEY_CHAIN[this.codeProgress]) {
            ++this.codeProgress;
            if (this.codeProgress == KEY_CHAIN.length) {
                if (this.mc.ingameGUI instanceof GuiIngameDoom) {
                    this.mc.ingameGUI = new GuiIngame(this.mc);
                } else {
                    this.mc.ingameGUI = new GuiIngameDoom(this.mc);
                }

                this.mc.sndManager.playSound("mob.pig", SoundCategory.GUI_SOUNDS, 1.0F, 1.0F);
                this.codeProgress = 0;
            }
        } else {
            this.codeProgress = 0;
        }

    }

    public void init() {
        I18n i18n = I18n.getInstance();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        if (calendar.get(2) + 1 == 1 && calendar.get(5) == 19) {
            this.splashText = i18n.translateKey("gui.main_menu.label.splash.jonkadelic_birthday");
        } else if (calendar.get(2) + 1 == 4 && calendar.get(5) == 29) {
            this.splashText = i18n.translateKey("gui.main_menu.label.splash.maggandgeez_birthday");
        } else if (calendar.get(2) + 1 == 5 && calendar.get(5) == 17) {
            this.splashText = i18n.translateKey("gui.main_menu.label.splash.minecraft_birthday");
        } else if (calendar.get(2) + 1 == 7 && calendar.get(5) == 20) {
            this.splashText = i18n.translateKey("gui.main_menu.label.splash.bta_birthday");
        } else if (calendar.get(2) + 1 == 12 && calendar.get(5) == 24) {
            this.splashText = i18n.translateKey("gui.main_menu.label.splash.xmas");
        } else if (calendar.get(2) + 1 == 1 && calendar.get(5) == 1) {
            this.splashText = i18n.translateKey("gui.main_menu.label.splash.new_year");
        } else if (calendar.get(2) + 1 == 10 && calendar.get(5) == 31) {
            this.splashText = i18n.translateKey("gui.main_menu.label.splash.halloween");
        }

        int i = this.height / 4 + 48;
        this.controlList.add(new GuiButton(1, this.width / 2 - 100, i, i18n.translateKey("gui.main_menu.button.singleplayer")));
        this.controlList.add(this.multiplayerButton = new GuiButton(2, this.width / 2 - 100, i + 24, i18n.translateKey("gui.main_menu.button.multiplayer")));
        this.controlList.add(new GuiButton(3, this.width / 2 - 100, i + 48, i18n.translateKey("gui.main_menu.button.texture_packs")));
        if (this.mc.hideQuitButton) {
            this.controlList.add(new GuiButton(0, this.width / 2 - 100, i + 72, i18n.translateKey("gui.main_menu.button.options")));
        } else {
            this.controlList.add(new GuiButton(0, this.width / 2 - 100, i + 72 + 12, 98, 20, i18n.translateKey("gui.main_menu.button.options")));
            this.controlList.add(new GuiButton(4, this.width / 2 + 2, i + 72 + 12, 98, 20, i18n.translateKey("gui.main_menu.button.quit_game")));
        }

        int d = 1;
        this.controlList.add(new GuiClickableLabel(5, d, this.height - 36 - d, i18n.translateKey("gui.main_menu.button.website"), 5941560));
        this.controlList.add(new GuiClickableLabel(6, d, this.height - 24 - d, i18n.translateKey("gui.main_menu.button.discord"), 5793266));
        this.controlList.add(new GuiClickableLabel(7, d, this.height - 12 - d, i18n.translateKey("gui.main_menu.button.youtube"), 16711680));
        this.controlList.add(new GuiTexturedButton(8, "/assets/minecraft/textures/gui/gui.png", this.width / 2 + 104, i + 72 + 12, 0, 86, 20, 20));
        GuiButton debugButton = new GuiButton(9, this.width - 8, this.height - 8, 8, 8, "");
        debugButton.visible = false;
        this.controlList.add(debugButton);
        if (this.mc.session == null) {
            this.multiplayerButton.enabled = false;
        }

    }

    protected void buttonPressed(GuiButton button) {
        if (button.id == 0) {
            this.mc.displayGuiScreen(new GuiOptions(this));
        }

        if (button.id == 1) {
            this.mc.displayGuiScreen(new GuiSelectWorld(this));
        }

        if (button.id == 2) {
            this.mc.displayGuiScreen(new GuiSelectServer(this));
        }

        if (button.id == 3) {
            this.mc.displayGuiScreen(new GuiOptions(this, OptionsPages.TEXTURE_PACKS));
        }

        if (button.id == 4) {
            this.mc.shutdown();
        }

        if (button.id == 8) {
            this.mc.displayGuiScreen(new GuiOptions(this, OptionsPages.LANGUAGE));
        }

        if (button.id == 9) {
            this.mc.displayGuiScreen(new GuiOptions(this, OptionsPages.DEBUG));
        }

        Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
        if (desktop != null && desktop.isSupported(Action.BROWSE)) {
            Exception e;
            if (button.id == 5) {
                try {
                    desktop.browse(URI.create("https://betterthanadventure.net"));
                } catch (Exception var6) {
                    e = var6;
                    e.printStackTrace();
                }
            }

            if (button.id == 6) {
                try {
                    desktop.browse(URI.create("https://betterthanadventure.net/discord"));
                } catch (Exception var5) {
                    e = var5;
                    e.printStackTrace();
                }
            }

            if (button.id == 7) {
                try {
                    desktop.browse(URI.create("https://www.youtube.com/channel/UC_GT4t8Io87-UTzaGNnsuMg"));
                } catch (Exception var4) {
                    e = var4;
                    e.printStackTrace();
                }
            }
        }

    }

    public void drawBackground() {
        if (this.mc.mainMenuBackground.isEnabled() && !(Boolean)this.mc.gameSettings.alphaMenu.value) {
            this.mc.mainMenuBackground.draw();
        } else {
            super.drawBackground();
        }

    }

    public void tick() {
        if (this.logoBlocks != null) {
            for(int var1 = 0; var1 < this.logoBlocks.length; ++var1) {
                for(int var2 = 0; var2 < this.logoBlocks[var1].length; ++var2) {
                    this.logoBlocks[var1][var2].updateLogoEffects();
                }
            }
        }

    }

    public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
        super.mouseClicked(mouseX, mouseY, mouseButton);
        int logoWidth = 274;
        int logoX = this.width / 2 - logoWidth / 2;
        int logoHeight = 30;
        int p = 8;
        if (mouseX >= logoX + p && mouseX <= logoX + 270 - p && mouseY >= logoHeight + p && mouseY <= logoHeight + 48 - p) {
            if (this.logoClickCounter >= 0) {
                this.indexToDraw = rand.nextInt(BLOCKS_TO_DRAW.length);
                this.mc.gameSettings.alphaMenu.toggle();
                if ((Boolean)this.mc.gameSettings.alphaMenu.value) {
                    this.logoBlocks = null;
                }

                this.logoClickCounter = 0;
            } else {
                ++this.logoClickCounter;
            }
        }

        if (mouseY >= 2 && mouseY <= 12 && mouseX >= 2 && mouseX <= 40) {
            this.showBuildTime = !this.showBuildTime;
        }

    }

    public void drawScreen(int mouseX, int mouseY, float partialTick) {
        I18n i18n = I18n.getInstance();
        this.drawDefaultBackground();
        char c = 274;
        int k = this.width / 2 - c / 2;
        byte byte0 = 30;
        if (!(Boolean)this.mc.gameSettings.alphaMenu.value) {
            GL11.glBindTexture(3553, this.mc.renderEngine.getTexture("/title/mclogo.png"));
            GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
            this.drawTexturedModalRect(k + 0, byte0 + 0, 0, 0, 155, 64);
            this.drawTexturedModalRect(k + 155, byte0 + 0, 0, 64, 155, 64);
        } else {
            this.drawLogo(partialTick);
        }

        GL11.glPushMatrix();
        GL11.glTranslatef((float)(this.width / 2 + 140), 80.0F, 0.0F);
        GL11.glRotatef(-20.0F, 0.0F, 0.0F, 1.0F);
        float f1 = 1.8F - MathHelper.abs(MathHelper.sin((float)(System.currentTimeMillis() % 1000L) / 1000.0F * 3.141593F * 2.0F) * 0.1F);
        f1 = f1 * 100.0F / (float)(this.fontRenderer.getStringWidth(this.splashText) + 32);
        GL11.glScalef(f1, f1, f1);
        this.drawStringCentered(this.fontRenderer, this.splashText, 0, -8, 16776960);
        GL11.glPopMatrix();
        int versionTextColor = !(Boolean)this.mc.gameSettings.alphaMenu.value && this.mc.mainMenuBackground.isEnabled() ? 16777215 : 5263440;
        if (this.showBuildTime) {
            this.drawString(this.fontRenderer, "2024-08-04 15:34:59", 2, 2, versionTextColor);
        } else {
            this.drawString(this.fontRenderer, "Better than Adventure! " + Minecraft.VERSION, 2, 2, versionTextColor);
        }

        String disclaimer1 = i18n.translateKey("gui.main_menu.label.disclaimer.1");
        this.drawString(this.fontRenderer, disclaimer1, this.width - this.fontRenderer.getStringWidth(disclaimer1) - 2, this.height - 20, 16777215);
        String disclaimer2 = i18n.translateKey("gui.main_menu.label.disclaimer.2");
        this.drawString(this.fontRenderer, disclaimer2, this.width - this.fontRenderer.getStringWidth(disclaimer2) - 2, this.height - 10, 16777215);
        super.drawScreen(mouseX, mouseY, partialTick);
        if (this.mc.downloadResourcesThread.downloading) {
            double b = Math.sin((double)System.currentTimeMillis() / 1000.0 * 4.0);
            b = b * 0.2 + 0.8;
            int ib = (int)Math.round(b * 255.0);
            int color = -16777216 + (ib << 16) + (ib << 8) + ib;
            this.mc.fontRenderer.drawCenteredString(i18n.translateKeyAndFormat("gui.main_menu.label.downloading_resources", new Object[]{this.mc.downloadResourcesThread.downloadProgress, this.mc.downloadResourcesThread.toDownload}), this.width / 2, 16, color);
        }

    }

    private void drawLogo(float var1) {
        int var3;
        int var4;
        if (this.logoBlocks == null) {
            this.logoBlocks = new LogoBlock[logoBlockLayers[0].length()][logoBlockLayers.length];

            for(var4 = 0; var4 < this.logoBlocks.length; ++var4) {
                for(var3 = 0; var3 < this.logoBlocks[var4].length; ++var3) {
                    this.logoBlocks[var4][var3] = new LogoBlock(var4, var3);
                }
            }
        }

        GL11.glMatrixMode(5889);
        GL11.glPushMatrix();
        GL11.glLoadIdentity();
        var3 = 120 * this.mc.resolution.scale;
        GLU.gluPerspective(70.0F, (float)this.mc.resolution.width / (float)var3, 0.05F, 100.0F);
        GL11.glViewport(0, this.mc.resolution.height - var3, this.mc.resolution.width, var3);
        GL11.glMatrixMode(5888);
        GL11.glPushMatrix();
        GL11.glLoadIdentity();
        GL11.glDisable(2884);
        GL11.glCullFace(1029);
        GL11.glDepthMask(true);
        GL11.glEnable(2929);

        for(var4 = 0; var4 < 3; ++var4) {
            GL11.glPushMatrix();
            GL11.glTranslatef(0.4F, 0.6F, -12.0F);
            if (var4 == 0) {
                GL11.glClear(256);
                GL11.glTranslatef(0.0F, -0.4F, 0.0F);
                GL11.glScalef(0.98F, 1.0F, 1.0F);
                GL11.glEnable(3042);
                GL11.glBlendFunc(770, 771);
            }

            if (var4 == 1) {
                GL11.glDisable(3042);
                GL11.glClear(256);
            }

            if (var4 == 2) {
                GL11.glEnable(3042);
                GL11.glBlendFunc(768, 1);
            }

            GL11.glScalef(1.0F, -1.0F, 1.0F);
            GL11.glRotatef(15.0F, 1.0F, 0.0F, 0.0F);
            GL11.glScalef(0.89F, 1.0F, 0.4F);
            GL11.glTranslatef((float)(-logoBlockLayers[0].length()) * 0.5F, (float)(-logoBlockLayers.length) * 0.5F, 0.0F);
            TextureRegistry.blockAtlas.bindTexture();
            if (var4 == 0) {
                GL11.glBindTexture(3553, this.mc.renderEngine.getTexture("/title/black.png"));
            }

            RenderBlocks var5 = new RenderBlocks();

            for(int var6 = 0; var6 < logoBlockLayers.length; ++var6) {
                for(int var7 = 0; var7 < logoBlockLayers[var6].length(); ++var7) {
                    char var8 = logoBlockLayers[var6].charAt(var7);
                    if (var8 != ' ') {
                        GL11.glPushMatrix();
                        LogoBlock var9 = this.logoBlocks[var7][var6];
                        float var10 = (float)(var9.prevHeight + (var9.height - var9.prevHeight) * (double)var1);
                        float var11 = 1.0F;
                        float var12 = 1.0F;
                        if (var4 == 0) {
                            var11 = var10 * 0.04F + 1.0F;
                            var12 = 1.0F / var11;
                            var10 = 0.0F;
                        }

                        GL11.glTranslatef((float)var7, (float)var6, var10);
                        GL11.glScalef(var11, var11, var11);
                        var5.renderBlockAsItem(Tessellator.instance, (BlockModel)BlockModelDispatcher.getInstance().getDispatch(BLOCKS_TO_DRAW[this.indexToDraw]), BLOCKS_TO_DRAW[this.indexToDraw], var12);
                        GL11.glPopMatrix();
                    }
                }
            }

            GL11.glPopMatrix();
        }

        GL11.glDisable(3042);
        GL11.glMatrixMode(5889);
        GL11.glPopMatrix();
        GL11.glMatrixMode(5888);
        GL11.glPopMatrix();
        GL11.glViewport(0, 0, this.mc.resolution.width, this.mc.resolution.height);
        GL11.glEnable(2884);
    }

    static Random getRandom() {
        return rand;
    }

    public static void loadLogo() {
        try {
            InputStream inputStream = GuiMainMenu.class.getResourceAsStream("/title/retrologo.png");
            BufferedImage image = ImageIO.read(inputStream);
            inputStream.close();
            logoBlockLayers = new String[image.getHeight()];

            for(int i = 0; i < image.getHeight(); ++i) {
                StringBuilder builder = new StringBuilder();

                for(int j = 0; j < image.getWidth(); ++j) {
                    int rgb = image.getRGB(j, i);
                    if ((rgb >> 16 & 255) > 128) {
                        builder.append('*');
                    } else {
                        builder.append(' ');
                    }
                }

                logoBlockLayers[i] = builder.toString();
            }
        } catch (Exception var6) {
            Exception e = var6;
            e.printStackTrace();
        }

    }

    static {
        BLOCKS_TO_DRAW = new Block[]{Block.stone, Block.dirt, Block.planksOak, Block.cobbleStone, Block.cobbleStoneMossy, Block.basalt, Block.limestone, Block.granite, Block.brickClay, Block.ice, Block.wool, Block.blockSnow, Block.obsidian, Block.netherrack};
        logoBlockLayers = null;
        KEY_CHAIN = new int[]{23, 32, 31, 25, 23, 31, 25, 24, 25, 32, 28};
        rand = new Random();
        loadLogo();
    }

    class LogoBlock {
        public double height;
        public double prevHeight;
        public double dropSpeed;

        public LogoBlock(int var2, int var3) {
            this.height = this.prevHeight = (double)(10 + var3) + GuiMainMenu.rand.nextDouble() * 32.0 + (double)var2;
        }

        public void updateLogoEffects() {
            this.prevHeight = this.height;
            if (this.height > 0.0) {
                this.dropSpeed -= 0.6;
            }

            this.height += this.dropSpeed;
            this.dropSpeed *= 0.9;
            if (this.height < 0.0) {
                this.height = 0.0;
                this.dropSpeed = 0.0;
            }

        }
    }
}

Functional GUI

To create your functional GUI, BUI provides many pre-built Elements for your GUI, so you don’t need to build everything from scratch. If you want to add your own custom element, you can extend the Node.java class or implement its interface INode.java.

BUI is modular, so you can use individual nodes to draw them in your classic GUI or, if necessary, use the advanced GUI classes for more complex GUIs in your mod. The simpler elements can be drawn by hand.

BUI also provides a GuiHelper class that helps with public static variables like mouseX and mouseY positions, as well as other functions to manage XML files.

XML

XML files use a specific syntax. When I implemented it, I created a schema so that users can benefit from autocompletion with error checking in IntelliJ IDEA.

As shown above, you can use XML tags to design your GUI without writing any Java code.

Additionally, I worked on a Template System. You can load components from modules using their domain and use the "call" tag to "copy-paste" and keep your XML code clean. You can also load these templates from Java to use them like React components!

YAML

With YAML, you can style your GUI using simple style properties—without needing any Java code! It's similar to CSS.

With the current system, you can create your own custom styles for your mod.

Compatibility

  • Resource Packs: Assets can overwrite XML and YAML files.

Basic Classes

To master BUI quickly, here are the main classes you should be aware of:

  • INode.java

  • Root.java

  • Node.java

  • AdvancedContainer.java

  • AdvancedGuiContainer.java

  • AdvancedGuiScreen.java

  • Router.java

Clone this wiki locally