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 6c0a753..99b4250 100755 --- a/common/src/main/java/eu/midnightdust/lib/config/MidnightConfig.java +++ b/common/src/main/java/eu/midnightdust/lib/config/MidnightConfig.java @@ -14,6 +14,7 @@ import net.minecraft.client.resource.language.I18n; import net.minecraft.registry.Registries; import net.minecraft.screen.ScreenTexts; import net.minecraft.text.Style; import net.minecraft.text.Text; +import net.minecraft.text.TranslatableTextContent; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import net.minecraft.util.TranslatableOption; import org.jetbrains.annotations.Nullable; @@ -107,6 +108,10 @@ public abstract class MidnightConfig { if (index >= list.size()) list.add(value); else list.set(index, value); } + public Tooltip getTooltip(boolean isButton) { + String key = this.modid + ".midnightconfig."+this.fieldName+(!isButton ? ".label" : "" )+".tooltip"; + return Tooltip.of(isButton && this.error != null ? this.error : I18n.hasTranslation(key) ? Text.translatable(key) : Text.empty()); + } } public static final Map> configClass = new HashMap<>(); @@ -120,11 +125,6 @@ public abstract class MidnightConfig { public Identifier read(JsonReader in) throws IOException { return Identifier.of(in.nextString()); } }).setPrettyPrinting().create(); - @SuppressWarnings("unused") // Utility for mod authors - public static @Nullable Object getDefaultValue(String modid, String entry) { - String key = modid + ":" + entry; - return entries.containsKey(key) ? entries.get(key).defaultValue : null; - } public static void loadValuesFromJson(String modid) { try { gson.fromJson(Files.newBufferedReader(path), configClass.get(modid)); } catch (Exception e) { write(modid); } @@ -188,10 +188,6 @@ public abstract class MidnightConfig { try { return (Class) rawType.getField("TYPE").get(null); // Tries to get primitive types from non-primitives (e.g. Boolean -> boolean) } catch (NoSuchFieldException | IllegalAccessException ignored) { return rawType; } } - public static Tooltip getTooltip(EntryInfo info, boolean isButton) { - String key = info.modid + ".midnightconfig."+info.fieldName+(!isButton ? ".label" : "" )+".tooltip"; - return Tooltip.of(isButton && info.error != null ? info.error : I18n.hasTranslation(key) ? Text.translatable(key) : Text.empty()); - } private static Text getEnumTranslatableText(Object value, String modid, EntryInfo info) { if (value instanceof TranslatableOption translatableOption) { @@ -216,7 +212,7 @@ public abstract class MidnightConfig { info.error = inLimits? null : Text.literal(value.doubleValue() < min ? "§cMinimum " + (isNumber? "value" : "length") + (cast? " is " + (int)min : " is " + min) : "§cMaximum " + (isNumber? "value" : "length") + (cast? " is " + (int)max : " is " + max)).formatted(Formatting.RED); - t.setTooltip(getTooltip(info, true)); + t.setTooltip(info.getTooltip(true)); } info.tempValue = s; @@ -248,6 +244,15 @@ public abstract class MidnightConfig { Files.write(path, gson.toJson(getClass(modid)).getBytes()); } catch (Exception e) { e.fillInStackTrace(); } } + + @SuppressWarnings("unused") // Utility for mod authors + public static @Nullable Object getDefaultValue(String modid, String entry) { + String key = modid + ":" + entry; + return entries.containsKey(key) ? entries.get(key).defaultValue : null; + } + + public void onTabInit(String tabName, MidnightConfigListWidget list, MidnightConfigScreen screen) {} + @Environment(EnvType.CLIENT) public static Screen getScreen(Screen parent, String modid) { return new MidnightConfigScreen(parent, modid); @@ -302,9 +307,9 @@ public abstract class MidnightConfig { public void updateButtons() { if (this.list != null) { for (ButtonEntry entry : this.list.children()) { - if (entry.buttons != null && entry.buttons.size() > 1) { + if (entry.buttons != null && entry.buttons.size() > 1 && entry.info.field != null) { if (entry.buttons.get(0) instanceof ClickableWidget widget) - if (widget.isFocused() || widget.isHovered()) widget.setTooltip(getTooltip(entry.info, true)); + if (widget.isFocused() || widget.isHovered()) widget.setTooltip(entry.info.getTooltip(true)); if (entry.buttons.get(1) instanceof ButtonWidget button) button.active = !Objects.equals(String.valueOf(entry.info.value), String.valueOf(entry.info.defaultValue)) && entry.info.conditionsMet; }}}} @@ -344,7 +349,9 @@ public abstract class MidnightConfig { this.list.clear(); fillList(); } public void fillList() { + MidnightConfig.getClass(modid).onTabInit(prevTab.getTitle().getContent() instanceof TranslatableTextContent translatable ? translatable.getKey().replace("%s.midnightconfig.category.".formatted(modid), "") : prevTab.getTitle().toString(), list, this); for (EntryInfo info : entries.values()) { + info.updateConditions(); if (!info.conditionsMet) { boolean visibleButLocked = false; for (Condition condition : info.conditions) { @@ -369,7 +376,7 @@ public abstract class MidnightConfig { if (info.dataType.isEnum()) { values.setValue(value -> getEnumTranslatableText(value, modid, info)); } - widget = ButtonWidget.builder(values.getValue().apply(info.value), values.getKey()).dimensions(width - 185, 0, 150, 20).tooltip(getTooltip(info, true)).build(); + widget = ButtonWidget.builder(values.getValue().apply(info.value), values.getKey()).dimensions(width - 185, 0, 150, 20).tooltip(info.getTooltip(true)).build(); if (info.dataType == boolean.class) info.actionButton = CheckboxWidget.builder(Text.empty(), textRenderer).callback((checkbox, checked) -> values.getKey().onPress((ButtonWidget) widget)).checked((Boolean) info.value).pos(widget.getX(), 1).build(); } else if (e.isSlider()) widget = new MidnightSliderWidget(width - 185, 0, 150, 20, Text.of(info.tempValue), (Double.parseDouble(info.tempValue) - e.min()) / (e.max() - e.min()), info); @@ -379,7 +386,7 @@ public abstract class MidnightConfig { Predicate processor = ((BiFunction>) info.function).apply(textField, done); textField.setTextPredicate(processor); } - widget.setTooltip(getTooltip(info, true)); + widget.setTooltip(info.getTooltip(true)); ButtonWidget cycleButton = null; if (info.field.getType() == List.class) { @@ -478,14 +485,14 @@ public abstract class MidnightConfig { if (text != null && (!text.getString().contains("spacer") || !buttons.isEmpty())) { title = new MultilineTextWidget((centered) ? (scaledWidth / 2 - (textRenderer.getWidth(text) / 2)) : 12, 0, Text.of(text), textRenderer); - if (info != null) title.setTooltip(getTooltip(info, false)); + if (info != null) title.setTooltip(info.getTooltip(false)); title.setMaxWidth(buttons.size() > 1 ? buttons.get(1).getX() - 24 : scaledWidth - 24); } } public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { buttons.forEach(b -> { b.setY(y + (b instanceof CheckboxWidget ? 1 : 0)); b.render(context, mouseX, mouseY, tickDelta);}); if (title != null) { - title.setY(y + 9); + title.setY(y+5); title.renderWidget(context, mouseX, mouseY, tickDelta); boolean tooltipVisible = mouseX >= title.getX() && mouseX < title.getWidth() + title.getX() && mouseY >= title.getY() && mouseY < title.getHeight() + title.getY(); diff --git a/test-fabric/src/main/java/eu/midnightdust/fabric/example/MidnightLibExtras.java b/test-fabric/src/main/java/eu/midnightdust/fabric/example/MidnightLibExtras.java new file mode 100644 index 0000000..12deb14 --- /dev/null +++ b/test-fabric/src/main/java/eu/midnightdust/fabric/example/MidnightLibExtras.java @@ -0,0 +1,96 @@ +package eu.midnightdust.fabric.example; + +import com.google.common.collect.Lists; +import eu.midnightdust.lib.config.MidnightConfig; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.gui.widget.TextIconButtonWidget; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.glfw.GLFW; + +/* + Pre-made additional (niche) functionality that is not included in MidnightLib to keep the file size small. + Feel free to copy the parts you need :) +*/ +public class MidnightLibExtras { + public static class KeybindButton extends ButtonWidget { + public static ButtonWidget focusedButton; + + public static void add(KeyBinding binding, MidnightConfig.MidnightConfigListWidget list, MidnightConfig.MidnightConfigScreen screen) { + KeybindButton editButton = new KeybindButton(screen.width - 185, 0, 150, 20, binding); + TextIconButtonWidget resetButton = TextIconButtonWidget.builder(Text.translatable("controls.reset"), (button -> { + binding.setBoundKey(binding.getDefaultKey()); + screen.updateList(); + }), true).texture(Identifier.of("midnightlib","icon/reset"), 12, 12).dimension(20, 20).build(); + resetButton.setPosition(screen.width - 205 + 150 + 25, 0); + editButton.resetButton = resetButton; + editButton.updateMessage(false); + MidnightConfig.EntryInfo info = new MidnightConfig.EntryInfo(null, screen.modid); + + list.addButton(Lists.newArrayList(editButton, resetButton), Text.translatable(binding.getTranslationKey()), info); + } + + private final KeyBinding binding; + private @Nullable ClickableWidget resetButton; + public KeybindButton(int x, int y, int width, int height, KeyBinding binding) { + super(x, y, width, height, binding.getBoundKeyLocalizedText(), (button) -> { + ((KeybindButton) button).updateMessage(true); + focusedButton = button; + }, (textSupplier) -> binding.isUnbound() ? Text.translatable("narrator.controls.unbound", binding.getTranslationKey()) : Text.translatable("narrator.controls.bound", binding.getTranslationKey(), textSupplier.get())); + this.binding = binding; + updateMessage(false); + } + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (focusedButton == this) { + if (keyCode == GLFW.GLFW_KEY_ESCAPE) { + this.binding.setBoundKey(InputUtil.UNKNOWN_KEY); + } else { + this.binding.setBoundKey(InputUtil.fromKeyCode(keyCode, scanCode)); + } + updateMessage(false); + + focusedButton = null; + return true; + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + + public void updateMessage(boolean focused) { + boolean hasConflicts = false; + MutableText conflictingBindings = Text.empty(); + if (focused) this.setMessage(Text.literal("> ").append(this.binding.getBoundKeyLocalizedText().copy().formatted(Formatting.WHITE, Formatting.UNDERLINE)).append(" <").formatted(Formatting.YELLOW)); + else { + this.setMessage(this.binding.getBoundKeyLocalizedText()); + + if (!this.binding.isUnbound()) { + for(KeyBinding keyBinding : MinecraftClient.getInstance().options.allKeys) { + if (keyBinding != this.binding && this.binding.equals(keyBinding)) { + if (hasConflicts) conflictingBindings.append(", "); + + hasConflicts = true; + conflictingBindings.append(Text.translatable(keyBinding.getTranslationKey())); + } + } + } + } + + if (this.resetButton != null) this.resetButton.active = !this.binding.isDefault(); + + if (hasConflicts) { + this.setMessage(Text.literal("[ ").append(this.getMessage().copy().formatted(Formatting.WHITE)).append(" ]").formatted(Formatting.RED)); + this.setTooltip(Tooltip.of(Text.translatable("controls.keybinds.duplicateKeybinds", conflictingBindings))); + } else { + this.setTooltip(null); + } + } + } +} 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 2b9c172..137ded6 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 @@ -1,16 +1,19 @@ package eu.midnightdust.fabric.example.config; import com.google.common.collect.Lists; +import eu.midnightdust.fabric.example.MidnightLibExtras; import eu.midnightdust.lib.config.MidnightConfig; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import net.minecraft.client.MinecraftClient; import net.minecraft.util.Identifier; import net.minecraft.util.TranslatableOption; import javax.swing.*; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** Every option in a MidnightConfig class has to be public and static, so we can access it from other classes. * The config class also has to extend MidnightConfig*/ @@ -22,6 +25,7 @@ public class MidnightConfigExample extends MidnightConfig { public static final String LISTS = "lists"; public static final String FILES = "files"; public static final String CONDITIONS = "conditions"; + public static final String EXTRAS = "extras"; @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! @@ -150,4 +154,16 @@ public class MidnightConfigExample extends MidnightConfig { @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 + + @Condition(requiredModId = "thismoddoesnotexist") + @Comment(category = EXTRAS) public static Comment iAmJustADummy; // We only have this to initialize an empty tab for the keybinds below + + @Override + public void onTabInit(String tabName, MidnightConfigListWidget list, MidnightConfigScreen screen) { + if (Objects.equals(tabName, EXTRAS)) { + MidnightLibExtras.KeybindButton.add(MinecraftClient.getInstance().options.advancementsKey, list, screen); + MidnightLibExtras.KeybindButton.add(MinecraftClient.getInstance().options.dropKey, list, screen); + } + } + } \ 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 976393b..a006c75 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 @@ -25,5 +25,6 @@ "modid.midnightconfig.category.lists": "Lists", "modid.midnightconfig.category.files": "Files", "modid.midnightconfig.category.conditions": "Quiz", + "modid.midnightconfig.category.extras": "Extras", "modid.midnightconfig.category.multiConditions": "Multi-Conditions" } \ No newline at end of file