From cacd3516c155fc67608fde9a7d1e11f28a46421f Mon Sep 17 00:00:00 2001 From: Motschen Date: Wed, 8 Mar 2023 21:27:41 +0100 Subject: [PATCH] Port to 1.19.4, Add tab support & Large code cleanup --- MidnightConfigExample.java | 25 ++-- build.gradle | 2 +- .../midnightdust/core/MidnightLibClient.java | 1 - .../screen/MidnightConfigOverviewScreen.java | 2 +- .../lib/config/MidnightConfig.java | 122 ++++++++++++------ .../screen/TexturedOverlayButtonWidget.java | 10 +- gradle.properties | 11 +- settings.gradle | 4 +- 8 files changed, 111 insertions(+), 66 deletions(-) diff --git a/MidnightConfigExample.java b/MidnightConfigExample.java index 1f4ce51..f6ceedf 100644 --- a/MidnightConfigExample.java +++ b/MidnightConfigExample.java @@ -1,5 +1,6 @@ package eu.midnightdust.core.config; +import com.google.common.collect.Lists; import eu.midnightdust.lib.config.MidnightConfig; import java.util.List; @@ -14,19 +15,19 @@ public class MidnightConfigExample extends MidnightConfig { @Comment public static Comment text1; // Comments are rendered like an option without a button and are excluded from the config file @Comment(centered = true) public static Comment text2; // Centered comments are the same as normal ones - just centered! - @Entry public static int fabric = 16777215; // Example for an int option - @Entry public static double world = 1.4D; // Example for a double option @Entry public static boolean showInfo = true; // Example for a boolean option - @Entry public static String name = "Hello World!"; // Example for a string option - @Entry public static TestEnum testEnum = TestEnum.FABRIC; // Example for an enum option + @Entry(category = "text") public static String name = "Hello World!"; // Example for a string option, which is in a category! + @Entry(category = "text") public static TestEnum testEnum = TestEnum.FABRIC; // Example for an enum option public enum TestEnum { // Enums allow the user to cycle through predefined options QUILT, FABRIC, FORGE } - @Entry(min=69,max=420) public static int hello = 420; // - The entered number has to be larger than 69 and smaller than 420 - @Entry(width = 7, min = 7, isColor = true, name = "I am a color!") public static String titleColor = "#ffffff"; // The isColor property adds a preview box for a hexadecimal color - @Entry(name = "I am an array list!") public static List arrayList = List.of("String1", "String2"); // Array String Lists are also supported - @Entry(name = "I am an int slider.",isSlider = true, min = 0, max = 100) public static int intSlider = 35; // Int fields can also be displayed as a Slider - @Entry(name = "I am a float slider!", isSlider = true, min = 0f, max = 1f, precision = 1000) public static float floatSlider = 0.24f; // And so can floats! Precision defines the amount of decimal places + @Entry(category = "numbers") public static int fabric = 16777215; // Example for an int option + @Entry(category = "numbers") public static double world = 1.4D; // Example for a double option + @Entry(category = "numbers", min=69,max=420) public static int hello = 420; // - The entered number has to be larger than 69 and smaller than 420 + @Entry(category = "text", width = 7, min = 7, isColor = true, name = "I am a color!") public static String titleColor = "#ffffff"; // The isColor property adds a preview box for a hexadecimal color + @Entry(category = "sliders", name = "I am an array list!") public static List arrayList = Lists.newArrayList("String1", "String2"); // Array String Lists are also supported + @Entry(category = "sliders", name = "I am an int slider.",isSlider = true, min = 0, max = 100) public static int intSlider = 35; // Int fields can also be displayed as a Slider + @Entry(category = "sliders", name = "I am a float slider!", isSlider = true, min = 0f, max = 1f, precision = 1000) public static float floatSlider = 0.24f; // And so can floats! Precision defines the amount of decimal places // The name field can be used to specify a custom translation string or plain text public static int imposter = 16777215; // - Entries without an @Entry or @Comment annotation are ignored @@ -49,11 +50,13 @@ public class MidnightConfigExample extends MidnightConfig { "modid.midnightconfig.testEnum":"I am an enum!", "modid.midnightconfig.enum.TestEnum.FORGE":"Slow", "modid.midnightconfig.enum.TestEnum.FABRIC":"Fancy", - "modid.midnightconfig.enum.TestEnum.QUILT":"Fabulous" + "modid.midnightconfig.enum.TestEnum.QUILT":"Fabulous", + "modid.midnightconfig.category.numbers": "Numbers", + "modid.midnightconfig.category.text": "Text", + "modid.midnightconfig.category.sliders": "Sliders" } To initialize the config you have to call "MidnightConfig.init("modid", MidnightConfigExample.class)" in your ModInitializer To get an instance of the config screen you have to call "MidnightConfig.getScreen(parent, "modid");" - If you don't use the whole library and therefore not the automatic ModMenu integration, the code in your ModMenu integration class would look something like this: @Override public ConfigScreenFactory getModConfigScreenFactory() { diff --git a/build.gradle b/build.gradle index 4e230e8..029beea 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ subprojects { // The following line declares the mojmap mappings, you may use other mappings as well //mappings loom.officialMojangMappings() // The following line declares the yarn mappings you may select this one as well. - mappings "net.fabricmc:yarn:1.19.3+build.3:v2" + mappings "net.fabricmc:yarn:${rootProject.yarn_mappings}:v2" } } diff --git a/common/src/main/java/eu/midnightdust/core/MidnightLibClient.java b/common/src/main/java/eu/midnightdust/core/MidnightLibClient.java index 549c7a1..dd5797f 100755 --- a/common/src/main/java/eu/midnightdust/core/MidnightLibClient.java +++ b/common/src/main/java/eu/midnightdust/core/MidnightLibClient.java @@ -14,7 +14,6 @@ public class MidnightLibClient { public static void onInitializeClient() { MidnightConfig.init("midnightlib", MidnightLibConfig.class); - hiddenMods.add("puzzle"); if (MidnightLibConfig.special_hats) HatLoader.init(); } diff --git a/common/src/main/java/eu/midnightdust/core/screen/MidnightConfigOverviewScreen.java b/common/src/main/java/eu/midnightdust/core/screen/MidnightConfigOverviewScreen.java index ffaff6b..6ac86ce 100755 --- a/common/src/main/java/eu/midnightdust/core/screen/MidnightConfigOverviewScreen.java +++ b/common/src/main/java/eu/midnightdust/core/screen/MidnightConfigOverviewScreen.java @@ -46,7 +46,7 @@ public class MidnightConfigOverviewScreen extends Screen { public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { this.renderBackground(matrices); this.list.render(matrices, mouseX, mouseY, delta); - drawCenteredText(matrices, textRenderer, title, width / 2, 15, 0xFFFFFF); + drawCenteredTextWithShadow(matrices, textRenderer, title, width / 2, 15, 0xFFFFFF); super.render(matrices, mouseX, mouseY, delta); } @Environment(EnvType.CLIENT) 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 0d91d4e..a8b1ad0 100755 --- a/common/src/main/java/eu/midnightdust/lib/config/MidnightConfig.java +++ b/common/src/main/java/eu/midnightdust/lib/config/MidnightConfig.java @@ -13,6 +13,9 @@ import net.minecraft.client.gui.DrawableHelper; import net.minecraft.client.gui.Element; import net.minecraft.client.gui.Selectable; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.tab.GridScreenTab; +import net.minecraft.client.gui.tab.Tab; +import net.minecraft.client.gui.tab.TabManager; import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.widget.*; import net.minecraft.client.resource.language.I18n; @@ -56,9 +59,8 @@ public abstract class MidnightConfig { Field field; Object widget; int width; - int max; boolean centered; - Map.Entry error; + Text error; Object defaultValue; Object value; String tempValue; @@ -67,6 +69,7 @@ public abstract class MidnightConfig { Text name; int index; ClickableWidget colorButton; + Tab tab; } public static final Map> configClass = new HashMap<>(); @@ -113,7 +116,6 @@ public abstract class MidnightConfig { else if (type == float.class) textField(info, Float::parseFloat, DECIMAL_ONLY, (float) e.min(), (float) e.max(), false); else if (type == double.class) textField(info, Double::parseDouble, DECIMAL_ONLY, e.min(), e.max(), false); else if (type == String.class || type == List.class) { - info.max = e.max() == Double.MAX_VALUE ? Integer.MAX_VALUE : (int) e.max(); textField(info, String::length, null, Math.min(e.min(), 0), Math.max(e.max(), 1), true); } else if (type == boolean.class) { Function func = value -> Text.translatable((Boolean) value ? "gui.yes" : "gui.no").formatted((Boolean) value ? Formatting.GREEN : Formatting.RED); @@ -146,9 +148,9 @@ public abstract class MidnightConfig { if (!(isNumber && s.isEmpty()) && !s.equals("-") && !s.equals(".")) { try { value = f.apply(s); } catch(NumberFormatException e){ return false; } inLimits = value.doubleValue() >= min && value.doubleValue() <= max; - info.error = inLimits? null : new AbstractMap.SimpleEntry<>(t, Text.literal(value.doubleValue() < min ? + 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))); + "§cMaximum " + (isNumber? "value" : "length") + (cast? " is " + (int)max : " is " + max)).formatted(Formatting.RED); } info.tempValue = s; @@ -200,11 +202,19 @@ public abstract class MidnightConfig { public final String modid; public MidnightConfigListWidget list; public boolean reload = false; + public TabManager tabManager = new TabManager(a -> refresh(), a -> refresh()); + public Tab prevTab; // Real Time config update // @Override public void tick() { super.tick(); + tabManager.tick(); + if (prevTab != null && prevTab != tabManager.getCurrentTab()) { + prevTab = tabManager.getCurrentTab(); + this.list.clear(); + fillList(); + } for (EntryInfo info : entries) { try {info.field.set(null, info.value);} catch (IllegalAccessException ignored) {} } @@ -232,18 +242,50 @@ public abstract class MidnightConfig { } } public Tooltip getTooltip(EntryInfo info) { - return Tooltip.of(I18n.hasTranslation(translationPrefix+info.field.getName()+".tooltip") ? Text.translatable(translationPrefix+info.field.getName()+".tooltip") : Text.empty()); + return Tooltip.of(info.error != null ? info.error : I18n.hasTranslation(translationPrefix+info.field.getName()+".tooltip") ? Text.translatable(translationPrefix+info.field.getName()+".tooltip") : Text.empty()); + } + public void refresh() { + double scrollAmount = list.getScrollAmount(); + list.clear(); + fillList(); + list.setScrollAmount(scrollAmount); } @Override public void init() { super.init(); - if (!reload) loadValues(); + loadValues(); + + Map tabs = new HashMap<>(); + for (EntryInfo e : entries) { + if (e.id.equals(modid)) { + String tabId = e.field.isAnnotationPresent(Entry.class) ? e.field.getAnnotation(Entry.class).category() : e.field.getAnnotation(Comment.class).category(); + String name = translationPrefix + "category." + tabId; + if (!I18n.hasTranslation(name) && tabId.equals("default")) + name = translationPrefix + "title"; + Tab tab = new GridScreenTab(Text.translatable(name)); + if (!tabs.containsKey(name)) { + e.tab = tab; + tabs.put(name, tab); + } else e.tab = tabs.get(name); + } + } + TabNavigationWidget tabNavigation = TabNavigationWidget.builder(tabManager, this.width).tabs(tabs.values().toArray(new Tab[0])).build(); + if (tabs.size() > 1) this.addDrawableChild(tabNavigation); + if (!reload) tabNavigation.selectTab(0, false); + tabNavigation.init(); + prevTab = tabManager.getCurrentTab(); this.addDrawableChild(ButtonWidget.builder(ScreenTexts.CANCEL, button -> { loadValues(); Objects.requireNonNull(client).setScreen(parent); }).dimensions(this.width / 2 - 154, this.height - 28, 150, 20).build()); + this.list = new MidnightConfigListWidget(this.client, this.width, this.height, 32, this.height - 32, 25); + if (this.client != null && this.client.world != null) this.list.setRenderBackground(false); + this.addSelectableChild(this.list); + this.fillList(); + } + public void fillList() { ButtonWidget done = this.addDrawableChild(ButtonWidget.builder(ScreenTexts.DONE, (button) -> { for (EntryInfo info : entries) if (info.id.equals(modid)) { @@ -254,52 +296,48 @@ public abstract class MidnightConfig { write(modid); Objects.requireNonNull(client).setScreen(parent); }).dimensions(this.width / 2 + 4, this.height - 28, 150, 20).build()); - - this.list = new MidnightConfigListWidget(this.client, this.width, this.height, 32, this.height - 32, 25); - if (this.client != null && this.client.world != null) this.list.setRenderBackground(false); - this.addSelectableChild(this.list); for (EntryInfo info : entries) { - if (info.id.equals(modid)) { + if (info.id.equals(modid) && (info.tab == null || info.tab == tabManager.getCurrentTab())) { Text name = Objects.requireNonNullElseGet(info.name, () -> Text.translatable(translationPrefix + info.field.getName())); ButtonWidget resetButton = ButtonWidget.builder(Text.literal("Reset").formatted(Formatting.RED), (button -> { info.value = info.defaultValue; info.tempValue = info.defaultValue.toString(); info.index = 0; - double scrollAmount = list.getScrollAmount(); - this.reload = true; - Objects.requireNonNull(client).setScreen(this); - list.setScrollAmount(scrollAmount); + this.refresh(); })).dimensions(width - 205, 0, 40, 20).build(); if (info.widget instanceof Map.Entry) { Map.Entry> widget = (Map.Entry>) info.widget; - if (info.field.getType().isEnum()) widget.setValue(value -> Text.translatable(translationPrefix + "enum." + info.field.getType().getSimpleName() + "." + info.value.toString())); - this.list.addButton(List.of(ButtonWidget.builder(widget.getValue().apply(info.value), widget.getKey()).dimensions(width - 160, 0,150, 20).tooltip(getTooltip(info)).build(),resetButton), name, info); + if (info.field.getType().isEnum()) + widget.setValue(value -> Text.translatable(translationPrefix + "enum." + info.field.getType().getSimpleName() + "." + info.value.toString())); + this.list.addButton(List.of(ButtonWidget.builder(widget.getValue().apply(info.value), widget.getKey()).dimensions(width - 160, 0, 150, 20).tooltip(getTooltip(info)).build(), resetButton), name, info); } else if (info.field.getType() == List.class) { if (!reload) info.index = 0; TextFieldWidget widget = new TextFieldWidget(textRenderer, width - 160, 0, 150, 20, Text.empty()); widget.setMaxLength(info.width); - if (info.index < ((List)info.value).size()) widget.setText((String.valueOf(((List)info.value).get(info.index)))); + if (info.index < ((List) info.value).size()) + widget.setText((String.valueOf(((List) info.value).get(info.index)))); Predicate processor = ((BiFunction>) info.widget).apply(widget, done); widget.setTextPredicate(processor); resetButton.setWidth(20); resetButton.setMessage(Text.literal("R").formatted(Formatting.RED)); ButtonWidget cycleButton = ButtonWidget.builder(Text.literal(String.valueOf(info.index)).formatted(Formatting.GOLD), (button -> { - ((List)info.value).remove(""); - double scrollAmount = list.getScrollAmount(); - this.reload = true; + if (((List) info.value).contains("")) ((List) info.value).remove(""); info.index = info.index + 1; - if (info.index > ((List)info.value).size()) info.index = 0; - Objects.requireNonNull(client).setScreen(this); - list.setScrollAmount(scrollAmount); + if (info.index > ((List) info.value).size()) info.index = 0; + this.reload = true; + refresh(); + this.reload = false; })).dimensions(width - 185, 0, 20, 20).build(); widget.setTooltip(getTooltip(info)); this.list.addButton(List.of(widget, resetButton, cycleButton), name, info); } else if (info.widget != null) { ClickableWidget widget; Entry e = info.field.getAnnotation(Entry.class); - if (e.isSlider()) widget = new MidnightSliderWidget(width - 160, 0, 150, 20, Text.of(info.tempValue), (Double.parseDouble(info.tempValue)-e.min()) / (e.max() - e.min()), info); - else widget = new TextFieldWidget(textRenderer, width - 160, 0, 150, 20, null, Text.of(info.tempValue)); + if (e.isSlider()) + widget = new MidnightSliderWidget(width - 160, 0, 150, 20, Text.of(info.tempValue), (Double.parseDouble(info.tempValue) - e.min()) / (e.max() - e.min()), info); + else + widget = new TextFieldWidget(textRenderer, width - 160, 0, 150, 20, null, Text.of(info.tempValue)); if (widget instanceof TextFieldWidget textField) { textField.setMaxLength(info.width); textField.setText(info.tempValue); @@ -310,26 +348,29 @@ public abstract class MidnightConfig { if (e.isColor()) { resetButton.setWidth(20); resetButton.setMessage(Text.literal("R").formatted(Formatting.RED)); - ButtonWidget colorButton = ButtonWidget.builder(Text.literal("⬛"), (button -> {})).dimensions(width - 185, 0, 20, 20).build(); - try {colorButton.setMessage(Text.literal("⬛").setStyle(Style.EMPTY.withColor(Color.decode(info.tempValue).getRGB())));} catch (Exception ignored) {} + ButtonWidget colorButton = ButtonWidget.builder(Text.literal("⬛"), (button -> { + })).dimensions(width - 185, 0, 20, 20).build(); + try { + colorButton.setMessage(Text.literal("⬛").setStyle(Style.EMPTY.withColor(Color.decode(info.tempValue).getRGB()))); + } catch (Exception ignored) { + } info.colorButton = colorButton; colorButton.active = false; this.list.addButton(List.of(widget, resetButton, colorButton), name, info); - } - else this.list.addButton(List.of(widget, resetButton), name, info); + } else this.list.addButton(List.of(widget, resetButton), name, info); } else { - this.list.addButton(List.of(),name, info); + this.list.addButton(List.of(), name, info); } } updateResetButtons(); } - } @Override public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { this.renderBackground(matrices); this.list.render(matrices, mouseX, mouseY, delta); - drawCenteredText(matrices, textRenderer, title, width / 2, 15, 0xFFFFFF); + + drawCenteredTextWithShadow(matrices, textRenderer, title, width / 2, 15, 0xFFFFFF); super.render(matrices,mouseX,mouseY,delta); } } @@ -348,16 +389,11 @@ public abstract class MidnightConfig { public void addButton(List buttons, Text text, EntryInfo info) { this.addEntry(new ButtonEntry(buttons, text, info)); } + public void clear() { + this.clearEntries(); + } @Override public int getRowWidth() { return 10000; } - public Optional getHoveredButton(double mouseX, double mouseY) { - for (ButtonEntry buttonEntry : this.children()) { - if (!buttonEntry.buttons.isEmpty() && buttonEntry.buttons.get(0).isMouseOver(mouseX, mouseY)) { - return Optional.of(buttonEntry.buttons.get(0)); - } - } - return Optional.empty(); - } } public static class ButtonEntry extends ElementListWidget.Entry { private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; @@ -413,12 +449,14 @@ public abstract class MidnightConfig { boolean isColor() default false; boolean isSlider() default false; int precision() default 100; + String category() default "default"; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Client {} @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Server {} @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Hidden {} @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Comment { boolean centered() default false; + String category() default "default"; } public static class HiddenAnnotationExclusionStrategy implements ExclusionStrategy { diff --git a/common/src/main/java/eu/midnightdust/lib/util/screen/TexturedOverlayButtonWidget.java b/common/src/main/java/eu/midnightdust/lib/util/screen/TexturedOverlayButtonWidget.java index fdfbe3a..4d10e1e 100644 --- a/common/src/main/java/eu/midnightdust/lib/util/screen/TexturedOverlayButtonWidget.java +++ b/common/src/main/java/eu/midnightdust/lib/util/screen/TexturedOverlayButtonWidget.java @@ -27,15 +27,19 @@ public class TexturedOverlayButtonWidget extends TexturedButtonWidget { @Override public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) { + int i = 66; + if (!this.isNarratable()) { + i += 40; + } else if (this.isHovered()) { + i += 20; + } RenderSystem.setShader(GameRenderer::getPositionTexProgram); RenderSystem.setShaderTexture(0, WIDGETS_TEXTURE); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); - int i = this.getYImage(this.isHovered()); RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc(); RenderSystem.enableDepthTest(); - this.drawTexture(matrices, this.getX(), this.getY(), 0, 46 + i * 20, this.width / 2, this.height); - this.drawTexture(matrices, this.getX() + this.width / 2, this.getY(), 200 - this.width / 2, 46 + i * 20, this.width / 2, this.height); + drawNineSlicedTexture(matrices, this.getX(), this.getY(), this.width, this.height, 4, 200, 20, 0, i); super.renderButton(matrices, mouseX, mouseY, delta); } diff --git a/gradle.properties b/gradle.properties index 23fe448..6ddd160 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,14 +1,15 @@ org.gradle.jvmargs=-Xmx4096M -minecraft_version=1.19.3 -enabled_platforms=quilt,fabric,forge +minecraft_version=1.19.4-pre4 +yarn_mappings=1.19.4-pre4+build.1 +enabled_platforms=fabric archives_base_name=midnightlib -mod_version=1.1.0 +mod_version=1.2.0 maven_group=eu.midnightdust -fabric_loader_version=0.14.11 -fabric_api_version=0.68.1+1.19.3 +fabric_loader_version=0.14.17 +fabric_api_version=0.75.3+1.19.4 forge_version=1.19.3-44.0.18 diff --git a/settings.gradle b/settings.gradle index c86c020..f83aad5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,7 +10,7 @@ pluginManagement { include("common") include("fabric-like") include("fabric") -include("quilt") -include("forge") +//include("quilt") +//include("forge") rootProject.name = "midnightlib"