diff --git a/.github/assets/hud-editor-dragged.png b/.github/assets/hud-editor-dragged.png new file mode 100644 index 0000000..6154061 Binary files /dev/null and b/.github/assets/hud-editor-dragged.png differ diff --git a/.github/assets/hud-editor.png b/.github/assets/hud-editor.png new file mode 100644 index 0000000..a8471fa Binary files /dev/null and b/.github/assets/hud-editor.png differ diff --git a/README.md b/README.md index 4fa98a4..7e097df 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,16 @@ A simple HUD with essential information easily accessible for Minecraft Fabric. -Requires [Cloth Config](https://modrinth.com/mod/cloth-config), [Mod Menu](https://modrinth.com/mod/modmenu), and [Fabric API](https://modrinth.com/mod/fabric-api). +Requires [Cloth Config](https://modrinth.com/mod/cloth-config) and +[Fabric API](https://modrinth.com/mod/fabric-api). ![Picture of HUD](https://cdn.modrinth.com/data/cached_images/4dd33c9ed510e100af5cc442eb93092624856ba5.png) -## Features +## HUD Editor -- **FPS**: Displays the current frames per second. -- **Ping**: Displays the player's ping to the server. -- **Momentum**: Displays the player's current momentum. -- **Coordinates**: Displays the player's current coordinates. -- **Biome**: Displays the current biome the player is in. -- **Facing**: Displays the direction the player is facing. -- **Time**: Displays the current real world time. +Use **Right Shift** in game (rebindable in Minecraft controls) or open BetterHUD settings with **Mod Menu** to edit BetterHUD. -## Configuration (requires Mod Menu) +## Configuration (requires Cloth Config) ![General Settings](https://cdn.modrinth.com/data/cached_images/9c3ed350ef0c2206a7439d1e1c15b708aa22a332.png) ![Mod Settings](https://cdn.modrinth.com/data/cached_images/f89e15c258a5a24cfda8fe9398406a7ce16edbcc.png) ## Installation diff --git a/build.gradle b/build.gradle index ee82dc8..c8d2fa7 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,6 @@ dependencies { processResources { def dependsExtra = ''', "fabric-api": "*", - "modmenu": "*", "cloth-config2": "*"''' def expansion = [ diff --git a/gradle.properties b/gradle.properties index 3c5c766..caf1575 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,6 @@ dev.kikugie.stonecutter.hard_mode=true loom_version=1.17-SNAPSHOT # Mod Properties (shared) -mod_version=2.1.1 +mod_version=3.0.0 maven_group=dsns.betterhud archives_base_name=betterhud diff --git a/launchtest/src/gametest-legacy/java/dsns/betterhud/gametest/BetterHudClientGameTest.java b/launchtest/src/gametest-legacy/java/dsns/betterhud/gametest/BetterHudClientGameTest.java index 9702c2e..7be1fe8 100644 --- a/launchtest/src/gametest-legacy/java/dsns/betterhud/gametest/BetterHudClientGameTest.java +++ b/launchtest/src/gametest-legacy/java/dsns/betterhud/gametest/BetterHudClientGameTest.java @@ -6,14 +6,19 @@ // Keep the two files in sync when changing the test. import java.io.IOException; +import java.io.InputStream; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Properties; import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest; +import net.fabricmc.fabric.api.client.gametest.v1.TestInput; import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; import net.fabricmc.fabric.api.client.gametest.v1.context.TestSingleplayerContext; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; +import org.lwjgl.glfw.GLFW; @SuppressWarnings("UnstableApiUsage") public class BetterHudClientGameTest implements FabricClientGameTest { @@ -28,7 +33,73 @@ public void runTest(ClientGameTestContext context) { context.waitTicks(40); assertScreenshotSaved(context.takeScreenshot("betterhud-survival-world")); + + exerciseHudEditor(context); + } + } + + private static void exerciseHudEditor(ClientGameTestContext context) { + TestInput input = context.getInput(); + + input.pressKey(GLFW.GLFW_KEY_RIGHT_SHIFT); + context.waitTicks(5); + assertScreenshotSaved(context.takeScreenshot("betterhud-hud-editor")); + + double guiScale = context.computeOnClient(client -> (double) client.getWindow().getGuiScale()); + + input.setCursorPos(12.0 * guiScale, 8.0 * guiScale); + input.holdMouse(GLFW.GLFW_MOUSE_BUTTON_LEFT); + for (int i = 0; i < 10; i++) { + input.moveCursor(8.0 * guiScale, 5.0 * guiScale); + context.waitTick(); + } + input.releaseMouse(GLFW.GLFW_MOUSE_BUTTON_LEFT); + assertScreenshotSaved(context.takeScreenshot("betterhud-hud-editor-dragged")); + + input.pressKey(GLFW.GLFW_KEY_ESCAPE); + context.waitTicks(2); + + Properties config = loadConfig(); + if (!"custom".equals(config.getProperty("FPS.Position"))) { + throw new AssertionError( + "Dragging in the HUD editor did not give the FPS element a custom position: " + config); + } + double customX = Double.parseDouble(config.getProperty("FPS.Custom X")); + double customY = Double.parseDouble(config.getProperty("FPS.Custom Y")); + if (customX <= 1 || customY <= 1) { + throw new AssertionError( + "Dragging in the HUD editor did not move the FPS element: x=" + customX + "% y=" + customY + "%"); + } + + input.pressKey(GLFW.GLFW_KEY_RIGHT_SHIFT); + context.waitTicks(5); + input.setCursorPos(92.0 * guiScale, 58.0 * guiScale); + input.holdMouse(GLFW.GLFW_MOUSE_BUTTON_LEFT); + for (int i = 0; i < 10; i++) { + input.moveCursor(-8.0 * guiScale, -5.0 * guiScale); + context.waitTick(); + } + input.releaseMouse(GLFW.GLFW_MOUSE_BUTTON_LEFT); + context.waitTicks(2); + input.pressKey(GLFW.GLFW_KEY_ESCAPE); + context.waitTicks(2); + + config = loadConfig(); + if (!"top-left".equals(config.getProperty("FPS.Position"))) { + throw new AssertionError( + "Dragging into the corner did not dock the FPS element back: " + config); + } + } + + private static Properties loadConfig() { + Path config = FabricLoader.getInstance().getConfigDir().resolve("betterhud.properties"); + Properties properties = new Properties(); + try (InputStream in = Files.newInputStream(config)) { + properties.load(in); + } catch (IOException e) { + throw new UncheckedIOException("Could not read " + config, e); } + return properties; } private static void assertScreenshotSaved(Path screenshot) { diff --git a/src/gametest/java/dsns/betterhud/gametest/BetterHudClientGameTest.java b/src/gametest/java/dsns/betterhud/gametest/BetterHudClientGameTest.java index 8057007..c7dc749 100644 --- a/src/gametest/java/dsns/betterhud/gametest/BetterHudClientGameTest.java +++ b/src/gametest/java/dsns/betterhud/gametest/BetterHudClientGameTest.java @@ -1,14 +1,19 @@ package dsns.betterhud.gametest; import java.io.IOException; +import java.io.InputStream; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Properties; import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest; +import net.fabricmc.fabric.api.client.gametest.v1.TestInput; import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext; import net.fabricmc.fabric.api.client.gametest.v1.context.TestSingleplayerContext; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; +import org.lwjgl.glfw.GLFW; @SuppressWarnings("UnstableApiUsage") public class BetterHudClientGameTest implements FabricClientGameTest { @@ -23,7 +28,73 @@ public void runTest(ClientGameTestContext context) { context.waitTicks(40); assertScreenshotSaved(context.takeScreenshot("betterhud-survival-world")); + + exerciseHudEditor(context); + } + } + + private static void exerciseHudEditor(ClientGameTestContext context) { + TestInput input = context.getInput(); + + input.pressKey(GLFW.GLFW_KEY_RIGHT_SHIFT); + context.waitTicks(5); + assertScreenshotSaved(context.takeScreenshot("betterhud-hud-editor")); + + double guiScale = context.computeOnClient(client -> (double) client.getWindow().getGuiScale()); + + input.setCursorPos(12.0 * guiScale, 8.0 * guiScale); + input.holdMouse(GLFW.GLFW_MOUSE_BUTTON_LEFT); + for (int i = 0; i < 10; i++) { + input.moveCursor(8.0 * guiScale, 5.0 * guiScale); + context.waitTick(); + } + input.releaseMouse(GLFW.GLFW_MOUSE_BUTTON_LEFT); + assertScreenshotSaved(context.takeScreenshot("betterhud-hud-editor-dragged")); + + input.pressKey(GLFW.GLFW_KEY_ESCAPE); + context.waitTicks(2); + + Properties config = loadConfig(); + if (!"custom".equals(config.getProperty("FPS.Position"))) { + throw new AssertionError( + "Dragging in the HUD editor did not give the FPS element a custom position: " + config); + } + double customX = Double.parseDouble(config.getProperty("FPS.Custom X")); + double customY = Double.parseDouble(config.getProperty("FPS.Custom Y")); + if (customX <= 1 || customY <= 1) { + throw new AssertionError( + "Dragging in the HUD editor did not move the FPS element: x=" + customX + "% y=" + customY + "%"); + } + + input.pressKey(GLFW.GLFW_KEY_RIGHT_SHIFT); + context.waitTicks(5); + input.setCursorPos(92.0 * guiScale, 58.0 * guiScale); + input.holdMouse(GLFW.GLFW_MOUSE_BUTTON_LEFT); + for (int i = 0; i < 10; i++) { + input.moveCursor(-8.0 * guiScale, -5.0 * guiScale); + context.waitTick(); + } + input.releaseMouse(GLFW.GLFW_MOUSE_BUTTON_LEFT); + context.waitTicks(2); + input.pressKey(GLFW.GLFW_KEY_ESCAPE); + context.waitTicks(2); + + config = loadConfig(); + if (!"top-left".equals(config.getProperty("FPS.Position"))) { + throw new AssertionError( + "Dragging into the corner did not dock the FPS element back: " + config); + } + } + + private static Properties loadConfig() { + Path config = FabricLoader.getInstance().getConfigDir().resolve("betterhud.properties"); + Properties properties = new Properties(); + try (InputStream in = Files.newInputStream(config)) { + properties.load(in); + } catch (IOException e) { + throw new UncheckedIOException("Could not read " + config, e); } + return properties; } private static void assertScreenshotSaved(Path screenshot) { diff --git a/src/main/java/dsns/betterhud/BetterHUD.java b/src/main/java/dsns/betterhud/BetterHUD.java index 9d2ffeb..a0a73bb 100644 --- a/src/main/java/dsns/betterhud/BetterHUD.java +++ b/src/main/java/dsns/betterhud/BetterHUD.java @@ -8,16 +8,23 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +//? if >=26 { +import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper; +//?} else { +/*import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;*/ +//?} //? if >=1.21.6 { import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElementRegistry; //?} else { /*import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; *///?} +import net.minecraft.client.KeyMapping; //? if >=26 { import net.minecraft.resources.Identifier; //?} else if >=1.21.6 { /*import net.minecraft.resources.ResourceLocation;*/ //?} +import org.lwjgl.glfw.GLFW; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,10 +44,34 @@ public class BetterHUD implements ClientModInitializer { ) ); + public static KeyMapping openEditorKey; + @Override public void onInitializeClient() { Config.configure(); + //? if >=26 { + openEditorKey = KeyMappingHelper.registerKeyMapping( + //?} else { + /*openEditorKey = KeyBindingHelper.registerKeyBinding(*/ + //?} + new KeyMapping( + "key.betterhud.open_editor", + GLFW.GLFW_KEY_RIGHT_SHIFT, + //? if >=26 { + KeyMapping.Category.register( + Identifier.fromNamespaceAndPath("betterhud", "main") + ) + //?} else if >=1.21.9 { + /*KeyMapping.Category.register( + ResourceLocation.fromNamespaceAndPath("betterhud", "main") + )*/ + //?} else { + /*"key.categories.betterhud"*/ + //?} + ) + ); + BetterHUDGUI betterHUDGUI = new BetterHUDGUI(); //? if >=26 { diff --git a/src/main/java/dsns/betterhud/BetterHUDGUI.java b/src/main/java/dsns/betterhud/BetterHUDGUI.java index 50b3913..74f0854 100644 --- a/src/main/java/dsns/betterhud/BetterHUDGUI.java +++ b/src/main/java/dsns/betterhud/BetterHUDGUI.java @@ -2,6 +2,8 @@ import dsns.betterhud.util.BaseMod; import dsns.betterhud.util.CustomText; +import dsns.betterhud.util.HudLayout; +import dsns.betterhud.util.HudRenderer; import dsns.betterhud.util.ModSettings; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.util.List; @@ -16,11 +18,6 @@ //?} else { /*import net.minecraft.client.gui.GuiGraphics;*/ //?} -//? if >=1.21.6 { -import org.joml.Matrix3x2fStack; -//?} else { -/*import com.mojang.blaze3d.vertex.PoseStack;*/ -//?} //? if >=1.21.6 { public class BetterHUDGUI implements ClientTickEvents.StartTick { @@ -28,28 +25,22 @@ public class BetterHUDGUI implements ClientTickEvents.StartTick { /*public class BetterHUDGUI implements HudRenderCallback, ClientTickEvents.StartTick {*/ //?} - public static int verticalPadding = 4; - public static int horizontalPadding = 4; - - public static int verticalMargin = 1; - public static int horizontalMargin = 1; - - public static int lineHeight = 1; - private final Minecraft client = Minecraft.getInstance(); - private final List topLeftText = new ObjectArrayList<>(); - private final List topRightText = new ObjectArrayList<>(); - private final List bottomLeftList = new ObjectArrayList<>(); - private final List bottomRightText = new ObjectArrayList<>(); - private final List customPositionText = new ObjectArrayList<>(); + private final List hudTexts = new ObjectArrayList<>(); @Override public void onStartTick(Minecraft client) { - this.topLeftText.clear(); - this.topRightText.clear(); - this.bottomLeftList.clear(); - this.bottomRightText.clear(); - this.customPositionText.clear(); + if (BetterHUD.openEditorKey != null) { + while (BetterHUD.openEditorKey.consumeClick()) { + //? if >=26.2 { + client.gui.setScreen(new HudEditorScreen()); + //?} else { + /*client.setScreen(new HudEditorScreen());*/ + //?} + } + } + + this.hudTexts.clear(); for (BaseMod mod : BetterHUD.mods) { ModSettings modSettings = mod.getModSettings(); @@ -58,28 +49,8 @@ public void onStartTick(Minecraft client) { CustomText modText = mod.onStartTick(client); if (modText == null) continue; - String orientation = modSettings - .getSetting("Orientation") - .getStringValue(); - - if (modSettings.getSetting("Custom Position").getBooleanValue()) { - modText.customPosition = true; - modText.customX = modSettings - .getSetting("Custom X") - .getIntValue(); - modText.customY = modSettings - .getSetting("Custom Y") - .getIntValue(); - this.customPositionText.add(modText); - } else if (orientation.equals("top-left")) { - this.topLeftText.add(modText); - } else if (orientation.equals("top-right")) { - this.topRightText.add(modText); - } else if (orientation.equals("bottom-left")) { - this.bottomLeftList.add(modText); - } else if (orientation.equals("bottom-right")) { - this.bottomRightText.add(modText); - } + modText.applyPlacement(modSettings); + this.hudTexts.add(modText); } } @@ -98,119 +69,14 @@ public void onHudRender( /*if (client.options.hideGui) return;*/ //?} - int x = horizontalMargin; - int y = verticalMargin; - - for (CustomText text : topLeftText) { - drawString(drawContext, text, x, y); - y += scaledElementHeight(text) + lineHeight; - } - - y = client.getWindow().getGuiScaledHeight() - verticalMargin; - - for (CustomText text : bottomLeftList) { - y -= scaledElementHeight(text); - drawString(drawContext, text, x, y); - y -= lineHeight; - } - - y = verticalMargin; - for (CustomText text : topRightText) { - int offset = - (client.font.width(text.text) - 1) + - (horizontalPadding * 2) + - horizontalMargin; - x = client.getWindow().getGuiScaledWidth() - offset; - drawString(drawContext, text, x, y); - y += scaledElementHeight(text) + lineHeight; - } - - y = client.getWindow().getGuiScaledHeight() - verticalMargin; - for (CustomText text : bottomRightText) { - int offset = scaledElementWidth(text) + horizontalMargin; - x = client.getWindow().getGuiScaledWidth() - offset; - y -= scaledElementHeight(text); - drawString(drawContext, text, x, y); - y -= lineHeight; - } - - for (CustomText text : customPositionText) { - float xPercent = text.customX / 100.0f; - float yPercent = text.customY / 100.0f; - - int maxX = - client.getWindow().getGuiScaledWidth() - scaledElementWidth(text); - int maxY = - client.getWindow().getGuiScaledHeight() - scaledElementHeight(text); - - int scaledX = (int) (xPercent * maxX); - int scaledY = (int) (yPercent * maxY); - - drawString(drawContext, text, scaledX, scaledY); - } - } - - private void drawString( - //? if >=26 { - GuiGraphicsExtractor drawContext, - //?} else { - /*GuiGraphics drawContext,*/ - //?} - CustomText text, - int x, - int y - ) { - //? if >=1.21.6 { - Matrix3x2fStack poses = drawContext.pose(); - poses.pushMatrix(); - poses.translate(x, y); - poses.scale(text.scale, text.scale); - //?} else { - /*PoseStack poses = drawContext.pose(); - poses.pushPose(); - poses.translate(x, y, 0); - poses.scale(text.scale, text.scale, 1);*/ - //?} - - int w = (client.font.width(text.text) - 1) + (horizontalPadding * 2); - int h = (client.font.lineHeight - 1) + (verticalPadding * 2); - - drawContext.fill(0, 0, w, h, text.backgroundColor); - - //? if >=26 { - drawContext.text( - client.font, - text.text, - horizontalPadding, - verticalPadding, - text.color, - true - ); - //?} else { - /*drawContext.drawString( - client.font, - text.text, - horizontalPadding, - verticalPadding, - text.color, - true - );*/ - //?} - - //? if >=1.21.6 { - poses.popMatrix(); + //? if >=26.2 { + if (client.gui.screen() instanceof HudEditorScreen) return; //?} else { - /*poses.popPose();*/ + /*if (client.screen instanceof HudEditorScreen) return;*/ //?} - } - private int scaledElementWidth(CustomText text) { - int w = (client.font.width(text.text) - 1) + (horizontalPadding * 2); - return (int) (w * text.scale); - } - - private int scaledElementHeight(CustomText text) { - int h = (client.font.lineHeight - 1) + (verticalPadding * 2); - return (int) (h * text.scale); + for (HudLayout.Placed placed : HudLayout.layout(client, hudTexts)) { + HudRenderer.draw(drawContext, client, placed.text, placed.x, placed.y); + } } } diff --git a/src/main/java/dsns/betterhud/Config.java b/src/main/java/dsns/betterhud/Config.java index deff5a1..db4722a 100644 --- a/src/main/java/dsns/betterhud/Config.java +++ b/src/main/java/dsns/betterhud/Config.java @@ -119,6 +119,30 @@ public static void deserialize() { } } + for (Map.Entry modEntry : settings.entrySet()) { + String modID = modEntry.getKey(); + if ( + map.containsKey(modID + ".Position") || + map.containsKey(modID + ".position") + ) continue; + + String legacyCustom = map.getOrDefault( + modID + ".Custom Position", + map.get(modID + ".customPosition") + ); + String legacyOrientation = map.getOrDefault( + modID + ".Orientation", + map.get(modID + ".orientation") + ); + + Setting position = modEntry.getValue().getSetting("Position"); + if ("true".equals(legacyCustom)) { + position.setValue("custom"); + } else if (legacyOrientation != null) { + position.setValue(legacyOrientation); + } + } + String globalBg = map.get("backgroundColor"); if (globalBg != null) { for (Map.Entry< diff --git a/src/main/java/dsns/betterhud/HudEditorScreen.java b/src/main/java/dsns/betterhud/HudEditorScreen.java new file mode 100644 index 0000000..b2195c4 --- /dev/null +++ b/src/main/java/dsns/betterhud/HudEditorScreen.java @@ -0,0 +1,664 @@ +package dsns.betterhud; + +import dsns.betterhud.util.BaseMod; +import dsns.betterhud.util.CustomText; +import dsns.betterhud.util.HudLayout; +import dsns.betterhud.util.HudRenderer; +import dsns.betterhud.util.ModSettings; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import net.minecraft.client.Minecraft; +//? if >=26 { +import net.minecraft.client.gui.GuiGraphicsExtractor; +//?} else { +/*import net.minecraft.client.gui.GuiGraphics;*/ +//?} +import net.minecraft.client.gui.screens.Screen; +//? if >=1.21.9 { +import net.minecraft.client.input.MouseButtonEvent; +//?} +import net.minecraft.client.resources.language.I18n; +import net.minecraft.network.chat.Component; +//? if >=1.21.6 { +import org.joml.Matrix3x2fStack; +//?} else { +/*import com.mojang.blaze3d.vertex.PoseStack;*/ +//?} + +public class HudEditorScreen extends Screen { + + private static final int SNAP_DISTANCE = 5; + + private static final int DOCK_SNAP_DISTANCE = 10; + + private static final String[] CORNERS = { + "top-left", + "top-right", + "bottom-left", + "bottom-right", + }; + + private static final int BACKDROP_COLOR = 0x50000000; + private static final int NO_LEVEL_BACKDROP_COLOR = 0xff181818; + + private static final int BUTTON_BORDER_COLOR = 0xff000000; + private static final int BUTTON_BORDER_HOVERED_COLOR = 0xffffffff; + private static final int BUTTON_BODY_COLOR = 0xff6c6c6c; + private static final int BUTTON_BODY_HOVERED_COLOR = 0xff8d8d8d; + private static final int BUTTON_BEVEL_LIGHT_COLOR = 0xffb4b4b4; + private static final int BUTTON_BEVEL_DARK_COLOR = 0xff414141; + + private static final float TITLE_SCALE = 2.0f; + private static final float BUTTON_TEXT_SCALE = 1.25f; + private static final int OUTLINE_COLOR = 0x66ffffff; + private static final int OUTLINE_HOVERED_COLOR = 0xccffffff; + private static final int OUTLINE_DRAGGED_COLOR = 0xff55ffff; + private static final int GUIDE_COLOR = 0xaa55ffff; + private static final int TEXT_COLOR = 0xffffffff; + private static final int TEXT_MUTED_COLOR = 0xffaaaaaa; + + private final Screen parent; + private final List texts = new ObjectArrayList<>(); + private final Map owners = new IdentityHashMap<>(); + private List placedElements = new ObjectArrayList<>(); + + private BaseMod draggedMod = null; + private double grabOffsetX = 0; + private double grabOffsetY = 0; + private boolean snappedCenterX = false; + private boolean snappedCenterY = false; + + private int settingsButtonX = 0; + private int settingsButtonY = 0; + private int settingsButtonWidth = 0; + private int settingsButtonHeight = 0; + + public HudEditorScreen() { + this(null); + } + + public HudEditorScreen(Screen parent) { + super(Component.translatable("betterhud.editor.title")); + this.parent = parent; + } + + @Override + protected void init() { + refreshElements(); + } + + @Override + public void tick() { + refreshElements(); + } + + private void refreshElements() { + Minecraft client = Minecraft.getInstance(); + + texts.clear(); + owners.clear(); + + for (BaseMod mod : BetterHUD.mods) { + ModSettings settings = mod.getModSettings(); + if (!settings.getSetting("Enabled").getBooleanValue()) continue; + + CustomText text = mod.onStartTick(client); + if (text == null) text = new CustomText(mod.getModID(), settings); + + texts.add(text); + owners.put(text, mod); + } + } + + private CustomText textFor(BaseMod mod) { + for (Map.Entry entry : owners.entrySet()) { + if (entry.getValue() == mod) return entry.getKey(); + } + return null; + } + + private HudLayout.Placed placedFor(BaseMod mod) { + for (HudLayout.Placed placed : placedElements) { + if (owners.get(placed.text) == mod) return placed; + } + return null; + } + + private HudLayout.Placed elementAt(double mouseX, double mouseY) { + for (int i = placedElements.size() - 1; i >= 0; i--) { + HudLayout.Placed placed = placedElements.get(i); + if ( + mouseX >= placed.x && + mouseX < placed.x + placed.width && + mouseY >= placed.y && + mouseY < placed.y + placed.height + ) return placed; + } + return null; + } + + private boolean overSettingsButton(double mouseX, double mouseY) { + return ( + settingsButtonWidth > 0 && + mouseX >= settingsButtonX && + mouseX < settingsButtonX + settingsButtonWidth && + mouseY >= settingsButtonY && + mouseY < settingsButtonY + settingsButtonHeight + ); + } + + private boolean handleMouseDown(double mouseX, double mouseY, int button) { + if (button == 0 && overSettingsButton(mouseX, mouseY)) { + Screen settings = SettingsScreenBuilder.createSettingsScreen(this); + if (settings != null) { + Minecraft client = Minecraft.getInstance(); + //? if >=26.2 { + client.gui.setScreen(settings); + //?} else { + /*client.setScreen(settings);*/ + //?} + } + return true; + } + + if (button != 0) return false; + + HudLayout.Placed placed = elementAt(mouseX, mouseY); + if (placed == null) return false; + + draggedMod = owners.get(placed.text); + grabOffsetX = mouseX - placed.x; + grabOffsetY = mouseY - placed.y; + return true; + } + + private boolean handleMouseDrag(double mouseX, double mouseY) { + if (draggedMod == null) return false; + + Minecraft client = Minecraft.getInstance(); + CustomText text = textFor(draggedMod); + if (text == null) return false; + + int elementWidth = HudLayout.elementWidth(client, text); + int elementHeight = HudLayout.elementHeight(client, text); + int maxX = this.width - elementWidth; + int maxY = this.height - elementHeight; + + double x = mouseX - grabOffsetX; + double y = mouseY - grabOffsetY; + + snappedCenterX = false; + snappedCenterY = false; + + ModSettings settings = draggedMod.getModSettings(); + + String dockCorner = dockCornerAt(x, y, maxX, maxY); + if (dockCorner != null) { + settings.getSetting("Position").setValue(dockCorner); + return true; + } + + double centerX = maxX / 2.0; + double centerY = maxY / 2.0; + if (Math.abs(x - centerX) <= SNAP_DISTANCE) { + x = centerX; + snappedCenterX = true; + } else if (Math.abs(x) <= SNAP_DISTANCE) { + x = 0; + } else if (Math.abs(x - maxX) <= SNAP_DISTANCE) { + x = maxX; + } + if (Math.abs(y - centerY) <= SNAP_DISTANCE) { + y = centerY; + snappedCenterY = true; + } else if (Math.abs(y) <= SNAP_DISTANCE) { + y = 0; + } else if (Math.abs(y - maxY) <= SNAP_DISTANCE) { + y = maxY; + } + + x = Math.clamp(x, 0, Math.max(maxX, 0)); + y = Math.clamp(y, 0, Math.max(maxY, 0)); + + if (overlapsOtherElement(x, y, elementWidth, elementHeight)) { + snappedCenterX = false; + snappedCenterY = false; + return true; + } + + double percentX = maxX <= 0 ? 0 : x * 100.0 / maxX; + double percentY = maxY <= 0 ? 0 : y * 100.0 / maxY; + + settings.getSetting("Position").setValue("custom"); + settings + .getSetting("Custom X") + .setValue(String.valueOf(Math.round(percentX * 100.0) / 100.0)); + settings + .getSetting("Custom Y") + .setValue(String.valueOf(Math.round(percentY * 100.0) / 100.0)); + return true; + } + + private String dockCornerAt(double x, double y, int maxX, int maxY) { + for (String corner : CORNERS) { + boolean left = corner.endsWith("-left"); + boolean top = corner.startsWith("top"); + double anchorX = left + ? HudLayout.HORIZONTAL_MARGIN + : maxX - HudLayout.HORIZONTAL_MARGIN; + double anchorY = top + ? HudLayout.VERTICAL_MARGIN + : maxY - HudLayout.VERTICAL_MARGIN; + if ( + Math.abs(x - anchorX) <= DOCK_SNAP_DISTANCE && + Math.abs(y - anchorY) <= DOCK_SNAP_DISTANCE + ) return corner; + } + return null; + } + + private boolean overlapsOtherElement( + double x, + double y, + int width, + int height + ) { + for (HudLayout.Placed placed : placedElements) { + if (owners.get(placed.text) == draggedMod) continue; + if ( + x < placed.x + placed.width && + x + width > placed.x && + y < placed.y + placed.height && + y + height > placed.y + ) return true; + } + return false; + } + + private boolean handleMouseUp() { + if (draggedMod == null) return false; + draggedMod = null; + snappedCenterX = false; + snappedCenterY = false; + return true; + } + + //? if >=1.21.9 { + @Override + public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { + return handleMouseDown(event.x(), event.y(), event.button()); + } + + @Override + public boolean mouseReleased(MouseButtonEvent event) { + return handleMouseUp(); + } + + @Override + public boolean mouseDragged( + MouseButtonEvent event, + double dragX, + double dragY + ) { + return handleMouseDrag(event.x(), event.y()); + } + //?} else { + /*@Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + return handleMouseDown(mouseX, mouseY, button); + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + return handleMouseUp(); + } + + @Override + public boolean mouseDragged( + double mouseX, + double mouseY, + int button, + double dragX, + double dragY + ) { + return handleMouseDrag(mouseX, mouseY); + } + *///?} + + @Override + public boolean mouseScrolled( + double mouseX, + double mouseY, + double scrollX, + double scrollY + ) { + HudLayout.Placed placed = draggedMod != null + ? placedFor(draggedMod) + : elementAt(mouseX, mouseY); + if (placed == null || scrollY == 0) return false; + + ModSettings settings = owners.get(placed.text).getModSettings(); + float oldScale = settings.getSetting("Scale").getFloatValue(); + float scale = oldScale + (scrollY > 0 ? 0.1f : -0.1f); + scale = Math.round(scale * 10.0f) / 10.0f; + scale = Math.clamp(scale, 0.1f, 10.0f); + + if (scale > oldScale && resizeWouldOverlap(placed.text, scale)) { + return true; + } + + settings.getSetting("Scale").setValue(String.valueOf(scale)); + return true; + } + + private boolean resizeWouldOverlap(CustomText subject, float newScale) { + Minecraft client = Minecraft.getInstance(); + float oldScale = subject.scale; + subject.scale = newScale; + List trial = HudLayout.layout(client, texts); + subject.scale = oldScale; + + HudLayout.Placed resized = null; + for (HudLayout.Placed placed : trial) { + if (placed.text == subject) resized = placed; + } + if (resized == null) return false; + + for (HudLayout.Placed placed : trial) { + if (placed.text == subject) continue; + if ( + resized.x < placed.x + placed.width && + resized.x + resized.width > placed.x && + resized.y < placed.y + placed.height && + resized.y + resized.height > placed.y + ) return true; + } + return false; + } + + //? if >=26 { + @Override + public void extractRenderState( + GuiGraphicsExtractor drawContext, + int mouseX, + int mouseY, + float delta + ) { + renderEditor(drawContext, mouseX, mouseY); + } + //?} else { + /*@Override + public void render( + GuiGraphics drawContext, + int mouseX, + int mouseY, + float delta + ) { + renderEditor(drawContext, mouseX, mouseY); + } + *///?} + + private void renderEditor( + //? if >=26 { + GuiGraphicsExtractor drawContext, + //?} else { + /*GuiGraphics drawContext,*/ + //?} + int mouseX, + int mouseY + ) { + Minecraft client = Minecraft.getInstance(); + + drawContext.fill( + 0, + 0, + this.width, + this.height, + client.level == null ? NO_LEVEL_BACKDROP_COLOR : BACKDROP_COLOR + ); + + for (CustomText text : texts) { + text.applyPlacement(owners.get(text).getModSettings()); + } + placedElements = HudLayout.layout(client, texts); + + if (snappedCenterX && draggedMod != null) { + drawContext.fill( + this.width / 2, + 0, + this.width / 2 + 1, + this.height, + GUIDE_COLOR + ); + } + if (snappedCenterY && draggedMod != null) { + drawContext.fill( + 0, + this.height / 2, + this.width, + this.height / 2 + 1, + GUIDE_COLOR + ); + } + + HudLayout.Placed hovered = elementAt(mouseX, mouseY); + + for (HudLayout.Placed placed : placedElements) { + HudRenderer.draw(drawContext, client, placed.text, placed.x, placed.y); + + int outlineColor = OUTLINE_COLOR; + if (owners.get(placed.text) == draggedMod) { + outlineColor = OUTLINE_DRAGGED_COLOR; + } else if (placed == hovered && draggedMod == null) { + outlineColor = OUTLINE_HOVERED_COLOR; + } + drawOutline( + drawContext, + placed.x - 1, + placed.y - 1, + placed.width + 2, + placed.height + 2, + outlineColor + ); + } + + String heading = I18n.get("betterhud.editor.title"); + drawScaledCenteredText( + drawContext, + client, + heading, + this.height / 2 - 26, + TEXT_COLOR, + TITLE_SCALE + ); + + drawSettingsButton(drawContext, client, mouseX, mouseY); + + HudLayout.Placed described = draggedMod != null + ? placedFor(draggedMod) + : hovered; + if (described != null) { + BaseMod mod = owners.get(described.text); + String info = + mod.getModID() + + " · " + + I18n.get( + "betterhud.editor.scale", + String.format("%.1f", described.text.scale) + ); + drawCenteredText(drawContext, client, info, this.height - 20, TEXT_COLOR); + } else { + drawCenteredText( + drawContext, + client, + I18n.get("betterhud.editor.hint"), + this.height - 32, + TEXT_MUTED_COLOR + ); + drawCenteredText( + drawContext, + client, + I18n.get("betterhud.editor.hint2"), + this.height - 20, + TEXT_MUTED_COLOR + ); + } + } + + private void drawSettingsButton( + //? if >=26 { + GuiGraphicsExtractor drawContext, + //?} else { + /*GuiGraphics drawContext,*/ + //?} + Minecraft client, + int mouseX, + int mouseY + ) { + if (!SettingsScreenBuilder.settingsScreenAvailable()) { + settingsButtonWidth = 0; + return; + } + + String label = I18n.get("betterhud.editor.settings"); + settingsButtonWidth = + (int) ((client.font.width(label) + 20) * BUTTON_TEXT_SCALE); + settingsButtonHeight = (int) (20 * BUTTON_TEXT_SCALE); + settingsButtonX = (this.width - settingsButtonWidth) / 2; + settingsButtonY = this.height / 2 + 2; + + boolean hovered = overSettingsButton(mouseX, mouseY); + int x = settingsButtonX; + int y = settingsButtonY; + int w = settingsButtonWidth; + int h = settingsButtonHeight; + + drawContext.fill( + x, + y, + x + w, + y + h, + hovered ? BUTTON_BORDER_HOVERED_COLOR : BUTTON_BORDER_COLOR + ); + drawContext.fill( + x + 1, + y + 1, + x + w - 1, + y + h - 1, + hovered ? BUTTON_BODY_HOVERED_COLOR : BUTTON_BODY_COLOR + ); + drawContext.fill(x + 1, y + 1, x + w - 1, y + 2, BUTTON_BEVEL_LIGHT_COLOR); + drawContext.fill(x + 1, y + 1, x + 2, y + h - 1, BUTTON_BEVEL_LIGHT_COLOR); + drawContext.fill(x + 1, y + h - 2, x + w - 1, y + h - 1, BUTTON_BEVEL_DARK_COLOR); + drawContext.fill(x + w - 2, y + 2, x + w - 1, y + h - 1, BUTTON_BEVEL_DARK_COLOR); + + drawScaledCenteredText( + drawContext, + client, + label, + settingsButtonY + + (int) ((settingsButtonHeight - 8 * BUTTON_TEXT_SCALE) / 2), + TEXT_COLOR, + BUTTON_TEXT_SCALE + ); + } + + private void drawScaledCenteredText( + //? if >=26 { + GuiGraphicsExtractor drawContext, + //?} else { + /*GuiGraphics drawContext,*/ + //?} + Minecraft client, + String text, + int y, + int color, + float scale + ) { + int x = (int) ((this.width - client.font.width(text) * scale) / 2); + + //? if >=1.21.6 { + Matrix3x2fStack poses = drawContext.pose(); + poses.pushMatrix(); + poses.translate(x, y); + poses.scale(scale, scale); + //?} else { + /*PoseStack poses = drawContext.pose(); + poses.pushPose(); + poses.translate(x, y, 0); + poses.scale(scale, scale, 1);*/ + //?} + + //? if >=26 { + drawContext.text(client.font, text, 0, 0, color, true); + //?} else { + /*drawContext.drawString(client.font, text, 0, 0, color, true);*/ + //?} + + //? if >=1.21.6 { + poses.popMatrix(); + //?} else { + /*poses.popPose();*/ + //?} + } + + private void drawCenteredText( + //? if >=26 { + GuiGraphicsExtractor drawContext, + //?} else { + /*GuiGraphics drawContext,*/ + //?} + Minecraft client, + String text, + int y, + int color + ) { + int x = (this.width - client.font.width(text)) / 2; + //? if >=26 { + drawContext.text(client.font, text, x, y, color, true); + //?} else { + /*drawContext.drawString(client.font, text, x, y, color, true);*/ + //?} + } + + private void drawOutline( + //? if >=26 { + GuiGraphicsExtractor drawContext, + //?} else { + /*GuiGraphics drawContext,*/ + //?} + int x, + int y, + int width, + int height, + int color + ) { + drawContext.fill(x, y, x + width, y + 1, color); + drawContext.fill(x, y + height - 1, x + width, y + height, color); + drawContext.fill(x, y + 1, x + 1, y + height - 1, color); + drawContext.fill(x + width - 1, y + 1, x + width, y + height - 1, color); + } + + @Override + public boolean isPauseScreen() { + return false; + } + + @Override + public void onClose() { + Minecraft client = Minecraft.getInstance(); + //? if >=26.2 { + client.gui.setScreen(parent); + //?} else { + /*client.setScreen(parent);*/ + //?} + } + + @Override + public void removed() { + Config.serialize(); + super.removed(); + } +} diff --git a/src/main/java/dsns/betterhud/ModMenu.java b/src/main/java/dsns/betterhud/ModMenu.java index 2d9a843..efcab82 100644 --- a/src/main/java/dsns/betterhud/ModMenu.java +++ b/src/main/java/dsns/betterhud/ModMenu.java @@ -2,127 +2,11 @@ import com.terraformersmc.modmenu.api.ConfigScreenFactory; import com.terraformersmc.modmenu.api.ModMenuApi; -import dsns.betterhud.util.BaseMod; -import dsns.betterhud.util.Setting; -import java.util.Arrays; -import java.util.Map; -import me.shedaniel.clothconfig2.api.ConfigBuilder; -import me.shedaniel.clothconfig2.api.ConfigCategory; -import me.shedaniel.clothconfig2.api.ConfigEntryBuilder; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.network.chat.Component; public class ModMenu implements ModMenuApi { @Override public ConfigScreenFactory getModConfigScreenFactory() { - if (!FabricLoader.getInstance().isModLoaded("cloth-config2")) { - return parent -> null; - } - return parent -> { - ConfigBuilder builder = ConfigBuilder.create() - .setParentScreen(parent) - .setTitle(Component.literal("BetterHUD Settings")); - - builder.setSavingRunnable(Config::serialize); - - ConfigEntryBuilder entryBuilder = builder.entryBuilder(); - - for (BaseMod mod : BetterHUD.mods) { - ConfigCategory category = builder.getOrCreateCategory( - Component.literal(mod.getModID()) - ); - - for (Map.Entry entry : mod - .getModSettings() - .getSettings() - .entrySet()) { - String key = entry.getKey(); - Setting setting = entry.getValue(); - - if (setting.getType().equals("boolean")) { - category.addEntry( - entryBuilder - .startBooleanToggle( - Component.literal(key), - setting.getBooleanValue() - ) - .setDefaultValue( - Boolean.valueOf(setting.getDefaultValue()) - ) - .setSaveConsumer(value -> - setting.setValue(String.valueOf(value)) - ) - .build() - ); - } else if (setting.getType().equals("string")) { - category.addEntry( - entryBuilder - .startStringDropdownMenu( - Component.literal(key), - setting.getStringValue() - ) - .setSelections( - Arrays.asList(setting.getPossibleValues()) - ) - .setDefaultValue(setting.getDefaultValue()) - .setSaveConsumer(value -> - setting.setValue(value) - ) - .build() - ); - } else if (setting.getType().equals("integer")) { - category.addEntry( - entryBuilder - .startIntField( - Component.literal(key), - setting.getIntValue() - ) - .setDefaultValue( - Integer.parseInt(setting.getDefaultValue()) - ) - .setSaveConsumer(value -> - setting.setValue(String.valueOf(value)) - ) - .build() - ); - } else if (setting.getType().equals("double")) { - category.addEntry( - entryBuilder - .startDoubleField( - Component.literal(key), - setting.getDoubleValue() - ) - .setDefaultValue( - Double.parseDouble( - setting.getDefaultValue() - ) - ) - .setSaveConsumer(value -> - setting.setValue(String.valueOf(value)) - ) - .build() - ); - } else if (setting.getType().equals("color")) { - category.addEntry( - entryBuilder - .startAlphaColorField( - Component.literal(key), - setting.getColorValue() - ) - .setDefaultValue( - Integer.parseInt(setting.getDefaultValue()) - ) - .setSaveConsumer(value -> - setting.setValue(String.valueOf(value)) - ) - .build() - ); - } - } - } - - return builder.build(); - }; + return HudEditorScreen::new; } } diff --git a/src/main/java/dsns/betterhud/SettingsScreenBuilder.java b/src/main/java/dsns/betterhud/SettingsScreenBuilder.java new file mode 100644 index 0000000..bc1185f --- /dev/null +++ b/src/main/java/dsns/betterhud/SettingsScreenBuilder.java @@ -0,0 +1,135 @@ +package dsns.betterhud; + +import dsns.betterhud.util.BaseMod; +import dsns.betterhud.util.Setting; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import me.shedaniel.clothconfig2.api.ConfigBuilder; +import me.shedaniel.clothconfig2.api.ConfigCategory; +import me.shedaniel.clothconfig2.api.ConfigEntryBuilder; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +public final class SettingsScreenBuilder { + + private static final Set EDITOR_MANAGED_SETTINGS = Set.of( + "Custom X", + "Custom Y" + ); + + private SettingsScreenBuilder() {} + + public static boolean settingsScreenAvailable() { + return FabricLoader.getInstance().isModLoaded("cloth-config2"); + } + + public static Screen createSettingsScreen(Screen parent) { + if (!settingsScreenAvailable()) { + return null; + } + + ConfigBuilder builder = ConfigBuilder.create() + .setParentScreen(parent) + .setTitle(Component.literal("BetterHUD Settings")); + + builder.setSavingRunnable(Config::serialize); + + ConfigEntryBuilder entryBuilder = builder.entryBuilder(); + + for (BaseMod mod : BetterHUD.mods) { + ConfigCategory category = builder.getOrCreateCategory( + Component.literal(mod.getModID()) + ); + + for (Map.Entry entry : mod + .getModSettings() + .getSettings() + .entrySet()) { + String key = entry.getKey(); + Setting setting = entry.getValue(); + + if (EDITOR_MANAGED_SETTINGS.contains(key)) continue; + + if (setting.getType().equals("boolean")) { + category.addEntry( + entryBuilder + .startBooleanToggle( + Component.literal(key), + setting.getBooleanValue() + ) + .setDefaultValue( + Boolean.valueOf(setting.getDefaultValue()) + ) + .setSaveConsumer(value -> + setting.setValue(String.valueOf(value)) + ) + .build() + ); + } else if (setting.getType().equals("string")) { + category.addEntry( + entryBuilder + .startStringDropdownMenu( + Component.literal(key), + setting.getStringValue() + ) + .setSelections( + Arrays.asList(setting.getPossibleValues()) + ) + .setDefaultValue(setting.getDefaultValue()) + .setSaveConsumer(value -> setting.setValue(value)) + .build() + ); + } else if (setting.getType().equals("integer")) { + category.addEntry( + entryBuilder + .startIntField( + Component.literal(key), + setting.getIntValue() + ) + .setDefaultValue( + Integer.parseInt(setting.getDefaultValue()) + ) + .setSaveConsumer(value -> + setting.setValue(String.valueOf(value)) + ) + .build() + ); + } else if (setting.getType().equals("double")) { + category.addEntry( + entryBuilder + .startDoubleField( + Component.literal(key), + setting.getDoubleValue() + ) + .setDefaultValue( + Double.parseDouble(setting.getDefaultValue()) + ) + .setSaveConsumer(value -> + setting.setValue(String.valueOf(value)) + ) + .build() + ); + } else if (setting.getType().equals("color")) { + category.addEntry( + entryBuilder + .startAlphaColorField( + Component.literal(key), + setting.getColorValue() + ) + .setDefaultValue( + Integer.parseInt(setting.getDefaultValue()) + ) + .setSaveConsumer(value -> + setting.setValue(String.valueOf(value)) + ) + .build() + ); + } + } + } + + return builder.build(); + } +} diff --git a/src/main/java/dsns/betterhud/util/CustomText.java b/src/main/java/dsns/betterhud/util/CustomText.java index b35bf40..c4a44d3 100644 --- a/src/main/java/dsns/betterhud/util/CustomText.java +++ b/src/main/java/dsns/betterhud/util/CustomText.java @@ -5,10 +5,10 @@ public class CustomText { public String text; public int color; // colors are in ARGB format public int backgroundColor; // colors are in ARGB format - public boolean customPosition = false; + public String position = "top-left"; // a corner, or "custom" public float scale = 1.0f; - public int customX = 0; - public int customY = 0; + public float customX = 0; + public float customY = 0; public CustomText(String text, int color, float scale, int backgroundColor) { this.text = text; @@ -34,4 +34,11 @@ public CustomText(String text, int color, ModSettings settings) { settings.getSetting("Background Color").getColorValue() ); } + + public void applyPlacement(ModSettings settings) { + this.position = settings.getSetting("Position").getStringValue(); + this.customX = settings.getSetting("Custom X").getFloatValue(); + this.customY = settings.getSetting("Custom Y").getFloatValue(); + this.scale = settings.getSetting("Scale").getFloatValue(); + } } diff --git a/src/main/java/dsns/betterhud/util/HudLayout.java b/src/main/java/dsns/betterhud/util/HudLayout.java new file mode 100644 index 0000000..82845fb --- /dev/null +++ b/src/main/java/dsns/betterhud/util/HudLayout.java @@ -0,0 +1,100 @@ +package dsns.betterhud.util; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.List; +import net.minecraft.client.Minecraft; + +public final class HudLayout { + + public static final int VERTICAL_PADDING = 4; + public static final int HORIZONTAL_PADDING = 4; + + public static final int VERTICAL_MARGIN = 1; + public static final int HORIZONTAL_MARGIN = 1; + + public static final int LINE_HEIGHT = 1; + + public static final class Placed { + + public final CustomText text; + public final int x; + public final int y; + public final int width; + public final int height; + + public Placed(CustomText text, int x, int y, int width, int height) { + this.text = text; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + } + + private HudLayout() {} + + public static int elementWidth(Minecraft client, CustomText text) { + int w = (client.font.width(text.text) - 1) + (HORIZONTAL_PADDING * 2); + return (int) (w * text.scale); + } + + public static int elementHeight(Minecraft client, CustomText text) { + int h = (client.font.lineHeight - 1) + (VERTICAL_PADDING * 2); + return (int) (h * text.scale); + } + + public static List layout(Minecraft client, List texts) { + List placed = new ObjectArrayList<>(); + + int screenWidth = client.getWindow().getGuiScaledWidth(); + int screenHeight = client.getWindow().getGuiScaledHeight(); + + int topLeftY = VERTICAL_MARGIN; + int topRightY = VERTICAL_MARGIN; + int bottomLeftY = screenHeight - VERTICAL_MARGIN; + int bottomRightY = screenHeight - VERTICAL_MARGIN; + + for (CustomText text : texts) { + int width = elementWidth(client, text); + int height = elementHeight(client, text); + + if (text.position.equals("custom")) { + int maxX = screenWidth - width; + int maxY = screenHeight - height; + + int x = (int) (text.customX / 100.0f * maxX); + int y = (int) (text.customY / 100.0f * maxY); + + placed.add(new Placed(text, x, y, width, height)); + } else if (text.position.equals("top-left")) { + placed.add( + new Placed(text, HORIZONTAL_MARGIN, topLeftY, width, height) + ); + topLeftY += height + LINE_HEIGHT; + } else if (text.position.equals("top-right")) { + int x = screenWidth - width - HORIZONTAL_MARGIN; + placed.add(new Placed(text, x, topRightY, width, height)); + topRightY += height + LINE_HEIGHT; + } else if (text.position.equals("bottom-left")) { + bottomLeftY -= height; + placed.add( + new Placed( + text, + HORIZONTAL_MARGIN, + bottomLeftY, + width, + height + ) + ); + bottomLeftY -= LINE_HEIGHT; + } else if (text.position.equals("bottom-right")) { + int x = screenWidth - width - HORIZONTAL_MARGIN; + bottomRightY -= height; + placed.add(new Placed(text, x, bottomRightY, width, height)); + bottomRightY -= LINE_HEIGHT; + } + } + + return placed; + } +} diff --git a/src/main/java/dsns/betterhud/util/HudRenderer.java b/src/main/java/dsns/betterhud/util/HudRenderer.java new file mode 100644 index 0000000..9e2ce0b --- /dev/null +++ b/src/main/java/dsns/betterhud/util/HudRenderer.java @@ -0,0 +1,76 @@ +package dsns.betterhud.util; + +import net.minecraft.client.Minecraft; +//? if >=26 { +import net.minecraft.client.gui.GuiGraphicsExtractor; +//?} else { +/*import net.minecraft.client.gui.GuiGraphics;*/ +//?} +//? if >=1.21.6 { +import org.joml.Matrix3x2fStack; +//?} else { +/*import com.mojang.blaze3d.vertex.PoseStack;*/ +//?} + +public final class HudRenderer { + + private HudRenderer() {} + + public static void draw( + //? if >=26 { + GuiGraphicsExtractor drawContext, + //?} else { + /*GuiGraphics drawContext,*/ + //?} + Minecraft client, + CustomText text, + int x, + int y + ) { + //? if >=1.21.6 { + Matrix3x2fStack poses = drawContext.pose(); + poses.pushMatrix(); + poses.translate(x, y); + poses.scale(text.scale, text.scale); + //?} else { + /*PoseStack poses = drawContext.pose(); + poses.pushPose(); + poses.translate(x, y, 0); + poses.scale(text.scale, text.scale, 1);*/ + //?} + + int w = + (client.font.width(text.text) - 1) + + (HudLayout.HORIZONTAL_PADDING * 2); + int h = + (client.font.lineHeight - 1) + (HudLayout.VERTICAL_PADDING * 2); + + drawContext.fill(0, 0, w, h, text.backgroundColor); + + //? if >=26 { + drawContext.text( + client.font, + text.text, + HudLayout.HORIZONTAL_PADDING, + HudLayout.VERTICAL_PADDING, + text.color, + true + ); + //?} else { + /*drawContext.drawString( + client.font, + text.text, + HudLayout.HORIZONTAL_PADDING, + HudLayout.VERTICAL_PADDING, + text.color, + true + );*/ + //?} + + //? if >=1.21.6 { + poses.popMatrix(); + //?} else { + /*poses.popPose();*/ + //?} + } +} diff --git a/src/main/java/dsns/betterhud/util/ModSettings.java b/src/main/java/dsns/betterhud/util/ModSettings.java index 97c2ce3..b021287 100644 --- a/src/main/java/dsns/betterhud/util/ModSettings.java +++ b/src/main/java/dsns/betterhud/util/ModSettings.java @@ -4,27 +4,26 @@ public class ModSettings { + public static final String[] POSITIONS = { + "top-left", + "top-right", + "bottom-left", + "bottom-right", + "custom", + }; + private LinkedHashMap settings = new LinkedHashMap<>(); public ModSettings() { settings.put("Enabled", Setting.createBooleanSetting(true)); settings.put( - "Orientation", - Setting.createStringSetting( - "top-left", - new String[] { - "top-left", - "top-right", - "bottom-left", - "bottom-right", - } - ) + "Position", + Setting.createStringSetting("top-left", POSITIONS) ); - settings.put("Custom Position", Setting.createBooleanSetting(false)); - settings.put("Custom X", Setting.createIntegerSetting(0, 0, 100)); - settings.put("Custom Y", Setting.createIntegerSetting(0, 0, 100)); + settings.put("Custom X", Setting.createDoubleSetting(0, 0, 100)); + settings.put("Custom Y", Setting.createDoubleSetting(0, 0, 100)); settings.put("Text Color", Setting.createColorSetting(0xffffffff)); settings.put("Scale", Setting.createDoubleSetting(1.0, 0.1, 10.0)); settings.put( @@ -33,19 +32,11 @@ public ModSettings() { ); } - public ModSettings(String orientation) { + public ModSettings(String position) { this(); settings.replace( - "Orientation", - Setting.createStringSetting( - orientation, - new String[] { - "top-left", - "top-right", - "bottom-left", - "bottom-right", - } - ) + "Position", + Setting.createStringSetting(position, POSITIONS) ); } diff --git a/src/main/resources/assets/betterhud/lang/en_us.json b/src/main/resources/assets/betterhud/lang/en_us.json new file mode 100644 index 0000000..098f67c --- /dev/null +++ b/src/main/resources/assets/betterhud/lang/en_us.json @@ -0,0 +1,10 @@ +{ + "key.betterhud.open_editor": "Edit HUD Layout", + "key.categories.betterhud": "BetterHUD", + "key.category.betterhud.main": "BetterHUD", + "betterhud.editor.title": "BetterHUD Editor", + "betterhud.editor.settings": "Settings", + "betterhud.editor.scale": "Scale: %s×", + "betterhud.editor.hint": "Drag an element to move it · Drop it near a corner to dock it · Scroll to resize", + "betterhud.editor.hint2": "Press Escape to save and close" +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 6900660..d0fae48 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -24,5 +24,8 @@ "fabricloader": "${loaderReq}", "minecraft": "${minecraftReq}", "java": "${javaReq}"${dependsExtra} + }, + "suggests": { + "modmenu": "*" } } diff --git a/supported-versions.json b/supported-versions.json index 6ef4606..e30307d 100644 --- a/supported-versions.json +++ b/supported-versions.json @@ -8,9 +8,9 @@ "1.21.6": { "variant": "1.21.6", "fabricApi": "0.128.2+1.21.6", "modmenu": "15.0.2", "cloth": "19.0.147", "clientGametest": true }, "1.21.7": { "variant": "1.21.6", "fabricApi": "0.129.0+1.21.7", "modmenu": "15.0.2", "cloth": "19.0.147", "clientGametest": true }, "1.21.8": { "variant": "1.21.6", "fabricApi": "0.136.1+1.21.8", "modmenu": "15.0.2", "cloth": "19.0.147", "clientGametest": true }, - "1.21.9": { "variant": "1.21.6", "fabricApi": "0.134.1+1.21.9", "modmenu": "16.0.1", "cloth": "20.0.149", "clientGametest": true }, - "1.21.10": { "variant": "1.21.6", "fabricApi": "0.138.4+1.21.10", "modmenu": "16.0.1", "cloth": "21.11.153", "clientGametest": true }, - "1.21.11": { "variant": "1.21.6", "fabricApi": "0.141.4+1.21.11", "modmenu": "17.0.0", "cloth": "21.11.153", "clientGametest": true }, + "1.21.9": { "variant": "1.21.9", "fabricApi": "0.134.1+1.21.9", "modmenu": "16.0.1", "cloth": "20.0.149", "clientGametest": true }, + "1.21.10": { "variant": "1.21.9", "fabricApi": "0.138.4+1.21.10", "modmenu": "16.0.1", "cloth": "21.11.153", "clientGametest": true }, + "1.21.11": { "variant": "1.21.9", "fabricApi": "0.141.4+1.21.11", "modmenu": "17.0.0", "cloth": "21.11.153", "clientGametest": true }, "26.1": { "variant": "26.1", "fabricApi": "0.145.1+26.1", "modmenu": "18.0.0-beta.1", "cloth": "26.1.154", "clientGametest": true }, "26.1.1": { "variant": "26.1", "fabricApi": "0.145.4+26.1.1", "modmenu": "18.0.0-beta.1", "cloth": "26.1.154", "clientGametest": true }, "26.1.2": { "variant": "26.1", "fabricApi": "0.154.0+26.1.2", "modmenu": "18.0.0-beta.1", "cloth": "26.1.154", "clientGametest": true }, diff --git a/versions/1.21.6/gradle.properties b/versions/1.21.6/gradle.properties index a6cb6ef..bd9d190 100644 --- a/versions/1.21.6/gradle.properties +++ b/versions/1.21.6/gradle.properties @@ -2,7 +2,7 @@ minecraft_version=1.21.6 loader_version=0.17.3 java_version=21 -mc_dep=>=1.21.6 <1.22 +mc_dep=>=1.21.6 <1.21.9 loader_dep=>=0.16.10 fabric_api_version=0.128.2+1.21.6 diff --git a/versions/1.21.9/gradle.properties b/versions/1.21.9/gradle.properties new file mode 100644 index 0000000..c416db0 --- /dev/null +++ b/versions/1.21.9/gradle.properties @@ -0,0 +1,10 @@ +minecraft_version=1.21.9 +loader_version=0.17.3 +java_version=21 + +mc_dep=>=1.21.9 <1.22 +loader_dep=>=0.16.10 + +fabric_api_version=0.134.1+1.21.9 +modmenu_version=16.0.1 +cloth_config_version=20.0.149