diff --git a/common/src/main/java/eu/midnightdust/lib/config/MidnightConfig.java b/common/src/main/java/eu/midnightdust/lib/config/MidnightConfig.java index 0e078ea..27021b1 100755 --- a/common/src/main/java/eu/midnightdust/lib/config/MidnightConfig.java +++ b/common/src/main/java/eu/midnightdust/lib/config/MidnightConfig.java @@ -40,27 +40,34 @@ public abstract class MidnightConfig { private static final Pattern HEXADECIMAL_ONLY = Pattern.compile("(-?[#0-9a-fA-F]*)"); private static final List entries = new ArrayList<>(); + private static boolean reloadScreen = false; public static class EntryInfo { public Entry entry; public Comment comment; - final Field field; - Class dataType; + public Condition condition; + public final Field field; + public final Class dataType; + public final String modid, fieldName; int listIndex; Object defaultValue, value, function; - String modid, fieldName, tempValue = ""; // The value visible in the config screen + String tempValue; // The value visible in the config screen boolean inLimits = true; Text name, error; ClickableWidget actionButton; // color picker button / explorer button Tab tab; + boolean conditionsMet = true; - public EntryInfo(Field field) { - this.field = field; + public EntryInfo(Field field, String modid) { + this.field = field; this.modid = modid; if (field != null) { - this.fieldName = field.getName(); - this.entry = field.getAnnotation(Entry.class); - this.comment = field.getAnnotation(Comment.class); + this.fieldName = field.getName(); this.dataType = getUnderlyingType(field); + this.entry = field.getAnnotation(Entry.class); this.comment = field.getAnnotation(Comment.class); this.condition = field.getAnnotation(Condition.class); + } else { + this.fieldName = ""; this.dataType = null; } + if (entry != null && !entry.name().isEmpty()) this.name = Text.translatable(entry.name()); + else if (comment != null && !comment.name().isEmpty()) this.name = Text.translatable(comment.name()); } public void setValue(Object value) { if (this.field.getType() != List.class) { this.value = value; @@ -73,7 +80,19 @@ public abstract class MidnightConfig { else try { return ((List) this.value).get(this.listIndex).toString(); } catch (Exception ignored) {return "";} } public void updateFieldValue() { - try { this.field.set(null, this.value); } catch (IllegalAccessException ignored) {} + try { if (this.field.get(null) != value) updateConditions(tempValue); + this.field.set(null, this.value); + } catch (IllegalAccessException ignored) {} + } + @SuppressWarnings("ConstantValue") //pertains to requiredModLoaded + public void updateConditions(String newTempValue) { + for (EntryInfo info : entries) { + boolean prevConditionState = info.conditionsMet; + if (info.condition != null && ((info.condition.requiredOption().contains(":") ? "" : info.modid + ":") + info.condition.requiredOption()).equals(this.modid + ":" + this.fieldName)) + info.conditionsMet = Objects.equals(info.condition.requiredValue(), newTempValue); + if (info.condition != null && !info.condition.requiredModId().isEmpty() && !PlatformFunctions.isModLoaded(info.condition.requiredModId())) info.conditionsMet = false; + if (prevConditionState != info.conditionsMet) reloadScreen = true; + } } public void writeList(int index, T value) { var list = (List) this.value; @@ -105,6 +124,7 @@ public abstract class MidnightConfig { for (EntryInfo info : entries) if (info.field != null && info.entry != null) { try { info.value = info.field.get(null); info.tempValue = info.toTemporaryValue(); + info.updateConditions(info.tempValue); } catch (IllegalAccessException ignored) {} } } @@ -113,7 +133,7 @@ public abstract class MidnightConfig { configClass.put(modid, config); for (Field field : config.getFields()) { - EntryInfo info = new EntryInfo(field); + EntryInfo info = new EntryInfo(field, modid); if ((field.isAnnotationPresent(Entry.class) || field.isAnnotationPresent(Comment.class)) && !field.isAnnotationPresent(Server.class) && !field.isAnnotationPresent(Hidden.class) && PlatformFunctions.isClientEnv()) initClient(modid, field, info); if (field.isAnnotationPresent(Entry.class)) @@ -122,19 +142,10 @@ public abstract class MidnightConfig { } loadValuesFromJson(modid); } - @SuppressWarnings("ConstantValue") //pertains to requiredModLoaded @Environment(EnvType.CLIENT) private static void initClient(String modid, Field field, EntryInfo info) { - info.dataType = getUnderlyingType(field); - Entry e = info.entry; Comment c = info.comment; - info.modid = modid; - boolean requiredModLoaded = true; - + Entry e = info.entry; if (e != null) { - if (!e.requiredMod().isEmpty()) requiredModLoaded = PlatformFunctions.isModLoaded(e.requiredMod()); - - if (!requiredModLoaded) return; - if (!e.name().isEmpty()) info.name = Text.translatable(e.name()); if (info.dataType == int.class) textField(info, Integer::parseInt, INTEGER_ONLY, (int) e.min(), (int) e.max(), true); else if (info.dataType == float.class) textField(info, Float::parseFloat, DECIMAL_ONLY, (float) e.min(), (float) e.max(), false); else if (info.dataType == double.class) textField(info, Double::parseDouble, DECIMAL_ONLY, e.min(), e.max(), false); @@ -154,10 +165,9 @@ public abstract class MidnightConfig { int index = values.indexOf(info.value) + 1; info.value = values.get(index >= values.size() ? 0 : index); button.setMessage(func.apply(info.value)); }, func); - }} else if (c != null) { - if (!c.requiredMod().isEmpty()) requiredModLoaded = PlatformFunctions.isModLoaded(c.requiredMod()); + } } - if (requiredModLoaded) entries.add(info); + entries.add(info); } public static Class getUnderlyingType(Field field) { Class rawType = field.getType(); @@ -259,12 +269,12 @@ public abstract class MidnightConfig { super.tick(); if (prevTab != null && prevTab != tabManager.getCurrentTab()) { prevTab = tabManager.getCurrentTab(); - this.list.clear(); fillList(); - list.setScrollY(0); + updateList(); list.setScrollY(0); } scrollProgress = list.getScrollY(); for (EntryInfo info : entries) info.updateFieldValue(); updateButtons(); + if (reloadScreen) { updateList(); reloadScreen = false; } } public void updateButtons() { if (this.list != null) { @@ -307,14 +317,18 @@ public abstract class MidnightConfig { this.addSelectableChild(this.list); fillList(); if (tabs.size() > 1) list.renderHeaderSeparator = false; } + public void updateList() { + this.list.clear(); fillList(); + } public void fillList() { for (EntryInfo info : entries) { + if (!info.conditionsMet && info.condition != null && !info.condition.visibleButLocked()) continue; if (info.modid.equals(modid) && (info.tab == null || info.tab == tabManager.getCurrentTab())) { Text name = Objects.requireNonNullElseGet(info.name, () -> Text.translatable(translationPrefix + info.fieldName)); TextIconButtonWidget resetButton = TextIconButtonWidget.builder(Text.translatable("controls.reset"), (button -> { info.value = info.defaultValue; info.listIndex = 0; info.tempValue = info.toTemporaryValue(); - list.clear(); fillList(); + updateList(); }), true).texture(Identifier.of("midnightlib","icon/reset"), 12, 12).dimension(20, 20).build(); resetButton.setPosition(width - 205 + 150 + 25, 0); @@ -348,7 +362,7 @@ public abstract class MidnightConfig { if (info.listIndex > values.size()) info.listIndex = 0; info.tempValue = info.toTemporaryValue(); if (info.listIndex == values.size()) info.tempValue = ""; - list.clear(); fillList(); + updateList(); })).dimensions(width - 185, 0, 20, 20).build(); } if (e.isColor()) { @@ -357,7 +371,7 @@ public abstract class MidnightConfig { Color newColor = JColorChooser.showDialog(null, Text.translatable("midnightconfig.colorChooser.title").getString(), Color.decode(!Objects.equals(info.tempValue, "") ? info.tempValue : "#FFFFFF")); if (newColor != null) { info.setValue("#" + Integer.toHexString(newColor.getRGB()).substring(2)); - list.clear(); fillList(); + updateList(); } }).start() ).dimensions(width - 185, 0, 20, 20).build(); @@ -375,13 +389,14 @@ public abstract class MidnightConfig { Text.translatable(translationPrefix + info.fieldName + ".fileFilter").getString(), e.fileExtensions())); if (fileChooser.showDialog(null, null) == JFileChooser.APPROVE_OPTION) { info.setValue(fileChooser.getSelectedFile().getAbsolutePath()); - list.clear(); fillList(); + updateList(); } }).start(), true ).texture(Identifier.of("midnightlib", "icon/explorer"), 12, 12).dimension(20, 20).build(); explorerButton.setPosition(width - 185, 0); info.actionButton = explorerButton; } + if (!info.conditionsMet) widget.active = false; List widgets = Lists.newArrayList(widget, resetButton); if (info.actionButton != null) { if (IS_SYSTEM_MAC) info.actionButton.active = false; @@ -516,7 +531,7 @@ public abstract class MidnightConfig { boolean isSlider() default false; int precision() default 100; String category() default "default"; - String requiredMod() default ""; + @Deprecated String requiredMod() default ""; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Client {} @@ -536,6 +551,25 @@ public abstract class MidnightConfig { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Comment { boolean centered() default false; String category() default "default"; - String requiredMod() default ""; + String name() default ""; + @Deprecated String requiredMod() default ""; + } + /** + * Condition Annotation
+ * - {@link Condition#requiredModId()}: The id of a mod that is required to be loaded.
+ * - {@link Condition#requiredOption()}: The {@link Field} which will be used to check the condition. Can also access options of other MidnightLib mods ("modid:optionName").
+ * - {@link Condition#requiredValue()}: The value that {@link Condition#requiredOption()} should be set to for the condition to be met.
+ * - {@link Condition#visibleButLocked()}: The behaviour to take when {@link Condition#requiredModId} is not loaded + * or {@link Condition#requiredOption()} returns a value that is not {@link Condition#requiredValue()}.
+ * true – Option is visible, but not editable
+ * false – Option is completely hidden + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface Condition { + String requiredModId() default ""; + String requiredOption() default ""; + String requiredValue() default "true"; + boolean visibleButLocked() default false; } } \ No newline at end of file diff --git a/test-fabric/src/main/java/eu/midnightdust/fabric/example/config/MidnightConfigExample.java b/test-fabric/src/main/java/eu/midnightdust/fabric/example/config/MidnightConfigExample.java index 12d29c2..bee921b 100644 --- a/test-fabric/src/main/java/eu/midnightdust/fabric/example/config/MidnightConfigExample.java +++ b/test-fabric/src/main/java/eu/midnightdust/fabric/example/config/MidnightConfigExample.java @@ -17,6 +17,7 @@ public class MidnightConfigExample extends MidnightConfig { public static final String SLIDERS = "sliders"; public static final String LISTS = "lists"; public static final String FILES = "files"; + public static final String CONDITIONS = "conditions"; @Comment(category = TEXT) public static Comment text1; // Comments are rendered like an option without a button and are excluded from the config file @Comment(category = TEXT, centered = true) public static Comment text2; // Centered comments are the same as normal ones - just centered! @@ -69,5 +70,32 @@ public class MidnightConfigExample extends MidnightConfig { name = "I am a mf file/directory list!") public static List fileOrDirectoryList = new ArrayList<>(); // Yes, that's right – you can even have lists of files/directories + @Condition(requiredModId = "midnightlib") // Conditional options are here! + @Entry(category = CONDITIONS, name="Turn me on!") + public static boolean turnMeOn = false; + @Condition(requiredOption = "modid:turnMeOn", visibleButLocked = true) + @Entry(category = CONDITIONS, name="Turn me off!") + public static Boolean turnMeOff = true; + @Condition(requiredOption = "modid:turnMeOff", requiredValue = "false") + @Entry(category = CONDITIONS, name="Which is the best modloader?") + public static String bestModloader = ""; + @Condition(requiredOption = "bestModloader", requiredValue = "Forge") + @Comment(category = CONDITIONS, name="❌ You have bad taste :(", centered = true) // Don't take this too seriously btw :) + public static Comment answerForge; // Comments can also be conditional! + @Condition(requiredOption = "bestModloader", requiredValue = "NeoForge") + @Comment(category = CONDITIONS, name="⛏ Not quite, but it's alright!", centered = true) + public static Comment answerNeoforge; + @Condition(requiredOption = "bestModloader", requiredValue = "Fabric") + @Comment(category = CONDITIONS, name="⭐ Correct! Fabric (and Quilt) are the best!", centered = true) + public static Comment answerFabric; + @Condition(requiredOption = "bestModloader", requiredValue = "Quilt") + @Comment(category = CONDITIONS, name="⭐ Correct! Quilt (and Fabric) are the best!", centered = true) + public static Comment answerQuilt; + + @Condition(requiredOption = "midnightlib:config_screen_list", requiredValue = "FALSE") // Access options of other mods that are also using MidnightLib + @Comment(category = CONDITIONS) public static Comment spaceracer; + @Condition(requiredOption = "midnightlib:config_screen_list", requiredValue = "FALSE") + @Comment(category = CONDITIONS, name="You disabled MidnightLib's config screen list. Why? :(", centered = true) public static Comment why; + public static int imposter = 16777215; // - Entries without an @Entry or @Comment annotation are ignored } \ No newline at end of file diff --git a/test-fabric/src/main/resources/assets/modid/lang/en_us.json b/test-fabric/src/main/resources/assets/modid/lang/en_us.json index 8ac67dd..463f00b 100644 --- a/test-fabric/src/main/resources/assets/modid/lang/en_us.json +++ b/test-fabric/src/main/resources/assets/modid/lang/en_us.json @@ -20,5 +20,6 @@ "modid.midnightconfig.category.text": "Text", "modid.midnightconfig.category.sliders": "Sliders", "modid.midnightconfig.category.lists": "Lists", - "modid.midnightconfig.category.files": "Files" + "modid.midnightconfig.category.files": "Files", + "modid.midnightconfig.category.conditions": "Quiz" } \ No newline at end of file