From 539d152caa4f9259c41b1c1fbfcb555f3021e46d Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 18 Apr 2026 09:44:37 -0500 Subject: [PATCH] workaround for java 8 and forgix Java 8 doesn't support "_" in the class name after "$". In other words inner classes can't have underscores in their names, which forgix adds for changed classes. Moving the DhConfigScreen/ClassicConfigGUI into separate classes mitigates the problem until forgix has a long-term fix. --- .../common/wrappers/DependencySetup.java | 2 +- .../common/wrappers/gui/ClassicConfigGUI.java | 1125 ----------------- .../common/wrappers/gui/GetConfigScreen.java | 1 + .../gui/classicConfig/ClassicConfigGUI.java | 384 ++++++ .../gui/classicConfig/DhConfigScreen.java | 799 ++++++++++++ 5 files changed, 1185 insertions(+), 1126 deletions(-) delete mode 100644 common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/ClassicConfigGUI.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/classicConfig/ClassicConfigGUI.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/classicConfig/DhConfigScreen.java diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/DependencySetup.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/DependencySetup.java index 8d257a2cf..9869daa3d 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/DependencySetup.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/DependencySetup.java @@ -25,7 +25,7 @@ import com.seibel.distanthorizons.common.render.blaze.BlazeDhRenderApiDefinition import com.seibel.distanthorizons.common.render.openGl.GlDhRenderApiDefinition; import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.render.renderer.GenericRenderObjectFactory; -import com.seibel.distanthorizons.common.wrappers.gui.ClassicConfigGUI; +import com.seibel.distanthorizons.common.wrappers.gui.classicConfig.ClassicConfigGUI; import com.seibel.distanthorizons.common.wrappers.gui.LangWrapper; import com.seibel.distanthorizons.common.wrappers.level.KeyedClientLevelManager; import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftServerWrapper; diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/ClassicConfigGUI.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/ClassicConfigGUI.java deleted file mode 100644 index 89030efa5..000000000 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/ClassicConfigGUI.java +++ /dev/null @@ -1,1125 +0,0 @@ -package com.seibel.distanthorizons.common.wrappers.gui; - -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.regex.Pattern; - -import com.seibel.distanthorizons.api.enums.config.DisallowSelectingViaConfigGui; -import com.seibel.distanthorizons.common.wrappers.gui.config.ConfigGuiInfo; -import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftClientWrapper; -import com.seibel.distanthorizons.core.config.Config; -import com.seibel.distanthorizons.core.config.ConfigHandler; -import com.seibel.distanthorizons.core.config.types.*; -import com.seibel.distanthorizons.common.wrappers.gui.updater.ChangelogScreen; - -import com.seibel.distanthorizons.core.config.types.enums.EConfigCommentTextPosition; -import com.seibel.distanthorizons.core.config.types.enums.EConfigValidity; -import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; -import com.seibel.distanthorizons.core.jar.updater.SelfUpdater; -import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; -import com.seibel.distanthorizons.core.util.AnnotationUtil; -import com.seibel.distanthorizons.core.wrapperInterfaces.config.IConfigGui; -import com.seibel.distanthorizons.core.wrapperInterfaces.config.ILangWrapper; -import com.seibel.distanthorizons.coreapi.ModInfo; -import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.components.AbstractWidget; -import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.components.ContainerObjectSelectionList; -import net.minecraft.client.gui.components.EditBox; -import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import com.seibel.distanthorizons.core.logging.DhLogger; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - - -#if MC_VER < MC_1_20_1 -import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.gui.GuiComponent; -#elif MC_VER <= MC_1_21_11 -import net.minecraft.client.gui.GuiGraphics; -#else -import net.minecraft.client.gui.GuiGraphicsExtractor; -#endif - -#if MC_VER >= MC_1_17_1 -import net.minecraft.client.gui.narration.NarratableEntry; -#endif - -#if MC_VER <= MC_1_21_10 -import net.minecraft.resources.ResourceLocation; -#else -import net.minecraft.resources.Identifier; -#endif - -import org.lwjgl.glfw.GLFW; -import com.mojang.blaze3d.platform.InputConstants; - -import static com.seibel.distanthorizons.common.wrappers.gui.GuiHelper.*; -import static com.seibel.distanthorizons.common.wrappers.gui.GuiHelper.Translatable; - - -/* - * Based upon TinyConfig but is highly modified - * https://github.com/Minenash/TinyConfig - * - * @author coolGi - * @author Motschen - * @author James Seibel - * @version 5-21-2022 - */ -@SuppressWarnings("unchecked") -public class ClassicConfigGUI -{ - private static final DhLogger LOGGER = new DhLoggerBuilder().build(); - public static final DhLogger RATE_LIMITED_LOGGER = new DhLoggerBuilder() - .maxCountPerSecond(1) - .build(); - - public static final ConfigCoreInterface CONFIG_CORE_INTERFACE = new ConfigCoreInterface(); - - private static final MinecraftClientWrapper MC_CLIENT = MinecraftClientWrapper.INSTANCE; - - - - //==============// - // Initializers // - //==============// - - // Some regexes to check if an input is valid - private static final Pattern INTEGER_ONLY_REGEX = Pattern.compile("(-?[0-9]*)"); - private static final Pattern DECIMAL_ONLY_REGEX = Pattern.compile("-?([\\d]+\\.?[\\d]*|[\\d]*\\.?[\\d]+|\\.)"); - - private static class ConfigScreenConfigs - { - // This contains all the configs for the configs - public static final int SPACE_FROM_RIGHT_SCREEN = 10; - public static final int SPACE_BETWEEN_TEXT_AND_OPTION_FIELD = 8; - public static final int BUTTON_WIDTH_SPACING = 5; - public static final int RESET_BUTTON_WIDTH = 60; - public static final int RESET_BUTTON_HEIGHT = 20; - public static final int OPTION_FIELD_WIDTH = 150; - public static final int OPTION_FIELD_HEIGHT = 20; - public static final int CATEGORY_BUTTON_WIDTH = 200; - public static final int CATEGORY_BUTTON_HEIGHT = 20; - - } - - - - //==============// - // GUI handling // - //==============// - - /** if you want to get this config gui's screen call this */ - public static Screen getScreen(Screen parent, String category) - { return new DhConfigScreen(parent, category); } - - private static class DhConfigScreen extends DhScreen - { - private static final ILangWrapper LANG_WRAPPER = SingletonInjector.INSTANCE.get(ILangWrapper.class); - - private static final String TRANSLATION_PREFIX = ModInfo.ID + ".config."; - - - private final Screen parent; - private final String category; - private ConfigListWidget configListWidget; - private boolean reload = false; - - private Button doneButton; - - - - //=============// - // constructor // - //=============// - - protected DhConfigScreen(Screen parent, String category) - { - super(Translatable( - LANG_WRAPPER.langExists(ModInfo.ID + ".config" + (category.isEmpty() ? "." + category : "") + ".title") ? - ModInfo.ID + ".config.title" : - ModInfo.ID + ".config" + (category.isEmpty() ? "" : "." + category) + ".title") - ); - this.parent = parent; - this.category = category; - } - - - @Override - public void tick() { super.tick(); } - - - - //==================// - // menu UI creation // - //==================// - - @Override - protected void init() - { - super.init(); - if (!this.reload) - { - ConfigHandler.INSTANCE.configFileHandler.loadFromFile(); - } - - // Changelog button - if (Config.Client.Advanced.AutoUpdater.enableAutoUpdater.get() - // we only have changelogs for stable builds - && !ModInfo.IS_DEV_BUILD) - { - this.addBtn(new TexturedButtonWidget( - // Where the button is on the screen - this.width - 28, this.height - 28, - // Width and height of the button - 20, 20, - // texture UV Offset - 0, 0, - // Some texture stuff - 0, - #if MC_VER < MC_1_21_1 - new ResourceLocation(ModInfo.ID, "textures/gui/changelog.png"), - #elif MC_VER <= MC_1_21_10 - ResourceLocation.fromNamespaceAndPath(ModInfo.ID, "textures/gui/changelog.png"), - #else - Identifier.fromNamespaceAndPath(ModInfo.ID, "textures/gui/changelog.png"), - #endif - 20, 20, - // Create the button and tell it where to go - (buttonWidget) -> { - ChangelogScreen changelogScreen = new ChangelogScreen(this); - if (changelogScreen.usable) - { - Objects.requireNonNull(this.minecraft).setScreen(changelogScreen); - } - else - { - LOGGER.warn("Changelog was not able to open"); - } - }, - // Add a title to the button - Translatable(ModInfo.ID + ".updater.title") - )); - } - - - // back button - this.addBtn(MakeBtn(Translatable("distanthorizons.general.back"), - (this.width / 2) - 154, this.height - 28, - ConfigScreenConfigs.OPTION_FIELD_WIDTH, ConfigScreenConfigs.OPTION_FIELD_HEIGHT, - (button) -> - { - ConfigHandler.INSTANCE.configFileHandler.loadFromFile(); - Objects.requireNonNull(this.minecraft).setScreen(this.parent); - })); - - // done/close button - this.doneButton = this.addBtn( - MakeBtn(Translatable("distanthorizons.general.done"), - (this.width / 2) + 4, this.height - 28, - ConfigScreenConfigs.OPTION_FIELD_WIDTH, ConfigScreenConfigs.OPTION_FIELD_HEIGHT, - (button) -> - { - ConfigHandler.INSTANCE.configFileHandler.saveToFile(); - Objects.requireNonNull(this.minecraft).setScreen(this.parent); - })); - - this.configListWidget = new ConfigListWidget(this.minecraft, this.width * 2, this.height, 32, 32, 25); - - #if MC_VER < MC_1_20_6 // no background is rendered in MC 1.20.6+ - if (this.minecraft != null && this.minecraft.level != null) - { - this.configListWidget.setRenderBackground(false); - } - #endif - - this.addWidget(this.configListWidget); - - for (AbstractConfigBase configEntry : ConfigHandler.INSTANCE.configBaseList) - { - try - { - if (configEntry.getCategory().matches(this.category) - && configEntry.getAppearance().showInGui) - { - this.addMenuItem(configEntry); - } - } - catch (Exception e) - { - String message = "ERROR: Failed to show [" + configEntry.getNameAndCategory() + "], error: ["+e.getMessage()+"]"; - if (configEntry.get() != null) - { - message += " with the value [" + configEntry.get() + "] with type [" + configEntry.getType() + "]"; - } - - LOGGER.error(message, e); - } - } - - CONFIG_CORE_INTERFACE.onScreenChangeListenerList.forEach((listener) -> listener.run()); - } - private void addMenuItem(AbstractConfigBase configEntry) - { - trySetupConfigEntry(configEntry); - - if (this.tryCreateInputField(configEntry)) return; - if (this.tryCreateCategoryButton(configEntry)) return; - if (this.tryCreateButton(configEntry)) return; - if (this.tryCreateComment(configEntry)) return; - if (this.tryCreateSpacer(configEntry)) return; - if (this.tryCreateLinkedEntry(configEntry)) return; - - LOGGER.warn("Config [" + configEntry.getNameAndCategory() + "] failed to show. Please try something like changing its type."); - } - - private static void trySetupConfigEntry(AbstractConfigBase configMenuOption) - { - configMenuOption.guiValue = new ConfigGuiInfo(); - Class configValueClass = configMenuOption.getType(); - - if (configMenuOption instanceof ConfigEntry) - { - ConfigEntry configEntry = (ConfigEntry) configMenuOption; - - if (configValueClass == Integer.class) - { - setupTextMenuOption(configEntry, Integer::parseInt, INTEGER_ONLY_REGEX, true); - } - else if (configValueClass == Double.class) - { - setupTextMenuOption(configEntry, Double::parseDouble, DECIMAL_ONLY_REGEX, false); - } - else if (configValueClass == Float.class) - { - setupTextMenuOption(configEntry, Float::parseFloat, DECIMAL_ONLY_REGEX, false); - } - else if (configValueClass == String.class || configValueClass == List.class) - { - // For string or list - setupTextMenuOption(configEntry, String::length, null, true); - } - else if (configValueClass == Boolean.class) - { - ConfigEntry booleanConfigEntry = (ConfigEntry) configEntry; - setupBooleanMenuOption(booleanConfigEntry); - } - else if (configValueClass.isEnum()) - { - ConfigEntry> enumConfigEntry = (ConfigEntry>) configEntry; - Class> configEnumClass = (Class>) configValueClass; - setupEnumMenuOption(enumConfigEntry, configEnumClass); - } - else - { - LOGGER.error("No definition for config with type: ["+configValueClass.getName()+"], for config: ["+configMenuOption.name+"]."); - } - } - - } - private static void setupTextMenuOption(AbstractConfigBase configMenuOption, Function parsingFunc, @Nullable Pattern pattern, boolean cast) - { - final ConfigGuiInfo configGuiInfo = ((ConfigGuiInfo) configMenuOption.guiValue); - - configGuiInfo.tooltipFunction = - (editBox, button) -> - (stringValue) -> - { - boolean isNumber = (pattern != null); - - stringValue = stringValue.trim(); - if (!(stringValue.isEmpty() || !isNumber || pattern.matcher(stringValue).matches())) - { - return false; - } - - - Number numberValue = configMenuOption.typeIsFloatingPointNumber() ? 0.0 : 0; // different default values are needed so implicit casting works correctly (if not done casting from 0 (an int) to a double will cause an exception) - configGuiInfo.errorMessage = null; - if (isNumber - && !stringValue.isEmpty() - && !stringValue.equals("-") - && !stringValue.equals(".")) - { - ConfigEntry numberConfigEntry = (ConfigEntry) configMenuOption; - - try - { - numberValue = parsingFunc.apply(stringValue); - } - catch (Exception e) - { - numberValue = null; - } - - EConfigValidity validity = numberConfigEntry.getValidity(numberValue); - switch (validity) - { - case VALID: - configGuiInfo.errorMessage = null; - break; - case NUMBER_TOO_LOW: - configGuiInfo.errorMessage = TextOrTranslatable("§cMinimum length is " + numberConfigEntry.getMin()); - break; - case NUMBER_TOO_HIGH: - configGuiInfo.errorMessage = TextOrTranslatable("§cMaximum length is " + numberConfigEntry.getMax()); - break; - case INVALID: - configGuiInfo.errorMessage = TextOrTranslatable("§cValue is invalid"); - break; - } - } - - editBox.setTextColor(((ConfigEntry) configMenuOption).getValidity(numberValue) == EConfigValidity.VALID ? 0xFFFFFFFF : 0xFFFF7777); // white and red - - - if (configMenuOption.getType() == String.class - || configMenuOption.getType() == List.class) - { - ((ConfigEntry) configMenuOption).uiSetWithoutSaving(stringValue); - } - else if (((ConfigEntry) configMenuOption).getValidity(numberValue) == EConfigValidity.VALID) - { - if (!cast) - { - ((ConfigEntry) configMenuOption).uiSetWithoutSaving(numberValue); - } - else - { - ((ConfigEntry) configMenuOption).uiSetWithoutSaving(numberValue != null ? numberValue.intValue() : 0); - } - } - - return true; - }; - } - private static void setupBooleanMenuOption(ConfigEntry booleanConfigEntry) - { - // For boolean - Function func = value -> Translatable("distanthorizons.general."+((Boolean) value ? "true" : "false")).withStyle((Boolean) value ? ChatFormatting.GREEN : ChatFormatting.RED); - - final ConfigGuiInfo configGuiInfo = ((ConfigGuiInfo) booleanConfigEntry.guiValue); - - configGuiInfo.buttonOptionMap = - new AbstractMap.SimpleEntry>( - (button) -> - { - button.active = !booleanConfigEntry.apiIsOverriding(); - - booleanConfigEntry.uiSetWithoutSaving(!booleanConfigEntry.get()); - button.setMessage(func.apply(booleanConfigEntry.get())); - }, func); - } - private static void setupEnumMenuOption(ConfigEntry> enumConfigEntry, Class> enumClass) - { - List> enumList = Arrays.asList(enumClass.getEnumConstants()); - - final ConfigGuiInfo configGuiInfo = ((ConfigGuiInfo) enumConfigEntry.guiValue); - - Function getEnumTranslatableFunc = (value) -> Translatable(TRANSLATION_PREFIX + "enum." + enumClass.getSimpleName() + "." + enumConfigEntry.get().toString()); - configGuiInfo.buttonOptionMap = - new AbstractMap.SimpleEntry>( - (button) -> - { - // get the currently selected enum and enum index - int startingIndex = enumList.indexOf(enumConfigEntry.get()); - Enum enumValue = enumList.get(startingIndex); - - boolean shiftPressed = - InputConstants.isKeyDown(MC_CLIENT.getGlfwWindowId(), GLFW.GLFW_KEY_LEFT_SHIFT) - || InputConstants.isKeyDown(MC_CLIENT.getGlfwWindowId(), GLFW.GLFW_KEY_RIGHT_SHIFT); - - - - // move forward or backwards depending on if the shift key is pressed - int index = shiftPressed ? startingIndex-1 : startingIndex+1; - - // wrap around to the other side of the array when necessary - if (index >= enumList.size()) { index = 0; } - else if (index < 0) { index = enumList.size() - 1; } - - - // walk through the enums to find the next selectable one - while (index != startingIndex) - { - enumValue = enumList.get(index); - if (!AnnotationUtil.doesEnumHaveAnnotation(enumValue, DisallowSelectingViaConfigGui.class)) - { - // this enum shouldn't be selectable via the UI, - // skip it - break; - } - - // move forward or backwards depending on if the shift key is pressed - index = shiftPressed ? index-1 : index+1; - - // wrap around to the other side of the array when necessary - if (index >= enumList.size()) { index = 0; } - else if (index < 0) { index = enumList.size() - 1; } - } - - - if (index == startingIndex) - { - // one of the enums should be selectable, this is a programmer error - enumValue = enumList.get(startingIndex); - LOGGER.warn("Enum [" + enumValue.getClass() + "] doesn't contain any values that should be selectable via the UI, sticking to the currently selected value [" + enumValue + "]."); - } - - - enumConfigEntry.uiSetWithoutSaving(enumValue); - - button.active = !enumConfigEntry.apiIsOverriding(); - - button.setMessage(getEnumTranslatableFunc.apply(enumConfigEntry.get())); - }, getEnumTranslatableFunc); - } - - private boolean tryCreateInputField(AbstractConfigBase configBase) - { - final ConfigGuiInfo configGuiInfo = ((ConfigGuiInfo) configBase.guiValue); - - if (configBase instanceof ConfigEntry) - { - ConfigEntry configEntry = (ConfigEntry) configBase; - - - //==============// - // reset button // - //==============// - - Button.OnPress btnAction = (button) -> - { - configEntry.uiSetWithoutSaving(configEntry.getDefaultValue()); - this.reload = true; - Objects.requireNonNull(this.minecraft).setScreen(this); - }; - - int resetButtonPosX = this.width - - ConfigScreenConfigs.RESET_BUTTON_WIDTH - - ConfigScreenConfigs.SPACE_FROM_RIGHT_SCREEN; - int resetButtonPosZ = 0; - - Button resetButton = MakeBtn( - Translatable("distanthorizons.general.reset").withStyle(ChatFormatting.RED), - resetButtonPosX, resetButtonPosZ, - ConfigScreenConfigs.RESET_BUTTON_WIDTH, ConfigScreenConfigs.RESET_BUTTON_HEIGHT, - btnAction); - - if (configEntry.apiIsOverriding()) - { - resetButton.active = false; - resetButton.setMessage(Translatable("distanthorizons.general.apiOverride").withStyle(ChatFormatting.DARK_GRAY)); - } - else - { - resetButton.active = true; - } - - - - //==============// - // option field // - //==============// - - Component textComponent = this.GetTranslatableTextComponentForConfig(configEntry); - - int optionFieldPosX = this.width - - ConfigScreenConfigs.SPACE_FROM_RIGHT_SCREEN - - ConfigScreenConfigs.RESET_BUTTON_WIDTH - - ConfigScreenConfigs.BUTTON_WIDTH_SPACING - - ConfigScreenConfigs.OPTION_FIELD_WIDTH; - int optionFieldPosZ = 0; - - if (configGuiInfo.buttonOptionMap != null) - { - // enum/multi option input button - - Map.Entry> widget = configGuiInfo.buttonOptionMap; - if (configEntry.getType().isEnum()) - { - widget.setValue((value) -> Translatable(TRANSLATION_PREFIX + "enum." + configEntry.getType().getSimpleName() + "." + configEntry.get().toString())); - } - - Button button = MakeBtn( - widget.getValue().apply(configEntry.get()), - optionFieldPosX, optionFieldPosZ, - ConfigScreenConfigs.OPTION_FIELD_WIDTH, ConfigScreenConfigs.CATEGORY_BUTTON_HEIGHT, - widget.getKey()); - - // deactivate the button if the API is overriding it - button.active = !configEntry.apiIsOverriding(); - - - this.configListWidget.addButton(this, configEntry, - button, - resetButton, - null, - textComponent); - - return true; - } - else - { - // text box input - - EditBox widget = new EditBox(this.font, - optionFieldPosX, optionFieldPosZ, - ConfigScreenConfigs.OPTION_FIELD_WIDTH - 4, ConfigScreenConfigs.CATEGORY_BUTTON_HEIGHT, - Translatable("")); - widget.setMaxLength(3_000_000); // hopefully 3 million characters should be enough for any normal use-case, lol - widget.insertText(String.valueOf(configEntry.get())); - - Predicate processor = configGuiInfo.tooltipFunction.apply(widget, this.doneButton); - #if MC_VER <= MC_1_21_11 - widget.setFilter(processor); - #else - widget.setResponder(processor::test); - #endif - - this.configListWidget.addButton(this, configEntry, widget, resetButton, null, textComponent); - - return true; - } - } - - return false; - } - private boolean tryCreateCategoryButton(AbstractConfigBase configType) - { - if (configType instanceof ConfigCategory) - { - ConfigCategory configCategory = (ConfigCategory) configType; - - Component textComponent = this.GetTranslatableTextComponentForConfig(configCategory); - - int categoryPosX = this.width - ConfigScreenConfigs.CATEGORY_BUTTON_WIDTH - ConfigScreenConfigs.SPACE_FROM_RIGHT_SCREEN; - int categoryPosZ = this.height - ConfigScreenConfigs.CATEGORY_BUTTON_HEIGHT; // Note: the posZ value here seems to be ignored - - Button widget = MakeBtn(textComponent, - categoryPosX, categoryPosZ, - ConfigScreenConfigs.CATEGORY_BUTTON_WIDTH, ConfigScreenConfigs.CATEGORY_BUTTON_HEIGHT, - ((button) -> - { - ConfigHandler.INSTANCE.configFileHandler.saveToFile(); - Objects.requireNonNull(this.minecraft).setScreen(ClassicConfigGUI.getScreen(this, configCategory.getDestination())); - })); - this.configListWidget.addButton(this, configType, widget, null, null, null); - - return true; - } - - return false; - } - private boolean tryCreateButton(AbstractConfigBase configType) - { - if (configType instanceof ConfigUIButton) - { - ConfigUIButton configUiButton = (ConfigUIButton) configType; - - Component textComponent = this.GetTranslatableTextComponentForConfig(configUiButton); - - int buttonPosX = this.width - ConfigScreenConfigs.CATEGORY_BUTTON_WIDTH - ConfigScreenConfigs.SPACE_FROM_RIGHT_SCREEN; - - Button widget = MakeBtn(textComponent, - buttonPosX, this.height - 28, - ConfigScreenConfigs.CATEGORY_BUTTON_WIDTH, ConfigScreenConfigs.CATEGORY_BUTTON_HEIGHT, - (button) -> ((ConfigUIButton) configType).runAction()); - this.configListWidget.addButton(this, configType, widget, null, null, null); - - return true; - } - - return false; - } - private boolean tryCreateComment(AbstractConfigBase configType) - { - if (configType instanceof ConfigUIComment) - { - ConfigUIComment configUiComment = (ConfigUIComment) configType; - - Component textComponent = this.GetTranslatableTextComponentForConfig(configUiComment); - if (configUiComment.parentConfigPath != null) - { - textComponent = Translatable(TRANSLATION_PREFIX + configUiComment.parentConfigPath); - } - - this.configListWidget.addButton(this, configType, null, null, null, textComponent); - - return true; - } - - return false; - } - private boolean tryCreateSpacer(AbstractConfigBase configType) - { - if (configType instanceof ConfigUISpacer) - { - Button spacerButton = MakeBtn(Translatable("distanthorizons.general.spacer"), - 10, 10, // having too small of a size causes division by 0 errors in older MC versions (IE 1.20.1) - 1, 1, - (button) -> {}); - - spacerButton.visible = false; - this.configListWidget.addButton(this, configType, spacerButton, null, null, null); - - return true; - } - - return false; - } - private boolean tryCreateLinkedEntry(AbstractConfigBase configType) - { - if (configType instanceof ConfigUiLinkedEntry) - { - this.addMenuItem(((ConfigUiLinkedEntry) configType).get()); - - return true; - } - - return false; - } - - private Component GetTranslatableTextComponentForConfig(AbstractConfigBase configType) - { return Translatable(TRANSLATION_PREFIX + configType.getNameAndCategory());} - - - - //===========// - // rendering // - //===========// - - @Override - #if MC_VER < MC_1_20_1 - public void render(PoseStack matrices, int mouseX, int mouseY, float delta) - #elif MC_VER <= MC_1_21_11 - public void render(GuiGraphics matrices, int mouseX, int mouseY, float delta) - #else - public void extractRenderState(GuiGraphicsExtractor matrices, int mouseX, int mouseY, float delta) - #endif - { - #if MC_VER < MC_1_20_2 // 1.20.2 now enables this by default in the `this.list.render` function - this.renderBackground(matrices); - #elif MC_VER <= MC_1_21_11 - super.render(matrices, mouseX, mouseY, delta); - #else - super.extractRenderState(matrices, mouseX, mouseY, delta); - #endif - - // Render buttons - #if MC_VER <= MC_1_21_11 - this.configListWidget.render(matrices, mouseX, mouseY, delta); - #else - this.configListWidget.extractRenderState(matrices, mouseX, mouseY, delta); - #endif - - - // Render config title - this.DhDrawCenteredString(matrices, this.font, this.title, - this.width / 2, 15, - #if MC_VER < MC_1_21_6 - 0xFFFFFF // RGB white - #else - 0xFFFFFFFF // ARGB white - #endif); - - - // render DH version - this.DhDrawString(matrices, this.font, TextOrLiteral(ModInfo.VERSION), 2, this.height - 10, - #if MC_VER < MC_1_21_6 - 0xAAAAAA // RGB white - #else - 0xFFAAAAAA // ARGB white - #endif); - - // If the update is pending, display this message to inform the user that it will apply when the game restarts - if (SelfUpdater.deleteOldJarOnJvmShutdown) - { - this.DhDrawString(matrices, this.font, Translatable(ModInfo.ID + ".updater.waitingForClose"), 4, this.height - 42, - #if MC_VER < MC_1_21_6 - 0xFFFFFF // RGB white - #else - 0xFFFFFFFF // ARGB white - #endif); - } - - - this.renderTooltip(matrices, mouseX, mouseY, delta); - - #if MC_VER < MC_1_20_2 - super.render(matrices, mouseX, mouseY, delta); - #endif - } - - #if MC_VER < MC_1_20_1 - private void renderTooltip(PoseStack matrices, int mouseX, int mouseY, float delta) - #elif MC_VER <= MC_1_21_11 - private void renderTooltip(GuiGraphics matrices, int mouseX, int mouseY, float delta) - #else - private void renderTooltip(GuiGraphicsExtractor matrices, int mouseX, int mouseY, float delta) - #endif - { - AbstractWidget hoveredWidget = this.configListWidget.getHoveredButton(mouseX, mouseY); - if (hoveredWidget == null) - { - return; - } - - - DhButtonEntry button = DhButtonEntry.BUTTON_BY_WIDGET.get(hoveredWidget); - - - // A quick fix for tooltips on linked entries - AbstractConfigBase configBase = ConfigUiLinkedEntry.class.isAssignableFrom(button.dhConfigType.getClass()) ? - ((ConfigUiLinkedEntry) button.dhConfigType).get() : - button.dhConfigType; - - boolean apiOverrideActive = false; - if (configBase instanceof ConfigEntry) - { - apiOverrideActive = ((ConfigEntry)configBase).apiIsOverriding(); - } - - String key = TRANSLATION_PREFIX + (configBase.category.isEmpty() ? "" : configBase.category + ".") + configBase.getName() + ".@tooltip"; - - if (apiOverrideActive) - { - key = "distanthorizons.general.disabledByApi.@tooltip"; - } - - // display the validation error tooltip if present - final ConfigGuiInfo configGuiInfo = ((ConfigGuiInfo) configBase.guiValue); - if (configGuiInfo.errorMessage != null) - { - this.DhRenderTooltip(matrices, this.font, configGuiInfo.errorMessage, mouseX, mouseY); - } - // display the tooltip if present - else if (LANG_WRAPPER.langExists(key)) - { - List list = new ArrayList<>(); - String lang = LANG_WRAPPER.getLang(key); - for (String langLine : lang.split("\n")) - { - list.add(TextOrTranslatable(langLine)); - } - - this.DhRenderComponentTooltip(matrices, this.font, list, mouseX, mouseY); - } - } - - - - //==========// - // shutdown // - //==========// - - /** When you close it, it goes to the previous screen and saves */ - @Override - public void onClose() - { - ConfigHandler.INSTANCE.configFileHandler.saveToFile(); - Objects.requireNonNull(this.minecraft).setScreen(this.parent); - - CONFIG_CORE_INTERFACE.onScreenChangeListenerList.forEach((listener) -> listener.run()); - } - - - } - - - - //================// - // helper classes // - //================// - - public static class ConfigListWidget extends ContainerObjectSelectionList - { - Font textRenderer; - - public ConfigListWidget(Minecraft minecraftClient, int canvasWidth, int canvasHeight, int topMargin, int botMargin, int itemSpacing) - { - #if MC_VER < MC_1_20_4 - super(minecraftClient, canvasWidth, canvasHeight, topMargin, canvasHeight - botMargin, itemSpacing); - #else - super(minecraftClient, canvasWidth, canvasHeight - (topMargin + botMargin), topMargin, itemSpacing); - #endif - - this.centerListVertically = false; - this.textRenderer = minecraftClient.font; - } - - public void addButton(DhConfigScreen gui, AbstractConfigBase dhConfigType, AbstractWidget button, AbstractWidget resetButton, AbstractWidget indexButton, Component text) - { this.addEntry(new DhButtonEntry(gui, dhConfigType, button, text, resetButton, indexButton)); } - - @Override - public int getRowWidth() { return 10_000; } - - public AbstractWidget getHoveredButton(double mouseX, double mouseY) - { - for (DhButtonEntry buttonEntry : this.children()) - { - AbstractWidget button = buttonEntry.button; - if (button != null - && button.visible) - { - #if MC_VER < MC_1_19_4 - double minX = button.x; - double minY = button.y; - #else - double minX = button.getX(); - double minY = button.getY(); - #endif - - double maxX = minX + button.getWidth(); - double maxY = minY + button.getHeight(); - - if (mouseX >= minX && mouseX < maxX - && mouseY >= minY && mouseY < maxY) - { - return button; - } - } - } - - return null; - } - - } - - - public static class DhButtonEntry extends ContainerObjectSelectionList.Entry - { - private static final Font textRenderer = Minecraft.getInstance().font; - - private final AbstractWidget button; - - private final DhConfigScreen gui; - private final AbstractConfigBase dhConfigType; - - private final AbstractWidget resetButton; - private final AbstractWidget indexButton; - private final Component text; - private final List children = new ArrayList<>(); - - @NotNull - private final EConfigCommentTextPosition textPosition; - - public static final Map TEXT_BY_WIDGET = new HashMap<>(); - public static final Map BUTTON_BY_WIDGET = new HashMap<>(); - - - - public DhButtonEntry( - DhConfigScreen gui, AbstractConfigBase dhConfigType, - AbstractWidget button, Component text, AbstractWidget resetButton, AbstractWidget indexButton) - { - TEXT_BY_WIDGET.put(button, text); - BUTTON_BY_WIDGET.put(button, this); - - this.gui = gui; - this.dhConfigType = dhConfigType; - - this.button = button; - this.resetButton = resetButton; - this.text = text; - this.indexButton = indexButton; - - if (button != null) { this.children.add(button); } - if (resetButton != null) { this.children.add(resetButton); } - if (indexButton != null) { this.children.add(indexButton); } - - - EConfigCommentTextPosition textPosition = null; - if (this.dhConfigType instanceof ConfigUIComment) - { - textPosition = ((ConfigUIComment)this.dhConfigType).textPosition; - } - - if (textPosition == null) - { - if (this.button != null) - { - // if a button is present - textPosition = EConfigCommentTextPosition.RIGHT_JUSTIFIED; - } - else - { - textPosition = EConfigCommentTextPosition.CENTERED_OVER_BUTTONS; - } - } - this.textPosition = textPosition; - - } - - - - @Override - #if MC_VER < MC_1_20_1 - public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) - #elif MC_VER < MC_1_21_9 - public void render(GuiGraphics matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) - #elif MC_VER <= MC_1_21_11 - public void renderContent(GuiGraphics matrices, int mouseX, int mouseY, boolean hovered, float tickDelta) - #else - public void extractContent(GuiGraphicsExtractor matrices, int mouseX, int mouseY, boolean hovered, float tickDelta) - #endif - { - try - { - // setting the "y" variable is necessary so each child item - // renders at the correct height, - // if not set they will render off-screen. - #if MC_VER < MC_1_21_9 - // Y value passed in from method args - #else - int y = this.getY(); - #endif - - - - if (this.button != null) - { - SetY(this.button, y); - #if MC_VER <= MC_1_21_11 - this.button.render(matrices, mouseX, mouseY, tickDelta); - #else - this.button.extractRenderState(matrices, mouseX, mouseY, tickDelta); - #endif - } - - if (this.resetButton != null) - { - SetY(this.resetButton, y); - #if MC_VER <= MC_1_21_11 - this.resetButton.render(matrices, mouseX, mouseY, tickDelta); - #else - this.resetButton.extractRenderState(matrices, mouseX, mouseY, tickDelta); - #endif - } - - if (this.indexButton != null) - { - SetY(this.indexButton, y); - #if MC_VER <= MC_1_21_11 - this.indexButton.render(matrices, mouseX, mouseY, tickDelta); - #else - this.indexButton.extractRenderState(matrices, mouseX, mouseY, tickDelta); - #endif - } - - if (this.text != null) - { - int translatedLength = textRenderer.width(this.text); - - int textXPos; - if (this.textPosition == EConfigCommentTextPosition.RIGHT_JUSTIFIED) - { - // text right justified aligned against the buttons - textXPos = this.gui.width - - translatedLength - - ConfigScreenConfigs.SPACE_BETWEEN_TEXT_AND_OPTION_FIELD - - ConfigScreenConfigs.SPACE_FROM_RIGHT_SCREEN - - ConfigScreenConfigs.OPTION_FIELD_WIDTH - - ConfigScreenConfigs.BUTTON_WIDTH_SPACING - - ConfigScreenConfigs.RESET_BUTTON_WIDTH; - } - else if (this.textPosition == EConfigCommentTextPosition.CENTERED_OVER_BUTTONS) - { - // have button centered relative to a category button - textXPos = this.gui.width - - (translatedLength / 2) - - (ConfigScreenConfigs.CATEGORY_BUTTON_WIDTH / 2) - - ConfigScreenConfigs.SPACE_FROM_RIGHT_SCREEN; - } - else if (this.textPosition == EConfigCommentTextPosition.CENTER_OF_SCREEN) - { - // have button centered in the screen - textXPos = (this.gui.width / 2) - - (translatedLength / 2); - } - else - { - throw new UnsupportedOperationException("No text position render defined for [" + this.textPosition + "]"); - } - - - #if MC_VER < MC_1_20_1 - GuiComponent.drawString(matrices, textRenderer, - this.text, - textXPos, y + 5, - 0xFFFFFF); - #elif MC_VER < MC_1_21_6 - matrices.drawString(textRenderer, - this.text, - textXPos, y + 5, - 0xFFFFFF); - #elif MC_VER <= MC_1_21_11 - matrices.drawString(textRenderer, - this.text, - textXPos, y + 5, - 0xFFFFFFFF); - #else - matrices.text(textRenderer, - this.text, - textXPos, y + 5, - 0xFFFFFFFF); - #endif - } - } - catch (Exception e) - { - // should prevent crashing the game if there's an issue - RATE_LIMITED_LOGGER.error("Unexpected gui rendering issue: ["+e.getMessage()+"]", e); - } - } - - @Override - public @NotNull List children() - { return this.children; } - - #if MC_VER >= MC_1_17_1 - @Override - public @NotNull List narratables() - { return this.children; } - #endif - - - - } - - - - //================// - // event handling // - //================// - - public static class ConfigCoreInterface implements IConfigGui - { - /** - * in the future it would be good to pass in the current page and other variables, - * but for now just knowing when the page is closed is good enough - */ - public final ArrayList onScreenChangeListenerList = new ArrayList<>(); - - - - @Override - public void addOnScreenChangeListener(Runnable newListener) { this.onScreenChangeListenerList.add(newListener); } - @Override - public void removeOnScreenChangeListener(Runnable oldListener) { this.onScreenChangeListenerList.remove(oldListener); } - - } - -} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/GetConfigScreen.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/GetConfigScreen.java index 777e1b943..9fc094553 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/GetConfigScreen.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/GetConfigScreen.java @@ -1,5 +1,6 @@ package com.seibel.distanthorizons.common.wrappers.gui; +import com.seibel.distanthorizons.common.wrappers.gui.classicConfig.ClassicConfigGUI; import com.seibel.distanthorizons.core.config.ConfigHandler; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.coreapi.ModInfo; diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/classicConfig/ClassicConfigGUI.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/classicConfig/ClassicConfigGUI.java new file mode 100644 index 000000000..fad8baa43 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/classicConfig/ClassicConfigGUI.java @@ -0,0 +1,384 @@ +package com.seibel.distanthorizons.common.wrappers.gui.classicConfig; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import com.seibel.distanthorizons.core.config.types.*; + +import com.seibel.distanthorizons.core.config.types.enums.EConfigCommentTextPosition; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.wrapperInterfaces.config.IConfigGui; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.ContainerObjectSelectionList; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import com.seibel.distanthorizons.core.logging.DhLogger; +import org.jetbrains.annotations.NotNull; + + +#if MC_VER < MC_1_20_1 +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.gui.GuiComponent; +#elif MC_VER <= MC_1_21_11 +import net.minecraft.client.gui.GuiGraphics; +#else +import net.minecraft.client.gui.GuiGraphicsExtractor; +#endif + +#if MC_VER >= MC_1_17_1 +import net.minecraft.client.gui.narration.NarratableEntry; +#endif + +#if MC_VER <= MC_1_21_10 +#else +import net.minecraft.resources.Identifier; +#endif + +import static com.seibel.distanthorizons.common.wrappers.gui.GuiHelper.*; + + +/* + * Based upon TinyConfig but is highly modified + * https://github.com/Minenash/TinyConfig + * + * @author coolGi + * @author Motschen + * @author James Seibel + * @version 5-21-2022 + */ +@SuppressWarnings("unchecked") +public class ClassicConfigGUI +{ + private static final DhLogger LOGGER = new DhLoggerBuilder().build(); + public static final DhLogger RATE_LIMITED_LOGGER = new DhLoggerBuilder() + .maxCountPerSecond(1) + .build(); + + public static final ConfigCoreInterface CONFIG_CORE_INTERFACE = new ConfigCoreInterface(); + + + + //==============// + // Initializers // + //==============// + + // Some regexes to check if an input is valid + public static final Pattern INTEGER_ONLY_REGEX = Pattern.compile("(-?[0-9]*)"); + public static final Pattern DECIMAL_ONLY_REGEX = Pattern.compile("-?([\\d]+\\.?[\\d]*|[\\d]*\\.?[\\d]+|\\.)"); + + public static class ConfigScreenConfigs + { + // This contains all the configs for the configs + public static final int SPACE_FROM_RIGHT_SCREEN = 10; + public static final int SPACE_BETWEEN_TEXT_AND_OPTION_FIELD = 8; + public static final int BUTTON_WIDTH_SPACING = 5; + public static final int RESET_BUTTON_WIDTH = 60; + public static final int RESET_BUTTON_HEIGHT = 20; + public static final int OPTION_FIELD_WIDTH = 150; + public static final int OPTION_FIELD_HEIGHT = 20; + public static final int CATEGORY_BUTTON_WIDTH = 200; + public static final int CATEGORY_BUTTON_HEIGHT = 20; + + } + + + + //==============// + // GUI handling // + //==============// + + /** if you want to get this config gui's screen call this */ + public static Screen getScreen(Screen parent, String category) + { return new DhConfigScreen(parent, category); } + + + + //================// + // helper classes // + //================// + + public static class ConfigListWidget extends ContainerObjectSelectionList + { + Font textRenderer; + + public ConfigListWidget(Minecraft minecraftClient, int canvasWidth, int canvasHeight, int topMargin, int botMargin, int itemSpacing) + { + #if MC_VER < MC_1_20_4 + super(minecraftClient, canvasWidth, canvasHeight, topMargin, canvasHeight - botMargin, itemSpacing); + #else + super(minecraftClient, canvasWidth, canvasHeight - (topMargin + botMargin), topMargin, itemSpacing); + #endif + + this.centerListVertically = false; + this.textRenderer = minecraftClient.font; + } + + public void addButton(DhConfigScreen gui, AbstractConfigBase dhConfigType, AbstractWidget button, AbstractWidget resetButton, AbstractWidget indexButton, Component text) + { this.addEntry(new DhButtonEntry(gui, dhConfigType, button, text, resetButton, indexButton)); } + + @Override + public int getRowWidth() { return 10_000; } + + public AbstractWidget getHoveredButton(double mouseX, double mouseY) + { + for (DhButtonEntry buttonEntry : this.children()) + { + AbstractWidget button = buttonEntry.button; + if (button != null + && button.visible) + { + #if MC_VER < MC_1_19_4 + double minX = button.x; + double minY = button.y; + #else + double minX = button.getX(); + double minY = button.getY(); + #endif + + double maxX = minX + button.getWidth(); + double maxY = minY + button.getHeight(); + + if (mouseX >= minX && mouseX < maxX + && mouseY >= minY && mouseY < maxY) + { + return button; + } + } + } + + return null; + } + + } + + + public static class DhButtonEntry extends ContainerObjectSelectionList.Entry + { + private static final Font textRenderer = Minecraft.getInstance().font; + + private final AbstractWidget button; + + private final DhConfigScreen gui; + + private final AbstractWidget resetButton; + private final AbstractWidget indexButton; + private final Component text; + private final List children = new ArrayList<>(); + + @NotNull + private final EConfigCommentTextPosition textPosition; + public final AbstractConfigBase dhConfigType; + + public static final Map TEXT_BY_WIDGET = new HashMap<>(); + public static final Map BUTTON_BY_WIDGET = new HashMap<>(); + + + + public DhButtonEntry( + DhConfigScreen gui, AbstractConfigBase dhConfigType, + AbstractWidget button, Component text, AbstractWidget resetButton, AbstractWidget indexButton) + { + TEXT_BY_WIDGET.put(button, text); + BUTTON_BY_WIDGET.put(button, this); + + this.gui = gui; + this.dhConfigType = dhConfigType; + + this.button = button; + this.resetButton = resetButton; + this.text = text; + this.indexButton = indexButton; + + if (button != null) { this.children.add(button); } + if (resetButton != null) { this.children.add(resetButton); } + if (indexButton != null) { this.children.add(indexButton); } + + + EConfigCommentTextPosition textPosition = null; + if (this.dhConfigType instanceof ConfigUIComment) + { + textPosition = ((ConfigUIComment)this.dhConfigType).textPosition; + } + + if (textPosition == null) + { + if (this.button != null) + { + // if a button is present + textPosition = EConfigCommentTextPosition.RIGHT_JUSTIFIED; + } + else + { + textPosition = EConfigCommentTextPosition.CENTERED_OVER_BUTTONS; + } + } + this.textPosition = textPosition; + + } + + + + @Override + #if MC_VER < MC_1_20_1 + public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) + #elif MC_VER < MC_1_21_9 + public void render(GuiGraphics matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) + #elif MC_VER <= MC_1_21_11 + public void renderContent(GuiGraphics matrices, int mouseX, int mouseY, boolean hovered, float tickDelta) + #else + public void extractContent(GuiGraphicsExtractor matrices, int mouseX, int mouseY, boolean hovered, float tickDelta) + #endif + { + try + { + // setting the "y" variable is necessary so each child item + // renders at the correct height, + // if not set they will render off-screen. + #if MC_VER < MC_1_21_9 + // Y value passed in from method args + #else + int y = this.getY(); + #endif + + + + if (this.button != null) + { + SetY(this.button, y); + #if MC_VER <= MC_1_21_11 + this.button.render(matrices, mouseX, mouseY, tickDelta); + #else + this.button.extractRenderState(matrices, mouseX, mouseY, tickDelta); + #endif + } + + if (this.resetButton != null) + { + SetY(this.resetButton, y); + #if MC_VER <= MC_1_21_11 + this.resetButton.render(matrices, mouseX, mouseY, tickDelta); + #else + this.resetButton.extractRenderState(matrices, mouseX, mouseY, tickDelta); + #endif + } + + if (this.indexButton != null) + { + SetY(this.indexButton, y); + #if MC_VER <= MC_1_21_11 + this.indexButton.render(matrices, mouseX, mouseY, tickDelta); + #else + this.indexButton.extractRenderState(matrices, mouseX, mouseY, tickDelta); + #endif + } + + if (this.text != null) + { + int translatedLength = textRenderer.width(this.text); + + int textXPos; + if (this.textPosition == EConfigCommentTextPosition.RIGHT_JUSTIFIED) + { + // text right justified aligned against the buttons + textXPos = this.gui.width + - translatedLength + - ConfigScreenConfigs.SPACE_BETWEEN_TEXT_AND_OPTION_FIELD + - ConfigScreenConfigs.SPACE_FROM_RIGHT_SCREEN + - ConfigScreenConfigs.OPTION_FIELD_WIDTH + - ConfigScreenConfigs.BUTTON_WIDTH_SPACING + - ConfigScreenConfigs.RESET_BUTTON_WIDTH; + } + else if (this.textPosition == EConfigCommentTextPosition.CENTERED_OVER_BUTTONS) + { + // have button centered relative to a category button + textXPos = this.gui.width + - (translatedLength / 2) + - (ConfigScreenConfigs.CATEGORY_BUTTON_WIDTH / 2) + - ConfigScreenConfigs.SPACE_FROM_RIGHT_SCREEN; + } + else if (this.textPosition == EConfigCommentTextPosition.CENTER_OF_SCREEN) + { + // have button centered in the screen + textXPos = (this.gui.width / 2) + - (translatedLength / 2); + } + else + { + throw new UnsupportedOperationException("No text position render defined for [" + this.textPosition + "]"); + } + + + #if MC_VER < MC_1_20_1 + GuiComponent.drawString(matrices, textRenderer, + this.text, + textXPos, y + 5, + 0xFFFFFF); + #elif MC_VER < MC_1_21_6 + matrices.drawString(textRenderer, + this.text, + textXPos, y + 5, + 0xFFFFFF); + #elif MC_VER <= MC_1_21_11 + matrices.drawString(textRenderer, + this.text, + textXPos, y + 5, + 0xFFFFFFFF); + #else + matrices.text(textRenderer, + this.text, + textXPos, y + 5, + 0xFFFFFFFF); + #endif + } + } + catch (Exception e) + { + // should prevent crashing the game if there's an issue + RATE_LIMITED_LOGGER.error("Unexpected gui rendering issue: ["+e.getMessage()+"]", e); + } + } + + @Override + public @NotNull List children() + { return this.children; } + + #if MC_VER >= MC_1_17_1 + @Override + public @NotNull List narratables() + { return this.children; } + #endif + + + + } + + + + //================// + // event handling // + //================// + + public static class ConfigCoreInterface implements IConfigGui + { + /** + * in the future it would be good to pass in the current page and other variables, + * but for now just knowing when the page is closed is good enough + */ + public final ArrayList onScreenChangeListenerList = new ArrayList<>(); + + + + @Override + public void addOnScreenChangeListener(Runnable newListener) { this.onScreenChangeListenerList.add(newListener); } + @Override + public void removeOnScreenChangeListener(Runnable oldListener) { this.onScreenChangeListenerList.remove(oldListener); } + + } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/classicConfig/DhConfigScreen.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/classicConfig/DhConfigScreen.java new file mode 100644 index 000000000..c5c4a110a --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/gui/classicConfig/DhConfigScreen.java @@ -0,0 +1,799 @@ +package com.seibel.distanthorizons.common.wrappers.gui.classicConfig; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +import com.seibel.distanthorizons.api.enums.config.DisallowSelectingViaConfigGui; +import com.seibel.distanthorizons.common.wrappers.gui.DhScreen; +import com.seibel.distanthorizons.common.wrappers.gui.TexturedButtonWidget; +import com.seibel.distanthorizons.common.wrappers.gui.config.ConfigGuiInfo; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftClientWrapper; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.config.ConfigHandler; +import com.seibel.distanthorizons.core.config.types.*; +import com.seibel.distanthorizons.common.wrappers.gui.updater.ChangelogScreen; + +import com.seibel.distanthorizons.core.config.types.enums.EConfigCommentTextPosition; +import com.seibel.distanthorizons.core.config.types.enums.EConfigValidity; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.jar.updater.SelfUpdater; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.util.AnnotationUtil; +import com.seibel.distanthorizons.core.wrapperInterfaces.config.IConfigGui; +import com.seibel.distanthorizons.core.wrapperInterfaces.config.ILangWrapper; +import com.seibel.distanthorizons.coreapi.ModInfo; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.ContainerObjectSelectionList; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import com.seibel.distanthorizons.core.logging.DhLogger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + + +#if MC_VER < MC_1_20_1 +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.gui.GuiComponent; +#elif MC_VER <= MC_1_21_11 +import net.minecraft.client.gui.GuiGraphics; +#else +import net.minecraft.client.gui.GuiGraphicsExtractor; +#endif + +#if MC_VER >= MC_1_17_1 +import net.minecraft.client.gui.narration.NarratableEntry; +#endif + +#if MC_VER <= MC_1_21_10 +import net.minecraft.resources.ResourceLocation; +#else +import net.minecraft.resources.Identifier; +#endif + +import org.lwjgl.glfw.GLFW; +import com.mojang.blaze3d.platform.InputConstants; + +import static com.seibel.distanthorizons.common.wrappers.gui.GuiHelper.*; +import static com.seibel.distanthorizons.common.wrappers.gui.GuiHelper.Translatable; + +class DhConfigScreen extends DhScreen +{ + private static final DhLogger LOGGER = new DhLoggerBuilder().build(); + private static final ILangWrapper LANG_WRAPPER = SingletonInjector.INSTANCE.get(ILangWrapper.class); + + private static final String TRANSLATION_PREFIX = ModInfo.ID + ".config."; + + private static final MinecraftClientWrapper MC_CLIENT = MinecraftClientWrapper.INSTANCE; + + + private final Screen parent; + private final String category; + private ClassicConfigGUI.ConfigListWidget configListWidget; + private boolean reload = false; + + private Button doneButton; + + + + //=============// + // constructor // + //=============// + + protected DhConfigScreen(Screen parent, String category) + { + super(Translatable( + LANG_WRAPPER.langExists(ModInfo.ID + ".config" + (category.isEmpty() ? "." + category : "") + ".title") ? + ModInfo.ID + ".config.title" : + ModInfo.ID + ".config" + (category.isEmpty() ? "" : "." + category) + ".title") + ); + this.parent = parent; + this.category = category; + } + + + @Override + public void tick() { super.tick(); } + + + + //==================// + // menu UI creation // + //==================// + + @Override + protected void init() + { + super.init(); + if (!this.reload) + { + ConfigHandler.INSTANCE.configFileHandler.loadFromFile(); + } + + // Changelog button + if (Config.Client.Advanced.AutoUpdater.enableAutoUpdater.get() + // we only have changelogs for stable builds + && !ModInfo.IS_DEV_BUILD) + { + this.addBtn(new TexturedButtonWidget( + // Where the button is on the screen + this.width - 28, this.height - 28, + // Width and height of the button + 20, 20, + // texture UV Offset + 0, 0, + // Some texture stuff + 0, + #if MC_VER < MC_1_21_1 + new ResourceLocation(ModInfo.ID, "textures/gui/changelog.png"), + #elif MC_VER <= MC_1_21_10 + ResourceLocation.fromNamespaceAndPath(ModInfo.ID, "textures/gui/changelog.png"), + #else + Identifier.fromNamespaceAndPath(ModInfo.ID, "textures/gui/changelog.png"), + #endif + 20, 20, + // Create the button and tell it where to go + (buttonWidget) -> { + ChangelogScreen changelogScreen = new ChangelogScreen(this); + if (changelogScreen.usable) + { + Objects.requireNonNull(this.minecraft).setScreen(changelogScreen); + } + else + { + LOGGER.warn("Changelog was not able to open"); + } + }, + // Add a title to the button + Translatable(ModInfo.ID + ".updater.title") + )); + } + + + // back button + this.addBtn(MakeBtn(Translatable("distanthorizons.general.back"), + (this.width / 2) - 154, this.height - 28, + ClassicConfigGUI.ConfigScreenConfigs.OPTION_FIELD_WIDTH, ClassicConfigGUI.ConfigScreenConfigs.OPTION_FIELD_HEIGHT, + (button) -> + { + ConfigHandler.INSTANCE.configFileHandler.loadFromFile(); + Objects.requireNonNull(this.minecraft).setScreen(this.parent); + })); + + // done/close button + this.doneButton = this.addBtn( + MakeBtn(Translatable("distanthorizons.general.done"), + (this.width / 2) + 4, this.height - 28, + ClassicConfigGUI.ConfigScreenConfigs.OPTION_FIELD_WIDTH, ClassicConfigGUI.ConfigScreenConfigs.OPTION_FIELD_HEIGHT, + (button) -> + { + ConfigHandler.INSTANCE.configFileHandler.saveToFile(); + Objects.requireNonNull(this.minecraft).setScreen(this.parent); + })); + + this.configListWidget = new ClassicConfigGUI.ConfigListWidget(this.minecraft, this.width * 2, this.height, 32, 32, 25); + + #if MC_VER < MC_1_20_6 // no background is rendered in MC 1.20.6+ + if (this.minecraft != null && this.minecraft.level != null) + { + this.configListWidget.setRenderBackground(false); + } + #endif + + this.addWidget(this.configListWidget); + + for (AbstractConfigBase configEntry : ConfigHandler.INSTANCE.configBaseList) + { + try + { + if (configEntry.getCategory().matches(this.category) + && configEntry.getAppearance().showInGui) + { + this.addMenuItem(configEntry); + } + } + catch (Exception e) + { + String message = "ERROR: Failed to show [" + configEntry.getNameAndCategory() + "], error: [" + e.getMessage() + "]"; + if (configEntry.get() != null) + { + message += " with the value [" + configEntry.get() + "] with type [" + configEntry.getType() + "]"; + } + + LOGGER.error(message, e); + } + } + + ClassicConfigGUI.CONFIG_CORE_INTERFACE.onScreenChangeListenerList.forEach((listener) -> listener.run()); + } + private void addMenuItem(AbstractConfigBase configEntry) + { + trySetupConfigEntry(configEntry); + + if (this.tryCreateInputField(configEntry)) return; + if (this.tryCreateCategoryButton(configEntry)) return; + if (this.tryCreateButton(configEntry)) return; + if (this.tryCreateComment(configEntry)) return; + if (this.tryCreateSpacer(configEntry)) return; + if (this.tryCreateLinkedEntry(configEntry)) return; + + LOGGER.warn("Config [" + configEntry.getNameAndCategory() + "] failed to show. Please try something like changing its type."); + } + + private static void trySetupConfigEntry(AbstractConfigBase configMenuOption) + { + configMenuOption.guiValue = new ConfigGuiInfo(); + Class configValueClass = configMenuOption.getType(); + + if (configMenuOption instanceof ConfigEntry) + { + ConfigEntry configEntry = (ConfigEntry) configMenuOption; + + if (configValueClass == Integer.class) + { + setupTextMenuOption(configEntry, Integer::parseInt, ClassicConfigGUI.INTEGER_ONLY_REGEX, true); + } + else if (configValueClass == Double.class) + { + setupTextMenuOption(configEntry, Double::parseDouble, ClassicConfigGUI.DECIMAL_ONLY_REGEX, false); + } + else if (configValueClass == Float.class) + { + setupTextMenuOption(configEntry, Float::parseFloat, ClassicConfigGUI.DECIMAL_ONLY_REGEX, false); + } + else if (configValueClass == String.class || configValueClass == List.class) + { + // For string or list + setupTextMenuOption(configEntry, String::length, null, true); + } + else if (configValueClass == Boolean.class) + { + ConfigEntry booleanConfigEntry = (ConfigEntry) configEntry; + setupBooleanMenuOption(booleanConfigEntry); + } + else if (configValueClass.isEnum()) + { + ConfigEntry> enumConfigEntry = (ConfigEntry>) configEntry; + Class> configEnumClass = (Class>) configValueClass; + setupEnumMenuOption(enumConfigEntry, configEnumClass); + } + else + { + LOGGER.error("No definition for config with type: [" + configValueClass.getName() + "], for config: [" + configMenuOption.name + "]."); + } + } + + } + private static void setupTextMenuOption(AbstractConfigBase configMenuOption, Function parsingFunc, @Nullable Pattern pattern, boolean cast) + { + final ConfigGuiInfo configGuiInfo = ((ConfigGuiInfo) configMenuOption.guiValue); + + configGuiInfo.tooltipFunction = + (editBox, button) -> + (stringValue) -> + { + boolean isNumber = (pattern != null); + + stringValue = stringValue.trim(); + if (!(stringValue.isEmpty() || !isNumber || pattern.matcher(stringValue).matches())) + { + return false; + } + + + Number numberValue = configMenuOption.typeIsFloatingPointNumber() ? 0.0 : 0; // different default values are needed so implicit casting works correctly (if not done casting from 0 (an int) to a double will cause an exception) + configGuiInfo.errorMessage = null; + if (isNumber + && !stringValue.isEmpty() + && !stringValue.equals("-") + && !stringValue.equals(".")) + { + ConfigEntry numberConfigEntry = (ConfigEntry) configMenuOption; + + try + { + numberValue = parsingFunc.apply(stringValue); + } + catch (Exception e) + { + numberValue = null; + } + + EConfigValidity validity = numberConfigEntry.getValidity(numberValue); + switch (validity) + { + case VALID: + configGuiInfo.errorMessage = null; + break; + case NUMBER_TOO_LOW: + configGuiInfo.errorMessage = TextOrTranslatable("§cMinimum length is " + numberConfigEntry.getMin()); + break; + case NUMBER_TOO_HIGH: + configGuiInfo.errorMessage = TextOrTranslatable("§cMaximum length is " + numberConfigEntry.getMax()); + break; + case INVALID: + configGuiInfo.errorMessage = TextOrTranslatable("§cValue is invalid"); + break; + } + } + + editBox.setTextColor(((ConfigEntry) configMenuOption).getValidity(numberValue) == EConfigValidity.VALID ? 0xFFFFFFFF : 0xFFFF7777); // white and red + + + if (configMenuOption.getType() == String.class + || configMenuOption.getType() == List.class) + { + ((ConfigEntry) configMenuOption).uiSetWithoutSaving(stringValue); + } + else if (((ConfigEntry) configMenuOption).getValidity(numberValue) == EConfigValidity.VALID) + { + if (!cast) + { + ((ConfigEntry) configMenuOption).uiSetWithoutSaving(numberValue); + } + else + { + ((ConfigEntry) configMenuOption).uiSetWithoutSaving(numberValue != null ? numberValue.intValue() : 0); + } + } + + return true; + }; + } + private static void setupBooleanMenuOption(ConfigEntry booleanConfigEntry) + { + // For boolean + Function func = value -> Translatable("distanthorizons.general." + ((Boolean) value ? "true" : "false")).withStyle((Boolean) value ? ChatFormatting.GREEN : ChatFormatting.RED); + + final ConfigGuiInfo configGuiInfo = ((ConfigGuiInfo) booleanConfigEntry.guiValue); + + configGuiInfo.buttonOptionMap = + new AbstractMap.SimpleEntry>( + (button) -> + { + button.active = !booleanConfigEntry.apiIsOverriding(); + + booleanConfigEntry.uiSetWithoutSaving(!booleanConfigEntry.get()); + button.setMessage(func.apply(booleanConfigEntry.get())); + }, func); + } + private static void setupEnumMenuOption(ConfigEntry> enumConfigEntry, Class> enumClass) + { + List> enumList = Arrays.asList(enumClass.getEnumConstants()); + + final ConfigGuiInfo configGuiInfo = ((ConfigGuiInfo) enumConfigEntry.guiValue); + + Function getEnumTranslatableFunc = (value) -> Translatable(TRANSLATION_PREFIX + "enum." + enumClass.getSimpleName() + "." + enumConfigEntry.get().toString()); + configGuiInfo.buttonOptionMap = + new AbstractMap.SimpleEntry>( + (button) -> + { + // get the currently selected enum and enum index + int startingIndex = enumList.indexOf(enumConfigEntry.get()); + Enum enumValue = enumList.get(startingIndex); + + boolean shiftPressed = + InputConstants.isKeyDown(MC_CLIENT.getGlfwWindowId(), GLFW.GLFW_KEY_LEFT_SHIFT) + || InputConstants.isKeyDown(MC_CLIENT.getGlfwWindowId(), GLFW.GLFW_KEY_RIGHT_SHIFT); + + + + // move forward or backwards depending on if the shift key is pressed + int index = shiftPressed ? startingIndex - 1 : startingIndex + 1; + + // wrap around to the other side of the array when necessary + if (index >= enumList.size()) + { + index = 0; + } + else if (index < 0) + { + index = enumList.size() - 1; + } + + + // walk through the enums to find the next selectable one + while (index != startingIndex) + { + enumValue = enumList.get(index); + if (!AnnotationUtil.doesEnumHaveAnnotation(enumValue, DisallowSelectingViaConfigGui.class)) + { + // this enum shouldn't be selectable via the UI, + // skip it + break; + } + + // move forward or backwards depending on if the shift key is pressed + index = shiftPressed ? index - 1 : index + 1; + + // wrap around to the other side of the array when necessary + if (index >= enumList.size()) + { + index = 0; + } + else if (index < 0) + { + index = enumList.size() - 1; + } + } + + + if (index == startingIndex) + { + // one of the enums should be selectable, this is a programmer error + enumValue = enumList.get(startingIndex); + LOGGER.warn("Enum [" + enumValue.getClass() + "] doesn't contain any values that should be selectable via the UI, sticking to the currently selected value [" + enumValue + "]."); + } + + + enumConfigEntry.uiSetWithoutSaving(enumValue); + + button.active = !enumConfigEntry.apiIsOverriding(); + + button.setMessage(getEnumTranslatableFunc.apply(enumConfigEntry.get())); + }, getEnumTranslatableFunc); + } + + private boolean tryCreateInputField(AbstractConfigBase configBase) + { + final ConfigGuiInfo configGuiInfo = ((ConfigGuiInfo) configBase.guiValue); + + if (configBase instanceof ConfigEntry) + { + ConfigEntry configEntry = (ConfigEntry) configBase; + + + //==============// + // reset button // + //==============// + + Button.OnPress btnAction = (button) -> + { + configEntry.uiSetWithoutSaving(configEntry.getDefaultValue()); + this.reload = true; + Objects.requireNonNull(this.minecraft).setScreen(this); + }; + + int resetButtonPosX = this.width + - ClassicConfigGUI.ConfigScreenConfigs.RESET_BUTTON_WIDTH + - ClassicConfigGUI.ConfigScreenConfigs.SPACE_FROM_RIGHT_SCREEN; + int resetButtonPosZ = 0; + + Button resetButton = MakeBtn( + Translatable("distanthorizons.general.reset").withStyle(ChatFormatting.RED), + resetButtonPosX, resetButtonPosZ, + ClassicConfigGUI.ConfigScreenConfigs.RESET_BUTTON_WIDTH, ClassicConfigGUI.ConfigScreenConfigs.RESET_BUTTON_HEIGHT, + btnAction); + + if (configEntry.apiIsOverriding()) + { + resetButton.active = false; + resetButton.setMessage(Translatable("distanthorizons.general.apiOverride").withStyle(ChatFormatting.DARK_GRAY)); + } + else + { + resetButton.active = true; + } + + + + //==============// + // option field // + //==============// + + Component textComponent = this.GetTranslatableTextComponentForConfig(configEntry); + + int optionFieldPosX = this.width + - ClassicConfigGUI.ConfigScreenConfigs.SPACE_FROM_RIGHT_SCREEN + - ClassicConfigGUI.ConfigScreenConfigs.RESET_BUTTON_WIDTH + - ClassicConfigGUI.ConfigScreenConfigs.BUTTON_WIDTH_SPACING + - ClassicConfigGUI.ConfigScreenConfigs.OPTION_FIELD_WIDTH; + int optionFieldPosZ = 0; + + if (configGuiInfo.buttonOptionMap != null) + { + // enum/multi option input button + + Map.Entry> widget = configGuiInfo.buttonOptionMap; + if (configEntry.getType().isEnum()) + { + widget.setValue((value) -> Translatable(TRANSLATION_PREFIX + "enum." + configEntry.getType().getSimpleName() + "." + configEntry.get().toString())); + } + + Button button = MakeBtn( + widget.getValue().apply(configEntry.get()), + optionFieldPosX, optionFieldPosZ, + ClassicConfigGUI.ConfigScreenConfigs.OPTION_FIELD_WIDTH, ClassicConfigGUI.ConfigScreenConfigs.CATEGORY_BUTTON_HEIGHT, + widget.getKey()); + + // deactivate the button if the API is overriding it + button.active = !configEntry.apiIsOverriding(); + + + this.configListWidget.addButton(this, configEntry, + button, + resetButton, + null, + textComponent); + + return true; + } + else + { + // text box input + + EditBox widget = new EditBox(this.font, + optionFieldPosX, optionFieldPosZ, + ClassicConfigGUI.ConfigScreenConfigs.OPTION_FIELD_WIDTH - 4, ClassicConfigGUI.ConfigScreenConfigs.CATEGORY_BUTTON_HEIGHT, + Translatable("")); + widget.setMaxLength(3_000_000); // hopefully 3 million characters should be enough for any normal use-case, lol + widget.insertText(String.valueOf(configEntry.get())); + + Predicate processor = configGuiInfo.tooltipFunction.apply(widget, this.doneButton); + #if MC_VER <= MC_1_21_11 + widget.setFilter(processor); + #else + widget.setResponder(processor::test); + #endif + + this.configListWidget.addButton(this, configEntry, widget, resetButton, null, textComponent); + + return true; + } + } + + return false; + } + private boolean tryCreateCategoryButton(AbstractConfigBase configType) + { + if (configType instanceof ConfigCategory) + { + ConfigCategory configCategory = (ConfigCategory) configType; + + Component textComponent = this.GetTranslatableTextComponentForConfig(configCategory); + + int categoryPosX = this.width - ClassicConfigGUI.ConfigScreenConfigs.CATEGORY_BUTTON_WIDTH - ClassicConfigGUI.ConfigScreenConfigs.SPACE_FROM_RIGHT_SCREEN; + int categoryPosZ = this.height - ClassicConfigGUI.ConfigScreenConfigs.CATEGORY_BUTTON_HEIGHT; // Note: the posZ value here seems to be ignored + + Button widget = MakeBtn(textComponent, + categoryPosX, categoryPosZ, + ClassicConfigGUI.ConfigScreenConfigs.CATEGORY_BUTTON_WIDTH, ClassicConfigGUI.ConfigScreenConfigs.CATEGORY_BUTTON_HEIGHT, + ((button) -> + { + ConfigHandler.INSTANCE.configFileHandler.saveToFile(); + Objects.requireNonNull(this.minecraft).setScreen(ClassicConfigGUI.getScreen(this, configCategory.getDestination())); + })); + this.configListWidget.addButton(this, configType, widget, null, null, null); + + return true; + } + + return false; + } + private boolean tryCreateButton(AbstractConfigBase configType) + { + if (configType instanceof ConfigUIButton) + { + ConfigUIButton configUiButton = (ConfigUIButton) configType; + + Component textComponent = this.GetTranslatableTextComponentForConfig(configUiButton); + + int buttonPosX = this.width - ClassicConfigGUI.ConfigScreenConfigs.CATEGORY_BUTTON_WIDTH - ClassicConfigGUI.ConfigScreenConfigs.SPACE_FROM_RIGHT_SCREEN; + + Button widget = MakeBtn(textComponent, + buttonPosX, this.height - 28, + ClassicConfigGUI.ConfigScreenConfigs.CATEGORY_BUTTON_WIDTH, ClassicConfigGUI.ConfigScreenConfigs.CATEGORY_BUTTON_HEIGHT, + (button) -> ((ConfigUIButton) configType).runAction()); + this.configListWidget.addButton(this, configType, widget, null, null, null); + + return true; + } + + return false; + } + private boolean tryCreateComment(AbstractConfigBase configType) + { + if (configType instanceof ConfigUIComment) + { + ConfigUIComment configUiComment = (ConfigUIComment) configType; + + Component textComponent = this.GetTranslatableTextComponentForConfig(configUiComment); + if (configUiComment.parentConfigPath != null) + { + textComponent = Translatable(TRANSLATION_PREFIX + configUiComment.parentConfigPath); + } + + this.configListWidget.addButton(this, configType, null, null, null, textComponent); + + return true; + } + + return false; + } + private boolean tryCreateSpacer(AbstractConfigBase configType) + { + if (configType instanceof ConfigUISpacer) + { + Button spacerButton = MakeBtn(Translatable("distanthorizons.general.spacer"), + 10, 10, // having too small of a size causes division by 0 errors in older MC versions (IE 1.20.1) + 1, 1, + (button) -> { }); + + spacerButton.visible = false; + this.configListWidget.addButton(this, configType, spacerButton, null, null, null); + + return true; + } + + return false; + } + private boolean tryCreateLinkedEntry(AbstractConfigBase configType) + { + if (configType instanceof ConfigUiLinkedEntry) + { + this.addMenuItem(((ConfigUiLinkedEntry) configType).get()); + + return true; + } + + return false; + } + + private Component GetTranslatableTextComponentForConfig(AbstractConfigBase configType) + { return Translatable(TRANSLATION_PREFIX + configType.getNameAndCategory()); } + + + + //===========// + // rendering // + //===========// + + @Override +#if MC_VER < MC_1_20_1 + public void render(PoseStack matrices, int mouseX, int mouseY, float delta) +#elif MC_VER <= MC_1_21_11 + public void render(GuiGraphics matrices, int mouseX, int mouseY, float delta) +#else + public void extractRenderState(GuiGraphicsExtractor matrices, int mouseX, int mouseY, float delta) + #endif + { + #if MC_VER < MC_1_20_2 // 1.20.2 now enables this by default in the `this.list.render` function + this.renderBackground(matrices); + #elif MC_VER <= MC_1_21_11 + super.render(matrices, mouseX, mouseY, delta); + #else + super.extractRenderState(matrices, mouseX, mouseY, delta); + #endif + + // Render buttons + #if MC_VER <= MC_1_21_11 + this.configListWidget.render(matrices, mouseX, mouseY, delta); + #else + this.configListWidget.extractRenderState(matrices, mouseX, mouseY, delta); + #endif + + + // Render config title + this.DhDrawCenteredString(matrices, this.font, this.title, + this.width / 2, 15, + #if MC_VER < MC_1_21_6 + 0xFFFFFF // RGB white + #else + 0xFFFFFFFF // ARGB white + #endif ); + + + // render DH version + this.DhDrawString(matrices, this.font, TextOrLiteral(ModInfo.VERSION), 2, this.height - 10, + #if MC_VER < MC_1_21_6 + 0xAAAAAA // RGB white + #else + 0xFFAAAAAA // ARGB white + #endif ); + + // If the update is pending, display this message to inform the user that it will apply when the game restarts + if (SelfUpdater.deleteOldJarOnJvmShutdown) + { + this.DhDrawString(matrices, this.font, Translatable(ModInfo.ID + ".updater.waitingForClose"), 4, this.height - 42, + #if MC_VER < MC_1_21_6 + 0xFFFFFF // RGB white + #else + 0xFFFFFFFF // ARGB white + #endif ); + } + + + this.renderTooltip(matrices, mouseX, mouseY, delta); + + #if MC_VER < MC_1_20_2 + super.render(matrices, mouseX, mouseY, delta); + #endif + } + + #if MC_VER < MC_1_20_1 + private void renderTooltip(PoseStack matrices, int mouseX, int mouseY, float delta) + #elif MC_VER <= MC_1_21_11 + private void renderTooltip(GuiGraphics matrices, int mouseX, int mouseY, float delta) +#else + private void renderTooltip(GuiGraphicsExtractor matrices, int mouseX, int mouseY, float delta) + #endif + { + AbstractWidget hoveredWidget = this.configListWidget.getHoveredButton(mouseX, mouseY); + if (hoveredWidget == null) + { + return; + } + + + ClassicConfigGUI.DhButtonEntry button = ClassicConfigGUI.DhButtonEntry.BUTTON_BY_WIDGET.get(hoveredWidget); + + + // A quick fix for tooltips on linked entries + AbstractConfigBase configBase = ConfigUiLinkedEntry.class.isAssignableFrom(button.dhConfigType.getClass()) ? + ((ConfigUiLinkedEntry) button.dhConfigType).get() : + button.dhConfigType; + + boolean apiOverrideActive = false; + if (configBase instanceof ConfigEntry) + { + apiOverrideActive = ((ConfigEntry) configBase).apiIsOverriding(); + } + + String key = TRANSLATION_PREFIX + (configBase.category.isEmpty() ? "" : configBase.category + ".") + configBase.getName() + ".@tooltip"; + + if (apiOverrideActive) + { + key = "distanthorizons.general.disabledByApi.@tooltip"; + } + + // display the validation error tooltip if present + final ConfigGuiInfo configGuiInfo = ((ConfigGuiInfo) configBase.guiValue); + if (configGuiInfo.errorMessage != null) + { + this.DhRenderTooltip(matrices, this.font, configGuiInfo.errorMessage, mouseX, mouseY); + } + // display the tooltip if present + else if (LANG_WRAPPER.langExists(key)) + { + List list = new ArrayList<>(); + String lang = LANG_WRAPPER.getLang(key); + for (String langLine : lang.split("\n")) + { + list.add(TextOrTranslatable(langLine)); + } + + this.DhRenderComponentTooltip(matrices, this.font, list, mouseX, mouseY); + } + } + + + + //==========// + // shutdown // + //==========// + + /** When you close it, it goes to the previous screen and saves */ + @Override + public void onClose() + { + ConfigHandler.INSTANCE.configFileHandler.saveToFile(); + Objects.requireNonNull(this.minecraft).setScreen(this.parent); + + ClassicConfigGUI.CONFIG_CORE_INTERFACE.onScreenChangeListenerList.forEach((listener) -> listener.run()); + } + + +}