diff --git a/src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java b/src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java index ac1d89666b..06bec73751 100644 --- a/src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java +++ b/src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java @@ -1,7 +1,7 @@ package de.hysky.skyblocker.config; -import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; import de.hysky.skyblocker.utils.render.RenderHelper; +import de.hysky.skyblocker.utils.render.gui.AbstractWidget; import it.unimi.dsi.fastutil.ints.IntIntMutablePair; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; @@ -13,129 +13,140 @@ /** * A screen for configuring the positions of HUD widgets. *

+ * Note: This is currently only used for title container. There is a new system for other HUD widgets, see {@link de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen} + *

* This class takes care of rendering the widgets, dragging them, and resetting their positions. * Create one subclass for each collection of HUD widgets that are displayed at the same time. * (i.e. one for dwarven mines, one for the end, etc.) See an implementation for an example. */ public abstract class HudConfigScreen extends Screen { - private final Screen parent; - private final List widgets; - - private HudWidget draggingWidget; - private double mouseClickRelativeX; - private double mouseClickRelativeY; - - /** - * Creates a new HudConfigScreen with the passed title, parent, and widget - * @param title the title of the screen - * @param parent the parent screen - * @param widget the widget to configure - */ - public HudConfigScreen(Text title, Screen parent, HudWidget widget) { - this(title, parent, List.of(widget)); - } - - /** - * Creates a new HudConfigScreen with the passed title, parent, and widgets - * @param title the title of the screen - * @param parent the parent screen - * @param widgets the widgets to configure - */ - public HudConfigScreen(Text title, Screen parent, List widgets) { - super(title); - this.parent = parent; - this.widgets = widgets; - resetPos(); - } - - @Override - public final void render(DrawContext context, int mouseX, int mouseY, float delta) { - super.render(context, mouseX, mouseY, delta); - renderWidget(context, widgets); - context.drawCenteredTextWithShadow(textRenderer, "Right Click To Reset Position", width / 2, height / 2, Color.GRAY.getRGB()); - } - - /** - * Renders the widgets using the default {@link HudWidget#render(DrawContext)} method. Override to change the behavior. - * @param context the context to render in - * @param widgets the widgets to render - */ - protected void renderWidget(DrawContext context, List widgets) { - for (HudWidget widget : widgets) { - widget.render(context); - } - } - - @Override - public final boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - if (button == 0 && draggingWidget != null) { - draggingWidget.setX((int) Math.clamp(mouseX - mouseClickRelativeX, 0, this.width - draggingWidget.getWidth())); - draggingWidget.setY((int) Math.clamp(mouseY - mouseClickRelativeY, 0, this.height - draggingWidget.getHeight())); - } - return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); - } - - @Override - public final boolean mouseClicked(double mouseX, double mouseY, int button) { - if (button == 0) { - for (HudWidget widget : widgets) { - if (RenderHelper.pointIsInArea(mouseX, mouseY, widget.getX(), widget.getY(), widget.getX() + widget.getWidth(), widget.getY() + widget.getHeight())) { - draggingWidget = widget; - mouseClickRelativeX = mouseX - widget.getX(); - mouseClickRelativeY = mouseY - widget.getY(); - break; - } - } - } else if (button == 1) { - resetPos(); - } - return super.mouseClicked(mouseX, mouseY, button); - } - - @Override - public final boolean mouseReleased(double mouseX, double mouseY, int button) { - draggingWidget = null; - return super.mouseReleased(mouseX, mouseY, button); - } - - /** - * Resets the positions of the widgets to the positions in the config. Override to change the behavior. - */ - protected void resetPos() { - List configPositions = getConfigPos(SkyblockerConfigManager.get()); - if (configPositions.size() != widgets.size()) { - throw new IllegalStateException("The number of positions (" + configPositions.size() + ") does not match the number of widgets (" + widgets.size() + ")"); - } - for (int i = 0; i < widgets.size(); i++) { - HudWidget widget = widgets.get(i); - IntIntMutablePair configPos = configPositions.get(i); - widget.setX(configPos.leftInt()); - widget.setY(configPos.rightInt()); - } - } - - /** - * Returns the positions of the widgets in the config - * @param config the config to get the positions from - * @return the positions of the widgets - */ - protected abstract List getConfigPos(SkyblockerConfig config); - - @Override - public final void close() { - SkyblockerConfig skyblockerConfig = SkyblockerConfigManager.get(); - savePos(skyblockerConfig, widgets); - SkyblockerConfigManager.save(); - - client.setScreen(parent); - } - - /** - * Saves the passed positions to the config. - *

- * NOTE: The parent class will call {@link SkyblockerConfigManager#save()} right after this method - * @param configManager the config so you don't have to get it - * @param widgets the widgets to save - */ - protected abstract void savePos(SkyblockerConfig configManager, List widgets); + protected final Screen parent; + protected final List widgets; + + private AbstractWidget draggingWidget; + private double mouseClickRelativeX; + private double mouseClickRelativeY; + + /** + * Creates a new HudConfigScreen with the passed title, parent, and widget + * + * @param title the title of the screen + * @param parent the parent screen + * @param widget the widget to configure + */ + public HudConfigScreen(Text title, Screen parent, AbstractWidget widget) { + this(title, parent, List.of(widget)); + } + + /** + * Creates a new HudConfigScreen with the passed title, parent, and widgets + * + * @param title the title of the screen + * @param parent the parent screen + * @param widgets the widgets to configure + */ + public HudConfigScreen(Text title, Screen parent, List widgets) { + super(title); + this.parent = parent; + this.widgets = widgets; + resetPos(); + } + + @Override + public final void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + renderWidget(context, widgets, delta); + context.drawCenteredTextWithShadow(textRenderer, "Right Click To Reset Position", width / 2, height / 2, Color.GRAY.getRGB()); + } + + /** + * Renders the widgets using the default {@link AbstractWidget#render(DrawContext, int, int, float)} method. Override to change the behavior. + * + * @param context the context to render in + * @param widgets the widgets to render + */ + protected void renderWidget(DrawContext context, List widgets, float delta) { + for (AbstractWidget widget : widgets) { + widget.render(context, -1, -1, delta); + } + } + + @Override + public final boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (button == 0 && draggingWidget != null) { + draggingWidget.setX((int) Math.clamp(mouseX - mouseClickRelativeX, 0, this.width - draggingWidget.getWidth()) - getWidgetXOffset(draggingWidget)); + draggingWidget.setY((int) Math.clamp(mouseY - mouseClickRelativeY, 0, this.height - draggingWidget.getHeight())); + } + return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + } + + @Override + public final boolean mouseClicked(double mouseX, double mouseY, int button) { + if (button == 0) { + for (AbstractWidget widget : widgets) { + if (RenderHelper.pointIsInArea(mouseX, mouseY, widget.getX() + getWidgetXOffset(widget), widget.getY(), widget.getX() + getWidgetXOffset(widget) + widget.getWidth(), widget.getY() + widget.getHeight())) { + draggingWidget = widget; + mouseClickRelativeX = mouseX - widget.getX() - getWidgetXOffset(widget); + mouseClickRelativeY = mouseY - widget.getY(); + break; + } + } + } else if (button == 1) { + resetPos(); + } + return super.mouseClicked(mouseX, mouseY, button); + } + + @Override + public final boolean mouseReleased(double mouseX, double mouseY, int button) { + draggingWidget = null; + return super.mouseReleased(mouseX, mouseY, button); + } + + protected int getWidgetXOffset(AbstractWidget widget) { + return 0; + } + + /** + * Resets the positions of the widgets to the positions in the config. Override to change the behavior. + */ + protected void resetPos() { + List configPositions = getConfigPos(SkyblockerConfigManager.get()); + if (configPositions.size() != widgets.size()) { + throw new IllegalStateException("The number of positions (" + configPositions.size() + ") does not match the number of widgets (" + widgets.size() + ")"); + } + for (int i = 0; i < widgets.size(); i++) { + AbstractWidget widget = widgets.get(i); + IntIntMutablePair configPos = configPositions.get(i); + widget.setX(configPos.leftInt()); + widget.setY(configPos.rightInt()); + } + } + + /** + * Returns the positions of the widgets in the config + * + * @param config the config to get the positions from + * @return the positions of the widgets + */ + protected abstract List getConfigPos(SkyblockerConfig config); + + @Override + public final void close() { + SkyblockerConfig skyblockerConfig = SkyblockerConfigManager.get(); + savePos(skyblockerConfig, widgets); + SkyblockerConfigManager.save(); + + client.setScreen(parent); + } + + /** + * Saves the passed positions to the config. + *

+ * NOTE: The parent class will call {@link SkyblockerConfigManager#save()} right after this method + * + * @param configManager the config so you don't have to get it + * @param widgets the widgets to save + */ + protected abstract void savePos(SkyblockerConfig configManager, List widgets); } diff --git a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java index 55ec50941e..deb981a180 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java @@ -131,10 +131,14 @@ public static class TitleContainer { public int y = 10; @SerialEntry - public Direction direction = Direction.HORIZONTAL; + public Direction direction = Direction.VERTICAL; @SerialEntry public Alignment alignment = Alignment.MIDDLE; + + public float getRenderScale() { + return titleContainerScale * 0.03f; + } } public enum Direction { @@ -147,7 +151,7 @@ public String toString() { } public enum Alignment { - LEFT, RIGHT, MIDDLE; + LEFT, MIDDLE, RIGHT; @Override public String toString() { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java index 90f7ddc19b..ee3f747a14 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java @@ -31,7 +31,6 @@ public DungeonMapConfigScreen(Screen parent) { @Override public void render(DrawContext context, int mouseX, int mouseY, float delta) { super.render(context, mouseX, mouseY, delta); - renderBackground(context, mouseX, mouseY, delta); renderHUDMap(context, mapX, mapY); renderHUDScore(context, scoreX, scoreY); context.drawCenteredTextWithShadow(textRenderer, "Right Click To Reset Position", width >> 1, height >> 1, Color.GRAY.getRGB()); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/EditBarWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/EditBarWidget.java index e3a94bcded..18c8905a06 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/EditBarWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/EditBarWidget.java @@ -1,5 +1,6 @@ package de.hysky.skyblocker.skyblock.fancybars; +import de.hysky.skyblocker.utils.EnumUtils; import it.unimi.dsi.fastutil.booleans.BooleanConsumer; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; @@ -211,7 +212,7 @@ public void setCurrent(T current) { @Override public void onClick(double mouseX, double mouseY) { - current = values[(current.ordinal() + 1) % values.length]; + current = EnumUtils.cycle(current); if (onChange != null) onChange.accept(current); super.onClick(mouseX, mouseY); } @@ -334,4 +335,4 @@ protected int getContentsHeightWithPadding() { protected double getDeltaYPerScroll() { return 0; } -} \ No newline at end of file +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockItemRarity.java b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockItemRarity.java index a5043e0650..9008fb4e9b 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockItemRarity.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockItemRarity.java @@ -1,6 +1,7 @@ package de.hysky.skyblocker.skyblock.item; import com.mojang.serialization.Codec; +import de.hysky.skyblocker.utils.EnumUtils; import net.minecraft.util.Formatting; import net.minecraft.util.StringIdentifiable; @@ -41,6 +42,6 @@ public String asString() { } public SkyblockItemRarity next() { - return values()[(ordinal() + 1) % values().length]; + return EnumUtils.cycle(this); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewTab.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewTab.java index f72bc2938a..2cd743ac74 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewTab.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/config/preview/PreviewTab.java @@ -11,6 +11,7 @@ import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; import de.hysky.skyblocker.skyblock.tabhud.widget.HudWidget; import de.hysky.skyblocker.skyblock.tabhud.widget.TabHudWidget; +import de.hysky.skyblocker.utils.EnumUtils; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.render.gui.DropdownWidget; @@ -307,8 +308,7 @@ void onHudWidgetSelected(@Nullable HudWidget hudWidget) { widgetOptions.addWidget(ButtonWidget.builder(Text.literal(ye), button -> { ScreenBuilder builder = ScreenMaster.getScreenBuilder(getCurrentLocation()); PositionRule rule = builder.getPositionRuleOrDefault(hudWidget.getInternalID()); - ScreenMaster.ScreenLayer[] values = ScreenMaster.ScreenLayer.values(); - ScreenMaster.ScreenLayer newLayer = values[(rule.screenLayer().ordinal() + 1) % values.length]; + ScreenMaster.ScreenLayer newLayer = EnumUtils.cycle(rule.screenLayer()); PositionRule newRule = new PositionRule( rule.parent(), diff --git a/src/main/java/de/hysky/skyblocker/utils/EnumUtils.java b/src/main/java/de/hysky/skyblocker/utils/EnumUtils.java new file mode 100644 index 0000000000..ba1ad1d2dc --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/EnumUtils.java @@ -0,0 +1,13 @@ +package de.hysky.skyblocker.utils; + +public class EnumUtils { + public static > T cycle(T current) { + T[] values = current.getDeclaringClass().getEnumConstants(); + return values[(current.ordinal() + 1) % values.length]; + } + + public static > T cycleBackwards(T current) { + T[] values = current.getDeclaringClass().getEnumConstants(); + return values[(current.ordinal() - 1 + values.length) % values.length]; + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/EmptyWidget.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/EmptyWidget.java new file mode 100644 index 0000000000..6d1324ec08 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/EmptyWidget.java @@ -0,0 +1,8 @@ +package de.hysky.skyblocker.utils.render.gui; + +import net.minecraft.client.gui.DrawContext; + +public class EmptyWidget extends AbstractWidget { + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) {} +} diff --git a/src/main/java/de/hysky/skyblocker/utils/render/title/Title.java b/src/main/java/de/hysky/skyblocker/utils/render/title/Title.java index 890976aea4..6b32da7d9e 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/title/Title.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/title/Title.java @@ -1,5 +1,6 @@ package de.hysky.skyblocker.utils.render.title; +import com.demonwav.mcdev.annotations.Translatable; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -10,46 +11,46 @@ * @see TitleContainer */ public class Title { - private MutableText text; - protected float x = -1; - protected float y = -1; - - /** - * Constructs a new title with the given translation key and formatting to be applied. - * - * @param textKey the translation key - * @param formatting the formatting to be applied to the text - */ - public Title(String textKey, Formatting formatting) { - this(Text.translatable(textKey).formatted(formatting)); - } - - /** - * Constructs a new title with the given {@link MutableText}. - * Use {@link Text#literal(String)} or {@link Text#translatable(String)} to create a {@link MutableText} - * - * @param text the mutable text - */ - public Title(MutableText text) { - this.text = text; - } - - public MutableText getText() { - return text; - } - - public Title setText(MutableText text) { - this.text = text; - - return this; - } - - protected boolean isDefaultPos() { - return x == -1 && y == -1; - } - - protected void resetPos() { - this.x = -1; - this.y = -1; - } + private MutableText text; + protected float x = -1; + protected float y = -1; + + /** + * Constructs a new title with the given translation key and formatting to be applied. + * + * @param textKey the translation key + * @param formatting the formatting to be applied to the text + */ + public Title(@Translatable String textKey, Formatting formatting) { + this(Text.translatable(textKey).formatted(formatting)); + } + + /** + * Constructs a new title with the given {@link MutableText}. + * Use {@link Text#literal(String)} or {@link Text#translatable(String)} to create a {@link MutableText} + * + * @param text the mutable text + */ + public Title(MutableText text) { + this.text = text; + } + + public MutableText getText() { + return text; + } + + public Title setText(MutableText text) { + this.text = text; + + return this; + } + + protected boolean isDefaultPos() { + return x == -1 && y == -1; + } + + protected void resetPos() { + this.x = -1; + this.y = -1; + } } diff --git a/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java b/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java index c3d7faa463..8354497691 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java @@ -17,162 +17,156 @@ import java.util.Set; public class TitleContainer { - /** - * The set of titles which will be rendered. - * - * @see #containsTitle(Title) - * @see #addTitle(Title) - * @see #addTitle(Title, int) - * @see #removeTitle(Title) - */ - private static final Set titles = new LinkedHashSet<>(); - - @Init - public static void init() { - HudRenderEvents.BEFORE_CHAT.register(TitleContainer::render); - ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("skyblocker") - .then(ClientCommandManager.literal("hud") - .then(ClientCommandManager.literal("titleContainer") - .executes(Scheduler.queueOpenScreenCommand(TitleContainerConfigScreen::new)))))); - } - - /** - * Returns {@code true} if the title is currently shown. - * - * @param title the title to check - * @return whether the title in currently shown - */ - public static boolean containsTitle(Title title) { - return titles.contains(title); - } - - /** - * Adds a title to be shown - * - * @param title the title to be shown - * @return whether the title is already currently being shown - */ - public static boolean addTitle(Title title) { - if (titles.add(title)) { - title.resetPos(); - return true; - } - return false; - } - - /** - * Adds a title to be shown for a set number of ticks - * - * @param title the title to be shown - * @param ticks the number of ticks to show the title - * @return whether the title is already currently being shown - */ - public static boolean addTitle(Title title, int ticks) { - if (addTitle(title)) { - Scheduler.INSTANCE.schedule(() -> TitleContainer.removeTitle(title), ticks); - return true; - } - return false; - } - - /** - * Stops showing a title - * - * @param title the title to stop showing - */ - public static void removeTitle(Title title) { - titles.remove(title); - } - - private static void render(DrawContext context, RenderTickCounter tickCounter) { - render(context, titles, SkyblockerConfigManager.get().uiAndVisuals.titleContainer.x, SkyblockerConfigManager.get().uiAndVisuals.titleContainer.y, tickCounter.getTickDelta(true)); - } - - protected static void render(DrawContext context, Set<Title> titles, int xPos, int yPos, float tickDelta) { - var client = MinecraftClient.getInstance(); - TextRenderer textRenderer = client.textRenderer; - - // Calculate Scale to use - float scale = 3F * (SkyblockerConfigManager.get().uiAndVisuals.titleContainer.titleContainerScale / 100F); - - // Grab direction and alignment values - UIAndVisualsConfig.Direction direction = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.direction; - UIAndVisualsConfig.Alignment alignment = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment; - // x/y refer to the starting position for the text - // y always starts at yPos - float x = 0; - float y = yPos; - - //Calculate the width of combined text - float width = 0; - for (Title title : titles) { - width += textRenderer.getWidth(title.getText()) * scale + 10; - } - - if (alignment == UIAndVisualsConfig.Alignment.MIDDLE) { - if (direction == UIAndVisualsConfig.Direction.HORIZONTAL) { - //If middle aligned horizontally, start the xPosition at half of the width to the left. - x = xPos - (width / 2); - } else { - //If middle aligned vertically, start at xPos, we will shift each text to the left later - x = xPos; - } - } - if (alignment == UIAndVisualsConfig.Alignment.LEFT || alignment == UIAndVisualsConfig.Alignment.RIGHT) { - //If left or right aligned, start at xPos, we will shift each text later - x = xPos; - } - - for (Title title : titles) { - - //Calculate which x the text should use - float xToUse; - if (direction == UIAndVisualsConfig.Direction.HORIZONTAL) { - xToUse = alignment == UIAndVisualsConfig.Alignment.RIGHT ? - x - (textRenderer.getWidth(title.getText()) * scale) : //if right aligned we need the text position to be aligned on the right side. - x; - } else { - xToUse = alignment == UIAndVisualsConfig.Alignment.MIDDLE ? - x - (textRenderer.getWidth(title.getText()) * scale) / 2 : //if middle aligned we need the text position to be aligned in the middle. - alignment == UIAndVisualsConfig.Alignment.RIGHT ? - x - (textRenderer.getWidth(title.getText()) * scale) : //if right aligned we need the text position to be aligned on the right side. - x; - } - - //Start displaying the title at the correct position, not at the default position - if (title.isDefaultPos()) { - title.x = xToUse; - title.y = y; - } - - //Lerp the texts x and y variables - title.x = MathHelper.lerp(tickDelta * 0.5F, title.x, xToUse); - title.y = MathHelper.lerp(tickDelta * 0.5F, title.y, y); - - //Translate the matrix to the texts position and scale - context.getMatrices().push(); - context.getMatrices().translate(title.x, title.y, 0); - context.getMatrices().scale(scale, scale, scale); - - //Draw text - context.drawTextWithShadow(textRenderer, title.getText(), 0, 0, 0xFFFFFF); - context.getMatrices().pop(); - - //Calculate the x and y positions for the next title - if (direction == UIAndVisualsConfig.Direction.HORIZONTAL) { - if (alignment == UIAndVisualsConfig.Alignment.MIDDLE || alignment == UIAndVisualsConfig.Alignment.LEFT) { - //Move to the right if middle or left aligned - x += textRenderer.getWidth(title.getText()) * scale + 10; - } - - if (alignment == UIAndVisualsConfig.Alignment.RIGHT) { - //Move to the left if right aligned - x -= textRenderer.getWidth(title.getText()) * scale + 10; - } - } else { - //Y always moves by the same amount if vertical - y += textRenderer.fontHeight * scale + 10; - } - } - } + /** + * The set of titles which will be rendered. + * + * @see #containsTitle(Title) + * @see #addTitle(Title) + * @see #addTitle(Title, int) + * @see #removeTitle(Title) + */ + private static final Set<Title> titles = new LinkedHashSet<>(); + + @Init + public static void init() { + HudRenderEvents.BEFORE_CHAT.register(TitleContainer::render); + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("skyblocker") + .then(ClientCommandManager.literal("hud") + .then(ClientCommandManager.literal("titleContainer") + .executes(Scheduler.queueOpenScreenCommand(TitleContainerConfigScreen::new)))))); + } + + /** + * Returns {@code true} if the title is currently shown. + * + * @param title the title to check + * @return whether the title in currently shown + */ + public static boolean containsTitle(Title title) { + return titles.contains(title); + } + + /** + * Adds a title to be shown + * + * @param title the title to be shown + * @return whether the title is already currently being shown + */ + public static boolean addTitle(Title title) { + if (titles.add(title)) { + title.resetPos(); + return true; + } + return false; + } + + /** + * Adds a title to be shown for a set number of ticks + * + * @param title the title to be shown + * @param ticks the number of ticks to show the title + * @return whether the title is already currently being shown + */ + public static boolean addTitle(Title title, int ticks) { + if (addTitle(title)) { + Scheduler.INSTANCE.schedule(() -> TitleContainer.removeTitle(title), ticks); + return true; + } + return false; + } + + /** + * Stops showing a title + * + * @param title the title to stop showing + */ + public static void removeTitle(Title title) { + titles.remove(title); + } + + private static void render(DrawContext context, RenderTickCounter tickCounter) { + render(context, titles, SkyblockerConfigManager.get().uiAndVisuals.titleContainer.x, SkyblockerConfigManager.get().uiAndVisuals.titleContainer.y, tickCounter.getTickDelta(true)); + } + + protected static void render(DrawContext context, Set<Title> titles, int xPos, int yPos, float tickDelta) { + TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + + // Calculate Scale to use + float scale = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.getRenderScale(); + // Grab direction and alignment values + UIAndVisualsConfig.Direction direction = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.direction; + UIAndVisualsConfig.Alignment alignment = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment; + + // x/y refer to the starting position for the text + // If left or right aligned or middle aligned vertically, start at xPos, we will shift each text later + float x = xPos; + // y always starts at yPos + float y = yPos; + + // Calculate the width of combined text + float totalWidth = getWidth(textRenderer, titles); + if (alignment == UIAndVisualsConfig.Alignment.MIDDLE && direction == UIAndVisualsConfig.Direction.HORIZONTAL) { + // If middle aligned horizontally, start the xPosition at half of the width to the left. + x = xPos - totalWidth / 2; + } + + for (Title title : titles) { + //Calculate which x the text should use + float xTextLeft = x; + if (alignment == UIAndVisualsConfig.Alignment.RIGHT) { + //if right aligned we need the text position to be aligned on the right side. + xTextLeft = x - textRenderer.getWidth(title.getText()) * scale; + } else if (direction == UIAndVisualsConfig.Direction.VERTICAL && alignment == UIAndVisualsConfig.Alignment.MIDDLE) { + //if middle aligned we need the text position to be aligned in the middle. + xTextLeft = x - (textRenderer.getWidth(title.getText()) * scale) / 2; + } + + //Start displaying the title at the correct position, not at the default position + if (title.isDefaultPos()) { + title.x = xTextLeft; + title.y = y; + } + + //Lerp the texts x and y variables + title.x = MathHelper.lerp(tickDelta * 0.5F, title.x, xTextLeft); + title.y = MathHelper.lerp(tickDelta * 0.5F, title.y, y); + + //Translate the matrix to the texts position and scale + context.getMatrices().push(); + context.getMatrices().translate(title.x, title.y, 0); + context.getMatrices().scale(scale, scale, scale); + + //Draw text + context.drawTextWithShadow(textRenderer, title.getText(), 0, 0, 0xFFFFFF); + context.getMatrices().pop(); + + //Calculate the x and y positions for the next title + if (direction == UIAndVisualsConfig.Direction.HORIZONTAL) { + if (alignment == UIAndVisualsConfig.Alignment.MIDDLE || alignment == UIAndVisualsConfig.Alignment.LEFT) { + //Move to the right if middle or left aligned + x += (textRenderer.getWidth(title.getText()) + 10) * scale; + } else if (alignment == UIAndVisualsConfig.Alignment.RIGHT) { + //Move to the left if right aligned + x -= (textRenderer.getWidth(title.getText()) + 10) * scale; + } + } else { + //Y always moves by the same amount if vertical + y += (textRenderer.fontHeight + 1) * scale; + } + } + } + + protected static int getWidth(TextRenderer textRenderer, Set<Title> titles) { + float scale = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.getRenderScale(); + return SkyblockerConfigManager.get().uiAndVisuals.titleContainer.direction == UIAndVisualsConfig.Direction.HORIZONTAL ? + (int) ((titles.stream().map(Title::getText).mapToInt(textRenderer::getWidth).mapToDouble(width -> width + 10).sum() - 10) * scale) : + (int) (titles.stream().map(Title::getText).mapToInt(textRenderer::getWidth).max().orElse(0) * scale); + } + + protected static int getHeight(TextRenderer textRenderer, Set<Title> titles) { + float scale = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.getRenderScale(); + return SkyblockerConfigManager.get().uiAndVisuals.titleContainer.direction == UIAndVisualsConfig.Direction.HORIZONTAL ? + (int) (textRenderer.fontHeight * scale) : + (int) ((textRenderer.fontHeight + 1) * titles.size() * scale); + } } diff --git a/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainerConfigScreen.java b/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainerConfigScreen.java index a8fbbea87c..69ba60a168 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainerConfigScreen.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainerConfigScreen.java @@ -1,191 +1,143 @@ package de.hysky.skyblocker.utils.render.title; +import com.google.common.collect.ImmutableSet; +import de.hysky.skyblocker.config.HudConfigScreen; +import de.hysky.skyblocker.config.SkyblockerConfig; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.UIAndVisualsConfig; -import de.hysky.skyblocker.utils.render.RenderHelper; +import de.hysky.skyblocker.utils.EnumUtils; +import de.hysky.skyblocker.utils.render.gui.AbstractWidget; +import de.hysky.skyblocker.utils.render.gui.EmptyWidget; import dev.isxander.yacl3.api.ConfigCategory; -import dev.isxander.yacl3.api.Option; import dev.isxander.yacl3.api.OptionGroup; import dev.isxander.yacl3.gui.YACLScreen; +import it.unimi.dsi.fastutil.ints.IntIntMutablePair; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.resource.language.I18n; -import net.minecraft.client.util.math.Vector2f; import net.minecraft.text.Text; +import net.minecraft.text.TranslatableTextContent; import net.minecraft.util.Formatting; -import net.minecraft.util.Pair; import org.lwjgl.glfw.GLFW; import java.awt.*; +import java.util.List; import java.util.Set; -public class TitleContainerConfigScreen extends Screen { - private final Title example1 = new Title(Text.literal("Test1").formatted(Formatting.RED)); - private final Title example2 = new Title(Text.literal("Test23").formatted(Formatting.AQUA)); - private final Title example3 = new Title(Text.literal("Testing1234").formatted(Formatting.DARK_GREEN)); - private float hudX = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.x; - private float hudY = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.y; - private final Screen parent; - private boolean changedScale; - - protected TitleContainerConfigScreen() { - this(null); - } - - public TitleContainerConfigScreen(Screen parent) { - super(Text.of("Title Container HUD Config")); - this.parent = parent; - } - - @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { - super.render(context, mouseX, mouseY, delta); - renderBackground(context, mouseX, mouseY, delta); - TitleContainer.render(context, Set.of(example1, example2, example3), (int) hudX, (int) hudY, delta); - UIAndVisualsConfig.Direction direction = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.direction; - UIAndVisualsConfig.Alignment alignment = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment; - context.drawCenteredTextWithShadow(textRenderer, "Press Q/E to change Alignment: " + alignment, width / 2, textRenderer.fontHeight * 2, Color.WHITE.getRGB()); - context.drawCenteredTextWithShadow(textRenderer, "Press R to change Direction: " + direction, width / 2, textRenderer.fontHeight * 3 + 5, Color.WHITE.getRGB()); - context.drawCenteredTextWithShadow(textRenderer, "Press +/- to change Scale", width / 2, textRenderer.fontHeight * 4 + 10, Color.WHITE.getRGB()); - context.drawCenteredTextWithShadow(textRenderer, "Right Click To Reset Position", width / 2, textRenderer.fontHeight * 5 + 15, Color.GRAY.getRGB()); - - Pair<Vector2f, Vector2f> boundingBox = getSelectionBoundingBox(); - int x1 = (int) boundingBox.getLeft().getX(); - int y1 = (int) boundingBox.getLeft().getY(); - int x2 = (int) boundingBox.getRight().getX(); - int y2 = (int) boundingBox.getRight().getY(); - - context.drawHorizontalLine(x1, x2, y1, Color.RED.getRGB()); - context.drawHorizontalLine(x1, x2, y2, Color.RED.getRGB()); - context.drawVerticalLine(x1, y1, y2, Color.RED.getRGB()); - context.drawVerticalLine(x2, y1, y2, Color.RED.getRGB()); - } - - private Pair<Vector2f, Vector2f> getSelectionBoundingBox() { - UIAndVisualsConfig.Alignment alignment = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment; - - float midWidth = getSelectionWidth() / 2F; - float x1 = 0; - float x2 = 0; - float y1 = hudY; - float y2 = hudY + getSelectionHeight(); - switch (alignment) { - case RIGHT -> { - x1 = hudX - midWidth * 2; - x2 = hudX; - } - case MIDDLE -> { - x1 = hudX - midWidth; - x2 = hudX + midWidth; - } - case LEFT -> { - x1 = hudX; - x2 = hudX + midWidth * 2; - } - } - return new Pair<>(new Vector2f(x1, y1), new Vector2f(x2, y2)); - } - - private float getSelectionHeight() { - float scale = (3F * (SkyblockerConfigManager.get().uiAndVisuals.titleContainer.titleContainerScale / 100F)); - return SkyblockerConfigManager.get().uiAndVisuals.titleContainer.direction == UIAndVisualsConfig.Direction.HORIZONTAL ? - (textRenderer.fontHeight * scale) : - (textRenderer.fontHeight + 10F) * 3F * scale; - } - - private float getSelectionWidth() { - float scale = (3F * (SkyblockerConfigManager.get().uiAndVisuals.titleContainer.titleContainerScale / 100F)); - return SkyblockerConfigManager.get().uiAndVisuals.titleContainer.direction == UIAndVisualsConfig.Direction.HORIZONTAL ? - (textRenderer.getWidth("Test1") + 10 + textRenderer.getWidth("Test23") + 10 + textRenderer.getWidth("Testing1234")) * scale : - textRenderer.getWidth("Testing1234") * scale; - } - - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - float midWidth = getSelectionWidth() / 2; - float midHeight = getSelectionHeight() / 2; - var alignment = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment; - - Pair<Vector2f, Vector2f> boundingBox = getSelectionBoundingBox(); - float x1 = boundingBox.getLeft().getX(); - float y1 = boundingBox.getLeft().getY(); - float x2 = boundingBox.getRight().getX(); - float y2 = boundingBox.getRight().getY(); - - if (RenderHelper.pointIsInArea(mouseX, mouseY, x1, y1, x2, y2) && button == 0) { - hudX = switch (alignment) { - case LEFT -> (int) mouseX - midWidth; - case MIDDLE -> (int) mouseX; - case RIGHT -> (int) mouseX + midWidth; - }; - hudY = (int) (mouseY - midHeight); - } - return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (button == 1) { - hudX = (float) this.width / 2; - hudY = this.height * 0.6F; - } - return super.mouseClicked(mouseX, mouseY, button); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (keyCode == GLFW.GLFW_KEY_Q) { - UIAndVisualsConfig.Alignment current = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment; - SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment = switch (current) { - case LEFT -> UIAndVisualsConfig.Alignment.MIDDLE; - case MIDDLE -> UIAndVisualsConfig.Alignment.RIGHT; - case RIGHT -> UIAndVisualsConfig.Alignment.LEFT; - }; - } - if (keyCode == GLFW.GLFW_KEY_E) { - UIAndVisualsConfig.Alignment current = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment; - SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment = switch (current) { - case LEFT -> UIAndVisualsConfig.Alignment.RIGHT; - case MIDDLE -> UIAndVisualsConfig.Alignment.LEFT; - case RIGHT -> UIAndVisualsConfig.Alignment.MIDDLE; - }; - } - if (keyCode == GLFW.GLFW_KEY_R) { - UIAndVisualsConfig.Direction current = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.direction; - SkyblockerConfigManager.get().uiAndVisuals.titleContainer.direction = switch (current) { - case HORIZONTAL -> UIAndVisualsConfig.Direction.VERTICAL; - case VERTICAL -> UIAndVisualsConfig.Direction.HORIZONTAL; - }; - } - if (keyCode == GLFW.GLFW_KEY_EQUAL) { - SkyblockerConfigManager.get().uiAndVisuals.titleContainer.titleContainerScale += 10; - changedScale = true; - } - if (keyCode == GLFW.GLFW_KEY_MINUS) { - SkyblockerConfigManager.get().uiAndVisuals.titleContainer.titleContainerScale -= 10; - changedScale = true; - } - return super.keyPressed(keyCode, scanCode, modifiers); - } - - - @Override - public void close() { - SkyblockerConfigManager.get().uiAndVisuals.titleContainer.x = (int) hudX; - SkyblockerConfigManager.get().uiAndVisuals.titleContainer.y = (int) hudY; - - //TODO Come up with a better, less hacky solution for this in the future (: - if (parent instanceof YACLScreen yaclScreen) { - ConfigCategory category = yaclScreen.config.categories().stream().filter(cat -> cat.name().getString().equals(I18n.translate("skyblocker.config.uiAndVisuals"))).findFirst().orElseThrow(); - OptionGroup group = category.groups().stream().filter(grp -> grp.name().getString().equals(I18n.translate("skyblocker.config.uiAndVisuals.titleContainer"))).findFirst().orElseThrow(); - - Option<?> scaleOpt = group.options().getFirst(); - - // Refresh the value in the config with the bound value - if (changedScale) scaleOpt.forgetPendingValue(); - } - - SkyblockerConfigManager.save(); - this.client.setScreen(parent); - } +public class TitleContainerConfigScreen extends HudConfigScreen { + // ImmutableSet preserves insertion order + private static final Set<Title> EXAMPLES = ImmutableSet.of( + new Title(Text.literal("Test1").formatted(Formatting.RED)), + new Title(Text.literal("Test23").formatted(Formatting.AQUA)), + new Title(Text.literal("Testing1234").formatted(Formatting.DARK_GREEN)) + ); + private boolean changedScale; + + protected TitleContainerConfigScreen() { + this(null); + } + + public TitleContainerConfigScreen(Screen parent) { + super(Text.of("Title Container HUD Config"), parent, new EmptyWidget()); + } + + @Override + protected void init() { + super.init(); + // Load the config positions here since #getConfigPos is used for resetting. This loads the config pos after the supertype constructor calls HudConfigScreen#resetPos. + widgets.getFirst().setPosition(SkyblockerConfigManager.get().uiAndVisuals.titleContainer.x, SkyblockerConfigManager.get().uiAndVisuals.titleContainer.y); + // Set the dimensions here or else Screen#textRenderer is null. + updateWidgetDimensions(); + } + + @Override + protected void renderWidget(DrawContext context, List<AbstractWidget> widgets, float delta) { + super.renderWidget(context, widgets, delta); + TitleContainer.render(context, EXAMPLES, widgets.getFirst().getX(), widgets.getFirst().getY(), delta); + UIAndVisualsConfig.Direction direction = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.direction; + UIAndVisualsConfig.Alignment alignment = SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment; + context.drawCenteredTextWithShadow(textRenderer, "Press Q/E to change Alignment: " + alignment, width / 2, textRenderer.fontHeight * 2, Color.WHITE.getRGB()); + context.drawCenteredTextWithShadow(textRenderer, "Press R to change Direction: " + direction, width / 2, textRenderer.fontHeight * 3 + 5, Color.WHITE.getRGB()); + context.drawCenteredTextWithShadow(textRenderer, "Press +/- to change Scale", width / 2, textRenderer.fontHeight * 4 + 10, Color.WHITE.getRGB()); + context.drawCenteredTextWithShadow(textRenderer, "Right Click To Reset Position", width / 2, textRenderer.fontHeight * 5 + 15, Color.GRAY.getRGB()); + + int selectionWidth = getSelectionWidth(); + int x1 = switch (alignment) { + case LEFT -> widgets.getFirst().getX(); + case MIDDLE -> widgets.getFirst().getX() - selectionWidth / 2; + case RIGHT -> widgets.getFirst().getX() - selectionWidth; + }; + int y1 = widgets.getFirst().getY(); + int x2 = x1 + selectionWidth; + int y2 = y1 + getSelectionHeight(); + + context.drawHorizontalLine(x1, x2, y1, Color.RED.getRGB()); + context.drawHorizontalLine(x1, x2, y2, Color.RED.getRGB()); + context.drawVerticalLine(x1, y1, y2, Color.RED.getRGB()); + context.drawVerticalLine(x2, y1, y2, Color.RED.getRGB()); + } + + private void updateWidgetDimensions() { + widgets.getFirst().setDimensions(getSelectionWidth(), getSelectionHeight()); + } + + private int getSelectionWidth() { + return TitleContainer.getWidth(textRenderer, EXAMPLES); + } + + private int getSelectionHeight() { + return TitleContainer.getHeight(textRenderer, EXAMPLES); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + switch (keyCode) { + case GLFW.GLFW_KEY_Q -> SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment = EnumUtils.cycle(SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment); + case GLFW.GLFW_KEY_E -> SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment = EnumUtils.cycleBackwards(SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment); + case GLFW.GLFW_KEY_R -> { + SkyblockerConfigManager.get().uiAndVisuals.titleContainer.direction = EnumUtils.cycle(SkyblockerConfigManager.get().uiAndVisuals.titleContainer.direction); + updateWidgetDimensions(); + } + case GLFW.GLFW_KEY_EQUAL -> { + SkyblockerConfigManager.get().uiAndVisuals.titleContainer.titleContainerScale += 10; + updateWidgetDimensions(); + changedScale = true; + } + case GLFW.GLFW_KEY_MINUS -> { + SkyblockerConfigManager.get().uiAndVisuals.titleContainer.titleContainerScale -= 10; + updateWidgetDimensions(); + changedScale = true; + } + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + protected int getWidgetXOffset(AbstractWidget widget) { + return switch (SkyblockerConfigManager.get().uiAndVisuals.titleContainer.alignment) { + case LEFT -> 0; + case MIDDLE -> -getSelectionWidth() / 2; + case RIGHT -> -getSelectionWidth(); + }; + } + + @Override + protected List<IntIntMutablePair> getConfigPos(SkyblockerConfig config) { + // This gets the reset pos. The actual config pos is loaded in #init. + return List.of(IntIntMutablePair.of(this.width / 2, (int) (this.height * 0.6))); + } + + @Override + protected void savePos(SkyblockerConfig configManager, List<AbstractWidget> widgets) { + SkyblockerConfigManager.get().uiAndVisuals.titleContainer.x = widgets.getFirst().getX(); + SkyblockerConfigManager.get().uiAndVisuals.titleContainer.y = widgets.getFirst().getY(); + + //TODO Come up with a better, less hacky solution for this in the future (: + if (changedScale && parent instanceof YACLScreen yaclScreen) { + ConfigCategory category = yaclScreen.config.categories().stream().filter(cat -> cat.name().getContent() instanceof TranslatableTextContent translatable && translatable.getKey().equals("skyblocker.config.uiAndVisuals")).findFirst().orElseThrow(); + OptionGroup group = category.groups().stream().filter(grp -> grp.name().getContent() instanceof TranslatableTextContent translatable && translatable.getKey().equals("skyblocker.config.uiAndVisuals.titleContainer")).findFirst().orElseThrow(); + + // Refresh the value in the config with the bound value + group.options().getFirst().forgetPendingValue(); + } + } }