diff --git a/.idea/scopes/Fabric_sources.xml b/.idea/scopes/Fabric_sources.xml deleted file mode 100644 index 0448412..0000000 --- a/.idea/scopes/Fabric_sources.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/scopes/Forge_sources.xml b/.idea/scopes/Forge_sources.xml deleted file mode 100644 index 7b5f24d..0000000 --- a/.idea/scopes/Forge_sources.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 00c5936..2ef376d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,10 @@ -Fix dependency logic +- Ensure inherited fields are present in config GUIs (closes #13). +- When both a mod ID and config file name are specified, the config file is now saved under + `config/{mod id}/{config name}.json5` (closes #12). + - This should not be a breaking change as I am not aware of any mods registering multiple configs currently. +- Switch to fabric-api mod ID in dependencies block (closes #10). +- Enable split source sets (closes #14). +- Identify config managers by `(MOD_ID, CONFIG_NAME)` rather than by just `(CONFIG_NAME)` (closes #15). +- Allow `List` config fields (closes #11). +- The reset button next to each config field now resets to the default value, rather than the value the field had when + the config screen was opened. \ No newline at end of file diff --git a/build.gradle b/build.gradle index d8f2df9..36c84d7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id "architectury-plugin" version "3.4-SNAPSHOT" - id "dev.architectury.loom" version "1.7-SNAPSHOT" apply false + id "dev.architectury.loom" version "1.9-SNAPSHOT" apply false id "com.github.breadmoirai.github-release" version "2.4.1" id "maven-publish" } @@ -26,7 +26,7 @@ subprojects { loom { silentMojangMappingsLicense() - + mixin { useLegacyMixinAp = false } diff --git a/common/build.gradle b/common/build.gradle index 1fa6e77..f314a78 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -3,7 +3,14 @@ architectury { } loom { - accessWidenerPath = file("src/main/resources/jamlib.accesswidener") + splitEnvironmentSourceSets() + + mods { + jamlib { + sourceSet sourceSets.main + sourceSet sourceSets.client + } + } } dependencies { diff --git a/common/src/main/java/io/github/jamalam360/jamlib/JamLibClient.java b/common/src/client/java/io/github/jamalam360/jamlib/client/JamLibClient.java similarity index 81% rename from common/src/main/java/io/github/jamalam360/jamlib/JamLibClient.java rename to common/src/client/java/io/github/jamalam360/jamlib/client/JamLibClient.java index 69d0d7a..6fe5960 100644 --- a/common/src/main/java/io/github/jamalam360/jamlib/JamLibClient.java +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/JamLibClient.java @@ -1,18 +1,21 @@ -package io.github.jamalam360.jamlib; +package io.github.jamalam360.jamlib.client; import static io.github.jamalam360.jamlib.JamLib.JAR_RENAMING_CHECKER; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; +import dev.architectury.event.events.client.ClientPlayerEvent; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.client.player.LocalPlayer; import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.ApiStatus; -@Environment(EnvType.CLIENT) public class JamLibClient { + @ApiStatus.Internal + public static void init() { + ClientPlayerEvent.CLIENT_PLAYER_JOIN.register(JamLibClient::onPlayerJoin); + } - public static void onPlayerJoin(LocalPlayer player) { + private static void onPlayerJoin(LocalPlayer player) { if (player != Minecraft.getInstance().player) { return; } diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/ConfigScreen.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/ConfigScreen.java new file mode 100644 index 0000000..4c4c621 --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/ConfigScreen.java @@ -0,0 +1,164 @@ +package io.github.jamalam360.jamlib.client.config.gui; + +import dev.architectury.platform.Platform; +import io.github.jamalam360.jamlib.JamLib; +import io.github.jamalam360.jamlib.client.config.gui.entry.ConfigEntry; +import io.github.jamalam360.jamlib.client.gui.WidgetList; +import io.github.jamalam360.jamlib.config.ConfigExtensions; +import io.github.jamalam360.jamlib.config.ConfigManager; +import io.github.jamalam360.jamlib.config.HiddenInGui; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.SpriteIconButton; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.resources.language.I18n; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.ApiStatus; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * A screen for editing a config managed through a {@link ConfigManager}. + */ +@ApiStatus.Internal +public class ConfigScreen extends Screen { + + protected final ConfigManager manager; + private final Screen parent; + private final List> entries; + private WidgetList widgetList; + private Button doneButton; + + public ConfigScreen(ConfigManager manager, Screen parent) { + super(createTitle(manager)); + this.manager = manager; + this.parent = parent; + this.entries = new ArrayList<>(); + } + + @ApiStatus.Internal + public static String createTranslationKey(String modId, String configName, String path) { + if (modId.equals(configName)) { + return "config." + modId + "." + path; + } else { + return "config." + modId + "." + configName + "." + path; + } + } + + protected static Component createTitle(ConfigManager manager) { + String translationKey = createTranslationKey(manager.getModId(), manager.getConfigName(), "title"); + + if (I18n.exists(translationKey)) { + return Component.translatable(translationKey); + } else { + return Component.literal(Platform.getMod(manager.getModId()).getName()); + } + } + + @Override + protected void init() { + super.init(); + + this.addRenderableWidget(Button.builder(CommonComponents.GUI_CANCEL, button -> { + this.manager.reloadFromDisk(); + Objects.requireNonNull(this.minecraft).setScreen(this.parent); + }).pos(this.width / 2 - 154, this.height - 28).size(150, 20).build()); + + this.doneButton = this.addRenderableWidget(Button.builder(CommonComponents.GUI_DONE, button -> { + if (this.hasChanges()) { + this.manager.save(); + } + + Objects.requireNonNull(this.minecraft).setScreen(this.parent); + }).pos(this.width / 2 + 4, this.height - 28).size(150, 20).build()); + + SpriteIconButton editManuallyButton = this.addRenderableWidget( + SpriteIconButton.builder(Component.translatable("config.jamlib.edit_manually"), button -> { + if (this.hasChanges()) { + this.manager.save(); + } + + Util.getPlatform().openFile(Platform.getConfigFolder().resolve(this.manager.getConfigName() + ".json5").toFile()); + Objects.requireNonNull(this.minecraft).setScreen(this.parent); + }, true).sprite(JamLib.id("writable_book"), 16, 16).size(20, 20).build() + ); + editManuallyButton.setX(7); + editManuallyButton.setY(7); + this.widgetList = new WidgetList(this.minecraft, this.width, this.height - 64, 32); + + if (this.entries.isEmpty()) { + for (Field field : this.manager.getConfigClass().getFields()) { + if (field.isAnnotationPresent(HiddenInGui.class)) { + continue; + } + + this.entries.add(ConfigEntry.createFromField(this.manager.getModId(), this.manager.getConfigName(), field)); + } + } + + for (ConfigEntry entry : this.entries) { + this.widgetList.addWidgetGroup(entry.createWidgets(this.width)); + } + + this.addRenderableWidget(this.widgetList); + + if (this.manager.get() instanceof ConfigExtensions ext) { + List links = ext.getLinks(); + + for (int i = 0; i < links.size(); i++) { + ConfigExtensions.Link link = links.get(i); + SpriteIconButton linkButton = this.addRenderableWidget( + SpriteIconButton.builder(link.getTooltip(), button -> { + try { + Util.getPlatform().openUri(link.getUrl().toURI()); + } catch (Exception e) { + JamLib.LOGGER.error("Failed to open link", e); + } + }, true).sprite(link.getTexture(), 16, 16).size(20, 20).build() + + ); + linkButton.setX(this.width - 30 - (28 * i)); + linkButton.setY(5); + } + } + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + super.render(graphics, mouseX, mouseY, delta); + graphics.drawCenteredString(Minecraft.getInstance().font, this.title, this.width / 2, 12, 0xFFFFFF); + } + + private boolean canExit() { + return this.entries.stream().allMatch(ConfigEntry::isValid); + } + + private boolean hasChanges() { + return this.entries.stream().anyMatch(ConfigEntry::hasChanged); + } + + @Override + public void tick() { + super.tick(); + boolean canExit = this.canExit(); + + if (this.doneButton.active != canExit) { + this.doneButton.active = canExit; + } + + for (int i = 0; i < this.entries.size(); i++) { + ConfigEntry entry = this.entries.get(i); + List widgets = entry.getNewWidgets(this.width); + if (widgets != null) { + this.widgetList.updateWidgetGroup(i, widgets); + } + } + } +} diff --git a/common/src/main/java/io/github/jamalam360/jamlib/config/gui/SelectConfigScreen.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/SelectConfigScreen.java similarity index 95% rename from common/src/main/java/io/github/jamalam360/jamlib/config/gui/SelectConfigScreen.java rename to common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/SelectConfigScreen.java index 79d7cdd..f265795 100644 --- a/common/src/main/java/io/github/jamalam360/jamlib/config/gui/SelectConfigScreen.java +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/SelectConfigScreen.java @@ -1,4 +1,4 @@ -package io.github.jamalam360.jamlib.config.gui; +package io.github.jamalam360.jamlib.client.config.gui; import dev.architectury.platform.Platform; import io.github.jamalam360.jamlib.config.ConfigManager; @@ -16,17 +16,16 @@ @ApiStatus.Internal public class SelectConfigScreen extends Screen { - private final String modId; private final Screen parent; public SelectConfigScreen(Screen parent, String modId) { - super(getTitleComponent(modId)); + super(createTitle(modId)); this.parent = parent; this.modId = modId; } - private static Component getTitleComponent(String modId) { + private static Component createTitle(String modId) { String translationKey = "config." + modId + ".title"; if (I18n.exists(translationKey)) { @@ -54,7 +53,6 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { } private static class ConfigSelectionList extends SelectionList { - public ConfigSelectionList(Minecraft minecraft, int width, int height, int y, int itemHeight) { super(minecraft, width, height, y, itemHeight); } diff --git a/common/src/main/java/io/github/jamalam360/jamlib/config/gui/SelectionList.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/SelectionList.java similarity index 97% rename from common/src/main/java/io/github/jamalam360/jamlib/config/gui/SelectionList.java rename to common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/SelectionList.java index 866fb92..270f962 100644 --- a/common/src/main/java/io/github/jamalam360/jamlib/config/gui/SelectionList.java +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/SelectionList.java @@ -1,4 +1,4 @@ -package io.github.jamalam360.jamlib.config.gui; +package io.github.jamalam360.jamlib.client.config.gui; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; @@ -10,7 +10,6 @@ @ApiStatus.Internal public class SelectionList extends ContainerObjectSelectionList { - public SelectionList(Minecraft minecraft, int width, int height, int y, int itemHeight) { super(minecraft, width, height, y, itemHeight); this.centerListVertically = false; diff --git a/common/src/main/java/io/github/jamalam360/jamlib/config/gui/SelectionListEntry.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/SelectionListEntry.java similarity index 98% rename from common/src/main/java/io/github/jamalam360/jamlib/config/gui/SelectionListEntry.java rename to common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/SelectionListEntry.java index cccf1d7..3016b20 100644 --- a/common/src/main/java/io/github/jamalam360/jamlib/config/gui/SelectionListEntry.java +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/SelectionListEntry.java @@ -1,4 +1,4 @@ -package io.github.jamalam360.jamlib.config.gui; +package io.github.jamalam360.jamlib.client.config.gui; import java.util.List; @@ -17,7 +17,6 @@ @ApiStatus.Internal public class SelectionListEntry extends ContainerObjectSelectionList.Entry { - private final Component title; private final List tooltip; private final List widgets; diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/BooleanConfigEntry.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/BooleanConfigEntry.java new file mode 100644 index 0000000..2d5ed8e --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/BooleanConfigEntry.java @@ -0,0 +1,39 @@ +package io.github.jamalam360.jamlib.client.config.gui.entry; + +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class BooleanConfigEntry extends ConfigEntry { + @Nullable + private Button button = null; + + public BooleanConfigEntry(String modId, String configName, ConfigField field) { + super(modId, configName, field); + } + + @Override + public List createElementWidgets(int left, int width) { + this.button = Button.builder(this.getComponent(Boolean.TRUE.equals(this.getFieldValue())), button -> this.setFieldValue(!(Boolean.TRUE.equals(this.getFieldValue())))).pos(left, 0).size(width, 20).build(); + + return List.of(this.button); + } + + @Override + public void onChange() { + super.onChange(); + + if (this.button != null) { + this.button.setMessage(getComponent(Boolean.TRUE.equals(this.getFieldValue()))); + } + } + + private Component getComponent(boolean value) { + return Component.literal(value ? "Yes" : "No").withStyle(s -> s.withColor(value ? ChatFormatting.GREEN : ChatFormatting.RED)); + } +} diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/ConfigEntry.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/ConfigEntry.java new file mode 100644 index 0000000..2073f64 --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/ConfigEntry.java @@ -0,0 +1,200 @@ +package io.github.jamalam360.jamlib.client.config.gui.entry; + +import com.google.gson.Gson; +import io.github.jamalam360.jamlib.JamLib; +import io.github.jamalam360.jamlib.client.config.gui.ConfigScreen; +import io.github.jamalam360.jamlib.client.gui.ScrollingStringWidget; +import io.github.jamalam360.jamlib.client.mixinsupport.MutableSpriteImageWidget$Sprite; +import io.github.jamalam360.jamlib.config.ConfigExtensions; +import io.github.jamalam360.jamlib.config.ConfigManager; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.ImageWidget; +import net.minecraft.client.gui.components.SpriteIconButton; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.resources.language.I18n; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public abstract class ConfigEntry { + private static final Gson GSON = new Gson(); + protected final ConfigField field; + protected final ConfigManager configManager; + protected final V originalValue; + private final String translationKey; + @Nullable + private final Component tooltip; + protected ImageWidget validationIcon; + @Nullable + protected List errors; + private boolean recreateWidgetsNextTick = false; + + @SuppressWarnings({"rawtypes", "unchecked"}) + public static ConfigEntry createFromField(String modId, String configName, Field field) { + Class c = field.getType(); + + if (c == boolean.class) { + return new BooleanConfigEntry<>(modId, configName, new FieldConfigField(field)); + } else if (c == float.class || c == double.class || c == int.class || c == long.class) { + return new NumberConfigEntry<>(modId, configName, new FieldConfigField(field)); + } else if (c == String.class) { + return new StringConfigEntry<>(modId, configName, new FieldConfigField(field)); + } else if (c.isEnum()) { + return new EnumConfigEntry<>(modId, configName, new FieldConfigField(field)); + } else if (Collection.class.isAssignableFrom(c)) { + return new ListConfigEntry<>(modId, configName, new FieldConfigField(field)); + } else { + throw new IllegalArgumentException("Unsupported config field type " + c); + } + } + + public ConfigEntry(String modId, String configName, ConfigField field) { + this.field = field; + //noinspection unchecked + this.configManager = (ConfigManager) ConfigManager.MANAGERS.get(new ConfigManager.Key(modId, configName)); + this.originalValue = this.cloneObject(this.getFieldValue()); + this.translationKey = ConfigScreen.createTranslationKey(modId, configName, field.getName()); + + if (I18n.exists(this.translationKey + ".tooltip")) { + this.tooltip = Component.translatable(this.translationKey + ".tooltip"); + } else { + this.tooltip = null; + } + } + + public List createWidgets(int width) { + List widgets = new ArrayList<>(); + + ScrollingStringWidget title = new ScrollingStringWidget(12, Minecraft.getInstance().font.lineHeight / 2 + 1, width / 2 - 10, Minecraft.getInstance().font.lineHeight, Component.translatable(this.translationKey), Minecraft.getInstance().font); + + if (this.tooltip != null) { + title.setTooltip(Tooltip.create(this.tooltip)); + } + + widgets.add(title); + + this.validationIcon = ImageWidget.sprite(20, 20, JamLib.id("validation_warning")); + this.validationIcon.setX(width - 212); + this.validationIcon.setY(0); + this.validationIcon.setTooltip(Tooltip.create(Component.translatable("config.jamlib.requires_restart_tooltip"))); + this.validationIcon.visible = false; + widgets.add(this.validationIcon); + + widgets.addAll(this.createElementWidgets(width - 188, 150)); + + SpriteIconButton resetButton = SpriteIconButton.builder(Component.translatable("config.jamlib.reset"), (button) -> this.setFieldValue(this.getDefaultValue()), true).sprite(JamLib.id("reset"), 16, 16).size(20, 20).build(); + resetButton.setX(width - 30); + resetButton.setY(0); + resetButton.setTooltip(Tooltip.create(Component.translatable("config.jamlib.reset_tooltip"))); + widgets.add(resetButton); + + return widgets; + } + + public abstract List createElementWidgets(int left, int width); + + public void onChange() { + this.validate(); + } + + protected void validate() { + V newValue = this.getFieldValue(); + + if (this.configManager.get() instanceof ConfigExtensions) { + @SuppressWarnings("unchecked") ConfigExtensions ext = (ConfigExtensions) this.configManager.get(); + this.errors = ext.getValidationErrors(this.configManager, new ConfigExtensions.FieldValidationInfo(this.field.getName(), newValue, this.originalValue, this.field.getBackingField())); + this.errors.sort((o1, o2) -> o2.type().ordinal() - o1.type().ordinal()); + this.updateValidationIcon(); + } + } + + protected void updateValidationIcon() { + if (this.validationIcon != null) { + if (this.isValid()) { + this.validationIcon.visible = false; + } else { + this.validationIcon.visible = true; + ((MutableSpriteImageWidget$Sprite) this.validationIcon).setSprite(this.errors.getFirst().type().getTexture()); + this.validationIcon.setTooltip(Tooltip.create(this.errors.getFirst().message())); + } + } + } + + @Nullable + public List getNewWidgets(int width) { + if (this.recreateWidgetsNextTick) { + this.recreateWidgetsNextTick = false; + return this.createWidgets(width); + } else { + return null; + } + } + + public void recreateWidgetsNextTick() { + this.recreateWidgetsNextTick = true; + } + + public boolean hasChanged() { + return this.getFieldValue().equals(this.originalValue); + } + + public boolean isValid() { + return this.errors == null || this.errors.stream().noneMatch(e -> e.type() == ConfigExtensions.ValidationError.Type.ERROR); + } + + public Component getName() { + return Component.translatable(this.translationKey); + } + + protected V getFieldValue() { + return this.field.getValue(this.configManager); + } + + protected void setFieldValue(V v) { + Object realValue = v; + + if (v instanceof Number n) { + Class c = this.field.getElementType(); + + if (c == double.class || c == Double.class) { + realValue = n.doubleValue(); + } else if (c == float.class || c == Float.class) { + realValue = n.floatValue(); + } else if (c == int.class || c == Integer.class) { + realValue = n.intValue(); + } else if (c == long.class || c == Long.class) { + realValue = n.longValue(); + } + } + + //noinspection unchecked + this.field.setValue(this.configManager, (V) realValue); + this.onChange(); + } + + private V getDefaultValue() { + try { + T defaultConfig = this.configManager.getConfigClass().getConstructor().newInstance(); + //noinspection unchecked + return (V) this.field.getBackingField().get(defaultConfig); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException e) { + throw new RuntimeException("Failed to get default config for config " + this.configManager.getConfigClass(), e); + } + } + + private V cloneObject(V object) { + if (object == null) { + return null; + } + + //noinspection unchecked + return (V) GSON.fromJson(GSON.toJson(object), object.getClass()); + } +} diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/ConfigField.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/ConfigField.java new file mode 100644 index 0000000..314d560 --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/ConfigField.java @@ -0,0 +1,16 @@ +package io.github.jamalam360.jamlib.client.config.gui.entry; + +import io.github.jamalam360.jamlib.config.ConfigManager; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +public interface ConfigField { + V getValue(ConfigManager manager); + void setValue(ConfigManager manager, V value); + boolean isAnnotationPresent(Class annotationClass); + A getAnnotation(Class annotationClass); + Class getElementType(); + String getName(); + Field getBackingField(); +} diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/EnumButton.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/EnumButton.java new file mode 100644 index 0000000..a78f61c --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/EnumButton.java @@ -0,0 +1,36 @@ +package io.github.jamalam360.jamlib.client.config.gui.entry; + +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.MutableComponent; + +import java.util.function.Consumer; + +public class EnumButton> extends Button { + private final Class enumClass; + private final Consumer> onChange; + private int index; + + @SuppressWarnings("unchecked") + protected EnumButton(int x, int y, int width, int height, MutableComponent description, Class> enumClass, Consumer> onChange) { + super(x, y, width, height, CommonComponents.EMPTY, b -> { + ((EnumButton) b).setIndex((((EnumButton) b).index + 1) % ((EnumButton) b).enumClass.getEnumConstants().length); + ((EnumButton) b).onChange.accept(((EnumButton) b)); + }, s -> description); + this.enumClass = (Class) enumClass; + this.onChange = onChange; + this.index = 0; + } + + protected E getValue() { + return this.enumClass.getEnumConstants()[this.index]; + } + + protected void setValue(E value) { + this.setIndex(value.ordinal()); + } + + protected void setIndex(int index) { + this.index = index; + } +} diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/EnumConfigEntry.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/EnumConfigEntry.java new file mode 100644 index 0000000..8ad7cdd --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/EnumConfigEntry.java @@ -0,0 +1,56 @@ +package io.github.jamalam360.jamlib.client.config.gui.entry; + +import io.github.jamalam360.jamlib.client.config.gui.ConfigScreen; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.resources.language.I18n; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class EnumConfigEntry> extends ConfigEntry { + @Nullable + private EnumButton button = null; + + public EnumConfigEntry(String modId, String configName, ConfigField field) { + super(modId, configName, field); + } + + @Override + public List createElementWidgets(int left, int width) { + //noinspection unchecked + this.button = new EnumButton<>( + left, + 0, + width, + 20, + CommonComponents.EMPTY.copy(), + (Class>) this.field.getElementType(), + (b) -> this.setFieldValue(b.getValue()) + ); + this.button.setValue(this.getFieldValue()); + this.button.setMessage(this.getComponent()); + + return List.of(this.button); + } + + @Override + public void onChange() { + super.onChange(); + + if (this.button != null) { + this.button.setMessage(this.getComponent()); + } + } + + private Component getComponent() { + String translationKey = ConfigScreen.createTranslationKey(this.configManager.getModId(), this.configManager.getConfigName(), field.getName() + "." + this.getFieldValue().name().toLowerCase()); + + if (I18n.exists(translationKey)) { + return Component.translatable(translationKey); + } else { + return Component.literal(this.getFieldValue().name()); + } + } +} diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/FieldConfigField.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/FieldConfigField.java new file mode 100644 index 0000000..2930e55 --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/FieldConfigField.java @@ -0,0 +1,61 @@ +package io.github.jamalam360.jamlib.client.config.gui.entry; + +import io.github.jamalam360.jamlib.JamLib; +import io.github.jamalam360.jamlib.config.ConfigManager; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +public class FieldConfigField implements ConfigField { + private final Field field; + + public FieldConfigField(Field field) { + this.field = field; + } + + @SuppressWarnings("unchecked") + @Override + public V getValue(ConfigManager manager) { + try { + return (V) this.field.get(manager.get()); + } catch (IllegalAccessException e) { + JamLib.LOGGER.error("Failed to access field for config {}", manager.getConfigName(), e); + return null; + } + } + + @Override + public void setValue(ConfigManager manager, V value) { + try { + this.field.set(manager.get(), value); + } catch (IllegalAccessException e) { + JamLib.LOGGER.error("Failed to access field for config {}", manager.getConfigName(), e); + } + } + + @Override + public boolean isAnnotationPresent(Class annotationClass) { + return this.field.isAnnotationPresent(annotationClass); + } + + @Override + public T1 getAnnotation(Class annotationClass) { + return this.field.getAnnotation(annotationClass); + } + + @SuppressWarnings("unchecked") + @Override + public Class getElementType() { + return (Class) this.field.getType(); + } + + @Override + public String getName() { + return this.field.getName(); + } + + @Override + public Field getBackingField() { + return this.field; + } +} diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/ListConfigEntry.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/ListConfigEntry.java new file mode 100644 index 0000000..ffdf2de --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/ListConfigEntry.java @@ -0,0 +1,124 @@ +package io.github.jamalam360.jamlib.client.config.gui.entry; + +import io.github.jamalam360.jamlib.JamLib; +import io.github.jamalam360.jamlib.client.gui.WidgetList; +import io.github.jamalam360.jamlib.config.ConfigExtensions; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.ImageWidget; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.network.chat.Component; + +import java.lang.reflect.ParameterizedType; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Predicate; + +public class ListConfigEntry extends ConfigEntry> { + private List> listMembers; + + public ListConfigEntry(String modId, String configName, ConfigField> field) { + super(modId, configName, field); + } + + @Override + public List createElementWidgets(int left, int width) { + this.createListMembers(); + List widgets = new ArrayList<>(); + int currentY = 0; + int bottom = 0; + int childWidth = width - 20 - WidgetList.PADDING; + + for (int i = 0; i < this.listMembers.size(); i++) { + ConfigEntry entry = this.listMembers.get(i); + List entryWidgets = entry.createElementWidgets(left, childWidth); + + for (AbstractWidget widget : entryWidgets) { + bottom = Math.max(widget.getBottom(), bottom); + widget.setY(widget.getY() + currentY); + } + + int finalI = i; + widgets.add(Button.builder(Component.literal("-"), button -> { + this.getFieldValue().remove(finalI); + this.recreateWidgetsNextTick(); + this.onChange(); + }).size(20, 20).pos(left + childWidth + WidgetList.PADDING, currentY).build()); + widgets.addAll(entryWidgets); + currentY += bottom + WidgetList.PADDING; + } + + widgets.add(Button.builder(Component.literal("+"), button -> { + //noinspection unchecked + this.getFieldValue().add((E) this.getDefaultNewValue()); + this.recreateWidgetsNextTick(); + this.onChange(); + }).size(width, 20).pos(left, currentY).build()); + this.updateValidationIcon(); + return widgets; + } + + @SuppressWarnings("unchecked") + private void createListMembers() { + this.listMembers = new ArrayList<>(); + List list = this.getFieldValue(); + Class elementType = (Class) ((ParameterizedType) this.field.getBackingField().getGenericType()).getActualTypeArguments()[0]; + for (int i = 0; i < list.size(); i++) { + if (elementType == boolean.class) { + this.listMembers.add((ConfigEntry) new BooleanConfigEntry<>(this.configManager.getModId(), this.configManager.getConfigName(), (ConfigField) new ListMemberConfigField<>(this.field.getBackingField(), elementType, i))); + } else if (elementType == float.class || elementType == double.class || elementType == int.class || elementType == long.class || elementType == Float.class || elementType == Double.class || elementType == Integer.class || elementType == Long.class) { + this.listMembers.add((ConfigEntry) new NumberConfigEntry<>(this.configManager.getModId(), this.configManager.getConfigName(), (ConfigField) new ListMemberConfigField<>(this.field.getBackingField(), elementType, i))); + } else if (elementType == String.class) { + this.listMembers.add((ConfigEntry) new StringConfigEntry<>(this.configManager.getModId(), this.configManager.getConfigName(), (ConfigField) new ListMemberConfigField<>(this.field.getBackingField(), elementType, i))); + } else if (elementType.isEnum()) { + this.listMembers.add((ConfigEntry) new EnumConfigEntry<>(this.configManager.getModId(), this.configManager.getConfigName(), (ConfigField) new ListMemberConfigField<>(this.field.getBackingField(), elementType, i))); + } else if (Collection.class.isAssignableFrom(elementType)) { + throw new IllegalArgumentException("Cannot nest collections in config"); + } else { + throw new IllegalArgumentException("Unsupported config field type " + elementType); + } + } + } + + @Override + protected void validate() { + super.validate(); + if (this.configManager.get() instanceof ConfigExtensions) { + @SuppressWarnings("unchecked") ConfigExtensions ext = (ConfigExtensions) this.configManager.get(); + + List elementErrors = new ArrayList<>(); + + for (ConfigEntry entry : this.listMembers) { + this.errors.addAll(ext.getValidationErrors(this.configManager, new ConfigExtensions.FieldValidationInfo(entry.field.getName(), entry.getFieldValue(), entry.originalValue, entry.field.getBackingField()))); + } + + this.errors.sort((o1, o2) -> o2.type().ordinal() - o1.type().ordinal()); + this.updateValidationIcon(); + } + } + + @Override + public boolean isValid() { + return super.isValid() && this.listMembers.stream().allMatch(ConfigEntry::isValid); + } + + @SuppressWarnings("unchecked") + private Object getDefaultNewValue() { + Class c = (Class) ((ParameterizedType) this.field.getBackingField().getGenericType()).getActualTypeArguments()[0]; + + if (c == boolean.class) { + return false; + } else if (c == float.class || c == double.class || c == int.class || c == long.class || c == Float.class || c == Double.class || c == Integer.class || c == Long.class) { + return 0; + } else if (c == String.class) { + return ""; + } else if (c.isEnum()) { + return c.getEnumConstants()[0]; + } else if (Collection.class.isAssignableFrom(c)) { + throw new IllegalArgumentException("Cannot nest collections in config"); + } else { + throw new IllegalArgumentException("Unsupported config field type " + c); + } + } +} diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/ListMemberConfigField.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/ListMemberConfigField.java new file mode 100644 index 0000000..2db249e --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/ListMemberConfigField.java @@ -0,0 +1,68 @@ +package io.github.jamalam360.jamlib.client.config.gui.entry; + +import io.github.jamalam360.jamlib.JamLib; +import io.github.jamalam360.jamlib.config.ConfigManager; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.List; + +public class ListMemberConfigField implements ConfigField { + private final Field listField; + private final Class elementClass; + private final int index; + + public ListMemberConfigField(Field listField, Class elementClass, int index) { + this.listField = listField; + this.elementClass = elementClass; + this.index = index; + } + + @SuppressWarnings("unchecked") + @Override + public V getValue(ConfigManager manager) { + try { + List list = (List) this.listField.get(manager.get()); + return this.index >= 0 && this.index < list.size() ? list.get(this.index) : null; + } catch (IllegalAccessException e) { + JamLib.LOGGER.error("Failed to access field for config {}", manager.getConfigName(), e); + return null; + } + } + + @SuppressWarnings("unchecked") + @Override + public void setValue(ConfigManager manager, V value) { + try { + List list = (List) this.listField.get(manager.get()); + list.set(this.index, value); + } catch (IllegalAccessException e) { + JamLib.LOGGER.error("Failed to access field for config {}", manager.getConfigName(), e); + } + } + + @Override + public boolean isAnnotationPresent(Class annotationClass) { + return this.listField.isAnnotationPresent(annotationClass); + } + + @Override + public T1 getAnnotation(Class annotationClass) { + return this.listField.getAnnotation(annotationClass); + } + + @Override + public Class getElementType() { + return this.elementClass; + } + + @Override + public String getName() { + return this.listField.getName() + "." + this.index; + } + + @Override + public Field getBackingField() { + return this.listField; + } +} diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/NumberConfigEntry.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/NumberConfigEntry.java new file mode 100644 index 0000000..94978d1 --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/NumberConfigEntry.java @@ -0,0 +1,97 @@ +package io.github.jamalam360.jamlib.client.config.gui.entry; + +import io.github.jamalam360.jamlib.config.Slider; +import io.github.jamalam360.jamlib.config.WithinRange; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.text.DecimalFormat; +import java.util.List; +import java.util.function.Function; +import java.util.regex.Pattern; + +public class NumberConfigEntry extends ConfigEntry { + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##"); + private final Function parser; + private final Pattern regex; + @Nullable + private EditBox editBox = null; + + public NumberConfigEntry(String modId, String configName, ConfigField field) { + super(modId, configName, field); + + Class c = field.getElementType(); + + if (c == float.class || c == Float.class) { + this.parser = Float::parseFloat; + this.regex = Pattern.compile("^-?\\d*\\.?\\d*$"); + } else if (c == double.class || c == Double.class) { + this.parser = Double::parseDouble; + this.regex = Pattern.compile("^-?\\d*\\.?\\d*$"); + } else if (c == int.class || c == Integer.class) { + this.parser = Integer::parseInt; + this.regex = Pattern.compile("^-?\\d*$"); + } else if (c == long.class || c == Long.class) { + this.parser = Long::parseLong; + this.regex = Pattern.compile("^-?\\d*$"); + } else { + throw new IllegalArgumentException("Unsupported class for NumberConfigEntry " + c); + } + } + + @Override + public List createElementWidgets(int left, int width) { + Number current = this.getFieldValue(); + + if (this.field.isAnnotationPresent(Slider.class)) { + WithinRange range = this.field.getAnnotation(WithinRange.class); + + if (current == null) { + current = range.min(); + } + + SliderButton slider = new SliderButton( + left, + 0, + width, + 20, + this.getComponent(current), + range.min(), + range.max(), + current.doubleValue(), + value -> { + //noinspection unchecked + this.setFieldValue((V) value); + return this.getComponent(value); + } + ); + return List.of(slider); + } else { + this.editBox = new EditBox( + Minecraft.getInstance().font, + left, + 0, + width, + 20, + CommonComponents.EMPTY + ); + this.editBox.setValue(DECIMAL_FORMAT.format(current.doubleValue())); + this.editBox.setFilter(s -> this.regex.matcher(s).matches()); + this.editBox.setResponder(s -> { + if (!s.isEmpty()) { + //noinspection unchecked + this.setFieldValue((V) this.parser.apply(s)); + } + }); + return List.of(this.editBox); + } + } + + private Component getComponent(Number value) { + return Component.literal(DECIMAL_FORMAT.format(value.doubleValue())); + } +} diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/SliderButton.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/SliderButton.java new file mode 100644 index 0000000..cb46c35 --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/SliderButton.java @@ -0,0 +1,34 @@ +package io.github.jamalam360.jamlib.client.config.gui.entry; + +import net.minecraft.client.gui.components.AbstractSliderButton; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; + +import java.util.function.Function; + +public class SliderButton extends AbstractSliderButton { + private final double min; + private final double max; + private final Function onChange; + + protected SliderButton(int x, int y, int width, int height, Component message, double min, double max, double value, Function onChange) { + super(x, y, width, height, message, value); + this.min = min; + this.max = max; + this.onChange = onChange; + this.value = ((Mth.clamp((float) value, this.min, this.max) - this.min) / (this.max - this.min)); + } + + public void setValue(double value) { + this.value = ((Mth.clamp((float) value, this.min, this.max) - this.min) / (this.max - this.min)); + } + + @Override + protected void updateMessage() { + } + + @Override + protected void applyValue() { + this.setMessage(this.onChange.apply(Mth.lerp(Mth.clamp(this.value, 0.0F, 1.0F), this.min, this.max))); + } +} diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/StringConfigEntry.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/StringConfigEntry.java new file mode 100644 index 0000000..208c0d2 --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/StringConfigEntry.java @@ -0,0 +1,35 @@ +package io.github.jamalam360.jamlib.client.config.gui.entry; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class StringConfigEntry extends ConfigEntry { + @Nullable + private EditBox editBox = null; + + public StringConfigEntry(String modId, String configName, ConfigField field) { + super(modId, configName, field); + } + + @Override + public List createElementWidgets(int left, int width) { + this.editBox = new EditBox( + Minecraft.getInstance().font, + left, + 0, + width, + 20, + CommonComponents.EMPTY + ); + this.editBox.setValue(this.getFieldValue()); + this.editBox.setResponder(this::setFieldValue); + + return List.of(this.editBox); + } +} diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/package-info.java b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/package-info.java new file mode 100644 index 0000000..2ba9aeb --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/config/gui/entry/package-info.java @@ -0,0 +1,7 @@ +/** + * All classes in this package are internal. + */ +@ApiStatus.Internal +package io.github.jamalam360.jamlib.client.config.gui.entry; + +import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/gui/ScrollingStringWidget.java b/common/src/client/java/io/github/jamalam360/jamlib/client/gui/ScrollingStringWidget.java new file mode 100644 index 0000000..f2626c6 --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/gui/ScrollingStringWidget.java @@ -0,0 +1,25 @@ +package io.github.jamalam360.jamlib.client.gui; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.StringWidget; +import net.minecraft.network.chat.Component; + +/** + * A string widget that scrolls if the component is too long for the width + */ +public class ScrollingStringWidget extends StringWidget { + public ScrollingStringWidget(int x, int y, int width, int height, Component component, Font font) { + super(x, y, width, height, component, font); + } + + @Override + public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + this.renderScrollingString(guiGraphics, this.getFont(), 2, this.getColor()); + + if (this.isMouseOver(mouseX, mouseY) && this.getTooltip() != null) { + guiGraphics.renderTooltip(Minecraft.getInstance().font, this.getTooltip().toCharSequence(Minecraft.getInstance()), mouseX, mouseY); + } + } +} diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/gui/WidgetList.java b/common/src/client/java/io/github/jamalam360/jamlib/client/gui/WidgetList.java new file mode 100644 index 0000000..30c0456 --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/gui/WidgetList.java @@ -0,0 +1,156 @@ +package io.github.jamalam360.jamlib.client.gui; + +import com.google.common.collect.ImmutableList; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +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.narration.NarratableEntry; +import net.minecraft.util.Mth; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * A scrollable list of widget groups. Widget groups can have arbitrary heights. + */ +public class WidgetList extends ContainerObjectSelectionList { + public static final int PADDING = 4; + + public WidgetList(Minecraft minecraft, int width, int height, int y) { + super(minecraft, width, height, y, 1); + this.centerListVertically = false; + this.headerHeight = PADDING; + } + + public void addWidgetGroup(List widgets) { + this.addEntry(new Entry(widgets)); + } + + public void updateWidgetGroup(int index, List widgets) { + this.children().set(index, new Entry(widgets)); + } + + @Override + public int getRowWidth() { + return this.width; + } + + @Nullable + public Entry getRealEntryAtPosition(double mouseX, double mouseY) { + int halfRowWidth = this.getRowWidth() / 2; + int centerX = this.getX() + this.width / 2; + int left = centerX - halfRowWidth; + int right = centerX + halfRowWidth; + int m = Mth.floor(mouseY - (double) this.getY()) - this.headerHeight + (int) this.getScrollAmount() - 4; + + if (mouseX < left || mouseX > right || m < 0) { + return null; + } + + int height = 0; + + for (int idx = 0; idx < this.getItemCount(); idx++) { + Entry entry = this.getEntry(idx); + height += entry.getHeight() + PADDING; + if (m < height) { + return entry; + } + } + + return null; + } + + @Override + protected void renderListItems(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { + for (int itemIdx = 0; itemIdx < this.getItemCount(); ++itemIdx) { + int top = this.getRowTop(itemIdx); + int bottom = this.getRowBottom(itemIdx); + if (bottom >= this.getY() && top <= this.getBottom()) { + this.renderItem(graphics, mouseX, mouseY, partialTick, itemIdx, this.getRowLeft(), top, this.getRowWidth(), this.getEntry(itemIdx).getHeight()); + } + } + } + + @Override + protected int getMaxPosition() { + int itemsHeight = 0; + + for (int i = 0; i < this.getItemCount(); i++) { + itemsHeight += this.getEntry(i).getHeight() + PADDING; + } + + return itemsHeight + this.headerHeight; + } + + @Override + public int getRowTop(int index) { + int itemsHeight = 0; + + for (int i = 0; i < index; i++) { + itemsHeight += this.getEntry(i).getHeight() + PADDING; + } + + return this.getY() - (int) this.getScrollAmount() + itemsHeight + this.headerHeight; + } + + @Override + public int getRowBottom(int index) { + return this.getRowTop(index) + this.getEntry(index).getHeight(); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { + this.setScrollAmount(this.getScrollAmount() - scrollY * 10); + return true; + } + + @Override + protected int getScrollbarPosition() { + return this.getX() + this.width - 6; + } + + public static class Entry extends ContainerObjectSelectionList.Entry { + private final List children; + private final List childYs; + + private Entry(List list) { + this.children = ImmutableList.copyOf(list); + this.childYs = this.children.stream().map(AbstractWidget::getY).toList(); + } + + @Override + public void render(GuiGraphics guiGraphics, int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean hovering, float partialTick) { + for (int i = 0; i < this.children.size(); i++) { + AbstractWidget widget = this.children.get(i); + int relativeY = this.childYs.get(i); + widget.setY(top + relativeY); + widget.render(guiGraphics, mouseX, mouseY, partialTick); + } + } + + public int getHeight() { + int maxY = 0; + + for (int i = 0; i < this.children.size(); i++) { + AbstractWidget widget = this.children.get(i); + int relativeY = this.childYs.get(i); + maxY = Math.max(relativeY + widget.getHeight(), maxY); + } + + return maxY; + } + + @Override + public @NotNull List children() { + return this.children; + } + + @Override + public @NotNull List narratables() { + return this.children; + } + } +} diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/mixin/AbstractSelectionListMixin.java b/common/src/client/java/io/github/jamalam360/jamlib/client/mixin/AbstractSelectionListMixin.java new file mode 100644 index 0000000..2cfcf83 --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/mixin/AbstractSelectionListMixin.java @@ -0,0 +1,22 @@ +package io.github.jamalam360.jamlib.client.mixin; + +import io.github.jamalam360.jamlib.client.gui.WidgetList; +import net.minecraft.client.gui.components.AbstractSelectionList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(AbstractSelectionList.class) +public class AbstractSelectionListMixin { + @Inject( + method = "getEntryAtPosition", + at = @At("HEAD"), + cancellable = true + ) + private void jamlib$modifyGetEntryAtPositionForWidgetList(double mouseX, double mouseY, CallbackInfoReturnable cir) { + if ((Object) this instanceof WidgetList widgetList) { + cir.setReturnValue(widgetList.getRealEntryAtPosition(mouseX, mouseY)); + } + } +} diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/mixin/ImageWidget$SpriteMixin.java b/common/src/client/java/io/github/jamalam360/jamlib/client/mixin/ImageWidget$SpriteMixin.java new file mode 100644 index 0000000..b0bc1e7 --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/mixin/ImageWidget$SpriteMixin.java @@ -0,0 +1,21 @@ +package io.github.jamalam360.jamlib.client.mixin; + +import io.github.jamalam360.jamlib.client.mixinsupport.MutableSpriteImageWidget$Sprite; +import net.minecraft.resources.ResourceLocation; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(targets = "net.minecraft.client.gui.components.ImageWidget$Sprite") +public class ImageWidget$SpriteMixin implements MutableSpriteImageWidget$Sprite { + @Mutable + @Shadow + @Final + private ResourceLocation sprite; + + @Override + public void setSprite(ResourceLocation sprite) { + this.sprite = sprite; + } +} diff --git a/common/src/main/java/io/github/jamalam360/jamlib/mixin/event/ClientPacketListenerMixin.java b/common/src/client/java/io/github/jamalam360/jamlib/client/mixin/event/ClientPacketListenerMixin.java similarity index 95% rename from common/src/main/java/io/github/jamalam360/jamlib/mixin/event/ClientPacketListenerMixin.java rename to common/src/client/java/io/github/jamalam360/jamlib/client/mixin/event/ClientPacketListenerMixin.java index 02b658f..ad57593 100644 --- a/common/src/main/java/io/github/jamalam360/jamlib/mixin/event/ClientPacketListenerMixin.java +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/mixin/event/ClientPacketListenerMixin.java @@ -1,4 +1,4 @@ -package io.github.jamalam360.jamlib.mixin.event; +package io.github.jamalam360.jamlib.client.mixin.event; import io.github.jamalam360.jamlib.events.client.ClientPlayLifecycleEvents; import net.fabricmc.api.EnvType; diff --git a/common/src/main/java/io/github/jamalam360/jamlib/mixin/event/ConnectionMixin.java b/common/src/client/java/io/github/jamalam360/jamlib/client/mixin/event/ConnectionMixin.java similarity index 95% rename from common/src/main/java/io/github/jamalam360/jamlib/mixin/event/ConnectionMixin.java rename to common/src/client/java/io/github/jamalam360/jamlib/client/mixin/event/ConnectionMixin.java index 3f99ca9..cafa229 100644 --- a/common/src/main/java/io/github/jamalam360/jamlib/mixin/event/ConnectionMixin.java +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/mixin/event/ConnectionMixin.java @@ -1,4 +1,4 @@ -package io.github.jamalam360.jamlib.mixin.event; +package io.github.jamalam360.jamlib.client.mixin.event; import io.github.jamalam360.jamlib.events.client.ClientPlayLifecycleEvents; import io.netty.channel.ChannelHandlerContext; diff --git a/common/src/client/java/io/github/jamalam360/jamlib/client/mixinsupport/MutableSpriteImageWidget$Sprite.java b/common/src/client/java/io/github/jamalam360/jamlib/client/mixinsupport/MutableSpriteImageWidget$Sprite.java new file mode 100644 index 0000000..385288b --- /dev/null +++ b/common/src/client/java/io/github/jamalam360/jamlib/client/mixinsupport/MutableSpriteImageWidget$Sprite.java @@ -0,0 +1,7 @@ +package io.github.jamalam360.jamlib.client.mixinsupport; + +import net.minecraft.resources.ResourceLocation; + +public interface MutableSpriteImageWidget$Sprite { + void setSprite(ResourceLocation texture); +} diff --git a/common/src/main/java/io/github/jamalam360/jamlib/events/client/ClientPlayLifecycleEvents.java b/common/src/client/java/io/github/jamalam360/jamlib/events/client/ClientPlayLifecycleEvents.java similarity index 89% rename from common/src/main/java/io/github/jamalam360/jamlib/events/client/ClientPlayLifecycleEvents.java rename to common/src/client/java/io/github/jamalam360/jamlib/events/client/ClientPlayLifecycleEvents.java index c0d8996..5f363a0 100644 --- a/common/src/main/java/io/github/jamalam360/jamlib/events/client/ClientPlayLifecycleEvents.java +++ b/common/src/client/java/io/github/jamalam360/jamlib/events/client/ClientPlayLifecycleEvents.java @@ -1,12 +1,10 @@ package io.github.jamalam360.jamlib.events.client; -import com.mojang.authlib.minecraft.client.MinecraftClient; import dev.architectury.event.Event; import dev.architectury.event.EventFactory; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; -import net.minecraft.world.entity.player.Player; /** * Events for client-side player lifecycle events. diff --git a/common/src/client/resources/jamlib.client.mixins.json b/common/src/client/resources/jamlib.client.mixins.json new file mode 100644 index 0000000..2546a69 --- /dev/null +++ b/common/src/client/resources/jamlib.client.mixins.json @@ -0,0 +1,19 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "io.github.jamalam360.jamlib.client.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "ImageWidget$SpriteMixin", + "event.ClientPacketListenerMixin", + "event.ConnectionMixin" + ], + "client": [ + "AbstractSelectionListMixin" + ], + "server": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/common/src/main/java/io/github/jamalam360/jamlib/JamLib.java b/common/src/main/java/io/github/jamalam360/jamlib/JamLib.java index 8cc4b6b..80611a4 100644 --- a/common/src/main/java/io/github/jamalam360/jamlib/JamLib.java +++ b/common/src/main/java/io/github/jamalam360/jamlib/JamLib.java @@ -14,14 +14,13 @@ public class JamLib { public static final String MOD_ID = "jamlib"; public static final String MOD_NAME = "JamLib"; public static final Logger LOGGER = LoggerFactory.getLogger(MOD_NAME); - protected static final JarRenamingChecker JAR_RENAMING_CHECKER = new JarRenamingChecker(); + @ApiStatus.Internal + public static final JarRenamingChecker JAR_RENAMING_CHECKER = new JarRenamingChecker(); @ApiStatus.Internal public static void init() { LOGGER.info("Initializing JamLib on {}", JamLibPlatform.getPlatform()); checkForJarRenaming(JamLib.class); - - EnvExecutor.runInEnv(EnvType.CLIENT, () -> () -> ClientPlayerEvent.CLIENT_PLAYER_JOIN.register(JamLibClient::onPlayerJoin)); } /** diff --git a/common/src/main/java/io/github/jamalam360/jamlib/config/ConfigExtensions.java b/common/src/main/java/io/github/jamalam360/jamlib/config/ConfigExtensions.java index 767d8c2..f9d76aa 100644 --- a/common/src/main/java/io/github/jamalam360/jamlib/config/ConfigExtensions.java +++ b/common/src/main/java/io/github/jamalam360/jamlib/config/ConfigExtensions.java @@ -26,6 +26,12 @@ default List getLinks() { return List.of(); } + /** + * Called after {@link ConfigManager#save()} has been called. + * This is a good place to manually perform syncing, if that is necessary for your config. + */ + default void afterSave() { } + /** * Can be used to validate your config fields after they have been edited in the config screen. Remember that this function is only called when the user is using a * config screen - if they are editing the file directly they are on their own. @@ -49,7 +55,7 @@ default List getValidationErrors(ConfigManager manager, Fiel result.add(new ValidationError(ValidationError.Type.WARNING, info, Component.translatable("config.jamlib.requires_restart_tooltip"))); } - if (info.backingField().isAnnotationPresent(WithinRange.class)) { + if (info.backingField().isAnnotationPresent(WithinRange.class) && !List.class.isAssignableFrom(info.backingField().getType())) { if (info.backingField().getType() != double.class && info.backingField().getType() != Double.class && info.backingField().getType() != float.class && info.backingField().getType() != Float.class && info.backingField().getType() != int.class && info.backingField().getType() != Integer.class @@ -77,9 +83,9 @@ default List getValidationErrors(ConfigManager manager, Fiel class Link { // I am not an artist but these are recognizable at least. - public static final ResourceLocation DISCORD = JamLib.id("textures/gui/link_discord.png"); - public static final ResourceLocation GENERIC_LINK = JamLib.id("textures/gui/link_generic.png"); - public static final ResourceLocation GITHUB = JamLib.id("textures/gui/link_github.png"); + public static final ResourceLocation DISCORD = JamLib.id("link_discord"); + public static final ResourceLocation GENERIC_LINK = JamLib.id("link_generic"); + public static final ResourceLocation GITHUB = JamLib.id("link_github"); private final ResourceLocation texture; private final URL url; @@ -144,7 +150,7 @@ public enum Type { private final ResourceLocation texture; Type() { - this.texture = JamLib.id("textures/gui/validation_" + this.name().toLowerCase() + ".png"); + this.texture = JamLib.id("validation_" + this.name().toLowerCase()); } public ResourceLocation getTexture() { diff --git a/common/src/main/java/io/github/jamalam360/jamlib/config/ConfigManager.java b/common/src/main/java/io/github/jamalam360/jamlib/config/ConfigManager.java index db2d8c5..f8fbfb3 100644 --- a/common/src/main/java/io/github/jamalam360/jamlib/config/ConfigManager.java +++ b/common/src/main/java/io/github/jamalam360/jamlib/config/ConfigManager.java @@ -24,10 +24,10 @@ * @see RequiresRestart */ public class ConfigManager { - @ApiStatus.Internal - public static final Map> MANAGERS = new HashMap<>(); + public static final Map> MANAGERS = new HashMap<>(); private static final Jankson JANKSON = Jankson.builder().build(); + private static final JsonGrammar JSON_GRAMMER = JsonGrammar.builder().bareRootObject(false).bareSpecialNumerics(false).printCommas(true).printWhitespace(true).printUnquotedKeys(true).withComments(true).build(); private final Path configPath; private final String modId; private final String configName; @@ -53,8 +53,14 @@ public ConfigManager(String modId, Class configClass) { * @param configClass The config class */ public ConfigManager(String modId, String configName, Class configClass) { - MANAGERS.put(configName, this); - this.configPath = Platform.getConfigFolder().resolve(configName + ".json5"); + MANAGERS.put(new Key(modId, configName), this); + + if (modId.equals(configName)) { + this.configPath = Platform.getConfigFolder().resolve(configName + ".json5"); + } else { + this.configPath = Platform.getConfigFolder().resolve(modId).resolve(configName + ".json5"); + } + this.configName = configName; this.modId = modId; this.configClass = configClass; @@ -106,15 +112,22 @@ public String getModId() { public void save() { JsonElement json = JANKSON.toJson(this.config); transformJsonBeforeSave(json); - JsonGrammar grammar = JsonGrammar.builder().bareRootObject(false).bareSpecialNumerics(false).printCommas(true).printWhitespace(true).printUnquotedKeys(true).withComments(true).build(); - String stringifiedJson = json.toJson(grammar); + String stringifiedJson = json.toJson(JSON_GRAMMER); try { + if (!Files.exists(this.configPath.getParent())) { + Files.createDirectories(this.configPath.getParent()); + } + Files.writeString(this.configPath, stringifiedJson); JamLib.LOGGER.info("Updated config file at {}", this.configPath); } catch (IOException e) { JamLib.LOGGER.error("Failed to write config file at {}", this.configPath, e); } + + if (this.config instanceof ConfigExtensions ext) { + ext.afterSave(); + } } /** @@ -148,7 +161,7 @@ private void validateConfigClass() { } } } catch (IllegalArgumentException | IllegalAccessException e) { - JamLib.LOGGER.error("Failed to validate config class " + this.configClass.getName(), e); + JamLib.LOGGER.error("Failed to validate config class {}", this.configClass.getName(), e); } } @@ -156,7 +169,7 @@ private T createDefaultConfig() { try { return this.configClass.getConstructor().newInstance(); } catch (Exception e) { - JamLib.LOGGER.error("Failed to create default config for " + this.configClass.getName(), e); + JamLib.LOGGER.error("Failed to create default config for {}", this.configClass.getName(), e); } return null; @@ -173,7 +186,7 @@ private void transformJsonBeforeSave(JsonElement e) { } } - private void attachDefaultComments(Class clazz, Object defaults, JsonObject obj) { + private void attachDefaultComments(Class clazz, T defaults, JsonObject obj) { for (String key : obj.keySet()) { JsonElement e = obj.get(key); if (e instanceof JsonObject) { @@ -237,4 +250,7 @@ private void attachDefaultComments(Class clazz, Object defaults, JsonObject o } } } + + public record Key(String modId, String configName) { + } } diff --git a/common/src/main/java/io/github/jamalam360/jamlib/config/HiddenInGui.java b/common/src/main/java/io/github/jamalam360/jamlib/config/HiddenInGui.java index 954472a..6350c01 100644 --- a/common/src/main/java/io/github/jamalam360/jamlib/config/HiddenInGui.java +++ b/common/src/main/java/io/github/jamalam360/jamlib/config/HiddenInGui.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; /** - * Applying this to a config field will cause it to not show in the {@link io.github.jamalam360.jamlib.config.gui.ConfigScreen}. + * Applying this to a config field will cause it to not show in the {@link io.github.jamalam360.jamlib.client.config.gui.ConfigScreen}. * * @see ConfigManager */ diff --git a/common/src/main/java/io/github/jamalam360/jamlib/config/gui/ButtonWithTextureWidget.java b/common/src/main/java/io/github/jamalam360/jamlib/config/gui/ButtonWithTextureWidget.java deleted file mode 100644 index 5fa9bb6..0000000 --- a/common/src/main/java/io/github/jamalam360/jamlib/config/gui/ButtonWithTextureWidget.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.github.jamalam360.jamlib.config.gui; - -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.components.Tooltip; -import net.minecraft.network.chat.CommonComponents; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.resources.ResourceLocation; - -class ButtonWithTextureWidget extends Button { - - private final ResourceLocation texture; - private final int textureWidth; - private final int textureHeight; - - protected ButtonWithTextureWidget(int x, int y, int width, int height, MutableComponent description, ResourceLocation texture, int textureWidth, int textureHeight, OnPress onPress) { - super(x, y, width, height, CommonComponents.EMPTY, onPress, s -> description); - this.setTooltip(Tooltip.create(description)); - this.texture = texture; - this.textureWidth = textureWidth; - this.textureHeight = textureHeight; - } - - @Override - protected void renderWidget(GuiGraphics guiGraphics, int i, int j, float f) { - super.renderWidget(guiGraphics, i, j, f); - - int x = this.getX() + (this.width - this.textureWidth) / 2; - int y = this.getY() + (this.height - this.textureHeight) / 2; - - guiGraphics.blit(this.texture, x, y, this.textureWidth, this.textureHeight, 0.0F, 0.0F, this.textureWidth, this.textureHeight, this.textureWidth, this.textureHeight); - } -} diff --git a/common/src/main/java/io/github/jamalam360/jamlib/config/gui/ConfigScreen.java b/common/src/main/java/io/github/jamalam360/jamlib/config/gui/ConfigScreen.java deleted file mode 100644 index 0dc23d1..0000000 --- a/common/src/main/java/io/github/jamalam360/jamlib/config/gui/ConfigScreen.java +++ /dev/null @@ -1,719 +0,0 @@ -package io.github.jamalam360.jamlib.config.gui; - -import com.mojang.blaze3d.systems.RenderSystem; -import dev.architectury.platform.Platform; -import io.github.jamalam360.jamlib.JamLib; -import io.github.jamalam360.jamlib.config.ConfigExtensions; -import io.github.jamalam360.jamlib.config.ConfigManager; -import io.github.jamalam360.jamlib.config.HiddenInGui; -import io.github.jamalam360.jamlib.config.Slider; -import io.github.jamalam360.jamlib.config.WithinRange; -import java.lang.reflect.Field; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.regex.Pattern; -import net.minecraft.ChatFormatting; -import net.minecraft.Util; -import net.minecraft.client.InputType; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.ComponentPath; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.AbstractSliderButton; -import net.minecraft.client.gui.components.AbstractWidget; -import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.components.EditBox; -import net.minecraft.client.gui.components.ImageWidget; -import net.minecraft.client.gui.components.Tooltip; -import net.minecraft.client.gui.narration.NarratedElementType; -import net.minecraft.client.gui.narration.NarrationElementOutput; -import net.minecraft.client.gui.navigation.CommonInputs; -import net.minecraft.client.gui.navigation.FocusNavigationEvent; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.resources.language.I18n; -import net.minecraft.client.sounds.SoundManager; -import net.minecraft.network.chat.CommonComponents; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.FormattedCharSequence; -import net.minecraft.util.Mth; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * A screen for editing a config managed through a {@link ConfigManager}. - */ -@ApiStatus.Internal -public class ConfigScreen extends Screen { - - protected final ConfigManager manager; - private final Screen parent; - private final List entries; - private final List changedFields; - private Button doneButton; - - public ConfigScreen(ConfigManager manager, Screen parent) { - super(createTitle(manager)); - this.manager = manager; - this.parent = parent; - this.entries = new ArrayList<>(); - this.changedFields = new ArrayList<>(); - } - - protected static String createTranslationKey(String modId, String configName, String path) { - if (modId.equals(configName)) { - return "config." + modId + "." + path; - } else { - return "config." + modId + "." + configName + "." + path; - } - } - - protected static Component createTitle(ConfigManager manager) { - String translationKey = createTranslationKey(manager.getModId(), manager.getConfigName(), "title"); - - if (I18n.exists(translationKey)) { - return Component.translatable(translationKey); - } else { - return Component.literal(Platform.getMod(manager.getModId()).getName()); - } - } - - @Override - protected void init() { - super.init(); - - this.addRenderableWidget(Button.builder(CommonComponents.GUI_CANCEL, button -> { - this.manager.reloadFromDisk(); - Objects.requireNonNull(this.minecraft).setScreen(this.parent); - }).pos(this.width / 2 - 154, this.height - 28).size(150, 20).build()); - - this.doneButton = this.addRenderableWidget(Button.builder(CommonComponents.GUI_DONE, button -> { - if (!this.changedFields.isEmpty()) { - this.manager.save(); - } - - Objects.requireNonNull(this.minecraft).setScreen(this.parent); - }).pos(this.width / 2 + 4, this.height - 28).size(150, 20).build()); - - this.addRenderableWidget( - new ButtonWithTextureWidget( - 7, 7, 20, 20, Component.translatable("config.jamlib.edit_manually"), ResourceLocation.withDefaultNamespace("textures/item/writable_book.png"), 16, 16, - button -> { - if (!this.changedFields.isEmpty()) { - this.manager.save(); - } - - Util.getPlatform().openFile(Platform.getConfigFolder().resolve(this.manager.getConfigName() + ".json5").toFile()); - Objects.requireNonNull(this.minecraft).setScreen(this.parent); - } - ) - ); - - ConfigEntryList list = new ConfigEntryList(this.minecraft, this.width, this.height - 64, 32, 25); - - if (this.entries.isEmpty()) { - for (Field field : this.manager.getConfigClass().getDeclaredFields()) { - if (field.isAnnotationPresent(HiddenInGui.class)) { - continue; - } - this.entries.add(new GuiEntry(this.manager.getModId(), this.manager.getConfigName(), field)); - } - } - - for (GuiEntry entry : this.entries) { - list.addEntry(entry); - } - - this.addRenderableWidget(list); - - if (this.manager.get() instanceof ConfigExtensions ext) { - List links = ext.getLinks(); - - for (int i = 0; i < links.size(); i++) { - ConfigExtensions.Link link = links.get(i); - this.addRenderableWidget( - new ButtonWithTextureWidget( - this.width - 30 - (28 * i), 5, 20, 20, (MutableComponent) link.getTooltip(), link.getTexture(), 16, 16, - button -> { - try { - Util.getPlatform().openUri(link.getUrl().toURI()); - } catch (Exception e) { - JamLib.LOGGER.error("Failed to open link", e); - } - }) - ); - } - } - } - - @Override - public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - super.render(graphics, mouseX, mouseY, delta); - graphics.drawCenteredString(Minecraft.getInstance().font, this.title, this.width / 2, 12, 0xFFFFFF); - } - - private boolean canExit() { - return this.entries.stream().allMatch(GuiEntry::isValid); - } - - @Override - public void tick() { - super.tick(); - boolean canExit = this.canExit(); - - if (this.doneButton.active != canExit) { - this.doneButton.active = canExit; - } - } - - /** - * A copy of {@link ImageWidget}{@code .Texture} that allows you to pass in an x and y value - */ - private static class TextureWidget extends AbstractWidget { - - private ResourceLocation texture; - - public TextureWidget(int x, int y, int width, int height, ResourceLocation texture) { - super(x, y, width, height, CommonComponents.EMPTY); - this.texture = texture; - } - - @Override - public boolean isMouseOver(double d, double e) { - return this.visible && d >= (double) this.getX() && e >= (double) this.getY() && d < (double) (this.getX() + this.width) && e < (double) (this.getY() + this.height); - } - - public void setTexture(ResourceLocation texture) { - this.texture = texture; - } - - @Override - public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - graphics.blit(this.texture, this.getX(), this.getY(), this.getWidth(), this.getHeight(), 0.0F, 0.0F, this.getWidth(), this.getHeight(), this.getWidth(), this.getHeight()); - } - - @Override - protected void updateWidgetNarration(NarrationElementOutput _ignored) { - } - - @Override - public void playDownSound(SoundManager _ignored) { - } - - @Override - public boolean isActive() { - return false; - } - - @Override - @Nullable - public ComponentPath nextFocusPath(FocusNavigationEvent _ignored) { - return null; - } - } - - /** - * Had to be copied from {@link AbstractSliderButton} because that was too private to extend properly - */ - private static class SliderButton extends AbstractWidget { - - private static final ResourceLocation SLIDER_SPRITE = ResourceLocation.withDefaultNamespace("widget/slider"); - private static final ResourceLocation HIGHLIGHTED_SPRITE = ResourceLocation.withDefaultNamespace("widget/slider_highlighted"); - private static final ResourceLocation SLIDER_HANDLE_SPRITE = ResourceLocation.withDefaultNamespace("widget/slider_handle"); - private static final ResourceLocation SLIDER_HANDLE_HIGHLIGHTED_SPRITE = ResourceLocation.withDefaultNamespace("widget/slider_handle_highlighted"); - private final double min; - private final double max; - private final Consumer onChange; - protected double value; - private boolean canChangeValue; - - public SliderButton(int x, int y, int width, int height, Component message, double min, double max, double current, Consumer onChange) { - super(x, y, width, height, message); - this.value = current; - this.min = min; - this.max = max; - this.onChange = onChange; - } - - private ResourceLocation getSprite() { - return this.isFocused() && !this.canChangeValue ? HIGHLIGHTED_SPRITE : SLIDER_SPRITE; - } - - private ResourceLocation getHandleSprite() { - return !this.isHovered && !this.canChangeValue ? SLIDER_HANDLE_SPRITE : SLIDER_HANDLE_HIGHLIGHTED_SPRITE; - } - - protected @NotNull MutableComponent createNarrationMessage() { - return Component.translatable("gui.narrate.slider", this.getMessage()); - } - - public void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { - narrationElementOutput.add(NarratedElementType.TITLE, this.createNarrationMessage()); - - if (this.active) { - if (this.isFocused()) { - narrationElementOutput.add(NarratedElementType.USAGE, Component.translatable("narration.slider.usage.focused")); - } else { - narrationElementOutput.add(NarratedElementType.USAGE, Component.translatable("narration.slider.usage.hovered")); - } - } - } - - public void renderWidget(GuiGraphics guiGraphics, int i, int j, float f) { - Minecraft minecraft = Minecraft.getInstance(); - guiGraphics.setColor(1.0F, 1.0F, 1.0F, this.alpha); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - RenderSystem.enableDepthTest(); - guiGraphics.blitSprite(this.getSprite(), this.getX(), this.getY(), this.getWidth(), this.getHeight()); - double position = (this.value) / (this.max - this.min); - double handleX = this.getX() + position * (this.getWidth() - 8); - guiGraphics.blitSprite(this.getHandleSprite(), (int) handleX, this.getY(), 8, this.getHeight()); - guiGraphics.setColor(1.0F, 1.0F, 1.0F, 1.0F); - int k = this.active ? 16777215 : 10526880; - this.renderScrollingString(guiGraphics, minecraft.font, 2, k | Mth.ceil(this.alpha * 255.0F) << 24); - } - - public void onClick(double d, double e) { - this.setValueFromMouse(d); - } - - public void setFocused(boolean bl) { - super.setFocused(bl); - - if (!bl) { - this.canChangeValue = false; - } else { - InputType inputType = Minecraft.getInstance().getLastInputType(); - - if (inputType == InputType.MOUSE || inputType == InputType.KEYBOARD_TAB) { - this.canChangeValue = true; - } - } - } - - public boolean keyPressed(int i, int j, int k) { - if (CommonInputs.selected(i)) { - this.canChangeValue = !this.canChangeValue; - return true; - } else { - if (this.canChangeValue) { - boolean bl = i == 263; - if (bl || i == 262) { - float f = bl ? -1.0F : 1.0F; - double step = (this.max - this.min) / (this.width / 8F); - this.setValue(Mth.clamp(this.value + step * f, this.min, this.max)); - return true; - } - } - - return false; - } - } - - private void setValueFromMouse(double d) { - double position = (d - (double) (this.getX() + 4)) / (double) (this.width - 8); - this.setValue(Mth.clamp(position * (this.max - this.min) + this.min, this.min, this.max)); - } - - protected void onDrag(double d, double e, double f, double g) { - this.setValueFromMouse(d); - super.onDrag(d, e, f, g); - } - - @Override - public void playDownSound(SoundManager soundManager) { - } - - @Override - public void onRelease(double d, double e) { - super.playDownSound(Minecraft.getInstance().getSoundManager()); - } - - protected double getValue() { - return this.value; - } - - private void setValue(double d) { - double e = this.value; - this.value = Mth.clamp(d, this.min, this.max); - if (e != this.value) { - this.onChange.accept(this); - } - } - } - - private static class EnumButton> extends Button { - - private final Class enumClass; - private final Consumer> onChange; - private int index; - - @SuppressWarnings("unchecked") - protected EnumButton(int x, int y, int width, int height, MutableComponent description, Class> enumClass, Consumer> onChange) { - super(x, y, width, height, CommonComponents.EMPTY, b -> { - ((EnumButton) b).setIndex((((EnumButton) b).index + 1) % ((EnumButton) b).enumClass.getEnumConstants().length); - ((EnumButton) b).onChange.accept(((EnumButton) b)); - }, s -> description); - this.enumClass = (Class) enumClass; - this.onChange = onChange; - this.index = 0; - } - - private E getValue() { - return this.enumClass.getEnumConstants()[this.index]; - } - - private void setValue(E value) { - this.setIndex(value.ordinal()); - } - - private void setIndex(int index) { - this.index = index; - } - } - - private class GuiEntry { - - private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##"); - private final Type type; - private final String translationKey; - private final List tooltip; - private final Field field; - private final Object initialValue; - private boolean isValid = true; - - @SuppressWarnings("unchecked") - protected GuiEntry(String modId, String configName, Field field) { - this.type = Type.fromField(field); - this.field = field; - this.initialValue = this.getFieldValue((ConfigManager) ConfigManager.MANAGERS.get(configName)); - this.translationKey = ConfigScreen.createTranslationKey(modId, configName, field.getName()); - - String tooltipTranslationKey = this.translationKey + ".tooltip"; - - if (I18n.exists(tooltipTranslationKey)) { - this.tooltip = Minecraft.getInstance().font.split(Component.translatable(tooltipTranslationKey), 200); - } else { - this.tooltip = null; - } - } - - private static Component getBooleanComponent(boolean v) { - return Component.literal(v ? "Yes" : "No").withStyle(s -> s.withColor(v ? ChatFormatting.GREEN : ChatFormatting.RED)); - } - - private static Component getEnumComponent(ConfigManager manager, Field field, Enum enumValue) { - String translationKey = ConfigScreen.createTranslationKey(manager.getModId(), manager.getConfigName(), field.getName() + "." + enumValue.name().toLowerCase()); - - if (I18n.exists(translationKey)) { - return Component.translatable(translationKey); - } else { - return Component.literal(enumValue.name()); - } - } - - protected java.util.List createWidget(ConfigManager manager, int width) { - java.util.List widgets = new ArrayList<>(); - - TextureWidget validationIcon = new TextureWidget(width - 212, 0, 20, 20, JamLib.id("textures/gui/validation_warning.png")); - validationIcon.setTooltip(Tooltip.create(Component.translatable("config.jamlib.requires_restart_tooltip"))); - validationIcon.visible = false; - widgets.add(validationIcon); - - switch (this.type) { - case BOOLEAN: - widgets.add(Button.builder(getBooleanComponent(Boolean.TRUE.equals(getFieldValue(manager))), (button) -> { - this.setFieldValue(manager, !(Boolean.TRUE.equals(this.getFieldValue(manager)))); - button.setMessage(handleUpdatesOnChange(manager, widgets, ConfigScreen.this.changedFields)); - }).pos(width - 188, 0).size(150, 20).build()); - break; - case FLOAT: - if (this.field.isAnnotationPresent(Slider.class)) { - widgets.add(this.createSlider(widgets)); - } else { - widgets.add(this.createEditBox(widgets, Pattern.compile("^-?\\d*\\.?\\d*$"), Float::parseFloat)); - } - - break; - case DOUBLE: - if (this.field.isAnnotationPresent(Slider.class)) { - widgets.add(this.createSlider(widgets)); - } else { - widgets.add(this.createEditBox(widgets, Pattern.compile("^-?\\d*\\.?\\d*$"), Double::parseDouble)); - } - - break; - case INTEGER: - if (this.field.isAnnotationPresent(Slider.class)) { - widgets.add(this.createSlider(widgets)); - } else { - widgets.add(this.createEditBox(widgets, Pattern.compile("^-?\\d*$"), Integer::parseInt)); - } - - break; - case LONG: - if (this.field.isAnnotationPresent(Slider.class)) { - widgets.add(this.createSlider(widgets)); - } else { - widgets.add(this.createEditBox(widgets, Pattern.compile("^-?\\d*$"), Long::parseLong)); - } - - break; - case STRING: - widgets.add(this.createEditBox(widgets, null, Function.identity())); - break; - case ENUM: - @SuppressWarnings("unchecked") EnumButton button = new EnumButton<>( - width - 188, - 0, - 150, - 20, - CommonComponents.EMPTY.copy(), - (Class>) this.field.getType(), - (b) -> { - this.setFieldValue(manager, b.getValue()); - b.setMessage(handleUpdatesOnChange(manager, widgets, ConfigScreen.this.changedFields)); - } - ); - - button.setValue(Objects.requireNonNull(this.getFieldValue(manager))); - button.setMessage(getEnumComponent(manager, this.field, button.getValue())); - widgets.add(button); - break; - case LIST: - break; - } - - widgets.add(new ButtonWithTextureWidget(width - 30, 0, 20, 20, Component.translatable("config.jamlib.reset"), JamLib.id("textures/gui/reset.png"), 16, 16, button -> { - this.setFieldValue(manager, this.initialValue); - widgets.get(1).setMessage(handleUpdatesOnChange(manager, widgets, ConfigScreen.this.changedFields)); - - if (widgets.get(1) instanceof EditBox box) { - box.setValue(String.valueOf(this.initialValue)); - } else if (widgets.get(1) instanceof SliderButton slider) { - slider.setValue(((Number) this.initialValue).doubleValue()); - } - })); - - validate(manager, widgets); - - return widgets; - } - - private EditBox createEditBox(List widgets, Pattern filter, Function parse) { - EditBox box = new EditBox( - Minecraft.getInstance().font, - width - 188, - 0, - 150, - 20, - CommonComponents.EMPTY - ); - - Object value = this.getFieldValue(manager); - - if (value instanceof Number number) { - box.setValue(DECIMAL_FORMAT.format(number.doubleValue())); - } else if (value instanceof String string) { - box.setValue(string); - } else if (value != null) { - box.setValue(value.toString()); - } else { - box.setValue(""); - } - - if (filter != null) { - box.setFilter(s -> filter.matcher(s).matches()); - } - - box.setResponder(s -> { - try { - this.setFieldValue(manager, parse.apply(s)); - } catch (Exception ignored) { - } - - box.setMessage(handleUpdatesOnChange(manager, widgets, ConfigScreen.this.changedFields)); - }); - - return box; - } - - private SliderButton createSlider(List widgets) { - WithinRange rangeAnnot = this.field.getAnnotation(WithinRange.class); - Number current = this.getFieldValue(manager); - - if (current == null) { - current = rangeAnnot.min(); - } - - SliderButton slider = new SliderButton( - width - 188, - 0, - 150, - 20, - CommonComponents.EMPTY, - rangeAnnot.min(), - rangeAnnot.max(), - current.doubleValue(), - s -> { - this.setFieldValue(manager, (Number) s.getValue()); - s.setMessage(handleUpdatesOnChange(manager, widgets, ConfigScreen.this.changedFields)); - } - ); - - slider.setMessage(Component.literal(DECIMAL_FORMAT.format(slider.getValue()))); - - return slider; - } - - private Component handleUpdatesOnChange(ConfigManager manager, List widgets, List changedFields) { - Object newValue = this.getFieldValue(manager); - - if (changedFields.contains(this) && this.initialValue.equals(newValue)) { - changedFields.remove(this); - } else if (!changedFields.contains(this) && !this.initialValue.equals(newValue)) { - changedFields.add(this); - } - - this.validate(manager, widgets); - - Class c = this.field.getType(); - - if (c == boolean.class) { - return getBooleanComponent(Boolean.TRUE.equals(newValue)); - } - - if (newValue instanceof Number number) { - return Component.literal(DECIMAL_FORMAT.format(number.doubleValue())); - } else if (newValue instanceof Enum enumValue) { - return getEnumComponent(manager, this.field, enumValue); - } else if (newValue instanceof Boolean boolValue) { - return getBooleanComponent(boolValue); - } else if (newValue != null) { - return Component.literal(newValue.toString()); - } else { - return Component.literal(""); - } - } - - @SuppressWarnings("unchecked") - private void validate(ConfigManager manager, List widgets) { - Object newValue = this.getFieldValue(manager); - - if (manager.get() instanceof ConfigExtensions ext) { - List errors = ((ConfigExtensions) ext).getValidationErrors(manager, new ConfigExtensions.FieldValidationInfo(this.field.getName(), newValue, this.initialValue, this.field)); - errors.sort((o1, o2) -> o2.type().ordinal() - o1.type().ordinal()); - - TextureWidget validationIcon = (TextureWidget) widgets.getFirst(); - if (!errors.isEmpty()) { - this.isValid = errors.getFirst().type() != ConfigExtensions.ValidationError.Type.ERROR; - validationIcon.visible = true; - validationIcon.setTexture(errors.getFirst().type().getTexture()); - validationIcon.setTooltip(Tooltip.create(errors.getFirst().message())); - } else { - this.isValid = true; - validationIcon.visible = false; - } - } - } - - protected Component getName() { - return Component.translatable(this.translationKey); - } - - protected List getTooltip() { - return this.tooltip; - } - - protected boolean isValid() { - return this.isValid; - } - - @SuppressWarnings("unchecked") - @Nullable - private V getFieldValue(ConfigManager manager) { - try { - return (V) this.field.get(manager.get()); - } catch (IllegalAccessException e) { - JamLib.LOGGER.error("Failed to access field for config {}", manager.getConfigName(), e); - return null; - } - } - - private void setFieldValue(ConfigManager manager, V v) { - Object realValue = v; - - if (v instanceof Number n) { - Class c = this.field.getType(); - - if (c == double.class || c == Double.class) { - realValue = n.doubleValue(); - } else if (c == float.class || c == Float.class) { - realValue = n.floatValue(); - } else if (c == int.class || c == Integer.class) { - realValue = n.intValue(); - } else if (c == long.class || c == Long.class) { - realValue = n.longValue(); - } - } - - try { - this.field.set(manager.get(), realValue); - } catch (IllegalAccessException e) { - JamLib.LOGGER.error("Failed to access field for config {}", manager.getConfigName(), e); - } - } - - private enum Type { - BOOLEAN, - FLOAT, - DOUBLE, - INTEGER, - LONG, - STRING, - ENUM, - LIST; - - private static Type fromField(Field field) { - Class c = field.getType(); - - if (c == boolean.class) { - return BOOLEAN; - } else if (c == float.class) { - return FLOAT; - } else if (c == double.class) { - return DOUBLE; - } else if (c == int.class) { - return INTEGER; - } else if (c == long.class) { - return LONG; - } else if (c == String.class) { - return STRING; - } else if (c.isEnum()) { - return ENUM; - } else if (java.util.List.class.isAssignableFrom(c)) { - return LIST; - } else { - throw new IllegalArgumentException("Unsupported config type: " + c); - } - } - } - } - - private class ConfigEntryList extends SelectionList { - - public ConfigEntryList(Minecraft minecraft, int width, int height, int y, int itemHeight) { - super(minecraft, width, height, y, itemHeight); - } - - protected void addEntry(GuiEntry entry) { - this.addEntry(new SelectionListEntry(entry.getName(), entry.getTooltip(), entry.createWidget(ConfigScreen.this.manager, this.width))); - } - } -} diff --git a/common/src/main/resources/architectury.common.json b/common/src/main/resources/architectury.common.json deleted file mode 100644 index 99e1ef4..0000000 --- a/common/src/main/resources/architectury.common.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "accessWidener": "jamlib.accesswidener" -} diff --git a/common/src/main/resources/assets/jamlib/lang/en_us.json b/common/src/main/resources/assets/jamlib/lang/en_us.json index d61f317..db30ab4 100644 --- a/common/src/main/resources/assets/jamlib/lang/en_us.json +++ b/common/src/main/resources/assets/jamlib/lang/en_us.json @@ -3,6 +3,7 @@ "config.jamlib.open": "Open", "config.jamlib.edit_manually": "Edit Manually", "config.jamlib.reset": "Reset", + "config.jamlib.reset_tooltip": "Reset to Default Value", "config.jamlib.matches_regex_tooltip": "Value should match the following regular expression: %s", "config.jamlib.requires_restart_tooltip": "A restart is required for this change to take effect", "config.jamlib.within_range_tooltip": "Value should be between %d and %d", diff --git a/common/src/main/resources/assets/jamlib/textures/gui/link_discord.png b/common/src/main/resources/assets/jamlib/textures/gui/sprites/link_discord.png similarity index 100% rename from common/src/main/resources/assets/jamlib/textures/gui/link_discord.png rename to common/src/main/resources/assets/jamlib/textures/gui/sprites/link_discord.png diff --git a/common/src/main/resources/assets/jamlib/textures/gui/link_generic.png b/common/src/main/resources/assets/jamlib/textures/gui/sprites/link_generic.png similarity index 100% rename from common/src/main/resources/assets/jamlib/textures/gui/link_generic.png rename to common/src/main/resources/assets/jamlib/textures/gui/sprites/link_generic.png diff --git a/common/src/main/resources/assets/jamlib/textures/gui/link_github.png b/common/src/main/resources/assets/jamlib/textures/gui/sprites/link_github.png similarity index 100% rename from common/src/main/resources/assets/jamlib/textures/gui/link_github.png rename to common/src/main/resources/assets/jamlib/textures/gui/sprites/link_github.png diff --git a/common/src/main/resources/assets/jamlib/textures/gui/reset.png b/common/src/main/resources/assets/jamlib/textures/gui/sprites/reset.png similarity index 100% rename from common/src/main/resources/assets/jamlib/textures/gui/reset.png rename to common/src/main/resources/assets/jamlib/textures/gui/sprites/reset.png diff --git a/common/src/main/resources/assets/jamlib/textures/gui/validation_error.png b/common/src/main/resources/assets/jamlib/textures/gui/sprites/validation_error.png similarity index 100% rename from common/src/main/resources/assets/jamlib/textures/gui/validation_error.png rename to common/src/main/resources/assets/jamlib/textures/gui/sprites/validation_error.png diff --git a/common/src/main/resources/assets/jamlib/textures/gui/validation_warning.png b/common/src/main/resources/assets/jamlib/textures/gui/sprites/validation_warning.png similarity index 100% rename from common/src/main/resources/assets/jamlib/textures/gui/validation_warning.png rename to common/src/main/resources/assets/jamlib/textures/gui/sprites/validation_warning.png diff --git a/common/src/main/resources/assets/jamlib/textures/gui/sprites/writable_book.png b/common/src/main/resources/assets/jamlib/textures/gui/sprites/writable_book.png new file mode 100644 index 0000000..b4fffce Binary files /dev/null and b/common/src/main/resources/assets/jamlib/textures/gui/sprites/writable_book.png differ diff --git a/common/src/main/resources/jamlib.accesswidener b/common/src/main/resources/jamlib.accesswidener deleted file mode 100644 index 236e6b1..0000000 --- a/common/src/main/resources/jamlib.accesswidener +++ /dev/null @@ -1 +0,0 @@ -accessWidener v2 named diff --git a/common/src/main/resources/jamlib.mixins.json b/common/src/main/resources/jamlib.mixins.json index 4b6b545..dd9baf5 100644 --- a/common/src/main/resources/jamlib.mixins.json +++ b/common/src/main/resources/jamlib.mixins.json @@ -6,8 +6,6 @@ "mixins": [ ], "client": [ - "event.ClientPacketListenerMixin", - "event.ConnectionMixin" ], "server": [ ], diff --git a/fabric/build.gradle b/fabric/build.gradle index 600ae88..63d8cc3 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -12,10 +12,6 @@ base { archivesName = "jamlib-fabric" } -loom { - accessWidenerPath = project(":common").loom.accessWidenerPath -} - configurations { common shadowCommon diff --git a/fabric/src/main/java/io/github/jamalam360/jamlib/fabric/JamLibClientFabric.java b/fabric/src/main/java/io/github/jamalam360/jamlib/fabric/JamLibClientFabric.java new file mode 100644 index 0000000..e4c25e8 --- /dev/null +++ b/fabric/src/main/java/io/github/jamalam360/jamlib/fabric/JamLibClientFabric.java @@ -0,0 +1,11 @@ +package io.github.jamalam360.jamlib.fabric; + +import io.github.jamalam360.jamlib.client.JamLibClient; +import net.fabricmc.api.ClientModInitializer; + +public class JamLibClientFabric implements ClientModInitializer { + @Override + public void onInitializeClient() { + JamLibClient.init(); + } +} diff --git a/fabric/src/main/java/io/github/jamalam360/jamlib/fabric/config/ModMenuCompatibility.java b/fabric/src/main/java/io/github/jamalam360/jamlib/fabric/config/ModMenuCompatibility.java index f20d75b..d8fa8d1 100644 --- a/fabric/src/main/java/io/github/jamalam360/jamlib/fabric/config/ModMenuCompatibility.java +++ b/fabric/src/main/java/io/github/jamalam360/jamlib/fabric/config/ModMenuCompatibility.java @@ -4,8 +4,8 @@ import com.terraformersmc.modmenu.api.ConfigScreenFactory; import com.terraformersmc.modmenu.api.ModMenuApi; import io.github.jamalam360.jamlib.config.ConfigManager; -import io.github.jamalam360.jamlib.config.gui.ConfigScreen; -import io.github.jamalam360.jamlib.config.gui.SelectConfigScreen; +import io.github.jamalam360.jamlib.client.config.gui.ConfigScreen; +import io.github.jamalam360.jamlib.client.config.gui.SelectConfigScreen; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index c91345b..5b908fd 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -19,15 +19,19 @@ "main": [ "io.github.jamalam360.jamlib.fabric.JamLibFabric" ], + "client": [ + "io.github.jamalam360.jamlib.fabric.JamLibClientFabric" + ], "modmenu": [ "io.github.jamalam360.jamlib.fabric.config.ModMenuCompatibility" ] }, "mixins": [ - "jamlib.mixins.json" + "jamlib.mixins.json", + "jamlib.client.mixins.json" ], "depends": { - "fabric": ">=${fabric_api_version}", + "fabric-api": ">=${fabric_api_version}", "minecraft": ">=${minecraft_version}", "architectury": ">=${architectury_version}" } diff --git a/gradle.properties b/gradle.properties index d5f6d80..fb7e568 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false org.gradle.parallel=true -version=1.2.2-build.2+1.21.1 +version=1.3.1+1.21.1 minecraft_version=1.21.1 additional_minecraft_versions=1.21 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e583..a4b76b9 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0d18421..94113f2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index a69d9cb..f5feea6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +82,12 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +134,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +201,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index f127cfd..9d21a21 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -26,6 +28,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -42,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/neoforge/build.gradle b/neoforge/build.gradle index a45edbb..fe22033 100644 --- a/neoforge/build.gradle +++ b/neoforge/build.gradle @@ -12,10 +12,6 @@ base { archivesName = "jamlib-neoforge" } -loom { - accessWidenerPath = project(":common").loom.accessWidenerPath -} - configurations { common shadowCommon diff --git a/neoforge/src/main/java/io/github/jamalam360/jamlib/neoforge/JamLibNeoForgeClient.java b/neoforge/src/main/java/io/github/jamalam360/jamlib/neoforge/JamLibNeoForgeClient.java index 7d52321..0669728 100644 --- a/neoforge/src/main/java/io/github/jamalam360/jamlib/neoforge/JamLibNeoForgeClient.java +++ b/neoforge/src/main/java/io/github/jamalam360/jamlib/neoforge/JamLibNeoForgeClient.java @@ -1,9 +1,10 @@ package io.github.jamalam360.jamlib.neoforge; import io.github.jamalam360.jamlib.JamLib; +import io.github.jamalam360.jamlib.client.JamLibClient; import io.github.jamalam360.jamlib.config.ConfigManager; -import io.github.jamalam360.jamlib.config.gui.ConfigScreen; -import io.github.jamalam360.jamlib.config.gui.SelectConfigScreen; +import io.github.jamalam360.jamlib.client.config.gui.ConfigScreen; +import io.github.jamalam360.jamlib.client.config.gui.SelectConfigScreen; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModList; @@ -17,6 +18,7 @@ public class JamLibNeoForgeClient { public JamLibNeoForgeClient(IEventBus bus) { + JamLibClient.init(); bus.addListener(this::onFmlLoadComplete); } diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml index 6f06682..0ca5ebd 100644 --- a/neoforge/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -35,3 +35,6 @@ side = "BOTH" [[mixins]] config = "jamlib.mixins.json" + +[[mixins]] +config = "jamlib.client.mixins.json" diff --git a/testmod-common/build.gradle b/testmod-common/build.gradle index 81bfa71..91e4cb0 100644 --- a/testmod-common/build.gradle +++ b/testmod-common/build.gradle @@ -1,7 +1,3 @@ -loom { - accessWidenerPath = project(':common').file("src/main/resources/jamlib.accesswidener") -} - dependencies { modImplementation libs.fabric.loader modImplementation libs.architectury.common diff --git a/testmod-common/src/main/java/io/github/jamalam360/testmod/TestMod.java b/testmod-common/src/main/java/io/github/jamalam360/testmod/TestMod.java index 0cffde8..7512cb1 100644 --- a/testmod-common/src/main/java/io/github/jamalam360/testmod/TestMod.java +++ b/testmod-common/src/main/java/io/github/jamalam360/testmod/TestMod.java @@ -3,6 +3,9 @@ import io.github.jamalam360.jamlib.JamLibPlatform; import io.github.jamalam360.jamlib.config.ConfigManager; import io.github.jamalam360.jamlib.events.client.ClientPlayLifecycleEvents; +import io.github.jamalam360.testmod.config.NestedConfigChild; +import io.github.jamalam360.testmod.config.QuickerConnectButtonTestConfig; +import io.github.jamalam360.testmod.config.TestConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,7 +17,7 @@ public class TestMod { public static final ConfigManager CONFIG_MANAGER = new ConfigManager<>(MOD_ID, "first_config", TestConfig.class); public static final ConfigManager CONFIG_MANAGER_2 = new ConfigManager<>(MOD_ID, "second_config", TestConfig.class); public static final ConfigManager QCB_CONFIG = new ConfigManager<>(MOD_ID, "quickerconnectbutton", QuickerConnectButtonTestConfig.class); - + public static final ConfigManager NESTED_CONFIG = new ConfigManager<>(MOD_ID, "nested", NestedConfigChild.class); public static void init() { LOGGER.info("Initializing JamLib Test Mod on {}", JamLibPlatform.getPlatform()); diff --git a/testmod-common/src/main/java/io/github/jamalam360/testmod/config/NestedConfigChild.java b/testmod-common/src/main/java/io/github/jamalam360/testmod/config/NestedConfigChild.java new file mode 100644 index 0000000..66e6acd --- /dev/null +++ b/testmod-common/src/main/java/io/github/jamalam360/testmod/config/NestedConfigChild.java @@ -0,0 +1,5 @@ +package io.github.jamalam360.testmod.config; + +public class NestedConfigChild extends NestedConfigParent { + public String fromChild = "child"; +} diff --git a/testmod-common/src/main/java/io/github/jamalam360/testmod/config/NestedConfigParent.java b/testmod-common/src/main/java/io/github/jamalam360/testmod/config/NestedConfigParent.java new file mode 100644 index 0000000..17f3593 --- /dev/null +++ b/testmod-common/src/main/java/io/github/jamalam360/testmod/config/NestedConfigParent.java @@ -0,0 +1,5 @@ +package io.github.jamalam360.testmod.config; + +public class NestedConfigParent { + public String fromParent = "parent"; +} diff --git a/testmod-common/src/main/java/io/github/jamalam360/testmod/QuickerConnectButtonTestConfig.java b/testmod-common/src/main/java/io/github/jamalam360/testmod/config/QuickerConnectButtonTestConfig.java similarity index 97% rename from testmod-common/src/main/java/io/github/jamalam360/testmod/QuickerConnectButtonTestConfig.java rename to testmod-common/src/main/java/io/github/jamalam360/testmod/config/QuickerConnectButtonTestConfig.java index a27ca06..151fdd8 100644 --- a/testmod-common/src/main/java/io/github/jamalam360/testmod/QuickerConnectButtonTestConfig.java +++ b/testmod-common/src/main/java/io/github/jamalam360/testmod/config/QuickerConnectButtonTestConfig.java @@ -1,4 +1,4 @@ -package io.github.jamalam360.testmod; +package io.github.jamalam360.testmod.config; import blue.endless.jankson.Comment; import io.github.jamalam360.jamlib.config.ConfigExtensions; diff --git a/testmod-common/src/main/java/io/github/jamalam360/testmod/TestConfig.java b/testmod-common/src/main/java/io/github/jamalam360/testmod/config/TestConfig.java similarity index 84% rename from testmod-common/src/main/java/io/github/jamalam360/testmod/TestConfig.java rename to testmod-common/src/main/java/io/github/jamalam360/testmod/config/TestConfig.java index 29d5002..7af2f33 100644 --- a/testmod-common/src/main/java/io/github/jamalam360/testmod/TestConfig.java +++ b/testmod-common/src/main/java/io/github/jamalam360/testmod/config/TestConfig.java @@ -1,4 +1,4 @@ -package io.github.jamalam360.testmod; +package io.github.jamalam360.testmod.config; import blue.endless.jankson.Comment; import io.github.jamalam360.jamlib.config.ConfigExtensions; @@ -8,12 +8,19 @@ import io.github.jamalam360.jamlib.config.RequiresRestart; import io.github.jamalam360.jamlib.config.Slider; import io.github.jamalam360.jamlib.config.WithinRange; + +import java.util.ArrayList; import java.util.List; import net.minecraft.network.chat.Component; public class TestConfig implements ConfigExtensions { - //public class TestConfig { + @Slider + @WithinRange(min = 1, max = 10) + public List listOfInts = new ArrayList<>(List.of(1, 2, 3)); + + public List listOfEnums = new ArrayList<>(List.of(ConfigEnum.SECOND)); + @Comment("This is a boolean") @RequiresRestart public boolean testBoolean = true; @@ -52,6 +59,10 @@ public List getValidationErrors(ConfigManager manag errors.add(new ValidationError(ValidationError.Type.ERROR, info, Component.translatable("config.testmod.i_dont_like_4"))); } + if (info.name().equals("listOfInts") && ((List) info.value()).size() != 3) { + errors.add(new ValidationError(ValidationError.Type.ERROR, info, Component.literal("Length must be 3"))); + } + return errors; } diff --git a/testmod-common/src/main/resources/assets/testmod/lang/en_us.json b/testmod-common/src/main/resources/assets/testmod/lang/en_us.json index 86976db..5836614 100644 --- a/testmod-common/src/main/resources/assets/testmod/lang/en_us.json +++ b/testmod-common/src/main/resources/assets/testmod/lang/en_us.json @@ -1,6 +1,7 @@ { "config.testmod.first_config.title": "First config", "config.testmod.second_config.title": "Second config", + "config.testmod.nested.title": "Nested", "config.testmod.first_config.tooltip": "Tooltip for the first config. It goes onto multiple lines as it is quite a long tooltip.", "config.testmod.second_config.tooltip": "Tooltip for the second config", "config.testmod.first_config.testBoolean": "Test boolean (it has quite a long name, so the text should scroll)", diff --git a/testmod-fabric/build.gradle b/testmod-fabric/build.gradle index 4636878..0a155db 100644 --- a/testmod-fabric/build.gradle +++ b/testmod-fabric/build.gradle @@ -3,8 +3,6 @@ plugins { } loom { - accessWidenerPath = project(":common").loom.accessWidenerPath - mixin { useLegacyMixinAp = true } } @@ -41,7 +39,6 @@ shadowJar { } remapJar { - injectAccessWidener = true input.set shadowJar.archiveFile dependsOn shadowJar } diff --git a/testmod-neoforge/build.gradle b/testmod-neoforge/build.gradle index 75f7834..f29996f 100644 --- a/testmod-neoforge/build.gradle +++ b/testmod-neoforge/build.gradle @@ -3,8 +3,6 @@ plugins { } loom { - accessWidenerPath = project(":common").loom.accessWidenerPath - mods { neoForge { sourceSet project(":neoforge").sourceSets.main