aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorKevin <92656833+kevinthegreat1@users.noreply.github.com>2024-04-21 16:10:15 -0400
committerGitHub <noreply@github.com>2024-04-21 16:10:15 -0400
commit01b4ff489368182d164e2d38560c7a06183018b8 (patch)
tree92f88214b70e824aa87bf2dc9eb899696134bd7d /src/main/java
parent95fa171cd0f981f77a9a64aa77d9d69b81503253 (diff)
parentc43370daf17ea547f80908cf9391fac1d7756b45 (diff)
downloadSkyblocker-01b4ff489368182d164e2d38560c7a06183018b8.tar.gz
Skyblocker-01b4ff489368182d164e2d38560c7a06183018b8.tar.bz2
Skyblocker-01b4ff489368182d164e2d38560c7a06183018b8.zip
Merge pull request #654 from viciscat/fancier-bars
Fancier bars
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java37
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java32
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/InGameHudMixin.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/WindowMixin.java16
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/accessor/DrawContextInvoker.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/FancyStatusBars.java191
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/auction/EditBidPopup.java13
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/fancybars/BarPositioner.java308
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/fancybars/EditBarColorPopup.java117
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/fancybars/EditBarWidget.java274
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/fancybars/FancyStatusBars.java288
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBar.java353
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBarsConfigScreen.java365
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java71
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractPopupScreen.java37
16 files changed, 1857 insertions, 253 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 091e8548..e0815eee 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -25,6 +25,7 @@ import de.hysky.skyblocker.skyblock.end.BeaconHighlighter;
import de.hysky.skyblocker.skyblock.end.EnderNodes;
import de.hysky.skyblocker.skyblock.end.TheEnd;
import de.hysky.skyblocker.skyblock.entity.MobBoundingBoxes;
+import de.hysky.skyblocker.skyblock.fancybars.FancyStatusBars;
import de.hysky.skyblocker.skyblock.garden.FarmingHud;
import de.hysky.skyblocker.skyblock.garden.LowerSensitivity;
import de.hysky.skyblocker.skyblock.garden.VisitorHelper;
@@ -172,6 +173,7 @@ public class SkyblockerMod implements ClientModInitializer {
Debug.init();
Kuudra.init();
RenderHelper.init();
+ FancyStatusBars.init();
containerSolverManager.init();
statusBarTracker.init();
BeaconHighlighter.init();
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
index 86405f58..912636d6 100644
--- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
@@ -305,41 +305,34 @@ public class SkyblockerConfig {
@SerialEntry
public boolean enableBars = true;
+ // Kept in for backwards compatibility, remove if needed
@SerialEntry
- public BarPositions barPositions = new BarPositions();
+ public OldBarPositions barPositions = new OldBarPositions();
}
- public static class BarPositions {
+ /**
+ * Backwards compat
+ */
+ public static class OldBarPositions {
@SerialEntry
- public BarPosition healthBarPosition = BarPosition.LAYER1;
+ public OldBarPosition healthBarPosition = OldBarPosition.LAYER1;
@SerialEntry
- public BarPosition manaBarPosition = BarPosition.LAYER1;
+ public OldBarPosition manaBarPosition = OldBarPosition.LAYER1;
@SerialEntry
- public BarPosition defenceBarPosition = BarPosition.LAYER1;
+ public OldBarPosition defenceBarPosition = OldBarPosition.LAYER1;
@SerialEntry
- public BarPosition experienceBarPosition = BarPosition.LAYER1;
+ public OldBarPosition experienceBarPosition = OldBarPosition.LAYER1;
}
- public enum BarPosition {
- LAYER1, LAYER2, RIGHT, NONE;
-
- @Override
- public String toString() {
- return I18n.translate("text.autoconfig.skyblocker.option.general.bars.barpositions." + name());
- }
-
- public int toInt() {
- return switch (this) {
- case LAYER1 -> 0;
- case LAYER2 -> 1;
- case RIGHT -> 2;
- case NONE -> -1;
- };
- }
+ /**
+ * Backwards compat
+ */
+ public enum OldBarPosition {
+ LAYER1, LAYER2, RIGHT, NONE
}
public static class Experiments {
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
index dbfbbb10..77627242 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
@@ -2,6 +2,7 @@ package de.hysky.skyblocker.config.categories;
import de.hysky.skyblocker.config.ConfigUtils;
import de.hysky.skyblocker.config.SkyblockerConfig;
+import de.hysky.skyblocker.skyblock.fancybars.StatusBarsConfigScreen;
import de.hysky.skyblocker.skyblock.shortcut.ShortcutsConfigScreen;
import de.hysky.skyblocker.utils.render.title.TitleContainerConfigScreen;
import de.hysky.skyblocker.utils.waypoint.Waypoint;
@@ -158,33 +159,10 @@ public class GeneralCategory {
newValue -> config.general.bars.enableBars = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
- .option(Option.<SkyblockerConfig.BarPosition>createBuilder()
- .name(Text.translatable("text.autoconfig.skyblocker.option.general.bars.barpositions.healthBarPosition"))
- .binding(defaults.general.bars.barPositions.healthBarPosition,
- () -> config.general.bars.barPositions.healthBarPosition,
- newValue -> config.general.bars.barPositions.healthBarPosition = newValue)
- .controller(ConfigUtils::createEnumCyclingListController)
- .build())
- .option(Option.<SkyblockerConfig.BarPosition>createBuilder()
- .name(Text.translatable("text.autoconfig.skyblocker.option.general.bars.barpositions.manaBarPosition"))
- .binding(defaults.general.bars.barPositions.manaBarPosition,
- () -> config.general.bars.barPositions.manaBarPosition,
- newValue -> config.general.bars.barPositions.manaBarPosition = newValue)
- .controller(ConfigUtils::createEnumCyclingListController)
- .build())
- .option(Option.<SkyblockerConfig.BarPosition>createBuilder()
- .name(Text.translatable("text.autoconfig.skyblocker.option.general.bars.barpositions.defenceBarPosition"))
- .binding(defaults.general.bars.barPositions.defenceBarPosition,
- () -> config.general.bars.barPositions.defenceBarPosition,
- newValue -> config.general.bars.barPositions.defenceBarPosition = newValue)
- .controller(ConfigUtils::createEnumCyclingListController)
- .build())
- .option(Option.<SkyblockerConfig.BarPosition>createBuilder()
- .name(Text.translatable("text.autoconfig.skyblocker.option.general.bars.barpositions.experienceBarPosition"))
- .binding(defaults.general.bars.barPositions.experienceBarPosition,
- () -> config.general.bars.barPositions.experienceBarPosition,
- newValue -> config.general.bars.barPositions.experienceBarPosition = newValue)
- .controller(ConfigUtils::createEnumCyclingListController)
+ .option(ButtonOption.createBuilder()
+ .name(Text.translatable("skyblocker.bars.config.openScreen"))
+ .text(Text.translatable("text.skyblocker.open"))
+ .action((screen, opt) -> MinecraftClient.getInstance().setScreen(new StatusBarsConfigScreen()))
.build())
.build())
diff --git a/src/main/java/de/hysky/skyblocker/mixin/InGameHudMixin.java b/src/main/java/de/hysky/skyblocker/mixin/InGameHudMixin.java
index 75c516df..b0970b4b 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/InGameHudMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/InGameHudMixin.java
@@ -5,7 +5,7 @@ import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.systems.RenderSystem;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
-import de.hysky.skyblocker.skyblock.FancyStatusBars;
+import de.hysky.skyblocker.skyblock.fancybars.FancyStatusBars;
import de.hysky.skyblocker.skyblock.dungeon.DungeonMap;
import de.hysky.skyblocker.skyblock.dungeon.DungeonScore;
import de.hysky.skyblocker.skyblock.dungeon.DungeonScoreHUD;
diff --git a/src/main/java/de/hysky/skyblocker/mixin/WindowMixin.java b/src/main/java/de/hysky/skyblocker/mixin/WindowMixin.java
new file mode 100644
index 00000000..481a70a6
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/mixin/WindowMixin.java
@@ -0,0 +1,16 @@
+package de.hysky.skyblocker.mixin;
+
+import de.hysky.skyblocker.skyblock.fancybars.FancyStatusBars;
+import net.minecraft.client.util.Window;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(Window.class)
+public class WindowMixin {
+ @Inject(method = "setScaleFactor", at = @At("TAIL"))
+ public void skyblocker$onScaleFactorChange(double scaleFactor, CallbackInfo ci) {
+ FancyStatusBars.updatePositions();
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/mixin/accessor/DrawContextInvoker.java b/src/main/java/de/hysky/skyblocker/mixin/accessor/DrawContextInvoker.java
index 8dcccf34..9c14fdc6 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/accessor/DrawContextInvoker.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/accessor/DrawContextInvoker.java
@@ -4,6 +4,7 @@ import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.tooltip.TooltipComponent;
import net.minecraft.client.gui.tooltip.TooltipPositioner;
+import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@@ -14,4 +15,7 @@ public interface DrawContextInvoker {
@Invoker
void invokeDrawTooltip(TextRenderer textRenderer, List<TooltipComponent> components, int x, int y, TooltipPositioner positioner);
+
+ @Invoker
+ void invokeDrawTexturedQuad(Identifier texture, int x1, int x2, int y1, int y2, int z, float u1, float u2, float v1, float v2, float red, float green, float blue, float alpha);
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/FancyStatusBars.java b/src/main/java/de/hysky/skyblocker/skyblock/FancyStatusBars.java
deleted file mode 100644
index 3456d1ad..00000000
--- a/src/main/java/de/hysky/skyblocker/skyblock/FancyStatusBars.java
+++ /dev/null
@@ -1,191 +0,0 @@
-package de.hysky.skyblocker.skyblock;
-
-import de.hysky.skyblocker.SkyblockerMod;
-import de.hysky.skyblocker.config.SkyblockerConfigManager;
-import de.hysky.skyblocker.utils.Utils;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.font.TextRenderer;
-import net.minecraft.client.gui.DrawContext;
-import net.minecraft.util.Identifier;
-
-public class FancyStatusBars {
- private static final Identifier BARS = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/bars.png");
-
- private final MinecraftClient client = MinecraftClient.getInstance();
- private final StatusBarTracker statusBarTracker = SkyblockerMod.getInstance().statusBarTracker;
-
- private final StatusBar[] bars = new StatusBar[]{
- new StatusBar(0, 16733525, 2), // Health Bar
- new StatusBar(1, 5636095, 2), // Intelligence Bar
- new StatusBar(2, 12106180, 1), // Defence Bar
- new StatusBar(3, 8453920, 1), // Experience Bar
- };
-
- // Positions to show the bars
- // 0: Hotbar Layer 1, 1: Hotbar Layer 2, 2: Right of hotbar
- // Anything outside the set values hides the bar
- private final int[] anchorsX = new int[3];
- private final int[] anchorsY = new int[3];
-
- public FancyStatusBars() {
- moveBar(0, 0);
- moveBar(1, 0);
- moveBar(2, 0);
- moveBar(3, 0);
- }
-
- private int fill(int value, int max) {
- return (100 * value) / max;
- }
-
- public boolean render(DrawContext context, int scaledWidth, int scaledHeight) {
- var player = client.player;
- if (!SkyblockerConfigManager.get().general.bars.enableBars || player == null || Utils.isInTheRift())
- return false;
- anchorsX[0] = scaledWidth / 2 - 91;
- anchorsY[0] = scaledHeight - 33;
- anchorsX[1] = anchorsX[0];
- anchorsY[1] = anchorsY[0] - 10;
- anchorsX[2] = (scaledWidth / 2 + 91) + 2;
- anchorsY[2] = scaledHeight - 16;
-
- bars[0].update(statusBarTracker.getHealth());
- bars[1].update(statusBarTracker.getMana());
- int def = statusBarTracker.getDefense();
- bars[2].fill[0] = fill(def, def + 100);
- bars[2].text = def;
- bars[3].fill[0] = (int) (100 * player.experienceProgress);
- bars[3].text = player.experienceLevel;
-
- // Update positions of bars from config
- for (int i = 0; i < 4; i++) {
- int configAnchorNum = switch (i) {
- case 0 -> SkyblockerConfigManager.get().general.bars.barPositions.healthBarPosition.toInt();
- case 1 -> SkyblockerConfigManager.get().general.bars.barPositions.manaBarPosition.toInt();
- case 2 -> SkyblockerConfigManager.get().general.bars.barPositions.defenceBarPosition.toInt();
- case 3 -> SkyblockerConfigManager.get().general.bars.barPositions.experienceBarPosition.toInt();
- default -> 0;
- };
-
- if (bars[i].anchorNum != configAnchorNum)
- moveBar(i, configAnchorNum);
- }
-
- for (var bar : bars) {
- bar.draw(context);
- }
- for (var bar : bars) {
- bar.drawText(context);
- }
- return true;
- }
-
- public void moveBar(int bar, int location) {
- // Set the bar to the new anchor
- bars[bar].anchorNum = location;
-
- // Count how many bars are in each location
- int layer1Count = 0, layer2Count = 0;
- for (int i = 0; i < 4; i++) {
- switch (bars[i].anchorNum) {
- case 0 -> layer1Count++;
- case 1 -> layer2Count++;
- }
- }
-
- // Set the bars width and offsetX according to their anchor and how many bars are on that layer
- int adjustedLayer1Count = 0, adjustedLayer2Count = 0, adjustedRightCount = 0;
- for (int i = 0; i < 4; i++) {
- switch (bars[i].anchorNum) {
- case 0 -> {
- bars[i].bar_width = (172 - ((layer1Count - 1) * 11)) / layer1Count;
- bars[i].offsetX = adjustedLayer1Count * (bars[i].bar_width + 11 + (layer1Count == 3 ? 0 : 1));
- adjustedLayer1Count++;
- }
- case 1 -> {
- bars[i].bar_width = (172 - ((layer2Count - 1) * 11)) / layer2Count;
- bars[i].offsetX = adjustedLayer2Count * (bars[i].bar_width + 11 + (layer2Count == 3 ? 0 : 1));
- adjustedLayer2Count++;
- }
- case 2 -> {
- bars[i].bar_width = 50;
- bars[i].offsetX = adjustedRightCount * (50 + 11);
- adjustedRightCount++;
- }
- }
- }
- }
-
- private class StatusBar {
- public final int[] fill;
- public int offsetX;
- private final int v;
- private final int text_color;
- public int anchorNum;
- public int bar_width;
- public Object text;
-
- private StatusBar(int i, int textColor, int fillNum) {
- this.v = i * 9;
- this.text_color = textColor;
- this.fill = new int[fillNum];
- this.fill[0] = 100;
- this.anchorNum = 0;
- this.text = "";
- }
-
- public void update(StatusBarTracker.Resource resource) {
- int max = resource.max();
- int val = resource.value();
- this.fill[0] = fill(val, max);
- this.fill[1] = fill(resource.overflow(), max);
- this.text = val;
- }
-
- public void draw(DrawContext context) {
- // Dont draw if anchorNum is outside of range
- if (anchorNum < 0 || anchorNum > 2) return;
-
- // Draw the icon for the bar
- context.drawTexture(BARS, anchorsX[anchorNum] + offsetX, anchorsY[anchorNum], 0, v, 9, 9);
-
- // Draw the background for the bar
- context.drawTexture(BARS, anchorsX[anchorNum] + offsetX + 10, anchorsY[anchorNum], 10, v, 2, 9);
- for (int i = 2; i < bar_width - 2; i += 58) {
- context.drawTexture(BARS, anchorsX[anchorNum] + offsetX + 10 + i, anchorsY[anchorNum], 12, v, Math.min(58, bar_width - 2 - i), 9);
- }
- context.drawTexture(BARS, anchorsX[anchorNum] + offsetX + 10 + bar_width - 2, anchorsY[anchorNum], 70, v, 2, 9);
-
- // Draw the filled part of the bar
- for (int i = 0; i < fill.length; i++) {
- int fill_width = this.fill[i] * (bar_width - 2) / 100;
- if (fill_width >= 1) {
- context.drawTexture(BARS, anchorsX[anchorNum] + offsetX + 11, anchorsY[anchorNum], 72 + i * 60, v, 1, 9);
- }
- for (int j = 1; j < fill_width - 1; j += 58) {
- context.drawTexture(BARS, anchorsX[anchorNum] + offsetX + 11 + j, anchorsY[anchorNum], 73 + i * 60, v, Math.min(58, fill_width - 1 - j), 9);
- }
- if (fill_width == bar_width - 2) {
- context.drawTexture(BARS, anchorsX[anchorNum] + offsetX + 11 + fill_width - 1, anchorsY[anchorNum], 131 + i * 60, v, 1, 9);
- }
- }
- }
-
- public void drawText(DrawContext context) {
- // Dont draw if anchorNum is outside of range
- if (anchorNum < 0 || anchorNum > 2) return;
-
- TextRenderer textRenderer = client.textRenderer;
- String text = this.text.toString();
- int x = anchorsX[anchorNum] + this.offsetX + 11 + (bar_width - textRenderer.getWidth(text)) / 2;
- int y = anchorsY[anchorNum] - 3;
-
- final int[] offsets = new int[]{-1, 1};
- for (int i : offsets) {
- context.drawText(textRenderer, text, x + i, y, 0, false);
- context.drawText(textRenderer, text, x, y + i, 0, false);
- }
- context.drawText(textRenderer, text, x, y, text_color, false);
- }
- }
-}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/EditBidPopup.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/EditBidPopup.java
index 6b90b86c..9d460803 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/auction/EditBidPopup.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/EditBidPopup.java
@@ -34,18 +34,7 @@ public class EditBidPopup extends AbstractPopupScreen {
super.init();
layout = DirectionalLayoutWidget.vertical();
layout.spacing(8).getMainPositioner().alignHorizontalCenter();
- textFieldWidget = new TextFieldWidget(textRenderer, 120, 15, Text.empty()) {
- @Override
- public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
- if (!super.keyPressed(keyCode, scanCode, modifiers)) {
- if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) {
- done(null);
- return true;
- }
- } else return true;
- return false;
- }
- };
+ textFieldWidget = new EnterConfirmTextFieldWidget(textRenderer, 120, 15, Text.empty(), () -> done(null));
textFieldWidget.setTextPredicate(this::isStringGood);
layout.add(new TextWidget(Text.literal("- Set Bid -").fillStyle(Style.EMPTY.withBold(true)), textRenderer));
layout.add(textFieldWidget);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/BarPositioner.java b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/BarPositioner.java
new file mode 100644
index 00000000..a72b955b
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/BarPositioner.java
@@ -0,0 +1,308 @@
+package de.hysky.skyblocker.skyblock.fancybars;
+
+import net.minecraft.client.gui.ScreenPos;
+import net.minecraft.client.gui.ScreenRect;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+public class BarPositioner {
+
+ private final Map<BarAnchor, LinkedList<LinkedList<StatusBar>>> map = new HashMap<>(BarAnchor.values().length);
+
+ public BarPositioner() {
+ for (BarAnchor value : BarAnchor.values()) {
+ map.put(value, new LinkedList<>());
+ }
+ }
+
+
+ public int getRowCount(@NotNull BarAnchor barAnchor) {
+ return map.get(barAnchor).size();
+ }
+
+ /**
+ * Adds a row to the end of an anchor
+ *
+ * @param barAnchor the anchor
+ */
+ public void addRow(@NotNull BarAnchor barAnchor) {
+ map.get(barAnchor).add(new LinkedList<>());
+ }
+
+ /**
+ * Adds a row at the specified index
+ *
+ * @param barAnchor the anchor
+ * @param row row index
+ */
+ public void addRow(@NotNull BarAnchor barAnchor, int row) {
+ map.get(barAnchor).add(row, new LinkedList<>());
+ }
+
+ /**
+ * adds a bar to the end of a row
+ *
+ * @param barAnchor the anchor
+ * @param row the row
+ * @param bar the bar to add
+ */
+ public void addBar(@NotNull BarAnchor barAnchor, int row, StatusBar bar) {
+ LinkedList<StatusBar> statusBars = map.get(barAnchor).get(row);
+ statusBars.add(bar);
+ bar.gridY = row;
+ bar.gridX = statusBars.lastIndexOf(bar); // optimization baby, start with the end!
+ bar.anchor = barAnchor;
+ }
+
+ /**
+ * adds a bar to the specified x in a row
+ *
+ * @param barAnchor the anchor
+ * @param row the row
+ * @param x the index in the row
+ * @param bar the bar to add
+ */
+ public void addBar(@NotNull BarAnchor barAnchor, int row, int x, StatusBar bar) {
+ LinkedList<StatusBar> statusBars = map.get(barAnchor).get(row);
+ statusBars.add(x, bar);
+ bar.gridY = row;
+ bar.gridX = statusBars.indexOf(bar);
+ bar.anchor = barAnchor;
+ }
+
+ /**
+ * removes the specified bar at x on the row. If it's row is empty after being removed, the row will be auto removed
+ *
+ * @param barAnchor the anchor
+ * @param row dah row
+ * @param x dah x
+ */
+ public void removeBar(@NotNull BarAnchor barAnchor, int row, int x) {
+ LinkedList<StatusBar> statusBars = map.get(barAnchor).get(row);
+ StatusBar remove = statusBars.remove(x);
+ remove.anchor = null;
+ for (int i = x; i < statusBars.size(); i++) {
+ statusBars.get(i).gridX--;
+ }
+ if (statusBars.isEmpty()) removeRow(barAnchor, row);
+ }
+
+ /**
+ * removes the specified bar on the row. If it's row is empty after being removed, the row will be auto removed
+ *
+ * @param barAnchor the anchor
+ * @param row dah row
+ * @param bar dah bar
+ */
+ public void removeBar(@NotNull BarAnchor barAnchor, int row, StatusBar bar) {
+ LinkedList<StatusBar> barRow = map.get(barAnchor).get(row);
+ int x = barRow.indexOf(bar);
+ if (x < 0) return; // probably a bad idea
+
+ barRow.remove(bar);
+ bar.anchor = null;
+ for (int i = x; i < barRow.size(); i++) {
+ barRow.get(i).gridX--;
+ }
+ if (barRow.isEmpty()) removeRow(barAnchor, row);
+ }
+
+ /**
+ * row must be empty
+ *
+ * @param barAnchor the anchor
+ * @param row the row to remove
+ */
+ public void removeRow(@NotNull BarAnchor barAnchor, int row) {
+ LinkedList<StatusBar> barRow = map.get(barAnchor).get(row);
+ if (!barRow.isEmpty())
+ throw new IllegalStateException("Can't remove a non-empty row (" + barAnchor + "," + row + ")");
+ map.get(barAnchor).remove(row);
+ for (int i = row; i < map.get(barAnchor).size(); i++) {
+ for (StatusBar statusBar : map.get(barAnchor).get(i)) {
+ statusBar.gridY--;
+ }
+ }
+ }
+
+
+ public LinkedList<StatusBar> getRow(@NotNull BarAnchor barAnchor, int row) {
+ return map.get(barAnchor).get(row);
+ }
+
+ public StatusBar getBar(@NotNull BarAnchor barAnchor, int row, int x) {
+ return map.get(barAnchor).get(row).get(x);
+ }
+
+ public boolean hasNeighbor(@NotNull BarAnchor barAnchor, int row, int x, boolean right) {
+ LinkedList<StatusBar> statusBars = map.get(barAnchor).get(row);
+ if (barAnchor.isRight()) {
+ return (right && x < statusBars.size() - 1) || (!right && x > 0);
+ } else {
+ return (right && x > 0) || (!right && x < statusBars.size() - 1);
+ }
+ }
+
+
+ public enum BarAnchor {
+ HOTBAR_LEFT(true, false,
+ (scaledWidth, scaledHeight) -> new ScreenPos(scaledWidth / 2 - 91 - 2, scaledHeight - 5),
+ SizeRule.freeSize(25, 2, 6)),
+
+ HOTBAR_RIGHT(true, true,
+ (scaledWidth, scaledHeight) -> new ScreenPos(scaledWidth / 2 + 91 + 2, scaledHeight - 5),
+ SizeRule.freeSize(25, 2, 6)),
+
+ HOTBAR_TOP(true, true,
+ (scaledWidth, scaledHeight) -> new ScreenPos(scaledWidth / 2 - 91, scaledHeight - 23),
+ SizeRule.targetSize(12, 182, 2),
+ anchorPosition -> new ScreenRect(anchorPosition.x(), anchorPosition.y() - 20, 182, 20)),
+
+ SCREEN_TOP_LEFT(false, true,
+ ((scaledWidth, scaledHeight) -> new ScreenPos(5, 5)),
+ SizeRule.freeSize(25, 2, 6)
+ ),
+ SCREEN_TOP_RIGHT(false, false,
+ ((scaledWidth, scaledHeight) -> new ScreenPos(scaledWidth - 5, 5)),
+ SizeRule.freeSize(25, 2, 6)
+ ),
+ SCREEN_BOTTOM_LEFT(true, true,
+ ((scaledWidth, scaledHeight) -> new ScreenPos(5, scaledHeight - 5)),
+ SizeRule.freeSize(25, 2, 6)
+ ),
+ SCREEN_BOTTOM_RIGHT(true, false,
+ ((scaledWidth, scaledHeight) -> new ScreenPos(scaledWidth - 5, scaledHeight - 5)),
+ SizeRule.freeSize(25, 2, 6)
+ );
+
+ private final AnchorPositionProvider positionProvider;
+ private final AnchorHitboxProvider hitboxProvider;
+ private final boolean up;
+ private final boolean right;
+ private final SizeRule sizeRule;
+
+ /**
+ * @param up whether the rows stack towards the top of the screen from the anchor (false is bottom)
+ * @param right whether the bars are line up towards the right of the screen from the anchor (false is left)
+ * @param positionProvider provides the position of the anchor for a give screen size
+ * @param sizeRule the rule the bars should follow. See {@link SizeRule}
+ * @param hitboxProvider provides the hitbox for when the anchor has no bars for the config screen
+ */
+ BarAnchor(boolean up, boolean right, AnchorPositionProvider positionProvider, SizeRule sizeRule, AnchorHitboxProvider hitboxProvider) {
+ this.positionProvider = positionProvider;
+ this.up = up;
+ this.right = right;
+ this.hitboxProvider = hitboxProvider;
+ this.sizeRule = sizeRule;
+ }
+
+ BarAnchor(boolean up, boolean right, AnchorPositionProvider positionProvider, SizeRule sizeRule) {
+ this(up, right, positionProvider, sizeRule,
+ anchorPosition -> new ScreenRect(anchorPosition.x() - (right ? 0 : 20), anchorPosition.y() - (up ? 20 : 0), 20, 20));
+ }
+
+ public ScreenPos getAnchorPosition(int scaledWidth, int scaledHeight) {
+ return positionProvider.getPosition(scaledWidth, scaledHeight);
+ }
+
+ public ScreenRect getAnchorHitbox(ScreenPos anchorPosition) {
+ return hitboxProvider.getHitbox(anchorPosition);
+ }
+
+ /**
+ * whether the rows stack towards the top of the screen from the anchor (false is bottom)
+ *
+ * @return true if towards the top, false otherwise
+ */
+ public boolean isUp() {
+ return up;
+ }
+
+ /**
+ * whether the bars are line up towards the right of the screen from the anchor (false is left)
+ *
+ * @return true if towards the right, false otherwise
+ */
+ public boolean isRight() {
+ return right;
+ }
+
+ public SizeRule getSizeRule() {
+ return sizeRule;
+ }
+
+ private static final List<BarAnchor> cached = List.of(values());
+
+ /**
+ * cached version of {@link BarAnchor#values()}
+ *
+ * @return the list of anchors
+ */
+ public static List<BarAnchor> allAnchors() {
+ return cached;
+ }
+ }
+
+ /**
+ * The rules the bars on an anchor should follow
+ *
+ * @param isTargetSize whether the bars went to fit to a target width
+ * @param targetSize the size of all the bars on a row should add up to this (target size)
+ * @param totalWidth the total width taken by all the bars on the row (target size)
+ * @param widthPerSize the width of each size "unit" (free size)
+ * @param minSize the minimum (free and target size)
+ * @param maxSize the maximum (free and target size, THIS SHOULD BE THE SAME AS {@code targetSize} FOR {@code isTargetSize = true})
+ */
+ public record SizeRule(boolean isTargetSize, int targetSize, int totalWidth, int widthPerSize, int minSize, int maxSize) {
+ public static SizeRule freeSize(int widthPerSize, int minSize, int maxSize) {
+ return new SizeRule(false, -1, -1, widthPerSize, minSize, maxSize);
+ }
+
+ public static SizeRule targetSize(int targetSize, int totalWidth, int minSize) {
+ return new SizeRule(true, targetSize, totalWidth, -1, minSize, targetSize);
+ }
+ }
+
+ /**
+ * A record representing a snapshot of a bar's position
+ *
+ * @param barAnchor
+ * @param x
+ * @param y the row
+ */
+ public record BarLocation(@Nullable BarAnchor barAnchor, int x, int y) {
+
+ public static final BarLocation NULL = new BarLocation(null, -1, -1);
+
+ public static BarLocation of(StatusBar bar) {
+ return new BarLocation(bar.anchor, bar.gridX, bar.gridY);
+ }
+
+ public boolean equals(BarAnchor barAnchor, int x, int y) {
+ return x == this.x && y == this.y && barAnchor == this.barAnchor;
+ }
+ }
+
+ /**
+ * provides the position of the anchor for a give screen size
+ */
+ @FunctionalInterface
+ interface AnchorPositionProvider {
+
+ ScreenPos getPosition(int scaledWidth, int scaledHeight);
+ }
+
+ @FunctionalInterface
+ interface AnchorHitboxProvider {
+
+ /**
+ * The hitbox, as in how large the area of "snapping" is if there are no bars on this anchor
+ *
+ * @param anchorPosition the position of the anchor
+ * @return the rectangle that represents the hitbox
+ */
+ ScreenRect getHitbox(ScreenPos anchorPosition);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/EditBarColorPopup.java b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/EditBarColorPopup.java
new file mode 100644
index 00000000..64e79bab
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/EditBarColorPopup.java
@@ -0,0 +1,117 @@
+package de.hysky.skyblocker.skyblock.fancybars;
+
+import de.hysky.skyblocker.utils.render.gui.AbstractPopupScreen;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
+import net.minecraft.client.gui.widget.*;
+import net.minecraft.text.Style;
+import net.minecraft.text.Text;
+
+import java.awt.*;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class EditBarColorPopup extends AbstractPopupScreen {
+
+ private final Consumer<Color> setColor;
+
+ private DirectionalLayoutWidget layout = DirectionalLayoutWidget.vertical();
+ private BasicColorSelector colorSelector;
+
+ protected EditBarColorPopup(Text title, Screen backgroundScreen, Consumer<Color> setColor) {
+ super(title, backgroundScreen);
+ this.setColor = setColor;
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+ layout = DirectionalLayoutWidget.vertical();
+ layout.spacing(8).getMainPositioner().alignHorizontalCenter();
+ layout.add(new TextWidget(title.copy().fillStyle(Style.EMPTY.withBold(true)), MinecraftClient.getInstance().textRenderer));
+ colorSelector = new BasicColorSelector(0, 0, 150, () -> done(null));
+ layout.add(colorSelector);
+
+ DirectionalLayoutWidget horizontal = DirectionalLayoutWidget.horizontal();
+ ButtonWidget buttonWidget = ButtonWidget.builder(Text.literal("Cancel"), button -> close()).width(80).build();
+ horizontal.add(buttonWidget);
+ horizontal.add(ButtonWidget.builder(Text.literal("Done"), this::done).width(80).build());
+
+ layout.add(horizontal);
+ layout.forEachChild(this::addDrawableChild);
+ this.layout.refreshPositions();
+ SimplePositioningWidget.setPos(layout, this.getNavigationFocus());
+ }
+
+ private void done(Object object) {
+ if (colorSelector.validColor) setColor.accept(new Color(colorSelector.getColor()));
+ close();
+ }
+
+ @Override
+ public void renderBackground(DrawContext context, int mouseX, int mouseY, float delta) {
+ super.renderBackground(context, mouseX, mouseY, delta);
+ drawPopupBackground(context, layout.getX(), layout.getY(), layout.getWidth(), layout.getHeight());
+ }
+
+ private static class BasicColorSelector extends ContainerWidget {
+
+ private final EnterConfirmTextFieldWidget textFieldWidget;
+
+ public BasicColorSelector(int x, int y, int width, Runnable onEnter) {
+ super(x, y, width, 15, Text.literal("edit color"));
+ textFieldWidget = new EnterConfirmTextFieldWidget(MinecraftClient.getInstance().textRenderer, getX() + 16, getY(), width - 16, 15, Text.empty(), onEnter);
+ textFieldWidget.setChangedListener(this::onTextChange);
+ textFieldWidget.setTextPredicate(s -> s.length() <= 6);
+ }
+
+ @Override
+ public List<? extends Element> children() {
+ return List.of(textFieldWidget);
+ }
+
+ public int getColor() {
+ return color;
+ }
+
+ private int color = 0xFF000000;
+ private boolean validColor = false;
+
+ private void onTextChange(String text) {
+ try {
+ color = Integer.parseInt(text, 16) | 0xFF000000;
+ validColor = true;
+ } catch (NumberFormatException e) {
+ color = 0;
+ validColor = false;
+ }
+ }
+
+ @Override
+ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+ context.drawBorder(getX(), getY(), 15, 15, validColor ? -1 : 0xFFDD0000);
+ context.fill(getX() + 1, getY() + 1, getX() + 14, getY() + 14, color);
+ textFieldWidget.renderWidget(context, mouseX, mouseY, delta);
+ }
+
+ @Override
+ protected void appendClickableNarrations(NarrationMessageBuilder builder) {
+
+ }
+
+ @Override
+ public void setX(int x) {
+ super.setX(x);
+ textFieldWidget.setX(getX() + 16);
+ }
+
+ @Override
+ public void setY(int y) {
+ super.setY(y);
+ textFieldWidget.setY(getY());
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/EditBarWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/EditBarWidget.java
new file mode 100644
index 00000000..54af7a03
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/EditBarWidget.java
@@ -0,0 +1,274 @@
+package de.hysky.skyblocker.skyblock.fancybars;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
+import net.minecraft.client.gui.tooltip.TooltipBackgroundRenderer;
+import net.minecraft.client.gui.widget.ClickableWidget;
+import net.minecraft.client.gui.widget.ContainerWidget;
+import net.minecraft.client.gui.widget.TextWidget;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Colors;
+import net.minecraft.util.Formatting;
+
+import java.awt.*;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class EditBarWidget extends ContainerWidget {
+
+ private final EnumCyclingOption<StatusBar.IconPosition> iconOption;
+ private final BooleanOption booleanOption;
+
+ private final ColorOption color1;
+ private final ColorOption color2;
+ private final ColorOption textColor;
+ private final TextWidget nameWidget;
+
+ private int contentsWidth = 0;
+
+ public EditBarWidget(int x, int y, Screen parent) {
+ super(x, y, 100, 66, Text.literal("Edit bar"));
+
+ TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+
+ nameWidget = new TextWidget(Text.empty(), textRenderer);
+
+ MutableText translatable = Text.translatable("skyblocker.bars.config.icon");
+ contentsWidth = Math.max(contentsWidth, textRenderer.getWidth(translatable) + textRenderer.getWidth("RIGHT") + 10);
+ iconOption = new EnumCyclingOption<>(0, 11, getWidth(), translatable, StatusBar.IconPosition.class);
+
+ translatable = Text.translatable("skyblocker.bars.config.showValue");
+ contentsWidth = Math.max(contentsWidth, textRenderer.getWidth(translatable) + 9 + 10);
+ booleanOption = new BooleanOption(0, 22, getWidth(), translatable);
+
+ // COLO(u)RS
+ translatable = Text.translatable("skyblocker.bars.config.mainColor");
+ contentsWidth = Math.max(contentsWidth, textRenderer.getWidth(translatable) + 9 + 10);
+ color1 = new ColorOption(0, 33, getWidth(), translatable, parent);
+
+ translatable = Text.translatable("skyblocker.bars.config.overflowColor");
+ contentsWidth = Math.max(contentsWidth, textRenderer.getWidth(translatable) + 9 + 10);
+ color2 = new ColorOption(0, 44, getWidth(), translatable, parent);
+
+ translatable = Text.translatable("skyblocker.bars.config.textColor");
+ contentsWidth = Math.max(contentsWidth, textRenderer.getWidth(translatable) + 9 + 10);
+ textColor = new ColorOption(0, 55, getWidth(), translatable, parent);
+
+ setWidth(contentsWidth);
+ }
+
+ @Override
+ public List<? extends Element> children() {
+ return List.of(iconOption, booleanOption, color1, color2, textColor);
+ }
+
+ public int insideMouseX = 0;
+ public int insideMouseY = 0;
+
+ @Override
+ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+ if (isHovered()) {
+ insideMouseX = mouseX;
+ insideMouseY = mouseY;
+ } else {
+ int i = mouseX - insideMouseX;
+ int j = mouseY - insideMouseY;
+ if (i * i + j * j > 30 * 30) visible = false;
+ }
+ TooltipBackgroundRenderer.render(context, getX(), getY(), getWidth(), getHeight(), 0);
+ MatrixStack matrices = context.getMatrices();
+ matrices.push();
+ matrices.translate(getX(), getY(), 0);
+ nameWidget.render(context, mouseX, mouseY, delta);
+ iconOption.renderWidget(context, mouseX - getX(), mouseY - getY(), delta);
+ booleanOption.renderWidget(context, mouseX - getX(), mouseY - getY(), delta);
+ color1.renderWidget(context, mouseX - getX(), mouseY - getY(), delta);
+ color2.renderWidget(context, mouseX - getX(), mouseY - getY(), delta);
+ textColor.renderWidget(context, mouseX - getX(), mouseY - getY(), delta);
+ matrices.pop();
+ }
+
+ @Override
+ protected void appendClickableNarrations(NarrationMessageBuilder builder) {
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (!visible) return false;
+ if (!isHovered()) visible = false;
+ return super.mouseClicked(mouseX - getX(), mouseY - getY(), button);
+ }
+
+ public void setStatusBar(StatusBar statusBar) {
+ iconOption.setCurrent(statusBar.getIconPosition());
+ iconOption.setOnChange(statusBar::setIconPosition);
+ booleanOption.setCurrent(statusBar.showText());
+ booleanOption.setOnChange(statusBar::setShowText);
+
+ color1.setCurrent(statusBar.getColors()[0].getRGB());
+ color1.setOnChange(color -> statusBar.getColors()[0] = color);
+
+ color2.active = statusBar.hasOverflow();
+ if (color2.active) {
+ color2.setCurrent(statusBar.getColors()[1].getRGB());
+ color2.setOnChange(color -> statusBar.getColors()[1] = color);
+ }
+
+ if (statusBar.getTextColor() != null) {
+ textColor.setCurrent(statusBar.getTextColor().getRGB());
+ }
+ textColor.setOnChange(statusBar::setTextColor);
+
+ MutableText formatted = statusBar.getName().copy().formatted(Formatting.BOLD);
+ nameWidget.setMessage(formatted);
+ setWidth(Math.max(MinecraftClient.getInstance().textRenderer.getWidth(formatted), contentsWidth));
+ }
+
+ @Override
+ public void setWidth(int width) {
+ super.setWidth(width);
+ iconOption.setWidth(width);
+ booleanOption.setWidth(width);
+ color1.setWidth(width);
+ color2.setWidth(width);
+ textColor.setWidth(width);
+ nameWidget.setWidth(width);
+
+ }
+
+ public static class EnumCyclingOption<T extends Enum<T>> extends ClickableWidget {
+
+ private T current;
+ private final T[] values;
+ private Consumer<T> onChange = null;
+
+ public EnumCyclingOption(int x, int y, int width, Text message, Class<T> enumClass) {
+ super(x, y, width, 11, message);
+ values = enumClass.getEnumConstants();
+ current = values[0];
+ }
+
+ @Override
+ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+ if (isMouseOver(mouseX, mouseY)) {
+ context.fill(getX(), getY(), getRight(), getBottom(), 0x20FFFFFF);
+ }
+ TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+ context.drawText(textRenderer, getMessage(), getX() + 1, getY() + 1, -1, true);
+ String string = current.toString();
+ context.drawText(textRenderer, string, getRight() - textRenderer.getWidth(string) - 1, getY() + 1, -1, true);
+ }
+
+ public void setCurrent(T current) {
+ this.current = current;
+ }
+
+ @Override
+ public void onClick(double mouseX, double mouseY) {
+ current = values[(current.ordinal() + 1) % values.length];
+ if (onChange != null) onChange.accept(current);
+ super.onClick(mouseX, mouseY);
+ }
+
+ @Override
+ protected void appendClickableNarrations(NarrationMessageBuilder builder) {
+ }
+
+ public void setOnChange(Consumer<T> onChange) {
+ this.onChange = onChange;
+ }
+ }
+
+ public static class BooleanOption extends ClickableWidget {
+
+ private boolean current = false;
+ private Consumer<Boolean> onChange = null;
+
+ public BooleanOption(int x, int y, int width, Text message) {
+ super(x, y, width, 11, message);
+ }
+
+ @Override
+ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+ if (isMouseOver(mouseX, mouseY)) {
+ context.fill(getX(), getY(), getRight(), getBottom(), 0x20FFFFFF);
+ }
+ TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+ context.drawText(textRenderer, getMessage(), getX() + 1, getY() + 1, -1, true);
+ context.drawBorder(getRight() - 10, getY() + 1, 9, 9, -1);
+ if (current) context.fill(getRight() - 8, getY() + 3, getRight() - 3, getY() + 8, -1);
+ }
+
+ @Override
+ public void onClick(double mouseX, double mouseY) {
+ current = !current;
+ if (onChange != null) onChange.accept(current);
+ super.onClick(mouseX, mouseY);
+ }
+
+ @Override
+ protected void appendClickableNarrations(NarrationMessageBuilder builder) {
+ }
+
+ public void setCurrent(boolean current) {
+ this.current = current;
+ }
+
+ public void setOnChange(Consumer<Boolean> onChange) {
+ this.onChange = onChange;
+ }
+ }
+
+ public static class ColorOption extends ClickableWidget {
+
+ public void setCurrent(int current) {
+ this.current = current;
+ }
+
+ private int current = 0;
+ private Consumer<Color> onChange = null;
+ private final Screen parent;
+
+ public ColorOption(int x, int y, int width, Text message, Screen parent) {
+ super(x, y, width, 11, message);
+ this.parent = parent;
+ }
+
+ @Override
+ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+ if (isMouseOver(mouseX, mouseY)) {
+ context.fill(getX(), getY(), getRight(), getBottom(), 0x20FFFFFF);
+ }
+ TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+ context.drawText(textRenderer, getMessage(), getX() + 1, getY() + 1, active ? -1 : Colors.GRAY, true);
+ context.drawBorder(getRight() - 10, getY() + 1, 9, 9, active ? -1 : Colors.GRAY);
+ context.fill(getRight() - 8, getY() + 3, getRight() - 3, getY() + 8, active ? current : Colors.GRAY);
+ }
+
+ @Override
+ public void onClick(double mouseX, double mouseY) {
+ super.onClick(mouseX, mouseY);
+ MinecraftClient.getInstance().setScreen(new EditBarColorPopup(Text.literal("Edit ").append(getMessage()), parent, this::set));
+ }
+
+ private void set(Color color) {
+ current = color.getRGB();
+ if (onChange != null) onChange.accept(color);
+ }
+
+ @Override
+ protected void appendClickableNarrations(NarrationMessageBuilder builder) {
+
+ }
+
+ public void setOnChange(Consumer<Color> onChange) {
+ this.onChange = onChange;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/FancyStatusBars.java b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/FancyStatusBars.java
new file mode 100644
index 00000000..a960c9af
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/FancyStatusBars.java
@@ -0,0 +1,288 @@
+package de.hysky.skyblocker.skyblock.fancybars;
+
+import com.google.gson.JsonObject;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.config.SkyblockerConfig;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.StatusBarTracker;
+import de.hysky.skyblocker.utils.Utils;
+import de.hysky.skyblocker.utils.scheduler.Scheduler;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.ScreenPos;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.math.MathHelper;
+import org.lwjgl.glfw.GLFW;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.awt.*;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+public class FancyStatusBars {
+ private static final Path FILE = SkyblockerMod.CONFIG_DIR.resolve("status_bars.json");
+ private static final Logger LOGGER = LoggerFactory.getLogger(FancyStatusBars.class);
+
+ private final MinecraftClient client = MinecraftClient.getInstance();
+ private final StatusBarTracker statusBarTracker = SkyblockerMod.getInstance().statusBarTracker;
+
+ public static BarPositioner barPositioner = new BarPositioner();
+ public static Map<String, StatusBar> statusBars = new HashMap<>();
+
+ public static void init() {
+ statusBars.put("health", new StatusBar(new Identifier(SkyblockerMod.NAMESPACE, "bars/icons/health"),
+ new Color[]{new Color(255, 0, 0), new Color(255, 220, 0)},
+ true, new Color(255, 85, 85), Text.translatable("skyblocker.bars.config.health")));
+ statusBars.put("intelligence", new StatusBar(new Identifier(SkyblockerMod.NAMESPACE, "bars/icons/intelligence"),
+ new Color[]{new Color(0, 255, 255), new Color(180, 0, 255)},
+ true, new Color(85, 255, 255), Text.translatable("skyblocker.bars.config.intelligence")));
+ statusBars.put("defense", new StatusBar(new Identifier(SkyblockerMod.NAMESPACE, "bars/icons/defense"),
+ new Color[]{new Color(255, 255, 255)},
+ false, new Color(185, 185, 185), Text.translatable("skyblocker.bars.config.defense")));
+ statusBars.put("experience", new StatusBar(new Identifier(SkyblockerMod.NAMESPACE, "bars/icons/experience"),
+ new Color[]{new Color(100, 230, 70)},
+ false, new Color(128, 255, 32), Text.translatable("skyblocker.bars.config.experience")));
+
+ // Fetch from old status bar config
+ int[] counts = new int[3]; // counts for RIGHT, LAYER1, LAYER2
+ StatusBar health = statusBars.get("health");
+ SkyblockerConfig.OldBarPositions barPositions = SkyblockerConfigManager.get().general.bars.barPositions;
+ loadOldBarPosition(health, counts, barPositions.healthBarPosition);
+ StatusBar intelligence = statusBars.get("intelligence");
+ loadOldBarPosition(intelligence, counts, barPositions.manaBarPosition);
+ StatusBar defense = statusBars.get("defense");
+ loadOldBarPosition(defense, counts, barPositions.defenceBarPosition);
+ StatusBar experience = statusBars.get("experience");
+ loadOldBarPosition(experience, counts, barPositions.experienceBarPosition);
+
+ CompletableFuture.supplyAsync(FancyStatusBars::loadBarConfig).thenAccept(object -> {
+ if (object != null) {
+ for (String s : object.keySet()) {
+ if (statusBars.containsKey(s)) {
+ try {
+ statusBars.get(s).loadFromJson(object.get(s).getAsJsonObject());
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Failed to load {} status bar", s, e);
+ }
+ } else {
+ LOGGER.warn("[Skyblocker] Unknown status bar: {}", s);
+ }
+ }
+ }
+ placeBarsInPositioner();
+ configLoaded = true;
+ }).exceptionally(throwable -> {
+ LOGGER.error("[Skyblocker] Failed reading status bars config", throwable);
+ return null;
+ });
+ ClientLifecycleEvents.CLIENT_STOPPING.register((client) -> {
+ saveBarConfig();
+ GLFW.glfwDestroyCursor(StatusBarsConfigScreen.RESIZE_CURSOR);
+ });
+
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(
+ ClientCommandManager.literal(SkyblockerMod.NAMESPACE)
+ .then(ClientCommandManager.literal("bars").executes(Scheduler.queueOpenScreenCommand(StatusBarsConfigScreen::new)))));
+ }
+
+ /**
+ * Loads the bar position from the old config
+ * @param bar the bar to load the position for
+ * @param counts the counts for each bar position (LAYER1, LAYER2, RIGHT)
+ * @param position the position to load
+ */
+ private static void loadOldBarPosition(StatusBar bar, int[] counts, SkyblockerConfig.OldBarPosition position) {
+ switch (position) {
+ case RIGHT:
+ bar.anchor = BarPositioner.BarAnchor.HOTBAR_RIGHT;
+ bar.gridY = 0;
+ bar.gridX = counts[position.ordinal()]++;
+ break;
+ case LAYER1:
+ bar.anchor = BarPositioner.BarAnchor.HOTBAR_TOP;
+ bar.gridY = 0;
+ bar.gridX = counts[position.ordinal()]++;
+ break;
+ case LAYER2:
+ bar.anchor = BarPositioner.BarAnchor.HOTBAR_TOP;
+ bar.gridY = 1;
+ bar.gridX = counts[position.ordinal()]++;
+ break;
+ }
+ }
+
+ private static boolean configLoaded = false;
+
+ private static void placeBarsInPositioner() {
+ List<StatusBar> original = statusBars.values().stream().toList();
+
+ for (BarPositioner.BarAnchor barAnchor : BarPositioner.BarAnchor.allAnchors()) {
+ List<StatusBar> barList = new ArrayList<>(original.stream().filter(bar -> bar.anchor == barAnchor).toList());
+ if (barList.isEmpty()) continue;
+ barList.sort((a, b) -> a.gridY == b.gridY ? Integer.compare(a.gridX, b.gridX) : Integer.compare(a.gridY, b.gridY));
+
+ int y = -1;
+ int rowNum = -1;
+ for (StatusBar statusBar : barList) {
+ if (statusBar.gridY > y) {
+ barPositioner.addRow(barAnchor);
+ rowNum++;
+ y = statusBar.gridY;
+ }
+ barPositioner.addBar(barAnchor, rowNum, statusBar);
+ }
+ }
+ }
+
+ public static JsonObject loadBarConfig() {
+ try (BufferedReader reader = Files.newBufferedReader(FILE)) {
+ return SkyblockerMod.GSON.fromJson(reader, JsonObject.class);
+ } catch (NoSuchFileException e) {
+ LOGGER.warn("[Skyblocker] No status bar config file found, using defaults");
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Failed to load status bars config", e);
+ }
+ return null;
+ }
+
+ public static void saveBarConfig() {
+ JsonObject output = new JsonObject();
+ statusBars.forEach((s, statusBar) -> output.add(s, statusBar.toJson()));
+ try (BufferedWriter writer = Files.newBufferedWriter(FILE)) {
+ SkyblockerMod.GSON.toJson(output, writer);
+ LOGGER.info("[Skyblocker] Saved status bars config");
+ } catch (IOException e) {
+ LOGGER.error("[Skyblocker] Failed to save status bars config", e);
+ }
+ }
+
+ public static void updatePositions() {
+ if (!configLoaded) return;
+ final int width = MinecraftClient.getInstance().getWindow().getScaledWidth();
+ final int height = MinecraftClient.getInstance().getWindow().getScaledHeight();
+
+ for (BarPositioner.BarAnchor barAnchor : BarPositioner.BarAnchor.allAnchors()) {
+ ScreenPos anchorPosition = barAnchor.getAnchorPosition(width, height);
+ BarPositioner.SizeRule sizeRule = barAnchor.getSizeRule();
+
+ if (sizeRule.isTargetSize()) {
+ for (int row = 0; row < barPositioner.getRowCount(barAnchor); row++) {
+ LinkedList<StatusBar> barRow = barPositioner.getRow(barAnchor, row);
+ if (barRow.isEmpty()) continue;
+
+ // FIX SIZES
+ int totalSize = 0;
+ for (StatusBar statusBar : barRow)
+ totalSize += (statusBar.size = MathHelper.clamp(statusBar.size, sizeRule.minSize(), sizeRule.maxSize()));
+
+ whileLoop:
+ while (totalSize != sizeRule.targetSize()) {
+ if (totalSize > sizeRule.targetSize()) {
+ for (StatusBar statusBar : barRow) {
+ if (statusBar.size > sizeRule.minSize()) {
+ statusBar.size--;
+ totalSize--;
+ if (totalSize == sizeRule.targetSize()) break whileLoop;
+ }
+ }
+ } else {
+ for (StatusBar statusBar : barRow) {
+ if (statusBar.size < sizeRule.maxSize()) {
+ statusBar.size++;
+ totalSize++;
+ if (totalSize == sizeRule.targetSize()) break whileLoop;
+ }
+ }
+ }
+ }
+
+ }
+ }
+
+ for (int row = 0; row < barPositioner.getRowCount(barAnchor); row++) {
+ List<StatusBar> barRow = barPositioner.getRow(barAnchor, row);
+ if (barRow.isEmpty()) continue;
+
+
+ // Update the positions
+ float widthPerSize;
+ if (sizeRule.isTargetSize())
+ widthPerSize = (float) sizeRule.totalWidth() / sizeRule.targetSize();
+ else
+ widthPerSize = sizeRule.widthPerSize();
+
+ int currSize = 0;
+ int rowSize = barRow.size();
+ for (int i = 0; i < rowSize; i++) {
+ // A bit of a padding
+ int offsetX = 0;
+ int lessWidth = 0;
+ if (rowSize > 1) { // Technically bars in the middle of 3+ bars will be smaller than the 2 side ones but shh
+ if (i == 0) lessWidth = 1;
+ else if (i == rowSize - 1) {
+ lessWidth = 1;
+ offsetX = 1;
+ } else {
+ lessWidth = 2;
+ offsetX = 1;
+ }
+ }
+ StatusBar statusBar = barRow.get(i);
+ statusBar.size = MathHelper.clamp(statusBar.size, sizeRule.minSize(), sizeRule.maxSize());
+
+ float x = barAnchor.isRight() ?
+ anchorPosition.x() + currSize * widthPerSize :
+ anchorPosition.x() - currSize * widthPerSize - statusBar.size * widthPerSize;
+ statusBar.setX(MathHelper.ceil(x) + offsetX);
+
+ int y = barAnchor.isUp() ?
+ anchorPosition.y() - (row + 1) * (statusBar.getHeight() + 1) :
+ anchorPosition.y() + row * (statusBar.getHeight() + 1);
+ statusBar.setY(y);
+
+ statusBar.setWidth(MathHelper.floor(statusBar.size * widthPerSize) - lessWidth);
+ currSize += statusBar.size;
+ statusBar.gridX = i;
+ statusBar.gridY = row;
+
+ }
+ }
+
+ }
+ }
+
+ public boolean render(DrawContext context, int scaledWidth, int scaledHeight) {
+ var player = client.player;
+ if (!SkyblockerConfigManager.get().general.bars.enableBars || player == null || Utils.isInTheRift())
+ return false;
+
+ Collection<StatusBar> barCollection = statusBars.values();
+ for (StatusBar value : barCollection) {
+ value.render(context, -1, -1, client.getLastFrameDuration());
+ }
+ for (StatusBar statusBar : barCollection) {
+ if (statusBar.showText()) statusBar.renderText(context);
+ }
+ StatusBarTracker.Resource health = statusBarTracker.getHealth();
+ statusBars.get("health").updateValues(health.value() / (float) health.max(), health.overflow() / (float) health.max(), health.value());
+
+ StatusBarTracker.Resource intelligence = statusBarTracker.getMana();
+ statusBars.get("intelligence").updateValues(intelligence.value() / (float) intelligence.max(), intelligence.overflow() / (float) intelligence.max(), intelligence.value());
+ int defense = statusBarTracker.getDefense();
+ statusBars.get("defense").updateValues(defense / (defense + 100.f), 0, defense);
+ statusBars.get("experience").updateValues(player.experienceProgress, 0, player.experienceLevel);
+ return true;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBar.java b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBar.java
new file mode 100644
index 00000000..f8109852
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBar.java
@@ -0,0 +1,353 @@
+package de.hysky.skyblocker.skyblock.fancybars;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.render.RenderHelper;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.*;
+import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
+import net.minecraft.client.gui.widget.ClickableWidget;
+import net.minecraft.client.gui.widget.Widget;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.*;
+import java.util.function.Consumer;
+
+public class StatusBar implements Widget, Drawable, Element, Selectable {
+
+ private static final Identifier BAR_FILL = new Identifier(SkyblockerMod.NAMESPACE, "bars/bar_fill");
+ private static final Identifier BAR_BACK = new Identifier(SkyblockerMod.NAMESPACE, "bars/bar_back");
+
+
+ /* public static final Codec<StatusBar> CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ Codec.INT.fieldOf("size").forGetter(bar -> bar.size),
+ Codec.INT.fieldOf("x").forGetter(bar -> bar.gridX),
+ Codec.INT.fieldOf("y").forGetter(bar -> bar.gridY),
+ Codec.STRING.listOf().fieldOf("colors").xmap(
+ strings -> strings.stream().map(s -> Integer.parseInt(s, 16)).map(Color::new).toArray(Color[]::new),
+ colors -> Arrays.stream(colors).map(color -> Integer.toHexString(color.getRGB())).toList())
+ .forGetter(StatusBar::getColors),
+ Codec.STRING.optionalFieldOf("text_color").xmap(
+ s -> {
+ if (s.isPresent()) {
+ return Optional.of(new Color(Integer.parseInt(s.get(), 16)));
+ } else return Optional.empty();
+ },
+ o -> o.map(object -> Integer.toHexString(((Color) object).getRGB())))
+ .forGetter(bar -> {
+ if (bar.getTextColor() != null) {
+ return Optional.of(bar.getTextColor());
+ } else return Optional.empty();
+ }),
+ Codec.BOOL.optionalFieldOf("show_text", true).forGetter(StatusBar::showText),
+ Codec.STRING.fieldOf("icon_position").xmap(
+ IconPosition::valueOf,
+ Enum::toString
+ ).forGetter(bar -> bar.iconPosition)
+ )
+
+ .apply(instance, ));*/
+
+ private final Identifier icon;
+
+ public Color[] getColors() {
+ return colors;
+ }
+
+ public boolean hasOverflow() {
+ return hasOverflow;
+ }
+
+ public @Nullable Color getTextColor() {
+ return textColor;
+ }
+
+ private Color[] colors;
+ private final boolean hasOverflow;
+
+ public void setTextColor(@Nullable Color textColor) {
+ this.textColor = textColor;
+ }
+
+ private @Nullable Color textColor;
+
+ public Text getName() {
+ return name;
+ }
+
+ private final Text name;
+
+ private @Nullable OnClick onClick = null;
+ public int gridX = 0;
+ public int gridY = 0;
+ public @Nullable BarPositioner.BarAnchor anchor = null;
+
+ public int size = 1;
+ private int width = 0;
+
+ public float fill = 0;
+ public float overflowFill = 0;
+ public boolean ghost = false;
+
+ private Object value = "";
+
+ private int x = 0;
+ private int y = 0;
+
+ private IconPosition iconPosition = IconPosition.LEFT;
+ private boolean showText = true;
+
+ public StatusBar(Identifier icon, Color[] colors, boolean hasOverflow, @Nullable Color textColor, Text name) {
+ this.icon = icon;
+ this.colors = colors;
+ this.hasOverflow = hasOverflow;
+ this.textColor = textColor;
+ this.name = name;
+ }
+
+ public StatusBar(Identifier icon, Color[] colors, boolean hasOverflow, @Nullable Color textColor) {
+ this(icon, colors, hasOverflow, textColor, Text.empty());
+ }
+
+ @Override
+ public void render(DrawContext context, int mouseX, int mouseY, float delta) {
+ if (width <= 0) return;
+ // half works lol. only puts transparency on the filler of the bar
+ if (ghost) context.setShaderColor(1f, 1f, 1f, 0.25f);
+ switch (iconPosition) {
+ case LEFT -> context.drawGuiTexture(icon, x, y, 9, 9);
+ case RIGHT -> context.drawGuiTexture(icon, x + width - 9, y, 9, 9);
+ }
+
+ int barWith = iconPosition.equals(IconPosition.OFF) ? width : width - 10;
+ int barX = iconPosition.equals(IconPosition.LEFT) ? x + 10 : x;
+ context.drawGuiTexture(BAR_BACK, barX, y + 1, barWith, 7);
+ RenderHelper.renderNineSliceColored(context, BAR_FILL, barX + 1, y + 2, (int) ((barWith - 2) * fill), 5, colors[0]);
+
+
+ if (hasOverflow && overflowFill > 0) {
+ RenderHelper.renderNineSliceColored(context, BAR_FILL, barX + 1, y + 2, (int) ((barWith - 2) * overflowFill), 5, colors[1]);
+ }
+ if (ghost) context.setShaderColor(1f, 1f, 1f, 1f);
+ //context.drawText(MinecraftClient.getInstance().textRenderer, gridX + " " + gridY + " s:" + size , x, y-9, Colors.WHITE, true);
+ }
+
+ public void updateValues(float fill, float overflowFill, Object text) {
+ this.value = text;
+ this.fill = fill;
+ this.overflowFill = overflowFill;
+ }
+
+ public void renderText(DrawContext context) {
+ TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+ int barWith = iconPosition.equals(IconPosition.OFF) ? width : width - 10;
+ int barX = iconPosition.equals(IconPosition.LEFT) ? x + 11 : x;
+ String text = this.value.toString();
+ int x = barX + (barWith - textRenderer.getWidth(text)) / 2;
+ int y = this.y - 3;
+
+ final int[] offsets = new int[]{-1, 1};
+ for (int i : offsets) {
+ context.drawText(textRenderer, text, x + i, y, 0, false);
+ context.drawText(textRenderer, text, x, y + i, 0, false);
+ }
+ context.drawText(textRenderer, text, x, y, (textColor == null ? colors[0] : textColor).getRGB(), false);
+ }
+
+ public void renderCursor(DrawContext context, int mouseX, int mouseY, float delta) {
+ int temp_x = x;
+ int temp_y = y;
+ int temp_width = width;
+ boolean temp_ghost = ghost;
+
+ x = mouseX;
+ y = mouseY;
+ width = 100;
+ ghost = false;
+
+ render(context, mouseX, mouseY, delta);
+
+ x = temp_x;
+ y = temp_y;
+ width = temp_width;
+ ghost = temp_ghost;
+ }
+
+ // GUI shenanigans
+
+ @Override
+ public void setX(int x) {
+ this.x = x;
+ }
+
+ @Override
+ public void setY(int y) {
+ this.y = y;
+ }
+
+ @Override
+ public int getX() {
+ return x;
+ }
+
+ @Override
+ public int getY() {
+ return y;
+ }
+
+ @Override
+ public int getWidth() {
+ return width;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ @Override
+ public int getHeight() {
+ return 9;
+ }
+
+ @Override
+ public ScreenRect getNavigationFocus() {
+ return Widget.super.getNavigationFocus();
+ }
+
+ @Override
+ public boolean isMouseOver(double mouseX, double mouseY) {
+ return mouseX >= x && mouseX <= x + getWidth() && mouseY >= y && mouseY <= y + getHeight();
+ }
+
+ @Override
+ public void forEachChild(Consumer<ClickableWidget> consumer) {
+ }
+
+ @Override
+ public void setFocused(boolean focused) {
+ }
+
+ @Override
+ public boolean isFocused() {
+ return false;
+ }
+
+ @Override
+ public SelectionType getType() {
+ return SelectionType.NONE;
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (!isMouseOver(mouseX, mouseY)) return false;
+ if (onClick != null) {
+ onClick.onClick(this, button, (int) mouseX, (int) mouseY);
+ }
+ return true;
+ }
+
+ public void setOnClick(@Nullable OnClick onClick) {
+ this.onClick = onClick;
+ }
+
+ @Override
+ public void appendNarrations(NarrationMessageBuilder builder) {
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("name", name)
+ .append("gridX", gridX)
+ .append("gridY", gridY)
+ .append("size", size)
+ .append("x", x)
+ .append("y", y)
+ .append("width", width)
+ .append("anchor", anchor)
+ .toString();
+ }
+
+ public IconPosition getIconPosition() {
+ return iconPosition;
+ }
+
+ public void setIconPosition(IconPosition iconPosition) {
+ this.iconPosition = iconPosition;
+ }
+
+ public boolean showText() {
+ return showText;
+ }
+
+ public void setShowText(boolean showText) {
+ this.showText = showText;
+ }
+
+ public enum IconPosition {
+ LEFT,
+ RIGHT,
+ OFF
+ }
+
+ @FunctionalInterface
+ public interface OnClick {
+
+ void onClick(StatusBar statusBar, int button, int mouseX, int mouseY);
+ }
+
+ public void loadFromJson(JsonObject object) {
+ // Make colors optional, so it's easy to reset to default
+ if (object.has("colors")) {
+ JsonArray colors1 = object.get("colors").getAsJsonArray();
+ if (colors1.size() < 2 && hasOverflow) {
+ throw new IllegalStateException("Missing second color of bar that has overflow");
+ }
+ Color[] newColors = new Color[colors1.size()];
+ for (int i = 0; i < colors1.size(); i++) {
+ JsonElement jsonElement = colors1.get(i);
+ newColors[i] = new Color(Integer.parseInt(jsonElement.getAsString(), 16));
+ }
+ this.colors = newColors;
+ }
+
+ if (object.has("text_color")) this.textColor = new Color(Integer.parseInt(object.get("text_color").getAsString(), 16));
+
+ String maybeAnchor = object.get("anchor").getAsString().trim();
+ this.anchor = maybeAnchor.equals("null") ? null : BarPositioner.BarAnchor.valueOf(maybeAnchor);
+ this.size = object.get("size").getAsInt();
+ this.gridX = object.get("x").getAsInt();
+ this.gridY = object.get("y").getAsInt();
+ // these are optional too, why not
+ if (object.has("icon_position")) this.iconPosition = IconPosition.valueOf(object.get("icon_position").getAsString().trim());
+ if (object.has("show_text")) this.showText = object.get("show_text").getAsBoolean();
+
+ }
+
+ public JsonObject toJson() {
+ JsonObject object = new JsonObject();
+ JsonArray colors1 = new JsonArray();
+ for (Color color : colors) {
+ colors1.add(Integer.toHexString(color.getRGB()).substring(2));
+ }
+ object.add("colors", colors1);
+ if (textColor != null) {
+ object.addProperty("text_color", Integer.toHexString(textColor.getRGB()).substring(2));
+ }
+ object.addProperty("size", size);
+ if (anchor != null) {
+ object.addProperty("anchor", anchor.toString());
+ } else object.addProperty("anchor", "null");
+ object.addProperty("x", gridX);
+ object.addProperty("y", gridY);
+ object.addProperty("icon_position", iconPosition.toString());
+ object.addProperty("show_text", showText);
+ return object;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBarsConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBarsConfigScreen.java
new file mode 100644
index 00000000..d1009a25
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBarsConfigScreen.java
@@ -0,0 +1,365 @@
+package de.hysky.skyblocker.skyblock.fancybars;
+
+import it.unimi.dsi.fastutil.Pair;
+import it.unimi.dsi.fastutil.objects.ObjectBooleanMutablePair;
+import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.ScreenPos;
+import net.minecraft.client.gui.ScreenRect;
+import net.minecraft.client.gui.navigation.NavigationAxis;
+import net.minecraft.client.gui.navigation.NavigationDirection;
+import net.minecraft.client.gui.screen.PopupScreen;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.widget.ButtonWidget;
+import net.minecraft.client.util.Window;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+import org.jetbrains.annotations.Nullable;
+import org.lwjgl.glfw.GLFW;
+import de.hysky.skyblocker.skyblock.fancybars.BarPositioner.BarLocation;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class StatusBarsConfigScreen extends Screen {
+ private static final Identifier HOTBAR_TEXTURE = new Identifier("hud/hotbar");
+
+ public static final long RESIZE_CURSOR = GLFW.glfwCreateStandardCursor(GLFW.GLFW_HRESIZE_CURSOR);
+
+ private final Map<ScreenRect, BarLocation> rectToBarLocation = new HashMap<>();
+ private static final int HOTBAR_WIDTH = 182;
+
+ private @Nullable StatusBar cursorBar = null;
+
+ public StatusBarsConfigScreen() {
+ super(Text.of("Status Bars Config"));
+ FancyStatusBars.updatePositions();
+ }
+
+ private BarLocation currentInsertLocation = new BarLocation(null, 0, 0);
+
+ private final Pair<BarLocation, Boolean> resizeHover = new ObjectBooleanMutablePair<>(BarLocation.NULL, false);
+
+ private final Pair<BarLocation, BarLocation> resizedBars = ObjectObjectMutablePair.of(BarLocation.NULL, BarLocation.NULL);
+ private boolean resizing = false;
+
+ private EditBarWidget editBarWidget;
+
+ // prioritize left and right cuz they are much smaller space than up and down
+ private static final NavigationDirection[] DIRECTION_CHECK_ORDER = new NavigationDirection[]{NavigationDirection.LEFT, NavigationDirection.RIGHT, NavigationDirection.UP, NavigationDirection.DOWN};
+
+ @Override
+ public void render(DrawContext context, int mouseX, int mouseY, float delta) {
+ /*for (ScreenRect screenRect : meaningFullName.keySet()) {
+ context.fillGradient(screenRect.position().x(), screenRect.position().y(), screenRect.position().x() + screenRect.width(), screenRect.position().y() + screenRect.height(), 0xFFFF0000, 0xFF0000FF);
+ }*/
+ super.render(context, mouseX, mouseY, delta);
+ context.drawGuiTexture(HOTBAR_TEXTURE, width / 2 - HOTBAR_WIDTH / 2, height - 22, HOTBAR_WIDTH, 22);
+ editBarWidget.render(context, mouseX, mouseY, delta);
+
+ ScreenRect mouseRect = new ScreenRect(new ScreenPos(mouseX - 1, mouseY - 1), 3, 3);
+ assert client != null;
+ Window window = client.getWindow();
+
+ if (cursorBar != null) {
+ cursorBar.renderCursor(context, mouseX, mouseY, delta);
+ boolean inserted = false;
+ rectLoop:
+ for (ScreenRect screenRect : rectToBarLocation.keySet()) {
+ for (NavigationDirection direction : DIRECTION_CHECK_ORDER) {
+ boolean overlaps = screenRect.getBorder(direction).add(direction).overlaps(mouseRect);
+
+ if (overlaps) {
+ BarLocation barSnap = rectToBarLocation.get(screenRect);
+ if (barSnap.barAnchor() == null) break;
+ if (direction.getAxis().equals(NavigationAxis.VERTICAL)) {
+ int neighborInsertY = getNeighborInsertY(barSnap, !direction.isPositive());
+ if (!currentInsertLocation.equals(barSnap.barAnchor(), barSnap.x(), neighborInsertY)) {
+ if (cursorBar.anchor != null)
+ FancyStatusBars.barPositioner.removeBar(cursorBar.anchor, cursorBar.gridY, cursorBar);
+ FancyStatusBars.barPositioner.addRow(barSnap.barAnchor(), neighborInsertY);
+ FancyStatusBars.barPositioner.addBar(barSnap.barAnchor(), neighborInsertY, cursorBar);
+ currentInsertLocation = BarLocation.of(cursorBar);
+ inserted = true;
+ }
+ } else {
+ int neighborInsertX = getNeighborInsertX(barSnap, direction.isPositive());
+ if (!currentInsertLocation.equals(barSnap.barAnchor(), neighborInsertX, barSnap.y())) {
+ if (cursorBar.anchor != null)
+ FancyStatusBars.barPositioner.removeBar(cursorBar.anchor, cursorBar.gridY, cursorBar);
+ FancyStatusBars.barPositioner.addBar(barSnap.barAnchor(), barSnap.y(), neighborInsertX, cursorBar);
+ currentInsertLocation = BarLocation.of(cursorBar);
+ inserted = true;
+ }
+ }
+ break rectLoop;
+ }
+ }
+ }
+ if (inserted) {
+ FancyStatusBars.updatePositions();
+ return;
+ }
+ // check for hovering empty anchors
+ for (BarPositioner.BarAnchor barAnchor : BarPositioner.BarAnchor.allAnchors()) {
+ if (FancyStatusBars.barPositioner.getRowCount(barAnchor) != 0) continue;
+ ScreenRect anchorHitbox = barAnchor.getAnchorHitbox(barAnchor.getAnchorPosition(width, height));
+ context.fill(anchorHitbox.getLeft(), anchorHitbox.getTop(), anchorHitbox.getRight(), anchorHitbox.getBottom(), 0x99FFFFFF);
+ if (anchorHitbox.overlaps(mouseRect) && currentInsertLocation.barAnchor() != barAnchor) {
+ if (cursorBar.anchor != null)
+ FancyStatusBars.barPositioner.removeBar(cursorBar.anchor, cursorBar.gridY, cursorBar);
+ FancyStatusBars.barPositioner.addRow(barAnchor);
+ FancyStatusBars.barPositioner.addBar(barAnchor, 0, cursorBar);
+ currentInsertLocation = BarLocation.of(cursorBar);
+ FancyStatusBars.updatePositions();
+ }
+ }
+ } else {
+ if (resizing) { // actively resizing one or 2 bars
+ int middleX;
+
+ BarLocation left = resizedBars.left();
+ BarLocation right = resizedBars.right();
+ boolean hasRight = right.barAnchor() != null;
+ boolean hasLeft = left.barAnchor() != null;
+ BarPositioner.BarAnchor barAnchor;
+ if (!hasRight) {
+ barAnchor = left.barAnchor();
+ StatusBar bar = FancyStatusBars.barPositioner.getBar(barAnchor, left.y(), left.x());
+ middleX = bar.getX() + bar.getWidth();
+ } else {
+ barAnchor = right.barAnchor();
+ middleX = FancyStatusBars.barPositioner.getBar(barAnchor, right.y(), right.x()).getX();
+ }
+
+ boolean doResize = true;
+ StatusBar rightBar = null;
+ StatusBar leftBar = null;
+
+ BarPositioner.SizeRule sizeRule = barAnchor.getSizeRule();
+
+ float widthPerSize;
+ if (sizeRule.isTargetSize())
+ widthPerSize = (float) sizeRule.totalWidth() / sizeRule.targetSize();
+ else
+ widthPerSize = sizeRule.widthPerSize();
+
+ // resize towards the left
+ if (mouseX < middleX) {
+ if (middleX - mouseX > widthPerSize / .75f) {
+ if (hasRight) {
+ rightBar = FancyStatusBars.barPositioner.getBar(barAnchor, right.y(), right.x());
+ if (rightBar.size + 1 > sizeRule.maxSize()) doResize = false;
+ }
+ if (hasLeft) {
+ leftBar = FancyStatusBars.barPositioner.getBar(barAnchor, left.y(), left.x());
+ if (leftBar.size - 1 < sizeRule.minSize()) doResize = false;
+ }
+
+ if (doResize) {
+ if (hasRight) rightBar.size++;
+ if (hasLeft) leftBar.size--;
+ FancyStatusBars.updatePositions();
+ }
+ }
+ } else { // towards the right
+ if (mouseX - middleX > widthPerSize / .75f) {
+ if (hasRight) {
+ rightBar = FancyStatusBars.barPositioner.getBar(barAnchor, right.y(), right.x());
+ if (rightBar.size - 1 < sizeRule.minSize()) doResize = false;
+ }
+ if (hasLeft) {
+ leftBar = FancyStatusBars.barPositioner.getBar(barAnchor, left.y(), left.x());
+ if (leftBar.size + 1 > sizeRule.maxSize()) doResize = false;
+ }
+
+ if (doResize) {
+ if (hasRight) rightBar.size--;
+ if (hasLeft) leftBar.size++;
+ FancyStatusBars.updatePositions();
+ }
+ }
+ }
+
+ } else { // hovering bars
+ rectLoop:
+ for (ScreenRect screenRect : rectToBarLocation.keySet()) {
+ for (NavigationDirection direction : new NavigationDirection[]{NavigationDirection.LEFT, NavigationDirection.RIGHT}) {
+ boolean overlaps = screenRect.getBorder(direction).add(direction).overlaps(mouseRect);
+
+ if (overlaps && !editBarWidget.isMouseOver(mouseX, mouseY)) {
+ BarLocation barLocation = rectToBarLocation.get(screenRect);
+ if (barLocation.barAnchor() == null) break;
+ boolean right = direction.equals(NavigationDirection.RIGHT);
+ // can't resize on the edge of a target size row!
+ if (barLocation.barAnchor().getSizeRule().isTargetSize() && !FancyStatusBars.barPositioner.hasNeighbor(barLocation.barAnchor(), barLocation.y(), barLocation.x(), right)) {
+ break;
+ }
+ if (!barLocation.barAnchor().getSizeRule().isTargetSize() && barLocation.x() == 0 && barLocation.barAnchor().isRight() != right)
+ break;
+ resizeHover.first(barLocation);
+ resizeHover.right(right);
+ GLFW.glfwSetCursor(window.getHandle(), RESIZE_CURSOR);
+ break rectLoop;
+ } else {
+ resizeHover.first(BarLocation.NULL);
+ GLFW.glfwSetCursor(window.getHandle(), 0);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static int getNeighborInsertX(BarLocation barLocation, boolean right) {
+ BarPositioner.BarAnchor barAnchor = barLocation.barAnchor();
+ int gridX = barLocation.x();
+ if (barAnchor == null) return 0;
+ if (right) {
+ return barAnchor.isRight() ? gridX + 1 : gridX;
+ } else {
+ return barAnchor.isRight() ? gridX : gridX + 1;
+ }
+ }
+
+ private static int getNeighborInsertY(BarLocation barLocation, boolean up) {
+ BarPositioner.BarAnchor barAnchor = barLocation.barAnchor();
+ int gridY = barLocation.y();
+ if (barAnchor == null) return 0;
+ if (up) {
+ return barAnchor.isUp() ? gridY + 1 : gridY;
+ } else {
+ return barAnchor.isUp() ? gridY : gridY + 1;
+ }
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+ editBarWidget = new EditBarWidget(0, 0, this);
+ editBarWidget.visible = false;
+ addSelectableChild(editBarWidget); // rendering separately to have it above hotbar
+ Collection<StatusBar> values = FancyStatusBars.statusBars.values();
+ values.forEach(this::setup);
+ checkNullAnchor(values);
+ updateScreenRects();
+ this.addDrawableChild(ButtonWidget.builder(Text.literal("?"),
+ button -> {
+ assert client != null;
+ client.setScreen(new PopupScreen.Builder(this, Text.translatable("skyblocker.bars.config.explanationTitle"))
+ .button(Text.translatable("gui.ok"), PopupScreen::close)
+ .message(Text.translatable("skyblocker.bars.config.explanation"))
+ .build());
+ })
+ .dimensions(width - 20, (height - 15) / 2, 15, 15)
+ .build());
+ }
+
+ private void setup(StatusBar statusBar) {
+ this.addDrawableChild(statusBar);
+ statusBar.setOnClick(this::onBarClick);
+ }
+
+ private static void checkNullAnchor(Iterable<StatusBar> bars) {
+ int offset = 0;
+ for (StatusBar statusBar : bars) {
+ if (statusBar.anchor == null) {
+ statusBar.setX(5);
+ statusBar.setY(50 + offset);
+ statusBar.setWidth(30);
+ offset += statusBar.getHeight();
+ }
+ }
+ }
+
+ @Override
+ public void removed() {
+ super.removed();
+ FancyStatusBars.statusBars.values().forEach(statusBar -> statusBar.setOnClick(null));
+ if (cursorBar != null) cursorBar.ghost = false;
+ FancyStatusBars.updatePositions();
+ assert client != null;
+ GLFW.glfwSetCursor(client.getWindow().getHandle(), 0);
+ FancyStatusBars.saveBarConfig();
+ }
+
+ @Override
+ public boolean shouldPause() {
+ return false;
+ }
+
+ private void onBarClick(StatusBar statusBar, int button, int mouseX, int mouseY) {
+ if (button == 0) {
+ cursorBar = statusBar;
+ cursorBar.ghost = true;
+ if (statusBar.anchor != null)
+ FancyStatusBars.barPositioner.removeBar(statusBar.anchor, statusBar.gridY, statusBar);
+ FancyStatusBars.updatePositions();
+ cursorBar.setX(width + 5); // send it to limbo lol
+ updateScreenRects();
+ } else if (button == 1) {
+ int x = Math.min(mouseX - 1, width - editBarWidget.getWidth());
+ int y = Math.min(mouseY - 1, height - editBarWidget.getHeight());
+ editBarWidget.visible = true;
+ editBarWidget.setStatusBar(statusBar);
+ editBarWidget.setX(x);
+ editBarWidget.setY(y);
+ }
+ }
+
+ private void updateScreenRects() {
+ rectToBarLocation.clear();
+ FancyStatusBars.statusBars.values().forEach(statusBar1 -> {
+ if (statusBar1.anchor == null) return;
+ rectToBarLocation.put(
+ new ScreenRect(new ScreenPos(statusBar1.getX(), statusBar1.getY()), statusBar1.getWidth(), statusBar1.getHeight()),
+ BarLocation.of(statusBar1));
+ });
+ }
+
+ @Override
+ public boolean mouseReleased(double mouseX, double mouseY, int button) {
+ if (cursorBar != null) {
+ cursorBar.ghost = false;
+ cursorBar = null;
+ FancyStatusBars.updatePositions();
+ checkNullAnchor(FancyStatusBars.statusBars.values());
+ updateScreenRects();
+ return true;
+ } else if (resizing) {
+ resizing = false;
+ resizedBars.left(BarLocation.NULL);
+ resizedBars.right(BarLocation.NULL);
+ updateScreenRects();
+ return true;
+ }
+ return super.mouseReleased(mouseX, mouseY, button);
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ BarLocation first = resizeHover.first();
+ // want the right click thing to have priority
+ if (!editBarWidget.isMouseOver(mouseX, mouseY) && button == 0 && !first.equals(BarLocation.NULL)) {
+ BarPositioner.BarAnchor barAnchor = first.barAnchor();
+ assert barAnchor != null;
+ if (resizeHover.right()) {
+ resizedBars.left(first);
+
+ if (FancyStatusBars.barPositioner.hasNeighbor(barAnchor, first.y(), first.x(), true)) {
+ resizedBars.right(new BarLocation(barAnchor, first.x() + (barAnchor.isRight() ? 1 : -1), first.y()));
+ } else resizedBars.right(BarLocation.NULL);
+ } else {
+ resizedBars.right(first);
+
+ if (FancyStatusBars.barPositioner.hasNeighbor(barAnchor, first.y(), first.x(), false)) {
+ resizedBars.left(new BarLocation(barAnchor, first.x() + (barAnchor.isRight() ? -1 : 1), first.y()));
+ } else resizedBars.left(BarLocation.NULL);
+ }
+ resizing = true;
+ return true;
+ }
+ return super.mouseClicked(mouseX, mouseY, button);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
index e39b5364..da179d0e 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
@@ -4,6 +4,7 @@ import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.logging.LogUtils;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.mixin.accessor.BeaconBlockEntityRendererInvoker;
+import de.hysky.skyblocker.mixin.accessor.DrawContextInvoker;
import de.hysky.skyblocker.utils.render.culling.OcclusionCulling;
import de.hysky.skyblocker.utils.render.title.Title;
import de.hysky.skyblocker.utils.render.title.TitleContainer;
@@ -12,8 +13,11 @@ import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.fabric.api.event.Event;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.render.*;
import net.minecraft.client.render.VertexFormat.DrawMode;
+import net.minecraft.client.texture.Scaling;
+import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.sound.SoundEvents;
import net.minecraft.text.OrderedText;
@@ -28,6 +32,7 @@ import org.joml.Vector3f;
import org.lwjgl.opengl.GL11;
import org.slf4j.Logger;
+import java.awt.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
@@ -370,6 +375,72 @@ public class RenderHelper {
return x >= x1 && x <= x2 && y >= y1 && y <= y2;
}
+ private static void drawSprite(DrawContext context, Sprite sprite, int i, int j, int k, int l, int x, int y, int z, int width, int height, float red, float green, float blue, float alpha) {
+ if (width == 0 || height == 0) {
+ return;
+ }
+ ((DrawContextInvoker) context).invokeDrawTexturedQuad(sprite.getAtlasId(), x, x + width, y, y + height, z, sprite.getFrameU((float)k / (float)i), sprite.getFrameU((float)(k + width) / (float)i), sprite.getFrameV((float)l / (float)j), sprite.getFrameV((float)(l + height) / (float)j), red, green, blue, alpha);
+ }
+ private static void drawSpriteTiled(DrawContext context, Sprite sprite, int x, int y, int z, int width, int height, int i, int j, int tileWidth, int tileHeight, int k, int l, float red, float green, float blue, float alpha) {
+ if (width <= 0 || height <= 0) {
+ return;
+ }
+ if (tileWidth <= 0 || tileHeight <= 0) {
+ throw new IllegalArgumentException("Tiled sprite texture size must be positive, got " + tileWidth + "x" + tileHeight);
+ }
+ for (int m = 0; m < width; m += tileWidth) {
+ int n = Math.min(tileWidth, width - m);
+ for (int o = 0; o < height; o += tileHeight) {
+ int p = Math.min(tileHeight, height - o);
+ drawSprite(context, sprite, k, l, i, j, x + m, y + o, z, n, p, red, green, blue, alpha);
+ }
+ }
+ }
+
+ public static void renderNineSliceColored(DrawContext context, Identifier texture, int x, int y, int width, int height, float red, float green, float blue, float alpha) {
+ Sprite sprite = MinecraftClient.getInstance().getGuiAtlasManager().getSprite(texture);
+ Scaling scaling = MinecraftClient.getInstance().getGuiAtlasManager().getScaling(sprite);
+ if (!(scaling instanceof Scaling.NineSlice nineSlice)) return;
+ Scaling.NineSlice.Border border = nineSlice.border();
+ int z = 0;
+
+ int i = Math.min(border.left(), width / 2);
+ int j = Math.min(border.right(), width / 2);
+ int k = Math.min(border.top(), height / 2);
+ int l = Math.min(border.bottom(), height / 2);
+ if (width == nineSlice.width() && height == nineSlice.height()) {
+ drawSprite(context, sprite, nineSlice.width(), nineSlice.height(), 0, 0, x, y, z, width, height, red, green, blue, alpha);
+ return;
+ }
+ if (height == nineSlice.height()) {
+ drawSprite(context, sprite, nineSlice.width(), nineSlice.height(), 0, 0, x, y, z, i, height, red, green, blue, alpha);
+ drawSpriteTiled(context, sprite, x + i, y, z, width - j - i, height, i, 0, nineSlice.width() - j - i, nineSlice.height(), nineSlice.width(), nineSlice.height(), red, green, blue, alpha);
+ drawSprite(context, sprite, nineSlice.width(), nineSlice.height(), nineSlice.width() - j, 0, x + width - j, y, z, j, height, red, green, blue, alpha);
+ return;
+ }
+ if (width == nineSlice.width()) {
+ drawSprite(context, sprite, nineSlice.width(), nineSlice.height(), 0, 0, x, y, z, width, k, red, green, blue, alpha);
+ drawSpriteTiled(context, sprite, x, y + k, z, width, height - l - k, 0, k, nineSlice.width(), nineSlice.height() - l - k, nineSlice.width(), nineSlice.height(), red, green, blue, alpha);
+ drawSprite(context, sprite, nineSlice.width(), nineSlice.height(), 0, nineSlice.height() - l, x, y + height - l, z, width, l, red, green, blue, alpha);
+ return;
+ }
+ drawSprite(context, sprite, nineSlice.width(), nineSlice.height(), 0, 0, x, y, z, i, k, red, green, blue, alpha);
+ drawSpriteTiled(context, sprite, x + i, y, z, width - j - i, k, i, 0, nineSlice.width() - j - i, k, nineSlice.width(), nineSlice.height(), red, green, blue, alpha);
+ drawSprite(context, sprite, nineSlice.width(), nineSlice.height(), nineSlice.width() - j, 0, x + width - j, y, z, j, k, red, green, blue, alpha);
+ drawSprite(context, sprite, nineSlice.width(), nineSlice.height(), 0, nineSlice.height() - l, x, y + height - l, z, i, l, red, green, blue, alpha);
+ drawSpriteTiled(context, sprite, x + i, y + height - l, z, width - j - i, l, i, nineSlice.height() - l, nineSlice.width() - j - i, l, nineSlice.width(), nineSlice.height(), red, green, blue, alpha);
+ drawSprite(context, sprite, nineSlice.width(), nineSlice.height(), nineSlice.width() - j, nineSlice.height() - l, x + width - j, y + height - l, z, j, l, red, green, blue, alpha);
+ drawSpriteTiled(context, sprite, x, y + k, z, i, height - l - k, 0, k, i, nineSlice.height() - l - k, nineSlice.width(), nineSlice.height(), red, green, blue, alpha);
+ drawSpriteTiled(context, sprite, x + i, y + k, z, width - j - i, height - l - k, i, k, nineSlice.width() - j - i, nineSlice.height() - l - k, nineSlice.width(), nineSlice.height(), red, green, blue, alpha);
+ drawSpriteTiled(context, sprite, x + width - j, y + k, z, i, height - l - k, nineSlice.width() - j, k, j, nineSlice.height() - l - k, nineSlice.width(), nineSlice.height(), red, green, blue, alpha);
+ }
+
+ private static final float[] colorBuffer = new float[4];
+ public static void renderNineSliceColored(DrawContext context, Identifier texture, int x, int y, int width, int height, Color color) {
+ color.getComponents(colorBuffer);
+ renderNineSliceColored(context, texture, x, y, width, height, colorBuffer[0],colorBuffer[1],colorBuffer[2],colorBuffer[3]);
+ }
+
// TODO Get rid of reflection once the new Sodium is released
private static MethodHandle getDeferredRenderTaskHandle() {
try {
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractPopupScreen.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractPopupScreen.java
index 2bd15955..e7a3e8b2 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractPopupScreen.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractPopupScreen.java
@@ -3,10 +3,16 @@ package de.hysky.skyblocker.utils.render.gui;
import com.mojang.blaze3d.platform.GlConst;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
+import org.jetbrains.annotations.Nullable;
+import org.lwjgl.glfw.GLFW;
+
+import java.util.function.Consumer;
/**
* A more bare-bones version of Vanilla's Popup Screen. Meant to be extended.
@@ -57,4 +63,35 @@ public class AbstractPopupScreen extends Screen {
super.onDisplayed();
this.backgroundScreen.blur();
}
+
+ public static class EnterConfirmTextFieldWidget extends TextFieldWidget {
+
+ private final Runnable onEnter;
+
+ public EnterConfirmTextFieldWidget(TextRenderer textRenderer, int width, int height, Text text, Runnable onEnter) {
+ this(textRenderer, 0, 0, width, height, text, onEnter);
+ }
+
+ public EnterConfirmTextFieldWidget(TextRenderer textRenderer, int x, int y, int width, int height, Text text,Runnable onEnter) {
+ this(textRenderer, x, y, width, height, null, text, onEnter);
+ }
+
+ public EnterConfirmTextFieldWidget(TextRenderer textRenderer, int x, int y, int width, int height, @Nullable TextFieldWidget copyFrom, Text text, Runnable onEnter) {
+ super(textRenderer, x, y, width, height, copyFrom, text);
+ this.onEnter = onEnter;
+ }
+
+
+ @Override
+ public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
+ if (!super.keyPressed(keyCode, scanCode, modifiers)) {
+ if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) {
+ onEnter.run();
+ return true;
+ }
+ } else return true;
+ return false;
+ }
+ }
+
} \ No newline at end of file