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.
This commit is contained in:
James Seibel
2026-04-18 09:44:37 -05:00
parent a1af4335e0
commit 539d152caa
5 changed files with 1185 additions and 1126 deletions
@@ -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;
@@ -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;
@@ -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<DhButtonEntry>
{
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<DhButtonEntry>
{
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<AbstractWidget> children = new ArrayList<>();
@NotNull
private final EConfigCommentTextPosition textPosition;
public final AbstractConfigBase dhConfigType;
public static final Map<AbstractWidget, Component> TEXT_BY_WIDGET = new HashMap<>();
public static final Map<AbstractWidget, DhButtonEntry> 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<? extends GuiEventListener> children()
{ return this.children; }
#if MC_VER >= MC_1_17_1
@Override
public @NotNull List<? extends NarratableEntry> 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<Runnable> onScreenChangeListenerList = new ArrayList<>();
@Override
public void addOnScreenChangeListener(Runnable newListener) { this.onScreenChangeListenerList.add(newListener); }
@Override
public void removeOnScreenChangeListener(Runnable oldListener) { this.onScreenChangeListenerList.remove(oldListener); }
}
}
@@ -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<Boolean> booleanConfigEntry = (ConfigEntry<Boolean>) configEntry;
setupBooleanMenuOption(booleanConfigEntry);
}
else if (configValueClass.isEnum())
{
ConfigEntry<Enum<?>> enumConfigEntry = (ConfigEntry<Enum<?>>) configEntry;
Class<? extends Enum<?>> configEnumClass = (Class<? extends Enum<?>>) 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<String, Number> 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<Number> numberConfigEntry = (ConfigEntry<Number>) 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<Number>) configMenuOption).getValidity(numberValue) == EConfigValidity.VALID ? 0xFFFFFFFF : 0xFFFF7777); // white and red
if (configMenuOption.getType() == String.class
|| configMenuOption.getType() == List.class)
{
((ConfigEntry<String>) configMenuOption).uiSetWithoutSaving(stringValue);
}
else if (((ConfigEntry<Number>) configMenuOption).getValidity(numberValue) == EConfigValidity.VALID)
{
if (!cast)
{
((ConfigEntry<Number>) configMenuOption).uiSetWithoutSaving(numberValue);
}
else
{
((ConfigEntry<Number>) configMenuOption).uiSetWithoutSaving(numberValue != null ? numberValue.intValue() : 0);
}
}
return true;
};
}
private static void setupBooleanMenuOption(ConfigEntry<Boolean> booleanConfigEntry)
{
// For boolean
Function<Object, Component> 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.OnPress, Function<Object, Component>>(
(button) ->
{
button.active = !booleanConfigEntry.apiIsOverriding();
booleanConfigEntry.uiSetWithoutSaving(!booleanConfigEntry.get());
button.setMessage(func.apply(booleanConfigEntry.get()));
}, func);
}
private static void setupEnumMenuOption(ConfigEntry<Enum<?>> enumConfigEntry, Class<? extends Enum<?>> enumClass)
{
List<Enum<?>> enumList = Arrays.asList(enumClass.getEnumConstants());
final ConfigGuiInfo configGuiInfo = ((ConfigGuiInfo) enumConfigEntry.guiValue);
Function<Object, Component> getEnumTranslatableFunc = (value) -> Translatable(TRANSLATION_PREFIX + "enum." + enumClass.getSimpleName() + "." + enumConfigEntry.get().toString());
configGuiInfo.buttonOptionMap =
new AbstractMap.SimpleEntry<Button.OnPress, Function<Object, Component>>(
(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<Button.OnPress, Function<Object, Component>> 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<String> 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<Component> 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());
}
}