mirror of
https://github.com/TeamMidnightDust/MidnightLib.git
synced 2025-12-15 17:05:09 +01:00
clean: extract inner classes
This commit is contained in:
@@ -15,7 +15,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static eu.midnightdust.lib.config.MidnightConfig.MidnightConfigListWidget;
|
||||
import eu.midnightdust.lib.config.MidnightConfigListWidget;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public class MidnightConfigOverviewScreen extends Screen {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package eu.midnightdust.lib.config;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gui.Click;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.Element;
|
||||
import net.minecraft.client.gui.Selectable;
|
||||
import net.minecraft.client.gui.screen.ConfirmLinkScreen;
|
||||
import net.minecraft.client.gui.widget.ClickableWidget;
|
||||
import net.minecraft.client.gui.widget.ElementListWidget;
|
||||
import net.minecraft.client.gui.widget.MultilineTextWidget;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ButtonEntry extends ElementListWidget.Entry<ButtonEntry> {
|
||||
private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
|
||||
public final Text text;
|
||||
public final List<ClickableWidget> buttons;
|
||||
public final EntryInfo info;
|
||||
public boolean centered = false;
|
||||
public MultilineTextWidget title;
|
||||
|
||||
public ButtonEntry(List<ClickableWidget> buttons, Text text, EntryInfo info) {
|
||||
this.buttons = buttons;
|
||||
this.text = text;
|
||||
this.info = info;
|
||||
if (info != null && info.comment != null)
|
||||
this.centered = info.comment.centered();
|
||||
int scaledWidth = MinecraftClient.getInstance().getWindow().getScaledWidth();
|
||||
|
||||
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);
|
||||
title.setCentered(centered);
|
||||
if (info != null)
|
||||
title.setTooltip(info.getTooltip(false));
|
||||
title.setMaxWidth(!buttons.isEmpty() ? buttons.get(buttons.size() > 2 ? buttons.size() - 1 : 0).getX() - 16 : scaledWidth - 24);
|
||||
}
|
||||
}
|
||||
|
||||
public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float tickDelta) {
|
||||
buttons.forEach(b -> {
|
||||
b.setY(this.getY());
|
||||
b.render(context, mouseX, mouseY, tickDelta);
|
||||
});
|
||||
if (title != null) {
|
||||
title.setY(this.getY() + 5);
|
||||
title.render(context, mouseX, mouseY, tickDelta);
|
||||
|
||||
if (info.entry != null && !this.buttons.isEmpty() && this.buttons.getFirst() instanceof ClickableWidget widget) {
|
||||
int idMode = this.info.entry.idMode();
|
||||
if (idMode != -1) context.drawItem(idMode == 0 ?
|
||||
Registries.ITEM.get(Identifier.tryParse(this.info.tempValue)).getDefaultStack()
|
||||
: Registries.BLOCK.get(Identifier.tryParse(this.info.tempValue)).asItem().getDefaultStack(),
|
||||
widget.getX() + widget.getWidth() - 18, this.getY() + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(Click click, boolean doubled) {
|
||||
if (this.info != null && this.info.comment != null && !this.info.comment.url().isBlank())
|
||||
ConfirmLinkScreen.open(MinecraftClient.getInstance().currentScreen, this.info.comment.url(), true);
|
||||
return super.mouseClicked(click, doubled);
|
||||
}
|
||||
|
||||
public List<? extends Element> children() {
|
||||
return Lists.newArrayList(buttons);
|
||||
}
|
||||
|
||||
public List<? extends Selectable> selectableChildren() {
|
||||
return Lists.newArrayList(buttons);
|
||||
}
|
||||
}
|
||||
102
common/src/main/java/eu/midnightdust/lib/config/EntryInfo.java
Normal file
102
common/src/main/java/eu/midnightdust/lib/config/EntryInfo.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package eu.midnightdust.lib.config;
|
||||
|
||||
import eu.midnightdust.lib.util.PlatformFunctions;
|
||||
import net.minecraft.client.gui.tab.Tab;
|
||||
import net.minecraft.client.gui.tooltip.Tooltip;
|
||||
import net.minecraft.client.gui.widget.ClickableWidget;
|
||||
import net.minecraft.client.resource.language.I18n;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
public class EntryInfo {
|
||||
public MidnightConfig.Entry entry;
|
||||
public MidnightConfig.Comment comment;
|
||||
public MidnightConfig.Condition[] conditions;
|
||||
public final Field field;
|
||||
public final Class<?> dataType;
|
||||
public final String modid, fieldName;
|
||||
int listIndex;
|
||||
Object defaultValue, value, function;
|
||||
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, String modid) {
|
||||
this.field = field;
|
||||
this.modid = modid;
|
||||
if (field != null) {
|
||||
this.fieldName = field.getName();
|
||||
this.dataType = MidnightConfig.getUnderlyingType(field);
|
||||
this.entry = field.getAnnotation(MidnightConfig.Entry.class);
|
||||
this.comment = field.getAnnotation(MidnightConfig.Comment.class);
|
||||
this.conditions = field.getAnnotationsByType(MidnightConfig.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;
|
||||
this.tempValue = value.toString();
|
||||
} else {
|
||||
writeList(this.listIndex, value);
|
||||
this.tempValue = toTemporaryValue();
|
||||
}
|
||||
}
|
||||
|
||||
public String toTemporaryValue() {
|
||||
if (this.field.getType() != List.class) return this.value.toString();
|
||||
else try {
|
||||
return ((List<?>) this.value).get(this.listIndex).toString();
|
||||
} catch (Exception ignored) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public void updateFieldValue() {
|
||||
try {
|
||||
if (this.field.get(null) != value) MidnightConfig.entries.values().forEach(EntryInfo::updateConditions);
|
||||
this.field.set(null, this.value);
|
||||
} catch (IllegalAccessException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantValue") //pertains to requiredModLoaded
|
||||
public void updateConditions() {
|
||||
boolean prevConditionState = this.conditionsMet;
|
||||
if (this.conditions.length > 0) this.conditionsMet = true; // reset conditions
|
||||
for (MidnightConfig.Condition condition : this.conditions) {
|
||||
if (!condition.requiredModId().isEmpty() && !PlatformFunctions.isModLoaded(condition.requiredModId()))
|
||||
this.conditionsMet = false;
|
||||
String requiredOption = condition.requiredOption().contains(":") ? condition.requiredOption() : (this.modid + ":" + condition.requiredOption());
|
||||
if (MidnightConfig.entries.get(requiredOption) instanceof EntryInfo info)
|
||||
this.conditionsMet &= List.of(condition.requiredValue()).contains(info.tempValue);
|
||||
if (!this.conditionsMet) break;
|
||||
}
|
||||
if (prevConditionState != this.conditionsMet) MidnightConfig.configInstances.get(modid).reloadScreen = true;
|
||||
}
|
||||
|
||||
public <T> void writeList(int index, T value) {
|
||||
var list = (List<T>) this.value;
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,19 @@
|
||||
package eu.midnightdust.lib.config;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.stream.*;
|
||||
import eu.midnightdust.lib.util.PlatformFunctions;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gl.RenderPipelines;
|
||||
import net.minecraft.client.gui.Click;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.Element;
|
||||
import net.minecraft.client.gui.Selectable;
|
||||
import net.minecraft.client.gui.screen.ConfirmLinkScreen;
|
||||
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.input.KeyInput;
|
||||
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.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.*;
|
||||
@@ -56,118 +38,36 @@ public abstract class MidnightConfig {
|
||||
private static final Pattern INTEGER_ONLY = Pattern.compile("(-?[0-9]*)");
|
||||
private static final Pattern DECIMAL_ONLY = Pattern.compile("-?(\\d+\\.?\\d*|\\d*\\.?\\d+|\\.)");
|
||||
private static final Pattern HEXADECIMAL_ONLY = Pattern.compile("(-?[#0-9a-fA-F]*)");
|
||||
|
||||
private static final LinkedHashMap<String, EntryInfo> entries = new LinkedHashMap<>(); // modid:fieldName -> EntryInfo
|
||||
private boolean reloadScreen = false;
|
||||
|
||||
public static class EntryInfo {
|
||||
public Entry entry;
|
||||
public Comment comment;
|
||||
public Condition[] conditions;
|
||||
public final Field field;
|
||||
public final Class<?> dataType;
|
||||
public final String modid, fieldName;
|
||||
int listIndex;
|
||||
Object defaultValue, value, function;
|
||||
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, String modid) {
|
||||
this.field = field; this.modid = modid;
|
||||
if (field != null) {
|
||||
this.fieldName = field.getName();
|
||||
this.dataType = getUnderlyingType(field);
|
||||
this.entry = field.getAnnotation(Entry.class);
|
||||
this.comment = field.getAnnotation(Comment.class);
|
||||
this.conditions = field.getAnnotationsByType(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;
|
||||
this.tempValue = value.toString();
|
||||
} else { writeList(this.listIndex, value);
|
||||
this.tempValue = toTemporaryValue(); }
|
||||
}
|
||||
public String toTemporaryValue() {
|
||||
if (this.field.getType() != List.class) return this.value.toString();
|
||||
else try { return ((List<?>) this.value).get(this.listIndex).toString(); }
|
||||
catch (Exception ignored) {return "";}
|
||||
}
|
||||
public void updateFieldValue() {
|
||||
try {
|
||||
if (this.field.get(null) != value) entries.values().forEach(EntryInfo::updateConditions);
|
||||
this.field.set(null, this.value);
|
||||
} catch (IllegalAccessException ignored) {}
|
||||
}
|
||||
@SuppressWarnings("ConstantValue") //pertains to requiredModLoaded
|
||||
public void updateConditions() {
|
||||
boolean prevConditionState = this.conditionsMet;
|
||||
if (this.conditions.length > 0) this.conditionsMet = true; // reset conditions
|
||||
for (Condition condition : this.conditions) {
|
||||
if (!condition.requiredModId().isEmpty() && !PlatformFunctions.isModLoaded(condition.requiredModId()))
|
||||
this.conditionsMet = false;
|
||||
String requiredOption = condition.requiredOption().contains(":") ? condition.requiredOption() : (this.modid + ":" + condition.requiredOption());
|
||||
if (entries.get(requiredOption) instanceof EntryInfo info)
|
||||
this.conditionsMet &= List.of(condition.requiredValue()).contains(info.tempValue);
|
||||
if (!this.conditionsMet) break;
|
||||
}
|
||||
if (prevConditionState != this.conditionsMet) configInstances.get(modid).reloadScreen = true;
|
||||
}
|
||||
public <T> void writeList(int index, T value) {
|
||||
var list = (List<T>) this.value;
|
||||
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<String, Class<? extends MidnightConfig>> configClass = new HashMap<>();
|
||||
public static final Map<String, MidnightConfig> configInstances = new HashMap<>();
|
||||
protected String modid;
|
||||
public Class<? extends MidnightConfig> configClass;
|
||||
|
||||
private static final Gson gson = new GsonBuilder()
|
||||
.excludeFieldsWithModifiers(Modifier.TRANSIENT).excludeFieldsWithModifiers(Modifier.PRIVATE)
|
||||
.addSerializationExclusionStrategy(new NonEntryExclusionStrategy())
|
||||
.addSerializationExclusionStrategy(new ExclusionStrategy() {
|
||||
public boolean shouldSkipClass(Class<?> clazz) { return false; }
|
||||
public boolean shouldSkipField(FieldAttributes fieldAttributes) { return fieldAttributes.getAnnotation(Entry.class) == null; }
|
||||
})
|
||||
.registerTypeAdapter(Identifier.class, new TypeAdapter<Identifier>() {
|
||||
public void write(JsonWriter out, Identifier id) throws IOException { out.value(id.toString()); }
|
||||
public Identifier read(JsonReader in) throws IOException { return Identifier.of(in.nextString()); }
|
||||
}).setPrettyPrinting().create();
|
||||
|
||||
public void loadValuesFromJson() {
|
||||
protected static final LinkedHashMap<String, EntryInfo> entries = new LinkedHashMap<>(); // modid:fieldName -> EntryInfo
|
||||
|
||||
public static final Map<String, MidnightConfig> configInstances = new HashMap<>();
|
||||
|
||||
protected String modid;
|
||||
protected boolean reloadScreen = false;
|
||||
public Class<? extends MidnightConfig> configClass;
|
||||
|
||||
public static <T extends MidnightConfig> T createInstance(String modid, Class<? extends MidnightConfig> configClass) { // This is basically an argumented constructor without the requirement of having one in each config class
|
||||
try {
|
||||
gson.fromJson(Files.newBufferedReader(getJsonFilePath()), configClass);
|
||||
T instance = (T) configClass.getDeclaredConstructor().newInstance();
|
||||
instance.modid = modid;
|
||||
instance.configClass = configClass;
|
||||
configInstances.put(modid, instance);
|
||||
return instance;
|
||||
}
|
||||
catch (Exception e) {
|
||||
write(modid);
|
||||
}
|
||||
entries.values().forEach(info -> {
|
||||
if (info.field != null && info.entry != null) {
|
||||
try {
|
||||
info.value = info.field.get(null) == null ? info.defaultValue : info.field.get(null);
|
||||
info.tempValue = info.toTemporaryValue();
|
||||
info.updateConditions();
|
||||
} catch (IllegalAccessException ignored) {}
|
||||
}
|
||||
});
|
||||
catch (Exception e) { throw new RuntimeException(e); }
|
||||
}
|
||||
|
||||
public static void init(String modid, Class<? extends MidnightConfig> config) {
|
||||
MidnightConfig instance = createInstance(modid, config);
|
||||
|
||||
@@ -181,6 +81,7 @@ public abstract class MidnightConfig {
|
||||
}
|
||||
instance.loadValuesFromJson();
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private void initClient(String modid, Field field, EntryInfo info) {
|
||||
Entry e = info.entry;
|
||||
@@ -207,6 +108,7 @@ public abstract class MidnightConfig {
|
||||
}
|
||||
entries.put(key, info);
|
||||
}
|
||||
|
||||
public static Class<?> getUnderlyingType(Field field) {
|
||||
Class<?> rawType = field.getType();
|
||||
if (field.getType() == List.class)
|
||||
@@ -215,13 +117,6 @@ public abstract class MidnightConfig {
|
||||
} catch (NoSuchFieldException | IllegalAccessException ignored) { return rawType; }
|
||||
}
|
||||
|
||||
private Text getEnumTranslatableText(Object value, EntryInfo info) {
|
||||
if (value instanceof TranslatableOption translatableOption) return translatableOption.getText();
|
||||
|
||||
String translationKey = "%s.midnightconfig.enum.%s.%s".formatted(modid, info.dataType.getSimpleName(), info.toTemporaryValue());
|
||||
return I18n.hasTranslation(translationKey) ? Text.translatable(translationKey) : Text.literal(info.toTemporaryValue());
|
||||
}
|
||||
|
||||
private static void textField(EntryInfo info, Function<String,Number> f, Pattern pattern, double min, double max, boolean cast) {
|
||||
boolean isNumber = pattern != null;
|
||||
info.function = (BiFunction<TextFieldWidget, ButtonWidget, Predicate<String>>) (t, b) -> s -> {
|
||||
@@ -259,16 +154,30 @@ public abstract class MidnightConfig {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
public static <T extends MidnightConfig> T createInstance(String modid, Class<? extends MidnightConfig> configClass) {
|
||||
if (configInstances.containsKey(modid)) return (T) configInstances.get(modid);
|
||||
|
||||
protected Text getEnumTranslatableText(Object value, EntryInfo info) {
|
||||
if (value instanceof TranslatableOption translatableOption) return translatableOption.getText();
|
||||
|
||||
String translationKey = "%s.midnightconfig.enum.%s.%s".formatted(modid, info.dataType.getSimpleName(), info.toTemporaryValue());
|
||||
return I18n.hasTranslation(translationKey) ? Text.translatable(translationKey) : Text.literal(info.toTemporaryValue());
|
||||
}
|
||||
|
||||
public void loadValuesFromJson() {
|
||||
try {
|
||||
T instance = (T) configClass.getDeclaredConstructor().newInstance();
|
||||
instance.modid = modid;
|
||||
instance.configClass = configClass;
|
||||
configInstances.put(modid, instance);
|
||||
return instance;
|
||||
gson.fromJson(Files.newBufferedReader(getJsonFilePath()), configClass);
|
||||
}
|
||||
catch (Exception e) { throw new RuntimeException(e); }
|
||||
catch (Exception e) {
|
||||
write(modid);
|
||||
}
|
||||
entries.values().forEach(info -> {
|
||||
if (info.field != null && info.entry != null) {
|
||||
try {
|
||||
info.value = info.field.get(null) == null ? info.defaultValue : info.field.get(null);
|
||||
info.tempValue = info.toTemporaryValue();
|
||||
info.updateConditions();
|
||||
} catch (IllegalAccessException ignored) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void write(String modid) {
|
||||
@@ -307,327 +216,6 @@ public abstract class MidnightConfig {
|
||||
public Screen getScreen(Screen parent) {
|
||||
return new MidnightConfigScreen(parent, modid);
|
||||
}
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static class MidnightConfigScreen extends Screen {
|
||||
public MidnightConfig instance;
|
||||
public final String translationPrefix, modid;
|
||||
public final Screen parent;
|
||||
public MidnightConfigListWidget list;
|
||||
public TabManager tabManager = new TabManager(a -> {}, a -> {});
|
||||
public Map<String, Tab> tabs = new LinkedHashMap<>();
|
||||
public Tab prevTab;
|
||||
public TabNavigationWidget tabNavigation;
|
||||
public ButtonWidget done;
|
||||
public double scrollProgress = 0d;
|
||||
|
||||
protected MidnightConfigScreen(Screen parent, String modid) {
|
||||
super(Text.translatable(modid + ".midnightconfig.title"));
|
||||
this.parent = parent; this.modid = modid;
|
||||
this.translationPrefix = modid + ".midnightconfig.";
|
||||
this.instance = configInstances.get(modid);
|
||||
instance.loadValuesFromJson();
|
||||
entries.values().forEach(info -> {
|
||||
if (info.modid.equals(modid)) {
|
||||
String tabId = info.entry != null ? info.entry.category() : info.comment.category();
|
||||
String name = translationPrefix + "category." + tabId;
|
||||
if (!I18n.hasTranslation(name) && tabId.equals("default"))
|
||||
name = translationPrefix + "title";
|
||||
if (!tabs.containsKey(name)) {
|
||||
info.tab = new GridScreenTab(Text.translatable(name));
|
||||
tabs.put(name, info.tab);
|
||||
} else info.tab = tabs.get(name);
|
||||
}
|
||||
});
|
||||
tabNavigation = TabNavigationWidget.builder(tabManager, this.width).tabs(tabs.values().toArray(new Tab[0])).build();
|
||||
tabNavigation.selectTab(0, false);
|
||||
tabNavigation.init();
|
||||
prevTab = tabManager.getCurrentTab();
|
||||
}
|
||||
|
||||
// Real Time config update //
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
if (prevTab != null && prevTab != tabManager.getCurrentTab()) {
|
||||
prevTab = tabManager.getCurrentTab();
|
||||
updateList();
|
||||
list.setScrollY(0);
|
||||
}
|
||||
scrollProgress = list.getScrollY();
|
||||
for (EntryInfo info : entries.values())
|
||||
info.updateFieldValue();
|
||||
updateButtons();
|
||||
if (instance.reloadScreen) {
|
||||
updateList();
|
||||
instance.reloadScreen = false;
|
||||
}
|
||||
}
|
||||
public void updateButtons() {
|
||||
if (this.list == null) return;
|
||||
|
||||
for (ButtonEntry entry : this.list.children()) {
|
||||
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(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean keyPressed(KeyInput input) {
|
||||
return this.tabNavigation.keyPressed(input) || super.keyPressed(input);
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
instance.loadValuesFromJson();
|
||||
entries.values().forEach(info -> {
|
||||
info.error = null; info.value = null; info.tempValue = null; info.actionButton = null; info.listIndex = 0; info.tab = null; info.inLimits = true;
|
||||
});
|
||||
Objects.requireNonNull(client).setScreen(parent);
|
||||
}
|
||||
@Override
|
||||
public void init() {
|
||||
super.init();
|
||||
tabNavigation.setWidth(this.width);
|
||||
tabNavigation.init();
|
||||
if (tabs.size() > 1)
|
||||
this.addDrawableChild(tabNavigation);
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(ScreenTexts.CANCEL, button -> this.close()).dimensions(this.width / 2 - 154, this.height - 26, 150, 20).build());
|
||||
done = this.addDrawableChild(ButtonWidget.builder(ScreenTexts.DONE, (button) -> {
|
||||
for (EntryInfo info : entries.values())
|
||||
if (info.modid.equals(modid))
|
||||
info.updateFieldValue();
|
||||
write(modid);
|
||||
close();
|
||||
}).dimensions(this.width / 2 + 4, this.height - 26, 150, 20).build());
|
||||
|
||||
this.list = new MidnightConfigListWidget(this.client, this.width, this.height - 57, 24, 25);
|
||||
this.addSelectableChild(this.list);
|
||||
updateList();
|
||||
if (tabs.size() > 1)
|
||||
list.renderHeaderSeparator = false;
|
||||
}
|
||||
public void updateList() {
|
||||
this.list.clear();
|
||||
instance.onTabInit(prevTab.getTitle().getContent() instanceof TranslatableTextContent translatable ?
|
||||
translatable.getKey().substring(translatable.getKey().lastIndexOf('.')+1) : prevTab.getTitle().toString(), list, this);
|
||||
for (EntryInfo info : entries.values()) {
|
||||
info.updateConditions();
|
||||
if (!info.conditionsMet) {
|
||||
boolean visibleButLocked = false;
|
||||
for (Condition condition : info.conditions)
|
||||
visibleButLocked |= condition.visibleButLocked();
|
||||
if (!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();
|
||||
updateList();
|
||||
}), true).texture(Identifier.of("midnightlib","icon/reset"), 12, 12).dimension(20, 20).build();
|
||||
resetButton.setPosition(width - 205 + 150 + 25, 0);
|
||||
|
||||
if (info.function != null) {
|
||||
ClickableWidget widget;
|
||||
Entry e = info.entry;
|
||||
if (info.function instanceof Map.Entry) { // Enums & booleans
|
||||
var values = (Map.Entry<ButtonWidget.PressAction, Function<Object, Text>>) info.function;
|
||||
if (info.dataType.isEnum()) {
|
||||
values.setValue(value -> instance.getEnumTranslatableText(value, info));
|
||||
}
|
||||
widget = ButtonWidget.builder(values.getValue().apply(info.value), values.getKey()).dimensions(width - 185, 0, 150, 20).tooltip(info.getTooltip(true)).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);
|
||||
else
|
||||
widget = new TextFieldWidget(textRenderer, width - 185, 0, 150, 20, Text.empty());
|
||||
|
||||
if (widget instanceof TextFieldWidget textField) {
|
||||
textField.setMaxLength(e.width());
|
||||
textField.setText(info.tempValue);
|
||||
Predicate<String> processor = ((BiFunction<TextFieldWidget, ButtonWidget, Predicate<String>>) info.function).apply(textField, done);
|
||||
textField.setTextPredicate(processor);
|
||||
}
|
||||
widget.setTooltip(info.getTooltip(true));
|
||||
|
||||
ButtonWidget cycleButton = null;
|
||||
if (info.field.getType() == List.class) {
|
||||
cycleButton = ButtonWidget.builder(Text.literal(String.valueOf(info.listIndex)).formatted(Formatting.GOLD), (button -> {
|
||||
var values = (List<?>) info.value;
|
||||
values.remove("");
|
||||
info.listIndex = info.listIndex != values.size() ? info.listIndex + 1 : 0;
|
||||
info.tempValue = info.listIndex != values.size() ? info.toTemporaryValue() : "";
|
||||
updateList();
|
||||
})).dimensions(width - 185, 0, 20, 20).build();
|
||||
}
|
||||
if (e.isColor()) {
|
||||
ButtonWidget colorButton = ButtonWidget.builder(Text.literal("⬛"),
|
||||
button -> new Thread(() -> {
|
||||
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));
|
||||
updateList();
|
||||
}
|
||||
}).start()
|
||||
).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.actionButton = colorButton;
|
||||
} else if (e.selectionMode() > -1) {
|
||||
ButtonWidget explorerButton = TextIconButtonWidget.builder(Text.empty(),
|
||||
button -> new Thread(() -> {
|
||||
JFileChooser fileChooser = new JFileChooser(info.tempValue);
|
||||
fileChooser.setFileSelectionMode(e.selectionMode());
|
||||
fileChooser.setDialogType(e.fileChooserType());
|
||||
fileChooser.setDialogTitle(Text.translatable(translationPrefix + info.fieldName + ".fileChooser").getString());
|
||||
if ((e.selectionMode() == JFileChooser.FILES_ONLY || e.selectionMode() == JFileChooser.FILES_AND_DIRECTORIES) && Arrays.stream(e.fileExtensions()).noneMatch("*"::equals))
|
||||
fileChooser.setFileFilter(new FileNameExtensionFilter(
|
||||
Text.translatable(translationPrefix + info.fieldName + ".fileFilter").getString(), e.fileExtensions()));
|
||||
if (fileChooser.showDialog(null, null) == JFileChooser.APPROVE_OPTION) {
|
||||
info.setValue(fileChooser.getSelectedFile().getAbsolutePath());
|
||||
updateList();
|
||||
}
|
||||
}).start(), true
|
||||
).texture(Identifier.of("midnightlib", "icon/explorer"), 12, 12).dimension(20, 20).build();
|
||||
explorerButton.setPosition(width - 185, 0);
|
||||
info.actionButton = explorerButton;
|
||||
}
|
||||
List<ClickableWidget> widgets = Lists.newArrayList(widget, resetButton);
|
||||
|
||||
if (info.actionButton != null) {
|
||||
if (Util.getOperatingSystem() == Util.OperatingSystem.OSX) info.actionButton.active = false;
|
||||
widget.setWidth(widget.getWidth() - 22);
|
||||
widget.setX(widget.getX() + 22);
|
||||
widgets.add(info.actionButton);
|
||||
}
|
||||
if (cycleButton != null) {
|
||||
if (info.actionButton != null) info.actionButton.setX(info.actionButton.getX() + 22);
|
||||
widget.setWidth(widget.getWidth() - 22);
|
||||
widget.setX(widget.getX() + 22);
|
||||
widgets.add(cycleButton);
|
||||
}
|
||||
if (!info.conditionsMet) widgets.forEach(w -> w.active = false);
|
||||
this.list.addButton(widgets, name, info);
|
||||
} else this.list.addButton(List.of(), name, info);
|
||||
} list.setScrollY(scrollProgress);
|
||||
updateButtons();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||
super.render(context, mouseX, mouseY, delta);
|
||||
this.list.render(context, mouseX, mouseY, delta);
|
||||
if (tabs.size() < 2) context.drawCenteredTextWithShadow(textRenderer, title, width / 2, 10, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static class MidnightConfigListWidget extends ElementListWidget<ButtonEntry> {
|
||||
public boolean renderHeaderSeparator = true;
|
||||
|
||||
public MidnightConfigListWidget(MinecraftClient client, int width, int height, int y, int itemHeight) {
|
||||
super(client, width, height, y, itemHeight);
|
||||
}
|
||||
|
||||
@Override public int getScrollbarX() {
|
||||
return this.width -7;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawHeaderAndFooterSeparators(DrawContext context) {
|
||||
if (renderHeaderSeparator)
|
||||
super.drawHeaderAndFooterSeparators(context);
|
||||
else
|
||||
context.drawTexture(RenderPipelines.GUI_TEXTURED, this.client.world == null ? Screen.FOOTER_SEPARATOR_TEXTURE : Screen.INWORLD_FOOTER_SEPARATOR_TEXTURE, this.getX(), this.getBottom(), 0, 0, this.getWidth(), 2, 32, 2);
|
||||
}
|
||||
public void addButton(List<ClickableWidget> buttons, Text text, EntryInfo info) {
|
||||
this.addEntry(new ButtonEntry(buttons, text, info));
|
||||
}
|
||||
public void clear() {
|
||||
this.clearEntries();
|
||||
}
|
||||
@Override public int getRowWidth() {
|
||||
return 10000;
|
||||
}
|
||||
}
|
||||
public static class ButtonEntry extends ElementListWidget.Entry<ButtonEntry> {
|
||||
private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
|
||||
public final Text text;
|
||||
public final List<ClickableWidget> buttons;
|
||||
public final EntryInfo info;
|
||||
public boolean centered = false;
|
||||
public MultilineTextWidget title;
|
||||
|
||||
public ButtonEntry(List<ClickableWidget> buttons, Text text, EntryInfo info) {
|
||||
this.buttons = buttons;
|
||||
this.text = text;
|
||||
this.info = info;
|
||||
if (info != null && info.comment != null)
|
||||
this.centered = info.comment.centered();
|
||||
int scaledWidth = MinecraftClient.getInstance().getWindow().getScaledWidth();
|
||||
|
||||
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);
|
||||
title.setCentered(centered);
|
||||
if (info != null)
|
||||
title.setTooltip(info.getTooltip(false));
|
||||
title.setMaxWidth(!buttons.isEmpty() ? buttons.get(buttons.size() > 2 ? buttons.size()-1 : 0).getX() - 16 : scaledWidth - 24);
|
||||
}
|
||||
}
|
||||
public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float tickDelta) {
|
||||
buttons.forEach(b -> {
|
||||
b.setY(this.getY());
|
||||
b.render(context, mouseX, mouseY, tickDelta);
|
||||
});
|
||||
if (title != null) {
|
||||
title.setY(this.getY()+5);
|
||||
title.render(context, mouseX, mouseY, tickDelta);
|
||||
|
||||
if (info.entry != null && !this.buttons.isEmpty() && this.buttons.getFirst() instanceof ClickableWidget widget) {
|
||||
int idMode = this.info.entry.idMode();
|
||||
if (idMode != -1) context.drawItem(idMode == 0 ?
|
||||
Registries.ITEM.get(Identifier.tryParse(this.info.tempValue)).getDefaultStack()
|
||||
: Registries.BLOCK.get(Identifier.tryParse(this.info.tempValue)).asItem().getDefaultStack(),
|
||||
widget.getX() + widget.getWidth() - 18, this.getY() + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(Click click, boolean doubled) {
|
||||
if (this.info != null && this.info.comment != null && !this.info.comment.url().isBlank())
|
||||
ConfirmLinkScreen.open(MinecraftClient.getInstance().currentScreen, this.info.comment.url(), true);
|
||||
return super.mouseClicked(click, doubled);
|
||||
}
|
||||
|
||||
public List<? extends Element> children() {return Lists.newArrayList(buttons);}
|
||||
public List<? extends Selectable> selectableChildren() {return Lists.newArrayList(buttons);}
|
||||
}
|
||||
public static class MidnightSliderWidget extends SliderWidget {
|
||||
private final EntryInfo info; private final Entry e;
|
||||
public MidnightSliderWidget(int x, int y, int width, int height, Text text, double value, EntryInfo info) {
|
||||
super(x, y, width, height, text, value);
|
||||
this.e = info.entry;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessage() { this.setMessage(Text.of(info.tempValue)); }
|
||||
|
||||
@Override
|
||||
public void applyValue() {
|
||||
if (info.dataType == int.class) info.setValue(((Number) (e.min() + value * (e.max() - e.min()))).intValue());
|
||||
else if (info.dataType == double.class) info.setValue(Math.round((e.min() + value * (e.max() - e.min())) * (double) e.precision()) / (double) e.precision());
|
||||
else if (info.dataType == float.class) info.setValue(Math.round((e.min() + value * (e.max() - e.min())) * (float) e.precision()) / (float) e.precision());
|
||||
}
|
||||
}
|
||||
public static class NonEntryExclusionStrategy implements ExclusionStrategy {
|
||||
public boolean shouldSkipClass(Class<?> clazz) { return false; }
|
||||
public boolean shouldSkipField(FieldAttributes fieldAttributes) { return fieldAttributes.getAnnotation(Entry.class) == null; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry Annotation<br>
|
||||
@@ -668,19 +256,25 @@ public abstract class MidnightConfig {
|
||||
@Deprecated String requiredMod() default "";
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Client {}
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Client {}
|
||||
|
||||
/**
|
||||
* Hides the entry in config screens, but still makes it accessible through the command {@code /midnightconfig MOD_ID ENTRY} and directly editing the config file.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Server {}
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Server {}
|
||||
|
||||
/**
|
||||
* Hides the entry entirely.
|
||||
* Accessible only through directly editing the config file.
|
||||
* Perfect for saving persistent internal data.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Hidden {}
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Hidden {}
|
||||
|
||||
/**
|
||||
* Comment Annotation<br>
|
||||
@@ -689,7 +283,9 @@ public abstract class MidnightConfig {
|
||||
* - <b>{@link Comment#name()}</b>: Will be used instead of the default translation key, if not empty<br>
|
||||
* - <b>{@link Comment#url()}</b>: The url of the comment should link to in the config screen (none if left empty)<br>
|
||||
* */
|
||||
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Comment {
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Comment {
|
||||
boolean centered() default false;
|
||||
String category() default "default";
|
||||
String name() default "";
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package eu.midnightdust.lib.config;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gl.RenderPipelines;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.ClickableWidget;
|
||||
import net.minecraft.client.gui.widget.ElementListWidget;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public class MidnightConfigListWidget extends ElementListWidget<ButtonEntry> {
|
||||
public boolean renderHeaderSeparator = true;
|
||||
|
||||
public MidnightConfigListWidget(MinecraftClient client, int width, int height, int y, int itemHeight) {
|
||||
super(client, width, height, y, itemHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScrollbarX() {
|
||||
return this.width - 7;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawHeaderAndFooterSeparators(DrawContext context) {
|
||||
if (renderHeaderSeparator)
|
||||
super.drawHeaderAndFooterSeparators(context);
|
||||
else
|
||||
context.drawTexture(RenderPipelines.GUI_TEXTURED, this.client.world == null ? Screen.FOOTER_SEPARATOR_TEXTURE : Screen.INWORLD_FOOTER_SEPARATOR_TEXTURE, this.getX(), this.getBottom(), 0, 0, this.getWidth(), 2, 32, 2);
|
||||
}
|
||||
|
||||
public void addButton(List<ClickableWidget> buttons, Text text, EntryInfo info) {
|
||||
this.addEntry(new ButtonEntry(buttons, text, info));
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.clearEntries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowWidth() {
|
||||
return 10000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package eu.midnightdust.lib.config;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
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.widget.*;
|
||||
import net.minecraft.client.input.KeyInput;
|
||||
import net.minecraft.client.resource.language.I18n;
|
||||
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.Util;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public class MidnightConfigScreen extends Screen {
|
||||
public MidnightConfig instance;
|
||||
public final String translationPrefix, modid;
|
||||
public final Screen parent;
|
||||
public MidnightConfigListWidget list;
|
||||
public TabManager tabManager = new TabManager(a -> {
|
||||
}, a -> {
|
||||
});
|
||||
public Map<String, Tab> tabs = new LinkedHashMap<>();
|
||||
public Tab prevTab;
|
||||
public TabNavigationWidget tabNavigation;
|
||||
public ButtonWidget done;
|
||||
public double scrollProgress = 0d;
|
||||
|
||||
public MidnightConfigScreen(Screen parent, String modid) {
|
||||
super(Text.translatable(modid + ".midnightconfig.title"));
|
||||
this.parent = parent;
|
||||
this.modid = modid;
|
||||
this.translationPrefix = modid + ".midnightconfig.";
|
||||
this.instance = MidnightConfig.configInstances.get(modid);
|
||||
instance.loadValuesFromJson();
|
||||
MidnightConfig.entries.values().forEach(info -> {
|
||||
if (Objects.equals(info.modid, modid)) {
|
||||
String tabId = info.entry != null ? info.entry.category() : info.comment.category();
|
||||
String name = translationPrefix + "category." + tabId;
|
||||
if (!I18n.hasTranslation(name) && tabId.equals("default"))
|
||||
name = translationPrefix + "title";
|
||||
if (!tabs.containsKey(name)) {
|
||||
info.tab = new GridScreenTab(Text.translatable(name));
|
||||
tabs.put(name, info.tab);
|
||||
} else info.tab = tabs.get(name);
|
||||
}
|
||||
});
|
||||
tabNavigation = TabNavigationWidget.builder(tabManager, this.width).tabs(tabs.values().toArray(new Tab[0])).build();
|
||||
tabNavigation.selectTab(0, false);
|
||||
tabNavigation.init();
|
||||
prevTab = tabManager.getCurrentTab();
|
||||
}
|
||||
|
||||
// Real Time config update //
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
if (prevTab != null && prevTab != tabManager.getCurrentTab()) {
|
||||
prevTab = tabManager.getCurrentTab();
|
||||
updateList();
|
||||
list.setScrollY(0);
|
||||
}
|
||||
scrollProgress = list.getScrollY();
|
||||
for (EntryInfo info : MidnightConfig.entries.values())
|
||||
info.updateFieldValue();
|
||||
updateButtons();
|
||||
if (instance.reloadScreen) {
|
||||
updateList();
|
||||
instance.reloadScreen = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateButtons() {
|
||||
if (this.list == null) return;
|
||||
|
||||
for (ButtonEntry entry : this.list.children()) {
|
||||
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(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyPressed(KeyInput input) {
|
||||
return this.tabNavigation.keyPressed(input) || super.keyPressed(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
instance.loadValuesFromJson();
|
||||
MidnightConfig.entries.values().forEach(info -> {
|
||||
info.error = null;
|
||||
info.value = null;
|
||||
info.tempValue = null;
|
||||
info.actionButton = null;
|
||||
info.listIndex = 0;
|
||||
info.tab = null;
|
||||
info.inLimits = true;
|
||||
});
|
||||
Objects.requireNonNull(client).setScreen(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
super.init();
|
||||
tabNavigation.setWidth(this.width);
|
||||
tabNavigation.init();
|
||||
if (tabs.size() > 1)
|
||||
this.addDrawableChild(tabNavigation);
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(ScreenTexts.CANCEL, button -> this.close()).dimensions(this.width / 2 - 154, this.height - 26, 150, 20).build());
|
||||
done = this.addDrawableChild(ButtonWidget.builder(ScreenTexts.DONE, (button) -> {
|
||||
for (EntryInfo info : MidnightConfig.entries.values())
|
||||
if (info.modid.equals(modid))
|
||||
info.updateFieldValue();
|
||||
MidnightConfig.write(modid);
|
||||
close();
|
||||
}).dimensions(this.width / 2 + 4, this.height - 26, 150, 20).build());
|
||||
|
||||
this.list = new MidnightConfigListWidget(this.client, this.width, this.height - 57, 24, 25);
|
||||
this.addSelectableChild(this.list);
|
||||
updateList();
|
||||
if (tabs.size() > 1)
|
||||
list.renderHeaderSeparator = false;
|
||||
}
|
||||
|
||||
public void updateList() {
|
||||
this.list.clear();
|
||||
instance.onTabInit(prevTab.getTitle().getContent() instanceof TranslatableTextContent translatable ?
|
||||
translatable.getKey().substring(translatable.getKey().lastIndexOf('.') + 1) : prevTab.getTitle().toString(), list, this);
|
||||
for (EntryInfo info : MidnightConfig.entries.values()) {
|
||||
info.updateConditions();
|
||||
if (!info.conditionsMet) {
|
||||
boolean visibleButLocked = false;
|
||||
for (MidnightConfig.Condition condition : info.conditions)
|
||||
visibleButLocked |= condition.visibleButLocked();
|
||||
if (!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();
|
||||
updateList();
|
||||
}), true).texture(Identifier.of("midnightlib", "icon/reset"), 12, 12).dimension(20, 20).build();
|
||||
resetButton.setPosition(width - 205 + 150 + 25, 0);
|
||||
|
||||
if (info.function != null) {
|
||||
ClickableWidget widget;
|
||||
MidnightConfig.Entry e = info.entry;
|
||||
if (info.function instanceof Map.Entry) { // Enums & booleans
|
||||
var values = (Map.Entry<ButtonWidget.PressAction, Function<Object, Text>>) info.function;
|
||||
if (info.dataType.isEnum()) {
|
||||
values.setValue(value -> instance.getEnumTranslatableText(value, info));
|
||||
}
|
||||
widget = ButtonWidget.builder(values.getValue().apply(info.value), values.getKey()).dimensions(width - 185, 0, 150, 20).tooltip(info.getTooltip(true)).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);
|
||||
else
|
||||
widget = new TextFieldWidget(textRenderer, width - 185, 0, 150, 20, Text.empty());
|
||||
|
||||
if (widget instanceof TextFieldWidget textField) {
|
||||
textField.setMaxLength(e.width());
|
||||
textField.setText(info.tempValue);
|
||||
Predicate<String> processor = ((BiFunction<TextFieldWidget, ButtonWidget, Predicate<String>>) info.function).apply(textField, done);
|
||||
textField.setTextPredicate(processor);
|
||||
}
|
||||
widget.setTooltip(info.getTooltip(true));
|
||||
|
||||
ButtonWidget cycleButton = null;
|
||||
if (info.field.getType() == List.class) {
|
||||
cycleButton = ButtonWidget.builder(Text.literal(String.valueOf(info.listIndex)).formatted(Formatting.GOLD), (button -> {
|
||||
var values = (List<?>) info.value;
|
||||
values.remove("");
|
||||
info.listIndex = info.listIndex != values.size() ? info.listIndex + 1 : 0;
|
||||
info.tempValue = info.listIndex != values.size() ? info.toTemporaryValue() : "";
|
||||
updateList();
|
||||
})).dimensions(width - 185, 0, 20, 20).build();
|
||||
}
|
||||
if (e.isColor()) {
|
||||
ButtonWidget colorButton = ButtonWidget.builder(Text.literal("⬛"),
|
||||
button -> new Thread(() -> {
|
||||
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));
|
||||
updateList();
|
||||
}
|
||||
}).start()
|
||||
).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.actionButton = colorButton;
|
||||
} else if (e.selectionMode() > -1) {
|
||||
ButtonWidget explorerButton = TextIconButtonWidget.builder(Text.empty(),
|
||||
button -> new Thread(() -> {
|
||||
JFileChooser fileChooser = new JFileChooser(info.tempValue);
|
||||
fileChooser.setFileSelectionMode(e.selectionMode());
|
||||
fileChooser.setDialogType(e.fileChooserType());
|
||||
fileChooser.setDialogTitle(Text.translatable(translationPrefix + info.fieldName + ".fileChooser").getString());
|
||||
if ((e.selectionMode() == JFileChooser.FILES_ONLY || e.selectionMode() == JFileChooser.FILES_AND_DIRECTORIES) && Arrays.stream(e.fileExtensions()).noneMatch("*"::equals))
|
||||
fileChooser.setFileFilter(new FileNameExtensionFilter(
|
||||
Text.translatable(translationPrefix + info.fieldName + ".fileFilter").getString(), e.fileExtensions()));
|
||||
if (fileChooser.showDialog(null, null) == JFileChooser.APPROVE_OPTION) {
|
||||
info.setValue(fileChooser.getSelectedFile().getAbsolutePath());
|
||||
updateList();
|
||||
}
|
||||
}).start(), true
|
||||
).texture(Identifier.of("midnightlib", "icon/explorer"), 12, 12).dimension(20, 20).build();
|
||||
explorerButton.setPosition(width - 185, 0);
|
||||
info.actionButton = explorerButton;
|
||||
}
|
||||
List<ClickableWidget> widgets = Lists.newArrayList(widget, resetButton);
|
||||
|
||||
if (info.actionButton != null) {
|
||||
if (Util.getOperatingSystem() == Util.OperatingSystem.OSX) info.actionButton.active = false;
|
||||
widget.setWidth(widget.getWidth() - 22);
|
||||
widget.setX(widget.getX() + 22);
|
||||
widgets.add(info.actionButton);
|
||||
}
|
||||
if (cycleButton != null) {
|
||||
if (info.actionButton != null) info.actionButton.setX(info.actionButton.getX() + 22);
|
||||
widget.setWidth(widget.getWidth() - 22);
|
||||
widget.setX(widget.getX() + 22);
|
||||
widgets.add(cycleButton);
|
||||
}
|
||||
if (!info.conditionsMet) widgets.forEach(w -> w.active = false);
|
||||
this.list.addButton(widgets, name, info);
|
||||
} else this.list.addButton(List.of(), name, info);
|
||||
}
|
||||
list.setScrollY(scrollProgress);
|
||||
updateButtons();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||
super.render(context, mouseX, mouseY, delta);
|
||||
this.list.render(context, mouseX, mouseY, delta);
|
||||
if (tabs.size() < 2) context.drawCenteredTextWithShadow(textRenderer, title, width / 2, 10, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package eu.midnightdust.lib.config;
|
||||
|
||||
import net.minecraft.client.gui.widget.SliderWidget;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
public class MidnightSliderWidget extends SliderWidget {
|
||||
private final EntryInfo info;
|
||||
private final MidnightConfig.Entry e;
|
||||
|
||||
public MidnightSliderWidget(int x, int y, int width, int height, Text text, double value, EntryInfo info) {
|
||||
super(x, y, width, height, text, value);
|
||||
this.e = info.entry;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMessage() {
|
||||
this.setMessage(Text.of(info.tempValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyValue() {
|
||||
if (info.dataType == int.class) info.setValue(((Number) (e.min() + value * (e.max() - e.min()))).intValue());
|
||||
else if (info.dataType == double.class)
|
||||
info.setValue(Math.round((e.min() + value * (e.max() - e.min())) * (double) e.precision()) / (double) e.precision());
|
||||
else if (info.dataType == float.class)
|
||||
info.setValue(Math.round((e.min() + value * (e.max() - e.min())) * (float) e.precision()) / (float) e.precision());
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package eu.midnightdust.test;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import eu.midnightdust.lib.config.MidnightConfig;
|
||||
import eu.midnightdust.lib.config.EntryInfo;
|
||||
import eu.midnightdust.lib.config.MidnightConfigListWidget;
|
||||
import eu.midnightdust.lib.config.MidnightConfigScreen;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.tooltip.Tooltip;
|
||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||
@@ -25,7 +27,7 @@ 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) {
|
||||
public static void add(KeyBinding binding, MidnightConfigListWidget list, 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());
|
||||
@@ -34,7 +36,7 @@ public class MidnightLibExtras {
|
||||
resetButton.setPosition(screen.width - 205 + 150 + 25, 0);
|
||||
editButton.resetButton = resetButton;
|
||||
editButton.updateMessage(false);
|
||||
MidnightConfig.EntryInfo info = new MidnightConfig.EntryInfo(null, screen.modid);
|
||||
EntryInfo info = new EntryInfo(null, screen.modid);
|
||||
|
||||
list.addButton(Lists.newArrayList(editButton, resetButton), Text.translatable(binding.getId()), info);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package eu.midnightdust.test.config;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import eu.midnightdust.lib.config.MidnightConfigListWidget;
|
||||
import eu.midnightdust.lib.config.MidnightConfigScreen;
|
||||
import eu.midnightdust.test.MidnightLibExtras;
|
||||
import eu.midnightdust.lib.config.MidnightConfig;
|
||||
import net.minecraft.text.MutableText;
|
||||
|
||||
Reference in New Issue
Block a user