Skip to content

Commit 71f45ca

Browse files
authored
feat: Add inbuilt config screen (#258)
* feat: Add inbuilt config screen * feat: Add `BalmClientRegistrars#configScreen` for registering a custom config screen factory for a mod * test: Add custom config screen to example mod * fix: Make NumberFormatException errors more clear in config screen validation * feat: Add list editor (#259) * refactor: Cleanup * fix(dev): Fix PrimitiveConfigCodecs#parse only supporting list properties * feat: Add CTRL+F to focus search in config screen * fix(dev): Fix edit button not focusing inline edit box * feat: Add support for child config screens inheriting state * feat: Add BalmConfigScreenSearch#categoryMatches, expose as API * feat: Add BalmConfigSchema#findCategory * feat: Add BalmConfigScreenSectionBuilder#button * test: Add child config screen example * feat: Add Discard changes? confirmation screen * feat: Add undo of deleting config list items * fix(dev): Accept Y for undo as well as a workaround to support QWERTZ
1 parent 084a648 commit 71f45ca

72 files changed

Lines changed: 3046 additions & 33 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

common-example/src/main/java/com/example/balm/ExampleConfig.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
import net.blay09.mods.balm.platform.config.reflection.CustomControl;
44
import net.blay09.mods.balm.platform.config.reflection.Comment;
55
import net.blay09.mods.balm.platform.config.reflection.Config;
6+
import net.blay09.mods.balm.platform.config.reflection.NestedType;
67
import net.blay09.mods.balm.platform.config.reflection.Range;
8+
import net.minecraft.resources.Identifier;
9+
import net.minecraft.util.StringRepresentable;
10+
11+
import java.util.List;
12+
import java.util.Set;
713

814
@Config("balm_example")
915
public class ExampleConfig {
@@ -14,4 +20,68 @@ public class ExampleConfig {
1420
@Comment("Example custom widget config value.")
1521
@CustomControl("fancy_button")
1622
public boolean fancyBoolean;
23+
24+
@Comment("Example string config value.")
25+
public String welcomeMessage = "Hello from Balm!";
26+
27+
@Comment("Example identifier config value.")
28+
public Identifier targetBlock = Identifier.withDefaultNamespace("diamond_block");
29+
30+
@Comment("Example enum config value.")
31+
public SpawnMode spawnMode = SpawnMode.NEAR_PLAYER;
32+
33+
@Comment("Example list config value.")
34+
@NestedType(String.class)
35+
public List<String> favoriteItems = List.of("minecraft:apple", "minecraft:bread");
36+
37+
@Comment("Example set config value.")
38+
@NestedType(Integer.class)
39+
public Set<Integer> luckyNumbers = Set.of(3, 7, 9);
40+
41+
@Comment("Example category for experimental settings.")
42+
public Experimental experimental = new Experimental();
43+
44+
@Comment("Example category shown as a child config screen.")
45+
public ChildScreen childScreen = new ChildScreen();
46+
47+
public enum SpawnMode implements StringRepresentable {
48+
NEAR_PLAYER("near_player"),
49+
WORLD_SPAWN("world_spawn"),
50+
DISABLED("disabled");
51+
52+
private final String serializedName;
53+
54+
SpawnMode(String serializedName) {
55+
this.serializedName = serializedName;
56+
}
57+
58+
@Override
59+
public String getSerializedName() {
60+
return serializedName;
61+
}
62+
}
63+
64+
public static class Experimental {
65+
@Comment("Show advanced experimental options in the custom config screen.")
66+
public boolean enabled;
67+
68+
@Comment("Example floating point config value.")
69+
@Range(min = "0", max = "1")
70+
public double chance = 0.25;
71+
72+
@Comment("Example long config value.")
73+
@Range(min = "0", max = "1000000")
74+
public long maxPower = 9001;
75+
}
76+
77+
public static class ChildScreen {
78+
@Comment("Example child screen toggle.")
79+
public boolean enabled = true;
80+
81+
@Comment("Example child screen message.")
82+
public String message = "Configured from a child screen";
83+
84+
@Comment("Example child screen number.")
85+
public int number = 987;
86+
}
1787
}
Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,11 @@
11
package com.example.balm.client;
22

33
import net.blay09.mods.balm.client.BalmClientRegistrars;
4-
import net.blay09.mods.balm.client.platform.config.ConfigControl;
5-
import net.blay09.mods.balm.client.platform.config.ConfigControlContext;
6-
import net.blay09.mods.balm.platform.config.schema.ConfigControlBinding;
7-
import net.minecraft.client.gui.components.Button;
8-
import net.minecraft.network.chat.Component;
94

105
public class BalmExampleClient {
116
public static void initialize(BalmClientRegistrars registrars) {
127
System.out.println("Hello client");
13-
registrars.customConfigControls(configControls -> configControls.register("fancy_button", ConfigControl.<Boolean>builder()
14-
.element(FancyConfigButton::new)
15-
.build()));
168
registrars.registerModule(new ClientCommandTestModule());
17-
}
18-
19-
private static class FancyConfigButton extends Button.Plain {
20-
private final ConfigControlBinding<Boolean> binding;
21-
22-
public FancyConfigButton(ConfigControlBinding<Boolean> binding, ConfigControlContext context) {
23-
super(0, 0, context.entryWidth(), context.entryHeight(), Component.empty(), (_) -> binding.set(!binding.get()), DEFAULT_NARRATION);
24-
this.binding = binding;
25-
}
26-
27-
@Override
28-
public Component getMessage() {
29-
return Component.literal("Custom: " + binding.get());
30-
}
9+
registrars.registerModule(new ConfigScreenTestModule());
3110
}
3211
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package com.example.balm.client;
2+
3+
import com.example.balm.ExampleConfig;
4+
import net.blay09.mods.balm.Balm;
5+
import net.blay09.mods.balm.client.platform.config.BalmConfigScreenRegistrar;
6+
import net.blay09.mods.balm.client.platform.config.BalmCustomConfigControlRegistrar;
7+
import net.blay09.mods.balm.client.platform.config.ConfigControl;
8+
import net.blay09.mods.balm.client.platform.config.ConfigControlContext;
9+
import net.blay09.mods.balm.client.platform.config.screen.BalmConfigScreen;
10+
import net.blay09.mods.balm.client.platform.config.screen.BalmConfigScreenContext;
11+
import net.blay09.mods.balm.client.platform.config.screen.BalmConfigScreenSearch;
12+
import net.blay09.mods.balm.client.platform.module.BalmClientModule;
13+
import net.blay09.mods.balm.platform.config.schema.BalmConfigSchema;
14+
import net.blay09.mods.balm.platform.config.schema.ConfigControlBinding;
15+
import net.blay09.mods.balm.platform.config.schema.ConfiguredProperty;
16+
import net.blay09.mods.balm.platform.config.schema.builder.ConfigCategory;
17+
import net.blay09.mods.balm.platform.config.util.ConfigLocalization;
18+
import net.minecraft.client.Minecraft;
19+
import net.minecraft.client.gui.components.Button;
20+
import net.minecraft.client.gui.screens.Screen;
21+
import net.minecraft.network.chat.Component;
22+
import net.minecraft.resources.Identifier;
23+
24+
import java.util.Objects;
25+
26+
public class ConfigScreenTestModule implements BalmClientModule {
27+
@Override
28+
public Identifier getId() {
29+
return Identifier.fromNamespaceAndPath("balm_example", "config_screen");
30+
}
31+
32+
@Override
33+
public void registerCustomConfigControls(BalmCustomConfigControlRegistrar customConfigControls) {
34+
customConfigControls.register("fancy_button", ConfigControl.<Boolean>builder()
35+
.element(FancyConfigButton::new)
36+
.build());
37+
}
38+
39+
@Override
40+
public void registerConfigScreen(BalmConfigScreenRegistrar configScreens) {
41+
configScreens.register(ConfigScreenTestModule::createConfigScreen);
42+
}
43+
44+
private static Screen createConfigScreen(Screen parent) {
45+
final var schema = Objects.requireNonNull(Balm.config().getSchema(ExampleConfig.class), "Example config schema not registered");
46+
final var rangedValue = rootProperty(schema, "rangedValue");
47+
final var fancyBoolean = rootProperty(schema, "fancyBoolean");
48+
final var welcomeMessage = rootProperty(schema, "welcomeMessage");
49+
final var targetBlock = rootProperty(schema, "targetBlock");
50+
final var spawnMode = rootProperty(schema, "spawnMode");
51+
final var favoriteItems = rootProperty(schema, "favoriteItems");
52+
final var luckyNumbers = rootProperty(schema, "luckyNumbers");
53+
final var experimentalEnabled = categoryProperty(schema, "experimental", "enabled");
54+
final var experimentalChance = categoryProperty(schema, "experimental", "chance");
55+
final var experimentalMaxPower = categoryProperty(schema, "experimental", "maxPower");
56+
final var childScreen = category(schema, "childScreen");
57+
58+
return BalmConfigScreen.builder()
59+
.title(Component.translatable("balm_example.configuration.custom.title"))
60+
.section(Component.translatable("balm_example.configuration.section.general"), section -> section
61+
.properties(rangedValue, fancyBoolean, welcomeMessage, targetBlock))
62+
.section(Component.translatable("balm_example.configuration.section.spawning"), section -> section
63+
.property(spawnMode)
64+
.property(experimentalEnabled)
65+
.property(experimentalChance, context -> isEnabled(context, experimentalEnabled))
66+
.property(experimentalMaxPower, context -> isEnabled(context, experimentalEnabled)))
67+
.section(Component.translatable("balm_example.configuration.section.child_screens"), section -> section
68+
.button(Component.translatable(ConfigLocalization.forCategory(childScreen)),
69+
Component.translatable(ConfigLocalization.forCategoryTooltip(childScreen)),
70+
Component.translatable("gui.balm.configuration.open"),
71+
screen -> Minecraft.getInstance().gui.setScreen(createChildConfigScreen(screen, childScreen)),
72+
filter -> BalmConfigScreenSearch.categoryMatches(childScreen, filter)))
73+
.section(Component.translatable("balm_example.configuration.section.collections"), section -> section
74+
.properties(favoriteItems, luckyNumbers))
75+
.build(parent);
76+
}
77+
78+
private static Screen createChildConfigScreen(Screen parent, ConfigCategory category) {
79+
return BalmConfigScreen.builder()
80+
.title(Component.translatable("balm_example.configuration.child_screen.title"))
81+
.section(Component.translatable(ConfigLocalization.forCategory(category)), section -> section.properties(category))
82+
.build(parent);
83+
}
84+
85+
@SuppressWarnings("unchecked")
86+
private static boolean isEnabled(BalmConfigScreenContext context, ConfiguredProperty<?> property) {
87+
return context.bindingFor((ConfiguredProperty<Boolean>) property).get();
88+
}
89+
90+
private static ConfiguredProperty<?> rootProperty(BalmConfigSchema schema, String name) {
91+
return Objects.requireNonNull(schema.findRootProperty(name), "Missing root config property: " + name);
92+
}
93+
94+
private static ConfiguredProperty<?> categoryProperty(BalmConfigSchema schema, String category, String name) {
95+
return Objects.requireNonNull(schema.findProperty(category, name), "Missing config property: " + category + "." + name);
96+
}
97+
98+
private static ConfigCategory category(BalmConfigSchema schema, String name) {
99+
return Objects.requireNonNull(schema.findCategory(name), "Missing config category: " + name);
100+
}
101+
102+
private static class FancyConfigButton extends Button.Plain {
103+
private final ConfigControlBinding<Boolean> binding;
104+
105+
public FancyConfigButton(ConfigControlBinding<Boolean> binding, ConfigControlContext context) {
106+
super(0, 0, context.entryWidth(), context.entryHeight(), Component.empty(), (_) -> binding.set(!binding.get()), DEFAULT_NARRATION);
107+
this.binding = binding;
108+
}
109+
110+
@Override
111+
public Component getMessage() {
112+
return Component.literal("Custom: " + binding.get());
113+
}
114+
}
115+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"balm_example.configuration.title": "Balm Example",
3+
"balm_example.configuration.common.title": "Balm Example Common",
4+
"balm_example.configuration.custom.title": "Balm Example Configuration",
5+
"balm_example.configuration.child_screen.title": "Balm Example Child Configuration",
6+
"balm_example.configuration.section.general": "General",
7+
"balm_example.configuration.section.spawning": "Spawning",
8+
"balm_example.configuration.section.child_screens": "Child Screens",
9+
"balm_example.configuration.section.collections": "Collections",
10+
"balm_example.configuration.rangedValue": "Ranged Value",
11+
"balm_example.configuration.rangedValue.tooltip": "Example ranged integer config value.",
12+
"balm_example.configuration.fancyBoolean": "Fancy Boolean",
13+
"balm_example.configuration.fancyBoolean.tooltip": "Example custom widget config value.",
14+
"balm_example.configuration.welcomeMessage": "Welcome Message",
15+
"balm_example.configuration.welcomeMessage.tooltip": "Example string config value.",
16+
"balm_example.configuration.targetBlock": "Target Block",
17+
"balm_example.configuration.targetBlock.tooltip": "Example identifier config value.",
18+
"balm_example.configuration.spawnMode": "Spawn Mode",
19+
"balm_example.configuration.spawnMode.tooltip": "Example enum config value.",
20+
"balm_example.configuration.spawnMode.near_player": "Near Player",
21+
"balm_example.configuration.spawnMode.world_spawn": "World Spawn",
22+
"balm_example.configuration.spawnMode.disabled": "Disabled",
23+
"balm_example.configuration.favoriteItems": "Favorite Items",
24+
"balm_example.configuration.favoriteItems.tooltip": "Example list config value.",
25+
"balm_example.configuration.luckyNumbers": "Lucky Numbers",
26+
"balm_example.configuration.luckyNumbers.tooltip": "Example set config value.",
27+
"balm_example.configuration.experimental": "Experimental",
28+
"balm_example.configuration.experimental.enabled": "Enable Experimental Options",
29+
"balm_example.configuration.experimental.enabled.tooltip": "Show advanced experimental options in the custom config screen.",
30+
"balm_example.configuration.experimental.chance": "Experimental Chance",
31+
"balm_example.configuration.experimental.chance.tooltip": "Example floating point config value.",
32+
"balm_example.configuration.experimental.maxPower": "Maximum Power",
33+
"balm_example.configuration.experimental.maxPower.tooltip": "Example long config value.",
34+
"balm_example.configuration.childScreen": "Child Screen",
35+
"balm_example.configuration.childScreen.tooltip": "Example category shown as a child config screen.",
36+
"balm_example.configuration.childScreen.enabled": "Enabled",
37+
"balm_example.configuration.childScreen.enabled.tooltip": "Example child screen toggle.",
38+
"balm_example.configuration.childScreen.message": "Message",
39+
"balm_example.configuration.childScreen.message.tooltip": "Example child screen message.",
40+
"balm_example.configuration.childScreen.number": "Number",
41+
"balm_example.configuration.childScreen.number.tooltip": "Example child screen number."
42+
}

common/src/main/java/net/blay09/mods/balm/client/BalmClientRegistrars.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import net.blay09.mods.balm.client.model.geom.BalmModelLayerRegistrar;
99
import net.blay09.mods.balm.client.particle.BalmParticleProviderRegistrar;
1010
import net.blay09.mods.balm.client.platform.config.BalmCustomConfigControlRegistrar;
11+
import net.blay09.mods.balm.client.platform.config.BalmConfigScreenRegistrar;
1112
import net.blay09.mods.balm.client.platform.config.internal.BalmCustomConfigControlRegistrarImpl;
1213
import net.blay09.mods.balm.client.renderer.block.model.BalmBlockStateModelRegistrar;
1314
import net.blay09.mods.balm.client.renderer.blockentity.BalmBlockEntityRendererRegistrar;
@@ -139,6 +140,15 @@ public void customConfigControls(Consumer<BalmCustomConfigControlRegistrar> init
139140
initializer.accept(new BalmCustomConfigControlRegistrarImpl(namespace));
140141
}
141142

143+
/**
144+
* Use this to register a custom config screen for this mod.
145+
*
146+
* @param initializer Callback that receives a scoped registrar for this mod's config screen.
147+
*/
148+
public void configScreen(Consumer<BalmConfigScreenRegistrar> initializer) {
149+
runtime.configScreen(namespace, initializer);
150+
}
151+
142152
public void registerModule(BalmClientModule module) {
143153
runtime.registerModule(this, module);
144154
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package net.blay09.mods.balm.client.platform.config;
2+
3+
public interface BalmConfigScreenRegistrar {
4+
void register(BalmConfigScreenFactory factory);
5+
}

common/src/main/java/net/blay09/mods/balm/client/platform/config/ConfigControlContext.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package net.blay09.mods.balm.client.platform.config;
22

3+
import net.blay09.mods.balm.platform.config.schema.ConfigControlBinding;
34
import net.blay09.mods.balm.platform.config.schema.ConfiguredProperty;
45
import net.minecraft.network.chat.Component;
56

@@ -13,4 +14,14 @@ public interface ConfigControlContext {
1314

1415
Component tooltip(ConfiguredProperty<?> property);
1516

16-
}
17+
<T> ConfigControlBinding<T> binding(ConfiguredProperty<T> property);
18+
19+
default <T> T get(ConfiguredProperty<T> property) {
20+
return binding(property).get();
21+
}
22+
23+
default <T> void set(ConfiguredProperty<T> property, T value) {
24+
binding(property).set(value);
25+
}
26+
27+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package net.blay09.mods.balm.client.platform.config.internal;
2+
3+
import net.blay09.mods.balm.client.platform.config.BalmConfigScreenFactory;
4+
import net.blay09.mods.balm.client.platform.config.BalmConfigScreenRegistrar;
5+
import net.blay09.mods.balm.platform.config.internal.BalmConfigScreenProviders;
6+
7+
public class BalmConfigScreenRegistrarImpl implements BalmConfigScreenRegistrar {
8+
9+
private final String namespace;
10+
11+
public BalmConfigScreenRegistrarImpl(String namespace) {
12+
this.namespace = namespace;
13+
}
14+
15+
@Override
16+
public void register(BalmConfigScreenFactory factory) {
17+
BalmConfigScreenProviders.registerModOverride(namespace, factory);
18+
}
19+
}

common/src/main/java/net/blay09/mods/balm/client/platform/config/internal/ConfigControlContextImpl.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
package net.blay09.mods.balm.client.platform.config.internal;
22

33
import net.blay09.mods.balm.client.platform.config.ConfigControlContext;
4+
import net.blay09.mods.balm.platform.config.schema.ConfigControlBinding;
45
import net.blay09.mods.balm.platform.config.schema.ConfiguredProperty;
56
import net.blay09.mods.balm.platform.config.util.ConfigLocalization;
67
import net.minecraft.network.chat.Component;
78

9+
import java.util.function.Function;
10+
811
public class ConfigControlContextImpl implements ConfigControlContext {
912
private final int entryWidth;
1013
private final int entryHeight;
14+
private final Function<ConfiguredProperty<?>, ConfigControlBinding<?>> bindingFactory;
1115

12-
public ConfigControlContextImpl(int entryWidth, int entryHeight) {
16+
public ConfigControlContextImpl(int entryWidth, int entryHeight, Function<ConfiguredProperty<?>, ConfigControlBinding<?>> bindingFactory) {
1317
this.entryWidth = entryWidth;
1418
this.entryHeight = entryHeight;
19+
this.bindingFactory = bindingFactory;
1520
}
1621

1722
@Override
@@ -33,4 +38,10 @@ public Component label(ConfiguredProperty<?> property) {
3338
public Component tooltip(ConfiguredProperty<?> property) {
3439
return Component.translatable(ConfigLocalization.forPropertyTooltip(property));
3540
}
41+
42+
@Override
43+
@SuppressWarnings("unchecked")
44+
public <T> ConfigControlBinding<T> binding(ConfiguredProperty<T> property) {
45+
return (ConfigControlBinding<T>) bindingFactory.apply(property);
46+
}
3647
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@NullMarked
2+
package net.blay09.mods.balm.client.platform.config;
3+
4+
import org.jspecify.annotations.NullMarked;

0 commit comments

Comments
 (0)