[config] Support references to other configs

This commit is contained in:
Johannes Frohnmeyer 2022-03-30 17:26:41 +02:00
parent b53b5b189c
commit e0fa641ac3
Signed by: Johannes
GPG Key ID: E76429612C2929F4
17 changed files with 249 additions and 119 deletions

View File

@ -76,3 +76,16 @@ public static void setIntTestIfDisable() {
if (disablePacks) intTest = 0;
}
```
## References
Sometimes, your mod interacts with other mods (such as libjf-web-v0), and you may wish to display their config screens as well.
If that other mod utilizes libjf-config-v0, you can simply add a json block as seen below to your fabric.mod.json
and it will be mentioned in the GUI.
```json
"custom": {
"libjf:config": {
"referencedConfigs": ["libjf-web-v0"]
}
}
```

View File

@ -1,5 +1,5 @@
# libjf-web-v0
libjf-web-v0 provides a HTTP web server you can use in your serverside (and technically also clientside) mods
libjf-web-v0 provides an HTTP web server you can use in your serverside (and technically also clientside) mods
to serve web content through a unified port.
libjf-web-v0 depends on libjf-config-v0 to provide its config, libjf-base, fabric-lifecycle-events-v1 and fabric-command-api-v1

View File

@ -0,0 +1,30 @@
package io.gitlab.jfronny.libjf.gson;
import com.google.gson.*;
import net.fabricmc.loader.api.metadata.CustomValue;
import java.util.Map;
public class FabricLoaderGsonGenerator {
public static JsonElement toGson(CustomValue customValue) {
if (customValue == null) return null;
return switch (customValue.getType()) {
case OBJECT -> {
JsonObject jo = new JsonObject();
for (Map.Entry<String, CustomValue> value : customValue.getAsObject())
jo.add(value.getKey(), toGson(value.getValue()));
yield jo;
}
case ARRAY -> {
JsonArray jo = new JsonArray();
for (CustomValue value : customValue.getAsArray())
jo.add(toGson(value));
yield jo;
}
case STRING -> new JsonPrimitive(customValue.getAsString());
case NUMBER -> new JsonPrimitive(customValue.getAsNumber());
case BOOLEAN -> new JsonPrimitive(customValue.getAsBoolean());
case NULL -> JsonNull.INSTANCE;
};
}
}

View File

@ -20,4 +20,5 @@ public interface ConfigInstance {
boolean matchesConfigClass(Class<?> candidate);
List<EntryInfo> getEntries();
Map<String, Runnable> getPresets();
List<String> getReferencedConfigs();
}

View File

@ -0,0 +1,12 @@
package io.gitlab.jfronny.libjf.config.impl;
import java.util.List;
public class AuxiliaryMetadata {
public List<String> referencedConfigs;
public AuxiliaryMetadata sanitize() {
if (referencedConfigs == null) referencedConfigs = List.of();
return this;
}
}

View File

@ -1,22 +1,26 @@
package io.gitlab.jfronny.libjf.config.impl;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonElement;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import io.gitlab.jfronny.libjf.config.impl.client.screen.TinyConfigScreen;
import net.minecraft.client.gui.screen.Screen;
import io.gitlab.jfronny.libjf.gson.FabricLoaderGsonGenerator;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import org.jetbrains.annotations.ApiStatus;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class ConfigHolderImpl implements ConfigHolder {
@ApiStatus.Internal
public static final ConfigHolderImpl INSTANCE = new ConfigHolderImpl();
private ConfigHolderImpl() {
}
public static final String MODULE_ID = LibJf.MOD_ID + ":config";
private final Map<String, ConfigInstance> configs = new HashMap<>();
private final Map<Path, ConfigInstance> configsByPath = new HashMap<>();
@ -32,7 +36,18 @@ public class ConfigHolderImpl implements ConfigHolder {
if (isRegistered(config)) {
LibJf.LOGGER.warn("Attempted to reuse config class " + config + ", this is unsupported");
}
ConfigInstanceImpl instance = new ConfigInstanceImpl(modId, config);
Optional<ModContainer> container = FabricLoader.getInstance().getModContainer(modId);
AuxiliaryMetadata meta = new AuxiliaryMetadata();
if (container.isPresent()) {
JsonElement el = FabricLoaderGsonGenerator.toGson(container.get().getMetadata().getCustomValue(MODULE_ID));
if (el != null) {
meta = LibJf.GSON.fromJson(el, AuxiliaryMetadata.class);
}
}
else {
LibJf.LOGGER.warn("Attempted to register config for a mod that is not installed: " + modId);
}
ConfigInstanceImpl instance = new ConfigInstanceImpl(modId, config, meta.sanitize());
configs.put(modId, instance);
configsByPath.put(instance.path, instance);
}

View File

@ -24,10 +24,12 @@ public class ConfigInstanceImpl implements ConfigInstance {
public final Path path;
public final String modid;
public final Class<?> configClass;
public final List<String> referencedConfigs;
public ConfigInstanceImpl(String modid, Class<?> config) {
public ConfigInstanceImpl(String modid, Class<?> config, AuxiliaryMetadata meta) {
this.modid = modid;
configClass = config;
referencedConfigs = List.copyOf(meta.referencedConfigs);
path = FabricLoader.getInstance().getConfigDir().resolve(modid + ".json");
for (Field field : config.getFields()) {
@ -173,4 +175,9 @@ public class ConfigInstanceImpl implements ConfigInstance {
public Map<String, Runnable> getPresets() {
return presets;
}
@Override
public List<String> getReferencedConfigs() {
return referencedConfigs;
}
}

View File

@ -1,59 +0,0 @@
package io.gitlab.jfronny.libjf.config.impl.client.screen;
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.gui.DrawableHelper;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.Selectable;
import net.minecraft.client.gui.widget.ClickableWidget;
import net.minecraft.client.gui.widget.ElementListWidget;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Environment(EnvType.CLIENT)
public class ConfigScreenEntry extends ElementListWidget.Entry<ConfigScreenEntry> {
private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
public final List<ClickableWidget> buttons = new ArrayList<>();
private final List<ClickableWidget> resetButtons = new ArrayList<>();
private final List<Text> texts = new ArrayList<>();
private final List<ClickableWidget> buttonsWithResetButtons = new ArrayList<>();
public static final Map<ClickableWidget, Text> buttonsWithText = new HashMap<>();
private ConfigScreenEntry(ClickableWidget button, Text text, ClickableWidget resetButton) {
buttonsWithText.put(button,text);
this.buttons.add(button);
this.resetButtons.add(resetButton);
this.texts.add(text);
this.buttonsWithResetButtons.add(button);
this.buttonsWithResetButtons.add(resetButton);
}
public static ConfigScreenEntry create(ClickableWidget button, Text text, ClickableWidget resetButton) {
return new ConfigScreenEntry(button, text, resetButton);
}
public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
this.buttons.forEach((button) -> {
button.y = y;
button.render(matrices, mouseX, mouseY, tickDelta);
});
this.texts.forEach((text) -> DrawableHelper.drawTextWithShadow(matrices,textRenderer, text,12,y+5,0xFFFFFF));
this.resetButtons.forEach((button) -> {
button.y = y;
button.render(matrices, mouseX, mouseY, tickDelta);
});
}
public List<? extends Element> children() {
return buttonsWithResetButtons;
}
@Override
public List<? extends Selectable> selectableChildren() {
return buttons;
}
}

View File

@ -4,14 +4,21 @@ 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.gui.Element;
import net.minecraft.client.gui.Selectable;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.ClickableWidget;
import net.minecraft.client.gui.widget.ElementListWidget;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
@Environment(EnvType.CLIENT)
public class MidnightConfigListWidget extends ElementListWidget<ConfigScreenEntry> {
public class MidnightConfigListWidget extends ElementListWidget<MidnightConfigListWidget.ConfigListEntryAbstract> {
TextRenderer textRenderer;
public MidnightConfigListWidget(MinecraftClient minecraftClient, int i, int j, int k, int l, int m) {
@ -19,23 +26,99 @@ public class MidnightConfigListWidget extends ElementListWidget<ConfigScreenEntr
this.centerListVertically = false;
textRenderer = minecraftClient.textRenderer;
}
@Override
public int getScrollbarPositionX() { return this.width -7; }
public int getScrollbarPositionX() {
return this.width -7;
}
public void addButton(ClickableWidget button, ClickableWidget resetButton, Text text) {
this.addEntry(ConfigScreenEntry.create(button, text, resetButton));
this.addEntry(new ConfigScreenEntry(button, text, resetButton));
}
@Override
public int getRowWidth() { return 10000; }
public Optional<ClickableWidget> getHoveredButton(double mouseY) {
for (ConfigScreenEntry buttonEntry : this.children()) {
for (ClickableWidget button : buttonEntry.buttons) {
if (button.visible && mouseY >= button.y && mouseY < button.y + itemHeight) {
return Optional.of(button);
}
public void addReference(int centerX, Text text, Supplier<Screen> targetScreen) {
this.addEntry(new ConfigListReferenceEntry(centerX, text, targetScreen));
}
@Override
public int getRowWidth() {
return 10000;
}
public Optional<Text> getHoveredEntryTitle(double mouseY) {
for (ConfigListEntryAbstract abstractEntry : this.children()) {
if (abstractEntry instanceof ConfigScreenEntry entry
&& entry.button.visible
&& mouseY >= entry.button.y && mouseY < entry.button.y + itemHeight) {
return Optional.of(entry.getText());
}
}
return Optional.empty();
}
@Environment(EnvType.CLIENT)
public static abstract class ConfigListEntryAbstract extends Entry<ConfigListEntryAbstract> {
}
@Environment(EnvType.CLIENT)
public static class ConfigListReferenceEntry extends ConfigListEntryAbstract {
private final ClickableWidget button;
public ConfigListReferenceEntry(int centerX, Text text, Supplier<Screen> targetScreen) {
this.button = new ButtonWidget(centerX - 154, 0, 308, 20, text, btn -> MinecraftClient.getInstance().setScreen(targetScreen.get()));
}
@Override
public List<? extends Selectable> selectableChildren() {
return List.of(button);
}
@Override
public List<? extends Element> children() {
return List.of(button);
}
@Override
public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
button.y = y;
button.render(matrices, mouseX, mouseY, tickDelta);
}
}
@Environment(EnvType.CLIENT)
public static class ConfigScreenEntry extends ConfigListEntryAbstract {
private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
public final ClickableWidget button;
private final ClickableWidget resetButton;
private final Text text;
public ConfigScreenEntry(ClickableWidget button, Text text, ClickableWidget resetButton) {
this.button = button;
this.resetButton = resetButton;
this.text = text;
}
@Override
public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
button.y = y;
button.render(matrices, mouseX, mouseY, tickDelta);
drawTextWithShadow(matrices,textRenderer, text,12,y+5,0xFFFFFF);
resetButton.y = y;
resetButton.render(matrices, mouseX, mouseY, tickDelta);
}
@Override
public List<? extends Element> children() {
return List.of(button, resetButton);
}
@Override
public List<? extends Selectable> selectableChildren() {
return List.of(button);
}
public Text getText() {
return text;
}
}
}

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.libjf.config.impl.client.screen;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import io.gitlab.jfronny.libjf.config.impl.EntryInfo;
import io.gitlab.jfronny.libjf.config.impl.client.screen.presets.PresetsScreen;
@ -9,7 +10,6 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.ScreenTexts;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.ClickableWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.util.math.MatrixStack;
@ -91,6 +91,14 @@ public class TinyConfigScreen extends Screen {
this.list.addButton(dummy,dummy,name);
}
}
for (String referencedConfig : config.getReferencedConfigs()) {
ConfigInstance ci = ConfigHolder.getInstance().get(referencedConfig);
if (ci != null) {
this.list.addReference(width / 2,
new TranslatableText("libjf-config-v0.see-also", new TranslatableText(ci.getModId() + ".jfconfig.title")),
() -> new TinyConfigScreen(ci, this));
}
}
}
@Override
@ -100,11 +108,10 @@ public class TinyConfigScreen extends Screen {
drawCenteredText(matrices, textRenderer, title, width / 2, 15, 0xFFFFFF);
Optional<ClickableWidget> widget = list.getHoveredButton(mouseY);
if (widget.isPresent()) {
Optional<Text> hovered = list.getHoveredEntryTitle(mouseY);
if (hovered.isPresent()) {
for (EntryInfo info : config.getEntries()) {
ClickableWidget buttonWidget = widget.get();
Text text = ConfigScreenEntry.buttonsWithText.get(buttonWidget);
Text text = hovered.get();
TranslatableText name = new TranslatableText(this.translationPrefix + info.field.getName());
boolean showTooltip = text.equals(name);
String tooltipKey = translationPrefix + info.field.getName() + ".tooltip";

View File

@ -1,32 +0,0 @@
package io.gitlab.jfronny.libjf.config.impl.client.screen.presets;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.Selectable;
import net.minecraft.client.gui.widget.ClickableWidget;
import net.minecraft.client.gui.widget.ElementListWidget;
import net.minecraft.client.util.math.MatrixStack;
import java.util.List;
public class PresetEntry extends ElementListWidget.Entry<PresetEntry> {
private final ClickableWidget button;
public PresetEntry(ClickableWidget button) {
this.button = button;
}
@Override
public List<? extends Selectable> selectableChildren() {
return List.of(button);
}
@Override
public List<? extends Element> children() {
return List.of(button);
}
@Override
public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
button.y = y;
button.render(matrices, mouseX, mouseY, tickDelta);
}
}

View File

@ -1,10 +1,15 @@
package io.gitlab.jfronny.libjf.config.impl.client.screen.presets;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.Selectable;
import net.minecraft.client.gui.widget.ClickableWidget;
import net.minecraft.client.gui.widget.ElementListWidget;
import net.minecraft.client.util.math.MatrixStack;
public class PresetListWidget extends ElementListWidget<PresetEntry> {
import java.util.List;
public class PresetListWidget extends ElementListWidget<PresetListWidget.PresetEntry> {
public PresetListWidget(MinecraftClient client, int i, int j, int k, int l, int m) {
super(client, i, j, k, l, m);
}
@ -14,7 +19,35 @@ public class PresetListWidget extends ElementListWidget<PresetEntry> {
}
@Override
public int getScrollbarPositionX() { return this.width -7; }
public int getScrollbarPositionX() {
return this.width -7;
}
@Override
public int getRowWidth() { return 10000; }
public int getRowWidth() {
return 10000;
}
public static class PresetEntry extends Entry<PresetEntry> {
private final ClickableWidget button;
public PresetEntry(ClickableWidget button) {
this.button = button;
}
@Override
public List<? extends Selectable> selectableChildren() {
return List.of(button);
}
@Override
public List<? extends Element> children() {
return List.of(button);
}
@Override
public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
button.y = y;
button.render(matrices, mouseX, mouseY, tickDelta);
}
}
}

View File

@ -3,6 +3,7 @@ package io.gitlab.jfronny.libjf.config.impl.entrypoint;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.JfConfig;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
@ -10,7 +11,7 @@ import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
public class JfConfigSafe implements PreLaunchEntrypoint {
@Override
public void onPreLaunch() {
for (EntrypointContainer<JfConfig> config : FabricLoader.getInstance().getEntrypointContainers(LibJf.MOD_ID + ":config", JfConfig.class)) {
for (EntrypointContainer<JfConfig> config : FabricLoader.getInstance().getEntrypointContainers(ConfigHolderImpl.MODULE_ID, JfConfig.class)) {
registerIfMissing(config.getProvider().getMetadata().getId(), config.getEntrypoint().getClass());
}
}

View File

@ -1,15 +1,15 @@
package io.gitlab.jfronny.libjf.config.impl.entrypoint;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.JfConfig;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
import io.gitlab.jfronny.libjf.unsafe.DynamicEntry;
import io.gitlab.jfronny.libjf.unsafe.UltraEarlyInit;
public class JfConfigUnsafe implements UltraEarlyInit {
@Override
public void init() {
DynamicEntry.execute(LibJf.MOD_ID + ":config", JfConfig.class,
DynamicEntry.execute(ConfigHolderImpl.MODULE_ID, JfConfig.class,
s -> JfConfigSafe.registerIfMissing(s.modId(), s.instance().getClass())
);
LibJf.LOGGER.info("Finished LibJF config entrypoint");

View File

@ -1,4 +1,5 @@
{
"libjf-config-v0.presets": "Presets",
"libjf-config-v0.default": "Default"
"libjf-config-v0.default": "Default",
"libjf-config-v0.see-also": "See also: %s"
}

View File

@ -7,5 +7,10 @@
"libjf:config": [
"io.gitlab.jfronny.libjf.config.test.TestConfig"
]
},
"custom": {
"libjf:config": {
"referencedConfigs": ["libjf-web-v0"]
}
}
}

View File

@ -0,0 +1,13 @@
{
"libjf-web-v0.jfconfig.title": "LibJF Web v0",
"libjf-web-v0.jfconfig.serverIp": "Server IP",
"libjf-web-v0.jfconfig.serverIp.tooltip": "The public IP/host name to send to clients",
"libjf-web-v0.jfconfig.port": "Port",
"libjf-web-v0.jfconfig.port.tooltip": "The port to host content on",
"libjf-web-v0.jfconfig.portOverride": "Port Override",
"libjf-web-v0.jfconfig.portOverride.tooltip": "The port to send to clients (for reverse proxies, -1 to disable)",
"libjf-web-v0.jfconfig.maxConnections": "Max. Connections",
"libjf-web-v0.jfconfig.maxConnections.tooltip": "The maximum number of concurrent connections to this server",
"libjf-web-v0.jfconfig.enableFileHost": "Enable File Host",
"libjf-web-v0.jfconfig.enableFileHost.tooltip": "Whether files from config/wwwroot should be hosted as static resources"
}